"""SQLite-backed tracking of active + historical streams. Backed by a file on the shared `appdata` PV (mounted at STATE_DB_PATH), so state survives pod restarts/redeploys. "Active" streams are just history rows with end IS NULL — no separate table needed, which also gives free crash recovery (if the pod dies mid-stream, the row is simply still open). """ import os import sqlite3 import threading from datetime import datetime, timezone def _now_iso(): return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") SCHEMA = """ CREATE TABLE IF NOT EXISTS streams ( id INTEGER PRIMARY KEY AUTOINCREMENT, path TEXT NOT NULL, source_type TEXT, start TEXT NOT NULL, end TEXT ); CREATE INDEX IF NOT EXISTS idx_streams_active ON streams (path) WHERE end IS NULL; """ class StreamState: def __init__(self, db_path, history_limit=50): self._db_path = db_path self._history_limit = history_limit self._lock = threading.Lock() os.makedirs(os.path.dirname(db_path) or ".", exist_ok=True) with self._connect() as conn: conn.executescript(SCHEMA) def _connect(self): conn = sqlite3.connect(self._db_path) conn.row_factory = sqlite3.Row return conn def on_ready(self, path, source_type=None): with self._lock, self._connect() as conn: conn.execute( "INSERT INTO streams (path, source_type, start, end) VALUES (?, ?, ?, NULL)", (path, source_type, _now_iso()), ) def on_not_ready(self, path): with self._lock, self._connect() as conn: row = conn.execute( "SELECT id FROM streams WHERE path = ? AND end IS NULL ORDER BY id DESC LIMIT 1", (path,), ).fetchone() if row: conn.execute( "UPDATE streams SET end = ? WHERE id = ?", (_now_iso(), row["id"]), ) def active(self): with self._connect() as conn: rows = conn.execute( "SELECT path, source_type, start FROM streams WHERE end IS NULL ORDER BY id DESC" ).fetchall() return [dict(r) for r in rows] def history(self): with self._connect() as conn: rows = conn.execute( "SELECT path, source_type, start, end FROM streams ORDER BY id DESC LIMIT ?", (self._history_limit,), ).fetchall() return [dict(r) for r in rows]