-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
filemanagerbookinfo.lua
683 lines (651 loc) · 26.9 KB
/
filemanagerbookinfo.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
--[[--
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")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local Utf8Proc = require("ffi/utf8proc")
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
local BookInfo = WidgetContainer:extend{
title = _("Book information"),
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 = self.title,
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, callback
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)
callback = function() -- proper text_type in TextViewer
self:showBookProp("description", prop)
end
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,
callback = callback,
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 = self.title,
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
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
-- 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)
UIManager:show(TextViewer:new{
title = self.prop_text[prop_key],
text = prop_text,
text_type = prop_key == "description" and "book_info" or nil,
})
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", util.htmlToPlainTextIfHtml(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: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
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?\nImage 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\nDocument: %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
return BookInfo