otclientv8/modules/updater/updater.lua

278 lines
8.0 KiB
Lua

Updater = { }
Updater.maxRetries = 5
--[[
]]--
local updaterWindow
local loadModulesFunction
local scheduledEvent
local httpOperationId = 0
local function onLog(level, message, time)
if level == LogError then
Updater.error(message)
g_logger.setOnLog(nil)
end
end
local function initAppWindow()
if g_resources.getLayout() == "mobile" then
g_window.setMinimumSize({ width = 640, height = 360 })
else
g_window.setMinimumSize({ width = 800, height = 640 })
end
-- window size
local size = { width = 1024, height = 600 }
size = g_settings.getSize('window-size', size)
g_window.resize(size)
-- window position, default is the screen center
local displaySize = g_window.getDisplaySize()
local defaultPos = { x = (displaySize.width - size.width)/2,
y = (displaySize.height - size.height)/2 }
local pos = g_settings.getPoint('window-pos', defaultPos)
pos.x = math.max(pos.x, 0)
pos.y = math.max(pos.y, 0)
g_window.move(pos)
-- window maximized?
local maximized = g_settings.getBoolean('window-maximized', false)
if maximized then g_window.maximize() end
g_window.setTitle(g_app.getName())
g_window.setIcon('/images/clienticon')
if g_app.isMobile() then
scheduleEvent(function()
g_app.scale(5.0)
end, 10)
end
end
local function loadModules()
if loadModulesFunction then
local tmpLoadFunc = loadModulesFunction
loadModulesFunction = nil
tmpLoadFunc()
end
end
local function downloadFiles(url, files, index, retries, doneCallback)
if not updaterWindow then return end
local entry = files[index]
if not entry then -- finished
return doneCallback()
end
local file = entry[1]
local file_checksum = entry[2]
if retries > 0 then
updaterWindow.downloadStatus:setText(tr("Downloading (%i retry):\n%s", retries, file))
else
updaterWindow.downloadStatus:setText(tr("Downloading:\n%s", file))
end
updaterWindow.downloadProgress:setPercent(0)
updaterWindow.mainProgress:setPercent(math.floor(100 * index / #files))
httpOperationId = HTTP.download(url .. file, file,
function (file, checksum, err)
if not err and checksum ~= file_checksum then
err = "Invalid checksum of: " .. file .. ".\nShould be " .. file_checksum .. ", is: " .. checksum
end
if err then
if retries >= Updater.maxRetries then
Updater.error("Can't download file: " .. file .. ".\nError: " .. err)
else
scheduledEvent = scheduleEvent(function()
downloadFiles(url, files, index, retries + 1, doneCallback)
end, 250)
end
return
end
downloadFiles(url, files, index + 1, 0, doneCallback)
end,
function (progress, speed)
updaterWindow.downloadProgress:setPercent(progress)
updaterWindow.downloadProgress:setText(speed .. " kbps")
end)
end
local function updateFiles(data, keepCurrentFiles)
if not updaterWindow then return end
if type(data) ~= "table" then
return Updater.error("Invalid data from updater api (not table)")
end
if type(data["error"]) == 'string' and data["error"]:len() > 0 then
return Updater.error(data["error"])
end
if not data["files"] or type(data["url"]) ~= 'string' or data["url"]:len() < 4 then
return Updater.error("Invalid data from updater api: " .. json.encode(data, 2))
end
if data["keepFiles"] then
keepCurrentFiles = true
end
local newFiles = false
local finalFiles = {}
local localFiles = g_resources.filesChecksums()
local toUpdate = {}
-- keep all files or files from data/things
for file, checksum in pairs(localFiles) do
if keepCurrentFiles or string.find(file, "data/things") then
table.insert(finalFiles, file)
end
end
-- update files
for file, checksum in pairs(data["files"]) do
table.insert(finalFiles, file)
if not localFiles[file] or localFiles[file] ~= checksum then
table.insert(toUpdate, {file, checksum})
newFiles = true
end
end
-- update binary
local binary = nil
if type(data["binary"]) == "table" and data["binary"]["file"]:len() > 1 then
local selfChecksum = g_resources.selfChecksum()
if selfChecksum:len() > 0 and selfChecksum ~= data["binary"]["checksum"] then
binary = data["binary"]["file"]
table.insert(toUpdate, {binary, data["binary"]["checksum"]})
end
end
if #toUpdate == 0 then -- nothing to update
updaterWindow.mainProgress:setPercent(100)
scheduledEvent = scheduleEvent(Updater.abort, 20)
return
end
-- update of some files require full client restart
local forceRestart = false
local reloadModules = false
local forceRestartPattern = {"init.lua", "corelib", "updater", "otmod"}
for _, file in ipairs(toUpdate) do
for __, pattern in ipairs(forceRestartPattern) do
if string.find(file[1], pattern) then
forceRestart = true
end
if not string.find(file[1], "data/things") then
reloadModules = true
end
end
end
updaterWindow.status:setText(tr("Updating %i files", #toUpdate))
updaterWindow.mainProgress:setPercent(0)
updaterWindow.downloadProgress:setPercent(0)
updaterWindow.downloadProgress:show()
updaterWindow.downloadStatus:show()
updaterWindow.changeUrlButton:hide()
downloadFiles(data["url"], toUpdate, 1, 0, function()
updaterWindow.status:setText(tr("Updating client (may take few seconds)"))
updaterWindow.mainProgress:setPercent(100)
updaterWindow.downloadProgress:hide()
updaterWindow.downloadStatus:hide()
scheduledEvent = scheduleEvent(function()
local restart = binary or (not loadModulesFunction and reloadModules) or forceRestart
if newFiles then
g_resources.updateData(finalFiles, not restart)
end
if binary then
g_resources.updateExecutable(binary)
end
if restart then
g_app.restart()
else
if reloadModules then
g_modules.reloadModules()
end
Updater.abort()
end
end, 100)
end)
end
-- public functions
function Updater.init(loadModulesFunc)
g_logger.setOnLog(onLog)
loadModulesFunction = loadModulesFunc
initAppWindow()
Updater.check()
end
function Updater.terminate()
loadModulesFunction = nil
Updater.abort()
end
function Updater.abort()
HTTP.cancel(httpOperationId)
removeEvent(scheduledEvent)
if updaterWindow then
updaterWindow:destroy()
updaterWindow = nil
end
loadModules()
end
function Updater.check(args)
if updaterWindow then return end
updaterWindow = g_ui.displayUI('updater')
updaterWindow:show()
updaterWindow:focus()
updaterWindow:raise()
local updateData = nil
local function progressUpdater(value)
removeEvent(scheduledEvent)
if value == 100 then
return Updater.error(tr("Timeout"))
end
if updateData and (value > 60 or (not g_app.isMobile() or not ALLOW_CUSTOM_SERVERS or not loadModulesFunc)) then -- gives 3s to set custom updater for mobile version
return updateFiles(updateData)
end
scheduledEvent = scheduleEvent(function() progressUpdater(value + 1) end, 50)
updaterWindow.mainProgress:setPercent(value)
end
progressUpdater(0)
httpOperationId = HTTP.postJSON(Services.updater, {
version = APP_VERSION,
build = g_app.getVersion(),
os = g_app.getOs(),
platform = g_window.getPlatformType(),
args = args or {}
}, function(data, err)
if err then
return Updater.error(err)
end
updateData = data
end)
end
function Updater.error(message)
removeEvent(scheduledEvent)
if not updaterWindow then return end
displayErrorBox(tr("Updater Error"), message).onOk = function()
Updater.abort()
end
end
function Updater.changeUrl()
removeEvent(scheduledEvent)
modules.client_textedit.edit(Services.updater, {title="Enter updater url", width=300}, function(newUrl)
if updaterWindow and newUrl:len() > 4 then
Services.updater = newUrl
end
if updaterWindow then
updaterWindow:destroy()
updaterWindow = nil
end
Updater.check()
end)
end