"""On-disk cache for the (FlatBook, BookLayout) pair a book open computes — extraction + row-layout for very large books (tens of thousands of blocks) can take several seconds each; reopening the same book at the same terminal width should be near-instant instead of paying that cost again every time. Not a correctness-critical cache: any miss (new book, different width, edited file) just falls back to recomputing from scratch, so a stale/corrupt cache entry is handled by overwriting it, never by crashing the reader. """ from __future__ import annotations import hashlib import pickle from pathlib import Path from platformdirs import user_cache_dir from .blocks import FlatBook from .layout import BookLayout _CACHE_DIR = Path(user_cache_dir("diora-tui", "diora")) / "layout_cache" def _cache_key(path: Path, width: int) -> str: stat = path.stat() raw = f"{path.resolve()}|{stat.st_size}|{stat.st_mtime_ns}|{width}" return hashlib.sha256(raw.encode()).hexdigest()[:32] def load(path: Path, width: int) -> tuple[FlatBook, BookLayout] | None: cache_file = _CACHE_DIR / f"{_cache_key(path, width)}.pickle" if not cache_file.exists(): return None try: with cache_file.open("rb") as f: book, book_layout = pickle.load(f) if not isinstance(book, FlatBook) or not isinstance(book_layout, BookLayout): return None return book, book_layout except Exception: return None def save(path: Path, width: int, book: FlatBook, book_layout: BookLayout) -> None: _CACHE_DIR.mkdir(parents=True, exist_ok=True) cache_file = _CACHE_DIR / f"{_cache_key(path, width)}.pickle" try: with cache_file.open("wb") as f: pickle.dump((book, book_layout), f, protocol=pickle.HIGHEST_PROTOCOL) except Exception: pass # best-effort — a failed cache write shouldn't break reading