Advertisement
ROFLCopter64bit

PocketShell Installer

Jun 28th, 2014
913
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 21.45 KB | None | 0 0
  1. --The majority of this code is oeed's install script, so thanks to him for this
  2.  
  3. if not term.isColor or not term.isColor() then
  4.         error('PocketShell requires an advanced PDA')
  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. print('PocketShell Installer')
  374. print('Please wait...')
  375.  
  376. Settings = {
  377.         InstallPath = '/', --Where the program's installed, don't always asume root
  378.         Hidden = false, --Whether or not the update is hidden (doesn't write to the screen), useful for background updates
  379.         GitHubUsername = 'ROFLCopter64bit', --Your GitHub username as it appears in the URL
  380.         GitHubRepoName = 'PocketShell', --The repo name as it appears in the URL
  381.         DownloadReleases = true, --If true it will download the latest release, otherwise it will download the files as they currently appear
  382.         UpdateFunction = nil, --Sent when something happens (file downloaded etc.)
  383.         TotalBytes = 0, --Do not change this value (especially programatically)!
  384.         DownloadedBytes = 0, --Do not change this value (especially programatically)!
  385.         Status = '',
  386.         SecondaryStatus = '',
  387. }
  388.  
  389. loadJSON()
  390.  
  391. function downloadJSON(path)
  392.         local _json = http.get(path)
  393.         if not _json then
  394.                 error('Could not download, check your connection.')
  395.         end
  396.         return JSON.decode(_json.readAll())
  397. end
  398.  
  399. if http then
  400.         print('HTTP enabled, attempting update...')
  401. else
  402.         print('HTTP is required to update.')
  403.                 sleep(5)
  404.         error('')
  405. end
  406.  
  407. print('Determining Latest Version')
  408. local releases = downloadJSON('https://api.github.com/repos/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/releases')
  409. local latestReleaseTag = releases[1].tag_name
  410. print('Obtaining Latest Version URL')
  411. local refs = downloadJSON('https://api.github.com/repos/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/git/refs')
  412. local latestReleaseSha = ''
  413. for i, v in ipairs(refs) do
  414.         if v.ref == 'refs/tags/'..latestReleaseTag then
  415.                 latestReleaseSha = v.object.sha
  416.         end
  417. end
  418.  
  419. print('Downloading File Listing')
  420.  
  421. local tree = downloadJSON('https://api.github.com/repos/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/git/trees/'..latestReleaseSha..'?recursive=1').tree
  422.  
  423. local blacklist = {
  424.         '/.gitignore',
  425.         '/README.md',
  426.         '/.gitattributes',
  427.     '/version'
  428. }
  429.  
  430. function isBlacklisted(path)
  431.         for i, item in ipairs(blacklist) do
  432.                 if item == path then
  433.                         return true
  434.                 end
  435.         end
  436.         return false
  437. end
  438.  
  439. Settings.TotalFiles = 0
  440. Settings.TotalBytes = 0
  441. for i, v in ipairs(tree) do
  442.         if not isBlacklisted(Settings.InstallPath..v.path) and v.size then
  443.                 Settings.TotalBytes = Settings.TotalBytes + v.size
  444.                 Settings.TotalFiles = Settings.TotalFiles + 1
  445.         end
  446. end
  447.  
  448. Settings.DownloadedBytes = 0
  449. function downloadBlob(v)
  450.         if isBlacklisted(Settings.InstallPath..v.path) then
  451.                 return
  452.         end
  453.         if v.type == 'tree' then
  454.                 print('Making folder: '..'/'..Settings.InstallPath..v.path)
  455.                 fs.makeDir('/'..Settings.InstallPath..v.path)
  456.         else
  457.                 print('Starting download for: '..Settings.InstallPath..v.path)
  458.                 local f = http.get(('https://raw.github.com/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/'..latestReleaseTag..Settings.InstallPath..v.path):gsub(' ','%%20'))
  459.                 if not f then
  460.                         error('Downloading failed, try again. '..('https://raw.github.com/'..Settings.GitHubUsername..'/'..Settings.GitHubRepoName..'/'..latestReleaseTag..Settings.InstallPath..v.path):gsub(' ','%%20'))
  461.                 end
  462.                 local h = fs.open('/'..Settings.InstallPath..v.path, 'w')
  463.                 h.write(f.readAll())
  464.                 h.close()
  465.                 print('('..math.floor(100*(Settings.DownloadedBytes/Settings.TotalBytes))..'%) Downloaded: '..Settings.InstallPath..v.path)
  466.                 if v.size then
  467.                         Settings.DownloadedBytes = Settings.DownloadedBytes + v.size
  468.                 end
  469.         end
  470. end
  471.  
  472. local downloads = {}
  473. for i, v in ipairs(tree) do
  474.         table.insert(downloads, function()downloadBlob(v)end)
  475. end
  476.  
  477. parallel.waitForAll(unpack(downloads))
  478.  
  479. local h = fs.open('/system/.version', 'w')
  480. h.write(latestReleaseTag)
  481. h.close()
  482.  
  483. print('Installation Complete!')
  484. print('Rebooting...')
  485. sleep(1)
  486. os.reboot()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement