diff --git a/kiauh/core/constants.py b/kiauh/core/constants.py index d613ccf..ea75928 100644 --- a/kiauh/core/constants.py +++ b/kiauh/core/constants.py @@ -33,7 +33,7 @@ CURRENT_USER = pwd.getpwuid(os.getuid())[0] # dirs SYSTEMD = Path("/etc/systemd/system") -PRINTER_CFG_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("printer-cfg-backups") +PRINTER_DATA_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("printer-data-backups") NGINX_SITES_AVAILABLE = Path("/etc/nginx/sites-available") NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled") NGINX_CONFD = Path("/etc/nginx/conf.d") diff --git a/kiauh/extensions/extensions_menu.py b/kiauh/extensions/extensions_menu.py index 60b1167..c44f555 100644 --- a/kiauh/extensions/extensions_menu.py +++ b/kiauh/extensions/extensions_menu.py @@ -58,12 +58,16 @@ class ExtensionsMenu(BaseMenu): module_path = f"kiauh.extensions.{ext.name}.{module_name}" # get the class name of the extension - ext_class: Type[BaseExtension] = inspect.getmembers( - importlib.import_module(module_path), - predicate=lambda o: inspect.isclass(o) - and issubclass(o, BaseExtension) - and o != BaseExtension, - )[0][1] + module = importlib.import_module(module_path) + + def predicate(o): + return ( + inspect.isclass(o) + and issubclass(o, BaseExtension) + and o != BaseExtension + ) + + ext_class: type = inspect.getmembers(module, predicate)[0][1] # instantiate the extension with its metadata and add to dict ext_instance: BaseExtension = ext_class(metadata) @@ -72,7 +76,7 @@ class ExtensionsMenu(BaseMenu): except (IOError, json.JSONDecodeError, ImportError) as e: print(f"Failed loading extension {ext}: {e}") - return dict(sorted(ext_dict.items())) + return dict(sorted(ext_dict.items(), key=lambda x: int(x[0]))) def extension_submenu(self, **kwargs): ExtensionSubmenu(kwargs.get("opt_data"), self.__class__).run() diff --git a/kiauh/extensions/simply_print/__init__.py b/kiauh/extensions/simply_print/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kiauh/extensions/simply_print/metadata.json b/kiauh/extensions/simply_print/metadata.json new file mode 100644 index 0000000..74213f1 --- /dev/null +++ b/kiauh/extensions/simply_print/metadata.json @@ -0,0 +1,13 @@ +{ + "metadata": { + "index": 10, + "module": "simply_print_extension", + "maintained_by": "dw-0", + "display_name": "SimplyPrint", + "description": [ + "3D Printer Cloud Management Software.", + "\n\n", + "3D printing doesn't have to be a complicated, analog, SD card-filled experience; step into the future of modern 3D printing" + ] + } +} diff --git a/kiauh/extensions/simply_print/simply_print_extension.py b/kiauh/extensions/simply_print/simply_print_extension.py new file mode 100644 index 0000000..e62c0d3 --- /dev/null +++ b/kiauh/extensions/simply_print/simply_print_extension.py @@ -0,0 +1,131 @@ +# ======================================================================= # +# 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 typing import List + +from components.moonraker.moonraker import Moonraker +from core.instance_manager.instance_manager import InstanceManager +from core.logger import DialogType, Logger +from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( + SimpleConfigParser, +) +from extensions.base_extension import BaseExtension +from utils.common import backup_printer_config_dir, moonraker_exists +from utils.input_utils import get_confirm + + +# noinspection PyMethodMayBeStatic +class SimplyPrintExtension(BaseExtension): + def install_extension(self, **kwargs) -> None: + Logger.print_status("Installing SimplyPrint ...") + + if not (mr_instances := moonraker_exists("SimplyPrint Installer")): + return + + Logger.print_dialog( + DialogType.INFO, + self._construct_dialog(mr_instances, True), + ) + + if not get_confirm( + "Continue SimplyPrint installation?", + default_choice=True, + allow_go_back=True, + ): + Logger.print_info("Exiting SimplyPrint installation ...") + return + + try: + self._patch_moonraker_confs(mr_instances, True) + + except Exception as e: + Logger.print_error(f"Error during SimplyPrint installation:\n{e}") + + def remove_extension(self, **kwargs) -> None: + Logger.print_status("Removing SimplyPrint ...") + + if not (mr_instances := moonraker_exists("SimplyPrint Uninstaller")): + return + + Logger.print_dialog( + DialogType.INFO, + self._construct_dialog(mr_instances, False), + ) + + if not get_confirm( + "Do you really want to uninstall SimplyPrint?", + default_choice=True, + allow_go_back=True, + ): + Logger.print_info("Exiting SimplyPrint uninstallation ...") + return + + try: + self._patch_moonraker_confs(mr_instances, False) + + except Exception as e: + Logger.print_error(f"Error during SimplyPrint installation:\n{e}") + + def _construct_dialog( + self, mr_instances: List[Moonraker], is_install: bool + ) -> List[str]: + mr_names = [f"● {m.service_file_path.name}" for m in mr_instances] + _type = "install" if is_install else "uninstall" + + return [ + "The following Moonraker instances were found:", + *mr_names, + "\n\n", + f"The setup will {_type} SimplyPrint for all Moonraker instances. " + f"After {_type}ation, all Moonraker services will be restarted!", + ] + + def _patch_moonraker_confs( + self, mr_instances: List[Moonraker], is_install: bool + ) -> None: + section = "simplyprint" + _type, _ft = ("Adding", "to") if is_install else ("Removing", "from") + + patched_files = [] + for moonraker in mr_instances: + Logger.print_status( + f"{_type} section 'simplyprint' {_ft} {moonraker.cfg_file} ..." + ) + scp = SimpleConfigParser() + scp.read_file(moonraker.cfg_file) + + install_and_has_section = is_install and scp.has_section(section) + uninstall_and_has_no_section = not is_install and not scp.has_section( + section + ) + + if install_and_has_section or uninstall_and_has_no_section: + status = "already" if is_install else "does not" + Logger.print_info( + f"Section 'simplyprint' {status} exists! Skipping ..." + ) + continue + + if is_install and not scp.has_section("simplyprint"): + backup_printer_config_dir() + scp.add_section(section) + elif not is_install and scp.has_section("simplyprint"): + backup_printer_config_dir() + scp.remove_section(section) + scp.write_file(moonraker.cfg_file) + patched_files.append(moonraker.cfg_file) + + if patched_files: + InstanceManager.restart_all(mr_instances) + + install_state = "successfully" if patched_files else "was already" + Logger.print_dialog( + DialogType.SUCCESS, + [f"SimplyPrint {install_state} {'' if is_install else 'un'}installed!"], + center_content=True, + ) diff --git a/kiauh/utils/common.py b/kiauh/utils/common.py index 4b89e9c..796839d 100644 --- a/kiauh/utils/common.py +++ b/kiauh/utils/common.py @@ -14,10 +14,11 @@ from pathlib import Path from typing import Dict, List, Literal, Optional, Set from components.klipper.klipper import Klipper +from components.moonraker.moonraker import Moonraker from core.constants import ( COLOR_CYAN, GLOBAL_DEPS, - PRINTER_CFG_BACKUP_DIR, + PRINTER_DATA_BACKUP_DIR, RESET_FORMAT, ) from core.logger import DialogType, Logger @@ -142,23 +143,25 @@ def backup_printer_config_dir() -> None: instances: List[Klipper] = get_instances(Klipper) bm = BackupManager() + if not instances: + Logger.print_info("Unable to find directory to backup!") + Logger.print_info("Are there no Klipper instances installed?") + return + for instance in instances: - name = f"config-{instance.data_dir.name}" bm.backup_directory( - name, + instance.data_dir.name, source=instance.base.cfg_dir, - target=PRINTER_CFG_BACKUP_DIR, + target=PRINTER_DATA_BACKUP_DIR, ) -def moonraker_exists(name: str = "") -> bool: +def moonraker_exists(name: str = "") -> List[Moonraker]: """ Helper method to check if a Moonraker instance exists :param name: Optional name of an installer where the check is performed :return: True if at least one Moonraker instance exists, False otherwise """ - from components.moonraker.moonraker import Moonraker - mr_instances: List[Moonraker] = get_instances(Moonraker) info = ( @@ -175,8 +178,8 @@ def moonraker_exists(name: str = "") -> bool: f"{info}. Please install Moonraker first!", ], ) - return False - return True + return [] + return mr_instances def trunc_string(input_str: str, length: int) -> str: