# ============================================================ # File: db/state_sql.py (UPDATED for book_idx-only architecture) # Purpose: # Low-level SQLite snapshot layer for BookScraper metadata. # Used ONLY through db.repository façade. # ============================================================ import sqlite3 import os from logbus.publisher import log # Must match db/db.py DB_PATH = os.getenv("BOOKSCRAPER_DB", "/app/data/books.db") # ------------------------------------------------------------ # INTERNAL HELPERS # ------------------------------------------------------------ def _connect(): conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row return conn # ------------------------------------------------------------ # FETCH # ------------------------------------------------------------ def sql_fetch_book(book_idx): conn = _connect() cur = conn.cursor() cur.execute("SELECT * FROM books WHERE book_idx = ?", (book_idx,)) row = cur.fetchone() conn.close() return dict(row) if row else None def sql_fetch_all_books(): conn = _connect() cur = conn.cursor() cur.execute("SELECT * FROM books ORDER BY created_at DESC") rows = cur.fetchall() conn.close() return [dict(r) for r in rows] # ------------------------------------------------------------ # REGISTER / UPDATE # ------------------------------------------------------------ def sql_register_book(book_idx, fields: dict): """ Insert or replace entire book record. book_idx is the PRIMARY KEY. """ conn = _connect() cur = conn.cursor() cols = ", ".join(["book_idx"] + list(fields.keys())) placeholders = ", ".join(["?"] * (1 + len(fields))) values = [book_idx] + list(fields.values()) cur.execute( f"INSERT OR REPLACE INTO books ({cols}) VALUES ({placeholders})", values, ) conn.commit() conn.close() def sql_update_book(book_idx, fields: dict): if not fields: return conn = _connect() cur = conn.cursor() set_clause = ", ".join([f"{k} = ?" for k in fields]) params = list(fields.values()) + [book_idx] cur.execute( f"UPDATE books SET {set_clause} WHERE book_idx = ?", params, ) conn.commit() conn.close() # ------------------------------------------------------------ # STATUS # ------------------------------------------------------------ def sql_set_status(book_idx, status: str): conn = _connect() cur = conn.cursor() cur.execute( "UPDATE books SET status = ? WHERE book_idx = ?", (status, book_idx), ) conn.commit() conn.close() # ------------------------------------------------------------ # CHAPTER TOTAL (snapshot) # ------------------------------------------------------------ def sql_set_chapters_total(book_idx, total: int): conn = _connect() cur = conn.cursor() cur.execute( "UPDATE books SET chapters_total = ? WHERE book_idx = ?", (total, book_idx), ) conn.commit() conn.close() # ------------------------------------------------------------ # COUNTERS (SNAPSHOT-ONLY) # ------------------------------------------------------------ def sql_inc_downloaded(book_idx, amount=1): conn = _connect() cur = conn.cursor() cur.execute( """ UPDATE books SET downloaded = COALESCE(downloaded,0) + ? WHERE book_idx = ? """, (amount, book_idx), ) conn.commit() conn.close() def sql_inc_parsed(book_idx, amount=1): conn = _connect() cur = conn.cursor() cur.execute( """ UPDATE books SET parsed = COALESCE(parsed,0) + ? WHERE book_idx = ? """, (amount, book_idx), ) conn.commit() conn.close() def sql_inc_audio_done(book_idx, amount=1): log(f"[DB-SQL] Incrementing audio_done for {book_idx} by {amount}") conn = _connect() cur = conn.cursor() cur.execute( """ UPDATE books SET audio_done = COALESCE(audio_done,0) + ? WHERE book_idx = ? """, (amount, book_idx), ) conn.commit() conn.close() def sql_inc_audio_skipped(book_idx, amount=1): log(f"[DB-SQL] Incrementing audio_skipped for {book_idx} by {amount}") conn = _connect() cur = conn.cursor() cur.execute( """ UPDATE books SET audio_skipped = COALESCE(audio_skipped,0) + ? WHERE book_idx = ? """, (amount, book_idx), ) conn.commit() conn.close()