import logging 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, QObject, pyqtSignal, pyqtSlot, Qt, QLibraryInfo, QLocale, ) from PyQt5.QtGui import QIcon from PyQt5.QtWidgets import QApplication, QMessageBox import rare.resources.resources from rare.models.options import options from rare.utils import paths from rare.utils.misc import set_color_pallete, set_style_sheet, get_static_style class RareAppException(QObject): exception = pyqtSignal(object, object, object) def __init__(self, parent=None): super(RareAppException, self).__init__(parent=parent) self.logger = logging.getLogger(type(self).__name__) 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) action = QMessageBox.warning( None, exc_type.__name__, message, buttons=QMessageBox.Ignore | QMessageBox.Abort, defaultButton=QMessageBox.Abort ) if action == QMessageBox.Abort: QApplication.instance().quit() class RareApp(QApplication): def __init__(self, args: Namespace, log_file: str): super(RareApp, self).__init__(sys.argv) self.logger = logging.getLogger(type(self).__name__) self._hook = RareAppException(self) self.setQuitOnLastWindowClosed(False) self.setAttribute(Qt.AA_DontUseNativeDialogs, True) self.setDesktopFileName("rare") self.setApplicationName("Rare") self.setOrganizationName("Rare") # Create directories after QStandardPaths has been initialized paths.create_dirs() # Clean any existing logging handlers from library imports for handler in logging.root.handlers[:]: logging.root.removeHandler(handler) start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute file_handler = logging.FileHandler( filename=os.path.join(paths.log_dir(), log_file.format(start_time)), encoding="utf-8", ) file_handler.setFormatter(fmt=logging.Formatter("[%(name)s] %(levelname)s: %(message)s")) # Set up common logging channel to stderr if args.debug: logging.basicConfig( format="[%(name)s] %(levelname)s: %(message)s", level=logging.DEBUG, stream=sys.stderr, ) file_handler.setLevel(logging.DEBUG) logging.root.addHandler(file_handler) logging.getLogger().setLevel(level=logging.DEBUG) # keep requests, asyncio and pillow quiet logging.getLogger("requests").setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("asyncio").setLevel(logging.WARNING) else: logging.basicConfig( format="[%(name)s] %(levelname)s: %(message)s", level=logging.INFO, stream=sys.stderr, ) file_handler.setLevel(logging.DEBUG) logging.root.addHandler(file_handler) self.logger.info( f"Launching Rare version {rare.__version__} Codename: {rare.__codename__}\n" f" - Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n" f" - Operating System: {platform.system()}, Python version: {platform.python_version()}\n" f" - Running {sys.executable} {' '.join(sys.argv)}\n" f" - Qt version: {QT_VERSION_STR}, PyQt version: {PYQT_VERSION_STR}" ) self.settings = QSettings(self) # # Translator # self.translator = QTranslator(self) # self.qt_translator = QTranslator(self) # Style # lk: this is a bit silly but serves well until we have a class # lk: store the default qt style name from the system on startup as a property for later reference self.setProperty("rareDefaultQtStyle", self.style().objectName()) if ( self.settings.value(options.style_sheet.key, None) is None and self.settings.value(options.color_scheme.key, None) is None ): self.settings.setValue(options.color_scheme.key, options.color_scheme.default) self.settings.setValue(options.style_sheet.key, options.style_sheet.default) if color_scheme := self.settings.value(options.color_scheme.key, False): self.settings.setValue(options.style_sheet.key, "") set_color_pallete(color_scheme) elif style_sheet := self.settings.value(options.style_sheet.key, False): self.settings.setValue(options.color_scheme.key, "") set_style_sheet(style_sheet) else: self.setStyleSheet(get_static_style()) self.setWindowIcon(QIcon(":/images/Rare.png")) def load_translator(self, lang: str): # translator for qt stuff locale = QLocale(lang) self.logger.info("Using locale: %s", locale.name()) translations = { "qtbase": QLibraryInfo.location(QLibraryInfo.TranslationsPath), "rare": os.path.join(paths.resources_path, "languages"), } for filename, path in translations.items(): translator = QTranslator(self) if translator.load(locale, filename, "_", path): self.logger.debug("Loaded translation file: %s", translator.filePath()) self.installTranslator(translator) else: self.logger.info("Couldn't find translation for locale: %s", locale.name()) translator.deleteLater()