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:
parent
dea01ae24b
commit
9d4e0995fd
44
rare/app.py
44
rare/app.py
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue