SHOW:
|
|
- or go back to the newest paste.
1 | if not term.isColor or not term.isColor() then | |
2 | error('OneOS Requires an Advanced (gold) Computer') | |
3 | end | |
4 | ||
5 | _jstr = [[ | |
6 | local base = _G | |
7 | ||
8 | ----------------------------------------------------------------------------- | |
9 | -- Module declaration | |
10 | ----------------------------------------------------------------------------- | |
11 | ||
12 | -- Public functions | |
13 | ||
14 | -- Private functions | |
15 | local decode_scanArray | |
16 | local decode_scanComment | |
17 | local decode_scanConstant | |
18 | local decode_scanNumber | |
19 | local decode_scanObject | |
20 | local decode_scanString | |
21 | local decode_scanWhitespace | |
22 | local encodeString | |
23 | local isArray | |
24 | local isEncodable | |
25 | ||
26 | ----------------------------------------------------------------------------- | |
27 | -- PUBLIC FUNCTIONS | |
28 | ----------------------------------------------------------------------------- | |
29 | --- Encodes an arbitrary Lua object / variable. | |
30 | -- @param v The Lua object / variable to be JSON encoded. | |
31 | -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) | |
32 | function encode (v) | |
33 | -- Handle nil values | |
34 | if v==nil then | |
35 | return "null" | |
36 | end | |
37 | ||
38 | local vtype = base.type(v) | |
39 | ||
40 | -- Handle strings | |
41 | if vtype=='string' then | |
42 | return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string | |
43 | end | |
44 | ||
45 | -- Handle booleans | |
46 | if vtype=='number' or vtype=='boolean' then | |
47 | return base.tostring(v) | |
48 | end | |
49 | ||
50 | -- Handle tables | |
51 | if vtype=='table' then | |
52 | local rval = {} | |
53 | -- Consider arrays separately | |
54 | local bArray, maxCount = isArray(v) | |
55 | if bArray then | |
56 | for i = 1,maxCount do | |
57 | table.insert(rval, encode(v[i])) | |
58 | end | |
59 | else -- An object, not an array | |
60 | for i,j in base.pairs(v) do | |
61 | if isEncodable(i) and isEncodable(j) then | |
62 | table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j)) | |
63 | end | |
64 | end | |
65 | end | |
66 | if bArray then | |
67 | return '[' .. table.concat(rval,',') ..']' | |
68 | else | |
69 | return '{' .. table.concat(rval,',') .. '}' | |
70 | end | |
71 | end | |
72 | ||
73 | -- Handle null values | |
74 | if vtype=='function' and v==null then | |
75 | return 'null' | |
76 | end | |
77 | ||
78 | base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v)) | |
79 | end | |
80 | ||
81 | ||
82 | --- Decodes a JSON string and returns the decoded value as a Lua data structure / value. | |
83 | -- @param s The string to scan. | |
84 | -- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. | |
85 | -- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, | |
86 | -- and the position of the first character after | |
87 | -- the scanned JSON object. | |
88 | function decode(s, startPos) | |
89 | startPos = startPos and startPos or 1 | |
90 | startPos = decode_scanWhitespace(s,startPos) | |
91 | base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') | |
92 | local curChar = string.sub(s,startPos,startPos) | |
93 | -- Object | |
94 | if curChar=='{' then | |
95 | return decode_scanObject(s,startPos) | |
96 | end | |
97 | -- Array | |
98 | if curChar=='[' then | |
99 | return decode_scanArray(s,startPos) | |
100 | end | |
101 | -- Number | |
102 | if string.find("+-0123456789.e", curChar, 1, true) then | |
103 | return decode_scanNumber(s,startPos) | |
104 | end | |
105 | -- String | |
106 | if curChar=='"' or curChar=="'" then | |
107 | return decode_scanString(s,startPos) | |
108 | end | |
109 | if string.sub(s,startPos,startPos+1)=='/*' then | |
110 | return decode(s, decode_scanComment(s,startPos)) | |
111 | end | |
112 | -- Otherwise, it must be a constant | |
113 | return decode_scanConstant(s,startPos) | |
114 | end | |
115 | ||
116 | --- The null function allows one to specify a null value in an associative array (which is otherwise | |
117 | -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } | |
118 | function null() | |
119 | return null -- so json.null() will also return null ;-) | |
120 | end | |
121 | ----------------------------------------------------------------------------- | |
122 | -- Internal, PRIVATE functions. | |
123 | -- Following a Python-like convention, I have prefixed all these 'PRIVATE' | |
124 | -- functions with an underscore. | |
125 | ----------------------------------------------------------------------------- | |
126 | ||
127 | --- Scans an array from JSON into a Lua object | |
128 | -- startPos begins at the start of the array. | |
129 | -- Returns the array and the next starting position | |
130 | -- @param s The string being scanned. | |
131 | -- @param startPos The starting position for the scan. | |
132 | -- @return table, int The scanned array as a table, and the position of the next character to scan. | |
133 | function decode_scanArray(s,startPos) | |
134 | local array = {} -- The return value | |
135 | local stringLen = string.len(s) | |
136 | base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) | |
137 | startPos = startPos + 1 | |
138 | -- Infinite loop for array elements | |
139 | repeat | |
140 | startPos = decode_scanWhitespace(s,startPos) | |
141 | base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') | |
142 | local curChar = string.sub(s,startPos,startPos) | |
143 | if (curChar==']') then | |
144 | return array, startPos+1 | |
145 | end | |
146 | if (curChar==',') then | |
147 | startPos = decode_scanWhitespace(s,startPos+1) | |
148 | end | |
149 | base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') | |
150 | object, startPos = decode(s,startPos) | |
151 | table.insert(array,object) | |
152 | until false | |
153 | end | |
154 | ||
155 | --- Scans a comment and discards the comment. | |
156 | -- Returns the position of the next character following the comment. | |
157 | -- @param string s The JSON string to scan. | |
158 | -- @param int startPos The starting position of the comment | |
159 | function decode_scanComment(s, startPos) | |
160 | base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) | |
161 | local endPos = string.find(s,'*/',startPos+2) | |
162 | base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos) | |
163 | return endPos+2 | |
164 | end | |
165 | ||
166 | --- Scans for given constants: true, false or null | |
167 | -- Returns the appropriate Lua type, and the position of the next character to read. | |
168 | -- @param s The string being scanned. | |
169 | -- @param startPos The position in the string at which to start scanning. | |
170 | -- @return object, int The object (true, false or nil) and the position at which the next character should be | |
171 | -- scanned. | |
172 | function decode_scanConstant(s, startPos) | |
173 | local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } | |
174 | local constNames = {"true","false","null"} | |
175 | ||
176 | for i,k in base.pairs(constNames) do | |
177 | --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k) | |
178 | if string.sub(s,startPos, startPos + string.len(k) -1 )==k then | |
179 | return consts[k], startPos + string.len(k) | |
180 | end | |
181 | end | |
182 | base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) | |
183 | end | |
184 | ||
185 | --- Scans a number from the JSON encoded string. | |
186 | -- (in fact, also is able to scan numeric +- eqns, which is not | |
187 | -- in the JSON spec.) | |
188 | -- Returns the number, and the position of the next character | |
189 | -- after the number. | |
190 | -- @param s The string being scanned. | |
191 | -- @param startPos The position at which to start scanning. | |
192 | -- @return number, int The extracted number and the position of the next character to scan. | |
193 | function decode_scanNumber(s,startPos) | |
194 | local endPos = startPos+1 | |
195 | local stringLen = string.len(s) | |
196 | local acceptableChars = "+-0123456789.e" | |
197 | while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) | |
198 | and endPos<=stringLen | |
199 | ) do | |
200 | endPos = endPos + 1 | |
201 | end | |
202 | local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) | |
203 | local stringEval = base.loadstring(stringValue) | |
204 | base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) | |
205 | return stringEval(), endPos | |
206 | end | |
207 | ||
208 | --- Scans a JSON object into a Lua object. | |
209 | -- startPos begins at the start of the object. | |
210 | -- Returns the object and the next starting position. | |
211 | -- @param s The string being scanned. | |
212 | -- @param startPos The starting position of the scan. | |
213 | -- @return table, int The scanned object as a table and the position of the next character to scan. | |
214 | function decode_scanObject(s,startPos) | |
215 | local object = {} | |
216 | local stringLen = string.len(s) | |
217 | local key, value | |
218 | base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) | |
219 | startPos = startPos + 1 | |
220 | repeat | |
221 | startPos = decode_scanWhitespace(s,startPos) | |
222 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') | |
223 | local curChar = string.sub(s,startPos,startPos) | |
224 | if (curChar=='}') then | |
225 | return object,startPos+1 | |
226 | end | |
227 | if (curChar==',') then | |
228 | startPos = decode_scanWhitespace(s,startPos+1) | |
229 | end | |
230 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') | |
231 | -- Scan the key | |
232 | key, startPos = decode(s,startPos) | |
233 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
234 | startPos = decode_scanWhitespace(s,startPos) | |
235 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
236 | base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) | |
237 | startPos = decode_scanWhitespace(s,startPos+1) | |
238 | base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) | |
239 | value, startPos = decode(s,startPos) | |
240 | object[key]=value | |
241 | until false -- infinite loop while key-value pairs are found | |
242 | end | |
243 | ||
244 | --- Scans a JSON string from the opening inverted comma or single quote to the | |
245 | -- end of the string. | |
246 | -- Returns the string extracted as a Lua string, | |
247 | -- and the position of the next non-string character | |
248 | -- (after the closing inverted comma or single quote). | |
249 | -- @param s The string being scanned. | |
250 | -- @param startPos The starting position of the scan. | |
251 | -- @return string, int The extracted string as a Lua string, and the next character to parse. | |
252 | function decode_scanString(s,startPos) | |
253 | base.assert(startPos, 'decode_scanString(..) called without start position') | |
254 | local startChar = string.sub(s,startPos,startPos) | |
255 | base.assert(startChar=="'" or startChar=='"','decode_scanString called for a non-string') | |
256 | local escaped = false | |
257 | local endPos = startPos + 1 | |
258 | local bEnded = false | |
259 | local stringLen = string.len(s) | |
260 | repeat | |
261 | local curChar = string.sub(s,endPos,endPos) | |
262 | -- Character escaping is only used to escape the string delimiters | |
263 | if not escaped then | |
264 | if curChar=='\\' then | |
265 | escaped = true | |
266 | else | |
267 | bEnded = curChar==startChar | |
268 | end | |
269 | else | |
270 | -- If we're escaped, we accept the current character come what may | |
271 | escaped = false | |
272 | end | |
273 | endPos = endPos + 1 | |
274 | base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos) | |
275 | until bEnded | |
276 | local stringValue = 'return ' .. string.sub(s, startPos, endPos-1) | |
277 | local stringEval = base.loadstring(stringValue) | |
278 | base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos) | |
279 | return stringEval(), endPos | |
280 | end | |
281 | ||
282 | --- Scans a JSON string skipping all whitespace from the current start position. | |
283 | -- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. | |
284 | -- @param s The string being scanned | |
285 | -- @param startPos The starting position where we should begin removing whitespace. | |
286 | -- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string | |
287 | -- was reached. | |
288 | function decode_scanWhitespace(s,startPos) | |
289 | local whitespace=" \n\r\t" | |
290 | local stringLen = string.len(s) | |
291 | while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do | |
292 | startPos = startPos + 1 | |
293 | end | |
294 | return startPos | |
295 | end | |
296 | ||
297 | --- Encodes a string to be JSON-compatible. | |
298 | -- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) | |
299 | -- @param s The string to return as a JSON encoded (i.e. backquoted string) | |
300 | -- @return The string appropriately escaped. | |
301 | function encodeString(s) | |
302 | s = string.gsub(s,'\\','\\\\') | |
303 | s = string.gsub(s,'"','\\"') | |
304 | s = string.gsub(s,"'","\\'") | |
305 | s = string.gsub(s,'\n','\\n') | |
306 | s = string.gsub(s,'\t','\\t') | |
307 | return s | |
308 | end | |
309 | ||
310 | -- Determines whether the given Lua type is an array or a table / dictionary. | |
311 | -- We consider any table an array if it has indexes 1..n for its n items, and no | |
312 | -- other data in the table. | |
313 | -- I think this method is currently a little 'flaky', but can't think of a good way around it yet... | |
314 | -- @param t The table to evaluate as an array | |
315 | -- @return boolean, number True if the table can be represented as an array, false otherwise. If true, | |
316 | -- the second returned value is the maximum | |
317 | -- number of indexed elements in the array. | |
318 | function isArray(t) | |
319 | -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable | |
320 | -- (with the possible exception of 'n') | |
321 | local maxIndex = 0 | |
322 | for k,v in base.pairs(t) do | |
323 | if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair | |
324 | if (not isEncodable(v)) then return false end -- All array elements must be encodable | |
325 | maxIndex = math.max(maxIndex,k) | |
326 | else | |
327 | if (k=='n') then | |
328 | if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements | |
329 | else -- Else of (k=='n') | |
330 | if isEncodable(v) then return false end | |
331 | end -- End of (k~='n') | |
332 | end -- End of k,v not an indexed pair | |
333 | end -- End of loop across all pairs | |
334 | return true, maxIndex | |
335 | end | |
336 | ||
337 | --- Determines whether the given Lua object / table / variable can be JSON encoded. The only | |
338 | -- types that are JSON encodable are: string, boolean, number, nil, table and json.null. | |
339 | -- In this implementation, all other types are ignored. | |
340 | -- @param o The object to examine. | |
341 | -- @return boolean True if the object should be JSON encoded, false if it should be ignored. | |
342 | function isEncodable(o) | |
343 | local t = base.type(o) | |
344 | return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) | |
345 | end | |
346 | ]] | |
347 | ||
348 | function loadJSON() | |
349 | local sName = 'JSON' | |
350 | ||
351 | local tEnv = {} | |
352 | setmetatable( tEnv, { __index = _G } ) | |
353 | local fnAPI, err = loadstring(_jstr) | |
354 | if fnAPI then | |
355 | setfenv( fnAPI, tEnv ) | |
356 | fnAPI() | |
357 | else | |
358 | printError( err ) | |
359 | return false | |
360 | end | |
361 | ||
362 | local tAPI = {} | |
363 | for k,v in pairs( tEnv ) do | |
364 | tAPI[k] = v | |
365 | end | |
366 | ||
367 | _G[sName] = tAPI | |
368 | return true | |
369 | end | |
370 | ||
371 | local mainTitle = 'OneOS Installer' | |
372 | local subTitle = 'Please wait...' | |
373 | ||
374 | function Draw() | |
375 | sleep(0) | |
376 | term.setBackgroundColour(colours.white) | |
377 | term.clear() | |
378 | local w, h = term.getSize() | |
379 | term.setTextColour(colours.blue) | |
380 | term.setCursorPos((w-#mainTitle)/2, 8) | |
381 | - | term.setTextColour(colours.lightBlue) |
381 | + | |
382 | - | term.setCursorPos(math.ceil((w-#mainTitle)/2), 8) |
382 | + | |
383 | term.setCursorPos((w-#subTitle)/2, 10) | |
384 | term.write(subTitle) | |
385 | - | term.setCursorPos(math.ceil((w-#subTitle)/2), 10) |
385 | + | |
386 | ||
387 | tArgs = {...} | |
388 | ||
389 | Settings = { | |
390 | InstallPath = '/', --Where the program's installed, don't always asume root (if it's run under something like OneOS) | |
391 | Hidden = false, --Whether or not the update is hidden (doesn't write to the screen), useful for background updates | |
392 | GitHubUsername = 'oeed', --Your GitHub username as it appears in the URL | |
393 | GitHubRepoName = 'OneOS', --The repo name as it appears in the URL | |
394 | DownloadReleases = true, --If true it will download the latest release, otherwise it will download the files as they currently appear | |
395 | UpdateFunction = nil, --Sent when something happens (file downloaded etc.) | |
396 | TotalBytes = 0, --Do not change this value (especially programatically)! | |
397 | DownloadedBytes = 0, --Do not change this value (especially programatically)! | |
398 | Status = '', | |
399 | SecondaryStatus = '', | |
400 | } | |
401 | ||
402 | loadJSON() | |
403 | ||
404 | function downloadJSON(path) | |
405 | local _json = http.get(path) | |
406 | if not _json then | |
407 | error('Could not download: '..path..' Check your connection.') | |
408 | end | |
409 | return JSON.decode(_json.readAll()) | |
410 | end | |
411 | ||
412 | if http then | |
413 | subTitle = 'HTTP enabled, attempting update...' | |
414 | Draw() | |
415 | else | |
416 | subTitle = 'HTTP is required to update.' | |
417 | Draw() | |
418 | error('') | |
419 | end | |
420 | ||
421 | subTitle = 'Determining Latest Version' | |
422 | Draw() | |
423 | local releases = downloadJSON('https://api.github.com/repos/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/releases') | |
424 | local latestReleaseTag = nil | |
425 | for i, v in ipairs(releases) do | |
426 | - | local latestReleaseTag = releases[1].tag_name |
426 | + | if not v.prerelease then |
427 | - | if not tArgs or #tArgs ~= 1 and tArgs[1] ~= 'beta' then |
427 | + | latestReleaseTag = v.tag_name |
428 | - | for i, v in ipairs(releases) do |
428 | + | break |
429 | - | if not v.prerelease then |
429 | + | |
430 | - | latestReleaseTag = v.tag_name |
430 | + | |
431 | - | break |
431 | + | |
432 | Draw() | |
433 | local refs = downloadJSON('https://api.github.com/repos/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/git/refs/tags/'..latestReleaseTag) | |
434 | local latestReleaseSha = refs.object.sha | |
435 | ||
436 | subTitle = 'Downloading File Listing' | |
437 | Draw() | |
438 | ||
439 | local tree = downloadJSON('https://api.github.com/repos/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/git/trees/'..latestReleaseSha..'?recursive=1').tree | |
440 | ||
441 | local blacklist = { | |
442 | '/.gitignore', | |
443 | '/README.md', | |
444 | '/TODO', | |
445 | '/Desktop/.Desktop.settings', | |
446 | '/.version' | |
447 | } | |
448 | ||
449 | function isBlacklisted(path) | |
450 | for i, item in ipairs(blacklist) do | |
451 | if item == path then | |
452 | return true | |
453 | end | |
454 | end | |
455 | return false | |
456 | end | |
457 | ||
458 | Settings.TotalFiles = 0 | |
459 | Settings.TotalBytes = 0 | |
460 | for i, v in ipairs(tree) do | |
461 | if not isBlacklisted(Settings.InstallPath..v.path) and v.size then | |
462 | Settings.TotalBytes = Settings.TotalBytes + v.size | |
463 | Settings.TotalFiles = Settings.TotalFiles + 1 | |
464 | end | |
465 | end | |
466 | ||
467 | Settings.DownloadedBytes = 0 | |
468 | function downloadBlob(v) | |
469 | if isBlacklisted(Settings.InstallPath..v.path) then | |
470 | return | |
471 | end | |
472 | - | Settings.DownloadedFiles = 0 |
472 | + | |
473 | subTitle = 'Making folder: '..'/'..Settings.InstallPath..v.path | |
474 | Draw() | |
475 | fs.makeDir('/'..Settings.InstallPath..v.path) | |
476 | else | |
477 | subTitle = 'Starting download for: '..Settings.InstallPath..v.path | |
478 | - | -- subTitle = 'Making folder: '..'/'..Settings.InstallPath..v.path |
478 | + | |
479 | local f = http.get(('https://raw.github.com/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/'..latestReleaseTag..Settings.InstallPath..v.path):gsub(' ','%%20')) | |
480 | if not f then | |
481 | error('Downloading failed, try again. '..('https://raw.github.com/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/'..latestReleaseTag..Settings.InstallPath..v.path):gsub(' ','%%20')) | |
482 | - | -- subTitle = 'Starting download for: '..Settings.InstallPath..v.path |
482 | + | |
483 | local h = fs.open('/'..Settings.InstallPath..v.path, 'w') | |
484 | h.write(f.readAll()) | |
485 | - | local tries, f = 0 |
485 | + | |
486 | - | repeat |
486 | + | subTitle = '('..math.floor(100*(Settings.DownloadedBytes/Settings.TotalBytes))..'%) Downloaded: '..Settings.InstallPath..v.path |
487 | - | f = http.get(('https://raw.github.com/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/'..latestReleaseTag..Settings.InstallPath..v.path):gsub(' ','%%20')) |
487 | + | |
488 | - | if not f then sleep(5) end |
488 | + | |
489 | - | tries = tries + 1 |
489 | + | |
490 | - | until tries > 5 or f |
490 | + | |
491 | end | |
492 | end | |
493 | ||
494 | local downloads = {} | |
495 | for i, v in ipairs(tree) do | |
496 | downloadBlob(v) | |
497 | end | |
498 | ||
499 | - | -- subTitle = 'Downloading: ' .. math.floor(100*(Settings.DownloadedBytes/Settings.TotalBytes))..'%' |
499 | + | |
500 | - | subTitle = 'Downloading: ' .. math.floor(100*(Settings.DownloadedFiles/Settings.TotalFiles))..'%' -- using the number of files over the number of bytes actually appears to be more accurate, the connection takes longer than sending the data |
500 | + | |
501 | - | -- subTitle = '('..math.floor(100*(Settings.DownloadedBytes/Settings.TotalBytes))..'%) Downloaded: '..Settings.InstallPath..v.path |
501 | + | |
502 | ||
503 | mainTitle = 'Installation Complete!' | |
504 | subTitle = 'Rebooting in 1 second...' | |
505 | - | Settings.DownloadedFiles = Settings.DownloadedFiles + 1 |
505 | + | |
506 | sleep(1) | |
507 | os.reboot() |