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