DocSettings: add support of centralized sdr storage (#10132)

reviewable/pr10135/r1
hius07 1 year ago committed by GitHub
parent e55b60175b
commit 15605291c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -182,13 +182,6 @@ function FileManager:setupLayout()
return true return true
end 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) function file_chooser:onFileHold(file)
if file_manager.select_mode then if file_manager.select_mode then
file_manager:tapPlus() file_manager:tapPlus()
@ -207,16 +200,16 @@ function FileManager:setupLayout()
text = C_("File", "Copy"), text = C_("File", "Copy"),
enabled = is_not_parent_folder, enabled = is_not_parent_folder,
callback = function() callback = function()
copyFile(file)
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
file_manager:copyFile(file)
end, end,
}, },
{ {
text = C_("File", "Paste"), text = C_("File", "Paste"),
enabled = file_manager.clipboard and true or false, enabled = file_manager.clipboard and true or false,
callback = function() callback = function()
pasteHere(file)
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
file_manager:pasteHere(file)
end, end,
}, },
{ {
@ -236,8 +229,8 @@ function FileManager:setupLayout()
text = _("Cut"), text = _("Cut"),
enabled = is_not_parent_folder, enabled = is_not_parent_folder,
callback = function() callback = function()
cutFile(file)
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
file_manager:cutFile(file)
end, end,
}, },
{ {
@ -245,16 +238,10 @@ function FileManager:setupLayout()
enabled = is_not_parent_folder, enabled = is_not_parent_folder,
callback = function() callback = function()
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
UIManager:show(ConfirmBox:new{ local function post_delete_callback()
text = is_file and T(_("Delete file?\n\n%1\n\nIf you delete a file, it is permanently lost."), BD.filepath(file)) or self:refreshPath()
T(_("Delete folder?\n\n%1\n\nIf you delete a folder, its content is permanently lost."), BD.filepath(file)), end
ok_text = _("Delete"), file_manager:showDeleteFileDialog(file, post_delete_callback)
ok_callback = function()
deleteFile(file)
require("readhistory"):fileDeleted(file)
self:refreshPath()
end,
})
end, end,
}, },
{ {
@ -262,123 +249,54 @@ function FileManager:setupLayout()
enabled = is_not_parent_folder, enabled = is_not_parent_folder,
callback = function() callback = function()
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
file_manager.rename_dialog = InputDialog:new{ file_manager:showRenameFileDialog(file, is_file)
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()
end, 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 if is_file then
local function status_button_callback() local function status_button_callback()
self:refreshPath()
UIManager:close(self.file_dialog) 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 end
table.insert(buttons, filemanagerutil.genStatusButtonsRow(file, status_button_callback))
table.insert(buttons, {}) -- separator
table.insert(buttons, { table.insert(buttons, {
filemanagerutil.genResetSettingsButton(file, nil, status_button_callback), filemanagerutil.genResetSettingsButton(file, status_button_callback),
{ {
text_func = function() text_func = function()
if ReadCollection:checkItemExist(file) then return ReadCollection:checkItemExist(file)
return _("Remove from favorites") and _("Remove from favorites") or _("Add to favorites")
else
return _("Add to favorites")
end
end, end,
enabled = DocumentRegistry:getProviders(file) ~= nil, enabled = has_provider,
callback = function() callback = function()
UIManager:close(self.file_dialog)
if ReadCollection:checkItemExist(file) then if ReadCollection:checkItemExist(file) then
ReadCollection:removeItem(file) ReadCollection:removeItem(file)
else else
ReadCollection:addItem(file) ReadCollection:addItem(file)
end end
UIManager:close(self.file_dialog)
end, end,
}, },
}) })
table.insert(buttons, { table.insert(buttons, {
{ {
text = _("Open with…"), text = _("Open with…"),
enabled = DocumentRegistry:getProviders(file) == nil or #(DocumentRegistry:getProviders(file)) > 1 or file_manager.texteditor,
callback = function() callback = function()
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
local one_time_providers = {} local one_time_providers = {
table.insert(one_time_providers, { {
provider_name = _("Text viewer"), provider_name = _("Text viewer"),
callback = function() callback = function()
file_manager:openTextViewer(file) file_manager:openTextViewer(file)
end, end,
}) },
}
if file_manager.texteditor then if file_manager.texteditor then
table.insert(one_time_providers, { table.insert(one_time_providers, {
provider_name = _("Text editor"), provider_name = _("Text editor"),
@ -393,18 +311,24 @@ function FileManager:setupLayout()
{ {
text = _("Book information"), text = _("Book information"),
id = "book_information", -- used by covermenu id = "book_information", -- used by covermenu
enabled = FileManagerBookInfo:isSupported(file),
callback = function() callback = function()
FileManagerBookInfo:show(file)
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
FileManagerBookInfo:show(file)
end, 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 if FileManagerConverter:isSupported(file) then
table.insert(buttons, { table.insert(buttons, {
{ {
text = _("Convert"), text = _("Convert"),
enabled = true,
callback = function() callback = function()
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
FileManagerConverter:showConvertButtons(file, self) FileManagerConverter:showConvertButtons(file, self)
@ -413,28 +337,21 @@ function FileManager:setupLayout()
}) })
end end
end end
if is_folder then if is_folder then
local realpath = BaseUtil.realpath(file)
table.insert(buttons, { table.insert(buttons, {
{ {
text = _("Set as HOME folder"), text = _("Set as HOME folder"),
callback = function() callback = function()
setHome(realpath)
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
file_manager:setHome(BaseUtil.realpath(file))
end end
} }
}) })
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{ self.file_dialog = ButtonDialogTitle:new{
title = title, title = is_file and BD.filename(file:match("([^/]+)$")) or BD.directory(file:match("([^/]+)$")),
title_align = "center", title_align = "center",
buttons = buttons, buttons = buttons,
} }
@ -607,9 +524,9 @@ function FileManager:tapPlus()
{ {
text = _("Select all files in folder"), text = _("Select all files in folder"),
callback = function() callback = function()
UIManager:close(self.file_dialog)
self.file_chooser:selectAllFilesInFolder() self.file_chooser:selectAllFilesInFolder()
self:onRefresh() self:onRefresh()
UIManager:close(self.file_dialog)
end, end,
}, },
{ {
@ -620,13 +537,13 @@ function FileManager:tapPlus()
text = _("Copy selected files to the current folder?"), text = _("Copy selected files to the current folder?"),
ok_text = _("Copy"), ok_text = _("Copy"),
ok_callback = function() ok_callback = function()
UIManager:close(self.file_dialog)
self.cutfile = false self.cutfile = false
for file in pairs(self.selected_files) do for file in pairs(self.selected_files) do
self.clipboard = file self.clipboard = file
self:pasteHere(self.file_chooser.path) self:pasteHere(self.file_chooser.path)
end end
self:onToggleSelectMode() self:onToggleSelectMode()
UIManager:close(self.file_dialog)
end, end,
}) })
end end
@ -637,9 +554,9 @@ function FileManager:tapPlus()
text = _("Deselect all"), text = _("Deselect all"),
enabled = actions_enabled, enabled = actions_enabled,
callback = function() callback = function()
UIManager:close(self.file_dialog)
self.selected_files = {} self.selected_files = {}
self:onRefresh() self:onRefresh()
UIManager:close(self.file_dialog)
end, end,
}, },
{ {
@ -650,13 +567,13 @@ function FileManager:tapPlus()
text = _("Move selected files to the current folder?"), text = _("Move selected files to the current folder?"),
ok_text = _("Move"), ok_text = _("Move"),
ok_callback = function() ok_callback = function()
UIManager:close(self.file_dialog)
self.cutfile = true self.cutfile = true
for file in pairs(self.selected_files) do for file in pairs(self.selected_files) do
self.clipboard = file self.clipboard = file
self:pasteHere(self.file_chooser.path) self:pasteHere(self.file_chooser.path)
end end
self:onToggleSelectMode() self:onToggleSelectMode()
UIManager:close(self.file_dialog)
end, end,
}) })
end end
@ -666,8 +583,8 @@ function FileManager:tapPlus()
{ {
text = _("Exit select mode"), text = _("Exit select mode"),
callback = function() callback = function()
self:onToggleSelectMode()
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
self:onToggleSelectMode()
end, end,
}, },
{ {
@ -678,19 +595,17 @@ function FileManager:tapPlus()
text = _("Delete selected files?\nIf you delete a file, it is permanently lost."), text = _("Delete selected files?\nIf you delete a file, it is permanently lost."),
ok_text = _("Delete"), ok_text = _("Delete"),
ok_callback = function() ok_callback = function()
local readhistory = require("readhistory") UIManager:close(self.file_dialog)
for file in pairs(self.selected_files) do for file in pairs(self.selected_files) do
self:deleteFile(file) self:deleteFile(file, true) -- only files can be selected
readhistory:fileDeleted(file)
end end
self:onToggleSelectMode() self:onToggleSelectMode()
UIManager:close(self.file_dialog)
end, end,
}) })
end end
}, },
}, },
{}, {}, -- separator
{ {
{ {
text = _("New folder"), text = _("New folder"),
@ -715,8 +630,8 @@ function FileManager:tapPlus()
{ {
text = _("Select files"), text = _("Select files"),
callback = function() callback = function()
self:onToggleSelectMode(true) -- no full screen refresh
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
self:onToggleSelectMode(true) -- no full screen refresh
end, end,
}, },
}, },
@ -734,9 +649,8 @@ function FileManager:tapPlus()
text = _("Paste"), text = _("Paste"),
enabled = self.clipboard and true or false, enabled = self.clipboard and true or false,
callback = function() callback = function()
self:pasteHere(self.file_chooser.path)
self:onRefresh()
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
self:pasteHere(self.file_chooser.path)
end, end,
}, },
}, },
@ -744,8 +658,8 @@ function FileManager:tapPlus()
{ {
text = _("Set as HOME folder"), text = _("Set as HOME folder"),
callback = function() callback = function()
self:setHome(self.file_chooser.path)
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
self:setHome(self.file_chooser.path)
end end
} }
}, },
@ -753,8 +667,8 @@ function FileManager:tapPlus()
{ {
text = _("Go to HOME folder"), text = _("Go to HOME folder"),
callback = function() callback = function()
self:goHome()
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
self:goHome()
end end
} }
}, },
@ -762,8 +676,8 @@ function FileManager:tapPlus()
{ {
text = _("Open random document"), text = _("Open random document"),
callback = function() callback = function()
self:openRandomFile(self.file_chooser.path)
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
self:openRandomFile(self.file_chooser.path)
end end
} }
}, },
@ -771,52 +685,47 @@ function FileManager:tapPlus()
{ {
text = _("Folder shortcuts"), text = _("Folder shortcuts"),
callback = function() callback = function()
self:handleEvent(Event:new("ShowFolderShortcutsDialog"))
UIManager:close(self.file_dialog) UIManager:close(self.file_dialog)
self:handleEvent(Event:new("ShowFolderShortcutsDialog"))
end 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 if Device:hasExternalSD() then
table.insert(buttons, 5, { table.insert(buttons, 4, { -- after "Paste" or "Import files here" button
{ {
text_func = function() text_func = function()
if Device:isValidPath(self.file_chooser.path) then return Device:isValidPath(self.file_chooser.path)
return _("Switch to SDCard") and _("Switch to SDCard") or _("Switch to internal storage")
else
return _("Switch to internal storage")
end
end, end,
callback = function() callback = function()
UIManager:close(self.file_dialog)
if Device:isValidPath(self.file_chooser.path) then if Device:isValidPath(self.file_chooser.path) then
local ok, sd_path = Device:hasExternalSD() local ok, sd_path = Device:hasExternalSD()
UIManager:close(self.file_dialog)
if ok then if ok then
self.file_chooser:changeToPath(sd_path) self.file_chooser:changeToPath(sd_path)
end end
else else
UIManager:close(self.file_dialog)
self.file_chooser:changeToPath(Device.home_dir) self.file_chooser:changeToPath(Device.home_dir)
end end
end, 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 end
self.file_dialog = ButtonDialogTitle:new{ self.file_dialog = ButtonDialogTitle:new{
@ -853,9 +762,7 @@ function FileManager:reinit(path, focused_file)
end end
function FileManager:getCurrentDir() function FileManager:getCurrentDir()
if FileManager.instance then return FileManager.instance and FileManager.instance.file_chooser.path
return FileManager.instance.file_chooser.path
end
end end
function FileManager:toggleHiddenFiles() function FileManager:toggleHiddenFiles()
@ -970,75 +877,64 @@ function FileManager:cutFile(file)
end end
function FileManager:pasteHere(file) function FileManager:pasteHere(file)
if self.clipboard then local orig_file = BaseUtil.realpath(self.clipboard)
file = BaseUtil.realpath(file) local orig_name = BaseUtil.basename(self.clipboard)
local orig_basename = BaseUtil.basename(self.clipboard) local dest_path = BaseUtil.realpath(file)
local orig = BaseUtil.realpath(self.clipboard) dest_path = lfs.attributes(dest_path, "mode") == "directory" and dest_path or dest_path:match("(.*/)")
local dest = lfs.attributes(file, "mode") == "directory" and local dest_file = BaseUtil.joinPath(dest_path, orig_name)
file or file:match("(.*/)") local is_file = lfs.attributes(orig_file, "mode") == "file"
local function infoCopyFile() local function infoCopyFile()
-- if we copy a file, also copy its sidecar directory if self:copyRecursive(orig_file, dest_path) then
if DocSettings:hasSidecarFile(orig) then if is_file then
BaseUtil.execute(self.cp_bin, "-r", DocSettings:getSidecarDir(orig), dest) DocSettings:update(orig_file, dest_file, true)
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",
})
end 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
end
local function infoMoveFile() local function infoMoveFile()
-- if we move a file, also move its sidecar directory if self:moveFile(orig_file, dest_path) then
if DocSettings:hasSidecarFile(orig) then if is_file then
self:moveFile(DocSettings:getSidecarDir(orig), dest) -- dest is always a directory DocSettings:update(orig_file, dest_file)
end require("readhistory"):updateItemByPath(orig_file, dest_file) -- (will update "lastfile" if needed)
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",
})
end end
end ReadCollection:updateItemByPath(orig_file, dest_file)
return true
local info_file
if self.cutfile then
info_file = infoMoveFile
else 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 end
local basename = BaseUtil.basename(self.clipboard) end
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
UIManager:show(ConfirmBox:new { local function doPaste()
text = text, local ok = self.cutfile and infoMoveFile() or infoCopyFile()
ok_text = _("Overwrite"), if ok then
ok_callback = function()
info_file()
self:onRefresh()
self.clipboard = nil
end,
})
else
info_file()
self:onRefresh() self:onRefresh()
self.clipboard = nil self.clipboard = nil
end end
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 end
function FileManager:createFolder() function FileManager:createFolder()
@ -1062,7 +958,7 @@ function FileManager:createFolder()
if new_folder_name == "" then return end if new_folder_name == "" then return end
UIManager:close(input_dialog) UIManager:close(input_dialog)
local new_folder = string.format("%s/%s", self.file_chooser.path, new_folder_name) 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 if check_button_enter_folder.checked then
self.file_chooser:changeToPath(new_folder) self.file_chooser:changeToPath(new_folder)
else else
@ -1089,8 +985,28 @@ function FileManager:createFolder()
input_dialog:onShowKeyboard() input_dialog:onShowKeyboard()
end end
function FileManager:deleteFile(file) function FileManager:showDeleteFileDialog(file, post_delete_callback, pre_delete_callback)
local ok, err, is_dir 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) local file_abs_path = BaseUtil.realpath(file)
if file_abs_path == nil then if file_abs_path == nil then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
@ -1100,24 +1016,19 @@ function FileManager:deleteFile(file)
return return
end end
local is_doc = DocumentRegistry:hasProvider(file_abs_path) local ok, err
if lfs.attributes(file_abs_path, "mode") == "file" then if is_file then
ok, err = os.remove(file_abs_path) ok, err = os.remove(file_abs_path)
else else
ok, err = BaseUtil.purgeDir(file_abs_path) ok, err = BaseUtil.purgeDir(file_abs_path)
is_dir = true
end end
if ok and not err then if ok and not err then
if is_doc then if is_file then
local doc_settings = DocSettings:open(file) DocSettings:update(file)
-- remove cache if any require("readhistory"):fileDeleted(file)
local cache_file_path = doc_settings:readSetting("cache_file_path")
if cache_file_path then
os.remove(cache_file_path)
end
doc_settings:purge()
end end
ReadCollection:removeItemByPath(file, is_dir) ReadCollection:removeItemByPath(file, not is_file)
return true
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = T(_("Failed to delete:\n%1"), BD.filepath(file)), text = T(_("Failed to delete:\n%1"), BD.filepath(file)),
@ -1126,76 +1037,86 @@ function FileManager:deleteFile(file)
end end
end end
function FileManager:renameFile(file) function FileManager:showRenameFileDialog(file, is_file)
local basename = self.rename_dialog:getInputText() local dialog
if BaseUtil.basename(file) ~= basename then dialog = InputDialog:new{
local dest = BaseUtil.joinPath(BaseUtil.dirname(file), basename) title = is_file and _("Rename file") or _("Rename folder"),
local function doRenameFile() input = BaseUtil.basename(file),
if self:moveFile(file, dest) then buttons = {{
require("readhistory"):updateItemByPath(file, dest) -- (will update "lastfile" if needed) {
ReadCollection:updateItemByPath(file, dest) text = _("Cancel"),
if lfs.attributes(dest, "mode") == "file" then id = "close",
local doc = require("docsettings") callback = function()
local move_history = true UIManager:close(dialog)
if lfs.attributes(doc:getHistoryPath(file), "mode") == "file" and end,
not self:moveFile(doc:getHistoryPath(file), doc:getHistoryPath(dest)) then },
move_history = false {
end text = _("Rename"),
if lfs.attributes(doc:getSidecarDir(file), "mode") == "directory" and callback = function()
not self:moveFile(doc:getSidecarDir(file), doc:getSidecarDir(dest)) then local new_name = dialog:getInputText()
move_history = false if new_name ~= "" then
end UIManager:close(dialog)
if not move_history then self:renameFile(file, new_name, is_file)
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",
})
end end
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", 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 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
end
local mode_dest = lfs.attributes(dest, "mode") local mode_dest = lfs.attributes(dest, "mode")
local mode_file = lfs.attributes(file, "mode") if mode_dest then
if mode_dest then local text, ok_text
local text, ok_text if (mode_dest == "file") ~= is_file then
if mode_dest ~= mode_file then if is_file then
if mode_file == "file" then text = T(_("Folder already exists:\n%1\nFile cannot be renamed."), BD.directory(basename))
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",
})
else else
if mode_file == "file" then text = T(_("File already exists:\n%1\nFolder cannot be renamed."), BD.filename(basename))
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,
})
end end
UIManager:show(InfoMessage:new {
text = text,
icon = "notice-warning",
})
else else
doRenameFile() if is_file then
self:onRefresh() 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 end
else
doRenameFile()
end end
end end

@ -1,18 +1,13 @@
local BD = require("ui/bidi")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local Device = require("device") local Device = require("device")
local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo")
local InfoMessage = require("ui/widget/infomessage")
local Menu = require("ui/widget/menu") local Menu = require("ui/widget/menu")
local ReadCollection = require("readcollection") local ReadCollection = require("readcollection")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local Screen = require("device").screen local Screen = require("device").screen
local filemanagerutil = require("apps/filemanager/filemanagerutil") local filemanagerutil = require("apps/filemanager/filemanagerutil")
local BaseUtil = require("ffi/util")
local util = require("util")
local _ = require("gettext") local _ = require("gettext")
local T = BaseUtil.template
local FileManagerCollection = WidgetContainer:extend{ local FileManagerCollection = WidgetContainer:extend{
coll_menu_title = _("Favorites"), coll_menu_title = _("Favorites"),
@ -42,97 +37,74 @@ function FileManagerCollection:updateItemTable()
end end
function FileManagerCollection:onMenuHold(item) 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 self.collfile_dialog = nil
local function status_button_callback() local function status_button_callback()
self._manager:updateItemTable()
UIManager:close(self.collfile_dialog) 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 end
local buttons = { table.insert(buttons, {
filemanagerutil.genStatusButtonsRow(item.file, status_button_callback), filemanagerutil.genResetSettingsButton(item.file, status_button_callback, is_currently_opened),
{},
{ {
filemanagerutil.genResetSettingsButton(item.file, currently_opened_file, status_button_callback), text = _("Remove from favorites"),
{ callback = function()
text = _("Remove from collection"), UIManager:close(self.collfile_dialog)
callback = function() ReadCollection:removeItem(item.file, self._manager.coll_menu.collection)
ReadCollection:removeItem(item.file, self._manager.coll_menu.collection) self._manager:updateItemTable()
self._manager:updateItemTable() end,
UIManager:close(self.collfile_dialog)
end,
},
}, },
})
table.insert(buttons, {
{ {
{ text = _("Sort favorites"),
text = _("Sort collection"), callback = function()
callback = function() UIManager:close(self.collfile_dialog)
UIManager:close(self.collfile_dialog) local item_table = {}
local item_table = {} for _, v in ipairs(self._manager.coll_menu.item_table) do
for i=1, #self._manager.coll_menu.item_table do table.insert(item_table, {text = v.text, label = v.file})
table.insert(item_table, {text = self._manager.coll_menu.item_table[i].text, label = self._manager.coll_menu.item_table[i].file}) end
end local SortWidget = require("ui/widget/sortwidget")
local SortWidget = require("ui/widget/sortwidget") local sort_item
local sort_item sort_item = SortWidget:new{
sort_item = SortWidget:new{ title = _("Sort favorites"),
title = _("Sort favorites"), item_table = item_table,
item_table = item_table, callback = function()
callback = function() local new_order_table = {}
local new_order_table = {} for i, v in ipairs(sort_item.item_table) do
for i=1, #sort_item.item_table do table.insert(new_order_table, {
table.insert(new_order_table, { file = v.label,
file = sort_item.item_table[i].label, order = i,
order = i })
})
end
ReadCollection:writeCollection(new_order_table, self._manager.coll_menu.collection)
self._manager:updateItemTable()
end end
} ReadCollection:writeCollection(new_order_table, self._manager.coll_menu.collection)
UIManager:show(sort_item) self._manager:updateItemTable()
end, end
}, }
{ UIManager:show(sort_item)
text = _("Book information"), end,
id = "book_information", -- used by covermenu
enabled = FileManagerBookInfo:isSupported(item.file),
callback = function()
FileManagerBookInfo:show(item.file)
UIManager:close(self.collfile_dialog)
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 if Device:canExecuteScript(item.file) then
local function button_callback()
UIManager:close(self.collfile_dialog)
end
table.insert(buttons, { table.insert(buttons, {
{ filemanagerutil.genExecuteScriptButton(item.file, button_callback)
-- @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,
}
}) })
end end

@ -1,17 +1,15 @@
local BD = require("ui/bidi") local BD = require("ui/bidi")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local FFIUtil = require("ffi/util")
local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo") local FileManagerBookInfo = require("apps/filemanager/filemanagerbookinfo")
local Menu = require("ui/widget/menu") local Menu = require("ui/widget/menu")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local Screen = require("device").screen local Screen = require("device").screen
local filemanagerutil = require("apps/filemanager/filemanagerutil") local filemanagerutil = require("apps/filemanager/filemanagerutil")
local lfs = require("libs/libkoreader-lfs")
local _ = require("gettext") local _ = require("gettext")
local C_ = _.pgettext local C_ = _.pgettext
local T = FFIUtil.template local T = require("ffi/util").template
local FileManagerHistory = WidgetContainer:extend{ local FileManagerHistory = WidgetContainer:extend{
hist_menu_title = _("History"), hist_menu_title = _("History"),
@ -31,7 +29,6 @@ function FileManagerHistory:init()
end end
function FileManagerHistory:addToMainMenu(menu_items) function FileManagerHistory:addToMainMenu(menu_items)
-- insert table to main tab of filemanager menu
menu_items.history = { menu_items.history = {
text = self.hist_menu_title, text = self.hist_menu_title,
callback = function() callback = function()
@ -41,16 +38,13 @@ function FileManagerHistory:addToMainMenu(menu_items)
end end
function FileManagerHistory:fetchStatuses(count) function FileManagerHistory:fetchStatuses(count)
local status
for _, v in ipairs(require("readhistory").hist) do for _, v in ipairs(require("readhistory").hist) do
if v.dim then v.status = v.dim and "deleted" or filemanagerutil.getStatus(v.file)
status = "deleted" if v.status == "new" and v.file == (self.ui.document and self.ui.document.file) then
else v.status = "reading" -- file currently opened for the first time
status = filemanagerutil.getStatus(v.file)
end end
v.status = status
if count then if count then
self.count[status] = self.count[status] + 1 self.count[v.status] = self.count[v.status] + 1
end end
end end
self.statuses_fetched = true self.statuses_fetched = true
@ -85,64 +79,60 @@ function FileManagerHistory:onSetDimensions(dimen)
end end
function FileManagerHistory:onMenuHold(item) 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 self.histfile_dialog = nil
local function status_button_callback() local function status_button_callback()
UIManager:close(self.histfile_dialog)
if self._manager.filter ~= "all" then if self._manager.filter ~= "all" then
self._manager:fetchStatuses(false) self._manager:fetchStatuses(false)
else else
self._manager.statuses_fetched = false self._manager.statuses_fetched = false
end end
self._manager:updateItemTable() 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 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()
text = _("Remove from history"), UIManager:close(self.histfile_dialog)
callback = function() require("readhistory"):removeItem(item)
require("readhistory"):removeItem(item) self._manager:updateItemTable()
self._manager:updateItemTable() end,
UIManager:close(self.histfile_dialog)
end,
},
}, },
})
table.insert(buttons, {
{ {
{ text = _("Delete"),
text = _("Delete"), enabled = not (item.dim or is_currently_opened),
enabled = (item.file ~= currently_opened_file and lfs.attributes(item.file, "mode")) and true or false, callback = function()
callback = function() local function post_delete_callback()
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)
UIManager:close(self.histfile_dialog) 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 text = _("Book information"),
table.insert(buttons, 1, filemanagerutil.genStatusButtonsRow(item.file, status_button_callback)) id = "book_information", -- used by covermenu
table.insert(buttons, 2, {}) enabled = not item.dim,
end callback = function()
UIManager:close(self.histfile_dialog)
FileManagerBookInfo:show(item.file)
end,
},
})
self.histfile_dialog = ButtonDialogTitle:new{ self.histfile_dialog = ButtonDialogTitle:new{
title = BD.filename(item.text:match("([^/]+)$")), title = BD.filename(item.text:match("([^/]+)$")),
title_align = "center", title_align = "center",
@ -190,6 +180,13 @@ function FileManagerHistory:onShowHist()
end end
self:updateItemTable() self:updateItemTable()
self.hist_menu.close_callback = function() 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 self.statuses_fetched = nil
UIManager:close(self.hist_menu) UIManager:close(self.hist_menu)
G_reader_settings:saveSetting("history_filter", self.filter) G_reader_settings:saveSetting("history_filter", self.filter)
@ -218,17 +215,15 @@ function FileManagerHistory:showHistDialog()
table.insert(buttons, { table.insert(buttons, {
genFilterButton("reading"), genFilterButton("reading"),
genFilterButton("abandoned"), genFilterButton("abandoned"),
})
table.insert(buttons, {
genFilterButton("complete"), genFilterButton("complete"),
genFilterButton("deleted"),
}) })
table.insert(buttons, { table.insert(buttons, {
genFilterButton("all"), genFilterButton("all"),
genFilterButton("new"), genFilterButton("new"),
genFilterButton("deleted"),
}) })
if self.count.deleted > 0 then if self.count.deleted > 0 then
table.insert(buttons, {}) table.insert(buttons, {}) -- separator
table.insert(buttons, { table.insert(buttons, {
{ {
text = _("Clear history of deleted files"), text = _("Clear history of deleted files"),
@ -243,7 +238,7 @@ function FileManagerHistory:showHistDialog()
end, end,
}) })
end, end,
}, },
}) })
end end
hist_dialog = ButtonDialogTitle:new{ hist_dialog = ButtonDialogTitle:new{

@ -6,10 +6,12 @@ local BD = require("ui/bidi")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device") local Device = require("device")
local DocSettings = require("docsettings") local DocSettings = require("docsettings")
local InfoMessage = require("ui/widget/infomessage")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local util = require("ffi/util") local ffiutil = require("ffi/util")
local util = require("util")
local _ = require("gettext") local _ = require("gettext")
local T = util.template local T = ffiutil.template
local filemanagerutil = {} local filemanagerutil = {}
@ -35,7 +37,7 @@ end
-- Purge doc settings in sidecar directory -- Purge doc settings in sidecar directory
function filemanagerutil.purgeSettings(file) function filemanagerutil.purgeSettings(file)
local file_abs_path = util.realpath(file) local file_abs_path = ffiutil.realpath(file)
if file_abs_path then if file_abs_path then
DocSettings:open(file_abs_path):purge() DocSettings:open(file_abs_path):purge()
end end
@ -54,7 +56,7 @@ function filemanagerutil.resetDocumentSettings(file)
last_page = true, last_page = true,
last_xpointer = true, last_xpointer = true,
} }
local file_abs_path = util.realpath(file) local file_abs_path = ffiutil.realpath(file)
if file_abs_path then if file_abs_path then
local doc_settings = DocSettings:open(file_abs_path) local doc_settings = DocSettings:open(file_abs_path)
for k in pairs(doc_settings.data) do for k in pairs(doc_settings.data) do
@ -67,54 +69,32 @@ function filemanagerutil.resetDocumentSettings(file)
end end
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) function filemanagerutil.getStatus(file)
local status = "new"
if DocSettings:hasSidecarFile(file) then if DocSettings:hasSidecarFile(file) then
local docinfo = DocSettings:open(file) -- no io handles created, do not close local summary = DocSettings:open(file):readSetting("summary")
if docinfo.data.summary and docinfo.data.summary.status and docinfo.data.summary.status ~= "" then if summary and summary.status and summary.status ~= "" then
status = docinfo.data.summary.status return summary.status
else
status = "reading"
end end
return "reading"
end end
return status return "new"
end end
-- Set a document's status -- Set a document status ("reading", "complete", or "abandoned")
function filemanagerutil.setStatus(file, status) function filemanagerutil.setStatus(file, status)
-- In case the book doesn't have a sidecar file, this'll create it -- In case the book doesn't have a sidecar file, this'll create it
local docinfo = DocSettings:open(file) local doc_settings = DocSettings:open(file)
local summary local summary = doc_settings:readSetting("summary") or {}
if docinfo.data.summary and docinfo.data.summary.status then summary.status = status
-- Book already had the full BookStatus table in its sidecar, easy peasy! summary.modified = os.date("%Y-%m-%d", os.time())
docinfo.data.summary.status = status doc_settings:saveSetting("summary", summary)
docinfo.data.summary.modified = os.date("%Y-%m-%d", os.time()) doc_settings:flush()
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()
end end
-- Generate all book status file dialog buttons in a row -- Generate all book status file dialog buttons in a row
function filemanagerutil.genStatusButtonsRow(file, caller_callback) function filemanagerutil.genStatusButtonsRow(file, caller_callback, current_status)
local status = filemanagerutil.getStatus(file) local status = current_status or filemanagerutil.getStatus(file)
local function genStatusButton(to_status) local function genStatusButton(to_status)
local status_text = { local status_text = {
reading = _("Reading"), reading = _("Reading"),
@ -122,7 +102,7 @@ function filemanagerutil.genStatusButtonsRow(file, caller_callback)
complete = _("Finished"), complete = _("Finished"),
} }
return { return {
text = status_text[to_status], text = status_text[to_status] .. (status == to_status and "" or ""),
id = to_status, -- used by covermenu id = to_status, -- used by covermenu
enabled = status ~= to_status, enabled = status ~= to_status,
callback = function() callback = function()
@ -138,15 +118,16 @@ function filemanagerutil.genStatusButtonsRow(file, caller_callback)
} }
end end
-- Generate a "Reset settings" file dialog button -- Generate "Reset" file dialog button
function filemanagerutil.genResetSettingsButton(file, currently_opened_file, caller_callback) function filemanagerutil.genResetSettingsButton(file, caller_callback, button_disabled)
return { return {
text = _("Reset"), text = _("Reset"),
id = "reset", -- used by covermenu 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() callback = function()
UIManager:show(ConfirmBox:new{ 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)), BD.filepath(file)),
ok_text = _("Reset"), ok_text = _("Reset"),
ok_callback = function() ok_callback = function()
@ -159,4 +140,42 @@ function filemanagerutil.genResetSettingsButton(file, currently_opened_file, cal
} }
end 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 return filemanagerutil

@ -1,4 +1,3 @@
local BD = require("ui/bidi")
local BookStatusWidget = require("ui/widget/bookstatuswidget") local BookStatusWidget = require("ui/widget/bookstatuswidget")
local ButtonDialogTitle = require("ui/widget/buttondialogtitle") local ButtonDialogTitle = require("ui/widget/buttondialogtitle")
local Device = require("device") local Device = require("device")
@ -8,7 +7,6 @@ local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer") local WidgetContainer = require("ui/widget/container/widgetcontainer")
local util = require("util") local util = require("util")
local _ = require("gettext") local _ = require("gettext")
local T = require("ffi/util").template
local ReaderStatus = WidgetContainer:extend{ local ReaderStatus = WidgetContainer:extend{
document = nil, document = nil,
@ -36,46 +34,39 @@ end
function ReaderStatus:onEndOfBook() function ReaderStatus:onEndOfBook()
Device:performHapticFeedback("CONTEXT_CLICK") 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 QuickStart = require("ui/quickstart")
local last_file = G_reader_settings:readSetting("lastfile") 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() self:openFileBrowser()
return return
end end
if G_reader_settings:readSetting("collate") == "access" then
collate = false
end
-- Should we start by marking the book as read? -- Should we start by marking the book as read?
if G_reader_settings:isTrue("end_document_auto_mark") then if G_reader_settings:isTrue("end_document_auto_mark") then
self:onMarkBook(true) self:onMarkBook(true)
end end
local top_wg = UIManager:getTopmostVisibleWidget() or {} local next_file_enabled = G_reader_settings:readSetting("collate") ~= "access"
if (settings == "pop-up" or settings == nil) and top_wg.name ~= "end_document" then 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 = { local buttons = {
{ {
{ {
text_func = function() text_func = function()
if self.settings.data.summary and self.settings.data.summary.status == "complete" then return self.summary.status == "complete" and _("Mark as reading") or _("Mark as finished")
return _("Mark as reading")
else
return _("Mark as finished")
end
end, end,
callback = function() callback = function()
self:onMarkBook() self:onMarkBook()
UIManager:close(choose_action) UIManager:close(button_dialog)
end, end,
}, },
{ {
text = _("Book status"), text = _("Book status"),
callback = function() callback = function()
self:onShowBookStatus() self:onShowBookStatus()
UIManager:close(choose_action) UIManager:close(button_dialog)
end, end,
}, },
@ -85,15 +76,15 @@ function ReaderStatus:onEndOfBook()
text = _("Go to beginning"), text = _("Go to beginning"),
callback = function() callback = function()
self.ui:handleEvent(Event:new("GoToBeginning")) self.ui:handleEvent(Event:new("GoToBeginning"))
UIManager:close(choose_action) UIManager:close(button_dialog)
end, end,
}, },
{ {
text = _("Open next file"), text = _("Open next file"),
enabled = collate, enabled = next_file_enabled,
callback = function() callback = function()
self:onOpenNextDocumentInFolder() self:onOpenNextDocumentInFolder()
UIManager:close(choose_action) UIManager:close(button_dialog)
end, end,
}, },
}, },
@ -101,46 +92,38 @@ function ReaderStatus:onEndOfBook()
{ {
text = _("Delete file"), text = _("Delete file"),
callback = function() callback = function()
self:deleteFile(self.document.file, false) self:deleteFile()
UIManager:close(choose_action) UIManager:close(button_dialog)
end, end,
}, },
{ {
text = _("File browser"), text = _("File browser"),
callback = function() callback = function()
self:openFileBrowser() self:openFileBrowser()
UIManager:close(choose_action) UIManager:close(button_dialog)
end,
},
},
{
{
text = _("Cancel"),
callback = function()
UIManager:close(choose_action)
end, end,
}, },
}, },
} }
choose_action = ButtonDialogTitle:new{ button_dialog = ButtonDialogTitle:new{
name = "end_document", name = "end_document",
title = _("You've reached the end of the document.\nWhat would you like to do?"), title = _("You've reached the end of the document.\nWhat would you like to do?"),
title_align = "center", title_align = "center",
buttons = buttons, buttons = buttons,
} }
UIManager:show(button_dialog)
UIManager:show(choose_action)
elseif settings == "book_status" then elseif settings == "book_status" then
self:onShowBookStatus() self:onShowBookStatus()
elseif settings == "next_file" then elseif settings == "next_file" then
if G_reader_settings:readSetting("collate") ~= "access" then if next_file_enabled then
local info = InfoMessage:new{ local info = InfoMessage:new{
text = _("Searching next file…"), text = _("Searching next file…"),
} }
UIManager:show(info) UIManager:show(info)
UIManager:forceRePaint() UIManager:forceRePaint()
UIManager:close(info) 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() UIManager:nextTick(function()
self:onOpenNextDocumentInFolder() self:onOpenNextDocumentInFolder()
end) end)
@ -171,7 +154,7 @@ function ReaderStatus:onEndOfBook()
elseif settings == "delete_file" then elseif settings == "delete_file" then
-- Ditto -- Ditto
UIManager:nextTick(function() UIManager:nextTick(function()
self:deleteFile(self.document.file, true) self:deleteFile()
end) end)
end end
end end
@ -200,28 +183,17 @@ function ReaderStatus:onOpenNextDocumentInFolder()
end end
end end
function ReaderStatus:deleteFile(file, text_end_book) function ReaderStatus:deleteFile()
local ConfirmBox = require("ui/widget/confirmbox") self.settings:flush() -- enable additional warning text for newly opened file
local message_end_book = "" local FileManager = require("apps/filemanager/filemanager")
if text_end_book then local function pre_delete_callback()
message_end_book = "You've reached the end of the document.\n" self.ui:onClose()
end end
UIManager:show(ConfirmBox:new{ local function post_delete_callback()
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)), local path = util.splitFilePathName(self.document.file)
ok_text = _("Delete"), FileManager:showFiles(path)
ok_callback = function() end
local FileManager = require("apps/filemanager/filemanager") FileManager:showDeleteFileDialog(self.document.file, post_delete_callback, pre_delete_callback)
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,
})
end end
function ReaderStatus:onShowBookStatus(before_show_callback) function ReaderStatus:onShowBookStatus(before_show_callback)
@ -240,31 +212,18 @@ function ReaderStatus:onShowBookStatus(before_show_callback)
return true return true
end end
-- If mark_read is true then we change status only from reading/abandoned to read (complete). -- If mark_read is true then we change status only from reading/abandoned to complete.
-- Otherwise we change status from reading/abandoned to read or from read to reading. -- Otherwise we change status from reading/abandoned to complete or from complete to reading.
function ReaderStatus:onMarkBook(mark_read) function ReaderStatus:onMarkBook(mark_read)
if self.settings.data.summary then self.summary.status = (not mark_read and self.summary.status == "complete") and "reading" or "complete"
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
-- If History is called over Reader, it will read the file to get the book status, so save and flush -- 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() self.settings:flush()
end end
function ReaderStatus:onReadSettings(config) function ReaderStatus:onReadSettings(config)
self.settings = config self.settings = config
self.summary = config:readSetting("summary") or {}
end end
return ReaderStatus return ReaderStatus

@ -43,6 +43,8 @@ local nb_drawings_since_last_collectgarbage = 0
-- in the real Menu class or instance -- in the real Menu class or instance
local CoverMenu = {} local CoverMenu = {}
local book_statuses = {"reading", "abandoned", "complete"}
function CoverMenu:updateCache(file, status) function CoverMenu:updateCache(file, status)
if self.cover_info_cache and self.cover_info_cache[file] then if self.cover_info_cache and self.cover_info_cache[file] then
if status then if status then
@ -348,8 +350,9 @@ function CoverMenu:updateItems(select_number)
end end
-- Fudge the status change button callbacks to also update the cover_info_cache -- 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) 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 local orig_status_callback = button.callback
button.callback = function() button.callback = function()
-- Update the cache -- Update the cache
@ -501,16 +504,15 @@ function CoverMenu:onHistoryMenuHold(item)
end end
-- Fudge the status change button callbacks to also update the cover_info_cache -- 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) button = self.histfile_dialog.button_table:getButtonById(status)
if button then if not button then break end -- status buttons are not shown
local orig_status_callback = button.callback local orig_status_callback = button.callback
button.callback = function() button.callback = function()
-- Update the cache -- Update the cache
self:updateCache(file, status) self:updateCache(file, status)
-- And then set the status on file as expected -- And then set the status on file as expected
orig_status_callback() orig_status_callback()
end
end end
end end
@ -648,8 +650,9 @@ function CoverMenu:onCollectionsMenuHold(item)
end end
-- Fudge the status change button callbacks to also update the cover_info_cache -- 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) 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 local orig_status_callback = button.callback
button.callback = function() button.callback = function()
-- Update the cache -- 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 -- Propagate a call to free() to all our sub-widgets, to release memory used by their _bb
self.item_group:free() 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 self.cover_info_cache = nil
-- Force garbage collecting when leaving too -- Force garbage collecting when leaving too

@ -32,11 +32,11 @@ describe("FileManager module", function()
assert.Equals(w.text, "File not found:\n"..tmp_fn) assert.Equals(w.text, "File not found:\n"..tmp_fn)
end end
assert.is_nil(lfs.attributes(tmp_fn)) assert.is_nil(lfs.attributes(tmp_fn))
filemanager:deleteFile(tmp_fn) filemanager:deleteFile(tmp_fn, true)
UIManager.show = old_show UIManager.show = old_show
filemanager:onClose() filemanager:onClose()
end) end)
it("should not delete settings for non-document file", function() it("should not delete not empty sidecar folder", function()
local filemanager = FileManager:new{ local filemanager = FileManager:new{
dimen = Screen:getSize(), dimen = Screen:getSize(),
root_path = "../../test", root_path = "../../test",
@ -47,30 +47,32 @@ describe("FileManager module", function()
local tmp_sidecar = docsettings:getSidecarDir(util.realpath(tmp_fn)) local tmp_sidecar = docsettings:getSidecarDir(util.realpath(tmp_fn))
lfs.mkdir(tmp_sidecar) lfs.mkdir(tmp_sidecar)
local tmp_history = docsettings:getHistoryPath(tmp_fn) local tmp_sidecar_file = docsettings:getSidecarFile(util.realpath(tmp_fn))
local tmpfp = io.open(tmp_history, "w") local tmp_sidecar_file_foo = tmp_sidecar_file .. ".foo" -- non-docsettings file
tmpfp:write("{}") local tmpsf = io.open(tmp_sidecar_file, "w")
tmpfp:close() tmpsf:write("{}")
tmpsf:close()
util.copyFile(tmp_sidecar_file, tmp_sidecar_file_foo)
local old_show = UIManager.show local old_show = UIManager.show
-- make sure file exists -- make sure file exists
assert.is_not_nil(lfs.attributes(tmp_fn)) assert.is_not_nil(lfs.attributes(tmp_fn))
assert.is_not_nil(lfs.attributes(tmp_sidecar)) 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) UIManager.show = function(self, w)
assert.Equals(w.text, "Deleted file:\n"..tmp_fn) assert.Equals(w.text, "Deleted file:\n"..tmp_fn)
end end
filemanager:deleteFile(tmp_fn) filemanager:deleteFile(tmp_fn, true)
UIManager.show = old_show UIManager.show = old_show
filemanager:onClose() filemanager:onClose()
-- make sure history file exists -- make sure sdr folder exists
assert.is_nil(lfs.attributes(tmp_fn)) assert.is_nil(lfs.attributes(tmp_fn))
assert.is_not_nil(lfs.attributes(tmp_sidecar)) 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_sidecar)
os.remove(tmp_history)
end) end)
it("should delete document with its settings", function() it("should delete document with its settings", function()
local filemanager = FileManager:new{ local filemanager = FileManager:new{
@ -83,6 +85,10 @@ describe("FileManager module", function()
local tmp_sidecar = docsettings:getSidecarDir(util.realpath(tmp_fn)) local tmp_sidecar = docsettings:getSidecarDir(util.realpath(tmp_fn))
lfs.mkdir(tmp_sidecar) 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 tmp_history = docsettings:getHistoryPath(tmp_fn)
local tmpfp = io.open(tmp_history, "w") local tmpfp = io.open(tmp_history, "w")
tmpfp:write("{}") tmpfp:write("{}")
@ -97,7 +103,7 @@ describe("FileManager module", function()
UIManager.show = function(self, w) UIManager.show = function(self, w)
assert.Equals(w.text, "Deleted file:\n"..tmp_fn) assert.Equals(w.text, "Deleted file:\n"..tmp_fn)
end end
filemanager:deleteFile(tmp_fn) filemanager:deleteFile(tmp_fn, true)
UIManager.show = old_show UIManager.show = old_show
filemanager:onClose() filemanager:onClose()

Loading…
Cancel
Save