Merge pull request 'refactored updater' (#1) from robin into master

Reviewed-on: freddy/PowerManager#1
Reviewed-by: freddy <freddy@noreply.git.realrobin.io>
This commit is contained in:
2024-02-26 21:21:50 +00:00
3 changed files with 341 additions and 253 deletions

View File

@@ -19,7 +19,6 @@ function clearScreen()
drawLine(1, 1, w, h, 0x000000)
end
function drawLine(startX, startY, stopX, stopY, colorOfLine)
local oldColor = gpu.getBackground(false)
gpu.setBackground(colorOfLine, false)

339
git-tool.lua Normal file
View File

@@ -0,0 +1,339 @@
local args = {...}
local internet=require("internet")
local filesystem=require("filesystem")
local unicode=require("unicode")
YES = {"y","yes","Y","Yes","YES"}
NO = {"n","no","N","No","NO"}
SupportedRemotes = {
Github = {
BaseUrl = "https://github.com/",
RawApiUrl = "https://raw.githubusercontent.com/",
Implemented = true
},
Gitea = {
BaseUrl = "https://git.realrobin.io",
Implemented = false
}
}
EmptyRepository = {
Owner = nil,
Name = nil,
ShortName = nil,
RepoIdentifier = nil,
Remote = nil,
CurrentBranch = nil,
CurrentLocalPath = nil
}
---default repo to update
DefaultRepository = {
Owner = "seesberger",
Name = "PowerManager",
ShortName = "powerman",
RepoIdentifier = "seesberger/PowerManager",
Remote = SupportedRemotes.Github,
CurrentBranch = "master",
CurrentLocalPath = ""
}
DefaultTemporaryDownloadPath = "/home/.tmp/git/"
DefaultInstallationPath = "/usr/"
local function askYesOrNoQuestion(question, expectedTrue, expectedFalse, defaultYesOnEnter)
local function checkContains(array,value)
for idx, val in pairs(array) do
if value == val then return true end
end
return false
end
if defaultYesOnEnter==true then print(question.." [Y/n]") else print(question.." [y/N]") end
while true do
local userInput = io.read("l")
if checkContains({""}, userInput) then return defaultYesOnEnter end
if checkContains(expectedTrue, userInput) then return true end
if checkContains(expectedFalse, userInput) then return false end
print("Please answer with yes or no. You can also press ENTER to choose the default option.")
end
end
local function askTextQuestion(question, defaultAnswerOnEnter, allowOnly)
local allowedInputsString = ""
for idx, entry in pairs(allowOnly) do allowedInputsString = allowedInputsString..entry end
if allowOnly then print(question.." ["..allowedInputsString.."]")
else print(question) end
local userInput = nil
local found = false
repeat
userInput = io.read("l")
if allowOnly then
for idx, entry in pairs(allowOnly) do if entry == userInput then found = true end end
else break end
until found
if userInput == "" then return defaultAnswerOnEnter else return userInput end
end
local function makeDirIfNotExists(target)
if filesystem.exists(target) then
if not filesystem.isDirectory(target) then error("target directory already exists and is not a directory.") end
if filesystem.get(target).isReadOnly() then error("target directory is read-only.") end
else
if not filesystem.makeDirectory(target) then error("directory creation failed") end
end
end
--- todo: pcall and catch errors
local function downloadRepo(repository, remote, autoOverride, targetDownloadPath)
local function validateRepositoryIdentifier(repository)
if not repository.RepoIdentifier:match("^[%w-.]*/[%w-.]*$") then
print('"'..repository.RepoIdentifier..'" does not look like a valid repo identifier.\nShould be <owner>/<reponame>')
return
end
end
validateRepositoryIdentifier(repository)
--- FIXME: If download only mode is enabled, set this to /home. still todo
repository.CurrentLocalPath = targetDownloadPath.."/"
local success, res = pcall(makeDirIfNotExists, targetDownloadPath)
if not success then error("the download failed because of filesystem errors.") end
local function fetchFilesAndSubdirs(repository, remote, dir)
dir = dir or "" -- default value, start at root dir
if remote == SupportedRemotes.Github then
print("fetching contents for "..repository.RepoIdentifier..dir)
local githubApiUrl="https://api.github.com/repos/"..repository.RepoIdentifier.."/contents"..dir
local success,chunks=pcall(internet.request,githubApiUrl)
local raw=""
local files={}
local directories={}
if success then for chunk in chunks do raw=raw..chunk end
else error("you've been cut off. Serves you right.") end
--- do not question the magic of the outer gods
--- turns raw response into t, which has usable fields.
raw=raw:gsub("%[","{"):gsub("%]","}"):gsub("(\".-\"):(.-[,{}])",function(a,b) return "["..a.."]="..b end)
local t=load("return "..raw)()
for i=1,#t do
if t[i].type=="dir" then
table.insert(directories,dir.."/"..t[i].name)
local subfiles,subdirs=fetchFilesAndSubdirs(repository,remote,dir.."/"..t[i].name)
for i=1,#subfiles do
table.insert(files,subfiles[i])
end
for i=1,#subdirs do
table.insert(directories,subdirs[i])
end
else
files[#files+1]=dir.."/"..t[i].name
end
end
return files, directories
else error("not Implemented") end
end
--- fetch and make dirs in the target download path recursively
local files,dirs=fetchFilesAndSubdirs(repository, repository.Remote, "")
for i=1,#dirs do
local success, err = pcall(makeDirIfNotExists, targetDownloadPath..dirs[i])
if not success then error(("the download failed because of filesystem errors. %x"):format(err)) end
end
local replaceMode="ask"
if autoOverride == true then replaceMode = "always" end
local function downloadFiles(files, targetDownloadPath, replaceMode)
local replace=nil
local downloadedFileTargets = {}
for i=1,#files do
if replaceMode == "always" then replace = true end
local fileExists = filesystem.exists(targetDownloadPath..files[i])
if fileExists then
--- FIXME dir löschen statt error
if filesystem.isDirectory(targetDownloadPath..files[i]) then error("file "..targetDownloadPath..files[i].." blocked by directory with same name!") end
if replace == nil then
local userInput = askTextQuestion("\nFile "..targetDownloadPath..files[i].." already exists.\nReplace with new version?","n",{"y","n","A","S"})
--- FIXME: replaceMode ist broken. A wird immer wieder gefragt.
if userInput=="A" then
replaceMode="always"
userInput="y"
elseif userInput=="S" then
replaceMode="never"
userInput="n"
end
if userInput == "y" then replace=true end
if userInput == "n" then replace=false end
end
end
if fileExists and replace then filesystem.remove(targetDownloadPath..files[i]) end
if replace or (replace == nil) then
--- TODO @Freddy: Coole Animation hinzufügen
print("downloading file "..files[i])
--- HACK: RawApiUrl is for using Github Raw API. may have to be patched for Gitea Support
local url=repository.Remote.RawApiUrl..repository.RepoIdentifier.."/"..repository.CurrentBranch..files[i]
local success,response=pcall(internet.request,url)
if success then
local raw=""
for chunk in response do
raw=raw..chunk
end
local absoluteDownloadFileTargetPath = targetDownloadPath..files[i]
print("writing to "..absoluteDownloadFileTargetPath)
local file=io.open(absoluteDownloadFileTargetPath,"w")
if file then -- might be nil under wierd circumstances
file:write(raw)
file:close()
end
table.insert(downloadedFileTargets, files[i])
else error("a file did not download correctly. aborting") end
else error("file not removed, but installation was cancelled - This might result in a broken install.") end
end
--- return list of all abolute paths the files were downloaded to
return downloadedFileTargets
end
success, res = pcall(downloadFiles, files, targetDownloadPath, replaceMode)
if success then print("All files downloaded successfully.") return res else error(res) end
end
local function installFiles(downloadedFiles, downloadTargetDir, installTargetDir)
local installedFiles = {}
--- FIXME: ask user about replacing files.
local replace = true
makeDirIfNotExists(installTargetDir)
for idx, file in pairs(downloadedFiles) do
local absoluteDownloadFilePath = downloadTargetDir.."/"..file:sub(2,-1)
print(idx..": Installing File "..absoluteDownloadFilePath.." to target directory "..installTargetDir..file)
local fileExists = filesystem.exists(installTargetDir..file)
if fileExists and replace then filesystem.remove(installTargetDir..file) end
if replace or (replace == nil) then
--- TODO @Freddy: Coole Animation hinzufügen
os.execute("cp "..absoluteDownloadFilePath.." "..installTargetDir..file)
table.insert(installedFiles, installTargetDir..file)
else error("file not removed, but installation was cancelled - This might result in a broken install.") end
end
return installedFiles
end
local function installShortcut(currentRepoPath, shortcutName, targetDir)
print("installing shortcut...")
makeDirIfNotExists(targetDir)
local shortcutInstallTarget = currentRepoPath.."shortcut.lua "..targetDir..shortcutName..".lua"
os.execute("mv "..shortcutInstallTarget)
return {shortcutInstallTarget}
end
--- todo: automatic read of dependency list (txt file containing lines with <link> <dst> or somethink like it)
--- removeme
local function legacyInstallDependencies()
print("Legacy Mode: Installing hardcoded dependencies...")
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/shapes_default.lua /lib/shapes_default.lua")
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/GUI.lua /lib/GUI.lua")
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/term_mod.lua /lib/term_mod.lua")
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/tech_demo.lua /home/GUI_tech_demo.lua")
local installedDependencies = {"/lib/shapes_default.lua", "/lib/GUI.lua", "/lib/term_mod.lua", "/home/GUI_tech_demo.lua"}
return installedDependencies
end
local function removeDownloads(downloadTargetDir, downloadedFiles)
for idx, file in pairs(downloadedFiles) do
file = file:sub(2,-1) --- get "file" from previous string "/file"
print(idx..": Removing temporary file "..downloadTargetDir..file)
local fileExists = filesystem.exists(downloadTargetDir..file)
if fileExists then filesystem.remove(downloadTargetDir..file) end
end
print("cleaned temporary files.")
end
local function removeFilesViaManifest(manifestTarget)
--- todo
end
local function createManifest(installedDependencies, installedFiles, installedShortcuts, manifestTarget, repoName)
makeDirIfNotExists(manifestTarget)
local manifest = ""
for idx, file in pairs(installedFiles) do
manifest = manifest..file.."\n"
end
for idy, shortcut in pairs(installedShortcuts) do
manifest = manifest..shortcut.."\n"
end
for idz, dependency in pairs(installedDependencies) do
manifest = manifest..dependency.."\n"
end
print("writing manifest to "..manifestTarget..repoName)
local file=io.open(manifestTarget..repoName,"w")
if file then -- might be nil under wierd circumstances
file:write(manifest)
file:close()
end
end
--- fix legacy shit
local function runFullInstallTask(repository, shortcutName)
--- first, download the actual repo.
--- then, find dependencies - if then exist, download and install them
--- then, install the actual program
local installTargetDir = "/usr/"..repository.Name
local shortcutTargetDir = "/usr/bin/"
local downloadTargetDir = DefaultTemporaryDownloadPath..repository.RepoIdentifier
local manifestTarget = "/etc/manifest/"
local installedDependencies = legacyInstallDependencies() --only for testing while new version not implemented yet
print("downloading "..repository.RepoIdentifier)
local downloadedFiles = downloadRepo(repository, repository.Remote, false, downloadTargetDir) --enable auto-overwrite in other situations still todo
local installedFiles = installFiles(downloadedFiles, downloadTargetDir, installTargetDir)
local installedShortcuts = installShortcut(repository.CurrentLocalPath, shortcutName, shortcutTargetDir)
--- remove temporary files and create manifest for later uninstall
removeDownloads(downloadTargetDir, downloadedFiles)
createManifest(installedDependencies, installedFiles, installedShortcuts, manifestTarget, repository.Name)
end
local function printHelpText()
local helpText = "This updater pulls the git files for installation and application updates.\n"..
"Usage:\n" ..
"updater <option> - no args: manual update and install\n"..
" '' -h or - this help text\n"..
" '' -d or - install default config"
print(helpText)
end
local function run(cliArgs)
local repo = EmptyRepository
local shortcutName = nil
if #cliArgs<1 then
print("No Arguments given. For help, please check -h or --help")
return
end
if cliArgs[1] == "-h" then
printHelpText()
return
elseif cliArgs[1] == "-d" then
--- ask user about repo
if askYesOrNoQuestion("Use default config? (github::seesberger/PowerManager)?",YES,NO,true) == true then
repo = DefaultRepository
--- ask user about shortcutname
if askYesOrNoQuestion("Use default shortcut name \"powerman\"?",YES,NO,true) then shortcutName = "powerman" end
runFullInstallTask(repo, shortcutName)
--- TODO non-default config
else print("other things not implemented yet") end
else
print('"'..cliArgs[1]..'" - Bad argument. Try --help')
print("Program exited.")
end
end
run(args)

View File

@@ -1,250 +0,0 @@
local args = {...}
local internet=require("internet")
local text=require("text")
local filesystem=require("filesystem")
local unicode=require("unicode")
local term=require("term")
local event=require("event")
local keyboard=require("keyboard")
local repository = "seesberger/PowerManager"
local repoTargetPath = "/usr/PowerManager/"
local libTargetPath = "/lib/"
local helpText = "This updater pulls the git files for installation and application updates.\n"..
"Usage:\n" ..
"updater <option> - no args: manual update and install\n"..
" '' -h - this help text\n" ..
" '' -a - automatic update no install\n" ..
" '' -i - automatic update and install"
function manualUpdate()
print("Manual repo pull...")
downloadRepo(repository, repoTargetPath, false)
end
function manualInstall()
print("installing shortcut...")
os.execute("mv "..repoTargetPath.."shortcut.lua /usr/bin/powerman.lua")
end
function automaticUpdate()
print("Auto repo pull...")
downloadRepo(repository, repoTargetPath, true)
end
function automaticInstall()
print("downloading GUI-API")
downloadDependencies()
manualInstall()
end
function downloadDependencies(automatic)
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/shapes_default.lua /lib/shapes_default.lua")
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/GUI.lua /lib/GUI.lua")
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/term_mod.lua /lib/term_mod.lua")
os.execute("wget -f https://github.com/kevinkk525/OC-GUI-API/raw/master/tech_demo.lua /home/GUI_tech_demo.lua")
end
function downloadRepo(repo, target, automatic)
if not repo:match("^[%w-.]*/[%w-.]*$") then
print('"'..repo..'" does not look like a valid repo identifier.\nShould be <owner>/<reponame>')
return
end
target=target and ("/"..target:match("^/?(.-)/?$").."/") or "/tmp/"..repo
if filesystem.exists(target) then
if not filesystem.isDirectory(target) then
print("target directory already exists and is not a directory.")
return
end
if filesystem.get(target).isReadOnly() then
print("target directory is read-only.")
return
end
else
if not filesystem.makeDirectory(target) then
print("target directory is read-only")
return
end
end
-- this isn't acually used, but it is tested and works on decoding the base64 encoded data that github
--sends for some queries, leaving it in here for possible future/related use, might be able to pull
--and display difs and things like that?
local symb="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
function decode64(text)
local val,bits=0,0
local output=""
for ch in text:gmatch(".") do
if symb:find(ch) then
--print("ch "..ch.."-> "..(symb:find(ch)-1))
val=bit32.lshift(val,6)+symb:find(ch)-1
else
print(ch.."?")
return
end
bits=bits+6
--print("bits : "..bits)
--print(string.format("val : 0x%04x",val))
if bits>=8 then
local och=unicode.char(bit32.rshift(val,bits-8))
--print("os<<"..och)
--print("& with "..(2^(bits-8)-1))
val=bit32.band(val,2^(bits-8)-1)
bits=bits-8
--print(string.format("val : 0x%04x",val))
output=output..och
end
end
return output
end
local function gitContents(repo, dir)
print("fetching contents for "..repo..dir)
local url="https://api.github.com/repos/"..repo.."/contents"..dir
local result,response=pcall(internet.request,url)
local raw=""
local files={}
local directories={}
if result then
for chunk in response do
raw=raw..chunk
end
else
error("you've been cut off. Serves you right.")
end
response=nil
raw=raw:gsub("%[","{"):gsub("%]","}"):gsub("(\".-\"):(.-[,{}])",function(a,b) return "["..a.."]="..b end)
local t=load("return "..raw)()
for i=1,#t do
if t[i].type=="dir" then
table.insert(directories,dir.."/"..t[i].name)
local subfiles,subdirs=gitContents(repo,dir.."/"..t[i].name)
for i=1,#subfiles do
table.insert(files,subfiles[i])
end
for i=1,#subdirs do
table.insert(directories,subdirs[i])
end
else
files[#files+1]=dir.."/"..t[i].name
end
end
return files, directories
end
local files,dirs=gitContents(repo,"")
for i=1,#dirs do
print("making dir "..target..dirs[i])
if filesystem.exists(target..dirs[i]) then
if not filesystem.isDirectory(target..dirs[i]) then
print("error: directory "..target..dirs[i].." blocked by file with the same name")
return
end
else
filesystem.makeDirectory(target..dirs[i])
end
end
local replaceMode="ask"
if automatic == true then replaceMode = "always" end
for i=1,#files do
local replace=nil
if filesystem.exists(target..files[i]) then
if filesystem.isDirectory(target..files[i]) then
print("Error: file "..target..files[i].." blocked by directory with same name!")
return
end
if replaceMode=="always" then
replace=true
elseif replaceMode=="never" then
replace=false
else
print("\nFile "..target..files[i].." already exists.\nReplace with new version?")
local response=""
while replace==nil do
term.write("yes,no,always,skip all[ynAS]: ")
local char
repeat
_,_,char=event.pull("key_down")
until not keyboard.isControl(char)
char=unicode.char(char)
print(char)
if char=="A" then
replaceMode="always"
replace=true
char="y"
elseif char=="S" then
replaceMode="never"
replace=false
char="n"
elseif char:lower()=="y" then
replace=true
elseif char:lower()=="n" then
replace=false
else
print("invalid response.")
end
end
end
if replace then
filesystem.remove(target..files[i])
end
end
if replace~=false then
print("downloading "..files[i])
local url="https://raw.github.com/"..repo.."/master"..files[i]
local result,response=pcall(internet.request,url)
if result then
local raw=""
for chunk in response do
raw=raw..chunk
end
print("writing to "..target..files[i])
local file=io.open(target..files[i],"w")
file:write(raw)
file:close()
else
print("failed, skipping")
end
end
end
end
if #args<1 then
print("No Arguments given. Manual process...")
manualUpdate()
automaticInstall()
return
end
if args[1] == "-h" then
print(helpText)
return
elseif args[1] == "-a" then
automaticUpdate()
return
elseif args[1] == "-i" then
automaticUpdate()
automaticInstall()
return
else
print('"'..args[1]..'" - Bad argument.')
print(helpText)
return
end