2020-06-19 10:22:38 +00:00
--[[
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 " )
2021-09-01 14:48:46 +00:00
local CalibreExtensions = require ( " extensions " )
2020-06-19 10:22:38 +00:00
local CalibreSearch = require ( " search " )
local CalibreWireless = require ( " wireless " )
2020-10-12 03:06:04 +00:00
local Dispatcher = require ( " dispatcher " )
2020-06-19 10:22:38 +00:00
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 " )
2022-03-16 15:18:18 +00:00
local C_ = _.pgettext
2020-06-19 10:22:38 +00:00
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 {
2020-06-19 10:22:38 +00:00
name = " calibre " ,
is_doc_only = false ,
}
function Calibre : onCalibreSearch ( )
CalibreSearch : ShowSearch ( )
return true
end
2023-02-14 23:40:40 +00:00
function Calibre : onCalibreBrowseBy ( field )
2020-06-19 10:22:38 +00:00
CalibreSearch.search_value = " "
2023-02-14 23:40:40 +00:00
CalibreSearch : find ( field )
2020-06-19 10:22:38 +00:00
return true
end
function Calibre : onNetworkDisconnected ( )
self : closeWirelessConnection ( )
end
function Calibre : onSuspend ( )
self : closeWirelessConnection ( )
end
function Calibre : onClose ( )
self : closeWirelessConnection ( )
end
2024-05-12 22:16:17 +00:00
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
2020-06-19 10:22:38 +00:00
function Calibre : closeWirelessConnection ( )
if CalibreWireless.calibre_socket then
CalibreWireless : disconnect ( )
end
end
2020-10-12 03:06:04 +00:00
function Calibre : onDispatcherRegisterActions ( )
2021-09-10 20:11:24 +00:00
Dispatcher : registerAction ( " calibre_search " , { category = " none " , event = " CalibreSearch " , title = _ ( " Calibre metadata search " ) , general = true , } )
2023-02-14 23:40:40 +00:00
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 , } )
2024-05-12 22:16:17 +00:00
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 , } )
2020-10-12 03:06:04 +00:00
end
2020-06-19 10:22:38 +00:00
function Calibre : init ( )
CalibreWireless : init ( )
2020-10-12 03:06:04 +00:00
self : onDispatcherRegisterActions ( )
2020-06-19 10:22:38 +00:00
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 ( ) ,
} ,
2024-06-05 23:06:46 +00:00
{
text = _ ( " JSON parser " ) ,
keep_menu_open = true ,
sub_item_table = self : getParserMenuTable ( ) ,
} ,
2020-06-19 10:22:38 +00:00
}
}
-- 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 = {
2021-04-02 15:59:29 +00:00
text = _ ( " Calibre metadata search " ) ,
2020-06-19 10:22:38 +00:00
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 )
2020-06-19 10:22:38 +00:00
for path , _ in pairs ( cache.data ) do
table.insert ( result , {
text = path ,
keep_menu_open = true ,
checked_func = function ( )
2021-03-06 21:44:18 +00:00
return cache : isTrue ( path )
2020-06-19 10:22:38 +00:00
end ,
callback = function ( )
2021-03-06 21:44:18 +00:00
cache : toggle ( path )
2020-06-19 10:22:38 +00:00
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 ( )
2021-03-06 21:44:18 +00:00
G_reader_settings : toggle ( " calibre_search_from_reader " )
2020-06-19 10:22:38 +00:00
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 ,
} ,
2023-02-14 23:40:40 +00:00
{
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 ,
} ,
2020-06-19 10:22:38 +00:00
{
text = _ ( " Search by path " ) ,
checked_func = function ( )
2023-02-14 23:40:40 +00:00
return G_reader_settings : isTrue ( " calibre_search_find_by_path " )
2020-06-19 10:22:38 +00:00
end ,
callback = function ( )
2023-02-14 23:40:40 +00:00
G_reader_settings : toggle ( " calibre_search_find_by_path " )
2020-06-19 10:22:38 +00:00
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
2021-09-01 14:48:46 +00:00
local t = {
2020-06-19 10:22:38 +00:00
{
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 ,
} ,
{
2021-02-22 17:44:16 +00:00
text = _ ( " Set inbox folder " ) ,
2020-06-19 10:22:38 +00:00
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 = {
{
2022-03-16 15:18:18 +00:00
text = C_ ( " Configuration type " , " Automatic " ) ,
2020-06-19 10:22:38 +00:00
checked_func = function ( )
return G_reader_settings : hasNot ( " calibre_wireless_url " )
end ,
callback = function ( )
G_reader_settings : delSetting ( " calibre_wireless_url " )
end ,
} ,
{
2022-03-16 15:18:18 +00:00
text = C_ ( " Configuration type " , " Manual " ) ,
2020-06-19 10:22:38 +00:00
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 " ) ,
2022-03-04 20:20:00 +00:00
id = " close " ,
2020-06-19 10:22:38 +00:00
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 ,
} ,
} ,
} ,
}
2021-09-01 14:48:46 +00:00
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
2020-06-19 10:22:38 +00:00
end
2024-06-05 23:06:46 +00:00
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
2020-06-19 10:22:38 +00:00
return Calibre