1
0
Fork 0
mirror of synced 2024-06-02 10:44:40 +12:00

RareAppException: Create exception handler that can show a dialog

The reason is that `sys.excepthook` is a global attribute which we
have to unset for threads because we show a Qt dialog in it and we
can't do that from threads. Before this change, we used to unset
it in threads, but since it is a global attr, that was unsetting it
for the whole application. We cannot reliably reset it because we
can have multiple threads executing and there will be race conditions.

To fix this situation, `RareAppException` implements a callback to
be patched into `sys.excepthook` which emits a signal to be serviced
by the the `RareAppException` instance in the main thread.
`RareAppException` can be subclassed to implement the
`RareAppException._handler` method for domain specific handling.

The `RareApp` base class instantiates its own `RareAppException`
instance for early basic handling. `RareAppException` is subclassed
into `RareException` and `RareLauncherExcpetion` in `Rare` and `RareLauncher`
respectively to implement the aforemention domain specific handling.
Each of these classes deletes the previous instance and replace it
with their specialized handlers.
This commit is contained in:
loathingKernel 2023-03-05 22:21:49 +02:00
parent dea01ae24b
commit 9d4e0995fd
No known key found for this signature in database
GPG key ID: CE0C72D0B53821FD
4 changed files with 76 additions and 47 deletions

View file

@ -16,34 +16,35 @@ from rare.components.dialogs.launch_dialog import LaunchDialog
from rare.components.main_window import MainWindow
from rare.shared import RareCore
from rare.utils import config_helper, paths
from rare.widgets.rare_app import RareApp
from rare.widgets.rare_app import RareApp, RareAppException
logger = logging.getLogger("Rare")
def excepthook(exc_type, exc_value, exc_tb):
tb = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
print("Error")
if exc_tb == HTTPError:
try:
if RareCore.instance().core().login():
return
else:
class RareException(RareAppException):
def __init__(self, parent=None):
super(RareException, self).__init__(parent=parent)
def _handler(self, exc_type, exc_value, exc_tb) -> bool:
if exc_type == HTTPError:
try:
if RareCore.instance() is not None:
if RareCore.instance().core().login():
return True
raise ValueError
except Exception as e:
logger.fatal(str(e))
QMessageBox.warning(None, "Error", QApplication.tr("Failed to login"))
QApplication.exit(1)
return
logger.fatal(tb)
QMessageBox.warning(None, "Error", tb)
QApplication.exit(1)
except Exception as e:
logger.fatal(str(e))
QMessageBox.warning(None, "Error", self.tr("Failed to login"))
QApplication.exit(1)
return False
class App(RareApp):
class Rare(RareApp):
def __init__(self, args: Namespace):
log_file = "Rare_{0}.log"
super(App, self).__init__(args, log_file)
super(Rare, self).__init__(args, log_file)
self._hook.deleteLater()
self._hook = RareException(self)
self.rcore = RareCore(args=args)
self.args = RareCore.instance().args()
self.signals = RareCore.instance().signals()
@ -119,13 +120,10 @@ class App(RareApp):
def start(args):
# set excepthook to show dialog with exception
sys.excepthook = excepthook
while True:
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
app = App(args)
app = Rare(args)
exit_code = app.exec_()
# if not restart
# restart app

View file

@ -16,7 +16,7 @@ from PyQt5.QtWidgets import QApplication
from rare.lgndr.core import LegendaryCore
from rare.models.launcher import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel
from rare.widgets.rare_app import RareApp
from rare.widgets.rare_app import RareApp, RareAppException
from .console import Console
from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError
@ -66,7 +66,25 @@ class PreLaunchThread(QRunnable):
return args
class GameProcessApp(RareApp):
class RareLauncherException(RareAppException):
def __init__(self, app: 'RareLauncher', args: Namespace, parent=None):
super(RareLauncherException, self).__init__(parent=parent)
self.__app = app
self.__args = args
def _handler(self, exc_type, exc_value, exc_tb) -> bool:
try:
self.__app.send_message(ErrorModel(
app_name=self.__args.app_name,
action=Actions.error,
error_string="".join(traceback.format_exception(exc_type, exc_value, exc_tb))
))
except RuntimeError:
pass
return False
class RareLauncher(RareApp):
game_process: QProcess
server: QLocalServer
socket: Optional[QLocalSocket] = None
@ -76,7 +94,9 @@ class GameProcessApp(RareApp):
def __init__(self, args: Namespace):
log_file = f"Rare_Launcher_{args.app_name}" + "_{0}.log"
super(GameProcessApp, self).__init__(args, log_file)
super(RareLauncher, self).__init__(args, log_file)
self._hook.deleteLater()
self._hook = RareLauncherException(self, args, self)
self.game_process = QProcess()
self.app_name = args.app_name
self.logger = getLogger(self.app_name)
@ -237,23 +257,9 @@ def start_game(args: Namespace):
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
app = GameProcessApp(args)
app = RareLauncher(args)
app.setQuitOnLastWindowClosed(True)
def excepthook(exc_type, exc_value, exc_tb):
tb = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
app.logger.fatal(tb)
try:
app.send_message(ErrorModel(
app_name=args.app_name,
action=Actions.error,
error_string=tb
))
except RuntimeError:
pass
app.stop()
sys.excepthook = excepthook
if not app.success:
return
app.start(args)

View file

@ -18,7 +18,6 @@ class Worker(QRunnable):
"""
def __init__(self):
sys.excepthook = sys.__excepthook__
super(Worker, self).__init__()
self.setAutoDelete(True)
self.__signals: Optional[QObject] = None
@ -40,7 +39,7 @@ class Worker(QRunnable):
@pyqtSlot()
def run(self):
self.run_real()
self.signals.deleteLater()
self.__signals.deleteLater()
class QueueWorkerState(IntEnum):

View file

@ -3,23 +3,49 @@ import os
import platform
import sys
import time
import traceback
from argparse import Namespace
import legendary
from PyQt5.QtCore import QSettings, QTranslator, QT_VERSION_STR, PYQT_VERSION_STR
from PyQt5.QtCore import QSettings, QTranslator, QT_VERSION_STR, PYQT_VERSION_STR, QObject, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QApplication, QMessageBox
import rare.resources.resources
from rare.utils import paths
from rare.utils.misc import set_color_pallete, set_style_sheet, get_static_style
class RareApp(QApplication):
logger = logging.getLogger("RareApp")
class RareAppException(QObject):
exception = pyqtSignal(object, object, object)
def __init__(self, parent=None):
self.logger = logging.getLogger(type(self).__name__)
super(RareAppException, self).__init__(parent=parent)
sys.excepthook = self._excepthook
self.exception.connect(self._on_exception)
def _excepthook(self, exc_type: object, exc_value: object, exc_tb: object):
self.exception.emit(exc_type, exc_value, exc_tb)
def _handler(self, exc_type, exc_value, exc_tb) -> bool:
return False
@pyqtSlot(object, object, object)
def _on_exception(self, exc_type, exc_value, exc_tb):
message = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
if self._handler(exc_type, exc_value, exc_tb):
return
self.logger.fatal(message)
QMessageBox.warning(None, exc_type.__name__, message)
QApplication.exit(1)
class RareApp(QApplication):
def __init__(self, args: Namespace, log_file: str):
self.logger = logging.getLogger(type(self).__name__)
super(RareApp, self).__init__(sys.argv)
self._hook = RareAppException(self)
self.setQuitOnLastWindowClosed(False)
self.setApplicationName("Rare")