Initial commit
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
local DocSettings = require("docsettings")
|
||||
local logger = require("logger")
|
||||
local lfs_ok, lfs = pcall(require, "lfs")
|
||||
local Reader = {}
|
||||
|
||||
local function live_ui()
|
||||
local ok, ReaderUI = pcall(require, "apps/reader/readerui")
|
||||
if ok and ReaderUI then return ReaderUI.instance end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function sidecar_for(file_path)
|
||||
if not file_path then return nil end
|
||||
return DocSettings:findSidecarFile(file_path)
|
||||
end
|
||||
|
||||
local function sidecar_signature(sidecar)
|
||||
if not lfs_ok or not sidecar then return nil end
|
||||
local attrs = lfs.attributes(sidecar)
|
||||
if not attrs then return nil end
|
||||
return tostring(attrs.modification or 0) .. ":" .. tostring(attrs.size or 0)
|
||||
end
|
||||
|
||||
local function clean_annotation(annotation, total_pages, legacy)
|
||||
if type(annotation) ~= "table" then return nil end
|
||||
local text = annotation.text
|
||||
if (not text or text == "") and legacy then text = annotation.notes end
|
||||
local kind = "bookmark"
|
||||
if annotation.note and annotation.note ~= "" then kind = "note"
|
||||
elseif text and text ~= "" then kind = "highlight"
|
||||
elseif annotation.drawer or annotation.highlighted then kind = "highlight" end
|
||||
return {
|
||||
datetime = annotation.datetime,
|
||||
datetime_updated = annotation.datetime_updated,
|
||||
drawer = annotation.drawer,
|
||||
color = annotation.color,
|
||||
text = text,
|
||||
note = annotation.note,
|
||||
chapter = annotation.chapter,
|
||||
pageno = annotation.pageno,
|
||||
page = ((type(annotation.page) == "string" or type(annotation.page) == "number") and annotation.page) or nil,
|
||||
total_pages = total_pages,
|
||||
annotation_type = kind,
|
||||
}
|
||||
end
|
||||
|
||||
local function read_set(sidecar)
|
||||
if not sidecar then return nil end
|
||||
local settings = DocSettings.openSettingsFile(sidecar)
|
||||
local md5 = settings:readSetting("partial_md5_checksum")
|
||||
if not md5 or md5 == "" then return nil end
|
||||
local total_pages = settings:readSetting("doc_pages")
|
||||
local props = settings:readSetting("doc_props") or {}
|
||||
local summary = settings:readSetting("summary") or {}
|
||||
local annotations = settings:readSetting("annotations")
|
||||
local legacy = false
|
||||
if type(annotations) ~= "table" then
|
||||
annotations = settings:readSetting("bookmarks")
|
||||
legacy = true
|
||||
end
|
||||
if type(annotations) ~= "table" then annotations = {} end
|
||||
local cleaned = {}
|
||||
for _, annotation in ipairs(annotations) do
|
||||
local item = clean_annotation(annotation, total_pages, legacy)
|
||||
if item then table.insert(cleaned, item) end
|
||||
end
|
||||
return { book_md5 = md5, identifiers = props.identifiers, reading_status = summary.status, reading_status_modified = summary.modified, annotations = cleaned }
|
||||
end
|
||||
|
||||
-- Scanning history is cheap; opening every sidecar is not. Persist each sidecar's
|
||||
-- mtime/size signature and only parse/transmit books whose sidecar changed.
|
||||
function Reader.changed(previous_versions)
|
||||
local ui = live_ui()
|
||||
if ui and ui.doc_settings then pcall(function() ui.doc_settings:flush() end) end
|
||||
previous_versions = type(previous_versions) == "table" and previous_versions or {}
|
||||
|
||||
local ok, ReadHistory = pcall(require, "readhistory")
|
||||
if not ok or not ReadHistory or type(ReadHistory.hist) ~= "table" then
|
||||
logger.warn("[kovi] Could not read KOReader history for annotation sync")
|
||||
return {}, previous_versions
|
||||
end
|
||||
|
||||
local sets, versions, seen_md5, present_md5, current_paths = {}, {}, {}, {}, {}
|
||||
local scanned, opened, unchanged, errors = 0, 0, 0, 0
|
||||
for _, entry in ipairs(ReadHistory.hist) do
|
||||
if entry.file and not entry.dim then
|
||||
scanned = scanned + 1
|
||||
local sidecar = sidecar_for(entry.file)
|
||||
if sidecar then
|
||||
current_paths[entry.file] = true
|
||||
local signature = sidecar_signature(sidecar)
|
||||
local previous = previous_versions[entry.file]
|
||||
local previous_signature = type(previous) == "table" and previous.signature or previous
|
||||
local previous_md5 = type(previous) == "table" and previous.book_md5 or nil
|
||||
if signature and previous_signature == signature and previous_md5 then
|
||||
versions[entry.file] = { signature = signature, book_md5 = previous_md5 }
|
||||
present_md5[previous_md5] = true
|
||||
unchanged = unchanged + 1
|
||||
else
|
||||
local success, set = pcall(read_set, sidecar)
|
||||
if success and set then
|
||||
present_md5[set.book_md5] = true
|
||||
if not seen_md5[set.book_md5] then
|
||||
seen_md5[set.book_md5] = true
|
||||
table.insert(sets, set)
|
||||
end
|
||||
if signature then versions[entry.file] = { signature = signature, book_md5 = set.book_md5 } end
|
||||
opened = opened + 1
|
||||
elseif not success then
|
||||
errors = errors + 1
|
||||
logger.warn("[kovi] Failed reading annotation sidecar", entry.file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- If a previously synced sidecar disappeared, send an empty set once so the
|
||||
-- server can remove annotations that no longer exist on this reader.
|
||||
for file_path, previous in pairs(previous_versions) do
|
||||
if not current_paths[file_path] and type(previous) == "table" and previous.book_md5 and not present_md5[previous.book_md5] and not seen_md5[previous.book_md5] then
|
||||
table.insert(sets, { book_md5 = previous.book_md5, annotations = {} })
|
||||
seen_md5[previous.book_md5] = true
|
||||
end
|
||||
end
|
||||
|
||||
logger.info("[kovi] Incremental annotation scan", scanned, "history entries,", opened, "changed sidecars,", unchanged, "unchanged,", errors, "errors")
|
||||
return sets, versions
|
||||
end
|
||||
|
||||
function Reader.all()
|
||||
return Reader.changed({})
|
||||
end
|
||||
|
||||
return Reader
|
||||
Reference in New Issue
Block a user