2
0
mirror of https://github.com/koreader/koreader synced 2024-11-04 12:00:25 +00:00
koreader/plugins/calibre.koplugin/main.lua

402 lines
15 KiB
Lua
Raw Normal View History

--[[
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
Clarify our OOP semantics across the codebase (#9586) Basically: * Use `extend` for class definitions * Use `new` for object instantiations That includes some minor code cleanups along the way: * Updated `Widget`'s docs to make the semantics clearer. * Removed `should_restrict_JIT` (it's been dead code since https://github.com/koreader/android-luajit-launcher/pull/283) * Minor refactoring of LuaSettings/LuaData/LuaDefaults/DocSettings to behave (mostly, they are instantiated via `open` instead of `new`) like everything else and handle inheritance properly (i.e., DocSettings is now a proper LuaSettings subclass). * Default to `WidgetContainer` instead of `InputContainer` for stuff that doesn't actually setup key/gesture events. * Ditto for explicit `*Listener` only classes, make sure they're based on `EventListener` instead of something uselessly fancier. * Unless absolutely necessary, do not store references in class objects, ever; only values. Instead, always store references in instances, to avoid both sneaky inheritance issues, and sneaky GC pinning of stale references. * ReaderUI: Fix one such issue with its `active_widgets` array, with critical implications, as it essentially pinned *all* of ReaderUI's modules, including their reference to the `Document` instance (i.e., that was a big-ass leak). * Terminal: Make sure the shell is killed on plugin teardown. * InputText: Fix Home/End/Del physical keys to behave sensibly. * InputContainer/WidgetContainer: If necessary, compute self.dimen at paintTo time (previously, only InputContainers did, which might have had something to do with random widgets unconcerned about input using it as a baseclass instead of WidgetContainer...). * OverlapGroup: Compute self.dimen at *init* time, because for some reason it needs to do that, but do it directly in OverlapGroup instead of going through a weird WidgetContainer method that it was the sole user of. * ReaderCropping: Under no circumstances should a Document instance member (here, self.bbox) risk being `nil`ed! * Kobo: Minor code cleanups.
2022-10-06 00:14:48 +00:00
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: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,})
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(),
},
}
}
-- 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.
2021-01-13 10:45:16 +00:00
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
return Calibre