feat: add SimplyPrint extension (#566)

* refactor: correctly sort extensions in extension menu

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* refactor: use different name for printer_data backup dir

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* refactor: change return type to List for moonraker_exists function

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

* feat: add SimplyPrint extension

Signed-off-by: Dominik Willner <th33xitus@gmail.com>

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
This commit is contained in:
dw-0
2024-10-13 09:35:15 +02:00
committed by GitHub
parent 43d6598be6
commit 55cfe124b2
6 changed files with 168 additions and 17 deletions

View File

@@ -33,7 +33,7 @@ CURRENT_USER = pwd.getpwuid(os.getuid())[0]
# dirs # dirs
SYSTEMD = Path("/etc/systemd/system") 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_AVAILABLE = Path("/etc/nginx/sites-available")
NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled") NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled")
NGINX_CONFD = Path("/etc/nginx/conf.d") NGINX_CONFD = Path("/etc/nginx/conf.d")

View File

@@ -58,12 +58,16 @@ class ExtensionsMenu(BaseMenu):
module_path = f"kiauh.extensions.{ext.name}.{module_name}" module_path = f"kiauh.extensions.{ext.name}.{module_name}"
# get the class name of the extension # get the class name of the extension
ext_class: Type[BaseExtension] = inspect.getmembers( module = importlib.import_module(module_path)
importlib.import_module(module_path),
predicate=lambda o: inspect.isclass(o) def predicate(o):
and issubclass(o, BaseExtension) return (
and o != BaseExtension, inspect.isclass(o)
)[0][1] 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 # instantiate the extension with its metadata and add to dict
ext_instance: BaseExtension = ext_class(metadata) ext_instance: BaseExtension = ext_class(metadata)
@@ -72,7 +76,7 @@ class ExtensionsMenu(BaseMenu):
except (IOError, json.JSONDecodeError, ImportError) as e: except (IOError, json.JSONDecodeError, ImportError) as e:
print(f"Failed loading extension {ext}: {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): def extension_submenu(self, **kwargs):
ExtensionSubmenu(kwargs.get("opt_data"), self.__class__).run() ExtensionSubmenu(kwargs.get("opt_data"), self.__class__).run()

View File

@@ -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"
]
}
}

View File

@@ -0,0 +1,131 @@
# ======================================================================= #
# Copyright (C) 2020 - 2024 Dominik Willner <th33xitus@gmail.com> #
# #
# 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,
)

View File

@@ -14,10 +14,11 @@ from pathlib import Path
from typing import Dict, List, Literal, Optional, Set from typing import Dict, List, Literal, Optional, Set
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker
from core.constants import ( from core.constants import (
COLOR_CYAN, COLOR_CYAN,
GLOBAL_DEPS, GLOBAL_DEPS,
PRINTER_CFG_BACKUP_DIR, PRINTER_DATA_BACKUP_DIR,
RESET_FORMAT, RESET_FORMAT,
) )
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
@@ -142,23 +143,25 @@ def backup_printer_config_dir() -> None:
instances: List[Klipper] = get_instances(Klipper) instances: List[Klipper] = get_instances(Klipper)
bm = BackupManager() 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: for instance in instances:
name = f"config-{instance.data_dir.name}"
bm.backup_directory( bm.backup_directory(
name, instance.data_dir.name,
source=instance.base.cfg_dir, 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 Helper method to check if a Moonraker instance exists
:param name: Optional name of an installer where the check is performed :param name: Optional name of an installer where the check is performed
:return: True if at least one Moonraker instance exists, False otherwise :return: True if at least one Moonraker instance exists, False otherwise
""" """
from components.moonraker.moonraker import Moonraker
mr_instances: List[Moonraker] = get_instances(Moonraker) mr_instances: List[Moonraker] = get_instances(Moonraker)
info = ( info = (
@@ -175,8 +178,8 @@ def moonraker_exists(name: str = "") -> bool:
f"{info}. Please install Moonraker first!", f"{info}. Please install Moonraker first!",
], ],
) )
return False return []
return True return mr_instances
def trunc_string(input_str: str, length: int) -> str: def trunc_string(input_str: str, length: int) -> str: