Reading progress sync: push EPUB progress to Kavita #8

Merged
365DevNet merged 0 commits from refs/pull/8/head into main 2026-07-16 00:44:20 +02:00
365DevNet commented 2026-07-16 00:43:29 +02:00 (Migrated from codeberg.org)

Pushes EPUB reading progress from the device to Kavita, automatically when the app opens and on demand via a Sync button.

Design spec: docs/superpowers/specs/2026-07-15-progress-sync-design.md

Scope (locked with Richard)

  • Push-only. Nothing is ever written to explorer-3.db — the device DB is opened READONLY, never with immutable=1 (which ignores the WAL and silently returns stale data).
  • Only books in /mnt/ext1/Kavita. Sideloaded books are ignored; we downloaded the Kavita ones, so we know exactly which chapter they are without guessing.
  • Never move Kavita backwards. Push only when the device is ahead. No local state needed.

Structure

  • core/DeviceReadingDb — real sqlite3, host-tested against a temp DB built in the test. The join to books_settings is a LEFT join: a loaded-but-never-opened book has no row and must read as zeros, not vanish.
  • core/SyncEngine — pure. Takes a candidate, returns Push or Skip. No sqlite, no HTTP, no inkview.
  • core/KavitaClient — getProgress / setProgress.
  • main.cpp — executes actions.

Two bugs the live verification caught

The plan required verifying Kavita's PageNum convention before shipping rather than assuming it. Doing so confirmed the design was right (pageNum == chapter.pages means finished) but found the client was non-functional in two ways, both silent:

  • GET /api/Reader/progress is a 404. The real path is /api/Reader/get-progress; only the POST lives at /progress. Since a non-200 means "unknown server state" and skips the book, every book was skipped — sync would have pushed nothing, forever, while reporting "Nothing to sync".
  • POST /api/Reader/progress requires libraryId. Kavita's ProgressDto marks it required and 400s the whole request without it. It cannot be read back from the server (an unread chapter reports libraryId 0), so it comes from the browse loop.

Neither was reachable by a unit test: the fakes faithfully replayed the shape we believed in, so green tests measured our belief rather than Kavita. Both came from endpoint notes that were simply wrong. Confirmed against Kavita's source (Kavita.Server/Controllers/ReaderController.cs, Kavita.Models/DTOs/Progress/ProgressDto.cs).

Not guessing which book a filename means

A filename can match more than one Kavita chapter, and first-match-wins could mark a never-downloaded book as read. Ambiguous matches are skipped. This was not hypothetical: at the time it was written, the only book on the device matched two chapters of the same series.

Verification

83 host tests, clean cross-build.

End-to-end on device, against the live server: device at page 10/102 of Ikigai (9.8%); Kavita gives that chapter 22 pages; SyncEngine computed round(10/102*22) = 2; Kavita now holds pageNum 2 with libraryId 4 recorded. Sync before reading correctly reported "Nothing to sync".

Pushes EPUB reading progress from the device to Kavita, automatically when the app opens and on demand via a Sync button. Design spec: docs/superpowers/specs/2026-07-15-progress-sync-design.md ## Scope (locked with Richard) - **Push-only.** Nothing is ever written to explorer-3.db — the device DB is opened READONLY, never with immutable=1 (which ignores the WAL and silently returns stale data). - **Only books in /mnt/ext1/Kavita.** Sideloaded books are ignored; we downloaded the Kavita ones, so we know exactly which chapter they are without guessing. - **Never move Kavita backwards.** Push only when the device is ahead. No local state needed. ## Structure - `core/DeviceReadingDb` — real sqlite3, host-tested against a temp DB built in the test. The join to books_settings is a LEFT join: a loaded-but-never-opened book has no row and must read as zeros, not vanish. - `core/SyncEngine` — pure. Takes a candidate, returns Push or Skip. No sqlite, no HTTP, no inkview. - `core/KavitaClient` — getProgress / setProgress. - `main.cpp` — executes actions. ## Two bugs the live verification caught The plan required verifying Kavita's PageNum convention before shipping rather than assuming it. Doing so confirmed the design was right (pageNum == chapter.pages means finished) but found the client was non-functional in two ways, both silent: - **GET /api/Reader/progress is a 404.** The real path is /api/Reader/get-progress; only the POST lives at /progress. Since a non-200 means "unknown server state" and skips the book, every book was skipped — sync would have pushed nothing, forever, while reporting "Nothing to sync". - **POST /api/Reader/progress requires libraryId.** Kavita's ProgressDto marks it required and 400s the whole request without it. It cannot be read back from the server (an unread chapter reports libraryId 0), so it comes from the browse loop. Neither was reachable by a unit test: the fakes faithfully replayed the shape we believed in, so green tests measured our belief rather than Kavita. Both came from endpoint notes that were simply wrong. Confirmed against Kavita's source (Kavita.Server/Controllers/ReaderController.cs, Kavita.Models/DTOs/Progress/ProgressDto.cs). ## Not guessing which book a filename means A filename can match more than one Kavita chapter, and first-match-wins could mark a never-downloaded book as read. Ambiguous matches are skipped. This was not hypothetical: at the time it was written, the only book on the device matched two chapters of the same series. ## Verification 83 host tests, clean cross-build. **End-to-end on device, against the live server:** device at page 10/102 of Ikigai (9.8%); Kavita gives that chapter 22 pages; SyncEngine computed round(10/102*22) = 2; Kavita now holds pageNum 2 with libraryId 4 recorded. Sync before reading correctly reported "Nothing to sync".
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
365DevNet/PocketBook_Kavita_sync!8
No description provided.