The stats pull keyed the statistics book table by md5 alone, while
KOReader's native statistics plugin keys rows by exact (title, authors,
md5). When the two parsers extract slightly different metadata for the
same file, the first native open creates a second, zeroed book row that
the KOReader UI reads, and the reading time synced from Readest stays
stranded on the sync-created row.
applyRemote now inserts a book row only for an md5 the DB has never
seen, attaches pulled events to the row the native plugin reads (native
rows always set pages and last_open; sync-created rows leave pages
NULL), and folds never-adopted duplicate rows into the surviving row on
every pull, so existing databases heal and the stranded time reappears.
Adopted rows and the live session's cached book id are never deleted,
and the totals recompute no longer regresses last_open below a real
native open timestamp.
Fixes#4861
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(koplugin): repair reading-stats sync push/pull
Reading statistics (statistics.sqlite3 page events) never reached the
Readest sync server. Three stacked defects in the koplugin stats path:
1. push/pull called settings:readSetting/saveSetting on self.settings,
which is the plain readest_sync data table (not a LuaSettings object),
so auto-sync crashed on every book open
("attempt to call method 'readSetting' (a nil value)").
2. pushChanges declares books/notes/configs as required_params, but the
stats push sent only statBooks/statPages, so Spore rejected the request
client-side ("books is required for method pushChanges").
3. statBooks/statPages were listed only under the spec's payload, not as
optional_params. Spore's expected-param set is
required_params + optional_params, so it rejected them too
("statBooks is not expected for method pushChanges").
Fixes:
- Read/persist the cursor as a plain field and save the whole table via
G_reader_settings:saveSetting, mirroring readest_syncauth.
- Send empty books/notes/configs alongside statBooks/statPages; the server
defaults each to [] and processes the stat arrays independently.
- Declare statBooks/statPages as optional_params in readest-sync-api.json.
Also add ReadestStats debug logging across pushBookStats/pullBookStats and
push/pull (cursor, collected counts, dispatch, response status, cursor
advance), and surface the push failure status/body that was previously
swallowed on non-interactive syncs.
Tests: cover push/pull cursor handling, the pushChanges required_params
contract, and a spec-level check that every stats payload key is an
expected pushChanges param.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(koplugin): add interactive Push/Pull stats now menu items
Add "Push stats now" and "Pull stats now" entries to the Readest sync
menu, placed after "Readest library" and before "Push books now" and
gated on being signed in (access_token + user_id) like the other
account-level items. They call pushBookStats(true) / pullBookStats(true)
for a manual interactive sync of reading statistics (the whole
statistics.sqlite3 delta), complementing the automatic pull-on-open /
push-on-close.
Interactive push/pull now also confirm their result (pushed / pulled /
up to date) to match the sibling "now" items, since a silent success
would look like a no-op.
Extract the five new strings into the koplugin .po catalogs (empty
msgstr, English fallback at runtime).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(koplugin): translate reading-stats sync strings (33 locales)
Fill the previously empty msgstr entries for the reading-statistics sync
strings across all koplugin locale catalogs: the two new "Push/Pull stats
now" menu items, the pushed / pulled / up-to-date confirmations, and the
push/pull failure messages. Each locale mirrors its existing
Push/Pull/Failed-to verbs and "reading" terminology for consistency.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Supersedes #3156. Adds a reading-statistics system whose canonical data model
is KOReader's own (book + page_stat_data), so stats round-trip losslessly
between Readest and KOReader.
- Storage: a cross-platform Turso statistics.db in KOReader's exact schema
(web/Workers, desktop, iOS, Android) — replacing #3156's Node-only
better-sqlite3 + statistics.json.
- Tracking: per-page reading events (time-on-page, idle-capped) flushed on
page-change/idle/hide/close — the KOReader model — not session aggregates.
- Sync: legacy /api/sync extended with a stats type backed by self-contained
Supabase tables (stat_books, stat_pages); union/longer-duration-wins merge
keyed on book_hash. apps/readest.koplugin syncs through the same endpoint.
- Scale & robustness: per-tab singleton connection (avoids OPFS lock
conflicts) + explicit WAL checkpoint; transactional bulk apply; chunked
resumable push; client-driven paged pull with trailing-ms completion;
paginated/scoped server merge.
Verified: 5668 unit tests, 155 koplugin busted tests, biome+tsgo + luacheck
all green; web OPFS DB verified live.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>