-
-
Notifications
You must be signed in to change notification settings - Fork 239
/
Copy pathupdater.lua
239 lines (209 loc) · 6.64 KB
/
updater.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
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 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 = {}
local toUpdateFiles = {}
-- 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 })
table.insert(toUpdateFiles, file)
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.updateFiles(toUpdateFiles, 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
Updater.check()
end
function Updater.terminate()
loadModulesFunction = nil
Updater.abort(true)
end
function Updater.abort(terminate)
HTTP.cancel(httpOperationId)
removeEvent(scheduledEvent)
if updaterWindow then
updaterWindow:destroy()
updaterWindow = nil
end
loadModules()
if not terminate then
signalcall(g_app.onUpdateFinished, g_app)
end
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_platform.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