Whisper sync feature

Now KOReader supports more sync options, and most of the sync operation can be
executed automatically.
pull/2268/head
Hzj_jie 8 years ago committed by Zijie He
parent 0ba2417e24
commit fd7ae875c0

@ -15,6 +15,7 @@ function KOSyncClient:new(o)
end end
function KOSyncClient:init() function KOSyncClient:init()
require("socket.http").TIMEOUT = 1
local Spore = require("Spore") local Spore = require("Spore")
self.client = Spore.new_from_spec(self.service_spec, { self.client = Spore.new_from_spec(self.service_spec, {
base_url = self.custom_url, base_url = self.custom_url,
@ -89,7 +90,7 @@ function KOSyncClient:authorize(username, password)
return res.status == 200, res.body return res.status == 200, res.body
else else
DEBUG("err:", res) DEBUG("err:", res)
return false, res return false, res.body
end end
end end
@ -123,7 +124,7 @@ function KOSyncClient:update_progress(
callback(res.status == 200, res.body) callback(res.status == 200, res.body)
else else
DEBUG("err:", res) DEBUG("err:", res)
callback(false, res) callback(false, res.body)
end end
end) end)
self.client:enable("AsyncHTTP", {thread = co}) self.client:enable("AsyncHTTP", {thread = co})
@ -153,7 +154,7 @@ function KOSyncClient:get_progress(
callback(res.status == 200, res.body) callback(res.status == 200, res.body)
else else
DEBUG("err:", res) DEBUG("err:", res)
callback(false, res) callback(false, res.body)
end end
end) end)
self.client:enable("AsyncHTTP", {thread = co}) self.client:enable("AsyncHTTP", {thread = co})

@ -2,7 +2,6 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local LoginDialog = require("ui/widget/logindialog") local LoginDialog = require("ui/widget/logindialog")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DocSettings = require("docsettings")
local NetworkMgr = require("ui/network/manager") local NetworkMgr = require("ui/network/manager")
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local Screen = require("device").screen local Screen = require("device").screen
@ -22,21 +21,62 @@ end
local KOSync = InputContainer:new{ local KOSync = InputContainer:new{
name = "kosync", name = "kosync",
title = _("Register/login to KOReader server"), 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,
} }
function KOSync:init() local function roundPercent(percent)
return math.floor(percent * 10000) / 10000
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
function KOSync:onReaderReady()
local settings = G_reader_settings:readSetting("kosync") or {} local settings = G_reader_settings:readSetting("kosync") or {}
self.kosync_custom_server = settings.custom_server self.kosync_custom_server = settings.custom_server
self.kosync_username = settings.username self.kosync_username = settings.username
self.kosync_userkey = settings.userkey self.kosync_userkey = settings.userkey
self.kosync_auto_sync = not (settings.auto_sync == false) self.kosync_auto_sync = not (settings.auto_sync == false)
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_device_id = G_reader_settings:readSetting("device_id") self.kosync_device_id = G_reader_settings:readSetting("device_id")
--assert(self.kosync_device_id) --assert(self.kosync_device_id)
self.ui:registerPostInitCallback(function() if self.kosync_auto_sync then
if self.kosync_auto_sync then self:_onResume()
UIManager:scheduleIn(1, function() self:getProgress() end) end
end self:registerEvents()
end)
self.ui.menu:registerToMainMenu(self) self.ui.menu:registerToMainMenu(self)
-- Make sure checksum has been calculated at the very first time a document has been opened, to -- 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 -- avoid document saving feature to impact the checksum, and eventually impact the document
@ -64,6 +104,7 @@ function KOSync:addToMainMenu(tab_item_table)
checked_func = function() return self.kosync_auto_sync end, checked_func = function() return self.kosync_auto_sync end,
callback = function() callback = function()
self.kosync_auto_sync = not self.kosync_auto_sync self.kosync_auto_sync = not self.kosync_auto_sync
self:registerEvents()
if self.kosync_auto_sync then if self.kosync_auto_sync then
-- since we will update the progress when closing document, we should pull -- since we will update the progress when closing document, we should pull
-- current progress now to avoid to overwrite it silently. -- current progress now to avoid to overwrite it silently.
@ -75,6 +116,74 @@ function KOSync:addToMainMenu(tab_item_table)
end end
end, end,
}, },
{
text = _("Whisper sync"),
enabled_func = function() return self.kosync_auto_sync end,
sub_item_table = {
{
text = "Sync to latest record >>>>",
enabled = false,
},
{
text = _(" Auto"),
checked_func = function()
return self.kosync_whisper_forward == SYNC_STRATEGY.WHISPER
end,
callback = function()
self.kosync_whisper_forward = SYNC_STRATEGY.WHISPER
end,
},
{
text = _(" Prompt"),
checked_func = function()
return self.kosync_whisper_forward == SYNC_STRATEGY.PROMPT
end,
callback = function()
self.kosync_whisper_forward = SYNC_STRATEGY.PROMPT
end,
},
{
text = _(" Disable"),
checked_func = function()
return self.kosync_whisper_forward == SYNC_STRATEGY.DISABLE
end,
callback = function()
self.kosync_whisper_forward = SYNC_STRATEGY.DISABLE
end,
},
{
text = "Sync to a previous record <<<<",
enabled = false,
},
{
text = _(" Auto"),
checked_func = function()
return self.kosync_whisper_backward == SYNC_STRATEGY.WHISPER
end,
callback = function()
self.kosync_whisper_backward = SYNC_STRATEGY.WHISPER
end,
},
{
text = _(" Prompt"),
checked_func = function()
return self.kosync_whisper_backward == SYNC_STRATEGY.PROMPT
end,
callback = function()
self.kosync_whisper_backward = SYNC_STRATEGY.PROMPT
end,
},
{
text = _(" Disable"),
checked_func = function()
return self.kosync_whisper_backward == SYNC_STRATEGY.DISABLE
end,
callback = function()
self.kosync_whisper_backward = SYNC_STRATEGY.DISABLE
end,
},
},
},
{ {
text = _("Push progress from this device"), text = _("Push progress from this device"),
enabled_func = function() enabled_func = function()
@ -189,23 +298,27 @@ function KOSync:doRegister(username, password)
} }
local userkey = md5.sum(password) local userkey = md5.sum(password)
local ok, status, body = pcall(client.register, client, username, userkey) local ok, status, body = pcall(client.register, client, username, userkey)
if not ok and status then if not ok then
UIManager:show(InfoMessage:new{
text = _("An error occurred while registering:") ..
"\n" .. status,
})
elseif ok then
if status then if status then
self.kosync_username = username
self.kosync_userkey = userkey
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = _("Registered to KOReader server."), text = _("An error occurred while registering:") ..
"\n" .. status,
}) })
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = _(body and body.message or "Unknown server error"), text = _("An unknown error occurred while registering."),
}) })
end end
elseif status then
self.kosync_username = username
self.kosync_userkey = userkey
UIManager:show(InfoMessage:new{
text = _("Registered to KOReader server."),
})
else
UIManager:show(InfoMessage:new{
text = _(body and body.message or "Unknown server error"),
})
end end
self:onSaveSettings() self:onSaveSettings()
@ -219,23 +332,28 @@ function KOSync:doLogin(username, password)
} }
local userkey = md5.sum(password) local userkey = md5.sum(password)
local ok, status, body = pcall(client.authorize, client, username, userkey) local ok, status, body = pcall(client.authorize, client, username, userkey)
if not ok and status then if not ok then
UIManager:show(InfoMessage:new{
text = _("An error occurred while logging in:") ..
"\n" .. status,
})
elseif ok then
if status then if status then
self.kosync_username = username
self.kosync_userkey = userkey
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = _("Logged in to KOReader server."), text = _("An error occurred while logging in:") ..
"\n" .. status,
}) })
else else
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = _(body and body.message or "Unknown server error"), text = _("An unknown error occurred while logging in."),
}) })
end end
return
elseif status then
self.kosync_username = username
self.kosync_userkey = userkey
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 end
self:onSaveSettings() self:onSaveSettings()
@ -247,10 +365,6 @@ function KOSync:logout()
self:onSaveSettings() self:onSaveSettings()
end end
local function roundPercent(percent)
return math.floor(percent * 10000) / 10000
end
function KOSync:getLastPercent() function KOSync:getLastPercent()
if self.ui.document.info.has_pages then if self.ui.document.info.has_pages then
return roundPercent(self.ui.paging:getLastPercent()) return roundPercent(self.ui.paging:getLastPercent())
@ -276,120 +390,165 @@ function KOSync:syncToProgress(progress)
end end
end 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
function KOSync:updateProgress(manual) function KOSync:updateProgress(manual)
if self.kosync_username and self.kosync_userkey then if not self.kosync_username or not self.kosync_userkey then
local KOSyncClient = require("KOSyncClient") if manual then
local client = KOSyncClient:new{ promptLogin()
custom_url = self.kosync_custom_server,
service_spec = self.path .. "/api.json"
}
local doc_digest = self.view.document:fastDigest()
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,
DeviceModel,
self.kosync_device_id,
function(ok, body)
DEBUG("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
if err then DEBUG("err:", err) end
end end
elseif manual then return
promptLogin() 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.view.document:fastDigest()
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,
DeviceModel,
self.kosync_device_id,
function(ok, body)
DEBUG("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
if err then DEBUG("err:", err) end
end end
end end
function KOSync:getProgress(manual) function KOSync:getProgress(manual)
if self.kosync_username and self.kosync_userkey then if not self.kosync_username or not self.kosync_userkey then
local KOSyncClient = require("KOSyncClient") if manual then
local client = KOSyncClient:new{ promptLogin()
custom_url = self.kosync_custom_server, end
service_spec = self.path .. "/api.json" return
} end
local doc_digest = self.view.document:fastDigest()
local ok, err = pcall(client.get_progress, local KOSyncClient = require("KOSyncClient")
client, local client = KOSyncClient:new{
self.kosync_username, custom_url = self.kosync_custom_server,
self.kosync_userkey, service_spec = self.path .. "/api.json"
doc_digest, }
function(ok, body) local doc_digest = self.view.document:fastDigest()
DEBUG("get progress for", self.view.document.file, ok, body) local ok, err = pcall(client.get_progress,
if body then client,
if body.percentage then self.kosync_username,
if body.device ~= DeviceModel self.kosync_userkey,
or body.device_id ~= self.kosync_device_id then doc_digest,
body.percentage = roundPercent(body.percentage) function(ok, body)
local progress = self:getLastProgress() DEBUG("get progress for", self.view.document.file, ok, body)
local percentage = self:getLastPercent() if not ok or not body then
DEBUG("current progress", percentage) if manual then
if body.percentage > percentage and body.progress ~= progress then
UIManager:show(ConfirmBox:new{
text = T(_("Sync to furthest location read (%1%) from device '%2'?"),
Math.round(body.percentage*100), body.device),
ok_callback = function()
self:syncToProgress(body.progress)
end,
})
elseif manual then
UIManager:show(InfoMessage:new{
text = _("Already synchronized."),
timeout = 3,
})
end
elseif manual then
UIManager:show(InfoMessage:new{
text = _("Latest progress is coming from this device."),
timeout = 3,
})
end
elseif manual then
UIManager:show(InfoMessage:new{
text = _("No progress found for this document."),
timeout = 3,
})
end
elseif manual then
showSyncError() showSyncError()
end end
end) return
if not ok then end
if manual then showSyncError() end
if err then DEBUG("err:", err) end if not body.percentage then
end if manual then
elseif manual then UIManager:show(InfoMessage:new{
promptLogin() text = _("No progress found for this document."),
timeout = 3,
})
end
return
end
if body.device == DeviceModel
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 = roundPercent(body.percentage)
local progress = self:getLastProgress()
local percentage = self:getLastPercent()
DEBUG("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 the latest record %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 a previous record %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
if err then DEBUG("err:", err) end
end end
end end
@ -399,6 +558,14 @@ function KOSync:onSaveSettings()
username = self.kosync_username, username = self.kosync_username,
userkey = self.kosync_userkey, userkey = self.kosync_userkey,
auto_sync = self.kosync_auto_sync, auto_sync = self.kosync_auto_sync,
whisper_forward =
(self.kosync_whisper_forward == SYNC_STRATEGY.DEFAULT_FORWARD
and nil
or self.kosync_whisper_forward),
whisper_backward =
(self.kosync_whisper_backward == SYNC_STRATEGY.DEFAULT_BACKWARD
and nil
or self.kosync_whisper_backward),
} }
G_reader_settings:saveSetting("kosync", settings) G_reader_settings:saveSetting("kosync", settings)
end end
@ -410,4 +577,38 @@ function KOSync:onCloseDocument()
end end
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 DAUTO_SAVE_PAGING_COUNT ~= nil
and (DAUTO_SAVE_PAGING_COUNT <= 0
or self.page_update_times == DAUTO_SAVE_PAGING_COUNT) 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:registerEvents()
if self.kosync_auto_sync then
self.onPageUpdate = self._onPageUpdate
self.onResume = self._onResume
else
self.onPageUpdate = nil
self.onResume = nil
end
end
return KOSync return KOSync

@ -173,4 +173,40 @@ describe("KOSync modules #notest #nocov", function()
DEBUG("Please retry later", res) DEBUG("Please retry later", res)
end end
end) end)
-- The response of mockKOSyncClient
local res = {
result = false,
body = {}
}
-- TODO: Test kosync module
local function mockKOSyncClient()
package.loaded["KOSyncClient"] = nil
local c = require("KOSyncClient")
c.new = function(o)
local o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
c.init = function() end
c.register = function(name, passwd)
return res.result, res.body
end
c.authorize = function(name, passwd)
return res.result, res.body
end
c.update_progress = function(name, passwd, doc, prog, percent, device, device_id, cb)
cb(res.result, res.body)
end
c.get_progress = function(name, passwd, doc, cb)
cb(res.result, res.body)
end
end
end) end)

Loading…
Cancel
Save