diff --git a/frontend/apps/reader/modules/readerannotation.lua b/frontend/apps/reader/modules/readerannotation.lua index 18c377abe..b497b41b4 100644 --- a/frontend/apps/reader/modules/readerannotation.lua +++ b/frontend/apps/reader/modules/readerannotation.lua @@ -1,5 +1,10 @@ +local DocSettings = require("docsettings") +local LuaSettings = require("luasettings") +local Notification = require("ui/widget/notification") local WidgetContainer = require("ui/widget/container/widgetcontainer") +local lfs = require("libs/libkoreader-lfs") local logger = require("logger") +local random = require("random") local _ = require("gettext") local T = require("ffi/util").template @@ -106,11 +111,11 @@ end function ReaderAnnotation:onReadSettings(config) local annotations = config:readSetting("annotations") + -- KOHighlights may set this key when it has merged annotations from different sources: + -- we want to make sure they are updated and sorted + local needs_update = annotations and config:isTrue("annotations_externally_modified") + local needs_sort -- if incompatible annotations were built of old highlights/bookmarks if annotations then - -- KOHighlights may set this key when it has merged annotations from different sources: - -- we want to make sure they are updated and sorted - local needs_update = config:isTrue("annotations_externally_modified") - local needs_sort -- if incompatible annotations were built of old highlights/bookmarks -- Annotation formats in crengine and mupdf are incompatible. local has_annotations = #annotations > 0 local annotations_type = has_annotations and type(annotations[1].page) @@ -131,12 +136,6 @@ function ReaderAnnotation:onReadSettings(config) needs_sort = true end self.annotations = annotations - if needs_update or needs_sort then - self.ui:registerPostReaderReadyCallback(function() - self:updateAnnotations(needs_update, needs_sort) - end) - config:delSetting("annotations_externally_modified") - end else -- first run if self.ui.rolling then self.ui:registerPostInitCallback(function() @@ -146,6 +145,15 @@ function ReaderAnnotation:onReadSettings(config) self:migrateToAnnotations(config) end end + if self:importAnnotations() then + needs_update = true + end + if needs_update or needs_sort then + self.ui:registerPostReaderReadyCallback(function() + self:updateAnnotations(needs_update, needs_sort) + end) + config:delSetting("annotations_externally_modified") + end end function ReaderAnnotation:migrateToAnnotations(config) @@ -240,6 +248,90 @@ end function ReaderAnnotation:onSaveSettings() self:updatePageNumbers() self.ui.doc_settings:saveSetting("annotations", self.annotations) + self:onExportAnnotations(true) +end + +function ReaderAnnotation:getExportAnnotationsFilepath() + local dir = G_reader_settings:readSetting("annotations_export_folder") or DocSettings:getSidecarDir(self.document.file) + local filename = self.document.file:gsub(".*/", "") + return dir .. "/" .. filename .. ".annotations.lua" +end + +function ReaderAnnotation:onExportAnnotations(on_closing) + local do_export = not on_closing or G_reader_settings:isTrue("annotations_export_on_closing") + if do_export and self:hasAnnotations() then + local file = self:getExportAnnotationsFilepath() + local anno = LuaSettings:open(file) + local device_id = G_reader_settings:readSetting("device_id", random.uuid()) + anno:saveSetting("device_id", device_id) + anno:saveSetting("datetime", os.date("%Y-%m-%d %H:%M:%S")) + anno:saveSetting("paging", self.ui.paging and true) + anno:saveSetting("annotations", self.annotations) + anno:flush() + os.remove(file .. ".old") + if not on_closing then + Notification:notify(_"Annotations exported") + end + end +end + +function ReaderAnnotation:importAnnotations() + local file = self:getExportAnnotationsFilepath() + if lfs.attributes(file, "mode") ~= "file" then return end -- no import file + local anno = LuaSettings:open(file) + if anno:readSetting("device_id") == G_reader_settings:readSetting("device_id") then return end -- same device + local new_annotations = anno:readSetting("annotations") + if (self.ui.paging and true) ~= anno:readSetting("paging") then return end -- incompatible annotations type + local new_datetime = anno:readSetting("datetime") + os.remove(file) + if #self.annotations == 0 then + self.annotations = new_annotations + return true + end + local doesMatch + if self.ui.rolling then + doesMatch = function(a, b) + if (not a.drawer) ~= (not b.drawer) or a.page ~= b.page or a.pos1 ~= b.pos1 then + return false + end + return true + end + else + doesMatch = function(a, b) + if (not a.drawer) ~= (not b.drawer) or a.page ~= b.page + or (a.pos0 and (a.pos0.x ~= b.pos0.x or a.pos1.x ~= b.pos1.x + or a.pos0.y ~= b.pos0.y or a.pos1.y ~= b.pos1.y)) then + return false + end + return true + end + end + for i = #self.annotations, 1, -1 do + local item = self.annotations[i] + local item_datetime = item.datetime_updated or item.datetime + local found + for j = #new_annotations, 1, -1 do + local new_item = new_annotations[j] + if doesMatch(item, new_item) then + if item_datetime < (new_item.datetime_updated or new_item.datetime) then + table.remove(self.annotations, i) -- new is newer, replace old with it + else + table.remove(new_annotations, j) -- old is newer, keep it + end + found = true + break + end + end + if not found then + if item_datetime < new_datetime then + table.remove(self.annotations, i) -- export is newer, remove old + end + end + end + if #new_annotations > 0 then + table.move(new_annotations, 1, #new_annotations, #self.annotations + 1, self.annotations) + end + return true end -- items handling diff --git a/frontend/apps/reader/modules/readerbookmark.lua b/frontend/apps/reader/modules/readerbookmark.lua index 02abaf95a..31f9ae58e 100644 --- a/frontend/apps/reader/modules/readerbookmark.lua +++ b/frontend/apps/reader/modules/readerbookmark.lua @@ -5,6 +5,7 @@ local CenterContainer = require("ui/widget/container/centercontainer") local CheckButton = require("ui/widget/checkbutton") local ConfirmBox = require("ui/widget/confirmbox") local Device = require("device") +local DocSettings = require("docsettings") local Event = require("ui/event") local Geom = require("ui/geometry") local GestureRange = require("ui/gesturerange") @@ -17,6 +18,7 @@ local SpinWidget = require("ui/widget/spinwidget") local TextViewer = require("ui/widget/textviewer") local UIManager = require("ui/uimanager") local Utf8Proc = require("ffi/utf8proc") +local filemanagerutil = require("apps/filemanager/filemanagerutil") local util = require("util") local _ = require("gettext") local N_ = _.ngettext @@ -239,6 +241,37 @@ function ReaderBookmark:addToMainMenu(menu_items) end, }, }, + separator = true, + }, + { + text = _("Export annotations on book closing"), + checked_func = function() + return G_reader_settings:isTrue("annotations_export_on_closing") + end, + callback = function() + G_reader_settings:flipNilOrFalse("annotations_export_on_closing") + end, + }, + { + text_func = function() + return T(_("Export / import folder: %1"), + G_reader_settings:readSetting("annotations_export_folder") or _("book metadata folder")) + end, + keep_menu_open = true, + callback = function(touchmenu_instance) + local title_header = _("Current annotations export folder:") + local default_path = DocSettings:getSidecarDir(self.ui.document.file) + local current_path = G_reader_settings:readSetting("annotations_export_folder") or default_path + local caller_callback = function(path) + if path == default_path then + G_reader_settings:delSetting("annotations_export_folder") + else + G_reader_settings:saveSetting("annotations_export_folder", path) + end + touchmenu_instance:updateItems() + end + filemanagerutil.showChooseDialog(title_header, caller_callback, current_path, default_path) + end, }, }, } @@ -901,6 +934,16 @@ function ReaderBookmark:onShowBookmark() }, }) table.insert(buttons, {}) -- separator + table.insert(buttons, { + { + text = _("Export annotations"), + callback = function() + UIManager:close(bm_dialog) + bookmark.ui.annotation:onExportAnnotations() + end, + }, + }) + table.insert(buttons, {}) -- separator table.insert(buttons, { { text = _("Current page"), diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua index 6059418f3..65c191a0a 100644 --- a/frontend/dispatcher.lua +++ b/frontend/dispatcher.lua @@ -204,6 +204,7 @@ local settingsList = { ---- flush_settings = {category="none", event="FlushSettings", arg=true, title=_("Save book metadata"), reader=true, separator=true}, ---- + export_annotations = {category="none", event="ExportAnnotations", title=_("Export annotations"), reader=true}, -- Reflowable documents set_font = {category="string", event="SetFont", title=_("Font face"), rolling=true, args_func=require("fontlist").getFontArgFunc,}, @@ -438,6 +439,7 @@ local dispatcher_menu_order = { ---- "flush_settings", ---- + "export_annotations", -- Reflowable documents "set_font",