29.06.2025 (show changelog)
This commit is contained in:
221
manager.py
221
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
|
||||
|
Reference in New Issue
Block a user