2017-07-01 10:11:44 +00:00
--[[--
This module provides a way to display book information ( filename and book metadata )
] ]
2020-01-04 00:18:51 +00:00
local BD = require ( " ui/bidi " )
2023-05-03 12:43:05 +00:00
local ButtonDialog = require ( " ui/widget/buttondialog " )
2023-09-01 05:07:29 +00:00
local ConfirmBox = require ( " ui/widget/confirmbox " )
local Device = require ( " device " )
2017-07-01 10:11:44 +00:00
local DocSettings = require ( " docsettings " )
2023-08-30 04:53:59 +00:00
local Document = require ( " document/document " )
2017-07-01 10:11:44 +00:00
local DocumentRegistry = require ( " document/documentregistry " )
2023-09-06 06:41:10 +00:00
local Event = require ( " ui/event " )
2017-07-01 10:11:44 +00:00
local InfoMessage = require ( " ui/widget/infomessage " )
2023-09-01 05:07:29 +00:00
local InputDialog = require ( " ui/widget/inputdialog " )
local TextViewer = require ( " ui/widget/textviewer " )
2017-07-01 10:11:44 +00:00
local UIManager = require ( " ui/uimanager " )
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 WidgetContainer = require ( " ui/widget/container/widgetcontainer " )
2023-11-09 05:34:56 +00:00
local Utf8Proc = require ( " ffi/utf8proc " )
2017-07-01 10:11:44 +00:00
local filemanagerutil = require ( " apps/filemanager/filemanagerutil " )
local lfs = require ( " libs/libkoreader-lfs " )
local util = require ( " util " )
local _ = require ( " gettext " )
2023-10-31 05:30:39 +00:00
local N_ = _.ngettext
local T = require ( " ffi/util " ) . template
2017-07-01 10:11:44 +00:00
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 BookInfo = WidgetContainer : extend {
2023-12-25 05:40:13 +00:00
title = _ ( " Book information " ) ,
2023-08-30 04:53:59 +00:00
props = {
" title " ,
" authors " ,
" series " ,
" series_index " ,
" language " ,
" keywords " ,
" description " ,
} ,
2023-09-01 05:07:29 +00:00
prop_text = {
cover = _ ( " Cover image: " ) ,
title = _ ( " Title: " ) ,
authors = _ ( " Authors: " ) ,
series = _ ( " Series: " ) ,
series_index = _ ( " Series index: " ) ,
language = _ ( " Language: " ) ,
keywords = _ ( " Keywords: " ) ,
description = _ ( " Description: " ) ,
pages = _ ( " Pages: " ) ,
} ,
2017-07-01 10:11:44 +00:00
}
function BookInfo : init ( )
2023-10-31 05:30:39 +00:00
if self.document then -- only for Reader menu
2017-07-01 10:11:44 +00:00
self.ui . menu : registerToMainMenu ( self )
end
end
function BookInfo : addToMainMenu ( menu_items )
menu_items.book_info = {
2023-12-25 05:40:13 +00:00
text = self.title ,
2017-07-01 10:11:44 +00:00
callback = function ( )
2019-03-12 08:17:27 +00:00
self : onShowBookInfo ( )
2017-07-01 10:11:44 +00:00
end ,
}
end
2023-08-30 04:53:59 +00:00
-- Shows book information.
2023-09-06 06:41:10 +00:00
function BookInfo : show ( file , book_props )
2023-09-01 05:07:29 +00:00
self.prop_updated = nil
2017-07-01 10:11:44 +00:00
local kv_pairs = { }
2023-03-31 09:59:11 +00:00
-- File section
local folder , filename = util.splitFilePathName ( file )
local __ , filetype = filemanagerutil.splitFileNameType ( filename )
local attr = lfs.attributes ( file )
local file_size = attr.size or 0
2017-10-20 17:29:52 +00:00
local size_f = util.getFriendlySize ( file_size )
local size_b = util.getFormattedSize ( file_size )
2020-01-04 00:18:51 +00:00
table.insert ( kv_pairs , { _ ( " Filename: " ) , BD.filename ( filename ) } )
2017-07-01 10:11:44 +00:00
table.insert ( kv_pairs , { _ ( " Format: " ) , filetype : upper ( ) } )
2023-03-31 09:59:11 +00:00
table.insert ( kv_pairs , { _ ( " Size: " ) , string.format ( " %s (%s bytes) " , size_f , size_b ) } )
table.insert ( kv_pairs , { _ ( " File date: " ) , os.date ( " %Y-%m-%d %H:%M:%S " , attr.modification ) } )
table.insert ( kv_pairs , { _ ( " Folder: " ) , BD.dirpath ( filemanagerutil.abbreviate ( folder ) ) , separator = true } )
2017-07-01 10:11:44 +00:00
2023-03-31 09:59:11 +00:00
-- Book section
2017-07-01 10:11:44 +00:00
-- book_props may be provided if caller already has them available
2021-03-06 21:44:18 +00:00
-- but it may lack "pages", that we may get from sidecar file
2017-07-01 10:11:44 +00:00
if not book_props or not book_props.pages then
2023-09-01 05:07:29 +00:00
book_props = BookInfo.getDocProps ( file , book_props )
end
-- cover image
2023-10-31 05:30:39 +00:00
self.custom_book_cover = DocSettings : findCustomCoverFile ( file )
2023-09-01 05:07:29 +00:00
local key_text = self.prop_text [ " cover " ]
if self.custom_book_cover then
key_text = " \u{F040} " .. key_text
end
table.insert ( kv_pairs , { key_text , _ ( " Tap to display " ) ,
callback = function ( )
self : onShowBookCover ( file )
end ,
hold_callback = function ( )
2023-09-06 06:41:10 +00:00
self : showCustomDialog ( file , book_props )
2023-09-01 05:07:29 +00:00
end ,
separator = true ,
} )
-- metadata
local custom_props
2023-10-31 05:30:39 +00:00
local custom_metadata_file = DocSettings : findCustomMetadataFile ( file )
2023-09-01 05:07:29 +00:00
if custom_metadata_file then
2023-10-31 05:30:39 +00:00
self.custom_doc_settings = DocSettings.openSettingsFile ( custom_metadata_file )
2023-09-01 05:07:29 +00:00
custom_props = self.custom_doc_settings : readSetting ( " custom_props " )
2017-07-01 10:11:44 +00:00
end
2023-12-25 05:40:13 +00:00
local values_lang , callback
2023-08-30 04:53:59 +00:00
for _i , prop_key in ipairs ( self.props ) do
2023-03-31 09:59:11 +00:00
local prop = book_props [ prop_key ]
if prop == nil or prop == " " then
prop = _ ( " N/A " )
elseif prop_key == " title " then
prop = BD.auto ( prop )
elseif prop_key == " authors " or prop_key == " keywords " then
if prop : find ( " \n " ) then -- BD auto isolate each entry
prop = util.splitToArray ( prop , " \n " )
for i = 1 , # prop do
prop [ i ] = BD.auto ( prop [ i ] )
end
prop = table.concat ( prop , " \n " )
else
prop = BD.auto ( prop )
end
elseif prop_key == " language " then
-- Get a chance to have title, authors... rendered with alternate
-- glyphs for the book language (e.g. japanese book in chinese UI)
values_lang = prop
elseif prop_key == " description " then
-- Description may (often in EPUB, but not always) or may not (rarely in PDF) be HTML
prop = util.htmlToPlainTextIfHtml ( prop )
2023-12-25 05:40:13 +00:00
callback = function ( ) -- proper text_type in TextViewer
self : showBookProp ( " description " , prop )
end
2020-01-04 00:18:51 +00:00
end
2023-09-01 05:07:29 +00:00
key_text = self.prop_text [ prop_key ]
if custom_props and custom_props [ prop_key ] then -- customized
key_text = " \u{F040} " .. key_text
2023-08-30 04:53:59 +00:00
end
2023-09-01 05:07:29 +00:00
table.insert ( kv_pairs , { key_text , prop ,
2023-12-25 05:40:13 +00:00
callback = callback ,
2023-09-01 05:07:29 +00:00
hold_callback = function ( )
2023-09-06 06:41:10 +00:00
self : showCustomDialog ( file , book_props , prop_key )
2023-09-01 05:07:29 +00:00
end ,
2023-05-03 12:43:05 +00:00
} )
2017-07-01 10:11:44 +00:00
end
2023-09-01 05:07:29 +00:00
-- pages
local is_doc = self.document and true or false
table.insert ( kv_pairs , { self.prop_text [ " pages " ] , book_props [ " pages " ] or _ ( " N/A " ) , separator = is_doc } )
2017-07-01 10:11:44 +00:00
2023-03-31 09:59:11 +00:00
-- Page section
if is_doc then
2023-09-01 05:07:29 +00:00
local lines_nb , words_nb = self.ui . view : getCurrentPageLineWordCounts ( )
2023-03-31 09:59:11 +00:00
if lines_nb == 0 then
lines_nb = _ ( " N/A " )
words_nb = _ ( " N/A " )
end
table.insert ( kv_pairs , { _ ( " Current page lines: " ) , lines_nb } )
table.insert ( kv_pairs , { _ ( " Current page words: " ) , words_nb } )
2020-01-23 16:35:57 +00:00
end
2023-03-31 09:59:11 +00:00
local KeyValuePage = require ( " ui/widget/keyvaluepage " )
2023-05-03 12:43:05 +00:00
self.kvp_widget = KeyValuePage : new {
2023-12-25 05:40:13 +00:00
title = self.title ,
2017-10-23 19:30:06 +00:00
value_overflow_align = " right " ,
2017-07-01 10:11:44 +00:00
kv_pairs = kv_pairs ,
2020-01-23 16:35:57 +00:00
values_lang = values_lang ,
2023-05-03 12:43:05 +00:00
close_callback = function ( )
2023-09-01 05:07:29 +00:00
self.custom_doc_settings = nil
2023-06-09 07:36:34 +00:00
self.custom_book_cover = nil
2023-09-01 05:07:29 +00:00
if self.prop_updated then
2023-10-03 06:24:29 +00:00
UIManager : broadcastEvent ( Event : new ( " InvalidateMetadataCache " , file ) )
2023-09-06 06:41:10 +00:00
UIManager : broadcastEvent ( Event : new ( " BookMetadataChanged " , self.prop_updated ) )
2023-05-03 12:43:05 +00:00
end
end ,
2017-07-01 10:11:44 +00:00
}
2023-05-03 12:43:05 +00:00
UIManager : show ( self.kvp_widget )
2017-07-01 10:11:44 +00:00
end
2023-09-23 07:21:19 +00:00
function BookInfo . getCustomProp ( prop_key , filepath )
2023-10-31 05:30:39 +00:00
local custom_metadata_file = DocSettings : findCustomMetadataFile ( filepath )
2023-09-23 07:21:19 +00:00
return custom_metadata_file
2023-10-31 05:30:39 +00:00
and DocSettings.openSettingsFile ( custom_metadata_file ) : readSetting ( " custom_props " ) [ prop_key ]
2023-09-23 07:21:19 +00:00
end
2023-09-01 05:07:29 +00:00
-- Returns extended and customized metadata.
function BookInfo . extendProps ( original_props , filepath )
2023-09-12 04:54:38 +00:00
-- do not customize if filepath is not passed (eg from covermenu)
2023-10-31 05:30:39 +00:00
local custom_metadata_file = filepath and DocSettings : findCustomMetadataFile ( filepath )
2023-09-01 05:07:29 +00:00
local custom_props = custom_metadata_file
2023-10-31 05:30:39 +00:00
and DocSettings.openSettingsFile ( custom_metadata_file ) : readSetting ( " custom_props " ) or { }
2023-08-30 04:53:59 +00:00
original_props = original_props or { }
local props = { }
2023-09-01 05:07:29 +00:00
for _ , prop_key in ipairs ( BookInfo.props ) do
2023-08-30 04:53:59 +00:00
props [ prop_key ] = custom_props [ prop_key ] or original_props [ prop_key ]
end
props.pages = original_props.pages
-- if original title is empty, generate it as filename without extension
props.display_title = props.title or filemanagerutil.splitFileNameType ( filepath )
return props
end
2023-09-01 05:07:29 +00:00
-- Returns customized document metadata, including number of pages.
function BookInfo . getDocProps ( file , book_props , no_open_document )
2023-03-07 20:24:42 +00:00
if DocSettings : hasSidecarFile ( file ) then
local doc_settings = DocSettings : open ( file )
if not book_props then
-- Files opened after 20170701 have a "doc_props" setting with
-- complete metadata and "doc_pages" with accurate nb of pages
book_props = doc_settings : readSetting ( " doc_props " )
end
if not book_props then
-- File last opened before 20170701 may have a "stats" setting.
-- with partial metadata, or empty metadata if statistics plugin
-- was not enabled when book was read (we can guess that from
-- the fact that stats.page = 0)
local stats = doc_settings : readSetting ( " stats " )
if stats and stats.pages ~= 0 then
2023-08-30 04:53:59 +00:00
-- title, authors, series, series_index, language
book_props = Document : getProps ( stats )
2023-03-07 20:24:42 +00:00
end
end
-- Files opened after 20170701 have an accurate "doc_pages" setting.
local doc_pages = doc_settings : readSetting ( " doc_pages " )
if doc_pages and book_props then
book_props.pages = doc_pages
end
end
2023-09-01 05:07:29 +00:00
-- If still no book_props (book never opened or empty "stats"),
-- but custom metadata exists, it has a copy of original doc_props
if not book_props then
2023-10-31 05:30:39 +00:00
local custom_metadata_file = DocSettings : findCustomMetadataFile ( file )
2023-09-01 05:07:29 +00:00
if custom_metadata_file then
2023-10-31 05:30:39 +00:00
book_props = DocSettings.openSettingsFile ( custom_metadata_file ) : readSetting ( " doc_props " )
2023-09-01 05:07:29 +00:00
end
end
-- If still no book_props, open the document to get them
2023-03-28 13:16:53 +00:00
if not book_props and not no_open_document then
2023-03-07 20:24:42 +00:00
local document = DocumentRegistry : openDocument ( file )
if document then
local loaded = true
local pages
if document.loadDocument then -- CreDocument
if not document : loadDocument ( false ) then -- load only metadata
-- failed loading, calling other methods would segfault
loaded = false
end
-- For CreDocument, we would need to call document:render()
-- to get nb of pages, but the nb obtained by simply calling
-- here document:getPageCount() is wrong, often 2 to 3 times
-- the nb of pages we see when opening the document (may be
-- some other cre settings should be applied before calling
-- render() ?)
else
-- for all others than crengine, we seem to get an accurate nb of pages
pages = document : getPageCount ( )
end
if loaded then
book_props = document : getProps ( )
book_props.pages = pages
end
document : close ( )
end
end
2023-09-01 05:07:29 +00:00
return BookInfo.extendProps ( book_props , file )
2023-03-07 20:24:42 +00:00
end
2023-11-09 05:34:56 +00:00
function BookInfo : findInProps ( book_props , search_string , case_sensitive )
for _ , key in ipairs ( self.props ) do
local prop = book_props [ key ]
if prop then
if key == " series_index " then
prop = tostring ( prop )
elseif key == " description " then
prop = util.htmlToPlainTextIfHtml ( prop )
end
if not case_sensitive then
prop = Utf8Proc.lowercase ( util.fixUtf8 ( prop , " ? " ) )
end
if prop : find ( search_string ) then
return true
end
end
end
end
2023-08-30 04:53:59 +00:00
-- Shows book information for currently opened document.
2019-03-12 08:17:27 +00:00
function BookInfo : onShowBookInfo ( )
2023-08-30 04:53:59 +00:00
if self.document then
self.ui . doc_props.pages = self.ui . doc_settings : readSetting ( " doc_pages " )
self : show ( self.document . file , self.ui . doc_props )
2019-03-12 08:17:27 +00:00
end
end
2023-09-01 05:07:29 +00:00
function BookInfo : showBookProp ( prop_key , prop_text )
UIManager : show ( TextViewer : new {
title = self.prop_text [ prop_key ] ,
text = prop_text ,
2023-12-25 05:40:13 +00:00
text_type = prop_key == " description " and " book_info " or nil ,
2023-09-01 05:07:29 +00:00
} )
end
2023-03-07 20:24:42 +00:00
function BookInfo : onShowBookDescription ( description , file )
if not description then
if file then
2023-09-01 05:07:29 +00:00
description = BookInfo.getDocProps ( file ) . description
2023-08-30 04:53:59 +00:00
elseif self.document then -- currently opened document
description = self.ui . doc_props.description
2023-03-07 20:24:42 +00:00
end
end
2023-09-01 05:07:29 +00:00
if description then
2023-12-25 05:40:13 +00:00
self : showBookProp ( " description " , util.htmlToPlainTextIfHtml ( description ) )
2019-03-15 14:29:25 +00:00
else
UIManager : show ( InfoMessage : new {
text = _ ( " No book description available. " ) ,
} )
end
end
2023-05-03 12:43:05 +00:00
function BookInfo : onShowBookCover ( file , force_orig )
local cover_bb = self : getCoverImage ( self.document , file , force_orig )
if cover_bb then
local ImageViewer = require ( " ui/widget/imageviewer " )
local imgviewer = ImageViewer : new {
image = cover_bb ,
with_title_bar = false ,
fullscreen = true ,
}
UIManager : show ( imgviewer )
2019-03-15 14:29:25 +00:00
else
2023-05-03 12:43:05 +00:00
UIManager : show ( InfoMessage : new {
text = _ ( " No cover image available. " ) ,
} )
2023-03-07 20:24:42 +00:00
end
2023-05-03 12:43:05 +00:00
end
function BookInfo : getCoverImage ( doc , file , force_orig )
local cover_bb
-- check for a custom cover (orig cover is forcibly requested in "Book information" only)
if not force_orig then
2023-10-31 05:30:39 +00:00
local custom_cover = DocSettings : findCustomCoverFile ( file or ( doc and doc.file ) )
2023-05-03 12:43:05 +00:00
if custom_cover then
local cover_doc = DocumentRegistry : openDocument ( custom_cover )
if cover_doc then
cover_bb = cover_doc : getCoverPageImage ( )
cover_doc : close ( )
2023-06-09 07:36:34 +00:00
return cover_bb , custom_cover
2023-05-03 12:43:05 +00:00
end
2023-03-07 20:24:42 +00:00
end
2023-05-03 12:43:05 +00:00
end
-- orig cover
local is_doc = doc and true or false
if not is_doc then
doc = DocumentRegistry : openDocument ( file )
if doc and doc.loadDocument then -- CreDocument
doc : loadDocument ( false ) -- load only metadata
2023-04-16 19:46:09 +00:00
end
2019-03-15 14:29:25 +00:00
end
2023-05-03 12:43:05 +00:00
if doc then
cover_bb = doc : getCoverPageImage ( )
if not is_doc then
doc : close ( )
end
end
return cover_bb
end
2023-09-06 06:41:10 +00:00
function BookInfo : updateBookInfo ( file , book_props , prop_updated , prop_value_old )
2023-10-31 05:30:39 +00:00
if self.document and prop_updated == " cover " then
self.ui . doc_settings : getCustomCoverFile ( true ) -- reset cover file cache
2023-05-03 12:43:05 +00:00
end
2023-09-06 06:41:10 +00:00
self.prop_updated = {
filepath = file ,
doc_props = book_props ,
metadata_key_updated = prop_updated ,
metadata_value_old = prop_value_old ,
}
2023-09-01 05:07:29 +00:00
self.kvp_widget : onClose ( )
2023-09-06 06:41:10 +00:00
self : show ( file , book_props )
2023-09-01 05:07:29 +00:00
end
2023-10-31 05:30:39 +00:00
function BookInfo : setCustomCover ( file , book_props )
2023-05-03 12:43:05 +00:00
if self.custom_book_cover then -- reset custom cover
2023-09-01 05:07:29 +00:00
if os.remove ( self.custom_book_cover ) then
2023-10-31 05:30:39 +00:00
DocSettings.removeSidecarDir ( util.splitFilePathName ( self.custom_book_cover ) )
2023-09-06 06:41:10 +00:00
self : updateBookInfo ( file , book_props , " cover " )
2023-09-01 05:07:29 +00:00
end
2023-05-03 12:43:05 +00:00
else -- choose an image and set custom cover
local PathChooser = require ( " ui/widget/pathchooser " )
local path_chooser = PathChooser : new {
select_directory = false ,
file_filter = function ( filename )
2023-06-12 06:08:56 +00:00
return DocumentRegistry : isImageFile ( filename )
2023-05-03 12:43:05 +00:00
end ,
onConfirm = function ( image_file )
2023-09-01 05:07:29 +00:00
if DocSettings : flushCustomCover ( file , image_file ) then
2023-09-06 06:41:10 +00:00
self : updateBookInfo ( file , book_props , " cover " )
2023-05-03 12:43:05 +00:00
end
end ,
}
UIManager : show ( path_chooser )
end
2019-03-15 14:29:25 +00:00
end
2023-12-16 08:36:57 +00:00
function BookInfo : setCustomCoverFromImage ( file , image_file )
local custom_book_cover = DocSettings : findCustomCoverFile ( file )
if custom_book_cover then
os.remove ( custom_book_cover )
end
DocSettings : flushCustomCover ( file , image_file )
if self.ui . doc_settings then
self.ui . doc_settings : getCustomCoverFile ( true ) -- reset cover file cache
end
UIManager : broadcastEvent ( Event : new ( " InvalidateMetadataCache " , file ) )
UIManager : broadcastEvent ( Event : new ( " BookMetadataChanged " ) )
end
2023-09-06 06:41:10 +00:00
function BookInfo : setCustomMetadata ( file , book_props , prop_key , prop_value )
2023-09-01 05:07:29 +00:00
-- in file
2023-10-31 05:30:39 +00:00
local custom_doc_settings , custom_props , display_title , no_custom_metadata
2023-09-01 05:07:29 +00:00
if self.custom_doc_settings then
custom_doc_settings = self.custom_doc_settings
else -- no custom metadata file, create new
2023-10-31 05:30:39 +00:00
custom_doc_settings = DocSettings.openSettingsFile ( )
2023-09-01 05:07:29 +00:00
display_title = book_props.display_title -- backup
book_props.display_title = nil
custom_doc_settings : saveSetting ( " doc_props " , book_props ) -- save a copy of original props
end
2023-10-31 05:30:39 +00:00
custom_props = custom_doc_settings : readSetting ( " custom_props " , { } )
2023-09-06 06:41:10 +00:00
local prop_value_old = custom_props [ prop_key ] or book_props [ prop_key ]
2023-09-01 05:07:29 +00:00
custom_props [ prop_key ] = prop_value -- nil when resetting a custom prop
if next ( custom_props ) == nil then -- no more custom metadata
2023-10-31 05:30:39 +00:00
os.remove ( custom_doc_settings.sidecar_file )
DocSettings.removeSidecarDir ( util.splitFilePathName ( custom_doc_settings.sidecar_file ) )
no_custom_metadata = true
2023-03-31 09:59:11 +00:00
else
2023-09-12 04:54:38 +00:00
if book_props.pages then -- keep a copy of original 'pages' up to date
local original_props = custom_doc_settings : readSetting ( " doc_props " )
original_props.pages = book_props.pages
end
2023-09-01 05:07:29 +00:00
custom_doc_settings : flushCustomMetadata ( file )
end
book_props.display_title = book_props.display_title or display_title -- restore
-- in memory
prop_value = prop_value or custom_doc_settings : readSetting ( " doc_props " ) [ prop_key ] -- set custom or restore original
book_props [ prop_key ] = prop_value
2023-09-12 04:54:38 +00:00
if prop_key == " title " then -- generate when resetting the customized title and original is empty
book_props.display_title = book_props.title or filemanagerutil.splitFileNameType ( file )
end
2023-10-31 05:30:39 +00:00
if self.document and self.document . file == file then -- currently opened document
self.ui . doc_props [ prop_key ] = prop_value
2023-09-12 04:54:38 +00:00
if prop_key == " title " then
2023-10-31 05:30:39 +00:00
self.ui . doc_props.display_title = book_props.display_title
end
if no_custom_metadata then
self.ui . doc_settings : getCustomMetadataFile ( true ) -- reset metadata file cache
2023-03-31 09:59:11 +00:00
end
end
2023-09-06 06:41:10 +00:00
self : updateBookInfo ( file , book_props , prop_key , prop_value_old )
2023-03-31 09:59:11 +00:00
end
2023-09-06 06:41:10 +00:00
function BookInfo : showCustomEditDialog ( file , book_props , prop_key )
2023-09-23 07:21:19 +00:00
local prop = book_props [ prop_key ]
if prop and prop_key == " description " then
prop = util.htmlToPlainTextIfHtml ( prop )
end
2023-09-01 05:07:29 +00:00
local input_dialog
input_dialog = InputDialog : new {
2023-09-06 06:41:10 +00:00
title = _ ( " Edit book metadata: " ) .. " " .. self.prop_text [ prop_key ] : gsub ( " : " , " " ) ,
2023-09-23 07:21:19 +00:00
input = prop ,
2023-09-01 05:07:29 +00:00
input_type = prop_key == " series_index " and " number " ,
allow_newline = prop_key == " authors " or prop_key == " keywords " or prop_key == " description " ,
buttons = {
{
{
text = _ ( " Cancel " ) ,
id = " close " ,
callback = function ( )
UIManager : close ( input_dialog )
end ,
} ,
{
text = _ ( " Save " ) ,
callback = function ( )
local prop_value = input_dialog : getInputValue ( )
if prop_value and prop_value ~= " " then
UIManager : close ( input_dialog )
2023-09-06 06:41:10 +00:00
self : setCustomMetadata ( file , book_props , prop_key , prop_value )
2023-09-01 05:07:29 +00:00
end
end ,
} ,
} ,
} ,
}
UIManager : show ( input_dialog )
input_dialog : onShowKeyboard ( )
end
2023-09-06 06:41:10 +00:00
function BookInfo : showCustomDialog ( file , book_props , prop_key )
2023-09-01 05:07:29 +00:00
local original_prop , custom_prop , prop_is_cover
if prop_key then -- metadata
if self.custom_doc_settings then
original_prop = self.custom_doc_settings : readSetting ( " doc_props " ) [ prop_key ]
custom_prop = self.custom_doc_settings : readSetting ( " custom_props " ) [ prop_key ]
else
original_prop = book_props [ prop_key ]
end
if original_prop and prop_key == " description " then
original_prop = util.htmlToPlainTextIfHtml ( original_prop )
end
prop_is_cover = false
else -- cover
prop_key = " cover "
prop_is_cover = true
end
2023-05-03 12:43:05 +00:00
local button_dialog
2023-09-01 05:07:29 +00:00
local buttons = {
2023-05-03 12:43:05 +00:00
{
2023-09-01 05:07:29 +00:00
{
text = _ ( " Copy original " ) ,
enabled = original_prop ~= nil and Device : hasClipboard ( ) ,
callback = function ( )
UIManager : close ( button_dialog )
Device.input . setClipboardText ( original_prop )
end ,
} ,
{
text = _ ( " View original " ) ,
enabled = original_prop ~= nil or prop_is_cover ,
callback = function ( )
if prop_is_cover then
self : onShowBookCover ( file , true )
else
self : showBookProp ( prop_key , original_prop )
end
end ,
} ,
2023-05-03 12:43:05 +00:00
} ,
2023-09-01 05:07:29 +00:00
{
{
text = _ ( " Reset custom " ) ,
enabled = custom_prop ~= nil or ( prop_is_cover and self.custom_book_cover ~= nil ) ,
callback = function ( )
local confirm_box = ConfirmBox : new {
text = prop_is_cover and _ ( " Reset custom cover? \n Image file will be deleted. " )
2023-09-06 06:41:10 +00:00
or _ ( " Reset custom book metadata field? " ) ,
2023-09-01 05:07:29 +00:00
ok_text = _ ( " Reset " ) ,
ok_callback = function ( )
UIManager : close ( button_dialog )
if prop_is_cover then
2023-10-31 05:30:39 +00:00
self : setCustomCover ( file , book_props )
2023-09-01 05:07:29 +00:00
else
2023-09-06 06:41:10 +00:00
self : setCustomMetadata ( file , book_props , prop_key )
2023-09-01 05:07:29 +00:00
end
end ,
}
UIManager : show ( confirm_box )
end ,
} ,
{
text = _ ( " Set custom " ) ,
enabled = not prop_is_cover or ( prop_is_cover and self.custom_book_cover == nil ) ,
callback = function ( )
UIManager : close ( button_dialog )
if prop_is_cover then
2023-10-31 05:30:39 +00:00
self : setCustomCover ( file , book_props )
2023-09-01 05:07:29 +00:00
else
2023-09-06 06:41:10 +00:00
self : showCustomEditDialog ( file , book_props , prop_key )
2023-09-01 05:07:29 +00:00
end
end ,
} ,
} ,
}
2023-05-03 12:43:05 +00:00
button_dialog = ButtonDialog : new {
2023-09-06 06:41:10 +00:00
title = _ ( " Book metadata: " ) .. " " .. self.prop_text [ prop_key ] : gsub ( " : " , " " ) ,
2023-09-01 05:07:29 +00:00
title_align = " center " ,
2023-05-03 12:43:05 +00:00
buttons = buttons ,
}
UIManager : show ( button_dialog )
end
2023-10-31 05:30:39 +00:00
function BookInfo : moveBookMetadata ( )
-- called by filemanagermenu only
local file_chooser = self.ui . file_chooser
local function scanPath ( )
local sys_folders = { -- do not scan sys_folders
[ " /dev " ] = true ,
[ " /proc " ] = true ,
[ " /sys " ] = true ,
}
local books_to_move = { }
local dirs = { file_chooser.path }
while # dirs ~= 0 do
local new_dirs = { }
for _ , d in ipairs ( dirs ) do
local ok , iter , dir_obj = pcall ( lfs.dir , d )
if ok then
for f in iter , dir_obj do
local fullpath = " / " .. f
if d ~= " / " then
fullpath = d .. fullpath
end
local attributes = lfs.attributes ( fullpath ) or { }
if attributes.mode == " directory " and f ~= " . " and f ~= " .. "
and file_chooser : show_dir ( f ) and not sys_folders [ fullpath ] then
table.insert ( new_dirs , fullpath )
elseif attributes.mode == " file " and not util.stringStartsWith ( f , " ._ " )
and file_chooser : show_file ( f )
and DocSettings.isSidecarFileNotInPreferredLocation ( fullpath ) then
table.insert ( books_to_move , fullpath )
end
end
end
end
dirs = new_dirs
end
return books_to_move
end
UIManager : show ( ConfirmBox : new {
text = _ ( " Scan books in current folder and subfolders for their metadata location? " ) ,
ok_text = _ ( " Scan " ) ,
ok_callback = function ( )
local books_to_move = scanPath ( )
local books_to_move_nb = # books_to_move
if books_to_move_nb == 0 then
UIManager : show ( InfoMessage : new {
text = _ ( " No books with metadata not in your preferred location found. " ) ,
} )
else
UIManager : show ( ConfirmBox : new {
text = T ( N_ ( " 1 book with metadata not in your preferred location found. " ,
" %1 books with metadata not in your preferred location found. " ,
books_to_move_nb ) , books_to_move_nb ) .. " \n " ..
_ ( " Move book metadata to your preferred location? " ) ,
ok_text = _ ( " Move " ) ,
ok_callback = function ( )
UIManager : close ( self.menu_container )
for _ , book in ipairs ( books_to_move ) do
DocSettings.updateLocation ( book , book )
end
file_chooser : refreshPath ( )
end ,
} )
end
end ,
} )
end
function BookInfo . showBooksWithHashBasedMetadata ( )
local header = T ( _ ( " Hash-based metadata has been saved in %1 for the following documents. Hash-based storage may slow down file browser navigation in large directories. Thus, if not using hash-based metadata storage, it is recommended to open the associated documents in KOReader to automatically migrate their metadata to the preferred storage location, or to delete %1, which will speed up file browser navigation. " ) ,
DocSettings.getSidecarStorage ( " hash " ) )
local file_info = { header .. " \n " }
local sdrs = DocSettings.findSidecarFilesInHashLocation ( )
for i , sdr in ipairs ( sdrs ) do
local sidecar_file , custom_metadata_file = unpack ( sdr )
local doc_settings = DocSettings.openSettingsFile ( sidecar_file )
local doc_props = doc_settings : readSetting ( " doc_props " )
local custom_props = custom_metadata_file
and DocSettings.openSettingsFile ( custom_metadata_file ) : readSetting ( " custom_props " ) or { }
local doc_path = doc_settings : readSetting ( " doc_path " )
local title = custom_props.title or doc_props.title or filemanagerutil.splitFileNameType ( doc_path )
local author = custom_props.authors or doc_props.authors or _ ( " N/A " )
doc_path = lfs.attributes ( doc_path , " mode " ) == " file " and doc_path or _ ( " N/A " )
local text = T ( _ ( " %1. Title: %2; Author: %3 \n Document: %4 " ) , i , title , author , doc_path )
table.insert ( file_info , text )
end
local doc_nb = # file_info - 1
UIManager : show ( TextViewer : new {
title = T ( N_ ( " 1 document with hash-based metadata " , " %1 documents with hash-based metadata " , doc_nb ) , doc_nb ) ,
title_multilines = true ,
text = table.concat ( file_info , " \n " ) ,
} )
end
2017-07-01 10:11:44 +00:00
return BookInfo