Aidan428

arpm copy

Jan 21st, 2020
65
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.76 KB | None | 0 0
  1. -- ###############################################
  2. -- # ARPM #
  3. -- # #
  4. -- # 08.2015 by: IlynPayne #
  5. -- ###############################################
  6.  
  7. --[[
  8. .----------------. .----------------. .----------------. .----------------.
  9. | .--------------. || .--------------. || .--------------. || .--------------. |
  10. | | __ | || | _______ | || | ______ | || | ____ ____ | |
  11. | | / \ | || | |_ __ \ | || | |_ __ \ | || ||_ \ / _|| |
  12. | | / /\ \ | || | | |__) | | || | | |__) | | || | | \/ | | |
  13. | | / ____ \ | || | | __ / | || | | ___/ | || | | |\ /| | | |
  14. | | _/ / \ \_ | || | _| | \ \_ | || | _| |_ | || | _| |_\/_| |_ | |
  15. | ||____| |____|| || | |____| |___| | || | |_____| | || ||_____||_____|| |
  16. | | | || | | || | | || | | |
  17. | '--------------' || '--------------' || '--------------' || '--------------' |
  18. '----------------' '----------------' '----------------' '----------------'
  19. ARPM REPOSITORY PACKAGE MANAGER
  20.  
  21.  
  22. ## Description ##
  23. Package manager that allows downloading applications from this repository. It uses global registry for application
  24. management (setup-list). Its structure is described in the section below. Only the newest versions of applications
  25. can be downloaded: when the newer version is released, the older one becomes inaccessible because registry is
  26. overwriten.
  27.  
  28. ## Registry structure ##
  29. {
  30. {
  31. [1] = application name: string,
  32. [2] = version: string,
  33. [3] = download link: string,
  34. [4] = description: string,
  35. [5] = author: string,
  36. [6] = dependencies: table or nil,
  37. [7] = whether application is a library: bool or nil,
  38. [8] = manual file name: string or nil,
  39. [9] = alternate application name: string or nil,
  40. [10] = archived: bool or nil
  41. },
  42. ...
  43. }
  44.  
  45. Additional info:
  46. [6] - currently package manager doesn't resolve dependencies recursively, so a complete list of dependencies
  47. must be provided.
  48. [7] - information displayed in app list
  49. [8] - manual file name (with an extension, if has one). Manuals are downloaded from 'man' directory.
  50. [9] - the actual mame under which application is saved on disk (may contain directory path)
  51. [10] - whehter application is archived (not developed anymore); used for app list filtering
  52.  
  53. ## Application options ##
  54. * list [-r]
  55. Displays available packages. [r] also displays archived packages.
  56. * info <package>
  57. Displays detailed information about specified package.
  58. * install <package> [-f] [-n]
  59. Installs selected package. [f] forces installation, [n] disables installation of dependencies.
  60. * remove <package> [-d]
  61. Removes specified package. [d] also removes dependencies.
  62. * test <path>
  63. Tests offline registry (setup-list) for known errors before uploading.
  64. ]]
  65.  
  66. local version = "0.3.2"
  67.  
  68. local component = require("component")
  69.  
  70. if not component.isAvailable("internet") then
  71. io.stderr:write("This application requires an internet card")
  72. return
  73. end
  74.  
  75. local inter2 = component.internet
  76. local gpu = component.gpu
  77. local internet = require("internet")
  78.  
  79. if not inter2.isHttpEnabled() then
  80. io.stderr:write("Internet connections are currently disabled in game config")
  81. return
  82. end
  83.  
  84. local fs = require("filesystem")
  85. local serial = require("serialization")
  86. local term = require("term")
  87. local event = require("event")
  88. local keyboard = require("keyboard")
  89. local shell = require("shell")
  90. local os = require("os")
  91.  
  92. local resolution = {gpu.getResolution()}
  93. local args, options = shell.parse(...)
  94.  
  95. local appList = nil
  96. local installed = {}
  97.  
  98. local colors = {
  99. white = 0xffffff,
  100. orange = 0xffa500,
  101. magenta = 0xff00ff,
  102. yellow = 0xffff00,
  103. red = 0xff0000,
  104. green = 0x00ff00,
  105. blue = 0x0000ff,
  106. cyan = 0x00ffff,
  107. brown = 0xa52a2a,
  108. gray = 0xc9c9c9,
  109. silver = 0xe0e0de,
  110. black = 0x000000
  111. }
  112.  
  113. local function textColor(color)
  114. if gpu.maxDepth() > 1 and color then
  115. return gpu.setForeground(color)
  116. end
  117. end
  118.  
  119. local function printCommand(com, desc)
  120. textColor(colors.orange)
  121. term.write(" arpm " .. com)
  122. textColor(colors.silver)
  123. print(" - " .. desc)
  124. end
  125.  
  126. local function ok()
  127. textColor(colors.green)
  128. term.write("OK")
  129. textColor(colors.cyan)
  130. end
  131.  
  132. local function usage()
  133. if args[1] and args[1]:len() > 0 then
  134. io.stderr:write("Action not found: " .. args[1] .. "\n\n")
  135. end
  136. local prev = nil
  137. prev = textColor(colors.green)
  138. print("ARPM - ARPM Repository Package Manager version " .. version)
  139. textColor(colors.cyan)
  140. print("Usage:")
  141. printCommand("list [-r]", "Displays available packages. [r] also displays archived packages.")
  142. printCommand("info <package>", "Displays detailed information about specified package.")
  143. printCommand("install <package> [-f] [-d]", "Installs selected package. [f] forces installation, [n] disables installation of dependencies.")
  144. printCommand("remove <package> [-d]", "Removes specified package. [d] also removes dependencies.")
  145. printCommand("refresh", "Forces refresh of the registry (downloads it again from server).")
  146. printCommand("test <path>", "Tests offline registry (setup-list) for known errors before uploading.")
  147. textColor(prev)
  148. end
  149.  
  150. local function getContent(url)
  151. local sContent = ""
  152. local result, response = pcall(internet.request, url)
  153. if not result then
  154. return nil
  155. end
  156. for chunk in response do
  157. sContent = sContent .. chunk
  158. end
  159. return sContent
  160. end
  161.  
  162. local function saveAppList(raw)
  163. local filename = "/tmp/setup-list"
  164. if fs.isDirectory(filename) then
  165. if not fs.remove(filename) then return end
  166. end
  167. local f = io.open(filename, "w")
  168. if f then
  169. f:write(raw)
  170. f:close()
  171. end
  172. end
  173.  
  174. local function fetchAppList(force)
  175. local filename = "/tmp/setup-list"
  176. local currentTime = math.floor(os.time() * (1000 / 60 / 60) / 20)
  177. if fs.exists(filename) and not fs.isDirectory(filename) and not force then
  178. local lastUpdate = tonumber(os.getenv("sl_update"))
  179. if lastUpdate ~= nil and lastUpdate + 3600 > currentTime then
  180. local f = io.open(filename, "r")
  181. if f then
  182. local s = serial.unserialize(f:read("*a"))
  183. f:close()
  184. if s then
  185. appList = s
  186. return true
  187. end
  188. end
  189. end
  190. end
  191. local resp = getContent("https://gitlab.com/d_rzepka/oc-equipment/raw/master/installer/setup-list")
  192. if resp then
  193. local s, e = serial.unserialize(resp)
  194. if not s then
  195. io.stderr:write("Couldn't read the registry: " .. e)
  196. return false
  197. end
  198. appList = s
  199. saveAppList(resp)
  200. os.setenv("sl_update", tostring(currentTime))
  201. else
  202. io.stderr:write("Couldn't establish internet connection.")
  203. return false
  204. end
  205. end
  206.  
  207. local function getApp(url)
  208. return getContent(url)
  209. end
  210.  
  211. local function getAppData(appName)
  212. for _, nm in pairs(appList) do
  213. if nm[1] == appName then return nm end
  214. end
  215. return nil
  216. end
  217.  
  218. local function testApp(app, all)
  219. local warn = {}
  220. if type(app[1]) ~= "string" then
  221. table.insert(warn, "application name (1) must be a string")
  222. elseif app[1]:len() == 0 then
  223. table.insert(warn, "application name (1) is too short")
  224. end
  225. if type(app[2]) ~= "string" then
  226. table.insert(warn, "version (2) must be a string")
  227. elseif app[2]:len() == 0 then
  228. table.insert(warn, "version (2) is too short")
  229. end
  230. if type(app[3]) ~= "string" then
  231. table.insert(warn, "download link (3) must be a string")
  232. else
  233. local s, i = pcall(component.internet.request, app[3])
  234. if s then
  235. local d, e = pcall(i.read, 1)
  236. if not d then
  237. table.insert(warn, "download link (3): " .. e)
  238. end
  239. i.close()
  240. else
  241. table.insert(warn, "download link (3): " .. i)
  242. end
  243. end
  244. if type(app[4]) ~= "string" then
  245. table.insert(warn, "description (4) must be a string")
  246. elseif app[4]:len() == 0 then
  247. table.insert(warn, "description (4) is empty")
  248. end
  249. if type(app[5]) ~= "string" then
  250. table.insert(warn, "author name (5) must be a string")
  251. elseif app[5]:len() == 0 then
  252. table.insert(warn, "author name (5) is empty")
  253. end
  254. if type(app[6]) == "table" then
  255. for _, dep in pairs(app[6]) do
  256. local found = false
  257. for _, a in pairs(all) do
  258. if a[1] == dep then
  259. found = true
  260. break
  261. end
  262. end
  263. if not found then
  264. table.insert(warn, "dependency '" .. dep .. "' not found")
  265. end
  266. end
  267. elseif type(app[6]) ~= "nil" then
  268. table.insert(warn, "dependency list (6) must be a table")
  269. end
  270. if type(app[7]) ~= "boolean" and type(app[7]) ~= "nil" then
  271. table.insert(warn, "library flag (7) must be boolean or nil")
  272. end
  273. if type(app[8]) == "string" then
  274. if app[8]:len() == 0 then
  275. table.insert(warn, "manual name (8) is too short")
  276. end
  277. elseif type(app[8]) ~= "nil" then
  278. table.insert(warn, "manual name (8) must be a string")
  279. end
  280. if type(app[9]) == "string" then
  281. if app[9]:len() == 0 then
  282. table.insert(warn, "alternate application name (9) is too short")
  283. end
  284. elseif type(app[9]) ~= "nil" then
  285. table.insert(warn, "alternate application name (9) must be a string or nil")
  286. end
  287. if type(app[10]) ~= "boolean" and type(app[10]) ~= "nil" then
  288. table.insert(warn, "archive flag (10) must be a boolean or nil")
  289. end
  290. return warn
  291. end
  292.  
  293. local function testRepo(path)
  294. if not path or path:len() == 0 then
  295. io.stderr:write("Registry name must be supplied")
  296. return
  297. end
  298. if path:sub(1, 1) ~= "/" then
  299. path = fs.concat(shell.getWorkingDirectory(), path)
  300. end
  301. if not fs.exists(path) then
  302. io.stderr:write("File not found")
  303. return
  304. end
  305. if fs.isDirectory(path) then
  306. io.stderr:write("Given path is a directory")
  307. return
  308. end
  309. local file, e = io.open(path, "r")
  310. if not file then
  311. io.stderr:write("Couldn't open a file: " .. e)
  312. return
  313. end
  314. local tab, e = serial.unserialize(file:read("*a"))
  315. file:close()
  316. if not tab then
  317. io.stderr:write("Couldn't process the file: " .. e)
  318. return
  319. end
  320. textColor(colors.cyan)
  321. term.write("Testing entries:")
  322. local errors = 0
  323. for _, t in pairs(tab) do
  324. textColor(colors.silver)
  325. term.write("\n" .. t[1] .. " ")
  326. local test = testApp(t, tab)
  327. if #test > 0 then
  328. textColor(colors.yellow)
  329. for _, s in pairs(test) do
  330. term.write("\n " .. s)
  331. errors = errors + 1
  332. end
  333. else
  334. ok()
  335. end
  336. end
  337. textColor(colors.cyan)
  338. print("\n\nVerified " .. tostring(#tab) .. " applications.")
  339. if errors > 0 then
  340. textColor(colors.orange)
  341. print("Test completed. Found " .. tostring(errors) .. " errors.")
  342. else
  343. textColor(colors.green)
  344. print("Test completed succesfully.")
  345. end
  346. end
  347.  
  348. local function packetInfo(packetName)
  349. if not packetName or packetName == "" then
  350. io.stderr:write("Package name not supplied.")
  351. return
  352. end
  353. fetchAppList()
  354. if appList then
  355. for _, packet in pairs(appList) do
  356. if type(packet) == "table" and packet[1] == packetName then
  357. textColor(colors.cyan)
  358. print("\n>> Package information <<")
  359. textColor(colors.yellow)
  360. io.write("\nPackage name: ")
  361. textColor(colors.gray)
  362. print(packet[1])
  363. if packet[9] then
  364. textColor(colors.yellow)
  365. io.write("File name: ")
  366. textColor(colors.gray)
  367. print(packet[9])
  368. end
  369. textColor(colors.yellow)
  370. io.write("Current version: ")
  371. textColor(colors.gray)
  372. print(packet[2])
  373. textColor(colors.yellow)
  374. io.write("Description: ")
  375. textColor(colors.gray)
  376. print(packet[4])
  377. textColor(colors.yellow)
  378. io.write("Author: ")
  379. textColor(colors.gray)
  380. print(packet[5])
  381. textColor(colors.yellow)
  382. io.write("Download link: ")
  383. textColor(colors.gray)
  384. do
  385. if packet[3]:len() > resolution[1] - 20 then
  386. print(packet[3]:sub(1, math.ceil(resolution[1] / 2) - 12) .. "..." .. packet[3]:sub(math.ceil(resolution[1] / 2) + 12, packet[3]:len()))
  387. else
  388. print(packet[3])
  389. end
  390. end
  391. if packet[6] then
  392. local deps = packet[6]
  393. textColor(colors.yellow)
  394. io.write("Dependencies: ")
  395. textColor(colors.gray)
  396. for i = 1, #deps do
  397. if i < #deps then io.write(deps[i] .. ", ")
  398. else print(deps[i]) end
  399. end
  400. end
  401. textColor(colors.yellow)
  402. io.write("Is library: ")
  403. textColor(colors.gray)
  404. if packet[7] then print("yes") else print("No") end
  405. textColor(colors.yellow)
  406. io.write("Has manual: ")
  407. textColor(colors.gray)
  408. if packet[8] then print("Yes") else print("No") end
  409. if packet[10] then
  410. textColor(colors.magenta)
  411. print("Is archive: Yes")
  412. end
  413. print()
  414. return
  415. end
  416. end
  417. io.stderr:write("Package with specified name not found")
  418. end
  419. end
  420.  
  421. local function printAppList(archive)
  422. fetchAppList()
  423. if appList then
  424. local page = 1
  425. local apps = {}
  426. for _, a in pairs(appList) do
  427. if not a[10] then
  428. table.insert(apps, {a[1], a[4]})
  429. elseif a[10] and archive then
  430. table.insert(apps, {a[1], a[4], true})
  431. end
  432. end
  433. while true do
  434. term.clear()
  435. term.setCursor(1, 1)
  436. textColor(colors.green)
  437. io.write("Package list ")
  438. textColor(colors.orange)
  439. print("page " .. tostring(page))
  440. for i = 1, resolution[2] - 3 do
  441. if i + (page - 1) * (resolution[2] - 3) > #apps then break end
  442. local app = apps[i + ((resolution[2] - 3) * (page - 1))]
  443. textColor(app[3] and colors.magenta or colors.yellow)
  444. io.write(i .. ". " .. app[1])
  445. textColor(colors.gray)
  446. print(" - " .. app[2])
  447. end
  448. term.setCursor(1, resolution[2])
  449. textColor(colors.green)
  450. term.write("Q - quit application ")
  451. if page > 1 then io.write(" [Left] - previous page") end
  452. if #apps > (resolution[2] * page) then io.write("[Right] - next page") end
  453. local ev = {event.pull("key_down")}
  454. if ev[4] == keyboard.keys.q then
  455. return
  456. elseif ev[4] == keyboard.keys.left and #apps > ((resolution[2] - 3) * page) then
  457. page = page + 1
  458. elseif ev[4] == keyboard.keys.right and page > 1 then
  459. page = page - 1
  460. end
  461. end
  462. else
  463. io.stderr:write("Couldn't download the registry")
  464. end
  465. end
  466.  
  467. local function clearAfterFail(tab)
  468. for _, appl in pairs(tab) do
  469. fs.remove(appl)
  470. end
  471. end
  472.  
  473. local generateList = nil
  474. generateList = function(appData, deps, list)
  475. --[[
  476. list = {
  477. {
  478. [1] = package name:string
  479. [2] = download link:string,
  480. [3] = directory:string,
  481. [4] = file name:string,
  482. [5] = version:string
  483. [6] = manual:string or nil
  484. }
  485. ...
  486. }
  487. ]]
  488. if not list then list = {} end
  489. local found = false
  490. for _, b in pairs(list) do
  491. if b[1] == appData[1] then
  492. found = true
  493. break
  494. end
  495. end
  496. if not found then
  497. local saveLocation = appData[7] and "/lib/" or "/usr/bin/"
  498. if appData[9] then
  499. saveLocation = saveLocation .. appData[9]
  500. else
  501. saveLocation = saveLocation .. appData[1] .. ".lua"
  502. end
  503. local segments = fs.segments(saveLocation)
  504. local dir = ""
  505. for i = 1, #segments - 1 do
  506. dir = dir .. "/" .. segments[i]
  507. end
  508. dir = dir .. "/"
  509. local add = {
  510. [1] = appData[1],
  511. [2] = appData[3],
  512. [3] = dir,
  513. [4] = segments[#segments],
  514. [5] = appData[2],
  515. [6] = appData[8]
  516. }
  517. table.insert(list, add)
  518. end
  519. if deps then
  520. for _, b in pairs(appData[6] or {}) do
  521. local dependency = getAppData(b)
  522. if not dependency then
  523. io.stderr:write("Dependency not found: " .. b)
  524. return
  525. end
  526. if not generateList(dependency, true, list) then return end
  527. end
  528. end
  529. return list
  530. end
  531.  
  532. local function installApp(appName, force_install, disable_dep_install)
  533. textColor(colors.blue)
  534. print("\nStarting installation...")
  535. os.sleep(0.2)
  536. textColor(colors.cyan)
  537. term.write("\nDownloading the registry... ")
  538. fetchAppList()
  539. if appList then
  540. application = getAppData(appName)
  541. if not application then
  542. textColor(colors.red)
  543. term.write("\nError: application with specified name not found")
  544. return
  545. end
  546. ok()
  547. term.write("\nGenerating installation list... ")
  548. local list = generateList(application, not disable_dep_install)
  549. if not list then
  550. textColor(colors.yellow)
  551. term.write("\nInstallation aborted.")
  552. return
  553. end
  554. ok()
  555. term.write("\nChecking directories... ")
  556. for _, t in pairs(list) do
  557. if not fs.isDirectory(t[3]) then
  558. local s, e = fs.makeDirectory(t[3])
  559. if not s then
  560. io.stderr:write("Cannot create directory " .. t[3] .. ": " .. e)
  561. textColor(colors.yellow)
  562. term.write("\nInstallation aborted.")
  563. return
  564. end
  565. end
  566. end
  567. ok()
  568. term.write("\nCopying files:")
  569. for _, t in pairs(list) do
  570. local filename = fs.concat(t[3], t[4])
  571. textColor(colors.silver)
  572. term.write("\n" .. filename)
  573. if fs.exists(filename) then
  574. local localfile = loadfile(filename)
  575. local version = localfile and localfile("version_check") or ""
  576. if version == t[5] and not force_install then
  577. textColor(colors.orange)
  578. term.write(" (up-to-date)")
  579. elseif type(version) == "string" and version:len() > 0 then
  580. textColor(colors.green)
  581. term.write(" (update: " .. version .. " -> " .. t[5] .. ")")
  582. else
  583. textColor(colors.brown)
  584. term.write(" (version unknown)")
  585. end
  586. end
  587. local output = io.open(filename, "w")
  588. if not output then
  589. io.stderr:write("\nCannot create file: " .. t[4])
  590. if not force_install then
  591. io.stderr:write("\nInstallation failed!")
  592. output:close()
  593. clearAfterFail(installed)
  594. end
  595. end
  596. table.insert(installed, filename)
  597. local source = getApp(t[2])
  598. if source then
  599. output:write(source)
  600. output:close()
  601. else
  602. io.stderr:write("\nCouldn't download file " .. t[4])
  603. if not force_install then
  604. io.stderr:write("\nInstallation failed!")
  605. output:close()
  606. clearAfterFail(installed)
  607. return
  608. else
  609. output:close()
  610. fs.remove(filename)
  611. end
  612. end
  613. end
  614. local manuals = {}
  615. for _, t in pairs(list) do
  616. if t[5] then
  617. table.insert(manuals, t[6])
  618. end
  619. end
  620. if #manuals > 0 then
  621. local manaddr = "https://gitlab.com/d_rzepka/oc-equipment/raw/master/man/"
  622. local mandir = "/usr/man/"
  623. textColor(colors.cyan)
  624. term.write("\nPreparing manual...")
  625. textColor(colors.silver)
  626. for _, s in pairs(manuals) do
  627. term.write("\n" .. s)
  628. local mansource = getapp(manaddr .. s)
  629. if mansource then
  630. local manfile = io.open(fs.concat(mandir, s), "w")
  631. if manfile then
  632. manfile:write(mansource)
  633. manfile:close()
  634. else
  635. io.stderr:write("\nCouldn't create manual file.")
  636. fs.remove(fs.concat(mandir, s))
  637. end
  638. else
  639. io.stderr:write("\nManual file not found.")
  640. end
  641. end
  642. end
  643. textColor(colors.green)
  644. term.write("\nInstallation sucessful")
  645. else
  646. io.stderr:write("Couldn't download the registry.")
  647. return
  648. end
  649. end
  650.  
  651. local function uninstallApp(appName, deps)
  652. if not appName then
  653. io.stderr:write("You must specifiy application name")
  654. return
  655. end
  656. local name = appName
  657. if string.sub(appName, string.len(appName) - 4, string.len(appName)) == ".lua" then
  658. name = string.sub(appName, 1, string.len(apppName) - 4)
  659. end
  660. textColor(colors.cyan)
  661. term.write("\nDownloading the registry... ")
  662. fetchAppList()
  663. if not appList then
  664. textColor(colors.red)
  665. term.write("Error\nRegistry download failed.")
  666. textColor(colors.yellow)
  667. term.write("\nDeinistallation aborted.")
  668. return
  669. end
  670. local application = getAppData(name)
  671. if not application then
  672. textColor(colors.red)
  673. term.write("Error\nCouldn't find the application with specified name")
  674. textColor(colors.yellow)
  675. term.write("\nDeinstalacja przerwana.")
  676. return
  677. end
  678. ok()
  679. term.write("\nGenerating deinstallation list... ")
  680. local list = generateList(application, deps)
  681. if not list then
  682. textColor(colors.yellow)
  683. term.write("\nDeinstallation aborted")
  684. return
  685. end
  686. ok()
  687. term.write("\nRemoving applications:")
  688. textColor(colors.silver)
  689. for _, t in pairs(list) do
  690. local filename = fs.concat(t[3], t[4])
  691. term.write("\n" .. filename)
  692. if fs.exists(filename) then
  693. local s, e = fs.remove(filename)
  694. if not s then
  695. io.stderr:write("\nError: " .. e)
  696. end
  697. end
  698. end
  699. textColor(colors.green)
  700. print("\nDeinstallation successful.")
  701. end
  702.  
  703. local function refreshRegistry()
  704. textColor(colors.cyan)
  705. print("\nRefreshing the registry...")
  706. fetchAppList(true)
  707. if not appList then
  708. io.stderr:write("Couldn't download the registry.")
  709. else
  710. textColor(colors.green)
  711. print("Update successful")
  712. end
  713. end
  714.  
  715. local function main()
  716. if args[1] == "list" then
  717. printAppList(options.r)
  718. elseif args[1] == "info" then
  719. packetInfo(args[2])
  720. elseif args[1] == "install" then
  721. installApp(args[2], options.f, options.n)
  722. elseif args[1] == "remove" then
  723. uninstallApp(args[2], options.d)
  724. elseif args[1] == "refresh" then
  725. refreshRegistry()
  726. elseif args[1] == "test" then
  727. testRepo(args[2])
  728. else
  729. usage()
  730. end
  731. end
  732.  
  733. local pprev = gpu.getForeground()
  734. main()
  735. gpu.setForeground(pprev)
Add Comment
Please, Sign In to add comment