2023-09-18 23:57:18 +00:00
|
|
|
local UIManager = require("ui/uimanager")
|
2023-09-20 12:26:44 +00:00
|
|
|
local http = require("socket.http")
|
|
|
|
local lfs = require("libs/libkoreader-lfs")
|
2023-09-18 23:57:18 +00:00
|
|
|
local logger = require("logger")
|
2023-09-20 12:26:44 +00:00
|
|
|
local ltn12 = require("ltn12")
|
|
|
|
local socket = require("socket")
|
|
|
|
local socketutil = require("socketutil")
|
2023-09-18 23:57:18 +00:00
|
|
|
|
|
|
|
-- Push/Pull
|
|
|
|
local SYNC_TIMEOUTS = {2, 5}
|
|
|
|
|
|
|
|
-- Login/Register
|
|
|
|
local AUTH_TIMEOUTS = {5, 10}
|
|
|
|
|
|
|
|
local SyncNinjaClient = {service_spec = nil, custom_url = nil}
|
|
|
|
|
|
|
|
function SyncNinjaClient:new(o)
|
|
|
|
if o == nil then o = {} end
|
|
|
|
setmetatable(o, self)
|
|
|
|
self.__index = self
|
|
|
|
if o.init then o:init() end
|
|
|
|
return o
|
|
|
|
end
|
|
|
|
|
|
|
|
function SyncNinjaClient:init()
|
|
|
|
local Spore = require("Spore")
|
|
|
|
self.client = Spore.new_from_spec(self.service_spec,
|
|
|
|
{base_url = self.custom_url})
|
|
|
|
package.loaded["Spore.Middleware.GinClient"] = {}
|
|
|
|
require("Spore.Middleware.GinClient").call = function(_, req)
|
|
|
|
req.headers["accept"] = "application/vnd.koreader.v1+json"
|
|
|
|
end
|
|
|
|
package.loaded["Spore.Middleware.SyncNinjaAuth"] = {}
|
|
|
|
require("Spore.Middleware.SyncNinjaAuth").call = function(args, req)
|
|
|
|
req.headers["x-auth-user"] = args.username
|
|
|
|
req.headers["x-auth-key"] = args.userkey
|
|
|
|
end
|
|
|
|
package.loaded["Spore.Middleware.AsyncHTTP"] = {}
|
|
|
|
require("Spore.Middleware.AsyncHTTP").call = function(args, req)
|
|
|
|
-- disable async http if Turbo looper is missing
|
|
|
|
if not UIManager.looper then return end
|
|
|
|
req:finalize()
|
|
|
|
local result
|
|
|
|
|
|
|
|
local turbo = require("turbo")
|
|
|
|
turbo.log.categories.success = false
|
|
|
|
turbo.log.categories.warning = false
|
|
|
|
|
|
|
|
local client = turbo.async.HTTPClient({verify_ca = false})
|
|
|
|
local res = coroutine.yield(client:fetch(request.url, {
|
|
|
|
url = req.url,
|
|
|
|
method = req.method,
|
|
|
|
body = req.env.spore.payload,
|
|
|
|
connect_timeout = 10,
|
|
|
|
request_timeout = 20,
|
|
|
|
on_headers = function(headers)
|
|
|
|
for header, value in pairs(req.headers) do
|
|
|
|
if type(header) == "string" then
|
|
|
|
headers:add(header, value)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
}))
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
|
|
|
-- return coroutine.create(function() coroutine.yield(result) end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
------------------------------------------
|
|
|
|
-------------- New Functions -------------
|
|
|
|
------------------------------------------
|
|
|
|
|
|
|
|
function SyncNinjaClient:check_activity(username, password, device_id, callback)
|
|
|
|
self.client:reset_middlewares()
|
|
|
|
self.client:enable("Format.JSON")
|
|
|
|
self.client:enable("GinClient")
|
|
|
|
self.client:enable("SyncNinjaAuth",
|
|
|
|
{username = username, userkey = password})
|
|
|
|
|
|
|
|
socketutil:set_timeout(SYNC_TIMEOUTS[1], SYNC_TIMEOUTS[2])
|
|
|
|
local co = coroutine.create(function()
|
|
|
|
local ok, res = pcall(function()
|
|
|
|
return self.client:check_activity({device_id = device_id})
|
|
|
|
end)
|
|
|
|
if ok then
|
|
|
|
callback(res.status == 200, res.body)
|
|
|
|
else
|
|
|
|
logger.dbg("SyncNinjaClient:check_activity failure:", res)
|
|
|
|
callback(false, res.body)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
self.client:enable("AsyncHTTP", {thread = co})
|
|
|
|
coroutine.resume(co)
|
|
|
|
if UIManager.looper then UIManager:setInputTimeout() end
|
|
|
|
socketutil:reset_timeout()
|
|
|
|
end
|
|
|
|
|
|
|
|
function SyncNinjaClient:add_activity(username, password, device_id, device,
|
|
|
|
activity, callback)
|
|
|
|
self.client:reset_middlewares()
|
|
|
|
self.client:enable("Format.JSON")
|
|
|
|
self.client:enable("GinClient")
|
|
|
|
self.client:enable("SyncNinjaAuth",
|
|
|
|
{username = username, userkey = password})
|
|
|
|
|
|
|
|
socketutil:set_timeout(SYNC_TIMEOUTS[1], SYNC_TIMEOUTS[2])
|
|
|
|
local co = coroutine.create(function()
|
|
|
|
local ok, res = pcall(function()
|
|
|
|
return self.client:add_activity({
|
|
|
|
device_id = device_id,
|
|
|
|
device = device,
|
|
|
|
activity = activity
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
|
|
|
|
if ok then
|
|
|
|
callback(res.status == 200, res.body)
|
|
|
|
else
|
|
|
|
logger.dbg("SyncNinjaClient:add_activity failure:", res)
|
|
|
|
callback(false, res.body)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
self.client:enable("AsyncHTTP", {thread = co})
|
|
|
|
coroutine.resume(co)
|
|
|
|
if UIManager.looper then UIManager:setInputTimeout() end
|
|
|
|
socketutil:reset_timeout()
|
|
|
|
end
|
|
|
|
|
|
|
|
function SyncNinjaClient:add_documents(username, password, documents, callback)
|
|
|
|
self.client:reset_middlewares()
|
|
|
|
self.client:enable("Format.JSON")
|
|
|
|
self.client:enable("GinClient")
|
|
|
|
self.client:enable("SyncNinjaAuth",
|
|
|
|
{username = username, userkey = password})
|
|
|
|
|
|
|
|
socketutil:set_timeout(SYNC_TIMEOUTS[1], SYNC_TIMEOUTS[2])
|
|
|
|
local co = coroutine.create(function()
|
|
|
|
local ok, res = pcall(function()
|
|
|
|
return self.client:add_documents({documents = documents})
|
|
|
|
end)
|
|
|
|
if ok then
|
|
|
|
callback(res.status == 200, res.body)
|
|
|
|
else
|
|
|
|
logger.dbg("SyncNinjaClient:add_documents failure:", res)(
|
|
|
|
"SyncNinjaClient:add_documents failure:", res)
|
|
|
|
callback(false, res.body)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
self.client:enable("AsyncHTTP", {thread = co})
|
|
|
|
coroutine.resume(co)
|
|
|
|
if UIManager.looper then UIManager:setInputTimeout() end
|
|
|
|
socketutil:reset_timeout()
|
|
|
|
end
|
|
|
|
|
|
|
|
function SyncNinjaClient:check_documents(username, password, device_id, device,
|
|
|
|
have, callback)
|
|
|
|
self.client:reset_middlewares()
|
|
|
|
self.client:enable("Format.JSON")
|
|
|
|
self.client:enable("GinClient")
|
|
|
|
self.client:enable("SyncNinjaAuth",
|
|
|
|
{username = username, userkey = password})
|
|
|
|
|
|
|
|
socketutil:set_timeout(SYNC_TIMEOUTS[1], SYNC_TIMEOUTS[2])
|
|
|
|
local co = coroutine.create(function()
|
|
|
|
local ok, res = pcall(function()
|
|
|
|
return self.client:check_documents({
|
|
|
|
device_id = device_id,
|
|
|
|
device = device,
|
|
|
|
have = have
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
if ok then
|
|
|
|
callback(res.status == 200, res.body)
|
|
|
|
else
|
|
|
|
logger.dbg("SyncNinjaClient:check_documents failure:", res)
|
|
|
|
callback(false, res.body)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
self.client:enable("AsyncHTTP", {thread = co})
|
|
|
|
coroutine.resume(co)
|
|
|
|
if UIManager.looper then UIManager:setInputTimeout() end
|
|
|
|
socketutil:reset_timeout()
|
|
|
|
end
|
|
|
|
|
|
|
|
function SyncNinjaClient:download_document(username, password, document,
|
|
|
|
callback)
|
|
|
|
self.client:reset_middlewares()
|
|
|
|
self.client:enable("Format.JSON")
|
|
|
|
self.client:enable("GinClient")
|
|
|
|
self.client:enable("SyncNinjaAuth",
|
|
|
|
{username = username, userkey = password})
|
|
|
|
|
|
|
|
local ok, res = pcall(function()
|
|
|
|
return self.client:download_document({document = document})
|
|
|
|
end)
|
|
|
|
if ok then
|
|
|
|
callback(res.status == 200, res.body)
|
|
|
|
else
|
|
|
|
logger.dbg("SyncNinjaClient:download_document failure:", res)
|
|
|
|
callback(false, res.body)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-09-20 12:26:44 +00:00
|
|
|
function SyncNinjaClient:upload_document(username, password, document, filepath,
|
2023-09-18 23:57:18 +00:00
|
|
|
callback)
|
2023-09-20 12:26:44 +00:00
|
|
|
-- Create URL
|
|
|
|
local url = self.custom_url ..
|
|
|
|
(self.custom_url:sub(-#"/") ~= "/" and "/" or "") ..
|
|
|
|
"api/ko/documents/" .. document .. "/file"
|
2023-09-18 23:57:18 +00:00
|
|
|
|
2023-09-20 12:26:44 +00:00
|
|
|
-- Track Length, Sources, and Boundary
|
|
|
|
local len = 0
|
|
|
|
local sources = {}
|
|
|
|
local boundary = "-----BoundaryePkpFF7tjBAqx29L"
|
|
|
|
|
|
|
|
-- Open File & Get Size
|
|
|
|
local file = io.open(filepath, "rb")
|
|
|
|
local file_size = lfs.attributes(filepath, 'size')
|
|
|
|
|
|
|
|
-- Insert File Start
|
|
|
|
local str_start = {}
|
|
|
|
table.insert(str_start, "--" .. boundary .. "\r\n")
|
|
|
|
table.insert(str_start, 'content-disposition: form-data; name="file";')
|
|
|
|
table.insert(str_start, 'filename="' .. "test" ..
|
|
|
|
'"\r\ncontent-type: application/octet-stream\r\n\r\n')
|
|
|
|
str_start = table.concat(str_start)
|
|
|
|
table.insert(sources, ltn12.source.string(str_start))
|
|
|
|
len = len + #str_start
|
|
|
|
|
|
|
|
-- Insert File
|
|
|
|
table.insert(sources, ltn12.source.file(file))
|
|
|
|
len = len + file_size
|
|
|
|
|
|
|
|
-- Insert File End
|
|
|
|
local str_end = "\r\n"
|
|
|
|
table.insert(sources, ltn12.source.string(str_end))
|
|
|
|
len = len + #str_end
|
|
|
|
|
|
|
|
-- Insert Multipart End
|
|
|
|
local str = string.format("--%s--\r\n", boundary)
|
|
|
|
table.insert(sources, ltn12.source.string(str))
|
|
|
|
len = len + #str
|
|
|
|
|
|
|
|
-- Execute Request
|
|
|
|
logger.dbg("SyncNinja: upload_document - Uploading [" .. len .. "]:",
|
|
|
|
filepath)
|
|
|
|
|
|
|
|
local resp = {}
|
|
|
|
local code, headers, status = socket.skip(1, http.request {
|
|
|
|
url = url,
|
|
|
|
method = 'PUT',
|
|
|
|
headers = {
|
|
|
|
["Content-Length"] = len,
|
|
|
|
['Content-Type'] = "multipart/form-data; boundary=" .. boundary,
|
|
|
|
['X-Auth-User'] = username,
|
|
|
|
['X-Auth-Key'] = password
|
|
|
|
},
|
|
|
|
source = ltn12.source.cat(unpack(sources)),
|
|
|
|
sink = ltn12.sink.table(resp)
|
|
|
|
})
|
|
|
|
|
|
|
|
if code ~= 200 then
|
2023-09-18 23:57:18 +00:00
|
|
|
logger.dbg("SyncNinjaClient:upload_document failure:", res)
|
|
|
|
end
|
2023-09-20 12:26:44 +00:00
|
|
|
callback(code == 200, resp)
|
2023-09-18 23:57:18 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
------------------------------------------
|
|
|
|
----------- Existing Functions -----------
|
|
|
|
------------------------------------------
|
|
|
|
|
|
|
|
function SyncNinjaClient:register(username, password)
|
|
|
|
self.client:reset_middlewares()
|
|
|
|
self.client:enable("Format.JSON")
|
|
|
|
self.client:enable("GinClient")
|
|
|
|
socketutil:set_timeout(AUTH_TIMEOUTS[1], AUTH_TIMEOUTS[2])
|
|
|
|
local ok, res = pcall(function()
|
|
|
|
return self.client:register({username = username, password = password})
|
|
|
|
end)
|
|
|
|
socketutil:reset_timeout()
|
|
|
|
if ok then
|
|
|
|
return res.status == 201, res.body
|
|
|
|
else
|
|
|
|
logger.dbg("SyncNinjaClient:register failure:", res)
|
|
|
|
return false, res.body
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function SyncNinjaClient:authorize(username, password)
|
|
|
|
self.client:reset_middlewares()
|
|
|
|
self.client:enable("Format.JSON")
|
|
|
|
self.client:enable("GinClient")
|
|
|
|
self.client:enable("SyncNinjaAuth",
|
|
|
|
{username = username, userkey = password})
|
|
|
|
socketutil:set_timeout(AUTH_TIMEOUTS[1], AUTH_TIMEOUTS[2])
|
|
|
|
local ok, res = pcall(function() return self.client:authorize() end)
|
|
|
|
socketutil:reset_timeout()
|
|
|
|
if ok then
|
|
|
|
return res.status == 200, res.body
|
|
|
|
else
|
|
|
|
logger.dbg("SyncNinjaClient:authorize failure:", res)
|
|
|
|
return false, res.body
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function SyncNinjaClient:update_progress(username, password, document, progress,
|
|
|
|
percentage, device, device_id, callback)
|
|
|
|
self.client:reset_middlewares()
|
|
|
|
self.client:enable("Format.JSON")
|
|
|
|
self.client:enable("GinClient")
|
|
|
|
self.client:enable("SyncNinjaAuth",
|
|
|
|
{username = username, userkey = password})
|
|
|
|
|
|
|
|
socketutil:set_timeout(SYNC_TIMEOUTS[1], SYNC_TIMEOUTS[2])
|
|
|
|
local co = coroutine.create(function()
|
|
|
|
local ok, res = pcall(function()
|
|
|
|
return self.client:update_progress({
|
|
|
|
document = document,
|
|
|
|
progress = tostring(progress),
|
|
|
|
percentage = percentage,
|
|
|
|
device = device,
|
|
|
|
device_id = device_id
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
if ok then
|
|
|
|
callback(res.status == 200, res.body)
|
|
|
|
else
|
|
|
|
logger.dbg("SyncNinjaClient:update_progress failure:", res)
|
|
|
|
callback(false, res.body)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
self.client:enable("AsyncHTTP", {thread = co})
|
|
|
|
coroutine.resume(co)
|
|
|
|
if UIManager.looper then UIManager:setInputTimeout() end
|
|
|
|
socketutil:reset_timeout()
|
|
|
|
end
|
|
|
|
|
|
|
|
function SyncNinjaClient:get_progress(username, password, document, callback)
|
|
|
|
self.client:reset_middlewares()
|
|
|
|
self.client:enable("Format.JSON")
|
|
|
|
self.client:enable("GinClient")
|
|
|
|
self.client:enable("SyncNinjaAuth",
|
|
|
|
{username = username, userkey = password})
|
|
|
|
|
|
|
|
socketutil:set_timeout(SYNC_TIMEOUTS[1], SYNC_TIMEOUTS[2])
|
|
|
|
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
|
|
|
|
logger.dbg("SyncNinjaClient:get_progress failure:", res)
|
|
|
|
callback(false, res.body)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
self.client:enable("AsyncHTTP", {thread = co})
|
|
|
|
coroutine.resume(co)
|
|
|
|
if UIManager.looper then UIManager:setInputTimeout() end
|
|
|
|
socketutil:reset_timeout()
|
|
|
|
end
|
|
|
|
|
|
|
|
return SyncNinjaClient
|