Calibre: Allow authors/title metadata browse/searches (#10113)

c.f., https://www.mobileread.com/forums/showthread.php?t=352142

This adds dedicated "browse by" buttons for authors & tags, and adds series & tags to the search settings for the meta "Search books" button.
reviewable/pr10128/r1
NiLuJe 1 year ago committed by GitHub
parent 91ff6ce2d8
commit 8e9fc9953f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -30,15 +30,9 @@ function Calibre:onCalibreSearch()
return true
end
function Calibre:onCalibreBrowseTags()
function Calibre:onCalibreBrowseBy(field)
CalibreSearch.search_value = ""
CalibreSearch:find("tags", 1)
return true
end
function Calibre:onCalibreBrowseSeries()
CalibreSearch.search_value = ""
CalibreSearch:find("series", 1)
CalibreSearch:find(field)
return true
end
@ -62,8 +56,10 @@ end
function Calibre:onDispatcherRegisterActions()
Dispatcher:registerAction("calibre_search", { category="none", event="CalibreSearch", title=_("Calibre metadata search"), general=true,})
Dispatcher:registerAction("calibre_browse_tags", { category="none", event="CalibreBrowseTags", title=_("Browse all calibre tags"), general=true,})
Dispatcher:registerAction("calibre_browse_series", { category="none", event="CalibreBrowseSeries", title=_("Browse all calibre series"), general=true, separator=true,})
Dispatcher:registerAction("calibre_browse_tags", { category="none", event="CalibreBrowseBy", arg="tags", title=_("Browse all calibre tags"), general=true,})
Dispatcher:registerAction("calibre_browse_series", { category="none", event="CalibreBrowseBy", arg="series", title=_("Browse all calibre series"), general=true,})
Dispatcher:registerAction("calibre_browse_authors", { category="none", event="CalibreBrowseBy", arg="authors", title=_("Browse all calibre authors"), general=true,})
Dispatcher:registerAction("calibre_browse_titles", { category="none", event="CalibreBrowseBy", arg="title", title=_("Browse all calibre titles"), general=true, separator=true,})
end
function Calibre:init()
@ -209,13 +205,31 @@ function Calibre:getSearchMenuTable()
G_reader_settings:flipNilOrTrue("calibre_search_find_by_authors")
end,
},
{
text = _("Search by series"),
checked_func = function()
return G_reader_settings:isTrue("calibre_search_find_by_series")
end,
callback = function()
G_reader_settings:toggle("calibre_search_find_by_series")
end,
},
{
text = _("Search by tag"),
checked_func = function()
return G_reader_settings:isTrue("calibre_search_find_by_tag")
end,
callback = function()
G_reader_settings:toggle("calibre_search_find_by_tag")
end,
},
{
text = _("Search by path"),
checked_func = function()
return G_reader_settings:nilOrTrue("calibre_search_find_by_path")
return G_reader_settings:isTrue("calibre_search_find_by_path")
end,
callback = function()
G_reader_settings:flipNilOrTrue("calibre_search_find_by_path")
G_reader_settings:toggle("calibre_search_find_by_path")
end,
},
}

@ -43,7 +43,7 @@ local function slim(book, is_search)
for _, k in ipairs(is_search and search_used_metadata or used_metadata) do
if k == "series" or k == "series_index" then
slim_book[k] = book[k] or rapidjson.null
elseif k == "tags" then
elseif k == "tags" or k == "authors" then
slim_book[k] = book[k] or {}
else
slim_book[k] = book[k]

@ -67,25 +67,26 @@ local function match(str, query, case_insensitive)
end
end
-- get books that exactly match the search tag
local function getBooksByTag(t, tag)
-- get books that exactly match the search in a specific flat field (series or title)
local function getBooksByField(t, field, query)
local result = {}
for _, book in ipairs(t) do
for __, _tag in ipairs(book.tags) do
if tag == _tag then
table.insert(result, book)
end
local data = book[field]
-- We can compare nil & rapidjson.null (light userdata) to a string safely
if data == query then
table.insert(result, book)
end
end
return result
end
-- get books that exactly match the search series
local function getBooksBySeries(t, series)
-- get books that exactly match the search in a specific array (tags or authors)
local function getBooksByNestedField(t, field, query)
local result = {}
for _, book in ipairs(t) do
if book.series and book.series ~= rapidjson.null then
if book.series == series then
local array = book[field]
for __, data in ipairs(array) do
if data == query then
table.insert(result, book)
end
end
@ -93,28 +94,29 @@ local function getBooksBySeries(t, series)
return result
end
-- get tags that match the search criteria and their frequency
local function searchByTag(t, query, case_insensitive)
-- generic search in a specific flat field (series or title), matching the search criteria and their frequency
local function searchByField(t, field, query, case_insensitive)
local freq = {}
for _, book in ipairs(t) do
if type(book.tags) == "table" then
for __, tag in ipairs(book.tags) do
if match(tag, query, case_insensitive) then
freq[tag] = (freq[tag] or 0) + 1
end
local data = book[field]
-- We have to make sure we only pass strings to match
if data and data ~= rapidjson.null then
if match(data, query, case_insensitive) then
freq[data] = (freq[data] or 0) + 1
end
end
end
return freq
end
-- get series that match the search criteria and their frequency
local function searchBySeries(t, query, case_insensitive)
-- generic search in a specific array (tags or authors), matching the search criteria and their frequency
local function searchByNestedField(t, field, query, case_insensitive)
local freq = {}
for _, book in ipairs(t) do
if book.series and book.series ~= rapidjson.null then
if match(book.series, query, case_insensitive) then
freq[book.series] = (freq[book.series] or 0) + 1
local array = book[field]
for __, data in ipairs(array) do
if match(data, query, case_insensitive) then
freq[data] = (freq[data] or 0) + 1
end
end
end
@ -162,11 +164,17 @@ local CalibreSearch = WidgetContainer:extend{
libraries = {},
natsort_cache = {},
last_scan = {},
search_options = {
-- These are enabled by default
default_search_options = {
"cache_metadata",
"case_insensitive",
"find_by_title",
"find_by_authors",
},
-- These aren't
extra_search_options = {
"find_by_series",
"find_by_tag",
"find_by_path",
},
@ -205,6 +213,26 @@ function CalibreSearch:ShowSearch()
end,
},
},
{
{
text = _("Browse authors"),
enabled = true,
callback = function()
self.search_value = self.search_dialog:getInputText()
self.lastsearch = "authors"
self:close()
end,
},
{
text = _("Browse titles"),
enabled = true,
callback = function()
self.search_value = self.search_dialog:getInputText()
self.lastsearch = "title"
self:close()
end,
},
},
{
{
text = _("Cancel"),
@ -300,11 +328,14 @@ function CalibreSearch:bookCatalog(t, option)
return catalog
end
-- find books, series or tags
-- find books, series, tags, authors or titles
function CalibreSearch:find(option)
for _, opt in ipairs(self.search_options) do
for _, opt in ipairs(self.default_search_options) do
self[opt] = G_reader_settings:nilOrTrue("calibre_search_"..opt)
end
for _, opt in ipairs(self.extra_search_options) do
self[opt] = G_reader_settings:isTrue("calibre_search_"..opt)
end
if #self.libraries == 0 then
local libs, err = self.cache_libs:load()
@ -336,6 +367,8 @@ function CalibreSearch:find(option)
"case sensitive: " .. tostring(not self.case_insensitive),
"title: " .. tostring(self.find_by_title),
"authors: " .. tostring(self.find_by_authors),
"series: " .. tostring(self.find_by_series),
"tag: " .. tostring(self.find_by_tag),
"path: " .. tostring(self.find_by_path)))
end
@ -362,6 +395,16 @@ function CalibreSearch:findBooks(query)
end
end
end
if self.find_by_series and bookMatch(book.series, pattern) then
return true
end
if self.find_by_tag then
for _, tag in ipairs(book.tags) do
if bookMatch(tag, pattern) then
return true
end
end
end
if self.find_by_path and bookMatch(book.lpath, pattern) then
return true
end
@ -393,16 +436,24 @@ function CalibreSearch:browse(option)
local source
if option == "tags" then
name = _("Browse by tags")
source = searchByTag(self.books, search_value, self.case_insensitive)
source = searchByNestedField(self.books, option, search_value, self.case_insensitive)
elseif option == "series" then
name = _("Browse by series")
source = searchBySeries(self.books, search_value, self.case_insensitive)
source = searchByField(self.books, option, search_value, self.case_insensitive)
elseif option == "authors" then
name = _("Browse by authors")
source = searchByNestedField(self.books, option, search_value, self.case_insensitive)
elseif option == "title" then
name = _("Browse by titles")
-- This is admittedly only midly useful in the face of the generic search above,
-- but makes finding duplicate titles easy, at least ;).
source = searchByField(self.books, option, search_value, self.case_insensitive)
end
for k, v in pairs(source) do
local entry = {}
entry.text = string.format("%s (%d)", k, v)
entry.callback = function()
self:expandTagOrSeries(option,k)
self:expandSearchResults(option, k)
end
table.insert(menu_entries, entry)
end
@ -430,13 +481,13 @@ function CalibreSearch:browse(option)
UIManager:show(self.search_menu)
end
function CalibreSearch:expandTagOrSeries(option, chosen_item)
function CalibreSearch:expandSearchResults(option, chosen_item)
local results
if option == "tags" then
results = getBooksByTag(self.books, chosen_item)
elseif option == "series" then
results = getBooksBySeries(self.books, chosen_item)
if option == "tags" or option == "authors" then
results = getBooksByNestedField(self.books, option, chosen_item)
else
results = getBooksByField(self.books, option, chosen_item)
end
if results then
local catalog = self:bookCatalog(results, option)

Loading…
Cancel
Save