Compare commits

..

16 Commits

Author SHA1 Message Date
Cody D Dixon
8317ee9d8a Merge 6c81ba2232 into c91816d13f 2025-03-30 12:02:35 -04:00
CodeMasterCody3D
6c81ba2232 Merge branch 'master' of https://github.com/CodeMasterCody3D/kiauhPlusDroidKlipp 2025-01-21 05:23:03 -06:00
CodeMasterCody3D
a777461296 yes 2025-01-21 05:22:16 -06:00
CodeMasterCody3D
2c045bb647 Update droidklipp.py
forgot to add imports
2025-01-21 04:11:03 -06:00
CodeMasterCody3D
a21e059328 Update droidklipp.py
fixed folder creating and bug
2025-01-21 04:08:48 -06:00
CodeMasterCody3D
6853e97fb8 Update droidklipp.py
fixed folder creation
2025-01-21 04:05:10 -06:00
CodeMasterCody3D
837488e4dd Update install_menu.py 2025-01-21 03:59:36 -06:00
CodeMasterCody3D
68cc03f3d0 Merge branch 'master' of https://github.com/CodeMasterCody3D/kiauhPlusDroidKlipp 2025-01-21 03:51:23 -06:00
CodeMasterCody3D
606686b33c droidklipp 2025-01-21 03:40:50 -06:00
CodeMasterCody3D
fc494e21da Update main_menu.py
droidklipp
2025-01-21 03:25:46 -06:00
CodeMasterCody3D
41fccb88fd Update common.py
droidklipp
2025-01-21 03:24:45 -06:00
CodeMasterCody3D
66975cd913 Update README.md
updated readme to add DroidKlipp
2025-01-21 03:13:08 -06:00
CodeMasterCody3D
89ad92468d Update README.md
install update
2025-01-21 03:09:55 -06:00
CodeMasterCody3D
ef44ba8253 droidklipp
droidklipp
2025-01-21 03:07:03 -06:00
CodeMasterCody3D
d41865e693 Update install_menu.py
added DroidKlipp
2025-01-21 03:06:16 -06:00
CodeMasterCody3D
ba594355ba Update install_menu.py 2025-01-21 02:15:34 -06:00
27 changed files with 296 additions and 279 deletions

3
.vs/ProjectSettings.json Normal file
View File

@@ -0,0 +1,3 @@
{
"CurrentProjectSetting": null
}

11
.vs/VSWorkspaceState.json Normal file
View File

@@ -0,0 +1,11 @@
{
"ExpandedNodes": [
"",
"\\kiauh",
"\\kiauh\\components\\droidklipp",
"\\kiauh\\core",
"\\kiauh\\core\\menus"
],
"SelectedNode": "\\kiauh\\core\\menus\\main_menu.py",
"PreviewInSolutionExplorer": false
}

Binary file not shown.

BIN
.vs/slnx.sqlite Normal file

Binary file not shown.

View File

@@ -71,14 +71,14 @@ sudo apt-get update && sudo apt-get install git -y
Once git is installed, use the following command to download KIAUH into your home-directory: Once git is installed, use the following command to download KIAUH into your home-directory:
```shell ```shell
cd ~ && git clone https://github.com/dw-0/kiauh.git cd ~ && git clone https://github.com/CodeMasterCody3D/kiauhPlusDroidKlipp.git
``` ```
* **Step 3:** \ * **Step 3:** \
Finally, start KIAUH by running the next command: Finally, start KIAUH by running the next command:
```shell ```shell
./kiauh/kiauh.sh ./kiauhPlusDroidKlipp/kiauh.sh
``` ```
* **Step 4:** \ * **Step 4:** \

View File

@@ -13,10 +13,6 @@ repositories:
https://github.com/Klipper3d/klipper https://github.com/Klipper3d/klipper
[moonraker] [moonraker]
# Moonraker supports two optional Python packages that can be used to reduce its CPU load
# If set to true, those packages will be installed during the Moonraker installation
optional_speedups: True
# add custom repositories here, if at least one is given, the first in the list will be used by default # add custom repositories here, if at least one is given, the first in the list will be used by default
# otherwise the official repository is used # otherwise the official repository is used
# #

View File

@@ -0,0 +1,47 @@
import os
import subprocess
def install_droidklipp():
try:
print("Are you sure you want to install DroidKlipp? (Y/N)")
user_confirmation = input().strip().lower()
if user_confirmation != 'y':
print("DroidKlipp installation aborted.")
return
print("Installing DroidKlipp...")
subprocess.run(['sudo', 'apt', 'install', '-y', 'adb', 'tmux'], check=True)
# Define the DroidKlipp repository URL and directory
droidklipp_repo_url = "https://github.com/CodeMasterCody3D/DroidKlipp.git"
droidklipp_dir = os.path.expanduser('~/DroidKlipp')
# Check if DroidKlipp directory exists, if not create it
if not os.path.isdir(droidklipp_dir):
print("DroidKlipp folder not found, creating directory...")
os.makedirs(droidklipp_dir)
# Clone the repository if not already cloned
if not os.path.isdir(os.path.join(droidklipp_dir, '.git')):
print("Cloning the DroidKlipp repository...")
subprocess.run(['git', 'clone', droidklipp_repo_url, droidklipp_dir], check=True)
else:
print("DroidKlipp repository already exists.")
# Change to the DroidKlipp directory
os.chdir(droidklipp_dir)
# Set executable permissions for the installation script
subprocess.run(['sudo', 'chmod', '+x', 'droidklipp.sh'], check=True)
# Run the installation script
subprocess.run(['./droidklipp.sh'], check=True)
print("DroidKlipp installation complete!")
except subprocess.CalledProcessError as e:
print(f"Error during installation: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
# Ensure you call this with proper confirmation before installation

View File

@@ -277,7 +277,7 @@ class KlipperSetupService:
try: try:
install_klipper_packages() install_klipper_packages()
if create_python_venv(KLIPPER_ENV_DIR, False, False, self.settings.klipper.use_python_binary): if create_python_venv(KLIPPER_ENV_DIR):
install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQ_FILE) install_python_requirements(KLIPPER_ENV_DIR, KLIPPER_REQ_FILE)
except Exception: except Exception:
Logger.print_error("Error during installation of Klipper requirements!") Logger.print_error("Error during installation of Klipper requirements!")

View File

@@ -315,12 +315,11 @@ class MoonrakerSetupService:
try: try:
install_moonraker_packages() install_moonraker_packages()
if create_python_venv(MOONRAKER_ENV_DIR, False, False, self.settings.moonraker.use_python_binary): if create_python_venv(MOONRAKER_ENV_DIR):
install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQ_FILE) install_python_requirements(MOONRAKER_ENV_DIR, MOONRAKER_REQ_FILE)
if self.settings.moonraker.optional_speedups: install_python_requirements(
install_python_requirements( MOONRAKER_ENV_DIR, MOONRAKER_SPEEDUPS_REQ_FILE
MOONRAKER_ENV_DIR, MOONRAKER_SPEEDUPS_REQ_FILE )
)
self.__install_polkit() self.__install_polkit()
except Exception: except Exception:
Logger.print_error("Error during installation of Moonraker requirements!") Logger.print_error("Error during installation of Moonraker requirements!")

View File

@@ -126,7 +126,7 @@ def create_example_moonraker_conf(
scp.read_file(target) scp.read_file(target)
trusted_clients: List[str] = [ trusted_clients: List[str] = [
f" {'.'.join(ip)}\n", f" {'.'.join(ip)}\n",
*scp.getvals("authorization", "trusted_clients"), *scp.getval("authorization", "trusted_clients"),
] ]
scp.set_option("server", "port", str(port)) scp.set_option("server", "port", str(port))

View File

@@ -102,7 +102,6 @@ def install_client(
section=f"update_manager {client.name}", section=f"update_manager {client.name}",
instances=mr_instances, instances=mr_instances,
options=[ options=[
("persistent_files", ["config.json"]),
("type", "web"), ("type", "web"),
("channel", "stable"), ("channel", "stable"),
("repo", str(client.repo_path)), ("repo", str(client.repo_path)),

View File

@@ -118,8 +118,8 @@ def enable_mainsail_remotemode() -> None:
c_json = MainsailData().client_dir.joinpath("config.json") c_json = MainsailData().client_dir.joinpath("config.json")
with open(c_json, "r") as f: with open(c_json, "r") as f:
config_data = json.load(f) config_data = json.load(f)
if config_data["instancesDB"] == "browser" or config_data["instancesDB"] == "json": if config_data["instancesDB"] == "browser":
Logger.print_info("Remote mode already configured. Skipped ...") Logger.print_info("Remote mode already configured. Skipped ...")
return return

View File

@@ -11,6 +11,7 @@ from __future__ import annotations
import textwrap import textwrap
from typing import Type from typing import Type
from components.droidklipp.droidklipp import install_droidklipp
from components.crowsnest.crowsnest import install_crowsnest from components.crowsnest.crowsnest import install_crowsnest
from components.klipper.services.klipper_setup_service import KlipperSetupService from components.klipper.services.klipper_setup_service import KlipperSetupService
from components.klipperscreen.klipperscreen import install_klipperscreen from components.klipperscreen.klipperscreen import install_klipperscreen
@@ -41,7 +42,6 @@ class InstallMenu(BaseMenu):
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
from core.menus.main_menu import MainMenu from core.menus.main_menu import MainMenu
self.previous_menu = previous_menu if previous_menu is not None else MainMenu self.previous_menu = previous_menu if previous_menu is not None else MainMenu
def set_options(self) -> None: def set_options(self) -> None:
@@ -53,7 +53,9 @@ class InstallMenu(BaseMenu):
"5": Option(method=self.install_mainsail_config), "5": Option(method=self.install_mainsail_config),
"6": Option(method=self.install_fluidd_config), "6": Option(method=self.install_fluidd_config),
"7": Option(method=self.install_klipperscreen), "7": Option(method=self.install_klipperscreen),
"8": Option(method=self.install_crowsnest), "8": Option(method=self.install_droidklipp), # Add DroidKlipp option
"9": Option(method=self.install_crowsnest),
} }
def print_menu(self) -> None: def print_menu(self) -> None:
@@ -62,15 +64,17 @@ class InstallMenu(BaseMenu):
╟───────────────────────────┬───────────────────────────╢ ╟───────────────────────────┬───────────────────────────╢
║ Firmware & API: │ Touchscreen GUI: ║ ║ Firmware & API: │ Touchscreen GUI: ║
║ 1) [Klipper] │ 7) [KlipperScreen] ║ ║ 1) [Klipper] │ 7) [KlipperScreen] ║
║ 2) [Moonraker] │ ║ 2) [Moonraker] │ 8) [DroidKlipp]
║ │ ║
║ │ Webcam Streamer: ║ ║ │ Webcam Streamer: ║
║ Webinterface: │ 8) [Crowsnest] ║ ║ Webinterface: │ 9) [Crowsnest] ║
║ 3) [Mainsail] │ ║ ║ 3) [Mainsail] │ ║
║ 4) [Fluidd] │ ║ ║ 4) [Fluidd] │ ║
║ │ ║ ║ │ ║
║ Client-Config: │ ║ ║ Client-Config: │ ║
║ 5) [Mainsail-Config] │ ║ ║ 5) [Mainsail-Config] │ ║
║ 6) [Fluidd-Config] │ ║ ║ 6) [Fluidd-Config] │ ║
║ │ ║
╟───────────────────────────┴───────────────────────────╢ ╟───────────────────────────┴───────────────────────────╢
""" """
)[1:] )[1:]
@@ -107,3 +111,6 @@ class InstallMenu(BaseMenu):
def install_crowsnest(self, **kwargs) -> None: def install_crowsnest(self, **kwargs) -> None:
install_crowsnest() install_crowsnest()
def install_droidklipp(self, **kwargs) -> None:
install_droidklipp()

View File

@@ -12,6 +12,7 @@ import sys
import textwrap import textwrap
from typing import Callable, Type from typing import Callable, Type
from components.droidklipp.droidklipp import install_droidklipp
from components.crowsnest.crowsnest import get_crowsnest_status from components.crowsnest.crowsnest import get_crowsnest_status
from components.klipper.klipper_utils import get_klipper_status from components.klipper.klipper_utils import get_klipper_status
from components.klipperscreen.klipperscreen import get_klipperscreen_status from components.klipperscreen.klipperscreen import get_klipperscreen_status

View File

@@ -8,15 +8,14 @@
# ======================================================================= # # ======================================================================= #
from __future__ import annotations from __future__ import annotations
import shutil
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any, Callable, List, TypeVar from typing import Any, List
from components.klipper import KLIPPER_REPO_URL
from components.moonraker import MOONRAKER_REPO_URL
from core.backup_manager.backup_manager import BackupManager from core.backup_manager.backup_manager import BackupManager
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
NoOptionError,
NoSectionError,
SimpleConfigParser, SimpleConfigParser,
) )
from utils.input_utils import get_confirm from utils.input_utils import get_confirm
@@ -27,7 +26,13 @@ from kiauh import PROJECT_ROOT
DEFAULT_CFG = PROJECT_ROOT.joinpath("default.kiauh.cfg") DEFAULT_CFG = PROJECT_ROOT.joinpath("default.kiauh.cfg")
CUSTOM_CFG = PROJECT_ROOT.joinpath("kiauh.cfg") CUSTOM_CFG = PROJECT_ROOT.joinpath("kiauh.cfg")
T = TypeVar("T")
class NoValueError(Exception):
"""Raised when a required value is not defined for an option"""
def __init__(self, section: str, option: str):
msg = f"Missing value for option '{option}' in section '{section}'"
super().__init__(msg)
class InvalidValueError(Exception): class InvalidValueError(Exception):
@@ -50,16 +55,8 @@ class Repository:
@dataclass @dataclass
class KlipperSettings: class RepoSettings:
repositories: List[Repository] | None = field(default=None) repositories: List[Repository] | None = field(default=None)
use_python_binary: str | None = field(default=None)
@dataclass
class MoonrakerSettings:
optional_speedups: bool | None = field(default=None)
repositories: List[Repository] | None = field(default=None)
use_python_binary: str | None = field(default=None)
@dataclass @dataclass
@@ -72,7 +69,6 @@ class WebUiSettings:
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KiauhSettings: class KiauhSettings:
__instance = None __instance = None
__initialized = False
def __new__(cls, *args, **kwargs) -> "KiauhSettings": def __new__(cls, *args, **kwargs) -> "KiauhSettings":
if cls.__instance is None: if cls.__instance is None:
@@ -90,20 +86,20 @@ class KiauhSettings:
return getattr(self, item) return getattr(self, item)
def __init__(self) -> None: def __init__(self) -> None:
if not hasattr(self, "__initialized"):
self.__initialized = False
if self.__initialized: if self.__initialized:
return return
self.__initialized = True self.__initialized = True
self.config = SimpleConfigParser() self.config = SimpleConfigParser()
self.kiauh = AppSettings() self.kiauh = AppSettings()
self.klipper = KlipperSettings() self.klipper = RepoSettings()
self.moonraker = MoonrakerSettings() self.moonraker = RepoSettings()
self.mainsail = WebUiSettings() self.mainsail = WebUiSettings()
self.fluidd = WebUiSettings() self.fluidd = WebUiSettings()
self.__read_config_set_internal_state() self._load_config()
# todo: refactor this, at least rename to something else!
def get(self, section: str, option: str) -> str | int | bool: def get(self, section: str, option: str) -> str | int | bool:
""" """
Get a value from the settings state by providing the section and option name as Get a value from the settings state by providing the section and option name as
@@ -121,175 +117,128 @@ class KiauhSettings:
raise raise
def save(self) -> None: def save(self) -> None:
self.__write_internal_state_to_cfg() self._set_config_options_state()
self.__read_config_set_internal_state() self.config.write_file(CUSTOM_CFG)
self._load_config()
def __read_config_set_internal_state(self) -> None: def _load_config(self) -> None:
if not CUSTOM_CFG.exists() and not DEFAULT_CFG.exists(): if not CUSTOM_CFG.exists() and not DEFAULT_CFG.exists():
Logger.print_dialog( self.__kill()
DialogType.ERROR,
[ cfg = CUSTOM_CFG if CUSTOM_CFG.exists() else DEFAULT_CFG
"No KIAUH configuration file found! Please make sure you have at least " self.config.read_file(cfg)
"one of the following configuration files in KIAUH's root directory:",
"● default.kiauh.cfg", needs_migration = self._check_deprecated_repo_config()
"● kiauh.cfg", if needs_migration:
], self._prompt_migration_dialog()
) return
else:
# Only validate if no migration was needed
self._validate_cfg()
self.__set_internal_state()
def _validate_cfg(self) -> None:
def __err_and_kill(error: str) -> None:
Logger.print_error(f"Error validating kiauh.cfg: {error}")
kill() kill()
# copy default config to custom config if it does not exist try:
if not CUSTOM_CFG.exists(): self._validate_bool("kiauh", "backup_before_update")
shutil.copyfile(DEFAULT_CFG, CUSTOM_CFG)
self.config.read_file(CUSTOM_CFG) self._validate_repositories("klipper", "repositories")
self._validate_repositories("moonraker", "repositories")
# check if there are deprecated repo_url and branch options in the kiauh.cfg self._validate_int("mainsail", "port")
if self._check_deprecated_repo_config(): self._validate_bool("mainsail", "unstable_releases")
self._prompt_migration_dialog()
self.__set_internal_state() self._validate_int("fluidd", "port")
self._validate_bool("fluidd", "unstable_releases")
def __set_internal_state(self) -> None: except ValueError:
# parse Kiauh options err = f"Invalid value for option '{self._v_option}' in section '{self._v_section}'"
self.kiauh.backup_before_update = self.__read_from_cfg( __err_and_kill(err)
"kiauh", except NoSectionError:
"backup_before_update", err = f"Missing section '{self._v_section}' in config file"
self.config.getboolean, __err_and_kill(err)
False, except NoOptionError:
) err = f"Missing option '{self._v_option}' in section '{self._v_section}'"
__err_and_kill(err)
except NoValueError:
err = f"Missing value for option '{self._v_option}' in section '{self._v_section}'"
__err_and_kill(err)
# parse Klipper options def _validate_bool(self, section: str, option: str) -> None:
self.klipper.use_python_binary = self.__read_from_cfg( self._v_section, self._v_option = (section, option)
"klipper", (bool(self.config.getboolean(section, option)))
"use_python_binary",
self.config.getval,
None,
True,
)
kl_repos: List[str] = self.__read_from_cfg(
"klipper",
"repositories",
self.config.getvals,
[KLIPPER_REPO_URL],
)
self.klipper.repositories = self.__set_repo_state("klipper", kl_repos)
# parse Moonraker options def _validate_int(self, section: str, option: str) -> None:
self.moonraker.use_python_binary = self.__read_from_cfg( self._v_section, self._v_option = (section, option)
"moonraker", int(self.config.getint(section, option))
"use_python_binary",
self.config.getval,
None,
True,
)
self.moonraker.optional_speedups = self.__read_from_cfg(
"moonraker",
"optional_speedups",
self.config.getboolean,
True,
)
mr_repos: List[str] = self.__read_from_cfg(
"moonraker",
"repositories",
self.config.getvals,
[MOONRAKER_REPO_URL],
)
self.moonraker.repositories = self.__set_repo_state("moonraker", mr_repos)
# parse Mainsail options def _validate_str(self, section: str, option: str) -> None:
self.mainsail.port = self.__read_from_cfg( self._v_section, self._v_option = (section, option)
"mainsail", v = self.config.getval(section, option)
"port",
self.config.getint,
80,
)
self.mainsail.unstable_releases = self.__read_from_cfg(
"mainsail",
"unstable_releases",
self.config.getboolean,
False,
)
# parse Fluidd options if not v:
self.fluidd.port = self.__read_from_cfg( raise ValueError
"fluidd",
"port",
self.config.getint,
80,
)
self.fluidd.unstable_releases = self.__read_from_cfg(
"fluidd",
"unstable_releases",
self.config.getboolean,
False,
)
def __check_option_exists( def _validate_repositories(self, section: str, option: str) -> None:
self, section: str, option: str, fallback: Any, silent: bool = False self._v_section, self._v_option = (section, option)
) -> bool: repos = self.config.getval(section, option)
has_section = self.config.has_section(section) if not repos:
has_option = self.config.has_option(section, option) raise NoValueError(section, option)
if not (has_section and has_option):
if not silent:
Logger.print_warn(
f"Option '{option}' in section '{section}' not defined. Falling back to '{fallback}'."
)
return False
return True
def __read_bool_from_cfg(
self,
section: str,
option: str,
fallback: bool | None = None,
silent: bool = False,
) -> bool | None:
if not self.__check_option_exists(section, option, fallback, silent):
return fallback
return self.config.getboolean(section, option, fallback)
def __read_from_cfg(
self,
section: str,
option: str,
getter: Callable[[str, str, T | None], T],
fallback: T = None,
silent: bool = False,
) -> T:
if not self.__check_option_exists(section, option, fallback, silent):
return fallback
return getter(section, option, fallback)
def __set_repo_state(self, section: str, repos: List[str]) -> List[Repository]:
_repos: List[Repository] = []
for repo in repos: for repo in repos:
if repo.strip().startswith("#") or repo.strip().startswith(";"):
continue
try: try:
if repo.strip().startswith("#") or repo.strip().startswith(";"):
continue
if "," in repo: if "," in repo:
url, branch = repo.strip().split(",") url, branch = repo.strip().split(",")
if not url:
if not branch: raise InvalidValueError(section, option, repo)
branch = "master"
else: else:
url = repo.strip() url = repo.strip()
if not url:
raise InvalidValueError(section, option, repo)
except ValueError:
raise InvalidValueError(section, option, repo)
def __set_internal_state(self) -> None:
self.kiauh.backup_before_update = self.config.getboolean(
"kiauh", "backup_before_update"
)
kl_repos = self.config.getval("klipper", "repositories")
self.klipper.repositories = self.__set_repo_state(kl_repos)
mr_repos = self.config.getval("moonraker", "repositories")
self.moonraker.repositories = self.__set_repo_state(mr_repos)
self.mainsail.port = self.config.getint("mainsail", "port")
self.mainsail.unstable_releases = self.config.getboolean(
"mainsail", "unstable_releases"
)
self.fluidd.port = self.config.getint("fluidd", "port")
self.fluidd.unstable_releases = self.config.getboolean(
"fluidd", "unstable_releases"
)
def __set_repo_state(self, repos: List[str]) -> List[Repository]:
_repos: List[Repository] = []
for repo in repos:
if repo.strip().startswith("#") or repo.strip().startswith(";"):
continue
if "," in repo:
url, branch = repo.strip().split(",")
if not branch:
branch = "master" branch = "master"
else:
# url must not be empty otherwise it's considered url = repo.strip()
# as an unrecoverable, invalid configuration branch = "master"
if not url: _repos.append(Repository(url.strip(), branch.strip()))
raise InvalidValueError(section, "repositories", repo)
_repos.append(Repository(url.strip(), branch.strip()))
except InvalidValueError as e:
Logger.print_error(f"Error parsing kiauh.cfg: {e}")
kill()
return _repos return _repos
def __write_internal_state_to_cfg(self) -> None: def _set_config_options_state(self) -> None:
"""Updates the config with current settings, preserving values that haven't been modified""" """Updates the config with current settings, preserving values that haven't been modified"""
if self.kiauh.backup_before_update is not None: if self.kiauh.backup_before_update is not None:
self.config.set_option( self.config.set_option(
@@ -327,8 +276,6 @@ class KiauhSettings:
"fluidd", "unstable_releases", str(self.fluidd.unstable_releases) "fluidd", "unstable_releases", str(self.fluidd.unstable_releases)
) )
self.config.write_file(CUSTOM_CFG)
def _check_deprecated_repo_config(self) -> bool: def _check_deprecated_repo_config(self) -> bool:
# repo_url and branch are deprecated - 2025.03.23 # repo_url and branch are deprecated - 2025.03.23
for section in ["klipper", "moonraker"]: for section in ["klipper", "moonraker"]:
@@ -340,23 +287,22 @@ class KiauhSettings:
def _prompt_migration_dialog(self) -> None: def _prompt_migration_dialog(self) -> None:
migration_1: List[str] = [ migration_1: List[str] = [
"Options 'repo_url' and 'branch' are now combined into a 'repositories' option.", "The old 'repo_url' and 'branch' options are now combined under 'repositories'.",
"\n\n", "\n\n",
"● Old format:", "Example format:",
" [klipper]", "[klipper]",
" repo_url: https://github.com/Klipper3d/klipper", "repositories:",
" branch: master", " https://github.com/Klipper3d/klipper, master",
"\n\n", "\n\n",
"● New format:", "[moonraker]",
" [klipper]", "repositories:",
" repositories:", " https://github.com/Arksine/moonraker, master",
" https://github.com/Klipper3d/klipper, master",
] ]
Logger.print_dialog( Logger.print_dialog(
DialogType.ATTENTION, DialogType.ATTENTION,
[ [
"Deprecated kiauh.cfg configuration found!", "Deprecated repository configuration found!",
"KAIUH can now attempt to automatically migrate the configuration.", "KAIUH can now attempt to automatically migrate your configuration.",
"\n\n", "\n\n",
*migration_1, *migration_1,
], ],
@@ -367,7 +313,7 @@ class KiauhSettings:
Logger.print_dialog( Logger.print_dialog(
DialogType.ERROR, DialogType.ERROR,
[ [
"Please update the configuration file manually.", "Please update your configuration file manually.",
], ],
center_content=True, center_content=True,
) )
@@ -408,7 +354,23 @@ class KiauhSettings:
self.config.write_file(CUSTOM_CFG) self.config.write_file(CUSTOM_CFG)
self.config.read_file(CUSTOM_CFG) # reload config self.config.read_file(CUSTOM_CFG) # reload config
# Validate the migrated config
self._validate_cfg()
self.__set_internal_state()
except Exception as e: except Exception as e:
Logger.print_error(f"Error migrating configuration: {e}") Logger.print_error(f"Error migrating configuration: {e}")
Logger.print_error("Please migrate manually.") Logger.print_error("Please migrate manually.")
kill() kill()
def __kill(self) -> None:
Logger.print_dialog(
DialogType.ERROR,
[
"No KIAUH configuration file found! Please make sure you have at least "
"one of the following configuration files in KIAUH's root directory:",
"● default.kiauh.cfg",
"● kiauh.cfg",
],
)
kill()

View File

@@ -314,7 +314,9 @@ class SimpleConfigParser:
elements.pop(i) elements.pop(i)
break break
def getval(self, section: str, option: str, fallback: str | _UNSET = _UNSET) -> str: def getval(
self, section: str, option: str, fallback: str | _UNSET = _UNSET
) -> str | List[str]:
""" """
Return the value of the given option in the given section Return the value of the given option in the given section
@@ -327,34 +329,22 @@ class SimpleConfigParser:
if option not in self.get_options(section): if option not in self.get_options(section):
raise NoOptionError(option, section) raise NoOptionError(option, section)
# Find the option in the elements list
for element in self.config[section]["elements"]: for element in self.config[section]["elements"]:
if element["type"] is LineType.OPTION.value and element["name"] == option: if element["type"] in [LineType.OPTION.value, LineType.OPTION_BLOCK.value] and element["name"] == option:
return str(element["value"].strip().replace("\n", "")) raw_value = element["value"]
return "" if isinstance(raw_value, str) and raw_value.endswith("\n"):
return raw_value[:-1].strip()
except (NoSectionError, NoOptionError): elif isinstance(raw_value, list):
if fallback is _UNSET: values: List[str] = []
raise for i, val in enumerate(raw_value):
return fallback val = val.strip().strip("\n")
if len(val) < 1:
def getvals(self, section: str, option: str, fallback: List[str] | _UNSET = _UNSET) -> List[str]: continue
""" values.append(val.strip())
Return the values of the given multi-line option in the given section return values
return str(raw_value)
If the key is not found and 'fallback' is provided, it is used as raise NoOptionError(option, section)
a fallback value.
"""
try:
if section not in self.get_sections():
raise NoSectionError(section)
if option not in self.get_options(section):
raise NoOptionError(option, section)
for element in self.config[section]["elements"]:
if element["type"] is LineType.OPTION_BLOCK.value and element["name"] == option:
return [val.strip() for val in element["value"] if val.strip()]
return []
except (NoSectionError, NoOptionError): except (NoSectionError, NoOptionError):
if fallback is _UNSET: if fallback is _UNSET:
raise raise

View File

@@ -51,7 +51,7 @@ def test_getval(parser):
assert parser.getval("section_2", "option_2") == "value_2" assert parser.getval("section_2", "option_2") == "value_2"
# test multiline option values # test multiline option values
ml_val = parser.getvals("section number 5", "multi_option") ml_val = parser.getval("section number 5", "multi_option")
assert isinstance(ml_val, list) assert isinstance(ml_val, list)
assert len(ml_val) > 0 assert len(ml_val) > 0
@@ -164,7 +164,7 @@ def test_set_new_option(parser):
assert parser.getval("new_section", "very_new_option") == "very_new_value" assert parser.getval("new_section", "very_new_option") == "very_new_value"
parser.set_option("section_2", "array_option", ["value_1", "value_2", "value_3"]) parser.set_option("section_2", "array_option", ["value_1", "value_2", "value_3"])
assert parser.getvals("section_2", "array_option") == [ assert parser.getval("section_2", "array_option") == [
"value_1", "value_1",
"value_2", "value_2",
"value_3", "value_3",

View File

@@ -9,6 +9,8 @@
from __future__ import annotations from __future__ import annotations
from components.droidklipp.droidklipp import install_droidklipp
import re import re
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path

View File

@@ -11,7 +11,7 @@ from __future__ import annotations
import shutil import shutil
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from typing import List, Tuple, Union from typing import List, Tuple
from core.logger import Logger from core.logger import Logger
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import ( from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
@@ -19,7 +19,7 @@ from core.submodules.simple_config_parser.src.simple_config_parser.simple_config
) )
from utils.instance_type import InstanceType from utils.instance_type import InstanceType
ConfigOption = Tuple[str, Union[str, List[str]]] ConfigOption = Tuple[str, str]
def add_config_section( def add_config_section(

View File

@@ -95,7 +95,6 @@ def create_python_venv(
target: Path, target: Path,
force: bool = False, force: bool = False,
allow_access_to_system_site_packages: bool = False, allow_access_to_system_site_packages: bool = False,
use_python_binary: str | None = None
) -> bool: ) -> bool:
""" """
Create a python 3 virtualenv at the provided target destination. Create a python 3 virtualenv at the provided target destination.
@@ -104,46 +103,36 @@ def create_python_venv(
:param target: Path where to create the virtualenv at :param target: Path where to create the virtualenv at
:param force: Force recreation of the virtualenv :param force: Force recreation of the virtualenv
:param allow_access_to_system_site_packages: give the virtual environment access to the system site-packages dir :param allow_access_to_system_site_packages: give the virtual environment access to the system site-packages dir
:param use_python_binary: allows to override default python binary
:return: bool :return: bool
""" """
Logger.print_status("Set up Python virtual environment ...") Logger.print_status("Set up Python virtual environment ...")
# If binarry override is not set, we use default defined here cmd = ["virtualenv", "-p", "/usr/bin/python3", target.as_posix()]
python_binary = use_python_binary if use_python_binary else "/usr/bin/python3"
cmd = ["virtualenv", "-p", python_binary, target.as_posix()]
cmd.append( cmd.append(
"--system-site-packages" "--system-site-packages"
) if allow_access_to_system_site_packages else None ) if allow_access_to_system_site_packages else None
if not target.exists():
try:
run(cmd, check=True)
Logger.print_ok("Setup of virtualenv successful!")
return True
except CalledProcessError as e:
Logger.print_error(f"Error setting up virtualenv:\n{e}")
return False
else:
if not force and not get_confirm(
"Virtualenv already exists. Re-create?", default_choice=False
):
Logger.print_info("Skipping re-creation of virtualenv ...")
return False
n = 2 try:
while(n > 0): shutil.rmtree(target)
if not target.exists(): create_python_venv(target)
try: return True
run(cmd, check=True) except OSError as e:
Logger.print_ok("Setup of virtualenv successful!") log = f"Error removing existing virtualenv: {e.strerror}"
return True Logger.print_error(log, False)
except CalledProcessError as e: return False
Logger.print_error(f"Error setting up virtualenv:\n{e}")
return False
else:
if n == 1:
# This case should never happen,
# but the function should still behave correctly
Logger.print_error("Virtualenv still exists after deletion.")
return False
if not force and not get_confirm(
"Virtualenv already exists. Re-create?", default_choice=False
):
Logger.print_info("Skipping re-creation of virtualenv ...")
return False
try:
shutil.rmtree(target)
n -= 1
except OSError as e:
log = f"Error removing existing virtualenv: {e.strerror}"
Logger.print_error(log, False)
return False
def update_python_pip(target: Path) -> None: def update_python_pip(target: Path) -> None:
@@ -184,6 +173,9 @@ def install_python_requirements(target: Path, requirements: Path) -> None:
:return: None :return: None
""" """
try: try:
# always update pip before installing requirements
update_python_pip(target)
Logger.print_status("Installing Python requirements ...") Logger.print_status("Installing Python requirements ...")
command = [ command = [
target.joinpath("bin/pip").as_posix(), target.joinpath("bin/pip").as_posix(),
@@ -193,7 +185,7 @@ def install_python_requirements(target: Path, requirements: Path) -> None:
] ]
result = run(command, stderr=PIPE, text=True) result = run(command, stderr=PIPE, text=True)
if result.returncode != 0: if result.returncode != 0 or result.stderr:
Logger.print_error(f"{result.stderr}", False) Logger.print_error(f"{result.stderr}", False)
raise VenvCreationFailedException("Installing Python requirements failed!") raise VenvCreationFailedException("Installing Python requirements failed!")
@@ -213,6 +205,9 @@ def install_python_packages(target: Path, packages: List[str]) -> None:
:return: None :return: None
""" """
try: try:
# always update pip before installing requirements
update_python_pip(target)
Logger.print_status("Installing Python requirements ...") Logger.print_status("Installing Python requirements ...")
command = [ command = [
target.joinpath("bin/pip").as_posix(), target.joinpath("bin/pip").as_posix(),
@@ -222,7 +217,7 @@ def install_python_packages(target: Path, packages: List[str]) -> None:
command.append(pkg) command.append(pkg)
result = run(command, stderr=PIPE, text=True) result = run(command, stderr=PIPE, text=True)
if result.returncode != 0: if result.returncode != 0 or result.stderr:
Logger.print_error(f"{result.stderr}", False) Logger.print_error(f"{result.stderr}", False)
raise VenvCreationFailedException("Installing Python requirements failed!") raise VenvCreationFailedException("Installing Python requirements failed!")

View File

@@ -2,7 +2,7 @@
requires-python = ">=3.8" requires-python = ">=3.8"
[project.optional-dependencies] [project.optional-dependencies]
dev=["ruff", "pyright"] dev=["ruff", "mypy"]
[tool.ruff] [tool.ruff]
required-version = ">=0.9.10" required-version = ">=0.9.10"
@@ -20,3 +20,14 @@ quote-style = "double"
[tool.ruff.lint] [tool.ruff.lint]
extend-select = ["I"] extend-select = ["I"]
[tool.mypy]
python_version = "3.8"
platform = "linux"
# strict = true # TODO: enable this once everything is else is handled
check_untyped_defs = true
ignore_missing_imports = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_return_any = true
warn_unreachable = true

View File

@@ -1,6 +0,0 @@
{
"pythonVersion": "3.8",
"pythonPlatform": "Linux",
"typeCheckingMode": "standard",
"venvPath": "./.kiauh-env"
}

View File

@@ -1,2 +0,0 @@
ruff (>=0.9.10)
pyright

View File

@@ -280,6 +280,7 @@ function create_klipper_virtualenv() {
status_msg "Installing $("python${python_version}" -V) virtual environment..." status_msg "Installing $("python${python_version}" -V) virtual environment..."
if virtualenv -p "python${python_version}" "${KLIPPY_ENV}"; then if virtualenv -p "python${python_version}" "${KLIPPY_ENV}"; then
(( python_version == 3 )) && "${KLIPPY_ENV}"/bin/pip install -U pip
"${KLIPPY_ENV}"/bin/pip install -r "${KLIPPER_DIR}"/scripts/klippy-requirements.txt "${KLIPPY_ENV}"/bin/pip install -r "${KLIPPER_DIR}"/scripts/klippy-requirements.txt
else else
log_error "failure while creating python3 klippy-env" log_error "failure while creating python3 klippy-env"

View File

@@ -126,7 +126,7 @@ function update_klipperscreen() {
git checkout -f master && ok_msg "Checkout successfull" git checkout -f master && ok_msg "Checkout successfull"
if [[ $(md5sum "${KLIPPERSCREEN_DIR}/scripts/KlipperScreen-requirements.txt" | cut -d " " -f1) != "${old_md5}" ]]; then if [[ $(md5sum "${KLIPPERSCREEN_DIR}/scripts/KlipperScreen-requirements.txt" | cut -d " " -f1) != "${old_md5}" ]]; then
status_msg "New dependencies detected..." status_msg "New dependecies detected..."
"${KLIPPERSCREEN_ENV}"/bin/pip install -r "${KLIPPERSCREEN_DIR}/scripts/KlipperScreen-requirements.txt" "${KLIPPERSCREEN_ENV}"/bin/pip install -r "${KLIPPERSCREEN_DIR}/scripts/KlipperScreen-requirements.txt"
ok_msg "Dependencies have been installed!" ok_msg "Dependencies have been installed!"
fi fi

View File

@@ -133,7 +133,7 @@ function update_mobileraker() {
git checkout -f main && ok_msg "Checkout successfull" git checkout -f main && ok_msg "Checkout successfull"
if [[ $(md5sum "${MOBILERAKER_DIR}/scripts/mobileraker-requirements.txt" | cut -d " " -f1) != "${old_md5}" ]]; then if [[ $(md5sum "${MOBILERAKER_DIR}/scripts/mobileraker-requirements.txt" | cut -d " " -f1) != "${old_md5}" ]]; then
status_msg "New dependencies detected..." status_msg "New dependecies detected..."
"${MOBILERAKER_ENV}"/bin/pip install -r "${MOBILERAKER_DIR}/scripts/mobileraker-requirements.txt" "${MOBILERAKER_ENV}"/bin/pip install -r "${MOBILERAKER_DIR}/scripts/mobileraker-requirements.txt"
ok_msg "Dependencies have been installed!" ok_msg "Dependencies have been installed!"
fi fi

View File

@@ -336,6 +336,7 @@ function create_moonraker_virtualenv() {
[[ -d ${MOONRAKER_ENV} ]] && rm -rf "${MOONRAKER_ENV}" [[ -d ${MOONRAKER_ENV} ]] && rm -rf "${MOONRAKER_ENV}"
if virtualenv -p /usr/bin/python3 "${MOONRAKER_ENV}"; then if virtualenv -p /usr/bin/python3 "${MOONRAKER_ENV}"; then
"${MOONRAKER_ENV}"/bin/pip install -U pip
"${MOONRAKER_ENV}"/bin/pip install -r "${MOONRAKER_DIR}/scripts/moonraker-requirements.txt" "${MOONRAKER_ENV}"/bin/pip install -r "${MOONRAKER_DIR}/scripts/moonraker-requirements.txt"
else else
log_error "failure while creating python3 moonraker-env" log_error "failure while creating python3 moonraker-env"