mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
1c27e5dd05
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
160 lines
5.2 KiB
Lua
160 lines
5.2 KiB
Lua
--[[--
|
|
Highlight sync summary helpers for BookOrbit.
|
|
|
|
Keeps the persisted diagnostic shape, count aggregation and concise user
|
|
messages consistent across open-book exchange, per-book sync and sweep.
|
|
]]
|
|
|
|
local T = require("ffi/util").template
|
|
local _ = require("gettext")
|
|
|
|
local HighlightSummary = {}
|
|
|
|
local COUNT_FIELDS = {
|
|
"uploaded",
|
|
"applied",
|
|
"deleted",
|
|
"failed",
|
|
"skipped",
|
|
"touched_books",
|
|
-- Books whose device copy differs from the library file: matched, but
|
|
-- highlights and bookmarks cannot be placed in it, so none were exchanged.
|
|
"incompatible",
|
|
}
|
|
|
|
-- Bookmarks are counted separately from highlights: they are a different user
|
|
-- surface, and folding them together would make "3 applied" unreadable.
|
|
local BOOKMARK_COUNT_FIELDS = {
|
|
"bm_uploaded",
|
|
"bm_applied",
|
|
"bm_deleted",
|
|
"bm_failed",
|
|
}
|
|
|
|
local ACTIONABLE_ERRORS = {
|
|
auth = true,
|
|
network = true,
|
|
unsupported_server = true,
|
|
partial_failure = true,
|
|
}
|
|
|
|
local function numberValue(value)
|
|
value = tonumber(value)
|
|
if not value or value < 0 then return 0 end
|
|
return value
|
|
end
|
|
|
|
function HighlightSummary.normalize(summary)
|
|
summary = summary or {}
|
|
local normalized = {
|
|
event = summary.event,
|
|
reason = summary.reason,
|
|
at = summary.at or os.time(),
|
|
message = summary.message,
|
|
}
|
|
for _, field in ipairs(COUNT_FIELDS) do
|
|
normalized[field] = numberValue(summary[field])
|
|
end
|
|
for _, field in ipairs(BOOKMARK_COUNT_FIELDS) do
|
|
normalized[field] = numberValue(summary[field])
|
|
end
|
|
return normalized
|
|
end
|
|
|
|
function HighlightSummary.add(summary, result, opts)
|
|
summary = HighlightSummary.normalize(summary)
|
|
result = result or {}
|
|
opts = opts or {}
|
|
|
|
for _, field in ipairs(COUNT_FIELDS) do
|
|
summary[field] = summary[field] + numberValue(result[field])
|
|
end
|
|
if opts.skipped then
|
|
summary.skipped = summary.skipped + numberValue(opts.skipped)
|
|
end
|
|
if opts.closed_book and numberValue(result.applied) + numberValue(result.deleted) > 0 then
|
|
summary.touched_books = summary.touched_books + 1
|
|
end
|
|
if result.had_errors and numberValue(result.failed) == 0 then
|
|
summary.failed = summary.failed + 1
|
|
end
|
|
return summary
|
|
end
|
|
|
|
-- Bookmark exchange results share the highlight result shape, so they are
|
|
-- folded into their own counters rather than the highlight ones.
|
|
function HighlightSummary.addBookmarks(summary, result)
|
|
summary = HighlightSummary.normalize(summary)
|
|
result = result or {}
|
|
summary.bm_uploaded = summary.bm_uploaded + numberValue(result.uploaded)
|
|
summary.bm_applied = summary.bm_applied + numberValue(result.applied)
|
|
summary.bm_deleted = summary.bm_deleted + numberValue(result.deleted)
|
|
summary.bm_failed = summary.bm_failed + numberValue(result.failed)
|
|
if result.had_errors and numberValue(result.failed) == 0 then
|
|
summary.bm_failed = summary.bm_failed + 1
|
|
end
|
|
return summary
|
|
end
|
|
|
|
function HighlightSummary.hasBookmarkCounts(summary)
|
|
summary = HighlightSummary.normalize(summary)
|
|
for _, field in ipairs(BOOKMARK_COUNT_FIELDS) do
|
|
if summary[field] > 0 then return true end
|
|
end
|
|
return false
|
|
end
|
|
|
|
function HighlightSummary.hasCounts(summary)
|
|
summary = HighlightSummary.normalize(summary)
|
|
for _, field in ipairs(COUNT_FIELDS) do
|
|
if summary[field] > 0 then return true end
|
|
end
|
|
return HighlightSummary.hasBookmarkCounts(summary)
|
|
end
|
|
|
|
function HighlightSummary.hasRemoteChanges(summary)
|
|
summary = HighlightSummary.normalize(summary)
|
|
return summary.applied + summary.deleted > 0
|
|
end
|
|
|
|
function HighlightSummary.hasRemoteBookmarkChanges(summary)
|
|
summary = HighlightSummary.normalize(summary)
|
|
return summary.bm_applied + summary.bm_deleted > 0
|
|
end
|
|
|
|
function HighlightSummary.actionableError(summary, err)
|
|
if ACTIONABLE_ERRORS[err] then return err end
|
|
summary = HighlightSummary.normalize(summary)
|
|
if summary.failed > 0 or summary.bm_failed > 0 then return "partial_failure" end
|
|
end
|
|
|
|
function HighlightSummary.message(summary)
|
|
summary = HighlightSummary.normalize(summary)
|
|
local text = T(_("Highlights synced: %1 uploaded, %2 applied, %3 deleted."),
|
|
summary.uploaded, summary.applied, summary.deleted)
|
|
if summary.failed > 0 or summary.skipped > 0 then
|
|
text = text .. "\n" .. T(_("Failed: %1. Skipped: %2."), summary.failed, summary.skipped)
|
|
end
|
|
if HighlightSummary.hasBookmarkCounts(summary) then
|
|
text = text .. "\n" .. T(_("Bookmarks: %1 uploaded, %2 applied, %3 deleted."),
|
|
summary.bm_uploaded, summary.bm_applied, summary.bm_deleted)
|
|
if summary.bm_failed > 0 then
|
|
text = text .. "\n" .. T(_("Bookmarks failed: %1."), summary.bm_failed)
|
|
end
|
|
end
|
|
if summary.touched_books > 0 then
|
|
text = text .. "\n" .. T(_("%1 closed book(s) updated."), summary.touched_books)
|
|
end
|
|
if summary.incompatible > 0 then
|
|
text = text .. "\n" .. T(_("%1 book(s) skipped: the copy on this device is a different file from the one in BookOrbit."), summary.incompatible)
|
|
end
|
|
return text
|
|
end
|
|
|
|
function HighlightSummary.diagnosticsText(summary)
|
|
if type(summary) ~= "table" then return _("none") end
|
|
return HighlightSummary.message(summary)
|
|
end
|
|
|
|
return HighlightSummary
|