diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 975bb73..9caba85 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -26,4 +26,4 @@ jobs: # --no-max-line-length = Disable warnings for long line lengths # --exclude-files ... = Exclude lockbox library (external) and config files # --globals ... = Override all globals overridden in .vscode/settings.json AND 'os' since CraftOS 'os' differs from Lua's 'os' - args: . --no-max-line-length -i 121 512 542 --exclude-files ./lockbox/* ./*/config.lua --globals os _HOST bit colors fs http keys parallel periphemu peripheral read rs settings shell term textutils window + args: . --no-max-line-length -i 121 512 542 --exclude-files ./lockbox/* --globals os _HOST bit colors fs http keys parallel periphemu peripheral read rs settings shell term textutils window diff --git a/.github/workflows/manifest.yml b/.github/workflows/manifest.yml index efafe3d..565363b 100644 --- a/.github/workflows/manifest.yml +++ b/.github/workflows/manifest.yml @@ -46,7 +46,7 @@ jobs: - name: Generate manifest and shields for main branch id: manifest-main if: ${{ (success() || failure()) && steps.checkout-main.outcome == 'success' }} - run: python imgen.py shields + run: python ./build/imgen.py shields - name: Save main's manifest if: ${{ (success() || failure()) && steps.manifest-main.outcome == 'success' }} run: mv install_manifest.json deploy/manifests/main @@ -61,7 +61,7 @@ jobs: - name: Generate manifest for devel id: manifest-devel if: ${{ (success() || failure()) && steps.checkout-devel.outcome == 'success' }} - run: python imgen.py + run: python ./build/imgen.py - name: Save devel's manifest if: ${{ (success() || failure()) && steps.manifest-devel.outcome == 'success' }} run: mv install_manifest.json deploy/manifests/devel diff --git a/.gitignore b/.gitignore index 0688a64..de579c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -_notes/ +_*/ /*program.sh \ No newline at end of file diff --git a/LICENSE b/LICENSE index 7578961..d7dcef6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright © 2022 - 2024 Mikayla Fischler +Copyright 2022 - 2024 Mikayla Fischler Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/build/_offline.lua b/build/_offline.lua new file mode 100644 index 0000000..52b119c --- /dev/null +++ b/build/_offline.lua @@ -0,0 +1,118 @@ +---@diagnostic disable: undefined-global +-- luacheck: push ignore install_manifest ccmsi_offline app_files dep_files lgray green white + +local b64_lookup = { + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7, ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, ['Y'] = 24, ['Z'] = 25, + ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47, ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, + ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63 +} + +local BYTE = 0xFF +local CHAR = string.char +local BOR = bit.bor ---@type function +local BAND = bit.band ---@type function +local LSHFT = bit.blshift ---@type function +local RSHFT = bit.blogic_rshift ---@type function + +-- decode a base64 string +---@param input string +local function b64_decode(input) +---@diagnostic disable-next-line: undefined-field + local t_start = os.epoch("local") + + local decoded = {} + + local c_idx, idx = 1, 1 + + for _ = 1, input:len() / 4 do + local block = input:sub(idx, idx + 4) + local word = 0x0 + + -- build the 24-bit sequence from the 4 characters + for i = 1, 4 do + local num = b64_lookup[block:sub(i, i)] + + if num then + word = BOR(word, LSHFT(b64_lookup[block:sub(i, i)], (4 - i) * 6)) + end + end + + -- decode the 24-bit sequence as 8 bytes + for i = 1, 3 do + local char = BAND(BYTE, RSHFT(word, (3 - i) * 8)) + + if char ~= 0 then + decoded[c_idx] = CHAR(char) + c_idx = c_idx + 1 + end + end + + idx = idx + 4 + end + +---@diagnostic disable-next-line: undefined-field + local elapsed = (os.epoch("local") - t_start) + local decoded_str = table.concat(decoded) + + return decoded_str, elapsed +end + +-- write files recursively from base64 encodings in a table +---@param files table +---@param path string +local function write_files(files, path) + fs.makeDir(path) + + for k, v in pairs(files) do + if type(v) == "table" then + if k == "system" then + -- write system files to root + write_files(v, "/") + else + -- descend into directories + write_files(v, path .. "/" .. k .. "/") + end + +---@diagnostic disable-next-line: undefined-field + os.sleep(0.05) + else + local handle = fs.open(path .. k, "w") + local text, time = b64_decode(v) + + print("decoded '" .. k .. "' in " .. time .. "ms") + + handle.write(text) + handle.close() + end + end +end + +-- write installation manifiest and offline install manager +local function write_install() + local handle = fs.open("install_manifest.json", "w") + handle.write(b64_decode(install_manifest)) + handle.close() + + handle = fs.open("ccmsim.lua", "w") + handle.write(b64_decode(ccmsi_offline)) + handle.close() +end + +lgray() + +-- write both app and dependency files +write_files(app_files, "/") +write_files(dep_files, "/") + +-- write an install manifest and the offline installer +write_install() + +green() +print("Done!") +white() +print("All files have been installed. The app can be started with 'startup' and configured with 'configure'.") +lgray() +print("Hint: You can use 'ccmsim' to manage your off-line installation.") +white() + +--luacheck: pop diff --git a/build/bundle.py b/build/bundle.py new file mode 100644 index 0000000..3894044 --- /dev/null +++ b/build/bundle.py @@ -0,0 +1,214 @@ +import base64 +import json +import os +import subprocess +import sys + +path_prefix = "./_minified/" + +# get git build info +build = subprocess.check_output(["git", "describe", "--tags"]).strip().decode('UTF-8') + +# list files in a directory +def list_files(path): + list = [] + + for (root, dirs, files) in os.walk(path): + for f in files: + list.append((root[2:] + "/" + f).replace('\\','/')) + + return list + +# recursively encode files with base64 +def encode_recursive(path): + list = {} + + for item in os.listdir(path): + item_path = path + '/' + item + + if os.path.isfile(item_path): + handle = open(item_path, 'r') + list[item] = base64.b64encode(bytes(handle.read(), 'UTF-8')).decode('ASCII') + handle.close() + else: + list[item] = encode_recursive(item_path) + + return list + +# encode listed files with base64 +def encode_files(files): + list = {} + + for item in files: + item_path = path_prefix + './' + item + + handle = open(item_path, 'r') + list[item] = base64.b64encode(bytes(handle.read(), 'UTF-8')).decode('ASCII') + handle.close() + + return list + +# get the version of an application at the provided path +def get_version(path, is_lib = False): + ver = "" + string = ".version = \"" + + if not is_lib: + string = "_VERSION = \"" + + f = open(path, "r") + + for line in f: + pos = line.find(string) + if pos >= 0: + ver = line[(pos + len(string)):(len(line) - 2)] + break + + f.close() + + return ver + +# file manifest (reflects imgen.py) +manifest = { + "common_versions" : { + "bootloader" : get_version("./startup.lua"), + "common" : get_version("./scada-common/util.lua", True), + "comms" : get_version("./scada-common/comms.lua", True), + "graphics" : get_version("./graphics/core.lua", True), + "lockbox" : get_version("./lockbox/init.lua", True), + }, + "app_versions" : { + "reactor-plc" : get_version("./reactor-plc/startup.lua"), + "rtu" : get_version("./rtu/startup.lua"), + "supervisor" : get_version("./supervisor/startup.lua"), + "coordinator" : get_version("./coordinator/startup.lua"), + "pocket" : get_version("./pocket/startup.lua") + }, + "files" : { + # common files + "system" : encode_files([ "initenv.lua", "startup.lua", "configure.lua", "LICENSE" ]), + "scada-common" : encode_recursive(path_prefix + "./scada-common"), + "graphics" : encode_recursive(path_prefix + "./graphics"), + "lockbox" : encode_recursive(path_prefix + "./lockbox"), + # platform files + "reactor-plc" : encode_recursive(path_prefix + "./reactor-plc"), + "rtu" : encode_recursive(path_prefix + "./rtu"), + "supervisor" : encode_recursive(path_prefix + "./supervisor"), + "coordinator" : encode_recursive(path_prefix + "./coordinator"), + "pocket" : encode_recursive(path_prefix + "./pocket"), + }, + "install_files" : { + # common files + "system" : [ "initenv.lua", "startup.lua", "configure.lua", "LICENSE" ], + "scada-common" : list_files("./scada-common"), + "graphics" : list_files("./graphics"), + "lockbox" : list_files("./lockbox"), + # platform files + "reactor-plc" : list_files("./reactor-plc"), + "rtu" : list_files("./rtu"), + "supervisor" : list_files("./supervisor"), + "coordinator" : list_files("./coordinator"), + "pocket" : list_files("./pocket"), + }, + "depends" : [ "system", "scada-common", "graphics", "lockbox" ] +} + +# write the application installation items as Lua tables +def write_items(body, items, indent): + indent_str = " " * indent + for key, value in items.items(): + if isinstance(value, str): + body = body + f"{indent_str}['{key}'] = \"{value}\",\n" + else: + body = body + f"{indent_str}['{key}'] = {{\n" + body = write_items(body, value, indent + 4) + body = body + f"{indent_str}}},\n" + + return body + +# create output directory +if not os.path.exists("./BUNDLE"): + os.makedirs("./BUNDLE") + +# get offline installer +ccmsim_file = open("./build/ccmsim.lua", "r") +ccmsim_script = ccmsim_file.read() +ccmsim_file.close() + +# create dependency bundled file +dep_file = "common_" + build + ".lua" +f_d = open("./BUNDLE/" + dep_file, "w") + +body_b = "local dep_files = {\n" + +for depend in manifest["depends"]: + body_b = body_b + write_items("", { f"{depend}": manifest["files"][depend] }, 4) +body_b = body_b + "}\n" + +body_b = body_b + f""" +if select("#", ...) == 0 then + term.setTextColor(colors.red) + print("You must run the other file you should have uploaded (it has the app in its name).") + term.setTextColor(colors.white) +end + +return dep_files +""" + +f_d.write(body_b) +f_d.close() + +# application bundled files +for app in [ "reactor-plc", "rtu", "supervisor", "coordinator", "pocket" ]: + app_file = app + "_" + build + ".lua" + + f_script = open("./build/_offline.lua", "r") + script = f_script.read() + f_script.close() + + f_a = open("./BUNDLE/" + app_file, "w") + + body_a = "local app_files = {\n" + + body_a = body_a + write_items("", { f"{app}": manifest["files"][app] }, 4) + "}\n" + + versions = manifest["common_versions"].copy() + versions[app] = manifest["app_versions"][app] + + depends = manifest["depends"].copy() + depends.append(app) + + install_manifest = json.dumps({ "versions" : versions, "files" : manifest["install_files"], "depends" : depends }) + + body_a = body_a + f""" +-- install manifest JSON and offline installer +local install_manifest = "{base64.b64encode(bytes(install_manifest, 'UTF-8')).decode('ASCII')}" +local ccmsi_offline = "{base64.b64encode(bytes(ccmsim_script, 'UTF-8')).decode('ASCII')}" + +local function red() term.setTextColor(colors.red) end +local function green() term.setTextColor(colors.green) end +local function white() term.setTextColor(colors.white) end +local function lgray() term.setTextColor(colors.lightGray) end + +if not fs.exists("{dep_file}") then + red() + print("Missing '{dep_file}'! Please upload it, then run this file again.") + white() + return +end + +-- rename the dependency file +fs.move("{dep_file}", "install_depends.lua") + +-- load the other file +local dep_files = require("install_depends") + +-- delete the uploaded files to free up space to actually install +fs.delete("{app_file}") +fs.delete("install_depends.lua") + +-- get started installing +{script}""" + + f_a.write(body_a) + f_a.close() diff --git a/build/ccmsim.lua b/build/ccmsim.lua new file mode 100644 index 0000000..d311a7c --- /dev/null +++ b/build/ccmsim.lua @@ -0,0 +1,237 @@ +local function println(message) print(tostring(message)) end +local function print(message) term.write(tostring(message)) end + +local opts = { ... } +local mode, app + +local function red() term.setTextColor(colors.red) end +local function orange() term.setTextColor(colors.orange) end +local function yellow() term.setTextColor(colors.yellow) end +local function green() term.setTextColor(colors.green) end +local function blue() term.setTextColor(colors.blue) end +local function white() term.setTextColor(colors.white) end +local function lgray() term.setTextColor(colors.lightGray) end + +-- get command line option in list +local function get_opt(opt, options) + for _, v in pairs(options) do if opt == v then return v end end + return nil +end + +-- wait for any key to be pressed +---@diagnostic disable-next-line: undefined-field +local function any_key() os.pullEvent("key_up") end + +-- ask the user yes or no +local function ask_y_n(question, default) + print(question) + if default == true then print(" (Y/n)? ") else print(" (y/N)? ") end + local response = read();any_key() + if response == "" then return default + elseif response == "Y" or response == "y" then return true + elseif response == "N" or response == "n" then return false + else return nil end +end + +-- read the local manifest file +local function read_local_manifest() + local local_ok = false + local local_manifest = {} + local imfile = fs.open("install_manifest.json", "r") + if imfile ~= nil then + local_ok, local_manifest = pcall(function () return textutils.unserializeJSON(imfile.readAll()) end) + imfile.close() + end + return local_ok, local_manifest +end + +-- recursively build a tree out of the file manifest +local function gen_tree(manifest, log) + local function _tree_add(tree, split) + if #split > 1 then + local name = table.remove(split, 1) + if tree[name] == nil then tree[name] = {} end + table.insert(tree[name], _tree_add(tree[name], split)) + else return split[1] end + return nil + end + + local list, tree = { log }, {} + + -- make a list of each and every file + for _, files in pairs(manifest.files) do for i = 1, #files do table.insert(list, files[i]) end end + + for i = 1, #list do + local split = {} +---@diagnostic disable-next-line: discard-returns + string.gsub(list[i], "([^/]+)", function(c) split[#split + 1] = c end) + if #split == 1 then table.insert(tree, list[i]) + else table.insert(tree, _tree_add(tree, split)) end + end + + return tree +end + +local function _in_array(val, array) + for _, v in pairs(array) do if v == val then return true end end + return false +end + +local function _clean_dir(dir, tree) + if tree == nil then tree = {} end + local ls = fs.list(dir) + for _, val in pairs(ls) do + local path = dir.."/"..val + if fs.isDir(path) then + _clean_dir(path, tree[val]) + if #fs.list(path) == 0 then fs.delete(path);println("deleted "..path) end + elseif (not _in_array(val, tree)) and (val ~= "config.lua" ) then + fs.delete(path) + println("deleted "..path) + end + end +end + +-- go through app/common directories to delete unused files +local function clean(manifest) + local log = nil + if fs.exists(app..".settings") and settings.load(app..".settings") then + log = settings.get("LogPath") + if log:sub(1, 1) == "/" then log = log:sub(2) end + end + + local tree = gen_tree(manifest, log) + + table.insert(tree, "install_manifest.json") + table.insert(tree, "ccmsim.lua") + + local ls = fs.list("/") + for _, val in pairs(ls) do + if fs.isDriveRoot(val) then + yellow();println("skipped mount '"..val.."'") + elseif fs.isDir(val) then + if tree[val] ~= nil then lgray();_clean_dir("/"..val, tree[val]) + else white(); if ask_y_n("delete the unused directory '"..val.."'") then lgray();_clean_dir("/"..val) end end + if #fs.list(val) == 0 then fs.delete(val);lgray();println("deleted empty directory '"..val.."'") end + elseif not _in_array(val, tree) and (string.find(val, ".settings") == nil) then + white();if ask_y_n("delete the unused file '"..val.."'") then fs.delete(val);lgray();println("deleted "..val) end + end + end + + white() +end + +-- get and validate command line options + +println("-- CC Mekanism SCADA Install Manager (Off-Line) --") + +if #opts == 0 or opts[1] == "help" then + println("usage: ccmsim ") + println("") + lgray() + println(" check - check your installed versions") + println(" update-rm - delete everything except the config,") + println(" so that you can upload files for a") + println(" new two-file off-line update") + println(" uninstall - delete all app files and config") + return +else + mode = get_opt(opts[1], { "check", "update-rm", "uninstall" }) + if mode == nil then + red();println("Unrecognized mode.");white() + return + end +end + +-- run selected mode +if mode == "check" then + local local_ok, manifest = read_local_manifest() + if not local_ok then + yellow();println("failed to load local installation information");white() + end + + -- list all versions + for key, value in pairs(manifest.versions) do + term.setTextColor(colors.purple) + print(string.format("%-14s", "["..key.."]")) + blue();println(value);white() + end +elseif mode == "update-rm" or mode == "uninstall" then + local ok, manifest = read_local_manifest() + if not ok then + red();println("Error parsing local installation manifest.");white() + return + end + + app = manifest.depends[#manifest.depends] + + if mode == "uninstall" then + orange();println("Uninstalling all app files...") + else + orange();println("Deleting all app files except for configuration...") + end + + -- ask for confirmation + if not ask_y_n("Continue", false) then return end + + -- delete unused files first + clean(manifest) + + local file_list = manifest.files + local dependencies = manifest.depends + + -- delete all installed files + lgray() + for _, dependency in pairs(dependencies) do + local files = file_list[dependency] + for _, file in pairs(files) do + if fs.exists(file) then fs.delete(file);println("deleted "..file) end + end + + local folder = files[1] + while true do + local dir = fs.getDir(folder) + if dir == "" or dir == ".." then break else folder = dir end + end + + if fs.isDir(folder) then + fs.delete(folder) + println("deleted directory "..folder) + end + end + + -- delete log file + local log_deleted = false + local settings_file = app..".settings" + + if fs.exists(settings_file) and settings.load(settings_file) then + local log = settings.get("LogPath") + if log ~= nil then + log_deleted = true + if fs.exists(log) then + fs.delete(log) + println("deleted log file "..log) + end + end + end + + if not log_deleted then + red();println("Failed to delete log file (it may not exist).");lgray() + end + + if mode == "uninstall" then + if fs.exists(settings_file) then + fs.delete(settings_file);println("deleted "..settings_file) + end + + fs.delete("install_manifest.json") + println("deleted install_manifest.json") + + fs.delete("ccmsim.lua") + println("deleted ccmsim.lua") + end + + green();println("Done!") +end + +white() diff --git a/imgen.py b/build/imgen.py similarity index 100% rename from imgen.py rename to build/imgen.py diff --git a/build/package.sh b/build/package.sh new file mode 100755 index 0000000..9288a05 --- /dev/null +++ b/build/package.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Create zips to attach to GitHub releases. +# These can be extracted onto a computer and will include all files CCMSI would otherwise install. + +tag=$(git describe --tags) +apps=(coordinator pocket reactor-plc rtu supervisor) + +for app in "${apps[@]}" do + mkdir ${tag}_${app} + cp -R $app scada-common graphics lockbox configure.lua initenv.lua startup.lua LICENSE ${tag}_${app} + zip -r ${tag}_${app}.zip ${tag}_${app} + rm -R ${tag}_${app} +done diff --git a/build/safemin.py b/build/safemin.py new file mode 100644 index 0000000..2576159 --- /dev/null +++ b/build/safemin.py @@ -0,0 +1,80 @@ +import os +import re + +# minify files in a directory +def min_files(path): + start_sum, end_sum = 0, 0 + + for (root, _, files) in os.walk(path): + os.makedirs('_minified/' + root, exist_ok=True) + + for f in files: + start, end = minify(root + "/" + f) + + start_sum = start_sum + start + end_sum = end_sum + end + + delta = start_sum - end_sum + + print(f"> done with '{path}': shrunk from {start_sum} bytes to {end_sum} bytes (saved {delta} bytes, or {(100*delta/start_sum):.2f}%)") + + return list + +# minify a file +def minify(path: str): + size_start = os.stat(path).st_size + + f = open(path, "r") + contents = f.read() + f.close() + + if re.search(r'--+\[+', contents) != None: + # absolutely not dealing with lua multiline comments + # - there are more important things to do + # - this minification is intended to be 100% safe, so working with multiline comments is asking for trouble + # - the project doesn't use them as of writing this (except in test/), and it might as well stay that way + raise Exception(f"no multiline comments allowed! (offending file: {path})") + + if re.search(r'\\$', contents, flags=re.MULTILINE) != None: + # '\' allows for multiline strings, which would require reverting to processing syntax line by line to support them + raise Exception(f"no escaping newlines! (offending file: {path})") + + # drop the comments, unless the line has quotes, because quotes are scary + # (quotes are scary since we could actually be inside a string: "-- ..." shouldn't get deleted) + # -> whitespace before '--' and anything after that, which includes '---' comments + minified = re.sub(r'\s*--+(?!.*[\'"]).*', '', contents) + + # drop leading whitespace on each line + minified = re.sub(r'^ +', '', minified, flags=re.MULTILINE) + + # drop blank lines + while minified != re.sub(r'\n\n', '\n', minified): + minified = re.sub(r'\n\n', '\n', minified) + + # write the minified file + f_min = open(f"_minified/{path}", "w") + f_min.write(minified) + f_min.close() + + size_end = os.stat(f"_minified/{path}").st_size + + print(f">> shrunk '{path}' from {size_start} bytes to {size_end} bytes (saved {size_start-size_end} bytes)") + + return size_start, size_end + +# minify applications and libraries +dirs = [ 'scada-common', 'graphics', 'lockbox', 'reactor-plc', 'rtu', 'supervisor', 'coordinator', 'pocket' ] +for _, d in enumerate(dirs): + min_files(d) + +# minify root files +minify("startup.lua") +minify("initenv.lua") +minify("configure.lua") + +# copy in license for build usage +lic1 = open("LICENSE", "r") +lic2 = open("_minified/LICENSE", "w") +lic2.write(lic1.read()) +lic1.close() +lic2.close() diff --git a/ccmsi.lua b/ccmsi.lua index 86f2094..bc91112 100644 --- a/ccmsi.lua +++ b/ccmsi.lua @@ -18,7 +18,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. local function println(message) print(tostring(message)) end local function print(message) term.write(tostring(message)) end -local CCMSI_VERSION = "v1.14" +local CCMSI_VERSION = "v1.15" local install_dir = "/.install-cache" local manifest_path = "https://mikaylafischler.github.io/cc-mek-scada/manifests/" @@ -121,7 +121,7 @@ local function write_install_manifest(manifest, dependencies) end -- recursively build a tree out of the file manifest -local function gen_tree(manifest) +local function gen_tree(manifest, log) local function _tree_add(tree, split) if #split > 1 then local name = table.remove(split, 1) @@ -131,7 +131,7 @@ local function gen_tree(manifest) return nil end - local list, tree = {}, {} + local list, tree = { log }, {} -- make a list of each and every file for _, files in pairs(manifest.files) do for i = 1, #files do table.insert(list, files[i]) end end @@ -160,7 +160,7 @@ local function _clean_dir(dir, tree) if fs.isDir(path) then _clean_dir(path, tree[val]) if #fs.list(path) == 0 then fs.delete(path);println("deleted "..path) end - elseif (not _in_array(val, tree)) and (val ~= "config.lua" ) then + elseif (not _in_array(val, tree)) and (val ~= "config.lua" ) then ---@todo remove config.lua on full release fs.delete(path) println("deleted "..path) end @@ -169,11 +169,15 @@ end -- go through app/common directories to delete unused files local function clean(manifest) - local tree = gen_tree(manifest) + local log = nil + if fs.exists(app..".settings") and settings.load(app..".settings") then + log = settings.get("LogPath") + end + + local tree = gen_tree(manifest, log) table.insert(tree, "install_manifest.json") table.insert(tree, "ccmsi.lua") - table.insert(tree, "log.txt") ---@fixme fix after migration to settings files? local ls = fs.list("/") for _, val in pairs(ls) do @@ -245,7 +249,6 @@ else end -- run selected mode - if mode == "check" then local ok, manifest = get_remote_manifest() if not ok then return end @@ -535,36 +538,8 @@ elseif mode == "uninstall" then table.insert(dependencies, app) - -- delete log file - local log_deleted = false - local settings_file = app..".settings" - local legacy_config_file = app.."/config.lua" - - lgray() - if fs.exists(legacy_config_file) then - log_deleted = pcall(function () - local config = require(app..".config") - if fs.exists(config.LOG_PATH) then - fs.delete(config.LOG_PATH) - println("deleted log file "..config.LOG_PATH) - end - end) - elseif fs.exists(settings_file) and settings.load(settings_file) then - local log = settings.get("LogPath") - if log ~= nil and fs.exists(log) then - log_deleted = true - fs.delete(log) - println("deleted log file "..log) - end - end - - if not log_deleted then - red();println("Failed to delete log file.") - white();println("press any key to continue...") - any_key();lgray() - end - -- delete all installed files + lgray() for _, dependency in pairs(dependencies) do local files = file_list[dependency] for _, file in pairs(files) do @@ -583,8 +558,23 @@ elseif mode == "uninstall" then end end - if fs.exists(legacy_config_file) then - fs.delete(legacy_config_file);println("deleted "..legacy_config_file) + -- delete log file + local log_deleted = false + local settings_file = app..".settings" + + if fs.exists(settings_file) and settings.load(settings_file) then + local log = settings.get("LogPath") + if log ~= nil then + log_deleted = true + if fs.exists(log) then + fs.delete(log) + println("deleted log file "..log) + end + end + end + + if not log_deleted then + red();println("Failed to delete log file (it may not exist).");lgray() end if fs.exists(settings_file) then