From 516bca6de56270974db79e9f12f4c4cd1f2c22f2 Mon Sep 17 00:00:00 2001 From: "peter.fong" Date: Fri, 12 Dec 2025 23:26:15 +0100 Subject: [PATCH] state bugs --- bookscraper/db/repository.py | 55 +++++++++++++ bookscraper/scraper/utils/state_sync.py | 84 +++++++++++++++++++- bookscraper/static/js/bookcard_controller.js | 7 +- 3 files changed, 142 insertions(+), 4 deletions(-) diff --git a/bookscraper/db/repository.py b/bookscraper/db/repository.py index bd44d22..989482f 100644 --- a/bookscraper/db/repository.py +++ b/bookscraper/db/repository.py @@ -299,3 +299,58 @@ def inc_parsed(book_idx, amount=1): @logcall def inc_audio_done_legacy(book_idx, amount=1): return inc_audio_done(book_idx, amount) + + +# ============================================================ +# READ — DERIVED BOOK STATE +# ============================================================ + + +@logcall +def get_book_state(book_idx): + """ + Canonical read-model for a single book. + + Responsibilities: + - Read SQLite snapshot (static metadata) + - Read Redis live state (counters / status) + - Compute derived fields (NO UI logic) + + Invariants: + - downloaded = chapters_download_done + chapters_download_skipped + """ + + # --- SQLite snapshot --- + sqlite_row = sql_fetch_book(book_idx) or {} + + # --- Redis live state --- + key = f"book:{book_idx}:state" + redis_state = _r.hgetall(key) or {} + + # Normalize numeric redis values + def _int(v): + try: + return int(v) + except Exception: + return 0 + + # --- primary counters --- + chapters_done = _int(redis_state.get("chapters_download_done")) + chapters_skipped = _int(redis_state.get("chapters_download_skipped")) + + # --- derived counters --- + downloaded = chapters_done + chapters_skipped + + # --- build canonical state --- + state = {} + + # 1) start with SQLite snapshot + state.update(sqlite_row) + + # 2) overlay Redis live fields + state.update(redis_state) + + # 3) enforce derived invariants + state["downloaded"] = downloaded + + return state diff --git a/bookscraper/scraper/utils/state_sync.py b/bookscraper/scraper/utils/state_sync.py index 7b5b20b..5bbbca6 100644 --- a/bookscraper/scraper/utils/state_sync.py +++ b/bookscraper/scraper/utils/state_sync.py @@ -46,7 +46,7 @@ def _build_card(sqlite_row, redis_state, merged): # ============================================================ # INSPECT ONLY — NO WRITES # ============================================================ -def inspect_books_state(): +def inspect_books_state_depecrated(): """ Reads all books from SQLite and fetches Redis progress. Builds: @@ -121,6 +121,88 @@ def inspect_books_state(): return results +# ============================================================ +# INSPECT ONLY — NO WRITES +# ============================================================ +def inspect_books_state(): + """ + Reads canonical book state from repository. + Builds: + • entry.sqlite + • entry.redis + • entry.would_merge_to + • entry.card (book-card compatible) + """ + + from db.repository import get_book_state + from db.db import get_db + + db = get_db() + cur = db.cursor() + + # Alleen nodig om te weten *welke* books er zijn + cur.execute("SELECT book_idx FROM books") + rows = cur.fetchall() + + results = [] + + for row in rows: + book_idx = row["book_idx"] + + # -------------------------------- + # Canonical state (ENIGE waarheid) + # -------------------------------- + state = get_book_state(book_idx) + + # SQLite-view = alleen SQLite-kolommen + sqlite_view = { + k: v + for k, v in state.items() + if k + in ( + "book_idx", + "title", + "author", + "description", + "cover_path", + "book_url", + "chapters_total", + "status", + "downloaded", + "parsed", + "audio_done", + "created_at", + "processdate", + "last_update", + ) + } + + # Redis-view = alleen Redis counters/status + redis_view = { + k: v + for k, v in state.items() + if k.startswith("chapters_") + or k in ("status", "audio_done", "audio_skipped") + } + + merged = state # letterlijk de canonieke state + + card = _build_card(sqlite_view, redis_view, merged) + + results.append( + { + "book_idx": book_idx, + "title": state.get("title"), + "sqlite": sqlite_view, + "redis": redis_view, + "would_merge_to": merged, + "card": card, + } + ) + + return results + + # ============================================================ # SYNC REDIS → SQLITE (writes) # ============================================================ diff --git a/bookscraper/static/js/bookcard_controller.js b/bookscraper/static/js/bookcard_controller.js index cfea644..9a8717b 100644 --- a/bookscraper/static/js/bookcard_controller.js +++ b/bookscraper/static/js/bookcard_controller.js @@ -93,9 +93,10 @@ function updateButtons(card, state) { function updateProgress(card, s) { const total = Number(s.chapters_total || 0); - const downloadDone = - Number(s.chapters_download_done || 0) + - Number(s.chapters_download_skipped || 0); + // const downloadDone = + // Number(s.chapters_download_done || 0) + + // Number(s.chapters_download_skipped || 0); + const downloadDone = Number(s.downloaded || 0); const audioDone = Number(s.audio_done || 0) + Number(s.audio_skipped || 0);