You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
544 lines
17 KiB
544 lines
17 KiB
--[[
|
|
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] <filter>' to get a list of available packages containing the specified substring")
|
|
print(" -i: Only list already installed packages")
|
|
print("'spm info <package>' to get further information about a program package")
|
|
print("'spm install [-f] [-m] <package> [path]' to download a package to a directory on your system (or /usr by default)")
|
|
print("'spm update <package>' to update an already installed package")
|
|
print("'spm update all' to update every already installed package")
|
|
print("'spm uninstall <package>' 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
|