* 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>
readest.koplugin tests
Unit tests for apps/readest.koplugin/library/ modules. Runs under LuaJIT
2.1 (the runtime KOReader uses) via busted.
Toolchain
One-time per machine:
# macOS
brew install luajit luarocks
# Linux (Debian/Ubuntu)
sudo apt-get install luajit luarocks
# Then, regardless of OS:
luarocks --lua-version=5.1 install busted
luarocks --lua-version=5.1 install lsqlite3complete
The --lua-version=5.1 flag is required: LuaJIT identifies itself as Lua 5.1, and we install rocks against that runtime so production code (which targets LuaJIT) and test code share a Lua interpreter.
Running
From the repo root:
pnpm test:lua
Or from this directory:
eval "$(luarocks --lua-version=5.1 path)"
busted --lua=$(which luajit)
Layout
spec/
├── spec_helper.lua # KOReader stubs + lua-ljsqlite3 shim (loaded once)
├── library/
│ ├── smoke_spec.lua # Sanity check that the harness boots
│ └── *_spec.lua # One per module under library/
└── README.md # This file
What spec_helper provides
require("lua-ljsqlite3/init")→ returns a SQLite shim wrappinglsqlite3complete. Exposes the subset of the lua-ljsqlite3 API our library modules use (open,exec,prepare,bind1,step,reset,clearbind,close, etc).require("logger")→ no-op logger (warn/info/dbg/errcallable).require("datastorage")→ fakeDataStorage:getSettingsDir()returning a per-testmktemp -dpath.require("device")→ stubDevice.canUseWAL() == true,Device.screenwithgetWidth/getHeight.G_reader_settings(global) → in-memoryreadSetting/saveSetting/flush.
Each spec calls require("spec_helper").reset() in before_each to wipe state.
Adding a new module
- Write production code at
apps/readest.koplugin/library/foo.lua. - Write
apps/readest.koplugin/spec/library/foo_spec.lua. - Run
pnpm test:luafrom the repo root. - Run
pnpm lint:luato syntax-check (LuaJIT bytecode compile).
Why LuaJIT and not stock Lua?
KOReader runs LuaJIT exclusively. LuaJIT extends Lua 5.1 with FFI and a few syntax tweaks; stock Lua 5.4 has features (integer division //, bit operators ~, <const> annotations) that LuaJIT rejects. Running tests under LuaJIT catches these incompatibilities at test time instead of when KOReader fails to load the plugin.