View difference between Paste ID: MnavXcWc and E1xftzLa
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()