Cache library index and refresh changed manga

This commit is contained in:
Codex committed 2026-10-08 21:53:08 +02:00
1 parent eef4ffa07c
commit 8ac27e1f3d
6 files changed
+269 -30

No files matched your search

+10 -3
View File
@@ -54,7 +54,10 @@ The catalog and relation store is reader-owned `data/catalog.json`. Sentinel wri
The admin browser never talks to the scraper daemon directly. Admin-only reader
routes proxy commands to the daemon's private API, so its shared token stays on the
server. The daemon publishes complete folders atomically and increments a durable
revision; the reader polls that revision and rescans the library after publication.
revision; the reader polls that revision and refreshes changed manga after publication.
The reader restores a disposable full-index snapshot at startup so browsing need not
wait for a filesystem walk. Shallow directory stamps identify changed manga; a
background and daily full reconciliation catches in-place manual edits.
| Path | Lines | Responsibility |
|------|------:|----------------|
@@ -78,7 +81,8 @@ revision; the reader polls that revision and rescans the library after publicati
- `lib` is the current library index, replaced as a whole after each scan.
- `db` is all user data, loaded from `db.json` at start.
- **Timers:**
- a library scan at start and every `SCAN_INTERVAL` minutes,
- a saved-index restore and shallow check at start (full scan when no snapshot),
- a shallow check every `SCAN_INTERVAL` minutes, full reconciliation every `FULL_SCAN_INTERVAL` minutes,
- a debounced `db.json` write, 500 ms after the last change.
- **Shutdown:** `SIGINT` / `SIGTERM` flush `db.json`; the daemon cooperatively stops at a page boundary and keeps its archive checkpoint.
- **Durability:** every file write goes to `*.tmp` first and is then renamed over the target, so a file is never half-written. A hard kill loses at most the changes of the last ~0.5 s. A corrupt `db.json` makes the server **refuse to start** rather than start empty and overwrite it.
@@ -146,6 +150,9 @@ This keeps ids human-readable and derivable without a database. The price: renam
```
- `data/scan-cache.json` holds `{ "<Manga>/<Book>/<Chapter>": { m: <folder mtimeMs>, p: [[file, w, h], …] } }`. It is disposable.
- `data/library-index.json` holds a disposable, versioned index snapshot and shallow
directory stamps. The library folders remain authoritative; a missing or invalid
snapshot triggers a full scan. Rollback-compatible `scan-cache.json` is retained.
- **Backups:** copy `data/db.json`. The atomic writes make a copy at any time consistent.
## 6. Server
@@ -331,7 +338,7 @@ docker compose up -d --build
- **Volumes:**
- `/library`: read-only in the reader and writable in the scraper. Files must be readable—and scraper targets writable—by uid 1000.
- `/data`: reader state must be writable; the scraper also needs its writable `/data/scraper` state.
- **Configuration:** process wiring uses environment variables (`PORT`, `LIBRARY`, `DATA`, `SIGNUP`, `SCAN_INTERVAL`, `COOKIE_SECURE`, `SCRAPER_URL`, and `SCRAPER_TOKEN`). Queue pacing and automatic-update schedule are persisted in `data/scraper/queue.json` and edited through the authenticated admin UI.
- **Configuration:** process wiring uses environment variables (`PORT`, `LIBRARY`, `DATA`, `SIGNUP`, `SCAN_INTERVAL`, `FULL_SCAN_INTERVAL`, `COOKIE_SECURE`, `SCRAPER_URL`, and `SCRAPER_TOKEN`). Queue pacing and automatic-update schedule are persisted in `data/scraper/queue.json` and edited through the authenticated admin UI.
- **Without Docker:** `npm run start:all` with Node ≥ 18, or supervise `npm start` and `npm run start:scraper` independently. On the current Windows machine that supervision is already owned by `\MangaCircus Supervisor`.
- **Behind a reverse proxy:** terminate TLS, set `COOKIE_SECURE=1`. A sub-path works.
- **Upgrades:** replace the code and restart. Reader state has no explicit migration;
+16 -6
View File
@@ -4,11 +4,12 @@ MangaCircus does not copy or convert anything. "Import" means **indexing the lib
```
┌───────────── triggers ─────────────┐
│ start-up · every SCAN_INTERVAL min │
│ start-up · shallow interval · daily full │
│ admin "Rescan" · admin "Full rescan"│
└────────────────┬───────────────────┘
▼
library/ ──read──▶ scanLibrary(root, oldCache) library.js
data/library-index.json ──▶ immediate in-memory index at start (if valid)
library/ ──read──▶ shallow changed-manga check or full scan library.js
├─ scanManga (per manga folder)
│ ├─ book folders → parseKey, book.json, cover
│ └─ chapter folders → scanVersion
@@ -20,19 +21,23 @@ MangaCircus does not copy or convert anything. "Import" means **indexing the lib
▼
lib = index (atomic swap, requests see old or new, never half)
▼
data/scan-cache.json (written atomically)
data/scan-cache.json + data/library-index.json (atomic)
```
## 1. Triggers
| Trigger | When | Cache |
|---------|------|-------|
| Start-up | Right after the HTTP server starts listening. Requests are served meanwhile; the library page shows "Scanning…" until the first scan is done | used |
| Interval | Every `SCAN_INTERVAL` minutes (default 10, `0` = off) | used |
| Start-up | Restore the saved index immediately if its library mount is present, then check changed manga. Without a valid snapshot, scan the whole library; the page shows "Scanning…" until done | used |
| Interval | Shallow change check every `SCAN_INTERVAL` minutes (default 10, `0` = off); full reconciliation every `FULL_SCAN_INTERVAL` minutes (default 1440, `0` = off) | used |
| **Rescan** (Settings → Admin, or `POST /api/admin/rescan {}`) | On demand; the request returns when the scan is done | used |
| **Full rescan** (`POST /api/admin/rescan {"full":true}`) | On demand | ignored: every image header is read again |
Scans are **single-flight**. A trigger that fires while a scan is running joins that scan instead of starting a second one.
Scans are **single-flight**. A trigger during a scan queues one coalesced follow-up,
with a full reconciliation taking precedence over a shallow check. Publication
revisions trigger a shallow check; if no changed directory is visible, the reader
falls back to a full scan. In-place edits to `chapter.json` are found by the full
reconciliation or an admin Rescan, not by shallow parent-directory stamps.
## 2. Walking the tree
@@ -155,6 +160,11 @@ The reader lays pages out before they load, so it needs every image's size. Inst
## 4. The scan cache
- Stored in `DATA/scan-cache.json`. It is rewritten atomically after every successful scan and holds only the folders seen in that scan, so deleted chapters drop out.
- `DATA/library-index.json` also stores the complete rebuildable index and shallow
manga/book directory stamps. It lets a restarted reader serve the dashboard and
manga pages before the background filesystem check completes. Both files can be
deleted safely; without the index the next startup walks the library, and without
the size cache it also rereads image headers.
- Invalidation is **per chapter folder, by its mtime**. Adding, removing or renaming a file changes the folder mtime, so that folder is re-read on the next scan.
- **Blind spot:** overwriting an image *in place* (same name) usually doesn't change the folder's mtime, so the old size stays cached. Run a **Full rescan** after such edits, or write new files and rename them (§7).
- Deleting the cache file is harmless. The next scan is simply slower.