2019-10-02 03:38:52 +02:00

317 lines
8.2 KiB
Lua

Updater = { }
Updater.maxRetries = 5
--[[
HOW IT WORKS:
1. init
2. show
3. generateChecksum and get checksums from url
4. compareChecksums
5. download files with different chekcums
6. call c++ update function
]]--
local filesUrl = ""
local updaterWindow = nil
local initialPanel = nil
local updatePanel = nil
local progressBar = nil
local updateProgressBar = nil
local downloadStatusLabel = nil
local downloadProgressBar = nil
local downloadRetries = 0
local generateChecksumsEvent = nil
local updateableFiles = nil
local binaryChecksum = nil
local binaryFile = ""
local fileChecksums = {}
local checksumIter = 0
local downloadIter = 0
local aborted = false
local statusData = nil
local thingsUpdate = {}
local toUpdate = {}
local thingsUpdateOptionalError = nil
local function onDownload(path, checksum, err)
if aborted then
return
end
if err then
if downloadRetries > Updater.maxRetries then
return updateError("Can't download file: " .. path .. ".\nError: " .. err)
else
downloadRetries = downloadRetries + 1
return downloadNextFile(true)
end
end
if statusData["files"][path] == nil then
return updateError("Invalid file path: " .. path)
elseif statusData["files"][path] ~= checksum then
return updateError("Invalid file checksum.\nFile: " .. path .. "\nShould be:\n" .. statusData["files"][path] .. "\nIs:\n" .. checksum)
end
downloadIter = downloadIter + 1
updateProgressBar:setPercent(math.ceil((100 * downloadIter) / #toUpdate))
downloadProgressBar:setPercent(100)
downloadProgressBar:setText("")
downloadNextFile(false)
end
local function onDownloadProgress(progress, speed)
downloadProgressBar:setPercent(progress)
downloadProgressBar:setText(speed .. " kbps")
end
local function gotStatus(data, err)
if err then
return updateError(err)
end
if data["error"] ~= nil and data["error"]:len() > 0 then
return updateError(data["error"])
end
if data["url"] == nil or data["files"] == nil or data["binary"] == nil then
return updateError("Invalid json data from server")
end
if data["things"] ~= nil then
for file, checksum in pairs(data["things"]) do
if #checksum > 1 then
for thingtype, thingdata in pairs(thingsUpdate) do
if string.match(file:lower(), thingdata[1]:lower()) then
data["files"][file] = checksum
break
end
end
end
end
end
statusData = data
if checksumIter == 100 then
compareChecksums()
end
end
-- public functions
function Updater.init()
updaterWindow = g_ui.displayUI('updater')
updaterWindow:hide()
initialPanel = updaterWindow:getChildById('initialPanel')
updatePanel = updaterWindow:getChildById('updatePanel')
progressBar = initialPanel:getChildById('progressBar')
updateProgressBar = updatePanel:getChildById('updateProgressBar')
downloadStatusLabel = updatePanel:getChildById('downloadStatusLabel')
downloadProgressBar = updatePanel:getChildById('downloadProgressBar')
updatePanel:hide()
scheduleEvent(Updater.show, 200)
end
function Updater.terminate()
updaterWindow:destroy()
updaterWindow = nil
removeEvent(generateChecksumsEvent)
end
local function clear()
removeEvent(generateChecksumsEvent)
updateableFiles = nil
binaryChecksum = nil
binaryFile = ""
fileChecksums = {}
checksumIter = 0
downloadIter = 0
aborted = false
statusData = nil
toUpdate = {}
progressBar:setPercent(0)
updateProgressBar:setPercent(0)
downloadProgressBar:setPercent(0)
downloadProgressBar:setText("")
end
function Updater.show()
if not g_resources.isLoadedFromArchive() or Services.updater == nil or Services.updater:len() < 4 then
return Updater.hide()
end
if updaterWindow:isVisible() then
return
end
updaterWindow:show()
updaterWindow:raise()
updaterWindow:focus()
if EnterGame then
EnterGame.hide()
end
clear()
updateableFiles = g_resources.listUpdateableFiles()
if #updateableFiles < 1 then
return updateError("Can't get list of files")
end
binaryChecksum = g_resources.selfChecksum():lower()
if binaryChecksum:len() ~= 32 then
return updateError("Invalid binary checksum: " .. binaryChecksum)
end
local data = {
version = APP_VERSION,
platform = g_window.getPlatformType(),
uid = G.UUID,
build_version = g_app.getVersion(),
build_revision = g_app.getBuildRevision(),
build_commit = g_app.getBuildCommit(),
build_date = g_app.getBuildDate(),
os = g_app.getOs(),
os_name = g_platform.getOSName()
}
HTTP.postJSON(Services.updater, data, gotStatus)
if generateChecksumsEvent == nil then
generateChecksumsEvent = scheduleEvent(generateChecksum, 5)
end
end
function Updater.isVisible()
return updaterWindow:isVisible()
end
function Updater.updateThings(things, optionalError)
thingsUpdate = things
thingsUpdateOptionalError = optionalError
Updater:show()
end
function Updater.hide()
updaterWindow:hide()
if thingsUpdateOptionalError then
local msgbox = displayErrorBox("Updater error", thingsUpdateOptionalError:trim())
msgbox.onOk = function() if EnterGame then EnterGame.show() end end
thingsUpdateOptionalError = nil
elseif EnterGame then
EnterGame.show()
end
end
function Updater.abort()
aborted = true
Updater:hide()
end
function generateChecksum()
local entries = #updateableFiles
local fromEntry = math.floor((checksumIter) * (entries / 100))
local toEntry = math.floor((checksumIter + 1) * (entries / 100))
if checksumIter == 99 then
toEntry = #updateableFiles
end
for i=fromEntry+1,toEntry do
local fileName = updateableFiles[i]
fileChecksums[fileName] = g_resources.fileChecksum(fileName):lower()
end
checksumIter = checksumIter + 1
if checksumIter == 100 then
generateChecksumsEvent = nil
gotChecksums()
else
progressBar:setPercent(math.ceil(checksumIter * 0.95))
generateChecksumsEvent = scheduleEvent(generateChecksum, 5)
end
end
function gotChecksums()
if statusData ~= nil then
compareChecksums()
end
end
function compareChecksums()
for file, checksum in pairs(statusData["files"]) do
checksum = checksum:lower()
if file == statusData["binary"] then
if binaryChecksum ~= checksum then
binaryFile = file
table.insert(toUpdate, binaryFile)
end
else
local localChecksum = fileChecksums[file]
if localChecksum ~= checksum then
table.insert(toUpdate, file)
end
end
end
if #toUpdate == 0 then
return upToDate()
end
-- outdated
filesUrl = statusData["url"]
initialPanel:hide()
updatePanel:show()
updatePanel:getChildById('updateStatusLabel'):setText(tr("Updating %i files", #toUpdate))
updaterWindow:setHeight(190)
downloadNextFile(false)
end
function upToDate()
Updater.hide()
end
function updateError(err)
Updater.hide()
local msgbox = displayErrorBox("Updater error", err)
msgbox.onOk = function() if EnterGame then EnterGame.show() end end
end
function urlencode(url)
url = url:gsub("\n", "\r\n")
url = url:gsub("([^%w ])", function(c) string.format("%%%02X", string.byte(c)) end)
url = url:gsub(" ", "+")
return url
end
function downloadNextFile(retry)
if aborted then
return
end
updaterWindow:show()
updaterWindow:raise()
updaterWindow:focus()
if downloadIter == #toUpdate then
return downloadingFinished()
end
if retry then
retry = " (" .. downloadRetries .. " retry)"
else
retry = ""
end
local file = toUpdate[downloadIter + 1]
downloadStatusLabel:setText(tr("Downloading %i of %i%s:\n%s", downloadIter + 1, #toUpdate, retry, file))
downloadProgressBar:setPercent(0)
downloadProgressBar:setText("")
HTTP.download(filesUrl .. urlencode(file), file, onDownload, onDownloadProgress)
end
function downloadingFinished()
thingsUpdateOptionalError = nil
UIMessageBox.display(tr("Success"), tr("Download complate.\nUpdating client..."), {}, nil, nil)
scheduleEvent(function()
local files = {}
for file, checksum in pairs(statusData["files"]) do
table.insert(files, file)
end
g_settings.save()
g_resources.updateClient(files, binaryFile)
g_app.quick_exit()
end, 1000)
end