[plugin] Add a caching mechanism for CoverImage (#7510)

reviewable/pr7582/r1
zwim 3 years ago committed by GitHub
parent 85085b545a
commit e4c9409f97
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -392,7 +392,20 @@ function Device:canExecuteScript(file)
end
function Device:isValidPath(path)
return android.isPathInsideSandbox(path)
-- the fast check
if android.isPathInsideSandbox(path) then
return true
end
-- the thorough check
local real_ext_storage = FFIUtil.realpath(android.getExternalStoragePath())
local real_path = FFIUtil.realpath(path)
if real_path then
return real_path:sub(1, #real_ext_storage) == real_ext_storage
else
return false
end
end
function Device:showLightDialog()
@ -432,6 +445,15 @@ function Device:untar(archive, extract_to)
return android.untar(archive, extract_to)
end
-- todo: Wouldn't we like an android.deviceIdentifier() method, so we can use better default paths?
function Device:getDefaultCoverPath()
if android.prop.product == "ntx_6sl" then -- Tolino HD4 and other
return android.getExternalStoragePath() .. "/suspend_others.jpg"
else
return android.getExternalStoragePath() .. "/cover.jpg"
end
end
android.LOGI(string.format("Android %s - %s (API %d) - flavor: %s",
android.prop.version, getCodename(), Device.firmware_rev, android.prop.flavor))

@ -4,6 +4,7 @@ Generic device abstraction.
This module defines stubs for common methods.
--]]
local DataStorage = require("datastorage")
local logger = require("logger")
local util = require("util")
local _ = require("gettext")
@ -503,6 +504,10 @@ function Device:isStartupScriptUpToDate()
return true
end
function Device:getDefaultCoverPath()
return DataStorage:getDataDir() .. "/cover.jpg"
end
--- Unpack an archive.
-- Extract the contents of an archive, detecting its format by
-- filename extension. Inspired by luarocks archive_unpack()

@ -363,6 +363,10 @@ function PocketBook:getDeviceModel()
return ffi.string(inkview.GetDeviceModel())
end
function PocketBook:getDefaultCoverPath()
return "/mnt/ext1/system/logo/offlogo/cover.bmp"
end
-- Pocketbook HW rotation modes start from landsape, CCW
local function landscape_ccw() return {
1, 0, 3, 2, -- PORTRAIT, LANDSCAPE, PORTRAIT_180, LANDSCAPE_180

@ -200,6 +200,10 @@ end
logger.info(string.format("Starting %s", rm_model))
function Remarkable:getDefaultCoverPath()
return "/usr/share/remarkable/poweroff.png"
end
if isRm2 then
if not os.getenv("RM2FB_SHIM") then
error("reMarkable2 requires RM2FB to work (https://github.com/ddvk/remarkable2-framebuffer)")

@ -6,31 +6,51 @@ if not (Device.isAndroid() or Device.isEmulator() or Device.isRemarkable() or De
return { disabled = true }
end
local A, android = pcall(require, "android") -- luacheck: ignore
local Blitbuffer = require("ffi/blitbuffer")
local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage")
local InfoMessage = require("ui/widget/infomessage")
local InputDialog = require("ui/widget/inputdialog")
local PathChooser = require("ui/widget/pathchooser")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local RenderImage = require("ui/renderimage")
local Screen = require("device").screen
local T = require("ffi/util").template
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local ffiutil = require("ffi/util")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
local md5 = require("ffi/sha2").md5
local util = require("util")
local _ = require("gettext")
local T = require("ffi/util").template
local function pathOk(filename)
local path, name = util.splitFilePathName(filename)
-- todo: please check the default paths directly on the depending Device:getDefaultCoverPath()
local function isPathAllowed(path)
-- don't allow a path that interferes with frontent cache-framework; quick and dirty check
if not Device:isValidPath(path) then -- isValidPath expects a trailing slash
return false, T(_("Path \"%1\" isn't in a writable location."), path)
return false
elseif not util.pathExists(path:gsub("/$", "")) then -- pathExists expects no trailing slash
return false, T(_("The path \"%1\" doesn't exist."), path)
elseif name == "" then
return false, _("Please enter a filename at the end of the path.")
elseif lfs.attributes(filename, "mode") == "directory" then
return false, T(_("The path \"%1\" must point to a file, but it points to a folder."), filename)
return false
elseif Device.isAndroid() then
return path ~= "/sdcard/koreader/cache/"
and ffiutil.realpath(path) ~= ffiutil.realpath(android.getExternalStoragePath() .. "/koreader/cache/")
else
return path ~= "./cache/" and ffiutil.realpath(path) ~= ffiutil.realpath("./cache/")
end
end
local function isFileOk(filename)
local path, name = util.splitFilePathName(filename)
return true
if not isPathAllowed(path) then
return false
end
return name ~="" and lfs.attributes(filename, "mode") ~= "directory"
end
local function getExtension(filename)
@ -43,53 +63,60 @@ local CoverImage = WidgetContainer:new{
is_doc_only = true,
}
local default_cache_path = DataStorage:getDataDir() .. "/cache/cover_image.cache/"
local default_fallback_path = DataStorage:getDataDir() .. "/"
function CoverImage:init()
self.cover_image_path = G_reader_settings:readSetting("cover_image_path") or "cover.jpg"
self.cover_image_path = G_reader_settings:readSetting("cover_image_path") or Device:getDefaultCoverPath()
self.cover_image_format = G_reader_settings:readSetting("cover_image_format") or "auto"
self.cover_image_extension = getExtension(self.cover_image_path)
self.cover_image_quality = G_reader_settings:readSetting("cover_image_quality") or 75
self.cover_image_stretch_limit = G_reader_settings:readSetting("cover_image_stretch_limit") or 5
self.cover_image_stretch_limit = G_reader_settings:readSetting("cover_image_stretch_limit") or 8
self.cover_image_background = G_reader_settings:readSetting("cover_image_background") or "black"
self.cover_image_fallback_path = G_reader_settings:readSetting("cover_image_fallback_path") or "cover_fallback.png"
self.enabled = G_reader_settings:isTrue("cover_image_enabled")
self.cover_image_fallback_path = G_reader_settings:readSetting("cover_image_fallback_path") or default_fallback_path
self.cover_image_cache_path = G_reader_settings:readSetting("cover_image_cache_path") or default_cache_path
self.cover_image_cache_maxfiles = G_reader_settings:readSetting("cover_image_cache_maxfiles") or 36
self.cover_image_cache_maxsize = G_reader_settings:readSetting("cover_image_cache_maxsize") or 5 -- MiB
self.cover_image_cache_prefix = "cover_"
self.cover = G_reader_settings:isTrue("cover_image_enabled")
self.fallback = G_reader_settings:isTrue("cover_image_fallback")
self.ui.menu:registerToMainMenu(self)
end
function CoverImage:_enabled()
return self.enabled
end
lfs.mkdir(self.cover_image_cache_path)
function CoverImage:_fallback()
return self.fallback
self.ui.menu:registerToMainMenu(self)
end
function CoverImage:cleanUpImage()
if self.cover_image_fallback_path == "" or not self.fallback then
if self.cover_image_fallback_path == "" or not self:fallbackEnabled() then
os.remove(self.cover_image_path)
elseif lfs.attributes(self.cover_image_fallback_path, "mode") ~= "file" then
UIManager:show(InfoMessage:new{
text = T(_("\"%1\" \nis not a valid image file!\nA valid fallback image is required in Cover-Image."), self.cover_image_fallback_path),
text = T(_("\"%1\"\nis not a valid image file!\nA valid fallback image is required in Cover-Image."), self.cover_image_fallback_path),
show_icon = true,
timeout = 10,
})
os.remove(self.cover_image_path)
elseif pathOk(self.cover_image_path) then
elseif isFileOk(self.cover_image_path) then
ffiutil.copyFile(self.cover_image_fallback_path, self.cover_image_path)
end
end
function CoverImage:createCoverImage(doc_settings)
if self.enabled and doc_settings:nilOrFalse("exclude_cover_image") then
if self:coverEnabled() and doc_settings:nilOrFalse("exclude_cover_image") then
local cover_image = self.ui.document:getCoverPageImage()
if cover_image then
local cache_file = self:getCacheFile()
if lfs.attributes(cache_file, "mode") == "file" then
ffiutil.copyFile(cache_file, self.cover_image_path)
lfs.touch(cache_file) -- update date
return
end
local s_w, s_h = Device.screen:getWidth(), Device.screen:getHeight()
local i_w, i_h = cover_image:getWidth(), cover_image:getHeight()
local scale_factor = math.min(s_w / i_w, s_h / i_h)
if self.cover_image_background == "none" or scale_factor == 1 then
local act_format = self.cover_image_format == "auto" and self.cover_image_extension or self.cover_image_format
local act_format = self.cover_image_format == "auto" and getExtension(self.cover_image_path) or self.cover_image_format
if not cover_image:writeToFile(self.cover_image_path, act_format, self.cover_image_quality) then
UIManager:show(InfoMessage:new{
text = _("Error writing file") .. "\n" .. self.cover_image_path,
@ -97,6 +124,8 @@ function CoverImage:createCoverImage(doc_settings)
})
end
cover_image:free()
ffiutil.copyFile(self.cover_image_path, cache_file)
self:cleanCache()
return
end
@ -132,7 +161,7 @@ function CoverImage:createCoverImage(doc_settings)
cover_image:free()
local act_format = self.cover_image_format == "auto" and self.cover_image_extension or self.cover_image_format
local act_format = self.cover_image_format == "auto" and getExtension(self.cover_image_path) or self.cover_image_format
if not image:writeToFile(self.cover_image_path, act_format, self.cover_image_quality) then
UIManager:show(InfoMessage:new{
text = _("Error writing file") .. "\n" .. self.cover_image_path,
@ -142,13 +171,16 @@ function CoverImage:createCoverImage(doc_settings)
image:free()
logger.dbg("CoverImage: image written to " .. self.cover_image_path)
ffiutil.copyFile(self.cover_image_path, cache_file)
self:cleanCache()
end
end
end
function CoverImage:onCloseDocument()
logger.dbg("CoverImage: onCloseDocument")
if self.fallback then
if self:fallbackEnabled() then
self:cleanUpImage()
end
end
@ -158,105 +190,515 @@ function CoverImage:onReaderReady(doc_settings)
self:createCoverImage(doc_settings)
end
function CoverImage:fallbackEnabled()
return self.fallback and isFileOk(self.cover_image_fallback_path)
end
function CoverImage:coverEnabled()
return self.cover and isFileOk(self.cover_image_path)
end
---------------------------
-- cache handling functions
---------------------------
function CoverImage:getCacheFile()
local dummy, document_name = util.splitFilePathName(self.ui.document.file)
-- use document_name here. Title may contain characters not allowed on every filesystem (esp. vfat on /sdcard)
local key = document_name .. "_" .. self.cover_image_quality .. "_" .. self.cover_image_stretch_limit .. "_"
.. self.cover_image_background .. "_" .. self.cover_image_format
return self.cover_image_cache_path .. self.cover_image_cache_prefix .. md5(key) .. "." .. getExtension(self.cover_image_path)
end
function CoverImage:emptyCache()
for entry in lfs.dir(self.cover_image_cache_path) do
if entry ~= "." and entry ~= ".." then
local file = self.cover_image_cache_path .. entry
if entry:sub(1, self.cover_image_cache_prefix:len()) == self.cover_image_cache_prefix
and lfs.attributes(file, "mode") == "file" then
os.remove(file)
end
end
end
end
function CoverImage:getCacheFiles(cache_path, cache_prefix)
local cache_count = 0
local cache_size_KiB = 0
local files = {}
for entry in lfs.dir(self.cover_image_cache_path) do
if entry ~= "." and entry ~= ".." then
local file = cache_path .. entry
if entry:sub(1, self.cover_image_cache_prefix:len()) == cache_prefix
and lfs.attributes(file, "mode") == "file" then
cache_count = cache_count + 1
files[cache_count] = {
name = file,
size = math.floor((lfs.attributes(file).size + 1023) / 1024), -- round up to KiB
mod = lfs.attributes(file).modification,
}
cache_size_KiB = cache_size_KiB + files[cache_count].size -- size in KiB
end
end
end
logger.dbg("CoverImage: start - cache size: ".. cache_size_KiB .. " KiB, cached files: " .. cache_count)
return cache_count, cache_size_KiB, files
end
function CoverImage:cleanCache()
if not self:isCacheEnabled() then
self:emptyCache()
return
end
local cache_count, cache_size_KiB, files = self:getCacheFiles(self.cover_image_cache_path, self.cover_image_cache_prefix)
-- delete the oldest files first
table.sort(files, function(a, b) return a.mod < b.mod end)
local index = 1
while (cache_count > self.cover_image_cache_maxfiles and self.cover_image_cache_maxfiles ~= 0)
or (cache_size_KiB > self.cover_image_cache_maxsize * 1024 and self.cover_image_cache_maxsize ~= 0)
and index <= #files do
os.remove(files[index].name)
cache_count = cache_count - 1
cache_size_KiB = cache_size_KiB - files[index].size
index = index + 1
end
logger.dbg("CoverImage: clean - cache size: ".. cache_size_KiB .. " KiB, cached files: " .. cache_count)
end
function CoverImage:isCacheEnabled(path)
if not path then
path = self.cover_image_cache_path
end
return self.cover_image_cache_maxfiles >= 0 and self.cover_image_cache_maxsize >= 0
and lfs.attributes(path, "mode") == "directory" and isPathAllowed(path)
end
-- callback for choosePathFile()
function CoverImage:migrateCache(old_path, new_path)
if old_path == new_path or not self:isCacheEnabled(new_path) then
return
end
for entry in lfs.dir(old_path) do
if entry ~= "." and entry ~= ".." then
local old_file = old_path .. entry
if lfs.attributes(old_file, "mode") == "file" and entry:sub(1, self.cover_image_cache_prefix:len()) == self.cover_image_cache_prefix then
local old_access_time = lfs.attributes(old_file, "access")
local new_file = new_path .. entry
os.rename(old_file, new_file)
lfs.touch(new_file, old_access_time) -- restore original time
end
end
end
end
-- callback for choosePathFile()
function CoverImage:migrateCover(old_file, new_file)
if old_file ~= new_file then
os.rename(old_file, new_file)
end
end
--[[--
chooses a path or (an existing) file
@touchmenu_instance for updating of the menu
@string key is the G_reader_setting key which is used and changed
@boolean folder_only just selects a path, no file handling
@boolean new_file allows to enter a new filename, or use just an existing file
@function migrate(a,b) callback to a function to mangle old folder/file with new folder/file.
Can be used for migrating the contents of the old path to the new one
]]
function CoverImage:choosePathFile(touchmenu_instance, key, folder_only, new_file, migrate)
local old_path, dummy = util.splitFilePathName(self[key])
UIManager:show(PathChooser:new{
select_directory = folder_only or new_file,
select_file = not folder_only,
height = Screen:getHeight(),
path = old_path,
onConfirm = function(dir_path)
local mode = lfs.attributes(dir_path, "mode")
if folder_only then -- just select a folder
if not dir_path:find("/$") then
dir_path = dir_path .. "/"
end
if migrate then
migrate(self, self[key], dir_path)
end
self[key] = dir_path
G_reader_settings:saveSetting(key, dir_path)
if touchmenu_instance then
touchmenu_instance:updateItems()
end
elseif new_file and mode == "directory" then -- new filename should be entered or a file could be selected
local file_input
file_input = InputDialog:new{
title = _("Append filename"),
input = dir_path .. "/",
buttons = {{
{
text = _("Cancel"),
callback = function()
UIManager:close(file_input)
end,
},
{
text = _("Save"),
callback = function()
local file = file_input:getInputText()
if migrate and self[key] and self[key] ~= "" then
migrate(self, self[key], file)
end
self[key] = file
G_reader_settings:saveSetting(key, file)
if touchmenu_instance then
touchmenu_instance:updateItems()
end
UIManager:close(file_input)
end,
},
}},
}
UIManager:show(file_input)
file_input:onShowKeyboard()
elseif mode == "file" then -- just select an existing file
if migrate then
migrate(self, self[key], dir_path)
end
self[key] = dir_path
G_reader_settings:saveSetting(key, dir_path)
if touchmenu_instance then
touchmenu_instance:updateItems()
end
end
end,
})
end
--[[--
Update a specific G_reader_setting's value via a Spinner
@touchmenu_instance used for updating the menu
@string setting is the G_reader_setting key which is used and changed
@string title shown in the spinner
@int min minimum value of the spinner
@int max maximum value of the spinner
@int default default value of the spinner
@function callback to call, when spinner changed the value
]]
function CoverImage:sizeSpinner(touchmenu_instance, setting, title, min, max, default, callback)
local SpinWidget = require("ui/widget/spinwidget")
local old_val = self[setting]
UIManager:show(SpinWidget:new{
width = math.floor(Device.screen:getWidth() * 0.6),
value = old_val,
value_min = min,
value_max = max,
default_value = default,
title_text = title,
ok_text = _("Set"),
callback = function(spin)
if self:coverEnabled() and spin.value ~= old_val then
self[setting] = spin.value
G_reader_settings:saveSetting(setting, self[setting])
if callback then
callback(self)
end
end
if touchmenu_instance then touchmenu_instance:updateItems() end
end
})
end
-------------- menus and longer texts -----------
local about_text = _([[
This plugin saves the current book cover to a file. That file can be used as a screensaver on certain Android devices, such as Tolinos.
This plugin saves a book cover to a file. That file can then be used as a screensaver on certain devices.
If enabled, the cover image of the current file is stored in the set path on book opening. Books can be excluded if desired.
If enabled, the cover image of the actual file is stored in the selected screensaver file. Books can be excluded if desired.
If disabled, the cover file will be deleted.
If fallback is activated, the fallback file will be copied to the screensaver file on book closing.
If the filename is empty or the file doesn't exist, the cover file will be deleted and the system screensaver will be used instead.
If fallback is enabled, the fallback file will be copied to the screensaver file on book closing.
If the filename is empty or the file doesn't exist, the cover file will be deleted.
If the fallback image isn't activated, the screensaver image will stay in place after closing a book.]])
If fallback is disabled, the screensaver image will stay in place after closing a book.]])
local set_image_text = _([[
You can either choose an existing file:
- Select a file
or specify a new file:
- First select a directory
- Then add the name of the new file
or delete the path:
- First select a directory
- Clear the name of the file]])
-- menu entry: Cache settings
function CoverImage:menu_entry_cache()
return {
text = _("Cache settings"),
checked_func = function()
return self:isCacheEnabled()
end,
sub_item_table = {
{
text_func = function()
local number
if self.cover_image_cache_maxfiles > 0 then
number = self.cover_image_cache_maxfiles
elseif self.cover_image_cache_maxfiles == 0 then
number = _("unlimited")
else
number = _("off")
end
return T(_("Maximum number of cached covers (%1)"), number)
end,
help_text = _("If set to zero the number of cache files is unlimited.\nIf set to -1 the cache is disabled."),
checked_func = function()
return self.cover_image_cache_maxfiles >= 0
end,
callback = function(touchmenu_instance)
self:sizeSpinner(touchmenu_instance, "cover_image_cache_maxfiles", _("Number of covers"), -1, 100, 36, self.cleanCache)
end,
},
{
text_func = function()
local number
if self.cover_image_cache_maxsize > 0 then
number = self.cover_image_cache_maxsize
elseif self.cover_image_cache_maxsize == 0 then
number = _("unlimited")
else
number = _("off")
end
return T(_("Maximum size of cached covers (%1MiB)"), number)
end,
help_text = _("If set to zero the cache size is unlimited.\nIf set to -1 the cache is disabled."),
checked_func = function()
return self.cover_image_cache_maxsize >= 0
end,
callback = function(touchmenu_instance)
self:sizeSpinner(touchmenu_instance, "cover_image_cache_maxsize", _("Cache size"), -1, 100, 5, self.cleanCache)
end,
},
self:menu_entry_set_path("cover_image_cache_path", _("Cover cache folder"), _("Current cache path:\n%1"),
("Select a cache folder. The contents of the old folder will be migrated."), default_cache_path, true, false, self.migrateCache),
{
text = _("Clear cached covers"),
help_text_func = function()
local cache_count, cache_size_KiB
= self:getCacheFiles(self.cover_image_cache_path, self.cover_image_cache_prefix)
return T(_("The cache contains %1 files and uses %2 MiB."), cache_count, math.floor((cache_size_KiB + 1023) / 1024))
end,
callback = function()
UIManager:show(ConfirmBox:new{
text = _("Cear the cover image cache?"),
ok_text = _("Clear"),
ok_callback = function()
self:emptyCache()
end,
})
end,
keep_menu_open = true,
},
},
}
end
--[[--
Menu entry for setting an specific G_reader_setting key for a path/file
@string key is the G_reader_setting key which is used and changed
@string title shown in the menu
@string help shown in the menu
@string info shown in the menu (if containing %1, the value of the key is shown)
@string the default value
@bool folder_only sets if only folders can be selected
@bool new_file sets if a new filename can be entered
@function migrate a callback for example moving the folder contents
]]
function CoverImage:menu_entry_set_path(key, title, help, info, default, folder_only, new_file, migrate)
return {
text = title,
help_text_func = function()
local text = self[key]
text = text ~= "" and text or _("not set")
return T(help, text)
end,
checked_func = function()
return isFileOk(self[key]) or (isPathAllowed(self[key]) and folder_only)
end,
callback = function(touchmenu_instance)
UIManager:show(ConfirmBox:new{
text = info,
ok_callback = function()
self:choosePathFile(touchmenu_instance, key, folder_only, new_file, migrate)
end,
other_buttons = {{
{
text = _("Default"),
callback = function()
if migrate then
migrate(self, self[key],default)
end
self[key] = default
G_reader_settings:saveSetting(key, default)
if touchmenu_instance then
touchmenu_instance:updateItems()
end
end
}
}},
})
end,
}
end
function CoverImage:menu_entry_format(title, format)
return {
text = title,
checked_func = function()
return self.cover_image_format == format
end,
callback = function()
local old_cover_image_format = self.cover_image_format
self.cover_image_format = format
G_reader_settings:saveSetting("cover_image_format", format)
if self:coverEnabld() and old_cover_image_format ~= format then
self:createCoverImage(self.ui.doc_settings)
end
end,
}
end
function CoverImage:menu_entry_background(color)
return {
text = _("Fit to screen, " .. color .. " background"),
checked_func = function()
return self.cover_image_background == color
end,
callback = function()
local old_background = self.cover_image_background
self.cover_image_background = color
G_reader_settings:saveSetting("cover_image_background", self.cover_image_background)
if self:coverEnabled() and old_background ~= self.cover_image_background then
self:createCoverImage(self.ui.doc_settings)
end
end,
}
end
-- menu entry: scale, background, format
function CoverImage:menu_entry_sbf()
return {
text = _("Size, background and format"),
enabled_func = function()
return self:coverEnabled()
end,
sub_item_table = {
{
text_func = function()
return T(_("Aspect ratio stretch threshold (%1)"),
self.cover_image_stretch_limit ~= 0 and self.cover_image_stretch_limit .."%" or "off")
end,
keep_menu_open = true,
help_text_func = function()
return T(_("If the image and the screen have a similar aspect ratio (±%1%), stretch the image instead of keeping its aspect ratio."), self.cover_image_stretch_limit )
end,
callback = function(touchmenu_instance)
local function createCover()
self:createCoverImage(self.ui.doc_settings)
end
self:sizeSpinner(touchmenu_instance, "cover_image_stretch_limit", _("Set strech threshold"), 0, 20, 8, createCover)
end,
},
self:menu_entry_background("black"),
self:menu_entry_background("white"),
self:menu_entry_background("gray"),
{
text = _("Original image"),
checked_func = function()
return self.cover_image_background == "none"
end,
callback = function()
local old_background = self.cover_image_background
self.cover_image_background = "none"
G_reader_settings:saveSetting("cover_image_background", self.cover_image_background)
if self:coverEnabled() and old_background ~= self.cover_image_background then
self:createCoverImage(self.ui.doc_settings)
end
end,
separator = true,
},
-- menu entries: File format
{
text = _("File format derived from filename"),
help_text = _("If the file format is not supported, then JPG will be used."),
checked_func = function()
return self.cover_image_format == "auto"
end,
callback = function()
local old_cover_image_format = self.cover_image_format
self.cover_image_format = "auto"
G_reader_settings:saveSetting("cover_image_format", self.cover_image_format)
if self:coverEnabled() and old_cover_image_format ~= self.cover_image_format then
self:createCoverImage(self.ui.doc_settings)
end
end,
},
self:menu_entry_format(_("JPG file format"), "jpg"),
self:menu_entry_format(_("PNG file format"), "png"),
self:menu_entry_format(_("BMP file format"), "bmp"),
},
}
end
-- CoverImage main menu
function CoverImage:addToMainMenu(menu_items)
menu_items.coverimage = {
sorting_hint = "screen",
text = _("Cover image"),
checked_func = function()
return self.enabled or self.fallback
return self:coverEnabled() or self:fallbackEnabled()
end,
sub_item_table = {
-- menu entry: about cover image
{
text = _("About cover image"),
keep_menu_open = true,
callback = function()
UIManager:show(InfoMessage:new{
text = about_text,
})
end,
keep_menu_open = true,
separator = true,
},
-- menu entry: filename dialog
{
text = _("Set image path"),
checked_func = function()
return self.cover_image_path ~= "" and pathOk(self.cover_image_path)
end,
help_text = _("The cover of the current book will be stored in this file."),
keep_menu_open = true,
callback = function(menu)
local InputDialog = require("ui/widget/inputdialog")
local sample_input
sample_input = InputDialog:new{
title = _("Screensaver image filename"),
input = self.cover_image_path,
input_type = "string",
description = _("You can enter the filename of the cover image here."),
buttons = {
{
{
text = _("Cancel"),
callback = function()
UIManager:close(sample_input)
end,
},
{
text = _("Save"),
is_enter_default = true,
callback = function()
local new_cover_image_path = sample_input:getInputText()
if new_cover_image_path ~= self.cover_image_path then
self:cleanUpImage() -- with old filename
self.cover_image_path = new_cover_image_path -- update filename
G_reader_settings:saveSetting("cover_image_path", self.cover_image_path)
local is_path_ok, is_path_ok_message = pathOk(self.cover_image_path)
if self.cover_image_path ~= "" and is_path_ok then
self:createCoverImage(self.ui.doc_settings) -- with new filename
else
self.enabled = false
UIManager:show(InfoMessage:new{
text = is_path_ok_message,
show_icon = true,
})
end
end
self.cover_image_extension = getExtension(self.cover_image_path)
UIManager:close(sample_input)
menu:updateItems()
end,
},
}
},
}
UIManager:show(sample_input)
sample_input:onShowKeyboard()
end,
},
self:menu_entry_set_path("cover_image_path", _("Set image path"), _("Current Cover image path:\n%1"), set_image_text,
Device:getDefaultCoverPath(), false, true, self.migrateCover),
-- menu entry: enable
{
text = _("Save cover image"),
checked_func = function()
return self:_enabled() and pathOk(self.cover_image_path)
return self:coverEnabled()
end,
enabled_func = function()
return self.cover_image_path ~= "" and pathOk(self.cover_image_path)
return self.cover_image_path ~= "" and isFileOk(self.cover_image_path)
end,
callback = function()
if self.cover_image_path ~= "" then
self.enabled = not self.enabled
G_reader_settings:saveSetting("cover_image_enabled", self.enabled)
if self.enabled then
self.cover = not self.cover
self.cover = self.cover and self:coverEnabled()
G_reader_settings:saveSetting("cover_image_enabled", self.cover)
if self:coverEnabled() then
self:createCoverImage(self.ui.doc_settings)
else
self:cleanUpImage()
@ -264,164 +706,8 @@ function CoverImage:addToMainMenu(menu_items)
end
end,
},
-- menu entry: scale book cover
{
text = _("Size, background and format"),
enabled_func = function()
return self.enabled
end,
sub_item_table = {
{
text_func = function()
return T(_("Aspect ratio stretch threshold (%1%)"), self.cover_image_stretch_limit )
end,
help_text_func = function()
return T(_("If the image and the screen have a similar aspect ratio (±%1%), stretch the image instead of keeping its aspect ratio."), self.cover_image_stretch_limit )
end,
keep_menu_open = true,
callback = function(touchmenu_instance)
local old_stretch_limit = self.cover_image_stretch_limit
local SpinWidget = require("ui/widget/spinwidget")
local size_spinner = SpinWidget:new{
width = math.floor(Device.screen:getWidth() * 0.6),
value = old_stretch_limit,
value_min = 0,
value_max = 25,
default_value = 5,
title_text = _("Set stretch threshold"),
ok_text = _("Set"),
callback = function(spin)
if self.enabled and spin.value ~= old_stretch_limit then
self.cover_image_stretch_limit = spin.value
G_reader_settings:saveSetting("cover_image_stretch_limit", self.cover_image_stretch_limit)
self:createCoverImage(self.ui.doc_settings)
end
if touchmenu_instance then touchmenu_instance:updateItems() end
end
}
UIManager:show(size_spinner)
if self.enabled and old_stretch_limit ~= self.cover_image_stretch_limit then
self:createCoverImage(self.ui.doc_settings)
end
end,
},
{
text = _("Fit to screen, black background"),
checked_func = function()
return self.cover_image_background == "black"
end,
callback = function()
local old_background = self.cover_image_background
self.cover_image_background = "black"
G_reader_settings:saveSetting("cover_image_background", self.cover_image_background)
if self.enabled and old_background ~= self.cover_image_background then
self:createCoverImage(self.ui.doc_settings)
end
end,
},
{
text = _("Fit to screen, white background"),
checked_func = function()
return self.cover_image_background == "white"
end,
callback = function()
local old_background = self.cover_image_background
self.cover_image_background = "white"
G_reader_settings:saveSetting("cover_image_background", self.cover_image_background)
if self.enabled and old_background ~= self.cover_image_background then
self:createCoverImage(self.ui.doc_settings)
end
end,
},
{
text = _("Fit to screen, gray background"),
checked_func = function()
return self.cover_image_background == "gray"
end,
callback = function()
local old_background = self.cover_image_background
self.cover_image_background = "gray"
G_reader_settings:saveSetting("cover_image_background", self.cover_image_background)
if self.enabled and old_background ~= self.cover_image_background then
self:createCoverImage(self.ui.doc_settings)
end
end,
},
{
text = _("Original image"),
checked_func = function()
return self.cover_image_background == "none"
end,
callback = function()
local old_background = self.cover_image_background
self.cover_image_background = "none"
G_reader_settings:saveSetting("cover_image_background", self.cover_image_background)
if self.enabled and old_background ~= self.cover_image_background then
self:createCoverImage(self.ui.doc_settings)
end
end,
separator = true,
},
-- menu entries: File format
{
text = _("File format derived from filename"),
help_text = _("If the file format is not supported, then JPG will be used."),
checked_func = function()
return self.cover_image_format == "auto"
end,
callback = function()
local old_cover_image_format = self.cover_image_format
self.cover_image_format = "auto"
G_reader_settings:saveSetting("cover_image_format", self.cover_image_format)
if self.enabled and old_cover_image_format ~= self.cover_image_format then
self:createCoverImage(self.ui.doc_settings)
end
end,
},
{
text = _("JPG file format"),
checked_func = function()
return self.cover_image_format == "jpg"
end,
callback = function()
local old_cover_image_format = self.cover_image_format
self.cover_image_format = "jpg"
G_reader_settings:saveSetting("cover_image_format", self.cover_image_format)
if self.enabled and old_cover_image_format ~= self.cover_image_format then
self:createCoverImage(self.ui.doc_settings)
end
end,
},
{
text = _("PNG file format"),
checked_func = function()
return self.cover_image_format == "png"
end,
callback = function()
local old_cover_image_format = self.cover_image_format
self.cover_image_format = "png"
G_reader_settings:saveSetting("cover_image_format", self.cover_image_format)
if self.enabled and old_cover_image_format ~= self.cover_image_format then
self:createCoverImage(self.ui.doc_settings)
end
end,
},
{
text = _("BMP file format"),
checked_func = function()
return self.cover_image_format == "bmp"
end,
callback = function()
local old_cover_image_format = self.cover_image_format
self.cover_image_format = "bmp"
G_reader_settings:saveSetting("cover_image_format", self.cover_image_format)
if self.enabled and old_cover_image_format ~= self.cover_image_format then
self:createCoverImage(self.ui.doc_settings)
end
end,
},
}
},
-- menu entry: scale, background, format
self:menu_entry_sbf(),
-- menu entry: exclude this cover
{
text = _("Exclude this book cover"),
@ -441,66 +727,29 @@ function CoverImage:addToMainMenu(menu_items)
separator = true,
},
-- menu entry: set fallback image
{
text = _("Set fallback image path"),
checked_func = function()
return lfs.attributes(self.cover_image_fallback_path, "mode") == "file"
end,
keep_menu_open = true,
callback = function(menu)
local InputDialog = require("ui/widget/inputdialog")
local sample_input
sample_input = InputDialog:new{
title = _("Fallback image filename"),
input = self.cover_image_fallback_path,
input_type = "string",
description = _("Leave this empty to remove the cover when the document is closed."),
buttons = {
{
{
text = _("Cancel"),
callback = function()
UIManager:close(sample_input)
end,
},
{
text = _("Save"),
is_enter_default = true,
callback = function()
self.cover_image_fallback_path = sample_input:getInputText()
G_reader_settings:saveSetting("cover_image_fallback_path", self.cover_image_fallback_path)
if lfs.attributes(self.cover_image_fallback_path, "mode") ~= "file"
and self.cover_image_fallback_path ~= "" then
UIManager:show(InfoMessage:new{
text = T(_("\"%1\" \nis not a valid image file!\nA valid fallback image is required in Cover-Image"),
self.cover_image_fallback_path),
show_icon = true,
timeout = 10,
})
end
UIManager:close(sample_input)
menu:updateItems()
end,
},
}
},
}
UIManager:show(sample_input)
sample_input:onShowKeyboard()
end,
},
self:menu_entry_set_path("cover_image_fallback_path", _("Set fallback path"),
_("The fallback image used on document close is:\n%1"), _("You can select a fallback image."), default_fallback_path, false, false),
-- menu entry: fallback
{
text = _("Turn on fallback image"),
checked_func = function()
return self:_fallback()
return self:fallbackEnabled()
end,
enabled_func = function()
return lfs.attributes(self.cover_image_fallback_path, "mode") == "file"
end,
callback = function()
self.fallback = not self.fallback
self.fallback = self.fallback and self:fallbackEnabled()
G_reader_settings:saveSetting("cover_image_fallback", self.fallback)
if not self:coverEnabled() then
self:cleanUpImage()
end
end,
separator = true,
},
-- menu entry: Cache settings
self:menu_entry_cache(),
},
}
end

Loading…
Cancel
Save