diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 307db5127..6956222a0 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -182,13 +182,6 @@ function FileManager:setupLayout() return true end - local copyFile = function(file) self:copyFile(file) end - local pasteHere = function(file) self:pasteHere(file) end - local cutFile = function(file) self:cutFile(file) end - local deleteFile = function(file) self:deleteFile(file) end - local renameFile = function(file) self:renameFile(file) end - local setHome = function(path) self:setHome(path) end - function file_chooser:onFileHold(file) if file_manager.select_mode then file_manager:tapPlus() @@ -207,16 +200,16 @@ function FileManager:setupLayout() text = C_("File", "Copy"), enabled = is_not_parent_folder, callback = function() - copyFile(file) UIManager:close(self.file_dialog) + file_manager:copyFile(file) end, }, { text = C_("File", "Paste"), enabled = file_manager.clipboard and true or false, callback = function() - pasteHere(file) UIManager:close(self.file_dialog) + file_manager:pasteHere(file) end, }, { @@ -236,8 +229,8 @@ function FileManager:setupLayout() text = _("Cut"), enabled = is_not_parent_folder, callback = function() - cutFile(file) UIManager:close(self.file_dialog) + file_manager:cutFile(file) end, }, { @@ -245,16 +238,10 @@ function FileManager:setupLayout() enabled = is_not_parent_folder, callback = function() UIManager:close(self.file_dialog) - UIManager:show(ConfirmBox:new{ - text = is_file and T(_("Delete file?\n\n%1\n\nIf you delete a file, it is permanently lost."), BD.filepath(file)) or - T(_("Delete folder?\n\n%1\n\nIf you delete a folder, its content is permanently lost."), BD.filepath(file)), - ok_text = _("Delete"), - ok_callback = function() - deleteFile(file) - require("readhistory"):fileDeleted(file) - self:refreshPath() - end, - }) + local function post_delete_callback() + self:refreshPath() + end + file_manager:showDeleteFileDialog(file, post_delete_callback) end, }, { @@ -262,123 +249,54 @@ function FileManager:setupLayout() enabled = is_not_parent_folder, callback = function() UIManager:close(self.file_dialog) - file_manager.rename_dialog = InputDialog:new{ - title = is_file and _("Rename file") or _("Rename folder"), - input = BaseUtil.basename(file), - buttons = {{ - { - text = _("Cancel"), - id = "close", - enabled = true, - callback = function() - UIManager:close(file_manager.rename_dialog) - end, - }, - { - text = _("Rename"), - enabled = true, - callback = function() - if file_manager.rename_dialog:getInputText() ~= "" then - renameFile(file) - UIManager:close(file_manager.rename_dialog) - end - end, - }, - }}, - } - UIManager:show(file_manager.rename_dialog) - file_manager.rename_dialog:onShowKeyboard() + file_manager:showRenameFileDialog(file, is_file) end, } }, - -- a little hack to get visual functionality grouping - { - }, + {}, -- separator } - if is_file and Device:canExecuteScript(file) then - -- NOTE: We populate the empty separator, in order not to mess with the button reordering code in CoverMenu - table.insert(buttons[3], - { - -- @translators This is the script's programming language (e.g., shell or python) - text = T(_("Execute %1 script"), util.getScriptType(file)), - enabled = true, - callback = function() - UIManager:close(self.file_dialog) - local script_is_running_msg = InfoMessage:new{ - -- @translators %1 is the script's programming language (e.g., shell or python), %2 is the filename - text = T(_("Running %1 script %2…"), util.getScriptType(file), BD.filename(BaseUtil.basename(file))), - } - UIManager:show(script_is_running_msg) - UIManager:scheduleIn(0.5, function() - local rv - if Device:isAndroid() then - Device:setIgnoreInput(true) - rv = os.execute("sh " .. BaseUtil.realpath(file)) -- run by sh, because sdcard has no execute permissions - Device:setIgnoreInput(false) - else - rv = os.execute(BaseUtil.realpath(file)) - end - UIManager:close(script_is_running_msg) - if rv == 0 then - UIManager:show(InfoMessage:new{ - text = _("The script exited successfully."), - }) - else - --- @note: Lua 5.1 returns the raw return value from the os's system call. Counteract this madness. - UIManager:show(InfoMessage:new{ - text = T(_("The script returned a non-zero status code: %1!"), bit.rshift(rv, 8)), - icon = "notice-warning", - }) - end - end) - end, - } - ) - table.insert(buttons, {}) -- separator - end - if is_file then local function status_button_callback() - self:refreshPath() UIManager:close(self.file_dialog) + self:refreshPath() -- sidecar folder may be created/deleted + end + local has_provider = DocumentRegistry:getProviders(file) ~= nil + if has_provider or DocSettings:hasSidecarFile(file) then + table.insert(buttons, filemanagerutil.genStatusButtonsRow(file, status_button_callback)) + table.insert(buttons, {}) -- separator end - table.insert(buttons, filemanagerutil.genStatusButtonsRow(file, status_button_callback)) - table.insert(buttons, {}) -- separator table.insert(buttons, { - filemanagerutil.genResetSettingsButton(file, nil, status_button_callback), + filemanagerutil.genResetSettingsButton(file, status_button_callback), { text_func = function() - if ReadCollection:checkItemExist(file) then - return _("Remove from favorites") - else - return _("Add to favorites") - end + return ReadCollection:checkItemExist(file) + and _("Remove from favorites") or _("Add to favorites") end, - enabled = DocumentRegistry:getProviders(file) ~= nil, + enabled = has_provider, callback = function() + UIManager:close(self.file_dialog) if ReadCollection:checkItemExist(file) then ReadCollection:removeItem(file) else ReadCollection:addItem(file) end - UIManager:close(self.file_dialog) end, }, }) table.insert(buttons, { { text = _("Open with…"), - enabled = DocumentRegistry:getProviders(file) == nil or #(DocumentRegistry:getProviders(file)) > 1 or file_manager.texteditor, callback = function() UIManager:close(self.file_dialog) - local one_time_providers = {} - table.insert(one_time_providers, { - provider_name = _("Text viewer"), - callback = function() - file_manager:openTextViewer(file) - end, - }) + local one_time_providers = { + { + provider_name = _("Text viewer"), + callback = function() + file_manager:openTextViewer(file) + end, + }, + } if file_manager.texteditor then table.insert(one_time_providers, { provider_name = _("Text editor"), @@ -393,18 +311,24 @@ function FileManager:setupLayout() { text = _("Book information"), id = "book_information", -- used by covermenu - enabled = FileManagerBookInfo:isSupported(file), callback = function() - FileManagerBookInfo:show(file) UIManager:close(self.file_dialog) + FileManagerBookInfo:show(file) end, } }) + if Device:canExecuteScript(file) then + local function button_callback() + UIManager:close(self.file_dialog) + end + table.insert(buttons, { + filemanagerutil.genExecuteScriptButton(file, button_callback) + }) + end if FileManagerConverter:isSupported(file) then table.insert(buttons, { { text = _("Convert"), - enabled = true, callback = function() UIManager:close(self.file_dialog) FileManagerConverter:showConvertButtons(file, self) @@ -413,28 +337,21 @@ function FileManager:setupLayout() }) end end + if is_folder then - local realpath = BaseUtil.realpath(file) table.insert(buttons, { { text = _("Set as HOME folder"), callback = function() - setHome(realpath) UIManager:close(self.file_dialog) + file_manager:setHome(BaseUtil.realpath(file)) end } }) end - local title - if is_folder then - title = BD.directory(file:match("([^/]+)$")) - else - title = BD.filename(file:match("([^/]+)$")) - end - self.file_dialog = ButtonDialogTitle:new{ - title = title, + title = is_file and BD.filename(file:match("([^/]+)$")) or BD.directory(file:match("([^/]+)$")), title_align = "center", buttons = buttons, } @@ -607,9 +524,9 @@ function FileManager:tapPlus() { text = _("Select all files in folder"), callback = function() + UIManager:close(self.file_dialog) self.file_chooser:selectAllFilesInFolder() self:onRefresh() - UIManager:close(self.file_dialog) end, }, { @@ -620,13 +537,13 @@ function FileManager:tapPlus() text = _("Copy selected files to the current folder?"), ok_text = _("Copy"), ok_callback = function() + UIManager:close(self.file_dialog) self.cutfile = false for file in pairs(self.selected_files) do self.clipboard = file self:pasteHere(self.file_chooser.path) end self:onToggleSelectMode() - UIManager:close(self.file_dialog) end, }) end @@ -637,9 +554,9 @@ function FileManager:tapPlus() text = _("Deselect all"), enabled = actions_enabled, callback = function() + UIManager:close(self.file_dialog) self.selected_files = {} self:onRefresh() - UIManager:close(self.file_dialog) end, }, { @@ -650,13 +567,13 @@ function FileManager:tapPlus() text = _("Move selected files to the current folder?"), ok_text = _("Move"), ok_callback = function() + UIManager:close(self.file_dialog) self.cutfile = true for file in pairs(self.selected_files) do self.clipboard = file self:pasteHere(self.file_chooser.path) end self:onToggleSelectMode() - UIManager:close(self.file_dialog) end, }) end @@ -666,8 +583,8 @@ function FileManager:tapPlus() { text = _("Exit select mode"), callback = function() - self:onToggleSelectMode() UIManager:close(self.file_dialog) + self:onToggleSelectMode() end, }, { @@ -678,19 +595,17 @@ function FileManager:tapPlus() text = _("Delete selected files?\nIf you delete a file, it is permanently lost."), ok_text = _("Delete"), ok_callback = function() - local readhistory = require("readhistory") + UIManager:close(self.file_dialog) for file in pairs(self.selected_files) do - self:deleteFile(file) - readhistory:fileDeleted(file) + self:deleteFile(file, true) -- only files can be selected end self:onToggleSelectMode() - UIManager:close(self.file_dialog) end, }) end }, }, - {}, + {}, -- separator { { text = _("New folder"), @@ -715,8 +630,8 @@ function FileManager:tapPlus() { text = _("Select files"), callback = function() - self:onToggleSelectMode(true) -- no full screen refresh UIManager:close(self.file_dialog) + self:onToggleSelectMode(true) -- no full screen refresh end, }, }, @@ -734,9 +649,8 @@ function FileManager:tapPlus() text = _("Paste"), enabled = self.clipboard and true or false, callback = function() - self:pasteHere(self.file_chooser.path) - self:onRefresh() UIManager:close(self.file_dialog) + self:pasteHere(self.file_chooser.path) end, }, }, @@ -744,8 +658,8 @@ function FileManager:tapPlus() { text = _("Set as HOME folder"), callback = function() - self:setHome(self.file_chooser.path) UIManager:close(self.file_dialog) + self:setHome(self.file_chooser.path) end } }, @@ -753,8 +667,8 @@ function FileManager:tapPlus() { text = _("Go to HOME folder"), callback = function() - self:goHome() UIManager:close(self.file_dialog) + self:goHome() end } }, @@ -762,8 +676,8 @@ function FileManager:tapPlus() { text = _("Open random document"), callback = function() - self:openRandomFile(self.file_chooser.path) UIManager:close(self.file_dialog) + self:openRandomFile(self.file_chooser.path) end } }, @@ -771,52 +685,47 @@ function FileManager:tapPlus() { text = _("Folder shortcuts"), callback = function() - self:handleEvent(Event:new("ShowFolderShortcutsDialog")) UIManager:close(self.file_dialog) + self:handleEvent(Event:new("ShowFolderShortcutsDialog")) end } } } - if Device:canImportFiles() then - table.insert(buttons, 4, { - { - text = _("Import files here"), - enabled = Device:isValidPath(self.file_chooser.path), - callback = function() - local current_dir = self.file_chooser.path - UIManager:close(self.file_dialog) - Device.importFile(current_dir) - end, - }, - }) - end - if Device:hasExternalSD() then - table.insert(buttons, 5, { + table.insert(buttons, 4, { -- after "Paste" or "Import files here" button { text_func = function() - if Device:isValidPath(self.file_chooser.path) then - return _("Switch to SDCard") - else - return _("Switch to internal storage") - end + return Device:isValidPath(self.file_chooser.path) + and _("Switch to SDCard") or _("Switch to internal storage") end, callback = function() + UIManager:close(self.file_dialog) if Device:isValidPath(self.file_chooser.path) then local ok, sd_path = Device:hasExternalSD() - UIManager:close(self.file_dialog) if ok then self.file_chooser:changeToPath(sd_path) end else - UIManager:close(self.file_dialog) self.file_chooser:changeToPath(Device.home_dir) end end, }, }) end + + if Device:canImportFiles() then + table.insert(buttons, 4, { -- always after "Paste" button + { + text = _("Import files here"), + enabled = Device:isValidPath(self.file_chooser.path), + callback = function() + UIManager:close(self.file_dialog) + Device.importFile(self.file_chooser.path) + end, + }, + }) + end end self.file_dialog = ButtonDialogTitle:new{ @@ -853,9 +762,7 @@ function FileManager:reinit(path, focused_file) end function FileManager:getCurrentDir() - if FileManager.instance then - return FileManager.instance.file_chooser.path - end + return FileManager.instance and FileManager.instance.file_chooser.path end function FileManager:toggleHiddenFiles() @@ -970,75 +877,64 @@ function FileManager:cutFile(file) end function FileManager:pasteHere(file) - if self.clipboard then - file = BaseUtil.realpath(file) - local orig_basename = BaseUtil.basename(self.clipboard) - local orig = BaseUtil.realpath(self.clipboard) - local dest = lfs.attributes(file, "mode") == "directory" and - file or file:match("(.*/)") - - local function infoCopyFile() - -- if we copy a file, also copy its sidecar directory - if DocSettings:hasSidecarFile(orig) then - BaseUtil.execute(self.cp_bin, "-r", DocSettings:getSidecarDir(orig), dest) - end - if BaseUtil.execute(self.cp_bin, "-r", orig, dest) ~= 0 then - UIManager:show(InfoMessage:new { - text = T(_("Failed to copy:\n%1\nto:\n%2"), BD.filepath(orig_basename), BD.dirpath(dest)), - icon = "notice-warning", - }) + local orig_file = BaseUtil.realpath(self.clipboard) + local orig_name = BaseUtil.basename(self.clipboard) + local dest_path = BaseUtil.realpath(file) + dest_path = lfs.attributes(dest_path, "mode") == "directory" and dest_path or dest_path:match("(.*/)") + local dest_file = BaseUtil.joinPath(dest_path, orig_name) + local is_file = lfs.attributes(orig_file, "mode") == "file" + + local function infoCopyFile() + if self:copyRecursive(orig_file, dest_path) then + if is_file then + DocSettings:update(orig_file, dest_file, true) end + return true + else + UIManager:show(InfoMessage:new { + text = T(_("Failed to copy:\n%1\nto:\n%2"), BD.filepath(orig_name), BD.dirpath(dest_path)), + icon = "notice-warning", + }) end + end - local function infoMoveFile() - -- if we move a file, also move its sidecar directory - if DocSettings:hasSidecarFile(orig) then - self:moveFile(DocSettings:getSidecarDir(orig), dest) -- dest is always a directory - end - if self:moveFile(orig, dest) then - -- Update history and collections. - local dest_file = string.format("%s/%s", dest, BaseUtil.basename(orig)) - require("readhistory"):updateItemByPath(orig, dest_file) -- (will update "lastfile" if needed) - ReadCollection:updateItemByPath(orig, dest_file) - else - UIManager:show(InfoMessage:new { - text = T(_("Failed to move:\n%1\nto:\n%2"), BD.filepath(orig_basename), BD.dirpath(dest)), - icon = "notice-warning", - }) + local function infoMoveFile() + if self:moveFile(orig_file, dest_path) then + if is_file then + DocSettings:update(orig_file, dest_file) + require("readhistory"):updateItemByPath(orig_file, dest_file) -- (will update "lastfile" if needed) end - end - - local info_file - if self.cutfile then - info_file = infoMoveFile + ReadCollection:updateItemByPath(orig_file, dest_file) + return true else - info_file = infoCopyFile + UIManager:show(InfoMessage:new { + text = T(_("Failed to move:\n%1\nto:\n%2"), BD.filepath(orig_name), BD.dirpath(dest_path)), + icon = "notice-warning", + }) end - local basename = BaseUtil.basename(self.clipboard) - local mode = lfs.attributes(string.format("%s/%s", dest, basename), "mode") - if mode == "file" or mode == "directory" then - local text - if mode == "file" then - text = T(_("File already exists:\n%1\nOverwrite file?"), BD.filename(basename)) - else - text = T(_("Folder already exists:\n%1\nOverwrite folder?"), BD.directory(basename)) - end + end - UIManager:show(ConfirmBox:new { - text = text, - ok_text = _("Overwrite"), - ok_callback = function() - info_file() - self:onRefresh() - self.clipboard = nil - end, - }) - else - info_file() + local function doPaste() + local ok = self.cutfile and infoMoveFile() or infoCopyFile() + if ok then self:onRefresh() self.clipboard = nil end end + + local mode = lfs.attributes(dest_file, "mode") + if mode then + UIManager:show(ConfirmBox:new { + text = mode == "file" and T(_("File already exists:\n%1\nOverwrite file?"), BD.filename(orig_name)) + or T(_("Folder already exists:\n%1\nOverwrite folder?"), BD.directory(orig_name)), + ok_text = _("Overwrite"), + ok_callback = function() + doPaste() + end, + }) + else + doPaste() + end end function FileManager:createFolder() @@ -1062,7 +958,7 @@ function FileManager:createFolder() if new_folder_name == "" then return end UIManager:close(input_dialog) local new_folder = string.format("%s/%s", self.file_chooser.path, new_folder_name) - if BaseUtil.execute(self.mkdir_bin, new_folder) == 0 then + if util.makePath(new_folder) then if check_button_enter_folder.checked then self.file_chooser:changeToPath(new_folder) else @@ -1089,8 +985,28 @@ function FileManager:createFolder() input_dialog:onShowKeyboard() end -function FileManager:deleteFile(file) - local ok, err, is_dir +function FileManager:showDeleteFileDialog(file, post_delete_callback, pre_delete_callback) + local file_abs_path = BaseUtil.realpath(file) + local is_file = lfs.attributes(file_abs_path, "mode") == "file" + local text = (is_file and _("Delete file permanently?") or _("Delete folder permanently?")) .. "\n\n" .. BD.filepath(file) + if is_file and DocSettings:hasSidecarFile(file_abs_path) then + text = text .. "\n\n" .. _("Book settings, highlights and notes will be deleted.") + end + UIManager:show(ConfirmBox:new{ + text = text, + ok_text = _("Delete"), + ok_callback = function() + if pre_delete_callback then + pre_delete_callback() + end + if self:deleteFile(file, is_file) and post_delete_callback then + post_delete_callback() + end + end, + }) +end + +function FileManager:deleteFile(file, is_file) local file_abs_path = BaseUtil.realpath(file) if file_abs_path == nil then UIManager:show(InfoMessage:new{ @@ -1100,24 +1016,19 @@ function FileManager:deleteFile(file) return end - local is_doc = DocumentRegistry:hasProvider(file_abs_path) - if lfs.attributes(file_abs_path, "mode") == "file" then + local ok, err + if is_file then ok, err = os.remove(file_abs_path) else ok, err = BaseUtil.purgeDir(file_abs_path) - is_dir = true end if ok and not err then - if is_doc then - local doc_settings = DocSettings:open(file) - -- remove cache if any - local cache_file_path = doc_settings:readSetting("cache_file_path") - if cache_file_path then - os.remove(cache_file_path) - end - doc_settings:purge() + if is_file then + DocSettings:update(file) + require("readhistory"):fileDeleted(file) end - ReadCollection:removeItemByPath(file, is_dir) + ReadCollection:removeItemByPath(file, not is_file) + return true else UIManager:show(InfoMessage:new{ text = T(_("Failed to delete:\n%1"), BD.filepath(file)), @@ -1126,76 +1037,86 @@ function FileManager:deleteFile(file) end end -function FileManager:renameFile(file) - local basename = self.rename_dialog:getInputText() - if BaseUtil.basename(file) ~= basename then - local dest = BaseUtil.joinPath(BaseUtil.dirname(file), basename) - local function doRenameFile() - if self:moveFile(file, dest) then - require("readhistory"):updateItemByPath(file, dest) -- (will update "lastfile" if needed) - ReadCollection:updateItemByPath(file, dest) - if lfs.attributes(dest, "mode") == "file" then - local doc = require("docsettings") - local move_history = true - if lfs.attributes(doc:getHistoryPath(file), "mode") == "file" and - not self:moveFile(doc:getHistoryPath(file), doc:getHistoryPath(dest)) then - move_history = false - end - if lfs.attributes(doc:getSidecarDir(file), "mode") == "directory" and - not self:moveFile(doc:getSidecarDir(file), doc:getSidecarDir(dest)) then - move_history = false - end - if not move_history then - UIManager:show(InfoMessage:new{ - text = T(_("Renamed file:\n%1\nto:\n%2\n\nFailed to move history data.\nThe reading history may be lost."), - BD.filepath(file), BD.filepath(dest)), - icon = "notice-warning", - }) +function FileManager:showRenameFileDialog(file, is_file) + local dialog + dialog = InputDialog:new{ + title = is_file and _("Rename file") or _("Rename folder"), + input = BaseUtil.basename(file), + buttons = {{ + { + text = _("Cancel"), + id = "close", + callback = function() + UIManager:close(dialog) + end, + }, + { + text = _("Rename"), + callback = function() + local new_name = dialog:getInputText() + if new_name ~= "" then + UIManager:close(dialog) + self:renameFile(file, new_name, is_file) end - end - else - UIManager:show(InfoMessage:new{ - text = T(_("Failed to rename:\n%1\nto:\n%2"), BD.filepath(file), BD.filepath(dest)), - icon = "notice-warning", - }) + end, + }, + }}, + } + UIManager:show(dialog) + dialog:onShowKeyboard() +end + +function FileManager:renameFile(file, basename, is_file) + if BaseUtil.basename(file) == basename then return end + local dest = BaseUtil.joinPath(BaseUtil.dirname(file), basename) + + local function doRenameFile() + if self:moveFile(file, dest) then + if is_file then + DocSettings:update(file, dest) + require("readhistory"):updateItemByPath(file, dest) -- (will update "lastfile" if needed) end + ReadCollection:updateItemByPath(file, dest) + self:onRefresh() + else + UIManager:show(InfoMessage:new{ + text = T(_("Failed to rename:\n%1\nto:\n%2"), BD.filepath(file), BD.filepath(dest)), + icon = "notice-warning", + }) end + end - local mode_dest = lfs.attributes(dest, "mode") - local mode_file = lfs.attributes(file, "mode") - if mode_dest then - local text, ok_text - if mode_dest ~= mode_file then - if mode_file == "file" then - text = T(_("Folder already exists:\n%1\nFile cannot be renamed."), BD.directory(basename)) - else - text = T(_("File already exists:\n%1\nFolder cannot be renamed."), BD.filename(basename)) - end - UIManager:show(InfoMessage:new { - text = text, - icon = "notice-warning", - }) + local mode_dest = lfs.attributes(dest, "mode") + if mode_dest then + local text, ok_text + if (mode_dest == "file") ~= is_file then + if is_file then + text = T(_("Folder already exists:\n%1\nFile cannot be renamed."), BD.directory(basename)) else - if mode_file == "file" then - text = T(_("File already exists:\n%1\nOverwrite file?"), BD.filename(basename)) - ok_text = _("Overwrite") - else - text = T(_("Folder already exists:\n%1\nMove the folder inside it?"), BD.directory(basename)) - ok_text = _("Move") - end - UIManager:show(ConfirmBox:new { - text = text, - ok_text = ok_text, - ok_callback = function() - doRenameFile() - self:onRefresh() - end, - }) + text = T(_("File already exists:\n%1\nFolder cannot be renamed."), BD.filename(basename)) end + UIManager:show(InfoMessage:new { + text = text, + icon = "notice-warning", + }) else - doRenameFile() - self:onRefresh() + if is_file then + text = T(_("File already exists:\n%1\nOverwrite file?"), BD.filename(basename)) + ok_text = _("Overwrite") + else + text = T(_("Folder already exists:\n%1\nMove the folder inside it?"), BD.directory(basename)) + ok_text = _("Move") + end + UIManager:show(ConfirmBox:new { + text = text, + ok_text = ok_text, + ok_callback = function() + doRenameFile() + end, + }) end + else + doRenameFile() end end diff --git a/frontend/apps/filemanager/filemanagercollection.lua b/frontend/apps/filemanager/filemanagercollection.lua index 83f3bf65f..3e38e8909 100644 --- a/frontend/apps/filemanager/filemanagercollection.lua +++ b/frontend/apps/filemanager/filemanagercollection.lua @@ -1,18 +1,13 @@ -local BD = require("ui/bidi") local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local Device = require("device") local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") -local InfoMessage = require("ui/widget/infomessage") local Menu = require("ui/widget/menu") local ReadCollection = require("readcollection") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local Screen = require("device").screen local filemanagerutil = require("apps/filemanager/filemanagerutil") -local BaseUtil = require("ffi/util") -local util = require("util") local _ = require("gettext") -local T = BaseUtil.template local FileManagerCollection = WidgetContainer:extend{ coll_menu_title = _("Favorites"), @@ -42,97 +37,74 @@ function FileManagerCollection:updateItemTable() end function FileManagerCollection:onMenuHold(item) - local readerui_instance = require("apps/reader/readerui"):_getRunningInstance() - local currently_opened_file = readerui_instance and readerui_instance.document and readerui_instance.document.file self.collfile_dialog = nil local function status_button_callback() - self._manager:updateItemTable() UIManager:close(self.collfile_dialog) + self._manager:updateItemTable() + end + local is_currently_opened = item.file == (self.ui.document and self.ui.document.file) + + local buttons = {} + if not (item.dim or is_currently_opened) then + table.insert(buttons, filemanagerutil.genStatusButtonsRow(item.file, status_button_callback)) + table.insert(buttons, {}) -- separator end - local buttons = { - filemanagerutil.genStatusButtonsRow(item.file, status_button_callback), - {}, + table.insert(buttons, { + filemanagerutil.genResetSettingsButton(item.file, status_button_callback, is_currently_opened), { - filemanagerutil.genResetSettingsButton(item.file, currently_opened_file, status_button_callback), - { - text = _("Remove from collection"), - callback = function() - ReadCollection:removeItem(item.file, self._manager.coll_menu.collection) - self._manager:updateItemTable() - UIManager:close(self.collfile_dialog) - end, - }, + text = _("Remove from favorites"), + callback = function() + UIManager:close(self.collfile_dialog) + ReadCollection:removeItem(item.file, self._manager.coll_menu.collection) + self._manager:updateItemTable() + end, }, + }) + table.insert(buttons, { { - { - text = _("Sort collection"), - callback = function() - UIManager:close(self.collfile_dialog) - local item_table = {} - for i=1, #self._manager.coll_menu.item_table do - table.insert(item_table, {text = self._manager.coll_menu.item_table[i].text, label = self._manager.coll_menu.item_table[i].file}) - end - local SortWidget = require("ui/widget/sortwidget") - local sort_item - sort_item = SortWidget:new{ - title = _("Sort favorites"), - item_table = item_table, - callback = function() - local new_order_table = {} - for i=1, #sort_item.item_table do - table.insert(new_order_table, { - file = sort_item.item_table[i].label, - order = i - }) - end - ReadCollection:writeCollection(new_order_table, self._manager.coll_menu.collection) - self._manager:updateItemTable() + text = _("Sort favorites"), + callback = function() + UIManager:close(self.collfile_dialog) + local item_table = {} + for _, v in ipairs(self._manager.coll_menu.item_table) do + table.insert(item_table, {text = v.text, label = v.file}) + end + local SortWidget = require("ui/widget/sortwidget") + local sort_item + sort_item = SortWidget:new{ + title = _("Sort favorites"), + item_table = item_table, + callback = function() + local new_order_table = {} + for i, v in ipairs(sort_item.item_table) do + table.insert(new_order_table, { + file = v.label, + order = i, + }) end - } - UIManager:show(sort_item) - end, - }, - { - text = _("Book information"), - id = "book_information", -- used by covermenu - enabled = FileManagerBookInfo:isSupported(item.file), - callback = function() - FileManagerBookInfo:show(item.file) - UIManager:close(self.collfile_dialog) - end, - }, + ReadCollection:writeCollection(new_order_table, self._manager.coll_menu.collection) + self._manager:updateItemTable() + end + } + UIManager:show(sort_item) + end, }, - } - -- NOTE: Duplicated from frontend/apps/filemanager/filemanager.lua + { + text = _("Book information"), + id = "book_information", -- used by covermenu + callback = function() + UIManager:close(self.collfile_dialog) + FileManagerBookInfo:show(item.file) + end, + }, + }) + if Device:canExecuteScript(item.file) then + local function button_callback() + UIManager:close(self.collfile_dialog) + end table.insert(buttons, { - { - -- @translators This is the script's programming language (e.g., shell or python) - text = T(_("Execute %1 script"), util.getScriptType(item.file)), - enabled = true, - callback = function() - UIManager:close(self.collfile_dialog) - local script_is_running_msg = InfoMessage:new{ - -- @translators %1 is the script's programming language (e.g., shell or python), %2 is the filename - text = T(_("Running %1 script %2…"), util.getScriptType(item.file), BD.filename(BaseUtil.basename(item.file))), - } - UIManager:show(script_is_running_msg) - UIManager:scheduleIn(0.5, function() - local rv = os.execute(BaseUtil.realpath(item.file)) - UIManager:close(script_is_running_msg) - if rv == 0 then - UIManager:show(InfoMessage:new{ - text = _("The script exited successfully."), - }) - else - UIManager:show(InfoMessage:new{ - text = T(_("The script returned a non-zero status code: %1!"), bit.rshift(rv, 8)), - icon = "notice-warning", - }) - end - end) - end, - } + filemanagerutil.genExecuteScriptButton(item.file, button_callback) }) end diff --git a/frontend/apps/filemanager/filemanagerhistory.lua b/frontend/apps/filemanager/filemanagerhistory.lua index 7ebf494a7..0677695fd 100644 --- a/frontend/apps/filemanager/filemanagerhistory.lua +++ b/frontend/apps/filemanager/filemanagerhistory.lua @@ -1,17 +1,15 @@ local BD = require("ui/bidi") local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ConfirmBox = require("ui/widget/confirmbox") -local FFIUtil = require("ffi/util") local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") local Menu = require("ui/widget/menu") local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local Screen = require("device").screen local filemanagerutil = require("apps/filemanager/filemanagerutil") -local lfs = require("libs/libkoreader-lfs") local _ = require("gettext") local C_ = _.pgettext -local T = FFIUtil.template +local T = require("ffi/util").template local FileManagerHistory = WidgetContainer:extend{ hist_menu_title = _("History"), @@ -31,7 +29,6 @@ function FileManagerHistory:init() end function FileManagerHistory:addToMainMenu(menu_items) - -- insert table to main tab of filemanager menu menu_items.history = { text = self.hist_menu_title, callback = function() @@ -41,16 +38,13 @@ function FileManagerHistory:addToMainMenu(menu_items) end function FileManagerHistory:fetchStatuses(count) - local status for _, v in ipairs(require("readhistory").hist) do - if v.dim then - status = "deleted" - else - status = filemanagerutil.getStatus(v.file) + v.status = v.dim and "deleted" or filemanagerutil.getStatus(v.file) + if v.status == "new" and v.file == (self.ui.document and self.ui.document.file) then + v.status = "reading" -- file currently opened for the first time end - v.status = status if count then - self.count[status] = self.count[status] + 1 + self.count[v.status] = self.count[v.status] + 1 end end self.statuses_fetched = true @@ -85,64 +79,60 @@ function FileManagerHistory:onSetDimensions(dimen) end function FileManagerHistory:onMenuHold(item) - local readerui_instance = require("apps/reader/readerui"):_getRunningInstance() - local currently_opened_file = readerui_instance and readerui_instance.document and readerui_instance.document.file self.histfile_dialog = nil local function status_button_callback() + UIManager:close(self.histfile_dialog) if self._manager.filter ~= "all" then self._manager:fetchStatuses(false) else self._manager.statuses_fetched = false end self._manager:updateItemTable() - UIManager:close(self.histfile_dialog) + self._manager.files_updated = true -- sidecar folder may be created/deleted + end + local is_currently_opened = item.file == (self.ui.document and self.ui.document.file) + + local buttons = {} + if not (item.dim or is_currently_opened) then + table.insert(buttons, filemanagerutil.genStatusButtonsRow(item.file, status_button_callback)) + table.insert(buttons, {}) -- separator end - local buttons = { + table.insert(buttons, { + filemanagerutil.genResetSettingsButton(item.file, status_button_callback, is_currently_opened), { - filemanagerutil.genResetSettingsButton(item.file, currently_opened_file, status_button_callback), - { - text = _("Remove from history"), - callback = function() - require("readhistory"):removeItem(item) - self._manager:updateItemTable() - UIManager:close(self.histfile_dialog) - end, - }, + text = _("Remove from history"), + callback = function() + UIManager:close(self.histfile_dialog) + require("readhistory"):removeItem(item) + self._manager:updateItemTable() + end, }, + }) + table.insert(buttons, { { - { - text = _("Delete"), - enabled = (item.file ~= currently_opened_file and lfs.attributes(item.file, "mode")) and true or false, - callback = function() - UIManager:show(ConfirmBox:new{ - text = T(_("Are you sure that you want to delete this document?\n\n%1\n\nIf you delete a file, it is permanently lost."), - BD.filepath(item.file)), - ok_text = _("Delete"), - ok_callback = function() - local FileManager = require("apps/filemanager/filemanager") - FileManager:deleteFile(item.file) - require("readhistory"):fileDeleted(item.file) -- (will update "lastfile" if needed) - self._manager:updateItemTable() - UIManager:close(self.histfile_dialog) - end, - }) - end, - }, - { - text = _("Book information"), - id = "book_information", -- used by covermenu - enabled = FileManagerBookInfo:isSupported(item.file), - callback = function() - FileManagerBookInfo:show(item.file) + text = _("Delete"), + enabled = not (item.dim or is_currently_opened), + callback = function() + local function post_delete_callback() UIManager:close(self.histfile_dialog) - end, - }, + self._manager:updateItemTable() + self._manager.files_updated = true + end + local FileManager = require("apps/filemanager/filemanager") + FileManager:showDeleteFileDialog(item.file, post_delete_callback) + end, }, - } - if not item.dim then - table.insert(buttons, 1, filemanagerutil.genStatusButtonsRow(item.file, status_button_callback)) - table.insert(buttons, 2, {}) - end + { + text = _("Book information"), + id = "book_information", -- used by covermenu + enabled = not item.dim, + callback = function() + UIManager:close(self.histfile_dialog) + FileManagerBookInfo:show(item.file) + end, + }, + }) + self.histfile_dialog = ButtonDialogTitle:new{ title = BD.filename(item.text:match("([^/]+)$")), title_align = "center", @@ -190,6 +180,13 @@ function FileManagerHistory:onShowHist() end self:updateItemTable() self.hist_menu.close_callback = function() + if self.files_updated then -- refresh Filemanager list of files + local FileManager = require("apps/filemanager/filemanager") + if FileManager.instance then + FileManager.instance:onRefresh() + end + self.files_updated = nil + end self.statuses_fetched = nil UIManager:close(self.hist_menu) G_reader_settings:saveSetting("history_filter", self.filter) @@ -218,17 +215,15 @@ function FileManagerHistory:showHistDialog() table.insert(buttons, { genFilterButton("reading"), genFilterButton("abandoned"), - }) - table.insert(buttons, { genFilterButton("complete"), - genFilterButton("deleted"), }) table.insert(buttons, { genFilterButton("all"), genFilterButton("new"), + genFilterButton("deleted"), }) if self.count.deleted > 0 then - table.insert(buttons, {}) + table.insert(buttons, {}) -- separator table.insert(buttons, { { text = _("Clear history of deleted files"), @@ -243,7 +238,7 @@ function FileManagerHistory:showHistDialog() end, }) end, - }, + }, }) end hist_dialog = ButtonDialogTitle:new{ diff --git a/frontend/apps/filemanager/filemanagerutil.lua b/frontend/apps/filemanager/filemanagerutil.lua index f0c13d7f8..db8c875ca 100644 --- a/frontend/apps/filemanager/filemanagerutil.lua +++ b/frontend/apps/filemanager/filemanagerutil.lua @@ -6,10 +6,12 @@ local BD = require("ui/bidi") local ConfirmBox = require("ui/widget/confirmbox") local Device = require("device") local DocSettings = require("docsettings") +local InfoMessage = require("ui/widget/infomessage") local UIManager = require("ui/uimanager") -local util = require("ffi/util") +local ffiutil = require("ffi/util") +local util = require("util") local _ = require("gettext") -local T = util.template +local T = ffiutil.template local filemanagerutil = {} @@ -35,7 +37,7 @@ end -- Purge doc settings in sidecar directory function filemanagerutil.purgeSettings(file) - local file_abs_path = util.realpath(file) + local file_abs_path = ffiutil.realpath(file) if file_abs_path then DocSettings:open(file_abs_path):purge() end @@ -54,7 +56,7 @@ function filemanagerutil.resetDocumentSettings(file) last_page = true, last_xpointer = true, } - local file_abs_path = util.realpath(file) + local file_abs_path = ffiutil.realpath(file) if file_abs_path then local doc_settings = DocSettings:open(file_abs_path) for k in pairs(doc_settings.data) do @@ -67,54 +69,32 @@ function filemanagerutil.resetDocumentSettings(file) end end --- Get a document's status ("new", "reading", "complete", or "abandoned") +-- Get a document status ("new", "reading", "complete", or "abandoned") function filemanagerutil.getStatus(file) - local status = "new" if DocSettings:hasSidecarFile(file) then - local docinfo = DocSettings:open(file) -- no io handles created, do not close - if docinfo.data.summary and docinfo.data.summary.status and docinfo.data.summary.status ~= "" then - status = docinfo.data.summary.status - else - status = "reading" + local summary = DocSettings:open(file):readSetting("summary") + if summary and summary.status and summary.status ~= "" then + return summary.status end + return "reading" end - return status + return "new" end --- Set a document's status +-- Set a document status ("reading", "complete", or "abandoned") function filemanagerutil.setStatus(file, status) -- In case the book doesn't have a sidecar file, this'll create it - local docinfo = DocSettings:open(file) - local summary - if docinfo.data.summary and docinfo.data.summary.status then - -- Book already had the full BookStatus table in its sidecar, easy peasy! - docinfo.data.summary.status = status - docinfo.data.summary.modified = os.date("%Y-%m-%d", os.time()) - summary = docinfo.data.summary - else - -- No BookStatus table, create a minimal one... - if docinfo.data.summary then - -- Err, a summary table with no status entry? Should never happen... - summary = { status = status } - -- Append the status entry to the existing summary... - require("util").tableMerge(docinfo.data.summary, summary) - docinfo.data.summary.modified = os.date("%Y-%m-%d", os.time()) - summary = docinfo.data.summary - else - -- No summary table at all, create a minimal one - summary = { - status = status, - modified = os.date("%Y-%m-%d", os.time()) - } - end - end - docinfo:saveSetting("summary", summary) - docinfo:flush() + local doc_settings = DocSettings:open(file) + local summary = doc_settings:readSetting("summary") or {} + summary.status = status + summary.modified = os.date("%Y-%m-%d", os.time()) + doc_settings:saveSetting("summary", summary) + doc_settings:flush() end -- Generate all book status file dialog buttons in a row -function filemanagerutil.genStatusButtonsRow(file, caller_callback) - local status = filemanagerutil.getStatus(file) +function filemanagerutil.genStatusButtonsRow(file, caller_callback, current_status) + local status = current_status or filemanagerutil.getStatus(file) local function genStatusButton(to_status) local status_text = { reading = _("Reading"), @@ -122,7 +102,7 @@ function filemanagerutil.genStatusButtonsRow(file, caller_callback) complete = _("Finished"), } return { - text = status_text[to_status], + text = status_text[to_status] .. (status == to_status and " ✓" or ""), id = to_status, -- used by covermenu enabled = status ~= to_status, callback = function() @@ -138,15 +118,16 @@ function filemanagerutil.genStatusButtonsRow(file, caller_callback) } end --- Generate a "Reset settings" file dialog button -function filemanagerutil.genResetSettingsButton(file, currently_opened_file, caller_callback) +-- Generate "Reset" file dialog button +function filemanagerutil.genResetSettingsButton(file, caller_callback, button_disabled) return { text = _("Reset"), id = "reset", -- used by covermenu - enabled = file ~= currently_opened_file and DocSettings:hasSidecarFile(util.realpath(file)), + enabled = not button_disabled and DocSettings:hasSidecarFile(ffiutil.realpath(file)), callback = function() UIManager:show(ConfirmBox:new{ - text = T(_("Reset this document?\n\n%1\n\nAll document progress, settings, bookmarks, highlights, and notes will be permanently lost."), + text = T(_("Reset this document?") .. "\n\n%1\n\n" .. + _("Document progress, settings, bookmarks, highlights and notes will be permanently lost."), BD.filepath(file)), ok_text = _("Reset"), ok_callback = function() @@ -159,4 +140,42 @@ function filemanagerutil.genResetSettingsButton(file, currently_opened_file, cal } end +-- Generate "Execute script" file dialog button +function filemanagerutil.genExecuteScriptButton(file, caller_callback) + return { + -- @translators This is the script's programming language (e.g., shell or python) + text = T(_("Execute %1 script"), util.getScriptType(file)), + callback = function() + caller_callback() + local script_is_running_msg = InfoMessage:new{ + -- @translators %1 is the script's programming language (e.g., shell or python), %2 is the filename + text = T(_("Running %1 script %2…"), util.getScriptType(file), BD.filename(ffiutil.basename(file))), + } + UIManager:show(script_is_running_msg) + UIManager:scheduleIn(0.5, function() + local rv + if Device:isAndroid() then + Device:setIgnoreInput(true) + rv = os.execute("sh " .. ffiutil.realpath(file)) -- run by sh, because sdcard has no execute permissions + Device:setIgnoreInput(false) + else + rv = os.execute(ffiutil.realpath(file)) + end + UIManager:close(script_is_running_msg) + if rv == 0 then + UIManager:show(InfoMessage:new{ + text = _("The script exited successfully."), + }) + else + --- @note: Lua 5.1 returns the raw return value from the os's system call. Counteract this madness. + UIManager:show(InfoMessage:new{ + text = T(_("The script returned a non-zero status code: %1!"), bit.rshift(rv, 8)), + icon = "notice-warning", + }) + end + end) + end, + } +end + return filemanagerutil diff --git a/frontend/apps/reader/modules/readerstatus.lua b/frontend/apps/reader/modules/readerstatus.lua index e31f2b2dd..0ee3fe2db 100644 --- a/frontend/apps/reader/modules/readerstatus.lua +++ b/frontend/apps/reader/modules/readerstatus.lua @@ -1,4 +1,3 @@ -local BD = require("ui/bidi") local BookStatusWidget = require("ui/widget/bookstatuswidget") local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local Device = require("device") @@ -8,7 +7,6 @@ local UIManager = require("ui/uimanager") local WidgetContainer = require("ui/widget/container/widgetcontainer") local util = require("util") local _ = require("gettext") -local T = require("ffi/util").template local ReaderStatus = WidgetContainer:extend{ document = nil, @@ -36,46 +34,39 @@ end function ReaderStatus:onEndOfBook() Device:performHapticFeedback("CONTEXT_CLICK") - local settings = G_reader_settings:readSetting("end_document_action") - local choose_action - local collate = true local QuickStart = require("ui/quickstart") local last_file = G_reader_settings:readSetting("lastfile") - if last_file and last_file == QuickStart.quickstart_filename then + if last_file == QuickStart.quickstart_filename then self:openFileBrowser() return end - if G_reader_settings:readSetting("collate") == "access" then - collate = false - end -- Should we start by marking the book as read? if G_reader_settings:isTrue("end_document_auto_mark") then self:onMarkBook(true) end - local top_wg = UIManager:getTopmostVisibleWidget() or {} - if (settings == "pop-up" or settings == nil) and top_wg.name ~= "end_document" then + local next_file_enabled = G_reader_settings:readSetting("collate") ~= "access" + local settings = G_reader_settings:readSetting("end_document_action") + local top_widget = UIManager:getTopmostVisibleWidget() or {} + if (settings == "pop-up" or settings == nil) and top_widget.name ~= "end_document" then + local button_dialog local buttons = { { { text_func = function() - if self.settings.data.summary and self.settings.data.summary.status == "complete" then - return _("Mark as reading") - else - return _("Mark as finished") - end + return self.summary.status == "complete" and _("Mark as reading") or _("Mark as finished") end, callback = function() self:onMarkBook() - UIManager:close(choose_action) + UIManager:close(button_dialog) end, }, { text = _("Book status"), callback = function() self:onShowBookStatus() - UIManager:close(choose_action) + UIManager:close(button_dialog) end, }, @@ -85,15 +76,15 @@ function ReaderStatus:onEndOfBook() text = _("Go to beginning"), callback = function() self.ui:handleEvent(Event:new("GoToBeginning")) - UIManager:close(choose_action) + UIManager:close(button_dialog) end, }, { text = _("Open next file"), - enabled = collate, + enabled = next_file_enabled, callback = function() self:onOpenNextDocumentInFolder() - UIManager:close(choose_action) + UIManager:close(button_dialog) end, }, }, @@ -101,46 +92,38 @@ function ReaderStatus:onEndOfBook() { text = _("Delete file"), callback = function() - self:deleteFile(self.document.file, false) - UIManager:close(choose_action) + self:deleteFile() + UIManager:close(button_dialog) end, }, { text = _("File browser"), callback = function() self:openFileBrowser() - UIManager:close(choose_action) - end, - }, - }, - { - { - text = _("Cancel"), - callback = function() - UIManager:close(choose_action) + UIManager:close(button_dialog) end, }, }, } - choose_action = ButtonDialogTitle:new{ + button_dialog = ButtonDialogTitle:new{ name = "end_document", title = _("You've reached the end of the document.\nWhat would you like to do?"), title_align = "center", buttons = buttons, } - - UIManager:show(choose_action) + UIManager:show(button_dialog) elseif settings == "book_status" then self:onShowBookStatus() elseif settings == "next_file" then - if G_reader_settings:readSetting("collate") ~= "access" then + if next_file_enabled then local info = InfoMessage:new{ text = _("Searching next file…"), } UIManager:show(info) UIManager:forceRePaint() UIManager:close(info) - -- Delay until the next tick, as this will destroy the Document instance, but we may not be the final Event caught by said Document... + -- Delay until the next tick, as this will destroy the Document instance, + -- but we may not be the final Event caught by said Document... UIManager:nextTick(function() self:onOpenNextDocumentInFolder() end) @@ -171,7 +154,7 @@ function ReaderStatus:onEndOfBook() elseif settings == "delete_file" then -- Ditto UIManager:nextTick(function() - self:deleteFile(self.document.file, true) + self:deleteFile() end) end end @@ -200,28 +183,17 @@ function ReaderStatus:onOpenNextDocumentInFolder() end end -function ReaderStatus:deleteFile(file, text_end_book) - local ConfirmBox = require("ui/widget/confirmbox") - local message_end_book = "" - if text_end_book then - message_end_book = "You've reached the end of the document.\n" +function ReaderStatus:deleteFile() + self.settings:flush() -- enable additional warning text for newly opened file + local FileManager = require("apps/filemanager/filemanager") + local function pre_delete_callback() + self.ui:onClose() end - UIManager:show(ConfirmBox:new{ - text = T(_("%1Are you sure that you want to delete this file?\n%2\nIf you delete a file, it is permanently lost."), message_end_book, BD.filepath(file)), - ok_text = _("Delete"), - ok_callback = function() - local FileManager = require("apps/filemanager/filemanager") - self.ui:onClose() - FileManager:deleteFile(file) - require("readhistory"):fileDeleted(file) -- (will update "lastfile") - if FileManager.instance then - FileManager.instance.file_chooser:refreshPath() - else - local path = util.splitFilePathName(file) - FileManager:showFiles(path) - end - end, - }) + local function post_delete_callback() + local path = util.splitFilePathName(self.document.file) + FileManager:showFiles(path) + end + FileManager:showDeleteFileDialog(self.document.file, post_delete_callback, pre_delete_callback) end function ReaderStatus:onShowBookStatus(before_show_callback) @@ -240,31 +212,18 @@ function ReaderStatus:onShowBookStatus(before_show_callback) return true end --- If mark_read is true then we change status only from reading/abandoned to read (complete). --- Otherwise we change status from reading/abandoned to read or from read to reading. +-- If mark_read is true then we change status only from reading/abandoned to complete. +-- Otherwise we change status from reading/abandoned to complete or from complete to reading. function ReaderStatus:onMarkBook(mark_read) - if self.settings.data.summary then - if self.settings.data.summary.status and self.settings.data.summary.status == "complete" then - if mark_read then - -- Keep mark as finished. - self.settings.data.summary.status = "complete" - else - -- Change current status from read (complete) to reading - self.settings.data.summary.status = "reading" - end - else - self.settings.data.summary.status = "complete" - end - else - self.settings.data.summary = {status = "complete"} - end + self.summary.status = (not mark_read and self.summary.status == "complete") and "reading" or "complete" -- If History is called over Reader, it will read the file to get the book status, so save and flush - self.settings:saveSetting("summary", self.settings.data.summary) + self.settings:saveSetting("summary", self.summary) self.settings:flush() end function ReaderStatus:onReadSettings(config) self.settings = config + self.summary = config:readSetting("summary") or {} end return ReaderStatus diff --git a/plugins/coverbrowser.koplugin/covermenu.lua b/plugins/coverbrowser.koplugin/covermenu.lua index d80ca3938..5ae24241a 100644 --- a/plugins/coverbrowser.koplugin/covermenu.lua +++ b/plugins/coverbrowser.koplugin/covermenu.lua @@ -43,6 +43,8 @@ local nb_drawings_since_last_collectgarbage = 0 -- in the real Menu class or instance local CoverMenu = {} +local book_statuses = {"reading", "abandoned", "complete"} + function CoverMenu:updateCache(file, status) if self.cover_info_cache and self.cover_info_cache[file] then if status then @@ -348,8 +350,9 @@ function CoverMenu:updateItems(select_number) end -- Fudge the status change button callbacks to also update the cover_info_cache - for _, status in ipairs({"reading", "abandoned", "complete"}) do + for _, status in ipairs(book_statuses) do button = self.file_dialog.button_table:getButtonById(status) + if not button then break end -- status buttons are not shown local orig_status_callback = button.callback button.callback = function() -- Update the cache @@ -501,16 +504,15 @@ function CoverMenu:onHistoryMenuHold(item) end -- Fudge the status change button callbacks to also update the cover_info_cache - for _, status in ipairs({"reading", "abandoned", "complete"}) do + for _, status in ipairs(book_statuses) do button = self.histfile_dialog.button_table:getButtonById(status) - if button then - local orig_status_callback = button.callback - button.callback = function() - -- Update the cache - self:updateCache(file, status) - -- And then set the status on file as expected - orig_status_callback() - end + if not button then break end -- status buttons are not shown + local orig_status_callback = button.callback + button.callback = function() + -- Update the cache + self:updateCache(file, status) + -- And then set the status on file as expected + orig_status_callback() end end @@ -648,8 +650,9 @@ function CoverMenu:onCollectionsMenuHold(item) end -- Fudge the status change button callbacks to also update the cover_info_cache - for _, status in ipairs({"reading", "abandoned", "complete"}) do + for _, status in ipairs(book_statuses) do button = self.collfile_dialog.button_table:getButtonById(status) + if not button then break end -- status buttons are not shown local orig_status_callback = button.callback button.callback = function() -- Update the cache @@ -694,7 +697,7 @@ function CoverMenu:onCloseWidget() -- Propagate a call to free() to all our sub-widgets, to release memory used by their _bb self.item_group:free() - -- Clean any short term cache (used by ListMenu to cache some DocSettings info) + -- Clean any short term cache (used by ListMenu to cache some Doc Settings info) self.cover_info_cache = nil -- Force garbage collecting when leaving too diff --git a/spec/unit/filemanager_spec.lua b/spec/unit/filemanager_spec.lua index 730afa7d6..552e09770 100644 --- a/spec/unit/filemanager_spec.lua +++ b/spec/unit/filemanager_spec.lua @@ -32,11 +32,11 @@ describe("FileManager module", function() assert.Equals(w.text, "File not found:\n"..tmp_fn) end assert.is_nil(lfs.attributes(tmp_fn)) - filemanager:deleteFile(tmp_fn) + filemanager:deleteFile(tmp_fn, true) UIManager.show = old_show filemanager:onClose() end) - it("should not delete settings for non-document file", function() + it("should not delete not empty sidecar folder", function() local filemanager = FileManager:new{ dimen = Screen:getSize(), root_path = "../../test", @@ -47,30 +47,32 @@ describe("FileManager module", function() local tmp_sidecar = docsettings:getSidecarDir(util.realpath(tmp_fn)) lfs.mkdir(tmp_sidecar) - local tmp_history = docsettings:getHistoryPath(tmp_fn) - local tmpfp = io.open(tmp_history, "w") - tmpfp:write("{}") - tmpfp:close() + local tmp_sidecar_file = docsettings:getSidecarFile(util.realpath(tmp_fn)) + local tmp_sidecar_file_foo = tmp_sidecar_file .. ".foo" -- non-docsettings file + local tmpsf = io.open(tmp_sidecar_file, "w") + tmpsf:write("{}") + tmpsf:close() + util.copyFile(tmp_sidecar_file, tmp_sidecar_file_foo) local old_show = UIManager.show -- make sure file exists assert.is_not_nil(lfs.attributes(tmp_fn)) assert.is_not_nil(lfs.attributes(tmp_sidecar)) - assert.is_not_nil(lfs.attributes(tmp_history)) + assert.is_not_nil(lfs.attributes(tmp_sidecar_file)) + assert.is_not_nil(lfs.attributes(tmp_sidecar_file_foo)) UIManager.show = function(self, w) assert.Equals(w.text, "Deleted file:\n"..tmp_fn) end - filemanager:deleteFile(tmp_fn) + filemanager:deleteFile(tmp_fn, true) UIManager.show = old_show filemanager:onClose() - -- make sure history file exists + -- make sure sdr folder exists assert.is_nil(lfs.attributes(tmp_fn)) assert.is_not_nil(lfs.attributes(tmp_sidecar)) - assert.is_not_nil(lfs.attributes(tmp_history)) + os.remove(tmp_sidecar_file_foo) os.remove(tmp_sidecar) - os.remove(tmp_history) end) it("should delete document with its settings", function() local filemanager = FileManager:new{ @@ -83,6 +85,10 @@ describe("FileManager module", function() local tmp_sidecar = docsettings:getSidecarDir(util.realpath(tmp_fn)) lfs.mkdir(tmp_sidecar) + local tmp_sidecar_file = docsettings:getSidecarFile(util.realpath(tmp_fn)) + local tmpsf = io.open(tmp_sidecar_file, "w") + tmpsf:write("{}") + tmpsf:close() local tmp_history = docsettings:getHistoryPath(tmp_fn) local tmpfp = io.open(tmp_history, "w") tmpfp:write("{}") @@ -97,7 +103,7 @@ describe("FileManager module", function() UIManager.show = function(self, w) assert.Equals(w.text, "Deleted file:\n"..tmp_fn) end - filemanager:deleteFile(tmp_fn) + filemanager:deleteFile(tmp_fn, true) UIManager.show = old_show filemanager:onClose()