asweigart

appstore (localhost testing version)

Oct 21st, 2017
179
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 12.36 KB | None | 0 0
  1. --[[ TurtleAppstore.com client program by Al Sweigart
  2. Used to download/upload files from Minecraft to TurtleAppstore.com
  3.  
  4. The appstore program uses a hidden file named .appstoreusername
  5.  
  6. Usage:
  7. > appstore <permalink>
  8. > appstore get <appname> # uses set username
  9. > appstore get <username> <appname> # also sets the username
  10. > appstore run <appname>
  11. > appstore run <username> <appname>
  12. > appstore put <username> <filename> <otp> # uses set username
  13. > appstore put <filename> <otp> # uses set username
  14. > appstore update <filename> # doesn't cache the username
  15. > appstore updateAll # doesn't cache the username
  16.  
  17. ]]
  18.  
  19. -- TODO: what happens if we can't write to the turtle's harddrive due to lack of disk space?
  20. -- TODO: set up otp with a certain checksum so we can tell if the user might have typed their real turtleappstore.com password instead of an otp
  21. -- TODO:
  22. -- TODO: figure out where the error messages should go. in functions? after returning?
  23.  
  24. -- Note that TAS_HOST does *not* have the "https://" or "http://" prefix.
  25. --local TAS_HOST = 'turtleappstore.com'
  26. local TAS_HOST = 'localhost:8000'
  27. local PERMALINK_LEN = 6
  28. local cliArgs = {...}
  29.  
  30.  
  31. -- START OF CLI COMPLETION FUNCTION
  32. -- Copied from http://www.computercraft.info/wiki/Shell.setCompletionFunction
  33. function tabCompletionFunction(shell, parNumber, curText, lastText)
  34. -- Check that the parameters entered so far are valid:
  35. local curParam = { ['get '] = {}, ['run '] = {}, ['put '] = {}, ['update '] = {}, ['updateall '] = {}, }
  36. for i = 2, #lastText do
  37. if curParam[lastText[i] .. " "] then
  38. curParam = curParam[lastText[i] .. " "]
  39. else
  40. return {}
  41. end
  42. end
  43.  
  44. -- Check for suitable words for the current parameter:
  45. local results = {}
  46. for word, _ in pairs(curParam) do
  47. if word:sub(1, #curText) == curText then
  48. results[#results + 1] = word:sub(#curText + 1)
  49. end
  50. end
  51. return results
  52. end
  53.  
  54. shell.setCompletionFunction("appstore", tabCompletionFunction)
  55. -- END OF CLI COMPLETION FUNCTION
  56.  
  57.  
  58. function main()
  59. local result, username, filename, otp, fo, sourceCode, appname
  60. -- handle command line arguments
  61.  
  62. -- display usage
  63. if cliArgs[1] == nil then
  64. printUsage()
  65. return
  66. end
  67.  
  68. if not canConnect() then
  69. print('Failed to connect to')
  70. print('turtleappstore.com.')
  71. error()
  72. end
  73.  
  74. local cmd = cliArgs[1]
  75.  
  76. if cmd == 'get' or cmd == 'run' then
  77. if cliArgs[3] ~= nil then
  78. -- Download an app via username & appname
  79. username = cliArgs[2]
  80. appname = cliArgs[3]
  81. username, appname = getAppByName(username, appname)
  82. if username == false then -- TODO: add failure
  83. print('Could not find an app with that name')
  84. print('from that user. Did you type the name')
  85. print('of the user and app correctly?')
  86. error()
  87. end
  88. cacheUsername(username)
  89. if cmd == 'run' then
  90. shell.run(appname) -- run the downloaded program
  91. end
  92.  
  93. elseif cliArgs[2] ~= nil then
  94. -- Download an app via appname & cached username
  95. username = getCachedUsername()
  96. appname = cliArgs[2]
  97. if username == nil then
  98. print('Please specify a username & appname.')
  99. error()
  100. end
  101. print('Using username ' .. username) -- tell the user what cached user we're using
  102.  
  103. username, appname = getAppByName(username, appname)
  104. if username == false then -- TODO add failure if not connected or site is down, etc.
  105. print('TODO failed msg2')
  106. error()
  107. end
  108. cacheUsername(username)
  109. if cmd == 'run' then
  110. shell.run(cliArgs[2]) -- run the downloaded program
  111. end
  112. end
  113.  
  114. elseif cmd == 'put' then
  115. -- Upload an app via filename
  116.  
  117. if cliArgs[2] == nil then
  118. -- user didn't supply anything after "put"
  119. print('Usage:')
  120. print(' appstore put <username> <filename> <otp>')
  121. error()
  122.  
  123. elseif cliArgs[4] == nil then
  124. -- user supplied 3 things, we assume they are 'put', filename, and otp (and assume the username is cached)
  125. username = getCachedUsername()
  126. if username == nil then
  127. print('Usage:')
  128. print(' appstore put <username> <filename> <otp>')
  129. error()
  130. end
  131. print('Using username ' .. username) -- tell the user what cached user we're using
  132.  
  133. filename = cliArgs[2]
  134. otp = cliArgs[3]
  135. else
  136. -- user supplied 'put', username, filename, and otp
  137. username = cliArgs[2]
  138. filename = cliArgs[3]
  139. otp = cliArgs[4]
  140. end
  141.  
  142. print('Connecting to turtleappstore.com...')
  143. result = putAppByName(username, filename, otp)
  144. print(result)
  145. if result ~= 'Uploaded ' .. filename then
  146. error()
  147. end
  148.  
  149.  
  150. elseif cmd == 'update' then
  151. filename = cliArgs[2]
  152. local result, errMsg = updateApp(filename)
  153. if not result then
  154. print(errMsg)
  155. error()
  156. end
  157.  
  158.  
  159. elseif cmd == 'updateall' then
  160. updateAllApps('.')
  161.  
  162.  
  163. elseif string.len(cmd) == PERMALINK_LEN then
  164. -- Download an app via permalink
  165. local permalink = cliArgs[1]
  166. if getAppByPermalink(permalink) == false then
  167. error()
  168. end
  169. return true
  170.  
  171. else
  172. print('Command not understood.')
  173. printUsage()
  174. error()
  175. end
  176. end
  177.  
  178.  
  179. function canConnect()
  180. return http.get('https://' .. TAS_HOST) ~= nil or http.get('http://' .. TAS_HOST) ~= nil
  181. end
  182.  
  183.  
  184. function getCachedUsername()
  185. if not fs.exists('.appstoreusername') then
  186. return nil
  187. end
  188.  
  189. local fo = fs.open('.appstoreusername', 'r')
  190. local cachedUsername = fo.readLine()
  191. fo.close()
  192.  
  193. if cachedUsername == '' then
  194. return nil
  195. end
  196.  
  197. return cachedUsername
  198. end
  199.  
  200.  
  201. function cacheUsername(username)
  202. writeFile('.appstoreusername', username) -- TODO: make sure username is always in the proper case here
  203. end
  204.  
  205.  
  206. function printUsage()
  207. print('Usage:')
  208. -- TODO
  209. print(' appstore <permalink>')
  210. print(' appstore get <username> <appname>')
  211. print(' appstore run <username> <appname>')
  212. print(' appstore put <filename> <otp>')
  213. print(' appstore update <filename>')
  214. print(' appstore updateall')
  215. end
  216.  
  217.  
  218. function updateApp(filename)
  219. if not fs.exists(filename) then
  220. return false, 'File ' .. filename .. ' doesn\'t exist on this turtle.'
  221. end
  222.  
  223. local username, appname, sourceCode = readApp(filename)
  224. if not username then
  225. return false, appstore .. ' couldn\'t be updated because it wasn\'t originally downloaded from turtleappstore.com.'
  226. end
  227.  
  228. local username, appname = getAppByName(username, appname)
  229. if not username then
  230. return false, 'Couldn\'t download ' .. appname .. ' from user ' .. username .. '. Does this user and app still exist on turtleappstore.com?'
  231. end
  232.  
  233. return username, appname
  234. end
  235.  
  236.  
  237. function updateAllApps(folder)
  238. -- autodetects appstore apps in the folder and updates them it there's a new version in the appstore
  239. -- TODO: I need an implementation of CRC32 or Adler32 that matches the output of Python's CRC32 and Adler32. For now, we update all apps regardless of changes or not.
  240.  
  241. -- Note: CC uses only absolute paths, so `updateall` will only update apps in the root folder.
  242. local updatedAtLeastOneApp = false
  243. for i, v in pairs(fs.list(folder)) do
  244. if not fs.isDir(v) then
  245. local username, appname = updateApp(v)
  246. if username then
  247. updatedAtLeastOneApp = true
  248. end
  249. end
  250. end
  251.  
  252. if not updatedAtLeastOneApp then
  253. print('No apps to update in this folder.')
  254. error()
  255. end
  256.  
  257. return true
  258. end
  259.  
  260.  
  261. function downloadApp(username, appname)
  262. -- On success, returns the username, appname, source code of the app.
  263. -- On failure, returns false.
  264. -- `username` and `appname` will have the correct casing
  265. return _download(TAS_HOST .. '/u/' .. username .. '/a/' .. appname .. '/raw')
  266. end
  267.  
  268.  
  269. function downloadPermalink(permalink)
  270. -- On success, returns the username, appname, source code of the app.
  271. -- On failure, returns false.
  272. -- `username` and `appname` will have the correct casing
  273. return _download(TAS_HOST .. '/permalink/' .. permalink .. '/raw')
  274. end
  275.  
  276.  
  277. function _download(url)
  278. -- On success, returns the username, appname, source code of the app.
  279. -- On failure, returns false.
  280. -- `url` parameter will *not* have the "https://" or "http://" prefix, so
  281. -- that we can try both. (Dan suspects that some Java configs disallow SSL,
  282. -- so we try https first and fall back to http.)
  283. -- This is the helper function called by downloadApp() and downloadPermalink()
  284. local response = http.get('https://' .. url) -- attempt https
  285. if response == nil then
  286. response = http.get('http://' .. url) -- attempt http as fallback
  287. if response == nil then
  288. return false
  289. end
  290. end
  291. local sourceCode = response.readAll()
  292. response.close()
  293.  
  294. local username, appname = readAppHeader(sourceCode)
  295. return username, appname, sourceCode
  296. end
  297.  
  298.  
  299. function getAppByPermalink(permalink)
  300. local username, appname, sourceCode = downloadPermalink(permalink)
  301. if username == false then
  302. print('Permalink "' .. permalink .. '" doesn\'t exist.')
  303. return false
  304. end
  305.  
  306. writeFile(appname, sourceCode)
  307. print('Downloaded ' .. appname .. ' by ' .. username)
  308. cacheUsername(username)
  309. return true
  310. end
  311.  
  312.  
  313. function writeFile(filename, content)
  314. -- TODO: handle out of memory error
  315. -- TODO: what should the return value of this be?
  316. local fo = fs.open(filename, 'w')
  317. fo.write(content)
  318. fo.close()
  319. end
  320.  
  321.  
  322. function getAppByName(username, appname)
  323. local username, appname, sourceCode = downloadApp(username, appname)
  324. if username == false then
  325. return false
  326. end
  327. writeFile(appname, sourceCode)
  328.  
  329. print('Downloaded ' .. appname .. ' by ' .. username)
  330. return username, appname -- returns correct case of username and appname
  331. end
  332.  
  333.  
  334. function putApp(filename, otp)
  335.  
  336. end
  337.  
  338.  
  339. function readApp(filename)
  340. local fo = fs.open(filename, 'r')
  341. local sourceCode = fo.readAll()
  342. fo.close()
  343. local username, appname = readAppHeader(sourceCode)
  344. return username, appname, sourceCode
  345. end
  346.  
  347.  
  348. function readAppHeader(sourceCode)
  349. -- returns false, false if there is no header, otherwise returns the username, appname
  350. if string.sub(sourceCode, 1, 32) ~= '-- https://turtleappstore.com/u/' then
  351. return false
  352. end
  353.  
  354. local usernameEnd = string.find(string.sub(sourceCode, 33), '/')
  355. local username = string.sub(sourceCode, 33, 33+usernameEnd-2)
  356. local appnameEnd = string.find(sourceCode, '\n')
  357. local appname = string.sub(sourceCode, 33+usernameEnd+2, appnameEnd - 1)
  358.  
  359. return username, appname
  360. end
  361.  
  362.  
  363. function putAppByName(username, filename, otp)
  364. -- returns the result as a string
  365. if not fs.exists(filename) then
  366. return 'Cannot find file ' .. filename
  367. end
  368.  
  369. local fo = fs.open(filename, 'r')
  370. local content = fo.readAll()
  371. fo.close()
  372.  
  373. local response = http.post('https://' .. TAS_HOST .. '/put/', 'username=' .. textutils.urlEncode(username) ..
  374. '&filename=' .. textutils.urlEncode(filename) ..
  375. '&otp=' .. textutils.urlEncode(otp) ..
  376. '&content=' .. textutils.urlEncode(content))
  377.  
  378. if response == nil then
  379. -- Fallback to http if https failed.
  380. response = http.post('http://' .. TAS_HOST .. '/put/', 'username=' .. textutils.urlEncode(username) ..
  381. '&filename=' .. textutils.urlEncode(filename) ..
  382. '&otp=' .. textutils.urlEncode(otp) ..
  383. '&content=' .. textutils.urlEncode(content))
  384. if response == nil then
  385. return 'Failed to connect.'
  386. end
  387. end
  388.  
  389. local result = response.readAll()
  390. response.close()
  391. cacheUsername(username)
  392. addAppHeader(filename, username)
  393. return result
  394. end
  395.  
  396.  
  397. function addAppHeader(filename, username)
  398. local fo = fs.open(filename, 'r')
  399. local content = fo.readAll()
  400. fo.close()
  401.  
  402. if string.sub(content, 1, 32) ~= '-- https://turtleappstore.com/u/' then
  403. local appHeader = '-- https://turtleappstore.com/u/' .. username .. '/a/' .. filename .. '\n'
  404. writeFile(filename, appHeader .. content)
  405. end
  406. end
  407.  
  408.  
  409. main()
Add Comment
Please, Sign In to add comment