[fix] large files cause OOM

This commit is contained in:
Evan Reichard 2023-09-20 08:26:44 -04:00
parent d02f8c324f
commit 240b3a2b67
2 changed files with 86 additions and 38 deletions

View File

@ -1,6 +1,10 @@
local UIManager = require("ui/uimanager") local UIManager = require("ui/uimanager")
local socketutil = require("socketutil") local http = require("socket.http")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger") local logger = require("logger")
local ltn12 = require("ltn12")
local socket = require("socket")
local socketutil = require("socketutil")
-- Push/Pull -- Push/Pull
local SYNC_TIMEOUTS = {2, 5} local SYNC_TIMEOUTS = {2, 5}
@ -199,23 +203,68 @@ function SyncNinjaClient:download_document(username, password, document,
end end
end end
function SyncNinjaClient:upload_document(username, password, document, file, function SyncNinjaClient:upload_document(username, password, document, filepath,
callback) callback)
self.client:reset_middlewares() -- Create URL
self.client:enable("Format.JSON") local url = self.custom_url ..
self.client:enable("GinClient") (self.custom_url:sub(-#"/") ~= "/" and "/" or "") ..
self.client:enable("SyncNinjaAuth", "api/ko/documents/" .. document .. "/file"
{username = username, userkey = password})
local ok, res = pcall(function() -- Track Length, Sources, and Boundary
return self.client:upload_document({document = document, file = file}) local len = 0
end) local sources = {}
if ok then local boundary = "-----BoundaryePkpFF7tjBAqx29L"
callback(res.status == 200, res.body)
else -- 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
logger.dbg("SyncNinjaClient:upload_document failure:", res) logger.dbg("SyncNinjaClient:upload_document failure:", res)
callback(false, res.body)
end end
callback(code == 200, resp)
end end
------------------------------------------ ------------------------------------------

View File

@ -1,7 +1,6 @@
local ConfirmBox = require("ui/widget/confirmbox") local ConfirmBox = require("ui/widget/confirmbox")
local DataStorage = require("datastorage") local DataStorage = require("datastorage")
local Device = require("device") local Device = require("device")
local Dispatcher = require("dispatcher")
local DocSettings = require("docsettings") local DocSettings = require("docsettings")
local InfoMessage = require("ui/widget/infomessage") local InfoMessage = require("ui/widget/infomessage")
local MultiInputDialog = require("ui/widget/multiinputdialog") local MultiInputDialog = require("ui/widget/multiinputdialog")
@ -15,11 +14,6 @@ local _ = require("gettext")
local logger = require("logger") local logger = require("logger")
local md5 = require("ffi/sha2").md5 local md5 = require("ffi/sha2").md5
-- TODO:
-- - Handle ReadHistory missing files (statistics.sqlite3, bookinfo_cache.sqlite3)
-- - Handle document uploads (Manual push only, warning saying this may take awhile)
-- - Configure activity bulk size? 1000, 5000, 10000? Separate manual settings to upload ALL?
------------------------------------------ ------------------------------------------
------------ Helper Functions ------------ ------------ Helper Functions ------------
------------------------------------------ ------------------------------------------
@ -574,9 +568,10 @@ function SyncNinja:performSync(interactive)
self:checkActivity(interactive) self:checkActivity(interactive)
self:checkDocuments(interactive) self:checkDocuments(interactive)
-- Notify
if interactive == true then if interactive == true then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
text = _("SyncNinja: Manual Sync Success"), text = _("SyncNinja: Manual Sync Initiated"),
timeout = 3 timeout = 3
}) })
end end
@ -634,7 +629,6 @@ function SyncNinja:uploadActivity(activity_data, interactive)
timeout = 3 timeout = 3
}) })
end end
return logger.dbg("SyncNinja: uploadActivity Error:", dump(body)) return logger.dbg("SyncNinja: uploadActivity Error:", dump(body))
end end
end end
@ -769,7 +763,6 @@ function SyncNinja:uploadDocumentFiles(doc_metadata, interactive)
if self.settings.sync_document_files ~= true then return end if self.settings.sync_document_files ~= true then return end
if interactive ~= true then return end if interactive ~= true then return end
-- API Callback Function
local callback_func = function(ok, body) local callback_func = function(ok, body)
if not ok then if not ok then
UIManager:show(InfoMessage:new{ UIManager:show(InfoMessage:new{
@ -787,24 +780,30 @@ function SyncNinja:uploadDocumentFiles(doc_metadata, interactive)
text = _("Uploading Documents - Please Wait...") text = _("Uploading Documents - Please Wait...")
}) })
-- API Client UIManager:nextTick(function()
local SyncNinjaClient = require("SyncNinjaClient") -- API Client
local client = SyncNinjaClient:new{ local SyncNinjaClient = require("SyncNinjaClient")
custom_url = self.settings.server, local client = SyncNinjaClient:new{
service_spec = self.path .. "/api.json" custom_url = self.settings.server,
} service_spec = self.path .. "/api.json"
}
for _, v in pairs(doc_metadata) do for _, v in pairs(doc_metadata) do
if v.filepath ~= nil then if v.filepath ~= nil then
-- TODO: Partial File Uploads (Resolve: OOM Issue) local ok, err = pcall(client.upload_document, client,
local ok, err = pcall(client.upload_document, client, self.settings.username,
self.settings.username, self.settings.password, v.id,
self.settings.password, v.id, v.filepath, v.filepath, callback_func)
callback_func) else
logger.dbg("SyncNinja: uploadDocumentFiles - no file for:",
v.id)
end
end end
end
UIManager:show(InfoMessage:new{text = _("Uploading Documents Complete")}) UIManager:show(InfoMessage:new{
text = _("Uploading Documents Complete")
})
end)
end end
UIManager:show(ConfirmBox:new{ UIManager:show(ConfirmBox:new{