diff --git a/portprotonqt/animations.py b/portprotonqt/animations.py index 8f70747..769110c 100644 --- a/portprotonqt/animations.py +++ b/portprotonqt/animations.py @@ -6,7 +6,6 @@ from portprotonqt.logger import get_logger from portprotonqt.config_utils import read_theme_from_config from portprotonqt.theme_manager import ThemeManager - logger = get_logger(__name__) class SafeOpacityEffect(QGraphicsOpacityEffect): @@ -210,7 +209,7 @@ class GameCardAnimations: def paint_border(self, painter: QPainter): if not painter.isActive(): - logger.warning("Painter is not active; skipping border paint") + logger.debug("Painter is not active; skipping border paint") return painter.setRenderHint(QPainter.RenderHint.Antialiasing) pen = QPen() @@ -259,7 +258,7 @@ class DetailPageAnimations: try: detail_page.setGraphicsEffect(original_effect) # type: ignore except RuntimeError: - logger.debug("Original effect already deleted") + logger.warning("Original effect already deleted") animation.finished.connect(restore_effect) animation.finished.connect(load_image_and_restore_effect) animation.finished.connect(opacity_effect.deleteLater) @@ -318,7 +317,7 @@ class DetailPageAnimations: if isinstance(animation, QAbstractAnimation) and animation.state() == QAbstractAnimation.State.Running: animation.stop() except RuntimeError: - logger.debug("Animation already deleted for page") + logger.warning("Animation already deleted for page") except Exception as e: logger.error(f"Error stopping existing animation: {e}", exc_info=True) finally: diff --git a/portprotonqt/app.py b/portprotonqt/app.py index d8e765a..903f42c 100644 --- a/portprotonqt/app.py +++ b/portprotonqt/app.py @@ -26,7 +26,7 @@ def main(): if qt_translator.load(system_locale, "qtbase", "_", translations_path): app.installTranslator(qt_translator) else: - logger.error(f"Qt translations for {system_locale.name()} not found in {translations_path}") + logger.warning(f"Qt translations for {system_locale.name()} not found in {translations_path}, using english language") args = parse_args() diff --git a/portprotonqt/config_utils.py b/portprotonqt/config_utils.py index 6e6cab6..999ba14 100644 --- a/portprotonqt/config_utils.py +++ b/portprotonqt/config_utils.py @@ -7,7 +7,7 @@ logger = get_logger(__name__) _portproton_location = None -# Пути к конфигурационным файлам +# Paths to configuration files CONFIG_FILE = os.path.join( os.getenv("XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config")), "PortProtonQt.conf" @@ -18,17 +18,32 @@ PORTPROTON_CONFIG_FILE = os.path.join( "PortProton.conf" ) -# Пути к папкам с темами +# Paths to theme directories xdg_data_home = os.getenv("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share")) THEMES_DIRS = [ os.path.join(xdg_data_home, "PortProtonQt", "themes"), os.path.join(os.path.dirname(os.path.abspath(__file__)), "themes") ] +def read_config_safely(config_file: str) -> configparser.ConfigParser | None: + """Safely reads a configuration file and returns a ConfigParser object or None if reading fails.""" + cp = configparser.ConfigParser() + if not os.path.exists(config_file): + logger.debug(f"Configuration file {config_file} not found") + return None + try: + cp.read(config_file, encoding="utf-8") + return cp + except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: + logger.warning(f"Invalid configuration file format: {e}") + return None + except Exception as e: + logger.warning(f"Failed to read configuration file: {e}") + return None + def read_config(): - """ - Читает конфигурационный файл и возвращает словарь параметров. - Пример строки в конфиге (без секций): + """Reads the configuration file and returns a dictionary of parameters. + Example line in config (no sections): detail_level = detailed """ config_dict = {} @@ -44,29 +59,17 @@ def read_config(): return config_dict def read_theme_from_config(): + """Reads the theme from the [Appearance] section of the configuration file. + Returns 'standart' if the parameter is not set. """ - Читает из конфигурационного файла тему из секции [Appearance]. - Если параметр не задан, возвращает "standart". - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) - return "standart" + cp = read_config_safely(CONFIG_FILE) + if cp is None: + return "standart" return cp.get("Appearance", "theme", fallback="standart") def save_theme_to_config(theme_name): - """ - Сохраняет имя выбранной темы в секции [Appearance] конфигурационного файла. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) + """Saves the selected theme name to the [Appearance] section of the configuration file.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Appearance" not in cp: cp["Appearance"] = {} cp["Appearance"]["theme"] = theme_name @@ -74,34 +77,18 @@ def save_theme_to_config(theme_name): cp.write(configfile) def read_time_config(): + """Reads time settings from the [Time] section of the configuration file. + If the section or parameter is missing, saves and returns 'detailed' as default. """ - Читает настройки времени из секции [Time] конфигурационного файла. - Если секция или параметр отсутствуют, сохраняет и возвращает "detailed" по умолчанию. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) - save_time_config("detailed") - return "detailed" - if not cp.has_section("Time") or not cp.has_option("Time", "detail_level"): - save_time_config("detailed") - return "detailed" - return cp.get("Time", "detail_level", fallback="detailed").lower() - return "detailed" + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("Time") or not cp.has_option("Time", "detail_level"): + save_time_config("detailed") + return "detailed" + return cp.get("Time", "detail_level", fallback="detailed").lower() def save_time_config(detail_level): - """ - Сохраняет настройку уровня детализации времени в секции [Time]. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) + """Saves the time detail level to the [Time] section of the configuration file.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Time" not in cp: cp["Time"] = {} cp["Time"]["detail_level"] = detail_level @@ -109,48 +96,42 @@ def save_time_config(detail_level): cp.write(configfile) def read_file_content(file_path): - """ - Читает содержимое файла и возвращает его как строку. - """ + """Reads the content of a file and returns it as a string.""" with open(file_path, encoding="utf-8") as f: return f.read().strip() def get_portproton_location(): - """ - Возвращает путь к директории PortProton. - Сначала проверяется кэшированный путь. Если он отсутствует, проверяется - наличие пути в файле PORTPROTON_CONFIG_FILE. Если путь недоступен, - используется директория по умолчанию. + """Returns the path to the PortProton directory. + Checks the cached path first. If not set, reads from PORTPROTON_CONFIG_FILE. + If the path is invalid, uses the default directory. """ global _portproton_location if _portproton_location is not None: return _portproton_location - # Попытка чтения пути из конфигурационного файла if os.path.isfile(PORTPROTON_CONFIG_FILE): try: location = read_file_content(PORTPROTON_CONFIG_FILE).strip() if location and os.path.isdir(location): _portproton_location = location - logger.info(f"Путь PortProton из конфигурации: {location}") + logger.info(f"PortProton path from configuration: {location}") return _portproton_location - logger.warning(f"Недействительный путь в конфиге PortProton: {location}") + logger.warning(f"Invalid PortProton path in configuration: {location}, using default path") except (OSError, PermissionError) as e: - logger.error(f"Ошибка чтения файла конфигурации PortProton: {e}") + logger.warning(f"Failed to read PortProton configuration file: {e}, using default path") default_dir = os.path.join(os.path.expanduser("~"), ".var", "app", "ru.linux_gaming.PortProton") if os.path.isdir(default_dir): _portproton_location = default_dir - logger.info(f"Используется директория flatpak PortProton: {default_dir}") + logger.info(f"Using flatpak PortProton directory: {default_dir}") return _portproton_location - logger.warning("Конфигурация и директория flatpak PortProton не найдены") + logger.warning("PortProton configuration and flatpak directory not found") return None def parse_desktop_entry(file_path): - """ - Читает и парсит .desktop файл с помощью configparser. - Если секция [Desktop Entry] отсутствует, возвращается None. + """Reads and parses a .desktop file using configparser. + Returns None if the [Desktop Entry] section is missing. """ cp = configparser.ConfigParser(interpolation=None) cp.read(file_path, encoding="utf-8") @@ -159,9 +140,8 @@ def parse_desktop_entry(file_path): return cp["Desktop Entry"] def load_theme_metainfo(theme_name): - """ - Загружает метаинформацию темы из файла metainfo.ini в корне папки темы. - Ожидаемые поля: author, author_link, description, name. + """Loads theme metadata from metainfo.ini in the theme's root directory. + Expected fields: author, author_link, description, name. """ meta = {} for themes_dir in THEMES_DIRS: @@ -179,34 +159,18 @@ def load_theme_metainfo(theme_name): return meta def read_card_size(): + """Reads the card size (width) from the [Cards] section. + Returns 250 if the parameter is not set. """ - Читает размер карточек (ширину) из секции [Cards], - Если параметр не задан, возвращает 250. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) - save_card_size(250) - return 250 - if not cp.has_section("Cards") or not cp.has_option("Cards", "card_width"): - save_card_size(250) - return 250 - return cp.getint("Cards", "card_width", fallback=250) - return 250 + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("Cards") or not cp.has_option("Cards", "card_width"): + save_card_size(250) + return 250 + return cp.getint("Cards", "card_width", fallback=250) def save_card_size(card_width): - """ - Сохраняет размер карточек (ширину) в секцию [Cards]. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) + """Saves the card size (width) to the [Cards] section.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Cards" not in cp: cp["Cards"] = {} cp["Cards"]["card_width"] = str(card_width) @@ -214,34 +178,18 @@ def save_card_size(card_width): cp.write(configfile) def read_sort_method(): + """Reads the sort method from the [Games] section. + Returns 'last_launch' if the parameter is not set. """ - Читает метод сортировки из секции [Games]. - Если параметр не задан, возвращает last_launch. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) - save_sort_method("last_launch") - return "last_launch" - if not cp.has_section("Games") or not cp.has_option("Games", "sort_method"): - save_sort_method("last_launch") - return "last_launch" - return cp.get("Games", "sort_method", fallback="last_launch").lower() - return "last_launch" + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("Games") or not cp.has_option("Games", "sort_method"): + save_sort_method("last_launch") + return "last_launch" + return cp.get("Games", "sort_method", fallback="last_launch").lower() def save_sort_method(sort_method): - """ - Сохраняет метод сортировки в секцию [Games]. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) + """Saves the sort method to the [Games] section.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Games" not in cp: cp["Games"] = {} cp["Games"]["sort_method"] = sort_method @@ -249,34 +197,18 @@ def save_sort_method(sort_method): cp.write(configfile) def read_display_filter(): + """Reads the display_filter parameter from the [Games] section. + Returns 'all' if the parameter is missing. """ - Читает параметр display_filter из секции [Games]. - Если параметр отсутствует, сохраняет и возвращает значение "all". - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфига: %s", e) - save_display_filter("all") - return "all" - if not cp.has_section("Games") or not cp.has_option("Games", "display_filter"): - save_display_filter("all") - return "all" - return cp.get("Games", "display_filter", fallback="all").lower() - return "all" + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("Games") or not cp.has_option("Games", "display_filter"): + save_display_filter("all") + return "all" + return cp.get("Games", "display_filter", fallback="all").lower() def save_display_filter(filter_value): - """ - Сохраняет параметр display_filter в секцию [Games] конфигурационного файла. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфига: %s", e) + """Saves the display_filter parameter to the [Games] section.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Games" not in cp: cp["Games"] = {} cp["Games"]["display_filter"] = filter_value @@ -284,37 +216,23 @@ def save_display_filter(filter_value): cp.write(configfile) def read_favorites(): + """Reads the list of favorite games from the [Favorites] section. + The list is stored as a quoted string with comma-separated names. + Returns an empty list if the section or parameter is missing. """ - Читает список избранных игр из секции [Favorites] конфигурационного файла. - Список хранится как строка, заключённая в кавычки, с именами, разделёнными запятыми. - Если секция или параметр отсутствуют, возвращает пустой список. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфига: %s", e) - return [] - if cp.has_section("Favorites") and cp.has_option("Favorites", "games"): - favs = cp.get("Favorites", "games", fallback="").strip() - # Если строка начинается и заканчивается кавычками, удаляем их - if favs.startswith('"') and favs.endswith('"'): - favs = favs[1:-1] - return [s.strip() for s in favs.split(",") if s.strip()] - return [] + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("Favorites") or not cp.has_option("Favorites", "games"): + return [] + favs = cp.get("Favorites", "games", fallback="").strip() + if favs.startswith('"') and favs.endswith('"'): + favs = favs[1:-1] + return [s.strip() for s in favs.split(",") if s.strip()] def save_favorites(favorites): + """Saves the list of favorite games to the [Favorites] section. + The list is stored as a quoted string with comma-separated names. """ - Сохраняет список избранных игр в секцию [Favorites] конфигурационного файла. - Список сохраняется как строка, заключённая в двойные кавычки, где имена игр разделены запятыми. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфига: %s", e) + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Favorites" not in cp: cp["Favorites"] = {} fav_str = ", ".join(favorites) @@ -323,34 +241,18 @@ def save_favorites(favorites): cp.write(configfile) def read_rumble_config(): + """Reads the gamepad rumble setting from the [Gamepad] section. + Returns False if the parameter is missing. """ - Читает настройку виброотдачи геймпада из секции [Gamepad]. - Если параметр отсутствует, сохраняет и возвращает False по умолчанию. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфигурационного файла: %s", e) - save_rumble_config(False) - return False - if not cp.has_section("Gamepad") or not cp.has_option("Gamepad", "rumble_enabled"): - save_rumble_config(False) - return False - return cp.getboolean("Gamepad", "rumble_enabled", fallback=False) - return False + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("Gamepad") or not cp.has_option("Gamepad", "rumble_enabled"): + save_rumble_config(False) + return False + return cp.getboolean("Gamepad", "rumble_enabled", fallback=False) def save_rumble_config(rumble_enabled): - """ - Сохраняет настройку виброотдачи геймпада в секцию [Gamepad]. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка чтения конфигурационного файла: %s", e) + """Saves the gamepad rumble setting to the [Gamepad] section.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Gamepad" not in cp: cp["Gamepad"] = {} cp["Gamepad"]["rumble_enabled"] = str(rumble_enabled) @@ -358,41 +260,28 @@ def save_rumble_config(rumble_enabled): cp.write(configfile) def ensure_default_proxy_config(): + """Ensures the [Proxy] section exists in the configuration file. + Creates it with empty values if missing. """ - Проверяет наличие секции [Proxy] в конфигурационном файле. - Если секция отсутствует, создаёт её с пустыми значениями. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфигурационного файла: %s", e) - return - if not cp.has_section("Proxy"): - cp.add_section("Proxy") - cp["Proxy"]["proxy_url"] = "" - cp["Proxy"]["proxy_user"] = "" - cp["Proxy"]["proxy_password"] = "" - with open(CONFIG_FILE, "w", encoding="utf-8") as configfile: - cp.write(configfile) + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() + if "Proxy" not in cp: + cp.add_section("Proxy") + cp["Proxy"]["proxy_url"] = "" + cp["Proxy"]["proxy_user"] = "" + cp["Proxy"]["proxy_password"] = "" + with open(CONFIG_FILE, "w", encoding="utf-8") as configfile: + cp.write(configfile) def read_proxy_config(): - """ - Читает настройки прокси из секции [Proxy] конфигурационного файла. - Если параметр proxy_url не задан или пустой, возвращает пустой словарь. + """Reads proxy settings from the [Proxy] section. + Returns an empty dict if proxy_url is not set or empty. """ ensure_default_proxy_config() - cp = configparser.ConfigParser() - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфигурационного файла: %s", e) + cp = read_config_safely(CONFIG_FILE) + if cp is None: return {} - proxy_url = cp.get("Proxy", "proxy_url", fallback="").strip() if proxy_url: - # Если указаны логин и пароль, добавляем их к URL proxy_user = cp.get("Proxy", "proxy_user", fallback="").strip() proxy_password = cp.get("Proxy", "proxy_password", fallback="").strip() if "://" in proxy_url and "@" not in proxy_url and proxy_user and proxy_password: @@ -402,16 +291,10 @@ def read_proxy_config(): return {} def save_proxy_config(proxy_url="", proxy_user="", proxy_password=""): + """Saves proxy settings to the [Proxy] section. + Creates the section if it does not exist. """ - Сохраняет настройки proxy в секцию [Proxy] конфигурационного файла. - Если секция отсутствует, создаёт её. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка чтения конфигурационного файла: %s", e) + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Proxy" not in cp: cp["Proxy"] = {} cp["Proxy"]["proxy_url"] = proxy_url @@ -421,34 +304,18 @@ def save_proxy_config(proxy_url="", proxy_user="", proxy_password=""): cp.write(configfile) def read_fullscreen_config(): + """Reads the fullscreen mode setting from the [Display] section. + Returns False if the parameter is missing. """ - Читает настройку полноэкранного режима приложения из секции [Display]. - Если параметр отсутствует, сохраняет и возвращает False по умолчанию. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфигурационного файла: %s", e) - save_fullscreen_config(False) - return False - if not cp.has_section("Display") or not cp.has_option("Display", "fullscreen"): - save_fullscreen_config(False) - return False - return cp.getboolean("Display", "fullscreen", fallback=False) - return False + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("Display") or not cp.has_option("Display", "fullscreen"): + save_fullscreen_config(False) + return False + return cp.getboolean("Display", "fullscreen", fallback=False) def save_fullscreen_config(fullscreen): - """ - Сохраняет настройку полноэкранного режима приложения в секцию [Display]. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка чтения конфигурационного файла: %s", e) + """Saves the fullscreen mode setting to the [Display] section.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Display" not in cp: cp["Display"] = {} cp["Display"]["fullscreen"] = str(fullscreen) @@ -456,33 +323,19 @@ def save_fullscreen_config(fullscreen): cp.write(configfile) def read_window_geometry() -> tuple[int, int]: + """Reads the window width and height from the [MainWindow] section. + Returns (0, 0) if the parameters are missing. """ - Читает ширину и высоту окна из секции [MainWindow] конфигурационного файла. - Возвращает кортеж (width, height). Если данные отсутствуют, возвращает (0, 0). - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) - return (0, 0) - if cp.has_section("MainWindow"): - width = cp.getint("MainWindow", "width", fallback=0) - height = cp.getint("MainWindow", "height", fallback=0) - return (width, height) - return (0, 0) + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("MainWindow"): + return (0, 0) + width = cp.getint("MainWindow", "width", fallback=0) + height = cp.getint("MainWindow", "height", fallback=0) + return (width, height) def save_window_geometry(width: int, height: int): - """ - Сохраняет ширину и высоту окна в секцию [MainWindow] конфигурационного файла. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка в конфигурационном файле: %s", e) + """Saves the window width and height to the [MainWindow] section.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "MainWindow" not in cp: cp["MainWindow"] = {} cp["MainWindow"]["width"] = str(width) @@ -491,59 +344,40 @@ def save_window_geometry(width: int, height: int): cp.write(configfile) def reset_config(): - """ - Сбрасывает конфигурационный файл, удаляя его. - После этого все настройки будут возвращены к значениям по умолчанию при следующем чтении. + """Resets the configuration file by deleting it. + Subsequent reads will use default values. """ if os.path.exists(CONFIG_FILE): try: os.remove(CONFIG_FILE) - logger.info("Конфигурационный файл %s удалён", CONFIG_FILE) + logger.info("Configuration file %s deleted", CONFIG_FILE) except Exception as e: - logger.error("Ошибка при удалении конфигурационного файла: %s", e) + logger.warning(f"Failed to delete configuration file: {e}") def clear_cache(): - """ - Очищает кэш PortProtonQt, удаляя папку кэша. - """ + """Clears the PortProtonQt cache by deleting the cache directory.""" xdg_cache_home = os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")) cache_dir = os.path.join(xdg_cache_home, "PortProtonQt") if os.path.exists(cache_dir): try: shutil.rmtree(cache_dir) - logger.info("Кэш PortProtonQt удалён: %s", cache_dir) + logger.info("PortProtonQt cache deleted: %s", cache_dir) except Exception as e: - logger.error("Ошибка при удалении кэша: %s", e) + logger.warning(f"Failed to delete cache: {e}") def read_auto_fullscreen_gamepad(): + """Reads the auto-fullscreen setting for gamepad from the [Display] section. + Returns False if the parameter is missing. """ - Читает настройку автоматического полноэкранного режима при подключении геймпада из секции [Display]. - Если параметр отсутствует, сохраняет и возвращает False по умолчанию. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфигурационного файла: %s", e) - save_auto_fullscreen_gamepad(False) - return False - if not cp.has_section("Display") or not cp.has_option("Display", "auto_fullscreen_gamepad"): - save_auto_fullscreen_gamepad(False) - return False - return cp.getboolean("Display", "auto_fullscreen_gamepad", fallback=False) - return False + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("Display") or not cp.has_option("Display", "auto_fullscreen_gamepad"): + save_auto_fullscreen_gamepad(False) + return False + return cp.getboolean("Display", "auto_fullscreen_gamepad", fallback=False) def save_auto_fullscreen_gamepad(auto_fullscreen): - """ - Сохраняет настройку автоматического полноэкранного режима при подключении геймпада в секцию [Display]. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e: - logger.error("Ошибка чтения конфигурационного файла: %s", e) + """Saves the auto-fullscreen setting for gamepad to the [Display] section.""" + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "Display" not in cp: cp["Display"] = {} cp["Display"]["auto_fullscreen_gamepad"] = str(auto_fullscreen) @@ -551,36 +385,23 @@ def save_auto_fullscreen_gamepad(auto_fullscreen): cp.write(configfile) def read_favorite_folders(): + """Reads the list of favorite folders from the [FavoritesFolders] section. + The list is stored as a quoted string with comma-separated paths. + Returns an empty list if the section or parameter is missing. """ - Читает список избранных папок из секции [FavoritesFolders] конфигурационного файла. - Список хранится как строка, заключённая в кавычки, с путями, разделёнными запятыми. - Если секция или параметр отсутствуют, возвращает пустой список. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфига: %s", e) - return [] - if cp.has_section("FavoritesFolders") and cp.has_option("FavoritesFolders", "folders"): - favs = cp.get("FavoritesFolders", "folders", fallback="").strip() - if favs.startswith('"') and favs.endswith('"'): - favs = favs[1:-1] - return [os.path.normpath(s.strip()) for s in favs.split(",") if s.strip() and os.path.isdir(os.path.normpath(s.strip()))] - return [] + cp = read_config_safely(CONFIG_FILE) + if cp is None or not cp.has_section("FavoritesFolders") or not cp.has_option("FavoritesFolders", "folders"): + return [] + favs = cp.get("FavoritesFolders", "folders", fallback="").strip() + if favs.startswith('"') and favs.endswith('"'): + favs = favs[1:-1] + return [os.path.normpath(s.strip()) for s in favs.split(",") if s.strip() and os.path.isdir(os.path.normpath(s.strip()))] def save_favorite_folders(folders): + """Saves the list of favorite folders to the [FavoritesFolders] section. + The list is stored as a quoted string with comma-separated paths. """ - Сохраняет список избранных папок в секцию [FavoritesFolders] конфигурационного файла. - Список сохраняется как строка, заключённая в двойные кавычки, где пути разделены запятыми. - """ - cp = configparser.ConfigParser() - if os.path.exists(CONFIG_FILE): - try: - cp.read(CONFIG_FILE, encoding="utf-8") - except Exception as e: - logger.error("Ошибка чтения конфига: %s", e) + cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() if "FavoritesFolders" not in cp: cp["FavoritesFolders"] = {} fav_str = ", ".join([os.path.normpath(folder) for folder in folders]) diff --git a/portprotonqt/context_menu_manager.py b/portprotonqt/context_menu_manager.py index 89cf4a1..64d1fdc 100644 --- a/portprotonqt/context_menu_manager.py +++ b/portprotonqt/context_menu_manager.py @@ -4,7 +4,6 @@ import glob import shutil import subprocess import threading -import logging import orjson import psutil import signal @@ -17,8 +16,9 @@ from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_s from portprotonqt.egs_api import add_egs_to_steam, get_egs_executable, remove_egs_from_steam from portprotonqt.dialogs import AddGameDialog, FileExplorer, generate_thumbnail from portprotonqt.theme_manager import ThemeManager +from portprotonqt.logger import get_logger -logger = logging.getLogger(__name__) +logger = get_logger(__name__) class ContextMenuSignals(QObject): """Signals for thread-safe UI updates from worker threads."""