mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
add simple sync service as a plugin
The 'KOSync' plugin will synchronize furthest reading progress across different koreader devices after users registering their devices. The synchronizing service is open-sourced as the project [koreader/koreader-sync-server](https://github.com/koreader/koreader-sync-server).
This commit is contained in:
@@ -22,6 +22,6 @@ indent_size = 8
|
|||||||
indent_style = tab
|
indent_style = tab
|
||||||
indent_size = 8
|
indent_size = 8
|
||||||
|
|
||||||
[*.{js,json,css,scss,sass,html,handlebars,tpl}]
|
[*.{js,css,scss,sass,html,handlebars,tpl}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|||||||
@@ -131,10 +131,20 @@ end
|
|||||||
function ReaderPaging:onSaveSettings()
|
function ReaderPaging:onSaveSettings()
|
||||||
self.ui.doc_settings:saveSetting("page_positions", self.page_positions)
|
self.ui.doc_settings:saveSetting("page_positions", self.page_positions)
|
||||||
self.ui.doc_settings:saveSetting("last_page", self:getTopPage())
|
self.ui.doc_settings:saveSetting("last_page", self:getTopPage())
|
||||||
self.ui.doc_settings:saveSetting("percent_finished", self.current_page/self.number_of_pages)
|
self.ui.doc_settings:saveSetting("percent_finished", self:getLastPercent())
|
||||||
self.ui.doc_settings:saveSetting("show_overlap_enable", self.show_overlap_enable)
|
self.ui.doc_settings:saveSetting("show_overlap_enable", self.show_overlap_enable)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ReaderPaging:getLastProgress()
|
||||||
|
return self:getTopPage()
|
||||||
|
end
|
||||||
|
|
||||||
|
function ReaderPaging:getLastPercent()
|
||||||
|
if self.current_page > 0 and self.number_of_pages > 0 then
|
||||||
|
return self.current_page/self.number_of_pages
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function ReaderPaging:addToMainMenu(tab_item_table)
|
function ReaderPaging:addToMainMenu(tab_item_table)
|
||||||
-- FIXME: repeated code with page overlap menu for readerrolling
|
-- FIXME: repeated code with page overlap menu for readerrolling
|
||||||
-- needs to keep only one copy of the logic as for the DRY principle.
|
-- needs to keep only one copy of the logic as for the DRY principle.
|
||||||
|
|||||||
@@ -208,6 +208,10 @@ function ReaderRolling:onSaveSettings()
|
|||||||
self.ui.doc_settings:saveSetting("show_overlap_enable", self.show_overlap_enable)
|
self.ui.doc_settings:saveSetting("show_overlap_enable", self.show_overlap_enable)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function ReaderRolling:getLastProgress()
|
||||||
|
return self.xpointer
|
||||||
|
end
|
||||||
|
|
||||||
function ReaderRolling:addToMainMenu(tab_item_table)
|
function ReaderRolling:addToMainMenu(tab_item_table)
|
||||||
-- FIXME: repeated code with page overlap menu for readerpaging
|
-- FIXME: repeated code with page overlap menu for readerpaging
|
||||||
-- needs to keep only one copy of the logic as for the DRY principle.
|
-- needs to keep only one copy of the logic as for the DRY principle.
|
||||||
|
|||||||
@@ -368,7 +368,8 @@ function ReaderUI:closeDocument()
|
|||||||
self.document = nil
|
self.document = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function ReaderUI:onCloseDocument()
|
function ReaderUI:notifyCloseDocument()
|
||||||
|
self:handleEvent(Event:new("CloseDocument"))
|
||||||
if self.document:isEdited() then
|
if self.document:isEdited() then
|
||||||
UIManager:show(ConfirmBox:new{
|
UIManager:show(ConfirmBox:new{
|
||||||
text = _("Do you want to save this document?"),
|
text = _("Do you want to save this document?"),
|
||||||
@@ -392,7 +393,7 @@ function ReaderUI:onClose()
|
|||||||
self:saveSettings()
|
self:saveSettings()
|
||||||
if self.document ~= nil then
|
if self.document ~= nil then
|
||||||
DEBUG("closing document")
|
DEBUG("closing document")
|
||||||
self:onCloseDocument()
|
self:notifyCloseDocument()
|
||||||
end
|
end
|
||||||
UIManager:close(self.dialog, "full")
|
UIManager:close(self.dialog, "full")
|
||||||
-- serialize last used items for later launch
|
-- serialize last used items for later launch
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ local UIManager = require("ui/uimanager")
|
|||||||
local DEBUG = require("dbg")
|
local DEBUG = require("dbg")
|
||||||
|
|
||||||
local HTTPClient = {
|
local HTTPClient = {
|
||||||
headers = {},
|
|
||||||
input_timeouts = 0,
|
input_timeouts = 0,
|
||||||
INPUT_TIMEOUT = 100*1000,
|
INPUT_TIMEOUT = 100*1000,
|
||||||
}
|
}
|
||||||
@@ -14,20 +13,7 @@ function HTTPClient:new()
|
|||||||
return o
|
return o
|
||||||
end
|
end
|
||||||
|
|
||||||
function HTTPClient:addHeader(header, value)
|
|
||||||
self.headers[header] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
function HTTPClient:removeHeader(header)
|
|
||||||
self.headers[header] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function HTTPClient:request(request, response_callback)
|
function HTTPClient:request(request, response_callback)
|
||||||
request.on_headers = function(headers)
|
|
||||||
for header, value in pairs(self.headers) do
|
|
||||||
headers[header] = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
request.connect_timeout = 10
|
request.connect_timeout = 10
|
||||||
request.request_timeout = 20
|
request.request_timeout = 20
|
||||||
UIManager:initLooper()
|
UIManager:initLooper()
|
||||||
|
|||||||
150
plugins/kosync.koplugin/KOSyncClient.lua
Normal file
150
plugins/kosync.koplugin/KOSyncClient.lua
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
local UIManager = require("ui/uimanager")
|
||||||
|
local DEBUG = require("dbg")
|
||||||
|
|
||||||
|
local KOSyncClient = {
|
||||||
|
service_spec = nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
function KOSyncClient:new(o)
|
||||||
|
local o = o or {}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
if o.init then o:init() end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSyncClient:init()
|
||||||
|
local Spore = require("Spore")
|
||||||
|
self.client = Spore.new_from_spec(self.service_spec)
|
||||||
|
package.loaded['Spore.Middleware.GinClient'] = {}
|
||||||
|
require('Spore.Middleware.GinClient').call = function(self, req)
|
||||||
|
req.headers['accept'] = "application/vnd.koreader.v1+json"
|
||||||
|
end
|
||||||
|
package.loaded['Spore.Middleware.KOSyncAuth'] = {}
|
||||||
|
require('Spore.Middleware.KOSyncAuth').call = function(args, req)
|
||||||
|
req.headers['x-auth-user'] = args.username
|
||||||
|
req.headers['x-auth-key'] = args.userkey
|
||||||
|
end
|
||||||
|
local HTTPClient = require("httpclient")
|
||||||
|
local async_http_client = HTTPClient:new()
|
||||||
|
package.loaded['Spore.Middleware.AsyncHTTP'] = {}
|
||||||
|
require('Spore.Middleware.AsyncHTTP').call = function(args, req)
|
||||||
|
req:finalize()
|
||||||
|
local result
|
||||||
|
async_http_client:request({
|
||||||
|
url = req.url,
|
||||||
|
method = req.method,
|
||||||
|
body = req.env.spore.payload,
|
||||||
|
on_headers = function(headers)
|
||||||
|
for header, value in pairs(req.headers) do
|
||||||
|
if type(header) == 'string' then
|
||||||
|
headers:add(header, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}, function(res)
|
||||||
|
result = res
|
||||||
|
-- Turbo HTTP client uses code instead of status
|
||||||
|
-- change to status so that Spore can understand
|
||||||
|
result.status = res.code
|
||||||
|
coroutine.resume(args.thread)
|
||||||
|
end)
|
||||||
|
return coroutine.create(function() coroutine.yield(result) end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSyncClient:register(username, password)
|
||||||
|
self.client:reset_middlewares()
|
||||||
|
self.client:enable('Format.JSON')
|
||||||
|
self.client:enable("GinClient")
|
||||||
|
local ok, res = pcall(function()
|
||||||
|
return self.client:register({
|
||||||
|
username = username,
|
||||||
|
password = password,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
return res.status == 201, res.body
|
||||||
|
else
|
||||||
|
DEBUG(ok, res)
|
||||||
|
return false, res.body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSyncClient:authorize(username, password)
|
||||||
|
self.client:reset_middlewares()
|
||||||
|
self.client:enable('Format.JSON')
|
||||||
|
self.client:enable("GinClient")
|
||||||
|
self.client:enable("KOSyncAuth", {
|
||||||
|
username = username,
|
||||||
|
userkey = password,
|
||||||
|
})
|
||||||
|
local ok, res = pcall(function()
|
||||||
|
return self.client:authorize()
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
return res.status == 200, res.body
|
||||||
|
else
|
||||||
|
DEBUG("err:", res)
|
||||||
|
return false, res
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSyncClient:update_progress(username, password,
|
||||||
|
document, progress, percentage, device, callback)
|
||||||
|
self.client:reset_middlewares()
|
||||||
|
self.client:enable('Format.JSON')
|
||||||
|
self.client:enable("GinClient")
|
||||||
|
self.client:enable("KOSyncAuth", {
|
||||||
|
username = username,
|
||||||
|
userkey = password,
|
||||||
|
})
|
||||||
|
local co = coroutine.create(function()
|
||||||
|
local ok, res = pcall(function()
|
||||||
|
return self.client:update_progress({
|
||||||
|
document = document,
|
||||||
|
progress = progress,
|
||||||
|
percentage = percentage,
|
||||||
|
device = device,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
callback(res.status == 200, res.body)
|
||||||
|
else
|
||||||
|
DEBUG("err:", res)
|
||||||
|
callback(false, res)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
self.client:enable("AsyncHTTP", {thread = co})
|
||||||
|
coroutine.resume(co)
|
||||||
|
UIManager.INPUT_TIMEOUT = 100
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSyncClient:get_progress(username, password,
|
||||||
|
document, callback)
|
||||||
|
self.client:reset_middlewares()
|
||||||
|
self.client:enable('Format.JSON')
|
||||||
|
self.client:enable("GinClient")
|
||||||
|
self.client:enable("KOSyncAuth", {
|
||||||
|
username = username,
|
||||||
|
userkey = password,
|
||||||
|
})
|
||||||
|
local co = coroutine.create(function()
|
||||||
|
local ok, res = pcall(function()
|
||||||
|
return self.client:get_progress({
|
||||||
|
document = document,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
callback(res.status == 200, res.body)
|
||||||
|
else
|
||||||
|
DEBUG("err:", res)
|
||||||
|
callback(false, res)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
self.client:enable("AsyncHTTP", {thread = co})
|
||||||
|
coroutine.resume(co)
|
||||||
|
UIManager.INPUT_TIMEOUT = 100
|
||||||
|
end
|
||||||
|
|
||||||
|
return KOSyncClient
|
||||||
49
plugins/kosync.koplugin/api.json
Normal file
49
plugins/kosync.koplugin/api.json
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"base_url" : "https://vislab.bjmu.edu.cn:7200/",
|
||||||
|
"name" : "koreader-sync-api",
|
||||||
|
"methods" : {
|
||||||
|
"register" : {
|
||||||
|
"path" : "/users/create",
|
||||||
|
"method" : "POST",
|
||||||
|
"required_params" : [
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
],
|
||||||
|
"payload" : [
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
],
|
||||||
|
"expected_status" : [201, 402]
|
||||||
|
},
|
||||||
|
"authorize" : {
|
||||||
|
"path" : "/users/auth",
|
||||||
|
"method" : "GET",
|
||||||
|
"expected_status" : [200, 401]
|
||||||
|
},
|
||||||
|
"update_progress" : {
|
||||||
|
"path" : "/syncs/progress",
|
||||||
|
"method" : "PUT",
|
||||||
|
"required_params" : [
|
||||||
|
"document",
|
||||||
|
"progress",
|
||||||
|
"percentage",
|
||||||
|
"device",
|
||||||
|
],
|
||||||
|
"payload" : [
|
||||||
|
"document",
|
||||||
|
"progress",
|
||||||
|
"percentage",
|
||||||
|
"device",
|
||||||
|
],
|
||||||
|
"expected_status" : [200, 202, 401]
|
||||||
|
},
|
||||||
|
"get_progress" : {
|
||||||
|
"path" : "/syncs/progress/:document",
|
||||||
|
"method" : "GET",
|
||||||
|
"required_params" : [
|
||||||
|
"document",
|
||||||
|
],
|
||||||
|
"expected_status" : [200, 401]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
272
plugins/kosync.koplugin/main.lua
Normal file
272
plugins/kosync.koplugin/main.lua
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
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/networkmgr")
|
||||||
|
local UIManager = require("ui/uimanager")
|
||||||
|
local Screen = require("device").screen
|
||||||
|
local Device = require("device")
|
||||||
|
local Event = require("ui/event")
|
||||||
|
local DEBUG = require("dbg")
|
||||||
|
local T = require("ffi/util").template
|
||||||
|
local _ = require("gettext")
|
||||||
|
local md5 = require("MD5")
|
||||||
|
|
||||||
|
local KOSync = InputContainer:new{
|
||||||
|
name = "kosync",
|
||||||
|
register_title = _("Register an account in Koreader server"),
|
||||||
|
login_title = _("Login to Koreader server"),
|
||||||
|
}
|
||||||
|
|
||||||
|
function KOSync:init()
|
||||||
|
local settings = G_reader_settings:readSetting("kosync") or {}
|
||||||
|
self.kosync_username = settings.username or ""
|
||||||
|
self.kosync_userkey = settings.userkey
|
||||||
|
self.ui:registerPostInitCallback(function()
|
||||||
|
UIManager:scheduleIn(1, function() self:getProgress() end)
|
||||||
|
end)
|
||||||
|
self.ui.menu:registerToMainMenu(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:addToMainMenu(tab_item_table)
|
||||||
|
table.insert(tab_item_table.plugins, {
|
||||||
|
text = _("Progress Sync"),
|
||||||
|
sub_item_table = {
|
||||||
|
{
|
||||||
|
text_func = function()
|
||||||
|
return self.kosync_userkey and (_("Logout"))
|
||||||
|
or _("Register") .. " / " .. _("Login")
|
||||||
|
end,
|
||||||
|
callback_func = function()
|
||||||
|
return self.kosync_userkey and
|
||||||
|
function() self:logout() end or
|
||||||
|
function() self:login() end
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:login()
|
||||||
|
if NetworkMgr:getWifiStatus() == false then
|
||||||
|
NetworkMgr:promptWifiOn()
|
||||||
|
end
|
||||||
|
self.login_dialog = LoginDialog:new{
|
||||||
|
title = self.kosync_username and self.login_title or self.register_title,
|
||||||
|
username = self.kosync_username or "",
|
||||||
|
buttons = {
|
||||||
|
{
|
||||||
|
{
|
||||||
|
text = _("Cancel"),
|
||||||
|
enabled = true,
|
||||||
|
callback = function()
|
||||||
|
self:closeDialog()
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Login"),
|
||||||
|
enabled = true,
|
||||||
|
callback = function()
|
||||||
|
local username, password = self:getCredential()
|
||||||
|
self:closeDialog()
|
||||||
|
UIManager:scheduleIn(0.5, function()
|
||||||
|
self:doLogin(username, password)
|
||||||
|
end)
|
||||||
|
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _("Logging in. Please wait..."),
|
||||||
|
timeout = 1,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text = _("Register"),
|
||||||
|
enabled = not self.kosync and true or false,
|
||||||
|
callback = function()
|
||||||
|
local username, password = self:getCredential()
|
||||||
|
self:closeDialog()
|
||||||
|
UIManager:scheduleIn(0.5, function()
|
||||||
|
self:doRegister(username, password)
|
||||||
|
end)
|
||||||
|
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _("Registering. Please wait..."),
|
||||||
|
timeout = 1,
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
width = Screen:getWidth() * 0.8,
|
||||||
|
height = Screen:getHeight() * 0.4,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.login_dialog:onShowKeyboard()
|
||||||
|
UIManager:show(self.login_dialog)
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:closeDialog()
|
||||||
|
self.login_dialog:onClose()
|
||||||
|
UIManager:close(self.login_dialog)
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:getCredential()
|
||||||
|
return self.login_dialog:getCredential()
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:doRegister(username, password)
|
||||||
|
local KOSyncClient = require("KOSyncClient")
|
||||||
|
local client = KOSyncClient:new{
|
||||||
|
service_spec = self.path .. "/api.json"
|
||||||
|
}
|
||||||
|
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 status then
|
||||||
|
self.kosync_username = username
|
||||||
|
self.kosync_userkey = userkey
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _("Registered to Koreader server successfully."),
|
||||||
|
})
|
||||||
|
else
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _(body.message or "Unknown server error"),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self:onSaveSettings()
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:doLogin(username, password)
|
||||||
|
local KOSyncClient = require("KOSyncClient")
|
||||||
|
local client = KOSyncClient:new{
|
||||||
|
service_spec = self.path .. "/api.json"
|
||||||
|
}
|
||||||
|
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 status then
|
||||||
|
self.kosync_username = username
|
||||||
|
self.kosync_userkey = userkey
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _("Logged in to Koreader server successfully."),
|
||||||
|
})
|
||||||
|
else
|
||||||
|
UIManager:show(InfoMessage:new{
|
||||||
|
text = _(body.message or "Unknown server error"),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self:onSaveSettings()
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:logout()
|
||||||
|
self.kosync_username = nil
|
||||||
|
self.kosync_userkey = nil
|
||||||
|
self:onSaveSettings()
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:getLastPercent()
|
||||||
|
if self.ui.document.info.has_pages then
|
||||||
|
return self.ui.paging:getLastPercent()
|
||||||
|
else
|
||||||
|
return 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:syncToProgress(progress)
|
||||||
|
DEBUG("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
|
||||||
|
|
||||||
|
function KOSync:updateProgress()
|
||||||
|
if self.kosync_username and self.kosync_userkey then
|
||||||
|
local KOSyncClient = require("KOSyncClient")
|
||||||
|
local client = KOSyncClient:new{
|
||||||
|
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, Device.model,
|
||||||
|
function(ok, body)
|
||||||
|
DEBUG("update progress for", self.view.document.file, ok)
|
||||||
|
end)
|
||||||
|
if not ok and err then
|
||||||
|
DEBUG("err:", err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:getProgress()
|
||||||
|
if self.kosync_username and self.kosync_userkey then
|
||||||
|
local KOSyncClient = require("KOSyncClient")
|
||||||
|
local client = KOSyncClient:new{
|
||||||
|
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 and body.percentage then
|
||||||
|
local percentage = self:getLastPercent()
|
||||||
|
DEBUG("current progress", percentage)
|
||||||
|
if (body.percentage - percentage) > 0.0001 then
|
||||||
|
UIManager:show(ConfirmBox:new{
|
||||||
|
text = T(_("Sync to furthest location from '%1'?"),
|
||||||
|
body.device),
|
||||||
|
ok_callback = function()
|
||||||
|
self:syncToProgress(body.progress)
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
if not ok and err then
|
||||||
|
DEBUG("err:", err)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:onSaveSettings()
|
||||||
|
local settings = {
|
||||||
|
username = self.kosync_username,
|
||||||
|
userkey = self.kosync_userkey,
|
||||||
|
}
|
||||||
|
G_reader_settings:saveSetting("kosync", settings)
|
||||||
|
end
|
||||||
|
|
||||||
|
function KOSync:onCloseDocument()
|
||||||
|
DEBUG("on close document")
|
||||||
|
self:updateProgress()
|
||||||
|
end
|
||||||
|
|
||||||
|
return KOSync
|
||||||
@@ -5,8 +5,8 @@ require "defaults"
|
|||||||
pcall(dofile, "defaults.persistent.lua")
|
pcall(dofile, "defaults.persistent.lua")
|
||||||
|
|
||||||
-- set search path for 'require()'
|
-- set search path for 'require()'
|
||||||
package.path = "common/?.lua;frontend/?.lua;" .. package.path
|
package.path = "common/?.lua;frontend/?.lua;rocks/share/lua/5.1/?.lua;" .. package.path
|
||||||
package.cpath = "common/?.so;common/?.dll;/usr/lib/lua/?.so;" .. package.cpath
|
package.cpath = "common/?.so;common/?.dll;/usr/lib/lua/?.so;rocks/lib/lua/5.1/?.so;" .. package.cpath
|
||||||
|
|
||||||
-- set search path for 'ffi.load()'
|
-- set search path for 'ffi.load()'
|
||||||
local ffi = require("ffi")
|
local ffi = require("ffi")
|
||||||
|
|||||||
178
spec/unit/kosync_spec.lua
Normal file
178
spec/unit/kosync_spec.lua
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package.path = "rocks/share/lua/5.1/?.lua;" .. package.path
|
||||||
|
package.cpath = "rocks/lib/lua/5.1/?.so;" .. package.cpath
|
||||||
|
require("commonrequire")
|
||||||
|
local UIManager = require("ui/uimanager")
|
||||||
|
local HTTPClient = require("httpclient")
|
||||||
|
local DEBUG = require("dbg")
|
||||||
|
local md5 = require("MD5")
|
||||||
|
DEBUG:turnOn()
|
||||||
|
|
||||||
|
local service = [[
|
||||||
|
{
|
||||||
|
"base_url" : "https://192.168.1.101:7200",
|
||||||
|
"name" : "api",
|
||||||
|
"methods" : {
|
||||||
|
"register" : {
|
||||||
|
"path" : "/users/create",
|
||||||
|
"method" : "POST",
|
||||||
|
"required_params" : [
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
],
|
||||||
|
"payload" : [
|
||||||
|
"username",
|
||||||
|
"password",
|
||||||
|
],
|
||||||
|
"expected_status" : [201, 402]
|
||||||
|
},
|
||||||
|
"authorize" : {
|
||||||
|
"path" : "/users/auth",
|
||||||
|
"method" : "GET",
|
||||||
|
"expected_status" : [200, 401]
|
||||||
|
},
|
||||||
|
"update_progress" : {
|
||||||
|
"path" : "/syncs/progress",
|
||||||
|
"method" : "PUT",
|
||||||
|
"required_params" : [
|
||||||
|
"document",
|
||||||
|
"progress",
|
||||||
|
"percentage",
|
||||||
|
"device",
|
||||||
|
],
|
||||||
|
"payload" : [
|
||||||
|
"document",
|
||||||
|
"progress",
|
||||||
|
"percentage",
|
||||||
|
"device",
|
||||||
|
],
|
||||||
|
"expected_status" : [200, 202, 401]
|
||||||
|
},
|
||||||
|
"get_progress" : {
|
||||||
|
"path" : "/syncs/progress/:document",
|
||||||
|
"method" : "GET",
|
||||||
|
"required_params" : [
|
||||||
|
"document",
|
||||||
|
],
|
||||||
|
"expected_status" : [200, 401]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
|
||||||
|
describe("KOSync modules #notest #nocov", function()
|
||||||
|
local Spore = require("Spore")
|
||||||
|
local client = Spore.new_from_string(service)
|
||||||
|
package.loaded['Spore.Middleware.GinClient'] = {}
|
||||||
|
require('Spore.Middleware.GinClient').call = function(self, req)
|
||||||
|
req.headers['accept'] = "application/vnd.koreader.v1+json"
|
||||||
|
end
|
||||||
|
package.loaded['Spore.Middleware.KOSyncAuth'] = {}
|
||||||
|
require('Spore.Middleware.KOSyncAuth').call = function(args, req)
|
||||||
|
req.headers['x-auth-user'] = args.username
|
||||||
|
req.headers['x-auth-key'] = args.userkey
|
||||||
|
end
|
||||||
|
-- password should be hashed before submitting to server
|
||||||
|
local username, password = "koreader", md5:sum("koreader")
|
||||||
|
-- fake progress data
|
||||||
|
local doc, percentage, progress, device =
|
||||||
|
"41cce710f34e5ec21315e19c99821415", -- fast digest of the document
|
||||||
|
0.356, -- percentage of the progress
|
||||||
|
"69", -- page number or xpointer
|
||||||
|
"my kpw" -- device name
|
||||||
|
it("should create new user", function()
|
||||||
|
client:reset_middlewares()
|
||||||
|
client:enable('Format.JSON')
|
||||||
|
client:enable("GinClient")
|
||||||
|
local ok, res = pcall(function()
|
||||||
|
return client:register({
|
||||||
|
username = username,
|
||||||
|
password = password,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
if res.status == 200 then
|
||||||
|
DEBUG("register successful to ", res.body.username)
|
||||||
|
elseif res.status == 402 then
|
||||||
|
DEBUG("register unsuccessful: ", res.body.message)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
DEBUG("Please retry later", res)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
it("should authorize user", function()
|
||||||
|
client:reset_middlewares()
|
||||||
|
client:enable('Format.JSON')
|
||||||
|
client:enable("GinClient")
|
||||||
|
client:enable("KOSyncAuth", {
|
||||||
|
username = username,
|
||||||
|
userkey = password,
|
||||||
|
})
|
||||||
|
local ok, res = pcall(function()
|
||||||
|
return client:authorize()
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
if res.status == 200 then
|
||||||
|
assert.are.same("OK", res.body.authorized)
|
||||||
|
else
|
||||||
|
DEBUG(res.body)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
DEBUG("Please retry later", res)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
it("should update progress", function()
|
||||||
|
client:reset_middlewares()
|
||||||
|
client:enable('Format.JSON')
|
||||||
|
client:enable("GinClient")
|
||||||
|
client:enable("KOSyncAuth", {
|
||||||
|
username = username,
|
||||||
|
userkey = password,
|
||||||
|
})
|
||||||
|
local ok, res = pcall(function()
|
||||||
|
return client:update_progress({
|
||||||
|
document = doc,
|
||||||
|
progress = progress,
|
||||||
|
percentage = percentage,
|
||||||
|
device = device,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
if res.status == 200 then
|
||||||
|
local result = res.body
|
||||||
|
assert.are.same(progress, result.progress)
|
||||||
|
assert.are.same(percentage, result.percentage)
|
||||||
|
assert.are.same(device, result.device)
|
||||||
|
else
|
||||||
|
DEBUG(res.body.message)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
DEBUG("Please retry later", res)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
it("should get progress", function()
|
||||||
|
client:reset_middlewares()
|
||||||
|
client:enable('Format.JSON')
|
||||||
|
client:enable("GinClient")
|
||||||
|
client:enable("KOSyncAuth", {
|
||||||
|
username = username,
|
||||||
|
userkey = password,
|
||||||
|
})
|
||||||
|
local ok, res = pcall(function()
|
||||||
|
return client:get_progress({
|
||||||
|
document = doc,
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
if ok then
|
||||||
|
if res.status == 200 then
|
||||||
|
local result = res.body
|
||||||
|
assert.are.same(progress, result.progress)
|
||||||
|
assert.are.same(percentage, result.percentage)
|
||||||
|
assert.are.same(device, result.device)
|
||||||
|
else
|
||||||
|
DEBUG(res.body.message)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
DEBUG("Please retry later", res)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
@@ -55,12 +55,35 @@ describe("Lua Spore modules #nocov", function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("Lua Spore modules with async request #nocov", function()
|
describe("Lua Spore modules with async http request #nocov", function()
|
||||||
local Spore = require("Spore")
|
local Spore = require("Spore")
|
||||||
local client = Spore.new_from_string(service)
|
local client = Spore.new_from_string(service)
|
||||||
client:enable("Format.JSON")
|
|
||||||
package.loaded['Spore.Middleware.Async'] = {}
|
|
||||||
local async_http_client = HTTPClient:new()
|
local async_http_client = HTTPClient:new()
|
||||||
|
package.loaded['Spore.Middleware.AsyncHTTP'] = {}
|
||||||
|
require('Spore.Middleware.AsyncHTTP').call = function(args, req)
|
||||||
|
req:finalize()
|
||||||
|
local result
|
||||||
|
async_http_client:request({
|
||||||
|
url = req.url,
|
||||||
|
method = req.method,
|
||||||
|
body = req.env.spore.payload,
|
||||||
|
on_headers = function(headers)
|
||||||
|
for header, value in pairs(req.headers) do
|
||||||
|
if type(header) == 'string' then
|
||||||
|
headers:add(header, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
}, function(res)
|
||||||
|
result = res
|
||||||
|
-- Turbo HTTP client uses code instead of status
|
||||||
|
-- change to status so that Spore can understand
|
||||||
|
result.status = res.code
|
||||||
|
coroutine.resume(args.thread)
|
||||||
|
UIManager.INPUT_TIMEOUT = 100 -- no need in production
|
||||||
|
end)
|
||||||
|
return coroutine.create(function() coroutine.yield(result) end)
|
||||||
|
end
|
||||||
it("should complete GET request", function()
|
it("should complete GET request", function()
|
||||||
UIManager:quit()
|
UIManager:quit()
|
||||||
local co = coroutine.create(function()
|
local co = coroutine.create(function()
|
||||||
@@ -69,22 +92,10 @@ describe("Lua Spore modules with async request #nocov", function()
|
|||||||
UIManager:quit()
|
UIManager:quit()
|
||||||
assert.are.same(res.body.args, info)
|
assert.are.same(res.body.args, info)
|
||||||
end)
|
end)
|
||||||
require('Spore.Middleware.Async').call = function(self, req)
|
client:reset_middlewares()
|
||||||
req:finalize()
|
client:enable("Format.JSON")
|
||||||
local result
|
client:enable("AsyncHTTP", {thread = co})
|
||||||
async_http_client:request({
|
|
||||||
url = req.url,
|
|
||||||
method = req.method,
|
|
||||||
}, function(res)
|
|
||||||
result = res
|
|
||||||
coroutine.resume(co)
|
coroutine.resume(co)
|
||||||
UIManager.INPUT_TIMEOUT = 100 -- no need in production
|
|
||||||
end)
|
|
||||||
return coroutine.create(function() coroutine.yield(result) end)
|
|
||||||
end
|
|
||||||
client:enable("Async")
|
|
||||||
coroutine.resume(co)
|
|
||||||
UIManager.INPUT_TIMEOUT = 100
|
|
||||||
UIManager:runForever()
|
UIManager:runForever()
|
||||||
end)
|
end)
|
||||||
it("should complete POST request", function()
|
it("should complete POST request", function()
|
||||||
@@ -95,22 +106,10 @@ describe("Lua Spore modules with async request #nocov", function()
|
|||||||
UIManager:quit()
|
UIManager:quit()
|
||||||
assert.are.same(res.body.json, info)
|
assert.are.same(res.body.json, info)
|
||||||
end)
|
end)
|
||||||
require('Spore.Middleware.Async').call = function(self, req)
|
client:reset_middlewares()
|
||||||
req:finalize()
|
client:enable("Format.JSON")
|
||||||
local result
|
client:enable("AsyncHTTP", {thread = co})
|
||||||
async_http_client:request({
|
|
||||||
url = req.url,
|
|
||||||
method = req.method,
|
|
||||||
}, function(res)
|
|
||||||
result = res
|
|
||||||
coroutine.resume(co)
|
coroutine.resume(co)
|
||||||
UIManager.INPUT_TIMEOUT = 100 -- no need in production
|
|
||||||
end)
|
|
||||||
return coroutine.create(function() coroutine.yield(result) end)
|
|
||||||
end
|
|
||||||
client:enable("Async")
|
|
||||||
coroutine.resume(co)
|
|
||||||
UIManager.INPUT_TIMEOUT = 100
|
|
||||||
UIManager:runForever()
|
UIManager:runForever()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user