diff --git a/.gitignore b/.gitignore index e0b46ee..df72d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,27 @@ debug.log .idea .vscode __pycache__ + +# Build-Artefakte +build/ +dist/ +certs/ +*.spec.bak +lxtools_installer +lxtools_installer.AppImage +lxtools_installer_compat +build_compatible.sh +build_local.sh +clean_build.sh +start_builder.sh +test_extract.py +test_paths.py +test_resources.py +manager_fixed.py + +# Docker-Build +docker_build/ +debug_docker.sh +Dockerfile.nuitka +DOCKER_BUILD_ANLEITUNG.md +nuitka_builder.py diff --git a/Changelog b/Changelog index c1e20ba..c5f082a 100644 --- a/Changelog +++ b/Changelog @@ -2,10 +2,20 @@ Changelog for LXTools installer ## [Unreleased] - - language set auto detection - - + + ### Added +29.06.2025 + +- add methode sigi, clean_files and remove_lxtools_files + for remove files and dirs on close lxtools_installer + +- fix message dialog on font and padding + +- add methods check polkit and check Networkmanager is installed + and view in header is result false + ### Added 23-06-2025 diff --git a/locale/de/LC_MESSAGES/lxtoolsinstaller.mo b/locale/de/LC_MESSAGES/lxtoolsinstaller.mo index 27eb7b9..191cb60 100644 Binary files a/locale/de/LC_MESSAGES/lxtoolsinstaller.mo and b/locale/de/LC_MESSAGES/lxtoolsinstaller.mo differ diff --git a/lx-icons/16/settings.png b/lx-icons/16/settings.png new file mode 100644 index 0000000..5a09d74 Binary files /dev/null and b/lx-icons/16/settings.png differ diff --git a/lx-icons/16/wg_vpn.png b/lx-icons/16/wg_vpn.png new file mode 100644 index 0000000..0bef818 Binary files /dev/null and b/lx-icons/16/wg_vpn.png differ diff --git a/lxtools_installer.py b/lxtools_installer.py index 2935061..4a30842 100755 --- a/lxtools_installer.py +++ b/lxtools_installer.py @@ -2,6 +2,7 @@ import tkinter as tk from tkinter import ttk import os +import sys import subprocess import getpass from datetime import datetime @@ -37,12 +38,12 @@ class InstallationManager: """Install or update project""" project_info = self.app_manager.get_project_info(project_key) if not project_info: - raise Exception(f"{LocaleStrings.MSG["unknow_project"]}{project_key}") + raise Exception(f"{LocaleStrings.MSG['unknow_project']}{project_key}") self.update_progress( - f"{LocaleStrings.MSGI["start_install"]}{project_info['name']}..." + f"{LocaleStrings.MSGI['start_install']}{project_info['name']}..." ) - self.log(f"=== {LocaleStrings.MSGI["install"]}{project_info['name']} ===") + self.log(f"=== {LocaleStrings.MSGI['install']}{project_info['name']} ===") try: # Create installation script @@ -52,19 +53,19 @@ class InstallationManager: self._execute_install_script(script_content) self.update_progress( - f"{project_info["name"]}{LocaleStrings.MSGI["install_success"]}" + f"{project_info['name']}{LocaleStrings.MSGI['install_success']}" ) self.log( - f"=== {project_info["name"]}{LocaleStrings.MSGI["install_success"]} ===" + f"=== {project_info['name']}{LocaleStrings.MSGI['install_success']} ===" ) # Set success icon self.update_icon("success") except Exception as e: - self.log(f"ERROR: {LocaleStrings.MSGI["install_failed"]}{e}") + self.log(f"ERROR: {LocaleStrings.MSGI['install_failed']}{e}") self.update_icon("error") - raise Exception(f"{LocaleStrings.MSGI["install_failed"]}{e}") + raise Exception(f"{LocaleStrings.MSGI['install_failed']}{e}") def _create_install_script(self, project_key): """Create installation script based on project""" @@ -73,7 +74,7 @@ class InstallationManager: elif project_key == "logviewer": return self._create_logviewer_install_script() else: - raise Exception(f"{LocaleStrings.MSGI["unknow_project"]}{project_key}") + raise Exception(f"{LocaleStrings.MSGI['unknow_project']}{project_key}") def _create_wirepy_install_script(self): detected_os = Detector.get_os() @@ -331,7 +332,7 @@ echo "LogViewer installation completed!" # Make script executable os.chmod(script_file.name, 0o755) - self.log(f"{LocaleStrings.MSGI["install_create"]}{script_file.name}") + self.log(f"{LocaleStrings.MSGI['install_create']}{script_file.name}") # Execute with pkexec result = subprocess.run( @@ -352,13 +353,13 @@ echo "LogViewer installation completed!" if result.returncode != 0: raise Exception( - f"{LocaleStrings.MSGI["install_script_failed"]}{result.stderr}" + f"{LocaleStrings.MSGI['install_script_failed']}{result.stderr}" ) except subprocess.TimeoutExpired: raise Exception(LocaleStrings.MSGI["install_timeout"]) except subprocess.CalledProcessError as e: - raise Exception(f"{LocaleStrings.MSGI["install_script_failed"]}{e}") + raise Exception(f"{LocaleStrings.MSGI['install_script_failed']}{e}") def update_progress(self, message): if self.progress_callback: @@ -383,17 +384,17 @@ class UninstallationManager: """Uninstall project""" project_info = self.app_manager.get_project_info(project_key) if not project_info: - raise Exception(f"{LocaleStrings.MSGO["unknow_project"]}{project_key}") + raise Exception(f"{LocaleStrings.MSGO['unknow_project']}{project_key}") if not self.app_manager.is_installed(project_key): raise Exception( - f"{project_info["name"]}{LocaleStrings.MSGO["not_installed"]}" + f"{project_info['name']}{LocaleStrings.MSGO['not_installed']}" ) self.update_progress( - f"{LocaleStrings.MSGU["uninstall"]}{project_info['name']}..." + f"{LocaleStrings.MSGU['uninstall']}{project_info['name']}..." ) - self.log(f"=== {LocaleStrings.MSGU["uninstall"]}{project_info['name']} ===") + self.log(f"=== {LocaleStrings.MSGU['uninstall']}{project_info['name']} ===") try: # Create uninstallation script @@ -403,15 +404,15 @@ class UninstallationManager: self._execute_uninstall_script(script_content) self.update_progress( - f"{project_info['name']}{LocaleStrings.MSGU["uninstall_success"]}" + f"{project_info['name']}{LocaleStrings.MSGU['uninstall_success']}" ) self.log( - f"=== {project_info['name']}{LocaleStrings.MSGU["uninstall_success"]} ===" + f"=== {project_info['name']}{LocaleStrings.MSGU['uninstall_success']} ===" ) except Exception as e: self.log(f"ERROR: {LocaleStrings.MSGU['uninstall_failed']}{e}") - raise Exception(f"{LocaleStrings.MSGU["uninstall_failed"]}{e}") + raise Exception(f"{LocaleStrings.MSGU['uninstall_failed']}{e}") def _create_uninstall_script(self, project_key): """Create uninstallation script based on project""" @@ -420,7 +421,7 @@ class UninstallationManager: elif project_key == "logviewer": return self._create_logviewer_uninstall_script() else: - raise Exception(f"{LocaleStrings.MSGO["unknow_project"]}{project_key}") + raise Exception(f"{LocaleStrings.MSGO['unknow_project']}{project_key}") def _create_wirepy_uninstall_script(self): detected_os = Detector.get_os() @@ -555,7 +556,7 @@ echo "LogViewer uninstallation completed!" # Make script executable os.chmod(script_file.name, 0o755) - self.log(f"{LocaleStrings.MSGU["uninstall_create"]}{script_file.name}") + self.log(f"{LocaleStrings.MSGU['uninstall_create']}{script_file.name}") # Execute with pkexec result = subprocess.run( @@ -576,13 +577,13 @@ echo "LogViewer uninstallation completed!" if result.returncode != 0: raise Exception( - f"{LocaleStrings.MSGU["uninstall_script_failed"]}{result.stderr}" + f"{LocaleStrings.MSGU['uninstall_script_failed']}{result.stderr}" ) except subprocess.TimeoutExpired: raise Exception(LocaleStrings.MSGU["uninstall_timeout"]) except subprocess.CalledProcessError as e: - raise Exception(f"{LocaleStrings.MSGU["uninstall_script_failed"]}{e}") + raise Exception(f"{LocaleStrings.MSGU['uninstall_script_failed']}{e}") def update_progress(self, message): if self.progress_callback: @@ -599,7 +600,7 @@ class DownloadManager: """Download and extract ZIP file""" try: if progress_callback: - progress_callback(f"{LocaleStrings.MSGO["download_from"]}{url}...") + progress_callback(f"{LocaleStrings.MSGO['download_from']}{url}...") with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file: urllib.request.urlretrieve(url, tmp_file.name) @@ -615,7 +616,7 @@ class DownloadManager: except Exception as e: if progress_callback: - progress_callback(f"{LocaleStrings.MSGO["download_failed"]}{e}") + progress_callback(f"{LocaleStrings.MSGO['download_failed']}{e}") return False @@ -738,7 +739,7 @@ class LXToolsGUI: tk.Label( text_frame, - text=f"v {LXToolsAppConfig.VERSION} • {LocaleStrings.MSGO["head_string3"]}", + text=f"v {LXToolsAppConfig.VERSION} • {LocaleStrings.MSGO['head_string3']}", font=("Helvetica", 9), fg="#bdc3c7", bg="#2c3e50", @@ -750,7 +751,7 @@ class LXToolsGUI: tk.Label( right_side, - text=f"{LocaleStrings.MSGO["head_string2"]}{self.detected_os}", + text=f"{LocaleStrings.MSGO['head_string2']}{self.detected_os}", font=("Helvetica", 11), fg="#ecf0f1", bg="#2c3e50", @@ -774,10 +775,21 @@ class LXToolsGUI: def check_ready_status(self): """Check if system is ready for installation""" # Checks: + polkit_ok = Detector.get_polkit() + networkmanager_ok = Detector.get_networkmanager() internet_ok = NetworkChecker.check_internet_connection() repo_ok = NetworkChecker.check_repository_access() result = Detector.get_host_python_version() + if not polkit_ok: + self.update_header_status( + LocaleStrings.MSGO["polkit_check"], "#e74c3c" + ) # Red + if not networkmanager_ok: + self.update_header_status( + LocaleStrings.MSGO["networkmanager_check"], "#e74c3c" + ) # Red + if internet_ok and repo_ok and result is not None: self.update_header_status(LocaleStrings.MSGO["ready"], "#1abc9c") # Green elif not internet_ok: @@ -897,7 +909,7 @@ class LXToolsGUI: # Status label status_label = tk.Label( status_frame, - text=f"❓ {LocaleStrings.MSGC["checking"]}", + text=f"❓ {LocaleStrings.MSGC['checking']}", font=("Helvetica", 10), bg=self.colors["card_bg"], fg="#95a5a6", @@ -991,7 +1003,7 @@ class LXToolsGUI: self._update_frame_children_bg(new_content, self.colors["selected_bg"]) project_info = self.app_manager.get_project_info(project_key) - self.log_message(f"{LocaleStrings.MSGL["selected_app"]}{project_info["name"]}") + self.log_message(f"{LocaleStrings.MSGL['selected_app']}{project_info['name']}") def _create_log_tab(self): """Create log tab""" @@ -1037,12 +1049,12 @@ class LXToolsGUI: self.log_message( f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ===" ) - self.log_message(f"{LocaleStrings.MSGL["work_dir"]}{LXToolsAppConfig.WORK_DIR}") + self.log_message(f"{LocaleStrings.MSGL['work_dir']}{LXToolsAppConfig.WORK_DIR}") self.log_message( - f"{LocaleStrings.MSGL["icons_dir"]}{LXToolsAppConfig.ICONS_DIR}" + f"{LocaleStrings.MSGL['icons_dir']}{LXToolsAppConfig.ICONS_DIR}" ) - self.log_message(f"{LocaleStrings.MSGL["detected_os"]}{self.detected_os}") - self.log_message(f"{LocaleStrings.MSGO["ready"]}...") + self.log_message(f"{LocaleStrings.MSGL['detected_os']}{self.detected_os}") + self.log_message(f"{LocaleStrings.MSGO['ready']}...") def _create_progress_section(self): """Create progress section with download icon""" @@ -1062,7 +1074,7 @@ class LXToolsGUI: # Progress Text (right, expandable) self.progress_label = tk.Label( progress_container, - text=f"{LocaleStrings.MSGO["ready"]}...", + text=f"{LocaleStrings.MSGO['ready']}...", font=("Helvetica", 10), fg="blue", anchor="w", @@ -1175,22 +1187,22 @@ class LXToolsGUI: """Refresh application status and version information""" self.update_progress(LocaleStrings.MSGI["refresh_and_check"]) self._reset_download_icon() - self.log_message(f"=== {LocaleStrings.MSGB["refresh"]} ===") + self.log_message(f"=== {LocaleStrings.MSGB['refresh']} ===") self.root.focus_set() for project_key, project_info in self.app_manager.get_all_projects().items(): status_label = self.status_labels[project_key] version_label = self.version_labels[project_key] - self.log_message(f"{LocaleStrings.MSGC["checking"]} {project_info['name']}") + self.log_message(f"{LocaleStrings.MSGC['checking']} {project_info['name']}") if self.app_manager.is_installed(project_key): installed_version = self.app_manager.get_installed_version(project_key) status_label.config( - text=f"✅ {LocaleStrings.MSGI["installed"]}({installed_version})", + text=f"✅ {LocaleStrings.MSGI['installed']}({installed_version})", fg="green", ) self.log_message( - f"{project_info['name']}: {LocaleStrings.MSGI["installed"]}({installed_version})" + f"{project_info['name']}: {LocaleStrings.MSGI['installed']}({installed_version})" ) # Get latest version from API @@ -1199,41 +1211,41 @@ class LXToolsGUI: if latest_version != "Unknown": if installed_version != f"v. {latest_version}": version_label.config( - text=f"{LocaleStrings.MSGC["latest"]}(v. {latest_version}) {LocaleStrings.MSGC["update_available"]}", + text=f"{LocaleStrings.MSGC['latest']}(v. {latest_version}) {LocaleStrings.MSGC['update_available']}", fg="orange", ) self.log_message( - f"{project_info['name']}: {LocaleStrings.MSGC["update_available"]}(v. {latest_version})" + f"{project_info['name']}: {LocaleStrings.MSGC['update_available']}(v. {latest_version})" ) else: version_label.config( - text=f"{LocaleStrings.MSGC["latest"]}: (v. {latest_version}) {LocaleStrings.MSGC["up_to_date"]}", + text=f"{LocaleStrings.MSGC['latest']}: (v. {latest_version}) {LocaleStrings.MSGC['up_to_date']}", fg="green", ) self.log_message( - f"{project_info['name']}: {LocaleStrings.MSGC["up_to_date"]}", + f"{project_info['name']}: {LocaleStrings.MSGC['up_to_date']}", ) else: version_label.config( text=LocaleStrings.MSGC["latest_unknown"], fg="gray" ) self.log_message( - f"{project_info['name']}: {LocaleStrings.MSGC["could_not_check"]}" + f"{project_info['name']}: {LocaleStrings.MSGC['could_not_check']}" ) except Exception as e: version_label.config( text=LocaleStrings.MSGC["check_last_failed"], fg="gray" ) self.log_message( - f"{project_info['name']}: {LocaleStrings.MSGC["version_check_failed"]}: {e}" + f"{project_info['name']}: {LocaleStrings.MSGC['version_check_failed']}: {e}" ) else: status_label.config( - text=f"❌ {LocaleStrings.MSGC["not_installed"]}", fg="red" + text=f"❌ {LocaleStrings.MSGC['not_installed']}", fg="red" ) self.log_message( - f"{project_info['name']}: {LocaleStrings.MSGC["not_installed"]}" + f"{project_info['name']}: {LocaleStrings.MSGC['not_installed']}" ) # Still show latest available version @@ -1241,12 +1253,12 @@ class LXToolsGUI: latest_version = self.app_manager.get_latest_version(project_key) if latest_version != "Unknown": version_label.config( - text=f"{project_info['name']} {LocaleStrings.MSGC["available"]}: v {latest_version}", + text=f"{project_info['name']} {LocaleStrings.MSGC['available']}: v {latest_version}", fg="blue", ) self.log_message( - f"{project_info['name']} {LocaleStrings.MSGC["available"]}: v {latest_version}" + f"{project_info['name']} {LocaleStrings.MSGC['available']}: v {latest_version}" ) else: version_label.config( @@ -1257,11 +1269,11 @@ class LXToolsGUI: text=LocaleStrings.MSGC["available_check_unknown"], fg="gray" ) self.log_message( - f" {project_info['name']}: {LocaleStrings.MSGC["version_check_failed"]}: {e}" + f" {project_info['name']}: {LocaleStrings.MSGC['version_check_failed']}: {e}" ) self.update_progress(LocaleStrings.MSGO["refresh2"]) - self.log_message(f"=== {LocaleStrings.MSGO["refresh2"]} ===") + self.log_message(f"=== {LocaleStrings.MSGO['refresh2']} ===") self.check_ready_status() def install_selected(self): @@ -1294,7 +1306,7 @@ class LXToolsGUI: self.update_download_icon("success") MessageDialog( "info", - f"{project_info["name"]} {LocaleStrings.MSGM["has_success_update"]}", + f"{project_info['name']} {LocaleStrings.MSGM['has_success_update']}", wraplength=400, ) @@ -1317,7 +1329,7 @@ class LXToolsGUI: if not self.app_manager.is_installed(self.selected_project): MessageDialog( - "error", f"{project_info["name"]} {LocaleStrings.MSGO["not_installed"]}" + "error", f"{project_info['name']} {LocaleStrings.MSGO['not_installed']}" ) self.root.focus_set() return @@ -1326,14 +1338,14 @@ class LXToolsGUI: self.uninstallation_manager.uninstall_project(self.selected_project) MessageDialog( "info", - f"{project_info["name"]} {LocaleStrings.MSGU["uninstall_success"]}", + f"{project_info['name']} {LocaleStrings.MSGU['uninstall_success']}", wraplength=400, ) self.refresh_status() self.root.focus_set() except Exception as e: - MessageDialog("error", f"{LocaleStrings.MSGU["uninstall_failed"]}: {e}") + MessageDialog("error", f"{LocaleStrings.MSGU['uninstall_failed']}: {e}") self.root.focus_set() def update_progress(self, message): @@ -1341,7 +1353,7 @@ class LXToolsGUI: if self.progress_label: self.progress_label.config(text=message) self.progress_label.update() - print(f"{LocaleStrings.MSGO["progress"]}: {message}") + print(f"{LocaleStrings.MSGO['progress']}: {message}") def log_message(self, message): """Add message to log""" @@ -1368,16 +1380,17 @@ class LXToolsGUI: def main(): """Main function to start the application""" print(f"=== {LXToolsAppConfig.APP_NAME} v {LXToolsAppConfig.VERSION} ===") - print(f"{LocaleStrings.MSGL["working_dir"]}{os.getcwd()}") + print(f"{LocaleStrings.MSGL['working_dir']}{os.getcwd()}") try: + LxTools.sigi(LXToolsAppConfig.TEMP_DIR) # Create and run the GUI app = LXToolsGUI() app.run() except KeyboardInterrupt: print(LocaleStrings.MSGL["user_interrupt"]) except Exception as e: - print(f"{LocaleStrings.MSGL["fatal_error"]}: {e}") + print(f"{LocaleStrings.MSGL['fatal_error']}: {e}") try: MessageDialog("error", f"{LocaleStrings.MSGL['fatal_app_error']}: {e}") @@ -1387,3 +1400,6 @@ def main(): if __name__ == "__main__": main() +LxTools.remove_lxtools_files() +LxTools.clean_files(LXToolsAppConfig.TEMP_DIR) +sys.exit(0) diff --git a/lxtools_installer.spec b/lxtools_installer.spec new file mode 100644 index 0000000..cf12796 --- /dev/null +++ b/lxtools_installer.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['lxtools_installer.py'], + pathex=[], + binaries=[], + datas=[('locale/de/LC_MESSAGES/lxtoolsinstaller.mo', 'locale/de/LC_MESSAGES/'), ('manager.py', '.'), ('network.py', '.'), ('message.py', '.'), ('TK-Themes/theme/dark/*.png', 'TK-Themes/theme/dark'), ('TK-Themes/theme/light/*.png', 'TK-Themes/theme/light'), ('TK-Themes/water.tcl', 'TK-Themes'), ('TK-Themes/LICENSE', 'TK-Themes'), ('TK-Themes/theme/dark.tcl', 'TK-Themes/theme'), ('TK-Themes/theme/light.tcl', 'TK-Themes/theme'), ('lx-icons/32/*.png', 'lx-icons/32'), ('lx-icons/48/*.png', 'lx-icons/48'), ('lx-icons/64/*.png', 'lx-icons/64'), ('lx-icons/128/*.png', 'lx-icons/128'), ('lx-icons/256/*.png', 'lx-icons/256'), ('certs/cacert.pem', 'certs')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='lxtools_installer', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/manager.py b/manager.py index b1075b4..19c4915 100644 --- a/manager.py +++ b/manager.py @@ -1,8 +1,11 @@ import locale import gettext +import signal import tkinter as tk from tkinter import ttk from pathlib import Path +from typing import Optional, NoReturn, Any, Dict +import logging import os import sys import shutil @@ -96,6 +99,114 @@ class Detector: else: return False + @staticmethod + def get_polkit() -> bool: + """Check if network manager is installed""" + os_system = Detector.get_os() + deb = ["Debian", "Ubuntu", "Linux Mint", "Pop!_OS"] + arch = ["Arch Linux", "Manjaro", "EndeavourOS", "ArcoLinux", "Garuda Linux"] + if os_system in deb: + result = subprocess.run( + ["apt", "list", "--installed", "|", "grep", "polkit"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return True + else: + return False + + elif os_system in arch: + result = subprocess.run( + ["pacman", "-Qs", "polkit"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return True + else: + return False + + elif os_system == "Fedora": + result = subprocess.run( + ["dnf", "list", "--installed", "|", "grep", "polkit"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return True + else: + return False + + elif os_system == "SUSE Tumbleweed" or os_system == "SUSE Leap": + result = subprocess.run( + ["zypper", "search", "--installed-only", "|", "grep", "polkit"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return True + else: + return False + + @staticmethod + def get_networkmanager() -> bool: + """Check if network manager is installed""" + os_system = Detector.get_os() + deb = ["Debian", "Ubuntu", "Linux Mint", "Pop!_OS"] + arch = ["Arch Linux", "Manjaro", "EndeavourOS", "ArcoLinux", "Garuda Linux"] + if os_system in deb: + result = subprocess.run( + ["apt", "list", "--installed", "|", "grep", "network-manager"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return True + else: + return False + + elif os_system in arch: + result = subprocess.run( + ["pacman", "-Qs", "networkmanager"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return True + else: + return False + + elif os_system == "Fedora": + result = subprocess.run( + ["dnf", "list", "--installed", "|", "grep", "NetworkManager"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return True + else: + return False + + elif os_system == "SUSE Tumbleweed" or os_system == "SUSE Leap": + result = subprocess.run( + ["zypper", "search", "--installed-only", "|", "grep", "networkmanager"], + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0: + return True + else: + return False + class Theme: @staticmethod @@ -180,7 +291,7 @@ class Image: def __init__(self): self.images = {} - def load_image(self, image_key, fallback_paths=None) -> None | tk.PhotoImage: + def load_image(self, image_key, fallback_paths=None) -> Optional[tk.PhotoImage]: """Load PNG image using tk.PhotoImage with fallback options""" if image_key in self.images: return self.images[image_key] @@ -228,7 +339,7 @@ class Image: self.images[image_key] = photo return photo except tk.TclError as e: - print(f"{LocaleStrings.MSGP["fail_load_image"]}{path}: {e}") + print(f"{LocaleStrings.MSGP['fail_load_image']}{path}: {e}") continue # Return None if no image found @@ -239,7 +350,7 @@ class AppManager: def __init__(self): self.projects = LXToolsAppConfig.PROJECTS - def get_project_info(self, project_key) -> dict | None: + def get_project_info(self, project_key) -> Optional[dict]: """Get project information by key""" return self.projects.get(project_key) @@ -281,10 +392,10 @@ class AppManager: # Debug logging print(LocaleStrings.MSGP["logviewer_check"]) - print(f"{LocaleStrings.MSGP["symlink_exist"]}{symlink_exists}") - print(f"{LocaleStrings.MSGP["executable_exist"]}{executable_exists}") - print(f"{LocaleStrings.MSGP["is_executable"]}{executable_is_executable}") - print(f"{LocaleStrings.MSGP["final_result"]}{is_installed}") + print(f"{LocaleStrings.MSGP['symlink_exist']}{symlink_exists}") + print(f"{LocaleStrings.MSGP['executable_exist']}{executable_exists}") + print(f"{LocaleStrings.MSGP['is_executable']}{executable_is_executable}") + print(f"{LocaleStrings.MSGP['final_result']}{is_installed}") return is_installed @@ -310,7 +421,7 @@ class AppManager: return version return "Unknown" except Exception as e: - print(f"{LocaleStrings.MSGP["get_version_error"]}{project_key}: {e}") + print(f"{LocaleStrings.MSGP['get_version_error']}{project_key}: {e}") return "Unknown" def get_latest_version(self, project_key) -> str: @@ -417,12 +528,98 @@ class LxTools: y = (screen_height - height) // 2 window.geometry(f"{width}x{height}+{x}+{y}") + @staticmethod + def clean_files(tmp_dir: Path = None, file: Path = None) -> None: + """ + Deletes temporary files and directories for cleanup when exiting the application. + + This method safely removes an optional directory defined by `AppConfig.TEMP_DIR` + and a single file to free up resources at the end of the program's execution. + All operations are performed securely, and errors such as `FileNotFoundError` + are ignored if the target files or directories do not exist. + :param tmp_dir: (Path, optional): Path to the temporary directory that should be deleted. + If `None`, the value of `AppConfig.TEMP_DIR` is used. + :param file: (Path, optional): Path to the file that should be deleted. + If `None`, no additional file will be deleted. + + Returns: + None: The method does not return any value. + """ + + if tmp_dir is not None: + shutil.rmtree(tmp_dir, ignore_errors=True) + try: + if file is not None: + Path.unlink(file) + + except FileNotFoundError: + pass + + @staticmethod + def sigi(file_path: Optional[Path] = None, file: Optional[Path] = None) -> None: + """ + Function for cleanup after a program interruption + + :param file: Optional - File to be deleted + :param file_path: Optional - Directory to be deleted + """ + + def signal_handler(signum: int, frame: Any) -> NoReturn: + """ + Determines clear text names for signal numbers and handles signals + + Args: + signum: The signal number + frame: The current stack frame + + Returns: + NoReturn since the function either exits the program or continues execution + """ + + signals_to_names_dict: Dict[int, str] = dict( + (getattr(signal, n), n) + for n in dir(signal) + if n.startswith("SIG") and "_" not in n + ) + + signal_name: str = signals_to_names_dict.get( + signum, f"Unnamed signal: {signum}" + ) + + # End program for certain signals, report to others only reception + if signum in (signal.SIGINT, signal.SIGTERM): + exit_code: int = 1 + logging.error( + f"\nSignal {signal_name} {signum} received. => Aborting with exit code {exit_code}.", + exc_info=True, + ) + LxTools.clean_files(file_path, file) + logging.info("Breakdown by user...") + sys.exit(exit_code) + else: + logging.info(f"Signal {signum} received and ignored.") + LxTools.clean_files(file_path, file) + logging.error("Process unexpectedly ended...") + + # Register signal handlers for various signals + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + signal.signal(signal.SIGHUP, signal_handler) + + @staticmethod + def remove_lxtools_files() -> None: + if getattr(sys, "_MEIPASS", None) is not None: + shutil.rmtree("./locale") + shutil.rmtree("./TK-Themes") + shutil.rmtree("./lx-icons") + class LXToolsAppConfig: @staticmethod def extract_data_files() -> None: if getattr(sys, "_MEIPASS", None) is not None: + os.makedirs("/tmp/lxtools", exist_ok=True) # Liste der Quellordner (entspricht dem "datas"-Eintrag in lxtools_installer.spec) source_dirs = [ os.path.join(sys._MEIPASS, "locale"), # für locale/... @@ -437,7 +634,7 @@ class LXToolsAppConfig: for source_dir in source_dirs: group_name = os.path.basename( source_dir - ) # Erhält den Gruppen-Name (z. B. 'locale', 'TK-Themes') + ) # Erhält den Gruppen-Name (z. B. 'locale', 'TK-Themes') for root, dirs, files in os.walk(source_dir): for file in files: @@ -476,15 +673,15 @@ class LXToolsAppConfig: pass return gettext.gettext - VERSION = "1.1.6" + VERSION = "1.1.7" APP_NAME = "lxtoolsinstaller" WINDOW_WIDTH = 450 WINDOW_HEIGHT = 580 - # Working directory WORK_DIR = os.getcwd() ICONS_DIR = os.path.join(WORK_DIR, "lx-icons") THEMES_DIR = os.path.join(WORK_DIR, "TK-Themes") + TEMP_DIR = "/tmp/lxtools" # Locale settings LOCALE_DIR = "./locale/" @@ -618,6 +815,8 @@ class LocaleStrings: "progress": _("Progress"), "refresh2": _("Status refresh completed"), "python_check": _("Python not installed"), + "polkit_check": _("Please install Polkit!"), + "networkmanager_check": _("Please install Networkmanager!"), } # MSGC = Strings on Cards diff --git a/message.py b/message.py index b953563..be1a6a7 100644 --- a/message.py +++ b/message.py @@ -129,7 +129,7 @@ class MessageDialog: self.window = tk.Toplevel(master) self.window.grab_set() self.window.resizable(False, False) - ttk.Style().configure("TButton", font=("Helvetica", 11), padding=5) + ttk.Style().configure("TButton") self.buttons_widgets = [] self.current_button_index = 0 self._load_icons()