From c17c3e9bd421c06c54f55c70cb36622d49265385 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 2 May 2024 22:54:49 +0200 Subject: [PATCH] feat: add KlipperScreen Signed-off-by: Dominik Willner --- kiauh/components/klipperscreen/__init__.py | 16 ++ .../components/klipperscreen/klipperscreen.py | 217 ++++++++++++++++++ kiauh/core/menus/backup_menu.py | 3 +- kiauh/core/menus/install_menu.py | 5 + kiauh/core/menus/main_menu.py | 2 + kiauh/core/menus/remove_menu.py | 5 + kiauh/core/menus/update_menu.py | 18 +- 7 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 kiauh/components/klipperscreen/__init__.py create mode 100644 kiauh/components/klipperscreen/klipperscreen.py diff --git a/kiauh/components/klipperscreen/__init__.py b/kiauh/components/klipperscreen/__init__.py new file mode 100644 index 0000000..c79b463 --- /dev/null +++ b/kiauh/components/klipperscreen/__init__.py @@ -0,0 +1,16 @@ +# ======================================================================= # +# Copyright (C) 2020 - 2024 Dominik Willner # +# # +# This file is part of KIAUH - Klipper Installation And Update Helper # +# https://github.com/dw-0/kiauh # +# # +# This file may be distributed under the terms of the GNU GPLv3 license # +# ======================================================================= # +from pathlib import Path + +from core.backup_manager import BACKUP_ROOT_DIR + +KLIPPERSCREEN_REPO = "https://github.com/KlipperScreen/KlipperScreen.git" +KLIPPERSCREEN_DIR = Path.home().joinpath("KlipperScreen") +KLIPPERSCREEN_ENV = Path.home().joinpath(".KlipperScreen-env") +KLIPPERSCREEN_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipperscreen-backups") diff --git a/kiauh/components/klipperscreen/klipperscreen.py b/kiauh/components/klipperscreen/klipperscreen.py new file mode 100644 index 0000000..5e3b44b --- /dev/null +++ b/kiauh/components/klipperscreen/klipperscreen.py @@ -0,0 +1,217 @@ +# ======================================================================= # +# Copyright (C) 2020 - 2024 Dominik Willner # +# # +# This file is part of KIAUH - Klipper Installation And Update Helper # +# https://github.com/dw-0/kiauh # +# # +# This file may be distributed under the terms of the GNU GPLv3 license # +# ======================================================================= # +import shutil +from pathlib import Path +from subprocess import run, CalledProcessError +from typing import List, Dict, Literal, Union + +from components.klipper.klipper import Klipper +from components.klipperscreen import ( + KLIPPERSCREEN_DIR, + KLIPPERSCREEN_REPO, + KLIPPERSCREEN_ENV, + KLIPPERSCREEN_BACKUP_DIR, +) +from components.moonraker.moonraker import Moonraker +from core.backup_manager.backup_manager import BackupManager +from core.instance_manager.instance_manager import InstanceManager +from core.settings.kiauh_settings import KiauhSettings +from utils.common import get_install_status +from utils.config_utils import add_config_section, remove_config_section +from utils.constants import SYSTEMD +from utils.fs_utils import remove_with_sudo +from utils.git_utils import ( + git_clone_wrapper, + git_pull_wrapper, + get_repo_name, + get_local_commit, + get_remote_commit, +) +from utils.input_utils import get_confirm +from utils.logger import Logger +from utils.sys_utils import ( + check_python_version, + check_package_install, + install_system_packages, + control_systemd_service, + install_python_requirements, +) + + +def install_klipperscreen() -> None: + Logger.print_status("Installing KlipperScreen ...") + + if not check_python_version(3, 7): + return + + mr_im = InstanceManager(Moonraker) + mr_instances = mr_im.instances + if not mr_instances: + # TODO: add moonraker not found dialog + print("Moonraker not found!") + if not get_confirm( + "Continue KlipperScreen installation?", + allow_go_back=True, + ): + return + + package_list = ["wget", "curl", "unzip", "dfu-util"] + packages = check_package_install(package_list) + if packages: + install_system_packages(packages) + + git_clone_wrapper(KLIPPERSCREEN_REPO, KLIPPERSCREEN_DIR) + + try: + script = f"{KLIPPERSCREEN_DIR}/scripts/KlipperScreen-install.sh" + run(script, shell=True, check=True) + if mr_instances: + patch_klipperscreen_update_manager(mr_instances) + mr_im.restart_all_instance() + else: + Logger.print_info( + "Moonraker is not installed! Cannot add KlipperScreen to update manager!" + ) + Logger.print_ok("KlipperScreen successfully installed!") + except CalledProcessError as e: + Logger.print_error(f"Error installing KlipperScreen:\n{e}") + return + + +def patch_klipperscreen_update_manager(instances: List[Moonraker]) -> None: + env_py = f"{KLIPPERSCREEN_ENV}/bin/python" + add_config_section( + section="update_manager KlipperScreen", + instances=instances, + options=[ + ("type", "git_repo"), + ("path", str(KLIPPERSCREEN_DIR)), + ("orgin", KLIPPERSCREEN_REPO), + ("env", env_py), + ("requirements", "scripts/KlipperScreen-requirements.txt"), + ("install_script", "scripts/KlipperScreen-install.sh"), + ], + ) + + +def update_klipperscreen() -> None: + try: + control_systemd_service("KlipperScreen", "stop") + + if not KLIPPERSCREEN_DIR.exists(): + Logger.print_info( + "KlipperScreen does not seem to be installed! Skipping ..." + ) + return + + Logger.print_status("Updating KlipperScreen ...") + + control_systemd_service("KlipperScreen", "stop") + + settings = KiauhSettings() + if settings.get("kiauh", "backup_before_update"): + backup_klipperscreen_dir() + + git_pull_wrapper(KLIPPERSCREEN_REPO, KLIPPERSCREEN_DIR) + + requirements = KLIPPERSCREEN_DIR.joinpath( + "/scripts/KlipperScreen-requirements.txt" + ) + install_python_requirements(KLIPPERSCREEN_ENV, requirements) + + control_systemd_service("KlipperScreen", "start") + + Logger.print_ok("KlipperScreen updated successfully.", end="\n\n") + except CalledProcessError as e: + Logger.print_error(f"Something went wrong! Please try again...\n{e}") + return + + +def get_klipperscreen_status() -> ( + Dict[ + Literal["status", "status_code", "repo", "local", "remote"], + Union[str, int], + ] +): + files = [ + KLIPPERSCREEN_DIR, + KLIPPERSCREEN_ENV, + SYSTEMD.joinpath("KlipperScreen.service"), + ] + status = get_install_status(KLIPPERSCREEN_DIR, files) + return { + "status": status.get("status"), + "status_code": status.get("status_code"), + "repo": get_repo_name(KLIPPERSCREEN_DIR), + "local": get_local_commit(KLIPPERSCREEN_DIR), + "remote": get_remote_commit(KLIPPERSCREEN_DIR), + } + + +def remove_klipperscreen() -> None: + Logger.print_status("Removing KlipperScreen ...") + try: + if KLIPPERSCREEN_DIR.exists(): + Logger.print_status("Removing KlipperScreen directory ...") + shutil.rmtree(KLIPPERSCREEN_DIR) + Logger.print_ok("KlipperScreen directory successfully removed!") + else: + Logger.print_warn("KlipperScreen directory not found!") + + if KLIPPERSCREEN_ENV.exists(): + Logger.print_status("Removing KlipperScreen environment ...") + shutil.rmtree(KLIPPERSCREEN_ENV) + Logger.print_ok("KlipperScreen environment successfully removed!") + else: + Logger.print_warn("KlipperScreen environment not found!") + + service = SYSTEMD.joinpath("KlipperScreen.service") + if service.exists(): + Logger.print_status("Removing KlipperScreen service ...") + control_systemd_service(service, "stop") + control_systemd_service(service, "disable") + remove_with_sudo(service) + Logger.print_ok("KlipperScreen service successfully removed!") + + logfile = Path("/tmp/KlipperScreen.log") + if logfile.exists(): + Logger.print_status("Removing KlipperScreen log file ...") + remove_with_sudo(logfile) + Logger.print_ok("KlipperScreen log file successfully removed!") + + kl_im = InstanceManager(Klipper) + kl_instances: List[Klipper] = kl_im.instances + for instance in kl_instances: + logfile = instance.log_dir.joinpath("KlipperScreen.log") + if logfile.exists(): + Logger.print_status(f"Removing {logfile} ...") + Path(logfile).unlink() + Logger.print_ok(f"{logfile} successfully removed!") + + mr_im = InstanceManager(Moonraker) + mr_instances: List[Moonraker] = mr_im.instances + if mr_instances: + Logger.print_status("Removing KlipperScreen from update manager ...") + remove_config_section("update_manager KlipperScreen", mr_instances) + Logger.print_ok("KlipperScreen successfully removed from update manager!") + + Logger.print_ok("KlipperScreen successfully removed!") + + except Exception as e: + Logger.print_error(f"Error removing KlipperScreen:\n{e}") + + +def backup_klipperscreen_dir() -> None: + bm = BackupManager() + bm.backup_directory( + "KlipperScreen", source=KLIPPERSCREEN_DIR, target=KLIPPERSCREEN_BACKUP_DIR + ) + bm.backup_directory( + "KlipperScreen-env", source=KLIPPERSCREEN_ENV, target=KLIPPERSCREEN_BACKUP_DIR + ) diff --git a/kiauh/core/menus/backup_menu.py b/kiauh/core/menus/backup_menu.py index 4bfb2c5..a7b28d6 100644 --- a/kiauh/core/menus/backup_menu.py +++ b/kiauh/core/menus/backup_menu.py @@ -11,6 +11,7 @@ import textwrap from typing import Type, Optional from components.klipper.klipper_utils import backup_klipper_dir +from components.klipperscreen.klipperscreen import backup_klipperscreen_dir from components.moonraker.moonraker_utils import ( backup_moonraker_dir, backup_moonraker_db_dir, @@ -104,4 +105,4 @@ class BackupMenu(BaseMenu): backup_client_config_data(FluiddData()) def backup_klipperscreen(self, **kwargs): - pass + backup_klipperscreen_dir() diff --git a/kiauh/core/menus/install_menu.py b/kiauh/core/menus/install_menu.py index 4d69b9e..3906f79 100644 --- a/kiauh/core/menus/install_menu.py +++ b/kiauh/core/menus/install_menu.py @@ -12,6 +12,7 @@ from typing import Type, Optional from components.crowsnest.crowsnest import install_crowsnest from components.klipper import klipper_setup +from components.klipperscreen.klipperscreen import install_klipperscreen from components.moonraker import moonraker_setup from components.webui_client import client_setup from components.webui_client.client_config import client_config_setup @@ -45,6 +46,7 @@ class InstallMenu(BaseMenu): "4": Option(method=self.install_fluidd, menu=False), "5": Option(method=self.install_mainsail_config, menu=False), "6": Option(method=self.install_fluidd_config, menu=False), + "7": Option(method=self.install_klipperscreen, menu=False), "9": Option(method=self.install_crowsnest, menu=False), } @@ -91,5 +93,8 @@ class InstallMenu(BaseMenu): def install_fluidd_config(self, **kwargs): client_config_setup.install_client_config(FluiddData()) + def install_klipperscreen(self, **kwargs): + install_klipperscreen() + def install_crowsnest(self, **kwargs): install_crowsnest() diff --git a/kiauh/core/menus/main_menu.py b/kiauh/core/menus/main_menu.py index 5e8464c..1c6541a 100644 --- a/kiauh/core/menus/main_menu.py +++ b/kiauh/core/menus/main_menu.py @@ -12,6 +12,7 @@ from typing import Type, Optional from components.crowsnest.crowsnest import get_crowsnest_status from components.klipper.klipper_utils import get_klipper_status +from components.klipperscreen.klipperscreen import get_klipperscreen_status from components.log_uploads.menus.log_upload_menu import LogUploadMenu from components.moonraker.moonraker_utils import get_moonraker_status from components.webui_client.client_utils import ( @@ -91,6 +92,7 @@ class MainMenu(BaseMenu): self.ms_status = get_client_status(MainsailData()) self.fl_status = get_client_status(FluiddData()) self.cc_status = get_current_client_config([MainsailData(), FluiddData()]) + self._update_status("ks", get_klipperscreen_status) self._update_status("cn", get_crowsnest_status) def _update_status(self, status_name: str, status_fn: callable) -> None: diff --git a/kiauh/core/menus/remove_menu.py b/kiauh/core/menus/remove_menu.py index b6f11b0..7339823 100644 --- a/kiauh/core/menus/remove_menu.py +++ b/kiauh/core/menus/remove_menu.py @@ -12,6 +12,7 @@ from typing import Type, Optional from components.crowsnest.crowsnest import remove_crowsnest from components.klipper.menus.klipper_remove_menu import KlipperRemoveMenu +from components.klipperscreen.klipperscreen import remove_klipperscreen from components.moonraker.menus.moonraker_remove_menu import ( MoonrakerRemoveMenu, ) @@ -43,6 +44,7 @@ class RemoveMenu(BaseMenu): "2": Option(method=self.remove_moonraker, menu=True), "3": Option(method=self.remove_mainsail, menu=True), "4": Option(method=self.remove_fluidd, menu=True), + "5": Option(method=self.remove_klipperscreen, menu=True), "6": Option(method=self.remove_crowsnest, menu=True), } @@ -81,5 +83,8 @@ class RemoveMenu(BaseMenu): def remove_fluidd(self, **kwargs): ClientRemoveMenu(previous_menu=self.__class__, client=FluiddData()).run() + def remove_klipperscreen(self, **kwargs): + remove_klipperscreen() + def remove_crowsnest(self, **kwargs): remove_crowsnest() diff --git a/kiauh/core/menus/update_menu.py b/kiauh/core/menus/update_menu.py index 4a4b236..9d0661c 100644 --- a/kiauh/core/menus/update_menu.py +++ b/kiauh/core/menus/update_menu.py @@ -15,6 +15,10 @@ from components.klipper.klipper_setup import update_klipper from components.klipper.klipper_utils import ( get_klipper_status, ) +from components.klipperscreen.klipperscreen import ( + update_klipperscreen, + get_klipperscreen_status, +) from components.moonraker.moonraker_setup import update_moonraker from components.moonraker.moonraker_utils import get_moonraker_status from components.webui_client.client_config.client_config_setup import ( @@ -58,6 +62,8 @@ class UpdateMenu(BaseMenu): self.mc_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.fc_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.fc_remote = f"{COLOR_WHITE}{RESET_FORMAT}" + self.ks_local = f"{COLOR_WHITE}{RESET_FORMAT}" + self.ks_remote = f"{COLOR_WHITE}{RESET_FORMAT}" self.cn_local = f"{COLOR_WHITE}{RESET_FORMAT}" self.cn_remote = f"{COLOR_WHITE}{RESET_FORMAT}" @@ -112,7 +118,7 @@ class UpdateMenu(BaseMenu): | 6) Fluidd-Config | {self.fc_local:<22} | {self.fc_remote:<22} | | | | | | Other: |---------------|---------------| - | 7) KlipperScreen | | | + | 7) KlipperScreen | {self.ks_local:<22} | {self.ks_remote:<22} | | 8) Mobileraker | | | | 9) Crowsnest | {self.cn_local:<22} | {self.cn_remote:<22} | | |-------------------------------| @@ -142,7 +148,8 @@ class UpdateMenu(BaseMenu): def update_fluidd_config(self, **kwargs): update_client_config(self.fluidd_client) - def update_klipperscreen(self, **kwargs): ... + def update_klipperscreen(self, **kwargs): + update_klipperscreen() def update_mobileraker(self, **kwargs): ... @@ -193,6 +200,13 @@ class UpdateMenu(BaseMenu): ) self.fc_remote = f"{COLOR_GREEN}{fc_status.get('remote')}{RESET_FORMAT}" + # klipperscreen + ks_status = get_klipperscreen_status() + self.ks_local = self.format_local_status( + ks_status.get("local"), ks_status.get("remote") + ) + self.ks_remote = f"{COLOR_GREEN}{ks_status.get('remote')}{RESET_FORMAT}" + # crowsnest cn_status = get_crowsnest_status() self.cn_local = self.format_local_status(