fix(reader): fall back to percentage jump when kosync response has no xpointer (#777)

* fix(reader): fall back to percentage jump when kosync response has no xpointer

The KOReader plugin's progress pull calls syncToProgress(body.progress),
which fires GotoXPointer(nil) or GotoPage(nil) when body.progress is nil.
Both silently no-op, so the reader position does not move and the user
sees 'Progress has been synchronized.' with nothing changing.

This happens whenever the kosync response has percentage-only progress
(e.g. progress written by the BookOrbit web reader, or by an external
audiobook->ebook progress bridge that only knows the percentage, not
an EPUB xpointer). hasUsableRemoteProgress already guards this case in
reconcileProgressBeforeBookSync, but getProgress / applyRemoteProgress
never got the same guard.

Extend syncToProgress to accept an optional percentage_fallback (0-1,
kosync scale). When there is no usable xpointer/page but a fallback
percentage is present, fire GotoPercent (0-100 scale) so the reader
actually jumps.

* fix(reader): apply percentage fallback in pre-book-sync reconcile

Drop the hasUsableRemoteProgress short-circuit so the reconcile that runs before a manual book
sync behaves like the pull paths. Now that syncToProgress falls back to a percentage jump, the
two flows disagreed on identical data: a manual pull moved the reader while the pre-sync
reconcile reported "no exact location" and changed nothing, which also left the percentage
argument passed to applyRemoteProgress unreachable.

An earlier guard already returns when body.percentage is absent, so every response reaching this
point can resolve a target. The strategy branches keep the conservative path intact: DISABLE
still skips the progress push when the remote is newer.

---------

Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
This commit is contained in:
Samuel Painter
2026-07-26 15:39:11 -07:00
committed by GitHub
parent 3fabadcf0d
commit 631ff92fa2
@@ -68,8 +68,15 @@ function ProgressSync:getDocumentDigest()
return computed
end
function ProgressSync:syncToProgress(progress)
logger.dbg("BookOrbit: syncing to progress", progress)
function ProgressSync:syncToProgress(progress, percentage_fallback)
logger.dbg("BookOrbit: syncing to progress", progress, "percent", percentage_fallback)
local has_position = progress ~= nil and progress ~= ""
if not has_position and percentage_fallback then
-- No exact position (push came from web/audio sync); jump by percentage.
-- kosync percentage is 0-1; GotoPercent expects 0-100.
self.ui:handleEvent(Event:new("GotoPercent", percentage_fallback * 100))
return
end
if self.ui.document.info.has_pages then
self.ui:handleEvent(Event:new("GotoPage", tonumber(progress)))
else
@@ -77,10 +84,6 @@ function ProgressSync:syncToProgress(progress)
end
end
local function hasUsableRemoteProgress(progress)
return progress ~= nil and progress ~= ""
end
function ProgressSync:remoteProgressIsNewer(body, local_percentage)
local local_timestamp = self.last_page_turn_timestamp or 0
if body.timestamp ~= nil then
@@ -93,7 +96,7 @@ function ProgressSync:remoteProgressIsNewer(body, local_percentage)
end
function ProgressSync:applyRemoteProgress(body, on_done)
self:syncToProgress(body.progress)
self:syncToProgress(body.progress, body.percentage)
if on_done then
UIManager:scheduleIn(0.1, function()
on_done(false)
@@ -142,19 +145,6 @@ function ProgressSync:reconcileProgressBeforeBookSync(digest, on_done)
end
local remote_newer = self:remoteProgressIsNewer(body, local_percentage)
if not hasUsableRemoteProgress(body.progress) then
if remote_newer then
UIManager:show(InfoMessage:new{
text = _("BookOrbit has newer progress but no exact location. Syncing book data without changing progress."),
timeout = 4,
})
on_done(true)
else
on_done(false)
end
return true
end
local strategy = remote_newer and self.settings.sync_forward or self.settings.sync_backward
if strategy == SYNC_STRATEGY.SILENT then
self:applyRemoteProgress(body, on_done)
@@ -331,7 +321,7 @@ function ProgressSync:getProgress(ensure_networking, interactive)
end
if interactive then
self:syncToProgress(body.progress)
self:syncToProgress(body.progress, body.percentage)
if self.recordSyncSuccess then
self:recordSyncSuccess("progress_pull", _("Progress pulled"))
end
@@ -348,7 +338,7 @@ function ProgressSync:getProgress(ensure_networking, interactive)
local strategy = self_older and self.settings.sync_forward or self.settings.sync_backward
if strategy == SYNC_STRATEGY.SILENT then
self:syncToProgress(body.progress)
self:syncToProgress(body.progress, body.percentage)
if self.recordSyncSuccess then
self:recordSyncSuccess("progress_pull", _("Progress pulled"))
end
@@ -359,7 +349,7 @@ function ProgressSync:getProgress(ensure_networking, interactive)
UIManager:show(ConfirmBox:new{
text = T(template, Math.round(body.percentage * 100), body.device),
ok_callback = function()
self:syncToProgress(body.progress)
self:syncToProgress(body.progress, body.percentage)
if self.recordSyncSuccess then
self:recordSyncSuccess("progress_pull", _("Progress pulled"))
end