--[[ Sothr Package Manager, browser and downloader, for easy access to many programs Author: Vexatos, WarrickSothr Forked and modified from: https://github.com/Wuerfel21/OpenComputers/blob/master/src/main/resources/assets/opencomputers/loot/OPPM/oppm.lua ]] local component = require("component") local event = require("event") local fs = require("filesystem") local process = require("process") local serial = require("serialization") local shell = require("shell") local term = require("term") local wget = loadfile("/bin/wget.lua") local gpu = component.gpu if not component.isAvailable("internet") then io.stderr:write("This program requires an internet card to run.") return end local internet = require("internet") local githubContentRoot = "https://raw.githubusercontent.com/" local repoLocationConfig = "{URL_BASE}/repos.cfg" local localInstalledPackagesFile = "/etc/spdata.svd" local localConfigFile = "/etc/spm.cfg" local args, options = shell.parse(...) local function printUsage() print("Sothr Package Manager, use this to browse through and download sothr packaged programs easily") print("Usage:") print("'spm list [-i]' to get a list of all the available program packages") print("'spm list [-i] ' to get a list of available packages containing the specified substring") print(" -i: Only list already installed packages") print("'spm info ' to get further information about a program package") print("'spm install [-f] [-m] [path]' to download a package to a directory on your system (or /usr by default)") print("'spm update ' to update an already installed package") print("'spm update all' to update every already installed package") print("'spm uninstall ' to remove a package from your system") print(" -f: Force creation of directories and overwriting of existing files.") end -- Make an internet http/s request for data local function getContent(url) local sContent = "" local result, response = pcall(internet.request, url) if not result then return nil end for chunk in response do sContent = sContent .. chunk end return sContent end -- Request repository information local function getRepos() local success, sRepos = pcall(getContent, repoLocationConfig) if not success then io.stderr:write("Could not connect to the Internet. Please ensure you have an Internet connection.") return -1 end return serial.unserialize(sRepos) end -- Get Packages for src local function getPackages(src) local success, sPackages = pcall(getContent, src .. "/programs.cfg") if not success or not sPackages then return -1 end return serial.unserialize(sPackages) end local function getGithubPackages(repo) return getPackages(githubContentRoot .. repo .. "/master") end --For sorting table values by alphabet local function compare(a, b) for i = 1, math.min(#a, #b) do if a:sub(i, i) ~= b:sub(i, i) then return a:sub(i, i) < b:sub(i, i) end end return #a < #b end local function downloadFile(url, path, force) if options.f or force then wget("-fq", url, path) else wget("-q", url, path) end end local function readFromFile(path) if not fs.exists(fs.path(path)) then fs.makeDirectory(fs.path(path)) end if not fs.exists(path) then return { -1 } end local file, msg = io.open(path, "rb") if not file then io.stderr:write("Error while trying to read file at " .. path .. ": " .. msg) return end local serializedTableData = file:read("*a") file:close() return serial.unserialize(serializedTableData) or { -1 } end local function readFromLocalInstallFile() return readFromFile(localInstalledPackagesFile) end local function readLocalConfigFile() local path = localConfigFile if not fs.exists(path) then return { -1 } else return readFromFile(path) end end local function saveToInstalledPackagesFile(installedPackages) local file, msg = io.open(localInstalledPackagesFile, "wb") if not file then io.stderr:write("Error while trying to save package names: " .. msg) return end local sPacks = serial.serialize(installedPackages) file:write(sPacks) file:close() end local function listPackages(filter) filter = filter or false if filter then filter = string.lower(filter) end local packageTable = {} print("Receiving Package list...") if not options.i then local success, repositories = pcall(getRepos) if not success or repositories == -1 then io.stderr:write("Unable to connect to the Internet.\n") return elseif repositories == nil then print("Error while trying to receive repository list") return end for _, repository in pairs(repositories) do local packages local target if repository.src then print("Checking Repository " .. repository.src) packages = getPackages(repository.src) target = repository.src elseif repository.repo then print("Checking Repository " .. repository.repo) packages = getGithubPackages(repository.repo) target = repository.repo end if packages == nil then io.stderr:write("Error while trying to receive package list for " .. target .. "\n") return elseif type(packages) == "table" then for package in pairs(packages) do if not package.hidden then table.insert(packageTable, package) end end end end else local localPackageTable = {} local localPackages = readFromLocalInstallFile() for localPackage in pairs(localPackages) do table.insert(localPackageTable, localPackage) end packageTable = localPackageTable end if filter then local packages = {} for _, package in ipairs(packageTable) do if (#package >= #filter) and string.find(package, filter, 1, true) ~= nil then table.insert(packages, package) end end packageTable = packages end table.sort(packageTable, compare) return packageTable end local function printPackages(packages) if packages == nil or not packages[1] then print("No package matching specified filter found.") return end term.clear() local xRes, yRes = gpu.getResolution() print("--SPM {ENV} Package list--") local xCur, yCur = term.getCursor() for _, package in ipairs(packages) do term.write(package .. "\n") yCur = yCur + 1 if yCur > yRes - 1 then term.write("[Press any key to continue]") local event = event.pull("key_down") if event then term.clear() print("--SPM {ENV} Package list--") xCur, yCur = term.getCursor() end end end end local function getInformation(requestedPackage) local success, repositories = pcall(getRepos) if not success or repositories == -1 then io.stderr:write("Unable to connect to the Internet.\n") return end for _, repository in pairs(repositories) do local packages local target if repository.src then packages = getPackages(repository.src) target = repository.src elseif repository.repo then packages = getGithubPackages(repository.repo) target = githubContentRoot .. repository.repo end if packages == nil then io.stderr:write("Error while trying to receive package list for " .. target .. "\n") elseif type(packages) == "table" then for package in pairs(packages) do if package == requestedPackage then return packages[package], target end end end end return nil end local function provideInfo(pack) if not pack then printUsage() return end pack = string.lower(pack) local info = getInformation(pack) if not info then print("Package does not exist") return end local done = false print("--Information about package '" .. pack .. "'--") if info.name then print("Name: " .. info.name) done = true end if info.version then print("Version: " .. info.version) done = true end if info.description then print("Description: " .. info.description) done = true end if info.authors then print("Authors: " .. info.authors) done = true end if info.note then print("Note: " .. info.note) done = true end if not done then print("No information provided.") end end local function installPackage(requestedPackage, installPath, doUpdate) doUpdate = doUpdate or false if not requestedPackage then printUsage() return end if not installPath and not doUpdate then local localConfig = readLocalConfigFile() installPath = localConfig.path or "/usr" print("Installing package to " .. installPath .. "...") elseif not doUpdate then installPath = shell.resolve(installPath) print("Installing package to " .. installPath .. "...") end requestedPackage = string.lower(requestedPackage) local locallyInstalledPackages = readFromLocalInstallFile() if not locallyInstalledPackages then io.stderr:write("Error while trying to read local package names") return elseif locallyInstalledPackages[1] == -1 then table.remove(locallyInstalledPackages, 1) end local packageInfo, repositoryPath = getInformation(requestedPackage) if not packageInfo then print("Package does not exist") return end if doUpdate then print("Updating package " .. requestedPackage) installPath = nil for filePath, fileInstallPath in pairs(packageInfo.files) do for installedFilePath, fileLocalInstallPath in pairs(locallyInstalledPackages[requestedPackage]) do if installedFilePath == filePath then installPath = string.gsub(fs.path(fileLocalInstallPath), fileInstallPath .. "/?$", "/") break end end if installPath then break end end installPath = shell.resolve(string.gsub(installPath, "^/?", "/"), nil) end if not doUpdate and fs.exists(installPath) then if not fs.isDirectory(installPath) then if options.f then installPath = fs.concat(fs.path(installPath), requestedPackage) fs.makeDirectory(installPath) else print("Path points to a file, needs to be a directory.") return end end elseif not doUpdate then if options.f then fs.makeDirectory(installPath) else print("Directory does not exist.") return end end if locallyInstalledPackages[requestedPackage] and (not doUpdate) then print("Package has already been installed") return elseif not locallyInstalledPackages[requestedPackage] and doUpdate then print("Package has not been installed.") print("If it has, uninstall it manually and reinstall it.") return end if doUpdate then term.write("Removing old files...") for installedFilePath, fileLocalInstallPath in pairs(locallyInstalledPackages[requestedPackage]) do fs.remove(fileLocalInstallPath) end term.write("Done.\n") end locallyInstalledPackages[requestedPackage] = {} term.write("Installing Files...") for filePath, requestedInstallPath in pairs(packageInfo.files) do local installedPath if string.find(requestedInstallPath, "^//") then local localInstalledPath = string.sub(requestedInstallPath, 2) if not fs.exists(localInstalledPath) then fs.makeDirectory(localInstalledPath) end installedPath = fs.concat(localInstalledPath, string.gsub(filePath, ".+(/.-)$", "%1"), nil) else local localInstalledPath = fs.concat(installPath, requestedInstallPath) if not fs.exists(localInstalledPath) then fs.makeDirectory(localInstalledPath) end installedPath = fs.concat(installPath, requestedInstallPath, string.gsub(filePath, ".+(/.-)$", "%1"), nil) end if packageInfo.minified or options.m then filePath = string.gsub(filePath, "(.+)%.lua$", "%1") .. ".min.lua" end local success = pcall(downloadFile, repositoryPath .. "/" .. filePath, installedPath) if success then locallyInstalledPackages[requestedPackage][filePath] = installedPath end end if packageInfo.dependencies then term.write("Done.\nInstalling Dependencies...") for packageDependency, dependencyPath in pairs(packageInfo.dependencies) do local installedDependencyPath if string.find(dependencyPath, "^//") then installedDependencyPath = string.sub(dependencyPath, 2) else installedDependencyPath = fs.concat(installPath, dependencyPath, string.gsub(packageDependency, ".+(/.-)$", "%1"), nil) end if string.lower(string.sub(packageDependency, 1, 4)) == "http" then local success = pcall(downloadFile, packageDependency, installedDependencyPath) if success then locallyInstalledPackages[requestedPackage][packageDependency] = installedDependencyPath end else local dependencyInfo = getInformation(string.lower(packageDependency)) if not dependencyInfo then term.write("\nDependency package " .. packageDependency .. " does not exist.") end installPackage(string.lower(packageDependency), fs.concat(installPath, dependencyPath)) end end end term.write("Done.\n") saveToInstalledPackagesFile(locallyInstalledPackages) print("Successfully installed package " .. requestedPackage) end local function uninstallPackage(requestedPackage) local packageInformation, repositoryLocation = getInformation(requestedPackage) if not packageInformation then print("Package does not exist") return end local locallyInstalledFiles = readFromLocalInstallFile() if not locallyInstalledFiles then io.stderr:write("Error while trying to read package names") return elseif locallyInstalledFiles[1] == -1 then table.remove(locallyInstalledFiles, 1) end if not locallyInstalledFiles[requestedPackage] then print("Package has not been installed.") print("If it has, you have to remove it manually.") return end term.write("Removing package files...") for installedFilePath, fileLocalInstallPath in pairs(locallyInstalledFiles[requestedPackage]) do fs.remove(fileLocalInstallPath) end term.write("Done\nRemoving references...") locallyInstalledFiles[requestedPackage] = nil saveToInstalledPackagesFile(locallyInstalledFiles) term.write("Done.\n") print("Successfully uninstalled package " .. requestedPackage) end local function updatePackage(requestedPackage) if requestedPackage == "all" then print("Updating everything...") local locallyInstalledPackages = readFromLocalInstallFile() if not locallyInstalledPackages then io.stderr:write("Error while trying to read package names") return elseif locallyInstalledPackages[1] == -1 then table.remove(locallyInstalledPackages, 1) end local done = false for localPackage in pairs(locallyInstalledPackages) do installPackage(localPackage, nil, true) done = true end if not done then print("No package has been installed so far.") end else installPackage(args[2], nil, true) end end if args[1] == "list" then local tPacks = listPackages(args[2]) printPackages(tPacks) elseif args[1] == "info" then provideInfo(args[2]) elseif args[1] == "install" then installPackage(args[2], args[3], false) elseif args[1] == "update" then updatePackage(args[2]) elseif args[1] == "uninstall" then uninstallPackage(args[2]) else printUsage() return end