You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koreader/plugins/archiveviewer.koplugin/main.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