2
0
mirror of https://github.com/koreader/koreader synced 2024-11-10 01:10:34 +00:00
koreader/plugins/calibre.koplugin/main.lua
Martín Fernández 79c13bee0c
Calibre: Metadata parser improvements (#11922)
* Added a safe pure-Lua SAX JSON parser (via LunaJSON).
* Updated RapidJSON.
* Also implemented a calibre-specific SAX parser in Lua-RapidJSON, and use it by default instead of the full RapidJSON one.
* Raised the file-size threshold to switch between the fast & safe parsers to 50MB.
* Added an UI option to switch between the three parsers.
2024-06-06 01:06:46 +02:00

469 lines
18 KiB
Lua

--[[
This plugin implements KOReader integration with *some* calibre features:
- metadata search
- wireless transfers
This module handles the UI part of the plugin.
--]]
local BD = require("ui/bidi")
local CalibreExtensions = require("extensions")
local CalibreSearch = require("search")
local CalibreWireless = require("wireless")
local Dispatcher = require("dispatcher")
local InfoMessage = require("ui/widget/infomessage")
local LuaSettings = require("luasettings")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
local _ = require("gettext")
local C_ = _.pgettext
local T = require("ffi/util").template
local Calibre = WidgetContainer:extend{
name = "calibre",
is_doc_only = false,
}
function Calibre:onCalibreSearch()
CalibreSearch:ShowSearch()
return true
end
function Calibre:onCalibreBrowseBy(field)
CalibreSearch.search_value = ""
CalibreSearch:find(field)
return true
end
function Calibre:onNetworkDisconnected()
self:closeWirelessConnection()
end
function Calibre:onSuspend()
self:closeWirelessConnection()
end
function Calibre:onClose()
self:closeWirelessConnection()
end
function Calibre:onStartWirelessConnection()
self:startWirelessConnection()
end
function Calibre:onCloseWirelessConnection()
self:closeWirelessConnection()
end
function Calibre:startWirelessConnection()
if not CalibreWireless.calibre_socket then
CalibreWireless:connect()
end
end
function Calibre:closeWirelessConnection()
if CalibreWireless.calibre_socket then
CalibreWireless:disconnect()
end
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="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,})
Dispatcher:registerAction("calibre_start_connection", { category="none", event="StartWirelessConnection", title=_("Calibre wireless connect"), general=true,})
Dispatcher:registerAction("calibre_close_connection", { category="none", event="CloseWirelessConnection", title=_("Calibre wireless disconnect"), general=true,})
end
function Calibre:init()
CalibreWireless:init()
self:onDispatcherRegisterActions()
self.ui.menu:registerToMainMenu(self)
end
function Calibre:addToMainMenu(menu_items)
menu_items.calibre = {
-- its name is "calibre", but all our top menu items are uppercase.
text = _("Calibre"),
sub_item_table = {
{
text_func = function()
if CalibreWireless.calibre_socket then
return _("Disconnect")
else
return _("Connect")
end
end,
separator = true,
enabled_func = function()
return G_reader_settings:nilOrTrue("calibre_wireless")
end,
callback = function()
if not CalibreWireless.calibre_socket then
CalibreWireless:connect()
else
CalibreWireless:disconnect()
end
end,
},
{ text = _("Search settings"),
keep_menu_open = true,
sub_item_table = self:getSearchMenuTable(),
},
{
text = _("Wireless settings"),
keep_menu_open = true,
sub_item_table = self:getWirelessMenuTable(),
},
{
text = _("JSON parser"),
keep_menu_open = true,
sub_item_table = self:getParserMenuTable(),
},
}
}
-- insert the metadata search
if G_reader_settings:isTrue("calibre_search_from_reader") or not self.ui.view then
menu_items.find_book_in_calibre_catalog = {
text = _("Calibre metadata search"),
callback = function()
CalibreSearch:ShowSearch()
end
}
end
end
-- search options available from UI
function Calibre:getSearchMenuTable()
return {
{
text = _("Manage libraries"),
separator = true,
keep_menu_open = true,
sub_item_table_func = function()
local result = {}
-- append previous scanned dirs to the list.
local cache = LuaSettings:open(CalibreSearch.cache_libs.path)
for path, _ in pairs(cache.data) do
table.insert(result, {
text = path,
keep_menu_open = true,
checked_func = function()
return cache:isTrue(path)
end,
callback = function()
cache:toggle(path)
cache:flush()
CalibreSearch:invalidateCache()
end,
})
end
-- if there's no result then no libraries are stored
if #result == 0 then
table.insert(result, {
text = _("No calibre libraries"),
enabled = false
})
end
table.insert(result, 1, {
text = _("Rescan disk for calibre libraries"),
separator = true,
callback = function()
CalibreSearch:prompt()
end,
})
return result
end,
},
{
text = _("Enable searches in the reader"),
checked_func = function()
return G_reader_settings:isTrue("calibre_search_from_reader")
end,
callback = function()
G_reader_settings:toggle("calibre_search_from_reader")
UIManager:show(InfoMessage:new{
text = _("This will take effect on next restart."),
})
end,
},
{
text = _("Store metadata in cache"),
checked_func = function()
return G_reader_settings:nilOrTrue("calibre_search_cache_metadata")
end,
callback = function()
G_reader_settings:flipNilOrTrue("calibre_search_cache_metadata")
end,
},
{
text = _("Case sensitive search"),
checked_func = function()
return not G_reader_settings:nilOrTrue("calibre_search_case_insensitive")
end,
callback = function()
G_reader_settings:flipNilOrTrue("calibre_search_case_insensitive")
end,
},
{
text = _("Search by title"),
checked_func = function()
return G_reader_settings:nilOrTrue("calibre_search_find_by_title")
end,
callback = function()
G_reader_settings:flipNilOrTrue("calibre_search_find_by_title")
end,
},
{
text = _("Search by authors"),
checked_func = function()
return G_reader_settings:nilOrTrue("calibre_search_find_by_authors")
end,
callback = function()
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:isTrue("calibre_search_find_by_path")
end,
callback = function()
G_reader_settings:toggle("calibre_search_find_by_path")
end,
},
}
end
-- wireless options available from UI
function Calibre:getWirelessMenuTable()
local function isEnabled()
local enabled = G_reader_settings:nilOrTrue("calibre_wireless")
return enabled and not CalibreWireless.calibre_socket
end
local t = {
{
text = _("Enable wireless client"),
separator = true,
enabled_func = function()
return not CalibreWireless.calibre_socket
end,
checked_func = function()
return G_reader_settings:nilOrTrue("calibre_wireless")
end,
callback = function()
G_reader_settings:flipNilOrTrue("calibre_wireless")
end,
},
{
text = _("Set password"),
enabled_func = isEnabled,
callback = function()
CalibreWireless:setPassword()
end,
},
{
text = _("Set inbox folder"),
enabled_func = isEnabled,
callback = function()
CalibreWireless:setInboxDir()
end,
},
{
text_func = function()
local address = _("automatic")
if G_reader_settings:has("calibre_wireless_url") then
address = G_reader_settings:readSetting("calibre_wireless_url")
address = string.format("%s:%s", address["address"], address["port"])
end
return T(_("Server address (%1)"), BD.ltr(address))
end,
enabled_func = isEnabled,
sub_item_table = {
{
text = C_("Configuration type", "Automatic"),
checked_func = function()
return G_reader_settings:hasNot("calibre_wireless_url")
end,
callback = function()
G_reader_settings:delSetting("calibre_wireless_url")
end,
},
{
text = C_("Configuration type", "Manual"),
checked_func = function()
return G_reader_settings:has("calibre_wireless_url")
end,
callback = function(touchmenu_instance)
local MultiInputDialog = require("ui/widget/multiinputdialog")
local url_dialog
local calibre_url = G_reader_settings:readSetting("calibre_wireless_url")
local calibre_url_address, calibre_url_port
if calibre_url then
calibre_url_address = calibre_url["address"]
calibre_url_port = calibre_url["port"]
end
url_dialog = MultiInputDialog:new{
title = _("Set custom calibre address"),
fields = {
{
text = calibre_url_address,
input_type = "string",
hint = _("IP Address"),
},
{
text = calibre_url_port,
input_type = "number",
hint = _("Port"),
},
},
buttons = {
{
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(url_dialog)
end,
},
{
text = _("OK"),
callback = function()
local fields = url_dialog:getFields()
if fields[1] ~= "" then
local port = tonumber(fields[2])
if not port or port < 1 or port > 65355 then
--default port
port = 9090
end
G_reader_settings:saveSetting("calibre_wireless_url", {address = fields[1], port = port })
end
UIManager:close(url_dialog)
if touchmenu_instance then touchmenu_instance:updateItems() end
end,
},
},
},
}
UIManager:show(url_dialog)
url_dialog:onShowKeyboard()
end,
},
},
},
}
if not CalibreExtensions:isCustom() then
table.insert(t, 2, {
text = _("File formats"),
enabled_func = isEnabled,
sub_item_table_func = function()
local submenu = {
{
text = _("About formats"),
keep_menu_open = true,
separator = true,
callback = function()
UIManager:show(InfoMessage:new{
text = string.format("%s: %s \n\n%s",
_("Supported file formats"),
CalibreExtensions:getInfo(),
_("Unsupported formats will be converted by calibre to the first format of the list."))
})
end,
}
}
for i, v in ipairs(CalibreExtensions.outputs) do
table.insert(submenu, {})
submenu[i+1].text = v
submenu[i+1].checked_func = function()
if v == CalibreExtensions.default_output then
return true
end
return false
end
submenu[i+1].callback = function()
if type(v) == "string" and v ~= CalibreExtensions.default_output then
CalibreExtensions.default_output = v
G_reader_settings:saveSetting("calibre_wireless_default_format", CalibreExtensions.default_output)
end
end
end
return submenu
end,
})
end
return t
end
function Calibre:getParserMenuTable()
return {
{
text = _("Automatic"),
help_text = _("The program will decide based on the size of the JSON file. Recommended"),
checked_func = function()
return G_reader_settings:hasNot("calibre_json_parser")
end,
callback = function()
G_reader_settings:delSetting("calibre_json_parser")
end,
},
{
text = _("Fast"),
help_text = _("Faster parsing, but may not take too kindly to malformed input files"),
checked_func = function()
return G_reader_settings:readSetting("calibre_json_parser") == "fast"
end,
callback = function()
G_reader_settings:saveSetting("calibre_json_parser", "fast")
end,
},
{
text = _("Safe"),
help_text = _("Slower, but safer. Useful if you're experiencing problems with the other modes"),
checked_func = function()
return G_reader_settings:readSetting("calibre_json_parser") == "safe"
end,
callback = function()
G_reader_settings:saveSetting("calibre_json_parser", "safe")
end,
},
{
text = _("Legacy"),
help_text = _("Fast, but requires more RAM, only recommended on modest library sizes (or beefier devices)"),
checked_func = function()
return G_reader_settings:readSetting("calibre_json_parser") == "legacy"
end,
callback = function()
G_reader_settings:saveSetting("calibre_json_parser", "legacy")
end,
},
}
end
return Calibre