You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
5.5 KiB
191 lines
5.5 KiB
# ============================================================
|
|
# File: scraper/utils/state_sync.py
|
|
# Purpose:
|
|
# State inspection + optional sync logic for unified book_idx model.
|
|
# Generates full book-card compatible dicts for debug UI.
|
|
# ============================================================
|
|
|
|
import os
|
|
import redis
|
|
from db.db import get_db
|
|
|
|
|
|
def _build_card(sqlite_row, redis_state, merged):
|
|
"""
|
|
Creates a dict that matches the fields required by components/bookcard.html:
|
|
b.book_idx
|
|
b.title
|
|
b.author
|
|
b.cover_path
|
|
b.status
|
|
b.created_at
|
|
b.download_done
|
|
b.download_total
|
|
b.audio_done
|
|
b.audio_total
|
|
"""
|
|
|
|
return {
|
|
"book_idx": sqlite_row.get("book_idx"),
|
|
"title": sqlite_row.get("title") or "Unknown",
|
|
"author": sqlite_row.get("author"),
|
|
"cover_path": sqlite_row.get("cover_path"),
|
|
# Use merged status (Redis > SQLite)
|
|
"status": merged.get("status") or sqlite_row.get("status") or "unknown",
|
|
# Meta
|
|
"created_at": sqlite_row.get("created_at"),
|
|
# Download counters
|
|
"download_done": merged.get("downloaded", 0),
|
|
"download_total": merged.get("chapters_total", 0),
|
|
# Audio counters
|
|
"audio_done": merged.get("audio_done", 0),
|
|
"audio_total": merged.get("chapters_total", 0),
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
# INSPECT ONLY — NO WRITES
|
|
# ============================================================
|
|
def inspect_books_state():
|
|
"""
|
|
Reads all books from SQLite and fetches Redis progress.
|
|
Builds:
|
|
• entry.sqlite
|
|
• entry.redis
|
|
• entry.would_merge_to
|
|
• entry.card (book-card compatible)
|
|
"""
|
|
|
|
r = redis.Redis.from_url(os.getenv("REDIS_BROKER"), decode_responses=True)
|
|
db = get_db()
|
|
cur = db.cursor()
|
|
|
|
cur.execute("SELECT * FROM books")
|
|
rows = cur.fetchall()
|
|
|
|
results = []
|
|
|
|
for row in rows:
|
|
sqlite_row = dict(row)
|
|
book_idx = sqlite_row["book_idx"]
|
|
|
|
redis_key = f"book:{book_idx}:state"
|
|
redis_state = r.hgetall(redis_key) or {}
|
|
|
|
# ================================
|
|
# DRY-RUN MERGE LOGIC
|
|
# ================================
|
|
merged = sqlite_row.copy()
|
|
|
|
if redis_state:
|
|
|
|
merged["downloaded"] = int(
|
|
redis_state.get("chapters_download_done", merged.get("downloaded", 0))
|
|
)
|
|
|
|
merged["parsed"] = int(
|
|
redis_state.get("chapters_parsed_done", merged.get("parsed", 0))
|
|
)
|
|
|
|
merged["audio_done"] = int(
|
|
redis_state.get("audio_done", merged.get("audio_done", 0))
|
|
)
|
|
|
|
merged["chapters_total"] = int(
|
|
redis_state.get("chapters_total", merged.get("chapters_total", 0))
|
|
)
|
|
|
|
merged["status"] = redis_state.get(
|
|
"status", merged.get("status", "unknown")
|
|
)
|
|
|
|
# ================================
|
|
# Build book-card data
|
|
# ================================
|
|
card = _build_card(sqlite_row, redis_state, merged)
|
|
|
|
# ================================
|
|
# Append final result entry
|
|
# ================================
|
|
results.append(
|
|
{
|
|
"book_idx": book_idx,
|
|
"title": sqlite_row.get("title"),
|
|
"sqlite": sqlite_row,
|
|
"redis": redis_state,
|
|
"would_merge_to": merged,
|
|
"card": card,
|
|
}
|
|
)
|
|
|
|
return results
|
|
|
|
|
|
# ============================================================
|
|
# SYNC REDIS → SQLITE (writes)
|
|
# ============================================================
|
|
def sync_books_from_redis():
|
|
"""
|
|
Writes Redis progress values back into SQLite.
|
|
Uses unified book_idx as identifier.
|
|
"""
|
|
|
|
r = redis.Redis.from_url(os.getenv("REDIS_BROKER"), decode_responses=True)
|
|
db = get_db()
|
|
cur = db.cursor()
|
|
|
|
cur.execute("SELECT * FROM books")
|
|
rows = cur.fetchall()
|
|
|
|
results = []
|
|
|
|
for row in rows:
|
|
before = dict(row)
|
|
book_idx = before["book_idx"]
|
|
|
|
redis_key = f"book:{book_idx}:state"
|
|
redis_state = r.hgetall(redis_key)
|
|
|
|
if not redis_state:
|
|
results.append(
|
|
{
|
|
"book_idx": book_idx,
|
|
"before": before,
|
|
"redis": {},
|
|
"after": before,
|
|
}
|
|
)
|
|
continue
|
|
|
|
# Extract progress from Redis
|
|
downloaded = int(redis_state.get("chapters_download_done", 0))
|
|
parsed = int(redis_state.get("chapters_parsed_done", 0))
|
|
audio_done = int(redis_state.get("audio_done", 0))
|
|
total = int(redis_state.get("chapters_total", 0))
|
|
status = redis_state.get("status", before.get("status"))
|
|
|
|
# Update SQLite
|
|
cur.execute(
|
|
"""
|
|
UPDATE books
|
|
SET downloaded = ?, parsed = ?, audio_done = ?, chapters_total = ?, status = ?, last_update = datetime('now')
|
|
WHERE book_idx = ?
|
|
""",
|
|
(downloaded, parsed, audio_done, total, status, book_idx),
|
|
)
|
|
db.commit()
|
|
|
|
cur.execute("SELECT * FROM books WHERE book_idx = ?", (book_idx,))
|
|
after = dict(cur.fetchone())
|
|
|
|
results.append(
|
|
{
|
|
"book_idx": book_idx,
|
|
"before": before,
|
|
"redis": redis_state,
|
|
"after": after,
|
|
}
|
|
)
|
|
|
|
return results
|