2021-05-13 10:58:37 +00:00
--[[--
2021-05-13 15:58:11 +00:00
@ module koplugin.coverimage
2021-05-13 10:58:37 +00:00
Plugin for saving a cover image to a file and scaling it to fit the screen .
] ]
2020-11-18 20:30:22 +00:00
2020-11-10 14:00:56 +00:00
local Device = require ( " device " )
2020-11-28 12:58:44 +00:00
if not ( Device.isAndroid ( ) or Device.isEmulator ( ) or Device.isRemarkable ( ) or Device.isPocketBook ( ) ) then
2020-11-10 14:00:56 +00:00
return { disabled = true }
end
2021-04-22 06:38:49 +00:00
local A , android = pcall ( require , " android " ) -- luacheck: ignore
2020-11-18 20:30:22 +00:00
local Blitbuffer = require ( " ffi/blitbuffer " )
2021-04-22 06:38:49 +00:00
local ConfirmBox = require ( " ui/widget/confirmbox " )
local DataStorage = require ( " datastorage " )
2023-06-09 07:36:34 +00:00
local FileManagerBookInfo = require ( " apps/filemanager/filemanagerbookinfo " )
2020-11-10 14:00:56 +00:00
local InfoMessage = require ( " ui/widget/infomessage " )
2021-04-22 06:38:49 +00:00
local InputDialog = require ( " ui/widget/inputdialog " )
local PathChooser = require ( " ui/widget/pathchooser " )
2020-11-10 14:00:56 +00:00
local UIManager = require ( " ui/uimanager " )
2020-11-18 20:30:22 +00:00
local RenderImage = require ( " ui/renderimage " )
2021-04-22 06:38:49 +00:00
local WidgetContainer = require ( " ui/widget/container/widgetcontainer " )
2020-11-10 14:00:56 +00:00
local ffiutil = require ( " ffi/util " )
local lfs = require ( " libs/libkoreader-lfs " )
local logger = require ( " logger " )
local util = require ( " util " )
local _ = require ( " gettext " )
2022-12-30 06:26:36 +00:00
local C_ = _.pgettext
local Screen = require ( " device " ) . screen
local T = require ( " ffi/util " ) . template
local md5 = require ( " ffi/sha2 " ) . md5
2020-11-10 14:00:56 +00:00
2021-04-22 06:38:49 +00:00
-- 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
2020-11-10 14:00:56 +00:00
if not Device : isValidPath ( path ) then -- isValidPath expects a trailing slash
2021-04-22 06:38:49 +00:00
return false
2020-11-10 14:00:56 +00:00
elseif not util.pathExists ( path : gsub ( " /$ " , " " ) ) then -- pathExists expects no trailing slash
2021-04-22 06:38:49 +00:00
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/ " )
2020-11-10 14:00:56 +00:00
end
2021-04-22 06:38:49 +00:00
end
local function isFileOk ( filename )
local path , name = util.splitFilePathName ( filename )
2020-11-10 14:00:56 +00:00
2021-04-22 06:38:49 +00:00
if not isPathAllowed ( path ) then
return false
end
return name ~= " " and lfs.attributes ( filename , " mode " ) ~= " directory "
2020-11-10 14:00:56 +00:00
end
2020-11-28 12:57:33 +00:00
local function getExtension ( filename )
local _ , name = util.splitFilePathName ( filename )
return util.getFileNameSuffix ( name ) : lower ( )
end
Clarify our OOP semantics across the codebase (#9586)
Basically:
* Use `extend` for class definitions
* Use `new` for object instantiations
That includes some minor code cleanups along the way:
* Updated `Widget`'s docs to make the semantics clearer.
* Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283)
* Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass).
* Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events.
* Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier.
* Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references.
* ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak).
* Terminal: Make sure the shell is killed on plugin teardown.
* InputText: Fix Home/End/Del physical keys to behave sensibly.
* InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...).
* OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of.
* ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed!
* Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
local CoverImage = WidgetContainer : extend {
2020-11-10 14:00:56 +00:00
name = " coverimage " ,
is_doc_only = true ,
}
2021-04-22 06:38:49 +00:00
local default_cache_path = DataStorage : getDataDir ( ) .. " /cache/cover_image.cache/ "
local default_fallback_path = DataStorage : getDataDir ( ) .. " / "
2020-11-10 14:00:56 +00:00
function CoverImage : init ( )
2021-04-22 06:38:49 +00:00
self.cover_image_path = G_reader_settings : readSetting ( " cover_image_path " ) or Device : getDefaultCoverPath ( )
2021-11-09 19:09:05 +00:00
self.cover_image_format = G_reader_settings : readSetting ( " cover_image_format " , " auto " )
self.cover_image_quality = G_reader_settings : readSetting ( " cover_image_quality " , 75 )
self.cover_image_grayscale = G_reader_settings : isTrue ( " cover_image_grayscale " )
self.cover_image_stretch_limit = G_reader_settings : readSetting ( " cover_image_stretch_limit " , 8 )
self.cover_image_background = G_reader_settings : readSetting ( " cover_image_background " , " black " )
self.cover_image_fallback_path = G_reader_settings : readSetting ( " cover_image_fallback_path " ,
default_fallback_path )
self.cover_image_cache_path = G_reader_settings : readSetting ( " cover_image_cache_path " ,
default_cache_path )
self.cover_image_cache_maxfiles = G_reader_settings : readSetting ( " cover_image_cache_maxfiles " , 36 )
self.cover_image_cache_maxsize = G_reader_settings : readSetting ( " cover_image_cache_maxsize " , 5 ) -- MB
2021-04-22 06:38:49 +00:00
self.cover_image_cache_prefix = " cover_ "
self.cover = G_reader_settings : isTrue ( " cover_image_enabled " )
2020-11-10 14:00:56 +00:00
self.fallback = G_reader_settings : isTrue ( " cover_image_fallback " )
2020-11-18 20:30:22 +00:00
2021-04-22 06:38:49 +00:00
lfs.mkdir ( self.cover_image_cache_path )
2020-11-10 14:00:56 +00:00
2021-04-22 06:38:49 +00:00
self.ui . menu : registerToMainMenu ( self )
2020-11-10 14:00:56 +00:00
end
function CoverImage : cleanUpImage ( )
2021-04-22 06:38:49 +00:00
if self.cover_image_fallback_path == " " or not self : fallbackEnabled ( ) then
2020-11-10 14:00:56 +00:00
os.remove ( self.cover_image_path )
elseif lfs.attributes ( self.cover_image_fallback_path , " mode " ) ~= " file " then
UIManager : show ( InfoMessage : new {
2021-04-22 06:38:49 +00:00
text = T ( _ ( " \" %1 \" \n is not a valid image file! \n A valid fallback image is required in Cover-Image. " ) , self.cover_image_fallback_path ) ,
2020-11-10 14:00:56 +00:00
show_icon = true ,
timeout = 10 ,
} )
os.remove ( self.cover_image_path )
2021-04-22 06:38:49 +00:00
elseif isFileOk ( self.cover_image_path ) then
2020-11-10 14:00:56 +00:00
ffiutil.copyFile ( self.cover_image_fallback_path , self.cover_image_path )
end
end
function CoverImage : createCoverImage ( doc_settings )
2021-04-22 06:38:49 +00:00
if self : coverEnabled ( ) and doc_settings : nilOrFalse ( " exclude_cover_image " ) then
2023-06-09 07:36:34 +00:00
local cover_image , custom_cover = FileManagerBookInfo : getCoverImage ( self.ui . document )
2020-11-18 20:30:22 +00:00
if cover_image then
2023-06-09 07:36:34 +00:00
local cache_file = self : getCacheFile ( custom_cover )
2021-04-22 06:38:49 +00:00
if lfs.attributes ( cache_file , " mode " ) == " file " then
ffiutil.copyFile ( cache_file , self.cover_image_path )
lfs.touch ( cache_file ) -- update date
return
end
2022-11-28 12:24:05 +00:00
local s_w , s_h = Screen : getWidth ( ) , Screen : getHeight ( )
2020-11-18 20:30:22 +00:00
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
2021-04-22 06:38:49 +00:00
local act_format = self.cover_image_format == " auto " and getExtension ( self.cover_image_path ) or self.cover_image_format
2021-08-16 00:11:54 +00:00
if not cover_image : writeToFile ( self.cover_image_path , act_format , self.cover_image_quality , self.cover_image_grayscale ) then
2020-12-05 22:54:06 +00:00
UIManager : show ( InfoMessage : new {
2020-12-12 20:04:58 +00:00
text = _ ( " Error writing file " ) .. " \n " .. self.cover_image_path ,
2020-12-05 22:54:06 +00:00
show_icon = true ,
} )
end
2020-11-18 20:30:22 +00:00
cover_image : free ( )
2021-04-22 06:38:49 +00:00
ffiutil.copyFile ( self.cover_image_path , cache_file )
self : cleanCache ( )
2020-11-18 20:30:22 +00:00
return
end
2021-01-31 20:34:37 +00:00
local screen_ratio = s_w / s_h
local image_ratio = i_w / i_h
local ratio_divergence_percent = math.abs ( 100 - image_ratio / screen_ratio * 100 )
2020-11-18 20:30:22 +00:00
2021-01-31 20:34:37 +00:00
logger.dbg ( " CoverImage: geometries screen= " .. screen_ratio .. " , image= " .. image_ratio .. " ; ratio= " .. ratio_divergence_percent )
local image
if ratio_divergence_percent < self.cover_image_stretch_limit then -- stretch
logger.dbg ( " CoverImage: stretch to fullscreen " )
image = RenderImage : scaleBlitBuffer ( cover_image , s_w , s_h )
else -- scale
local scaled_w , scaled_h = math.floor ( i_w * scale_factor ) , math.floor ( i_h * scale_factor )
logger.dbg ( " CoverImage: scale to fullscreen, fill background " )
2020-11-18 20:30:22 +00:00
2021-01-31 20:34:37 +00:00
cover_image = RenderImage : scaleBlitBuffer ( cover_image , scaled_w , scaled_h )
-- new buffer with screen dimensions,
image = Blitbuffer.new ( s_w , s_h , cover_image : getType ( ) ) -- new buffer, filled with black
if self.cover_image_background == " white " then
image : fill ( Blitbuffer.COLOR_WHITE )
elseif self.cover_image_background == " gray " then
image : fill ( Blitbuffer.COLOR_GRAY )
end
-- copy scaled image to buffer
if s_w > scaled_w then -- move right
image : blitFrom ( cover_image , math.floor ( ( s_w - scaled_w ) / 2 ) , 0 , 0 , 0 , scaled_w , scaled_h )
else -- move down
image : blitFrom ( cover_image , 0 , math.floor ( ( s_h - scaled_h ) / 2 ) , 0 , 0 , scaled_w , scaled_h )
end
2020-11-18 20:30:22 +00:00
end
2021-01-31 20:34:37 +00:00
2020-11-18 20:30:22 +00:00
cover_image : free ( )
2020-11-28 12:57:33 +00:00
2021-04-22 06:38:49 +00:00
local act_format = self.cover_image_format == " auto " and getExtension ( self.cover_image_path ) or self.cover_image_format
2021-08-16 00:11:54 +00:00
if not image : writeToFile ( self.cover_image_path , act_format , self.cover_image_quality , self.cover_image_grayscale ) then
2020-12-05 22:54:06 +00:00
UIManager : show ( InfoMessage : new {
2020-12-12 20:04:58 +00:00
text = _ ( " Error writing file " ) .. " \n " .. self.cover_image_path ,
2020-12-05 22:54:06 +00:00
show_icon = true ,
} )
end
2020-11-28 12:57:33 +00:00
2020-11-18 20:30:22 +00:00
image : free ( )
2020-11-10 14:00:56 +00:00
logger.dbg ( " CoverImage: image written to " .. self.cover_image_path )
2021-04-22 06:38:49 +00:00
ffiutil.copyFile ( self.cover_image_path , cache_file )
self : cleanCache ( )
2020-11-10 14:00:56 +00:00
end
end
end
function CoverImage : onCloseDocument ( )
logger.dbg ( " CoverImage: onCloseDocument " )
2021-04-22 06:38:49 +00:00
if self : fallbackEnabled ( ) then
2020-11-10 14:00:56 +00:00
self : cleanUpImage ( )
end
end
function CoverImage : onReaderReady ( doc_settings )
logger.dbg ( " CoverImage: onReaderReady " )
self : createCoverImage ( doc_settings )
end
2021-04-22 06:38:49 +00:00
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
---------------------------
2023-06-09 07:36:34 +00:00
function CoverImage : getCacheFile ( custom_cover )
local custom_cover_mtime = custom_cover and lfs.attributes ( custom_cover , " modification " ) or " "
2021-04-22 06:38:49 +00:00
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)
2023-06-09 07:36:34 +00:00
local key = document_name .. custom_cover_mtime .. self.cover_image_quality .. self.cover_image_stretch_limit
.. self.cover_image_background .. self.cover_image_format .. tostring ( self.cover_image_grayscale )
2021-04-22 06:38:49 +00:00
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 )
2021-05-18 09:52:15 +00:00
local cache_size = 0
2021-04-22 06:38:49 +00:00
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
2021-05-18 09:52:15 +00:00
local blocksize = lfs.attributes ( file ) . blksize or 4096
2021-12-25 10:29:57 +00:00
local size = math.floor ( ( ( lfs.attributes ( file ) . size ) + blocksize - 1 ) / blocksize ) * blocksize
table.insert ( files , {
2021-04-22 06:38:49 +00:00
name = file ,
2021-12-25 10:29:57 +00:00
size = size ,
2021-04-22 06:38:49 +00:00
mod = lfs.attributes ( file ) . modification ,
2021-12-25 10:29:57 +00:00
} )
cache_size = cache_size + size
2021-04-22 06:38:49 +00:00
end
end
end
2021-12-25 10:29:57 +00:00
logger.dbg ( " CoverImage: start - cache size: " .. util.getFriendlySize ( cache_size ) ..
" , cached files: " .. # files )
return # files , cache_size , files
2021-04-22 06:38:49 +00:00
end
function CoverImage : cleanCache ( )
if not self : isCacheEnabled ( ) then
self : emptyCache ( )
return
end
2021-05-18 09:52:15 +00:00
local cache_count , cache_size , files = self : getCacheFiles ( self.cover_image_cache_path , self.cover_image_cache_prefix )
2021-04-22 06:38:49 +00:00
-- 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 )
2021-05-18 09:52:15 +00:00
or ( cache_size > self.cover_image_cache_maxsize * 1000 * 1000 and self.cover_image_cache_maxsize ~= 0 )
2021-04-22 06:38:49 +00:00
and index <= # files do
os.remove ( files [ index ] . name )
cache_count = cache_count - 1
2021-05-18 09:52:15 +00:00
cache_size = cache_size - files [ index ] . size
2021-04-22 06:38:49 +00:00
index = index + 1
end
2021-12-25 10:29:57 +00:00
logger.dbg ( " CoverImage: clean - cache size: " .. util.getFriendlySize ( cache_size ) ..
" , cached files: " .. cache_count )
2021-04-22 06:38:49 +00:00
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 " ) ,
2022-03-04 20:20:00 +00:00
id = " close " ,
2021-04-22 06:38:49 +00:00
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
] ]
2022-12-30 06:26:36 +00:00
function CoverImage : sizeSpinner ( touchmenu_instance , setting , title , min , max , default , callback , unit )
2021-04-22 06:38:49 +00:00
local SpinWidget = require ( " ui/widget/spinwidget " )
UIManager : show ( SpinWidget : new {
2021-12-25 10:29:57 +00:00
value = self [ setting ] ,
2021-04-22 06:38:49 +00:00
value_min = min ,
value_max = max ,
2022-12-30 06:26:36 +00:00
unit = unit ,
2021-04-22 06:38:49 +00:00
default_value = default ,
title_text = title ,
ok_text = _ ( " Set " ) ,
callback = function ( spin )
2021-12-25 10:29:57 +00:00
self [ setting ] = spin.value
G_reader_settings : saveSetting ( setting , self [ setting ] )
if callback then
callback ( self )
2021-04-22 06:38:49 +00:00
end
if touchmenu_instance then touchmenu_instance : updateItems ( ) end
end
} )
end
-------------- menus and longer texts -----------
2020-11-10 14:00:56 +00:00
local about_text = _ ( [ [
2021-04-22 06:38:49 +00:00
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 .
2020-11-10 14:00:56 +00:00
2021-04-22 06:38:49 +00:00
If disabled , the cover file will be deleted .
2020-11-10 14:00:56 +00:00
2021-04-22 06:38:49 +00:00
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.
2020-11-10 14:00:56 +00:00
2021-04-22 06:38:49 +00:00
If fallback is disabled , the screensaver image will stay in place after closing a book . ] ] )
2020-11-10 14:00:56 +00:00
2021-04-22 06:38:49 +00:00
local set_image_text = _ ( [ [
You can either choose an existing file :
2021-08-24 20:19:07 +00:00
- Choose a file
2021-04-22 06:38:49 +00:00
or specify a new file :
2021-08-24 20:19:07 +00:00
- First choose a folder
2021-04-22 06:38:49 +00:00
- Then add the name of the new file
or delete the path :
2021-08-24 20:19:07 +00:00
- First choose a folder
2021-04-22 06:38:49 +00:00
- Clear the name of the file ] ] )
-- menu entry: Cache settings
2021-04-24 12:00:38 +00:00
function CoverImage : menuEntryCache ( )
2021-04-22 06:38:49 +00:00
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
2021-11-09 19:09:05 +00:00
return T ( _ ( " Maximum number of cached covers: %1 " ) , number )
2021-04-22 06:38:49 +00:00
end ,
help_text = _ ( " If set to zero the number of cache files is unlimited. \n If 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
2021-04-30 19:24:04 +00:00
number = util.getFriendlySize ( self.cover_image_cache_maxsize * 1e6 )
2021-04-22 06:38:49 +00:00
elseif self.cover_image_cache_maxsize == 0 then
number = _ ( " unlimited " )
else
number = _ ( " off " )
end
2021-11-09 19:09:05 +00:00
return T ( _ ( " Maximum size of cached covers: %1 " ) , number )
2021-04-22 06:38:49 +00:00
end ,
help_text = _ ( " If set to zero the cache size is unlimited. \n If set to -1 the cache is disabled. " ) ,
checked_func = function ( )
return self.cover_image_cache_maxsize >= 0
end ,
callback = function ( touchmenu_instance )
2022-12-30 06:26:36 +00:00
self : sizeSpinner ( touchmenu_instance , " cover_image_cache_maxsize " , _ ( " Cache size " ) , - 1 , 100 , 5 , self.cleanCache , C_ ( " Data storage size " , " MB " ) )
2021-04-22 06:38:49 +00:00
end ,
} ,
2021-04-24 12:00:38 +00:00
self : menuEntrySetPath ( " cover_image_cache_path " , _ ( " Cover cache folder " ) , _ ( " Current cache path: \n %1 " ) ,
2022-07-06 15:15:47 +00:00
_ ( " Choose a cache folder. The contents of the old folder will be migrated. " ) ,
2021-11-09 19:09:05 +00:00
default_cache_path , true , false , self.migrateCache ) ,
2021-04-22 06:38:49 +00:00
{
text = _ ( " Clear cached covers " ) ,
help_text_func = function ( )
2021-05-18 09:52:15 +00:00
local cache_count , cache_size
2021-04-22 06:38:49 +00:00
= self : getCacheFiles ( self.cover_image_cache_path , self.cover_image_cache_prefix )
2021-05-18 09:52:15 +00:00
return T ( _ ( " The cache contains %1 files and uses %2. " ) , cache_count , util.getFriendlySize ( cache_size ) )
2021-04-22 06:38:49 +00:00
end ,
callback = function ( )
UIManager : show ( ConfirmBox : new {
2021-04-22 20:30:46 +00:00
text = _ ( " Clear the cover image cache? " ) ,
2021-04-22 06:38:49 +00:00
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
] ]
2021-04-24 12:00:38 +00:00
function CoverImage : menuEntrySetPath ( key , title , help , info , default , folder_only , new_file , migrate )
2021-04-22 06:38:49 +00:00
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
2021-08-16 00:11:54 +00:00
function CoverImage : menuEntryFormat ( title , format , grayscale )
2021-04-22 06:38:49 +00:00
return {
text = title ,
checked_func = function ( )
2021-08-16 00:11:54 +00:00
return self.cover_image_format == format and self.cover_image_grayscale == grayscale
2021-04-22 06:38:49 +00:00
end ,
callback = function ( )
local old_cover_image_format = self.cover_image_format
2021-08-16 00:11:54 +00:00
local old_cover_image_grayscale = self.cover_image_grayscale
2021-04-22 06:38:49 +00:00
self.cover_image_format = format
G_reader_settings : saveSetting ( " cover_image_format " , format )
2021-08-16 00:11:54 +00:00
self.cover_image_grayscale = grayscale
G_reader_settings : saveSetting ( " cover_image_grayscale " , grayscale )
if self : coverEnabled ( ) and ( old_cover_image_format ~= format or old_cover_image_grayscale ~= grayscale ) then
2021-04-22 06:38:49 +00:00
self : createCoverImage ( self.ui . doc_settings )
end
end ,
}
end
2021-04-24 12:00:38 +00:00
function CoverImage : menuEntryBackground ( color , color_translatable )
2021-04-22 06:38:49 +00:00
return {
2021-04-24 12:00:38 +00:00
text = T ( _ ( " Fit to screen, %1 background " ) , _ ( color_translatable ) ) ,
2021-04-22 06:38:49 +00:00
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
2021-04-24 12:00:38 +00:00
function CoverImage : menuEntrySBF ( )
2021-04-22 06:38:49 +00:00
return {
text = _ ( " Size, background and format " ) ,
enabled_func = function ( )
return self : coverEnabled ( )
end ,
sub_item_table = {
{
text_func = function ( )
2021-11-09 19:09:05 +00:00
return T ( _ ( " Aspect ratio stretch threshold: %1 " ) ,
2022-12-30 06:26:36 +00:00
self.cover_image_stretch_limit ~= 0 and self.cover_image_stretch_limit .. " % " or _ ( " off " ) )
2021-04-22 06:38:49 +00:00
end ,
keep_menu_open = true ,
help_text_func = function ( )
2022-12-30 06:26:36 +00:00
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 )
2021-04-22 06:38:49 +00:00
end ,
callback = function ( touchmenu_instance )
local function createCover ( )
self : createCoverImage ( self.ui . doc_settings )
end
2022-12-30 06:26:36 +00:00
self : sizeSpinner ( touchmenu_instance , " cover_image_stretch_limit " , _ ( " Set stretch threshold " ) , 0 , 20 , 8 , createCover , " % " )
2021-04-22 06:38:49 +00:00
end ,
} ,
2021-04-24 12:00:38 +00:00
self : menuEntryBackground ( " black " , _ ( " black " ) ) ,
self : menuEntryBackground ( " white " , _ ( " white " ) ) ,
self : menuEntryBackground ( " gray " , _ ( " gray " ) ) ,
2021-04-22 06:38:49 +00:00
{
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 ,
} ,
2021-04-24 12:00:38 +00:00
self : menuEntryFormat ( _ ( " JPG file format " ) , " jpg " ) ,
self : menuEntryFormat ( _ ( " PNG file format " ) , " png " ) ,
2021-08-16 00:11:54 +00:00
self : menuEntryFormat ( _ ( " BMP file format (color) " ) , " bmp " ) ,
self : menuEntryFormat ( _ ( " BMP file format (grayscale) " ) , " bmp " , true ) ,
2021-04-22 06:38:49 +00:00
} ,
}
end
-- CoverImage main menu
2020-11-10 14:00:56 +00:00
function CoverImage : addToMainMenu ( menu_items )
menu_items.coverimage = {
sorting_hint = " screen " ,
2021-02-20 23:17:42 +00:00
text = _ ( " Cover image " ) ,
2020-11-10 14:00:56 +00:00
checked_func = function ( )
2021-04-22 06:38:49 +00:00
return self : coverEnabled ( ) or self : fallbackEnabled ( )
2020-11-10 14:00:56 +00:00
end ,
sub_item_table = {
-- menu entry: about cover image
{
text = _ ( " About cover image " ) ,
callback = function ( )
UIManager : show ( InfoMessage : new {
text = about_text ,
} )
end ,
2021-04-22 06:38:49 +00:00
keep_menu_open = true ,
2020-11-10 14:00:56 +00:00
separator = true ,
} ,
-- menu entry: filename dialog
2021-04-24 12:00:38 +00:00
self : menuEntrySetPath ( " cover_image_path " , _ ( " Set image path " ) , _ ( " Current Cover image path: \n %1 " ) , set_image_text ,
2021-04-22 06:38:49 +00:00
Device : getDefaultCoverPath ( ) , false , true , self.migrateCover ) ,
2020-11-10 14:00:56 +00:00
-- menu entry: enable
{
2021-02-20 23:17:42 +00:00
text = _ ( " Save cover image " ) ,
2020-11-10 14:00:56 +00:00
checked_func = function ( )
2021-04-22 06:38:49 +00:00
return self : coverEnabled ( )
2020-11-10 14:00:56 +00:00
end ,
enabled_func = function ( )
2021-04-22 06:38:49 +00:00
return self.cover_image_path ~= " " and isFileOk ( self.cover_image_path )
2020-11-10 14:00:56 +00:00
end ,
callback = function ( )
if self.cover_image_path ~= " " then
2021-04-22 06:38:49 +00:00
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
2020-11-10 14:00:56 +00:00
self : createCoverImage ( self.ui . doc_settings )
else
self : cleanUpImage ( )
end
end
end ,
} ,
2021-04-22 06:38:49 +00:00
-- menu entry: scale, background, format
2021-04-24 12:00:38 +00:00
self : menuEntrySBF ( ) ,
2020-11-10 14:00:56 +00:00
-- menu entry: exclude this cover
{
text = _ ( " Exclude this book cover " ) ,
checked_func = function ( )
2021-03-06 21:44:18 +00:00
return self.ui and self.ui . doc_settings and self.ui . doc_settings : isTrue ( " exclude_cover_image " )
2020-11-10 14:00:56 +00:00
end ,
callback = function ( )
2021-03-06 21:44:18 +00:00
if self.ui . doc_settings : isTrue ( " exclude_cover_image " ) then
self.ui . doc_settings : makeFalse ( " exclude_cover_image " )
2020-11-10 14:00:56 +00:00
self : createCoverImage ( self.ui . doc_settings )
else
2021-03-06 21:44:18 +00:00
self.ui . doc_settings : makeTrue ( " exclude_cover_image " )
2020-11-10 14:00:56 +00:00
self : cleanUpImage ( )
end
self.ui : saveSettings ( )
end ,
separator = true ,
} ,
-- menu entry: set fallback image
2021-04-24 12:00:38 +00:00
self : menuEntrySetPath ( " cover_image_fallback_path " , _ ( " Set fallback path " ) ,
2021-08-24 20:19:07 +00:00
_ ( " The fallback image used on document close is: \n %1 " ) , _ ( " You can choose a fallback image. " ) , default_fallback_path , false , false ) ,
2020-11-10 14:00:56 +00:00
-- menu entry: fallback
{
text = _ ( " Turn on fallback image " ) ,
checked_func = function ( )
2021-04-22 06:38:49 +00:00
return self : fallbackEnabled ( )
end ,
enabled_func = function ( )
return lfs.attributes ( self.cover_image_fallback_path , " mode " ) == " file "
2020-11-10 14:00:56 +00:00
end ,
callback = function ( )
self.fallback = not self.fallback
2021-04-22 06:38:49 +00:00
self.fallback = self.fallback and self : fallbackEnabled ( )
2020-11-10 14:00:56 +00:00
G_reader_settings : saveSetting ( " cover_image_fallback " , self.fallback )
2021-04-22 06:38:49 +00:00
if not self : coverEnabled ( ) then
self : cleanUpImage ( )
end
2020-11-10 14:00:56 +00:00
end ,
separator = true ,
} ,
2021-04-22 06:38:49 +00:00
-- menu entry: Cache settings
2021-04-24 12:00:38 +00:00
self : menuEntryCache ( ) ,
2020-11-10 14:00:56 +00:00
} ,
}
end
return CoverImage