diff --git a/BookhoardAPI.lua b/BookhoardAPI.lua index b5208bc..8fc05d7 100644 --- a/BookhoardAPI.lua +++ b/BookhoardAPI.lua @@ -126,6 +126,13 @@ function BookhoardAPI:syncProgress(book_data, sync_mode) }, PROGRESS_TIMEOUTS) end +-- Read-only SHA-256 → book UUID lookup. Used to link a freshly downloaded +-- book WITHOUT pushing progress (a push would transmit the device's +-- first-page position and clobber/conflict with real server progress). +function BookhoardAPI:resolveBook(sha256) + return self:_request("GET", "/api/sync/koreader/resolve?sha256=" .. sha256, nil, PROGRESS_TIMEOUTS) +end + function BookhoardAPI:getMetadata(uuid) return self:_request("GET", "/api/sync/koreader/metadata/" .. uuid, nil, PROGRESS_TIMEOUTS) end diff --git a/main.lua b/main.lua index f8405c1..250c909 100644 --- a/main.lua +++ b/main.lua @@ -121,17 +121,13 @@ end function Bookhoard:onReaderReady() if self.settings.auto_sync then UIManager:nextTick(function() - -- On the first open of a freshly downloaded book there is no cached - -- bookhoard UUID yet (it is only learned from a successful push - -- response). Pulling now would just fail with "Push progress first". - -- Instead, push once to bootstrap identity: the server resolves the - -- book by SHA-256 (format-aware) and returns the UUID, which we then - -- cache. After that, push and pull both work without ordering. - if not self:getBookhoardUUID() then - self:updateProgress(true, false) - else - self:getProgress(true, false) - end + -- Pull-first on every open. A freshly downloaded book has no cached + -- bookhoard UUID yet; getProgress resolves it by SHA-256 with a + -- read-only lookup (no progress push). The previous push-to- + -- bootstrap strategy transmitted the device's first-page position + -- to the server on books already mid-read elsewhere, creating a + -- sync conflict before the first pull could ever happen. + self:getProgress(true, false) end) end self:registerEvents() @@ -1116,9 +1112,70 @@ function Bookhoard:getProgress(ensure_networking, interactive) self.pull_timestamp = now end --- Establish the bookhoard UUID for the current document by pushing once, then --- (once cached) perform the originally-requested pull. Used when a pull is --- requested before any push has run on a newly downloaded book. +-- Link the current document to Bookhoard WITHOUT pushing progress, then run +-- the pull. The UUID is learned via a read-only SHA-256 resolve; only when +-- the server does not know the hash at all (sideloaded book) do we fall back +-- to the legacy push-bootstrap, and only for background (non-interactive) +-- flows — a user-initiated pull must never push this device's position. +function Bookhoard:_linkBookThenPull(interactive) + local file_sha256 = self:getFileSHA256() + if not file_sha256 then + if interactive then + UIManager:show(InfoMessage:new{ + text = _("Could not read this file to link it with Bookhoard."), + timeout = 3, + }) + end + return + end + + UIManager:scheduleIn(0.5, function() + local api = self:getAPI() + local ok, result = api:resolveBook(file_sha256) + + UIManager:nextTick(function() + if not self.ui.doc_settings then return end + + if ok and type(result) == "table" and result.book_uuid then + self.ui.doc_settings:saveSetting("bookhoard_uuid", result.book_uuid) + self.ui.doc_settings:flush() + logger.dbg("Bookhoard: linked book by SHA-256 to", result.book_uuid) + self:_doGetProgress(interactive) + return + end + + if result and result.status == 404 then + -- Genuinely unknown to the server (sideloaded book). The + -- legacy push-bootstrap only ever linked such books via fuzzy + -- title/author matching; keep that as a background attempt, + -- but tell interactive users to push explicitly. + if interactive then + UIManager:show(InfoMessage:new{ + text = _("This book is not in your Bookhoard library yet. Push progress to link it."), + timeout = 4, + }) + else + self:_bootstrapUUIDThenPull(interactive) + end + return + end + + logger.warn("Bookhoard: failed to resolve book by SHA-256") + if interactive then + UIManager:show(InfoMessage:new{ + text = _("Could not reach Bookhoard. Check your network connection and try again."), + timeout = 4, + }) + end + end) + end) +end + +-- Legacy fallback: establish the bookhoard UUID for the current document by +-- pushing once, then (once cached) perform the originally-requested pull. +-- Only reached from background flows when the SHA-256 resolve reported the +-- book as unknown to the server; the push mainly serves fuzzy title/author +-- linking and device alias registration for sideloaded books. function Bookhoard:_bootstrapUUIDThenPull(interactive) self:updateProgress(false, false) UIManager:scheduleIn(2, function()