diff --git a/plugins/calibre.koplugin/main.lua b/plugins/calibre.koplugin/main.lua index 5801bf2ac..a44bd6c8f 100644 --- a/plugins/calibre.koplugin/main.lua +++ b/plugins/calibre.koplugin/main.lua @@ -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, }, } diff --git a/plugins/calibre.koplugin/metadata.lua b/plugins/calibre.koplugin/metadata.lua index 55541cedc..fa78d21ab 100644 --- a/plugins/calibre.koplugin/metadata.lua +++ b/plugins/calibre.koplugin/metadata.lua @@ -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] diff --git a/plugins/calibre.koplugin/search.lua b/plugins/calibre.koplugin/search.lua index 3b91809d0..573cfa19e 100644 --- a/plugins/calibre.koplugin/search.lua +++ b/plugins/calibre.koplugin/search.lua @@ -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)