From 107bc8620f4a5c1d9de43d3ae6cfd063c606b4e5 Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Tue, 16 Jun 2020 12:36:10 -0400 Subject: [PATCH 01/16] Show progress when updating program --- config.py | 4 +-- prog_manage.py | 72 +++++++++++++++++++++++++++-------------- tarstall_execs/tarstall | 2 +- version | 2 +- 4 files changed, 52 insertions(+), 28 deletions(-) diff --git a/config.py b/config.py index f845538..4e01911 100644 --- a/config.py +++ b/config.py @@ -22,8 +22,8 @@ ###VERSIONS### -version = "1.5.3" -prog_internal_version = 94 +version = "1.5.4" +prog_internal_version = 95 file_version = 15 ############# diff --git a/prog_manage.py b/prog_manage.py index 363d98a..d6f37b7 100644 --- a/prog_manage.py +++ b/prog_manage.py @@ -63,11 +63,13 @@ def remove_update_url(program): config.write_db() -def wget_program(program): +def wget_program(program, show_progress=False, progress_modifier=1): """Wget an Archive and Overwrite Program. Args: program (str): Program that has an update_url to update + show_progress (bool): Whether to display a progress bar. Defaults to False. + progress_modifier (int): The number to divide the total progress by. Defaults to 1. Returns: str: "No wget", "Wget error", "Install error" if install() fails, "Success" on success. @@ -83,6 +85,7 @@ def wget_program(program): pass os.mkdir("/tmp/tarstall-temp2") os.chdir("/tmp/tarstall-temp2") + generic.progress(10 / progress_modifier, show_progress) config.vprint("Downloading archive...") url = config.db["programs"][program]["update_url"] if config.verbose: @@ -91,23 +94,27 @@ def wget_program(program): err = call(["wget", url], stdout=DEVNULL, stderr=DEVNULL) if err != 0: return "Wget error" + generic.progress(65 / progress_modifier, show_progress) files = os.listdir() config.vprint("Renaming archive") os.rename("/tmp/tarstall-temp2/{}".format(files[0]), "/tmp/tarstall-temp2/{}".format(program + ".tar.gz")) os.chdir("/tmp/") + generic.progress(70 / progress_modifier, show_progress) config.vprint("Using install to install the program.") inst_status = pre_install("/tmp/tarstall-temp2/{}".format(program + ".tar.gz"), True, show_progress=False) + generic.progress(95 / progress_modifier, show_progress) try: rmtree(config.full("/tmp/tarstall-temp2")) except FileNotFoundError: pass + generic.progress(100 / progress_modifier, show_progress) if inst_status != "Installed": return "Install error" else: return "Success" -def update_program(program): +def update_program(program, show_progress=False): """Update Program. Args: @@ -122,33 +129,43 @@ def update_program(program): something from wget_program(). """ + progs = 0 + if config.db["programs"][program]["git_installed"] or config.db["programs"][program]["update_url"] is not None: + progs += 1 + if config.db["programs"][program]["post_upgrade_script"] is not None: + if not config.exists(config.db["programs"][program]["post_upgrade_script"]): + config.db["programs"][program]["post_upgrade_script"] = None + config.write_db() + return "No script" + else: + progs += 1 if config.db["programs"][program]["git_installed"]: - status = update_git_program(program) - if config.db["programs"][program]["post_upgrade_script"] is None: + status = update_git_program(program, show_progress, progs) + if status != "Success" and status != "No update": + return status + elif config.db["programs"][program]["post_upgrade_script"] is None: return status - elif status != "Success": + elif status == "No update": + generic.progress(100) return status - if config.db["programs"][program]["update_url"] is not None: - status = wget_program(program) - if config.db["programs"][program]["post_upgrade_script"] is None: + elif config.db["programs"][program]["update_url"] is not None: + status = wget_program(program, show_progress, progs) + if status != "Success": return status - elif status != "Success": + elif config.db["programs"][program]["post_upgrade_script"] is None: return status if config.db["programs"][program]["post_upgrade_script"] is not None: - if not config.db["programs"][program]["post_upgrade_script"]: - config.db["programs"][program]["post_upgrade_script"] = None - config.write_db() - return "No script" - else: - try: - err = call(config.db["programs"][program]["post_upgrade_script"], - cwd=config.full("~/.tarstall/bin/{}".format(program)), stdout=c_out) - if err != 0: - return "Script error" - else: - return "Success" - except OSError: - return "OSError" + try: + generic.progress(50 * (progs - 1), show_progress) + err = call(config.db["programs"][program]["post_upgrade_script"], + cwd=config.full("~/.tarstall/bin/{}".format(program)), stdout=c_out) + generic.progress(100, show_progress) + if err != 0: + return "Script error" + else: + return "Success" + except OSError: + return "OSError" return "Does not update" @@ -175,11 +192,13 @@ def update_script(program, script_path): return "Success" -def update_git_program(program): +def update_git_program(program, show_progress=False, progress_modifier=1): """Update Git Program. Args: program (str): Name of program to update + show_progress (bool): Whether to display a progress bar. Defaults to False. + progress_modifier (int): The number to divide the total progress by. Defaults to 1. Returns: str: "No git" if git isn't found, "Error updating" on a generic failure, "Success" on a successful update, and @@ -189,18 +208,23 @@ def update_git_program(program): if not config.check_bin("git"): config.vprint("git isn't installed!") return "No git" + generic.progress(5 / progress_modifier, show_progress) outp = run(["git", "pull"], cwd=config.full("~/.tarstall/bin/{}".format(program)), stdout=PIPE, stderr=PIPE) + generic.progress(95 / progress_modifier, show_progress) err = outp.returncode output = str(outp.stdout) + "\n\n\n" + str(outp.stderr) if err != 0: config.vprint("Failed updating: {}".format(program)) + generic.progress(100 / progress_modifier, show_progress) return "Error updating" else: if "Already up to date." in output: config.vprint("{} is already up to date!".format(program)) + generic.progress(100 / progress_modifier, show_progress) return "No update" else: config.vprint("Successfully updated: {}".format(program)) + generic.progress(100 / progress_modifier, show_progress) return "Success" diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index 65a9044..ab9cb53 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -414,7 +414,7 @@ def update_program_gui(program): program (str): Program to update. """ - status = prog_manage.update_program(program) + status = prog_manage.update_program(program, True) if status == "Success": generic.ppause("Program upgrading successful!") elif status == "No update": diff --git a/version b/version index 63c55e1..e2ffb50 100644 --- a/version +++ b/version @@ -1 +1 @@ -15.94 \ No newline at end of file +15.95 \ No newline at end of file From ddd0e39be33157c8be65102997d1a1d2dc5887a9 Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Tue, 16 Jun 2020 18:42:44 -0400 Subject: [PATCH 02/16] Isolate Unit Tests --- tests/__init__.py | 0 tests/{test_a_setup.py => conftest.py} | 12 ++++++------ tests/test_config.py | 13 ++++++++----- tests/test_prog_manage.py | 11 ++++++++--- 4 files changed, 22 insertions(+), 14 deletions(-) create mode 100644 tests/__init__.py rename tests/{test_a_setup.py => conftest.py} (55%) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_a_setup.py b/tests/conftest.py similarity index 55% rename from tests/test_a_setup.py rename to tests/conftest.py index b6cb94a..19b39f0 100644 --- a/tests/test_a_setup.py +++ b/tests/conftest.py @@ -1,16 +1,16 @@ import pytest + import prog_manage -import generic from io import StringIO -# This is more of an init that removes and re-installs tarstall so we have a blank slate to work with for testing. - - -def test_reset(monkeypatch): +@pytest.fixture(autouse=True) +def pre_test(monkeypatch): assert prog_manage.erase() == "Erased" or prog_manage.erase() == "Not installed" prog_manage.first_time_setup() + prog_manage.verbose_toggle() + monkeypatch.setattr('sys.stdin', StringIO("n\nn\nn\nn\nn\nn\nn\nn\nn\nn\nn\nn\nn\n")) - prog_manage.install("./tests/fake_packages/package.tar.gz") + prog_manage.install("./tests/fake_packages/package.tar.gz") \ No newline at end of file diff --git a/tests/test_config.py b/tests/test_config.py index 6701f05..81204db 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -26,18 +26,18 @@ def test_replace_in_file(): def test_read_config(): - assert config.read_config("Verbose") is False + assert config.read_config("Verbose") is True def test_change_config(): - config.change_config("Verbose", "flip") - assert config.read_config("Verbose") is True config.change_config("Verbose", "flip") assert config.read_config("Verbose") is False + config.change_config("Verbose", "flip") + assert config.read_config("Verbose") is True def test_vcheck(): - assert config.vcheck() is False + assert config.vcheck() is True def test_lock(): @@ -56,7 +56,7 @@ def test_unlock(): def test_get_db(): assert config.get_db() == { "options": { - "Verbose": False, + "Verbose": True, "AutoInstall": False, "ShellFile": config.get_shell_file(), "SkipQuestions": False, @@ -118,6 +118,9 @@ def test_create(): def test_remove_line(): # TODO: Test other modes + config.create("~/.tarstall/config") + config.add_line("Test Line", "~/.tarstall/config") + config.add_line("Verbose=True", "~/.tarstall/config") config.remove_line("Test Line", "~/.tarstall/config", "fuzzy") assert config.check_line("Verbose=False", "~/.tarstall/config", "fuzzy") is False diff --git a/tests/test_prog_manage.py b/tests/test_prog_manage.py index a31cf79..b55781f 100644 --- a/tests/test_prog_manage.py +++ b/tests/test_prog_manage.py @@ -39,10 +39,10 @@ def test_pathify(): def test_verbose_toggle(): - prog_manage.verbose_toggle() - assert config.read_config("Verbose") is True prog_manage.verbose_toggle() assert config.read_config("Verbose") is False + prog_manage.verbose_toggle() + assert config.read_config("Verbose") is True def test_list_programs(capsys): @@ -55,6 +55,11 @@ def test_create_desktop(monkeypatch): def test_remove_desktop(): + prog_manage.create_desktop("package", "Name", "test.sh", "Comment here", "False") + try: + assert config.exists("~/.local/share/applications/test.sh-package.desktop") + except AssertionError: + print("create_desktop() failure, remove_desktop() might be okay.") prog_manage.remove_desktop("package", "test.sh-package") assert not config.exists("~/.local/share/applications/test.sh-package.desktop") @@ -95,7 +100,7 @@ def test_create_db(): } -def test_erase(): +def atest_erase(): assert prog_manage.erase() == "Erased" assert os.path.isfile(config.full("~/.tarstall/tarstall.py")) is False try: From a8bdf8b4453246cc33a42cd076b2104134e2cf8e Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Wed, 17 Jun 2020 01:58:14 -0400 Subject: [PATCH 03/16] fish shell support!!!! --- config.py | 28 +++++++++++++++++++++++++++- install_tarstall | 2 +- prog_manage.py | 39 ++++++++++++++++++++++++++++++--------- tarstall_execs/tarstall | 2 ++ version | 2 +- 5 files changed, 61 insertions(+), 12 deletions(-) diff --git a/config.py b/config.py index 4e01911..34b0616 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.5.4" -prog_internal_version = 95 +prog_internal_version = 96 file_version = 15 ############# @@ -53,17 +53,43 @@ def get_shell_file(): """ shell = os.environ["SHELL"] + return ".config/fish/config.fish" if "bash" in shell: vprint("Auto-detected bash") return ".bashrc" elif "zsh" in shell: vprint("Auto-detected zsh") return ".zshrc" + elif "fish" in shell: + vprint("Auto-detected fish") + return ".config/fish/config.fish" else: vprint("Couldn't auto-detect shell environment!") return None +def get_shell_path(): + """Get Shell Path. + + Attempts to automatically obtain the file used by the user's shell for PATH, + variable exporting, etc. + + Returns: + str: Full path to the shell file. + + """ + shell_file = get_shell_file() + if shell_file: + if shell_file == ".bashrc": + return full("~/.bashrc") + elif shell_file == ".zshrc": + return full("~/.zshrc") + elif "fish" in shell_file: + return full("~/.config/fish/config.fish") + else: + return None + + def read_config(key): """Read config value. diff --git a/install_tarstall b/install_tarstall index 3af1279..6ac629f 100755 --- a/install_tarstall +++ b/install_tarstall @@ -89,7 +89,7 @@ def setup(): # Checks status("Checking for anything missing for tarstall setup") supported_package_managers = ["apt", "apt-get", "dnf", "pacman"] - supported_shells = ["bash", "zsh"] + supported_shells = ["bash", "zsh", "fish"] if which("sudo") is None: status("Please install 'sudo'!") sys.exit(1) diff --git a/prog_manage.py b/prog_manage.py index d6f37b7..3df635d 100644 --- a/prog_manage.py +++ b/prog_manage.py @@ -557,6 +557,7 @@ def remove_paths_and_binlinks(program): if not config.db["programs"][program]["has_path"] and config.db["programs"][program]["binlinks"] == []: return "None exist" config.remove_line(program, "~/.tarstall/.bashrc", 'poundword') + config.remove_line(program, "~/.tarstall/.fishrc", 'poundword') config.db["programs"][program]["has_path"] = False config.db["programs"][program]["binlinks"] = [] config.write_db() @@ -582,11 +583,16 @@ def rename(program, new_name): config.db["programs"][new_name] = config.db["programs"].pop(program) config.replace_in_file("export PATH=$PATH:~/.tarstall/bin/" + program, "export PATH=$PATH:~/.tarstall/bin/" + new_name, "~/.tarstall/.bashrc") + config.replace_in_file("set PATH $PATH ~/.tarstall/bin/" + program + ' # ' + program, + "set PATH $PATH ~/.tarstall/bin/" + new_name + ' # ' + new_name, "~/.tarstall/.fishrc") generic.progress(50) config.replace_in_file("'cd " + config.full('~/.tarstall/bin/' + program), "'cd " + config.full('~/.tarstall/bin/' + new_name), "~/.tarstall/.bashrc") + config.replace_in_file(";cd " + config.full("~/.tarstall/bin/" + program) + "/;./", + ";cd " + config.full("~/.tarstall/bin/" + new_name) + "/;./", "~/.tarstall/.fishrc") generic.progress(75) config.replace_in_file("# " + program, "# " + new_name, "~/.tarstall/.bashrc") + config.replace_in_file("# " + program, "# " + new_name, "~/.tarstall/.fishrc") move(config.full("~/.tarstall/bin/" + program), config.full("~/.tarstall/bin/" + new_name)) config.write_db() generic.progress(100) @@ -749,8 +755,10 @@ def add_binlink(file_chosen, program_internal_name): return "Already there" line_to_add = '\nalias ' + name + "='cd " + config.full('~/.tarstall/bin/' + program_internal_name) + \ '/ && ./' + file_chosen + "' # " + program_internal_name - config.vprint("Adding alias to bashrc") + config.vprint("Adding alias to bashrc and fishrc") config.add_line(line_to_add, "~/.tarstall/.bashrc") + line_to_add = "\nfunction " + name + ";cd " + config.full("~/.tarstall/bin/" + program_internal_name) + "/;./" + file_chosen + ";end # " + program_internal_name + config.add_line(line_to_add, "~/.tarstall/.fishrc") config.db["programs"][program_internal_name]["binlinks"].append(name) config.write_db() return "Added" @@ -759,7 +767,7 @@ def add_binlink(file_chosen, program_internal_name): def pathify(program_internal_name): """Add Program to Path. - Adds a program to PATH through ~/.tarstall/.bashrc + Adds a program to PATH through ~/.tarstall/.bashrc and ~/.tarstall/.fishrc Args: program_internal_name (str): Name of program to add to PATH @@ -773,7 +781,10 @@ def pathify(program_internal_name): config.vprint('Adding program to PATH') line_to_write = "\nexport PATH=$PATH:~/.tarstall/bin/" + program_internal_name + ' # ' + program_internal_name config.add_line(line_to_write, "~/.tarstall/.bashrc") + line_to_write = "\nset PATH $PATH ~/.tarstall/bin/" + program_internal_name + ' # ' + program_internal_name + config.add_line(line_to_write, "~/.tarstall/.fishrc") config.db["programs"][program_internal_name]["has_path"] = True + config.write_db() return "Complete" @@ -874,12 +885,16 @@ def erase(): """ if not (config.exists(config.full("~/.tarstall/tarstall_execs/tarstall"))): return "Not installed" - config.vprint('Removing source line from bashrc') - try: - config.remove_line("~/.tarstall/.bashrc", "~/{}".format(config.read_config("ShellFile")), "word") - to_return = "Erased" - except FileNotFoundError: + config.vprint('Removing source line from bashrc and fishrc') + if config.get_shell_file() is not None: + if "fish" in config.get_shell_file(): + path_to_remove = "fishrc" + else: + path_to_remove = "bashrc" + config.remove_line("~/.tarstall/.{}".format(path_to_remove), config.get_shell_path(), "word") + else: to_return = "No line" + to_return = "Erased" generic.progress(10) config.vprint("Removing .desktop files") for prog in config.db["programs"]: @@ -942,6 +957,11 @@ def first_time_setup(): config.create("~/.tarstall/database") create_db() config.create("~/.tarstall/.bashrc") # Create directories and files + if not config.exists("~/.config/fish"): + os.mkdir(config.full("~/.config/fish")) + if not config.exists("~/.config/fish/config.fish"): + config.create("~/.config/fish/config.fish") + config.create("~/.tarstall/.fishrc") generic.progress(15) progress = 15 files = os.listdir() @@ -956,12 +976,12 @@ def first_time_setup(): progress += prog_change generic.progress(progress) generic.progress(70) - shell_file = config.read_config("ShellFile") + shell_file = config.get_shell_path() if shell_file is None: to_return = "Unsupported shell" else: to_return = "Success" - config.add_line("source ~/.tarstall/.bashrc\n", "~/{}".format(shell_file)) + config.add_line("source ~/.tarstall/.fishrc\n", shell_file) generic.progress(75) copytree(config.full("./tarstall_execs/"), config.full("~/.tarstall/tarstall_execs/")) # Move tarstall.py to execs dir generic.progress(90) @@ -970,6 +990,7 @@ def first_time_setup(): generic.progress(92) config.add_line("export PATH=$PATH:{}".format( config.full("~/.tarstall/tarstall_execs")), "~/.tarstall/.bashrc") # Add bashrc line + config.add_line("set PATH $PATH {}".format(config.full("~/.tarstall/tarstall_execs")), "~/.tarstall/.fishrc") generic.progress(95) os.system('sh -c "chmod +x ~/.tarstall/tarstall_execs/tarstall"') config.unlock() diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index ab9cb53..b15d20e 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -537,6 +537,8 @@ E - Exit program management""".format(program=program, git=git_msg, g=g_msg, us= os.chdir(config.full("~/.tarstall/bin/" + program + "/")) if config.get_shell_file() == ".zshrc": call(["/bin/zsh"]) + elif "fish" in config.get_shell_file(): + call(["/usr/bin/fish"]) else: call(["/bin/bash"]) elif option == 'g' and g_msg == "g/": diff --git a/version b/version index e2ffb50..cde7e18 100644 --- a/version +++ b/version @@ -1 +1 @@ -15.95 \ No newline at end of file +15.96 \ No newline at end of file From 76577cc8cbe98b4337b40b5ab1bfe1547cef7d80 Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Wed, 17 Jun 2020 02:05:14 -0400 Subject: [PATCH 04/16] Installer msg change + disable force-fish --- config.py | 3 +-- install_tarstall | 4 +++- version | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/config.py b/config.py index 34b0616..8597304 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.5.4" -prog_internal_version = 96 +prog_internal_version = 97 file_version = 15 ############# @@ -53,7 +53,6 @@ def get_shell_file(): """ shell = os.environ["SHELL"] - return ".config/fish/config.fish" if "bash" in shell: vprint("Auto-detected bash") return ".bashrc" diff --git a/install_tarstall b/install_tarstall index 6ac629f..f4c5f4b 100755 --- a/install_tarstall +++ b/install_tarstall @@ -107,6 +107,7 @@ def setup(): if sshell in shell: shell_supported = True break + msg = "All of the checks passed!" if not shell_supported: msg = "WARNING: YOUR SHELL IS NOT SUPPORTED!\n\nYOU WILL HAVE TO MANUALLY ADD ~/.tarstall/tarstall_execs TO YOUR PATH, " \ "AND ANY PATH/BINLINK FUNCTIONS WILL NOT WORK!\nWOULD YOU LIKE TO PROCEED WITH INSTALLATION ANYWAYS?" @@ -115,7 +116,8 @@ def setup(): if should_proceed.lower() != "yes": status("Installation cancelled!") sys.exit(1) - print("All of the checks passed!\n\n\n") + msg = "All of the checks besides the shell check passed, while the shell check was skipped by the user!" + print("{}\n\n\n".format(msg)) # User information status("Welcome!\nThis installer is going to install tarstall! You'll need to enter your sudo password at some points. " + diff --git a/version b/version index cde7e18..8439ed6 100644 --- a/version +++ b/version @@ -1 +1 @@ -15.96 \ No newline at end of file +15.97 \ No newline at end of file From 4055766d81aed07958755d3ec15357c147ffdd82 Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Wed, 17 Jun 2020 02:07:25 -0400 Subject: [PATCH 05/16] Remove print previously used for debugging --- config.py | 2 +- tarstall_execs/tarstall | 1 - version | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index 8597304..a0d9860 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.5.4" -prog_internal_version = 97 +prog_internal_version = 98 file_version = 15 ############# diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index b15d20e..143bcd3 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -843,7 +843,6 @@ def parse_args(args=None): elif not did_fts: - print(args.update_programs) generic.pprint(""" tarstall. A Python based package manager to manage archives. Written by: hammy3502 diff --git a/version b/version index 8439ed6..2670165 100644 --- a/version +++ b/version @@ -1 +1 @@ -15.97 \ No newline at end of file +15.98 \ No newline at end of file From de0f116d5b6714ce0f7f075e1c66d3184c770daf Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Wed, 1 Jul 2020 20:44:41 +0000 Subject: [PATCH 06/16] easy_get_action() --- config.py | 2 +- generic.py | 45 ++++++++++++++++- tarstall_execs/tarstall | 104 +++++++++++++++++----------------------- version | 2 +- 4 files changed, 90 insertions(+), 63 deletions(-) diff --git a/config.py b/config.py index a0d9860..2f63e56 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.5.4" -prog_internal_version = 98 +prog_internal_version = 99 file_version = 15 ############# diff --git a/generic.py b/generic.py index ee032c9..03c0649 100644 --- a/generic.py +++ b/generic.py @@ -136,7 +136,48 @@ def ask_file(question): return values["answer"] -def get_input(question, options, default, gui_labels=[]): +def easy_get_action(options, replacements=[]): + """Easy get_input() + + options should contain a list of dictionaries, each representing an option. + Dictionary should be layed out as such: + { + "shorthand": "b", # One or two letters representing the option (the selector in CLI mode) + "gui-label": "Create binlinks", # The label for the option to show in the GUI + "description": "Create binlinks for {program}", # The description of the action being performed + "is-default": False # Optional. If specified and set to True, will be the default option on an enter press.* + } + + *: Note: If multiple parameters are passed in as default, only the first one will actually be the default. + Note: If default is not specified for any options, the last option will be the default. + + Args: + options (dict[]): See dictionary format above. + replacements (dict[]): The key should be what you want to replace and the value be what you're replacing with. + + """ + gui_labels = [] + options_list = [] + default = None + msg = "Select an option:" + for option in options: + selector = option["shorthand"].lower() + try: + is_default = option["is-default"] + except KeyError: + is_default = False + if is_default or (default is None and option is options[len(options) - 1]): + default = option["shorthand"] + selector = selector.upper() + gui_labels.append(option["gui-label"]) + options_list.append(option["shorthand"]) + msg += "\n" + selector + " - " + option["description"] + for replacement in replacements: + msg = msg.replace(list(replacement.keys())[0], list(replacement.values())[0]) + return get_input(msg, options_list, default, gui_labels, True) + + +def get_input(question, options, default, gui_labels=[], from_easy=False): """Get User Input. Get user input, except make sure the input provided matches one of the options we're looking for @@ -154,7 +195,7 @@ def get_input(question, options, default, gui_labels=[]): if config.mode == "cli": options_form = list(options) # Otherwise, Python would "link" options_form with options options_form[options_form.index(default)] = options_form[options_form.index(default)].upper() - if len(options) > 3: + if len(options) > 3 or from_easy: question += "\n[" + "/".join(options_form) + "]" else: question += " [" + "/".join(options_form) + "]" diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index 143bcd3..054fc16 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -255,23 +255,26 @@ E - Exit branch wizard and don't change branches.""" def configure(): """Change tarstall Options.""" while True: - msg = """ -Select an option: -au - Enable/disable the ability to install updates when tarstall is run. Currently {au}. -v - Enable/disable verbose mode, showing more output when tarstall commands are run. Currently {v}. -b - Swap branches in tarstall. Allows you to get updates sooner at the cost of possible bugs. Current branch: {b}. -m - Whether or not to use the GUI for tarstall. Currently {gui}. -s - Whether or not to skip ending questions and confirmations. Currently {skip}. -u - Whether or not to update programs that have a URL attatched to them for updating. Currently {url}. -c - Whether or not to have some messages have you "Press ENTER to continue...". Currently {skipenter}. -e - Exit tarstall""".format( - au=generic.endi(config.read_config("AutoInstall")), v=generic.endi(config.read_config("Verbose")), - b=config.db["version"]["branch"], gui=generic.endi(config.read_config("Mode") == "gui"), - skip=generic.endi(config.read_config("SkipQuestions")), url=generic.endi(config.read_config("UpdateURLPrograms")), - skipenter=generic.endi(config.read_config("PressEnterKey")) - ) - option = generic.get_input(msg, ['au', 'v', 'b', 'm', 's', 'u', 'c', 'e'], 'e', - ["Autoupdate", "Verbosity", "Change Branches", "Change Interaction Mode", "Skip Questions", "Update URL-Attatched Programs", "Exit"]) + options = [ + {"shorthand": 'au', "gui-label": "Autoupdate", "description": "Enable/disable the ability to install updates when tarstall is run. Currently {au}."}, + {"shorthand": 'v', "gui-label": "Verbosity", "description": "Enable/disable verbose mode, showing more output when tarstall commands are run. Currently {v}."}, + {"shorthand": 'b', "gui-label": "Change Branches", "description": "Swap branches in tarstall. Allows you to get updates sooner at the cost of possible bugs. Current branch: {b}."}, + {"shorthand": 'm', "gui-label": "Change Interaction Mode", "description": "Whether or not to use the GUI for tarstall. Currently {gui}."}, + {"shorthand": 's', "gui-label": "Skip Questions", "description": "Whether or not to skip ending questions and confirmations. Currently {skip}."}, + {"shorthand": 'u', "gui-label": "Update URL-Attatched Programs", "description": "Whether or not to update programs that have a URL attatched to them for updating. Currently {url}."}, + {"shorthand": 'c', "gui-label": "Press ENTER messages (CLI only)", "description": "Whether or not to have some messages have you \"Press ENTER to continue...\". Currently {skipenter}."}, + {"shorthand": 'e', "gui-label": "Exit", "description": "Exit tarstall", "is-default": True}, + ] + replacements = [ + {"{au}": generic.endi(config.read_config("AutoInstall"))}, + {"{v}": generic.endi(config.read_config("Verbose"))}, + {"{b}": config.db["version"]["branch"]}, + {"{gui}": generic.endi(config.read_config("Mode") == "gui")}, + {"{skip}": generic.endi(config.read_config("SkipQuestions"))}, + {"{url}": generic.endi(config.read_config("UpdateURLPrograms"))}, + {"{skipenter}": generic.endi(config.read_config("PressEnterKey"))} + ] + option = generic.easy_get_action(options, replacements) if option == 'au': if not prog_manage.can_update: generic.pprint("requests isn't installed, so AutoInstall cannot be enabled!") @@ -449,51 +452,34 @@ def manage(program): if not program in config.db["programs"]: generic.pprint("{} not installed!".format(program)) return - options = ['b', 'p', 'n', 'u', 'r', 'd', 'rd', 's', 'us', 'e'] - option_strings = ["Create binlinks", "Add to PATH", "Rename", "Uninstall", "Remove all binlinks and PATHs", - "Create a .desktop file", "Remove a .desktop file", "Launch a shell inside the program's directory", - "Add upgrade script", "Exit"] - q_msg = "" - q = "" - if config.db["programs"][program]["git_installed"]: - git_msg = "\ng - Manage git-related settings for {program}".format(program=program) - g_msg = "g/" - options.append('g') - option_strings.append("Manage git settings") + options = [ + {"shorthand": 'b', "gui-label": "Create binlinks", "description": "Create binlinks for {program}"}, + {"shorthand": 'p', "gui-label": "Add to PATH", "description": "Add {program} to PATH"}, + {"shorthand": 'n', "gui-label": "Rename", "description": "Rename {program}"}, + {"shorthand": 'u', "gui-label": "Uninstall", "description": "Uninstall {program}"}, + {"shorthand": 'r', "gui-label": "Remove all binlinks and PATHs", "description": "Remove all binlinks + PATHs for {program}"}, + {"shorthand": 'd', "gui-label": "Create .desktop file", "description": "Create a .desktop file for {program}"}, + {"shorthand": 'rd', "gui-label": "Remove .desktop file", "description": "Remove a .desktop file for {program}"}, + {"shorthand": 's', "gui-label": "Launch shell", "description": "Launch a shell inside {program}'s directory"}, + {"shorthand": 'us', "gui-label": "Add upgrade script", "description": "Add {us} script for {program}"} + ] + can_update = config.db["programs"][program]["git_installed"] or config.db["programs"][program]["post_upgrade_script"] or config.db["programs"][program]["update_url"] + is_git = config.db["programs"][program]["git_installed"] + is_wget = config.db["programs"][program]["update_url"] + if is_git: + options.append({"shorthand": 'g', "gui-label": "Manage git settings", "description": "Manage git-related settings for {program}"}) us = "a post-upgrade" - q_msg = "\nq - Upgrade {program}".format(program=program) - q = "q/" - options.append('q') - option_strings.append("Upgrade program") else: - git_msg = "\nw - Add/remove an update URL for {}".format(program) - g_msg = "w/" - options.append('w') - option_strings.append("Add/remove update URL") - if config.db["programs"][program]["update_url"]: + options.append({"shorthand": 'w', "gui-label": "Add/remove update URL", "description": "Add/remove an update URL for {program}"}) + if is_wget: us = "a post-upgrade" else: us = "an upgrade" - if q == "" and (config.db["programs"][program]["post_upgrade_script"] or config.db["programs"][program]["update_url"]): - q_msg = "\nq - Upgrade {program}".format(program=program) - q = "q/" - options.append('q') - option_strings.append("Upgrade program") + if can_update: + options.append({"shorthand": 'q', "gui-label": "Upgrade program", "description": "Upgrade {program}"}) + options.append({"shorthand": 'e', "gui-label": "Exit", "description": "Exit program management", "is-default": True}) while True: - msg = """ -Enter an option to manage program: -b - Create binlinks for {program} -p - Add {program} to PATH -n - Rename {program} -u - Uninstall {program} -r - Remove all binlinks + PATHs for {program} -d - Create a .desktop file for {program} -rd - Remove a .desktop file for {program} -s - Launch a shell inside {program}'s directory{git} -us - Add {us} script to program{q_msg} -E - Exit program management""".format(program=program, git=git_msg, g=g_msg, us=us, - q_msg=q_msg) - option = generic.get_input(msg, options, 'e', option_strings) + option = generic.easy_get_action(options, [{"{program}": program}, {"{us}": us}]) if option == 'b': binlink(program) elif option == 'p': @@ -541,11 +527,11 @@ E - Exit program management""".format(program=program, git=git_msg, g=g_msg, us= call(["/usr/bin/fish"]) else: call(["/bin/bash"]) - elif option == 'g' and g_msg == "g/": + elif option == 'g' and is_git: git_wizard(program) - elif option == 'w' and g_msg == "w/": + elif option == 'w' and is_wget: wget_wizard(program) - elif option == 'q' and q != "": + elif option == 'q' and can_update: update_program_gui(program) elif option == 'us': msg = """ diff --git a/version b/version index 2670165..5100ebe 100644 --- a/version +++ b/version @@ -1 +1 @@ -15.98 \ No newline at end of file +15.99 \ No newline at end of file From 9c1129efbd029bb7ce291b8b1de9c45de1c98f3a Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Fri, 3 Jul 2020 04:25:25 +0000 Subject: [PATCH 07/16] SingleFile installs + other stuff in comments --- config.py | 14 ++-- prog_manage.py | 89 ++++++++++++++++++++++-- tarstall_execs/tarstall | 142 ++++++++++++++++++++++++++++---------- tests/conftest.py | 2 +- tests/test_config.py | 2 +- tests/test_prog_manage.py | 2 +- version | 2 +- 7 files changed, 199 insertions(+), 54 deletions(-) diff --git a/config.py b/config.py index 2f63e56..5cab150 100644 --- a/config.py +++ b/config.py @@ -22,9 +22,9 @@ ###VERSIONS### -version = "1.5.4" -prog_internal_version = 99 -file_version = 15 +version = "1.6.0" +prog_internal_version = 100 +file_version = 16 ############# @@ -275,9 +275,11 @@ def extension(program): return program[-3:].lower() elif program[-4:].lower() in ['.zip', '.rar', '.git']: return program[-4:] - else: - # Default to returning the last 7 characters + elif program[-7:].lower() in ['.tar.gz', '.tar.xz']: return program[-7:] + else: + # Default to returning everything after the last . + return program[program.rfind("."):] def exists(file_name): @@ -476,7 +478,7 @@ def char_check(name): } "programs": { "package": { - "git_installed": False, + "install_type": "default", "post_upgrade_script": None, "desktops": [ "desktop_file_name" diff --git a/prog_manage.py b/prog_manage.py index 3df635d..7961998 100644 --- a/prog_manage.py +++ b/prog_manage.py @@ -130,7 +130,7 @@ def update_program(program, show_progress=False): """ progs = 0 - if config.db["programs"][program]["git_installed"] or config.db["programs"][program]["update_url"] is not None: + if config.db["programs"][program]["install_type"] == "git" or config.db["programs"][program]["update_url"] is not None: progs += 1 if config.db["programs"][program]["post_upgrade_script"] is not None: if not config.exists(config.db["programs"][program]["post_upgrade_script"]): @@ -139,7 +139,7 @@ def update_program(program, show_progress=False): return "No script" else: progs += 1 - if config.db["programs"][program]["git_installed"]: + if config.db["programs"][program]["install_type"] == "git": status = update_git_program(program, show_progress, progs) if status != "Success" and status != "No update": return status @@ -245,7 +245,7 @@ def update_programs(): statuses = {} generic.progress(progress) for p in config.db["programs"].keys(): - if not config.db["programs"][p]["update_url"] and (config.db["programs"][p]["git_installed"] or config.db["programs"][p]["post_upgrade_script"]): + if not config.db["programs"][p]["update_url"] and (config.db["programs"][p]["install_type"] == "git" or config.db["programs"][p]["post_upgrade_script"]): statuses[p] = update_program(p) elif (config.db["programs"][p]["update_url"] and config.read_config("UpdateURLPrograms")): statuses[p] = update_program(p) @@ -405,6 +405,15 @@ def tarstall_startup(start_fts=False, del_lock=False, old_upgrade=False): elif file_version == 14: config.vprint("Adding 'PressEnterKey' to config database.") config.db["options"]["PressEnterKey"] = True + + elif file_version == 15: + config.vprint("Swapping to new saving of program type") + for program in config.db["programs"]: + if config.db["programs"][program]["git_installed"]: + config.db["programs"][program]["install_type"] = "git" + else: + config.db["programs"][program]["install_type"] = "default" + del config.db["programs"][program]["git_installed"] config.db["version"]["file_version"] += 1 file_version = get_file_version('file') @@ -454,6 +463,20 @@ def pre_install(program, overwrite=None, show_progress=True): config.write_db() +def pre_singleinstall(program, reinstall=None): + if not config.exists(program): + return "Bad file" + program_internal_name = config.name(program) + if program_internal_name in config.db["programs"]: # Reinstall check + if reinstall is None: + return "Application exists" + else: + return single_install(program, program_internal_name, True) + else: + return single_install(program, program_internal_name) # No reinstall needed to be asked, install program + config.write_db() + + def pre_gitinstall(program, overwrite=None): """Before Git Installs. @@ -574,38 +597,59 @@ def rename(program, new_name): str/None: New program name or None if program already exists """ + is_single = config.db["programs"][program]["install_type"] == "single" + config.vprint("Checking that program name isn't already in use") if new_name in config.db["programs"]: return None + config.vprint("Updating .desktop files") for d in config.db["programs"][program]["desktops"]: config.replace_in_file("/.tarstall/bin/{}".format(program), "/.tarstall/bin/{}".format(new_name), "~/.local/share/applications/{}.desktop".format(d)) + if is_single: + config.replace_in_file("/.tarstall/bin/{}/{}".format(new_name, program), "/.tarstall/bin/{}/{}".format(new_name, new_name), + "~/.local/share/applications/{}.desktop".format(d)) + move(config.full("~/.local/share/applications/{p}-{p}.desktop".format(p=program)), + config.full("~/.local/share/applications/{p}-{p}.desktop".format(p=new_name))) generic.progress(25) + config.vprint("Replacing PATHs") config.db["programs"][new_name] = config.db["programs"].pop(program) config.replace_in_file("export PATH=$PATH:~/.tarstall/bin/" + program, "export PATH=$PATH:~/.tarstall/bin/" + new_name, "~/.tarstall/.bashrc") config.replace_in_file("set PATH $PATH ~/.tarstall/bin/" + program + ' # ' + program, "set PATH $PATH ~/.tarstall/bin/" + new_name + ' # ' + new_name, "~/.tarstall/.fishrc") generic.progress(50) + config.vprint("Replacing binlinks") config.replace_in_file("'cd " + config.full('~/.tarstall/bin/' + program), "'cd " + config.full('~/.tarstall/bin/' + new_name), "~/.tarstall/.bashrc") config.replace_in_file(";cd " + config.full("~/.tarstall/bin/" + program) + "/;./", ";cd " + config.full("~/.tarstall/bin/" + new_name) + "/;./", "~/.tarstall/.fishrc") + if is_single: + config.replace_in_file("./" + program, "./" + new_name, "~/.tarstall/.bashrc") + config.replace_in_file("alias " + program, "alias " + new_name, "~/.tarstall/.bashrc") + config.replace_in_file("./" + program, "./" + new_name, "~/.tarstall/.fishrc") + config.replace_in_file("function " + program, "function " + new_name, "~/.tarstall/.fishrc") generic.progress(75) + config.vprint("Replacing program recognisers") config.replace_in_file("# " + program, "# " + new_name, "~/.tarstall/.bashrc") config.replace_in_file("# " + program, "# " + new_name, "~/.tarstall/.fishrc") move(config.full("~/.tarstall/bin/" + program), config.full("~/.tarstall/bin/" + new_name)) config.write_db() + generic.progress(90) + if is_single: + config.vprint("Renaming single-file") + move(config.full("~/.tarstall/bin/{}/{}".format(new_name, program)), config.full("~/.tarstall/bin/{}/{}".format(new_name, new_name))) generic.progress(100) return new_name -def finish_install(program_internal_name, is_git=False): +def finish_install(program_internal_name, install_type="default"): """End of Install. Ran after every program install. Args: program_internal_name (str): Name of program as stored in the database + install_type (str): Type of install. Should be 'default', 'git', or 'single'. Defaults to "default" Returns: str: "Installed". @@ -619,7 +663,7 @@ def finish_install(program_internal_name, is_git=False): pass config.vprint("Adding program to tarstall list of programs") generic.progress(95) - config.db["programs"].update({program_internal_name: {"git_installed": is_git, "desktops": [], + config.db["programs"].update({program_internal_name: {"install_type": install_type, "desktops": [], "post_upgrade_script": None, "update_url": None, "has_path": False, "binlinks": []}}) config.write_db() generic.progress(100) @@ -730,7 +774,7 @@ def gitinstall(git_url, program_internal_name, overwrite=False, reinstall=False) if overwrite: call(["rsync", "-a", "/tmp/tarstall-temp/{}/".format(program_internal_name), config.full("~/.tarstall/bin/{}".format(program_internal_name))], stdout=c_out) if not overwrite: - return finish_install(program_internal_name, True) + return finish_install(program_internal_name, "git") else: generic.progress(100) return "Installed" @@ -1144,6 +1188,39 @@ def install(program, overwrite=False, reinstall=False, show_progress=True): return "Installed" +def single_install(program, program_internal_name, reinstall=False): + """Install Single File. + + Will create a folder to put the single file in. + + Args: + program (str): Path to file to install + program_internal_name (str): Name to use internally for program + reinstall (bool, optional): Whether to reinstall or not. Defaults to False. + + """ + if not reinstall: + config.vprint("Creating folder to house file in") + os.mkdir(config.full("~/.tarstall/bin/{}".format(program_internal_name))) + generic.progress(5) + config.vprint("Marking executable as... executable") + os.system('sh -c "chmod +x {}"'.format(program)) + generic.progress(10) + if reinstall: + config.vprint("Deleting old single-executable...") + os.remove(config.full("~/.tarstall/bin/{p}/{p}".format(p=program_internal_name))) + generic.progress(15) + config.vprint("Moving to tarstall directory and renaming...") + move(config.full(program), config.full("~/.tarstall/bin/{p}/{p}".format(p=program_internal_name))) + generic.progress(90) + if not reinstall: + return finish_install(program_internal_name, "single") + else: + generic.progress(100) + return "Installed" + + + def dirinstall(program_path, program_internal_name, overwrite=False, reinstall=False): """Install Directory. diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index 054fc16..6eeab54 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -341,14 +341,17 @@ def binlink(program): yn = generic.get_input('Would you like to add another binlink?', ['y', 'n'], 'n') -def desktop_wizard(program): +def desktop_wizard(program, is_single=False): """Desktop Creation Wizard. Args: program (str): Program to create .desktop file of """ - program_file = generic.file_browser(config.full('~/.tarstall/bin/' + program + '/')) + if is_single: + program_file = program + else: + program_file = generic.file_browser(config.full('~/.tarstall/bin/' + program + '/')) if program_file is None: return comment = "/" @@ -388,7 +391,7 @@ def desktop_wizard(program): generic.ppause(".desktop file already exists!") -def install_wrap_up(program): +def install_wrap_up(program, is_single=False): """End of Install. Runs at the end of an install to ask users about different "shortcut" creations/PATH creation @@ -398,15 +401,24 @@ def install_wrap_up(program): """ if not config.read_config("SkipQuestions"): - yn = generic.get_input('Would you like to add the program to your PATH?', ['y', 'n'], 'y', ["Yes", "No"]) - if yn == 'y': - pathify(program) - yn = generic.get_input('Would you like to create a binlink?', ['y', 'n'], 'n', ["Yes", "No"]) - if yn == 'y': - binlink(program) - yn = generic.get_input('Would you like to create a desktop file?', ['y', 'n'], 'n', ["Yes", "No"]) - if yn == 'y': - desktop_wizard(program) + if is_single: + yn = generic.get_input('Would you like to create the binlink for this program?', ['y', 'n'], 'n', ["Yes", "No"]) + if yn == 'y': + prog_manage.add_binlink(program, program) + generic.ppause("Binlink created!") + yn = generic.get_input('Would you like to create the desktop file for this program?', ['y', 'n'], 'n', ["Yes", "No"]) + if yn == 'y': + desktop_wizard(program, True) + else: + yn = generic.get_input('Would you like to add the program to your PATH?', ['y', 'n'], 'y', ["Yes", "No"]) + if yn == 'y': + pathify(program) + yn = generic.get_input('Would you like to create a binlink?', ['y', 'n'], 'n', ["Yes", "No"]) + if yn == 'y': + binlink(program) + yn = generic.get_input('Would you like to create a desktop file?', ['y', 'n'], 'n', ["Yes", "No"]) + if yn == 'y': + desktop_wizard(program) generic.pprint("Installation complete!") @@ -452,19 +464,32 @@ def manage(program): if not program in config.db["programs"]: generic.pprint("{} not installed!".format(program)) return - options = [ - {"shorthand": 'b', "gui-label": "Create binlinks", "description": "Create binlinks for {program}"}, - {"shorthand": 'p', "gui-label": "Add to PATH", "description": "Add {program} to PATH"}, - {"shorthand": 'n', "gui-label": "Rename", "description": "Rename {program}"}, - {"shorthand": 'u', "gui-label": "Uninstall", "description": "Uninstall {program}"}, - {"shorthand": 'r', "gui-label": "Remove all binlinks and PATHs", "description": "Remove all binlinks + PATHs for {program}"}, - {"shorthand": 'd', "gui-label": "Create .desktop file", "description": "Create a .desktop file for {program}"}, - {"shorthand": 'rd', "gui-label": "Remove .desktop file", "description": "Remove a .desktop file for {program}"}, - {"shorthand": 's', "gui-label": "Launch shell", "description": "Launch a shell inside {program}'s directory"}, - {"shorthand": 'us', "gui-label": "Add upgrade script", "description": "Add {us} script for {program}"} - ] - can_update = config.db["programs"][program]["git_installed"] or config.db["programs"][program]["post_upgrade_script"] or config.db["programs"][program]["update_url"] - is_git = config.db["programs"][program]["git_installed"] + if config.db["programs"][program]["install_type"] == "single": + options = [ + {"shorthand": 'b', "gui-label": "Create binlink", "description": "Create binlink for {program}"}, + {"shorthand": 'n', "gui-label": "Rename", "description": "Rename {program}"}, + {"shorthand": 'u', "gui-label": "Uninstall", "description": "Uninstall {program}"}, + {"shorthand": 'r', "gui-label": "Remove binlink", "description": "Remove binlink for {program}"}, + {"shorthand": 'd', "gui-label": "Create .desktop file", "description": "Create the .desktop file for {program}"}, + {"shorthand": 'rd', "gui-label": "Remove .desktop file", "description": "Remove the .desktop file for {program}"}, + {"shorthand": 's', "gui-label": "Launch shell", "description": "Launch a shell inside {program}'s directory"}, + {"shorthand": 'us', "gui-label": "Add upgrade script", "description": "Add {us} script for {program}"} + ] + else: + options = [ + {"shorthand": 'b', "gui-label": "Create binlinks", "description": "Create binlinks for {program}"}, + {"shorthand": 'p', "gui-label": "Add to PATH", "description": "Add {program} to PATH"}, + {"shorthand": 'n', "gui-label": "Rename", "description": "Rename {program}"}, + {"shorthand": 'u', "gui-label": "Uninstall", "description": "Uninstall {program}"}, + {"shorthand": 'r', "gui-label": "Remove all binlinks and PATHs", "description": "Remove all binlinks + PATHs for {program}"}, + {"shorthand": 'd', "gui-label": "Create .desktop file", "description": "Create a .desktop file for {program}"}, + {"shorthand": 'rd', "gui-label": "Remove .desktop file", "description": "Remove a .desktop file for {program}"}, + {"shorthand": 's', "gui-label": "Launch shell", "description": "Launch a shell inside {program}'s directory"}, + {"shorthand": 'us', "gui-label": "Add upgrade script", "description": "Add {us} script for {program}"} + ] + can_update = config.db["programs"][program]["install_type"] == "git" or config.db["programs"][program]["post_upgrade_script"] or config.db["programs"][program]["update_url"] + is_git = config.db["programs"][program]["install_type"] == "git" + is_single = config.db["programs"][program]["install_type"] == "single" is_wget = config.db["programs"][program]["update_url"] if is_git: options.append({"shorthand": 'g', "gui-label": "Manage git settings", "description": "Manage git-related settings for {program}"}) @@ -481,8 +506,12 @@ def manage(program): while True: option = generic.easy_get_action(options, [{"{program}": program}, {"{us}": us}]) if option == 'b': - binlink(program) - elif option == 'p': + if is_single: + prog_manage.add_binlink(program, program) + generic.ppause("Binlink created!") + else: + binlink(program) + elif option == 'p' and not is_single: pathify(program) elif option == 'n': new_name = "!" @@ -501,20 +530,29 @@ def manage(program): elif option == 'r': status = prog_manage.remove_paths_and_binlinks(program) if status == "Complete": - generic.ppause("Removal of PATHs and binlinks complete!") + if is_single: + generic.ppause("Binlink removal complete!") + else: + generic.ppause("Removal of PATHs and binlinks complete!") elif status == "None exist": - generic.ppause("The program isn't added to PATH and has no binlinks, so none removed!") + if is_single: + generic.ppause("The program doesn't have their binlink assigned!") + else: + generic.ppause("The program isn't added to PATH and has no binlinks, so none removed!") elif option == 'd': - desktop_wizard(program) + desktop_wizard(program, is_single) elif option == 'rd': - msg = "Desktops:\n" - for d in config.db["programs"][program]["desktops"]: - msg += d + "\n" - inp = "/ choose desktop" - while not (inp in config.db["programs"][program]["desktops"]) and inp != "exit": - inp = generic.ask(msg + "\nPlease enter the desktop you would like to remove or type \"exit\" to exit: ") - if inp != "exit": - prog_manage.remove_desktop(program, inp) + if is_single: + prog_manage.remove_desktop(program, "{p}-{p}".format(p=program)) + else: + msg = "Desktops:\n" + for d in config.db["programs"][program]["desktops"]: + msg += d + "\n" + inp = "/ choose desktop" + while not (inp in config.db["programs"][program]["desktops"]) and inp != "exit": + inp = generic.ask(msg + "\nPlease enter the desktop you would like to remove or type \"exit\" to exit: ") + if inp != "exit": + prog_manage.remove_desktop(program, inp) elif option == 's': if mode == "gui": generic.pprint("This feature can only be used from the command line version of tarstall!") @@ -593,6 +631,7 @@ def parse_args(args=None): group.add_argument('-i', "--install", help="Install a .tar.gz, .tar.xz, or .zip") group.add_argument('-d', "--dirinstall", help="Install a directory") group.add_argument('-g', '--gitinstall', help="Install by retrieving a git repository") + group.add_argument('-s', '--singleinstall', help="Install a program stored as a single executable file") group.add_argument('-r', "--remove", help="Remove an insatlled program") group.add_argument('-l', "--list", help="List installed programs", action="store_true") group.add_argument('-f', "--first", help="Run first time setup", action="store_true") @@ -702,6 +741,33 @@ def parse_args(args=None): elif status == "Error": generic.pprint("An error occured while attempting to git clone!") exit_code = 1 + + + elif args.singleinstall is not None: + status = prog_manage.pre_singleinstall(args.singleinstall) + if status == "Bad file": + generic.pprint("The specified file does not exist!") + exit_code = 1 + elif status == "Application exists": + reinstall = generic.get_input("Application already exists! Would you like to reinstall?", + ["r", "n"], "n", ["Reinstall", "Cancel"]) # Ask to reinstall + if reinstall == "r": + status = prog_manage.pre_singleinstall(args.singleinstall, True) + else: + generic.pprint("Reinstall cancelled.") + elif status == "Installed": + install_wrap_up(config.name(args.singleinstall), True) + elif status.startswith("No"): + generic.pprint("{} needs to be installed! Installation halted.".format(status[3:])) + elif status == "No rsync": + generic.pprint("rsync not installed! Please install it!") + exit_code = 1 + elif status == "Bad name": + generic.pprint("Archive name cannot contain a space or #!") + exit_code = 1 + elif status == "Error": + generic.pprint("Error occured while extracting archive!") + exit_code = 1 elif args.dirinstall is not None: diff --git a/tests/conftest.py b/tests/conftest.py index 19b39f0..75367ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -12,5 +12,5 @@ def pre_test(monkeypatch): prog_manage.verbose_toggle() - monkeypatch.setattr('sys.stdin', StringIO("n\nn\nn\nn\nn\nn\nn\nn\nn\nn\nn\nn\nn\n")) + monkeypatch.setattr('sys.stdin', StringIO("n\n"*3)) prog_manage.install("./tests/fake_packages/package.tar.gz") \ No newline at end of file diff --git a/tests/test_config.py b/tests/test_config.py index 81204db..440a2fb 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -70,7 +70,7 @@ def test_get_db(): }, "programs": { "package": { - "git_installed": False, + "install_type": "default", "desktops": [], "post_upgrade_script": None, "update_url": None, diff --git a/tests/test_prog_manage.py b/tests/test_prog_manage.py index b55781f..7bf7381 100644 --- a/tests/test_prog_manage.py +++ b/tests/test_prog_manage.py @@ -100,7 +100,7 @@ def test_create_db(): } -def atest_erase(): +def test_erase(): assert prog_manage.erase() == "Erased" assert os.path.isfile(config.full("~/.tarstall/tarstall.py")) is False try: diff --git a/version b/version index 5100ebe..6b5458a 100644 --- a/version +++ b/version @@ -1 +1 @@ -15.99 \ No newline at end of file +16.100 \ No newline at end of file From 250f9b7d47d97ef27956cd65f087632aca35df27 Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Fri, 3 Jul 2020 13:13:23 -0400 Subject: [PATCH 08/16] Fixed bug with fish on new installs --- config.py | 2 +- generic.py | 3 +-- prog_manage.py | 5 ++++- version | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/config.py b/config.py index 5cab150..391ad48 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.6.0" -prog_internal_version = 100 +prog_internal_version = 101 file_version = 16 ############# diff --git a/generic.py b/generic.py index 03c0649..14387e1 100644 --- a/generic.py +++ b/generic.py @@ -250,8 +250,7 @@ def endi(state): """ if state: return "enabled" - else: - return "disabled" + return "disabled" def pprint(st, title="tarstall-gui"): diff --git a/prog_manage.py b/prog_manage.py index 7961998..3bf88f1 100644 --- a/prog_manage.py +++ b/prog_manage.py @@ -1025,7 +1025,10 @@ def first_time_setup(): to_return = "Unsupported shell" else: to_return = "Success" - config.add_line("source ~/.tarstall/.fishrc\n", shell_file) + if "shrc" in config.get_shell_file(): + config.add_line("source ~/.tarstall/.bashrc\n", shell_file) + elif "fish" in config.get_shell_file(): + config.add_line("source ~/.tarstall/.fishrc\n", shell_file) generic.progress(75) copytree(config.full("./tarstall_execs/"), config.full("~/.tarstall/tarstall_execs/")) # Move tarstall.py to execs dir generic.progress(90) diff --git a/version b/version index 6b5458a..4b80297 100644 --- a/version +++ b/version @@ -1 +1 @@ -16.100 \ No newline at end of file +16.101 \ No newline at end of file From f537982d4249a6a02727f7bbb1cef6c61c93217d Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Mon, 6 Jul 2020 01:45:51 +0000 Subject: [PATCH 09/16] Repair Database Feature --- config.py | 2 +- prog_manage.py | 80 +++++++++++++++++++++++++++++++++++++++-- tarstall_execs/tarstall | 39 ++++++++++++++++++++ version | 2 +- 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/config.py b/config.py index 391ad48..9fb2e59 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.6.0" -prog_internal_version = 101 +prog_internal_version = 102 file_version = 16 ############# diff --git a/prog_manage.py b/prog_manage.py index 3bf88f1..157b05d 100644 --- a/prog_manage.py +++ b/prog_manage.py @@ -20,6 +20,7 @@ import sys import re import getpass +import datetime try: import requests @@ -40,6 +41,73 @@ else: c_out = DEVNULL +def repair_db(): + """Attempts to Repair Tarstall DB. + + WARNING: THIS SHOULD NOT BE USED UNLESS THE DATABASE CANNOT BE RECOVERED OTHERWISE + BECAUSE OF LIMITED KNOWLEDGE OF THE CODE ITSELF, THINGS SUCH AS PROGRAM TYPE HAVE TO BE ASSUMEDF + THIS ONLY EXISTS AS A LAST RESORT OPTION!!!!!!! + """ + config.vprint("Attempting repair of database, things are going to get crazy!") + + config.vprint("Getting stock database to build off of") + new_db = get_default_db() + generic.progress(5) + + config.vprint("Re-discovering programs:") + for pf in os.listdir(config.full("~/.tarstall/bin/")): + print("Re-discovering " + pf, end="\r") + prog_info = {pf: {"install_type": "default", "desktops": [], + "post_upgrade_script": None, "update_url": None, "has_path": False, "binlinks": []}} + if ".git" in os.listdir(config.full("~/.tarstall/bin/{}".format(pf))): + prog_info[pf]["install_type"] = "git" + elif len(os.listdir(config.full("~/.tarstall/bin/{}".format(pf)))) == 1: + prog_info[pf]["install_type"] = "single" + new_db["programs"].update(prog_info) + + generic.progress(20) + + config.vprint("Reading tarstall's bashrc file for further operations...") + with open(config.full("~/.tarstall/.bashrc")) as f: + bashrc_lines = f.readlines() + + generic.progress(25) + + config.vprint("Re-registering PATHs") + for l in bashrc_lines: + if l.startswith("export PATH=$PATH") and '#' in l: + program = l[l.find("#")+2:].rstrip() + print("Re-registering PATH for " + program, end="\r") + new_db["programs"][program]["has_path"] = True + + generic.progress(45) + + config.vprint("Re-registering binlinks") + for l in bashrc_lines: + if l.startswith("alias ") and '#' in l: + program = l[l.find("#")+2:].rstrip() + print("Re-registering a binlink or binlinks for " + program, end="\r") + binlinked_file = l[6:l.find("=")] + new_db["programs"][program]["binlinks"].append(binlinked_file) + + generic.progress(70) + + config.vprint("Backing up old database...") + date_str = datetime.datetime.today().strftime("%d-%m-%Y-%H-%M-%S") + move(config.full("~/.tarstall/database"), config.full("~/.tarstall/database-backup-{}.bak".format(date_str))) + + generic.progress(95) + + config.vprint("Writing new database...") + config.db = new_db + config.write_db() + + config.vprint("Database write complete!") + generic.progress(100) + return + + + def add_upgrade_url(program, url): """Adds an Upgrade URL to a Program. @@ -525,8 +593,8 @@ def pre_dirinstall(program, overwrite=None): config.write_db() -def create_db(): - """Creates Database.""" +def get_default_db(): + """Get Default DB""" db_template = { "options": { "Verbose": False, @@ -544,7 +612,13 @@ def create_db(): "programs": { } } - config.db = db_template + return db_template + + + +def create_db(): + """Creates Database.""" + config.db = get_default_db() config.write_db() diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index 6eeab54..9cccb7d 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -124,6 +124,41 @@ def gui_loop(): window.Element("manage").Update(disabled=False) +def possibly_repair_db(possibly_corrupt=False): + if not possibly_corrupt: + msg = """ +######################################### +#################WARNING################# +######################################### +WARNING: This feature should ONLY be used if your database is corrupt in some manner, as information can and will be lost: + +- .desktop files previously made will no longer be tracked, and must be either deleted and re-created, or managed manually! They can be found at ~/.local/share/applications/ . +- Upgrade scripts/post-upgrade scripts will no longer be linked to their respective programs! +- The program type (default vs. git-installed vs. single-file) may be mis-recognized/misconfigured! +- tarstall will assume you're on the master branch! If you're currently on beta, you will have to wait until the master branch surpasses your current version, or you have to switch to the beta branch after repairs! + +If ~/.tarstall or ~/.tarstall/bin were tampered with, than database recovery may have invalid information mixed in! + +Are you sure you would like to attempt repairing? +#########################################""" + yn = generic.get_input(msg, ['y', 'n'], 'n', ["Yes", "No"]) + if yn != 'y': + generic.ppause("Repairs not attempted!") + else: + yn = generic.get_input("Are you SURE you would like to attempt database repair?", ['y', 'n'], 'n', ["Yes", "No"]) + if yn != 'y': + generic.ppause("Repairs not attempted!") + else: + yn = generic.get_input("If you proceed, .desktop data, (post-)upgrade script references will be lost, and programs may be misconfigured? Are you SURE you would like to continue?", ['y', 'n'], 'n', ["Yes", "No"]) + if yn != 'y': + generic.ppause("Repairs not attempted!") + else: + prog_manage.repair_db() + generic.ppause("Database repair complete!") + config.unlock() + sys.exit(0) + + def wget_wizard(program): if not config.check_bin("wget"): generic.ppause("You must have 'wget' installed to use this feature!") @@ -263,6 +298,7 @@ def configure(): {"shorthand": 's', "gui-label": "Skip Questions", "description": "Whether or not to skip ending questions and confirmations. Currently {skip}."}, {"shorthand": 'u', "gui-label": "Update URL-Attatched Programs", "description": "Whether or not to update programs that have a URL attatched to them for updating. Currently {url}."}, {"shorthand": 'c', "gui-label": "Press ENTER messages (CLI only)", "description": "Whether or not to have some messages have you \"Press ENTER to continue...\". Currently {skipenter}."}, + {"shorthand": 'rd', "gui-label": "Attempt Database Repair", "description": "Attempt to repiar tarstall's database."}, {"shorthand": 'e', "gui-label": "Exit", "description": "Exit tarstall", "is-default": True}, ] replacements = [ @@ -302,6 +338,9 @@ def configure(): key = "UpdateURLPrograms" elif option == 'c': key = "PressEnterKey" + elif option == 'rd': + possibly_repair_db() + key = None elif option == 'e': return if key is not None: diff --git a/version b/version index 4b80297..8f43f35 100644 --- a/version +++ b/version @@ -1 +1 @@ -16.101 \ No newline at end of file +16.102 \ No newline at end of file From db4d67d1c8cb182a8581a3c2efad6bda6c4477ee Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Mon, 6 Jul 2020 02:12:58 +0000 Subject: [PATCH 10/16] See comment --- config.py | 2 +- prog_manage.py | 32 ++++++++++++++--- tarstall_execs/tarstall | 79 +++++++++++++++++++++++++++++++---------- version | 2 +- 4 files changed, 90 insertions(+), 25 deletions(-) diff --git a/config.py b/config.py index 9fb2e59..9f2110c 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.6.0" -prog_internal_version = 102 +prog_internal_version = 103 file_version = 16 ############# diff --git a/prog_manage.py b/prog_manage.py index 157b05d..379ee90 100644 --- a/prog_manage.py +++ b/prog_manage.py @@ -41,6 +41,19 @@ else: c_out = DEVNULL +def repair_tarstall(): + """Attempts to Repair Tarstall. + + Unlike the Database repair-er, this won't have the user lose any data. + + Returns: + str: Any string from update() + + """ + config.vprint("Forcing tarstall update to repair tarstall!") + return update(True, True) + + def repair_db(): """Attempts to Repair Tarstall DB. @@ -401,7 +414,7 @@ def change_branch(branch, reset=False): return "Waiting" -def tarstall_startup(start_fts=False, del_lock=False, old_upgrade=False): +def tarstall_startup(start_fts=False, del_lock=False, old_upgrade=False, force_fix=False): """Run on Startup. Runs on tarstall startup to perform any required checks and upgrades. @@ -415,7 +428,8 @@ def tarstall_startup(start_fts=False, del_lock=False, old_upgrade=False): str: One of many different values indicating the status of tarstall. Those include: "Not installed", "Locked", "Good" (nothing bad happened), "Root", "Old" (happens when upgrading from tarstall prog_version 1), and "Unlocked" if tarstall - was successfully unlocked. Can also return a string from first_time_setup. + was successfully unlocked. Can also return a string from first_time_setup or + "DB Broken" if the database is corrupt. """ if config.locked(): # Lock check @@ -444,6 +458,12 @@ def tarstall_startup(start_fts=False, del_lock=False, old_upgrade=False): os.mkdir(config.full("~/.tarstall/bin")) generic.progress(100) print("We're done! Continuing tarstall execution...") + + if config.db == {}: + if force_fix: + pass + else: + return "DB Broken" if start_fts: # Check if -f or --first is supplied return first_time_setup() @@ -911,6 +931,10 @@ def update(force_update=False, show_progress=True): Checks to see if we should update tarstall, then does so if one is available + Args: + force_update (bool): Whether or not to force an update to happen. Defaults to False. + show_progress (bool): Whether or not to show progress to the user. Defaults to True. + Returns: str: "No requests" if requests isn't installed, "No internet if there isn't an internet connection, "Newer version" if the installed @@ -959,7 +983,7 @@ def update(force_update=False, show_progress=True): config.vprint("Removing old tarstall files") os.chdir(config.full("~/.tarstall/")) files = os.listdir() - to_keep = ["bin", "database", ".bashrc"] + to_keep = ["bin", "database", ".bashrc", ".fishrc"] for f in files: if f not in to_keep: if os.path.isdir(config.full("~/.tarstall/{}".format(f))): @@ -970,7 +994,7 @@ def update(force_update=False, show_progress=True): config.vprint("Moving in new tarstall files") os.chdir("/tmp/tarstall-update/tarstall/") files = os.listdir() - to_ignore = [".git", ".gitignore", "README.md", "readme-images", "COPYING", "requirements.txt", "requirements-gui.txt", "tests", "install_tarstall"] + to_ignore = [".git", ".gitignore", "README.md", "readme-images", "COPYING", "requirements.txt", "requirements-gui.txt", "tests", "install_tarstall", "version"] for f in files: if f not in to_ignore: move("/tmp/tarstall-update/tarstall/{}".format(f), config.full("~/.tarstall/{}".format(f))) diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index 9cccb7d..28867ce 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -124,6 +124,48 @@ def gui_loop(): window.Element("manage").Update(disabled=False) +def process_update_status(status): + """Show End User Status from prog_manage.update(). + + Args: + status (str): Status from prog_manage.update() + + """ + if status == "No git": + generic.pprint("git isn't installed, please install it!") + exit_code = 1 + elif status == "No requests": + generic.pprint("requests isn't installed, please install it!") + exit_code = 1 + elif status == "Newer version": + generic.pprint("The installed version is newer than the one found online!") + elif status == "No update": + generic.pprint("No update was found!") + elif status == "Failed": + generic.pprint("tarstall update failed!") + exit_code = 1 + elif status == "No internet": + generic.pprint("Failed to connect to the internet!") + exit_code = 1 + elif status == "Updated": + generic.pprint("tarstall successfully updated!") + elif status == "Repaired": + generic.pprint("tarstall successfully repaired!") + config.unlock() + sys.exit(0) + + +def repair_tarstall(): + yn = generic.get_input("Would you like to repair tarstall? This process does require internet access!", ['y', 'n'], 'n', ["Yes", "No"]) + if yn != 'y': + generic.ppause("Repairs not attempted!") + else: + status = prog_manage.repair_tarstall() + if status == "Updated": + status = "Repaired" + process_update_status(status) + + def possibly_repair_db(possibly_corrupt=False): if not possibly_corrupt: msg = """ @@ -136,6 +178,7 @@ WARNING: This feature should ONLY be used if your database is corrupt in some ma - Upgrade scripts/post-upgrade scripts will no longer be linked to their respective programs! - The program type (default vs. git-installed vs. single-file) may be mis-recognized/misconfigured! - tarstall will assume you're on the master branch! If you're currently on beta, you will have to wait until the master branch surpasses your current version, or you have to switch to the beta branch after repairs! +- Any update URLs programs have will no longer have them! If ~/.tarstall or ~/.tarstall/bin were tampered with, than database recovery may have invalid information mixed in! @@ -298,7 +341,8 @@ def configure(): {"shorthand": 's', "gui-label": "Skip Questions", "description": "Whether or not to skip ending questions and confirmations. Currently {skip}."}, {"shorthand": 'u', "gui-label": "Update URL-Attatched Programs", "description": "Whether or not to update programs that have a URL attatched to them for updating. Currently {url}."}, {"shorthand": 'c', "gui-label": "Press ENTER messages (CLI only)", "description": "Whether or not to have some messages have you \"Press ENTER to continue...\". Currently {skipenter}."}, - {"shorthand": 'rd', "gui-label": "Attempt Database Repair", "description": "Attempt to repiar tarstall's database."}, + {"shorthand": 'rd', "gui-label": "Attempt Database Repair", "description": "Attempt to repiar tarstall's database. Only use as a last resort!"}, + {"shorthand": 'rt', "gui-label": "Attempt tarstall Repair", "description": "Attempt to repiar tarstall itself."}, {"shorthand": 'e', "gui-label": "Exit", "description": "Exit tarstall", "is-default": True}, ] replacements = [ @@ -341,6 +385,9 @@ def configure(): elif option == 'rd': possibly_repair_db() key = None + elif option == 'rt': + repair_tarstall() + key = None elif option == 'e': return if key is not None: @@ -726,6 +773,17 @@ def parse_args(args=None): elif status == "Old upgrade": generic.ppause("You are upgrading from a VERY old version of tarstall. Upgradiing will wipe your database! To upgrade: ") prog_manage.tarstall_startup(start_fts=args.first, del_lock=args.remove_lock, old_upgrade=True) + + elif status == "DB Broken": + yn = generic.get_input("Your tarstall database is corrupt! Would you like to attempt to fix it? You will lose things such as knowledge of .desktop files, update URLs, and some other things!", ['y', 'n'], 'n', ["Yes", "No"]) + if yn != 'y': + config.unlock() + generic.pprint("Not upgrading.") + sys.exit(1) + else: + prog_manage.repair_db() + config.unlock() + sys.exit(0) if args.install is not None: status = prog_manage.pre_install(args.install) @@ -877,24 +935,7 @@ def parse_args(args=None): elif args.update: status = prog_manage.update() - if status == "No git": - generic.pprint("git isn't installed, please install it!") - exit_code = 1 - elif status == "No requests": - generic.pprint("requests isn't installed, please install it!") - exit_code = 1 - elif status == "Newer version": - generic.pprint("The installed version is newer than the one found online!") - elif status == "No update": - generic.pprint("No update was found!") - elif status == "Updated": - generic.pprint("tarstall successfully updated!") - elif status == "Failed": - generic.pprint("tarstall update failed!") - exit_code = 1 - elif status == "No internet": - generic.pprint("Failed to connect to the internet!") - exit_code = 1 + process_update_status(status) elif args.config: configure() diff --git a/version b/version index 8f43f35..192878b 100644 --- a/version +++ b/version @@ -1 +1 @@ -16.102 \ No newline at end of file +16.103 \ No newline at end of file From 6de5fc35fcf90545175ada6e10777a1da63ca8b8 Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Tue, 7 Jul 2020 03:54:09 +0000 Subject: [PATCH 11/16] Fix #32 --- config.py | 4 ++-- version | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index 9f2110c..b3e6600 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.6.0" -prog_internal_version = 103 +prog_internal_version = 104 file_version = 16 ############# @@ -239,7 +239,7 @@ def name(program): """ program_internal_name = re.sub(r'.*/', '/', program) extension_length = len(extension(program)) - program_internal_name = program_internal_name[1:(len(program_internal_name) - extension_length)] + program_internal_name = program_internal_name[program_internal_name.find('/')+1:(len(program_internal_name) - extension_length)] return program_internal_name diff --git a/version b/version index 192878b..036f3b5 100644 --- a/version +++ b/version @@ -1 +1 @@ -16.103 \ No newline at end of file +16.104 \ No newline at end of file From 90806f54c7b6c1877dcea6dbbffb6b6456f24307 Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Wed, 8 Jul 2020 05:42:38 +0000 Subject: [PATCH 12/16] Fix wget_wizard access --- config.py | 2 +- tarstall_execs/tarstall | 2 +- version | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index b3e6600..f9f5914 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.6.0" -prog_internal_version = 104 +prog_internal_version = 105 file_version = 16 ############# diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index 28867ce..d5a6b30 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -653,7 +653,7 @@ def manage(program): call(["/bin/bash"]) elif option == 'g' and is_git: git_wizard(program) - elif option == 'w' and is_wget: + elif option == 'w' and not is_git: wget_wizard(program) elif option == 'q' and can_update: update_program_gui(program) diff --git a/version b/version index 036f3b5..069531a 100644 --- a/version +++ b/version @@ -1 +1 @@ -16.104 \ No newline at end of file +16.105 \ No newline at end of file From eb9835e911df3e4e5d342afecab8f7e90acb7c4e Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Wed, 8 Jul 2020 20:00:08 +0000 Subject: [PATCH 13/16] Smooth progress for wget --- config.py | 2 +- prog_manage.py | 18 +++++++++++++++--- version | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/config.py b/config.py index f9f5914..be46dea 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.6.0" -prog_internal_version = 105 +prog_internal_version = 106 file_version = 16 ############# diff --git a/prog_manage.py b/prog_manage.py index 379ee90..8e3aece 100644 --- a/prog_manage.py +++ b/prog_manage.py @@ -16,7 +16,7 @@ import os from shutil import copyfile, rmtree, move, which, copy, copytree -from subprocess import call, run, DEVNULL, PIPE +from subprocess import call, run, DEVNULL, PIPE, Popen, STDOUT import sys import re import getpass @@ -170,9 +170,21 @@ def wget_program(program, show_progress=False, progress_modifier=1): config.vprint("Downloading archive...") url = config.db["programs"][program]["update_url"] if config.verbose: - err = call(["wget", url]) + process = Popen(["wget", url]) else: - err = call(["wget", url], stdout=DEVNULL, stderr=DEVNULL) + process = Popen(["wget", url], stdout=PIPE, stderr=STDOUT) + if not config.verbose: + while process.poll() is None: + p_status = process.stdout.readline().decode("utf-8") + try: + percent_complete = int(p_status[p_status.rfind("%")-2:p_status.rfind("%")].strip()) + if percent_complete > 0: + generic.progress((10 + (55 * (percent_complete / 100))) / progress_modifier, show_progress) + except (TypeError, ValueError): + pass + else: + process.wait() + err = process.poll() if err != 0: return "Wget error" generic.progress(65 / progress_modifier, show_progress) diff --git a/version b/version index 069531a..44f79e2 100644 --- a/version +++ b/version @@ -1 +1 @@ -16.105 \ No newline at end of file +16.106 \ No newline at end of file From 755152b69517e38a472476ec88d8f40a06a1a51e Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Wed, 8 Jul 2020 20:16:41 +0000 Subject: [PATCH 14/16] Overwrite/reinstall bugfixes + consistency --- config.py | 2 +- prog_manage.py | 11 +++-------- tarstall_execs/tarstall | 14 ++++++++++---- version | 2 +- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/config.py b/config.py index be46dea..c730ac7 100644 --- a/config.py +++ b/config.py @@ -23,7 +23,7 @@ ###VERSIONS### version = "1.6.0" -prog_internal_version = 106 +prog_internal_version = 107 file_version = 16 ############# diff --git a/prog_manage.py b/prog_manage.py index 8e3aece..4861cb1 100644 --- a/prog_manage.py +++ b/prog_manage.py @@ -1294,7 +1294,7 @@ def install(program, overwrite=False, reinstall=False, show_progress=True): rmtree(config.full("/tmp/tarstall-temp")) except FileNotFoundError: config.vprint('Temp folder not found so not deleted!') - if not reinstall: + if not overwrite: return finish_install(program_internal_name) else: generic.progress(100, show_progress) @@ -1326,12 +1326,7 @@ def single_install(program, program_internal_name, reinstall=False): config.vprint("Moving to tarstall directory and renaming...") move(config.full(program), config.full("~/.tarstall/bin/{p}/{p}".format(p=program_internal_name))) generic.progress(90) - if not reinstall: - return finish_install(program_internal_name, "single") - else: - generic.progress(100) - return "Installed" - + return finish_install(program_internal_name, "single") def dirinstall(program_path, program_internal_name, overwrite=False, reinstall=False): @@ -1357,7 +1352,7 @@ def dirinstall(program_path, program_internal_name, overwrite=False, reinstall=F rmtree(program_path) else: move(program_path, config.full("~/.tarstall/bin/")) - if not reinstall: + if not overwrite: return finish_install(program_internal_name) else: return "Installed" diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index d5a6b30..d12db35 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -786,6 +786,7 @@ def parse_args(args=None): sys.exit(0) if args.install is not None: + overwrite = False status = prog_manage.pre_install(args.install) if status == "Bad file": generic.pprint("The specified file does not exist!") @@ -797,9 +798,10 @@ def parse_args(args=None): status = prog_manage.pre_install(args.install, False) elif reinstall == "o": status = prog_manage.pre_install(args.install, True) + overwrite = True else: generic.pprint("Reinstall cancelled.") - elif status == "Installed": + if status == "Installed" and not overwrite: install_wrap_up(config.name(args.install)) elif status.startswith("No"): generic.pprint("{} needs to be installed! Installation halted.".format(status[3:])) @@ -814,6 +816,7 @@ def parse_args(args=None): exit_code = 1 elif args.gitinstall is not None: + overwrite = False status = prog_manage.pre_gitinstall(args.gitinstall) if status == "No git": generic.pprint("git not installed! Please install it before using this feature!") @@ -828,9 +831,10 @@ def parse_args(args=None): status = prog_manage.pre_gitinstall(args.gitinstall, False) elif reinstall == "o": status = prog_manage.pre_gitinstall(args.gitinstall, True) + overwrite = True else: generic.pprint("Reinstall cancelled.") - if status == "Installed": + if status == "Installed" and not overwrite: install_wrap_up(config.name(args.gitinstall)) elif status == "No rsync": generic.pprint("rsync not installed! Please install it!") @@ -852,7 +856,7 @@ def parse_args(args=None): status = prog_manage.pre_singleinstall(args.singleinstall, True) else: generic.pprint("Reinstall cancelled.") - elif status == "Installed": + if status == "Installed": install_wrap_up(config.name(args.singleinstall), True) elif status.startswith("No"): generic.pprint("{} needs to be installed! Installation halted.".format(status[3:])) @@ -868,6 +872,7 @@ def parse_args(args=None): elif args.dirinstall is not None: + overwrite = False status = prog_manage.pre_dirinstall(args.dirinstall) if status == "Bad folder": generic.pprint("Please specify a valid directory path that ends in a \"/\"!") @@ -879,9 +884,10 @@ def parse_args(args=None): status = prog_manage.pre_dirinstall(args.dirinstall, False) elif reinstall == 'o': status = prog_manage.pre_dirinstall(args.dirinstall, True) + overwrite = True else: generic.pprint("Reinstall cancelled.") - if status == "Installed": + if status == "Installed" and not overwrite: install_wrap_up(config.dirname(args.dirinstall)) elif status == "No rsync": generic.pprint("rsync not installed! Please install it!") diff --git a/version b/version index 44f79e2..a4da92d 100644 --- a/version +++ b/version @@ -1 +1 @@ -16.106 \ No newline at end of file +16.107 \ No newline at end of file From 4af7bf07d17909150278a98a9812e7e356cfb0a0 Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Wed, 8 Jul 2020 21:52:23 +0000 Subject: [PATCH 15/16] Install deps if missing + skip warning --- config.py | 6 ++-- install_tarstall | 13 ++++---- prog_manage.py | 70 ++++++++++++++++++++++++++++++++++++----- tarstall_execs/tarstall | 23 +++++++++++++- version | 2 +- 5 files changed, 95 insertions(+), 19 deletions(-) diff --git a/config.py b/config.py index c730ac7..4168c16 100644 --- a/config.py +++ b/config.py @@ -23,8 +23,8 @@ ###VERSIONS### version = "1.6.0" -prog_internal_version = 107 -file_version = 16 +prog_internal_version = 108 +file_version = 17 ############# @@ -103,7 +103,7 @@ def read_config(key): except KeyError: if key in ["Verbose", "AutoInstall", "SkipQuestions", "UpdateURLPrograms"]: return False - elif key in ["PressEnterKey"]: + elif key in ["PressEnterKey", "WarnMissingDeps"]: return True elif key == "ShellFile": return get_shell_file() diff --git a/install_tarstall b/install_tarstall index f4c5f4b..364d04b 100755 --- a/install_tarstall +++ b/install_tarstall @@ -120,12 +120,13 @@ def setup(): print("{}\n\n\n".format(msg)) # User information - status("Welcome!\nThis installer is going to install tarstall! You'll need to enter your sudo password at some points. " + - "This will use your distro's package manager along with pip to install the dependencies required for tarstall!") - cancel = input("When you're ready to start installation, press ENTER! If you would like to cancel, type 'c', then press ENTER!") - if cancel.lower() == 'c': - status("Cancelling tarstall setup...") - sys.exit() + if not os.path.isfile("/tmp/dont-ask-me"): + status("Welcome!\nThis installer is going to install tarstall! You'll need to enter your sudo password at some points. " + + "This will use your distro's package manager along with pip to install the dependencies required for tarstall!") + cancel = input("When you're ready to start installation, press ENTER! If you would like to cancel, type 'c', then press ENTER!") + if cancel.lower() == 'c': + status("Cancelling tarstall setup...") + sys.exit() # Install requirements obtained through package manager status("Installing tarstall's package requirements") diff --git a/prog_manage.py b/prog_manage.py index 4861cb1..d9f5b8a 100644 --- a/prog_manage.py +++ b/prog_manage.py @@ -27,11 +27,6 @@ can_update = True except ImportError: can_update = False - print("##########WARNING##########") - print("requests library not installed! Ability to update tarstall") - print("has been disabled! Use `pip3 install requests` or ") - print("`python3 -m pip install requests` to install it!") - print("###########################") import config import generic @@ -41,6 +36,45 @@ else: c_out = DEVNULL + +def reinstall_deps(): + """Reinstall Dependencies + + Install the dependencies for tarstall by using the installer. + + Returns: + str: "No wget", "Wget error", "Installer error", or "Success" + + """ + if which("wget") is None: + return "No wget" + config.vprint("Deleting and re-creating temp directory") + try: + rmtree("/tmp/tarstall-temp") + except FileNotFoundError: + pass + os.mkdir("/tmp/tarstall-temp/") + os.chdir("/tmp/tarstall-temp/") + generic.progress(5) + config.vprint("Obtaining tarstall installer...") + url = "https://raw.githubusercontent.com/hammy3502/tarstall/{}/install_tarstall".format(config.db["version"]["branch"]) + err = call(["wget", url], stdout=c_out, stderr=c_out) + if err != 0: + return "Wget error" + generic.progress(60) + config.vprint("Creating file to skip tarstall's installer prompt") + config.create("/tmp/dont-ask-me") + config.vprint("Running tarstall setup to (re)-install dependencies") + err = call([sys.executable, "install_tarstall"], stdout=c_out, stderr=c_out) + generic.progress(95) + config.vprint("Removing installer skip file") + os.remove("/tmp/dont-ask-me") + generic.progress(100) + if err != 0: + return "Installer error" + return "Success" + + def repair_tarstall(): """Attempts to Repair Tarstall. @@ -440,10 +474,26 @@ def tarstall_startup(start_fts=False, del_lock=False, old_upgrade=False, force_f str: One of many different values indicating the status of tarstall. Those include: "Not installed", "Locked", "Good" (nothing bad happened), "Root", "Old" (happens when upgrading from tarstall prog_version 1), and "Unlocked" if tarstall - was successfully unlocked. Can also return a string from first_time_setup or - "DB Broken" if the database is corrupt. + was successfully unlocked. Can also return a string from first_time_setup, + "DB Broken" if the database is corrupt, or "Missing Deps" if missing one or more dependencies. """ + final_status = "Good" + missing_deps = False + try: + import tkinter + except (ModuleNotFoundError, ImportError): + missing_deps = True + try: + import PySimpleGUI + except (ModuleNotFoundError, ImportError): + missing_deps = True + try: + import requests + except (ModuleNotFoundError, ImportError): + missing_deps = True + if missing_deps: + final_status = "Missing Deps" if config.locked(): # Lock check config.vprint("Lock file detected at /tmp/tarstall-lock.") if del_lock: @@ -514,6 +564,10 @@ def tarstall_startup(start_fts=False, del_lock=False, old_upgrade=False, force_f else: config.db["programs"][program]["install_type"] = "default" del config.db["programs"][program]["git_installed"] + + elif file_version == 16: + config.vprint("Adding WarnMissingDeps key...") + config.db["options"]["WarnMissingDeps"] = True config.db["version"]["file_version"] += 1 file_version = get_file_version('file') @@ -530,7 +584,7 @@ def tarstall_startup(start_fts=False, del_lock=False, old_upgrade=False, force_f config.vprint("We're running as root!") return "Root" - return "Good" + return final_status def pre_install(program, overwrite=None, show_progress=True): diff --git a/tarstall_execs/tarstall b/tarstall_execs/tarstall index d12db35..18eee14 100755 --- a/tarstall_execs/tarstall +++ b/tarstall_execs/tarstall @@ -343,6 +343,7 @@ def configure(): {"shorthand": 'c', "gui-label": "Press ENTER messages (CLI only)", "description": "Whether or not to have some messages have you \"Press ENTER to continue...\". Currently {skipenter}."}, {"shorthand": 'rd', "gui-label": "Attempt Database Repair", "description": "Attempt to repiar tarstall's database. Only use as a last resort!"}, {"shorthand": 'rt', "gui-label": "Attempt tarstall Repair", "description": "Attempt to repiar tarstall itself."}, + {"shorthand": 'w', "gui-label": "Skip Missing Dependency Warnings", "description": "Whether or not to skip missing dependency warning. Currently {depen}"}, {"shorthand": 'e', "gui-label": "Exit", "description": "Exit tarstall", "is-default": True}, ] replacements = [ @@ -352,7 +353,8 @@ def configure(): {"{gui}": generic.endi(config.read_config("Mode") == "gui")}, {"{skip}": generic.endi(config.read_config("SkipQuestions"))}, {"{url}": generic.endi(config.read_config("UpdateURLPrograms"))}, - {"{skipenter}": generic.endi(config.read_config("PressEnterKey"))} + {"{skipenter}": generic.endi(config.read_config("PressEnterKey"))}, + {"{depen}": generic.endi(config.read_config("WarnMissingDeps"))} ] option = generic.easy_get_action(options, replacements) if option == 'au': @@ -388,6 +390,8 @@ def configure(): elif option == 'rt': repair_tarstall() key = None + elif option == 'w': + key = "WarnMissingDeps" elif option == 'e': return if key is not None: @@ -785,6 +789,23 @@ def parse_args(args=None): config.unlock() sys.exit(0) + if status == "Missing Deps" and config.read_config("WarnMissingDeps"): + yn = generic.get_input("You are missing some dependencies to allow full usage of tarstall! Would you like to re-run the installer to install the dependencies?", ['y', 'n'], 'y', ["Yes", "No"]) + if yn == 'y': + exit_code = 1 + status = prog_manage.reinstall_deps() + if status == "Success": + generic.pprint("Successfully re-installed dependencies!") + exit_code = 0 + elif status == "No wget": + generic.pprint("You don't have wget, please install it!") + elif status == "Wget error": + generic.pprint("An error occured while trying to obtain the tarstall installer. Are you connected to the internet?") + elif status == "Installer error": + generic.pprint("An error occured while running the installer") + config.unlock() + sys.exit(exit_code) + if args.install is not None: overwrite = False status = prog_manage.pre_install(args.install) diff --git a/version b/version index a4da92d..08a730c 100644 --- a/version +++ b/version @@ -1 +1 @@ -16.107 \ No newline at end of file +17.108 \ No newline at end of file From a376e5a4e1d5dc3a3af26d563e35e474f911b9a6 Mon Sep 17 00:00:00 2001 From: hammy3502 Date: Wed, 8 Jul 2020 22:27:37 +0000 Subject: [PATCH 16/16] Just an extra unit test --- tests/test_prog_manage.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_prog_manage.py b/tests/test_prog_manage.py index 7bf7381..9b00522 100644 --- a/tests/test_prog_manage.py +++ b/tests/test_prog_manage.py @@ -78,6 +78,11 @@ def test_install(monkeypatch): assert os.path.isfile(os.path.expanduser("~/.tarstall/bin/package/test.sh")) +def test_repair_db(): + prog_manage.repair_db() + assert config.db["programs"]["package"]["install_type"] == "single" # Since the archive only contains one file, it gets re-detected as single-file + + def test_create_db(): prog_manage.create_db() #TODO: Fake os so we can test get_shell_file in any environment