(gui): improved error handling

This commit is contained in:
Sergey Palcheh
2026-01-22 16:56:24 +06:00
parent 2a5003c15b
commit 3e18a73383

View File

@@ -17,6 +17,7 @@ from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QProp
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QCursor, QTextCharFormat, QColor, QPalette
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
from typing import Optional, Dict, Any, Callable, List, Set, Tuple, Union
import traceback
class Var:
@@ -35,6 +36,32 @@ class Var:
WH_WINETRICKS: Optional[str] = os.environ.get("WH_WINETRICKS")
class ErrorReporter:
"""Утилита для централизованного отчета об ошибках."""
@staticmethod
def show_error(parent, title: str, message: str, detailed_text: Optional[str] = None):
"""Показывает диалог с сообщением об ошибке."""
msg_box = QMessageBox(parent)
msg_box.setIcon(QMessageBox.Critical)
msg_box.setWindowTitle(title)
msg_box.setText(message)
if detailed_text:
msg_box.setDetailedText(detailed_text)
msg_box.exec_()
@staticmethod
def log_error(message: str, exception: Optional[Exception] = None):
"""Логирует ошибку в консоль."""
if exception:
detailed_error = f"{message}\n{traceback.format_exc()}"
print(detailed_error)
else:
print(f"ERROR: {message}")
class WinetricksManagerDialog(QDialog):
"""Диалог для управления компонентами Winetricks."""
@@ -684,8 +711,17 @@ class ScriptParser:
except (ValueError, IndexError):
continue
return icon_names
except FileNotFoundError:
print(f"Ошибка: файл скрипта не найден для извлечения иконки: {script_path}")
return []
except PermissionError:
print(f"Ошибка: нет доступа к файлу скрипта для извлечения иконки: {script_path}")
return []
except UnicodeDecodeError:
print(f"Ошибка: невозможно декодировать файл скрипта (неподдерживаемая кодировка): {script_path}")
return []
except Exception as e:
print(f"Ошибка чтения файла для извлечения иконки: {str(e)}")
print(f"Неизвестная ошибка при чтении файла для извлечения иконки {script_path}: {str(e)}")
return []
@staticmethod
@@ -699,8 +735,17 @@ class ScriptParser:
if name:
return name
return None
except FileNotFoundError:
print(f"Ошибка: файл скрипта не найден для извлечения PROG_NAME: {script_path}")
return None
except PermissionError:
print(f"Ошибка: нет доступа к файлу скрипта для извлечения PROG_NAME: {script_path}")
return None
except UnicodeDecodeError:
print(f"Ошибка: невозможно декодировать файл скрипта (неподдерживаемая кодировка): {script_path}")
return None
except Exception as e:
print(f"Ошибка чтения файла для извлечения PROG_NAME: {str(e)}")
print(f"Неизвестная ошибка при чтении файла для извлечения PROG_NAME {script_path}: {str(e)}")
return None
@staticmethod
@@ -712,8 +757,17 @@ class ScriptParser:
if line.startswith('export PROG_URL='):
return line.replace('export PROG_URL=', '').strip().strip('"\'')
return None
except FileNotFoundError:
print(f"Ошибка: файл скрипта не найден для извлечения PROG_URL: {script_path}")
return None
except PermissionError:
print(f"Ошибка: нет доступа к файлу скрипта для извлечения PROG_URL: {script_path}")
return None
except UnicodeDecodeError:
print(f"Ошибка: невозможно декодировать файл скрипта (неподдерживаемая кодировка): {script_path}")
return None
except Exception as e:
print(f"Ошибка чтения файла для извлечения PROG_URL: {str(e)}")
print(f"Неизвестная ошибка при чтении файла для извлечения PROG_URL {script_path}: {str(e)}")
return None
@staticmethod
@@ -725,8 +779,14 @@ class ScriptParser:
if line.startswith('# info_ru:'):
return line.replace('# info_ru:', '').strip()
return "Описание отсутствует"
except FileNotFoundError:
return f"Ошибка: файл скрипта не найден: {script_path}"
except PermissionError:
return f"Ошибка: нет доступа к файлу скрипта: {script_path}"
except UnicodeDecodeError:
return f"Ошибка: невозможно декодировать файл скрипта (неподдерживаемая кодировка): {script_path}"
except Exception as e:
return f"Ошибка чтения файла: {str(e)}"
return f"Неизвестная ошибка при чтении файла {script_path}: {str(e)}"
class WineVersionSelectionDialog(QDialog):
"""Диалог для выбора версии Wine/Proton с группировкой."""
@@ -811,8 +871,17 @@ class WineVersionSelectionDialog(QDialog):
if filename.endswith('.tar.xz'):
version_name = filename[:-7]
self.wine_versions_data[current_group].append(version_name)
except IOError as e:
QMessageBox.warning(self, "Ошибка", f"Не удалось прочитать файл версий:\n{e}")
except FileNotFoundError:
QMessageBox.critical(self, "Ошибка", f"Файл с версиями не найден:\n{sha256_path}")
self.wine_versions_data = {}
except PermissionError:
QMessageBox.critical(self, "Ошибка", f"Нет доступа к файлу с версиями:\n{sha256_path}")
self.wine_versions_data = {}
except UnicodeDecodeError:
QMessageBox.critical(self, "Ошибка", f"Невозможно декодировать файл с версиями (неподдерживаемая кодировка):\n{sha256_path}")
self.wine_versions_data = {}
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Неизвестная ошибка при чтении файла версий:\n{str(e)}")
self.wine_versions_data = {}
def _get_installed_versions(self) -> List[str]:
@@ -1160,8 +1229,14 @@ class CreatePrefixDialog(QDialog):
if arch:
self.prepared_prefixes[arch].append((current_prefix_name, current_description.strip().replace('\\n', '\n')))
except IOError as e:
QMessageBox.warning(self, "Ошибка", f"Не удалось прочитать файл с описаниями префиксов: {e}")
except FileNotFoundError:
QMessageBox.critical(self, "Ошибка", f"Файл с описаниями префиксов не найден: {sha256_file}")
except PermissionError:
QMessageBox.critical(self, "Ошибка", f"Нет доступа к файлу с описаниями префиксов: {sha256_file}")
except UnicodeDecodeError:
QMessageBox.critical(self, "Ошибка", f"Невозможно декодировать файл с описаниями префиксов (неподдерживаемая кодировка): {sha256_file}")
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Неизвестная ошибка при чтении файла с описаниями префиксов: {str(e)}")
# Добавляем опцию "Чистый префикс" для обеих архитектур
self.prepared_prefixes['win32'].insert(0, ('none', 'Создать чистый префикс без дополнительных библиотек'))
@@ -1226,16 +1301,19 @@ class CreatePrefixDialog(QDialog):
prefix_name = self.prefix_name_edit.text().strip()
if not prefix_name:
QMessageBox.warning(self, "Ошибка", "Имя префикса не может быть пустым.")
error_msg = "Имя префикса не может быть пустым."
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
if not re.match(r'^[a-zA-Z0-9_-]+$', prefix_name):
QMessageBox.warning(self, "Ошибка", "Имя префикса может содержать только латинские буквы, цифры, дефисы и знаки подчеркивания.")
error_msg = "Имя префикса может содержать только латинские буквы, цифры, дефисы и знаки подчеркивания."
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)
if os.path.exists(prefix_path):
QMessageBox.warning(self, "Ошибка", f"Префикс с именем '{prefix_name}' уже существует.")
error_msg = f"Префикс с именем '{prefix_name}' уже существует."
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
# Save data
@@ -1303,15 +1381,9 @@ class FileAssociationsDialog(QDialog):
found_forbidden = entered_extensions.intersection(forbidden_extensions)
if found_forbidden:
msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Warning)
msg_box.setWindowTitle("Недопустимые расширения")
msg_box.setTextFormat(Qt.RichText)
msg_box.setText(
"Следующие расширения запрещены и не могут быть использованы:<br><br>"
f"<b>{', '.join(sorted(list(found_forbidden)))}</b>"
)
msg_box.exec_()
error_msg = "Следующие расширения запрещены и не могут быть использованы:<br><br>" \
f"<b>{', '.join(sorted(list(found_forbidden)))}</b>"
ErrorReporter.show_error(self, "Недопустимые расширения", error_msg)
return
# Сохраняем результат в виде отсортированной строки
@@ -1401,7 +1473,17 @@ class ComponentVersionSelectionDialog(QDialog):
if filename.endswith('.tar.xz'):
version_name = filename[:-7]
self.versions_data.append(version_name)
except IOError:
except FileNotFoundError:
print(f"Ошибка: файл с версиями компонента не найден: {sha256_path}")
self.versions_data = []
except PermissionError:
print(f"Ошибка: нет доступа к файлу с версиями компонента: {sha256_path}")
self.versions_data = []
except UnicodeDecodeError:
print(f"Ошибка: невозможно декодировать файл с версиями компонента (неподдерживаемая кодировка): {sha256_path}")
self.versions_data = []
except Exception as e:
print(f"Неизвестная ошибка при чтении файла версий компонента {sha256_path}: {str(e)}")
self.versions_data = []
def populate_ui(self, start_row: int) -> None:
@@ -2532,7 +2614,8 @@ class WineHelperGUI(QMainWindow):
self._remove_prefix_from_gui_state(prefix_name)
self.update_installed_apps()
else:
QMessageBox.critical(self, "Ошибка удаления", f"Не удалось удалить префикс '{prefix_name}'.\nПодробности смотрите в логе.")
error_msg = f"Не удалось удалить префикс '{prefix_name}'.\nПодробности смотрите в логе."
ErrorReporter.show_error(self, "Ошибка удаления", error_msg)
def create_base_prefix_from_selected(self):
"""Создает шаблон префикса из выбранного в выпадающем списке."""
@@ -2588,8 +2671,15 @@ class WineHelperGUI(QMainWindow):
if os.path.isdir(prefix_path):
try:
subprocess.Popen(['xdg-open', prefix_path])
except FileNotFoundError:
error_msg = "Команда 'xdg-open' не найдена. Убедитесь, что у вас установлен файловый менеджер по умолчанию."
ErrorReporter.show_error(self, "Ошибка", error_msg)
except PermissionError:
error_msg = f"Нет доступа к директории префикса:\n{prefix_path}"
ErrorReporter.show_error(self, "Ошибка", error_msg)
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Не удалось открыть директорию:\n{prefix_path}\n\nОшибка: {e}")
error_msg = f"Не удалось открыть директорию:\n{prefix_path}\n\nОшибка: {e}"
ErrorReporter.show_error(self, "Ошибка", error_msg, traceback.format_exc())
else:
QMessageBox.warning(self, "Ошибка", f"Директория префикса не найдена:\n{prefix_path}")
@@ -2636,8 +2726,17 @@ class WineHelperGUI(QMainWindow):
key = parts[0].strip()
value = parts[1].strip().strip('"\'')
all_vars[key] = value
except IOError as e:
self.prefix_info_display.setHtml(f"<p>Ошибка чтения last.conf: {e}</p>")
except FileNotFoundError:
self.prefix_info_display.setHtml(f"<p>Файл конфигурации last.conf не найден для префикса '{prefix_name}'.</p>")
return
except PermissionError:
self.prefix_info_display.setHtml(f"<p>Нет доступа к файлу конфигурации last.conf для префикса '{prefix_name}'.</p>")
return
except UnicodeDecodeError:
self.prefix_info_display.setHtml(f"<p>Невозможно декодировать файл конфигурации last.conf (неподдерживаемая кодировка) для префикса '{prefix_name}'.</p>")
return
except Exception as e:
self.prefix_info_display.setHtml(f"<p>Ошибка чтения last.conf для префикса '{prefix_name}': {str(e)}</p>")
return
# --- Обновить кнопки ESync/FSync ---
@@ -2661,8 +2760,14 @@ class WineHelperGUI(QMainWindow):
verb = line.split('#', 1)[0].strip()
if verb:
installed_verbs.append(verb)
except IOError as e:
print(f"Ошибка чтения winetricks.log: {e}")
except FileNotFoundError:
print(f"Файл winetricks.log не найден для префикса '{prefix_name}': {winetricks_log_path}")
except PermissionError:
print(f"Нет доступа к файлу winetricks.log для префикса '{prefix_name}': {winetricks_log_path}")
except UnicodeDecodeError:
print(f"Невозможно декодировать файл winetricks.log (неподдерживаемая кодировка) для префикса '{prefix_name}': {winetricks_log_path}")
except Exception as e:
print(f"Ошибка чтения winetricks.log для префикса '{prefix_name}': {str(e)}")
# Фильтруем служебные компоненты, чтобы не засорять вывод
verbs_to_ignore = {
@@ -2784,8 +2889,17 @@ class WineHelperGUI(QMainWindow):
value = parts[1].strip().strip('"\'')
# Возвращаем значение, только если оно не пустое.
return value if value else None
except IOError as e:
print(f"Ошибка чтения last.conf для {prefix_name}: {e}")
except FileNotFoundError:
print(f"Файл last.conf не найден для префикса {prefix_name}: {last_conf_path}")
return None
except PermissionError:
print(f"Нет доступа к файлу last.conf для префикса {prefix_name}: {last_conf_path}")
return None
except UnicodeDecodeError:
print(f"Невозможно декодировать файл last.conf (неподдерживаемая кодировка) для префикса {prefix_name}: {last_conf_path}")
return None
except Exception as e:
print(f"Ошибка чтения last.conf для {prefix_name}: {str(e)}")
return None
return None
@@ -3012,8 +3126,17 @@ class WineHelperGUI(QMainWindow):
with open(last_conf_path, 'w', encoding='utf-8') as f:
f.writelines(lines)
except IOError as e:
QMessageBox.critical(self, "Ошибка записи", f"Не удалось обновить файл last.conf: {e}")
except FileNotFoundError:
QMessageBox.critical(self, "Ошибка", f"Файл last.conf не найден для префикса '{prefix_name}':\n{last_conf_path}")
return
except PermissionError:
QMessageBox.critical(self, "Ошибка", f"Нет доступа к файлу last.conf для записи:\n{last_conf_path}")
return
except UnicodeDecodeError:
QMessageBox.critical(self, "Ошибка", f"Невозможно декодировать файл last.conf (неподдерживаемая кодировка):\n{last_conf_path}")
return
except Exception as e:
QMessageBox.critical(self, "Ошибка записи", f"Не удалось обновить файл last.conf: {str(e)}")
return
prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)
@@ -3106,7 +3229,7 @@ class WineHelperGUI(QMainWindow):
try:
if not Var.GENERAL or not os.path.exists(Var.GENERAL):
raise FileNotFoundError
raise FileNotFoundError("Файл общей информации не найден")
with open(Var.GENERAL, 'r', encoding='utf-8') as f:
general_content = f.read()
@@ -3131,8 +3254,14 @@ class WineHelperGUI(QMainWindow):
html_content += f'<p>{line}</p>'
general_text.setHtml(html_content)
except (FileNotFoundError, TypeError):
except FileNotFoundError:
general_text.setHtml(f'<h2>Ошибка</h2><p>Не удалось загрузить файл с общей информацией по пути:<br>{Var.GENERAL}</p>')
except PermissionError:
general_text.setHtml(f'<h2>Ошибка</h2><p>Нет доступа к файлу с общей информацией:<br>{Var.GENERAL}</p>')
except UnicodeDecodeError:
general_text.setHtml(f'<h2>Ошибка</h2><p>Невозможно декодировать файл с общей информацией (неподдерживаемая кодировка):<br>{Var.GENERAL}</p>')
except Exception as e:
general_text.setHtml(f'<h2>Ошибка</h2><p>Произошла ошибка при чтении файла общей информации:<br>{str(e)}</p>')
general_layout.addWidget(general_text)
help_subtabs.addTab(general_tab, "Общее")
@@ -3168,7 +3297,7 @@ class WineHelperGUI(QMainWindow):
try:
if not Var.LICENSE_FILE or not os.path.exists(Var.LICENSE_FILE):
raise FileNotFoundError
raise FileNotFoundError("Файл лицензии не найден")
with open(Var.LICENSE_FILE, 'r', encoding='utf-8') as f:
license_content = f.read()
@@ -3188,26 +3317,39 @@ class WineHelperGUI(QMainWindow):
third_party_html = ""
third_party_file_path = Var.THIRD_PARTY_FILE
if third_party_file_path and os.path.exists(third_party_file_path):
with open(third_party_file_path, 'r', encoding='utf-8') as f_tp:
third_party_content = f_tp.read()
try:
with open(third_party_file_path, 'r', encoding='utf-8') as f_tp:
third_party_content = f_tp.read()
# Преобразуем контент в HTML
third_party_html += '<blockquote>'
for line in third_party_content.splitlines():
line = line.strip()
if not line:
third_party_html += '<br>'
continue
escaped_line = html.escape(line)
if line.startswith('http'):
third_party_html += f'&nbsp;&nbsp;&nbsp;&nbsp;<a href="{escaped_line}" style="font-size: 10pt;">{escaped_line}</a><br>'
else:
third_party_html += f'<b>{escaped_line}</b><br>'
third_party_html += '</blockquote>'
# Преобразуем контент в HTML
third_party_html += '<blockquote>'
for line in third_party_content.splitlines():
line = line.strip()
if not line:
third_party_html += '<br>'
continue
escaped_line = html.escape(line)
if line.startswith('http'):
third_party_html += f'&nbsp;&nbsp;&nbsp;&nbsp;<a href="{escaped_line}" style="font-size: 10pt;">{escaped_line}</a><br>'
else:
third_party_html += f'<b>{escaped_line}</b><br>'
third_party_html += '</blockquote>'
except FileNotFoundError:
third_party_html += f'<p>Файл THIRD-PARTY не найден по пути: {third_party_file_path}</p>'
except PermissionError:
third_party_html += f'<p>Нет доступа к файлу THIRD-PARTY: {third_party_file_path}</p>'
except UnicodeDecodeError:
third_party_html += f'<p>Невозможно декодировать файл THIRD-PARTY (неподдерживаемая кодировка): {third_party_file_path}</p>'
except Exception as e:
third_party_html += f'<p>Ошибка чтения файла THIRD-PARTY: {str(e)}</p>'
license_text.setHtml(license_html + third_party_html)
except (FileNotFoundError, TypeError):
except FileNotFoundError:
license_text.setHtml(f'<h2>Лицензия</h2><p>Не удалось загрузить файл лицензии по пути:<br>{Var.LICENSE_FILE}</p>')
except PermissionError:
license_text.setHtml(f'<h2>Лицензия</h2><p>Нет доступа к файлу лицензии:<br>{Var.LICENSE_FILE}</p>')
except UnicodeDecodeError:
license_text.setHtml(f'<h2>Лицензия</h2><p>Невозможно декодировать файл лицензии (неподдерживаемая кодировка):<br>{Var.LICENSE_FILE}</p>')
except Exception as e:
license_text.setHtml(f'<h2>Лицензия</h2><p>Произошла ошибка при чтении файла лицензии:<br>{str(e)}</p>')
@@ -3223,12 +3365,16 @@ class WineHelperGUI(QMainWindow):
try:
if not Var.CHANGELOG_FILE or not os.path.exists(Var.CHANGELOG_FILE):
raise FileNotFoundError
raise FileNotFoundError("Файл истории изменений не найден")
with open(Var.CHANGELOG_FILE, 'r', encoding='utf-8') as f:
changelog_content = f.read()
changelog_text.setText(changelog_content)
except (FileNotFoundError, TypeError):
except FileNotFoundError:
changelog_text.setText(f"Файл истории изменений не найден по пути:\n{Var.CHANGELOG_FILE}")
except PermissionError:
changelog_text.setText(f"Нет доступа к файлу истории изменений:\n{Var.CHANGELOG_FILE}")
except UnicodeDecodeError:
changelog_text.setText(f"Невозможно декодировать файл истории изменений (неподдерживаемая кодировка):\n{Var.CHANGELOG_FILE}")
except Exception as e:
changelog_text.setText(f"Не удалось прочитать файл истории изменений:\n{str(e)}")
@@ -3363,8 +3509,14 @@ class WineHelperGUI(QMainWindow):
icon_file = line.split('=', 1)[1].strip()
if os.path.exists(icon_file):
icon_path = icon_file
except FileNotFoundError:
print(f"Файл .desktop не найден: {desktop_path}")
except PermissionError:
print(f"Нет доступа к файлу .desktop: {desktop_path}")
except UnicodeDecodeError:
print(f"Невозможно декодировать файл .desktop (неподдерживаемая кодировка): {desktop_path}")
except Exception as e:
print(f"Error reading {desktop_path}: {str(e)}")
print(f"Ошибка чтения файла .desktop {desktop_path}: {str(e)}")
btn = self._create_app_button(display_name, [icon_path], self.INSTALLED_BUTTON_LIST_STYLE)
# Обертка для рамки выделения
@@ -3487,6 +3639,18 @@ class WineHelperGUI(QMainWindow):
self.uninstall_button.setVisible(True)
self.manual_install_path_widget.setVisible(False)
except FileNotFoundError:
QMessageBox.critical(self, "Ошибка", f"Файл .desktop не найден:\n{desktop_path}")
self.current_selected_app = None
self.info_panel.setVisible(False)
except PermissionError:
QMessageBox.critical(self, "Ошибка", f"Нет доступа к файлу .desktop:\n{desktop_path}")
self.current_selected_app = None
self.info_panel.setVisible(False)
except UnicodeDecodeError:
QMessageBox.critical(self, "Ошибка", f"Невозможно декодировать файл .desktop (неподдерживаемая кодировка):\n{desktop_path}")
self.current_selected_app = None
self.info_panel.setVisible(False)
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Не удалось прочитать информацию о приложении: {str(e)}")
self.current_selected_app = None
@@ -3508,8 +3672,15 @@ class WineHelperGUI(QMainWindow):
if os.path.isdir(log_dir_path):
try:
subprocess.Popen(['xdg-open', log_dir_path])
except FileNotFoundError:
error_msg = "Команда 'xdg-open' не найдена. Убедитесь, что у вас установлен файловый менеджер по умолчанию."
ErrorReporter.show_error(self, "Ошибка", error_msg)
except PermissionError:
error_msg = f"Нет доступа к директории с логами:\n{log_dir_path}"
ErrorReporter.show_error(self, "Ошибка", error_msg)
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Не удалось открыть директорию:\n{log_dir_path}\n\nОшибка: {e}")
error_msg = f"Не удалось открыть директорию:\n{log_dir_path}\n\nОшибка: {e}"
ErrorReporter.show_error(self, "Ошибка", error_msg, traceback.format_exc())
else:
QMessageBox.information(self, "Информация", f"Директория с логами не найдена:\n{log_dir_path}")
@@ -3530,8 +3701,14 @@ class WineHelperGUI(QMainWindow):
prefix_part = exec_line.split("prefixes/")[1].split("/")[0]
if prefix_part:
return prefix_part
except FileNotFoundError:
print(f"Файл .desktop не найден при попытке получить имя префикса: {desktop_file}")
except PermissionError:
print(f"Нет доступа к файлу .desktop при попытке получить имя префикса: {desktop_file}")
except UnicodeDecodeError:
print(f"Невозможно декодировать файл .desktop (неподдерживаемая кодировка) при попытке получить имя префикса: {desktop_file}")
except Exception as e:
print(f"Error getting prefix name from {desktop_file}: {e}")
print(f"Ошибка получения имени префикса из {desktop_file}: {e}")
return None
def _get_current_app_title(self):
@@ -3648,13 +3825,14 @@ class WineHelperGUI(QMainWindow):
prefix_name = self._get_prefix_name_for_selected_app()
if not prefix_name:
QMessageBox.warning(self, "Менеджер Winetricks",
"Не удалось определить префикс. Выберите установленное приложение или создайте новый префикс.")
error_msg = "Не удалось определить префикс. Выберите установленное приложение или создайте новый префикс."
ErrorReporter.show_error(self, "Менеджер Winetricks", error_msg)
return
prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)
if not os.path.isdir(prefix_path):
QMessageBox.critical(self, "Ошибка", f"Каталог префикса не найден:\n{prefix_path}")
error_msg = f"Каталог префикса не найден:\n{prefix_path}"
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
winetricks_path = Var.WH_WINETRICKS
@@ -3678,6 +3856,12 @@ class WineHelperGUI(QMainWindow):
if value:
wh_wine_use = value
break
except FileNotFoundError:
print(f"Файл last.conf не найден при попытке получить версию Wine: {last_conf_path}")
except PermissionError:
print(f"Нет доступа к файлу last.conf при попытке получить версию Wine: {last_conf_path}")
except UnicodeDecodeError:
print(f"Невозможно декодировать файл last.conf (неподдерживаемая кодировка) при попытке получить версию Wine: {last_conf_path}")
except Exception as e:
print(f"Предупреждение: не удалось прочитать или обработать {last_conf_path}: {e}")
@@ -3698,7 +3882,8 @@ class WineHelperGUI(QMainWindow):
last_conf_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "last.conf")
if not os.path.exists(last_conf_path):
QMessageBox.warning(self, "Ошибка", f"Файл last.conf не найден для префикса '{prefix_name}'.")
error_msg = f"Файл last.conf не найден для префикса '{prefix_name}'."
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
new_value = "1" if is_enabled else "0"
@@ -3724,8 +3909,14 @@ class WineHelperGUI(QMainWindow):
# Обновляем информационную панель, чтобы отразить изменения
self.update_prefix_info_display(prefix_name)
except IOError as e:
QMessageBox.critical(self, "Ошибка записи", f"Не удалось обновить файл last.conf:\n{e}")
except FileNotFoundError:
QMessageBox.critical(self, "Ошибка", f"Файл last.conf не найден для префикса '{prefix_name}':\n{last_conf_path}")
except PermissionError:
QMessageBox.critical(self, "Ошибка", f"Нет доступа к файлу last.conf для записи:\n{last_conf_path}")
except UnicodeDecodeError:
QMessageBox.critical(self, "Ошибка", f"Невозможно декодировать файл last.conf (неподдерживаемая кодировка):\n{last_conf_path}")
except Exception as e:
QMessageBox.critical(self, "Ошибка записи", f"Не удалось обновить файл last.conf:\n{str(e)}")
def _run_wine_util(self, util_name, prefix_name=None):
"""Запускает стандартную утилиту Wine для выбранного префикса."""
@@ -3733,13 +3924,14 @@ class WineHelperGUI(QMainWindow):
prefix_name = self._get_prefix_name_for_selected_app()
if not prefix_name:
QMessageBox.warning(self, "Ошибка",
"Не удалось определить префикс. Выберите установленное приложение или создайте новый префикс.")
error_msg = "Не удалось определить префикс. Выберите установленное приложение или создайте новый префикс."
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)
if not os.path.isdir(prefix_path):
QMessageBox.critical(self, "Ошибка", f"Каталог префикса не найден:\n{prefix_path}")
error_msg = f"Каталог префикса не найден:\n{prefix_path}"
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
wine_executable = self._get_wine_executable_for_prefix(prefix_name)
@@ -3754,23 +3946,35 @@ class WineHelperGUI(QMainWindow):
# x-terminal-emulator - стандартный способ вызова терминала по умолчанию
subprocess.Popen(['x-terminal-emulator', '-e', terminal_command])
except FileNotFoundError:
QMessageBox.critical(self, "Ошибка", "Не удалось найти `x-terminal-emulator`.\nУбедитесь, что у вас установлен терминал по умолчанию (например, mate-terminal или xterm).")
error_msg = "Не удалось найти `x-terminal-emulator`.\nУбедитесь, что у вас установлен терминал по умолчанию (например, mate-terminal или xterm)."
ErrorReporter.show_error(self, "Ошибка", error_msg)
except PermissionError:
error_msg = f"Нет доступа к терминалу для запуска команды:\n{terminal_command}"
ErrorReporter.show_error(self, "Ошибка", error_msg)
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось запустить терминал: {e}")
error_msg = f"Не удалось запустить терминал: {e}"
ErrorReporter.show_error(self, "Ошибка", error_msg, traceback.format_exc())
return
# Для остальных утилит
command = [wine_executable, util_name]
try:
subprocess.Popen(command, env=env)
except FileNotFoundError:
error_msg = f"Файл Wine не найден:\n{wine_executable}"
ErrorReporter.show_error(self, "Ошибка", error_msg)
except PermissionError:
error_msg = f"Нет доступа к файлу Wine для запуска:\n{wine_executable}"
ErrorReporter.show_error(self, "Ошибка", error_msg)
except Exception as e:
QMessageBox.critical(self, "Ошибка запуска",
f"Не удалось запустить команду:\n{' '.join(command)}\n\nОшибка: {str(e)}")
error_msg = f"Не удалось запустить команду:\n{' '.join(command)}\n\nОшибка: {str(e)}"
ErrorReporter.show_error(self, "Ошибка запуска", error_msg, traceback.format_exc())
def toggle_run_stop_app(self):
"""Запускает или останавливает выбранное приложение."""
if not self.current_selected_app or 'desktop_path' not in self.current_selected_app:
QMessageBox.warning(self, "Ошибка", "Сначала выберите приложение.")
error_msg = "Сначала выберите приложение."
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
desktop_path = self.current_selected_app['desktop_path']
@@ -3847,7 +4051,8 @@ class WineHelperGUI(QMainWindow):
def _run_app_launcher(self, debug=False):
"""Внутренний метод для запуска приложения (с отладкой или без) с использованием QProcess."""
if not self.current_selected_app or 'exec' not in self.current_selected_app:
QMessageBox.warning(self, "Ошибка", "Сначала выберите приложение.")
error_msg = "Сначала выберите приложение."
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
desktop_path = self.current_selected_app['desktop_path']
@@ -4021,14 +4226,16 @@ class WineHelperGUI(QMainWindow):
def uninstall_app(self):
"""Удаляет выбранное установленное приложение и его префикс"""
if not self.current_selected_app or 'desktop_path' not in self.current_selected_app:
QMessageBox.warning(self, "Ошибка", "Сначала выберите приложение.")
error_msg = "Сначала выберите приложение."
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
app_name = self.current_selected_app.get('name', 'это приложение')
prefix_name = self._get_prefix_name_for_selected_app()
if not prefix_name:
QMessageBox.warning(self, "Ошибка", "Не удалось определить префикс для выбранного приложения.")
error_msg = "Не удалось определить префикс для выбранного приложения."
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
# Создаем кастомные кнопки
@@ -4087,6 +4294,10 @@ class WineHelperGUI(QMainWindow):
print(f"Удален префикс: {prefix_path}")
else:
print(f"Префикс не найден: {prefix_path}")
except FileNotFoundError:
raise RuntimeError(f"Префикс не найден: {prefix_path}")
except PermissionError:
raise RuntimeError(f"Нет доступа к префиксу для удаления: {prefix_path}")
except Exception as e:
raise RuntimeError(f"Ошибка удаления префикса: {str(e)}")
@@ -4115,14 +4326,32 @@ class WineHelperGUI(QMainWindow):
]
for f in menu_files:
if os.path.exists(f):
os.remove(f)
try:
os.remove(f)
except FileNotFoundError:
print(f"Файл меню не найден для удаления: {f}")
except PermissionError:
print(f"Нет доступа к файлу меню для удаления: {f}")
except Exception as e:
print(f"Ошибка удаления файла меню {f}: {str(e)}")
except FileNotFoundError:
print(f"Директория меню не найдена для удаления: {menu_dir}")
except PermissionError:
print(f"Нет доступа к директории меню для удаления: {menu_dir}")
except OSError as e:
print(f"Ошибка удаления директории меню {menu_dir}: {str(e)}")
except Exception as e:
print(f"Ошибка удаления пустой категории меню: {str(e)}")
# Обновляем кэш desktop файлов
try:
subprocess.run(
["update-desktop-database", os.path.join(os.path.expanduser("~"), ".local/share/applications")])
result = subprocess.run(
["update-desktop-database", os.path.join(os.path.expanduser("~"), ".local/share/applications")],
capture_output=True, text=True, check=False)
if result.returncode != 0:
print(f"Предупреждение: update-desktop-database завершился с кодом {result.returncode}: {result.stderr}")
except FileNotFoundError:
print("Предупреждение: команда update-desktop-database не найдена")
except Exception as e:
print(f"Ошибка обновления кэша desktop файлов: {str(e)}")
@@ -4146,11 +4375,11 @@ class WineHelperGUI(QMainWindow):
self._reset_info_panel_to_default("Установленные")
except Exception as e:
QMessageBox.critical(self, "Ошибка",
f"Не удалось удалить приложение: {str(e)}\n\n"
f"Desktop файл: {self.current_selected_app.get('desktop_path', 'не определен')}\n"
f"Префикс: {prefix_name}\n"
f"Путь к префиксу: {prefix_path if 'prefix_path' in locals() else 'не определен'}")
error_msg = f"Не удалось удалить приложение: {str(e)}\n\n" \
f"Desktop файл: {self.current_selected_app.get('desktop_path', 'не определен')}\n" \
f"Префикс: {prefix_name}\n" \
f"Путь к префиксу: {prefix_path if 'prefix_path' in locals() else 'не определен'}"
ErrorReporter.show_error(self, "Ошибка", error_msg, traceback.format_exc())
def _filter_buttons_in_grid(self, search_text, button_list, grid_layout):
"""Общий метод для фильтрации кнопок и перестроения сетки (helper)."""
search_text_lower = search_text.lower()
@@ -4259,7 +4488,7 @@ class WineHelperGUI(QMainWindow):
try:
license_file_path = Var.LICENSE_AGREEMENT_FILE
if not license_file_path or not os.path.exists(license_file_path):
raise FileNotFoundError
raise FileNotFoundError("Файл лицензионного соглашения не найден")
with open(license_file_path, 'r', encoding='utf-8') as f:
license_content = f.read()
@@ -4268,8 +4497,12 @@ class WineHelperGUI(QMainWindow):
license_text.setHtml(f"""
<pre style="font-family: sans-serif; font-size: 10pt; white-space: pre-wrap; word-wrap: break-word;">{escaped_license_content}</pre>
""")
except (FileNotFoundError, TypeError):
except FileNotFoundError:
license_text.setHtml(f'<h3>Лицензионные соглашения</h3><p>Не удалось загрузить файл лицензионного соглашения по пути:<br>{Var.LICENSE_AGREEMENT_FILE}</p>')
except PermissionError:
license_text.setHtml(f'<h3>Лицензионные соглашения</h3><p>Нет доступа к файлу лицензионного соглашения:<br>{Var.LICENSE_AGREEMENT_FILE}</p>')
except UnicodeDecodeError:
license_text.setHtml(f'<h3>Лицензионные соглашения</h3><p>Невозможно декодировать файл лицензионного соглашения (неподдерживаемая кодировка):<br>{Var.LICENSE_AGREEMENT_FILE}</p>')
except Exception as e:
license_text.setHtml(f'<h3>Лицензионные соглашения</h3><p>Произошла ошибка при чтении файла лицензии:<br>{str(e)}</p>')
@@ -4298,11 +4531,13 @@ class WineHelperGUI(QMainWindow):
def install_current_script(self):
"""Устанавливает текущий выбранный скрипт"""
if not self.current_script:
QMessageBox.warning(self, "Ошибка", "Не выбрана программа для установки")
error_msg = "Не выбрана программа для установки"
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
if self.current_script in self.manualinstall_scripts and not self.install_path_edit.text().strip():
QMessageBox.warning(self, "Ошибка", "Укажите путь к установочному файлу")
error_msg = "Укажите путь к установочному файлу"
ErrorReporter.show_error(self, "Ошибка", error_msg)
return
if not self._show_license_agreement_dialog():
@@ -4364,10 +4599,12 @@ class WineHelperGUI(QMainWindow):
self.current_script)
if not os.path.exists(winehelper_path):
QMessageBox.critical(self.install_dialog, "Ошибка", f"winehelper не найден по пути:\n{winehelper_path}")
error_msg = f"winehelper не найден по пути:\n{winehelper_path}"
ErrorReporter.show_error(self.install_dialog, "Ошибка", error_msg)
return
if not os.path.exists(script_path):
QMessageBox.critical(self.install_dialog, "Ошибка", f"Скрипт установки не найден:\n{script_path}")
error_msg = f"Скрипт установки не найден:\n{script_path}"
ErrorReporter.show_error(self.install_dialog, "Ошибка", error_msg)
return
if self.current_script in self.manualinstall_scripts:
@@ -4375,6 +4612,14 @@ class WineHelperGUI(QMainWindow):
if not install_file:
QMessageBox.critical(self.install_dialog, "Ошибка", "Не указан путь к установочному файлу")
return
if not os.path.exists(install_file):
error_msg = f"Установочный файл не найден:\n{install_file}"
ErrorReporter.show_error(self.install_dialog, "Ошибка", error_msg)
return
if not os.access(install_file, os.R_OK):
error_msg = f"Нет доступа для чтения к установочному файлу:\n{install_file}"
ErrorReporter.show_error(self.install_dialog, "Ошибка", error_msg)
return
QTimer.singleShot(100, lambda: self._start_installation(winehelper_path, script_path, install_file))
else:
QTimer.singleShot(100, lambda: self._start_installation(winehelper_path, script_path))
@@ -4407,9 +4652,25 @@ class WineHelperGUI(QMainWindow):
if not self.install_process.waitForStarted(3000):
raise RuntimeError("Не удалось запустить процесс установки")
self.append_log("Процесс установки запущен...")
except FileNotFoundError:
error_msg = f"Файл winehelper не найден: {winehelper_path}"
self.append_log(f"\n=== ОШИБКА: {error_msg} ===", is_error=True)
ErrorReporter.show_error(self.install_dialog, "Ошибка запуска установки", error_msg)
self.cleanup_process()
except PermissionError:
error_msg = f"Нет доступа к файлу winehelper: {winehelper_path}"
self.append_log(f"\n=== ОШИБКА: {error_msg} ===", is_error=True)
ErrorReporter.show_error(self.install_dialog, "Ошибка запуска установки", error_msg)
self.cleanup_process()
except OSError as e:
error_msg = f"Ошибка операционной системы при запуске установки: {str(e)}"
self.append_log(f"\n=== ОШИБКА: {error_msg} ===", is_error=True)
ErrorReporter.show_error(self.install_dialog, "Ошибка запуска установки", error_msg, str(e))
self.cleanup_process()
except Exception as e:
self.append_log(f"\n=== ОШИБКА: {str(e)} ===", is_error=True)
QMessageBox.critical(self.install_dialog, "Ошибка", f"Не удалось запустить установку:\n{str(e)}")
error_msg = f"Не удалось запустить установку: {str(e)}"
self.append_log(f"\n=== ОШИБКА: {error_msg} ===", is_error=True)
ErrorReporter.show_error(self.install_dialog, "Ошибка запуска установки", error_msg, traceback.format_exc())
self.cleanup_process()
def _append_to_log(self, log_widget, text, is_error=False, add_newline=True):
@@ -4706,7 +4967,15 @@ class WineHelperGUI(QMainWindow):
process.setProcessEnvironment(env)
log_output.append(f"Выполнение: {shlex.quote(command)} {' '.join(shlex.quote(a) for a in args)}")
process.start(command, args)
# Check if the command exists before starting the process
if not os.path.exists(command):
error_msg = f"Файл команды не найден: {command}"
log_output.append(f"\n=== ОШИБКА: {error_msg} ===")
ErrorReporter.log_error(error_msg)
process = None # Prevent starting a non-existent command
else:
process.start(command, args)
return dialog, process, log_output, close_button
@@ -4730,6 +4999,14 @@ class WineHelperGUI(QMainWindow):
def _run_simple_command(self, command, args=None):
"""Запускает простую команду winehelper и выводит лог."""
# Check if the winehelper executable exists before starting the process
if not os.path.exists(self.winehelper_path):
error_msg = f"Файл winehelper не найден: {self.winehelper_path}"
if hasattr(self, 'command_log_output') and self.command_log_output:
self.command_log_output.append(f"\n=== ОШИБКА: {error_msg} ===")
ErrorReporter.log_error(error_msg)
return
self.command_process = QProcess(self.command_dialog)
self.command_process.setProcessChannelMode(QProcess.MergedChannels)
self.command_process.readyReadStandardOutput.connect(self._handle_command_output)
@@ -4749,7 +5026,9 @@ class WineHelperGUI(QMainWindow):
if exit_code == 0:
log_output.append(f"\n=== Команда успешно завершена ===")
else:
log_output.append(f"\n=== Ошибка выполнения (код: {exit_code}) ===")
error_msg = f"Ошибка выполнения (код: {exit_code})"
log_output.append(f"\n=== {error_msg} ===")
ErrorReporter.log_error(f"Команда завершилась с кодом ошибки {exit_code}")
log_output.ensureCursorVisible()
if process: