diff --git a/base b/base index 3b5dcdce5..60145efe0 160000 --- a/base +++ b/base @@ -1 +1 @@ -Subproject commit 3b5dcdce5d5fdb0032957acb17b18d4fe3c7cfe0 +Subproject commit 60145efe0d80c6ff502413294be38533b1a2e3bb diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index b058b2a7f..df8ed936c 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -4,6 +4,7 @@ ReaderUI is an abstraction for a reader interface. It works using data gathered from a document interface. ]]-- +local Archiver = require("ffi/archiver") local BD = require("ui/bidi") local BookList = require("ui/widget/booklist") local Device = require("device") @@ -622,18 +623,16 @@ function ReaderUI:extendProvider(file, provider, is_provider_forced) -- or on the original file double extension ("fb2.zip" etc). local _, file_type = filemanagerutil.splitFileNameType(file) -- supports double-extension if file_type == "zip" then - -- read the content of zip-file and get extension of the 1st file - local std_out = io.popen("unzip -qql \"" .. file .. "\"") - if std_out then - local size, ext - for line in std_out:lines() do - size, ext = string.match(line, "%s+(%d+)%s+.+%.([^.]+)") - if size and ext then break end - end - std_out:close() - if ext ~= nil then - file_type = ext:lower() + local arc = Archiver.Reader:new() + if arc:open(file) then + for entry in arc:iterate() do + local ext = util.getFileNameSuffix(entry.path) + if ext and entry.mode == "file" and entry.size > 0 then + file_type = ext:lower() + break + end end + arc:close() end if not is_provider_forced then local providers = DocumentRegistry:getProviders("dummy." .. file_type) diff --git a/frontend/device/android/device.lua b/frontend/device/android/device.lua index 65b86e427..29e77c4d3 100644 --- a/frontend/device/android/device.lua +++ b/frontend/device/android/device.lua @@ -544,10 +544,6 @@ function Device:_showLightDialog() end end -function Device:untar(archive, extract_to) - return android.untar(archive, extract_to) -end - function Device:download(link, name, ok_text) local ConfirmBox = require("ui/widget/confirmbox") local InfoMessage = require("ui/widget/infomessage") diff --git a/frontend/device/generic/device.lua b/frontend/device/generic/device.lua index 3199548cd..7154d1a63 100644 --- a/frontend/device/generic/device.lua +++ b/frontend/device/generic/device.lua @@ -4,6 +4,7 @@ Generic device abstraction. This module defines stubs for common methods. --]] +local Archiver = require("ffi/archiver") local DataStorage = require("datastorage") local Event = require("ui/event") local Geom = require("ui/geometry") @@ -1024,26 +1025,36 @@ end function Device:unpackArchive(archive, extract_to, with_stripped_root) require("dbg").dassert(type(archive) == "string") local BD = require("ui/bidi") - local ok - if archive:match("%.tar%.bz2$") or archive:match("%.tar%.gz$") or archive:match("%.tar%.lz$") or archive:match("%.tgz$") then - ok = self:untar(archive, extract_to, with_stripped_root) - else - return false, T(_("Couldn't extract archive:\n\n%1\n\nUnrecognized filename extension."), BD.filepath(archive)) + local arc = Archiver.Reader:new() + local ok = arc:open(archive) + if ok then + for entry in arc:iterate() do + local dest_path = entry.path + if with_stripped_root then + local __, tail = dest_path:match("([^/]*)/*(.*)") + if tail then + -- Non-root: strip one level. + dest_path = tail + elseif entry.mode == 'directory' then + -- Root directory: ignore. + goto continue + else -- luacheck: ignore 542 + -- Root non-directory: don't strip. + end + end + if not arc:extractToPath(entry.path, extract_to.."/"..dest_path) then + break + end + ::continue:: + end + ok = not arc.err end if not ok then - return false, T(_("Extracting archive failed:\n\n%1"), BD.filepath(archive)) + return false, T(_("Extracting archive failed:\n\n%1"), BD.filepath(archive))..string.format("\n\n(%s)", arc.err) end return true end -function Device:untar(archive, extract_to, with_stripped_root) - local cmd = "./tar xf %q -C %q" - if with_stripped_root then - cmd = cmd .. " --strip-components=1" - end - return os.execute(cmd:format(archive, extract_to)) -end - -- Update our UIManager reference once it's ready function Device:_UIManagerReady(uimgr) -- Our own ref diff --git a/frontend/device/kindle/device.lua b/frontend/device/kindle/device.lua index c79a819ba..76e1a99ad 100644 --- a/frontend/device/kindle/device.lua +++ b/frontend/device/kindle/device.lua @@ -776,11 +776,6 @@ function Kindle:readyToSuspend(delay) self.suspend_time = time.boottime_or_realtime_coarse() end --- We add --no-same-permissions --no-same-owner to make the userstore fuse proxy happy... -function Kindle:untar(archive, extract_to) - return os.execute(("./tar --no-same-permissions --no-same-owner -xf %q -C %q"):format(archive, extract_to)) -end - function Kindle:UIManagerReady(uimgr) UIManager = uimgr end diff --git a/frontend/ui/wikipedia.lua b/frontend/ui/wikipedia.lua index 774c8fb5c..f4df2eb6f 100644 --- a/frontend/ui/wikipedia.lua +++ b/frontend/ui/wikipedia.lua @@ -836,28 +836,31 @@ function Wikipedia:createEpub(epub_path, page, lang, with_images) -- Open the zip file (with .tmp for now, as crengine may still -- have a handle to the final epub_path, and we don't want to -- delete a good one if we fail/cancel later) + local Archiver = require("ffi/archiver") + local epub = Archiver.Writer:new{} local epub_path_tmp = epub_path .. ".tmp" - local ZipWriter = require("ffi/zipwriter") - local epub = ZipWriter:new{} - if not epub:open(epub_path_tmp) then + if not epub:open(epub_path_tmp, "epub") then return false end -- We now create and add all the required epub files + local mtime = os.time() -- ---------------------------------------------------------------- -- /mimetype : always "application/epub+zip" - epub:add("mimetype", "application/epub+zip", true) + epub:setZipCompression("store") + epub:addFileFromMemory("mimetype", "application/epub+zip", mtime) + epub:setZipCompression("deflate") -- ---------------------------------------------------------------- -- /META-INF/container.xml : always the same content - epub:add("META-INF/container.xml", [[ + epub:addFileFromMemory("META-INF/container.xml", [[ -]]) +]], mtime) -- ---------------------------------------------------------------- -- OEBPS/content.opf : metadata + list of other files (paths relative to OEBPS/ directory) @@ -916,14 +919,14 @@ function Wikipedia:createEpub(epub_path, page, lang, with_images) ]]) - epub:add("OEBPS/content.opf", table.concat(content_opf_parts)) + epub:addFileFromMemory("OEBPS/content.opf", table.concat(content_opf_parts), mtime) -- ---------------------------------------------------------------- -- OEBPS/stylesheet.css -- crengine will use its own data/epub.css, we just add/fix a few styles -- to look more alike wikipedia web pages (that the user can ignore -- with "Embedded Style" off) - epub:add("OEBPS/stylesheet.css", [[ + epub:addFileFromMemory("OEBPS/stylesheet.css", [[ /* Generic styling picked from our epub.css (see it for comments), to give this epub a book look even if used with html5.css */ body { @@ -1270,7 +1273,7 @@ abbr.abbr { display: none; } /* hiding .noprint may discard some interesting links */ -]]) +]], mtime) -- ---------------------------------------------------------------- -- OEBPS/toc.ncx : table of content @@ -1351,7 +1354,7 @@ abbr.abbr { ]]) - epub:add("OEBPS/toc.ncx", table.concat(toc_ncx_parts)) + epub:addFileFromMemory("OEBPS/toc.ncx", table.concat(toc_ncx_parts), mtime) -- ---------------------------------------------------------------- -- HTML table of content @@ -1479,7 +1482,7 @@ abbr.abbr { if self:isWikipediaLanguageRTL(lang) then html_dir = ' dir="rtl"' end - epub:add("OEBPS/content.html", string.format([[ + epub:addFileFromMemory("OEBPS/content.html", string.format([[ %s @@ -1493,7 +1496,7 @@ abbr.abbr { %s -]], html_dir, page_cleaned, page_htmltitle, lang:upper(), saved_on, see_online_version, html)) +]], html_dir, page_cleaned, page_htmltitle, lang:upper(), saved_on, see_online_version, html), mtime) -- Force a GC to free the memory we used till now (the second call may -- help reclaim more memory). @@ -1535,11 +1538,11 @@ abbr.abbr { end if success then -- Images do not need to be compressed, so spare some cpu cycles - local no_compression = true - if img.mimetype == "image/svg+xml" then -- except for SVG images (which are XML text) - no_compression = false + if img.mimetype ~= "image/svg+xml" then -- except for SVG images (which are XML text) + epub:setZipCompression("store") end - epub:add("OEBPS/"..img.imgpath, content, no_compression) + epub:addFileFromMemory("OEBPS/"..img.imgpath, content, mtime) + epub:setZipCompression("deflate") else go_on = UI:confirm(T(_("Downloading image %1 failed. Continue anyway?"), inum), _("Stop"), _("Continue")) if not go_on then diff --git a/plugins/archiveviewer.koplugin/main.lua b/plugins/archiveviewer.koplugin/main.lua index eb7edfdf8..2d1bb072c 100644 --- a/plugins/archiveviewer.koplugin/main.lua +++ b/plugins/archiveviewer.koplugin/main.lua @@ -1,3 +1,4 @@ +local Archiver = require("ffi/archiver") local BD = require("ui/bidi") local ButtonDialog = require("ui/widget/buttondialog") local DocumentRegistry = require("document/documentregistry") @@ -15,28 +16,22 @@ local T = ffiUtil.template local ArchiveViewer = WidgetContainer:extend{ name = "archiveviewer", fullname = _("Archive viewer"), - arc_file = nil, -- archive + arc = 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 +local SUPPORTED_EXTENSIONS = { + cbr = true, + cbz = true, + epub = true, + rar = true, + zip = true, +} function ArchiveViewer:init() self:registerDocumentRegistryAuxProvider() @@ -53,23 +48,18 @@ function ArchiveViewer:registerDocumentRegistryAuxProvider() end function ArchiveViewer:isFileTypeSupported(file) - return self.arc_ext[getSuffix(file)] and true or false + return SUPPORTED_EXTENSIONS[util.getFileNameSuffix(file):lower()] ~= nil 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.arc = Archiver.Reader:new() self.fm_updated = nil self.list_table = {} - if self.arc_type == "zip" then - self:getZipListTable() - else -- add other archivers here - return + + if self.arc:open(file) then + self:getListTable() end self.menu = Menu:new{ @@ -97,7 +87,7 @@ function ArchiveViewer:openFile(file) UIManager:show(self.menu) end -function ArchiveViewer:getZipListTable() +function ArchiveViewer:getListTable() local function parse_path(filepath, filesize) if not filepath then return end local path, name = util.splitFilePathName(filepath) @@ -119,14 +109,10 @@ function ArchiveViewer:getZipListTable() 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) + for entry in self.arc:iterate() do + if entry.mode == "file" then + parse_path(entry.path, entry.size) end - std_out:close() end end @@ -266,29 +252,12 @@ function ArchiveViewer:viewFile(filepath) 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 + local directory = util.splitFilePathName(self.arc.filepath) + self.fm_updated = self.arc:extractToPath(filepath, directory .. filepath) 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 + return self.arc:extractToMemory(filepath) end return ArchiveViewer diff --git a/plugins/newsdownloader.koplugin/epubdownloadbackend.lua b/plugins/newsdownloader.koplugin/epubdownloadbackend.lua index 4d0cf5c54..f3b714b43 100644 --- a/plugins/newsdownloader.koplugin/epubdownloadbackend.lua +++ b/plugins/newsdownloader.koplugin/epubdownloadbackend.lua @@ -442,29 +442,32 @@ function EpubDownloadBackend:createEpub(epub_path, html, url, include_images, me -- Open the zip file (with .tmp for now, as crengine may still -- have a handle to the final epub_path, and we don't want to -- delete a good one if we fail/cancel later) + local Archiver = require("ffi/archiver") + local epub = Archiver.Writer:new{} local epub_path_tmp = epub_path .. ".tmp" - local ZipWriter = require("ffi/zipwriter") - local epub = ZipWriter:new{} - if not epub:open(epub_path_tmp) then + if not epub:open(epub_path_tmp, "epub") then logger.dbg("Failed to open epub_path_tmp") return false end -- We now create and add all the required epub files + local mtime = os.time() -- ---------------------------------------------------------------- -- /mimetype : always "application/epub+zip" - epub:add("mimetype", "application/epub+zip", true) + epub:setZipCompression("store") + epub:addFileFromMemory("mimetype", "application/epub+zip", mtime) + epub:setZipCompression("deflate") -- ---------------------------------------------------------------- -- /META-INF/container.xml : always the same content - epub:add("META-INF/container.xml", [[ + epub:addFileFromMemory("META-INF/container.xml", [[ -]]) +]], mtime) logger.dbg("Added META-INF/container.xml") -- ---------------------------------------------------------------- @@ -517,7 +520,7 @@ function EpubDownloadBackend:createEpub(epub_path, html, url, include_images, me ]]) - epub:add("OEBPS/content.opf", table.concat(content_opf_parts)) + epub:addFileFromMemory("OEBPS/content.opf", table.concat(content_opf_parts), mtime) logger.dbg("Added OEBPS/content.opf") -- ---------------------------------------------------------------- @@ -525,9 +528,9 @@ function EpubDownloadBackend:createEpub(epub_path, html, url, include_images, me --- @todo We told it we'd include a stylesheet.css, so it's probably best -- that we do. In theory, we could try to fetch any *.css files linked in -- the main html. - epub:add("OEBPS/stylesheet.css", [[ + epub:addFileFromMemory("OEBPS/stylesheet.css", [[ /* Empty */ -]]) +]], mtime) logger.dbg("Added OEBPS/stylesheet.css") -- ---------------------------------------------------------------- @@ -567,12 +570,12 @@ function EpubDownloadBackend:createEpub(epub_path, html, url, include_images, me ]]) - epub:add("OEBPS/toc.ncx", table.concat(toc_ncx_parts)) + epub:addFileFromMemory("OEBPS/toc.ncx", table.concat(toc_ncx_parts), mtime) logger.dbg("Added OEBPS/toc.ncx") -- ---------------------------------------------------------------- -- OEBPS/content.html - epub:add("OEBPS/content.html", html) + epub:addFileFromMemory("OEBPS/content.html", html, mtime) logger.dbg("Added OEBPS/content.html") -- Force a GC to free the memory we used till now (the second call may @@ -619,7 +622,7 @@ function EpubDownloadBackend:createEpub(epub_path, html, url, include_images, me if img.mimetype == "image/svg+xml" then -- except for SVG images (which are XML text) no_compression = false end - epub:add("OEBPS/"..img.imgpath, content, no_compression) + epub:addFileFromMemory("OEBPS/"..img.imgpath, content, no_compression, mtime) else go_on = UI:confirm(T(_("Downloading image %1 failed. Continue anyway?"), inum), _("Stop"), _("Continue")) if not go_on then