Pulling progress crashed KOReader in syncToProgress when the target
wasn't a page number or xpointer (e.g. an epubcfi string from the
server on a rolling document, falling to the percentage branch):
'attempt to call method gotoPercent (a nil value)'. Documents have no
such method — navigation goes through events, exactly as the other
branches in this function already do.
The percentage fallback now dispatches Event GotoPercent (0-100),
handled by ReaderRolling:onGotoPercent and ReaderPaging:onGotoPercent
(the built-in Go-to-% handlers, which also refresh the view). The
redundant UpdatePos follow-up is gone; handlers do it.
Verified all four paths in the stub harness: rolling+percentage →
Event:GotoPercent, rolling+xpointer → Event:GotoXPointer, paging+
page → Event:GotoPage, paging+unresolvable → no-op. No crashes.
A device log showed pushes carrying highlights:[] while the document
had highlights — progress synced fine, annotations never moved.
Root cause: G_reader_settings:readSetting(key, default) returns the
STORED table verbatim and does not merge defaults into it. A settings
file saved by an older plugin version has sync_progress=true but no
sync_bookmarks/sync_highlights/sync_notes keys, so all three read as
nil; the 'or' chain in _doUpdateProgress then fell to the else branch
that pushes empty annotation arrays — silently, since progress in the
same request succeeds.
init() now reads into a fresh table, merges any missing
default_settings keys, and persists once (so the settings file and
the menu checkboxes self-heal on first run). Verified in the KOReader
stub harness against the live server with the exact scenario: stale
settings shaped like the device's, one highlight, one local bookmark
list — after init sync_highlights=true, and the pushed request body
contains the highlight (previously empty).
isValidPos0 compared pos0:sub(1,7) against the 6-char string '/body/',
so it tested '/body/D' == '/body/' — false for every CRE xpointer, and
applyServerAnnotations silently dropped ALL pulled highlights/notes/
bookmarks on rolling documents. Caught by running the real plugin
code against the live server in a stubbed KOReader harness: push
stored rows on the server, pull applied nothing — matching the
reported 'neither direction syncs' symptom.
sub(1,6) now matches. Both directions re-verified end-to-end through
the actual plugin: a KOReader-style highlight pushes and lands in the
server DB (with server-derived percentage), and a metadata pull
populates a fresh device's bookmark list with web- and device-sourced
highlights (xpointer pos0s) plus web bookmarks (numeric page pos0s on
paging documents).
- collectAnnotations no longer calls getPageFromXPointer per
annotation (a CRE-engine lookup each — the most expensive thing the
sync loop did on weak hardware). Paging documents compute
percentage arithmetically; CRE documents omit it entirely and the
server derives it from the locator against the actual book.
- chapter/page are sent raw; the server's tolerant types accept
strings, empty strings, and CRE xpointers in the page field.
Previously a single annotation with chapter:'' failed the ENTIRE
progress push with a 400, silently killing progress sync too.
- applyServerAnnotations validates pos0 before applying: rolling
documents require '/body/...' xpointers, paging documents numeric
pages. Unplaceable locators are skipped instead of becoming junk
local bookmarks that would re-push as duplicates.
On the first open of a freshly downloaded book there is no cached
bookhoard UUID yet, since it is only learned from a successful push
response. Pulling at that point simply failed with "Push progress
first", which also broke auto sync on open.
- onReaderReady auto sync: if no UUID is cached, push once to
bootstrap identity (the server resolves the book by format-aware
SHA-256 and returns the UUID, which we then cache); otherwise pull
as before
- getProgress: when no UUID is cached, instead of showing the dead-end
"Push progress first" message, show "Linking this book to Bookhoard
first…", push once via _bootstrapUUIDThenPull, and retry the pull
once the UUID is cached
- _bootstrapUUIDThenPull: new helper that pushes, waits 2s for the
UUID to be cached, then performs the originally-requested pull,
falling back to an error message on failure
After this bootstrap both push and pull work without ordering.
Bring the plugin to feature parity with the server's annotation sync
system. Previously annotations were push-only (and even that was broken
because the server ignored inline annotations in progress pushes).
ANNOTATION PULL (server → device):
- Add applyServerAnnotations() method called from _doGetProgress
after progress handling
- Parses annotations.highlights, annotations.notes, and
annotations.bookmarks from GetMetadata response
- Matches incoming annotations against local bookmarks by pos0
(start position) to detect existing entries
- New entries are inserted with correct KOReader bookmark format:
page, pos0, pos1, datetime, text, notes, color, chapter
- Existing entries with changed content are updated (LWW: server
version is authoritative since the server already resolved conflicts)
- Respects sync_highlights, sync_notes, sync_bookmarks toggles for
inbound sync, not just outbound
- Triggers onSortBookmarks + saveSettings on change for persistence
DELETE PROPAGATION:
- Parses annotations.deleted_highlights and annotations.deleted_bookmarks
from GetMetadata response
- Each entry contains device_sync_data (pos0, datetime, page) from the
original device push
- Matches local bookmarks by pos0 and removes them
- Deleted highlights only remove entries that have text (to avoid
removing plain bookmarks at the same position)
CLEANUP:
- Fix BookhoardAPI.lua: rename pcall return _ to err for clarity
(was functional but misleading variable name)
- Remove dead code: unused getLibrary() and syncBookmarks() API methods
- Remove sync_endpoints field (stored from registration response but
never read by any code)
The pull path (_doGetProgress + syncToProgress) still used the
reflowable-oriented nav_target selection that prefers xpointer/cfi
over page. For has_pages documents, if the server returned an epubcfi
(e.g., a fake CFI from the web reader's comic renderer), nav_target
became a CFI string, tonumber() returned nil, and GotoPage(nil)
crashed koreader when the user confirmed the sync prompt. After the
crash the old position was retained because GotoPage never completed.
This makes the pull path symmetric with the push path (ef129fd, which
omits epubcfi for paging documents):
- _doGetProgress: for has_pages, select nav_target from progress.page
and show the actual page number in the sync prompt instead of
computing it from server_percentage × total_pages (which could
display the wrong page due to rounding differences between foliate
and koreader page math).
- syncToProgress: add a nil guard so a missing page number skips
gracefully instead of crashing.
collectBookData put getLastProgress() directly into the epubcfi field
for every document. For paging documents (has_pages == true: PDF, CBZ,
CBR, CB7, CBT, DjVu) that value is the current page as a *number*, so
the payload carried "epubcfi": 5 (a JSON number). The server types
KOReaderBookProgress.Epubcfi as *string, and the strict JSON binder
rejected it with a 400 ("cannot unmarshal number into ... epububcfi of
type string"), which the plugin surfaced as the misleading
"Failed to push progress. Check your network connection." Reflowable
EPUBs were unaffected because CREngine returns an xpointer string.
Only set epubcfi for rolling (reflowable) documents. Paging documents
carry their position in the numeric page/total_pages fields, which the
server already treats as the canonical locator for fixed-layout/comic
formats. The pull path already falls back to progress.page, so restore
keeps working.
Lua's json.encode produces {} for empty tables instead of [].
Use json.util.InitArray to mark tables as arrays so they encode
correctly as [] for authors, bookmarks, highlights, notes, and books.
- Append ?token= auth param to OPDS catalog URL
- Replace any existing stale Bookhoard entry (with or without token)
- Hot-update OPDS plugin's in-memory servers table so catalog
appears immediately without restarting KOReader
Writes directly to KOReader's opds.lua settings so the user
doesn't have to type the long URL. Also updates the manual
menu item to auto-configure instead of showing the URL.
Instead of showing a long auth URL that's impractical to type on
e-ink, tell users to open their Bookhoard web UI and approve from
Device Management. Uses persistent InfoMessage (no timeout) so
the message stays visible while they switch devices.
pcall wraps socket.http.request returns as:
true, 1, status_code, headers, status_line
The old code captured '1' as the status code instead of
the actual HTTP status. Added '_' to skip the first return.