mirror of
https://github.com/koreader/koreader.git
synced 2025-12-13 20:36:53 +01:00
Update KOSyncClient
This commit is contained in:
@@ -99,7 +99,10 @@ function Document:discardChange()
|
||||
self.is_edited = false
|
||||
end
|
||||
|
||||
-- calculate partial digest of the document
|
||||
-- calculate partial digest of the document and store in its docsettings to avoid document saving
|
||||
-- feature to change its checksum.
|
||||
--
|
||||
-- To the calculating mechanism itself.
|
||||
-- since only PDF documents could be modified by KOReader by appending data
|
||||
-- at the end of the files when highlighting, we use a non-even sampling
|
||||
-- algorithm which samples with larger weight at file head and much smaller
|
||||
@@ -109,23 +112,31 @@ end
|
||||
-- 1048576, 4194304, 16777216, 67108864, 268435456 or 1073741824, appending data
|
||||
-- by highlighting in KOReader may change the digest value.
|
||||
function Document:fastDigest()
|
||||
local md5 = require("ffi/MD5")
|
||||
local lshift = bit.lshift
|
||||
if not self.file then return end
|
||||
local file = io.open(self.file, 'rb')
|
||||
if file then
|
||||
local step, size = 1024, 1024
|
||||
local m = md5.new()
|
||||
for i = -1, 10 do
|
||||
file:seek("set", lshift(step, 2*i))
|
||||
local sample = file:read(size)
|
||||
if sample then
|
||||
m:update(sample)
|
||||
else
|
||||
break
|
||||
local docsettings = require("docsettings"):open(self.file)
|
||||
local result = docsettings:readSetting("partial_md5_checksum")
|
||||
if not result then
|
||||
local md5 = require("ffi/MD5")
|
||||
local lshift = bit.lshift
|
||||
local step, size = 1024, 1024
|
||||
local m = md5.new()
|
||||
for i = -1, 10 do
|
||||
file:seek("set", lshift(step, 2*i))
|
||||
local sample = file:read(size)
|
||||
if sample then
|
||||
m:update(sample)
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
result = m:sum()
|
||||
docsettings:saveSetting("partial_md5_checksum", result)
|
||||
end
|
||||
docsettings:close()
|
||||
file:close()
|
||||
return m:sum()
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
33
frontend/random.lua
Normal file
33
frontend/random.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
-- A set of functions to extend math.random and math.randomseed.
|
||||
|
||||
local random = {}
|
||||
|
||||
-- Use current time as seed to randomlize.
|
||||
function random.seed()
|
||||
math.randomseed(os.time())
|
||||
end
|
||||
|
||||
random.seed()
|
||||
|
||||
-- Return a UUID (v4, random).
|
||||
function random.uuid(with_dash)
|
||||
local array = {}
|
||||
for i = 1, 16 do
|
||||
table.insert(array, math.random(256) - 1)
|
||||
end
|
||||
-- The 13th character should be 4.
|
||||
array[7] = bit.band(array[7], 79)
|
||||
array[7] = bit.bor(array[7], 64)
|
||||
-- The 17th character should be 8 / 9 / a / b.
|
||||
array[9] = bit.band(array[9], 191)
|
||||
array[9] = bit.bor(array[9], 128)
|
||||
if with_dash then
|
||||
return string.format("%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
|
||||
unpack(array))
|
||||
else
|
||||
return string.format("%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
unpack(array))
|
||||
end
|
||||
end
|
||||
|
||||
return random
|
||||
@@ -93,8 +93,15 @@ function KOSyncClient:authorize(username, password)
|
||||
end
|
||||
end
|
||||
|
||||
function KOSyncClient:update_progress(username, password,
|
||||
document, progress, percentage, device, callback)
|
||||
function KOSyncClient:update_progress(
|
||||
username,
|
||||
password,
|
||||
document,
|
||||
progress,
|
||||
percentage,
|
||||
device,
|
||||
device_id,
|
||||
callback)
|
||||
self.client:reset_middlewares()
|
||||
self.client:enable('Format.JSON')
|
||||
self.client:enable("GinClient")
|
||||
@@ -109,6 +116,7 @@ function KOSyncClient:update_progress(username, password,
|
||||
progress = progress,
|
||||
percentage = percentage,
|
||||
device = device,
|
||||
device_id = device_id,
|
||||
})
|
||||
end)
|
||||
if ok then
|
||||
@@ -123,8 +131,11 @@ function KOSyncClient:update_progress(username, password,
|
||||
if UIManager.looper then UIManager:setInputTimeout() end
|
||||
end
|
||||
|
||||
function KOSyncClient:get_progress(username, password,
|
||||
document, callback)
|
||||
function KOSyncClient:get_progress(
|
||||
username,
|
||||
password,
|
||||
document,
|
||||
callback)
|
||||
self.client:reset_middlewares()
|
||||
self.client:enable('Format.JSON')
|
||||
self.client:enable("GinClient")
|
||||
|
||||
@@ -28,12 +28,14 @@
|
||||
"progress",
|
||||
"percentage",
|
||||
"device",
|
||||
"device_id",
|
||||
],
|
||||
"payload" : [
|
||||
"document",
|
||||
"progress",
|
||||
"percentage",
|
||||
"device",
|
||||
"device_id",
|
||||
],
|
||||
"expected_status" : [200, 202, 401]
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ local DocSettings = require("docsettings")
|
||||
local NetworkMgr = require("ui/network/manager")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local Screen = require("device").screen
|
||||
local Device = require("device")
|
||||
local DeviceModel = require("device").model
|
||||
local Event = require("ui/event")
|
||||
local Math = require("optmath")
|
||||
local DEBUG = require("dbg")
|
||||
@@ -14,12 +14,6 @@ local T = require("ffi/util").template
|
||||
local _ = require("gettext")
|
||||
local md5 = require("ffi/MD5")
|
||||
|
||||
local l10n = {
|
||||
_("Unknown server error."),
|
||||
_("Unauthorized"),
|
||||
_("Username is already registered."),
|
||||
}
|
||||
|
||||
local KOSync = InputContainer:new{
|
||||
name = "kosync",
|
||||
title = _("Register/login to KOReader server"),
|
||||
@@ -31,12 +25,18 @@ function KOSync:init()
|
||||
self.kosync_username = settings.username
|
||||
self.kosync_userkey = settings.userkey
|
||||
self.kosync_auto_sync = not (settings.auto_sync == false)
|
||||
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)
|
||||
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()
|
||||
end
|
||||
|
||||
function KOSync:addToMainMenu(tab_item_table)
|
||||
@@ -55,19 +55,36 @@ function KOSync:addToMainMenu(tab_item_table)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Auto sync"),
|
||||
text = _("Auto sync now and future"),
|
||||
checked_func = function() return self.kosync_auto_sync end,
|
||||
callback = function()
|
||||
self.kosync_auto_sync = not self.kosync_auto_sync
|
||||
if self.kosync_auto_sync then
|
||||
-- since we will update the progress when closing document, we should pull
|
||||
-- current progress now to avoid to overwrite it silently.
|
||||
self:getProgress(true)
|
||||
else
|
||||
-- since we won't update the progress when closing document, we should push
|
||||
-- current progress now to avoid to lose it silently.
|
||||
self:updateProgress(true)
|
||||
end
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Sync now"),
|
||||
text = _("Push progress from this device"),
|
||||
enabled_func = function()
|
||||
return self.kosync_userkey ~= nil
|
||||
end,
|
||||
callback = function()
|
||||
self:updateProgress(true)
|
||||
end,
|
||||
},
|
||||
{
|
||||
text = _("Pull progress from other devices"),
|
||||
enabled_func = function()
|
||||
return self.kosync_userkey ~= nil
|
||||
end,
|
||||
callback = function()
|
||||
self:updateProgress()
|
||||
self:getProgress(true)
|
||||
end,
|
||||
},
|
||||
@@ -225,11 +242,15 @@ 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 self.ui.paging:getLastPercent()
|
||||
return roundPercent(self.ui.paging:getLastPercent())
|
||||
else
|
||||
return self.ui.rolling:getLastPercent()
|
||||
return roundPercent(self.ui.rolling:getLastPercent())
|
||||
end
|
||||
end
|
||||
|
||||
@@ -250,7 +271,21 @@ function KOSync:syncToProgress(progress)
|
||||
end
|
||||
end
|
||||
|
||||
function KOSync:updateProgress()
|
||||
local function promptLogin()
|
||||
UIManager:show(InfoMessage:new{
|
||||
text = _("Please register / login before using 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{
|
||||
@@ -260,15 +295,34 @@ function KOSync:updateProgress()
|
||||
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,
|
||||
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 and err then
|
||||
DEBUG("err:", err)
|
||||
if not ok then
|
||||
if manual then showSyncError() end
|
||||
if err then DEBUG("err:", err) end
|
||||
end
|
||||
elseif manual then
|
||||
promptLogin()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -280,34 +334,57 @@ function KOSync:getProgress(manual)
|
||||
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)
|
||||
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 progress = self:getLastProgress()
|
||||
local percentage = self:getLastPercent()
|
||||
DEBUG("current progress", percentage)
|
||||
if body.percentage > percentage
|
||||
and tostring(body.progress) ~= tostring(progress) then
|
||||
UIManager:show(ConfirmBox:new{
|
||||
text = T(_("Sync to furthest location %1% from device '%2'?"),
|
||||
Math.round(body.percentage*100), body.device),
|
||||
ok_callback = function()
|
||||
self:syncToProgress(body.progress)
|
||||
end,
|
||||
})
|
||||
elseif manual and body.progress == progress then
|
||||
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 %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 = _("Already synchronized."),
|
||||
text = _("No progress found for this document."),
|
||||
timeout = 3,
|
||||
})
|
||||
end
|
||||
elseif manual then
|
||||
showSyncError()
|
||||
end
|
||||
end)
|
||||
if not ok and err then
|
||||
DEBUG("err:", err)
|
||||
if not ok then
|
||||
if manual then showSyncError() end
|
||||
if err then DEBUG("err:", err) end
|
||||
end
|
||||
elseif manual then
|
||||
promptLogin()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -96,6 +96,11 @@ local lfs = require("libs/libkoreader-lfs")
|
||||
local UIManager = require("ui/uimanager")
|
||||
local Device = require("device")
|
||||
local Font = require("ui/font")
|
||||
local random = require("random")
|
||||
|
||||
if not G_reader_settings:readSetting("device_id") then
|
||||
G_reader_settings:saveSetting("device_id", random.uuid())
|
||||
end
|
||||
|
||||
-- read some global reader setting here:
|
||||
-- font
|
||||
|
||||
33
spec/unit/random_spec.lua
Normal file
33
spec/unit/random_spec.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
describe("random package tests", function()
|
||||
local random
|
||||
|
||||
local function is_magic_char(c)
|
||||
return c == "8" or c == "9" or c == "A" or c == "B"
|
||||
end
|
||||
|
||||
setup(function()
|
||||
random = require("frontend/random")
|
||||
end)
|
||||
|
||||
it("should generate uuid without dash", function()
|
||||
for i = 1, 10000 do
|
||||
local uuid = random.uuid()
|
||||
assert.Equals(uuid:len(), 32)
|
||||
assert.Equals(uuid:sub(13, 13), "4")
|
||||
assert.is_true(is_magic_char(uuid:sub(17, 17)))
|
||||
end
|
||||
end)
|
||||
|
||||
it("should generate uuid with dash", function()
|
||||
for i = 1, 10000 do
|
||||
local uuid = random.uuid(true)
|
||||
assert.Equals(uuid:len(), 36)
|
||||
assert.Equals(uuid:sub(9, 9), "-")
|
||||
assert.Equals(uuid:sub(14, 14), "-")
|
||||
assert.Equals(uuid:sub(19, 19), "-")
|
||||
assert.Equals(uuid:sub(24, 24), "-")
|
||||
assert.Equals(uuid:sub(15, 15), "4")
|
||||
assert.is_true(is_magic_char(uuid:sub(20, 20)))
|
||||
end
|
||||
end)
|
||||
end)
|
||||
Reference in New Issue
Block a user