diora-web/tui/diora_tui/library_meta.py
mrwnslz 695037fb9c TUI: Bibliothek wie im Web (Weiterlesen-Karte, Status- und Ordner-Chips, Suche, Sortierung, Länge)
library.py hält die Regeln der Web-Bibliothek (53-library.js) als reine Funktionen:
Status Angefangen/Ungelesen/Gelesen, Ordner (mehrere je Buch, Reihenfolge aus den
verschlüsselten Bibliothekseinstellungen, "Ohne Ordner"), Suche ohne Akzente, sechs
Sortierungen mit natürlicher Zahlenordnung, Weiterlesen-Karte aus den zuletzt gelesenen,
Bücher/Zeitschriften. library_screen.py zeigt sie an (Tasten 1-4, o, /, s, m, c; Chips und
Karte klickbar). Metadaten (Ordner, Seitenzahl, Zeitschrift) und die Ordnerreihenfolge
kommen beim Abgleich; fehlt die Länge, rechnet die TUI sie für geladene Bücher aus dem Text
(1800 Zeichen je Seite). crypto.encrypt für Stufe 3. 54 Tests, Smoke prüft Ordner/Länge/Prefs
gegen den lokalen Server.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 13:18:49 +01:00

75 lines
2.3 KiB
Python

"""What the TUI knows about the server's books beyond the files: title, author, read status
and which books exist at all (so a book can be listed before it is downloaded). Set only by
`sync`; the reader's position is progress.py.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
from . import paths
_DATA_DIR = paths.data_dir()
_META_FILE = _DATA_DIR / "library.json"
def _load_all() -> dict[str, dict]:
try:
data = json.loads(_META_FILE.read_text())
except (OSError, ValueError):
return {}
return data if isinstance(data, dict) else {}
def _save_all(data: dict[str, dict]) -> None:
_DATA_DIR.mkdir(parents=True, exist_ok=True)
tmp = _META_FILE.with_suffix(".tmp")
tmp.write_text(json.dumps(data, indent=1))
os.replace(tmp, _META_FILE)
def is_read(book_key: str) -> bool:
entry = _load_all().get(book_key)
return bool(entry and entry.get("is_read"))
def get(book_key: str) -> dict | None:
return _load_all().get(book_key)
def get_extra(name: str, default=None):
"""A setting kept beside the books (stored under '_<name>'): the folder order, the sort."""
entry = _load_all().get("_" + name)
return entry["value"] if isinstance(entry, dict) and "value" in entry else default
def set_extra(name: str, value) -> None:
data = _load_all()
data["_" + name] = {"value": value}
_save_all(data)
def set_local_pages(book_key: str, pages: int) -> None:
"""A book's length worked out from the file here (the server's meta may not have it)."""
data = _load_all()
entry = data.setdefault("_pages", {"value": {}})
entry.setdefault("value", {})[book_key] = int(pages)
_save_all(data)
def local_pages() -> dict[str, int]:
return dict(get_extra("pages", {}) or {})
def remote_entries() -> dict[str, dict]:
"""Every server book: key -> {server_id, title, author, is_read, uploaded_at}."""
return {k: v for k, v in _load_all().items() if isinstance(v, dict) and v.get("server_id") is not None}
def set_remote(entries: dict[str, dict]) -> None:
"""Replace the server books with `entries` (a book deleted on the server drops out)."""
data = {k: v for k, v in _load_all().items() if not (isinstance(v, dict) and v.get("server_id") is not None)}
data.update(entries)
_save_all(data)