From f6ca1c7c0a06f159ebaa6b5460bf7273fb98e2ae Mon Sep 17 00:00:00 2001 From: Frans de Jonge Date: Fri, 2 Feb 2018 21:21:52 +0100 Subject: [PATCH] [feat] Open with: choose which engine to use for file (#3653) Fixes #3345 * Add SVG to MuPDF filetypes --- frontend/apps/filemanager/filemanager.lua | 9 ++ frontend/apps/reader/readerui.lua | 37 +++++- frontend/document/credocument.lua | 1 + frontend/document/djvudocument.lua | 1 + frontend/document/documentregistry.lua | 130 +++++++++++++++++++++- frontend/document/pdfdocument.lua | 6 +- frontend/document/picdocument.lua | 1 + spec/unit/document_registry_spec.lua | 62 ++++++++++- 8 files changed, 233 insertions(+), 14 deletions(-) diff --git a/frontend/apps/filemanager/filemanager.lua b/frontend/apps/filemanager/filemanager.lua index 2a08481d7..2051c08a2 100644 --- a/frontend/apps/filemanager/filemanager.lua +++ b/frontend/apps/filemanager/filemanager.lua @@ -272,6 +272,15 @@ function FileManager:init() -- a little hack to get visual functionality grouping {}, { + { + text = _("Open with…"), + enabled = lfs.attributes(file, "mode") == "file" + and #(DocumentRegistry:getProviders(file)) > 1, + callback = function() + UIManager:close(self.file_dialog) + DocumentRegistry:showSetProviderButtons(file, FileManager.instance, self, ReaderUI) + end, + }, { text = _("Convert"), enabled = lfs.attributes(file, "mode") == "file" diff --git a/frontend/apps/reader/readerui.lua b/frontend/apps/reader/readerui.lua index c4f3d4748..fcdd67cee 100644 --- a/frontend/apps/reader/readerui.lua +++ b/frontend/apps/reader/readerui.lua @@ -380,15 +380,44 @@ function ReaderUI:showFileManager() end end -function ReaderUI:showReader(file) +function ReaderUI:showReader(file, provider) logger.dbg("show reader ui") require("readhistory"):addItem(file) + if lfs.attributes(file, "mode") ~= "file" then UIManager:show(InfoMessage:new{ text = T(_("File '%1' does not exist."), file) }) return end + + -- prevent crash due to incompatible bookmarks + -- @TODO split bookmarks from metadata and do per-engine in conversion + provider = provider or DocumentRegistry:getProvider(file) + if provider.provider then + local doc_settings = DocSettings:open(file) + local bookmarks = doc_settings:readSetting("bookmarks") or {} + if #bookmarks >= 1 and + ((provider.provider == "crengine" and type(bookmarks[1].page) == "number") or + (provider.provider == "mupdf" and type(bookmarks[1].page) == "string")) then + UIManager:show(ConfirmBox:new{ + text = T(_("The document '%1' with bookmarks or highlights was previously opened with a different engine. To prevent issues, bookmarks need to be deleted before continuing."), + file), + ok_text = _("Delete"), + ok_callback = function() + doc_settings:delSetting("bookmarks") + doc_settings:close() + self:showReaderCoroutine(file, provider) + end, + cancel_callback = self.showFileManager, + }) + else + self:showReaderCoroutine(file, provider) + end + end +end + +function ReaderUI:showReaderCoroutine(file, provider) UIManager:show(InfoMessage:new{ text = T(_("Opening file '%1'."), file), timeout = 0.0, @@ -398,7 +427,7 @@ function ReaderUI:showReader(file) UIManager:nextTick(function() logger.dbg("creating coroutine for showing reader") local co = coroutine.create(function() - self:doShowReader(file) + self:doShowReader(file, provider) end) local ok, err = coroutine.resume(co) if err ~= nil or ok == false then @@ -410,13 +439,13 @@ function ReaderUI:showReader(file) end local _running_instance = nil -function ReaderUI:doShowReader(file) +function ReaderUI:doShowReader(file, provider) logger.info("opening file", file) -- keep only one instance running if _running_instance then _running_instance:onClose() end - local document = DocumentRegistry:openDocument(file) + local document = DocumentRegistry:openDocument(file, provider) if not document then UIManager:show(InfoMessage:new{ text = _("No reader engine for this file or invalid file.") diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index c0eec4e94..88584d3b1 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -26,6 +26,7 @@ local CreDocument = Document:new{ fallback_font = G_reader_settings:readSetting("fallback_font") or "Noto Sans CJK SC", default_css = "./data/cr3.css", options = CreOptions, + provider = "crengine", provider_name = "Cool Reader Engine", } diff --git a/frontend/document/djvudocument.lua b/frontend/document/djvudocument.lua index 046e3d784..c4f6e4df0 100644 --- a/frontend/document/djvudocument.lua +++ b/frontend/document/djvudocument.lua @@ -12,6 +12,7 @@ local DjvuDocument = Document:new{ options = KoptOptions, koptinterface = nil, color_bb_type = Blitbuffer.TYPE_BBRGB24, + provider = "djvulibre", provider_name = "DjVu Libre", } diff --git a/frontend/document/documentregistry.lua b/frontend/document/documentregistry.lua index 91dbd0cc5..32bde394e 100644 --- a/frontend/document/documentregistry.lua +++ b/frontend/document/documentregistry.lua @@ -2,7 +2,13 @@ This is a registry for document providers ]]-- +local ButtonDialogTitle = require("ui/widget/buttondialogtitle") +local ConfirmBox = require("ui/widget/confirmbox") +local UIManager = require("ui/uimanager") +local gettext = require("gettext") local logger = require("logger") +local util = require("util") +local T = require("ffi/util").template local DocumentRegistry = { registry = {}, @@ -20,11 +26,35 @@ end --- Returns the preferred registered document handler. -- @string file --- @treturn string provider, or nil +-- @treturn table provider, or nil function DocumentRegistry:getProvider(file) local providers = self:getProviders(file) if providers then + -- provider for document + local doc_settings_provider = require("docsettings"):open(file):readSetting("provider") + + if doc_settings_provider then + for _, provider in ipairs(providers) do + if provider.provider.provider == doc_settings_provider then + return provider.provider + end + end + end + + -- global provider for filetype + local filename_suffix = util.getFileNameSuffix(file) + local g_settings_provider = G_reader_settings:readSetting("provider") + + if g_settings_provider and g_settings_provider[filename_suffix] then + for _, provider in ipairs(providers) do + if provider.provider.provider == g_settings_provider[filename_suffix] then + return provider.provider + end + end + end + + -- highest weighted provider return providers[1].provider end end @@ -54,14 +84,108 @@ function DocumentRegistry:getProviders(file) end end -function DocumentRegistry:openDocument(file) +--- Sets the preferred registered document handler. +-- @string file +-- @bool all +function DocumentRegistry:setProvider(file, provider, all) + local _, filename_suffix = util.splitFileNameSuffix(file) + + -- per-document + if not all then + local DocSettings = require("docsettings"):open(file) + DocSettings:saveSetting("provider", provider.provider) + DocSettings:flush() + -- global + else + local filetype_provider = G_reader_settings:readSetting("provider") or {} + filetype_provider[filename_suffix] = provider.provider + G_reader_settings:saveSetting("provider", filetype_provider) + end +end + +function DocumentRegistry:showSetProviderButtons(file, filemanager_instance, ui, reader_ui) + local _, filename_pure = util.splitFilePathName(file) + local filename_suffix = util.getFileNameSuffix(file) + + local buttons = {} + local providers = self:getProviders(file) + + for _, provider in ipairs(providers) do + -- we have no need for extension, mimetype, weights, etc. here + provider = provider.provider + table.insert(buttons, { + { + text = string.format("** %s **", provider.provider_name), + }, + }) + table.insert(buttons, { + { + text = gettext("Just once"), + callback = function() + filemanager_instance:onClose() + reader_ui:showReader(file, provider) + UIManager:close(self.set_provider_dialog) + end, + }, + }) + table.insert(buttons, { + { + text = gettext("This document"), + callback = function() + UIManager:show(ConfirmBox:new{ + text = T(gettext("Always open '%2' with %1?"), + provider.provider_name, filename_pure), + ok_text = gettext("Always"), + ok_callback = function() + self:setProvider(file, provider, false) + + filemanager_instance:onClose() + reader_ui:showReader(file, provider) + UIManager:close(self.set_provider_dialog) + end, + }) + end, + }, + }) + table.insert(buttons, { + { + text = gettext("All documents"), + callback = function() + UIManager:show(ConfirmBox:new{ + text = T(gettext("Always open %2 files with %1?"), + provider.provider_name, filename_suffix), + ok_text = gettext("Always"), + ok_callback = function() + self:setProvider(file, provider, true) + + filemanager_instance:onClose() + reader_ui:showReader(file, provider) + UIManager:close(self.set_provider_dialog) + end, + }) + end, + }, + }) + -- little trick for visual separation + table.insert(buttons, {}) + end + + self.set_provider_dialog = ButtonDialogTitle:new{ + title = T(gettext("Open %1 with:"), filename_pure), + buttons = buttons, + } + UIManager:show(self.set_provider_dialog) +end + +function DocumentRegistry:openDocument(file, provider) -- force a GC, so that any previous document used memory can be reused -- immediately by this new document without having to wait for the -- next regular gc. The second call may help reclaming more memory. collectgarbage() collectgarbage() if not self.registry[file] then - local provider = self:getProvider(file) + provider = provider or self:getProvider(file) + if provider ~= nil then local ok, doc = pcall(provider.new, provider, {file = file}) if ok then diff --git a/frontend/document/pdfdocument.lua b/frontend/document/pdfdocument.lua index 40e359cfc..a2a112da0 100644 --- a/frontend/document/pdfdocument.lua +++ b/frontend/document/pdfdocument.lua @@ -13,6 +13,7 @@ local PdfDocument = Document:new{ dc_null = DrawContext.new(), options = KoptOptions, koptinterface = nil, + provider = "mupdf", provider_name = "MuPDF", } @@ -250,7 +251,7 @@ function PdfDocument:register(registry) registry:addProvider("xhtml", "application/xhtml+xml", self, 100) registry:addProvider("xml", "application/xml", self, 10) registry:addProvider("xps", "application/oxps", self, 100) - registry:addProvider("zip", "application/zip", self, 100) + registry:addProvider("zip", "application/zip", self, 20) --- Picture types --- registry:addProvider("gif", "image/gif", self, 90) @@ -267,7 +268,8 @@ function PdfDocument:register(registry) registry:addProvider("pgm", "image/x‑portable‑bitmap", self, 90) registry:addProvider("png", "image/png", self, 90) registry:addProvider("pnm", "image/x‑portable‑bitmap", self, 90) - registry:addProvider("ppm", "image/gif", self, 90) + registry:addProvider("ppm", "image/x‑portable‑bitmap", self, 90) + registry:addProvider("svg", "image/svg+xml", self, 90) registry:addProvider("tif", "image/tiff", self, 90) registry:addProvider("tiff", "image/tiff", self, 90) -- Windows Media Photo == JPEG XR diff --git a/frontend/document/picdocument.lua b/frontend/document/picdocument.lua index 17dc93446..154e65871 100644 --- a/frontend/document/picdocument.lua +++ b/frontend/document/picdocument.lua @@ -7,6 +7,7 @@ local PicDocument = Document:new{ _document = false, is_pic = true, dc_null = DrawContext.new(), + provider = "picdocument", provider_name = "Picture Document", } diff --git a/spec/unit/document_registry_spec.lua b/spec/unit/document_registry_spec.lua index c3e544b9c..3bf61730c 100644 --- a/spec/unit/document_registry_spec.lua +++ b/spec/unit/document_registry_spec.lua @@ -1,16 +1,17 @@ describe("document registry module", function() - local DocumentRegistry + local DocSettings, DocumentRegistry setup(function() require("commonrequire") + DocSettings = require("docsettings") DocumentRegistry = require("document/documentregistry") end) it("should get preferred rendering engine", function() - assert.is_equal("Cool Reader Engine", - DocumentRegistry:getProvider("bla.epub").provider_name) - assert.is_equal("MuPDF", - DocumentRegistry:getProvider("bla.pdf").provider_name) + assert.is_equal("crengine", + DocumentRegistry:getProvider("bla.epub").provider) + assert.is_equal("mupdf", + DocumentRegistry:getProvider("bla.pdf").provider) end) it("should return all supported rendering engines", function() @@ -20,4 +21,55 @@ describe("document registry module", function() assert.is_equal("MuPDF", providers[2].provider.provider_name) end) + + it("should set per-document setting for rendering engine", function() + local path = "../../foo.epub" + local pdf_provider = DocumentRegistry:getProvider("bla.pdf") + DocumentRegistry:setProvider(path, pdf_provider, false) + + local provider = DocumentRegistry:getProvider(path) + + assert.is_equal("mupdf", provider.provider) + + local docsettings = DocSettings:open(path) + docsettings:purge() + docsettings:flush() + end) + it("should set global setting for rendering engine", function() + local path = "../../foo.fb2" + local pdf_provider = DocumentRegistry:getProvider("bla.pdf") + DocumentRegistry:setProvider(path, pdf_provider, true) + + local provider = DocumentRegistry:getProvider(path) + + assert.is_equal("mupdf", provider.provider) + + G_reader_settings:delSetting("provider") + end) + + it("should return per-document setting for rendering engine", function() + local path = "../../foofoo.epub" + local docsettings = DocSettings:open(path) + docsettings:saveSetting("provider", "mupdf") + docsettings:flush() + + local provider = DocumentRegistry:getProvider(path) + + assert.is_equal("mupdf", provider.provider) + + docsettings:purge() + docsettings:flush() + end) + it("should return global setting for rendering engine", function() + local path = "../../foofoo.fb2" + local provider_setting = {} + provider_setting.fb2 = "mupdf" + G_reader_settings:saveSetting("provider", provider_setting) + + local provider = DocumentRegistry:getProvider(path) + + assert.is_equal("mupdf", provider.provider) + + G_reader_settings:delSetting("provider") + end) end)