fix(koreader): keep manual links to a different copy matched and suggest links

Report a linked copy whose bytes differ from the library file as positionIncompatible instead of
unmatched, so the plugin keeps the match, skips highlights without a failure and still syncs progress.
Treat a position-less place within a page as the same place and jump to the nearest page.
Suggest library books with the same title and authors for books to review, with bulk linking.
See docs/KOREADER_HASH_MATCHING.md.

Closes #1872
Closes #1919
This commit is contained in:
neon
2026-10-26 15:06:04 +00:00
parent 834da8c954
commit 1c27e5dd05
36 changed files with 1634 additions and 63 deletions
@@ -340,7 +340,7 @@ stepMatch = function(ctx)
for _, match in ipairs(body.matches or {}) do
if match.hash == ctx.snap.digest then
ctx.state:setMatched(match.hash, match.bookFileId, match.bookId, ctx.snap.file)
ctx.state:setMatched(match.hash, match.bookFileId, match.bookId, ctx.snap.file, nil, match.positionCompatible)
bindStatsRow(ctx, ctx.state:getBook(ctx.snap.digest))
if not acknowledge(ctx, "match") then return end
return step(ctx, stepStats)
@@ -561,7 +561,8 @@ stepAnnotations = function(ctx)
closed_book = apply_mode == "sidecar",
})
if not result.had_errors then
ctx.sidecar_ann_done = true
-- An incompatible copy exchanged nothing, so its sidecar is not marked as known.
if not result.incompatible then ctx.sidecar_ann_done = true end
book.annCount = ctx.snap.ann_count
-- Only park remote changes when the user actually wants them applied;
-- with two-way highlights off there is nothing to come back for.
@@ -716,7 +717,11 @@ stepBookmarks = function(ctx)
return step(ctx, stepState)
end
if result.had_errors then ctx.had_errors = true else ctx.sidecar_bm_done = true end
if result.had_errors then
ctx.had_errors = true
elseif not result.incompatible then
ctx.sidecar_bm_done = true
end
ctx.counts.bookmarks = (ctx.counts.bookmarks or 0) + result.uploaded
ctx.highlight_summary = BookOrbitHighlightSummary.addBookmarks(ctx.highlight_summary, result)
if not result.had_errors and result.remote_pending and ctx.on_remote_pending then