Sync fixes for underpowered devices: thin client, server-heavy

- 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.
This commit is contained in:
2026-08-18 19:14:18 -04:00
parent c79868cffb
commit 57434dde6e
+27 -9
View File
@@ -827,19 +827,21 @@ function Bookhoard:collectAnnotations()
if not all_bookmarks then if not all_bookmarks then
return bookmarks, highlights, notes return bookmarks, highlights, notes
end end
local file_sha256 = self:getFileSHA256() local file_sha256 = self:getFileSHA256()
local total_pages = self.ui.document:getPageCount() local total_pages = self.ui.document:getPageCount()
local has_pages = self.ui.document.info.has_pages
for _, bm in ipairs(all_bookmarks) do for _, bm in ipairs(all_bookmarks) do
local page_num = bm.page -- Thin-client policy: no per-annotation CRE lookups here (a
if type(page_num) == "string" and self.ui.document.info.has_pages == false then -- getPageFromXPointer call each is the most expensive thing this
page_num = self.ui.document:getPageFromXPointer(page_num) -- loop can do on weak hardware). Percentages are arithmetic for
end -- paging documents and simply omitted for CRE documents — the
page_num = tonumber(page_num) or 0 -- server derives them from the locator against the actual book.
local page_num = tonumber(bm.page) or 0
local percentage = has_pages and total_pages > 0 and (page_num / total_pages) or nil
local percentage = total_pages > 0 and (page_num / total_pages) or 0
local chapter = bm.chapter or "" local chapter = bm.chapter or ""
if type(chapter) == "table" then chapter = "" end
local entry = { local entry = {
chapter = chapter, chapter = chapter,
@@ -849,9 +851,11 @@ function Bookhoard:collectAnnotations()
pos1 = bm.pos1 or "", pos1 = bm.pos1 or "",
page = tostring(bm.page or ""), page = tostring(bm.page or ""),
text = bm.text or "", text = bm.text or "",
percentage = Math.roundPercent(percentage),
book_sha256 = file_sha256, book_sha256 = file_sha256,
} }
if percentage then
entry.percentage = Math.roundPercent(percentage)
end
local has_text = bm.text and bm.text ~= "" local has_text = bm.text and bm.text ~= ""
local has_notes = bm.notes and bm.notes ~= "" local has_notes = bm.notes and bm.notes ~= ""
@@ -1159,9 +1163,23 @@ function Bookhoard:applyServerAnnotations(annotations)
return pos0 return pos0
end end
local function isValidPos0(pos0)
-- Rolling (CRE) documents address bookmarks/highlights by xpointer;
-- paging documents (PDF/comics/DjVu) by page number. Anything else
-- (a raw "cfi:" locator, an epubcfi(...) string, a JSON anchor, …)
-- cannot be placed in this document and must not become a local
-- bookmark: it could never be matched again and would be re-pushed
-- to the server as a junk duplicate on the next sync.
if pos0 == "" then return false end
if has_pages then
return tonumber(pos0) ~= nil
end
return pos0:sub(1, 7) == "/body/"
end
local function addOrUpdate(server_entry, has_text) local function addOrUpdate(server_entry, has_text)
local pos0 = server_entry.pos0 or "" local pos0 = server_entry.pos0 or ""
if pos0 == "" then return end if not isValidPos0(pos0) then return end
local idx = findLocalByPos0(pos0) local idx = findLocalByPos0(pos0)
if idx then if idx then
local bm = local_bms[idx] local bm = local_bms[idx]