mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
Filesearcher: add search in book metadata (#10198)
This commit is contained in:
@@ -134,15 +134,12 @@ function FileManager:setupLayout()
|
||||
-- remember to adjust the height when new item is added to the group
|
||||
path = self.root_path,
|
||||
focused_path = self.focused_file,
|
||||
collate = G_reader_settings:readSetting("collate") or "strcoll",
|
||||
reverse_collate = G_reader_settings:isTrue("reverse_collate"),
|
||||
show_parent = self.show_parent,
|
||||
show_hidden = show_hidden,
|
||||
width = Screen:getWidth(),
|
||||
height = Screen:getHeight() - self.title_bar:getHeight(),
|
||||
is_popout = false,
|
||||
is_borderless = true,
|
||||
has_close_button = true,
|
||||
show_hidden = show_hidden,
|
||||
show_unsupported = show_unsupported,
|
||||
file_filter = function(filename)
|
||||
if DocumentRegistry:hasProvider(filename) then
|
||||
@@ -793,16 +790,6 @@ function FileManager:toggleUnsupportedFiles()
|
||||
G_reader_settings:saveSetting("show_unsupported", self.file_chooser.show_unsupported)
|
||||
end
|
||||
|
||||
function FileManager:setCollate(collate)
|
||||
self.file_chooser:setCollate(collate)
|
||||
G_reader_settings:saveSetting("collate", self.file_chooser.collate)
|
||||
end
|
||||
|
||||
function FileManager:toggleReverseCollate()
|
||||
self.file_chooser:toggleReverseCollate()
|
||||
G_reader_settings:saveSetting("reverse_collate", self.file_chooser.reverse_collate)
|
||||
end
|
||||
|
||||
function FileManager:onClose()
|
||||
logger.dbg("close filemanager")
|
||||
PluginLoader:finalize()
|
||||
@@ -876,7 +863,6 @@ function FileManager:openRandomFile(dir)
|
||||
self:openRandomFile(dir)
|
||||
end,
|
||||
})
|
||||
UIManager:close(self.file_dialog)
|
||||
else
|
||||
UIManager:show(InfoMessage:new {
|
||||
text = _("File not found"),
|
||||
@@ -1138,106 +1124,6 @@ function FileManager:renameFile(file, basename, is_file)
|
||||
end
|
||||
end
|
||||
|
||||
function FileManager:getSortingMenuTable()
|
||||
local fm = self
|
||||
local collates = {
|
||||
strcoll = {_("filename"), _("Sort by filename")},
|
||||
natural = {_("natural"), _("Sort by filename (natural sorting)")},
|
||||
strcoll_mixed = {_("name mixed"), _("Sort by name – mixed files and folders")},
|
||||
access = {_("date read"), _("Sort by last read date")},
|
||||
change = {_("date added"), _("Sort by date added")},
|
||||
modification = {_("date modified"), _("Sort by date modified")},
|
||||
size = {_("size"), _("Sort by size")},
|
||||
type = {_("type"), _("Sort by type")},
|
||||
percent_unopened_first = {_("percent – unopened first"), _("Sort by percent – unopened first")},
|
||||
percent_unopened_last = {_("percent – unopened last"), _("Sort by percent – unopened last")},
|
||||
}
|
||||
local set_collate_table = function(collate)
|
||||
return {
|
||||
text = collates[collate][2],
|
||||
checked_func = function()
|
||||
return fm.file_chooser.collate == collate
|
||||
end,
|
||||
callback = function() fm:setCollate(collate) end,
|
||||
}
|
||||
end
|
||||
local get_collate_percent = function()
|
||||
local collate_type = G_reader_settings:readSetting("collate")
|
||||
if collate_type == "percent_unopened_first" or collate_type == "percent_unopened_last" then
|
||||
return collates[collate_type][2]
|
||||
else
|
||||
return _("Sort by percent")
|
||||
end
|
||||
end
|
||||
return {
|
||||
text_func = function()
|
||||
return T(
|
||||
_("Sort by: %1"),
|
||||
collates[fm.file_chooser.collate][1]
|
||||
)
|
||||
end,
|
||||
sub_item_table = {
|
||||
set_collate_table("strcoll"),
|
||||
set_collate_table("natural"),
|
||||
set_collate_table("strcoll_mixed"),
|
||||
set_collate_table("access"),
|
||||
set_collate_table("change"),
|
||||
set_collate_table("modification"),
|
||||
set_collate_table("size"),
|
||||
set_collate_table("type"),
|
||||
{
|
||||
text_func = get_collate_percent,
|
||||
checked_func = function()
|
||||
return fm.file_chooser.collate == "percent_unopened_first"
|
||||
or fm.file_chooser.collate == "percent_unopened_last"
|
||||
end,
|
||||
sub_item_table = {
|
||||
set_collate_table("percent_unopened_first"),
|
||||
set_collate_table("percent_unopened_last"),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
function FileManager:getStartWithMenuTable()
|
||||
local start_with_setting = G_reader_settings:readSetting("start_with") or "filemanager"
|
||||
local start_withs = {
|
||||
filemanager = {_("file browser"), _("Start with file browser")},
|
||||
history = {_("history"), _("Start with history")},
|
||||
favorites = {_("favorites"), _("Start with favorites")},
|
||||
folder_shortcuts = {_("folder shortcuts"), _("Start with folder shortcuts")},
|
||||
last = {_("last file"), _("Start with last file")},
|
||||
}
|
||||
local set_sw_table = function(start_with)
|
||||
return {
|
||||
text = start_withs[start_with][2],
|
||||
checked_func = function()
|
||||
return start_with_setting == start_with
|
||||
end,
|
||||
callback = function()
|
||||
start_with_setting = start_with
|
||||
G_reader_settings:saveSetting("start_with", start_with)
|
||||
end,
|
||||
}
|
||||
end
|
||||
return {
|
||||
text_func = function()
|
||||
return T(
|
||||
_("Start with: %1"),
|
||||
start_withs[start_with_setting][1]
|
||||
)
|
||||
end,
|
||||
sub_item_table = {
|
||||
set_sw_table("filemanager"),
|
||||
set_sw_table("history"),
|
||||
set_sw_table("favorites"),
|
||||
set_sw_table("folder_shortcuts"),
|
||||
set_sw_table("last"),
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
--- @note: This is the *only* safe way to instantiate a new FileManager instance!
|
||||
function FileManager:showFiles(path, focused_file)
|
||||
-- Warn about and close any pre-existing FM instances first...
|
||||
|
||||
@@ -157,7 +157,7 @@ function BookInfo:show(file, book_props)
|
||||
UIManager:show(widget)
|
||||
end
|
||||
|
||||
function BookInfo:getBookProps(file, book_props)
|
||||
function BookInfo:getBookProps(file, book_props, no_open_document)
|
||||
if DocSettings:hasSidecarFile(file) then
|
||||
local doc_settings = DocSettings:open(file)
|
||||
if not book_props then
|
||||
@@ -185,7 +185,7 @@ function BookInfo:getBookProps(file, book_props)
|
||||
end
|
||||
|
||||
-- If still no book_props (book never opened or empty "stats"), open the document to get them
|
||||
if not book_props then
|
||||
if not book_props and not no_open_document then
|
||||
local document = DocumentRegistry:openDocument(file)
|
||||
if document then
|
||||
local loaded = true
|
||||
|
||||
@@ -3,10 +3,9 @@ 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 FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo")
|
||||
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")
|
||||
@@ -14,39 +13,139 @@ local Utf8Proc = require("ffi/utf8proc")
|
||||
local lfs = require("libs/libkoreader-lfs")
|
||||
local util = require("util")
|
||||
local _ = require("gettext")
|
||||
local N_ = _.ngettext
|
||||
local Screen = require("device").screen
|
||||
local T = require("ffi/util").template
|
||||
local T = BaseUtil.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,
|
||||
include_metadata = false,
|
||||
}
|
||||
|
||||
function FileSearcher:init()
|
||||
self.dirs = {}
|
||||
self.files = {}
|
||||
self.results = {}
|
||||
end
|
||||
|
||||
function FileSearcher:readDir()
|
||||
local ReaderUI = require("apps/reader/readerui")
|
||||
function FileSearcher:onShowFileSearch(search_string)
|
||||
local search_dialog
|
||||
local check_button_case, check_button_subfolders, check_button_metadata
|
||||
search_dialog = InputDialog:new{
|
||||
title = _("Enter text to search for in filename"),
|
||||
input = search_string or self.search_value,
|
||||
buttons = {
|
||||
{
|
||||
{
|
||||
text = _("Cancel"),
|
||||
id = "close",
|
||||
callback = function()
|
||||
UIManager:close(search_dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Home folder"),
|
||||
enabled = G_reader_settings:has("home_dir"),
|
||||
callback = function()
|
||||
self.search_value = search_dialog:getInputText()
|
||||
if self.search_value == "" then return end
|
||||
UIManager:close(search_dialog)
|
||||
self.path = G_reader_settings:readSetting("home_dir")
|
||||
self:doSearch()
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = self.ui.file_chooser and _("Current folder") or _("Book folder"),
|
||||
is_enter_default = true,
|
||||
callback = function()
|
||||
self.search_value = search_dialog:getInputText()
|
||||
if self.search_value == "" 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()
|
||||
end,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
check_button_case = CheckButton:new{
|
||||
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
|
||||
check_button_metadata = CheckButton:new{
|
||||
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
|
||||
|
||||
UIManager:show(search_dialog)
|
||||
search_dialog:onShowKeyboard()
|
||||
end
|
||||
|
||||
function FileSearcher:doSearch()
|
||||
local results
|
||||
local dirs, files = self:getList()
|
||||
-- If we have a FileChooser instance, use it, to be able to make use of its natsort cache
|
||||
if self.ui.file_chooser then
|
||||
results = self.ui.file_chooser:genItemTable(dirs, files)
|
||||
else
|
||||
results = FileChooser:genItemTable(dirs, files)
|
||||
end
|
||||
if #results > 0 then
|
||||
self:showSearchResults(results)
|
||||
else
|
||||
self:showSearchResultsMessage(true)
|
||||
end
|
||||
end
|
||||
|
||||
function FileSearcher:getList()
|
||||
self.no_metadata_count = 0
|
||||
local sys_folders = { -- do not search in sys_folders
|
||||
["/dev"] = true,
|
||||
["/proc"] = true,
|
||||
["/sys"] = true,
|
||||
}
|
||||
local show_hidden = G_reader_settings:isTrue("show_hidden")
|
||||
local show_unsupported = G_reader_settings:isTrue("show_unsupported")
|
||||
self.dirs = {self.path}
|
||||
self.files = {}
|
||||
while #self.dirs ~= 0 do
|
||||
local collate = G_reader_settings:readSetting("collate")
|
||||
local keywords = self.search_value
|
||||
if keywords ~= "*" then -- one * to show all files
|
||||
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("%?","%.")
|
||||
end
|
||||
|
||||
local dirs, files = {}, {}
|
||||
local scan_dirs = {self.path}
|
||||
while #scan_dirs ~= 0 do
|
||||
local new_dirs = {}
|
||||
-- handle each dir
|
||||
for __, d in pairs(self.dirs) do
|
||||
for _, d in ipairs(scan_dirs) do
|
||||
-- handle files in d
|
||||
local ok, iter, dir_obj = pcall(lfs.dir, d)
|
||||
if ok then
|
||||
@@ -58,218 +157,178 @@ function FileSearcher:readDir()
|
||||
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
|
||||
and (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,
|
||||
})
|
||||
if self:isFileMatch(f, fullpath, keywords) then
|
||||
table.insert(dirs, FileChooser:getListItem(f, fullpath, attributes))
|
||||
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,
|
||||
})
|
||||
and (show_unsupported or DocumentRegistry:hasProvider(fullpath))
|
||||
and FileChooser:show_file(f) then
|
||||
if self:isFileMatch(f, fullpath, keywords, true) then
|
||||
table.insert(files, FileChooser:getListItem(f, fullpath, attributes, collate))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.dirs = new_dirs
|
||||
scan_dirs = new_dirs
|
||||
end
|
||||
return dirs, files
|
||||
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)
|
||||
function FileSearcher:isFileMatch(filename, fullpath, keywords, is_file)
|
||||
local metadata_keys = {
|
||||
"authors",
|
||||
"title",
|
||||
"series",
|
||||
"description",
|
||||
"keywords",
|
||||
"language",
|
||||
}
|
||||
if keywords == "*" then
|
||||
return true
|
||||
end
|
||||
if not self.case_sensitive then
|
||||
filename = Utf8Proc.lowercase(util.fixUtf8(filename, "?"))
|
||||
end
|
||||
if string.find(filename, keywords) then
|
||||
return true
|
||||
end
|
||||
if self.include_metadata and is_file and DocumentRegistry:hasProvider(fullpath) then
|
||||
local book_props = self.ui.coverbrowser:getBookInfo(fullpath) or
|
||||
FileManagerBookInfo:getBookProps(fullpath, nil, true)
|
||||
if next(book_props) ~= nil then
|
||||
for _, key in ipairs(metadata_keys) do
|
||||
local prop = book_props[key]
|
||||
if prop and prop ~= "" then
|
||||
if not self.case_sensitive then
|
||||
prop = Utf8Proc.lowercase(util.fixUtf8(prop, "?"))
|
||||
end
|
||||
if key == "description" then
|
||||
prop = util.htmlToPlainTextIfHtml(prop)
|
||||
end
|
||||
if string.find(prop, keywords) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self.no_metadata_count = self.no_metadata_count + 1
|
||||
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()
|
||||
function FileSearcher:showSearchResultsMessage(no_results)
|
||||
local text = no_results and T(_("No results for '%1'."), self.search_value)
|
||||
if self.no_metadata_count == 0 then
|
||||
local InfoMessage = require("ui/widget/infomessage")
|
||||
UIManager:show(InfoMessage:new{ text = text })
|
||||
else
|
||||
UIManager:show(
|
||||
InfoMessage:new{
|
||||
text = BaseUtil.template(_("No results for '%1'."),
|
||||
self.search_value)
|
||||
}
|
||||
)
|
||||
local txt = T(N_("1 book has been skipped.", "%1 books have been skipped.",
|
||||
self.no_metadata_count), self.no_metadata_count) .. "\n" ..
|
||||
_("Not all books metadata extracted yet.\nExtract metadata now?")
|
||||
text = no_results and text .. "\n\n" .. txt or txt
|
||||
local ConfirmBox = require("ui/widget/confirmbox")
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = text,
|
||||
ok_text = _("Extract"),
|
||||
ok_callback = function()
|
||||
if not no_results then
|
||||
self.search_menu.close_callback()
|
||||
end
|
||||
self.ui.coverbrowser:extractBooksInDirectory(self.path)
|
||||
end
|
||||
})
|
||||
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()
|
||||
function FileSearcher:showSearchResults(results)
|
||||
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),
|
||||
width = Screen:getWidth(),
|
||||
height = Screen:getHeight(),
|
||||
is_borderless = true,
|
||||
is_popout = false,
|
||||
show_parent = menu_container,
|
||||
onMenuSelect = self.onMenuSelect,
|
||||
onMenuHold = self.onMenuHold,
|
||||
handle_hold_on_hold_release = true,
|
||||
_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")
|
||||
-- If we have a FileChooser instance, use it, to be able to make use of its natsort cache
|
||||
local sorting
|
||||
if self.ui.file_chooser then
|
||||
sorting = self.ui.file_chooser:getSortingFunction(collate, reverse_collate)
|
||||
else
|
||||
sorting = FileChooser:getSortingFunction(collate, reverse_collate)
|
||||
end
|
||||
|
||||
table.sort(self.results, sorting)
|
||||
self.search_menu:switchItemTable(T(_("Search results (%1)"), #self.results), self.results)
|
||||
self.search_menu:switchItemTable(T(_("Search results (%1)"), #results), results)
|
||||
UIManager:show(menu_container)
|
||||
if self.no_metadata_count ~= 0 then
|
||||
self:showSearchResultsMessage()
|
||||
end
|
||||
end
|
||||
|
||||
function FileSearcher:onMenuSelect(item)
|
||||
local dialog
|
||||
local buttons = {}
|
||||
if item.is_file then
|
||||
table.insert(buttons, {
|
||||
{
|
||||
text = _("Book information"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
FileManagerBookInfo:show(item.path)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Open"),
|
||||
enabled = DocumentRegistry:hasProvider(item.path),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
self.close_callback()
|
||||
require("apps/reader/readerui"):showReader(item.path)
|
||||
end,
|
||||
},
|
||||
})
|
||||
end
|
||||
table.insert(buttons, {
|
||||
{
|
||||
text = _("Cancel"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Show folder"),
|
||||
callback = function()
|
||||
UIManager:close(dialog)
|
||||
self.close_callback()
|
||||
self._manager:showFolder(item.path)
|
||||
end,
|
||||
},
|
||||
})
|
||||
dialog = ButtonDialogTitle:new{
|
||||
title = item.path,
|
||||
buttons = buttons,
|
||||
}
|
||||
UIManager:show(dialog)
|
||||
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,
|
||||
})
|
||||
if item.is_file then
|
||||
if DocumentRegistry:hasProvider(item.path) then
|
||||
self.close_callback()
|
||||
require("apps/reader/readerui"):showReader(item.path)
|
||||
end
|
||||
else
|
||||
self.close_callback()
|
||||
self._manager:showFolder(item.path)
|
||||
end
|
||||
|
||||
self.results_dialog = ButtonDialogTitle:new{
|
||||
title = fullpath,
|
||||
buttons = buttons,
|
||||
}
|
||||
UIManager:show(self.results_dialog)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
@@ -396,7 +396,7 @@ To:
|
||||
UIManager:show(items)
|
||||
end,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
for _, widget in pairs(self.registered_widgets) do
|
||||
@@ -406,19 +406,48 @@ To:
|
||||
end
|
||||
end
|
||||
|
||||
self.menu_items.sort_by = self.ui:getSortingMenuTable()
|
||||
self.menu_items.sort_by = self:getSortingMenuTable()
|
||||
self.menu_items.reverse_sorting = {
|
||||
text = _("Reverse sorting"),
|
||||
checked_func = function() return self.ui.file_chooser.reverse_collate end,
|
||||
callback = function() self.ui:toggleReverseCollate() end
|
||||
checked_func = function()
|
||||
return G_reader_settings:isTrue("reverse_collate")
|
||||
end,
|
||||
callback = function()
|
||||
G_reader_settings:flipNilOrFalse("reverse_collate")
|
||||
self.ui.file_chooser:refreshPath()
|
||||
end,
|
||||
}
|
||||
self.menu_items.start_with = self.ui:getStartWithMenuTable()
|
||||
self.menu_items.sort_mixed = {
|
||||
text = _("Folders and files mixed"),
|
||||
enabled_func = function()
|
||||
local collate = G_reader_settings:readSetting("collate")
|
||||
return collate ~= "size" and
|
||||
collate ~= "type" and
|
||||
collate ~= "percent_unopened_first" and
|
||||
collate ~= "percent_unopened_last"
|
||||
end,
|
||||
checked_func = function()
|
||||
local collate = G_reader_settings:readSetting("collate")
|
||||
return G_reader_settings:isTrue("collate_mixed") and
|
||||
collate ~= "size" and
|
||||
collate ~= "type" and
|
||||
collate ~= "percent_unopened_first" and
|
||||
collate ~= "percent_unopened_last"
|
||||
end,
|
||||
callback = function()
|
||||
G_reader_settings:flipNilOrFalse("collate_mixed")
|
||||
self.ui.file_chooser:refreshPath()
|
||||
end,
|
||||
}
|
||||
self.menu_items.start_with = self:getStartWithMenuTable()
|
||||
|
||||
if Device:supportsScreensaver() then
|
||||
self.menu_items.screensaver = {
|
||||
text = _("Screensaver"),
|
||||
sub_item_table = require("ui/elements/screensaver_menu"),
|
||||
}
|
||||
end
|
||||
|
||||
-- insert common settings
|
||||
for id, common_setting in pairs(dofile("frontend/ui/elements/common_settings_menu_table.lua")) do
|
||||
self.menu_items[id] = common_setting
|
||||
@@ -501,7 +530,7 @@ To:
|
||||
end
|
||||
end,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
if Device:isKobo() and not Device:isSunxi() then
|
||||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||||
@@ -665,8 +694,8 @@ To:
|
||||
G_reader_settings:flipNilOrFalse("dev_reverse_ui_text_direction")
|
||||
UIManager:askForRestart()
|
||||
end
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
table.insert(self.menu_items.developer_options.sub_item_table, {
|
||||
text_func = function()
|
||||
@@ -800,6 +829,77 @@ dbg:guard(FileManagerMenu, 'setUpdateItemTable',
|
||||
end
|
||||
end)
|
||||
|
||||
function FileManagerMenu:getSortingMenuTable()
|
||||
local collates = {
|
||||
{ _("name"), "strcoll" },
|
||||
{ _("name (natural sorting)"), "natural" },
|
||||
{ _("last read date"), "access" },
|
||||
{ _("date added"), "change" },
|
||||
{ _("date modified"), "modification" },
|
||||
{ _("size"), "size" },
|
||||
{ _("type"), "type" },
|
||||
{ _("percent – unopened first"), "percent_unopened_first" },
|
||||
{ _("percent – unopened last"), "percent_unopened_last" },
|
||||
}
|
||||
local sub_item_table = {}
|
||||
for i, v in ipairs(collates) do
|
||||
table.insert(sub_item_table, {
|
||||
text = v[1],
|
||||
checked_func = function()
|
||||
return v[2] == G_reader_settings:readSetting("collate", "strcoll")
|
||||
end,
|
||||
callback = function()
|
||||
G_reader_settings:saveSetting("collate", v[2])
|
||||
self.ui.file_chooser:refreshPath()
|
||||
end,
|
||||
})
|
||||
end
|
||||
return {
|
||||
text_func = function()
|
||||
local collate = G_reader_settings:readSetting("collate")
|
||||
for i, v in ipairs(collates) do
|
||||
if v[2] == collate then
|
||||
return T(_("Sort by: %1"), v[1])
|
||||
end
|
||||
end
|
||||
end,
|
||||
sub_item_table = sub_item_table,
|
||||
}
|
||||
end
|
||||
|
||||
function FileManagerMenu:getStartWithMenuTable()
|
||||
local start_withs = {
|
||||
{ _("file browser"), "filemanager" },
|
||||
{ _("history"), "history" },
|
||||
{ _("favorites"), "favorites" },
|
||||
{ _("folder shortcuts"), "folder_shortcuts" },
|
||||
{ _("last file"), "last" },
|
||||
}
|
||||
local sub_item_table = {}
|
||||
for i, v in ipairs(start_withs) do
|
||||
table.insert(sub_item_table, {
|
||||
text = v[1],
|
||||
checked_func = function()
|
||||
return v[2] == G_reader_settings:readSetting("start_with", "filemanager")
|
||||
end,
|
||||
callback = function()
|
||||
G_reader_settings:saveSetting("start_with", v[2])
|
||||
end,
|
||||
})
|
||||
end
|
||||
return {
|
||||
text_func = function()
|
||||
local start_with = G_reader_settings:readSetting("start_with")
|
||||
for i, v in ipairs(start_withs) do
|
||||
if v[2] == start_with then
|
||||
return T(_("Start with: %1"), v[1])
|
||||
end
|
||||
end
|
||||
end,
|
||||
sub_item_table = sub_item_table,
|
||||
}
|
||||
end
|
||||
|
||||
function FileManagerMenu:moveBookMetadata()
|
||||
local DocSettings = require("docsettings")
|
||||
local FileChooser = self.ui.file_chooser
|
||||
|
||||
@@ -15,6 +15,7 @@ local order = {
|
||||
"----------------------------",
|
||||
"sort_by",
|
||||
"reverse_sorting",
|
||||
"sort_mixed",
|
||||
"----------------------------",
|
||||
"start_with",
|
||||
},
|
||||
|
||||
@@ -71,8 +71,6 @@ local FileChooser = Menu:extend{
|
||||
"^%.fat32%-epoch$",
|
||||
"^%.metadata%.json$",
|
||||
},
|
||||
collate = "strcoll",
|
||||
reverse_collate = false,
|
||||
path_items = nil, -- hash, store last browsed location (item index) for each path
|
||||
goto_letter = true,
|
||||
}
|
||||
@@ -97,120 +95,115 @@ function FileChooser:show_file(filename)
|
||||
end
|
||||
|
||||
function FileChooser:init()
|
||||
self.up_folder_arrow = BD.mirroredUILayout() and BD.ltr("../ ⬆") or "⬆ ../"
|
||||
self.path_items = {}
|
||||
self.width = Screen:getWidth()
|
||||
self.list = function(path, dirs, files, count_only)
|
||||
-- lfs.dir directory without permission will give error
|
||||
local ok, iter, dir_obj = pcall(lfs.dir, path)
|
||||
if ok then
|
||||
unreadable_dir_content[path] = nil
|
||||
for f in iter, dir_obj do
|
||||
if self.show_hidden or not util.stringStartsWith(f, ".") then
|
||||
local filename = path.."/"..f
|
||||
local attributes = lfs.attributes(filename)
|
||||
if attributes ~= nil then
|
||||
local item = true
|
||||
if attributes.mode == "directory" and f ~= "." and f ~= ".." then
|
||||
if self:show_dir(f) then
|
||||
if not count_only then
|
||||
item = {name = f,
|
||||
fullpath = filename,
|
||||
attr = attributes,}
|
||||
end
|
||||
table.insert(dirs, item)
|
||||
self.item_table = self:genItemTableFromPath(self.path)
|
||||
Menu.init(self) -- call parent's init()
|
||||
end
|
||||
|
||||
function FileChooser:getList(path, collate)
|
||||
local dirs, files = {}, {}
|
||||
-- lfs.dir directory without permission will give error
|
||||
local ok, iter, dir_obj = pcall(lfs.dir, path)
|
||||
if ok then
|
||||
unreadable_dir_content[path] = nil
|
||||
for f in iter, dir_obj do
|
||||
if self.show_hidden or not util.stringStartsWith(f, ".") then
|
||||
local filename = path.."/"..f
|
||||
local attributes = lfs.attributes(filename)
|
||||
if attributes ~= nil then
|
||||
local item = true
|
||||
if attributes.mode == "directory" and f ~= "." and f ~= ".." then
|
||||
if self:show_dir(f) then
|
||||
if collate then -- when collate == nil count only to display in folder mandatory
|
||||
item = self:getListItem(f, filename, attributes)
|
||||
end
|
||||
-- Always ignore macOS resource forks.
|
||||
elseif attributes.mode == "file" and not util.stringStartsWith(f, "._") then
|
||||
if self:show_file(f) then
|
||||
if not count_only then
|
||||
local percent_finished
|
||||
if self.collate == "percent_unopened_first" or self.collate == "percent_unopened_last" then
|
||||
if DocSettings:hasSidecarFile(filename) then
|
||||
local docinfo = DocSettings:open(filename)
|
||||
percent_finished = docinfo:readSetting("percent_finished")
|
||||
end
|
||||
end
|
||||
item = {name = f,
|
||||
fullpath = filename,
|
||||
attr = attributes,
|
||||
suffix = util.getFileNameSuffix(f),
|
||||
percent_finished = percent_finished or 0,}
|
||||
end
|
||||
table.insert(files, item)
|
||||
table.insert(dirs, item)
|
||||
end
|
||||
-- Always ignore macOS resource forks.
|
||||
elseif attributes.mode == "file" and not util.stringStartsWith(f, "._") then
|
||||
if self:show_file(f) then
|
||||
if collate then -- when collate == nil count only to display in folder mandatory
|
||||
item = self:getListItem(f, filename, attributes, collate)
|
||||
end
|
||||
table.insert(files, item)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else -- error, probably "permission denied"
|
||||
if unreadable_dir_content[path] then
|
||||
-- Add this dummy item that will be replaced with a message
|
||||
-- by genItemTableFromPath()
|
||||
table.insert(dirs, {
|
||||
name = "./.",
|
||||
fullpath = path,
|
||||
attr = lfs.attributes(path),
|
||||
})
|
||||
-- If we knew about some content (if we had come up from them
|
||||
-- to this directory), have them shown
|
||||
for k, v in pairs(unreadable_dir_content[path]) do
|
||||
if v.attr and v.attr.mode == "directory" then
|
||||
table.insert(dirs, v)
|
||||
else
|
||||
table.insert(files, v)
|
||||
end
|
||||
end
|
||||
else -- error, probably "permission denied"
|
||||
if unreadable_dir_content[path] then
|
||||
-- Add this dummy item that will be replaced with a message by genItemTable()
|
||||
table.insert(dirs, self:getListItem("./.", path, lfs.attributes(path)))
|
||||
-- If we knew about some content (if we had come up from them
|
||||
-- to this directory), have them shown
|
||||
for k, v in pairs(unreadable_dir_content[path]) do
|
||||
if v.attr and v.attr.mode == "directory" then
|
||||
table.insert(dirs, v)
|
||||
else
|
||||
table.insert(files, v)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return dirs, files
|
||||
end
|
||||
|
||||
self.item_table = self:genItemTableFromPath(self.path)
|
||||
Menu.init(self) -- call parent's init()
|
||||
function FileChooser:getListItem(f, filename, attributes, collate)
|
||||
local item = {
|
||||
text = f,
|
||||
fullpath = filename,
|
||||
attr = attributes,
|
||||
}
|
||||
if collate then -- file
|
||||
if G_reader_settings:readSetting("show_file_in_bold") then
|
||||
item.opened = DocSettings:hasSidecarFile(filename)
|
||||
end
|
||||
if collate == "type" then
|
||||
item.suffix = util.getFileNameSuffix(f)
|
||||
elseif collate == "percent_unopened_first" or collate == "percent_unopened_last" then
|
||||
local percent_finished
|
||||
item.opened = DocSettings:hasSidecarFile(filename)
|
||||
if item.opened then
|
||||
local doc_settings = DocSettings:open(filename)
|
||||
percent_finished = doc_settings:readSetting("percent_finished")
|
||||
end
|
||||
item.percent_finished = percent_finished or 0
|
||||
end
|
||||
end
|
||||
return item
|
||||
end
|
||||
|
||||
function FileChooser:getSortingFunction(collate, reverse_collate)
|
||||
local sorting
|
||||
if collate == "strcoll" then
|
||||
sorting = function(a, b)
|
||||
return ffiUtil.strcoll(a.name, b.name)
|
||||
return ffiUtil.strcoll(a.text, b.text)
|
||||
end
|
||||
elseif collate == "natural" then
|
||||
local natsort
|
||||
-- Only keep the cache if we're an *instance* of FileChooser
|
||||
if self ~= FileChooser then
|
||||
natsort, self.natsort_cache = sort.natsort_cmp(self.natsort_cache)
|
||||
sorting = function(a, b)
|
||||
return natsort(a.name, b.name)
|
||||
end
|
||||
else
|
||||
natsort = sort.natsort_cmp()
|
||||
sorting = function(a, b)
|
||||
return natsort(a.name, b.name)
|
||||
end
|
||||
end
|
||||
elseif self.collate == "strcoll_mixed" then
|
||||
sorting = function(a, b)
|
||||
if b.text == self.up_folder_arrow then return false end
|
||||
return ffiUtil.strcoll(a.text, b.text)
|
||||
return natsort(a.text, b.text)
|
||||
end
|
||||
elseif collate == "access" then
|
||||
sorting = function(a, b)
|
||||
return a.attr.access > b.attr.access
|
||||
end
|
||||
elseif collate == "change" then
|
||||
sorting = function(a, b)
|
||||
return a.attr.change > b.attr.change
|
||||
end
|
||||
elseif collate == "modification" then
|
||||
sorting = function(a, b)
|
||||
return a.attr.modification > b.attr.modification
|
||||
end
|
||||
elseif collate == "change" then
|
||||
sorting = function(a, b)
|
||||
local a_opened = DocSettings:hasSidecarFile(a.fullpath)
|
||||
local b_opened = DocSettings:hasSidecarFile(b.fullpath)
|
||||
if a_opened == b_opened then
|
||||
return a.attr.change > b.attr.change
|
||||
end
|
||||
return b_opened
|
||||
end
|
||||
elseif collate == "size" then
|
||||
sorting = function(a, b)
|
||||
return a.attr.size < b.attr.size
|
||||
@@ -220,22 +213,20 @@ function FileChooser:getSortingFunction(collate, reverse_collate)
|
||||
if (a.suffix or b.suffix) and a.suffix ~= b.suffix then
|
||||
return ffiUtil.strcoll(a.suffix, b.suffix)
|
||||
end
|
||||
return ffiUtil.strcoll(a.name, b.name)
|
||||
return ffiUtil.strcoll(a.text, b.text)
|
||||
end
|
||||
else -- collate == "percent_unopened_first" or collate == "percent_unopened_last"
|
||||
sorting = function(a, b)
|
||||
local a_opened = DocSettings:hasSidecarFile(a.fullpath)
|
||||
local b_opened = DocSettings:hasSidecarFile(b.fullpath)
|
||||
if a_opened == b_opened then
|
||||
if a_opened then
|
||||
if a.opened == b.opened then
|
||||
if a.opened then
|
||||
return a.percent_finished < b.percent_finished
|
||||
end
|
||||
return a.name < b.name
|
||||
return ffiUtil.strcoll(a.text, b.text)
|
||||
end
|
||||
if collate == "percent_unopened_first" then
|
||||
return b_opened
|
||||
return b.opened
|
||||
end
|
||||
return a_opened
|
||||
return a.opened
|
||||
end
|
||||
end
|
||||
|
||||
@@ -248,59 +239,47 @@ function FileChooser:getSortingFunction(collate, reverse_collate)
|
||||
end
|
||||
|
||||
function FileChooser:genItemTableFromPath(path)
|
||||
local dirs = {}
|
||||
local files = {}
|
||||
local collate = G_reader_settings:readSetting("collate", "strcoll")
|
||||
local dirs, files = self:getList(path, collate)
|
||||
return self:genItemTable(dirs, files, path)
|
||||
end
|
||||
|
||||
self.list(path, dirs, files)
|
||||
|
||||
local sorting = self:getSortingFunction(self.collate, self.reverse_collate)
|
||||
|
||||
if self.collate ~= "strcoll_mixed" then
|
||||
function FileChooser:genItemTable(dirs, files, path)
|
||||
local collate = G_reader_settings:readSetting("collate")
|
||||
local collate_not_for_mixed = collate == "size" or
|
||||
collate == "type" or
|
||||
collate == "percent_unopened_first" or
|
||||
collate == "percent_unopened_last"
|
||||
local collate_mixed = G_reader_settings:isTrue("collate_mixed")
|
||||
local reverse_collate = G_reader_settings:isTrue("reverse_collate")
|
||||
local sorting = self:getSortingFunction(collate, reverse_collate)
|
||||
if collate_not_for_mixed or not collate_mixed then
|
||||
table.sort(files, sorting)
|
||||
if self.collate == "size" or
|
||||
self.collate == "type" or
|
||||
self.collate == "percent_unopened_first" or
|
||||
self.collate == "percent_unopened_last" then
|
||||
sorting = self:getSortingFunction("strcoll", self.reverse_collate)
|
||||
if collate_not_for_mixed then
|
||||
sorting = self:getSortingFunction("strcoll", reverse_collate)
|
||||
end
|
||||
table.sort(dirs, sorting)
|
||||
end
|
||||
if path ~= "/" and not (G_reader_settings:isTrue("lock_home_folder") and
|
||||
path == G_reader_settings:readSetting("home_dir")) then
|
||||
table.insert(dirs, 1, {name = ".."})
|
||||
end
|
||||
if self.show_current_dir_for_hold then
|
||||
table.insert(dirs, 1, {name = "."})
|
||||
end
|
||||
|
||||
local item_table = {}
|
||||
|
||||
for i, dir in ipairs(dirs) do
|
||||
local subdir_path = self.path.."/"..dir.name
|
||||
local text, bidi_wrap_func, istr
|
||||
if dir.name == ".." then
|
||||
text = self.up_folder_arrow
|
||||
elseif dir.name == "." then -- possible with show_current_dir_for_hold
|
||||
text = _("Long-press to choose current folder")
|
||||
elseif dir.name == "./." then -- added as content of an unreadable directory
|
||||
local text, bidi_wrap_func, mandatory
|
||||
if dir.text == "./." then -- added as content of an unreadable directory
|
||||
text = _("Current folder not readable. Some content may not be shown.")
|
||||
else
|
||||
text = dir.name.."/"
|
||||
text = dir.text.."/"
|
||||
bidi_wrap_func = BD.directory
|
||||
-- count number of folders and files inside dir
|
||||
local sub_dirs = {}
|
||||
local dir_files = {}
|
||||
self.list(subdir_path, sub_dirs, dir_files, true)
|
||||
istr = T("%1 \u{F016}", #dir_files)
|
||||
if #sub_dirs > 0 then
|
||||
istr = T("%1 \u{F114} ", #sub_dirs) .. istr
|
||||
if path then -- file browser or PathChooser
|
||||
mandatory = self:getMenuItemMandatory(dir)
|
||||
end
|
||||
end
|
||||
table.insert(item_table, {
|
||||
text = text,
|
||||
attr = dir.attr,
|
||||
bidi_wrap_func = bidi_wrap_func,
|
||||
mandatory = istr,
|
||||
path = subdir_path,
|
||||
is_go_up = dir.name == "..",
|
||||
mandatory = mandatory,
|
||||
path = dir.fullpath,
|
||||
})
|
||||
end
|
||||
|
||||
@@ -310,30 +289,47 @@ function FileChooser:genItemTableFromPath(path)
|
||||
local show_file_in_bold = G_reader_settings:readSetting("show_file_in_bold")
|
||||
|
||||
for i, file in ipairs(files) do
|
||||
local full_path = self.path.."/"..file.name
|
||||
local sstr = util.getFriendlySize(file.attr.size or 0)
|
||||
local file_item = {
|
||||
text = file.name,
|
||||
text = file.text,
|
||||
attr = file.attr,
|
||||
bidi_wrap_func = BD.filename,
|
||||
mandatory = sstr,
|
||||
path = full_path,
|
||||
mandatory = self:getMenuItemMandatory(file, collate),
|
||||
path = file.fullpath,
|
||||
is_file = true,
|
||||
}
|
||||
if show_file_in_bold ~= false then
|
||||
file_item.bold = DocSettings:hasSidecarFile(full_path)
|
||||
file_item.bold = file.opened
|
||||
if show_file_in_bold ~= "opened" then
|
||||
file_item.bold = not file_item.bold
|
||||
end
|
||||
end
|
||||
if self.filemanager and self.filemanager.selected_files and self.filemanager.selected_files[full_path] then
|
||||
if self.filemanager and self.filemanager.selected_files and self.filemanager.selected_files[file.fullpath] then
|
||||
file_item.dim = true
|
||||
end
|
||||
table.insert(item_table, file_item)
|
||||
end
|
||||
|
||||
if self.collate == "strcoll_mixed" then
|
||||
if not collate_not_for_mixed and collate_mixed then
|
||||
table.sort(item_table, sorting)
|
||||
end
|
||||
|
||||
if path then -- file browser or PathChooser
|
||||
if path ~= "/" and not (G_reader_settings:isTrue("lock_home_folder") and
|
||||
path == G_reader_settings:readSetting("home_dir")) then
|
||||
table.insert(item_table, 1, {
|
||||
text = BD.mirroredUILayout() and BD.ltr("../ ⬆") or "⬆ ../",
|
||||
path = path.."/..",
|
||||
is_go_up = true,
|
||||
})
|
||||
end
|
||||
if self.show_current_dir_for_hold then
|
||||
table.insert(item_table, 1, {
|
||||
text = _("Long-press to choose current folder"),
|
||||
path = path.."/.",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- lfs.dir iterated node string may be encoded with some weird codepage on
|
||||
-- Windows we need to encode them to utf-8
|
||||
if ffi.os == "Windows" then
|
||||
@@ -347,6 +343,31 @@ function FileChooser:genItemTableFromPath(path)
|
||||
return item_table
|
||||
end
|
||||
|
||||
function FileChooser:getMenuItemMandatory(item, collate)
|
||||
local text
|
||||
if collate then -- file
|
||||
-- display the sorting parameter in mandatory
|
||||
if collate == "access" then
|
||||
text = os.date("%Y-%m-%d %H:%M", item.attr.access)
|
||||
elseif collate == "change" then
|
||||
text = os.date("%Y-%m-%d %H:%M", item.attr.change)
|
||||
elseif collate == "modification" then
|
||||
text = os.date("%Y-%m-%d %H:%M", item.attr.modification)
|
||||
elseif collate == "percent_unopened_first" or collate == "percent_unopened_last" then
|
||||
text = item.opened and string.format("%d %%", 100 * item.percent_finished) or "–"
|
||||
else
|
||||
text = util.getFriendlySize(item.attr.size or 0)
|
||||
end
|
||||
else -- folder, count number of folders and files inside it
|
||||
local sub_dirs, dir_files = self:getList(item.fullpath)
|
||||
text = T("%1 \u{F016}", #dir_files)
|
||||
if #sub_dirs > 0 then
|
||||
text = T("%1 \u{F114} ", #sub_dirs) .. text
|
||||
end
|
||||
end
|
||||
return text
|
||||
end
|
||||
|
||||
function FileChooser:updateItems(select_number)
|
||||
Menu.updateItems(self, select_number) -- call parent's updateItems()
|
||||
self:mergeTitleBarIntoLayout()
|
||||
@@ -444,16 +465,6 @@ function FileChooser:toggleUnsupportedFiles()
|
||||
self:refreshPath()
|
||||
end
|
||||
|
||||
function FileChooser:setCollate(collate)
|
||||
self.collate = collate
|
||||
self:refreshPath()
|
||||
end
|
||||
|
||||
function FileChooser:toggleReverseCollate()
|
||||
self.reverse_collate = not self.reverse_collate
|
||||
self:refreshPath()
|
||||
end
|
||||
|
||||
function FileChooser:onMenuSelect(item)
|
||||
-- parent directory of dir without permission get nil mode
|
||||
-- we need to change to parent path in this case
|
||||
@@ -542,16 +553,14 @@ function FileChooser:showSetProviderButtons(file, one_time_providers)
|
||||
},
|
||||
})
|
||||
end
|
||||
if one_time_providers and #one_time_providers > 0 then
|
||||
for ___, provider in ipairs(one_time_providers) do
|
||||
provider.one_time_provider = true
|
||||
table.insert(radio_buttons, {
|
||||
{
|
||||
text = provider.provider_name,
|
||||
provider = provider,
|
||||
},
|
||||
})
|
||||
end
|
||||
for _, provider in ipairs(one_time_providers) do
|
||||
provider.one_time_provider = true
|
||||
table.insert(radio_buttons, {
|
||||
{
|
||||
text = provider.provider_name,
|
||||
provider = provider,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
table.insert(buttons, {
|
||||
|
||||
@@ -131,7 +131,7 @@ function MenuItem:init()
|
||||
},
|
||||
HoldSelect = {
|
||||
GestureRange:new{
|
||||
ges = "hold",
|
||||
ges = self.handle_hold_on_hold_release and "hold_release" or "hold",
|
||||
range = self.dimen,
|
||||
},
|
||||
},
|
||||
@@ -1067,6 +1067,7 @@ function Menu:updateItems(select_number)
|
||||
with_dots = self.with_dots,
|
||||
line_color = self.line_color,
|
||||
items_padding = self.items_padding,
|
||||
handle_hold_on_hold_release = self.handle_hold_on_hold_release,
|
||||
}
|
||||
table.insert(self.item_group, item_tmp)
|
||||
-- this is for focus manager
|
||||
|
||||
@@ -668,4 +668,15 @@ function CoverBrowser:setupCollectionDisplayMode(display_mode)
|
||||
end
|
||||
end
|
||||
|
||||
function CoverBrowser:getBookInfo(file)
|
||||
return BookInfoManager:getBookInfo(file)
|
||||
end
|
||||
|
||||
function CoverBrowser:extractBooksInDirectory(path)
|
||||
local Trapper = require("ui/trapper")
|
||||
Trapper:wrap(function()
|
||||
BookInfoManager:extractBooksInDirectory(path)
|
||||
end)
|
||||
end
|
||||
|
||||
return CoverBrowser
|
||||
|
||||
Reference in New Issue
Block a user