diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 274a2473a..e4c284666 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -33,16 +33,16 @@ local ReadHistory = require("readhistory") local Screenshoter = require("ui/widget/screenshoter") local TitleBar = require("ui/widget/titlebar") local UIManager = require("ui/uimanager") +local ffiUtil = require("ffi/util") local filemanagerutil = require("apps/filemanager/filemanagerutil") local lfs = require("libs/libkoreader-lfs") local logger = require("logger") -local BaseUtil = require("ffi/util") local util = require("util") local _ = require("gettext") local C_ = _.pgettext local N_ = _.ngettext local Screen = Device.screen -local T = BaseUtil.template +local T = ffiUtil.template local FileManager = InputContainer:extend{ title = _("KOReader"), @@ -106,14 +106,15 @@ function FileManager:onSetDimensions(dimen) end function FileManager:updateTitleBarPath(path) - path = path or self.file_chooser.path local text = BD.directory(filemanagerutil.abbreviate(path)) - if FileManagerShortcuts:hasFolderShortcut(path) then + if self.folder_shortcuts:hasFolderShortcut(path) then text = "☆ " .. text end self.title_bar:setSubTitle(text) end +FileManager.onPathChanged = FileManager.updateTitleBarPath + function FileManager:setupLayout() self.show_parent = self.show_parent or self self.title_bar = TitleBar:new{ @@ -135,7 +136,6 @@ function FileManager:setupLayout() right_icon_tap_callback = function() self:onShowPlusMenu() end, right_icon_hold_callback = false, -- propagate long-press to dispatcher } - self:updateTitleBarPath(self.root_path) local file_chooser = FileChooser:new{ path = self.root_path, @@ -158,11 +158,6 @@ function FileManager:setupLayout() local file_manager = self - function file_chooser:onPathChanged(path) - file_manager:updateTitleBarPath(path) - return true - end - function file_chooser:onFileSelect(item) if file_manager.selected_files then -- toggle selection item.dim = not item.dim and true or nil @@ -262,7 +257,7 @@ function FileManager:setupLayout() self.book_props = nil -- in 'self' to provide access to it in CoverBrowser local has_provider = DocumentRegistry:hasProvider(file) local has_sidecar = DocSettings:hasSidecarFile(file) - local doc_settings_or_file + local doc_settings_or_file = file if has_provider or has_sidecar then self.book_props = file_manager.coverbrowser and file_manager.coverbrowser:getBookInfo(file) if has_sidecar then @@ -272,8 +267,6 @@ function FileManager:setupLayout() self.book_props = FileManagerBookInfo.extendProps(props, file) self.book_props.has_cover = true -- to enable "Book cover" button, we do not know if cover exists end - else - doc_settings_or_file = file end table.insert(buttons, filemanagerutil.genStatusButtonsRow(doc_settings_or_file, close_dialog_refresh_callback)) table.insert(buttons, {}) -- separator @@ -315,7 +308,7 @@ function FileManager:setupLayout() }) end else -- folder - local folder = BaseUtil.realpath(file) + local folder = ffiUtil.realpath(file) table.insert(buttons, { { text = _("Set as HOME folder"), @@ -431,6 +424,7 @@ function FileManager:init() self:initGesListener() self:handleEvent(Event:new("SetDimensions", self.dimen)) + self:handleEvent(Event:new("PathChanged", self.file_chooser.path)) if FileManager.instance == nil then logger.dbg("Spinning up new FileManager instance", tostring(self)) @@ -726,7 +720,7 @@ function FileManager:reinit(path, focused_file) UIManager:flushSettings() self.dimen = Screen:getSize() -- backup the root path and path items - self.root_path = BaseUtil.realpath(path or self.file_chooser.path) + self.root_path = ffiUtil.realpath(path or self.file_chooser.path) local path_items_backup = {} for k, v in pairs(self.file_chooser.path_items) do path_items_backup[k] = v @@ -826,7 +820,7 @@ function FileManager:openRandomFile(dir) local random_file = filemanagerutil.getRandomFile(dir, match_func) if random_file then UIManager:show(MultiConfirmBox:new{ - text = T(_("Do you want to open %1?"), BD.filename(BaseUtil.basename(random_file))), + text = T(_("Do you want to open %1?"), BD.filename(ffiUtil.basename(random_file))), choice1_text = _("Open"), choice1_callback = function() local ReaderUI = require("apps/reader/readerui") @@ -856,11 +850,11 @@ function FileManager:cutFile(file) end function FileManager:pasteFileFromClipboard(file) - local orig_file = BaseUtil.realpath(self.clipboard) - local orig_name = BaseUtil.basename(orig_file) - local dest_path = BaseUtil.realpath(file or self.file_chooser.path) + local orig_file = ffiUtil.realpath(self.clipboard) + local orig_name = ffiUtil.basename(orig_file) + local dest_path = ffiUtil.realpath(file or self.file_chooser.path) dest_path = isFile(dest_path) and dest_path:match("(.*/)") or dest_path - local dest_file = BaseUtil.joinPath(dest_path, orig_name) + local dest_file = ffiUtil.joinPath(dest_path, orig_name) if orig_file == dest_file or orig_file == dest_path then -- do not paste to itself self.clipboard = nil return @@ -951,12 +945,12 @@ function FileManager:showCopyMoveSelectedFilesDialog(close_callback) end function FileManager:pasteSelectedFiles(overwrite) - local dest_path = BaseUtil.realpath(self.file_chooser.path) + local dest_path = ffiUtil.realpath(self.file_chooser.path) local ok_files = {} for orig_file in pairs(self.selected_files) do - local orig_name = BaseUtil.basename(orig_file) - local dest_file = BaseUtil.joinPath(dest_path, orig_name) - if BaseUtil.realpath(orig_file) == dest_file then -- do not paste to itself + local orig_name = ffiUtil.basename(orig_file) + local dest_file = ffiUtil.joinPath(dest_path, orig_name) + if ffiUtil.realpath(orig_file) == dest_file then -- do not paste to itself self.selected_files[orig_file] = nil else local ok @@ -1046,7 +1040,7 @@ function FileManager:createFolder() end function FileManager:showDeleteFileDialog(filepath, post_delete_callback, pre_delete_callback) - local file = BaseUtil.realpath(filepath) + local file = ffiUtil.realpath(filepath) if file == nil then UIManager:show(InfoMessage:new{ text = T(_("File not found:\n%1"), BD.filepath(filepath)), @@ -1083,7 +1077,7 @@ function FileManager:deleteFile(file, is_file) return true end else - local ok = BaseUtil.purgeDir(file) + local ok = ffiUtil.purgeDir(file) if ok then ReadHistory:folderDeleted(file) -- will delete sdr ReadCollection:removeItemsByPath(file) @@ -1099,7 +1093,7 @@ end function FileManager:deleteSelectedFiles() local ok_files = {} for orig_file in pairs(self.selected_files) do - local file_abs_path = BaseUtil.realpath(orig_file) + local file_abs_path = ffiUtil.realpath(orig_file) local ok = file_abs_path and os.remove(file_abs_path) if ok then DocSettings.updateLocation(file_abs_path) -- delete sdr @@ -1129,7 +1123,7 @@ function FileManager:showRenameFileDialog(file, is_file) local dialog dialog = InputDialog:new{ title = is_file and _("Rename file") or _("Rename folder"), - input = BaseUtil.basename(file), + input = ffiUtil.basename(file), buttons = {{ { text = _("Cancel"), @@ -1155,8 +1149,8 @@ function FileManager:showRenameFileDialog(file, is_file) end function FileManager:renameFile(file, basename, is_file) - if BaseUtil.basename(file) == basename then return end - local dest = BaseUtil.joinPath(BaseUtil.dirname(file), basename) + if ffiUtil.basename(file) == basename then return end + local dest = ffiUtil.joinPath(ffiUtil.dirname(file), basename) local function doRenameFile() if self:moveFile(file, dest) then @@ -1220,7 +1214,7 @@ function FileManager:showFiles(path, focused_file, selected_files) FileManager.instance:onClose() end - path = BaseUtil.realpath(path or G_reader_settings:readSetting("lastdir") or filemanagerutil.getDefaultDir()) + path = ffiUtil.realpath(path or G_reader_settings:readSetting("lastdir") or filemanagerutil.getDefaultDir()) G_reader_settings:saveSetting("lastdir", path) self:setRotationMode() local file_manager = FileManager:new{ @@ -1236,19 +1230,19 @@ end --- A shortcut to execute mv. -- @treturn boolean result of mv command function FileManager:moveFile(from, to) - return BaseUtil.execute(self.mv_bin, from, to) == 0 + return ffiUtil.execute(self.mv_bin, from, to) == 0 end --- A shortcut to execute cp. -- @treturn boolean result of cp command function FileManager:copyFileFromTo(from, to) - return BaseUtil.execute(self.cp_bin, from, to) == 0 + return ffiUtil.execute(self.cp_bin, from, to) == 0 end --- A shortcut to execute cp recursively. -- @treturn boolean result of cp command function FileManager:copyRecursive(from, to) - return BaseUtil.execute(self.cp_bin, "-r", from, to ) == 0 + return ffiUtil.execute(self.cp_bin, "-r", from, to ) == 0 end function FileManager:onHome() @@ -1341,9 +1335,9 @@ function FileManager:showSelectedFilesList() local a_path, a_name = util.splitFilePathName(a.text) local b_path, b_name = util.splitFilePathName(b.text) if a_path == b_path then - return BaseUtil.strcoll(a_name, b_name) + return ffiUtil.strcoll(a_name, b_name) end - return BaseUtil.strcoll(a_path, b_path) + return ffiUtil.strcoll(a_path, b_path) end table.sort(selected_files, sorting) diff --git a/frontend/apps/filemanager/filemanagercollection.lua b/frontend/apps/filemanager/filemanagercollection.lua index fcba7086a..814582579 100644 --- a/frontend/apps/filemanager/filemanagercollection.lua +++ b/frontend/apps/filemanager/filemanagercollection.lua @@ -3,7 +3,6 @@ local ButtonDialog = require("ui/widget/buttondialog") local ConfirmBox = require("ui/widget/confirmbox") local Device = require("device") local DocSettings = require("docsettings") -local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") local InfoMessage = require("ui/widget/infomessage") local InputDialog = require("ui/widget/inputdialog") local Menu = require("ui/widget/menu") @@ -70,12 +69,17 @@ function FileManagerCollection:onShowColl(collection_name) title_bar_fm_style = true, title_bar_left_icon = "appbar.menu", onLeftButtonTap = function() self:showCollDialog() end, + onReturn = function() + self.coll_menu.close_callback() + self:onShowCollList() + end, onMenuChoice = self.onMenuChoice, onMenuHold = self.onMenuHold, _manager = self, _recreate_func = function() self:onShowColl(collection_name) end, collection_name = collection_name, } + table.insert(self.coll_menu.paths, true) -- enable onReturn button self.coll_menu.close_callback = function() self:refreshFileManager() UIManager:close(self.coll_menu) @@ -146,7 +150,7 @@ function FileManagerCollection:onMenuHold(item) doc_settings_or_file = DocSettings:open(file) if not self.book_props then local props = doc_settings_or_file:readSetting("doc_props") - self.book_props = FileManagerBookInfo.extendProps(props, file) + self.book_props = self.ui.bookinfo.extendProps(props, file) self.book_props.has_cover = true end else @@ -285,14 +289,18 @@ end -- collection list -function FileManagerCollection:onShowCollList(file_or_files, caller_callback, no_dialog) - self.selected_colections = nil - if file_or_files then -- select mode - if type(file_or_files) == "string" then -- checkmark collections containing the file - self.selected_colections = ReadCollection:getCollectionsWithFile(file_or_files) - else -- do not checkmark any - self.selected_colections = {} +function FileManagerCollection:onShowCollList(file_or_selected_collections, caller_callback, no_dialog) + local title_bar_left_icon + if file_or_selected_collections ~= nil then -- select mode + title_bar_left_icon = "check" + if type(file_or_selected_collections) == "string" then -- checkmark collections containing the file + self.selected_collections = ReadCollection:getCollectionsWithFile(file_or_selected_collections) + else + self.selected_collections = util.tableDeepCopy(file_or_selected_collections) end + else + title_bar_left_icon = "appbar.menu" + self.selected_collections = nil end self.coll_list = Menu:new{ subtitle = "", @@ -300,15 +308,15 @@ function FileManagerCollection:onShowCollList(file_or_files, caller_callback, no is_borderless = true, is_popout = false, title_bar_fm_style = true, - title_bar_left_icon = file_or_files and "check" or "appbar.menu", + title_bar_left_icon = title_bar_left_icon, onLeftButtonTap = function() self:showCollListDialog(caller_callback, no_dialog) end, onMenuChoice = self.onCollListChoice, onMenuHold = self.onCollListHold, _manager = self, - _recreate_func = function() self:onShowCollList(file_or_files, caller_callback, no_dialog) end, + _recreate_func = function() self:onShowCollList(file_or_selected_collections, caller_callback, no_dialog) end, } self.coll_list.close_callback = function(force_close) - if force_close or self.selected_colections == nil then + if force_close or self.selected_collections == nil then self:refreshFileManager() UIManager:close(self.coll_list) self.coll_list = nil @@ -325,8 +333,8 @@ function FileManagerCollection:updateCollListItemTable(do_init, item_number) item_table = {} for name, coll in pairs(ReadCollection.coll) do local mandatory - if self.selected_colections then - mandatory = self.selected_colections[name] and self.checkmark or " " + if self.selected_collections then + mandatory = self.selected_collections[name] and self.checkmark or " " self.coll_list.items_mandatory_font_size = self.coll_list.font_size else mandatory = util.tableSize(coll) @@ -346,12 +354,12 @@ function FileManagerCollection:updateCollListItemTable(do_init, item_number) end local title = T(_("Collections (%1)"), #item_table) local subtitle - if self.selected_colections then - local selected_nb = util.tableSize(self.selected_colections) - subtitle = self.selected_colections and T(_("Selected: %1"), selected_nb) + if self.selected_collections then + local selected_nb = util.tableSize(self.selected_collections) + subtitle = self.selected_collections and T(_("Selected: %1"), selected_nb) if do_init and selected_nb > 0 then -- show first collection containing the long-pressed book for i, item in ipairs(item_table) do - if self.selected_colections[item.name] then + if self.selected_collections[item.name] then item_number = i break end @@ -362,13 +370,13 @@ function FileManagerCollection:updateCollListItemTable(do_init, item_number) end function FileManagerCollection:onCollListChoice(item) - if self._manager.selected_colections then + if self._manager.selected_collections then if item.mandatory == self._manager.checkmark then self.item_table[item.idx].mandatory = " " - self._manager.selected_colections[item.name] = nil + self._manager.selected_collections[item.name] = nil else self.item_table[item.idx].mandatory = self._manager.checkmark - self._manager.selected_colections[item.name] = true + self._manager.selected_collections[item.name] = true end self._manager:updateCollListItemTable() else @@ -378,7 +386,7 @@ end function FileManagerCollection:onCollListHold(item) if item.name == ReadCollection.default_collection_name -- Favorites non-editable - or self._manager.selected_colections then -- select mode + or self._manager.selected_collections then -- select mode return end @@ -412,7 +420,7 @@ end function FileManagerCollection:showCollListDialog(caller_callback, no_dialog) if no_dialog then - caller_callback() + caller_callback(self.selected_collections) self.coll_list.close_callback(true) return end @@ -427,7 +435,7 @@ function FileManagerCollection:showCollListDialog(caller_callback, no_dialog) end, }, } - if self.selected_colections then -- select mode + if self.selected_collections then -- select mode buttons = { new_collection_button, {}, -- separator @@ -436,8 +444,8 @@ function FileManagerCollection:showCollListDialog(caller_callback, no_dialog) text = _("Deselect all"), callback = function() UIManager:close(button_dialog) - for name in pairs(self.selected_colections) do - self.selected_colections[name] = nil + for name in pairs(self.selected_collections) do + self.selected_collections[name] = nil end self:updateCollListItemTable(true) end, @@ -447,7 +455,7 @@ function FileManagerCollection:showCollListDialog(caller_callback, no_dialog) callback = function() UIManager:close(button_dialog) for name in pairs(ReadCollection.coll) do - self.selected_colections[name] = true + self.selected_collections[name] = true end self:updateCollListItemTable(true) end, @@ -458,7 +466,7 @@ function FileManagerCollection:showCollListDialog(caller_callback, no_dialog) text = _("Apply selection"), callback = function() UIManager:close(button_dialog) - caller_callback() + caller_callback(self.selected_collections) self.coll_list.close_callback(true) end, }, @@ -523,8 +531,8 @@ function FileManagerCollection:addCollection() local editCallback = function(name) ReadCollection:addCollection(name) local mandatory - if self.selected_colections then - self.selected_colections[name] = true + if self.selected_collections then + self.selected_collections[name] = true mandatory = self.checkmark else mandatory = 0 @@ -579,6 +587,7 @@ end -- external function FileManagerCollection:genAddToCollectionButton(file_or_files, caller_pre_callback, caller_post_callback, button_disabled) + local is_single_file = type(file_or_files) == "string" return { text = _("Collections…"), enabled = not button_disabled, @@ -586,17 +595,18 @@ function FileManagerCollection:genAddToCollectionButton(file_or_files, caller_pr if caller_pre_callback then caller_pre_callback() end - local caller_callback = function() - if type(file_or_files) == "string" then - ReadCollection:addRemoveItemMultiple(file_or_files, self.selected_colections) + local caller_callback = function(selected_collections) + if is_single_file then + ReadCollection:addRemoveItemMultiple(file_or_files, selected_collections) else -- selected files - ReadCollection:addItemsMultiple(file_or_files, self.selected_colections) + ReadCollection:addItemsMultiple(file_or_files, selected_collections) end if caller_post_callback then caller_post_callback() end end - self:onShowCollList(file_or_files, caller_callback) + -- if selected files, do not checkmark any collection on start + self:onShowCollList(is_single_file and file_or_files or {}, caller_callback) end, } end diff --git a/frontend/apps/filemanager/filemanagerhistory.lua b/frontend/apps/filemanager/filemanagerhistory.lua index 3af6346d6..cf4766ee9 100644 --- a/frontend/apps/filemanager/filemanagerhistory.lua +++ b/frontend/apps/filemanager/filemanagerhistory.lua @@ -3,7 +3,6 @@ local ButtonDialog = require("ui/widget/buttondialog") local CheckButton = require("ui/widget/checkbutton") local ConfirmBox = require("ui/widget/confirmbox") local DocSettings = require("docsettings") -local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") local InputDialog = require("ui/widget/inputdialog") local Menu = require("ui/widget/menu") local ReadCollection = require("readcollection") @@ -85,7 +84,7 @@ function FileManagerHistory:updateItemTable() local subtitle = "" if self.search_string then subtitle = T(_("Search results (%1)"), #item_table) - elseif self.selected_colections then + elseif self.selected_collections then subtitle = T(_("Filtered by collections (%1)"), #item_table) elseif self.filter ~= "all" then subtitle = T(_("Status: %1 (%2)"), filter_text[self.filter]:lower(), #item_table) @@ -109,8 +108,8 @@ function FileManagerHistory:isItemMatch(item) end end end - if self.selected_colections then - for name in pairs(self.selected_colections) do + if self.selected_collections then + for name in pairs(self.selected_collections) do if not ReadCollection:isFileInCollection(item.file, name) then return false end @@ -173,7 +172,7 @@ function FileManagerHistory:onMenuHold(item) doc_settings_or_file = DocSettings:open(file) if not self.book_props then local props = doc_settings_or_file:readSetting("doc_props") - self.book_props = FileManagerBookInfo.extendProps(props, file) + self.book_props = self.ui.bookinfo.extendProps(props, file) self.book_props.has_cover = true end else @@ -203,7 +202,7 @@ function FileManagerHistory:onMenuHold(item) UIManager:close(self.histfile_dialog) -- The item's idx field is tied to the current *view*, so we can only pass it as-is when there's no filtering *at all* involved. local index = item.idx - if self._manager.search_string or self._manager.selected_colections or self._manager.filter ~= "all" then + if self._manager.search_string or self._manager.selected_collections or self._manager.filter ~= "all" then index = nil end require("readhistory"):removeItem(item, index) @@ -252,7 +251,7 @@ function FileManagerHistory:onShowHist(search_info) self.case_sensitive = search_info.case_sensitive else self.search_string = nil - self.selected_colections = nil + self.selected_collections = nil end self.filter = G_reader_settings:readSetting("history_filter", "all") self.is_frozen = G_reader_settings:isTrue("history_freeze_finished_books") @@ -291,7 +290,7 @@ function FileManagerHistory:showHistDialog() self.filter = filter if filter == "all" then -- reset all filters self.search_string = nil - self.selected_colections = nil + self.selected_collections = nil end self:updateItemTable() end, @@ -312,11 +311,11 @@ function FileManagerHistory:showHistDialog() text = _("Filter by collections"), callback = function() UIManager:close(hist_dialog) - local caller_callback = function() - self.selected_colections = self.ui.collections.selected_colections + local caller_callback = function(selected_collections) + self.selected_collections = selected_collections self:updateItemTable() end - self.ui.collections:onShowCollList({}, caller_callback, true) -- do not select any, no dialog to apply + self.ui.collections:onShowCollList(self.selected_collections or {}, caller_callback, true) -- no dialog to apply end, }, }) diff --git a/frontend/ui/widget/filechooser.lua b/frontend/ui/widget/filechooser.lua index 31c6e7eb4..9f4be134d 100644 --- a/frontend/ui/widget/filechooser.lua +++ b/frontend/ui/widget/filechooser.lua @@ -3,6 +3,7 @@ local datetime = require("datetime") local Device = require("device") local DocSettings = require("docsettings") local DocumentRegistry = require("document/documentregistry") +local Event = require("ui/event") local FileManagerShortcuts = require("apps/filemanager/filemanagershortcuts") local filemanagerutil = require("apps/filemanager/filemanagerutil") local Menu = require("ui/widget/menu") @@ -505,11 +506,11 @@ function FileChooser:refreshPath() if self.focused_path then itemmatch = {path = self.focused_path} -- We use focused_path only once, but remember it - -- for CoverBrower to re-apply it on startup if needed + -- for CoverBrowser to re-apply it on startup if needed self.prev_focused_path = self.focused_path self.focused_path = nil end - local subtitle = BD.directory(filemanagerutil.abbreviate(self.path)) + local subtitle = self.filemanager == nil and BD.directory(filemanagerutil.abbreviate(self.path)) self:switchItemTable(nil, self:genItemTableFromPath(self.path), self.path_items[self.path], itemmatch, subtitle) end @@ -535,7 +536,9 @@ function FileChooser:changeToPath(path, focused_path) end self:refreshPath() - self:onPathChanged(path) + if self.filemanager then + self.filemanager:handleEvent(Event:new("PathChanged", path)) + end end function FileChooser:goHome() @@ -609,10 +612,6 @@ function FileChooser:onFileHold(item) return true end -function FileChooser:onPathChanged(path) - return true -end - -- Used in ReaderStatus:onOpenNextDocumentInFolder(). function FileChooser:getNextFile(curr_file) local show_finished = FileChooser.show_finished diff --git a/frontend/util.lua b/frontend/util.lua index 39d78d438..5707f0a2f 100644 --- a/frontend/util.lua +++ b/frontend/util.lua @@ -2,13 +2,13 @@ This module contains miscellaneous helper functions for the KOReader frontend. ]] -local BaseUtil = require("ffi/util") local Utf8Proc = require("ffi/utf8proc") +local ffiUtil = require("ffi/util") local lfs = require("libs/libkoreader-lfs") local md5 = require("ffi/sha2").md5 local _ = require("gettext") local C_ = _.pgettext -local T = BaseUtil.template +local T = ffiUtil.template local lshift = bit.lshift local rshift = bit.rshift @@ -489,7 +489,7 @@ function util.splitToChars(text) local hi_surrogate local hi_surrogate_uchar for uchar in text:gmatch(util.UTF8_CHAR_PATTERN) do - charcode = BaseUtil.utf8charcode(uchar) + charcode = ffiUtil.utf8charcode(uchar) -- (not sure why we need this prevcharcode check; we could get -- charcode=nil with invalid UTF-8, but should we then really -- ignore the following charcode ?) @@ -530,7 +530,7 @@ function util.isCJKChar(c) if #c < 3 then return false end - local code = BaseUtil.utf8charcode(c) + local code = ffiUtil.utf8charcode(c) -- The weird bracketing is intentional -- we use the lowest possible -- codepoint as a shortcut so if the codepoint is below U+1100 we -- immediately return false. @@ -894,7 +894,7 @@ function util.removePath(path) return nil, "Encountered a component that isn't a directory" .. " (removing `" .. component .. "` for `" .. path .. "`)" end - local parent = BaseUtil.dirname(component) + local parent = ffiUtil.dirname(component) component = parent until parent == "." or parent == "/" return true, nil @@ -988,7 +988,7 @@ function util.getSafeFilename(str, path, limit, limit_ext) limit_ext = limit_ext or 10 -- Always assume the worst on Android (#7837) - if path and not BaseUtil.isAndroid() then + if path and not ffiUtil.isAndroid() then local file_system = util.getFilesystemType(path) if file_system ~= "vfat" and file_system ~= "fuse.fsp" then replaceFunc = replaceSlashChar @@ -1140,11 +1140,11 @@ function util.writeToFile(data, filepath, force_flush, lua_dofile_ready, directo end file:write(data) if force_flush then - BaseUtil.fsyncOpenedFile(file) + ffiUtil.fsyncOpenedFile(file) end file:close() if directory_updated then - BaseUtil.fsyncDirectory(filepath) + ffiUtil.fsyncDirectory(filepath) end return true end @@ -1512,6 +1512,7 @@ end -- @treturn table Text char list -- @treturn table Search string char list function util.stringSearch(txt, str, case_sensitive, start_pos) + start_pos = start_pos or 1 if not case_sensitive then str = Utf8Proc.lowercase(util.fixUtf8(str, "?")) end diff --git a/plugins/profiles.koplugin/main.lua b/plugins/profiles.koplugin/main.lua index 3fde0c35c..534d87b06 100644 --- a/plugins/profiles.koplugin/main.lua +++ b/plugins/profiles.koplugin/main.lua @@ -161,10 +161,11 @@ function Profiles:getSubMenuItems() }, self:genAutoExecMenuItem(_("on KOReader start"), "Start", k), self:genAutoExecMenuItem(_("on wake-up"), "Resume", k), - self:genAutoExecMenuItem(_("on rotation"), "SetRotationMode", k, true), + self:genAutoExecMenuItem(_("on rotation"), "SetRotationMode", k), + self:genAutoExecMenuItem(_("on showing folder"), "PathChanged", k, true), -- separator self:genAutoExecMenuItem(_("on book opening"), "ReaderReadyAll", k), - self:genAutoExecMenuItem(_("on book closing"), "CloseDocument", k), + self:genAutoExecMenuItem(_("on book closing"), "CloseDocumentAll", k), } end, hold_callback = function(touchmenu_instance) @@ -464,8 +465,10 @@ end function Profiles:genAutoExecMenuItem(text, event, profile_name, separator) if event == "SetRotationMode" then return self:genAutoExecSetRotationModeMenuItem(text, event, profile_name, separator) - elseif event == "ReaderReadyAll" then - return self:genAutoExecReaderReadyAllMenuItem(text, event, profile_name, separator) + elseif event == "PathChanged" then + return self:genAutoExecPathChangedMenuItem(text, event, profile_name, separator) + elseif event == "ReaderReadyAll" or event == "CloseDocumentAll" then + return self:genAutoExecDocConditionalMenuItem(text, event, profile_name, separator) end return { text = text, @@ -477,8 +480,9 @@ function Profiles:genAutoExecMenuItem(text, event, profile_name, separator) util.tableRemoveValue(self.autoexec, event, profile_name) else util.tableSetValue(self.autoexec, true, event, profile_name) - if event == "ReaderReady" then -- "always" is checked, clear all conditional triggers - util.tableRemoveValue(self.autoexec, "ReaderReadyAll", profile_name) + if event == "ReaderReady" or event == "CloseDocument" then + -- "always" is checked, clear all conditional triggers + util.tableRemoveValue(self.autoexec, event .. "All", profile_name) end end end, @@ -520,8 +524,84 @@ function Profiles:genAutoExecSetRotationModeMenuItem(text, event, profile_name, } end -function Profiles:genAutoExecReaderReadyAllMenuItem(text, event, profile_name, separator) - local event_always = "ReaderReady" +function Profiles:genAutoExecPathChangedMenuItem(text, event, profile_name, separator) + return { + text = text, + checked_func = function() + return util.tableGetValue(self.autoexec, event, profile_name) and true + end, + sub_item_table_func = function() + local conditions = { + { _("if folder path contains"), "has" }, + { _("if folder path does not contain"), "has_not" }, + } + local sub_item_table = {} + for i, mode in ipairs(conditions) do + sub_item_table[i] = { + text_func = function() + local txt = conditions[i][1] + local value = util.tableGetValue(self.autoexec, event, profile_name, conditions[i][2]) + return value and txt .. ": " .. value or txt + end, + checked_func = function() + return util.tableGetValue(self.autoexec, event, profile_name, conditions[i][2]) + end, + callback = function(touchmenu_instance) + local condition = conditions[i][2] + local dialog + local buttons = {{ + { + text = _("Current folder"), + callback = function() + local curr_path = self.ui.file_chooser and self.ui.file_chooser.path or self.ui:getLastDirFile() + dialog:addTextToInput(curr_path) + end, + }, + }} + table.insert(buttons, { + { + text = _("Cancel"), + id = "close", + callback = function() + UIManager:close(dialog) + end, + }, + { + text = _("Save"), + callback = function() + local txt = dialog:getInputText() + if txt == "" then + util.tableRemoveValue(self.autoexec, event, profile_name, condition) + else + util.tableSetValue(self.autoexec, txt, event, profile_name, condition) + end + UIManager:close(dialog) + touchmenu_instance:updateItems() + end, + }, + }) + dialog = InputDialog:new{ + title = _("Enter text contained in folder path"), + input = util.tableGetValue(self.autoexec, event, profile_name, condition), + buttons = buttons, + } + UIManager:show(dialog) + dialog:onShowKeyboard() + end, + } + end + return sub_item_table + end, + hold_callback = function(touchmenu_instance) + util.tableRemoveValue(self.autoexec, event, profile_name) + touchmenu_instance:updateItems() + end, + separator = separator, + } +end + +function Profiles:genAutoExecDocConditionalMenuItem(text, event, profile_name, separator) + local event_always = event:gsub("All", "") return { text = text, checked_func = function() @@ -532,6 +612,7 @@ function Profiles:genAutoExecReaderReadyAllMenuItem(text, event, profile_name, s { _("if device orientation is"), "orientation" }, { _("if book metadata contains"), "doc_props" }, { _("if book file path contains"), "filepath" }, + { _("if book is in collections"), "collections" }, } local sub_item_table = { self:genAutoExecMenuItem(_("always"), event_always, profile_name, true), @@ -706,6 +787,42 @@ function Profiles:genAutoExecReaderReadyAllMenuItem(text, event, profile_name, s touchmenu_instance:updateItems() end, }, + { + text_func = function() -- collections + local txt = conditions[4][1] + local collections = util.tableGetValue(self.autoexec, event, profile_name, conditions[4][2]) + if collections then + local collections_nb = util.tableSize(collections) + return txt .. ": " .. + (collections_nb == 1 and self.ui.collections:getCollectionTitle(next(collections)) + or "(" .. collections_nb .. ")") + end + return txt + end, + enabled_func = function() + return not util.tableGetValue(self.autoexec, event_always, profile_name) + end, + checked_func = function() + return util.tableGetValue(self.autoexec, event, profile_name, conditions[4][2]) and true + end, + callback = function(touchmenu_instance) + local condition = conditions[4][2] + local collections = util.tableGetValue(self.autoexec, event, profile_name, condition) + local caller_callback = function(selected_collections) + if next(selected_collections) == nil then + util.tableRemoveValue(self.autoexec, event, profile_name, condition) + else + util.tableSetValue(self.autoexec, selected_collections, event, profile_name, condition) + end + touchmenu_instance:updateItems() + end + self.ui.collections:onShowCollList(collections or {}, caller_callback, true) + end, + hold_callback = function(touchmenu_instance) + util.tableRemoveValue(self.autoexec, event, profile_name, conditions[4][2]) + touchmenu_instance:updateItems() + end, + }, } return sub_item_table end, @@ -729,16 +846,39 @@ function Profiles:onResume() -- global self:executeAutoExecEvent("Resume") end -function Profiles:onSetRotationMode(rotation) -- global +function Profiles:onSetRotationMode(mode) -- global local event = "SetRotationMode" - if self.autoexec[event] then - for profile_name, modes in pairs(self.autoexec[event]) do - if modes[rotation] then - if self.ui.config then -- close bottom menu to let Dispatcher execute profile - self.ui.config:onCloseConfigMenu() - end - self:executeAutoExec(profile_name) + if self.autoexec[event] == nil then return end + for profile_name, modes in pairs(self.autoexec[event]) do + if modes[mode] then + if self.ui.config then -- close bottom menu to let Dispatcher execute profile + self.ui.config:onCloseConfigMenu() end + self:executeAutoExec(profile_name) + end + end +end + +function Profiles:onPathChanged(path) -- global + local event = "PathChanged" + if self.autoexec[event] == nil then return end + local function is_match(txt, pattern) + for str in util.gsplit(pattern, ",") do -- comma separated patterns are allowed + if util.stringSearch(txt, str) ~= 0 then + return true + end + end + end + for profile_name, conditions in pairs(self.autoexec[event]) do + local do_execute + if conditions.has then + do_execute = is_match(path, conditions.has) + end + if do_execute == nil and conditions.has_not then + do_execute = not is_match(path, conditions.has_not) + end + if do_execute then + self:executeAutoExec(profile_name) end end end @@ -746,21 +886,21 @@ end function Profiles:onReaderReady() -- global if not self.ui.reloading then self:executeAutoExecEvent("ReaderReady") - self:executeAutoExecReaderReadyAll() + self:executeAutoExecDocConditional("ReaderReadyAll") end end function Profiles:onCloseDocument() -- global if not self.ui.reloading then self:executeAutoExecEvent("CloseDocument") + self:executeAutoExecDocConditional("CloseDocumentAll") end end function Profiles:executeAutoExecEvent(event) - if self.autoexec[event] then - for profile_name in pairs(self.autoexec[event]) do - self:executeAutoExec(profile_name) - end + if self.autoexec[event] == nil then return end + for profile_name in pairs(self.autoexec[event]) do + self:executeAutoExec(profile_name) end end @@ -786,25 +926,24 @@ function Profiles:executeAutoExec(profile_name) end end -function Profiles:executeAutoExecReaderReadyAll() - local event = "ReaderReadyAll" - local function is_match(text, pattern) - text = text:lower() - for str in util.gsplit(pattern, ",") do -- comma separated patterns are allowed - if text:find(str:lower()) then +function Profiles:executeAutoExecDocConditional(event) + if self.autoexec[event] == nil then return end + local function is_match(txt, pattern) + for str in util.gsplit(pattern, ",") do + if util.stringSearch(txt, str) ~= 0 then return true end end end - if self.autoexec[event] then - for profile_name, conditions in pairs(self.autoexec[event]) do - if self.data[profile_name] then - local do_execute - for condition, trigger in pairs(conditions) do - if condition == "orientation" then - local mode = Screen:getRotationMode() - do_execute = trigger[mode] - elseif condition == "doc_props" then + for profile_name, conditions in pairs(self.autoexec[event]) do + if self.data[profile_name] then + local do_execute + for condition, trigger in pairs(conditions) do + if condition == "orientation" then + local mode = Screen:getRotationMode() + do_execute = trigger[mode] + elseif condition == "doc_props" then + if self.ui.document then for prop_name, pattern in pairs(trigger) do local prop = self.ui.doc_props[prop_name == "title" and "display_title" or prop_name] do_execute = is_match(prop, pattern) @@ -812,17 +951,29 @@ function Profiles:executeAutoExecReaderReadyAll() break -- any prop match is enough end end - elseif condition == "filepath" then + end + elseif condition == "filepath" then + if self.ui.document then do_execute = is_match(self.ui.document.file, trigger) end - if do_execute then - break -- execute profile only once + elseif condition == "collections" then + if self.ui.document then + local ReadCollection = require("readcollection") + for collection_name in pairs(trigger) do + if ReadCollection:isFileInCollection(self.ui.document.file, collection_name) then + do_execute = true + break -- any collection is enough + end + end end end if do_execute then - self:executeAutoExec(profile_name) + break -- execute profile only once end end + if do_execute then + self:executeAutoExec(profile_name) + end end end end