Advertisement
Oeed

ComputerCraft App Store

Feb 7th, 2014
2,815
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 54.08 KB | None | 0 0
  1. local _jstr = [[
  2.         local base = _G
  3.  
  4.         -----------------------------------------------------------------------------
  5.         -- Module declaration
  6.         -----------------------------------------------------------------------------
  7.  
  8.         -- Public functions
  9.  
  10.         -- Private functions
  11.         local decode_scanArray
  12.         local decode_scanComment
  13.         local decode_scanConstant
  14.         local decode_scanNumber
  15.         local decode_scanObject
  16.         local decode_scanString
  17.         local decode_scanWhitespace
  18.         local encodeString
  19.         local isArray
  20.         local isEncodable
  21.  
  22.         -----------------------------------------------------------------------------
  23.         -- PUBLIC FUNCTIONS
  24.         -----------------------------------------------------------------------------
  25.         --- Encodes an arbitrary Lua object / variable.
  26.         -- @param v The Lua object / variable to be JSON encoded.
  27.         -- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
  28.         function encode (v)
  29.           -- Handle nil values
  30.           if v==nil then
  31.             return "null"
  32.           end
  33.          
  34.           local vtype = base.type(v)  
  35.  
  36.           -- Handle strings
  37.           if vtype=='string' then    
  38.             return '"' .. encodeString(v) .. '"'      -- Need to handle encoding in string
  39.           end
  40.          
  41.           -- Handle booleans
  42.           if vtype=='number' or vtype=='boolean' then
  43.             return base.tostring(v)
  44.           end
  45.          
  46.           -- Handle tables
  47.           if vtype=='table' then
  48.             local rval = {}
  49.             -- Consider arrays separately
  50.             local bArray, maxCount = isArray(v)
  51.             if bArray then
  52.               for i = 1,maxCount do
  53.                 table.insert(rval, encode(v[i]))
  54.               end
  55.             else -- An object, not an array
  56.               for i,j in base.pairs(v) do
  57.                 if isEncodable(i) and isEncodable(j) then
  58.                   table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
  59.                 end
  60.               end
  61.             end
  62.             if bArray then
  63.               return '[' .. table.concat(rval,',') ..']'
  64.             else
  65.               return '{' .. table.concat(rval,',') .. '}'
  66.             end
  67.           end
  68.          
  69.           -- Handle null values
  70.           if vtype=='function' and v==null then
  71.             return 'null'
  72.           end
  73.          
  74.           base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
  75.         end
  76.  
  77.  
  78.         --- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
  79.         -- @param s The string to scan.
  80.         -- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1.
  81.         -- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil,
  82.         -- and the position of the first character after
  83.         -- the scanned JSON object.
  84.         function decode(s, startPos)
  85.           startPos = startPos and startPos or 1
  86.           startPos = decode_scanWhitespace(s,startPos)
  87.           base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
  88.           local curChar = string.sub(s,startPos,startPos)
  89.           -- Object
  90.           if curChar=='{' then
  91.             return decode_scanObject(s,startPos)
  92.           end
  93.           -- Array
  94.           if curChar=='[' then
  95.             return decode_scanArray(s,startPos)
  96.           end
  97.           -- Number
  98.           if string.find("+-0123456789.e", curChar, 1, true) then
  99.             return decode_scanNumber(s,startPos)
  100.           end
  101.           -- String
  102.           if curChar=='"' or curChar=="'" then
  103.             return decode_scanString(s,startPos)
  104.           end
  105.           if string.sub(s,startPos,startPos+1)=='/*' then
  106.             return decode(s, decode_scanComment(s,startPos))
  107.           end
  108.           -- Otherwise, it must be a constant
  109.           return decode_scanConstant(s,startPos)
  110.         end
  111.  
  112.         --- The null function allows one to specify a null value in an associative array (which is otherwise
  113.         -- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
  114.         function null()
  115.           return null -- so json.null() will also return null ;-)
  116.         end
  117.         -----------------------------------------------------------------------------
  118.         -- Internal, PRIVATE functions.
  119.         -- Following a Python-like convention, I have prefixed all these 'PRIVATE'
  120.         -- functions with an underscore.
  121.         -----------------------------------------------------------------------------
  122.  
  123.         --- Scans an array from JSON into a Lua object
  124.         -- startPos begins at the start of the array.
  125.         -- Returns the array and the next starting position
  126.         -- @param s The string being scanned.
  127.         -- @param startPos The starting position for the scan.
  128.         -- @return table, int The scanned array as a table, and the position of the next character to scan.
  129.         function decode_scanArray(s,startPos)
  130.           local array = {}   -- The return value
  131.           local stringLen = string.len(s)
  132.           base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
  133.           startPos = startPos + 1
  134.           -- Infinite loop for array elements
  135.           repeat
  136.             startPos = decode_scanWhitespace(s,startPos)
  137.             base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
  138.             local curChar = string.sub(s,startPos,startPos)
  139.             if (curChar==']') then
  140.               return array, startPos+1
  141.             end
  142.             if (curChar==',') then
  143.               startPos = decode_scanWhitespace(s,startPos+1)
  144.             end
  145.             base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
  146.             object, startPos = decode(s,startPos)
  147.             table.insert(array,object)
  148.           until false
  149.         end
  150.  
  151.         --- Scans a comment and discards the comment.
  152.         -- Returns the position of the next character following the comment.
  153.         -- @param string s The JSON string to scan.
  154.         -- @param int startPos The starting position of the comment
  155.         function decode_scanComment(s, startPos)
  156.           base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
  157.           local endPos = string.find(s,'*/',startPos+2)
  158.           base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
  159.           return endPos+2  
  160.         end
  161.  
  162.         --- Scans for given constants: true, false or null
  163.         -- Returns the appropriate Lua type, and the position of the next character to read.
  164.         -- @param s The string being scanned.
  165.         -- @param startPos The position in the string at which to start scanning.
  166.         -- @return object, int The object (true, false or nil) and the position at which the next character should be
  167.         -- scanned.
  168.         function decode_scanConstant(s, startPos)
  169.           local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
  170.           local constNames = {"true","false","null"}
  171.  
  172.           for i,k in base.pairs(constNames) do
  173.             --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
  174.             if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
  175.               return consts[k], startPos + string.len(k)
  176.             end
  177.           end
  178.           base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
  179.         end
  180.  
  181.         --- Scans a number from the JSON encoded string.
  182.         -- (in fact, also is able to scan numeric +- eqns, which is not
  183.         -- in the JSON spec.)
  184.         -- Returns the number, and the position of the next character
  185.         -- after the number.
  186.         -- @param s The string being scanned.
  187.         -- @param startPos The position at which to start scanning.
  188.         -- @return number, int The extracted number and the position of the next character to scan.
  189.         function decode_scanNumber(s,startPos)
  190.           local endPos = startPos+1
  191.           local stringLen = string.len(s)
  192.           local acceptableChars = "+-0123456789.e"
  193.           while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
  194.            and endPos<=stringLen
  195.            ) do
  196.             endPos = endPos + 1
  197.           end
  198.           local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
  199.           local stringEval = base.loadstring(stringValue)
  200.           base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
  201.           return stringEval(), endPos
  202.         end
  203.  
  204.         --- Scans a JSON object into a Lua object.
  205.         -- startPos begins at the start of the object.
  206.         -- Returns the object and the next starting position.
  207.         -- @param s The string being scanned.
  208.         -- @param startPos The starting position of the scan.
  209.         -- @return table, int The scanned object as a table and the position of the next character to scan.
  210.         function decode_scanObject(s,startPos)
  211.           local object = {}
  212.           local stringLen = string.len(s)
  213.           local key, value
  214.           base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
  215.           startPos = startPos + 1
  216.           repeat
  217.             startPos = decode_scanWhitespace(s,startPos)
  218.             base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
  219.             local curChar = string.sub(s,startPos,startPos)
  220.             if (curChar=='}') then
  221.               return object,startPos+1
  222.             end
  223.             if (curChar==',') then
  224.               startPos = decode_scanWhitespace(s,startPos+1)
  225.             end
  226.             base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
  227.             -- Scan the key
  228.             key, startPos = decode(s,startPos)
  229.             base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  230.             startPos = decode_scanWhitespace(s,startPos)
  231.             base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  232.             base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
  233.             startPos = decode_scanWhitespace(s,startPos+1)
  234.             base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
  235.             value, startPos = decode(s,startPos)
  236.             object[key]=value
  237.           until false  -- infinite loop while key-value pairs are found
  238.         end
  239.  
  240.         --- Scans a JSON string from the opening inverted comma or single quote to the
  241.         -- end of the string.
  242.         -- Returns the string extracted as a Lua string,
  243.         -- and the position of the next non-string character
  244.         -- (after the closing inverted comma or single quote).
  245.         -- @param s The string being scanned.
  246.         -- @param startPos The starting position of the scan.
  247.         -- @return string, int The extracted string as a Lua string, and the next character to parse.
  248.         function decode_scanString(s,startPos)
  249.           base.assert(startPos, 'decode_scanString(..) called without start position')
  250.           local startChar = string.sub(s,startPos,startPos)
  251.           base.assert(startChar=="'" or startChar=='"','decode_scanString called for a non-string')
  252.           local escaped = false
  253.           local endPos = startPos + 1
  254.           local bEnded = false
  255.           local stringLen = string.len(s)
  256.           repeat
  257.             local curChar = string.sub(s,endPos,endPos)
  258.             -- Character escaping is only used to escape the string delimiters
  259.             if not escaped then
  260.               if curChar=='\\' then
  261.                 escaped = true
  262.               else
  263.                 bEnded = curChar==startChar
  264.               end
  265.             else
  266.               -- If we're escaped, we accept the current character come what may
  267.               escaped = false
  268.             end
  269.             endPos = endPos + 1
  270.             base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
  271.           until bEnded
  272.           local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
  273.           local stringEval = base.loadstring(stringValue)
  274.           base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
  275.           return stringEval(), endPos  
  276.         end
  277.  
  278.         --- Scans a JSON string skipping all whitespace from the current start position.
  279.         -- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached.
  280.         -- @param s The string being scanned
  281.         -- @param startPos The starting position where we should begin removing whitespace.
  282.         -- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string
  283.         -- was reached.
  284.         function decode_scanWhitespace(s,startPos)
  285.           local whitespace=" \n\r\t"
  286.           local stringLen = string.len(s)
  287.           while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true)  and startPos <= stringLen) do
  288.             startPos = startPos + 1
  289.           end
  290.           return startPos
  291.         end
  292.  
  293.         --- Encodes a string to be JSON-compatible.
  294.         -- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
  295.         -- @param s The string to return as a JSON encoded (i.e. backquoted string)
  296.         -- @return The string appropriately escaped.
  297.         function encodeString(s)
  298.           s = string.gsub(s,'\\','\\\\')
  299.           s = string.gsub(s,'"','\\"')
  300.           s = string.gsub(s,"'","\\'")
  301.           s = string.gsub(s,'\n','\\n')
  302.           s = string.gsub(s,'\t','\\t')
  303.           return s
  304.         end
  305.  
  306.         -- Determines whether the given Lua type is an array or a table / dictionary.
  307.         -- We consider any table an array if it has indexes 1..n for its n items, and no
  308.         -- other data in the table.
  309.         -- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
  310.         -- @param t The table to evaluate as an array
  311.         -- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
  312.         -- the second returned value is the maximum
  313.         -- number of indexed elements in the array.
  314.         function isArray(t)
  315.           -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable
  316.           -- (with the possible exception of 'n')
  317.           local maxIndex = 0
  318.           for k,v in base.pairs(t) do
  319.             if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then   -- k,v is an indexed pair
  320.               if (not isEncodable(v)) then return false end   -- All array elements must be encodable
  321.               maxIndex = math.max(maxIndex,k)
  322.             else
  323.               if (k=='n') then
  324.                 if v ~= table.getn(t) then return false end  -- False if n does not hold the number of elements
  325.               else -- Else of (k=='n')
  326.                 if isEncodable(v) then return false end
  327.               end  -- End of (k~='n')
  328.             end -- End of k,v not an indexed pair
  329.           end  -- End of loop across all pairs
  330.           return true, maxIndex
  331.         end
  332.  
  333.         --- Determines whether the given Lua object / table / variable can be JSON encoded. The only
  334.         -- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
  335.         -- In this implementation, all other types are ignored.
  336.         -- @param o The object to examine.
  337.         -- @return boolean True if the object should be JSON encoded, false if it should be ignored.
  338.         function isEncodable(o)
  339.           local t = base.type(o)
  340.           return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
  341.         end
  342. ]]
  343.  
  344. local _api = [[
  345.  
  346.   local function contains(table, element)
  347.     for _, value in pairs(table) do
  348.       if value == element then
  349.         return true
  350.       end
  351.     end
  352.     return false
  353.   end
  354.  
  355.   local apiURL = "http://appstore.olivercooper.me/api/"
  356.  
  357.   local function checkHTTP()
  358.     if http then
  359.       return true
  360.     else
  361.       return false
  362.     end
  363.   end
  364.  
  365.   local function requireHTTP()
  366.     if checkHTTP() then
  367.       return true
  368.     else
  369.       error("The 'http' API is not enabled!")
  370.     end
  371.   end
  372.  
  373.   function doRequest(command, subcommand, values)
  374.     values = values or {}
  375.     requireHTTP()
  376.  
  377.     local url = apiURL .. "?command=" .. command .."&subcommand=" .. subcommand
  378.     for k, v in pairs(values) do
  379.       url = url .. "&" .. k .. "=" .. v
  380.     end
  381.     local request = http.get(url)
  382.     if request then
  383.       local response = request.readAll()
  384.       request.close()
  385.       if response == "<h2>The server is too busy at the moment.</h2><p>Please reload this page few seconds later.</p>" then
  386.         error("Server is too busy at the moment.")
  387.       end
  388.       return textutils.unserialize(response)
  389.     end
  390.     return nil
  391.   end
  392.  
  393.   function getAllApplications()
  394.     return doRequest('application', 'all')
  395.   end
  396.  
  397.   function getTopCharts()
  398.     return doRequest('application', 'topcharts')
  399.   end
  400.  
  401.   function getApplicationsInCategory(name)
  402.     return doRequest('application', 'category', {name = name})
  403.   end
  404.  
  405.   function getFeaturedApplications()
  406.     return doRequest('application', 'featured')
  407.   end
  408.  
  409.   function getApplication(id)
  410.     return doRequest('application', 'get', {id = id})
  411.   end
  412.  
  413.   function getCategories(id)
  414.     return doRequest('application', 'categories')
  415.   end
  416.  
  417.   function addApplication(username, password, serializeddata, name, description, sdescription, category)
  418.     return doRequest('application', 'add', {username = username, password = password, serializeddata = serializeddata, name = name, description = description, sdescription = sdescription, category = category})
  419.   end
  420.  
  421.   function addChangeLogToApplication(id, username, password, changelog, version)
  422.     return doRequest('application', 'addchangelog', {id = id, username = username, password = password, changelog = changelog, version = version})
  423.   end
  424.  
  425.   function downloadApplication(id)
  426.     return doRequest('application', 'download', {id = id})
  427.   end
  428.  
  429.   function searchApplications(name)
  430.     return doRequest('application', 'search', {name = name})
  431.   end
  432.  
  433.   function getAllNews()
  434.     return doRequest('news', 'all')
  435.   end
  436.  
  437.   function getNews(id)
  438.     return doRequest('news', 'get', {id = id})
  439.   end
  440.  
  441.   function getInstalledApplications(id)
  442.     return doRequest('computer', 'get', {id = id})
  443.   end
  444.  
  445.   local function resolve( _sPath )
  446.     local sStartChar = string.sub( _sPath, 1, 1 )
  447.     if sStartChar == "/" or sStartChar == "\\" then
  448.       return fs.combine( "", _sPath )
  449.     else
  450.       return fs.combine( sDir, _sPath )
  451.     end
  452.   end
  453.  
  454.   function saveApplicationIcon(id, path)
  455.     local app = getApplication(id)
  456.     local icon = app.icon
  457.     local _fs = fs
  458.     if OneOS then
  459.       _fs = OneOS.FS
  460.     end
  461.     local h = _fs.open(path, 'w')
  462.     h.write(icon)
  463.     h.close()
  464.   end
  465.   --Downloads and installs an application
  466.   --id = the id of the application
  467.   --path = the path is the name of the folder/file it'll be copied too
  468.   --removeSpaces = removes spaces from the name (useful if its being run from the shell)
  469.   --alwaysFolder = be default if there is only one file it will save it as a single file, if true files will always be placed in a folder
  470.   --fullPath = if true the given path will not be changed, if false the program name will be appended
  471.   function installApplication(id, path, removeSpaces, alwaysFolder, fullPath)
  472.     local package = downloadApplication(id)
  473.     if type(package) ~= 'string' or #package == 0 then
  474.       error('The application did not download correctly or is empty. Try again.')
  475.     end
  476.     local pack = JSON.decode(package)
  477.     if pack then
  478.  
  479.       local _fs = fs
  480.       if OneOS then
  481.         _fs = OneOS.FS
  482.       end
  483.       local function makeFile(_path,_content)
  484.         sleep(0)
  485.         local file=_fs.open(_path,"w")
  486.         file.write(_content)
  487.         file.close()
  488.       end
  489.       local function makeFolder(_path,_content)
  490.         _fs.makeDir(_path)
  491.           for k,v in pairs(_content) do
  492.             if type(v)=="table" then
  493.               makeFolder(_path.."/"..k,v)
  494.             else
  495.               makeFile(_path.."/"..k,v)
  496.             end
  497.           end
  498.       end
  499.  
  500.       local app = getApplication(id)
  501.       local appName = app['name']
  502.       local keyCount = 0
  503.       for k, v in pairs(pack) do
  504.         keyCount = keyCount + 1
  505.       end
  506.       if removeSpaces then
  507.         appName = appName:gsub(" ", "")
  508.       end
  509.       local location = path..'/'
  510.       if not fullPath then
  511.         location = location .. appName
  512.       end
  513.       if keyCount == 1 and not alwaysFolder then
  514.         makeFile(location, pack['startup'])
  515.       else
  516.         makeFolder(location, pack)
  517.         location = location .. '/startup'
  518.       end
  519.  
  520.       return location
  521.     else
  522.       error('The application appears to be corrupt. Try downloading it again.')
  523.     end
  524.   end
  525.  
  526.   function registerComputer(realid, username, password)
  527.     return doRequest('computer', 'register', {realid = realid, username = username, password = password})
  528.   end
  529.  
  530.   function getAllComments(type, id)
  531.     return doRequest('comment', 'get', {ctype = type, ctypeid = id})
  532.   end
  533.  
  534.   function getComment(id)
  535.     return doRequest('comment', 'get', {id = id})
  536.   end
  537.  
  538.   function deleteComment(id, username, password)
  539.     return doRequest('comment', 'delete', {id = id, username = username, password = password})
  540.   end
  541.  
  542.   function addComments()
  543.     return doRequest('comment', 'get', {id = id})
  544.   end
  545.  
  546.   function getUser()
  547.     return doRequest('user', 'get', {id = id})
  548.   end
  549.  
  550.   function registerUser(username, password, email, mcusername)
  551.     return doRequest('user', 'register', {username = username, password = password, email = email, mcusername = mcusername})
  552.   end
  553.  
  554.   function testConnection()
  555.     local ok = false
  556.       parallel.waitForAny(function()
  557.         if http and http.get(apiURL) then
  558.         ok = true
  559.       end
  560.     end,function()
  561.         sleep(10)
  562.     end)
  563.     return ok
  564.   end
  565. ]]
  566.  
  567. local function loadJSON()
  568.         local sName = 'JSON'
  569.                
  570.         local tEnv = {}
  571.         setmetatable( tEnv, { __index = _G } )
  572.         local fnAPI, err = loadstring(_jstr)
  573.         if fnAPI then
  574.                 setfenv( fnAPI, tEnv )
  575.                 fnAPI()
  576.         else
  577.                 printError( err )
  578.                 return false
  579.         end
  580.        
  581.         local tAPI = {}
  582.         for k,v in pairs( tEnv ) do
  583.                 tAPI[k] =  v
  584.         end
  585.        
  586.         _G[sName] = tAPI
  587.         return true
  588. end
  589.  
  590. local function loadAPI()
  591.         local sName = 'api'
  592.                
  593.         local tEnv = {}
  594.         setmetatable( tEnv, { __index = _G } )
  595.         local fnAPI, err = loadstring(_api)
  596.         if fnAPI then
  597.                 setfenv( fnAPI, tEnv )
  598.                 fnAPI()
  599.         else
  600.                 printError( err )
  601.                 return false
  602.         end
  603.        
  604.         local tAPI = {}
  605.         for k,v in pairs( tEnv ) do
  606.                 tAPI[k] =  v
  607.         end
  608.        
  609.         _G[sName] = tAPI
  610.         return true
  611. end
  612.  
  613. local tArgs = {...}
  614.  
  615. loadJSON()
  616. loadAPI()
  617.  
  618. Settings = {
  619.   InstallLocation = '/', --if you have a folder you'd like programs to be installed to (for an OS) change this (e.g. /Programs/)
  620.   AlwaysFolder = false, --when false if there is only one file it will save it as a single file, if true files will always be placed in a folder
  621. }
  622.  
  623. local isMenuVisible = false
  624. local currentPage = ''
  625. local listItems = {}
  626.  
  627. local isRunning = true
  628. local currentScroll = 0
  629. local maxScroll = 0
  630. local pageHeight = 0
  631.  
  632. local searchBox = nil
  633. local featuredBannerTimer = nil
  634.  
  635. Values = {
  636.   ToolbarHeight = 2,
  637. }
  638.  
  639. Current = {
  640.   CursorBlink = false,
  641.   CursorPos = {},
  642.   CursorColour = colours.black
  643. }
  644.  
  645. local function split(str, sep)
  646.         local sep, fields = sep or ":", {}
  647.         local pattern = string.format("([^%s]+)", sep)
  648.         str:gsub(pattern, function(c) fields[#fields+1] = c end)
  649.         return fields
  650. end
  651. --This is my drawing API, is is pretty much identical to what drives PearOS.
  652. local _w, _h = term.getSize()
  653. Drawing = {
  654.  
  655.   Screen = {
  656.     Width = _w,
  657.     Height = _h
  658.   },
  659.  
  660.   DrawCharacters = function (x, y, characters, textColour,bgColour)
  661.     Drawing.WriteStringToBuffer(x, y, characters, textColour, bgColour)
  662.   end,
  663.  
  664.   DrawBlankArea = function (x, y, w, h, colour)
  665.     Drawing.DrawArea (x, y, w, h, " ", 1, colour)
  666.   end,
  667.  
  668.   DrawArea = function (x, y, w, h, character, textColour, bgColour)
  669.     --width must be greater than 1, other wise we get a stack overflow
  670.     if w < 0 then
  671.       w = w * -1
  672.     elseif w == 0 then
  673.       w = 1
  674.     end
  675.  
  676.     for ix = 1, w do
  677.       local currX = x + ix - 1
  678.       for iy = 1, h do
  679.         local currY = y + iy - 1
  680.         Drawing.WriteToBuffer(currX, currY, character, textColour, bgColour)
  681.       end
  682.     end
  683.   end,
  684.  
  685.   LoadImage = function(str)
  686.     local image = {
  687.       text = {},
  688.       textcol = {}
  689.     }
  690.     local tLines = split(str, '\n')
  691.     for num, sLine in ipairs(tLines) do
  692.             table.insert(image, num, {})
  693.             table.insert(image.text, num, {})
  694.             table.insert(image.textcol, num, {})
  695.                                        
  696.             --As we're no longer 1-1, we keep track of what index to write to
  697.             local writeIndex = 1
  698.             --Tells us if we've hit a 30 or 31 (BG and FG respectively)- next char specifies the curr colour
  699.             local bgNext, fgNext = false, false
  700.             --The current background and foreground colours
  701.             local currBG, currFG = nil,nil
  702.             for i=1,#sLine do
  703.                     local nextChar = string.sub(sLine, i, i)
  704.                     if nextChar:byte() == 30 then
  705.                             bgNext = true
  706.                     elseif nextChar:byte() == 31 then
  707.                             fgNext = true
  708.                     elseif bgNext then
  709.                             currBG = Drawing.GetColour(nextChar)
  710.                             bgNext = false
  711.                     elseif fgNext then
  712.                             currFG = Drawing.GetColour(nextChar)
  713.                             fgNext = false
  714.                     else
  715.                             if nextChar ~= " " and currFG == nil then
  716.                                     currFG = colours.white
  717.                             end
  718.                             image[num][writeIndex] = currBG
  719.                             image.textcol[num][writeIndex] = currFG
  720.                             image.text[num][writeIndex] = nextChar
  721.                             writeIndex = writeIndex + 1
  722.                     end
  723.             end
  724.             num = num+1
  725.         end
  726.     return image
  727.   end,
  728.  
  729.   DrawImage = function(_x,_y,tImage, w, h)
  730.     if tImage then
  731.       for y = 1, h do
  732.         if not tImage[y] then
  733.           break
  734.         end
  735.         for x = 1, w do
  736.           if not tImage[y][x] then
  737.             break
  738.           end
  739.           local bgColour = tImage[y][x]
  740.                 local textColour = tImage.textcol[y][x] or colours.white
  741.                 local char = tImage.text[y][x]
  742.                 Drawing.WriteToBuffer(x+_x-1, y+_y-1, char, textColour, bgColour)
  743.         end
  744.       end
  745.     elseif w and h then
  746.       Drawing.DrawBlankArea(_x, _y, w, h, colours.lightGrey)
  747.     end
  748.   end,
  749.  
  750.   DrawCharactersCenter = function(x, y, w, h, characters, textColour,bgColour)
  751.     w = w or Drawing.Screen.Width
  752.     h = h or Drawing.Screen.Height
  753.     x = x or math.floor((w - #characters) / 2)
  754.     y = y or math.floor(h / 2)
  755.  
  756.     Drawing.DrawCharacters(x, y, characters, textColour, bgColour)
  757.   end,
  758.  
  759.   GetColour = function(hex)
  760.       local value = tonumber(hex, 16)
  761.       if not value then return nil end
  762.       value = math.pow(2,value)
  763.       return value
  764.   end,
  765.  
  766.   Clear = function (_colour)
  767.     _colour = _colour or colours.black
  768.     Drawing.ClearBuffer()
  769.     Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, Drawing.Screen.Height, _colour)
  770.   end,
  771.  
  772.   Buffer = {},
  773.   BackBuffer = {},
  774.  
  775.   DrawBuffer = function()
  776.     for y,row in pairs(Drawing.Buffer) do
  777.       for x,pixel in pairs(row) do
  778.         local shouldDraw = true
  779.         local hasBackBuffer = true
  780.         if Drawing.BackBuffer[y] == nil or Drawing.BackBuffer[y][x] == nil or #Drawing.BackBuffer[y][x] ~= 3 then
  781.           hasBackBuffer = false
  782.         end
  783.         if hasBackBuffer and Drawing.BackBuffer[y][x][1] == Drawing.Buffer[y][x][1] and Drawing.BackBuffer[y][x][2] == Drawing.Buffer[y][x][2] and Drawing.BackBuffer[y][x][3] == Drawing.Buffer[y][x][3] then
  784.           shouldDraw = false
  785.         end
  786.         if shouldDraw then
  787.           term.setBackgroundColour(pixel[3])
  788.           term.setTextColour(pixel[2])
  789.           term.setCursorPos(x, y)
  790.           term.write(pixel[1])
  791.         end
  792.       end
  793.     end
  794.     Drawing.BackBuffer = Drawing.Buffer
  795.     Drawing.Buffer = {}
  796.   end,
  797.  
  798.   ClearBuffer = function()
  799.     Drawing.Buffer = {}
  800.   end,
  801.  
  802.   Offset = {
  803.     X = 0,
  804.     Y = 0,
  805.   },
  806.  
  807.   SetOffset = function(x, y)
  808.     Drawing.Offset.X = x
  809.     Drawing.Offset.Y = y
  810.   end,
  811.  
  812.   ClearOffset = function()
  813.     Drawing.Offset = {
  814.       X = 0,
  815.       Y = 0,
  816.     }
  817.   end,
  818.  
  819.   WriteStringToBuffer = function (x, y, characters, textColour,bgColour)
  820.     for i = 1, #tostring(characters) do
  821.         local character = tostring(characters):sub(i,i)
  822.         Drawing.WriteToBuffer(x + i - 1, y, character, textColour, bgColour)
  823.     end
  824.   end,
  825.  
  826.   WriteToBuffer = function(x, y, character, textColour,bgColour)
  827.     x = x + Drawing.Offset.X
  828.     y = y + Drawing.Offset.Y
  829.     Drawing.Buffer[y] = Drawing.Buffer[y] or {}
  830.     Drawing.Buffer[y][x] = {character, textColour, bgColour}
  831.   end
  832.  
  833. }
  834.  
  835. SearchPage = {
  836.   X = 0,
  837.   Y = 0,
  838.   Width = 0,
  839.   Height = 3,
  840.   Text = "",
  841.   Placeholder = "Search...",
  842.   CursorPos = 1,
  843.  
  844.   Draw = function(self)
  845.     Drawing.DrawBlankArea(self.X+1, self.Y+1, self.Width-6, self.Height, colours.grey)
  846.     Drawing.DrawBlankArea(self.X, self.Y, self.Width-6, self.Height, colours.white)
  847.  
  848.  
  849.     Drawing.DrawBlankArea(self.X + self.Width - 5 + 1, self.Y + 1, 6, self.Height, colours.grey)
  850.     Drawing.DrawBlankArea(self.X + self.Width - 5, self.Y, 6, self.Height, colours.blue)
  851.     Drawing.DrawCharacters(self.X + self.Width - 3, self.Y + 1, "GO", colours.white, colours.blue)
  852.  
  853.     RegisterClick(self.X + self.Width - 5, self.Y, 6, self.Height, function()
  854.       ChangePage('Search Results', self.Text)
  855.     end)
  856.  
  857.     if self.Text == "" then
  858.       Drawing.DrawCharacters(self.X+1, self.Y+1, self.Placeholder, colours.lightGrey, colours.white)
  859.     else
  860.       Drawing.DrawCharacters(self.X+1, self.Y+1, self.Text, colours.black, colours.white)
  861.     end
  862.  
  863.     Current.CursorBlink = true
  864.     Current.CursorPos = {self.X+self.CursorPos, self.Y+3}
  865.     Current.CursorColour = colours.black
  866.  
  867.   end,
  868.  
  869.   Initialise = function(self)
  870.     local new = {}    -- the new instance
  871.     setmetatable( new, {__index = self} )
  872.     new.Y = math.floor((Drawing.Screen.Height - 1 - new.Height) / 2)
  873.     new.X = 2
  874.     new.Width = Drawing.Screen.Width - 4
  875.     return new
  876.   end
  877. }
  878.  
  879. ListItem = {
  880.   X = 0,
  881.   Y = 0,
  882.   XMargin = 1,
  883.   YMargin = 1,
  884.   Width = 0,
  885.   Height = 6,
  886.   AppID = 0,
  887.   Title = '',
  888.   Author = '',
  889.   Rating = 0,
  890.   Description = {},
  891.   Icon = {},
  892.   Downloads = 0,
  893.   Category = '?',
  894.   Version = 1,
  895.   Type = 0, --0 = app list item, 1 = more info, 2 category
  896.  
  897.   CalculateWrapping = function(self, text)
  898.  
  899.     local numberOfLines = false
  900.  
  901.     if self.Type == 0 then
  902.       numberOfLines = 2
  903.     end
  904.    
  905.     local textWidth = self.Width - 8
  906.  
  907.     local lines = {''}
  908.         for word, space in text:gmatch('(%S+)(%s*)') do
  909.                 local temp = lines[#lines] .. word .. space:gsub('\n','')
  910.                 if #temp > textWidth then
  911.                         table.insert(lines, '')
  912.                 end
  913.                 if space:find('\n') then
  914.                         lines[#lines] = lines[#lines] .. word
  915.                        
  916.                         space = space:gsub('\n', function()
  917.                                 table.insert(lines, '')
  918.                                 return ''
  919.                         end)
  920.                 else
  921.                         lines[#lines] = lines[#lines] .. word .. space
  922.                 end
  923.         end
  924.  
  925.         if not numberOfLines then
  926.           return lines
  927.         else
  928.           local _lines = {}
  929.           for i, v in ipairs(lines) do
  930.             _lines[i] = v
  931.             if i >= numberOfLines then
  932.               return _lines
  933.             end
  934.           end
  935.           return _lines
  936.         end
  937.   end,
  938.   Draw = function(self)
  939.     if self.Y + Drawing.Offset.Y >= Drawing.Screen.Height + 1 or self.Y + Drawing.Offset.Y + self.Height <= 1 then
  940.       return
  941.     end
  942.     --register clicks
  943.     --install
  944.  
  945.     local installPos = 1
  946.  
  947.     if self.Type == 1 then
  948.       installPos = 2
  949.     end
  950.  
  951.     RegisterClick(self.Width - 7, self.Y + Drawing.Offset.Y + installPos - 1, 9, 1, function()
  952.       Load("Installing App", function()
  953.         api.installApplication(tonumber(self.AppID), Settings.InstallLocation..'/', true, Settings.AlwaysFolder)
  954.         --api.saveApplicationIcon(tonumber(self.AppID), Settings.InstallLocation..'/'..self.Title.."/icon")
  955.       end)
  956.       Load("Application Installed!", function()
  957.         sleep(1)
  958.       end)
  959.     end)
  960.  
  961.     --more info
  962.     if self.Type == 0 then
  963.       RegisterClick(self.X, self.Y + Drawing.Offset.Y, self.Width, self.Height, function()
  964.         ChangePage('more-info',self.AppID)
  965.       end)
  966.     elseif self.Type == 2 then
  967.       RegisterClick(self.X, self.Y + Drawing.Offset.Y, self.Width, self.Height, function()
  968.         ChangePage('Category Items',self.Title)
  969.       end)
  970.     end
  971.  
  972.     Drawing.DrawBlankArea(self.X+1, self.Y+1, self.Width, self.Height, colours.grey)
  973.     Drawing.DrawBlankArea(self.X, self.Y, self.Width, self.Height, colours.white)
  974.    
  975.     --Drawing.DrawBlankArea(self.X+1, self.Y+1, 6, 4, colours.green)
  976.     Drawing.DrawCharacters(self.X + 8, self.Y + 1, self.Title, colours.black, colours.white)
  977.     if self.Type ~= 2 then
  978.       Drawing.DrawCharacters(self.X + 8, self.Y + 2, "by "..self.Author, colours.grey, colours.white)
  979.       Drawing.DrawCharacters(self.Width - 8, self.Y + installPos - 1, " Install ", colours.white, colours.green)
  980.     end
  981.  
  982.     Drawing.DrawImage(self.X+1, self.Y+1, self.Icon, 4, 3)
  983.    
  984.  
  985.     if self.Type == 1 then
  986.       Drawing.DrawCharacters(self.X, self.Y + 6, "Category", colours.grey, colours.white)
  987.       Drawing.DrawCharacters(math.ceil(self.X+(8-#self.Category)/2), self.Y + 7, self.Category, colours.grey, colours.white)
  988.  
  989.       Drawing.DrawCharacters(self.X+1, self.Y + 9, "Dwnlds", colours.grey, colours.white)
  990.       Drawing.DrawCharacters(math.ceil(self.X+(8-#tostring(self.Downloads))/2), self.Y + 10, tostring(self.Downloads), colours.grey, colours.white)
  991.  
  992.       Drawing.DrawCharacters(self.X+1, self.Y + 12, "Version", colours.grey, colours.white)
  993.       Drawing.DrawCharacters(math.ceil(self.X+(8-#tostring(self.Version))/2), self.Y + 13, tostring(self.Version), colours.grey, colours.white)
  994.  
  995.     end
  996.  
  997.     if self.Type ~= 2 then
  998.       --draw the rating
  999.       local starColour = colours.yellow
  1000.       local halfColour = colours.lightGrey
  1001.       local emptyColour = colours.lightGrey
  1002.  
  1003.       local sX = self.X + 8 + #("by "..self.Author) + 1
  1004.       local sY = self.Y + 2
  1005.  
  1006.         local s1C = emptyColour
  1007.         local s1S = " "
  1008.  
  1009.         local s2C = emptyColour
  1010.         local s2S = " "
  1011.  
  1012.         local s3C = emptyColour
  1013.         local s3S = " "
  1014.  
  1015.         local s4C = emptyColour
  1016.         local s4S = " "
  1017.  
  1018.         local s5C = emptyColour
  1019.         local s5S = " "
  1020.  
  1021.       if self.Rating >= .5 then
  1022.         s1C = halfColour
  1023.         s1S = "#"
  1024.       end
  1025.  
  1026.       if self.Rating >= 1 then
  1027.         s1C = starColour
  1028.         s1S = " "
  1029.       end
  1030.  
  1031.  
  1032.       if self.Rating >= 1.5 then
  1033.         s2C = halfColour
  1034.         s2S = "#"
  1035.       end
  1036.  
  1037.       if self.Rating >= 2 then
  1038.         s2C = starColour
  1039.         s2S = " "
  1040.       end
  1041.  
  1042.        
  1043.       if self.Rating >= 2.5 then
  1044.         s3C = halfColour
  1045.         s3S = "#"
  1046.       end
  1047.  
  1048.       if self.Rating >= 3 then
  1049.         s3C = starColour
  1050.         s3S = " "
  1051.       end
  1052.  
  1053.        
  1054.       if self.Rating >= 3.5 then
  1055.         s4C = halfColour
  1056.         s4S = "#"
  1057.       end
  1058.  
  1059.       if self.Rating >= 4 then
  1060.         s4C = starColour
  1061.         s4S = " "
  1062.       end
  1063.  
  1064.        
  1065.       if self.Rating >= 4.5 then
  1066.         s5C = halfColour
  1067.         s5S = "#"
  1068.       end
  1069.  
  1070.       if self.Rating == 5 then
  1071.         s5C = starColour
  1072.         s5S = " "
  1073.       end
  1074.  
  1075.       Drawing.DrawCharacters(sX, sY, s1S, starColour, s1C)
  1076.       Drawing.DrawCharacters(sX + 2, sY, s2S, starColour, s2C)
  1077.       Drawing.DrawCharacters(sX + 4, sY, s3S, starColour, s3C)
  1078.       Drawing.DrawCharacters(sX + 6, sY, s4S, starColour, s4C)
  1079.       Drawing.DrawCharacters(sX + 8, sY, s5S, starColour, s5C)
  1080.     end
  1081.  
  1082.     local descPos = 2
  1083.  
  1084.  
  1085.  
  1086.     if self.Type == 1 then
  1087.       descPos = 3
  1088.     elseif self.Type == 2 then
  1089.       descPos = 1
  1090.     end
  1091.  
  1092.     for _,line in ipairs(self.Description) do
  1093.       Drawing.DrawCharacters(self.X + 8, self.Y + descPos + _, line, colours.lightGrey, colours.white)
  1094.     end
  1095.   end,
  1096.   Initialise = function(self, y, appid, title, icon, description, author, rating, version, category, downloads, Type)
  1097.     Type = Type or 0
  1098.     local new = {}    -- the new instance
  1099.     setmetatable( new, {__index = self} )
  1100.     new.Y = y
  1101.     new.Type = Type
  1102.     new:UpdateSize()
  1103.     new.AppID = appid
  1104.     new.Title = title
  1105.     new.Icon = Drawing.LoadImage(icon)
  1106.     new.Icon[5] = nil
  1107.     new.Description = new:CalculateWrapping(description)
  1108.     new.Author = author
  1109.     new.Rating = rating
  1110.     new.Version = version
  1111.     new.Category = category
  1112.     new.Downloads = downloads
  1113.     return new
  1114.   end,
  1115.   UpdateSize = function(self)
  1116.     self.X = self.XMargin + 1
  1117.     self.Width = Drawing.Screen.Width - 2 * self.XMargin - 2
  1118.  
  1119.     if self.Type == 1 then
  1120.       self.Height = 15
  1121.     end
  1122.   end,
  1123. }
  1124.  
  1125. Clicks = {
  1126.  
  1127. }
  1128.  
  1129. function RegisterClick(x, y, width, height, action)
  1130.   table.insert(Clicks,{
  1131.     X = x,
  1132.     Y = y,
  1133.     Width = width,
  1134.     Height = height,
  1135.     Action = action
  1136.   })
  1137. end
  1138.  
  1139. function Load(title, func)
  1140.   Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width+1, Drawing.Screen.Height+1, colours.lightGrey)
  1141.   Drawing.DrawCharactersCenter(nil, Drawing.Screen.Height/2, nil, nil, title, colours.white, colours.lightGrey)
  1142.   isLoading = true
  1143.   parallel.waitForAny(function()
  1144.     func()
  1145.     isLoading = false
  1146.   end, DisplayLoader)
  1147. end
  1148.  
  1149. function DisplayLoader()
  1150.   local maxStep = 100 -- about 10 seconds, timeout
  1151.   local currStep = 0
  1152.   local loadStep = 0
  1153.   while isLoading do
  1154.     local Y = Drawing.Screen.Height/2 + 2
  1155.     local cX = Drawing.Screen.Width/2
  1156.  
  1157.     Drawing.DrawCharacters(cX-3, Y, ' ', colours.black, colours.grey)
  1158.     Drawing.DrawCharacters(cX-1, Y, ' ', colours.black, colours.grey)
  1159.     Drawing.DrawCharacters(cX+1, Y, ' ', colours.black, colours.grey)
  1160.     Drawing.DrawCharacters(cX+3, Y, ' ', colours.black, colours.grey)
  1161.    
  1162.     if loadStep ~= -1 then
  1163.       Drawing.DrawCharacters(cX-3 + (loadStep * 2), Y, ' ', colours.black, colours.white)  
  1164.     end
  1165.  
  1166.     loadStep = loadStep + 1
  1167.     if loadStep >= 4 then
  1168.       loadStep = -1
  1169.     end
  1170.  
  1171.     currStep = currStep + 1
  1172.     if currStep >= maxStep then
  1173.       isLoading = false
  1174.       error('Load timeout. Check your internet connection and try again. The server may also be down, try again in 10 minutes.')
  1175.     end
  1176.  
  1177.     Drawing.DrawBuffer()
  1178.     sleep(0.15)
  1179.   end
  1180. end
  1181.  
  1182. function ChangePage(title, arg)
  1183.   ClearCurrentPage()
  1184.   if title == 'Top Charts' then
  1185.     LoadList(api.getTopCharts)
  1186.   elseif title == 'Search Results' then
  1187.     LoadList(function() return api.searchApplications(arg) end)
  1188.   elseif title == "Featured" then
  1189.     LoadFeatured()
  1190.   elseif title == "Categories" then
  1191.     LoadCategories()
  1192.   elseif title == "more-info" then
  1193.     LoadAboutApp(arg)
  1194.   elseif title == "Search" then
  1195.     LoadSearch()
  1196.   elseif title == "Category Items" then
  1197.     LoadList(function() return api.getApplicationsInCategory(arg) end)
  1198.   end
  1199.  
  1200.   currentPage = title
  1201.  
  1202.   maxScroll = getMaxScroll()
  1203. end
  1204.  
  1205. function LoadAboutApp(id)
  1206.   Load("Loading Application", function()
  1207.     --ClearCurrentPage()
  1208.     local app = api.getApplication(id)
  1209.     local item = ListItem:Initialise(1, app.id, app.name, app.icon, app.description, app.user.username, app.stars, app.version, app.category, app.downloads, 1)
  1210.     table.insert(listItems, item)
  1211.   end)
  1212.  
  1213. end
  1214.  
  1215. function LoadFeatured()
  1216.   Load("Loading", function()
  1217.     local tApps = api.getFeaturedApplications()
  1218.  
  1219.     --all items
  1220.     for i, app in ipairs(tApps) do
  1221.       local item = ListItem:Initialise(1+(i-1)*(ListItem.Height + 2),
  1222.         app.id, app.name, app.icon, app.description,
  1223.          app.user.username, app.stars, app.version,
  1224.           app.category, app.downloads)
  1225.       table.insert(listItems, item)
  1226.     end
  1227.   end)  
  1228. end
  1229.  
  1230. function LoadCategories()
  1231.   Load("Loading", function()
  1232.     local tApps = api.getCategories()
  1233.     local i = 1
  1234.     for name, category in pairs(tApps) do
  1235.       local item = ListItem:Initialise(1+(i-1)*(ListItem.Height + 2),
  1236.         0, name, category.icon, category.description, nil, nil, nil, nil, nil, 2)
  1237.       table.insert(listItems, item)
  1238.       i = i + 1
  1239.     end
  1240.   end)
  1241. end
  1242.  
  1243. function LoadSearch(id)
  1244.     local item = SearchPage:Initialise()
  1245.     searchBox = item
  1246.     --featuredBannerTimer = os.startTimer(5)
  1247.     table.insert(listItems, item)
  1248.  
  1249. end
  1250.  
  1251. function ClearCurrentPage()
  1252.   --listItems = {}
  1253.   for i,v in ipairs(listItems) do listItems[i]=nil end
  1254.   currentScroll = 0
  1255.   searchBox = nil
  1256.   featuredBannerTimer = nil
  1257.  
  1258.   Current.CursorBlink = false
  1259.   Draw()
  1260. end
  1261.  
  1262. function LoadList(func)
  1263.   Load("Loading", function()
  1264.     local tApps = func()
  1265.     if tApps == nil then
  1266.       error('Can not connect to the App Store server.')
  1267.     elseif type(tApps) ~= 'table' then
  1268.       error('The server is too busy. Try again in a few minutes.')
  1269.     end
  1270.     for i, app in ipairs(tApps) do
  1271.       local item = ListItem:Initialise(1+(i-1)*(ListItem.Height + 2),
  1272.         app.id, app.name, app.icon, app.description,
  1273.          app.user.username, app.stars, app.version,
  1274.           app.category, app.downloads)
  1275.       table.insert(listItems, item)
  1276.     end
  1277.   end)
  1278. end
  1279.  
  1280. function Draw()
  1281.   Clicks = {}
  1282.   Drawing.Clear(colours.lightGrey)
  1283.   DrawList()
  1284.   DrawToolbar()
  1285.  
  1286.   --DrawScrollbar()
  1287.  
  1288.   Drawing.DrawBuffer()
  1289.  
  1290.   if Current.CursorPos and Current.CursorPos[1] and Current.CursorPos[2] then
  1291.     term.setCursorPos(unpack(Current.CursorPos))
  1292.   end
  1293.   term.setTextColour(Current.CursorColour)
  1294.   term.setCursorBlink(Current.CursorBlink)
  1295. end
  1296.  
  1297. function DrawList()
  1298.   Drawing.SetOffset(0, -currentScroll + 2)
  1299.     for i, v in ipairs(listItems) do
  1300.       v:Draw()
  1301.     end
  1302.   Drawing.ClearOffset()
  1303.  
  1304.   if getMaxScroll() ~= 0 then
  1305.     DrawScrollBar(Drawing.Screen.Width, currentScroll, getMaxScroll())
  1306.   end
  1307. end
  1308.  
  1309. --[[
  1310. function DrawScrollbar()
  1311.  
  1312.   local scrollBarHeight = Drawing.Screen.Height - 1
  1313.   local scrollBarPosition = 0
  1314.  
  1315.   if pageHeight > 0 and maxScroll > 0 then
  1316.     scrollBarHeight = (Drawing.Screen.Height / pageHeight) * (Drawing.Screen.Height - 1)
  1317.     scrollBarPosition = (currentScroll / pageHeight) * (Drawing.Screen.Height - 1)
  1318.   end
  1319.  
  1320.   Drawing.DrawBlankArea(Drawing.Screen.Width, scrollBarPosition + 2, 1, scrollBarHeight, colours.blue)
  1321.  
  1322.  
  1323.   Drawing.DrawCharacters(Drawing.Screen.Width, scrollBarPosition + 2, "-", colours.black,colours.white)
  1324.  
  1325.   Drawing.DrawCharacters(Drawing.Screen.Width-1, 2, "+", colours.black,colours.white)
  1326.  
  1327.   --Drawing.DrawBuffer()
  1328.  
  1329.   Drawing.DrawBlankArea(51, 2, 1, 18, colours.green)
  1330.  
  1331.  
  1332. end
  1333. ]]--
  1334.  
  1335. function DrawScrollBar(x, current, max)
  1336.   local fullHeight = Drawing.Screen.Height - 3
  1337.   local barHeight = (fullHeight - max)
  1338.   if barHeight < 5 then
  1339.     barHeight = 5
  1340.   end
  1341.   Drawing.DrawBlankArea(x, 4, 1, fullHeight, colours.grey)
  1342.   Drawing.DrawBlankArea(x, 4+current, 1, barHeight, colours.lightGrey)
  1343. end
  1344.  
  1345. function DrawToolbar()
  1346.   Drawing.DrawBlankArea(1, 1, Drawing.Screen.Width, 1, colours.white)
  1347.   local items = {
  1348.     {
  1349.       active = false,
  1350.       title = "Featured"
  1351.     },
  1352.     {
  1353.       active = false,
  1354.       title = "Top Charts"
  1355.     },
  1356.     {
  1357.       active = false,
  1358.       title = "Categories"
  1359.     },
  1360.     {
  1361.       active = false,
  1362.       title = "Search"
  1363.     }
  1364.   }
  1365.   local itemsLength = 0
  1366.   local itemsString = ""
  1367.   for i, v in ipairs(items) do
  1368.     itemsLength = itemsLength + #v.title + 3
  1369.     itemsString = itemsString .. v.title .. " | "
  1370.   end
  1371.   itemsLength = itemsLength - 3
  1372.  
  1373.   local itemX = (Drawing.Screen.Width - itemsLength) / 2
  1374.  
  1375.   for i, v in ipairs(items) do
  1376.     local border = " | "
  1377.     if i == #items then
  1378.       border = ""
  1379.     end
  1380.     Drawing.DrawCharacters(itemX, 1, v.title .. border, colours.blue, colours.white)
  1381.     RegisterClick(itemX-1, 1, #v.title + 2, 1, function()
  1382.       ChangePage(v.title)
  1383.     end)
  1384.     itemX = itemX + #(v.title .. border)
  1385.   end
  1386.   Drawing.DrawCharacters(Drawing.Screen.Width, 1, "X", colours.white, colours.red)
  1387.  
  1388.   RegisterClick(Drawing.Screen.Width, 1, 1, 1, function()
  1389.     if OneOS then
  1390.       OneOS.Close()
  1391.     end
  1392.     isRunning = false
  1393.     term.setBackgroundColour(colours.black)
  1394.     term.setTextColour(colours.white)
  1395.     term.clear()
  1396.     term.setCursorPos(1,1)
  1397.     print = _print
  1398.     print('Thanks for using the App Store!')
  1399.     print('(c) oeed 2013 - 2014')
  1400.   end)
  1401. end
  1402.  
  1403. function getMaxScroll()
  1404.   local totalHeight = 0
  1405.   for i, v in ipairs(listItems) do
  1406.     totalHeight = totalHeight + v.Height + 2
  1407.   end
  1408.  
  1409.   local s = totalHeight - Drawing.Screen.Height + 2
  1410.    
  1411.   if s < 0 then
  1412.     s = 0
  1413.   end
  1414.  
  1415.   pageHeight = totalHeight
  1416.  
  1417.   return s
  1418. end
  1419.  
  1420. function setScroll(iScroll)
  1421.   maxScroll = getMaxScroll()
  1422.   currentScroll = iScroll
  1423.   if currentScroll < 0 then
  1424.     currentScroll = 0
  1425.   elseif currentScroll > maxScroll then
  1426.     currentScroll = maxScroll
  1427.   end
  1428. end
  1429.  
  1430. function EventHandler()
  1431.   while isRunning do
  1432.     local event, arg, x, y = os.pullEvent()
  1433.  
  1434.     if event == "mouse_scroll" then
  1435.       setScroll(currentScroll + (arg * 3))
  1436.       Draw()
  1437.     elseif event == "timer" then
  1438.       if arg == featuredBannerTimer and currentPage == 'Featured' then
  1439.  
  1440.         --featuredBannerTimer = os.startTimer(5)
  1441.         listItems[1]:NextPage()
  1442.         Draw()
  1443.       end
  1444.     elseif event == "char" then
  1445.       if currentPage == 'Search' then
  1446.         searchBox.Text = searchBox.Text .. arg
  1447.         searchBox.CursorPos = searchBox.CursorPos + 1
  1448.         Draw()
  1449.       end
  1450.     elseif event == "key" then
  1451.       if arg == keys.down then
  1452.         setScroll(currentScroll + 3)
  1453.         Draw()
  1454.  
  1455.       elseif arg == keys.up then
  1456.         setScroll(currentScroll - 3)
  1457.         Draw()
  1458.       end
  1459.  
  1460.       if arg == keys.backspace and currentPage == 'Search' then
  1461.         searchBox.Text = string.sub(searchBox.Text,0,#searchBox.Text-1)
  1462.         searchBox.CursorPos = searchBox.CursorPos - 1
  1463.         if searchBox.CursorPos < 1 then
  1464.           searchBox.CursorPos = 1
  1465.         end
  1466.         Draw()
  1467.       elseif arg == keys.enter and currentPage == 'Search' then
  1468.         ChangePage('Search Results', searchBox.Text)
  1469.         Draw()
  1470.       end
  1471.  
  1472.     elseif event == "mouse_click" then
  1473.       local clicked = false
  1474.       for i = 1, #Clicks do
  1475.         local v = Clicks[(#Clicks - i) + 1]
  1476.         if not clicked and x >= v.X and (v.X + v.Width) > x and y >= v.Y and (v.Y + v.Height) > y then
  1477.           clicked = true
  1478.  
  1479.           local iMV = isMenuVisible
  1480.           v:Action()
  1481.  
  1482.           if iMV == isMenuVisible then
  1483.             isMenuVisible = false
  1484.           end
  1485.  
  1486.           Draw()
  1487.         end
  1488.       end
  1489.  
  1490.       if not clicked then
  1491.         isMenuVisible = false
  1492.         Draw()
  1493.       end
  1494.     end
  1495.  
  1496.  
  1497.  
  1498.   end
  1499. end
  1500.  
  1501. function TidyPath(path)
  1502.   if fs.exists(path) and fs.isDir(path) then
  1503.     path = path .. '/'
  1504.   end
  1505.  
  1506.   path, n = path:gsub("//", "/")
  1507.   while n > 0 do
  1508.     path, n = path:gsub("//", "/")
  1509.   end
  1510.   return path
  1511. end
  1512.  
  1513. function Initialise()
  1514.   if tArgs and tArgs[1] then
  1515.     if tArgs[1] == 'install' and tArgs[2] and tonumber(tArgs[2]) then
  1516.       print('Connecting...')
  1517.       if api.testConnection() then
  1518.         print('Downloading program...')
  1519.         local path = tArgs[3] or shell.dir()
  1520.         local location = api.installApplication(tonumber(tArgs[2]), path, true)
  1521.         if location then
  1522.           print('Program installed!')
  1523.           print("Type '"..TidyPath(location).."' to run it.")
  1524.         else
  1525.           printError('Download failed. Check the ID and try again.')
  1526.         end
  1527.       else
  1528.         printError('Could not connect to the App Store.')
  1529.         printError('Check your connection and try again.')
  1530.       end
  1531.     elseif tArgs[1] == 'submit' and tArgs[2] and fs.exists(shell.resolve(tArgs[2])) then
  1532.       print('Packaging...')
  1533.       local pkg = Package(shell.resolve(tArgs[2]))
  1534.       if pkg then
  1535.         print('Connecting...')
  1536.         if api.testConnection() then
  1537.           print('Uploading...')
  1538.           local str = JSON.encode(pkg)
  1539.           str = str:gsub("\\'","'")
  1540.           local h = http.post('http://appstore.olivercooper.me/submitPreupload.php',
  1541.                                   "file="..textutils.urlEncode(str));
  1542.           if h then
  1543.             local id = h.readAll()
  1544.             if id:sub(1,2) == 'OK' then
  1545.               print('Your program has been uploaded.')
  1546.               print('It\'s unique ID is: '..id:sub(3))
  1547.               print('Go to ccappstore.com/submit/ and select "In Game" as the upload option and enter the above code.')
  1548.             else
  1549.               printError('The server rejected the file. Try again or PM oeed. ('..h.getResponseCode()..' error)')
  1550.             end
  1551.           else
  1552.               printError('Could not submit file.')
  1553.           end
  1554.         else
  1555.           printError('Could not connect to the App Store.')
  1556.           printError('Check your connection and try again.')
  1557.         end
  1558.       end
  1559.     else
  1560.       print('Useage: appstore install <app id> <path (optional)>')
  1561.       print('Or: appstore submit <path>')
  1562.     end
  1563.   else
  1564.     Load('Connecting', api.testConnection)
  1565.     ChangePage('Top Charts')
  1566.     Draw()
  1567.     EventHandler()
  1568.   end
  1569. end
  1570.  
  1571. function addFile(package, path, name)
  1572.   if name == '.DS_Store' or shell.resolve(path) == shell.resolve(shell.getRunningProgram()) then
  1573.     return package
  1574.   end
  1575.   local h = fs.open(path, 'r')
  1576.   if not h then
  1577.     error('Failed reading file: '..path)
  1578.   end
  1579.   package[name] = h.readAll()
  1580.   h.close()
  1581.   return package
  1582. end
  1583.  
  1584. function addFolder(package, path, master)
  1585.   local subPkg = {}
  1586.  
  1587.   if path:sub(1,4) == '/rom' then
  1588.     return package
  1589.   end
  1590.   for i, v in ipairs(fs.list(path)) do
  1591.     if fs.isDir(path..'/'..v) then
  1592.       subPkg = addFolder(subPkg, path..'/'..v)
  1593.     else
  1594.       subPkg = addFile(subPkg, path..'/'..v, v)
  1595.     end
  1596.   end
  1597.  
  1598.   if master then
  1599.     package = subPkg
  1600.   else
  1601.     package[fs.getName(path)] = subPkg
  1602.   end
  1603.   return package
  1604. end
  1605.  
  1606. function Package(path)
  1607.   local pkg = {}
  1608.   if fs.isDir(path) then
  1609.     pkg = addFolder(pkg, path, true)
  1610.   else
  1611.     pkg = addFile(pkg, path, 'startup')
  1612.   end
  1613.   if not pkg['startup'] then
  1614.     print('You must have a file named startup in your program. This is the file used to start the program.')
  1615.   else
  1616.     return pkg
  1617.   end
  1618. end
  1619.  
  1620. if term.isColor and term.isColor() then
  1621.   local httpTest = nil
  1622.   if http then
  1623.     httpTest = true-- http.get('http://appstore.olivercooper.me/api/')
  1624.   end
  1625.   if httpTest == nil then
  1626.     print = _print
  1627.     term.setBackgroundColor(colours.grey)
  1628.     term.setTextColor(colours.white)
  1629.     term.clear()
  1630.     term.setCursorPos(3, 3)
  1631.  
  1632.     print("Could not connect to the App Store server!\n\n")
  1633.  
  1634.     term.setTextColor(colours.white)
  1635.     print("Try the following steps:")
  1636.     term.setTextColor(colours.lightGrey)
  1637.     print(' - Ensure you have enabled the HTTP API')
  1638.     print(' - Check your internet connection is working')
  1639.     print(' - Retrying again in 10 minutes')
  1640.     print(' - Get assistance on the forum page')
  1641.     print()
  1642.     print()
  1643.     print()
  1644.     term.setTextColor(colours.white)
  1645.     print(" Click anywhere to exit...")
  1646.     os.pullEvent("mouse_click")
  1647.     OneOS.Close()
  1648.  
  1649.   else
  1650.  
  1651.     -- Run main function
  1652.     local _, err = pcall(Initialise)
  1653.     if err then
  1654.       print = _print
  1655.       term.setBackgroundColor(colours.lightGrey)
  1656.       term.setTextColor(colours.white)
  1657.       term.clear()
  1658.  
  1659.  
  1660.       term.setBackgroundColor(colours.grey)
  1661.       term.setCursorPos(1, 2)
  1662.       term.clearLine()
  1663.       term.setCursorPos(1, 3)
  1664.       term.clearLine()
  1665.       term.setCursorPos(1, 4)
  1666.       term.clearLine()
  1667.       term.setCursorPos(3, 3)
  1668.  
  1669.       print("The ComputerCraft App Store has crashed!\n\n")
  1670.  
  1671.       term.setBackgroundColour(colours.lightGrey)
  1672.       print("Try repeating what you just did, if this is the second time you've seen this message go to")
  1673.       term.setTextColour(colours.black)
  1674.       print("http://appstore.olivercooper.me/help/crash/\n")
  1675.       term.setTextColour(colours.white)    
  1676.       print("The error was:")
  1677.  
  1678.       term.setTextColour(colours.black)
  1679.       print(" " .. tostring(err) .. "\n\n")
  1680.  
  1681.       term.setTextColour(colours.white)
  1682.       print(" Click anywhere to exit...")
  1683.       os.pullEvent("mouse_click")
  1684.       if OneOS then
  1685.         OneOS.Close()
  1686.       end
  1687.       term.setTextColour(colours.white)
  1688.       term.setBackgroundColour(colours.black)
  1689.       term.clear()
  1690.       term.setCursorPos(1,1)
  1691.     end
  1692.   end
  1693. else
  1694.   print('The App Store requires an Advanced (gold) Computer!')
  1695. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement