mirror of
				https://github.com/OTCv8/otclientv8.git
				synced 2025-10-31 10:56:24 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			278 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			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 | 
