"""SQLite persistence: settings (API keys) and download history/queue.""" from __future__ import annotations import sqlite3 import threading import time from typing import Any, Optional from .config import DB_PATH, ensure_dirs # A single connection guarded by a lock keeps things simple and safe across the # async event loop + background download tasks (sqlite writes are serialized). _lock = threading.Lock() _conn: Optional[sqlite3.Connection] = None def _connect() -> sqlite3.Connection: global _conn if _conn is None: ensure_dirs() _conn = sqlite3.connect(str(DB_PATH), check_same_thread=False) _conn.row_factory = sqlite3.Row _conn.execute("PRAGMA journal_mode=WAL") return _conn def init_db() -> None: with _lock: conn = _connect() conn.execute( """ CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT ) """ ) conn.execute( """ CREATE TABLE IF NOT EXISTS downloads ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL, source TEXT, model_type TEXT, filename TEXT, dest_path TEXT, status TEXT NOT NULL DEFAULT 'queued', bytes_done INTEGER NOT NULL DEFAULT 0, bytes_total INTEGER NOT NULL DEFAULT 0, error TEXT, created_at REAL NOT NULL, updated_at REAL NOT NULL ) """ ) conn.commit() # ---- settings ------------------------------------------------------------- def get_setting(key: str) -> Optional[str]: with _lock: row = _connect().execute( "SELECT value FROM settings WHERE key = ?", (key,) ).fetchone() return row["value"] if row else None def set_setting(key: str, value: Optional[str]) -> None: with _lock: conn = _connect() if value is None or value == "": conn.execute("DELETE FROM settings WHERE key = ?", (key,)) else: conn.execute( "INSERT INTO settings(key, value) VALUES(?, ?) " "ON CONFLICT(key) DO UPDATE SET value = excluded.value", (key, value), ) conn.commit() # ---- downloads ------------------------------------------------------------ def create_download(url: str, source: str, model_type: str, filename: str, dest_path: str) -> int: now = time.time() with _lock: conn = _connect() cur = conn.execute( "INSERT INTO downloads(url, source, model_type, filename, dest_path, " "status, created_at, updated_at) VALUES(?, ?, ?, ?, ?, 'queued', ?, ?)", (url, source, model_type, filename, dest_path, now, now), ) conn.commit() return int(cur.lastrowid) def update_download(download_id: int, **fields: Any) -> None: if not fields: return fields["updated_at"] = time.time() cols = ", ".join(f"{k} = ?" for k in fields) values = list(fields.values()) + [download_id] with _lock: conn = _connect() conn.execute(f"UPDATE downloads SET {cols} WHERE id = ?", values) conn.commit() def get_download(download_id: int) -> Optional[dict]: with _lock: row = _connect().execute( "SELECT * FROM downloads WHERE id = ?", (download_id,) ).fetchone() return dict(row) if row else None def list_downloads() -> list[dict]: with _lock: rows = _connect().execute( "SELECT * FROM downloads ORDER BY id DESC" ).fetchall() return [dict(r) for r in rows] def delete_download(download_id: int) -> None: with _lock: conn = _connect() conn.execute("DELETE FROM downloads WHERE id = ?", (download_id,)) conn.commit()