feat: implement bidirectional annotation sync and delete propagation

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)
This commit is contained in:
2026-07-29 14:49:41 -04:00
parent 138347c821
commit 9d7b0f689f
2 changed files with 121 additions and 14 deletions
+5 -11
View File
@@ -60,23 +60,23 @@ function BookhoardAPI:_request(method, path, body, timeouts)
socketutil:set_timeout(timeouts[1], timeouts[2])
local ok, _, code
local ok, err, code
if url:match("^https://") then
if ssl_ok then
ok, _, code = pcall(ssl_https.request, request)
ok, err, code = pcall(ssl_https.request, request)
else
socketutil:reset_timeout()
return false, { error = "HTTPS not supported on this device" }
end
else
ok, _, code = pcall(http.request, request)
ok, err, code = pcall(http.request, request)
end
socketutil:reset_timeout()
if not ok then
logger.warn("BookhoardAPI: request failed:", _)
return false, { error = tostring(_) }
logger.warn("BookhoardAPI: request failed:", err)
return false, { error = tostring(err) }
end
local response_body = table.concat(sink)
@@ -130,12 +130,6 @@ function BookhoardAPI:getMetadata(uuid)
return self:_request("GET", "/api/sync/koreader/metadata/" .. uuid, nil, PROGRESS_TIMEOUTS)
end
function BookhoardAPI:getLibrary()
return self:_request("GET", "/api/sync/koreader/library", nil, PROGRESS_TIMEOUTS)
end
function BookhoardAPI:syncBookmarks(data)
return self:_request("POST", "/api/sync/koreader/bookmarks", data, PROGRESS_TIMEOUTS)
end
return BookhoardAPI