From 25dece7f4edc76d18d1fb3e6879a0332a8ed8377 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sat, 27 Nov 2021 23:08:57 -0500 Subject: [PATCH] Fixes #86 Stop uncaught exceptions from silently crashing the app --- src/AutoSplit.py | 57 +++++++++++++++++++++++++++++++------------ src/error_messages.py | 26 +++++++++++++++----- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 20ce39cd..20a7a3f3 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -1,7 +1,8 @@ #!/usr/bin/python3.9 # -*- coding: utf-8 -*- -from types import FunctionType -from typing import Callable, List, Optional +import traceback +from types import FunctionType, TracebackType +from typing import Callable, List, Optional, Type from copy import copy from PyQt6 import QtCore, QtGui, QtTest, QtWidgets @@ -32,7 +33,9 @@ DISPLAY_RESIZE_WIDTH = 240 DISPLAY_RESIZE_HEIGHT = 180 DISPLAY_RESIZE = (DISPLAY_RESIZE_WIDTH, DISPLAY_RESIZE_HEIGHT) - +CREATE_NEW_ISSUE_MESSAGE = \ + "Please create a New Issue at " +"github.com/Toufool/Auto-Split/issues, describe what happened, and copy & paste the error message below" class AutoSplit(QtWidgets.QMainWindow, design.Ui_MainWindow): from hotkeys import send_command @@ -1197,22 +1200,46 @@ def exit(): def main(): app = QtWidgets.QApplication(sys.argv) - app.setWindowIcon(QtGui.QIcon(':/resources/icon.ico')) - - main_window = AutoSplit() - main_window.show() - if main_window.actionCheck_for_Updates_on_Open.isChecked(): - checkForUpdates(main_window, check_for_updates_on_open=True) + try: + app.setWindowIcon(QtGui.QIcon(':/resources/icon.ico')) + main_window = AutoSplit() + main_window.show() + # Needs to be after main_window.show() to be shown over + if main_window.actionCheck_for_Updates_on_Open.isChecked(): + checkForUpdates(main_window, check_for_updates_on_open=True) + + # Kickoff the event loop every so often so we can handle KeyboardInterrupt (^C) + timer = QtCore.QTimer() + timer.timeout.connect(lambda: None) + timer.start(500) + + exit_code = app.exec() + except Exception as exception: + # Print error to console if not running in executable + if getattr(sys, 'frozen', False): + error_messages.exceptionTraceback( + f"AutoSplit encountered an unrecoverable exception and will now close. {CREATE_NEW_ISSUE_MESSAGE}", + exception) + else: + traceback.print_exception(type(exception), exception, exception.__traceback__) + sys.exit(1) - # Kickoff the event loop every so often so we can handle KeyboardInterrupt (^C) - timer = QtCore.QTimer() - timer.timeout.connect(lambda: None) - timer.start(500) # Catch Keyboard Interrupts for a clean close - signal.signal(signal.SIGINT, lambda _, __: sys.exit(app)) + signal.signal(signal.SIGINT, lambda code, _: sys.exit(code)) - sys.exit(app.exec()) + sys.exit(exit_code) + + +def excepthook(exceptionType: Type[BaseException], exception: BaseException, traceback: Optional[TracebackType]): + # Catch Keyboard Interrupts for a clean close + if exceptionType is KeyboardInterrupt: + sys.exit(0) + error_messages.exceptionTraceback( + "AutoSplit encountered an unhandled exception and will try to recover, " + f"however, there is no guarantee everything will work correctly. {CREATE_NEW_ISSUE_MESSAGE}", + exception) if __name__ == '__main__': + sys.excepthook = excepthook main() diff --git a/src/error_messages.py b/src/error_messages.py index 6f0add2c..925d6341 100644 --- a/src/error_messages.py +++ b/src/error_messages.py @@ -1,12 +1,20 @@ # Error messages -from PyQt6 import QtWidgets +import traceback +from PyQt6 import QtCore, QtWidgets -def setTextMessage(message: str): - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle('Error') - msgBox.setText(message) - msgBox.exec() +def setTextMessage(message: str, details: str = ''): + messageBox = QtWidgets.QMessageBox() + messageBox.setWindowTitle('Error') + messageBox.setTextFormat(QtCore.Qt.TextFormat.RichText) + messageBox.setText(message) + if details: + messageBox.setDetailedText(details) + for button in messageBox.buttons(): + if messageBox.buttonRole(button) == QtWidgets.QMessageBox.ButtonRole.ActionRole: + button.click() + break + messageBox.exec() def splitImageDirectoryError(): @@ -83,3 +91,9 @@ def checkForUpdatesError(): def stdinLostError(): setTextMessage("stdin not supported or lost, external control like LiveSplit integration will not work.") + + +def exceptionTraceback(message: str, exception: Exception): + setTextMessage( + message, + "\n".join(traceback.format_exception(None, exception, exception.__traceback__)))