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

@ -2,7 +2,6 @@ local InputContainer = require("ui/widget/container/inputcontainer")
local LoginDialog = require("ui/widget/logindialog")
local InfoMessage = require("ui/widget/infomessage")
local ConfirmBox = require("ui/widget/confirmbox")
local DocSettings = require("docsettings")
local NetworkMgr = require("ui/network/manager")
local UIManager = require("ui/uimanager")
local Screen = require("device").screen
@ -22,21 +21,62 @@ end
local KOSync = InputContainer:new{
name = "kosync",
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 {}
self.kosync_custom_server = settings.custom_server
self.kosync_username = settings.username
self.kosync_userkey = settings.userkey
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")
--assert(self.kosync_device_id)
self.ui:registerPostInitCallback(function()
if self.kosync_auto_sync then
UIManager:scheduleIn(1, function() self:getProgress() end)
end
end)
if self.kosync_auto_sync then
self:_onResume()
end
self:registerEvents()
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
@ -64,6 +104,7 @@ function KOSync:addToMainMenu(tab_item_table)
checked_func = function() return self.kosync_auto_sync end,
callback = function()
self.kosync_auto_sync = not self.kosync_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.
@ -75,6 +116,74 @@ function KOSync:addToMainMenu(tab_item_table)
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"),
enabled_func = function()
@ -189,23 +298,27 @@ function KOSync:doRegister(username, password)
}
local userkey = md5.sum(password)
local ok, status, body = pcall(client.register, client, username, userkey)
if not ok and status then
UIManager:show(InfoMessage:new{
text = _("An error occurred while registering:") ..
"\n" .. status,
})
elseif ok then
if not ok then
if status then
self.kosync_username = username
self.kosync_userkey = userkey
UIManager:show(InfoMessage:new{
text = _("Registered to KOReader server."),
text = _("An error occurred while registering:") ..
"\n" .. status,
})
else
UIManager:show(InfoMessage:new{
text = _(body and body.message or "Unknown server error"),
text = _("An unknown error occurred while registering."),
})
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
self:onSaveSettings()
@ -219,23 +332,28 @@ function KOSync:doLogin(username, password)
}
local userkey = md5.sum(password)
local ok, status, body = pcall(client.authorize, client, username, userkey)
if not ok and status then
UIManager:show(InfoMessage:new{
text = _("An error occurred while logging in:") ..
"\n" .. status,
})
elseif ok then
if not ok then
if status then
self.kosync_username = username
self.kosync_userkey = userkey
UIManager:show(InfoMessage:new{
text = _("Logged in to KOReader server."),
text = _("An error occurred while logging in:") ..
"\n" .. status,
})
else
UIManager:show(InfoMessage:new{
text = _(body and body.message or "Unknown server error"),
text = _("An unknown error occurred while logging in."),
})
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
self:onSaveSettings()
@ -247,10 +365,6 @@ function KOSync:logout()
self:onSaveSettings()
end
local function roundPercent(percent)
return math.floor(percent * 10000) / 10000
end
function KOSync:getLastPercent()
if self.ui.document.info.has_pages then
return roundPercent(self.ui.paging:getLastPercent())
@ -276,120 +390,165 @@ function KOSync:syncToProgress(progress)
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)
if self.kosync_username and self.kosync_userkey then
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
if not self.kosync_username or not self.kosync_userkey then
if manual then
promptLogin()
end
elseif manual then
promptLogin()
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.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
function KOSync:getProgress(manual)
if self.kosync_username and self.kosync_userkey then
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 ok, err = pcall(client.get_progress,
client,
self.kosync_username,
self.kosync_userkey,
doc_digest,
function(ok, body)
DEBUG("get progress for", self.view.document.file, ok, body)
if body then
if body.percentage then
if body.device ~= DeviceModel
or body.device_id ~= self.kosync_device_id then
body.percentage = roundPercent(body.percentage)
local progress = self:getLastProgress()
local percentage = self:getLastPercent()
DEBUG("current progress", percentage)
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
if not self.kosync_username or not self.kosync_userkey then
if manual then
promptLogin()
end
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.view.document:fastDigest()
local ok, err = pcall(client.get_progress,
client,
self.kosync_username,
self.kosync_userkey,
doc_digest,
function(ok, body)
DEBUG("get progress for", self.view.document.file, ok, body)
if not ok or not body then
if manual then
showSyncError()
end
end)
if not ok then
if manual then showSyncError() end
if err then DEBUG("err:", err) end
end
elseif manual then
promptLogin()
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
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
@ -399,6 +558,14 @@ function KOSync:onSaveSettings()
username = self.kosync_username,
userkey = self.kosync_userkey,
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)
end
@ -410,4 +577,38 @@ function KOSync:onCloseDocument()
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

@ -173,4 +173,40 @@ describe("KOSync modules #notest #nocov", function()
DEBUG("Please retry later", res)
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)

Loading…
Cancel
Save