2
0
mirror of https://github.com/koreader/koreader synced 2024-11-16 06:12:56 +00:00
koreader/plugins/kosync.koplugin/main.lua

821 lines
29 KiB
Lua
Raw Normal View History

local ConfirmBox = require("ui/widget/confirmbox")
2020-06-13 11:13:23 +00:00
local Device = require("device")
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 Dispatcher = require("dispatcher")
local Event = require("ui/event")
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 InfoMessage = require("ui/widget/infomessage")
2015-03-10 07:09:42 +00:00
local Math = require("optmath")
local MultiInputDialog = require("ui/widget/multiinputdialog")
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 NetworkMgr = require("ui/network/manager")
local UIManager = require("ui/uimanager")
local WidgetContainer = require("ui/widget/container/widgetcontainer")
2020-06-13 11:47:07 +00:00
local logger = require("logger")
local md5 = require("ffi/sha2").md5
local random = require("random")
local util = require("util")
2020-06-13 11:47:07 +00:00
local T = require("ffi/util").template
local _ = require("gettext")
if G_reader_settings:hasNot("device_id") then
G_reader_settings:saveSetting("device_id", random.uuid())
end
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 KOSync = WidgetContainer:extend{
name = "kosync",
is_doc_only = true,
2016-03-28 01:50:23 +00:00
title = _("Register/login to KOReader server"),
page_update_times = 0,
last_page = -1,
last_page_turn_ticks = 0,
}
local SYNC_STRATEGY = {
-- Forward and backward whisper sync settings are using different
-- default value, so none of following opinions should be zero.
PROMPT = 1,
WHISPER = 2,
DISABLE = 3,
DEFAULT_FORWARD = 1,
DEFAULT_BACKWARD = 3,
}
local CHECKSUM_METHOD = {
BINARY = 0,
FILENAME = 1
}
2020-04-25 07:04:44 +00:00
local function getNameStrategy(type)
if type == 1 then
return _("Prompt")
elseif type == 2 then
return _("Auto")
else
return _("Disable")
end
end
local function showSyncedMessage()
UIManager:show(InfoMessage:new{
text = _("Progress has been synchronized."),
timeout = 3,
})
end
local function promptLogin()
UIManager:show(InfoMessage:new{
text = _("Please register or login before using the progress synchronization feature."),
timeout = 3,
})
end
local function showSyncError()
UIManager:show(InfoMessage:new{
text = _("Something went wrong when syncing progress, please check your network connection and try again later."),
timeout = 3,
})
end
local function validate(entry)
if not entry then return false end
if type(entry) == "string" then
if entry == "" or not entry:match("%S") then return false end
end
return true
end
local function validateUser(user, pass)
local error_message = nil
local user_ok = validate(user)
local pass_ok = validate(pass)
if not user_ok and not pass_ok then
error_message = _("invalid username and password")
elseif not user_ok then
error_message = _("invalid username")
elseif not pass_ok then
error_message = _("invalid password")
end
if not error_message then
return user_ok and pass_ok
else
return user_ok and pass_ok, error_message
end
end
function KOSync:onDispatcherRegisterActions()
Dispatcher:registerAction("kosync_push_progress", { category="none", event="KOSyncPushProgress", title=_("Push progress from this device"), reader=true,})
Dispatcher:registerAction("kosync_pull_progress", { category="none", event="KOSyncPullProgress", title=_("Pull progress from other devices"), reader=true, separator=true,})
end
function KOSync:onReaderReady()
--- @todo: Viable candidate for a port to the new readSetting API
local settings = G_reader_settings:readSetting("kosync") or {}
self.kosync_custom_server = settings.custom_server
2015-03-10 07:49:33 +00:00
self.kosync_username = settings.username
self.kosync_userkey = settings.userkey
self.kosync_auto_sync = settings.auto_sync ~= false
self.kosync_pages_before_update = settings.pages_before_update
self.kosync_whisper_forward = settings.whisper_forward or SYNC_STRATEGY.DEFAULT_FORWARD
self.kosync_whisper_backward = settings.whisper_backward or SYNC_STRATEGY.DEFAULT_BACKWARD
self.kosync_checksum_method = settings.checksum_method or CHECKSUM_METHOD.BINARY
2016-07-30 00:38:02 +00:00
self.kosync_device_id = G_reader_settings:readSetting("device_id")
--assert(self.kosync_device_id)
if self.kosync_auto_sync then
self:_onResume()
end
self:registerEvents()
self:onDispatcherRegisterActions()
self.ui.menu:registerToMainMenu(self)
2016-07-30 00:38:02 +00:00
-- Make sure checksum has been calculated at the very first time a document has been opened, to
-- avoid document saving feature to impact the checksum, and eventually impact the document
-- identity in the progress sync feature.
self.view.document:fastDigest(self.ui.doc_settings)
end
function KOSync:addToMainMenu(menu_items)
menu_items.progress_sync = {
2015-03-10 07:12:44 +00:00
text = _("Progress sync"),
sub_item_table = {
{
text_func = function()
return self.kosync_userkey and (_("Logout"))
or _("Register") .. " / " .. _("Login")
end,
keep_menu_open = true,
callback_func = function()
if self.kosync_userkey then
return function(menu)
self._menu_to_update = menu
self:logout()
end
else
return function(menu)
self._menu_to_update = menu
self:login()
end
end
end,
},
2015-03-10 07:09:42 +00:00
{
2016-07-30 00:38:02 +00:00
text = _("Auto sync now and future"),
2015-03-10 07:09:42 +00:00
checked_func = function() return self.kosync_auto_sync end,
callback = function()
self.kosync_auto_sync = not self.kosync_auto_sync
self:registerEvents()
2016-07-30 00:38:02 +00:00
if self.kosync_auto_sync then
-- since we will update the progress when closing document, we should pull
-- current progress now to avoid to overwrite it silently.
self:getProgress(true)
else
-- since we won't update the progress when closing document, we should push
-- current progress now to avoid to lose it silently.
self:updateProgress(true)
end
self:saveSettings()
2015-03-10 07:09:42 +00:00
end,
},
{
text = _("Whisper sync"),
enabled_func = function() return self.kosync_auto_sync end,
sub_item_table = {
{
2020-04-25 07:04:44 +00:00
text_func = function()
return T(_("Sync to latest record (%1)"), getNameStrategy(self.kosync_whisper_forward))
end,
2020-04-25 07:04:44 +00:00
sub_item_table = {
{
text = _("Auto"),
checked_func = function()
return self.kosync_whisper_forward == SYNC_STRATEGY.WHISPER
end,
callback = function()
self:setWhisperForward(SYNC_STRATEGY.WHISPER)
end,
},
{
text = _("Prompt"),
checked_func = function()
return self.kosync_whisper_forward == SYNC_STRATEGY.PROMPT
end,
callback = function()
self:setWhisperForward(SYNC_STRATEGY.PROMPT)
end,
},
{
text = _("Disable"),
checked_func = function()
return self.kosync_whisper_forward == SYNC_STRATEGY.DISABLE
end,
callback = function()
self:setWhisperForward(SYNC_STRATEGY.DISABLE)
end,
},
}
},
{
2020-04-25 07:04:44 +00:00
text_func = function()
return T(_("Sync to a previous record (%1)"), getNameStrategy(self.kosync_whisper_backward))
end,
2020-04-25 07:04:44 +00:00
sub_item_table = {
{
text = _("Auto"),
checked_func = function()
return self.kosync_whisper_backward == SYNC_STRATEGY.WHISPER
end,
callback = function()
self:setWhisperBackward(SYNC_STRATEGY.WHISPER)
end,
},
{
text = _("Prompt"),
checked_func = function()
return self.kosync_whisper_backward == SYNC_STRATEGY.PROMPT
end,
callback = function()
self:setWhisperBackward(SYNC_STRATEGY.PROMPT)
end,
},
{
text = _("Disable"),
checked_func = function()
return self.kosync_whisper_backward == SYNC_STRATEGY.DISABLE
end,
callback = function()
self:setWhisperBackward(SYNC_STRATEGY.DISABLE)
end,
},
}
},
},
},
2015-03-10 07:09:42 +00:00
{
2016-07-30 00:38:02 +00:00
text = _("Push progress from this device"),
enabled_func = function()
return self.kosync_userkey ~= nil
end,
callback = function()
self:updateProgress(true)
end,
},
{
text = _("Pull progress from other devices"),
2015-03-10 07:09:42 +00:00
enabled_func = function()
return self.kosync_userkey ~= nil
end,
callback = function()
self:getProgress(true)
end,
},
{
text = _("Custom sync server"),
keep_menu_open = true,
tap_input_func = function()
return {
-- @translators Server address defined by user for progress sync.
title = _("Custom progress sync server address"),
input = self.kosync_custom_server or "https://",
type = "text",
callback = function(input)
self:setCustomServer(input)
end,
}
end,
},
{
text = _("Sync every # pages"),
keep_menu_open = true,
callback = function()
local SpinWidget = require("ui/widget/spinwidget")
local items = SpinWidget:new{
text = _([[This value determines how many page turns it takes to update book progress.
If set to 0, updating progress based on page turns will be disabled.]]),
value = self.kosync_pages_before_update or 0,
value_min = 0,
value_max = 999,
value_step = 1,
value_hold_step = 10,
ok_text = _("Set"),
title_text = _("Number of pages before update"),
default_value = 0,
callback = function(spin)
self:setPagesBeforeUpdate(spin.value)
end
}
UIManager:show(items)
end,
},
{
text = _("Document matching method"),
sub_item_table = {
{
text = _("Binary. Only identical files will sync progress."),
checked_func = function()
return self.kosync_checksum_method == CHECKSUM_METHOD.BINARY
end,
callback = function()
self:setChecksumMethod(CHECKSUM_METHOD.BINARY)
end,
},
{
text = _("Filename. Files with the same name will sync progress."),
checked_func = function()
return self.kosync_checksum_method == CHECKSUM_METHOD.FILENAME
end,
callback = function()
self:setChecksumMethod(CHECKSUM_METHOD.FILENAME)
end,
},
}
},
}
}
end
function KOSync:setPagesBeforeUpdate(pages_before_update)
self.kosync_pages_before_update = pages_before_update > 0 and pages_before_update or nil
self:saveSettings()
end
function KOSync:setCustomServer(server)
2020-06-13 11:47:07 +00:00
logger.dbg("set custom server", server)
self.kosync_custom_server = server ~= "" and server or nil
self:saveSettings()
end
function KOSync:setWhisperForward(strategy)
self.kosync_whisper_forward = strategy
self:saveSettings()
end
function KOSync:setWhisperBackward(strategy)
self.kosync_whisper_backward = strategy
self:saveSettings()
end
function KOSync:setChecksumMethod(method)
self.kosync_checksum_method = method
self:saveSettings()
end
function KOSync:login()
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
if NetworkMgr:willRerunWhenOnline(function() self:login() end) then
return
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
local dialog
dialog = MultiInputDialog:new{
2015-03-10 07:49:33 +00:00
title = self.title,
fields = {
{
text = self.kosync_username,
hint = "username",
},
{
hint = "password",
text_type = "password",
},
},
buttons = {
{
{
text = _("Cancel"),
id = "close",
callback = function()
UIManager:close(dialog)
end,
},
{
text = _("Login"),
callback = function()
local username, password = unpack(dialog:getFields())
local ok, err = validateUser(username, password)
if not ok then
UIManager:show(InfoMessage:new{
text = T(_("Cannot login: %1"), err),
timeout = 2,
})
else
UIManager:close(dialog)
UIManager:scheduleIn(0.5, function()
self:doLogin(username, password)
end)
UIManager:show(InfoMessage:new{
text = _("Logging in. Please wait…"),
timeout = 1,
})
end
end,
},
{
text = _("Register"),
callback = function()
local username, password = unpack(dialog:getFields())
local ok, err = validateUser(username, password)
if not ok then
UIManager:show(InfoMessage:new{
text = T(_("Cannot register: %1"), err),
timeout = 2,
})
else
UIManager:close(dialog)
UIManager:scheduleIn(0.5, function()
self:doRegister(username, password)
end)
UIManager:show(InfoMessage:new{
text = _("Registering. Please wait…"),
timeout = 1,
})
end
end,
},
},
},
}
UIManager:show(dialog)
dialog:onShowKeyboard()
end
function KOSync:doRegister(username, password)
local KOSyncClient = require("KOSyncClient")
local client = KOSyncClient:new{
custom_url = self.kosync_custom_server,
service_spec = self.path .. "/api.json"
}
2020-06-13 11:47:07 +00:00
-- on Android to avoid ANR (no-op on other platforms)
Device:setIgnoreInput(true)
local userkey = md5(password)
local ok, status, body = pcall(client.register, client, username, userkey)
if not ok then
if status then
UIManager:show(InfoMessage:new{
text = _("An error occurred while registering:") ..
"\n" .. status,
})
else
UIManager:show(InfoMessage:new{
text = _("An unknown error occurred while registering."),
})
end
elseif status then
self.kosync_username = username
self.kosync_userkey = userkey
self._menu_to_update:updateItems()
UIManager:show(InfoMessage:new{
text = _("Registered to KOReader server."),
})
else
UIManager:show(InfoMessage:new{
text = body and body.message or _("Unknown server error"),
})
end
Device:setIgnoreInput(false)
self:saveSettings()
end
function KOSync:doLogin(username, password)
local KOSyncClient = require("KOSyncClient")
local client = KOSyncClient:new{
custom_url = self.kosync_custom_server,
service_spec = self.path .. "/api.json"
}
Device:setIgnoreInput(true)
local userkey = md5(password)
local ok, status, body = pcall(client.authorize, client, username, userkey)
if not ok then
if status then
UIManager:show(InfoMessage:new{
text = _("An error occurred while logging in:") ..
"\n" .. status,
})
else
UIManager:show(InfoMessage:new{
text = _("An unknown error occurred while logging in."),
})
end
Device:setIgnoreInput(false)
return
elseif status then
self.kosync_username = username
self.kosync_userkey = userkey
self._menu_to_update:updateItems()
UIManager:show(InfoMessage:new{
text = _("Logged in to KOReader server."),
})
else
UIManager:show(InfoMessage:new{
text = body and body.message or _("Unknown server error"),
})
end
Device:setIgnoreInput(false)
self:saveSettings()
end
function KOSync:logout()
self.kosync_userkey = nil
2015-03-10 07:09:42 +00:00
self.kosync_auto_sync = true
self._menu_to_update:updateItems()
self:saveSettings()
end
function KOSync:getLastPercent()
if self.ui.document.info.has_pages then
return Math.roundPercent(self.ui.paging:getLastPercent())
else
return Math.roundPercent(self.ui.rolling:getLastPercent())
end
end
function KOSync:getLastProgress()
if self.ui.document.info.has_pages then
return self.ui.paging:getLastProgress()
else
return self.ui.rolling:getLastProgress()
end
end
function KOSync:getDocumentDigest()
if self.kosync_checksum_method == CHECKSUM_METHOD.FILENAME then
return self:getFileNameDigest()
else
return self:getFileDigest()
end
end
function KOSync:getFileDigest()
return self.view.document:fastDigest()
end
function KOSync:getFileNameDigest()
local file = self.ui.document.file
if not file then return end
local file_path, file_name = util.splitFilePathName(file) -- luacheck: no unused
if not file_name then return end
return md5(file_name)
end
function KOSync:syncToProgress(progress)
2020-06-13 11:47:07 +00:00
logger.dbg("sync to", progress)
if self.ui.document.info.has_pages then
self.ui:handleEvent(Event:new("GotoPage", tonumber(progress)))
else
self.ui:handleEvent(Event:new("GotoXPointer", progress))
end
end
2016-07-30 00:38:02 +00:00
function KOSync:updateProgress(manual)
if not self.kosync_username or not self.kosync_userkey then
if manual then
promptLogin()
end
return
end
if manual and NetworkMgr:willRerunWhenOnline(function() self:updateProgress(manual) end) then
return
end
local KOSyncClient = require("KOSyncClient")
local client = KOSyncClient:new{
custom_url = self.kosync_custom_server,
service_spec = self.path .. "/api.json"
}
local doc_digest = self:getDocumentDigest()
local progress = self:getLastProgress()
local percentage = self:getLastPercent()
local ok, err = pcall(client.update_progress,
client,
self.kosync_username,
self.kosync_userkey,
doc_digest,
progress,
percentage,
2020-06-13 11:13:23 +00:00
Device.model,
self.kosync_device_id,
function(ok, body)
2020-06-13 11:47:07 +00:00
logger.dbg("update progress for", self.view.document.file, ok)
if manual then
if ok then
UIManager:show(InfoMessage:new{
text = _("Progress has been pushed."),
timeout = 3,
})
else
showSyncError()
end
end
end)
if not ok then
if manual then showSyncError() end
2020-06-13 11:47:07 +00:00
if err then logger.dbg("err:", err) end
end
end
2015-03-10 07:09:42 +00:00
function KOSync:getProgress(manual)
if not self.kosync_username or not self.kosync_userkey then
if manual then
promptLogin()
end
return
end
if manual and NetworkMgr:willRerunWhenOnline(function() self:getProgress(manual) end) then
return
end
local KOSyncClient = require("KOSyncClient")
local client = KOSyncClient:new{
custom_url = self.kosync_custom_server,
service_spec = self.path .. "/api.json"
}
local doc_digest = self:getDocumentDigest()
local ok, err = pcall(client.get_progress,
client,
self.kosync_username,
self.kosync_userkey,
doc_digest,
function(ok, body)
2020-06-13 11:47:07 +00:00
logger.dbg("get progress for", self.view.document.file, ok, body)
if not ok or not body then
if manual then
2016-07-30 00:38:02 +00:00
showSyncError()
end
return
end
if not body.percentage then
if manual then
UIManager:show(InfoMessage:new{
text = _("No progress found for this document."),
timeout = 3,
})
end
return
end
2020-06-13 11:13:23 +00:00
if body.device == Device.model
and body.device_id == self.kosync_device_id then
if manual then
UIManager:show(InfoMessage:new{
text = _("Latest progress is coming from this device."),
timeout = 3,
})
end
return
end
body.percentage = Math.roundPercent(body.percentage)
local progress = self:getLastProgress()
local percentage = self:getLastPercent()
2020-06-13 11:47:07 +00:00
logger.dbg("current progress", percentage)
if percentage == body.percentage
or body.progress == progress then
if manual then
UIManager:show(InfoMessage:new{
text = _("The progress has already been synchronized."),
timeout = 3,
})
end
return
end
-- The progress needs to be updated.
if manual then
-- If user actively pulls progress from other devices, we always update the
-- progress without further confirmation.
self:syncToProgress(body.progress)
showSyncedMessage()
return
end
local self_older
if body.timestamp ~= nil then
self_older = (body.timestamp > self.last_page_turn_ticks)
else
-- If we are working with old sync server, we can only use
-- percentage field.
self_older = (body.percentage > percentage)
end
if self_older then
if self.kosync_whisper_forward == SYNC_STRATEGY.WHISPER then
self:syncToProgress(body.progress)
showSyncedMessage()
elseif self.kosync_whisper_forward == SYNC_STRATEGY.PROMPT then
UIManager:show(ConfirmBox:new{
text = T(_("Sync to latest location %1% from device '%2'?"),
Math.round(body.percentage * 100),
body.device),
ok_callback = function()
self:syncToProgress(body.progress)
end,
})
end
else -- if not self_older then
if self.kosync_whisper_backward == SYNC_STRATEGY.WHISPER then
self:syncToProgress(body.progress)
showSyncedMessage()
elseif self.kosync_whisper_backward == SYNC_STRATEGY.PROMPT then
UIManager:show(ConfirmBox:new{
text = T(_("Sync to previous location %1% from device '%2'?"),
Math.round(body.percentage * 100),
body.device),
ok_callback = function()
self:syncToProgress(body.progress)
end,
})
end
end
end)
if not ok then
if manual then showSyncError() end
2020-06-13 11:47:07 +00:00
if err then logger.dbg("err:", err) end
end
end
function KOSync:saveSettings()
local settings = {
custom_server = self.kosync_custom_server,
username = self.kosync_username,
userkey = self.kosync_userkey,
2015-03-10 07:09:42 +00:00
auto_sync = self.kosync_auto_sync,
pages_before_update = self.kosync_pages_before_update,
whisper_forward =
(self.kosync_whisper_forward ~= SYNC_STRATEGY.DEFAULT_FORWARD
and self.kosync_whisper_forward
or nil),
whisper_backward =
(self.kosync_whisper_backward ~= SYNC_STRATEGY.DEFAULT_BACKWARD
and self.kosync_whisper_backward
or nil),
checksum_method = self.kosync_checksum_method,
}
G_reader_settings:saveSetting("kosync", settings)
end
function KOSync:onCloseDocument()
2020-06-13 11:47:07 +00:00
logger.dbg("on close document")
2015-03-10 07:09:42 +00:00
if self.kosync_auto_sync then
self:updateProgress()
end
end
function KOSync:_onPageUpdate(page)
if page == nil then
return
end
if self.last_page == -1 then
self.last_page = page
elseif self.last_page ~= page then
self.last_page = page
self.last_page_turn_ticks = os.time()
self.page_update_times = self.page_update_times + 1
if self.kosync_pages_before_update and self.page_update_times == self.kosync_pages_before_update then
self.page_update_times = 0
UIManager:scheduleIn(1, function() self:updateProgress() end)
end
end
end
function KOSync:_onResume()
UIManager:scheduleIn(1, function() self:getProgress() end)
end
function KOSync:_onFlushSettings()
if self.ui == nil or self.ui.document == nil then return end
self:updateProgress()
end
function KOSync:_onNetworkConnected()
self:_onResume()
end
function KOSync:onKOSyncPushProgress()
if not self.kosync_userkey then return end
self:updateProgress(true)
end
function KOSync:onKOSyncPullProgress()
if not self.kosync_userkey then return end
self:getProgress(true)
end
function KOSync:registerEvents()
if self.kosync_auto_sync then
self.onPageUpdate = self._onPageUpdate
self.onResume = self._onResume
self.onFlushSettings = self._onFlushSettings
self.onNetworkConnected = self._onNetworkConnected
else
self.onPageUpdate = nil
self.onResume = nil
self.onFlushSettings = nil
self.onNetworkConnected = nil
end
end
return KOSync