import sqlite3 from app.settings import settings # Columns actually used by the app today. beets' `items` table has many more; # add columns here as new features need them rather than SELECT *. _ITEM_COLUMNS = ["id", "path", "title", "artist", "albumartist", "genres", "grouping", "isrc", "added", "format"] def db_exists() -> bool: return settings.beets_db_path.exists() def _connect() -> sqlite3.Connection: # mode=ro: alembic never writes through this connection. Mutations (tag # edits, imports) go through the `beet` CLI or beets.library.Library # in-process -- see services/library_edit.py (manual-fix feature). conn = sqlite3.connect(f"file:{settings.beets_db_path}?mode=ro", uri=True) conn.row_factory = sqlite3.Row return conn def _decode_path(value) -> str: """beets stores `path` as a BLOB (raw filesystem bytes), not TEXT.""" if isinstance(value, bytes): return value.decode("utf-8", errors="replace") return value or "" def _row_to_dict(row: sqlite3.Row) -> dict: d = dict(row) if "path" in d: d["path"] = _decode_path(d["path"]) return d def query_items(grouping: str | None = None) -> list[dict]: """All items, optionally filtered to one playlist's grouping tag.""" if not db_exists(): return [] cols = ", ".join(_ITEM_COLUMNS) conn = _connect() try: if grouping is not None: cur = conn.execute(f"SELECT {cols} FROM items WHERE grouping = ?", (grouping,)) else: cur = conn.execute(f"SELECT {cols} FROM items") return [_row_to_dict(row) for row in cur.fetchall()] finally: conn.close() def get_item(item_id: int) -> dict | None: if not db_exists(): return None conn = _connect() try: cur = conn.execute("SELECT * FROM items WHERE id = ?", (item_id,)) row = cur.fetchone() return _row_to_dict(row) if row else None finally: conn.close() def stats() -> dict: """Cheap summary for the dashboard and for migration-verification (compare against `beet stats` during Stage 0 cutover).""" if not db_exists(): return {"db_exists": False, "total_tracks": 0, "groupings": []} conn = _connect() try: total = conn.execute("SELECT COUNT(*) FROM items").fetchone()[0] rows = conn.execute( "SELECT DISTINCT grouping FROM items WHERE grouping IS NOT NULL AND grouping != ''" ).fetchall() return { "db_exists": True, "total_tracks": total, "groupings": sorted(r[0] for r in rows), } finally: conn.close()