From 2c36ffcd51c82e48be2ba282a12c5ff5350f803f Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 1 Sep 2022 11:49:42 +0300 Subject: [PATCH 01/15] Lgndr: Adjust for https://github.com/derrod/legendary/commit/d8af06c936a446b245ae27f3f39baabbf25b946d --- rare/components/dialogs/install_dialog.py | 22 +++++++++---------- .../tabs/downloads/download_thread.py | 4 ++-- rare/lgndr/api_arguments.py | 2 +- rare/lgndr/cli.py | 8 +++---- rare/models/install.py | 4 ++-- rare/ui/components/dialogs/install_dialog.py | 20 ++++++++--------- rare/ui/components/dialogs/install_dialog.ui | 4 ++-- .../dialogs/install_dialog_advanced.py | 22 +++++++++---------- .../dialogs/install_dialog_advanced.ui | 6 ++--- 9 files changed, 46 insertions(+), 46 deletions(-) diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py index c4e2281c..ca58ecb6 100644 --- a/rare/components/dialogs/install_dialog.py +++ b/rare/components/dialogs/install_dialog.py @@ -145,9 +145,9 @@ class InstallDialog(QDialog): self.ui.shortcut_cb.setChecked(False) self.ui.shortcut_cb.setToolTip(self.tr("Creating a shortcut is not supported on MacOS")) - self.ui.install_preqs_lbl.setVisible(False) - self.ui.install_preqs_check.setVisible(False) - self.ui.install_preqs_check.stateChanged.connect(lambda: self.non_reload_option_changed("install_preqs")) + self.ui.install_prereqs_lbl.setVisible(False) + self.ui.install_prereqs_check.setVisible(False) + self.ui.install_prereqs_check.stateChanged.connect(lambda: self.non_reload_option_changed("install_prereqs")) self.non_reload_option_changed("shortcut") @@ -155,7 +155,7 @@ class InstallDialog(QDialog): self.ui.verify_button.clicked.connect(self.verify_clicked) self.ui.install_button.clicked.connect(self.install_clicked) - self.ui.install_preqs_check.setChecked(self.dl_item.options.install_preqs) + self.ui.install_prereqs_check.setChecked(self.dl_item.options.install_prereqs) self.ui.install_dialog_layout.setSizeConstraint(QLayout.SetFixedSize) @@ -209,7 +209,7 @@ class InstallDialog(QDialog): self.dl_item.options.ignore_space = self.ui.ignore_space_check.isChecked() self.dl_item.options.no_install = self.ui.download_only_check.isChecked() self.dl_item.options.platform = self.ui.platform_combo_box.currentText() - self.dl_item.options.install_preqs = self.ui.install_preqs_check.isChecked() + self.dl_item.options.install_prereqs = self.ui.install_prereqs_check.isChecked() self.dl_item.options.create_shortcut = self.ui.shortcut_cb.isChecked() if self.sdl_list_cbs: self.dl_item.options.install_tag = [""] @@ -254,8 +254,8 @@ class InstallDialog(QDialog): elif option == "shortcut": QSettings().setValue("create_shortcut", self.ui.shortcut_cb.isChecked()) self.dl_item.options.create_shortcut = self.ui.shortcut_cb.isChecked() - elif option == "install_preqs": - self.dl_item.options.install_preqs = self.ui.install_preqs_check.isChecked() + elif option == "install_prereqs": + self.dl_item.options.install_prereqs = self.ui.install_prereqs_check.isChecked() def cancel_clicked(self): if self.config_tags: @@ -289,10 +289,10 @@ class InstallDialog(QDialog): self.ui.cancel_button.setEnabled(True) if pf.system() == "Windows" or ArgumentsSingleton().debug: if dl_item.igame.prereq_info and not dl_item.igame.prereq_info.get("installed", False): - self.ui.install_preqs_check.setVisible(True) - self.ui.install_preqs_lbl.setVisible(True) - self.ui.install_preqs_check.setChecked(True) - self.ui.install_preqs_check.setText( + self.ui.install_prereqs_check.setVisible(True) + self.ui.install_prereqs_lbl.setVisible(True) + self.ui.install_prereqs_check.setChecked(True) + self.ui.install_prereqs_check.setText( self.tr("Also install: {}").format(dl_item.igame.prereq_info.get("name", "")) ) if self.silent: diff --git a/rare/components/tabs/downloads/download_thread.py b/rare/components/tabs/downloads/download_thread.py index 5f672558..cefc4a94 100644 --- a/rare/components/tabs/downloads/download_thread.py +++ b/rare/components/tabs/downloads/download_thread.py @@ -98,7 +98,7 @@ class DownloadThread(QThread): # postinstall, # self.item.download.igame, # False, - # self.item.options.install_preqs, + # self.item.options.install_prereqs, # ) self._handle_postinstall(postinstall, self.item.download.igame) @@ -140,7 +140,7 @@ class DownloadThread(QThread): logger.info("This game lists the following prequisites to be installed:") logger.info(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}') if platform.system() == "Windows": - if not self.item.options.install_preqs: + if not self.item.options.install_prereqs: logger.info("Marking prerequisites as installed...") self.core.prereq_installed(self.item.download.igame.app_name) else: diff --git a/rare/lgndr/api_arguments.py b/rare/lgndr/api_arguments.py index abbe9bba..03a28942 100644 --- a/rare/lgndr/api_arguments.py +++ b/rare/lgndr/api_arguments.py @@ -114,7 +114,7 @@ class LgndrInstallGameRealArgs: dlm_debug: bool = False yes: bool = False # Rare: Extra arguments - install_preqs: bool = False + install_prereqs: bool = False indirect_status: LgndrIndirectStatus = LgndrIndirectStatus() ui_update: Callable[[UIUpdate], None] = lambda ui: None dlm_signals: DLManagerSignals = DLManagerSignals() diff --git a/rare/lgndr/cli.py b/rare/lgndr/cli.py index 34622028..667b19a3 100644 --- a/rare/lgndr/cli.py +++ b/rare/lgndr/cli.py @@ -248,7 +248,7 @@ class LegendaryCLI(LegendaryCLIReal): postinstall = self.core.install_game(igame) if postinstall: - self._handle_postinstall(postinstall, igame, yes=args.yes, choice=args.install_preqs) + self._handle_postinstall(postinstall, igame, skip_prereqs=args.yes, choice=args.install_prereqs) dlcs = self.core.get_dlc_for_game(game.app_name) if dlcs and not args.skip_dlcs: @@ -301,7 +301,7 @@ class LegendaryCLI(LegendaryCLIReal): self.core.uninstall_tag(old_igame) self.core.install_game(old_igame) - def _handle_postinstall(self, postinstall, igame, yes=False, choice=True): + def _handle_postinstall(self, postinstall, igame, skip_prereqs=False, choice=True): # Override logger for the local context to use message as part of the indirect return value logger = LgndrIndirectLogger(LgndrIndirectStatus(), self.logger) # noinspection PyShadowingBuiltins @@ -309,12 +309,12 @@ class LegendaryCLI(LegendaryCLIReal): # noinspection PyShadowingBuiltins def input(x): return 'y' if choice else 'i' - print('\nThis game lists the following prequisites to be installed:') + print('\nThis game lists the following prerequisites to be installed:') print(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}') print('') if os.name == 'nt': - if yes: + if skip_prereqs: c = 'n' # we don't want to launch anything, just silent install. else: choice = input('Do you wish to install the prerequisites? ([y]es, [n]o, [i]gnore): ') diff --git a/rare/models/install.py b/rare/models/install.py index b4dedef8..d9a487c9 100644 --- a/rare/models/install.py +++ b/rare/models/install.py @@ -31,7 +31,7 @@ class InstallOptionsModel: overlay: bool = False update: bool = False silent: bool = False - install_preqs: bool = pf.system() == "Windows" + install_prereqs: bool = pf.system() == "Windows" def __post_init__(self): self.sdl_prompt: Callable[[str, str], list] = \ @@ -41,7 +41,7 @@ class InstallOptionsModel: return { k: getattr(self, k) for k in self.__dict__ - if k not in ["update", "silent", "create_shortcut", "overlay", "install_preqs"] + if k not in ["update", "silent", "create_shortcut", "overlay", "install_prereqs"] } diff --git a/rare/ui/components/dialogs/install_dialog.py b/rare/ui/components/dialogs/install_dialog.py index df52eea6..41afaa0c 100644 --- a/rare/ui/components/dialogs/install_dialog.py +++ b/rare/ui/components/dialogs/install_dialog.py @@ -109,17 +109,17 @@ class Ui_InstallDialog(object): self.max_memory_info_label.setObjectName("max_memory_info_label") self.max_memory_layout.addWidget(self.max_memory_info_label) self.advanced_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.max_memory_layout) - self.install_preqs_lbl = QtWidgets.QLabel(InstallDialog) - self.install_preqs_lbl.setObjectName("install_preqs_lbl") - self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.install_preqs_lbl) - self.install_preqs_check = QtWidgets.QCheckBox(InstallDialog) + self.install_prereqs_lbl = QtWidgets.QLabel(InstallDialog) + self.install_prereqs_lbl.setObjectName("install_prereqs_lbl") + self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.install_prereqs_lbl) + self.install_prereqs_check = QtWidgets.QCheckBox(InstallDialog) font = QtGui.QFont() font.setItalic(True) - self.install_preqs_check.setFont(font) - self.install_preqs_check.setText("") - self.install_preqs_check.setChecked(False) - self.install_preqs_check.setObjectName("install_preqs_check") - self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.install_preqs_check) + self.install_prereqs_check.setFont(font) + self.install_prereqs_check.setText("") + self.install_prereqs_check.setChecked(False) + self.install_prereqs_check.setObjectName("install_prereqs_check") + self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.install_prereqs_check) self.dl_optimizations_label = QtWidgets.QLabel(InstallDialog) self.dl_optimizations_label.setObjectName("dl_optimizations_label") self.advanced_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.dl_optimizations_label) @@ -216,7 +216,7 @@ class Ui_InstallDialog(object): self.max_memory_label.setText(_translate("InstallDialog", "Max shared memory")) self.max_memory_spin.setSuffix(_translate("InstallDialog", "MiB")) self.max_memory_info_label.setText(_translate("InstallDialog", "Less is slower (0: Default)")) - self.install_preqs_lbl.setText(_translate("InstallDialog", "Install prerequisites")) + self.install_prereqs_lbl.setText(_translate("InstallDialog", "Install prerequisites")) self.dl_optimizations_label.setText(_translate("InstallDialog", "Enable reordering")) self.force_download_label.setText(_translate("InstallDialog", "Force redownload")) self.ignore_space_label.setText(_translate("InstallDialog", "Ignore free space")) diff --git a/rare/ui/components/dialogs/install_dialog.ui b/rare/ui/components/dialogs/install_dialog.ui index 0e3c9e53..c276cfda 100644 --- a/rare/ui/components/dialogs/install_dialog.ui +++ b/rare/ui/components/dialogs/install_dialog.ui @@ -185,14 +185,14 @@ - + Install prerequisites - + true diff --git a/rare/ui/components/dialogs/install_dialog_advanced.py b/rare/ui/components/dialogs/install_dialog_advanced.py index 36151d05..c853bb74 100644 --- a/rare/ui/components/dialogs/install_dialog_advanced.py +++ b/rare/ui/components/dialogs/install_dialog_advanced.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_InstallDialogAdvanced(object): def setupUi(self, InstallDialogAdvanced): InstallDialogAdvanced.setObjectName("InstallDialogAdvanced") - InstallDialogAdvanced.resize(359, 208) + InstallDialogAdvanced.resize(379, 208) self.install_dialog_advanced_layout = QtWidgets.QFormLayout(InstallDialogAdvanced) self.install_dialog_advanced_layout.setObjectName("install_dialog_advanced_layout") self.max_workers_label = QtWidgets.QLabel(InstallDialogAdvanced) @@ -63,17 +63,17 @@ class Ui_InstallDialogAdvanced(object): self.max_memory_info_label.setObjectName("max_memory_info_label") self.max_memory_layout.addWidget(self.max_memory_info_label) self.install_dialog_advanced_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.max_memory_layout) - self.install_preqs_lbl = QtWidgets.QLabel(InstallDialogAdvanced) - self.install_preqs_lbl.setObjectName("install_preqs_lbl") - self.install_dialog_advanced_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.install_preqs_lbl) - self.install_preqs_check = QtWidgets.QCheckBox(InstallDialogAdvanced) + self.install_prereqs_lbl = QtWidgets.QLabel(InstallDialogAdvanced) + self.install_prereqs_lbl.setObjectName("install_prereqs_lbl") + self.install_dialog_advanced_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.install_prereqs_lbl) + self.install_prereqs_check = QtWidgets.QCheckBox(InstallDialogAdvanced) font = QtGui.QFont() font.setItalic(True) - self.install_preqs_check.setFont(font) - self.install_preqs_check.setText("") - self.install_preqs_check.setChecked(False) - self.install_preqs_check.setObjectName("install_preqs_check") - self.install_dialog_advanced_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.install_preqs_check) + self.install_prereqs_check.setFont(font) + self.install_prereqs_check.setText("") + self.install_prereqs_check.setChecked(False) + self.install_prereqs_check.setObjectName("install_prereqs_check") + self.install_dialog_advanced_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.install_prereqs_check) self.dl_optimizations_label = QtWidgets.QLabel(InstallDialogAdvanced) self.dl_optimizations_label.setObjectName("dl_optimizations_label") self.install_dialog_advanced_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.dl_optimizations_label) @@ -119,7 +119,7 @@ class Ui_InstallDialogAdvanced(object): self.max_memory_label.setText(_translate("InstallDialogAdvanced", "Max shared memory")) self.max_memory_spin.setSuffix(_translate("InstallDialogAdvanced", "MiB")) self.max_memory_info_label.setText(_translate("InstallDialogAdvanced", "Less is slower (0: Default)")) - self.install_preqs_lbl.setText(_translate("InstallDialogAdvanced", "Install prerequisites")) + self.install_prereqs_lbl.setText(_translate("InstallDialogAdvanced", "Install prerequisites")) self.dl_optimizations_label.setText(_translate("InstallDialogAdvanced", "Enable reordering")) self.force_download_label.setText(_translate("InstallDialogAdvanced", "Force redownload")) self.ignore_space_label.setText(_translate("InstallDialogAdvanced", "Ignore free space")) diff --git a/rare/ui/components/dialogs/install_dialog_advanced.ui b/rare/ui/components/dialogs/install_dialog_advanced.ui index fda1db03..160c386f 100644 --- a/rare/ui/components/dialogs/install_dialog_advanced.ui +++ b/rare/ui/components/dialogs/install_dialog_advanced.ui @@ -6,7 +6,7 @@ 0 0 - 359 + 379 208 @@ -102,14 +102,14 @@ - + Install prerequisites - + true From fb708ce5fd409d455f73860b0c1d1030f604ebba Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 1 Sep 2022 15:13:58 +0300 Subject: [PATCH 02/15] LoginDialog: Update to legendary 0.20.28 --- rare/app.py | 2 +- rare/components/dialogs/launch_dialog.py | 11 +++++---- rare/components/dialogs/login/__init__.py | 24 +++++++++++++------ .../components/dialogs/login/browser_login.py | 21 +++++++--------- .../components/dialogs/login/browser_login.py | 9 ++----- .../components/dialogs/login/browser_login.ui | 6 ++--- rare/widgets/rare_app.py | 2 -- 7 files changed, 37 insertions(+), 38 deletions(-) diff --git a/rare/app.py b/rare/app.py index 46be17fd..35671637 100644 --- a/rare/app.py +++ b/rare/app.py @@ -113,7 +113,7 @@ class App(RareApp): ) # launch app - self.launch_dialog = LaunchDialog() + self.launch_dialog = LaunchDialog(parent=None) self.launch_dialog.quit_app.connect(self.launch_dialog.close) self.launch_dialog.quit_app.connect(lambda ec: exit(ec)) self.launch_dialog.start_app.connect(self.start_app) diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 4a1de3e8..76f58441 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -82,14 +82,13 @@ class ApiRequestWorker(LaunchWorker): self.signals.result.emit(result, "32bit") -class LaunchDialog(QDialog, Ui_LaunchDialog): +class LaunchDialog(QDialog): quit_app = pyqtSignal(int) start_app = pyqtSignal() completed = 0 def __init__(self, parent=None): super(LaunchDialog, self).__init__(parent=parent) - self.setupUi(self) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags( Qt.Window @@ -101,6 +100,8 @@ class LaunchDialog(QDialog, Ui_LaunchDialog): | Qt.MSWindowsFixedSizeDialogHint ) self.setWindowModality(Qt.WindowModal) + self.ui = Ui_LaunchDialog() + self.ui.setupUi(self) self.core = LegendaryCoreSingleton() self.args = ArgumentsSingleton() @@ -143,7 +144,7 @@ class LaunchDialog(QDialog, Ui_LaunchDialog): def launch(self): if not self.args.offline: - self.image_info.setText(self.tr("Downloading Images")) + self.ui.image_info.setText(self.tr("Downloading Images")) image_worker = ImageWorker() image_worker.signals.result.connect(self.handle_api_worker_result) image_worker.signals.progress.connect(self.update_image_progbar) @@ -202,13 +203,13 @@ class LaunchDialog(QDialog, Ui_LaunchDialog): self.finish() def update_image_progbar(self, i: int): - self.image_prog_bar.setValue(i) + self.ui.image_prog_bar.setValue(i) def finish(self): self.completed += 1 if self.completed == 2: logger.info("App starting") - self.image_info.setText(self.tr("Starting...")) + self.ui.image_info.setText(self.tr("Starting...")) ApiResultsSingleton(self.api_results) self.completed += 1 self.start_app.emit() diff --git a/rare/components/dialogs/login/__init__.py b/rare/components/dialogs/login/__init__.py index 2357adcf..eb0399e6 100644 --- a/rare/components/dialogs/login/__init__.py +++ b/rare/components/dialogs/login/__init__.py @@ -36,8 +36,6 @@ class LoginDialog(QDialog): def __init__(self, core: LegendaryCore, parent=None): super(LoginDialog, self).__init__(parent=parent) - self.ui = Ui_LoginDialog() - self.ui.setupUi(self) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags( Qt.Window @@ -50,6 +48,8 @@ class LoginDialog(QDialog): | Qt.MSWindowsFixedSizeDialogHint ) self.setWindowModality(Qt.WindowModal) + self.ui = Ui_LoginDialog() + self.ui.setupUi(self) self.core = core self.args = ArgumentsSingleton() @@ -76,7 +76,10 @@ class LoginDialog(QDialog): self.ui.back_button.setEnabled(False) self.landing_page.ui.login_browser_radio.clicked.connect(lambda: self.ui.next_button.setEnabled(True)) + self.landing_page.ui.login_browser_radio.clicked.connect(self.browser_radio_clicked) self.landing_page.ui.login_import_radio.clicked.connect(lambda: self.ui.next_button.setEnabled(True)) + self.landing_page.ui.login_import_radio.clicked.connect(self.import_radio_clicked) + self.ui.exit_button.clicked.connect(self.close) self.ui.back_button.clicked.connect(self.back_clicked) self.ui.next_button.clicked.connect(self.next_clicked) @@ -90,15 +93,22 @@ class LoginDialog(QDialog): self.ui.next_button.setEnabled(True) self.login_stack.slideInIndex(self.pages.landing) + def browser_radio_clicked(self): + self.login_stack.slideInIndex(self.pages.browser) + self.ui.back_button.setEnabled(True) + self.ui.next_button.setEnabled(False) + + def import_radio_clicked(self): + self.login_stack.slideInIndex(self.pages.import_egl) + self.ui.back_button.setEnabled(True) + self.ui.next_button.setEnabled(self.import_page.is_valid()) + def next_clicked(self): if self.login_stack.currentIndex() == self.pages.landing: if self.landing_page.ui.login_browser_radio.isChecked(): - self.login_stack.slideInIndex(self.pages.browser) - self.ui.next_button.setEnabled(False) + self.browser_radio_clicked() if self.landing_page.ui.login_import_radio.isChecked(): - self.login_stack.slideInIndex(self.pages.import_egl) - self.ui.next_button.setEnabled(self.import_page.is_valid()) - self.ui.back_button.setEnabled(True) + self.import_radio_clicked() elif self.login_stack.currentIndex() == self.pages.browser: self.browser_page.do_login() elif self.login_stack.currentIndex() == self.pages.import_egl: diff --git a/rare/components/dialogs/login/browser_login.py b/rare/components/dialogs/login/browser_login.py index 49c1eb66..6be59419 100644 --- a/rare/components/dialogs/login/browser_login.py +++ b/rare/components/dialogs/login/browser_login.py @@ -18,9 +18,6 @@ logger = getLogger("BrowserLogin") class BrowserLogin(QFrame): success = pyqtSignal() changed = pyqtSignal() - login_url = ( - "https://www.epicgames.com/id/login?redirectUrl=https%3A%2F%2Fwww.epicgames.com%2Fid%2Fapi%2Fredirect" - ) def __init__(self, core: LegendaryCore, parent=None): super(BrowserLogin, self).__init__(parent=parent) @@ -29,9 +26,10 @@ class BrowserLogin(QFrame): self.ui.setupUi(self) self.core = core + self.login_url = self.core.egs.get_auth_url() self.sid_edit = IndicatorLineEdit( - placeholder=self.tr("Insert SID here"), edit_func=self.text_changed, parent=self + placeholder=self.tr("Insert authorizationCode here"), edit_func=self.text_changed, parent=self ) self.ui.link_text.setText(self.login_url) self.ui.copy_button.setIcon(icon("mdi.content-copy", "fa.copy")) @@ -56,7 +54,7 @@ class BrowserLogin(QFrame): text = text.strip() if text.startswith("{") and text.endswith("}"): try: - text = json.loads(text).get("sid") + text = json.loads(text).get("authorizationCode") except json.JSONDecodeError: return False, text, IndicatorLineEdit.reasons.wrong_format elif '"' in text: @@ -67,10 +65,9 @@ class BrowserLogin(QFrame): def do_login(self): self.ui.status_label.setText(self.tr("Logging in...")) - sid = self.sid_edit.text() + auth_code = self.sid_edit.text() try: - token = self.core.auth_sid(sid) - if self.core.auth_code(token): + if self.core.auth_code(auth_code): logger.info(f"Successfully logged in as {self.core.lgd.userdata['displayName']}") self.success.emit() else: @@ -80,13 +77,11 @@ class BrowserLogin(QFrame): logger.warning(e) def open_browser(self): - if webview_login.webview_available is False: - logger.warning("You don't have webengine installed, " "you will need to manually copy the SID.") + if not webview_login.webview_available: + logger.warning("You don't have webengine installed, you will need to manually copy the authorizationCode.") QDesktopServices.openUrl(QUrl(self.login_url)) else: - if webview_login.do_webview_login( - callback_sid=self.core.auth_sid, callback_code=self.core.auth_code - ): + if webview_login.do_webview_login(callback_code=self.core.auth_ex_token): logger.info("Successfully logged in as " f"{self.core.lgd.userdata['displayName']}") self.success.emit() else: diff --git a/rare/ui/components/dialogs/login/browser_login.py b/rare/ui/components/dialogs/login/browser_login.py index 30fadae5..804e41f3 100644 --- a/rare/ui/components/dialogs/login/browser_login.py +++ b/rare/ui/components/dialogs/login/browser_login.py @@ -14,12 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_BrowserLogin(object): def setupUi(self, BrowserLogin): BrowserLogin.setObjectName("BrowserLogin") - BrowserLogin.resize(400, 200) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(BrowserLogin.sizePolicy().hasHeightForWidth()) - BrowserLogin.setSizePolicy(sizePolicy) + BrowserLogin.resize(182, 210) BrowserLogin.setWindowTitle("BrowserLogin") self.browser_layout = QtWidgets.QGridLayout(BrowserLogin) self.browser_layout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) @@ -78,7 +73,7 @@ class Ui_BrowserLogin(object): _translate = QtCore.QCoreApplication.translate self.open_button.setText(_translate("BrowserLogin", "Open Browser")) self.title_label.setText(_translate("BrowserLogin", "Login through browser")) - self.info_label.setText(_translate("BrowserLogin", "Click the button to open the login page in a browser or copy the link and paste it in a browser. After logging in, copy the SID code in the input above.")) + self.info_label.setText(_translate("BrowserLogin", "Click the button to open the login page in a browser or copy the link and paste it in a browser. After logging in, copy the authorizationCode in the input above.")) if __name__ == "__main__": diff --git a/rare/ui/components/dialogs/login/browser_login.ui b/rare/ui/components/dialogs/login/browser_login.ui index a7712213..98577f99 100644 --- a/rare/ui/components/dialogs/login/browser_login.ui +++ b/rare/ui/components/dialogs/login/browser_login.ui @@ -6,8 +6,8 @@ 0 0 - 246 - 184 + 182 + 210 @@ -94,7 +94,7 @@ - Click the button to open the login page in a browser or copy the link and paste it in a browser. After logging in, copy the SID code in the input above. + Click the button to open the login page in a browser or copy the link and paste it in a browser. After logging in, copy the <b><code>authorizationCode</code></b> in the input above. true diff --git a/rare/widgets/rare_app.py b/rare/widgets/rare_app.py index a6964350..1b03cabc 100644 --- a/rare/widgets/rare_app.py +++ b/rare/widgets/rare_app.py @@ -20,7 +20,6 @@ class RareApp(QApplication): def __init__(self): super(RareApp, self).__init__(sys.argv) self.setQuitOnLastWindowClosed(False) - self.core = LegendaryCore() if hasattr(Qt, "AA_UseHighDpiPixmaps"): self.setAttribute(Qt.AA_UseHighDpiPixmaps) @@ -36,7 +35,6 @@ class RareApp(QApplication): # 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("color_scheme", None) is None and self.settings.value("style_sheet", None) is None From 0388d4bf9dc397d0b01106287576c5a364605f52 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 1 Sep 2022 22:49:09 +0300 Subject: [PATCH 03/15] ImportLogin: Fix failure to login using EGL data --- rare/components/dialogs/launch_dialog.py | 14 ++++++--- rare/components/dialogs/login/import_login.py | 29 ++++++++++++++----- .../components/dialogs/login/import_login.py | 10 ++----- .../components/dialogs/login/import_login.ui | 4 +-- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 76f58441..1ea25720 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -2,7 +2,7 @@ import platform from logging import getLogger from PyQt5.QtCore import Qt, pyqtSignal, QRunnable, QObject, QThreadPool, QSettings -from PyQt5.QtWidgets import QDialog, qApp +from PyQt5.QtWidgets import QDialog, QApplication from requests.exceptions import ConnectionError, HTTPError from rare.components.dialogs.login import LoginDialog @@ -108,23 +108,29 @@ class LaunchDialog(QDialog): self.thread_pool = QThreadPool().globalInstance() self.api_results = ApiResults() + self.login_dialog = LoginDialog(core=self.core, parent=self) + def login(self): do_launch = True try: if self.args.offline: pass else: - qApp.processEvents() + QApplication.instance().processEvents() if self.core.login(): logger.info("You are logged in") else: raise ValueError("You are not logged in. Open Login Window") except ValueError as e: logger.info(str(e)) + # Force an update check and notice in case there are API changes + self.core.check_for_updates(force=True) + self.core.force_show_update = True # Do not set parent, because it won't show a task bar icon # Update: Inherit the same parent as LaunchDialog - do_launch = LoginDialog(core=self.core, parent=self.parent()).login() - except ConnectionError as e: + do_launch = self.login_dialog.login() + self.login_dialog.deleteLater() + except (HTTPError, ConnectionError) as e: logger.warning(e) self.args.offline = True finally: diff --git a/rare/components/dialogs/login/import_login.py b/rare/components/dialogs/login/import_login.py index b891e685..093f3c44 100644 --- a/rare/components/dialogs/login/import_login.py +++ b/rare/components/dialogs/login/import_login.py @@ -5,6 +5,7 @@ from logging import getLogger from PyQt5.QtCore import pyqtSignal from PyQt5.QtWidgets import QFrame, QFileDialog from legendary.core import LegendaryCore +from legendary.utils.wine_helpers import read_registry, get_shell_folders from rare.ui.components.dialogs.login.import_login import Ui_ImportLogin @@ -18,7 +19,7 @@ class ImportLogin(QFrame): localappdata = os.path.expandvars("%LOCALAPPDATA%") else: localappdata = os.path.join("drive_c/users", getuser(), "Local Settings/Application Data") - appdata_path = os.path.join(localappdata, "EpicGamesLauncher/Saved/Config/Windows") + egl_appdata = os.path.join(localappdata, "EpicGamesLauncher", "Saved", "Config", "Windows") found = False def __init__(self, core: LegendaryCore, parent=None): @@ -33,16 +34,18 @@ class ImportLogin(QFrame): self.text_egl_notfound = self.tr("Could not find EGL Program Data. ") if os.name == "nt": - if not self.core.egl.appdata_path and os.path.exists(self.appdata_path): - self.core.egl.appdata_path = self.appdata_path + if not self.core.egl.appdata_path and os.path.exists(self.egl_appdata): + self.core.egl.appdata_path = self.egl_appdata if not self.core.egl.appdata_path: self.ui.status_label.setText(self.text_egl_notfound) else: self.ui.status_label.setText(self.text_egl_found) self.found = True else: + if wine_pfx := self.core.egl.programdata_path.split("drive_c"): + self.ui.prefix_combo.addItems(wine_pfx) self.ui.info_label.setText( - self.tr("Please select the Wine prefix" " where Epic Games Launcher is installed. ") + self.tr("Please select the Wine prefix where Epic Games Launcher is installed. ") + self.ui.info_label.text() ) prefixes = self.get_wine_prefixes() @@ -62,7 +65,7 @@ class ImportLogin(QFrame): ] prefixes = [] for prefix in possible_prefixes: - if os.path.exists(os.path.join(prefix, self.appdata_path)): + if os.path.exists(os.path.join(prefix, self.egl_appdata)): prefixes.append(prefix) return prefixes @@ -73,18 +76,28 @@ class ImportLogin(QFrame): names = prefix_dialog.selectedFiles() self.ui.prefix_combo.setCurrentText(names[0]) - def is_valid(self): + def is_valid(self) -> bool: if os.name == "nt": return self.found else: - return os.path.exists(os.path.join(self.ui.prefix_combo.currentText(), self.appdata_path)) + egl_wine_pfx = self.ui.prefix_combo.currentText() + try: + wine_folders = get_shell_folders(read_registry(egl_wine_pfx), egl_wine_pfx) + self.egl_appdata = os.path.realpath( + os.path.join(wine_folders['Local AppData'], 'EpicGamesLauncher', 'Saved', 'Config', 'Windows')) + if path_exists := os.path.exists(self.egl_appdata): + self.ui.status_label.setText(self.text_egl_found) + return path_exists + except KeyError: + return False def do_login(self): self.ui.status_label.setText(self.tr("Loading...")) if os.name == "nt": pass else: - self.core.egl.appdata_path = os.path.join(self.ui.prefix_combo.currentText(), self.appdata_path) + logger.info(f'Using EGL appdata path at "{self.egl_appdata}"') + self.core.egl.appdata_path = self.egl_appdata try: if self.core.auth_import(): logger.info(f"Logged in as {self.core.lgd.userdata['displayName']}") diff --git a/rare/ui/components/dialogs/login/import_login.py b/rare/ui/components/dialogs/login/import_login.py index b4c61533..9c43b0ce 100644 --- a/rare/ui/components/dialogs/login/import_login.py +++ b/rare/ui/components/dialogs/login/import_login.py @@ -14,12 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ImportLogin(object): def setupUi(self, ImportLogin): ImportLogin.setObjectName("ImportLogin") - ImportLogin.resize(400, 200) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(ImportLogin.sizePolicy().hasHeightForWidth()) - ImportLogin.setSizePolicy(sizePolicy) + ImportLogin.resize(242, 120) ImportLogin.setWindowTitle("ImportLogin") self.import_layout = QtWidgets.QGridLayout(ImportLogin) self.import_layout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) @@ -51,6 +46,7 @@ class Ui_ImportLogin(object): font.setItalic(True) self.status_label.setFont(font) self.status_label.setText("") + self.status_label.setWordWrap(True) self.status_label.setObjectName("status_label") self.import_layout.addWidget(self.status_label, 2, 1, 1, 2) self.info_label = QtWidgets.QLabel(ImportLogin) @@ -68,7 +64,7 @@ class Ui_ImportLogin(object): def retranslateUi(self, ImportLogin): _translate = QtCore.QCoreApplication.translate - self.prefix_label.setText(_translate("ImportLogin", "Select path")) + self.prefix_label.setText(_translate("ImportLogin", "Select prefix")) self.title_label.setText(_translate("ImportLogin", "Import existing session from EGL")) self.prefix_tool.setText(_translate("ImportLogin", "Browse")) self.info_label.setText(_translate("ImportLogin", "You will get logged out from EGL in the process.")) diff --git a/rare/ui/components/dialogs/login/import_login.ui b/rare/ui/components/dialogs/login/import_login.ui index 3ef1d2df..d49ca635 100644 --- a/rare/ui/components/dialogs/login/import_login.ui +++ b/rare/ui/components/dialogs/login/import_login.ui @@ -6,7 +6,7 @@ 0 0 - 235 + 242 120 @@ -20,7 +20,7 @@ - Select path + Select prefix From b3348a1ecad9505746cd6b03420b457f298e0b90 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 1 Sep 2022 23:48:30 +0300 Subject: [PATCH 04/15] ImportLogin: Check if programdata is set --- rare/components/dialogs/login/import_login.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rare/components/dialogs/login/import_login.py b/rare/components/dialogs/login/import_login.py index 093f3c44..1a7ea4b1 100644 --- a/rare/components/dialogs/login/import_login.py +++ b/rare/components/dialogs/login/import_login.py @@ -42,8 +42,9 @@ class ImportLogin(QFrame): self.ui.status_label.setText(self.text_egl_found) self.found = True else: - if wine_pfx := self.core.egl.programdata_path.split("drive_c"): - self.ui.prefix_combo.addItems(wine_pfx) + if programdata_path := self.core.egl.programdata_path: + if wine_pfx := programdata_path.split("drive_c"): + self.ui.prefix_combo.addItems(wine_pfx) self.ui.info_label.setText( self.tr("Please select the Wine prefix where Epic Games Launcher is installed. ") + self.ui.info_label.text() From 345ee443edc5945dae5b4dcbf41c1c50206f185d Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 2 Sep 2022 00:02:20 +0300 Subject: [PATCH 05/15] ImportLogin: Only add the first part of the split --- rare/components/dialogs/login/import_login.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rare/components/dialogs/login/import_login.py b/rare/components/dialogs/login/import_login.py index 1a7ea4b1..a84ce2ad 100644 --- a/rare/components/dialogs/login/import_login.py +++ b/rare/components/dialogs/login/import_login.py @@ -43,8 +43,8 @@ class ImportLogin(QFrame): self.found = True else: if programdata_path := self.core.egl.programdata_path: - if wine_pfx := programdata_path.split("drive_c"): - self.ui.prefix_combo.addItems(wine_pfx) + if wine_pfx := programdata_path.split("drive_c")[0]: + self.ui.prefix_combo.addItem(wine_pfx) self.ui.info_label.setText( self.tr("Please select the Wine prefix where Epic Games Launcher is installed. ") + self.ui.info_label.text() From 3c5575fda927126f78b9c5bd13d842bc972ebcbf Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 2 Sep 2022 00:04:39 +0300 Subject: [PATCH 06/15] LaunchDialog: Don't explicitly delete LoginDialog --- rare/components/dialogs/launch_dialog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 1ea25720..72243e8c 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -129,7 +129,6 @@ class LaunchDialog(QDialog): # Do not set parent, because it won't show a task bar icon # Update: Inherit the same parent as LaunchDialog do_launch = self.login_dialog.login() - self.login_dialog.deleteLater() except (HTTPError, ConnectionError) as e: logger.warning(e) self.args.offline = True From dfb388a9bab2ecfef01bc1ee23a5c5277819523c Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 2 Sep 2022 00:20:04 +0300 Subject: [PATCH 07/15] App: Do a bit more cleanup to avoid crashes --- rare/app.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rare/app.py b/rare/app.py index 35671637..06ca7554 100644 --- a/rare/app.py +++ b/rare/app.py @@ -211,7 +211,7 @@ class App(RareApp): logger.info("Show App") def exit_app(self, exit_code=0): - # FIXME: Fix this with the downlaod tab redesign + # FIXME: Fix this with the download tab redesign if self.mainwindow is not None: if not self.args.offline and self.mainwindow.tab_widget.downloadTab.is_download_active: question = QMessageBox.question( @@ -237,8 +237,15 @@ class App(RareApp): self.core.exit() if self.mainwindow is not None: self.mainwindow.close() + self.mainwindow.deleteLater() + self.mainwindow = None if self.tray_icon is not None: self.tray_icon.deleteLater() + self.tray_icon = None + if self.timer is not None: + self.timer.stop() + self.timer.deleteLater() + self.timer = None self.processEvents() shutil.rmtree(tmp_dir) os.makedirs(tmp_dir) From 385291cbfc03ea377a95356586e9563206708467 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 2 Sep 2022 00:23:16 +0300 Subject: [PATCH 08/15] Update pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a84874f1..96f09ecc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ pywebview = [ { version = "^3.6.3", extras = ["cef"], platform = "windows", optional = true }, { version = "^3.6.3", extras = ["gtk"], platform = "linux", optional = true }, ] -legendary-gl = "^0.20.27" +legendary-gl = "^0.20.28" typing-extensions = "^4.3.0" From 48867a8656ef7b7bbbef55ad5924afcb287964f7 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 2 Sep 2022 00:31:10 +0300 Subject: [PATCH 09/15] DebugSettings: Add restart button, thanks Dummerle! --- rare/components/tabs/settings/debug.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rare/components/tabs/settings/debug.py b/rare/components/tabs/settings/debug.py index 66e4cacf..4677d3f5 100644 --- a/rare/components/tabs/settings/debug.py +++ b/rare/components/tabs/settings/debug.py @@ -1,5 +1,7 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton +from rare.shared import GlobalSignalsSingleton + class DebugSettings(QWidget): def __init__(self): @@ -9,6 +11,9 @@ class DebugSettings(QWidget): self.raise_runtime_exception_button = QPushButton("Raise Exception") self.layout().addWidget(self.raise_runtime_exception_button) self.raise_runtime_exception_button.clicked.connect(self.raise_exception) + self.restart_button = QPushButton("Restart") + self.layout().addWidget(self.restart_button) + self.restart_button.clicked.connect(lambda: GlobalSignalsSingleton().exit_app.emit(-133742)) self.layout().addStretch(1) From 0a89f0e0b8a7393b983dabc5ce6913398027a1d5 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 2 Sep 2022 13:17:05 +0300 Subject: [PATCH 10/15] Make `launch` and alias for `start` --- rare/__main__.py | 25 ++++++++----------------- rare/components/main_window.py | 16 +++------------- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/rare/__main__.py b/rare/__main__.py index fb84f876..95390609 100644 --- a/rare/__main__.py +++ b/rare/__main__.py @@ -45,10 +45,7 @@ def main(): ) subparsers = parser.add_subparsers(title="Commands", dest="subparser") - launch_parser = subparsers.add_parser("launch") - launch_parser.add_argument("app_name", help="Name of the app", metavar="") - - launch_minimal_parser = subparsers.add_parser("start") + launch_minimal_parser = subparsers.add_parser("start", aliases=["launch"]) launch_minimal_parser.add_argument("app_name", help="AppName of the game to launch", metavar="", action="store") launch_minimal_parser.add_argument("--offline", help="Launch game offline", @@ -56,11 +53,11 @@ def main(): launch_minimal_parser.add_argument("--skip_update_check", help="Do not check for updates", action="store_true") launch_minimal_parser.add_argument('--wine-bin', dest='wine_bin', action='store', metavar='', - default=os.environ.get('LGDRY_WINE_BINARY', None), - help='Set WINE binary to use to launch the app') + default=os.environ.get('LGDRY_WINE_BINARY', None), + help='Set WINE binary to use to launch the app') launch_minimal_parser.add_argument('--wine-prefix', dest='wine_pfx', action='store', metavar='', - default=os.environ.get('LGDRY_WINE_PREFIX', None), - help='Set WINE prefix to use') + default=os.environ.get('LGDRY_WINE_PREFIX', None), + help='Set WINE prefix to use') launch_minimal_parser.add_argument("--ask-alyways-sync", help="Ask for cloud saves", action="store_true") @@ -80,10 +77,10 @@ def main(): if args.version: from rare import __version__, code_name - print(f"Rare {__version__} Codename: {code_name}") return - if args.subparser == "start": + + if args.subparser == "start" or args.subparser == "launch": from rare import game_launch_helper as helper helper.start_game(args) return @@ -99,16 +96,10 @@ def main(): from rare.utils.paths import data_dir with open(os.path.join(data_dir, "lockfile"), "w") as file: - if args.subparser == "launch": - file.write(f"launch {args.app_name}") - else: - file.write("start") + file.write("show") file.close() return - if args.subparser == "launch": - args.silent = True - from rare.app import start start(args) diff --git a/rare/components/main_window.py b/rare/components/main_window.py index 42487a90..7f0c98c9 100644 --- a/rare/components/main_window.py +++ b/rare/components/main_window.py @@ -13,8 +13,8 @@ logger = getLogger("Window") class MainWindow(QMainWindow): - def __init__(self): - super(MainWindow, self).__init__() + def __init__(self, parent=None): + super(MainWindow, self).__init__(parent=parent) self.setAttribute(Qt.WA_DeleteOnClose) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() @@ -83,17 +83,7 @@ class MainWindow(QMainWindow): file = open(file_path, "r") action = file.read() file.close() - if action.startswith("launch"): - game = action.replace("launch ", "").replace("\n", "") - if game in [ - i.app_name for i in self.tab_widget.games_tab.game_list - ] and self.core.is_installed(game): - self.tab_widget.games_tab.game_utils.prepare_launch( - game, offline=self.args.offline - ) - else: - logger.info(f"Could not find {game} in Games") - elif action.startswith("start"): + if action.startswith("show"): self.show() os.remove(file_path) self.timer.start(1000) From 4951743bbf654a74b93577ff2dfa0d8906b74278 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 2 Sep 2022 17:53:56 +0300 Subject: [PATCH 11/15] ConfigHelper: Protect name space from globals --- rare/utils/config_helper.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/rare/utils/config_helper.py b/rare/utils/config_helper.py index 50a992e9..086e7f1f 100644 --- a/rare/utils/config_helper.py +++ b/rare/utils/config_helper.py @@ -1,38 +1,38 @@ -from typing import Callable +from typing import Callable, Optional from legendary.core import LegendaryCore from legendary.utils.config import LGDConf -config: LGDConf = None -save_config: Callable[[], None] = None +_config: Optional[LGDConf] = None +_save_config: Optional[Callable[[], None]] = None def init_config_handler(core: LegendaryCore): - global config, save_config - config = core.lgd.config - save_config = core.lgd.save_config + global _config, _save_config + _config = core.lgd.config + _save_config = core.lgd.save_config def add_option(app_name: str, option: str, value: str): value = value.replace("%%", "%").replace("%", "%%") - if not config.has_section(app_name): - config[app_name] = {} + if not _config.has_section(app_name): + _config[app_name] = {} - config.set(app_name, option, value) - save_config() + _config.set(app_name, option, value) + _save_config() def remove_option(app_name, option): - if config.has_option(app_name, option): - config.remove_option(app_name, option) + if _config.has_option(app_name, option): + _config.remove_option(app_name, option) - if config.has_section(app_name) and not config[app_name]: - config.remove_section(app_name) + if _config.has_section(app_name) and not _config[app_name]: + _config.remove_section(app_name) - save_config() + _save_config() def remove_section(app_name): - if config.has_section(app_name): - config.remove_section(app_name) - save_config() + if _config.has_section(app_name): + _config.remove_section(app_name) + _save_config() From afcdc1dea1c4f0e544a5ade76fc7b6467543d049 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 4 Sep 2022 01:14:43 +0300 Subject: [PATCH 12/15] App: Move legendary initialization to the singleton App: Move tray to MainWindow Shared: Add destructor for singleton instances --- rare/app.py | 146 ++++++--------------------------- rare/components/main_window.py | 121 +++++++++++++++++++++------ rare/components/tray_icon.py | 6 +- rare/shared/__init__.py | 67 +++++++++++++-- rare/utils/extra_widgets.py | 16 ++-- 5 files changed, 194 insertions(+), 162 deletions(-) diff --git a/rare/app.py b/rare/app.py index 06ca7554..bba47458 100644 --- a/rare/app.py +++ b/rare/app.py @@ -13,14 +13,19 @@ from typing import Optional import legendary import requests.exceptions from PyQt5.QtCore import QThreadPool, QTimer, QT_VERSION_STR, PYQT_VERSION_STR -from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox +from PyQt5.QtWidgets import QApplication, QMessageBox from requests import HTTPError import rare from rare.components.dialogs.launch_dialog import LaunchDialog from rare.components.main_window import MainWindow -from rare.components.tray_icon import TrayIcon -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton +from rare.shared import ( + LegendaryCoreSingleton, + GlobalSignalsSingleton, + ArgumentsSingleton, + ApiResultsSingleton, + clear_singleton_instance +) from rare.shared.image_manager import ImageManagerSingleton from rare.utils import legendary_utils, config_helper from rare.utils.paths import cache_dir, tmp_dir @@ -54,64 +59,21 @@ def excepthook(exc_type, exc_value, exc_tb): class App(RareApp): - mainwindow: Optional[MainWindow] = None - tray_icon: Optional[QSystemTrayIcon] = None - def __init__(self, args: Namespace): super(App, self).__init__() + self.core = LegendaryCoreSingleton() self.args = ArgumentsSingleton(args) # add some options - self.window_launched = False - - # init Legendary - try: - self.core = LegendaryCoreSingleton(init=True) - except configparser.MissingSectionHeaderError as e: - logger.warning(f"Config is corrupt: {e}") - if config_path := os.environ.get("XDG_CONFIG_HOME"): - path = os.path.join(config_path, "legendary") - else: - path = os.path.expanduser("~/.config/legendary") - with open(os.path.join(path, "config.ini"), "w") as config_file: - config_file.write("[Legendary]") - self.core = LegendaryCoreSingleton(init=True) - if "Legendary" not in self.core.lgd.config.sections(): - self.core.lgd.config.add_section("Legendary") - self.core.lgd.save_config() lang = self.settings.value("language", self.core.language_code, type=str) self.load_translator(lang) - config_helper.init_config_handler(self.core) - - # workaround if egl sync enabled, but no programdata_path - # programdata_path might be unset if logging in through the browser - if self.core.egl_sync_enabled: - if self.core.egl.programdata_path is None: - self.core.lgd.config.remove_option("Legendary", "egl_sync") - self.core.lgd.save_config() - else: - if not os.path.exists(self.core.egl.programdata_path): - self.core.lgd.config.remove_option("Legendary", "egl_sync") - self.core.lgd.save_config() - # set Application name for settings - self.launch_dialog = None + self.mainwindow: Optional[MainWindow] = None + self.launch_dialog: Optional[LaunchDialog] = None self.signals = GlobalSignalsSingleton(init=True) self.image_manager = ImageManagerSingleton(init=True) - self.signals.exit_app.connect(self.exit_app) - self.signals.send_notification.connect( - lambda title: self.tray_icon.showMessage( - self.tr("Download finished"), - self.tr("Download finished. {} is playable now").format(title), - QSystemTrayIcon.Information, - 4000, - ) - if self.settings.value("notification", True, bool) - else None - ) - # launch app self.launch_dialog = LaunchDialog(parent=None) self.launch_dialog.quit_app.connect(self.launch_dialog.close) @@ -140,12 +102,6 @@ class App(RareApp): td = abs(dt_exp - dt_now) self.timer.start(int(td.total_seconds() - 60) * 1000) - def show_mainwindow(self): - if self.window_launched: - self.mainwindow.show() - else: - self.mainwindow.show_window_centered() - def start_app(self): for igame in self.core.get_installed_list(): if not os.path.exists(igame.install_path): @@ -167,85 +123,27 @@ class App(RareApp): logger.info(f"{igame.title} needs verification") self.mainwindow = MainWindow() - self.tray_icon: TrayIcon = TrayIcon(self) - self.tray_icon.exit_action.triggered.connect(self.exit_app) - self.tray_icon.start_rare.triggered.connect(self.show_mainwindow) - self.tray_icon.activated.connect( - lambda r: self.show_mainwindow() - if r == QSystemTrayIcon.DoubleClick - else None - ) + self.mainwindow.exit_app.connect(self.exit_app) if not self.args.silent: - self.mainwindow.show_window_centered() - self.window_launched = True - - if self.args.subparser == "launch": - if self.args.app_name in [ - i.app_name for i in self.core.get_installed_list() - ]: - logger.info( - f"Launching {self.core.get_installed_game(self.args.app_name).title}" - ) - self.mainwindow.tab_widget.games_tab.game_utils.prepare_launch( - self.args.app_name - ) - else: - logger.error( - f"Could not find {self.args.app_name} in Games or it is not installed" - ) - QMessageBox.warning( - self.mainwindow, - "Warning", - self.tr( - "Could not find {} in installed games. Did you modify the shortcut? " - ).format(self.args.app_name), - ) + self.mainwindow.show() if self.args.test_start: self.exit_app(0) - def tray(self, reason): - if reason == QSystemTrayIcon.DoubleClick: - self.mainwindow.show() - logger.info("Show App") - def exit_app(self, exit_code=0): - # FIXME: Fix this with the download tab redesign - if self.mainwindow is not None: - if not self.args.offline and self.mainwindow.tab_widget.downloadTab.is_download_active: - question = QMessageBox.question( - self.mainwindow, - self.tr("Close"), - self.tr( - "There is a download active. Do you really want to exit app?" - ), - QMessageBox.Yes, - QMessageBox.No, - ) - if question == QMessageBox.No: - return - else: - # clear queue - self.mainwindow.tab_widget.downloadTab.queue_widget.update_queue([]) - self.mainwindow.tab_widget.downloadTab.stop_download() - # FIXME: End of FIXME - self.mainwindow.timer.stop() - self.mainwindow.hide() threadpool = QThreadPool.globalInstance() threadpool.waitForDone() - self.core.exit() - if self.mainwindow is not None: - self.mainwindow.close() - self.mainwindow.deleteLater() - self.mainwindow = None - if self.tray_icon is not None: - self.tray_icon.deleteLater() - self.tray_icon = None if self.timer is not None: self.timer.stop() self.timer.deleteLater() self.timer = None + if self.mainwindow is not None: + self.mainwindow.close() + self.mainwindow = None + clear_singleton_instance(self.signals) + clear_singleton_instance(self.args) + clear_singleton_instance(ApiResultsSingleton()) self.processEvents() shutil.rmtree(tmp_dir) os.makedirs(tmp_dir) @@ -284,10 +182,16 @@ def start(args): logger.info(f"Operating System: {platform.system()}") while True: + core = LegendaryCoreSingleton(init=True) + config_helper.init_config_handler(core) app = App(args) exit_code = app.exec_() # if not restart # restart app del app + core.exit() + clear_singleton_instance(core) if exit_code != -133742: break + + diff --git a/rare/components/main_window.py b/rare/components/main_window.py index 7f0c98c9..b5e50942 100644 --- a/rare/components/main_window.py +++ b/rare/components/main_window.py @@ -1,18 +1,22 @@ import os from logging import getLogger -from PyQt5.QtCore import Qt, QSettings, QTimer, QSize +from PyQt5.QtCore import Qt, QSettings, QTimer, QSize, pyqtSignal, pyqtSlot from PyQt5.QtGui import QCloseEvent, QCursor -from PyQt5.QtWidgets import QMainWindow, QApplication, QStatusBar, QScrollArea, QScroller, QComboBox +from PyQt5.QtWidgets import QMainWindow, QApplication, QStatusBar, QScrollArea, QScroller, QComboBox, QMessageBox from rare.components.tabs import TabWidget +from rare.components.tray_icon import TrayIcon from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton from rare.utils.paths import data_dir -logger = getLogger("Window") +logger = getLogger("MainWindow") class MainWindow(QMainWindow): + # int: exit code + exit_app: pyqtSignal = pyqtSignal(int) + def __init__(self, parent=None): super(MainWindow, self).__init__(parent=parent) self.setAttribute(Qt.WA_DeleteOnClose) @@ -46,8 +50,42 @@ class MainWindow(QMainWindow): self.timer.timeout.connect(self.timer_finished) self.timer.start(1000) - def show_window_centered(self): - self.show() + self.signals.exit_app.connect(self.on_exit_app) + self.exit_code = 0 + self.accept_close = False + + self.tray_icon: TrayIcon = TrayIcon(self) + self.tray_icon.exit_action.triggered.connect(self.on_exit_app) + self.tray_icon.start_rare.triggered.connect(self.show) + self.tray_icon.activated.connect( + lambda r: self.toggle() + if r == self.tray_icon.DoubleClick + else None + ) + + self.signals.send_notification.connect( + lambda title: self.tray_icon.showMessage( + self.tr("Download finished"), + self.tr("Download finished. {} is playable now").format(title), + self.tray_icon.Information, + 4000, + ) + if self.settings.value("notification", True, bool) + else None + ) + + self.window_launched = False + + # enable kinetic scrolling + for scroll_area in self.findChildren(QScrollArea): + if not scroll_area.property("no_kinetic_scroll"): + QScroller.grabGesture(scroll_area.viewport(), QScroller.LeftMouseButtonGesture) + + # fix scrolling + for combo_box in scroll_area.findChildren(QComboBox): + combo_box.wheelEvent = lambda e: e.ignore() + + def center_window(self): # get the margins of the decorated window margins = self.windowHandle().frameMargins() # get the screen the cursor is on @@ -68,14 +106,23 @@ class MainWindow(QMainWindow): - self.rect().adjusted(0, 0, decor_width, decor_height).center() ) - # enable kinetic scrolling - for scroll_area in self.findChildren(QScrollArea): - if not scroll_area.property("no_kinetic_scroll"): - QScroller.grabGesture(scroll_area.viewport(), QScroller.LeftMouseButtonGesture) + def show(self) -> None: + super(MainWindow, self).show() + if not self.window_launched: + self.center_window() + self.window_launched = True - # fix scrolling - for combo_box in scroll_area.findChildren(QComboBox): - combo_box.wheelEvent = lambda e: e.ignore() + def hide(self) -> None: + if self.settings.value("save_size", False, bool): + size = self.size().width(), self.size().height() + self.settings.setValue("window_size", size) + super(MainWindow, self).hide() + + def toggle(self): + if self.isHidden(): + self.show() + else: + self.hide() def timer_finished(self): file_path = os.path.join(data_dir, "lockfile") @@ -88,15 +135,43 @@ class MainWindow(QMainWindow): os.remove(file_path) self.timer.start(1000) + @pyqtSlot() + @pyqtSlot(int) + def on_exit_app(self, exit_code=0) -> None: + # FIXME: Fix this with the download tab redesign + if not self.args.offline and self.tab_widget.downloadTab.is_download_active: + question = QMessageBox.question( + self, + self.tr("Close"), + self.tr( + "There is a download active. Do you really want to exit app?" + ), + QMessageBox.Yes, + QMessageBox.No, + ) + if question == QMessageBox.No: + return + else: + # clear queue + self.tab_widget.downloadTab.queue_widget.update_queue([]) + self.tab_widget.downloadTab.stop_download() + # FIXME: End of FIXME + self.exit_code = exit_code + self.close() + + def close(self) -> bool: + self.accept_close = True + return super(MainWindow, self).close() + def closeEvent(self, e: QCloseEvent): - if self.settings.value("save_size", False, bool): - size = self.size().width(), self.size().height() - self.settings.setValue("window_size", size) - if self.settings.value("sys_tray", True, bool): - self.hide() - e.ignore() - return - elif self.args.offline: - pass - self.signals.exit_app.emit(0) - e.ignore() + if not self.accept_close: + if self.settings.value("sys_tray", True, bool): + self.hide() + e.ignore() + return + self.timer.stop() + self.tray_icon.deleteLater() + self.hide() + self.exit_app.emit(self.exit_code) + super(MainWindow, self).closeEvent(e) + e.accept() diff --git a/rare/components/tray_icon.py b/rare/components/tray_icon.py index c01ea4fb..8c29e30c 100644 --- a/rare/components/tray_icon.py +++ b/rare/components/tray_icon.py @@ -13,7 +13,7 @@ logger = getLogger("TrayIcon") class TrayIcon(QSystemTrayIcon): def __init__(self, parent): - super(TrayIcon, self).__init__(parent) + super(TrayIcon, self).__init__(parent=parent) self.core = LegendaryCoreSingleton() self.setIcon(QIcon(":/images/Rare.png")) @@ -33,7 +33,7 @@ class TrayIcon(QSystemTrayIcon): if len(installed := self.core.get_installed_list()) < 5: last_played = [GameMeta(i.app_name) for i in sorted(installed, key=lambda x: x.title)] elif games := sorted( - parent.mainwindow.tab_widget.games_tab.game_utils.game_meta.get_games(), + parent.tab_widget.games_tab.game_utils.game_meta.get_games(), key=lambda x: x.last_played, reverse=True): last_played: List[GameMeta] = games[0:5] else: @@ -46,7 +46,7 @@ class TrayIcon(QSystemTrayIcon): a.setProperty("app_name", game.app_name) self.game_actions.append(a) a.triggered.connect( - lambda: parent.mainwindow.tab_widget.games_tab.game_utils.prepare_launch( + lambda: parent.tab_widget.games_tab.game_utils.prepare_launch( self.sender().property("app_name")) ) diff --git a/rare/shared/__init__.py b/rare/shared/__init__.py index 6bbbf05a..788e63fc 100644 --- a/rare/shared/__init__.py +++ b/rare/shared/__init__.py @@ -5,14 +5,18 @@ Each of the objects in this module should be instantiated ONCE and only ONCE! """ +import configparser +import logging +import os from argparse import Namespace -from typing import Optional +from typing import Optional, Union from rare.lgndr.core import LegendaryCore - from rare.models.apiresults import ApiResults from rare.models.signals import GlobalSignals +logger = logging.getLogger("Singleton") + _legendary_core_singleton: Optional[LegendaryCore] = None _global_signals_singleton: Optional[GlobalSignals] = None _arguments_singleton: Optional[Namespace] = None @@ -23,8 +27,33 @@ def LegendaryCoreSingleton(init: bool = False) -> LegendaryCore: global _legendary_core_singleton if _legendary_core_singleton is None and not init: raise RuntimeError("Uninitialized use of LegendaryCoreSingleton") - if _legendary_core_singleton is None: - _legendary_core_singleton = LegendaryCore() + if _legendary_core_singleton is not None and init: + raise RuntimeError("LegendaryCore already initialized") + if init: + try: + _legendary_core_singleton = LegendaryCore() + except configparser.MissingSectionHeaderError as e: + logger.warning(f"Config is corrupt: {e}") + if config_path := os.environ.get("XDG_CONFIG_HOME"): + path = os.path.join(config_path, "legendary") + else: + path = os.path.expanduser("~/.config/legendary") + with open(os.path.join(path, "config.ini"), "w") as config_file: + config_file.write("[Legendary]") + _legendary_core_singleton = LegendaryCore() + if "Legendary" not in _legendary_core_singleton.lgd.config.sections(): + _legendary_core_singleton.lgd.config.add_section("Legendary") + _legendary_core_singleton.lgd.save_config() + # workaround if egl sync enabled, but no programdata_path + # programdata_path might be unset if logging in through the browser + if _legendary_core_singleton.egl_sync_enabled: + if _legendary_core_singleton.egl.programdata_path is None: + _legendary_core_singleton.lgd.config.remove_option("Legendary", "egl_sync") + _legendary_core_singleton.lgd.save_config() + else: + if not os.path.exists(_legendary_core_singleton.egl.programdata_path): + _legendary_core_singleton.lgd.config.remove_option("Legendary", "egl_sync") + _legendary_core_singleton.lgd.save_config() return _legendary_core_singleton @@ -32,7 +61,9 @@ def GlobalSignalsSingleton(init: bool = False) -> GlobalSignals: global _global_signals_singleton if _global_signals_singleton is None and not init: raise RuntimeError("Uninitialized use of GlobalSignalsSingleton") - if _global_signals_singleton is None: + if _global_signals_singleton is not None and init: + raise RuntimeError("GlobalSignals already initialized") + if init: _global_signals_singleton = GlobalSignals() return _global_signals_singleton @@ -41,7 +72,9 @@ def ArgumentsSingleton(args: Namespace = None) -> Optional[Namespace]: global _arguments_singleton if _arguments_singleton is None and args is None: raise RuntimeError("Uninitialized use of ArgumentsSingleton") - if _arguments_singleton is None: + if _arguments_singleton is not None and args is not None: + raise RuntimeError("Arguments already initialized") + if args is not None: _arguments_singleton = args return _arguments_singleton @@ -50,7 +83,27 @@ def ApiResultsSingleton(res: ApiResults = None) -> Optional[ApiResults]: global _api_results_singleton if _api_results_singleton is None and res is None: raise RuntimeError("Uninitialized use of ApiResultsSingleton") - if _api_results_singleton is None: + if _api_results_singleton is not None and res is not None: + raise RuntimeError("ApiResults already initialized") + if res is not None: _api_results_singleton = res return _api_results_singleton + +def clear_singleton_instance(instance: Union[LegendaryCore, GlobalSignals, Namespace, ApiResults]): + global _legendary_core_singleton, _global_signals_singleton, _arguments_singleton, _api_results_singleton + if isinstance(instance, LegendaryCore): + del instance + _legendary_core_singleton = None + elif isinstance(instance, GlobalSignals): + instance.deleteLater() + del instance + _global_signals_singleton = None + elif isinstance(instance, Namespace): + del instance + _arguments_singleton = None + elif isinstance(instance, ApiResults): + del instance + _api_results_singleton = None + else: + raise RuntimeError(f"Instance is of unknown type \"{type(instance)}\"") diff --git a/rare/utils/extra_widgets.py b/rare/utils/extra_widgets.py index bb95e09a..a6ef5db2 100644 --- a/rare/utils/extra_widgets.py +++ b/rare/utils/extra_widgets.py @@ -54,7 +54,6 @@ class IndicatorReasons: class IndicatorLineEdit(QWidget): textChanged = pyqtSignal(str) - is_valid = False reasons = IndicatorReasons() def __init__( @@ -97,9 +96,10 @@ class IndicatorLineEdit(QWidget): layout.addWidget(self.indicator_label) if not placeholder: - _translate = QCoreApplication.translate + _translate = QCoreApplication.instance().translate self.line_edit.setPlaceholderText(_translate(self.__class__.__name__, "Default")) + self.is_valid = False self.edit_func = edit_func self.save_func = save_func self.line_edit.textChanged.connect(self.__edit) @@ -107,7 +107,7 @@ class IndicatorLineEdit(QWidget): self.line_edit.textChanged.connect(self.__save) # lk: this can be placed here to trigger __edit - # lk: it going to save the input again if it is valid which + # lk: it is going to save the input again if it is valid which # lk: is ok to do given the checks don't misbehave (they shouldn't) # lk: however it is going to edit any "understood" bad input to good input # lk: and we might not want that (but the validity check reports on the edited string) @@ -185,9 +185,6 @@ class PathEditIconProvider(QFileIconProvider): class PathEdit(IndicatorLineEdit): - completer = QCompleter() - compl_model = QFileSystemModel() - def __init__( self, path: str = "", @@ -200,6 +197,9 @@ class PathEdit(IndicatorLineEdit): horiz_policy: QSizePolicy = QSizePolicy.Expanding, parent=None, ): + self.completer = QCompleter() + self.compl_model = QFileSystemModel() + try: self.compl_model.setOptions( QFileSystemModel.DontWatchForChanges @@ -230,7 +230,7 @@ class PathEdit(IndicatorLineEdit): layout = self.layout() layout.addWidget(self.path_select) - _translate = QCoreApplication.translate + _translate = QCoreApplication.instance().translate self.path_select.setText(_translate("PathEdit", "Browse...")) self.type_filter = type_filter @@ -414,7 +414,7 @@ class SelectViewWidget(QWidget): class ImageLabel(QLabel): image = None img_size = None - name = str() + name = "" def __init__(self): super(ImageLabel, self).__init__() From 70201411480c007a4d945df3be7bc67357135a6d Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 4 Sep 2022 19:51:23 +0300 Subject: [PATCH 13/15] LaunchDialog: Move update check before login() --- rare/components/dialogs/launch_dialog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 72243e8c..458b5591 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -117,15 +117,15 @@ class LaunchDialog(QDialog): pass else: QApplication.instance().processEvents() + # Force an update check and notice in case there are API changes + self.core.check_for_updates(force=True) + self.core.force_show_update = True if self.core.login(): logger.info("You are logged in") else: raise ValueError("You are not logged in. Open Login Window") except ValueError as e: logger.info(str(e)) - # Force an update check and notice in case there are API changes - self.core.check_for_updates(force=True) - self.core.force_show_update = True # Do not set parent, because it won't show a task bar icon # Update: Inherit the same parent as LaunchDialog do_launch = self.login_dialog.login() From 1ebd0b18d8829d919cbe6bb80d135b082699d756 Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 4 Sep 2022 20:38:24 +0300 Subject: [PATCH 14/15] Introduce a very basic RareCore to handle signletons and their cleanup. --- rare/app.py | 23 ++- rare/components/dialogs/launch_dialog.py | 5 +- rare/components/tabs/games/__init__.py | 2 +- .../tabs/games/game_info/game_dlc.py | 4 +- .../tabs/games/game_info/game_info.py | 3 +- .../tabs/games/game_info/uninstalled_info.py | 3 +- .../game_widgets/base_installed_widget.py | 4 +- .../game_widgets/base_uninstalled_widget.py | 3 +- .../game_widgets/installing_game_widget.py | 4 +- rare/shared/__init__.py | 101 ++----------- rare/shared/image_manager.py | 23 +-- rare/shared/rare_core.py | 133 ++++++++++++++++++ 12 files changed, 177 insertions(+), 131 deletions(-) create mode 100644 rare/shared/rare_core.py diff --git a/rare/app.py b/rare/app.py index bba47458..efd10a64 100644 --- a/rare/app.py +++ b/rare/app.py @@ -1,4 +1,3 @@ -import configparser import logging import os import platform @@ -23,10 +22,8 @@ from rare.shared import ( LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, - ApiResultsSingleton, - clear_singleton_instance ) -from rare.shared.image_manager import ImageManagerSingleton +from rare.shared.rare_core import RareCore from rare.utils import legendary_utils, config_helper from rare.utils.paths import cache_dir, tmp_dir from rare.widgets.rare_app import RareApp @@ -61,8 +58,12 @@ def excepthook(exc_type, exc_value, exc_tb): class App(RareApp): def __init__(self, args: Namespace): super(App, self).__init__() + self.rare_core = RareCore(args=args) + self.args = ArgumentsSingleton() + self.signals = GlobalSignalsSingleton() self.core = LegendaryCoreSingleton() - self.args = ArgumentsSingleton(args) # add some options + + config_helper.init_config_handler(self.core) lang = self.settings.value("language", self.core.language_code, type=str) self.load_translator(lang) @@ -71,9 +72,6 @@ class App(RareApp): self.mainwindow: Optional[MainWindow] = None self.launch_dialog: Optional[LaunchDialog] = None - self.signals = GlobalSignalsSingleton(init=True) - self.image_manager = ImageManagerSingleton(init=True) - # launch app self.launch_dialog = LaunchDialog(parent=None) self.launch_dialog.quit_app.connect(self.launch_dialog.close) @@ -141,9 +139,8 @@ class App(RareApp): if self.mainwindow is not None: self.mainwindow.close() self.mainwindow = None - clear_singleton_instance(self.signals) - clear_singleton_instance(self.args) - clear_singleton_instance(ApiResultsSingleton()) + self.rare_core.deleteLater() + del self.rare_core self.processEvents() shutil.rmtree(tmp_dir) os.makedirs(tmp_dir) @@ -182,15 +179,11 @@ def start(args): logger.info(f"Operating System: {platform.system()}") while True: - core = LegendaryCoreSingleton(init=True) - config_helper.init_config_handler(core) app = App(args) exit_code = app.exec_() # if not restart # restart app del app - core.exit() - clear_singleton_instance(core) if exit_code != -133742: break diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 458b5591..91c507aa 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -7,12 +7,11 @@ from requests.exceptions import ConnectionError, HTTPError from rare.components.dialogs.login import LoginDialog from rare.models.apiresults import ApiResults -from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton -from rare.shared.image_manager import ImageManagerSingleton +from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton, ApiResultsSingleton, ImageManagerSingleton from rare.ui.components.dialogs.launch_dialog import Ui_LaunchDialog from rare.utils.misc import CloudWorker -logger = getLogger("Login") +logger = getLogger("LoginDialog") class LaunchWorker(QRunnable): diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index e6db3967..71e319f7 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -11,7 +11,7 @@ from rare.shared import ( ArgumentsSingleton, ApiResultsSingleton, ) -from rare.shared.image_manager import ImageManagerSingleton +from rare.shared import ImageManagerSingleton from rare.widgets.library_layout import LibraryLayout from rare.widgets.sliding_stack import SlidingStackedWidget from .cloud_save_utils import CloudSaveUtils diff --git a/rare/components/tabs/games/game_info/game_dlc.py b/rare/components/tabs/games/game_info/game_dlc.py index 192cd28c..82510dea 100644 --- a/rare/components/tabs/games/game_info/game_dlc.py +++ b/rare/components/tabs/games/game_info/game_dlc.py @@ -3,8 +3,8 @@ from PyQt5.QtWidgets import QFrame, QWidget, QMessageBox from legendary.models.game import Game from rare.components.tabs.games.game_utils import GameUtils -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.games.game_info.game_dlc import Ui_GameDlc from rare.ui.components.tabs.games.game_info.game_dlc_widget import Ui_GameDlcWidget from rare.models.install import InstallOptionsModel diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index 4d513b7a..58ffc112 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -27,7 +27,8 @@ from rare.shared import ( GlobalSignalsSingleton, ArgumentsSingleton, ) -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo from rare.utils.legendary_utils import VerifyWorker from rare.utils.misc import get_size diff --git a/rare/components/tabs/games/game_info/uninstalled_info.py b/rare/components/tabs/games/game_info/uninstalled_info.py index 5c99e648..85f8b188 100644 --- a/rare/components/tabs/games/game_info/uninstalled_info.py +++ b/rare/components/tabs/games/game_info/uninstalled_info.py @@ -11,7 +11,8 @@ from rare.shared import ( ArgumentsSingleton, ApiResultsSingleton, ) -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo from rare.utils.extra_widgets import SideTabWidget from rare.utils.json_formatter import QJsonModel diff --git a/rare/components/tabs/games/game_widgets/base_installed_widget.py b/rare/components/tabs/games/game_widgets/base_installed_widget.py index 7f1d9837..f493553e 100644 --- a/rare/components/tabs/games/game_widgets/base_installed_widget.py +++ b/rare/components/tabs/games/game_widgets/base_installed_widget.py @@ -7,8 +7,8 @@ from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QFrame, QMessageBox, QAction from rare.components.tabs.games.game_utils import GameUtils -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.utils.misc import create_desktop_link from rare.widgets.image_widget import ImageWidget diff --git a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py b/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py index 01860985..4ff61cf8 100644 --- a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py +++ b/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py @@ -4,7 +4,8 @@ from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtWidgets import QFrame, QAction from legendary.models.game import Game -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.widgets.image_widget import ImageWidget logger = getLogger("Uninstalled") diff --git a/rare/components/tabs/games/game_widgets/installing_game_widget.py b/rare/components/tabs/games/game_widgets/installing_game_widget.py index 4800a757..cc7cf4da 100644 --- a/rare/components/tabs/games/game_widgets/installing_game_widget.py +++ b/rare/components/tabs/games/game_widgets/installing_game_widget.py @@ -3,8 +3,8 @@ from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QFrame from legendary.models.game import Game -from rare.shared import LegendaryCoreSingleton -from rare.shared.image_manager import ImageManagerSingleton, ImageSize +from rare.shared import LegendaryCoreSingleton, ImageManagerSingleton +from rare.shared.image_manager import ImageSize from rare.widgets.elide_label import ElideLabel from .library_widget import LibraryWidget diff --git a/rare/shared/__init__.py b/rare/shared/__init__.py index 788e63fc..3decb371 100644 --- a/rare/shared/__init__.py +++ b/rare/shared/__init__.py @@ -5,105 +5,34 @@ Each of the objects in this module should be instantiated ONCE and only ONCE! """ -import configparser import logging -import os from argparse import Namespace -from typing import Optional, Union +from typing import Optional from rare.lgndr.core import LegendaryCore from rare.models.apiresults import ApiResults from rare.models.signals import GlobalSignals +from .image_manager import ImageManager +from .rare_core import RareCore -logger = logging.getLogger("Singleton") - -_legendary_core_singleton: Optional[LegendaryCore] = None -_global_signals_singleton: Optional[GlobalSignals] = None -_arguments_singleton: Optional[Namespace] = None -_api_results_singleton: Optional[ApiResults] = None +logger = logging.getLogger("Shared") -def LegendaryCoreSingleton(init: bool = False) -> LegendaryCore: - global _legendary_core_singleton - if _legendary_core_singleton is None and not init: - raise RuntimeError("Uninitialized use of LegendaryCoreSingleton") - if _legendary_core_singleton is not None and init: - raise RuntimeError("LegendaryCore already initialized") - if init: - try: - _legendary_core_singleton = LegendaryCore() - except configparser.MissingSectionHeaderError as e: - logger.warning(f"Config is corrupt: {e}") - if config_path := os.environ.get("XDG_CONFIG_HOME"): - path = os.path.join(config_path, "legendary") - else: - path = os.path.expanduser("~/.config/legendary") - with open(os.path.join(path, "config.ini"), "w") as config_file: - config_file.write("[Legendary]") - _legendary_core_singleton = LegendaryCore() - if "Legendary" not in _legendary_core_singleton.lgd.config.sections(): - _legendary_core_singleton.lgd.config.add_section("Legendary") - _legendary_core_singleton.lgd.save_config() - # workaround if egl sync enabled, but no programdata_path - # programdata_path might be unset if logging in through the browser - if _legendary_core_singleton.egl_sync_enabled: - if _legendary_core_singleton.egl.programdata_path is None: - _legendary_core_singleton.lgd.config.remove_option("Legendary", "egl_sync") - _legendary_core_singleton.lgd.save_config() - else: - if not os.path.exists(_legendary_core_singleton.egl.programdata_path): - _legendary_core_singleton.lgd.config.remove_option("Legendary", "egl_sync") - _legendary_core_singleton.lgd.save_config() - return _legendary_core_singleton +def ArgumentsSingleton() -> Optional[Namespace]: + return RareCore.instance().args() -def GlobalSignalsSingleton(init: bool = False) -> GlobalSignals: - global _global_signals_singleton - if _global_signals_singleton is None and not init: - raise RuntimeError("Uninitialized use of GlobalSignalsSingleton") - if _global_signals_singleton is not None and init: - raise RuntimeError("GlobalSignals already initialized") - if init: - _global_signals_singleton = GlobalSignals() - return _global_signals_singleton +def GlobalSignalsSingleton() -> GlobalSignals: + return RareCore.instance().signals() -def ArgumentsSingleton(args: Namespace = None) -> Optional[Namespace]: - global _arguments_singleton - if _arguments_singleton is None and args is None: - raise RuntimeError("Uninitialized use of ArgumentsSingleton") - if _arguments_singleton is not None and args is not None: - raise RuntimeError("Arguments already initialized") - if args is not None: - _arguments_singleton = args - return _arguments_singleton +def LegendaryCoreSingleton() -> LegendaryCore: + return RareCore.instance().core() + + +def ImageManagerSingleton() -> ImageManager: + return RareCore.instance().image_manager() def ApiResultsSingleton(res: ApiResults = None) -> Optional[ApiResults]: - global _api_results_singleton - if _api_results_singleton is None and res is None: - raise RuntimeError("Uninitialized use of ApiResultsSingleton") - if _api_results_singleton is not None and res is not None: - raise RuntimeError("ApiResults already initialized") - if res is not None: - _api_results_singleton = res - return _api_results_singleton - - -def clear_singleton_instance(instance: Union[LegendaryCore, GlobalSignals, Namespace, ApiResults]): - global _legendary_core_singleton, _global_signals_singleton, _arguments_singleton, _api_results_singleton - if isinstance(instance, LegendaryCore): - del instance - _legendary_core_singleton = None - elif isinstance(instance, GlobalSignals): - instance.deleteLater() - del instance - _global_signals_singleton = None - elif isinstance(instance, Namespace): - del instance - _arguments_singleton = None - elif isinstance(instance, ApiResults): - del instance - _api_results_singleton = None - else: - raise RuntimeError(f"Instance is of unknown type \"{type(instance)}\"") + return RareCore.instance().api_results(res) diff --git a/rare/shared/image_manager.py b/rare/shared/image_manager.py index 9d2791cd..a64cb62f 100644 --- a/rare/shared/image_manager.py +++ b/rare/shared/image_manager.py @@ -7,7 +7,7 @@ import zlib from logging import getLogger from pathlib import Path from typing import TYPE_CHECKING -from typing import Tuple, Dict, Union, Type, List, Callable, Optional +from typing import Tuple, Dict, Union, Type, List, Callable import requests from PyQt5.QtCore import ( @@ -26,7 +26,8 @@ from PyQt5.QtGui import ( from PyQt5.QtWidgets import QApplication from legendary.models.game import Game -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton +from rare.lgndr.core import LegendaryCore +from rare.models.signals import GlobalSignals from rare.utils.paths import image_dir, resources_path if TYPE_CHECKING: @@ -111,10 +112,10 @@ class ImageManager(QObject): __dl_retries = 1 __worker_app_names: List[str] = list() - def __init__(self): + def __init__(self, signals: GlobalSignals, core: LegendaryCore): super(QObject, self).__init__() - self.core = LegendaryCoreSingleton() - self.signals = GlobalSignalsSingleton() + self.signals = signals + self.core = core self.image_dir = Path(image_dir) if not self.image_dir.is_dir(): @@ -357,15 +358,3 @@ class ImageManager(QObject): """ image: QImage = self.__get_cover(QImage, app_name, color) return image - - -_image_manager_singleton: Optional[ImageManager] = None - - -def ImageManagerSingleton(init: bool = False) -> ImageManager: - global _image_manager_singleton - if _image_manager_singleton is None and not init: - raise RuntimeError("Uninitialized use of ImageManagerSingleton") - if _image_manager_singleton is None: - _image_manager_singleton = ImageManager() - return _image_manager_singleton diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py new file mode 100644 index 00000000..def6af99 --- /dev/null +++ b/rare/shared/rare_core.py @@ -0,0 +1,133 @@ +import configparser +import os +from argparse import Namespace +from logging import getLogger +from typing import Optional + +from PyQt5.QtCore import QObject + +from rare.lgndr.core import LegendaryCore +from rare.models.apiresults import ApiResults +from rare.models.signals import GlobalSignals +from .image_manager import ImageManager + +logger = getLogger("RareCore") + + +class RareCore(QObject): + _instance: Optional['RareCore'] = None + + def __init__(self, args: Namespace): + if self._instance is not None: + raise RuntimeError("RareCore already initialized") + super(RareCore, self).__init__() + self._args: Optional[Namespace] = None + self._signals: Optional[GlobalSignals] = None + self._core: Optional[LegendaryCore] = None + self._image_manager: Optional[ImageManager] = None + self._api_results: Optional[ApiResults] = None + + self.args(args) + self.signals(init=True) + self.core(init=True) + self.image_manager(init=True) + + RareCore._instance = self + + @staticmethod + def instance() -> 'RareCore': + if RareCore._instance is None: + raise RuntimeError("Uninitialized use of RareCore") + return RareCore._instance + + def signals(self, init: bool = False) -> GlobalSignals: + if self._signals is None and not init: + raise RuntimeError("Uninitialized use of GlobalSignalsSingleton") + if self._signals is not None and init: + raise RuntimeError("GlobalSignals already initialized") + if init: + self._signals = GlobalSignals() + return self._signals + + def args(self, args: Namespace = None) -> Optional[Namespace]: + if self._args is None and args is None: + raise RuntimeError("Uninitialized use of ArgumentsSingleton") + if self._args is not None and args is not None: + raise RuntimeError("Arguments already initialized") + if args is not None: + self._args = args + return self._args + + def core(self, init: bool = False) -> LegendaryCore: + if self._core is None and not init: + raise RuntimeError("Uninitialized use of LegendaryCoreSingleton") + if self._core is not None and init: + raise RuntimeError("LegendaryCore already initialized") + if init: + try: + self._core = LegendaryCore() + except configparser.MissingSectionHeaderError as e: + logger.warning(f"Config is corrupt: {e}") + if config_path := os.environ.get("XDG_CONFIG_HOME"): + path = os.path.join(config_path, "legendary") + else: + path = os.path.expanduser("~/.config/legendary") + with open(os.path.join(path, "config.ini"), "w") as config_file: + config_file.write("[Legendary]") + self._core = LegendaryCore() + if "Legendary" not in self._core.lgd.config.sections(): + self._core.lgd.config.add_section("Legendary") + self._core.lgd.save_config() + # workaround if egl sync enabled, but no programdata_path + # programdata_path might be unset if logging in through the browser + if self._core.egl_sync_enabled: + if self._core.egl.programdata_path is None: + self._core.lgd.config.remove_option("Legendary", "egl_sync") + self._core.lgd.save_config() + else: + if not os.path.exists(self._core.egl.programdata_path): + self._core.lgd.config.remove_option("Legendary", "egl_sync") + self._core.lgd.save_config() + return self._core + + def image_manager(self, init: bool = False) -> ImageManager: + if self._image_manager is None and not init: + raise RuntimeError("Uninitialized use of ImageManagerSingleton") + if self._image_manager is not None and init: + raise RuntimeError("ImageManager already initialized") + if self._image_manager is None: + self._image_manager = ImageManager(self.signals(), self.core()) + return self._image_manager + + def api_results(self, res: ApiResults = None) -> Optional[ApiResults]: + if self._api_results is None and res is None: + raise RuntimeError("Uninitialized use of ApiResultsSingleton") + if self._api_results is not None and res is not None: + raise RuntimeError("ApiResults already initialized") + if res is not None: + self._api_results = res + return self._api_results + + def deleteLater(self) -> None: + del self._api_results + self._api_results = None + + self._image_manager.deleteLater() + del self._image_manager + self._image_manager = None + + self._core.exit() + del self._core + self._core = None + + self._signals.deleteLater() + del self._signals + self._signals = None + + del self._args + self._args = None + + RareCore._instance = None + + super(RareCore, self).deleteLater() + From aa9cf4c5b5fb5ab634e159bcbd8fe58d60a841fc Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Sun, 4 Sep 2022 21:40:41 +0300 Subject: [PATCH 15/15] GameLaunchHelper: Remove LegendaryCoreSingleton usage --- rare/game_launch_helper/__init__.py | 12 ++++++------ rare/game_launch_helper/lgd_helper.py | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/rare/game_launch_helper/__init__.py b/rare/game_launch_helper/__init__.py index 6773b866..35a13e3f 100644 --- a/rare/game_launch_helper/__init__.py +++ b/rare/game_launch_helper/__init__.py @@ -11,11 +11,11 @@ from PyQt5.QtCore import QObject, QProcess, pyqtSignal, QUrl, QRunnable, QThread from PyQt5.QtGui import QDesktopServices from PyQt5.QtNetwork import QLocalServer, QLocalSocket +from rare.lgndr.core import LegendaryCore +from rare.widgets.rare_app import RareApp from .console import Console from .lgd_helper import get_launch_args, InitArgs, get_configured_process, LaunchArgs, GameArgsError from .message_models import ErrorModel, Actions, FinishedModel, BaseModel, StateChangedModel -from ..shared import LegendaryCoreSingleton -from ..widgets.rare_app import RareApp class PreLaunchThread(QRunnable): @@ -25,9 +25,9 @@ class PreLaunchThread(QRunnable): pre_launch_command_finished = pyqtSignal(int) # exit_code error_occurred = pyqtSignal(str) - def __init__(self, args: InitArgs): + def __init__(self, core: LegendaryCore, args: InitArgs): super(PreLaunchThread, self).__init__() - self.core = LegendaryCoreSingleton() + self.core = core self.app_name = args.app_name self.signals = self.Signals() @@ -69,7 +69,7 @@ class GameProcessApp(RareApp): self.game_process = QProcess() self.app_name = app_name self.logger = getLogger(self.app_name) - self.core = LegendaryCoreSingleton(init=True) + self.core = LegendaryCore() lang = self.settings.value("language", self.core.language_code, type=str) self.load_translator(lang) @@ -187,7 +187,7 @@ class GameProcessApp(RareApp): self.logger.error("Not logged in. Try to launch game offline") args.offline = True - worker = PreLaunchThread(args) + worker = PreLaunchThread(self.core, args) worker.signals.ready_to_launch.connect(self.launch_game) worker.signals.error_occurred.connect(self.error_occurred) # worker.signals.started_pre_launch_command(None) diff --git a/rare/game_launch_helper/lgd_helper.py b/rare/game_launch_helper/lgd_helper.py index 00d3cc74..7e82960c 100644 --- a/rare/game_launch_helper/lgd_helper.py +++ b/rare/game_launch_helper/lgd_helper.py @@ -6,9 +6,11 @@ from logging import getLogger from typing import List from PyQt5.QtCore import QProcess, QProcessEnvironment -from legendary.core import LegendaryCore from legendary.models.game import InstalledGame, LaunchParameters +from rare.lgndr.core import LegendaryCore + + logger = getLogger("Helper")