mirror of
https://github.com/koreader/koreader
synced 2024-11-02 15:40:16 +00:00
295 lines
9.1 KiB
Lua
295 lines
9.1 KiB
Lua
local BD = require("ui/bidi")
|
|
local ButtonDialog = require("ui/widget/buttondialog")
|
|
local DocumentRegistry = require("document/documentregistry")
|
|
local ImageViewer = require("ui/widget/imageviewer")
|
|
local Menu = require("ui/widget/menu")
|
|
local RenderImage = require("ui/renderimage")
|
|
local TextViewer = require("ui/widget/textviewer")
|
|
local UIManager = require("ui/uimanager")
|
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
|
local ffiUtil = require("ffi/util")
|
|
local util = require("util")
|
|
local _ = require("gettext")
|
|
local T = ffiUtil.template
|
|
|
|
local ArchiveViewer = WidgetContainer:extend{
|
|
name = "archiveviewer",
|
|
fullname = _("Archive viewer"),
|
|
arc_file = nil, -- archive
|
|
-- list_table is a flat table containing archive files and folders
|
|
-- key - a full path of the folder ("/" for root), for all folders and subfolders of any level
|
|
-- value - a subtable of subfolders and files in the folder
|
|
-- subtable key - a name of a subfolder ending with /, or a name of a file (without path)
|
|
-- subtable value - false for subfolders, or file size (string)
|
|
list_table = nil,
|
|
arc_type = nil,
|
|
arc_ext = {
|
|
cbz = true,
|
|
epub = true,
|
|
zip = true,
|
|
},
|
|
}
|
|
|
|
local ZIP_LIST = "unzip -qql \"%1\""
|
|
local ZIP_EXTRACT_CONTENT = "unzip -qqp \"%1\" \"%2\""
|
|
local ZIP_EXTRACT_FILE = "unzip -qqo \"%1\" \"%2\" -d \"%3\"" -- overwrite
|
|
|
|
local function getSuffix(file)
|
|
return util.getFileNameSuffix(file):lower()
|
|
end
|
|
|
|
function ArchiveViewer:init()
|
|
self:registerDocumentRegistryAuxProvider()
|
|
end
|
|
|
|
function ArchiveViewer:registerDocumentRegistryAuxProvider()
|
|
DocumentRegistry:addAuxProvider({
|
|
provider_name = self.fullname,
|
|
provider = self.name,
|
|
order = 40, -- order in OpenWith dialog
|
|
disable_file = true,
|
|
disable_type = false,
|
|
})
|
|
end
|
|
|
|
function ArchiveViewer:isFileTypeSupported(file)
|
|
return self.arc_ext[getSuffix(file)] and true or false
|
|
end
|
|
|
|
function ArchiveViewer:openFile(file)
|
|
local _, filename = util.splitFilePathName(file)
|
|
local fileext = getSuffix(filename)
|
|
if fileext == "cbz" or fileext == "epub" or fileext == "zip" then
|
|
self.arc_type = "zip"
|
|
end
|
|
self.arc_file = file
|
|
|
|
self.fm_updated = nil
|
|
self.list_table = {}
|
|
if self.arc_type == "zip" then
|
|
self:getZipListTable()
|
|
else -- add other archivers here
|
|
return
|
|
end
|
|
|
|
self.menu = Menu:new{
|
|
title = filename,
|
|
item_table = self:getItemTable(),
|
|
covers_fullscreen = true,
|
|
is_borderless = true,
|
|
is_popout = false,
|
|
title_multilines = true,
|
|
onMenuSelect = function(self_menu, item)
|
|
if item.is_file then
|
|
self:showFileDialog(item.path)
|
|
else
|
|
local title = item.path == "" and filename or filename.."/"..item.path
|
|
self_menu:switchItemTable(title, self:getItemTable(item.path))
|
|
end
|
|
end,
|
|
close_callback = function()
|
|
UIManager:close(self.menu)
|
|
if self.fm_updated then
|
|
self.ui:onRefresh()
|
|
end
|
|
end,
|
|
}
|
|
UIManager:show(self.menu)
|
|
end
|
|
|
|
function ArchiveViewer:getZipListTable()
|
|
local function parse_path(filepath, filesize)
|
|
if not filepath then return end
|
|
local path, name = util.splitFilePathName(filepath)
|
|
if path == "" then
|
|
path = "/"
|
|
end
|
|
if not self.list_table[path] then
|
|
self.list_table[path] = {}
|
|
end
|
|
if name == "" then -- some archivers include subfolder name as a separate entry ending with "/"
|
|
if path ~= "/" then
|
|
parse_path(path:sub(1,-2), false) -- filesize == false for subfolders
|
|
end
|
|
else
|
|
if self.list_table[path][name] == nil then
|
|
self.list_table[path][name] = filesize
|
|
parse_path(path:sub(1,-2), false) -- go up to include subfolders of the branch into the table
|
|
end
|
|
end
|
|
end
|
|
|
|
local std_out = io.popen(T(ZIP_LIST, self.arc_file))
|
|
if std_out then
|
|
for line in std_out:lines() do
|
|
-- entry datetime not used so far
|
|
local fsize, fname = string.match(line, "%s+(%d+)%s+[-0-9]+%s+[0-9:]+%s+(.+)")
|
|
parse_path(fname, fsize or 0)
|
|
end
|
|
std_out:close()
|
|
end
|
|
end
|
|
|
|
function ArchiveViewer:getItemTable(path)
|
|
local prefix, item_table
|
|
if path == nil or path == "" then -- root
|
|
path = "/"
|
|
prefix = ""
|
|
item_table = {}
|
|
else
|
|
prefix = path
|
|
item_table = {
|
|
{
|
|
text = BD.mirroredUILayout() and BD.ltr("../ ⬆") or "⬆ ../",
|
|
path = util.splitFilePathName(path:sub(1,-2)),
|
|
},
|
|
}
|
|
end
|
|
|
|
local files, dirs = {}, {}
|
|
for name, v in pairs(self.list_table[path] or {}) do
|
|
if v then -- file
|
|
table.insert(files, {
|
|
text = name,
|
|
is_file = true,
|
|
bidi_wrap_func = BD.filename,
|
|
path = prefix..name,
|
|
mandatory = util.getFriendlySize(tonumber(v)),
|
|
})
|
|
else -- folder
|
|
local dirname = name.."/"
|
|
table.insert(dirs, {
|
|
text = dirname,
|
|
bidi_wrap_func = BD.directory,
|
|
path = prefix..dirname,
|
|
mandatory = self:getItemDirMandatory(prefix..dirname),
|
|
})
|
|
end
|
|
end
|
|
local sorting = function(a, b) -- by name, folders first
|
|
return ffiUtil.strcoll(a.text, b.text)
|
|
end
|
|
table.sort(dirs, sorting)
|
|
table.sort(files, sorting)
|
|
table.move(dirs, 1, #dirs, #item_table + 1, item_table)
|
|
table.move(files, 1, #files, #item_table + 1, item_table)
|
|
return item_table
|
|
end
|
|
|
|
function ArchiveViewer:getItemDirMandatory(name)
|
|
local sub_dirs, dir_files = 0, 0
|
|
for _, v in pairs(self.list_table[name]) do
|
|
if v then
|
|
dir_files = dir_files + 1
|
|
else
|
|
sub_dirs = sub_dirs + 1
|
|
end
|
|
end
|
|
local text = T("%1 \u{F016}", dir_files)
|
|
if sub_dirs > 0 then
|
|
text = T("%1 \u{F114} ", sub_dirs) .. text
|
|
end
|
|
return text
|
|
end
|
|
|
|
function ArchiveViewer:showFileDialog(filepath)
|
|
local dialog
|
|
local buttons = {
|
|
{
|
|
{
|
|
text = _("Extract"),
|
|
callback = function()
|
|
UIManager:close(dialog)
|
|
self:extractFile(filepath)
|
|
end,
|
|
},
|
|
{
|
|
text = _("View"),
|
|
callback = function()
|
|
UIManager:close(dialog)
|
|
self:viewFile(filepath)
|
|
end,
|
|
},
|
|
},
|
|
}
|
|
dialog = ButtonDialog:new{
|
|
title = filepath .. "\n\n" .. _("On extraction, if the file already exists, it will be overwritten."),
|
|
width_factor = 0.8,
|
|
buttons = buttons,
|
|
}
|
|
UIManager:show(dialog)
|
|
end
|
|
|
|
function ArchiveViewer:viewFile(filepath)
|
|
if DocumentRegistry:isImageFile(filepath) then
|
|
local index = 0
|
|
local curr_index
|
|
local images_list = {}
|
|
for i, item in ipairs(self.menu.item_table) do
|
|
local item_path = item.path
|
|
if item.is_file and DocumentRegistry:isImageFile(item_path) then
|
|
table.insert(images_list, item_path)
|
|
if not curr_index then
|
|
index = index + 1
|
|
if item_path == filepath then
|
|
curr_index = index
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local image_table = { image_disposable = true }
|
|
setmetatable(image_table, {__index = function (_, key)
|
|
local content = self:extractContent(images_list[key])
|
|
if content then
|
|
return RenderImage:renderImageData(content, #content)
|
|
end
|
|
end
|
|
})
|
|
local viewer = ImageViewer:new{
|
|
image = image_table,
|
|
images_list_nb = #images_list,
|
|
fullscreen = true,
|
|
with_title_bar = false,
|
|
image_disposable = false,
|
|
}
|
|
UIManager:show(viewer)
|
|
viewer:switchToImageNum(curr_index)
|
|
else
|
|
local viewer = TextViewer:new{
|
|
title = filepath,
|
|
title_multilines = true,
|
|
text = self:extractContent(filepath),
|
|
text_type = "file_content",
|
|
}
|
|
UIManager:show(viewer)
|
|
end
|
|
end
|
|
|
|
function ArchiveViewer:extractFile(filepath)
|
|
if self.arc_type == "zip" then
|
|
local std_out = io.popen(T(ZIP_EXTRACT_FILE, self.arc_file, filepath, util.splitFilePathName(self.arc_file)))
|
|
if std_out then
|
|
std_out:close()
|
|
end
|
|
else
|
|
return
|
|
end
|
|
self.fm_updated = true
|
|
end
|
|
|
|
function ArchiveViewer:extractContent(filepath)
|
|
local content
|
|
if self.arc_type == "zip" then
|
|
local std_out = io.popen(T(ZIP_EXTRACT_CONTENT, self.arc_file, filepath))
|
|
if std_out then
|
|
content = std_out:read("*all")
|
|
std_out:close()
|
|
return content
|
|
end
|
|
else
|
|
return
|
|
end
|
|
end
|
|
|
|
return ArchiveViewer
|