Philipou

oppm gui

Aug 21st, 2022
60
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 19.30 KB | None | 0 0
  1. --[[
  2. OpenPrograms package manager, browser and downloader, for easy access to many programs
  3. Author: Vexatos
  4. ]]
  5. local component = require("component")
  6. local event = require("event")
  7. local fs = require("filesystem")
  8. local serial = require("serialization")
  9. local shell = require("shell")
  10. local term = require("term")
  11.  
  12. local gpu = component.gpu
  13.  
  14. local internet
  15. local wget
  16.  
  17. local args, options = shell.parse(...)
  18.  
  19. local function getInternet()
  20.   if not component.isAvailable("internet") then
  21.     io.stderr:write("This program requires an internet card to run.")
  22.     return false
  23.   end
  24.   internet = require("internet")
  25.   wget = loadfile("/bin/wget.lua")
  26.   return true
  27. end
  28.  
  29. local function printUsage()
  30.   print("OpenPrograms Package Manager, use this to browse through and download OpenPrograms programs easily")
  31.   print("Usage:")
  32.   print("'oppm list [-i]' to get a list of all the available program packages")
  33.   print("'oppm list [-i] <filter>' to get a list of available packages containing the specified substring")
  34.   print(" -i: Only list already installed packages")
  35.   print("'oppm info <package>' to get further information about a program package")
  36.   print("'oppm install [-f] <package> [path]' to download a package to a directory on your system (or /usr by default)")
  37.   print("'oppm update <package>' to update an already installed package")
  38.   print("'oppm update all' to update every already installed package")
  39.   print("'oppm uninstall <package>' to remove a package from your system")
  40.   print("'oppm register <repository>' to register a package repository locally\n  Must be a valid GitHub repo containing programs.cfg")
  41.   print("'oppm unregister <repository>' to remove a package repository from your local registry")
  42.   print(" -f: Force creation of directories and overwriting of existing files.")
  43. end
  44.  
  45. local function getContent(url)
  46.   local sContent = ""
  47.   local result, response = pcall(internet.request, url)
  48.   if not result then
  49.     return nil
  50.   end
  51.   for chunk in response do
  52.     sContent = sContent..chunk
  53.   end
  54.   return sContent
  55. end
  56.  
  57. local NIL = {}
  58. local function cached(f)
  59.   return options.nocache and f or setmetatable(
  60.     {},
  61.     {
  62.       __index=function(t,k)
  63.         local v = f(k)
  64.         t[k] = v
  65.         return v
  66.       end,
  67.       __call=function(t,k)
  68.         if k == nil then
  69.           k = NIL
  70.         end
  71.         return t[k]
  72.       end,
  73.     }
  74.   )
  75. end
  76.  
  77. --For sorting table values by alphabet
  78. local function compare(a,b)
  79.   for i=1,math.min(#a,#b) do
  80.     if a:sub(i,i)~=b:sub(i,i) then
  81.       return a:sub(i,i) < b:sub(i,i)
  82.     end
  83.   end
  84.   return #a < #b
  85. end
  86.  
  87. local function downloadFile(url,path,force,soft)
  88.   if options.f or force then
  89.     return wget("-fq",url,path)
  90.   else
  91.     if fs.exists(path) then
  92.       if soft then
  93.         return true
  94.       else
  95.         error("file already exists and option -f is not enabled")
  96.       end
  97.     end
  98.     return wget("-q",url,path)
  99.   end
  100. end
  101.  
  102. local function readFromFile(fNum)
  103.   local path
  104.   if fNum == 1 then
  105.     path = "/etc/opdata.svd"
  106.   elseif fNum == 2 then
  107.     path = "/etc/oppm.cfg"
  108.     if not fs.exists(path) then
  109.       local tProcess = os.getenv("_")
  110.       path = fs.concat(fs.path(shell.resolve(tProcess)),"/etc/oppm.cfg")
  111.     end
  112.   end
  113.   if not fs.exists(fs.path(path)) then
  114.     fs.makeDirectory(fs.path(path))
  115.   end
  116.   if not fs.exists(path) then
  117.     return {-1}
  118.   end
  119.   local file,msg = io.open(path,"rb")
  120.   if not file then
  121.     io.stderr:write("Error while trying to read file at "..path..": "..msg)
  122.     return
  123.   end
  124.   local sPacks = file:read("*a")
  125.   file:close()
  126.   return serial.unserialize(sPacks) or {-1}
  127. end
  128.  
  129. local function saveToFile(packs)
  130.   local file,msg = io.open("/etc/opdata.svd","wb")
  131.   if not file then
  132.     io.stderr:write("Error while trying to save package names: "..msg)
  133.     return
  134.   end
  135.   local sPacks = serial.serialize(packs)
  136.   file:write(sPacks)
  137.   file:close()
  138. end
  139.  
  140. local getRepos = cached(function()
  141.   local success, sRepos = pcall(getContent,"https://raw.githubusercontent.com/OpenPrograms/openprograms.github.io/master/repos.cfg")
  142.   if not success then
  143.     io.stderr:write("Could not connect to the Internet. Please ensure you have an Internet connection.")
  144.     return -1
  145.   end
  146.   local repos = serial.unserialize(sRepos)
  147.   local svd = readFromFile(1)
  148.   if not svd then
  149.     io.stderr:write("Error while trying to read save file")
  150.     return
  151.   elseif svd[1] == -1 then
  152.     table.remove(svd, 1)
  153.   end
  154.   if svd._repos then
  155.     for i, j in pairs(svd._repos) do
  156.       if not repos[i] then
  157.         repos[i] = j
  158.       end
  159.     end
  160.   end
  161.   return repos
  162. end)
  163.  
  164. local getPackages = cached(function(repo)
  165.   local success, sPackages = pcall(getContent,"https://raw.githubusercontent.com/"..repo.."/master/programs.cfg")
  166.   if not success or not sPackages then
  167.     return -1
  168.   end
  169.   return serial.unserialize(sPackages)
  170. end)
  171.  
  172. local function listPackages(filter)
  173.   filter = filter or false
  174.   if filter then
  175.     filter = string.lower(filter)
  176.   end
  177.   local packages = {}
  178.   if not options.i then
  179.     local success, repos = pcall(getRepos)
  180.     if not success or repos==-1 then
  181.       io.stderr:write("Unable to connect to the Internet.\n")
  182.       return
  183.     elseif repos==nil then
  184.         print("Error while trying to receive repository list")
  185.         return
  186.     end
  187.     for _,j in pairs(repos) do
  188.       if j.repo then
  189.         local lPacks = getPackages(j.repo)
  190.         if lPacks==nil then
  191.           io.stderr:write("Error while trying to receive package list for " .. j.repo.."\n")
  192.         elseif type(lPacks) == "table" then
  193.           for k,kt in pairs(lPacks) do
  194.             if not kt.hidden then
  195.               table.insert(packages,k)
  196.             end
  197.           end
  198.         end
  199.       end
  200.     end
  201.     local lRepos = readFromFile(2)
  202.     if lRepos and lRepos.repos then
  203.       for _,j in pairs(lRepos.repos) do
  204.         for k,kt in pairs(j) do
  205.           if not kt.hidden then
  206.             table.insert(packages,k)
  207.           end
  208.         end
  209.       end
  210.     end
  211.   else
  212.     local lPacks = {}
  213.     local packs = readFromFile(1)
  214.     for i in pairs(packs) do
  215.       table.insert(lPacks,i)
  216.     end
  217.     packages = lPacks
  218.   end
  219.   if filter then
  220.     local lPacks = {}
  221.     for i,j in ipairs(packages) do
  222.       if (#j>=#filter) and string.find(j,filter,1,true)~=nil then
  223.           table.insert(lPacks,j)
  224.       end
  225.     end
  226.     packages = lPacks
  227.   end
  228.   table.sort(packages,compare)
  229.   return packages
  230. end
  231.  
  232. local function printPackages(packs)
  233.   if packs==nil or not packs[1] then
  234.     print("No package matching specified filter found.")
  235.     return
  236.   end
  237.   term.clear()
  238.   local xRes,yRes = gpu.getResolution()
  239.   --print("--OpenPrograms Package list--")
  240.   local xCur,yCur = term.getCursor()
  241.   for _,j in ipairs(packs) do
  242.     term.write(j.."\n")
  243.     yCur = yCur+1
  244.   end
  245. end
  246.  
  247. local function parseFolders(pack, repo, info)
  248.  
  249.   local function getFolderTable(repo, namePath, branch)
  250.     local success, filestring = pcall(getContent,"https://api.github.com/repos/"..repo.."/contents/"..namePath.."?ref="..branch)
  251.     if not success or filestring:find('"message": "Not Found"') then
  252.       io.stderr:write("Error while trying to parse folder names in declaration of package "..pack..".\n")
  253.       if filestring:find('"message": "Not Found"') then
  254.         io.stderr:write("Folder "..namePath.." does not exist.\n")
  255.       else
  256.         io.stderr:write(filestring.."\n")
  257.       end
  258.       io.stderr:write("Please contact the author of that package.\n")
  259.       return nil
  260.     end
  261.     return serial.unserialize(filestring:gsub("%[", "{"):gsub("%]", "}"):gsub("(\"[^%s,]-\")%s?:", "[%1] = "), nil)
  262.   end
  263.  
  264.   local function nonSpecial(text)
  265.     return text:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
  266.   end
  267.  
  268.   local function unserializeFiles(files, repo, namePath, branch, relPath)
  269.     if not files then return nil end
  270.     local tFiles = {}
  271.     for _,v in pairs(files) do
  272.       if v["type"] == "file" then
  273.         local newPath = v["download_url"]:gsub("https?://raw.githubusercontent.com/"..nonSpecial(repo).."(.+)$", "%1"):gsub("/*$",""):gsub("^/*","")
  274.         tFiles[newPath] = relPath
  275.       elseif v["type"] == "dir" then
  276.         local newNamePath = namePath.."/"..v["name"]
  277.         local newFiles = unserializeFiles(getFolderTable(repo, newNamePath, branch), repo, newNamePath, branch, fs.concat(relPath, v["name"]))
  278.         if newFiles then
  279.           for p,q in pairs(newFiles) do
  280.             tFiles[p] = q
  281.           end
  282.         end
  283.       end
  284.     end
  285.     return tFiles
  286.   end
  287.  
  288.   local newInfo = info
  289.   for i,j in pairs(info.files) do
  290.     if string.find(i,"^:")  then
  291.       local iPath = i:gsub("^:","")
  292.       local branch = string.gsub(iPath,"^(.-)/.+","%1"):gsub("/*$",""):gsub("^/*","")
  293.       local namePath = string.gsub(iPath,".-(/.+)$","%1"):gsub("/*$",""):gsub("^/*","")
  294.       local absolutePath = j:find("^//")
  295.  
  296.       local files = unserializeFiles(getFolderTable(repo, namePath, branch), repo, namePath, branch, j:gsub("^//","/"))
  297.       if not files then return nil end
  298.       for p,q in pairs(files) do
  299.         if absolutePath then
  300.           newInfo.files[p] = "/"..q
  301.         else
  302.           newInfo.files[p] = q
  303.         end
  304.       end
  305.       newInfo.files[i] = nil
  306.     end
  307.   end
  308.   return newInfo
  309. end
  310.  
  311. local function getInformation(pack)
  312.   local success, repos = pcall(getRepos)
  313.   if not success or repos==-1 then
  314.     io.stderr:write("Unable to connect to the Internet.\n")
  315.     return
  316.   end
  317.   for _,j in pairs(repos) do
  318.     if j.repo then
  319.       local lPacks = getPackages(j.repo)
  320.       if lPacks==nil then
  321.         io.stderr:write("Error while trying to receive package list for "..j.repo.."\n")
  322.       elseif type(lPacks) == "table" then
  323.         for k in pairs(lPacks) do
  324.           if k==pack then
  325.             return parseFolders(pack, j.repo, lPacks[k]),j.repo
  326.           end
  327.         end
  328.       end
  329.     end
  330.   end
  331.   local lRepos = readFromFile(2)
  332.   if lRepos then
  333.     for i,j in pairs(lRepos.repos) do
  334.       for k in pairs(j) do
  335.         if k==pack then
  336.           return parseFolders(pack, i, j[k]),i
  337.         end
  338.       end
  339.     end
  340.   end
  341.   return nil
  342. end
  343.  
  344. local function provideInfo(pack)
  345.   if not pack then
  346.     printUsage()
  347.     return
  348.   end
  349.   pack = string.lower(pack)
  350.   local info = getInformation(pack)
  351.   if not info then
  352.     print("Package does not exist")
  353.     return
  354.   end
  355.   local done = false
  356.   print("--Information about package '"..pack.."'--")
  357.   if info.name then
  358.     print("Name: "..info.name)
  359.     done = true
  360.   end
  361.   if info.description then
  362.     print("Description: "..info.description)
  363.     done = true
  364.   end
  365.   if info.authors then
  366.     print("Authors: "..info.authors)
  367.     done = true
  368.   end
  369.   if info.note then
  370.     print("Note: "..info.note)
  371.     done = true
  372.   end
  373.   if info.files then
  374.     local c = 0
  375.     for i in pairs(info.files) do
  376.      c = c + 1
  377.     end
  378.     if c > 0 then
  379.       print("Number of files: "..tostring(c))
  380.       done = true
  381.     end
  382.   end
  383.   if not done then
  384.     print("No information provided.")
  385.   end
  386. end
  387.  
  388. local function installPackage(pack,path,update,tPacks)
  389.   tPacks = tPacks or readFromFile(1)
  390.   update = update or false
  391.   if not pack then
  392.     printUsage()
  393.     return
  394.   end
  395.   if not path then
  396.     local lConfig = readFromFile(2)
  397.     path = lConfig.path or "/usr"
  398.     if not update then
  399.       print("Installing package to "..path.."...")
  400.     end
  401.   elseif not update then
  402.     path = shell.resolve(path)
  403.     if not update then
  404.       print("Installing package to "..path.."...")
  405.     end
  406.   end
  407.   pack = string.lower(pack)
  408.  
  409.   if not tPacks then
  410.     io.stderr:write("Error while trying to read local package names")
  411.     return
  412.   elseif tPacks[1]==-1 then
  413.     table.remove(tPacks,1)
  414.   end
  415.  
  416.   local info,repo = getInformation(pack)
  417.   if not info then
  418.     print("Package does not exist")
  419.     return
  420.   end
  421.   if update then
  422.     print("Updating package "..pack)
  423.     if not tPacks[pack] then
  424.       io.stderr:write("error while checking update path\n")
  425.       return
  426.     end
  427.     for i,j in pairs(info.files) do
  428.       if not string.find(j,"^//") then
  429.         for k,v in pairs(tPacks[pack]) do
  430.           if k==i then
  431.             path = string.gsub(fs.path(v),j.."/?$","/")
  432.             break
  433.           end
  434.         end
  435.         if path then
  436.           break
  437.         end
  438.       end
  439.     end
  440.     path = shell.resolve(string.gsub(path,"^/?","/"),nil)
  441.   end
  442.   if not update and fs.exists(path) then
  443.     if not fs.isDirectory(path) then
  444.       if options.f then
  445.         path = fs.concat(fs.path(path),pack)
  446.         fs.makeDirectory(path)
  447.       else
  448.         print("Path points to a file, needs to be a directory.")
  449.         return
  450.       end
  451.     end
  452.   elseif not update then
  453.     if options.f then
  454.       fs.makeDirectory(path)
  455.     else
  456.       print("Directory does not exist.")
  457.       return
  458.     end
  459.   end
  460.   if tPacks[pack] and (not update) then
  461.     print("Package has already been installed")
  462.     return
  463.   elseif not tPacks[pack] and update then
  464.     print("Package has not been installed.")
  465.     print("If it has, uninstall it manually and reinstall it.")
  466.     return
  467.   end
  468.   if update then
  469.     term.write("Removing old files...")
  470.     for i,j in pairs(tPacks[pack]) do
  471.       if not string.find(i, "^%?") then
  472.         fs.remove(j)
  473.       end
  474.     end
  475.     term.write("Done.\n")
  476.   end
  477.   tPacks[pack] = {}
  478.   term.write("Installing Files...")
  479.   for i,j in pairs(info.files) do
  480.     local nPath
  481.     if string.find(j,"^//") then
  482.       local lPath = string.sub(j,2)
  483.       if not fs.exists(lPath) then
  484.         fs.makeDirectory(lPath)
  485.       end
  486.       nPath = fs.concat(lPath,string.gsub(i,".+(/.-)$","%1"),nil)
  487.     else
  488.       local lPath = fs.concat(path,j)
  489.       if not fs.exists(lPath) then
  490.         fs.makeDirectory(lPath)
  491.       end
  492.       nPath = fs.concat(path,j,string.gsub(i,".+(/.-)$","%1"),nil)
  493.     end
  494.     local soft = string.find(i, "^%?") and fs.exists(nPath)
  495.     local success,response = pcall(downloadFile,"https://raw.githubusercontent.com/"..repo.."/"..string.gsub(i,"^%?",""),nPath, nil, soft)
  496.     if success and response then
  497.       tPacks[pack][i] = nPath
  498.     else
  499.       response = response or "no error message"
  500.       term.write("Error while installing files for package '"..pack.."': "..response..". Reverting installation... ")
  501.       fs.remove(nPath)
  502.       for o,p in pairs(tPacks[pack]) do
  503.         fs.remove(p)
  504.         tPacks[pack][o]=nil
  505.       end
  506.       print("Done.\nPlease contact the package author about this problem.")
  507.       return
  508.     end
  509.   end
  510.  
  511.   if info.dependencies then
  512.     term.write("Done.\nInstalling Dependencies...\n")
  513.     for i,j in pairs(info.dependencies) do
  514.       local nPath
  515.       if string.find(j,"^//") then
  516.         nPath = string.sub(j,2)
  517.       else
  518.         nPath = fs.concat(path,j)
  519.       end
  520.       if string.lower(string.sub(i,1,4))=="http" then
  521.         nPath = fs.concat(nPath, string.gsub(i,".+(/.-)$","%1"),nil)
  522.         local success,response = pcall(downloadFile,i,nPath)
  523.         if success and response then
  524.           tPacks[pack][i] = nPath
  525.           saveToFile(tPacks)
  526.         else
  527.           response = response or "no error message"
  528.           term.write("Error while installing files for package '"..pack.."': "..response..". Reverting installation... ")
  529.           fs.remove(nPath)
  530.           for o,p in pairs(tPacks[pack]) do
  531.             fs.remove(p)
  532.             tPacks[pack][o]=nil
  533.           end
  534.           saveToFile(tPacks)
  535.           print("Done.\nPlease contact the package author about this problem.")
  536.           return tPacks
  537.         end
  538.       else
  539.         local depInfo = getInformation(string.lower(i))
  540.         if not depInfo then
  541.           term.write("\nDependency package "..i.." does not exist.")
  542.         end
  543.         local tNewPacks = installPackage(string.lower(i),nPath,update,tPacks)
  544.         if tNewPacks then
  545.           tPacks = tNewPacks
  546.         end
  547.       end
  548.     end
  549.   end
  550.   saveToFile(tPacks)
  551.   term.write("Done.\n")
  552.   print("Successfully installed package "..pack)
  553.   return tPacks
  554. end
  555.  
  556. local function uninstallPackage(pack)
  557.   local tFiles = readFromFile(1)
  558.   if not tFiles then
  559.     io.stderr:write("Error while trying to read package names")
  560.     return
  561.   elseif tFiles[1]==-1 then
  562.     table.remove(tFiles,1)
  563.   end
  564.   if not tFiles[pack] then
  565.       print("Package has not been installed.")
  566.       print("If it has, the package could not be identified.")
  567.       print("In this case you have to remove it manually.")
  568.       return
  569.   end
  570.   term.write("Removing package files...")
  571.   for i,j in pairs(tFiles[pack]) do
  572.     fs.remove(j)
  573.   end
  574.   term.write("Done\nRemoving references...")
  575.   tFiles[pack]=nil
  576.   saveToFile(tFiles)
  577.   term.write("Done.\n")
  578.   print("Successfully uninstalled package "..pack)
  579. end
  580.  
  581. local function updatePackage(pack)
  582.   if pack=="all" then
  583.     print("Updating everything...")
  584.     local tFiles = readFromFile(1)
  585.     if not tFiles then
  586.       io.stderr:write("Error while trying to read package names")
  587.       return
  588.     elseif tFiles[1]==-1 then
  589.       table.remove(tFiles,1)
  590.     end
  591.     local done = false
  592.     for i in pairs(tFiles) do
  593.       installPackage(i,nil,true)
  594.       done = true
  595.     end
  596.     if not done then
  597.       print("No package has been installed so far.")
  598.     end
  599.   else
  600.     installPackage(args[2],nil,true)
  601.   end
  602. end
  603.  
  604. local function registerRepo(repo)
  605.   if not repo then
  606.     printUsage()
  607.     return
  608.   end
  609.   print("Checking Repository "..repo)
  610.   local lPacks = getPackages(repo)
  611.   if type(lPacks) == "table" then
  612.     local svd = readFromFile(1)
  613.     if not svd then
  614.       io.stderr:write("Error while trying to read save file")
  615.       return
  616.     elseif svd[1] == -1 then
  617.       table.remove(svd, 1)
  618.     end
  619.     svd._repos = svd._repos or {}
  620.     if svd._repos[repo] then
  621.       io.stderr:write("Repository " .. repo.." already registered\n")
  622.       return
  623.     else
  624.       svd._repos[repo] = {["repo"] = repo}
  625.       saveToFile(svd)
  626.       term.write("Done.\n")
  627.       print("Successfully registered repository "..repo)
  628.     end
  629.   else
  630.     io.stderr:write("Repository " .. repo.." not found or not containing programs.cfg\n")
  631.     return
  632.   end
  633. end
  634.  
  635. local function unregisterRepo(repo)
  636.   if not repo then
  637.     printUsage()
  638.     return
  639.   end
  640.   local svd = readFromFile(1)
  641.   if not svd then
  642.     io.stderr:write("Error while trying to read save file")
  643.     return
  644.   elseif svd[1] == -1 then
  645.     table.remove(svd, 1)
  646.   end
  647.   if svd._repos then
  648.     if not svd._repos[repo] then
  649.       io.stderr:write("Repository " .. repo .. " not registered\n")
  650.       return
  651.     else
  652.       svd._repos[repo] = nil
  653.       saveToFile(svd)
  654.       term.write("Done.\n")
  655.       print("Successfully unregistered repository " .. repo)
  656.     end
  657.   end
  658. end
  659.  
  660. if args[1] == "list" then
  661.   if not getInternet() then return end
  662.   local packs = listPackages(args[2])
  663.   printPackages(packs)
  664. elseif args[1] == "info" then
  665.   if not getInternet() then return end
  666.   provideInfo(args[2])
  667. elseif args[1] == "install" then
  668.   if not getInternet() then return end
  669.   installPackage(args[2],args[3],false)
  670. elseif args[1] == "update" then
  671.   if not getInternet() then return end
  672.   updatePackage(args[2])
  673. elseif args[1] == "uninstall" then
  674.   uninstallPackage(args[2])
  675. elseif args[1] == "register" then
  676.   if not getInternet() then return end
  677.   registerRepo(args[2])
  678. elseif args[1] == "unregister" then
  679.   unregisterRepo(args[2])
  680. else
  681.   printUsage()
  682.   return
  683. end
Add Comment
Please, Sign In to add comment