"""SQLite access and schema migrations. WAL is mandatory: the hourly cron job and the long-running admin server both write. """ from __future__ import annotations import sqlite3 from pathlib import Path from . import config SCHEMA_VERSION = 1 _SCHEMA_V1 = """ CREATE TABLE IF NOT EXISTS channel ( id INTEGER PRIMARY KEY, channel_id TEXT NOT NULL UNIQUE, handle TEXT, title TEXT NOT NULL, description TEXT, dir_name TEXT NOT NULL UNIQUE, added_at TEXT NOT NULL, backfilled INTEGER NOT NULL DEFAULT 0, retention_days INTEGER, last_polled_at TEXT, last_poll_ok INTEGER, consecutive_poll_failures INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS video ( id INTEGER PRIMARY KEY, video_id TEXT NOT NULL UNIQUE, channel_pk INTEGER NOT NULL REFERENCES channel(id) ON DELETE CASCADE, title TEXT, upload_date TEXT, duration INTEGER, season INTEGER, episode INTEGER, state TEXT NOT NULL, discovery_source TEXT NOT NULL, rel_path TEXT, size_bytes INTEGER, attempts INTEGER NOT NULL DEFAULT 0, last_error TEXT, discovered_at TEXT NOT NULL, downloaded_at TEXT, deleted_at TEXT ); CREATE INDEX IF NOT EXISTS idx_video_state ON video(state); CREATE INDEX IF NOT EXISTS idx_video_upload_date ON video(upload_date); CREATE INDEX IF NOT EXISTS idx_video_channel ON video(channel_pk); CREATE TABLE IF NOT EXISTS setting ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); """ def connect(path: Path | None = None) -> sqlite3.Connection: """Open the database, applying migrations if needed.""" path = Path(path) if path is not None else config.DB_PATH path.parent.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(path, timeout=30.0, isolation_level=None) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode = WAL") conn.execute("PRAGMA foreign_keys = ON") conn.execute("PRAGMA busy_timeout = 30000") migrate(conn) return conn def migrate(conn: sqlite3.Connection) -> int: """Bring the schema up to SCHEMA_VERSION. Idempotent.""" current = conn.execute("PRAGMA user_version").fetchone()[0] if current >= SCHEMA_VERSION: return current with conn: if current < 1: conn.executescript(_SCHEMA_V1) # Future migrations append here, each guarded by `if current < N`. conn.execute(f"PRAGMA user_version = {SCHEMA_VERSION}") return SCHEMA_VERSION