mirror of https://github.com/koreader/koreader
Archive viewer (#10584)
parent
64f778d97d
commit
54ab0a04f8
@ -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…
Reference in New Issue