mirror of
https://github.com/g0ldyy/comet.git
synced 2026-01-12 01:16:12 +01:00
786 lines
26 KiB
Python
786 lines
26 KiB
Python
import asyncio
|
|
import os
|
|
import time
|
|
import traceback
|
|
|
|
from comet.core.logger import logger
|
|
from comet.core.models import database, settings
|
|
|
|
STARTUP_CLEANUP_LOCK_ID = 0xC0FFEE
|
|
|
|
DATABASE_VERSION = "1.0"
|
|
|
|
|
|
async def setup_database():
|
|
try:
|
|
if settings.DATABASE_TYPE == "sqlite":
|
|
os.makedirs(os.path.dirname(settings.DATABASE_PATH), exist_ok=True)
|
|
|
|
if not os.path.exists(settings.DATABASE_PATH):
|
|
open(settings.DATABASE_PATH, "a").close()
|
|
|
|
await database.connect()
|
|
|
|
await _migrate_indexes()
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS db_version (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
version TEXT
|
|
)
|
|
"""
|
|
)
|
|
|
|
current_version = await database.fetch_val(
|
|
"""
|
|
SELECT version FROM db_version WHERE id = 1
|
|
"""
|
|
)
|
|
|
|
if current_version != DATABASE_VERSION:
|
|
logger.log(
|
|
"COMET",
|
|
f"Database: Migration from {current_version} to {DATABASE_VERSION} version",
|
|
)
|
|
|
|
if settings.DATABASE_TYPE == "sqlite":
|
|
tables = await database.fetch_all(
|
|
"""
|
|
SELECT name FROM sqlite_master
|
|
WHERE type='table' AND name != 'db_version' AND name != 'sqlite_sequence'
|
|
"""
|
|
)
|
|
|
|
for table in tables:
|
|
await database.execute(f"DROP TABLE IF EXISTS {table['name']}")
|
|
else:
|
|
await database.execute(
|
|
"""
|
|
DO $$ DECLARE
|
|
r RECORD;
|
|
BEGIN
|
|
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema() AND tablename != 'db_version') LOOP
|
|
EXECUTE 'DROP TABLE IF EXISTS ' || quote_ident(r.tablename) || ' CASCADE';
|
|
END LOOP;
|
|
END $$;
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
INSERT INTO db_version VALUES (1, :version)
|
|
ON CONFLICT (id) DO UPDATE SET version = :version
|
|
""",
|
|
{"version": DATABASE_VERSION},
|
|
)
|
|
|
|
logger.log(
|
|
"COMET", f"Database: Migration to version {DATABASE_VERSION} completed"
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS db_maintenance (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
last_startup_cleanup REAL
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS scrape_locks (
|
|
lock_key TEXT PRIMARY KEY,
|
|
instance_id TEXT,
|
|
timestamp INTEGER,
|
|
expires_at INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS first_searches (
|
|
media_id TEXT PRIMARY KEY,
|
|
timestamp INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS admin_sessions (
|
|
session_id TEXT PRIMARY KEY,
|
|
created_at INTEGER,
|
|
expires_at INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS metadata_cache (
|
|
media_id TEXT PRIMARY KEY,
|
|
title TEXT,
|
|
year INTEGER,
|
|
year_end INTEGER,
|
|
aliases TEXT,
|
|
timestamp INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS torrents (
|
|
media_id TEXT,
|
|
info_hash TEXT,
|
|
file_index INTEGER,
|
|
season INTEGER,
|
|
episode INTEGER,
|
|
title TEXT,
|
|
seeders INTEGER,
|
|
size BIGINT,
|
|
tracker TEXT,
|
|
sources TEXT,
|
|
parsed TEXT,
|
|
timestamp INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS unq_torrents_series
|
|
ON torrents (media_id, info_hash, season, episode)
|
|
WHERE season IS NOT NULL AND episode IS NOT NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS unq_torrents_season
|
|
ON torrents (media_id, info_hash, season)
|
|
WHERE season IS NOT NULL AND episode IS NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS unq_torrents_episode
|
|
ON torrents (media_id, info_hash, episode)
|
|
WHERE season IS NULL AND episode IS NOT NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS unq_torrents_movie
|
|
ON torrents (media_id, info_hash)
|
|
WHERE season IS NULL AND episode IS NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS debrid_availability (
|
|
debrid_service TEXT,
|
|
info_hash TEXT,
|
|
file_index TEXT,
|
|
title TEXT,
|
|
season INTEGER,
|
|
episode INTEGER,
|
|
size BIGINT,
|
|
parsed TEXT,
|
|
timestamp INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS unq_debrid_series
|
|
ON debrid_availability (debrid_service, info_hash, season, episode)
|
|
WHERE season IS NOT NULL AND episode IS NOT NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS unq_debrid_season
|
|
ON debrid_availability (debrid_service, info_hash, season)
|
|
WHERE season IS NOT NULL AND episode IS NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS unq_debrid_episode
|
|
ON debrid_availability (debrid_service, info_hash, episode)
|
|
WHERE season IS NULL AND episode IS NOT NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS unq_debrid_movie
|
|
ON debrid_availability (debrid_service, info_hash)
|
|
WHERE season IS NULL AND episode IS NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS download_links_cache (
|
|
debrid_key TEXT,
|
|
info_hash TEXT,
|
|
season INTEGER,
|
|
episode INTEGER,
|
|
download_url TEXT,
|
|
timestamp INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS download_links_series_both_idx
|
|
ON download_links_cache (debrid_key, info_hash, season, episode)
|
|
WHERE season IS NOT NULL AND episode IS NOT NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS download_links_season_only_idx
|
|
ON download_links_cache (debrid_key, info_hash, season)
|
|
WHERE season IS NOT NULL AND episode IS NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS download_links_episode_only_idx
|
|
ON download_links_cache (debrid_key, info_hash, episode)
|
|
WHERE season IS NULL AND episode IS NOT NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE UNIQUE INDEX IF NOT EXISTS download_links_no_season_episode_idx
|
|
ON download_links_cache (debrid_key, info_hash)
|
|
WHERE season IS NULL AND episode IS NULL
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS active_connections (
|
|
id TEXT PRIMARY KEY,
|
|
ip TEXT,
|
|
content TEXT,
|
|
timestamp INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS bandwidth_stats (
|
|
id INTEGER PRIMARY KEY,
|
|
total_bytes BIGINT,
|
|
last_updated INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS background_scraper_progress (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
current_run_started_at INTEGER,
|
|
last_completed_run_at INTEGER,
|
|
is_running BOOLEAN DEFAULT FALSE,
|
|
current_phase TEXT,
|
|
total_movies_processed INTEGER,
|
|
total_series_processed INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS background_scraper_state (
|
|
media_id TEXT PRIMARY KEY,
|
|
media_type TEXT,
|
|
title TEXT,
|
|
year INTEGER,
|
|
scraped_at INTEGER,
|
|
total_torrents_found INTEGER,
|
|
scrape_failed_attempts INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS metrics_cache (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
data TEXT,
|
|
timestamp INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS anime_entries (
|
|
id INTEGER PRIMARY KEY,
|
|
data TEXT
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS anime_ids (
|
|
provider TEXT,
|
|
provider_id TEXT,
|
|
entry_id INTEGER,
|
|
PRIMARY KEY (provider, provider_id)
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_anime_ids_entry_provider
|
|
ON anime_ids (entry_id, provider, provider_id)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS anime_mapping_state (
|
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
refreshed_at INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
CREATE TABLE IF NOT EXISTS digital_release_cache (
|
|
media_id TEXT PRIMARY KEY,
|
|
release_date BIGINT,
|
|
timestamp INTEGER
|
|
)
|
|
"""
|
|
)
|
|
|
|
# =============================================================================
|
|
# TORRENTS TABLE INDEXES
|
|
# =============================================================================
|
|
|
|
# Primary lookup index: media_id + season + episode (nullable) + timestamp
|
|
# Covers: get_cached_torrents, check_torrents_cache
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_torrents_lookup
|
|
ON torrents (media_id, season, episode, timestamp)
|
|
"""
|
|
)
|
|
|
|
# Optimization for concurrent DELETEs: info_hash + season
|
|
# Covers: DELETE FROM torrents WHERE (info_hash, season) IN (...)
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_torrents_info_hash_season
|
|
ON torrents (info_hash, season)
|
|
"""
|
|
)
|
|
|
|
# Optimization for lookups by info_hash only
|
|
# Covers: SELECT sources, media_id FROM torrents WHERE info_hash = $1
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_torrents_info_hash
|
|
ON torrents (info_hash)
|
|
"""
|
|
)
|
|
|
|
# =============================================================================
|
|
# DEBRID_AVAILABILITY TABLE INDEXES
|
|
# =============================================================================
|
|
|
|
# Primary lookup index: service + info_hash + timestamp
|
|
# Covers: get_cached_availability (info_hash IN ...)
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_debrid_lookup
|
|
ON debrid_availability (debrid_service, info_hash, timestamp)
|
|
"""
|
|
)
|
|
|
|
# Index for torrent mode: queries without debrid_service filter
|
|
# Covers: get_cached_availability when debrid_service == "torrent"
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_debrid_info_hash
|
|
ON debrid_availability (info_hash)
|
|
"""
|
|
)
|
|
|
|
# Composite index for full filter path with season/episode
|
|
# Covers: get_cached_availability with season/episode filters
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_debrid_hash_season_episode
|
|
ON debrid_availability (info_hash, season, episode, timestamp)
|
|
"""
|
|
)
|
|
|
|
# =============================================================================
|
|
# DOWNLOAD_LINKS_CACHE TABLE INDEXES
|
|
# =============================================================================
|
|
|
|
# Primary playback lookup: debrid_key + info_hash + season + episode
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_download_links_playback
|
|
ON download_links_cache (debrid_key, info_hash, season, episode, timestamp)
|
|
"""
|
|
)
|
|
|
|
# Timestamp-based cleanup
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_download_links_cleanup
|
|
ON download_links_cache (timestamp)
|
|
"""
|
|
)
|
|
|
|
# =============================================================================
|
|
# METADATA_CACHE TABLE INDEXES
|
|
# =============================================================================
|
|
|
|
# Primary cache lookup: media_id + timestamp
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_metadata_cache_lookup
|
|
ON metadata_cache (media_id, timestamp)
|
|
"""
|
|
)
|
|
|
|
# =============================================================================
|
|
# ACTIVE_CONNECTIONS TABLE INDEXES
|
|
# =============================================================================
|
|
|
|
# Admin dashboard ordering: timestamp DESC (most recent first)
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_connections_timestamp_desc
|
|
ON active_connections (timestamp DESC)
|
|
"""
|
|
)
|
|
|
|
# IP-based filtering and monitoring
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_connections_ip_filter
|
|
ON active_connections (ip, timestamp)
|
|
"""
|
|
)
|
|
|
|
# Connection monitoring by content type
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_connections_content_monitoring
|
|
ON active_connections (content, timestamp)
|
|
"""
|
|
)
|
|
|
|
# Instance-based lock monitoring
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_scrape_locks_instance
|
|
ON scrape_locks (instance_id, timestamp)
|
|
"""
|
|
)
|
|
|
|
# =============================================================================
|
|
# ADMIN_SESSIONS TABLE INDEXES
|
|
# =============================================================================
|
|
|
|
# Session cleanup: expires_at < current_time
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_admin_sessions_expires
|
|
ON admin_sessions (expires_at)
|
|
"""
|
|
)
|
|
|
|
# =============================================================================
|
|
# BACKGROUND_SCRAPER_STATE TABLE INDEXES
|
|
# =============================================================================
|
|
|
|
# Media type filtering for scraper analytics
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_scraper_state_media_type
|
|
ON background_scraper_state (media_type, scraped_at)
|
|
"""
|
|
)
|
|
|
|
# Scraping timestamp for progress tracking
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_scraper_state_scraped_at
|
|
ON background_scraper_state (scraped_at)
|
|
"""
|
|
)
|
|
|
|
# Failed attempts monitoring
|
|
await database.execute(
|
|
"""
|
|
CREATE INDEX IF NOT EXISTS idx_scraper_state_failures
|
|
ON background_scraper_state (scrape_failed_attempts, scraped_at)
|
|
"""
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_digital_release_timestamp
|
|
ON digital_release_cache (timestamp)
|
|
"""
|
|
)
|
|
|
|
if settings.DATABASE_TYPE == "sqlite":
|
|
await database.execute("PRAGMA busy_timeout=30000") # 30 seconds timeout
|
|
await database.execute("PRAGMA journal_mode=WAL")
|
|
await database.execute("PRAGMA synchronous=OFF")
|
|
await database.execute("PRAGMA temp_store=MEMORY")
|
|
await database.execute("PRAGMA mmap_size=30000000000")
|
|
await database.execute("PRAGMA page_size=4096")
|
|
await database.execute("PRAGMA cache_size=-2000")
|
|
await database.execute("PRAGMA foreign_keys=OFF")
|
|
await database.execute("PRAGMA count_changes=OFF")
|
|
await database.execute("PRAGMA secure_delete=OFF")
|
|
await database.execute("PRAGMA auto_vacuum=OFF")
|
|
|
|
await database.execute("DELETE FROM active_connections")
|
|
await database.execute("DELETE FROM metrics_cache")
|
|
|
|
await _run_startup_cleanup()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error setting up the database: {e}")
|
|
logger.exception(traceback.format_exc())
|
|
|
|
|
|
async def _run_startup_cleanup():
|
|
interval = settings.DATABASE_STARTUP_CLEANUP_INTERVAL
|
|
if interval is None or interval < 0:
|
|
return
|
|
|
|
current_time = time.time()
|
|
should_run = (
|
|
True
|
|
if interval == 0
|
|
else await _should_run_startup_cleanup(current_time, interval)
|
|
)
|
|
if not should_run:
|
|
logger.log("DATABASE", "Startup cleanup skipped (recent run)")
|
|
return
|
|
|
|
try:
|
|
async with database.transaction():
|
|
if settings.DATABASE_TYPE == "postgresql":
|
|
acquired = await database.fetch_val(
|
|
"SELECT pg_try_advisory_xact_lock(:lock_id)",
|
|
{"lock_id": STARTUP_CLEANUP_LOCK_ID},
|
|
force_primary=True,
|
|
)
|
|
if not acquired:
|
|
logger.log(
|
|
"DATABASE",
|
|
"Startup cleanup already running elsewhere; skipping",
|
|
)
|
|
return
|
|
|
|
logger.log("DATABASE", "Running startup cleanup sweep")
|
|
|
|
await database.execute(
|
|
"""
|
|
DELETE FROM first_searches
|
|
WHERE timestamp < CAST(:current_time AS BIGINT) - CAST(:cache_ttl AS BIGINT);
|
|
""",
|
|
{"cache_ttl": settings.TORRENT_CACHE_TTL, "current_time": current_time},
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
DELETE FROM metadata_cache
|
|
WHERE timestamp < CAST(:current_time AS BIGINT) - CAST(:cache_ttl AS BIGINT);
|
|
""",
|
|
{
|
|
"cache_ttl": settings.METADATA_CACHE_TTL,
|
|
"current_time": current_time,
|
|
},
|
|
)
|
|
|
|
if settings.TORRENT_CACHE_TTL >= 0:
|
|
await database.execute(
|
|
"""
|
|
DELETE FROM torrents
|
|
WHERE timestamp < CAST(:current_time AS BIGINT) - CAST(:cache_ttl AS BIGINT);
|
|
""",
|
|
{
|
|
"cache_ttl": settings.TORRENT_CACHE_TTL,
|
|
"current_time": current_time,
|
|
},
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
DELETE FROM debrid_availability
|
|
WHERE timestamp < CAST(:current_time AS BIGINT) - CAST(:cache_ttl AS BIGINT);
|
|
""",
|
|
{"cache_ttl": settings.DEBRID_CACHE_TTL, "current_time": current_time},
|
|
)
|
|
|
|
await database.execute(
|
|
"""
|
|
DELETE FROM digital_release_cache
|
|
WHERE timestamp < CAST(:current_time AS BIGINT) - CAST(:cache_ttl AS BIGINT);
|
|
""",
|
|
{
|
|
"cache_ttl": settings.METADATA_CACHE_TTL,
|
|
"current_time": current_time,
|
|
},
|
|
)
|
|
|
|
await database.execute("DELETE FROM download_links_cache")
|
|
|
|
await database.execute(
|
|
"""
|
|
INSERT INTO db_maintenance (id, last_startup_cleanup)
|
|
VALUES (1, :timestamp)
|
|
ON CONFLICT (id) DO UPDATE SET last_startup_cleanup = :timestamp
|
|
""",
|
|
{"timestamp": current_time},
|
|
force_primary=True,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Error executing startup cleanup: {e}")
|
|
|
|
|
|
async def _should_run_startup_cleanup(current_time: float, interval: int):
|
|
row = await database.fetch_one(
|
|
"SELECT last_startup_cleanup FROM db_maintenance WHERE id = 1",
|
|
force_primary=True,
|
|
)
|
|
if not row or row["last_startup_cleanup"] is None:
|
|
return True
|
|
|
|
last_run = float(row["last_startup_cleanup"])
|
|
return (current_time - last_run) >= interval
|
|
|
|
|
|
async def cleanup_expired_locks():
|
|
while True:
|
|
try:
|
|
current_time = time.time()
|
|
await database.execute(
|
|
"DELETE FROM scrape_locks WHERE expires_at < :current_time",
|
|
{"current_time": current_time},
|
|
)
|
|
except Exception as e:
|
|
logger.log("LOCK", f"❌ Error during periodic lock cleanup: {e}")
|
|
|
|
await asyncio.sleep(60)
|
|
|
|
|
|
async def cleanup_expired_sessions():
|
|
while True:
|
|
try:
|
|
current_time = time.time()
|
|
await database.execute(
|
|
"DELETE FROM admin_sessions WHERE expires_at < :current_time",
|
|
{"current_time": current_time},
|
|
)
|
|
except Exception as e:
|
|
logger.log("SESSION", f"❌ Error during periodic session cleanup: {e}")
|
|
|
|
await asyncio.sleep(5) # Clean up every 5 seconds
|
|
|
|
|
|
async def _migrate_indexes():
|
|
try:
|
|
old_indexes = [
|
|
"torrents_series_both_idx",
|
|
"torrents_season_only_idx",
|
|
"torrents_episode_only_idx",
|
|
"torrents_no_season_episode_idx",
|
|
"idx_torrents_media_cache_lookup",
|
|
"idx_torrents_tracker_analytics",
|
|
"idx_torrents_size_filter",
|
|
"idx_torrents_seeders_desc",
|
|
"idx_torrents_quality_cache",
|
|
"idx_torrents_media_season_episode",
|
|
"debrid_series_both_idx",
|
|
"debrid_season_only_idx",
|
|
"debrid_episode_only_idx",
|
|
"debrid_no_season_episode_idx",
|
|
"idx_debrid_service_hash_cache",
|
|
"idx_debrid_season_episode_filter",
|
|
"idx_debrid_service_timestamp",
|
|
"idx_debrid_title_filter",
|
|
"idx_debrid_comprehensive",
|
|
"idx_debrid_info_hash_season_episode",
|
|
"idx_debrid_timestamp",
|
|
"torrents_cache_lookup_idx",
|
|
"idx_scrape_locks_expires_at",
|
|
"idx_scrape_locks_lock_key",
|
|
"idx_torrents_timestamp",
|
|
"torrents_seeders_idx",
|
|
"idx_first_searches_cleanup",
|
|
"idx_metadata_title_search",
|
|
"idx_anime_ids_entry_id",
|
|
]
|
|
|
|
dropped_count = 0
|
|
for index_name in old_indexes:
|
|
if settings.DATABASE_TYPE == "sqlite":
|
|
exists = await database.fetch_val(
|
|
f"SELECT 1 FROM sqlite_master WHERE type='index' AND name='{index_name}'"
|
|
)
|
|
else:
|
|
exists = await database.fetch_val(
|
|
f"SELECT 1 FROM pg_indexes WHERE indexname='{index_name}'"
|
|
)
|
|
|
|
if exists:
|
|
await database.execute(f"DROP INDEX IF EXISTS {index_name}")
|
|
dropped_count += 1
|
|
logger.log("COMET", f"Database: Dropped legacy index '{index_name}'")
|
|
|
|
if dropped_count > 0:
|
|
logger.log(
|
|
"COMET",
|
|
f"Database: Legacy indexes cleanup completed. Dropped {dropped_count} indexes.",
|
|
)
|
|
except Exception as e:
|
|
logger.warning(f"Error during index migration: {e}")
|
|
|
|
|
|
async def teardown_database():
|
|
try:
|
|
await database.disconnect()
|
|
except Exception as e:
|
|
logger.error(f"Error tearing down the database: {e}")
|
|
logger.exception(traceback.format_exc())
|