From 957ae80836380fc8e21449336f2279dfb890580d Mon Sep 17 00:00:00 2001 From: John O'Keefe Date: Sat, 22 Aug 2026 11:06:58 -0400 Subject: [PATCH] fix(sync): don't treat a stale-server 404 as 'book not in library' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The resolve-endpoint 404 handler conflated two very different failures because they share a status code. The server's resolve handler answers a book miss with JSON {"error": "book not found"}, but a 404 produced by anything else — a server image predating the resolve endpoint, a reverse proxy, or a mistyped server URL — returns an HTML error page the API layer surfaces as error="HTTP 404". Both landed in the 'book not in your library, push progress to link it' branch. Following that advice against a stale server resurrects the original first-pull bug through a different door: the push fuzzy-matches the book by title and overwrites the server's mid-read progress with the device's first-page position. Plugin and server deploy independently, so version skew will happen again; this makes it harmless. Now only the JSON book-not-found body advises pushing (and only background flows attempt the legacy push-bootstrap). Any other 404 is reported as an outdated/unreachable server and, critically, pushes nothing. Verified against a live server: bogus hash -> 404 {"error":"book not found"}; route missing (pre-endpoint image) -> HTML 404. --- main.lua | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/main.lua b/main.lua index 6591c13..b4f6d07 100644 --- a/main.lua +++ b/main.lua @@ -1186,17 +1186,36 @@ function Bookhoard:_linkBookThenPull(interactive) 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, - }) + -- Two very different failures share this status. The server's + -- resolve handler answers a book miss with JSON + -- {"error": "book not found"}; anything else behind a 404 + -- (an HTML error page) means the route itself is missing — + -- a server image predating the resolve endpoint, a reverse + -- proxy, or a wrong server URL. Only the former may advise + -- pushing to link: acting on a stale-server 404 would push + -- this device's first-page position over the server's real + -- progress — the very conflict this flow exists to prevent. + if result.error == "book not found" 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 else - self:_bootstrapUUIDThenPull(interactive) + logger.warn("Bookhoard: resolve endpoint returned 404 without a book-not-found body (stale server?)") + if interactive then + UIManager:show(InfoMessage:new{ + text = _("Your Bookhoard server does not support this request and may be outdated. Update the server and try again."), + timeout = 5, + }) + end end return end