@ -11,6 +11,7 @@ local WidgetContainer = require("ui/widget/container/widgetcontainer")
local logger = require ( " logger " )
local md5 = require ( " ffi/sha2 " ) . md5
local random = require ( " random " )
local time = require ( " ui/time " )
local util = require ( " util " )
local T = require ( " ffi/util " ) . template
local _ = require ( " gettext " )
@ -24,20 +25,21 @@ local KOSync = WidgetContainer:extend{
is_doc_only = true ,
title = _ ( " Register/login to KOReader server " ) ,
page_update_times = 0 ,
last_page = - 1 ,
last_page_turn_ticks = 0 ,
push_timestamp = nil ,
pull_timestamp = nil ,
page_update_counter = nil ,
last_page = nil ,
last_page_turn_timestamp = nil ,
periodic_push_task = nil ,
periodic_push_scheduled = nil ,
settings = nil ,
}
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 ,
SILENT = 2 ,
DISABLE = 3 ,
DEFAULT_FORWARD = 1 ,
DEFAULT_BACKWARD = 3 ,
}
local CHECKSUM_METHOD = {
@ -45,6 +47,64 @@ local CHECKSUM_METHOD = {
FILENAME = 1
}
-- Debounce push/pull attempts
local API_CALL_DEBOUNCE_DELAY = time.s ( 25 )
-- NOTE: This is used in a migration script by ui/data/onetime_migration,
-- which is why it's public.
KOSync.default_settings = {
custom_server = nil ,
username = nil ,
userkey = nil ,
-- Do *not* default to auto-sync on devices w/ NetworkManager support, as wifi is unlikely to be on at all times there, and the nagging enabling this may cause requires careful consideration.
auto_sync = not Device : hasWifiManager ( ) ,
pages_before_update = nil ,
sync_forward = SYNC_STRATEGY.PROMPT ,
sync_backward = SYNC_STRATEGY.DISABLE ,
checksum_method = CHECKSUM_METHOD.BINARY ,
}
function KOSync : init ( )
self.push_timestamp = 0
self.pull_timestamp = 0
self.page_update_counter = 0
self.last_page = - 1
self.last_page_turn_timestamp = 0
self.periodic_push_scheduled = false
-- Like AutoSuspend, we need an instance-specific task for scheduling/resource management reasons.
self.periodic_push_task = function ( )
self.periodic_push_scheduled = false
self.page_update_counter = 0
-- We do *NOT* want to make sure networking is up here, as the nagging would be extremely annoying; we're leaving that to the network activity check...
self : updateProgress ( false , false )
end
self.settings = G_reader_settings : readSetting ( " kosync " , self.default_settings )
self.device_id = G_reader_settings : readSetting ( " device_id " )
-- Disable auto-sync if beforeWifiAction was reset to "prompt" behind our back...
if self.settings . auto_sync and Device : hasWifiManager ( ) and G_reader_settings : readSetting ( " wifi_enable_action " ) ~= " turn_on " then
self.settings . auto_sync = false
logger.warn ( " KOSync: Automatic sync has been disabled because wifi_enable_action is *not* turn_on " )
end
self.ui . menu : registerToMainMenu ( self )
end
function KOSync : getSyncPeriod ( )
if not self.settings . auto_sync then
return _ ( " Unavailable " )
end
local period = self.settings . pages_before_update
if period and period > 0 then
return period
else
return _ ( " Never " )
end
end
local function getNameStrategy ( type )
if type == 1 then
return _ ( " Prompt " )
@ -109,218 +169,224 @@ function KOSync:onDispatcherRegisterActions()
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
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
self.kosync_device_id = G_reader_settings : readSetting ( " device_id " )
--assert(self.kosync_device_id)
if self.kosync_auto_sync then
self : _onResume ( )
-- Make sure checksum has been calculated before we ever query it,
-- to prevent document saving features from affecting the checksum,
-- and eventually affecting the document identity for the progress sync feature.
self.view . document : fastDigest ( self.ui . doc_settings )
if self.settings . auto_sync then
UIManager : nextTick ( function ( )
self : getProgress ( true , false )
end )
end
self : registerEvents ( )
self : onDispatcherRegisterActions ( )
self.ui . menu : registerToMainMenu ( self )
-- 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 )
self.last_page = self.ui : getCurrentPage ( )
end
function KOSync : addToMainMenu ( menu_items )
menu_items.progress_sync = {
text = _ ( " Progress sync " ) ,
sub_item_table = {
{
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.settings . custom_server or " https:// " ,
type = " text " ,
callback = function ( input )
self : setCustomServer ( input )
end ,
}
end ,
} ,
{
text_func = function ( )
return self.kosync_userkey and ( _ ( " Logout " ) )
return self. settings. userkey and ( _ ( " Logout " ) )
or _ ( " Register " ) .. " / " .. _ ( " Login " )
end ,
keep_menu_open = true ,
callback_func = function ( )
if self.kosync_userkey then
if self. settings. userkey then
return function ( menu )
self._menu_to_update = menu
self : logout ( )
self : logout ( menu )
end
else
return function ( menu )
self._menu_to_update = menu
self : login ( )
self : login ( menu )
end
end
end ,
separator = true ,
} ,
{
text = _ ( " Auto sync now and future " ) ,
checked_func = function ( ) return self.kosync_auto_sync end ,
text = _ ( " Automatically keep documents in sync " ) ,
checked_func = function ( ) return self.settings . auto_sync end ,
help_text = _ ( [[This may lead to nagging about toggling WiFi on document close and suspend/resume, depending on the device's connectivity.]] ) ,
callback = function ( )
self.kosync_auto_sync = not self.kosync_auto_sync
-- Actively recommend switching the before wifi action to "turn_on" instead of prompt, as prompt will just not be practical (or even plain usable) here.
if Device : hasWifiManager ( ) and G_reader_settings : readSetting ( " wifi_enable_action " ) ~= " turn_on " then
UIManager : show ( InfoMessage : new { text = _ ( " You will have to switch the 'Action when Wi-Fi is off' Network setting to 'turn on' to be able to enable this feature! " ) } )
return
end
self.settings . auto_sync = not self.settings . auto_sync
self : registerEvents ( )
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 )
if self. settings. auto_sync then
-- Since we will update the progress when closing the document,
-- pull the current progress now so as not to silently overwrite it .
self : getProgress ( true , 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 )
-- Since we won't update the progress when closing the document,
-- push the current progress now so as not to lose it.
self : updateProgress ( true , true )
end
end ,
} ,
{
text_func = function ( )
return T ( _ ( " Periodically sync every # pages (%1) " ) , self : getSyncPeriod ( ) )
end ,
enabled_func = function ( ) return self.settings . auto_sync end ,
-- This is the condition that allows enabling auto_disable_wifi in NetworkManager ;).
help_text = NetworkMgr : getNetworkInterfaceName ( ) and _ ( [[Unlike the automatic sync above, this will *not* attempt to setup a network connection, but instead relies on it being already up, and may trigger enough network activity to passively keep WiFi enabled!]] ) ,
keep_menu_open = true ,
callback = function ( touchmenu_instance )
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.settings . 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 )
if touchmenu_instance then touchmenu_instance : updateItems ( ) end
end
self : saveSettings ( )
}
UIManager : show ( items )
end ,
separator = true ,
} ,
{
text = _ ( " Whisper sync " ) ,
enabled_func = function ( ) return self.kosync_auto_sync end ,
text = _ ( " Sync behavior " ) ,
sub_item_table = {
{
text_func = function ( )
return T ( _ ( " Sync to latest record (%1) " ) , getNameStrategy ( self.kosync_whisper_forward ) )
-- NOTE: With an up-to-date Sync server, "forward" means *newer*, not necessarily ahead in the document.
return T ( _ ( " Sync to a newer state (%1) " ) , getNameStrategy ( self.settings . sync_forward ) )
end ,
sub_item_table = {
{
text = _ ( " Auto " ) ,
text = _ ( " Silently " ) ,
checked_func = function ( )
return self.kosync_whisper_forward == SYNC_STRATEGY.WHISPER
return self. settings. sync_forward == SYNC_STRATEGY.SILENT
end ,
callback = function ( )
self : setWhisperForward ( SYNC_STRATEGY.WHISPER )
self : set SyncForward( SYNC_STRATEGY.SILENT )
end ,
} ,
{
text = _ ( " Prompt " ) ,
checked_func = function ( )
return self.kosync_whisper_forward == SYNC_STRATEGY.PROMPT
return self. settings. sync _forward == SYNC_STRATEGY.PROMPT
end ,
callback = function ( )
self : set Whisper Forward( SYNC_STRATEGY.PROMPT )
self : set Sync Forward( SYNC_STRATEGY.PROMPT )
end ,
} ,
{
text = _ ( " Disable " ) ,
text = _ ( " Never " ) ,
checked_func = function ( )
return self. kosync_whisper _forward == SYNC_STRATEGY.DISABLE
return self. settings. sync _forward == SYNC_STRATEGY.DISABLE
end ,
callback = function ( )
self : set Whisper Forward( SYNC_STRATEGY.DISABLE )
self : set Sync Forward( SYNC_STRATEGY.DISABLE )
end ,
} ,
}
} ,
{
text_func = function ( )
return T ( _ ( " Sync to a previous record (%1)" ) , getNameStrategy ( self.kosync_whisper _backward) )
return T ( _ ( " Sync to a n older state (%1)" ) , getNameStrategy ( self.settings . sync _backward) )
end ,
sub_item_table = {
{
text = _ ( " Auto " ) ,
text = _ ( " Silently " ) ,
checked_func = function ( )
return self. kosync_whisper_backward == SYNC_STRATEGY.WHISPER
return self. settings. sync_backward == SYNC_STRATEGY.SILENT
end ,
callback = function ( )
self : set WhisperBackward( SYNC_STRATEGY.WHISPER )
self : set SyncBackward( SYNC_STRATEGY.SILENT )
end ,
} ,
{
text = _ ( " Prompt " ) ,
checked_func = function ( )
return self. kosync_whisper _backward == SYNC_STRATEGY.PROMPT
return self. settings. sync _backward == SYNC_STRATEGY.PROMPT
end ,
callback = function ( )
self : set Whisper Backward( SYNC_STRATEGY.PROMPT )
self : set Sync Backward( SYNC_STRATEGY.PROMPT )
end ,
} ,
{
text = _ ( " Disable " ) ,
text = _ ( " Never " ) ,
checked_func = function ( )
return self. kosync_whisper _backward == SYNC_STRATEGY.DISABLE
return self. settings. sync _backward == SYNC_STRATEGY.DISABLE
end ,
callback = function ( )
self : set Whisper Backward( SYNC_STRATEGY.DISABLE )
self : set Sync Backward( SYNC_STRATEGY.DISABLE )
end ,
} ,
}
} ,
} ,
separator = true ,
} ,
{
text = _ ( " Push progress from this device " ) ,
text = _ ( " Push progress from this device now " ) ,
enabled_func = function ( )
return self. kosync_ userkey ~= nil
return self. settings. userkey ~= nil
end ,
callback = function ( )
self : updateProgress ( true )
self : updateProgress ( true , true )
end ,
} ,
{
text = _ ( " Pull progress from other devices " ) ,
text = _ ( " Pull progress from other devices now " ) ,
enabled_func = function ( )
return self. kosync_ userkey ~= nil
return self. settings. 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 )
self : getProgress ( true , true )
end ,
separator = true ,
} ,
{
text = _ ( " Document matching method " ) ,
sub_item_table = {
{
text = _ ( " Binary. Only identical files will sync progress ." ) ,
text = _ ( " Binary. Only identical files will be kept in sync. " ) ,
checked_func = function ( )
return self. kosync_ checksum_method == CHECKSUM_METHOD.BINARY
return self.settings . checksum_method == CHECKSUM_METHOD.BINARY
end ,
callback = function ( )
self : setChecksumMethod ( CHECKSUM_METHOD.BINARY )
end ,
} ,
{
text = _ ( " Filename. Files with the same name will sync progress ." ) ,
text = _ ( " Filename. Files with matching names will be kept in sync. " ) ,
checked_func = function ( )
return self. kosync_ checksum_method == CHECKSUM_METHOD.FILENAME
return self.settings . checksum_method == CHECKSUM_METHOD.FILENAME
end ,
callback = function ( )
self : setChecksumMethod ( CHECKSUM_METHOD.FILENAME )
@ -333,33 +399,28 @@ If set to 0, updating progress based on page turns will be disabled.]]),
end
function KOSync : setPagesBeforeUpdate ( pages_before_update )
self.kosync_pages_before_update = pages_before_update > 0 and pages_before_update or nil
self : saveSettings ( )
self.settings . pages_before_update = pages_before_update > 0 and pages_before_update or nil
end
function KOSync : setCustomServer ( server )
logger.dbg ( " set custom server " , server )
self.kosync_custom_server = server ~= " " and server or nil
self : saveSettings ( )
logger.dbg ( " KOSync: Setting custom server to: " , server )
self.settings . custom_server = server ~= " " and server or nil
end
function KOSync : setWhisperForward ( strategy )
self.kosync_whisper_forward = strategy
self : saveSettings ( )
function KOSync : setSyncForward ( strategy )
self.settings . sync_forward = strategy
end
function KOSync : setWhisperBackward ( strategy )
self.kosync_whisper_backward = strategy
self : saveSettings ( )
function KOSync : setSyncBackward ( strategy )
self.settings . sync_backward = strategy
end
function KOSync : setChecksumMethod ( method )
self.kosync_checksum_method = method
self : saveSettings ( )
self.settings . checksum_method = method
end
function KOSync : login ( )
if NetworkMgr : willRerunWhenOnline ( function ( ) self : login ( ) end ) then
function KOSync : login ( menu )
if NetworkMgr : willRerunWhenOnline ( function ( ) self : login ( menu ) end ) then
return
end
@ -368,7 +429,7 @@ function KOSync:login()
title = self.title ,
fields = {
{
text = self. kosync_ username,
text = self. settings. username,
hint = " username " ,
} ,
{
@ -398,7 +459,7 @@ function KOSync:login()
else
UIManager : close ( dialog )
UIManager : scheduleIn ( 0.5 , function ( )
self : doLogin ( username , password )
self : doLogin ( username , password , menu )
end )
UIManager : show ( InfoMessage : new {
text = _ ( " Logging in. Please wait… " ) ,
@ -420,7 +481,7 @@ function KOSync:login()
else
UIManager : close ( dialog )
UIManager : scheduleIn ( 0.5 , function ( )
self : doRegister ( username , password )
self : doRegister ( username , password , menu )
end )
UIManager : show ( InfoMessage : new {
text = _ ( " Registering. Please wait… " ) ,
@ -436,10 +497,10 @@ function KOSync:login()
dialog : onShowKeyboard ( )
end
function KOSync : doRegister ( username , password )
function KOSync : doRegister ( username , password , menu )
local KOSyncClient = require ( " KOSyncClient " )
local client = KOSyncClient : new {
custom_url = self. kosync_ custom_server,
custom_url = self. settings. custom_server,
service_spec = self.path .. " /api.json "
}
-- on Android to avoid ANR (no-op on other platforms)
@ -458,9 +519,11 @@ function KOSync:doRegister(username, password)
} )
end
elseif status then
self.kosync_username = username
self.kosync_userkey = userkey
self._menu_to_update : updateItems ( )
self.settings . username = username
self.settings . userkey = userkey
if menu then
menu : updateItems ( )
end
UIManager : show ( InfoMessage : new {
text = _ ( " Registered to KOReader server. " ) ,
} )
@ -470,13 +533,12 @@ function KOSync:doRegister(username, password)
} )
end
Device : setIgnoreInput ( false )
self : saveSettings ( )
end
function KOSync : doLogin ( username , password )
function KOSync : doLogin ( username , password , menu )
local KOSyncClient = require ( " KOSyncClient " )
local client = KOSyncClient : new {
custom_url = self. kosync_ custom_server,
custom_url = self. settings. custom_server,
service_spec = self.path .. " /api.json "
}
Device : setIgnoreInput ( true )
@ -496,9 +558,11 @@ function KOSync:doLogin(username, password)
Device : setIgnoreInput ( false )
return
elseif status then
self.kosync_username = username
self.kosync_userkey = userkey
self._menu_to_update : updateItems ( )
self.settings . username = username
self.settings . userkey = userkey
if menu then
menu : updateItems ( )
end
UIManager : show ( InfoMessage : new {
text = _ ( " Logged in to KOReader server. " ) ,
} )
@ -508,14 +572,14 @@ function KOSync:doLogin(username, password)
} )
end
Device : setIgnoreInput ( false )
self : saveSettings ( )
end
function KOSync : logout ( )
self.kosync_userkey = nil
self.kosync_auto_sync = true
self._menu_to_update : updateItems ( )
self : saveSettings ( )
function KOSync : logout ( menu )
self.settings . userkey = nil
self.settings . auto_sync = true
if menu then
menu : updateItems ( )
end
end
function KOSync : getLastPercent ( )
@ -535,7 +599,7 @@ function KOSync:getLastProgress()
end
function KOSync : getDocumentDigest ( )
if self. kosync_ checksum_method == CHECKSUM_METHOD.FILENAME then
if self. settings. checksum_method == CHECKSUM_METHOD.FILENAME then
return self : getFileNameDigest ( )
else
return self : getFileDigest ( )
@ -543,7 +607,7 @@ function KOSync:getDocumentDigest()
end
function KOSync : getFileDigest ( )
return self. view. document : fastDigest ( )
return self. ui. doc_settings : readSetting ( " partial_md5_checksum " )
end
function KOSync : getFileNameDigest ( )
@ -557,7 +621,7 @@ function KOSync:getFileNameDigest()
end
function KOSync : syncToProgress ( progress )
logger.dbg ( " sync to" , progress )
logger.dbg ( " KOSync: [Sync] progress to" , progress )
if self.ui . document.info . has_pages then
self.ui : handleEvent ( Event : new ( " GotoPage " , tonumber ( progress ) ) )
else
@ -565,21 +629,27 @@ function KOSync:syncToProgress(progress)
end
end
function KOSync : updateProgress ( manual )
if not self. kosync_username or not self.kosync_ userkey then
if manual then
function KOSync : updateProgress ( ensure_networking, interactive , refresh_on_success )
if not self. settings. username or not self.settings . userkey then
if interactive then
promptLogin ( )
end
return
end
if manual and NetworkMgr : willRerunWhenOnline ( function ( ) self : updateProgress ( manual ) end ) then
local now = UIManager : getElapsedTimeSinceBoot ( )
if not interactive and now - self.push_timestamp <= API_CALL_DEBOUNCE_DELAY then
logger.dbg ( " KOSync: We've already pushed progress less than 25s ago! " )
return
end
if ensure_networking and NetworkMgr : willRerunWhenOnline ( function ( ) self : updateProgress ( ensure_networking , interactive , refresh_on_success ) end ) then
return
end
local KOSyncClient = require ( " KOSyncClient " )
local client = KOSyncClient : new {
custom_url = self.kosync_custom_server ,
custom_url = self. settings. custom_server,
service_spec = self.path .. " /api.json "
}
local doc_digest = self : getDocumentDigest ( )
@ -587,16 +657,17 @@ function KOSync:updateProgress(manual)
local percentage = self : getLastPercent ( )
local ok , err = pcall ( client.update_progress ,
client ,
self. kosync_ username,
self. kosync_ userkey,
self. settings. username,
self. settings. userkey,
doc_digest ,
progress ,
percentage ,
Device.model ,
self. kosync_ device_id,
self. device_id,
function ( ok , body )
logger.dbg ( " update progress for " , self.view . document.file , ok )
if manual then
logger.dbg ( " KOSync: [Push] progress to " , percentage * 100 , " % => " , progress , " for " , self.view . document.file )
logger.dbg ( " KOSync: ok: " , ok , " body: " , body )
if interactive then
if ok then
UIManager : show ( InfoMessage : new {
text = _ ( " Progress has been pushed. " ) ,
@ -608,45 +679,67 @@ function KOSync:updateProgress(manual)
end
end )
if not ok then
if manual then showSyncError ( ) end
if interactive then showSyncError ( ) end
if err then logger.dbg ( " err: " , err ) end
else
-- This is solely for onSuspend's sake, to clear the ghosting left by the the "Connected" InfoMessage
if refresh_on_success then
-- Our top-level widget should be the "Connected to network" InfoMessage from NetworkMgr's reconnectOrShowNetworkMenu
local widget = UIManager : getTopmostVisibleWidget ( )
if widget and widget.modal and widget.tag == " NetworkMgr " and not widget.dismiss_callback then
-- We want a full-screen flash on dismiss
widget.dismiss_callback = function ( )
-- Enqueued, because we run before the InfoMessage's close
UIManager : setDirty ( nil , " full " )
end
end
end
end
function KOSync : getProgress ( manual )
if not self.kosync_username or not self.kosync_userkey then
if manual then
self.push_timestamp = now
end
function KOSync : getProgress ( ensure_networking , interactive )
if not self.settings . username or not self.settings . userkey then
if interactive then
promptLogin ( )
end
return
end
if manual and NetworkMgr : willRerunWhenOnline ( function ( ) self : getProgress ( manual ) end ) then
local now = UIManager : getElapsedTimeSinceBoot ( )
if not interactive and now - self.pull_timestamp <= API_CALL_DEBOUNCE_DELAY then
logger.dbg ( " KOSync: We've already pulled progress less than 25s ago! " )
return
end
if ensure_networking and NetworkMgr : willRerunWhenOnline ( function ( ) self : getProgress ( ensure_networking , interactive ) end ) then
return
end
local KOSyncClient = require ( " KOSyncClient " )
local client = KOSyncClient : new {
custom_url = self.kosync_custom_server ,
custom_url = self. settings. 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,
self. settings. username,
self. settings. userkey,
doc_digest ,
function ( ok , body )
logger.dbg ( " get progress for " , self.view . document.file , ok , body )
logger.dbg ( " KOSync: [Pull] progress for " , self.view . document.file )
logger.dbg ( " KOSync: ok: " , ok , " body: " , body )
if not ok or not body then
if manual then
if interactive then
showSyncError ( )
end
return
end
if not body.percentage then
if manual then
if interactive then
UIManager : show ( InfoMessage : new {
text = _ ( " No progress found for this document. " ) ,
timeout = 3 ,
@ -656,8 +749,8 @@ function KOSync:getProgress(manual)
end
if body.device == Device.model
and body.device_id == self. kosync_ device_id then
if manual then
and body.device_id == self. device_id then
if interactive then
UIManager : show ( InfoMessage : new {
text = _ ( " Latest progress is coming from this device. " ) ,
timeout = 3 ,
@ -669,11 +762,11 @@ function KOSync:getProgress(manual)
body.percentage = Math.roundPercent ( body.percentage )
local progress = self : getLastProgress ( )
local percentage = self : getLastPercent ( )
logger.dbg ( " current progress" , percentage )
logger.dbg ( " KOSyn c: C urrent progress: " , percentage * 100 , " % => " , progress )
if percentage == body.percentage
or body.progress == progress then
if manual then
if interactive then
UIManager : show ( InfoMessage : new {
text = _ ( " The progress has already been synchronized. " ) ,
timeout = 3 ,
@ -683,9 +776,9 @@ function KOSync:getProgress(manual)
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.
if interactive then
-- If user actively pulls progress from other devices,
-- we always update the progress without further confirmation.
self : syncToProgress ( body.progress )
showSyncedMessage ( )
return
@ -693,17 +786,16 @@ function KOSync:getProgress(manual)
local self_older
if body.timestamp ~= nil then
self_older = ( body.timestamp > self.last_page_turn_ti cks )
self_older = ( body.timestamp > self.last_page_turn_ti mestamp )
else
-- If we are working with old sync server, we can only use
-- percentage field.
-- If we are working with an old sync server, we can only use the percentage field.
self_older = ( body.percentage > percentage )
end
if self_older then
if self. kosync_whisper_forward == SYNC_STRATEGY.WHISPER then
if self. settings. sync_forward == SYNC_STRATEGY.SILENT then
self : syncToProgress ( body.progress )
showSyncedMessage ( )
elseif self. kosync_whisper _forward == SYNC_STRATEGY.PROMPT then
elseif self. settings. sync _forward == SYNC_STRATEGY.PROMPT then
UIManager : show ( ConfirmBox : new {
text = T ( _ ( " Sync to latest location %1% from device '%2'? " ) ,
Math.round ( body.percentage * 100 ) ,
@ -714,10 +806,10 @@ function KOSync:getProgress(manual)
} )
end
else -- if not self_older then
if self. kosync_whisper_backward == SYNC_STRATEGY.WHISPER then
if self. settings. sync_backward == SYNC_STRATEGY.SILENT then
self : syncToProgress ( body.progress )
showSyncedMessage ( )
elseif self. kosync_whisper _backward == SYNC_STRATEGY.PROMPT then
elseif self. settings. sync _backward == SYNC_STRATEGY.PROMPT then
UIManager : show ( ConfirmBox : new {
text = T ( _ ( " Sync to previous location %1% from device '%2'? " ) ,
Math.round ( body.percentage * 100 ) ,
@ -730,36 +822,30 @@ function KOSync:getProgress(manual)
end
end )
if not ok then
if manual then showSyncError ( ) end
if interactive then showSyncError ( ) end
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 ,
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 )
self.pull_timestamp = now
end
function KOSync : onCloseDocument ( )
logger.dbg ( " on close document " )
if self.kosync_auto_sync then
self : updateProgress ( )
function KOSync : _onCloseDocument ( )
logger.dbg ( " KOSync: onCloseDocument " )
-- NOTE: Because we'll lose the document instance on return, we need to *block* until the connection is actually up here,
-- we cannot rely on willRerunWhenOnline, because if we're not currently online,
-- it *will* return early, and that means the actual callback *will* run *after* teardown of the document instance
-- (and quite likely ours, too).
NetworkMgr : goOnlineToRun ( function ( )
-- Drop the inner willRerunWhenOnline ;).
self : updateProgress ( false , false )
end )
end
function KOSync : schedulePeriodicPush ( )
UIManager : unschedule ( self.periodic_push_task )
-- Use a sizable delay to make debouncing this on skim feasible...
UIManager : scheduleIn ( 10 , self.periodic_push_task )
self.periodic_push_scheduled = true
end
function KOSync : _onPageUpdate ( page )
@ -767,54 +853,81 @@ function KOSync:_onPageUpdate(page)
return
end
if self.last_page == - 1 then
self.last_page = page
elseif self.last_page ~= page then
if self.last_page ~= page then
self.last_page = page
self.last_page_turn_ti cks = 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 )
self.last_page_turn_timestamp = os.time ( )
self.page_update_ counter = self.page_update_counter + 1
-- If we've already scheduled a push, regardless of the counter's state, delay it until we're *actually* idle
if self.periodic_push_scheduled or self.settings . pages_before_update and self.page_update_counter >= self.settings . pages_before_update then
self: schedulePeriodicPush ( )
end
end
end
function KOSync : _onResume ( )
UIManager : scheduleIn ( 1 , function ( ) self : getProgress ( ) end )
logger.dbg ( " KOSync: onResume " )
-- If we have auto_restore_wifi enabled, skip this to prevent both the "Connecting..." UI to pop-up,
-- *and* a duplicate NetworkConnected event from firing...
if Device : hasWifiManager ( ) and NetworkMgr.wifi_was_on and G_reader_settings : isTrue ( " auto_restore_wifi " ) then
return
end
-- And if we don't, this *will* (attempt to) trigger a connection and as such a NetworkConnected event,
-- but only a single pull will happen, since getProgress debounces itself.
UIManager : scheduleIn ( 1 , function ( )
self : getProgress ( true , false )
end )
end
function KOSync : _onFlushSettings ( )
if self.ui == nil or self.ui . document == nil then return end
self : updateProgress ( )
function KOSync : _onSuspend ( )
logger.dbg ( " KOSync: onSuspend " )
-- We request an extra flashing refresh on success, to deal with potential ghosting left by the NetworkMgr UI
self : updateProgress ( true , false , true )
end
function KOSync : _onNetworkConnected ( )
self : _onResume ( )
logger.dbg ( " KOSync: onNetworkConnected " )
UIManager : scheduleIn ( 0.5 , function ( )
-- Network is supposed to be on already, don't wrap this in willRerunWhenOnline
self : getProgress ( false , false )
end )
end
function KOSync : _onNetworkDisconnecting ( )
logger.dbg ( " KOSync: onNetworkDisconnecting " )
-- Network is supposed to be on already, don't wrap this in willRerunWhenOnline
self : updateProgress ( false , false )
end
function KOSync : onKOSyncPushProgress ( )
if not self.kosync_userkey then return end
self : updateProgress ( true )
self : updateProgress ( true , true )
end
function KOSync : onKOSyncPullProgress ( )
if not self.kosync_userkey then return end
self : getProgress ( true )
self : getProgress ( true , true )
end
function KOSync : registerEvents ( )
if self.kosync_auto_sync then
if self.settings . auto_sync then
self.onCloseDocument = self._onCloseDocument
self.onPageUpdate = self._onPageUpdate
self.onResume = self._onResume
self.on FlushSettings = self._onFlushSettings
self.on Suspend = self._onSuspend
self.onNetworkConnected = self._onNetworkConnected
self.onNetworkDisconnecting = self._onNetworkDisconnecting
else
self.onCloseDocument = nil
self.onPageUpdate = nil
self.onResume = nil
self.on FlushSettings = nil
self.on Suspend = nil
self.onNetworkConnected = nil
self.onNetworkDisconnecting = nil
end
end
function KOSync : onCloseWidget ( )
UIManager : unschedule ( self.periodic_push_task )
self.periodic_push_task = nil
end
return KOSync