From 14a9154366d2651dc2cf75903744553a64319eb3 Mon Sep 17 00:00:00 2001 From: DevilXD <4180725+DevilXD@users.noreply.github.com> Date: Mon, 1 Jul 2024 19:25:16 +0200 Subject: [PATCH] Implement autostart querying upon application start --- constants.py | 7 ++++ gui.py | 90 ++++++++++++++++++++++++++++++++++++++-------------- main.py | 10 ++---- registry.py | 15 +++++---- settings.py | 3 -- 5 files changed, 83 insertions(+), 42 deletions(-) diff --git a/constants.py b/constants.py index 22149740..04313027 100644 --- a/constants.py +++ b/constants.py @@ -121,6 +121,13 @@ def _merge_vars(base_vars: JsonType, vars: JsonType) -> None: # Strings WINDOW_TITLE = f"Twitch Drops Miner v{__version__} (by DevilXD)" # Logging +LOGGING_LEVELS = { + 0: logging.ERROR, + 1: logging.WARNING, + 2: logging.INFO, + 3: CALL, + 4: logging.DEBUG, +} FILE_FORMATTER = logging.Formatter( "{asctime}.{msecs:03.0f}:\t{levelname:>7}:\t{message}", style='{', diff --git a/gui.py b/gui.py index 5ae0dc4c..5af35dc5 100644 --- a/gui.py +++ b/gui.py @@ -33,10 +33,16 @@ from exceptions import MinerException, ExitRequest from utils import resource_path, set_root_icon, webopen, Game, _T from constants import ( - SELF_PATH, OUTPUT_FORMATTER, WS_TOPICS_LIMIT, MAX_WEBSOCKETS, WINDOW_TITLE, State + SELF_PATH, + WINDOW_TITLE, + LOGGING_LEVELS, + MAX_WEBSOCKETS, + WS_TOPICS_LIMIT, + OUTPUT_FORMATTER, + State, ) if sys.platform == "win32": - from registry import RegistryKey, ValueType + from registry import RegistryKey, ValueType, ValueNotFound if TYPE_CHECKING: @@ -1492,7 +1498,7 @@ def __init__(self, manager: GUIManager, master: ttk.Widget): self._vars: _SettingsVars = { "proxy": StringVar(master, str(self._settings.proxy)), "tray": IntVar(master, self._settings.autostart_tray), - "autostart": IntVar(master, self._settings.autostart), + "autostart": IntVar(master, 0), "priority_only": IntVar(master, self._settings.priority_only), "tray_notifications": IntVar(master, self._settings.tray_notifications), } @@ -1647,6 +1653,8 @@ def __init__(self, manager: GUIManager, master: ttk.Widget): command=self._twitch.state_change(State.INVENTORY_FETCH), ).grid(column=1, row=0) + self._vars["autostart"].set(self._query_autostart()) + def clear_selection(self) -> None: self._priority_list.selection_clear(0, "end") self._exclude_list.selection_clear(0, "end") @@ -1654,45 +1662,79 @@ def clear_selection(self) -> None: def update_notifications(self) -> None: self._settings.tray_notifications = bool(self._vars["tray_notifications"].get()) - def _get_autostart_path(self, tray: bool) -> str: - self_path = f'"{SELF_PATH.resolve()!s}"' - if tray: - self_path += " --tray" - return self_path + def _get_self_path(self) -> str: + # NOTE: we need double quotes in case the path contains spaces + return f'"{SELF_PATH.resolve()!s}"' + + def _get_autostart_path(self) -> str: + flags: list[str] = [''] # this will add a space between self path and flags + # if non-zero, include the current logging level as well + if self._settings.logging_level > 0: + for lvl_idx, lvl_value in LOGGING_LEVELS.items(): + if lvl_value == self._settings.logging_level: + flags.append(f"-{'v' * lvl_idx}") + break + if self._vars["tray"].get(): + flags.append("--tray") + return self._get_self_path() + ' '.join(flags) + + def _get_linux_autostart_filepath(self) -> Path: + autostart_folder: Path = Path("~/.config/autostart").expanduser() + if (config_home := os.environ.get("XDG_CONFIG_HOME")) is not None: + config_autostart: Path = Path(config_home, "autostart").expanduser() + if config_autostart.exists(): + autostart_folder = config_autostart + return autostart_folder / f"{self.AUTOSTART_NAME}.desktop" + + def _query_autostart(self) -> bool: + if sys.platform == "win32": + with RegistryKey(self.AUTOSTART_KEY, read_only=True) as key: + try: + value_type, value = key.get(self.AUTOSTART_NAME) + except ValueNotFound: + return False + if ( + value_type is not ValueType.REG_SZ + or self._get_self_path() not in value + ): + # TODO: Consider deleting the old value to avoid autostart errors + return False + return True + elif sys.platform == "linux": + autostart_file: Path = self._get_linux_autostart_filepath() + if not autostart_file.exists(): + return False + with autostart_file.open('r', encoding="utf8") as file: + # TODO: Consider deleting the old file to avoid autostart errors + return self._get_self_path() not in file.read() def update_autostart(self) -> None: enabled = bool(self._vars["autostart"].get()) - tray = bool(self._vars["tray"].get()) - self._settings.autostart = enabled - self._settings.autostart_tray = tray + self._settings.autostart_tray = bool(self._vars["tray"].get()) if sys.platform == "win32": if enabled: - # NOTE: we need double quotes in case the path contains spaces - autostart_path = self._get_autostart_path(tray) with RegistryKey(self.AUTOSTART_KEY) as key: - key.set(self.AUTOSTART_NAME, ValueType.REG_SZ, autostart_path) + key.set( + self.AUTOSTART_NAME, + ValueType.REG_SZ, + self._get_autostart_path(), + ) else: with RegistryKey(self.AUTOSTART_KEY) as key: key.delete(self.AUTOSTART_NAME, silent=True) elif sys.platform == "linux": - autostart_folder: Path = Path("~/.config/autostart").expanduser() - if (config_home := os.environ.get("XDG_CONFIG_HOME")) is not None: - config_autostart: Path = Path(config_home, "autostart").expanduser() - if config_autostart.exists(): - autostart_folder = config_autostart - autostart_file: Path = autostart_folder / f"{self.AUTOSTART_NAME}.desktop" + autostart_file: Path = self._get_linux_autostart_filepath() if enabled: - autostart_path = self._get_autostart_path(tray) - file_contents = dedent( + file_contents: str = dedent( f""" [Desktop Entry] Type=Application Name=Twitch Drops Miner Description=Mine timed drops on Twitch - Exec=sh -c '{autostart_path}' + Exec=sh -c '{self._get_autostart_path()}' """ ) - with autostart_file.open("w", encoding="utf8") as file: + with autostart_file.open('w', encoding="utf8") as file: file.write(file_contents) else: autostart_file.unlink(missing_ok=True) diff --git a/main.py b/main.py index cf4011cc..f192ee9e 100644 --- a/main.py +++ b/main.py @@ -28,7 +28,7 @@ from version import __version__ from exceptions import CaptchaRequired from utils import lock_file, resource_path, set_root_icon - from constants import CALL, SELF_PATH, FILE_FORMATTER, LOG_PATH, LOCK_PATH + from constants import LOGGING_LEVELS, SELF_PATH, FILE_FORMATTER, LOG_PATH, LOCK_PATH warnings.simplefilter("default", ResourceWarning) @@ -58,13 +58,7 @@ class ParsedArgs(argparse.Namespace): # TODO: replace int with union of literal values once typeshed updates @property def logging_level(self) -> int: - return { - 0: logging.ERROR, - 1: logging.WARNING, - 2: logging.INFO, - 3: CALL, - 4: logging.DEBUG, - }[min(self._verbose, 4)] + return LOGGING_LEVELS[min(self._verbose, 4)] @property def debug_ws(self) -> int: diff --git a/registry.py b/registry.py index b717b07e..8ee9c83e 100644 --- a/registry.py +++ b/registry.py @@ -10,7 +10,7 @@ class RegistryError(Exception): pass -class ValueNotExists(RegistryError): +class ValueNotFound(RegistryError): pass @@ -58,13 +58,14 @@ class ValueType(Enum): class RegistryKey: - def __init__(self, path: str): + def __init__(self, path: str, *, read_only: bool = False): main_key, _, path = path.replace('/', '\\').partition('\\') self.main_key = MainKey[main_key] self.path = path - self._handle = reg.OpenKey( - self.main_key.value, path, access=(Access.KEY_QUERY_VALUE | Access.KEY_SET_VALUE).value - ) + access_flags = Access.KEY_QUERY_VALUE + if not read_only: + access_flags |= Access.KEY_SET_VALUE + self._handle = reg.OpenKey(self.main_key.value, path, access=access_flags.value) def __enter__(self) -> RegistryKey: return self @@ -77,7 +78,7 @@ def get(self, name: str) -> tuple[ValueType, Any]: value, value_type = reg.QueryValueEx(self._handle, name) except FileNotFoundError: # TODO: consider returning None for missing values - raise ValueNotExists(name) + raise ValueNotFound(name) return (ValueType(value_type), value) def set(self, name: str, value_type: ValueType, value: Any) -> bool: @@ -89,7 +90,7 @@ def delete(self, name: str, *, silent: bool = False) -> bool: reg.DeleteValue(self._handle, name) except FileNotFoundError: if not silent: - raise ValueNotExists(name) + raise ValueNotFound(name) return False return True diff --git a/settings.py b/settings.py index 4bd68701..fe0d13ea 100644 --- a/settings.py +++ b/settings.py @@ -14,7 +14,6 @@ class SettingsFile(TypedDict): proxy: URL language: str - autostart: bool exclude: set[str] priority: list[str] priority_only: bool @@ -27,7 +26,6 @@ class SettingsFile(TypedDict): "proxy": URL(), "priority": [], "exclude": set(), - "autostart": False, "priority_only": True, "autostart_tray": False, "connection_quality": 1, @@ -48,7 +46,6 @@ class Settings: # from settings file proxy: URL language: str - autostart: bool exclude: set[str] priority: list[str] priority_only: bool