progress.py setzt docs/progress-sync.md um (read_at in Serverzeit mit Uhrversatz, sent_at beim
Senden, force, Warteschlange je Buch, Neuanwendung nach dem Abgleich; dieselben Regeln wie
Ops.kt/ops.js). Der Reader speichert nur, was der Leser bewegt hat; wiederherstellen,
Umbruch und Sprünge schreiben nichts. Neuere Stellen anderer Geräte werden angeboten
("Auf X weitergelesen – springen?"), nie von allein angewendet; "w" springt zur weitesten
Stelle. Fortschritt als Textanteil in Zeichen statt 0.0.
Abgleich über GET /books/ (ohne Markierungs-Blobs), Bücher erst beim Öffnen geladen
(☁ in der Liste). `diora-tui login` meldet wie die Apps an, liest den vorhandenen Token,
leitet den Schlüssel aus dem Passwort ab und speichert beides geschützt (DPAPI).
DIORA_TUI_HOME legt alle Dateien in einen Ordner (platformdirs folgt unter Windows den
Umgebungsvariablen nicht). 35 Tests, tools/smoke_server.py gegen einen lokalen Server.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
35 lines
1 KiB
Python
35 lines
1 KiB
Python
"""Library scanning and the stable per-book key shared across progress.py, blocks.py and
|
|
remote.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import re
|
|
from pathlib import Path
|
|
|
|
# Books downloaded from the server are named "<server id, 4+ digits> - <title>.epub".
|
|
_SERVER_FILE_RE = re.compile(r"^(\d{4,7}) - ")
|
|
|
|
|
|
def server_id_of(path: Path) -> int | None:
|
|
m = _SERVER_FILE_RE.match(path.name)
|
|
return int(m.group(1)) if m else None
|
|
|
|
|
|
def server_key(server_id: int) -> str:
|
|
return f"s{server_id}"
|
|
|
|
|
|
def _book_id(path: Path) -> str:
|
|
"""A book's key for reading state: the server's id for a downloaded book (the same whether
|
|
or not, and under whatever name, the file is there), else a hash of its path."""
|
|
sid = server_id_of(path)
|
|
if sid is not None:
|
|
return server_key(sid)
|
|
return hashlib.sha256(str(path.resolve()).encode()).hexdigest()[:16]
|
|
|
|
|
|
def scan_library(library_dir: Path) -> list[Path]:
|
|
if not library_dir.exists():
|
|
return []
|
|
return sorted(library_dir.rglob("*.epub"))
|