Files
readest/apps/readest-app
Huang Xin df34de1c38 fix(sync): WebDAV upload-after-enable and deletion propagation (#4856, #4860) (#4892)
* fix(sync): upload book files when Upload Book Files is enabled after first sync (#4856)

Incremental sync decided what to push purely from `isLocalNewer`
(`book.updatedAt` vs the shared index). A book's config/cover change over
time, but its FILE is immutable per hash and only needs uploading once.
After a first sync with "Upload Book Files" off, toggling it on never
bumped `book.updatedAt`, so the book was skipped and its file never
reached the remote.

Record which book FILES are already on the remote in library.json
(`uploadedHashes`) and split the push decision: config/cover stay gated on
the incremental "changed locally" cursor, while a file is (re)uploaded only
when syncBooks is on and its hash isn't recorded yet. This keeps an
incremental "Sync now" O(changed) — once a file is recorded, later syncs
skip it with no per-book HEAD probe, so large libraries don't pay an
O(library) cost on every sync. Full Sync bypasses the record as an escape
hatch for out-of-band drift. The record is additive and optional, so an
old client that rewrites the index just drops it and the next new-client
sync re-verifies each file once and re-records it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(sync): propagate WebDAV book deletions to peers and the server (#4860)

A book deleted on one device only tombstoned itself in library.json — the
deletion never reached other devices or the server:
  - peers kept the book: the reconcile pass skipped deletedAt entries, so a
    tombstone never removed the local copy;
  - the server kept the files: the per-hash directory was never GC'd;
  - the tombstone could vanish entirely: a device that had never seen the
    book rebuilt the index purely from its own library, dropping the
    tombstone and silently reviving the book for everyone.

Fixes all three in engine.syncLibrary:
  - apply a peer's tombstone locally (LocalStore.deleteBookLocally removes
    the app-managed copy and persists the tombstone), with
    edit-wins-over-delete LWW so a book still being read isn't yanked;
  - GC the remote per-hash directory of tombstoned books, scoped to the
    dirs the discovery scan saw so removed dirs are never re-DELETEd;
  - union remote-only entries (chiefly tombstones) into the re-pushed index
    so a deletion can't be dropped by a device that never had the book.

Books tombstoned mid-run are excluded from the push pass via the merged
state so a just-deleted book isn't re-uploaded right before it is GC'd.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:28:42 +02:00
..