--[[--
This module provides a way to display book information ( filename and book metadata )
] ]
local BD = require ( " ui/bidi " )
local ButtonDialog = require ( " ui/widget/buttondialog " )
local ConfirmBox = require ( " ui/widget/confirmbox " )
local Device = require ( " device " )
local DocSettings = require ( " docsettings " )
local Document = require ( " document/document " )
local DocumentRegistry = require ( " document/documentregistry " )
local Event = require ( " ui/event " )
local InfoMessage = require ( " ui/widget/infomessage " )
local InputDialog = require ( " ui/widget/inputdialog " )
local TextViewer = require ( " ui/widget/textviewer " )
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.
2 years ago
local WidgetContainer = require ( " ui/widget/container/widgetcontainer " )
local filemanagerutil = require ( " apps/filemanager/filemanagerutil " )
local lfs = require ( " libs/libkoreader-lfs " )
local util = require ( " util " )
local _ = require ( " gettext " )
local N_ = _.ngettext
local T = require ( " ffi/util " ) . template
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.
2 years ago
local BookInfo = WidgetContainer : extend {
props = {
" title " ,
" authors " ,
" series " ,
" series_index " ,
" language " ,
" keywords " ,
" description " ,
} ,
prop_text = {
cover = _ ( " Cover image: " ) ,
title = _ ( " Title: " ) ,
authors = _ ( " Authors: " ) ,
series = _ ( " Series: " ) ,
series_index = _ ( " Series index: " ) ,
language = _ ( " Language: " ) ,
keywords = _ ( " Keywords: " ) ,
description = _ ( " Description: " ) ,
pages = _ ( " Pages: " ) ,
} ,
}
function BookInfo : init ( )
if self.document then -- only for Reader menu
self.ui . menu : registerToMainMenu ( self )
end
end
function BookInfo : addToMainMenu ( menu_items )
menu_items.book_info = {
text = _ ( " Book information " ) ,
callback = function ( )
self : onShowBookInfo ( )
end ,
}
end
-- Shows book information.
function BookInfo : show ( file , book_props )
self.prop_updated = nil
local kv_pairs = { }
-- 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
local size_f = util.getFriendlySize ( file_size )
local size_b = util.getFormattedSize ( file_size )
table.insert ( kv_pairs , { _ ( " Filename: " ) , BD.filename ( filename ) } )
table.insert ( kv_pairs , { _ ( " Format: " ) , filetype : upper ( ) } )
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 } )
-- Book section
-- book_props may be provided if caller already has them available
-- but it may lack "pages", that we may get from sidecar file
if not book_props or not book_props.pages then
book_props = BookInfo.getDocProps ( file , book_props )
end
-- cover image
self.custom_book_cover = DocSettings : findCustomCoverFile ( file )
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 ( )
self : showCustomDialog ( file , book_props )
end ,
separator = true ,
} )
-- metadata
local custom_props
local custom_metadata_file = DocSettings : findCustomMetadataFile ( file )
if custom_metadata_file then
self.custom_doc_settings = DocSettings.openSettingsFile ( custom_metadata_file )
custom_props = self.custom_doc_settings : readSetting ( " custom_props " )
end
local values_lang
for _i , prop_key in ipairs ( self.props ) do
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 )
end
key_text = self.prop_text [ prop_key ]
if custom_props and custom_props [ prop_key ] then -- customized
key_text = " \u{F040} " .. key_text
end
table.insert ( kv_pairs , { key_text , prop ,
hold_callback = function ( )
self : showCustomDialog ( file , book_props , prop_key )
end ,
} )
end
-- 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 } )
-- Page section
if is_doc then
local lines_nb , words_nb = self.ui . view : getCurrentPageLineWordCounts ( )
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 } )
end
local KeyValuePage = require ( " ui/widget/keyvaluepage " )
self.kvp_widget = KeyValuePage : new {
title = _ ( " Book information " ) ,
value_overflow_align = " right " ,
kv_pairs = kv_pairs ,
values_lang = values_lang ,
close_callback = function ( )
self.custom_doc_settings = nil
self.custom_book_cover = nil
if self.prop_updated then
UIManager : broadcastEvent ( Event : new ( " InvalidateMetadataCache " , file ) )
UIManager : broadcastEvent ( Event : new ( " BookMetadataChanged " , self.prop_updated ) )
end
end ,
}
UIManager : show ( self.kvp_widget )
end
function BookInfo . getCustomProp ( prop_key , filepath )
local custom_metadata_file = DocSettings : findCustomMetadataFile ( filepath )
return custom_metadata_file
and DocSettings.openSettingsFile ( custom_metadata_file ) : readSetting ( " custom_props " ) [ prop_key ]
end
-- Returns extended and customized metadata.
function BookInfo . extendProps ( original_props , filepath )
-- do not customize if filepath is not passed (eg from covermenu)
local custom_metadata_file = filepath and DocSettings : findCustomMetadataFile ( filepath )
local custom_props = custom_metadata_file
and DocSettings.openSettingsFile ( custom_metadata_file ) : readSetting ( " custom_props " ) or { }
original_props = original_props or { }
local props = { }
for _ , prop_key in ipairs ( BookInfo.props ) do
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
-- Returns customized document metadata, including number of pages.
function BookInfo . getDocProps ( file , book_props , no_open_document )
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
-- title, authors, series, series_index, language
book_props = Document : getProps ( stats )
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
-- 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
local custom_metadata_file = DocSettings : findCustomMetadataFile ( file )
if custom_metadata_file then
book_props = DocSettings.openSettingsFile ( custom_metadata_file ) : readSetting ( " doc_props " )
end
end
-- If still no book_props, open the document to get them
if not book_props and not no_open_document then
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
return BookInfo.extendProps ( book_props , file )
end
-- Shows book information for currently opened document.
function BookInfo : onShowBookInfo ( )
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 )
end
end
function BookInfo : showBookProp ( prop_key , prop_text )
if prop_key == " description " then
prop_text = util.htmlToPlainTextIfHtml ( prop_text )
end
UIManager : show ( TextViewer : new {
title = self.prop_text [ prop_key ] ,
text = prop_text ,
} )
end
function BookInfo : onShowBookDescription ( description , file )
if not description then
if file then
description = BookInfo.getDocProps ( file ) . description
elseif self.document then -- currently opened document
description = self.ui . doc_props.description
end
end
if description then
self : showBookProp ( " description " , description )
else
UIManager : show ( InfoMessage : new {
text = _ ( " No book description available. " ) ,
} )
end
end
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 )
else
UIManager : show ( InfoMessage : new {
text = _ ( " No cover image available. " ) ,
} )
end
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
local custom_cover = DocSettings : findCustomCoverFile ( file or ( doc and doc.file ) )
if custom_cover then
local cover_doc = DocumentRegistry : openDocument ( custom_cover )
if cover_doc then
cover_bb = cover_doc : getCoverPageImage ( )
cover_doc : close ( )
return cover_bb , custom_cover
end
end
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
end
end
if doc then
cover_bb = doc : getCoverPageImage ( )
if not is_doc then
doc : close ( )
end
end
return cover_bb
end
function BookInfo : updateBookInfo ( file , book_props , prop_updated , prop_value_old )
if self.document and prop_updated == " cover " then
self.ui . doc_settings : getCustomCoverFile ( true ) -- reset cover file cache
end
self.prop_updated = {
filepath = file ,
doc_props = book_props ,
metadata_key_updated = prop_updated ,
metadata_value_old = prop_value_old ,
}
self.kvp_widget : onClose ( )
self : show ( file , book_props )
end
function BookInfo : setCustomCover ( file , book_props )
if self.custom_book_cover then -- reset custom cover
if os.remove ( self.custom_book_cover ) then
DocSettings.removeSidecarDir ( util.splitFilePathName ( self.custom_book_cover ) )
self : updateBookInfo ( file , book_props , " cover " )
end
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 )
return DocumentRegistry : isImageFile ( filename )
end ,
onConfirm = function ( image_file )
if DocSettings : flushCustomCover ( file , image_file ) then
self : updateBookInfo ( file , book_props , " cover " )
end
end ,
}
UIManager : show ( path_chooser )
end
end
function BookInfo : setCustomMetadata ( file , book_props , prop_key , prop_value )
-- in file
local custom_doc_settings , custom_props , display_title , no_custom_metadata
if self.custom_doc_settings then
custom_doc_settings = self.custom_doc_settings
else -- no custom metadata file, create new
custom_doc_settings = DocSettings.openSettingsFile ( )
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
custom_props = custom_doc_settings : readSetting ( " custom_props " , { } )
local prop_value_old = custom_props [ prop_key ] or book_props [ prop_key ]
custom_props [ prop_key ] = prop_value -- nil when resetting a custom prop
if next ( custom_props ) == nil then -- no more custom metadata
os.remove ( custom_doc_settings.sidecar_file )
DocSettings.removeSidecarDir ( util.splitFilePathName ( custom_doc_settings.sidecar_file ) )
no_custom_metadata = true
else
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
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
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
if self.document and self.document . file == file then -- currently opened document
self.ui . doc_props [ prop_key ] = prop_value
if prop_key == " title " then
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
end
end
self : updateBookInfo ( file , book_props , prop_key , prop_value_old )
end
function BookInfo : showCustomEditDialog ( file , book_props , prop_key )
local prop = book_props [ prop_key ]
if prop and prop_key == " description " then
prop = util.htmlToPlainTextIfHtml ( prop )
end
local input_dialog
input_dialog = InputDialog : new {
title = _ ( " Edit book metadata: " ) .. " " .. self.prop_text [ prop_key ] : gsub ( " : " , " " ) ,
input = prop ,
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 )
self : setCustomMetadata ( file , book_props , prop_key , prop_value )
end
end ,
} ,
} ,
} ,
}
UIManager : show ( input_dialog )
input_dialog : onShowKeyboard ( )
end
function BookInfo : showCustomDialog ( file , book_props , prop_key )
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
local button_dialog
local buttons = {
{
{
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 ,
} ,
} ,
{
{
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. " )
or _ ( " Reset custom book metadata field? " ) ,
ok_text = _ ( " Reset " ) ,
ok_callback = function ( )
UIManager : close ( button_dialog )
if prop_is_cover then
self : setCustomCover ( file , book_props )
else
self : setCustomMetadata ( file , book_props , prop_key )
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
self : setCustomCover ( file , book_props )
else
self : showCustomEditDialog ( file , book_props , prop_key )
end
end ,
} ,
} ,
}
button_dialog = ButtonDialog : new {
title = _ ( " Book metadata: " ) .. " " .. self.prop_text [ prop_key ] : gsub ( " : " , " " ) ,
title_align = " center " ,
buttons = buttons ,
}
UIManager : show ( button_dialog )
end
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 ,
justified = false ,
text = table.concat ( file_info , " \n " ) ,
} )
end
return BookInfo