mirror of
https://github.com/koreader/koreader.git
synced 2025-08-10 00:52:38 +00:00
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.
This commit is contained in:
@@ -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()
|
||||
@@ -210,12 +206,30 @@ function Calibre:getSearchMenuTable()
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Search by path"),
|
||||
text = _("Search by series"),
|
||||
checked_func = function()
|
||||
return G_reader_settings:nilOrTrue("calibre_search_find_by_path")
|
||||
return G_reader_settings:isTrue("calibre_search_find_by_series")
|
||||
end,
|
||||
callback = function()
|
||||
G_reader_settings:flipNilOrTrue("calibre_search_find_by_path")
|
||||
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:isTrue("calibre_search_find_by_path")
|
||||
end,
|
||||
callback = function()
|
||||
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,12 +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
|
||||
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 in a specific array (tags or authors)
|
||||
local function getBooksByNestedField(t, field, query)
|
||||
local result = {}
|
||||
for _, book in ipairs(t) do
|
||||
local array = book[field]
|
||||
for __, data in ipairs(array) do
|
||||
if data == query then
|
||||
table.insert(result, book)
|
||||
end
|
||||
end
|
||||
@@ -80,41 +94,29 @@ local function getBooksByTag(t, tag)
|
||||
return result
|
||||
end
|
||||
|
||||
-- get books that exactly match the search series
|
||||
local function getBooksBySeries(t, series)
|
||||
local result = {}
|
||||
for _, book in ipairs(t) do
|
||||
if book.series and book.series ~= rapidjson.null then
|
||||
if book.series == series then
|
||||
table.insert(result, book)
|
||||
end
|
||||
end
|
||||
end
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user