diff --git a/frontend/apps/filemanager/filemanagerfilesearcher.lua b/frontend/apps/filemanager/filemanagerfilesearcher.lua index 22c9d4cdd..150db75d7 100644 --- a/frontend/apps/filemanager/filemanagerfilesearcher.lua +++ b/frontend/apps/filemanager/filemanagerfilesearcher.lua @@ -36,11 +36,23 @@ function FileSearcher:registerKeyEvents() end function FileSearcher:onShowFileSearch(search_string) - local search_dialog - local check_button_case, check_button_subfolders, check_button_metadata + local search_dialog, check_button_case, check_button_subfolders, check_button_metadata + local function _doSearch() + local search_str = search_dialog:getInputText() + if search_str == "" then return end + FileSearcher.search_string = search_str + UIManager:close(search_dialog) + self.case_sensitive = check_button_case.checked + self.include_subfolders = check_button_subfolders.checked + self.include_metadata = check_button_metadata and check_button_metadata.checked + local Trapper = require("ui/trapper") + Trapper:wrap(function() + self:doSearch() + end) + end search_dialog = InputDialog:new{ title = _("Enter text to search for in filename"), - input = search_string or self.search_string, + input = search_string or FileSearcher.search_string, buttons = { { { @@ -54,22 +66,16 @@ function FileSearcher:onShowFileSearch(search_string) text = _("Home folder"), enabled = G_reader_settings:has("home_dir"), callback = function() - self.search_string = search_dialog:getInputText() - if self.search_string == "" then return end - UIManager:close(search_dialog) self.path = G_reader_settings:readSetting("home_dir") - self:doSearch() + _doSearch() end, }, { text = self.ui.file_chooser and _("Current folder") or _("Book folder"), is_enter_default = true, callback = function() - self.search_string = search_dialog:getInputText() - if self.search_string == "" then return end - UIManager:close(search_dialog) self.path = self.ui.file_chooser and self.ui.file_chooser.path or self.ui:getLastDirFile() - self:doSearch() + _doSearch() end, }, }, @@ -79,18 +85,12 @@ function FileSearcher:onShowFileSearch(search_string) text = _("Case sensitive"), checked = self.case_sensitive, parent = search_dialog, - callback = function() - self.case_sensitive = check_button_case.checked - end, } search_dialog:addWidget(check_button_case) check_button_subfolders = CheckButton:new{ text = _("Include subfolders"), checked = self.include_subfolders, parent = search_dialog, - callback = function() - self.include_subfolders = check_button_subfolders.checked - end, } search_dialog:addWidget(check_button_subfolders) if self.ui.coverbrowser then @@ -98,9 +98,6 @@ function FileSearcher:onShowFileSearch(search_string) text = _("Also search in book metadata"), checked = self.include_metadata, parent = search_dialog, - callback = function() - self.include_metadata = check_button_metadata.checked - end, } search_dialog:addWidget(check_button_metadata) end @@ -110,25 +107,50 @@ function FileSearcher:onShowFileSearch(search_string) end function FileSearcher:doSearch() - local dirs, files = self:getList() - -- If we have a FileChooser instance, use it, to be able to make use of its natsort cache - local results = (self.ui.file_chooser or FileChooser):genItemTable(dirs, files) - if #results > 0 then - self:showSearchResults(results) + local search_hash = self.path .. (FileSearcher.search_string or "") .. + tostring(self.case_sensitive) .. tostring(self.include_subfolders) .. tostring(self.include_metadata) + local not_cached = FileSearcher.search_hash ~= search_hash + if not_cached then + local Trapper = require("ui/trapper") + local info = InfoMessage:new{ text = _("Searching… (tap to cancel)") } + UIManager:show(info) + UIManager:forceRePaint() + local completed, dirs, files, no_metadata_count = Trapper:dismissableRunInSubprocess(function() + return self:getList() + end, info) + if not completed then return end + UIManager:close(info) + FileSearcher.search_hash = search_hash + self.no_metadata_count = no_metadata_count + -- Cannot do this in getList() within Trapper (cannot serialize function) + local collate = FileChooser:getCollate() + for i, v in ipairs(dirs) do + local f, fullpath, attributes = unpack(v) + dirs[i] = FileChooser:getListItem(nil, f, fullpath, attributes, collate) + end + for i, v in ipairs(files) do + local f, fullpath, attributes = unpack(v) + files[i] = FileChooser:getListItem(nil, f, fullpath, attributes, collate) + end + -- If we have a FileChooser instance, use it, to be able to make use of its natsort cache + FileSearcher.search_results = (self.ui.file_chooser or FileChooser):genItemTable(dirs, files) + end + if #FileSearcher.search_results > 0 then + self:onShowSearchResults(not_cached) else self:showSearchResultsMessage(true) end end function FileSearcher:getList() - self.no_metadata_count = 0 + self.no_metadata_count = 0 -- will be updated in doSearch() with result from subprocess local sys_folders = { -- do not search in sys_folders ["/dev"] = true, ["/proc"] = true, ["/sys"] = true, + ["/mnt/base-us"] = true, -- Kindle } - local collate = FileChooser:getCollate() - local search_string = self.search_string + local search_string = FileSearcher.search_string if search_string ~= "*" then -- one * to show all files if not self.case_sensitive then search_string = Utf8Proc.lowercase(util.fixUtf8(search_string, "?")) @@ -164,14 +186,14 @@ function FileSearcher:getList() table.insert(new_dirs, fullpath) end if self:isFileMatch(f, fullpath, search_string) then - table.insert(dirs, FileChooser:getListItem(nil, f, fullpath, attributes, collate)) + table.insert(dirs, { f, fullpath, attributes }) end -- Always ignore macOS resource forks, too. elseif attributes.mode == "file" and not util.stringStartsWith(f, "._") and (FileChooser.show_unsupported or DocumentRegistry:hasProvider(fullpath)) and FileChooser:show_file(f) then if self:isFileMatch(f, fullpath, search_string, true) then - table.insert(files, FileChooser:getListItem(nil, f, fullpath, attributes, collate)) + table.insert(files, { f, fullpath, attributes }) end end end @@ -179,7 +201,7 @@ function FileSearcher:getList() end scan_dirs = new_dirs end - return dirs, files + return dirs, files, self.no_metadata_count end function FileSearcher:isFileMatch(filename, fullpath, search_string, is_file) @@ -206,9 +228,16 @@ function FileSearcher:isFileMatch(filename, fullpath, search_string, is_file) end function FileSearcher:showSearchResultsMessage(no_results) - local text = no_results and T(_("No results for '%1'."), self.search_string) + local text = no_results and T(_("No results for '%1'."), FileSearcher.search_string) if self.no_metadata_count == 0 then - UIManager:show(InfoMessage:new{ text = text }) + UIManager:show(ConfirmBox:new{ + text = text, + icon = "notice-info", + ok_text = _("File search"), + ok_callback = function() + self:onShowFileSearch() + end, + }) else local txt = T(N_("1 book has been skipped.", "%1 books have been skipped.", self.no_metadata_count), self.no_metadata_count) .. "\n" .. @@ -222,14 +251,19 @@ function FileSearcher:showSearchResultsMessage(no_results) self.search_menu.close_callback() end self.ui.coverbrowser:extractBooksInDirectory(self.path) - end + end, }) end end -function FileSearcher:showSearchResults(results) +function FileSearcher:onShowSearchResults(not_cached) + if not not_cached and FileSearcher.search_results == nil then + self:onShowFileSearch() + return + end + self.search_menu = Menu:new{ - subtitle = T(_("Query: %1"), self.search_string), + subtitle = T(_("Query: %1"), FileSearcher.search_string), covers_fullscreen = true, -- hint for UIManager:_repaint() is_borderless = true, is_popout = false, @@ -249,9 +283,9 @@ function FileSearcher:showSearchResults(results) self.ui.file_chooser:refreshPath() end end - self:updateMenu(results) + self:updateMenu(FileSearcher.search_results) UIManager:show(self.search_menu) - if self.no_metadata_count ~= 0 then + if not_cached and self.no_metadata_count ~= 0 then self:showSearchResultsMessage() end end @@ -262,6 +296,7 @@ function FileSearcher:updateMenu(item_table) end function FileSearcher:onMenuSelect(item) + if lfs.attributes(item.path) == nil then return end if self._manager.selected_files then if item.is_file then item.dim = not item.dim and true or nil @@ -310,6 +345,7 @@ function FileSearcher:showFileDialog(item) callback = function() local function post_delete_callback() UIManager:close(dialog) + table.remove(FileSearcher.search_results, item.idx) table.remove(self.search_menu.item_table, item.idx) self:updateMenu() end @@ -349,7 +385,7 @@ function FileSearcher:showFileDialog(item) end function FileSearcher:onMenuHold(item) - if self._manager.selected_files then return true end + if self._manager.selected_files or lfs.attributes(item.path) == nil then return true end if item.is_file then if DocumentRegistry:hasProvider(item.path, nil, true) then self.close_callback() diff --git a/frontend/apps/filemanager/filemanagermenu.lua b/frontend/apps/filemanager/filemanagermenu.lua index 91738f9fc..a37b37ef0 100644 --- a/frontend/apps/filemanager/filemanagermenu.lua +++ b/frontend/apps/filemanager/filemanagermenu.lua @@ -794,7 +794,7 @@ To: end, } - self.menu_items.find_file = { + self.menu_items.file_search = { -- @translators Search for files by name. text = _("File search"), help_text = _([[Search a book by filename in the current or home folder and its subfolders. @@ -807,7 +807,13 @@ The sorting order is the same as in filemanager. Tap a book in the search results to open it.]]), callback = function() self.ui.filesearcher:onShowFileSearch() - end + end, + } + self.menu_items.file_search_results = { + text = _("Last file search results"), + callback = function() + self.ui.filesearcher:onShowSearchResults() + end, } -- main menu tab diff --git a/frontend/dispatcher.lua b/frontend/dispatcher.lua index 00a8fe20d..f9b21c519 100644 --- a/frontend/dispatcher.lua +++ b/frontend/dispatcher.lua @@ -130,13 +130,14 @@ local settingsList = { set_reverse_sorting = {category="string", event="SetReverseSorting", title=_("Reverse sorting"), args={true, false}, toggle={_("on"), _("off")}, filemanager=true}, set_mixed_sorting = {category="string", event="SetMixedSorting", title=_("Folders and files mixed"), args={true, false}, toggle={_("on"), _("off")}, filemanager=true, separator=true}, ---- - folder_up = {category="none", event="FolderUp", title=_("Folder up"), filemanager=true}, show_plus_menu = {category="none", event="ShowPlusMenu", title=_("Show plus menu"), filemanager=true}, toggle_select_mode = {category="none", event="ToggleSelectMode", title=_("Toggle select mode"), filemanager=true}, refresh_content = {category="none", event="RefreshContent", title=_("Refresh content"), filemanager=true}, folder_shortcuts = {category="none", event="ShowFolderShortcutsDialog", title=_("Folder shortcuts"), filemanager=true}, file_search = {category="none", event="ShowFileSearch", title=_("File search"), filemanager=true}, + file_search_results = {category="none", event="ShowSearchResults", title=_("Last file search results"), filemanager=true}, ---- + folder_up = {category="none", event="FolderUp", title=_("Folder up"), filemanager=true}, -- go_to -- back @@ -362,13 +363,14 @@ local dispatcher_menu_order = { "set_reverse_sorting", "set_mixed_sorting", ---- - "folder_up", "show_plus_menu", "toggle_select_mode", "refresh_content", "folder_shortcuts", "file_search", + "file_search_results", ---- + "folder_up", -- "go_to" -- "back" diff --git a/frontend/ui/elements/filemanager_menu_order.lua b/frontend/ui/elements/filemanager_menu_order.lua index 2ff56a296..fdd564428 100644 --- a/frontend/ui/elements/filemanager_menu_order.lua +++ b/frontend/ui/elements/filemanager_menu_order.lua @@ -155,8 +155,9 @@ local order = { "wikipedia_lookup", "wikipedia_history", "----------------------------", + "file_search", + "file_search_results", "find_book_in_calibre_catalog", - "find_file", "----------------------------", "opds", },