updater.lua

main 278 lines · 7.9 KB Raw
Alex Alex Commit Client Upload Part 2 19/10/2026 00:06
1Updater = { }
2
3Updater.maxRetries = 5
4
5--[[
6
7]]--
8
9local updaterWindow
10local loadModulesFunction
11local scheduledEvent
12local httpOperationId = 0
13
14local function onLog(level, message, time)
15 if level == LogError then
16 Updater.error(message)
17 g_logger.setOnLog(nil)
18 end
19end
20
21local function initAppWindow()
22 if g_resources.getLayout() == "mobile" then
23 g_window.setMinimumSize({ width = 640, height = 360 })
24 else
25 g_window.setMinimumSize({ width = 800, height = 640 })
26 end
27
28 -- window size
29 local size = { width = 1024, height = 600 }
30 size = g_settings.getSize('window-size', size)
31 g_window.resize(size)
32
33 -- window position, default is the screen center
34 local displaySize = g_window.getDisplaySize()
35 local defaultPos = { x = (displaySize.width - size.width)/2,
36 y = (displaySize.height - size.height)/2 }
37 local pos = g_settings.getPoint('window-pos', defaultPos)
38 pos.x = math.max(pos.x, 0)
39 pos.y = math.max(pos.y, 0)
40 g_window.move(pos)
41
42 -- window maximized?
43 local maximized = g_settings.getBoolean('window-maximized', false)
44 if maximized then g_window.maximize() end
45
46 g_window.setTitle(g_app.getName())
47 g_window.setIcon('/images/clienticon')
48
49 if g_app.isMobile() then
50 scheduleEvent(function()
51 g_app.scale(5.0)
52 end, 10)
53 end
54end
55
56local function loadModules()
57 if loadModulesFunction then
58 local tmpLoadFunc = loadModulesFunction
59 loadModulesFunction = nil
60 tmpLoadFunc()
61 end
62end
63
64local function downloadFiles(url, files, index, retries, doneCallback)
65 if not updaterWindow then return end
66 local entry = files[index]
67 if not entry then -- finished
68 return doneCallback()
69 end
70 local file = entry[1]
71 local file_checksum = entry[2]
72
73 if retries > 0 then
74 updaterWindow.downloadStatus:setText(tr("Downloading (%i retry):\n%s", retries, file))
75 else
76 updaterWindow.downloadStatus:setText(tr("Downloading:\n%s", file))
77 end
78 updaterWindow.downloadProgress:setPercent(0)
79 updaterWindow.mainProgress:setPercent(math.floor(100 * index / #files))
80
81 httpOperationId = HTTP.download(url .. file, file,
82 function (file, checksum, err)
83 if not err and checksum ~= file_checksum then
84 err = "Invalid checksum of: " .. file .. ".\nShould be " .. file_checksum .. ", is: " .. checksum
85 end
86 if err then
87 if retries >= Updater.maxRetries then
88 Updater.error("Can't download file: " .. file .. ".\nError: " .. err)
89 else
90 scheduledEvent = scheduleEvent(function()
91 downloadFiles(url, files, index, retries + 1, doneCallback)
92 end, 250)
93 end
94 return
95 end
96 downloadFiles(url, files, index + 1, 0, doneCallback)
97 end,
98 function (progress, speed)
99 updaterWindow.downloadProgress:setPercent(progress)
100 updaterWindow.downloadProgress:setText(speed .. " kbps")
101 end)
102end
103
104local function updateFiles(data, keepCurrentFiles)
105 if not updaterWindow then return end
106 if type(data) ~= "table" then
107 return Updater.error("Invalid data from updater api (not table)")
108 end
109 if type(data["error"]) == 'string' and data["error"]:len() > 0 then
110 return Updater.error(data["error"])
111 end
112 if not data["files"] or type(data["url"]) ~= 'string' or data["url"]:len() < 4 then
113 return Updater.error("Invalid data from updater api: " .. json.encode(data, 2))
114 end
115 if data["keepFiles"] then
116 keepCurrentFiles = true
117 end
118
119 local newFiles = false
120 local finalFiles = {}
121 local localFiles = g_resources.filesChecksums()
122 local toUpdate = {}
123 -- keep all files or files from data/things
124 for file, checksum in pairs(localFiles) do
125 if keepCurrentFiles or string.find(file, "data/things") then
126 table.insert(finalFiles, file)
127 end
128 end
129 -- update files
130 for file, checksum in pairs(data["files"]) do
131 table.insert(finalFiles, file)
132 if not localFiles[file] or localFiles[file] ~= checksum then
133 table.insert(toUpdate, {file, checksum})
134 newFiles = true
135 end
136 end
137 -- update binary
138 local binary = nil
139 if type(data["binary"]) == "table" and data["binary"]["file"]:len() > 1 then
140 local selfChecksum = g_resources.selfChecksum()
141 if selfChecksum:len() > 0 and selfChecksum ~= data["binary"]["checksum"] then
142 binary = data["binary"]["file"]
143 table.insert(toUpdate, {binary, data["binary"]["checksum"]})
144 end
145 end
146
147 if #toUpdate == 0 then -- nothing to update
148 updaterWindow.mainProgress:setPercent(100)
149 scheduledEvent = scheduleEvent(Updater.abort, 20)
150 return
151 end
152
153 -- update of some files require full client restart
154 local forceRestart = false
155 local reloadModules = false
156 local forceRestartPattern = {"init.lua", "corelib", "updater", "otmod"}
157 for _, file in ipairs(toUpdate) do
158 for __, pattern in ipairs(forceRestartPattern) do
159 if string.find(file[1], pattern) then
160 forceRestart = true
161 end
162 if not string.find(file[1], "data/things") then
163 reloadModules = true
164 end
165 end
166 end
167
168 updaterWindow.status:setText(tr("Updating %i files", #toUpdate))
169 updaterWindow.mainProgress:setPercent(0)
170 updaterWindow.downloadProgress:setPercent(0)
171 updaterWindow.downloadProgress:show()
172 updaterWindow.downloadStatus:show()
173 updaterWindow.changeUrlButton:hide()
174 downloadFiles(data["url"], toUpdate, 1, 0, function()
175 updaterWindow.status:setText(tr("Preparando o cliente."))
176 updaterWindow.mainProgress:setPercent(100)
177 updaterWindow.downloadProgress:hide()
178 updaterWindow.downloadStatus:hide()
179 scheduledEvent = scheduleEvent(function()
180 local restart = binary or (not loadModulesFunction and reloadModules) or forceRestart
181 if newFiles then
182 g_resources.updateData(finalFiles, not restart)
183 end
184 if binary then
185 g_resources.updateExecutable(binary)
186 end
187 if restart then
188 g_app.restart()
189 else
190 if reloadModules then
191 g_modules.reloadModules()
192 end
193 Updater.abort()
194 end
195 end, 100)
196 end)
197end
198
199-- public functions
200function Updater.init(loadModulesFunc)
201 g_logger.setOnLog(onLog)
202 loadModulesFunction = loadModulesFunc
203 initAppWindow()
204 Updater.check()
205end
206
207function Updater.terminate()
208 loadModulesFunction = nil
209 Updater.abort()
210end
211
212function Updater.abort()
213 HTTP.cancel(httpOperationId)
214 removeEvent(scheduledEvent)
215 if updaterWindow then
216 updaterWindow:destroy()
217 updaterWindow = nil
218 end
219 loadModules()
220end
221
222function Updater.check(args)
223 if updaterWindow then return end
224
225 updaterWindow = g_ui.displayUI('updater')
226 updaterWindow:show()
227 updaterWindow:focus()
228 updaterWindow:raise()
229
230 local updateData = nil
231 local function progressUpdater(value)
232 removeEvent(scheduledEvent)
233 if value == 100 then
234 return Updater.error(tr("Timeout"))
235 end
236 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
237 return updateFiles(updateData)
238 end
239 scheduledEvent = scheduleEvent(function() progressUpdater(value + 1) end, 50)
240 updaterWindow.mainProgress:setPercent(value)
241 end
242 progressUpdater(0)
243
244 httpOperationId = HTTP.postJSON(Services.updater, {
245 version = APP_VERSION,
246 build = g_app.getVersion(),
247 os = g_app.getOs(),
248 platform = g_window.getPlatformType(),
249 args = args or {}
250 }, function(data, err)
251 if err then
252 return Updater.error(err)
253 end
254 updateData = data
255 end)
256end
257
258function Updater.error(message)
259 removeEvent(scheduledEvent)
260 if not updaterWindow then return end
261 displayErrorBox(tr("Updater Error"), message).onOk = function()
262 Updater.abort()
263 end
264end
265
266function Updater.changeUrl()
267 removeEvent(scheduledEvent)
268 modules.client_textedit.edit(Services.updater, {title="Enter updater url", width=300}, function(newUrl)
269 if updaterWindow and newUrl:len() > 4 then
270 Services.updater = newUrl
271 end
272 if updaterWindow then
273 updaterWindow:destroy()
274 updaterWindow = nil
275 end
276 Updater.check()
277 end)
278end
Top