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:
+5
-11
@@ -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
|
||||
|
||||
@@ -68,7 +68,6 @@ Bookhoard.default_settings = {
|
||||
sync_highlights = true,
|
||||
sync_notes = true,
|
||||
sync_mode = SYNC_MODE.IMMEDIATE,
|
||||
sync_endpoints = nil,
|
||||
}
|
||||
|
||||
function Bookhoard:init()
|
||||
@@ -212,7 +211,6 @@ function Bookhoard:buildMainMenu()
|
||||
ok_callback = function()
|
||||
self.settings.auth_token = nil
|
||||
self.settings.device_id = nil
|
||||
self.settings.sync_endpoints = nil
|
||||
self.settings.auto_sync = false
|
||||
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
||||
self:registerEvents()
|
||||
@@ -623,7 +621,6 @@ function Bookhoard:startRegistrationPoll()
|
||||
self.registration_id = nil
|
||||
self.settings.auth_token = result.auth_token
|
||||
self.settings.device_id = result.device_id and tostring(result.device_id) or nil
|
||||
self.settings.sync_endpoints = result.sync_endpoints
|
||||
G_reader_settings:saveSetting(self.settings_key, self.settings)
|
||||
self:registerEvents()
|
||||
self:setupOPDS()
|
||||
@@ -1039,6 +1036,10 @@ function Bookhoard:_doGetProgress(interactive)
|
||||
return
|
||||
end
|
||||
|
||||
if result.annotations then
|
||||
self:applyServerAnnotations(result.annotations)
|
||||
end
|
||||
|
||||
local progress = result.progress
|
||||
local percentage = self:getLastPercent()
|
||||
local server_percentage = progress.percentage or 0
|
||||
@@ -1100,6 +1101,118 @@ function Bookhoard:_doGetProgress(interactive)
|
||||
end)
|
||||
end
|
||||
|
||||
function Bookhoard:applyServerAnnotations(annotations)
|
||||
if not self.ui.bookmark or not self.ui.bookmark.bookmarks then
|
||||
return
|
||||
end
|
||||
if not annotations then return end
|
||||
|
||||
local local_bms = self.ui.bookmark.bookmarks
|
||||
local changed = false
|
||||
local has_pages = self.ui.document.info.has_pages
|
||||
|
||||
local function findLocalByPos0(pos0)
|
||||
for i, bm in ipairs(local_bms) do
|
||||
if bm.pos0 == pos0 then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function makePageFromPos(pos0)
|
||||
if has_pages then
|
||||
local page = tonumber(pos0:match("(%d+)$"))
|
||||
return page or pos0
|
||||
end
|
||||
return pos0
|
||||
end
|
||||
|
||||
local function addOrUpdate(server_entry, has_text)
|
||||
local pos0 = server_entry.pos0 or ""
|
||||
if pos0 == "" then return end
|
||||
local idx = findLocalByPos0(pos0)
|
||||
if idx then
|
||||
local bm = local_bms[idx]
|
||||
local srv_text = server_entry.text or ""
|
||||
local srv_notes = server_entry.notes or ""
|
||||
if (bm.text or "") ~= srv_text or (bm.notes or "") ~= srv_notes then
|
||||
bm.text = srv_text
|
||||
bm.notes = srv_notes
|
||||
if server_entry.color then bm.color = server_entry.color end
|
||||
changed = true
|
||||
end
|
||||
else
|
||||
local entry = {
|
||||
page = makePageFromPos(pos0),
|
||||
pos0 = pos0,
|
||||
pos1 = server_entry.pos1 or pos0,
|
||||
datetime = server_entry.datetime or "",
|
||||
text = server_entry.text or "",
|
||||
notes = server_entry.notes or "",
|
||||
}
|
||||
if server_entry.color then entry.color = server_entry.color end
|
||||
if server_entry.chapter then entry.chapter = server_entry.chapter end
|
||||
table.insert(local_bms, entry)
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
|
||||
if self.settings.sync_highlights and annotations.highlights then
|
||||
for _, hl in ipairs(annotations.highlights) do
|
||||
addOrUpdate(hl, true)
|
||||
end
|
||||
end
|
||||
|
||||
if self.settings.sync_notes and annotations.notes then
|
||||
for _, note in ipairs(annotations.notes) do
|
||||
addOrUpdate(note, true)
|
||||
end
|
||||
end
|
||||
|
||||
if self.settings.sync_bookmarks and annotations.bookmarks then
|
||||
for _, bm in ipairs(annotations.bookmarks) do
|
||||
addOrUpdate(bm, false)
|
||||
end
|
||||
end
|
||||
|
||||
if annotations.deleted_highlights then
|
||||
for _, del in ipairs(annotations.deleted_highlights) do
|
||||
local pos0 = del.pos0 or ""
|
||||
if pos0 ~= "" then
|
||||
local idx = findLocalByPos0(pos0)
|
||||
if idx and (local_bms[idx].text or "") ~= "" then
|
||||
table.remove(local_bms, idx)
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if annotations.deleted_bookmarks then
|
||||
for _, del in ipairs(annotations.deleted_bookmarks) do
|
||||
local pos0 = del.pos0 or ""
|
||||
if pos0 ~= "" then
|
||||
local idx = findLocalByPos0(pos0)
|
||||
if idx then
|
||||
table.remove(local_bms, idx)
|
||||
changed = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if changed then
|
||||
if self.ui.bookmark.onSortBookmarks then
|
||||
self.ui.bookmark:onSortBookmarks()
|
||||
end
|
||||
if self.ui.saveSettings then
|
||||
self.ui:saveSettings()
|
||||
end
|
||||
logger.dbg("Bookhoard: annotations updated from server")
|
||||
end
|
||||
end
|
||||
|
||||
function Bookhoard:_showSyncedMessage()
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Progress has been synchronized."),
|
||||
|
||||
Reference in New Issue
Block a user