Archive viewer (#10584)

reviewable/pr10601/r1
hius07 11 months ago committed by GitHub
parent 64f778d97d
commit 54ab0a04f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -302,6 +302,14 @@ function FileManager:setupLayout()
end,
})
end
if file_manager.archiveviewer and file_manager.archiveviewer:isSupported(file) then
table.insert(one_time_providers, {
provider_name = _("Archive viewer"),
callback = function()
file_manager.archiveviewer:openArchiveViewer(file)
end,
})
end
self:showSetProviderButtons(file, one_time_providers)
end,
},

@ -0,0 +1,6 @@
local _ = require("gettext")
return {
name = "archiveviewer",
fullname = _("Archive viewer"),
description = _([[Enables exploring the content of zip archives.]]),
}

@ -0,0 +1,188 @@
local BD = require("ui/bidi")
local CenterContainer = require("ui/widget/container/centercontainer")
local ConfirmBox = require("ui/widget/confirmbox")
local Menu = require("ui/widget/menu")
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 Screen = require("device").screen
local T = ffiUtil.template
local ArchiveViewer = WidgetContainer:extend{
-- 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,
},
}
function ArchiveViewer:isSupported(file)
return self.arc_ext[util.getFileNameSuffix(file):lower()] and true or false
end
function ArchiveViewer:openArchiveViewer(file)
local _, filename = util.splitFilePathName(file)
local fileext = util.getFileNameSuffix(file):lower()
if fileext == "cbz" or fileext == "epub" or fileext == "zip" then
self.arc_type = "zip"
end
self.fm_updated = nil
self.list_table = {}
if self.arc_type == "zip" then
self:getZipListTable(file)
else -- add other archivers here
return
end
local item_table = self:getItemTable() -- root
local menu_container = CenterContainer:new{
dimen = Screen:getSize(),
}
local menu = Menu:new{
is_borderless = true,
is_popout = false,
title_multilines = true,
show_parent = menu_container,
onMenuSelect = function(self_menu, item)
if item.text:sub(-1) == "/" then -- folder
local title = item.path == "" and filename or filename.."/"..item.path
self_menu:switchItemTable(title, self:getItemTable(item.path))
else
self:extractFile(file, item.path)
end
end,
close_callback = function()
UIManager:close(menu_container)
if self.fm_updated then
self.ui:onRefresh()
end
end,
}
table.insert(menu_container, menu)
menu:switchItemTable(filename, item_table)
UIManager:show(menu_container)
end
function ArchiveViewer:getZipListTable(file)
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("unzip ".."-qql \""..file.."\"")
for line in std_out:lines() do
-- entry datetime not used so far
local fsize, fname = string.match(line, "%s+(%d+)%s+%d%d%-%d%d%-%d%d%d%d%s+%d%d:%d%d%s+(.+)")
parse_path(fname, fsize or 0)
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,
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)
for _, v in ipairs(dirs) do
table.insert(item_table, v)
end
for _, v in ipairs(files) do
table.insert(item_table, v)
end
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:extractFile(arcfile, filepath)
UIManager:show(ConfirmBox:new{
text = _("Extract this file?") .. "\n\n" .. filepath .. "\n\n" ..
_("If the file already exists, it will be overwritten."),
ok_text = _("Extract"),
ok_callback = function()
if self.arc_type == "zip" then
io.popen("unzip ".."-qo \""..arcfile.."\"".." ".."\""..filepath.."\""..
" -d ".."\""..util.splitFilePathName(arcfile).."\"")
else -- add other archivers here
return
end
self.fm_updated = true
end,
})
end
return ArchiveViewer
Loading…
Cancel
Save