2020-01-04 00:18:51 +00:00
local BD = require ( " ui/bidi " )
2017-06-18 16:08:57 +00:00
local ConfirmBox = require ( " ui/widget/confirmbox " )
2015-10-03 06:18:47 +00:00
local DataStorage = require ( " datastorage " )
2015-04-15 12:28:32 +00:00
local Device = require ( " device " )
2017-04-07 13:20:57 +00:00
local DictQuickLookup = require ( " ui/widget/dictquicklookup " )
2018-01-15 22:51:43 +00:00
local Geom = require ( " ui/geometry " )
2017-04-07 13:20:57 +00:00
local InfoMessage = require ( " ui/widget/infomessage " )
2017-06-18 16:08:57 +00:00
local InputContainer = require ( " ui/widget/container/inputcontainer " )
2019-03-02 12:29:10 +00:00
local InputDialog = require ( " ui/widget/inputdialog " )
2015-03-21 03:39:25 +00:00
local JSON = require ( " json " )
2017-10-07 20:13:46 +00:00
local KeyValuePage = require ( " ui/widget/keyvaluepage " )
local LuaData = require ( " luadata " )
2019-10-25 15:25:26 +00:00
local MultiConfirmBox = require ( " ui/widget/multiconfirmbox " )
2018-12-13 06:27:49 +00:00
local NetworkMgr = require ( " ui/network/manager " )
2020-07-30 13:14:45 +00:00
local SortWidget = require ( " ui/widget/sortwidget " )
2017-09-19 19:24:48 +00:00
local Trapper = require ( " ui/trapper " )
2017-04-07 13:20:57 +00:00
local UIManager = require ( " ui/uimanager " )
2018-12-13 06:27:49 +00:00
local ffiUtil = require ( " ffi/util " )
2016-12-29 08:10:38 +00:00
local logger = require ( " logger " )
2022-05-05 19:00:22 +00:00
local time = require ( " ui/time " )
2016-12-06 21:15:52 +00:00
local util = require ( " util " )
2014-07-24 14:11:25 +00:00
local _ = require ( " gettext " )
2022-05-23 11:52:52 +00:00
local Input = Device.input
2020-09-15 18:39:32 +00:00
local T = ffiUtil.template
2013-04-23 22:59:52 +00:00
2017-08-29 10:52:14 +00:00
-- We'll store the list of available dictionaries as a module local
-- so we only have to look for them on the first :init()
local available_ifos = nil
2017-10-07 20:13:46 +00:00
local lookup_history = nil
2017-08-29 10:52:14 +00:00
local function getIfosInDir ( path )
2017-08-29 20:27:43 +00:00
-- Get all the .ifo under directory path.
2018-01-11 19:28:34 +00:00
-- Don't walk into "res/" subdirectories, as per Stardict specs, they
-- may contain possibly many resource files (image, audio files...)
-- that could slow down our walk here.
2017-08-29 10:52:14 +00:00
local ifos = { }
local ok , iter , dir_obj = pcall ( lfs.dir , path )
if ok then
for name in iter , dir_obj do
2018-01-11 19:28:34 +00:00
if name ~= " . " and name ~= " .. " and name ~= " res " then
2017-08-29 10:52:14 +00:00
local fullpath = path .. " / " .. name
2018-01-11 19:28:34 +00:00
local attributes = lfs.attributes ( fullpath )
if attributes ~= nil then
if attributes.mode == " directory " then
local dirifos = getIfosInDir ( fullpath ) -- recurse
for _ , ifo in pairs ( dirifos ) do
table.insert ( ifos , ifo )
end
elseif fullpath : match ( " %.ifo$ " ) then
table.insert ( ifos , fullpath )
2017-08-29 10:52:14 +00:00
end
end
end
end
end
return ifos
end
2015-04-15 12:28:32 +00:00
local ReaderDictionary = InputContainer : new {
data_dir = nil ,
2016-06-28 16:35:00 +00:00
dict_window_list = { } ,
2017-10-07 20:13:46 +00:00
lookup_msg = _ ( " Searching dictionary for: \n %1 " ) ,
2015-04-15 12:28:32 +00:00
}
2014-10-15 10:01:58 +00:00
2018-01-09 20:38:49 +00:00
-- For a HTML dict, one can specify a specific stylesheet
-- in a file named as the .ifo with a .css extension
2018-01-07 19:24:15 +00:00
local function readDictionaryCss ( path )
local f = io.open ( path , " r " )
if not f then
return nil
end
local content = f : read ( " *all " )
f : close ( )
return content
end
2018-01-09 20:38:49 +00:00
-- For a HTML dict, one can specify a function called on
-- the raw returned definition to "fix" the HTML if needed
-- (as MuPDF, used for rendering, is quite sensitive to the
-- HTML quality) in a file named as the .ifo with a .lua
-- extension, containing for example:
-- return function(html)
2018-04-29 13:15:11 +00:00
-- html = html:gsub("<hr>", "<hr/>")
-- return html
2018-01-09 20:38:49 +00:00
-- end
local function getDictionaryFixHtmlFunc ( path )
if lfs.attributes ( path , " mode " ) == " file " then
local ok , func = pcall ( dofile , path )
if ok and func then
return func
else
logger.warn ( " Dict's user provided file failed: " , func )
end
end
end
2014-10-15 10:01:58 +00:00
function ReaderDictionary : init ( )
2021-03-06 21:44:18 +00:00
self.disable_lookup_history = G_reader_settings : isTrue ( " disable_lookup_history " )
self.dicts_order = G_reader_settings : readSetting ( " dicts_order " , { } )
self.dicts_disabled = G_reader_settings : readSetting ( " dicts_disabled " , { } )
2021-10-23 10:13:09 +00:00
if self.ui then
self.ui . menu : registerToMainMenu ( self )
end
2020-04-14 17:36:44 +00:00
self.data_dir = STARDICT_DATA_DIR or
os.getenv ( " STARDICT_DATA_DIR " ) or
2015-10-03 06:18:47 +00:00
DataStorage : getDataDir ( ) .. " /data/dict "
2017-08-29 10:52:14 +00:00
2021-01-07 17:05:18 +00:00
-- Show the "Seaching..." InfoMessage after this delay
self.lookup_msg_delay = 0.5
-- Allow quick interruption or dismiss of search result window
-- with tap if done before this delay. After this delay, the
-- result window is shown and dismiss prevented for a few 100ms
2022-05-05 19:00:22 +00:00
self.quick_dismiss_before_delay = time.s ( 3 )
2021-01-07 17:05:18 +00:00
2017-08-29 10:52:14 +00:00
-- Gather info about available dictionaries
if not available_ifos then
available_ifos = { }
logger.dbg ( " Getting list of dictionaries " )
local ifo_files = getIfosInDir ( self.data_dir )
local dict_ext = self.data_dir .. " _ext "
if lfs.attributes ( dict_ext , " mode " ) == " directory " then
local extifos = getIfosInDir ( dict_ext )
for _ , ifo in pairs ( extifos ) do
table.insert ( ifo_files , ifo )
end
end
for _ , ifo_file in pairs ( ifo_files ) do
2017-08-29 20:27:43 +00:00
local f = io.open ( ifo_file , " r " )
2017-08-29 10:52:14 +00:00
if f then
local content = f : read ( " *all " )
f : close ( )
2017-08-29 20:27:43 +00:00
local dictname = content : match ( " \n bookname=(.-) \n " )
2018-01-07 19:24:15 +00:00
local is_html = content : find ( " sametypesequence=h " , 1 , true ) ~= nil
2017-08-29 10:52:14 +00:00
-- sdcv won't use dict that don't have a bookname=
if dictname then
table.insert ( available_ifos , {
file = ifo_file ,
name = dictname ,
2018-01-07 19:24:15 +00:00
is_html = is_html ,
2018-01-09 20:38:49 +00:00
css = readDictionaryCss ( ifo_file : gsub ( " %.ifo$ " , " .css " ) ) ,
fix_html_func = getDictionaryFixHtmlFunc ( ifo_file : gsub ( " %.ifo$ " , " .lua " ) ) ,
2017-08-29 10:52:14 +00:00
} )
end
end
end
logger.dbg ( " found " , # available_ifos , " dictionaries " )
2020-07-30 13:14:45 +00:00
self : sortAvailableIfos ( )
2017-08-29 10:52:14 +00:00
end
2020-07-30 13:14:45 +00:00
-- Prepare the -u options to give to sdcv the dictionary order and if some are disabled
2017-08-29 20:27:43 +00:00
self : updateSdcvDictNamesOptions ( )
2021-01-06 20:49:23 +00:00
2017-10-07 20:13:46 +00:00
if not lookup_history then
lookup_history = LuaData : open ( DataStorage : getSettingsDir ( ) .. " /lookup_history.lua " , { name = " LookupHistory " } )
end
2017-08-29 20:27:43 +00:00
end
2020-07-30 13:14:45 +00:00
function ReaderDictionary : sortAvailableIfos ( )
table.sort ( available_ifos , function ( lifo , rifo )
2021-03-06 21:44:18 +00:00
local lord = self.dicts_order [ lifo.file ]
local rord = self.dicts_order [ rifo.file ]
2020-07-30 13:14:45 +00:00
-- Both ifos without an explicit position -> lexical comparison
if lord == rord then
return ffiUtil.strcoll ( lifo.name , rifo.name )
end
-- Ifos without an explicit position come last.
return lord ~= nil and ( rord == nil or lord < rord )
end )
end
2017-08-29 20:27:43 +00:00
function ReaderDictionary : updateSdcvDictNamesOptions ( )
-- We cannot tell sdcv which dictionaries to ignore, but we
-- can tell it which dictionaries to use, by using multiple
-- -u <dictname> options.
2020-07-30 13:14:45 +00:00
-- The order of the -u options controls the dictionary order
-- that sdcv uses to order its results.
2021-01-06 20:49:23 +00:00
self.enabled_dict_names = { }
-- First, insert any preferred dicts, even if globally disabled
-- (this might allow enabling a dict only for a specific book,
-- while keeping it disabled for all others)
local preferred_names_already_in = { }
if self.preferred_dictionaries then
for _ , name in ipairs ( self.preferred_dictionaries ) do
table.insert ( self.enabled_dict_names , name )
preferred_names_already_in [ name ] = true
end
end
2017-08-29 20:27:43 +00:00
local dicts_disabled = G_reader_settings : readSetting ( " dicts_disabled " )
for _ , ifo in pairs ( available_ifos ) do
2021-01-06 20:49:23 +00:00
if not dicts_disabled [ ifo.file ] and not preferred_names_already_in [ ifo.name ] then
2018-01-15 22:51:43 +00:00
table.insert ( self.enabled_dict_names , ifo.name )
2017-08-29 20:27:43 +00:00
end
end
2014-10-15 10:01:58 +00:00
end
2017-03-04 13:46:38 +00:00
function ReaderDictionary : addToMainMenu ( menu_items )
menu_items.dictionary_lookup = {
2014-10-15 10:01:58 +00:00
text = _ ( " Dictionary lookup " ) ,
2019-03-02 12:29:10 +00:00
callback = function ( )
self : onShowDictionaryLookup ( )
end ,
2017-02-28 21:46:32 +00:00
}
2017-10-07 20:13:46 +00:00
menu_items.dictionary_lookup_history = {
text = _ ( " Dictionary lookup history " ) ,
enabled_func = function ( )
return lookup_history : has ( " lookup_history " )
end ,
callback = function ( )
local lookup_history_table = lookup_history : readSetting ( " lookup_history " )
local kv_pairs = { }
local previous_title
for i = # lookup_history_table , 1 , - 1 do
local value = lookup_history_table [ i ]
if value.book_title ~= previous_title then
table.insert ( kv_pairs , { value.book_title .. " : " , " " } )
end
previous_title = value.book_title
table.insert ( kv_pairs , {
os.date ( " %Y-%m-%d %H:%M:%S " , value.time ) ,
value.word ,
callback = function ( )
2021-01-01 13:34:51 +00:00
-- Word had been cleaned before being added to history
self : onLookupWord ( value.word , true )
2017-10-07 20:13:46 +00:00
end
} )
end
UIManager : show ( KeyValuePage : new {
title = _ ( " Dictionary lookup history " ) ,
2021-02-20 19:15:43 +00:00
value_overflow_align = " right " ,
2017-10-07 20:13:46 +00:00
kv_pairs = kv_pairs ,
} )
end ,
}
2017-08-22 15:24:31 +00:00
menu_items.dictionary_settings = {
text = _ ( " Dictionary settings " ) ,
sub_item_table = {
2017-08-29 10:52:14 +00:00
{
2020-07-30 13:14:45 +00:00
keep_menu_open = true ,
2017-08-29 10:52:14 +00:00
text_func = function ( )
2017-09-05 05:56:05 +00:00
local nb_available , nb_enabled , nb_disabled = self : getNumberOfDictionaries ( )
2017-08-29 20:27:43 +00:00
local nb_str = nb_available
if nb_disabled > 0 then
nb_str = nb_enabled .. " / " .. nb_available
2017-08-29 10:52:14 +00:00
end
2021-11-09 19:15:52 +00:00
return T ( _ ( " Manage dictionaries: %1 " ) , nb_str )
2017-08-29 10:52:14 +00:00
end ,
2017-09-05 05:56:05 +00:00
enabled_func = function ( )
return self : getNumberOfDictionaries ( ) > 0
end ,
2020-07-30 13:14:45 +00:00
callback = function ( touchmenu_instance )
self : showDictionariesMenu ( function ( )
if touchmenu_instance then touchmenu_instance : updateItems ( ) end
end )
2017-12-17 12:02:08 +00:00
end ,
2017-08-29 10:52:14 +00:00
} ,
2018-12-13 06:27:49 +00:00
{
text = _ ( " Download dictionaries " ) ,
sub_item_table = self : _genDownloadDictionariesMenu ( )
} ,
2017-08-22 15:24:31 +00:00
{
2017-12-17 12:02:08 +00:00
text = _ ( " Enable fuzzy search " ) ,
2017-08-22 15:24:31 +00:00
checked_func = function ( )
2017-12-17 12:02:08 +00:00
return not self.disable_fuzzy_search == true
2017-08-22 15:24:31 +00:00
end ,
callback = function ( )
self.disable_fuzzy_search = not self.disable_fuzzy_search
end ,
hold_callback = function ( )
2019-10-25 15:25:26 +00:00
self : toggleFuzzyDefault ( )
2017-08-22 15:24:31 +00:00
end ,
2017-12-17 12:02:08 +00:00
separator = true ,
2017-08-22 15:24:31 +00:00
} ,
2017-10-07 20:13:46 +00:00
{
2017-12-17 12:02:08 +00:00
text = _ ( " Enable dictionary lookup history " ) ,
2017-10-07 20:13:46 +00:00
checked_func = function ( )
2017-12-17 12:02:08 +00:00
return not self.disable_lookup_history
2017-10-07 20:13:46 +00:00
end ,
callback = function ( )
self.disable_lookup_history = not self.disable_lookup_history
G_reader_settings : saveSetting ( " disable_lookup_history " , self.disable_lookup_history )
end ,
} ,
{
text = _ ( " Clean dictionary lookup history " ) ,
2021-05-06 15:28:54 +00:00
enabled_func = function ( )
return lookup_history : has ( " lookup_history " )
end ,
2018-09-04 21:55:58 +00:00
keep_menu_open = true ,
2021-05-06 15:28:54 +00:00
callback = function ( touchmenu_instance )
2017-10-07 20:13:46 +00:00
UIManager : show ( ConfirmBox : new {
text = _ ( " Clean dictionary lookup history? " ) ,
ok_text = _ ( " Clean " ) ,
ok_callback = function ( )
-- empty data table to replace current one
lookup_history : reset { }
2021-05-06 15:28:54 +00:00
touchmenu_instance : updateItems ( )
2017-10-07 20:13:46 +00:00
end ,
} )
end ,
2017-12-17 12:02:08 +00:00
separator = true ,
2017-10-07 20:13:46 +00:00
} ,
2017-11-20 08:40:00 +00:00
{ -- setting used by dictquicklookup
text = _ ( " Large window " ) ,
checked_func = function ( )
return G_reader_settings : isTrue ( " dict_largewindow " )
end ,
callback = function ( )
G_reader_settings : flipNilOrFalse ( " dict_largewindow " )
end ,
} ,
2017-08-22 15:24:31 +00:00
{ -- setting used by dictquicklookup
text = _ ( " Justify text " ) ,
checked_func = function ( )
return G_reader_settings : nilOrTrue ( " dict_justify " )
end ,
callback = function ( )
G_reader_settings : flipNilOrTrue ( " dict_justify " )
end ,
2019-11-27 22:59:08 +00:00
} ,
{ -- setting used by dictquicklookup
text_func = function ( )
local font_size = G_reader_settings : readSetting ( " dict_font_size " ) or 20
2021-11-09 19:15:52 +00:00
return T ( _ ( " Font size: %1 " ) , font_size )
2019-11-27 22:59:08 +00:00
end ,
2019-11-28 09:54:06 +00:00
callback = function ( touchmenu_instance )
2019-11-27 22:59:08 +00:00
local SpinWidget = require ( " ui/widget/spinwidget " )
local font_size = G_reader_settings : readSetting ( " dict_font_size " ) or 20
local items_font = SpinWidget : new {
value = font_size ,
value_min = 8 ,
value_max = 32 ,
default_value = 20 ,
2021-04-02 15:59:29 +00:00
title_text = _ ( " Dictionary font size " ) ,
2019-11-27 22:59:08 +00:00
callback = function ( spin )
G_reader_settings : saveSetting ( " dict_font_size " , spin.value )
2019-11-28 09:54:06 +00:00
if touchmenu_instance then touchmenu_instance : updateItems ( ) end
2019-11-27 22:59:08 +00:00
end ,
}
UIManager : show ( items_font )
2019-11-28 09:54:06 +00:00
end ,
keep_menu_open = true ,
2017-08-22 15:24:31 +00:00
}
}
2017-06-18 16:08:57 +00:00
}
2019-07-08 12:19:36 +00:00
if Device : canExternalDictLookup ( ) then
local function genExternalDictItems ( )
local items_table = { }
for i , v in ipairs ( Device : getExternalDictLookupList ( ) ) do
local setting = v [ 1 ]
local dict_name = v [ 2 ]
local is_enabled = v [ 3 ]
table.insert ( items_table , {
text = dict_name ,
checked_func = function ( )
return setting == G_reader_settings : readSetting ( " external_dict_lookup_method " )
end ,
enabled_func = function ( )
return is_enabled == true
end ,
callback = function ( )
G_reader_settings : saveSetting ( " external_dict_lookup_method " , v [ 1 ] )
end ,
} )
end
return items_table
end
table.insert ( menu_items.dictionary_settings . sub_item_table , 1 , {
text = _ ( " Use external dictionary " ) ,
checked_func = function ( )
return G_reader_settings : isTrue ( " external_dict_lookup " )
end ,
callback = function ( )
G_reader_settings : flipNilOrFalse ( " external_dict_lookup " )
end ,
} )
table.insert ( menu_items.dictionary_settings . sub_item_table , 2 , {
text_func = function ( )
local display_name = _ ( " none " )
local ext_id = G_reader_settings : readSetting ( " external_dict_lookup_method " )
for i , v in ipairs ( Device : getExternalDictLookupList ( ) ) do
if v [ 1 ] == ext_id then
display_name = v [ 2 ]
break
end
end
return T ( _ ( " Dictionary: %1 " ) , display_name )
end ,
enabled_func = function ( )
return G_reader_settings : isTrue ( " external_dict_lookup " )
end ,
sub_item_table = genExternalDictItems ( ) ,
separator = true ,
} )
end
2014-10-15 10:01:58 +00:00
end
2013-04-23 22:59:52 +00:00
2021-10-23 10:12:56 +00:00
function ReaderDictionary : onLookupWord ( word , is_sane , boxes , highlight , link )
logger.dbg ( " dict lookup word: " , word , boxes )
2018-01-15 22:51:43 +00:00
-- escape quotes and other funny characters in word
2021-01-01 13:34:51 +00:00
word = self : cleanSelection ( word , is_sane )
2018-01-15 22:51:43 +00:00
logger.dbg ( " dict stripped word: " , word )
2014-03-13 13:52:43 +00:00
self.highlight = highlight
2018-01-15 22:51:43 +00:00
2017-09-19 19:24:48 +00:00
-- Wrapped through Trapper, as we may be using Trapper:dismissablePopen() in it
Trapper : wrap ( function ( )
2021-10-23 10:12:56 +00:00
self : stardictLookup ( word , self.enabled_dict_names , not self.disable_fuzzy_search , boxes , link )
2017-09-19 19:24:48 +00:00
end )
2014-08-20 06:41:45 +00:00
return true
2013-04-30 10:45:12 +00:00
end
2018-01-15 22:51:43 +00:00
function ReaderDictionary : onHtmlDictionaryLinkTapped ( dictionary , link )
if not link.uri then
return
end
-- The protocol is either "bword" or there is no protocol, only the word.
-- https://github.com/koreader/koreader/issues/3588#issuecomment-357088125
local url_prefix = " bword:// "
local word
if link.uri : sub ( 1 , url_prefix : len ( ) ) == url_prefix then
word = link.uri : sub ( url_prefix : len ( ) + 1 )
elseif link.uri : find ( " :// " ) then
return
else
word = link.uri
end
if word == " " then
return
end
local link_box = Geom : new {
x = link.x0 ,
y = link.y0 ,
w = math.abs ( link.x1 - link.x0 ) ,
h = math.abs ( link.y1 - link.y0 ) ,
}
-- Only the first dictionary window stores the highlight, this way the highlight
-- is only removed when there are no more dictionary windows open.
self.highlight = nil
-- Wrapped through Trapper, as we may be using Trapper:dismissablePopen() in it
Trapper : wrap ( function ( )
2021-10-23 10:12:56 +00:00
self : stardictLookup ( word , { dictionary } , false , { link_box } , nil )
2018-01-15 22:51:43 +00:00
end )
end
2017-09-05 05:56:05 +00:00
--- Gets number of available, enabled, and disabled dictionaries
-- @treturn int nb_available
-- @treturn int nb_enabled
-- @treturn int nb_disabled
function ReaderDictionary : getNumberOfDictionaries ( )
local nb_available = # available_ifos
2018-01-09 12:28:17 +00:00
local nb_enabled = 0
2017-09-05 05:56:05 +00:00
local nb_disabled = 0
2018-01-09 12:28:17 +00:00
for _ , ifo in pairs ( available_ifos ) do
2021-03-06 21:44:18 +00:00
if self.dicts_disabled [ ifo.file ] then
2018-01-09 12:28:17 +00:00
nb_disabled = nb_disabled + 1
else
nb_enabled = nb_enabled + 1
end
2017-09-05 05:56:05 +00:00
end
return nb_available , nb_enabled , nb_disabled
end
2018-12-13 06:27:49 +00:00
function ReaderDictionary : _genDownloadDictionariesMenu ( )
local downloadable_dicts = require ( " ui/data/dictionaries " )
local languages = { }
for i = 1 , # downloadable_dicts do
local dict = downloadable_dicts [ i ]
local dict_lang_in = dict.lang_in
local dict_lang_out = dict.lang_out
if not languages [ dict_lang_in ] then
languages [ dict_lang_in ] = { }
end
table.insert ( languages [ dict_lang_in ] , dict )
if not languages [ dict_lang_out ] then
languages [ dict_lang_out ] = { }
end
table.insert ( languages [ dict_lang_out ] , dict )
end
-- remove duplicates
for lang_key , lang in pairs ( languages ) do
local hash = { }
local res = { }
for k , v in ipairs ( lang ) do
if not hash [ v.name ] then
res [ # res + 1 ] = v
hash [ v.name ] = true
end
end
languages [ lang_key ] = res
end
local menu_items = { }
for lang_key , available_langs in ffiUtil.orderedPairs ( languages ) do
table.insert ( menu_items , {
2018-12-23 18:49:06 +00:00
keep_menu_open = true ,
2018-12-13 06:27:49 +00:00
text = lang_key ,
callback = function ( )
self : showDownload ( available_langs )
end
} )
end
return menu_items
end
2020-07-30 13:14:45 +00:00
function ReaderDictionary : showDictionariesMenu ( changed_callback )
-- Work on local copy, save to settings only when SortWidget is closed with the accept button
2021-03-06 21:44:18 +00:00
local dicts_disabled = util.tableDeepCopy ( self.dicts_disabled )
2020-07-30 13:14:45 +00:00
local sort_items = { }
2017-08-29 10:52:14 +00:00
for _ , ifo in pairs ( available_ifos ) do
2020-07-30 13:14:45 +00:00
table.insert ( sort_items , {
2017-08-29 10:52:14 +00:00
text = ifo.name ,
callback = function ( )
2017-08-29 20:27:43 +00:00
if dicts_disabled [ ifo.file ] then
dicts_disabled [ ifo.file ] = nil
else
dicts_disabled [ ifo.file ] = true
2017-08-29 10:52:14 +00:00
end
end ,
checked_func = function ( )
2017-08-29 20:27:43 +00:00
return not dicts_disabled [ ifo.file ]
2020-07-30 13:14:45 +00:00
end ,
ifo = ifo ,
2017-08-29 10:52:14 +00:00
} )
end
2020-07-30 13:14:45 +00:00
local sort_widget = SortWidget : new {
title = _ ( " Manage installed dictionaries " ) ,
item_table = sort_items ,
callback = function ( )
2021-03-06 21:44:18 +00:00
-- Update both references to point to that new object
self.dicts_disabled = dicts_disabled
G_reader_settings : saveSetting ( " dicts_disabled " , self.dicts_disabled )
2020-07-30 13:14:45 +00:00
2021-04-16 20:27:52 +00:00
-- Write back the sorted items array to dicts_order
2020-07-30 13:14:45 +00:00
local dicts_order = { }
2021-04-16 20:27:52 +00:00
for i , sort_item in ipairs ( sort_items ) do
2020-07-30 13:14:45 +00:00
dicts_order [ sort_item.ifo . file ] = i
end
2021-03-06 21:44:18 +00:00
self.dicts_order = dicts_order
G_reader_settings : saveSetting ( " dicts_order " , self.dicts_order )
2020-07-30 13:14:45 +00:00
self : sortAvailableIfos ( )
self : updateSdcvDictNamesOptions ( )
UIManager : setDirty ( nil , " ui " )
changed_callback ( )
end
}
UIManager : show ( sort_widget )
2017-08-29 10:52:14 +00:00
end
2017-04-26 06:12:25 +00:00
local function dictDirsEmpty ( dict_dirs )
for _ , dict_dir in ipairs ( dict_dirs ) do
if not util.isEmptyDir ( dict_dir ) then
return false
end
end
return true
end
2018-01-07 19:24:15 +00:00
local function getAvailableIfoByName ( dictionary_name )
for _ , ifo in ipairs ( available_ifos ) do
if ifo.name == dictionary_name then
return ifo
end
end
return nil
end
2016-12-06 21:15:52 +00:00
local function tidyMarkup ( results )
2014-10-28 07:57:01 +00:00
local cdata_tag = " <!%[CDATA%[(.-)%]%]> "
local format_escape = " &[29Ib%+]{(.-)} "
for _ , result in ipairs ( results ) do
2018-01-07 19:24:15 +00:00
local ifo = getAvailableIfoByName ( result.dict )
if ifo and ifo.is_html then
result.is_html = ifo.is_html
result.css = ifo.css
2018-01-09 20:38:49 +00:00
if ifo.fix_html_func then
2020-12-26 17:30:19 +00:00
local dict_path = util.splitFilePathName ( ifo.file )
local ok , fixed_definition = pcall ( ifo.fix_html_func , result.definition , dict_path )
2018-01-09 20:38:49 +00:00
if ok then
result.definition = fixed_definition
else
logger.warn ( " Dict's user provided funcion failed: " , fixed_definition )
end
end
2018-01-07 19:24:15 +00:00
else
local def = result.definition
-- preserve the <br> tag for line break
def = def : gsub ( " <[bB][rR] ?/?> " , " \n " )
-- parse CDATA text in XML
if def : find ( cdata_tag ) then
def = def : gsub ( cdata_tag , " %1 " )
-- ignore format strings
while def : find ( format_escape ) do
def = def : gsub ( format_escape , " %1 " )
end
2014-10-28 07:57:01 +00:00
end
2018-05-01 11:30:02 +00:00
-- convert any htmlentities (>, "...)
def = util.htmlEntitiesToUtf8 ( def )
2018-01-07 19:24:15 +00:00
-- ignore all markup tags
def = def : gsub ( " %b<> " , " " )
-- strip all leading empty lines/spaces
def = def : gsub ( " ^%s+ " , " " )
result.definition = def
2014-10-28 07:57:01 +00:00
end
end
return results
end
2021-01-01 13:34:51 +00:00
function ReaderDictionary : cleanSelection ( text , is_sane )
2016-12-06 21:15:52 +00:00
-- Will be used by ReaderWikipedia too
if not text then
return " "
end
2017-04-09 14:58:41 +00:00
-- crengine does now a much better job at finding word boundaries, but
-- some cleanup is still needed for selection we get from other engines
-- (example: pdf selection "qu’ autrefois," will be cleaned to "autrefois")
2016-12-06 21:15:52 +00:00
--
2017-04-09 14:58:41 +00:00
-- Replace no-break space with regular space
2018-10-22 16:40:28 +00:00
text = text : gsub ( " \xC2 \xA0 " , ' ' ) -- U+00A0 no-break space
2021-01-01 13:34:51 +00:00
-- Trim any space at start or end
2018-10-22 16:40:28 +00:00
text = text : gsub ( " ^%s+ " , " " )
text = text : gsub ( " %s+$ " , " " )
2021-01-01 13:34:51 +00:00
if not is_sane then
-- Replace extended quote (included in the general puncturation range)
-- with plain ascii quote (for french words like "aujourd’ hui")
text = text : gsub ( " \xE2 \x80 \x99 " , " ' " ) -- U+2019 (right single quotation mark)
-- Strip punctuation characters around selection
text = util.stripPunctuation ( text )
-- Strip some common english grammatical construct
text = text : gsub ( " 's$ " , ' ' ) -- english possessive
-- Strip some common french grammatical constructs
text = text : gsub ( " ^[LSDMNTlsdmnt]' " , ' ' ) -- french l' s' t'...
text = text : gsub ( " ^[Qq][Uu]' " , ' ' ) -- french qu'
-- There may be a need to remove some (all?) diacritical marks
-- https://en.wikipedia.org/wiki/Combining_character#Unicode_ranges
-- see discussion at https://github.com/koreader/koreader/issues/1649
-- Commented for now, will have to be checked by people who read
-- languages and texts that use them.
-- text = text:gsub("\204[\128-\191]", '') -- U+0300 to U+033F
-- text = text:gsub("\205[\128-\175]", '') -- U+0340 to U+036F
-- Trim any space now at start or end after above changes
text = text : gsub ( " ^%s+ " , " " )
text = text : gsub ( " %s+$ " , " " )
end
2016-12-06 21:15:52 +00:00
return text
end
2020-12-22 17:45:29 +00:00
function ReaderDictionary : showLookupInfo ( word , show_delay )
2016-12-06 21:15:52 +00:00
local text = T ( self.lookup_msg , word )
2020-12-22 17:45:29 +00:00
self.lookup_progress_msg = InfoMessage : new {
text = text ,
show_delay = show_delay ,
}
2016-12-06 21:15:52 +00:00
UIManager : show ( self.lookup_progress_msg )
end
2017-06-18 16:08:57 +00:00
function ReaderDictionary : dismissLookupInfo ( )
2016-12-06 21:15:52 +00:00
if self.lookup_progress_msg then
UIManager : close ( self.lookup_progress_msg )
end
self.lookup_progress_msg = nil
end
2019-03-02 12:29:10 +00:00
function ReaderDictionary : onShowDictionaryLookup ( )
self.dictionary_lookup_dialog = InputDialog : new {
2021-04-02 15:59:29 +00:00
title = _ ( " Enter a word or phrase to look up " ) ,
2019-03-02 12:29:10 +00:00
input = " " ,
input_type = " text " ,
buttons = {
{
{
text = _ ( " Cancel " ) ,
2022-03-04 20:20:00 +00:00
id = " close " ,
2019-03-02 12:29:10 +00:00
callback = function ( )
UIManager : close ( self.dictionary_lookup_dialog )
end ,
} ,
{
text = _ ( " Search dictionary " ) ,
is_enter_default = true ,
callback = function ( )
2021-04-04 15:27:17 +00:00
if self.dictionary_lookup_dialog : getInputText ( ) == " " then return end
2019-03-02 12:29:10 +00:00
UIManager : close ( self.dictionary_lookup_dialog )
2021-01-01 13:34:51 +00:00
-- Trust that input text does not need any cleaning (allows querying for "-suffix")
self : onLookupWord ( self.dictionary_lookup_dialog : getInputText ( ) , true )
2019-03-02 12:29:10 +00:00
end ,
} ,
}
} ,
}
UIManager : show ( self.dictionary_lookup_dialog )
self.dictionary_lookup_dialog : onShowKeyboard ( )
return true
end
2021-10-23 10:13:00 +00:00
function ReaderDictionary : rawSdcv ( words , dict_names , fuzzy_search , lookup_progress_msg )
2016-12-06 21:15:52 +00:00
-- Allow for two sdcv calls : one in the classic data/dict, and
-- another one in data/dict_ext if it exists
-- We could put in data/dict_ext dictionaries with a great number of words
-- but poor definitions as a fall back. If these were in data/dict,
-- they would prevent fuzzy searches in other dictories with better
-- definitions, and masks such results. This way, we can get both.
local dict_dirs = { self.data_dir }
local dict_ext = self.data_dir .. " _ext "
if lfs.attributes ( dict_ext , " mode " ) == " directory " then
table.insert ( dict_dirs , dict_ext )
end
2017-04-26 06:12:25 +00:00
-- early exit if no dictionaries
if dictDirsEmpty ( dict_dirs ) then
2021-10-23 10:13:00 +00:00
return false , nil
2017-04-26 06:12:25 +00:00
end
2021-11-21 18:13:29 +00:00
local all_results = { }
2017-09-19 19:24:48 +00:00
local lookup_cancelled = false
2016-12-06 21:15:52 +00:00
for _ , dict_dir in ipairs ( dict_dirs ) do
2017-09-19 19:24:48 +00:00
if lookup_cancelled then
break -- don't do any more lookup on additional dict_dirs
end
2018-01-15 22:51:43 +00:00
2021-01-11 13:32:10 +00:00
local args = { " ./sdcv " , " --utf8-input " , " --utf8-output " , " --json-output " , " --non-interactive " , " --data-dir " , dict_dir }
2018-01-15 22:51:43 +00:00
if not fuzzy_search then
table.insert ( args , " --exact-search " )
end
if dict_names then
for _ , opt in pairs ( dict_names ) do
table.insert ( args , " -u " )
table.insert ( args , opt )
end
end
2021-10-23 10:13:09 +00:00
table.insert ( args , " -- " ) -- prevent words starting with a "-" to be interpreted as a sdcv option
2021-10-23 10:13:00 +00:00
util.arrayAppend ( args , words )
2018-01-15 22:51:43 +00:00
2019-08-03 09:46:03 +00:00
local cmd = util.shell_escape ( args )
-- cmd = "sleep 7 ; " .. cmd -- uncomment to simulate long lookup time
-- Some sdcv lookups, when using fuzzy search with many dictionaries
-- and a really bad selected text, can take up to 10 seconds.
-- It is nice to be able to cancel it when noticing wrong text was
-- selected.
-- Because sdcv starts outputing its output only at the end when it has
-- done its work, we can use Trapper:dismissablePopen() to cancel it as
-- long as we are waiting for output.
-- When fuzzy search is enabled, we have a lookup_progress_msg that can
-- be used to catch a tap and trigger cancellation.
-- When fuzzy search is disabled, we provide false instead so an
-- invisible non-event-forwarding TrapWidget is used to catch a tap
-- and trigger cancellation (invisible so there's no need for repaint
-- and refresh with the usually fast non-fuzzy search lookups).
-- We must ensure we will have some output to be readable (if no
-- definition found, sdcv will output some message on stderr, and
-- let stdout empty) by appending an "echo":
cmd = cmd .. " ; echo "
2021-10-23 10:13:00 +00:00
local completed , results_str = Trapper : dismissablePopen ( cmd , lookup_progress_msg )
2019-08-03 09:46:03 +00:00
lookup_cancelled = not completed
2017-09-19 19:24:48 +00:00
if results_str and results_str ~= " \n " then -- \n is when lookup was cancelled
2021-10-23 10:13:00 +00:00
-- sdcv can return multiple results if we passed multiple words to
-- the cmdline. In this case, the lookup results for each word are
-- newline separated. The JSON output doesn't contain raw newlines
-- so it's safe to split. Ideally luajson would support jsonl but
-- unfortunately it doesn't and it also seems to decode the last
-- object rather than the first one if there are multiple.
2021-12-07 12:51:27 +00:00
local result_word_idx = 0
2021-10-23 10:13:00 +00:00
for _ , entry_str in ipairs ( util.splitToArray ( results_str , " \n " ) ) do
2021-12-07 12:51:27 +00:00
result_word_idx = result_word_idx + 1
2021-10-23 10:13:00 +00:00
local ok , results = pcall ( JSON.decode , entry_str )
2021-12-07 12:51:27 +00:00
if not ok or not results then
2021-10-23 10:13:00 +00:00
logger.warn ( " JSON data cannot be decoded " , results )
-- Need to insert an empty table so that the word entries
-- match up to the result entries (so that callers can
-- batch lookups to reduce the cost of bulk lookups while
-- still being able to figure out which lookup came from
-- which word).
2021-12-07 12:51:27 +00:00
results = { }
2021-10-23 10:13:00 +00:00
end
2021-12-07 12:51:27 +00:00
if all_results [ result_word_idx ] then
util.arrayAppend ( all_results [ result_word_idx ] , results )
else
table.insert ( all_results , results )
end
end
if result_word_idx ~= # words then
logger.warn ( " sdcv returned a different number of results than the number of words " )
2021-10-23 10:13:00 +00:00
end
end
end
return lookup_cancelled , all_results
end
function ReaderDictionary : startSdcv ( word , dict_names , fuzzy_search )
local words = { word }
2021-10-23 10:13:09 +00:00
2021-10-25 06:45:26 +00:00
if self.ui . languagesupport and self.ui . languagesupport : hasActiveLanguagePlugins ( ) then
2021-10-23 10:13:09 +00:00
-- Get any other candidates from any language-specific plugins we have.
-- We prefer the originally selected word first (in case there is a
-- dictionary entry for whatever text the user selected).
local candidates = self.ui . languagesupport : extraDictionaryFormCandidates ( word )
if candidates then
util.arrayAppend ( words , candidates )
end
end
2021-11-21 18:13:29 +00:00
-- If every word contains a CJK character, every word candidate is
-- (probably) a CJK word. We don't want fuzzy searching in this case
-- because sdcv cannot handle CJK text properly when fuzzy searching (with
-- Japanese, it returns hundreds of useless results).
local shouldnt_fuzzy_search = true
2021-11-21 18:33:09 +00:00
for _ , w in ipairs ( words ) do
if not util.hasCJKChar ( w ) then
2021-11-21 18:13:29 +00:00
shouldnt_fuzzy_search = false
break
end
end
if shouldnt_fuzzy_search then
logger.dbg ( " disabling fuzzy searching for all-CJK word search: " , words )
fuzzy_search = false
end
2021-10-24 08:58:14 +00:00
local lookup_cancelled , results = self : rawSdcv ( words , dict_names , fuzzy_search , self.lookup_progress_msg or false )
2021-10-23 10:13:00 +00:00
if results == nil then -- no dictionaries found
return {
{
dict = " " ,
word = word ,
definition = _ ( [[No dictionaries installed. Please search for "Dictionary support" in the KOReader Wiki to get more information about installing new dictionaries.]] ) ,
}
}
else -- flatten any possible results
local flat_results = { }
local seen_results = { }
-- Flatten the array, removing any duplicates we may have gotten (sdcv
-- may do multiple queries, in fixed mode then in fuzzy mode, and the
2021-10-23 10:13:09 +00:00
-- language-specific plugin may have also returned multiple equivalent
2021-10-23 10:13:00 +00:00
-- results).
local h
for _ , term_results in ipairs ( results ) do
for _ , r in ipairs ( term_results ) do
h = r.dict .. r.word .. r.definition
if seen_results [ h ] == nil then
table.insert ( flat_results , r )
seen_results [ h ] = true
2016-12-06 21:15:52 +00:00
end
end
2014-03-13 13:52:43 +00:00
end
2021-10-23 10:13:00 +00:00
results = flat_results
2014-03-13 13:52:43 +00:00
end
2021-10-23 10:13:00 +00:00
if # results == 0 then -- no results found
2016-12-06 21:15:52 +00:00
-- dummy results
2021-10-23 10:13:00 +00:00
results = {
2016-12-06 21:15:52 +00:00
{
2021-05-31 20:19:24 +00:00
dict = _ ( " Not available " ) ,
2016-12-06 21:15:52 +00:00
word = word ,
2021-04-02 15:59:29 +00:00
definition = lookup_cancelled and _ ( " Dictionary lookup interrupted. " ) or _ ( " No results. " ) ,
2020-12-22 17:45:32 +00:00
no_result = true ,
lookup_cancelled = lookup_cancelled ,
2016-12-06 21:15:52 +00:00
}
}
end
2021-01-07 17:05:18 +00:00
if lookup_cancelled then
-- Also put this as a k/v into the results array: when using dict_ext,
-- we may get results from the 1st lookup, and have interrupted the 2nd.
2021-10-23 10:13:00 +00:00
results.lookup_cancelled = true
2021-01-07 17:05:18 +00:00
end
2021-10-23 10:13:00 +00:00
return results
2018-01-15 22:51:43 +00:00
end
2021-10-23 10:12:56 +00:00
function ReaderDictionary : stardictLookup ( word , dict_names , fuzzy_search , boxes , link )
2018-01-15 22:51:43 +00:00
if word == " " then
return
end
if not self.disable_lookup_history then
local book_title = self.ui . doc_settings and self.ui . doc_settings : readSetting ( " doc_props " ) . title or _ ( " Dictionary lookup " )
2018-01-29 08:46:50 +00:00
if book_title == " " then -- no or empty metadata title
if self.ui . document and self.ui . document.file then
local directory , filename = util.splitFilePathName ( self.ui . document.file ) -- luacheck: no unused
book_title = util.splitFileNameSuffix ( filename )
end
end
2018-01-15 22:51:43 +00:00
lookup_history : addTableItem ( " lookup_history " , {
book_title = book_title ,
time = os.time ( ) ,
word = word ,
} )
end
2019-07-08 12:19:36 +00:00
if Device : canExternalDictLookup ( ) and G_reader_settings : isTrue ( " external_dict_lookup " ) then
Device : doExternalDictLookup ( word , G_reader_settings : readSetting ( " external_dict_lookup_method " ) , function ( )
if self.highlight then
local clear_id = self.highlight : getClearId ( )
UIManager : scheduleIn ( 0.5 , function ( )
self.highlight : clear ( clear_id )
end )
end
end )
return
end
2021-05-31 20:19:24 +00:00
-- If the user disabled all the dictionaries, go away.
if dict_names and # dict_names == 0 then
-- Dummy result
local nope = {
{
dict = _ ( " Not available " ) ,
word = word ,
definition = _ ( " There are no enabled dictionaries. \n Please check the 'Dictionary settings' menu. " ) ,
no_result = true ,
lookup_cancelled = false ,
}
}
2021-10-23 10:12:56 +00:00
self : showDict ( word , nope , boxes , link )
2021-05-31 20:19:24 +00:00
return
end
2021-01-07 17:05:18 +00:00
self : showLookupInfo ( word , self.lookup_msg_delay )
2018-01-15 22:51:43 +00:00
2022-05-05 19:00:22 +00:00
self._lookup_start_time = UIManager : getTime ( )
2018-01-15 22:51:43 +00:00
local results = self : startSdcv ( word , dict_names , fuzzy_search )
2022-05-05 19:00:22 +00:00
if results and results.lookup_cancelled
and ( time.now ( ) - self._lookup_start_time ) <= self.quick_dismiss_before_delay then
2020-12-22 17:45:32 +00:00
-- If interrupted quickly just after launch, don't display anything
-- (this might help avoiding refreshes and the need to dismiss
-- after accidental long-press when holding a device).
if self.highlight then
self.highlight : clear ( )
end
return
end
2021-10-23 10:12:56 +00:00
self : showDict ( word , tidyMarkup ( results ) , boxes , link )
2013-04-23 22:59:52 +00:00
end
2013-04-24 14:57:03 +00:00
2021-10-23 10:12:56 +00:00
function ReaderDictionary : showDict ( word , results , boxes , link )
2014-08-17 16:32:09 +00:00
if results and results [ 1 ] then
2022-01-16 16:48:13 +00:00
logger.dbg ( " showing quick lookup window " , # self.dict_window_list + 1 , " : " , word , results )
2014-11-21 10:32:43 +00:00
self.dict_window = DictQuickLookup : new {
2016-06-28 16:35:00 +00:00
window_list = self.dict_window_list ,
2014-03-13 13:52:43 +00:00
ui = self.ui ,
highlight = self.highlight ,
dialog = self.dialog ,
2014-08-20 10:25:37 +00:00
-- original lookup word
word = word ,
2017-09-09 16:30:00 +00:00
-- selected link, if any
selected_link = link ,
2014-03-13 13:52:43 +00:00
results = results ,
2021-10-23 10:12:56 +00:00
word_boxes = boxes ,
2021-01-06 20:49:23 +00:00
preferred_dictionaries = self.preferred_dictionaries ,
2014-08-20 06:41:45 +00:00
-- differentiate between dict and wiki
2016-12-06 21:15:52 +00:00
is_wiki = self.is_wiki ,
2017-03-24 07:20:37 +00:00
refresh_callback = function ( )
2017-05-12 16:28:42 +00:00
if self.view then
-- update info in footer (time, battery, etc)
2020-07-12 18:47:49 +00:00
self.view . footer : onUpdateFooter ( )
2017-05-12 16:28:42 +00:00
end
2017-03-24 07:20:37 +00:00
end ,
2018-01-15 22:51:43 +00:00
html_dictionary_link_tapped_callback = function ( dictionary , html_link )
self : onHtmlDictionaryLinkTapped ( dictionary , html_link )
end ,
2014-11-21 10:32:43 +00:00
}
2020-12-22 17:45:29 +00:00
table.insert ( self.dict_window_list , self.dict_window )
2020-12-03 16:37:46 +00:00
if self.lookup_progress_msg then
2020-12-22 17:45:29 +00:00
-- If we have a lookup InfoMessage that ended up being displayed, make
-- it *not* refresh on close if it is hidden by our DictQuickLookup
-- to avoid refreshes competition and possible glitches
local msg_dimen = self.lookup_progress_msg : getVisibleArea ( )
if msg_dimen then -- not invisible
local dict_dimen = self.dict_window : getInitialVisibleArea ( )
if dict_dimen and dict_dimen : contains ( msg_dimen ) then
self.lookup_progress_msg . no_refresh_on_close = true
end
2020-12-03 16:37:46 +00:00
end
end
2014-03-13 13:52:43 +00:00
end
2020-12-03 16:37:46 +00:00
self : dismissLookupInfo ( )
if results and results [ 1 ] then
UIManager : show ( self.dict_window )
2022-05-05 19:00:22 +00:00
if not results.lookup_cancelled and self._lookup_start_time
and ( time.now ( ) - self._lookup_start_time ) > self.quick_dismiss_before_delay then
2021-01-07 17:05:18 +00:00
-- If the search took more than a few seconds to be done, discard
2022-05-23 11:52:52 +00:00
-- queued and coming up input events to avoid a voluntary dismissal
2021-01-07 17:05:18 +00:00
-- (because the user felt the result would not come) to kill the
-- result that finally came and is about to be displayed
2022-05-23 11:52:52 +00:00
Input : inhibitInputUntil ( true )
2021-01-01 13:34:55 +00:00
end
2020-12-03 16:37:46 +00:00
end
2013-04-24 14:57:03 +00:00
end
2013-07-21 06:23:54 +00:00
2018-12-13 06:27:49 +00:00
function ReaderDictionary : showDownload ( downloadable_dicts )
local kv_pairs = { }
for dummy , dict in ipairs ( downloadable_dicts ) do
table.insert ( kv_pairs , { dict.name , " " ,
callback = function ( )
Various Wi-Fi QoL improvements (#6424)
* Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback).
* Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental.
* Consistently use "Wi-Fi" everywhere.
* On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery.
(i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again).
* Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic).
* restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it).
* We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed.
* Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state.
* Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing.
* Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421
Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results).
* For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
2020-07-27 01:39:06 +00:00
local connect_callback = function ( )
self : downloadDictionaryPrep ( dict )
2018-12-13 06:27:49 +00:00
end
Various Wi-Fi QoL improvements (#6424)
* Revamped most actions that require an internet connection to a new/fixed backend that allows forwarding the initial action and running it automatically once connected. (i.e., it'll allow you to set "Action when Wi-Fi is off" to "turn_on", and whatch stuff connect and do what you wanted automatically without having to re-click anywhere instead of showing you a Wi-Fi prompt and then not doing anything without any other feedback).
* Speaking of, fixed the "turn_on" beforeWifi action to, well, actually work. It's no longer marked as experimental.
* Consistently use "Wi-Fi" everywhere.
* On Kobo/Cervantes/Sony, implemented a "Kill Wi-Fi connection when inactive" system that will automatically disconnect from Wi-Fi after sustained *network* inactivity (i.e., you can keep reading, it'll eventually turn off on its own). This should be smart and flexible enough not to murder Wi-Fi while you need it, while still not keeping it uselessly on and murdering your battery.
(i.e., enable that + turn Wi-Fi on when off and enjoy never having to bother about Wi-Fi ever again).
* Made sending `NetworkConnected` / `NetworkDisconnected` events consistent (they were only being sent... sometimes, which made relying on 'em somewhat problematic).
* restoreWifiAsync is now only run when really needed (i.e., we no longer stomp on an existing working connection just for the hell of it).
* We no longer attempt to kill a bogus non-existent Wi-Fi connection when going to suspend, we only do it when it's actually needed.
* Every method of enabling Wi-Fi will now properly tear down Wi-Fi on failure, instead of leaving it in an undefined state.
* Fixed an issue in the fancy crash screen on Kobo/reMarkable that could sometime lead to the log excerpt being missing.
* Worked-around a number of sneaky issues related to low-level Wi-Fi/DHCP/DNS handling on Kobo (see the lengthy comments [below](https://github.com/koreader/koreader/pull/6424#issuecomment-663881059) for details). Fix #6421
Incidentally, this should also fix the inconsistencies experienced re: Wi-Fi behavior in Nickel when toggling between KOReader and Nickel (use NM/KFMon, and run a current FW for best results).
* For developers, this involves various cleanups around NetworkMgr and NetworkListener. Documentation is in-line, above the concerned functions.
2020-07-27 01:39:06 +00:00
NetworkMgr : runWhenOnline ( connect_callback )
2018-12-13 06:27:49 +00:00
end } )
local lang
if dict.lang_in == dict.lang_out then
lang = string.format ( " %s " , dict.lang_in )
else
lang = string.format ( " %s– %s " , dict.lang_in , dict.lang_out )
end
table.insert ( kv_pairs , { lang , " " } )
table.insert ( kv_pairs , { " " .. _ ( " License " ) , dict.license } )
2021-02-20 19:15:43 +00:00
table.insert ( kv_pairs , { " " .. _ ( " Entries " ) , dict.entries , separator = true } )
2018-12-13 06:27:49 +00:00
end
self.download_window = KeyValuePage : new {
2020-03-15 10:40:24 +00:00
title = _ ( " Tap dictionary name to download " ) ,
2018-12-13 06:27:49 +00:00
kv_pairs = kv_pairs ,
}
UIManager : show ( self.download_window )
end
function ReaderDictionary : downloadDictionaryPrep ( dict , size )
local dummy , filename = util.splitFilePathName ( dict.url )
local download_location = string.format ( " %s/%s " , self.data_dir , filename )
local lfs = require ( " libs/libkoreader-lfs " )
if lfs.attributes ( download_location ) then
UIManager : show ( ConfirmBox : new {
text = _ ( " File already exists. Overwrite? " ) ,
ok_text = _ ( " Overwrite " ) ,
ok_callback = function ( )
self : downloadDictionary ( dict , download_location )
end ,
} )
else
self : downloadDictionary ( dict , download_location )
end
end
function ReaderDictionary : downloadDictionary ( dict , download_location , continue )
continue = continue or false
local socket = require ( " socket " )
2021-03-15 00:25:10 +00:00
local socketutil = require ( " socketutil " )
2018-12-13 06:27:49 +00:00
local http = socket.http
local ltn12 = require ( " ltn12 " )
if not continue then
local file_size
2021-03-15 00:25:10 +00:00
-- Skip body & code args
socketutil : set_timeout ( )
local headers = socket.skip ( 2 , http.request {
method = " HEAD " ,
url = dict.url ,
2018-12-13 06:27:49 +00:00
--redirect = true,
} )
2021-03-15 00:25:10 +00:00
socketutil : reset_timeout ( )
2018-12-13 06:27:49 +00:00
--logger.dbg(headers)
file_size = headers and headers [ " content-length " ]
UIManager : show ( ConfirmBox : new {
text = T ( _ ( " Dictionary filesize is %1 (%2 bytes). Continue with download? " ) , util.getFriendlySize ( file_size ) , util.getFormattedSize ( file_size ) ) ,
ok_text = _ ( " Download " ) ,
ok_callback = function ( )
-- call ourselves with continue = true
self : downloadDictionary ( dict , download_location , true )
end ,
} )
return
else
UIManager : nextTick ( function ( )
UIManager : show ( InfoMessage : new {
text = _ ( " Downloading… " ) ,
timeout = 3 ,
} )
end )
end
2021-03-15 00:25:10 +00:00
socketutil : set_timeout ( socketutil.FILE_BLOCK_TIMEOUT , socketutil.FILE_TOTAL_TIMEOUT )
local c = socket.skip ( 1 , http.request {
url = dict.url ,
sink = ltn12.sink . file ( io.open ( download_location , " w " ) ) ,
} )
socketutil : reset_timeout ( )
2018-12-13 06:27:49 +00:00
if c == 200 then
logger.dbg ( " file downloaded to " , download_location )
else
UIManager : show ( InfoMessage : new {
2020-01-04 00:18:51 +00:00
text = _ ( " Could not save file to: \n " ) .. BD.filepath ( download_location ) ,
2018-12-13 06:27:49 +00:00
--timeout = 3,
} )
return false
end
2020-12-10 15:59:14 +00:00
local ok , error = Device : unpackArchive ( download_location , self.data_dir )
2018-12-13 06:27:49 +00:00
if ok then
available_ifos = false
self : init ( )
UIManager : show ( InfoMessage : new {
text = _ ( " Dictionary downloaded: \n " ) .. dict.name ,
} )
return true
else
UIManager : show ( InfoMessage : new {
text = _ ( " Dictionary failed to download: \n " ) .. string.format ( " %s \n %s " , dict.name , error ) ,
} )
return false
end
end
2013-07-21 06:23:54 +00:00
function ReaderDictionary : onReadSettings ( config )
2021-01-06 20:49:23 +00:00
self.preferred_dictionaries = config : readSetting ( " preferred_dictionaries " ) or { }
if # self.preferred_dictionaries == 0 then
-- Legacy setting, when only one dict could be set as default/first to show
local default_dictionary = config : readSetting ( " default_dictionary " )
if default_dictionary then
table.insert ( self.preferred_dictionaries , default_dictionary )
config : delSetting ( " default_dictionary " )
end
end
if # self.preferred_dictionaries > 0 then
self : updateSdcvDictNamesOptions ( )
end
2021-03-06 21:44:18 +00:00
if config : has ( " disable_fuzzy_search " ) then
self.disable_fuzzy_search = config : isTrue ( " disable_fuzzy_search " )
else
2017-06-18 16:08:57 +00:00
self.disable_fuzzy_search = G_reader_settings : isTrue ( " disable_fuzzy_search " )
end
2013-07-21 06:23:54 +00:00
end
2013-12-27 15:18:16 +00:00
function ReaderDictionary : onSaveSettings ( )
2020-02-17 15:53:09 +00:00
if self.ui . doc_settings then
2021-01-06 20:49:23 +00:00
self.ui . doc_settings : saveSetting ( " preferred_dictionaries " , self.preferred_dictionaries )
2020-02-17 15:53:09 +00:00
self.ui . doc_settings : saveSetting ( " disable_fuzzy_search " , self.disable_fuzzy_search )
end
2017-06-18 16:08:57 +00:00
end
2021-01-06 20:49:23 +00:00
function ReaderDictionary : onTogglePreferredDict ( dict )
if not self.preferred_dictionaries then
-- Invoked from FileManager: no preferred dict to manage
return true
end
local removed = false
for idx , name in ipairs ( self.preferred_dictionaries ) do
if dict == name then
removed = true
table.remove ( self.preferred_dictionaries , idx )
break
end
end
if not removed then -- insert it as first
table.insert ( self.preferred_dictionaries , 1 , dict )
end
UIManager : show ( InfoMessage : new {
text = removed and T ( _ ( " %1 is no longer a preferred dictionary for this document. " ) , dict )
or T ( _ ( " %1 is now the preferred dictionary for this document. " ) , dict ) ,
timeout = 2 ,
} )
self : updateSdcvDictNamesOptions ( )
return true
end
2019-10-25 15:25:26 +00:00
function ReaderDictionary : toggleFuzzyDefault ( )
local disable_fuzzy_search = G_reader_settings : isTrue ( " disable_fuzzy_search " )
UIManager : show ( MultiConfirmBox : new {
2017-06-18 16:08:57 +00:00
text = T (
disable_fuzzy_search
2019-10-25 15:25:26 +00:00
and _ ( [ [
Would you like to enable or disable fuzzy search by default ?
Fuzzy search can match epuisante , é puisante and é puisantes to é puisant , even if only the latter has an entry in the dictionary . It can be disabled to improve performance , but it might be worthwhile to look into disabling unneeded dictionaries before disabling fuzzy search .
The current default ( ★ ) is disabled . ] ] )
or _ ( [ [
Would you like to enable or disable fuzzy search by default ?
Fuzzy search can match epuisante , é puisante and é puisantes to é puisant , even if only the latter has an entry in the dictionary . It can be disabled to improve performance , but it might be worthwhile to look into disabling unneeded dictionaries before disabling fuzzy search .
The current default ( ★ ) is enabled . ] ] )
2017-10-07 20:13:46 +00:00
) ,
2019-10-25 15:25:26 +00:00
choice1_text_func = function ( )
return disable_fuzzy_search and _ ( " Disable (★) " ) or _ ( " Disable " )
end ,
choice1_callback = function ( )
2021-03-06 21:44:18 +00:00
G_reader_settings : makeTrue ( " disable_fuzzy_search " )
2019-10-25 15:25:26 +00:00
end ,
choice2_text_func = function ( )
return disable_fuzzy_search and _ ( " Enable " ) or _ ( " Enable (★) " )
end ,
choice2_callback = function ( )
2021-03-06 21:44:18 +00:00
G_reader_settings : makeFalse ( " disable_fuzzy_search " )
2017-06-18 16:08:57 +00:00
end ,
} )
2013-07-21 06:23:54 +00:00
end
2013-10-18 20:38:07 +00:00
return ReaderDictionary