2
0
mirror of https://github.com/koreader/koreader synced 2024-11-04 12:00:25 +00:00
koreader/frontend/apps/filemanager/filemanagerfilesearcher.lua
NiLuJe fadee1f5dc
Clarify our OOP semantics across the codebase (#9586)
Basically:

* Use `extend` for class definitions
* Use `new` for object instantiations

That includes some minor code cleanups along the way:

* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
  * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 02:14:48 +02:00

281 lines
9.8 KiB
Lua

local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local CheckButton = require("ui/widget/checkbutton")
local CenterContainer = require("ui/widget/container/centercontainer")
local DocumentRegistry = require("document/documentregistry")
local FileChooser = require("ui/widget/filechooser")
local InfoMessage = require("ui/widget/infomessage")
local InputDialog = require("ui/widget/inputdialog")
local Menu = require("ui/widget/menu")
local Size = require("ui/size")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local BaseUtil = require("ffi/util")
local Utf8Proc = require("ffi/utf8proc")
local lfs = require("libs/libkoreader-lfs")
local util = require("util")
local _ = require("gettext")
local Screen = require("device").screen
local T = require("ffi/util").template
local FileSearcher = WidgetContainer:extend{
dirs = nil, -- table
files = nil, -- table
results = nil, -- table
case_sensitive = false,
include_subfolders = true,
}
local sys_folders = { -- do not search in sys_folders
["/dev"] = true,
["/proc"] = true,
["/sys"] = true,
}
function FileSearcher:init()
self.dirs = {}
self.files = {}
self.results = {}
end
function FileSearcher:readDir()
local ReaderUI = require("apps/reader/readerui")
local show_unsupported = G_reader_settings:isTrue("show_unsupported")
self.dirs = {self.path}
self.files = {}
while #self.dirs ~= 0 do
local new_dirs = {}
-- handle each dir
for __, d in pairs(self.dirs) do
-- handle files in d
local ok, iter, dir_obj = pcall(lfs.dir, d)
if ok then
for f in iter, dir_obj do
local fullpath = "/" .. f
if d ~= "/" then
fullpath = d .. fullpath
end
local attributes = lfs.attributes(fullpath) or {}
-- Don't traverse hidden folders if we're not showing them
if attributes.mode == "directory" and f ~= "." and f ~= ".."
and (G_reader_settings:isTrue("show_hidden") or not util.stringStartsWith(f, "."))
and FileChooser:show_dir(f)
then
if self.include_subfolders and not sys_folders[fullpath] then
table.insert(new_dirs, fullpath)
end
table.insert(self.files, {
dir = d,
name = f,
text = f.."/",
attr = attributes,
callback = function()
self:showFolder(fullpath .. "/")
end,
})
-- Always ignore macOS resource forks, too.
elseif attributes.mode == "file" and not util.stringStartsWith(f, "._")
and (show_unsupported or DocumentRegistry:hasProvider(fullpath))
and FileChooser:show_file(f)
then
table.insert(self.files, {
dir = d,
name = f,
text = f,
mandatory = util.getFriendlySize(attributes.size or 0),
attr = attributes,
callback = function()
ReaderUI:showReader(fullpath)
end,
})
end
end
end
end
self.dirs = new_dirs
end
end
function FileSearcher:setSearchResults()
local keywords = self.search_value
self.results = {}
if keywords == "*" then -- one * to show all files
self.results = self.files
else
if not self.case_sensitive then
keywords = Utf8Proc.lowercase(util.fixUtf8(keywords, "?"))
end
-- replace '.' with '%.'
keywords = keywords:gsub("%.","%%%.")
-- replace '*' with '.*'
keywords = keywords:gsub("%*","%.%*")
-- replace '?' with '.'
keywords = keywords:gsub("%?","%.")
for __,f in pairs(self.files) do
if self.case_sensitive then
if string.find(f.name, keywords) then
table.insert(self.results, f)
end
else
if string.find(Utf8Proc.lowercase(util.fixUtf8(f.name, "?")), keywords) then
table.insert(self.results, f)
end
end
end
end
end
function FileSearcher:close()
UIManager:close(self.search_dialog)
self:readDir() --- @todo this probably doesn't need to be repeated once it's been done
self:setSearchResults() --- @todo doesn't have to be repeated if the search term is the same
if #self.results > 0 then
self:showSearchResults()
else
UIManager:show(
InfoMessage:new{
text = BaseUtil.template(_("No results for '%1'."),
self.search_value)
}
)
end
end
function FileSearcher:onShowFileSearch(search_string)
self.search_dialog = InputDialog:new{
title = _("Enter filename to search for"),
input = search_string or self.search_value,
buttons = {
{
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(self.search_dialog)
end,
},
{
text = _("Home folder"),
enabled = G_reader_settings:has("home_dir"),
callback = function()
self.search_value = self.search_dialog:getInputText()
if self.search_value == "" then return end
self.path = G_reader_settings:readSetting("home_dir")
self:close()
end,
},
{
text = _("Current folder"),
is_enter_default = true,
callback = function()
self.search_value = self.search_dialog:getInputText()
if self.search_value == "" then return end
self.path = self.ui.file_chooser and self.ui.file_chooser.path or self.ui:getLastDirFile()
self:close()
end,
},
},
},
}
self.check_button_case = CheckButton:new{
text = _("Case sensitive"),
checked = self.case_sensitive,
parent = self.search_dialog,
callback = function()
self.case_sensitive = self.check_button_case.checked
end,
}
self.search_dialog:addWidget(self.check_button_case)
self.check_button_subfolders = CheckButton:new{
text = _("Include subfolders"),
checked = self.include_subfolders,
parent = self.search_dialog,
callback = function()
self.include_subfolders = self.check_button_subfolders.checked
end,
}
self.search_dialog:addWidget(self.check_button_subfolders)
UIManager:show(self.search_dialog)
self.search_dialog:onShowKeyboard()
end
function FileSearcher:showSearchResults()
local menu_container = CenterContainer:new{
dimen = Screen:getSize(),
}
self.search_menu = Menu:new{
width = Screen:getWidth() - (Size.margin.fullscreen_popout * 2),
height = Screen:getHeight() - (Size.margin.fullscreen_popout * 2),
show_parent = menu_container,
onMenuHold = self.onMenuHold,
_manager = self,
}
table.insert(menu_container, self.search_menu)
self.search_menu.close_callback = function()
UIManager:close(menu_container)
end
local collate = G_reader_settings:readSetting("collate") or "strcoll"
local reverse_collate = G_reader_settings:isTrue("reverse_collate")
local sorting = FileChooser:getSortingFunction(collate, reverse_collate)
table.sort(self.results, sorting)
self.search_menu:switchItemTable(T(_("Search results (%1)"), #self.results), self.results)
UIManager:show(menu_container)
end
function FileSearcher:onMenuHold(item)
local ReaderUI = require("apps/reader/readerui")
local is_file = item.attr.mode == "file"
local fullpath = item.dir .. "/" .. item.name .. (is_file and "" or "/")
local buttons = {
{
{
text = _("Cancel"),
callback = function()
UIManager:close(self.results_dialog)
end,
},
{
text = _("Show folder"),
callback = function()
UIManager:close(self.results_dialog)
self.close_callback()
self._manager:showFolder(fullpath)
end,
},
},
}
if is_file then
table.insert(buttons[1], {
text = _("Open"),
callback = function()
UIManager:close(self.results_dialog)
self.close_callback()
ReaderUI:showReader(fullpath)
end,
})
end
self.results_dialog = ButtonDialogTitle:new{
title = fullpath,
buttons = buttons,
}
UIManager:show(self.results_dialog)
return true
end
function FileSearcher:showFolder(path)
if self.ui.file_chooser then
local pathname = util.splitFilePathName(path)
self.ui.file_chooser:changeToPath(pathname, path)
else -- called from Reader
self.ui:onClose()
self.ui:showFileManager(path)
end
end
return FileSearcher