From 8f89eb6e88adc5b925411a014f57a0118b26aee2 Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Fri, 24 Dec 2021 11:09:50 +0200 Subject: [PATCH] Rare: pass through Black formatter --- rare/__main__.py | 51 +++-- rare/app.py | 88 +++++--- rare/components/dialogs/install_dialog.py | 137 ++++++++----- rare/components/dialogs/launch_dialog.py | 34 +++- rare/components/dialogs/login/__init__.py | 9 +- .../components/dialogs/login/browser_login.py | 8 +- rare/components/dialogs/login/import_login.py | 35 +++- rare/components/dialogs/path_input_dialog.py | 9 +- rare/components/dialogs/uninstall_dialog.py | 18 +- rare/components/extra/console.py | 20 +- rare/components/main_window.py | 17 +- rare/components/tabs/__init__.py | 29 ++- rare/components/tabs/account/__init__.py | 15 +- rare/components/tabs/downloads/__init__.py | 76 +++++-- .../tabs/downloads/dl_queue_widget.py | 31 ++- .../tabs/downloads/download_thread.py | 83 +++++--- rare/components/tabs/games/__init__.py | 65 ++++-- .../components/tabs/games/cloud_save_utils.py | 75 +++++-- .../tabs/games/game_info/__init__.py | 5 +- .../tabs/games/game_info/game_dlc.py | 26 ++- .../tabs/games/game_info/game_info.py | 68 +++++-- .../tabs/games/game_info/game_settings.py | 173 +++++++++++----- rare/components/tabs/games/game_utils.py | 166 +++++++++++---- .../game_widgets/base_installed_widget.py | 75 ++++--- .../game_widgets/base_uninstalled_widget.py | 8 +- .../game_widgets/installed_icon_widget.py | 12 +- .../game_widgets/installed_list_widget.py | 12 +- .../game_widgets/installing_game_widget.py | 32 ++- .../game_widgets/uninstalled_icon_widget.py | 5 +- .../game_widgets/uninstalled_list_widget.py | 5 +- rare/components/tabs/games/head_bar.py | 34 +++- .../tabs/games/import_sync/__init__.py | 18 +- .../tabs/games/import_sync/egl_sync_group.py | 92 +++++---- .../tabs/games/import_sync/import_group.py | 43 ++-- rare/components/tabs/settings/__init__.py | 4 +- rare/components/tabs/settings/about.py | 12 +- rare/components/tabs/settings/dxvk.py | 18 +- rare/components/tabs/settings/legendary.py | 63 +++--- rare/components/tabs/settings/linux.py | 24 ++- rare/components/tabs/settings/rare.py | 50 +++-- .../tabs/settings/ubisoft_activation.py | 75 ++++--- rare/components/tabs/shop/__init__.py | 12 +- rare/components/tabs/shop/constants.py | 163 ++++++++------- rare/components/tabs/shop/game_info.py | 55 ++++- rare/components/tabs/shop/game_widgets.py | 29 ++- rare/components/tabs/shop/search_results.py | 16 +- rare/components/tabs/shop/shop_api_core.py | 88 +++++--- rare/components/tabs/shop/shop_models.py | 59 ++++-- rare/components/tabs/shop/shop_widget.py | 129 +++++++++--- rare/components/tabs/shop/wishlist.py | 45 ++-- rare/components/tabs/tab_utils.py | 2 +- rare/utils/extra_widgets.py | 192 ++++++++++++------ rare/utils/json_formatter.py | 30 ++- rare/utils/legendary_utils.py | 110 ++++++---- rare/utils/models.py | 44 ++-- rare/utils/qt_requests.py | 35 +++- rare/utils/rpc.py | 20 +- rare/utils/singleton.py | 30 +-- rare/utils/steam_grades.py | 25 ++- rare/utils/utils.py | 181 +++++++++++------ 60 files changed, 2128 insertions(+), 957 deletions(-) diff --git a/rare/__main__.py b/rare/__main__.py index c4c7a118..ff039c43 100644 --- a/rare/__main__.py +++ b/rare/__main__.py @@ -10,6 +10,7 @@ def main(): # fix cx_freeze import multiprocessing + multiprocessing.freeze_support() # insert legendary for installed via pip/setup.py submodule to path @@ -18,39 +19,61 @@ def main(): # CLI Options parser = ArgumentParser() - parser.add_argument("-V", "--version", action="store_true", help="Shows version and exits") - parser.add_argument("-S", "--silent", action="store_true", - help="Launch Rare in background. Open it from System Tray Icon") + parser.add_argument( + "-V", "--version", action="store_true", help="Shows version and exits" + ) + parser.add_argument( + "-S", + "--silent", + action="store_true", + help="Launch Rare in background. Open it from System Tray Icon", + ) parser.add_argument("--debug", action="store_true", help="Launch in debug mode") - parser.add_argument("--offline", action="store_true", help="Launch Rare in offline mode") - parser.add_argument("--test-start", action="store_true", help="Quit immediately after launch") + parser.add_argument( + "--offline", action="store_true", help="Launch Rare in offline mode" + ) + parser.add_argument( + "--test-start", action="store_true", help="Quit immediately after launch" + ) - parser.add_argument("--desktop-shortcut", action="store_true", dest="desktop_shortcut", - help="Use this, if there is no link on desktop to start Rare") - parser.add_argument("--startmenu-shortcut", action="store_true", dest="startmenu_shortcut", - help="Use this, if there is no link in start menu to launch Rare") + parser.add_argument( + "--desktop-shortcut", + action="store_true", + dest="desktop_shortcut", + help="Use this, if there is no link on desktop to start Rare", + ) + parser.add_argument( + "--startmenu-shortcut", + action="store_true", + dest="startmenu_shortcut", + help="Use this, if there is no link in start menu to launch Rare", + ) 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_parser.add_argument("app_name", help="Name of the app", metavar="") args = parser.parse_args() if args.desktop_shortcut: from rare.utils import utils + utils.create_rare_desktop_link("desktop") print("Link created") if args.startmenu_shortcut: from rare.utils import utils + utils.create_rare_desktop_link("start_menu") print("link created") if args.version: from rare import __version__, code_name + print(f"Rare {__version__} Codename: {code_name}") return from rare.utils import singleton + try: # this object only allows one instance per machine @@ -58,6 +81,7 @@ def main(): except singleton.SingleInstanceException: print("Rare is already running") from rare import data_dir + with open(os.path.join(data_dir, "lockfile"), "w") as file: if args.subparser == "launch": file.write("launch " + args.app_name) @@ -70,13 +94,16 @@ def main(): args.silent = True from rare.app import start + start(args) -if __name__ == '__main__': +if __name__ == "__main__": # run from source # insert raw legendary submodule - sys.path.insert(0, os.path.join(pathlib.Path(__file__).parent.absolute(), "legendary")) + sys.path.insert( + 0, os.path.join(pathlib.Path(__file__).parent.absolute(), "legendary") + ) # insert source directory sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute())) diff --git a/rare/app.py b/rare/app.py index 5bc0cd44..f59b14e0 100644 --- a/rare/app.py +++ b/rare/app.py @@ -12,6 +12,7 @@ from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox from requests import HTTPError import legendary + # noinspection PyUnresolvedReferences import rare.resources.resources import rare.shared as shared @@ -21,7 +22,7 @@ from rare.components.main_window import MainWindow from rare.components.tray_icon import TrayIcon from rare.utils.utils import set_color_pallete, set_style_sheet -start_time = time.strftime('%y-%m-%d--%H-%M') # year-month-day-hour-minute +start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute file_name = os.path.join(cache_dir, "logs", f"Rare_{start_time}.log") if not os.path.exists(os.path.dirname(file_name)): os.makedirs(os.path.dirname(file_name)) @@ -63,10 +64,10 @@ class App(QApplication): self.core = shared.init_legendary() 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') + if config_path := os.environ.get("XDG_CONFIG_HOME"): + path = os.path.join(config_path, "legendary") else: - path = os.path.expanduser('~/.config/legendary') + path = os.path.expanduser("~/.config/legendary") with open(os.path.join(path, "config.ini"), "w") as config_file: config_file.write("[Legendary]") self.core = shared.init_legendary() @@ -95,12 +96,15 @@ class App(QApplication): self.signals.exit_app.connect(self.exit_app) self.signals.send_notification.connect( - lambda title: - self.tray_icon.showMessage( + 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) + QSystemTrayIcon.Information, + 4000, + ) + if self.settings.value("notification", True, bool) + else None + ) # Translator self.translator = QTranslator() @@ -122,9 +126,12 @@ class App(QApplication): # 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()) + self.setProperty("rareDefaultQtStyle", self.style().objectName()) - if self.settings.value("color_scheme", None) is None and self.settings.value("style_sheet", None) is None: + if ( + self.settings.value("color_scheme", None) is None + and self.settings.value("style_sheet", None) is None + ): self.settings.setValue("color_scheme", "") self.settings.setValue("style_sheet", "RareStyle") @@ -157,23 +164,38 @@ class App(QApplication): self.tray_icon = 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.tray_icon.activated.connect( + lambda r: self.show_mainwindow() + if r == QSystemTrayIcon.DoubleClick + else None + ) if not self.args.silent: self.mainwindow.show_window_centralized() self.window_launched = True if shared.args.subparser == "launch": - if shared.args.app_name in [i.app_name for i in self.core.get_installed_list()]: - logger.info("Launching " + self.core.get_installed_game(shared.args.app_name).title) - self.mainwindow.tab_widget.games_tab.game_utils.prepare_launch(shared.args.app_name) + if shared.args.app_name in [ + i.app_name for i in self.core.get_installed_list() + ]: + logger.info( + "Launching " + + self.core.get_installed_game(shared.args.app_name).title + ) + self.mainwindow.tab_widget.games_tab.game_utils.prepare_launch( + shared.args.app_name + ) else: logger.error( - f"Could not find {shared.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( - shared.args.app_name)) + f"Could not find {shared.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(shared.args.app_name), + ) if shared.args.test_start: self.exit_app(0) @@ -190,8 +212,12 @@ class App(QApplication): 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) + self.tr( + "There is a download active. Do you really want to exit app?" + ), + QMessageBox.Yes, + QMessageBox.No, + ) if question == QMessageBox.No: return else: @@ -218,19 +244,23 @@ def start(args): # configure logging if args.debug: - logging.basicConfig(format='[%(name)s] %(levelname)s: %(message)s', level=logging.DEBUG) + logging.basicConfig( + format="[%(name)s] %(levelname)s: %(message)s", level=logging.DEBUG + ) 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("requests").setLevel(logging.WARNING) + logging.getLogger("urllib3").setLevel(logging.WARNING) logging.getLogger("asyncio").setLevel(logging.WARNING) - logger.info(f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\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)}") + logger.info( + f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\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)}" + ) else: logging.basicConfig( - format='[%(name)s] %(levelname)s: %(message)s', + format="[%(name)s] %(levelname)s: %(message)s", level=logging.INFO, filename=file_name, ) diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py index 1a6aa14b..13f4c939 100644 --- a/rare/components/dialogs/install_dialog.py +++ b/rare/components/dialogs/install_dialog.py @@ -18,7 +18,9 @@ from rare.utils.utils import get_size class InstallDialog(QDialog, Ui_InstallDialog): result_ready = pyqtSignal(InstallQueueItemModel) - def __init__(self, dl_item: InstallQueueItemModel, update=False, silent=False, parent=None): + def __init__( + self, dl_item: InstallQueueItemModel, update=False, silent=False, parent=None + ): super(InstallDialog, self).__init__(parent) self.setupUi(self) self.setAttribute(Qt.WA_DeleteOnClose, True) @@ -40,17 +42,19 @@ class InstallDialog(QDialog, Ui_InstallDialog): self.threadpool.setMaxThreadCount(1) header = self.tr("Update") if update else self.tr("Install") - self.install_dialog_label.setText(f"

{header} \"{self.game.app_title}\"

") - self.setWindowTitle(f"{self.windowTitle()} - {header} \"{self.game.app_title}\"") + self.install_dialog_label.setText(f'

{header} "{self.game.app_title}"

') + self.setWindowTitle(f'{self.windowTitle()} - {header} "{self.game.app_title}"') default_path = os.path.expanduser("~/legendary") if self.core.lgd.config.has_option("Legendary", "install_dir"): default_path = self.core.lgd.config.get("Legendary", "install_dir") - self.install_dir_edit = PathEdit(path=default_path, - file_type=QFileDialog.DirectoryOnly, - edit_func=self.option_changed, - parent=self) + self.install_dir_edit = PathEdit( + path=default_path, + file_type=QFileDialog.DirectoryOnly, + edit_func=self.option_changed, + parent=self, + ) self.install_dir_layout.addWidget(self.install_dir_edit) if self.update: @@ -66,10 +70,23 @@ class InstallDialog(QDialog, Ui_InstallDialog): if dl_item.options.app_name in shared.api_results.mac_games: platforms.append("Mac") self.platform_combo_box.addItems(platforms) - self.platform_combo_box.currentIndexChanged.connect(lambda: self.option_changed(None)) - self.platform_combo_box.currentIndexChanged.connect(lambda i: QMessageBox.warning(self, "Warning", self.tr( - "You will not be able to run the Game if you choose {}").format(self.platform_combo_box.itemText(i))) - if (self.platform_combo_box.currentText() == "Mac" and platform.system() != "Darwin") else None) + self.platform_combo_box.currentIndexChanged.connect( + lambda: self.option_changed(None) + ) + self.platform_combo_box.currentIndexChanged.connect( + lambda i: QMessageBox.warning( + self, + "Warning", + self.tr("You will not be able to run the Game if you choose {}").format( + self.platform_combo_box.itemText(i) + ), + ) + if ( + self.platform_combo_box.currentText() == "Mac" + and platform.system() != "Darwin" + ) + else None + ) if platform.system() == "Darwin" and "Mac" in platforms: self.platform_combo_box.setCurrentIndex(platforms.index("Mac")) @@ -83,14 +100,16 @@ class InstallDialog(QDialog, Ui_InstallDialog): self.force_download_check.stateChanged.connect(self.option_changed) self.ignore_space_check.stateChanged.connect(self.option_changed) - self.download_only_check.stateChanged.connect(lambda: self.non_reload_option_changed("download_only")) + self.download_only_check.stateChanged.connect( + lambda: self.non_reload_option_changed("download_only") + ) self.sdl_list_checks = list() try: for key, info in games[self.app_name].items(): - cb = QDataCheckBox(info['name'], info['tags']) - if key == '__required': - self.dl_item.options.sdl_list.extend(info['tags']) + cb = QDataCheckBox(info["name"], info["tags"]) + if key == "__required": + self.dl_item.options.sdl_list.extend(info["tags"]) cb.setChecked(True) cb.setDisabled(True) self.sdl_list_layout.addWidget(cb) @@ -120,13 +139,15 @@ class InstallDialog(QDialog, Ui_InstallDialog): self.show() def get_options(self): - self.dl_item.options.base_path = self.install_dir_edit.text() if not self.update else None + self.dl_item.options.base_path = ( + self.install_dir_edit.text() if not self.update else None + ) self.dl_item.options.max_workers = self.max_workers_spin.value() self.dl_item.options.force = self.force_download_check.isChecked() self.dl_item.options.ignore_space_req = self.ignore_space_check.isChecked() self.dl_item.options.no_install = self.download_only_check.isChecked() self.dl_item.options.platform = self.platform_combo_box.currentText() - self.dl_item.options.sdl_list = [''] + self.dl_item.options.sdl_list = [""] for cb in self.sdl_list_checks: if data := cb.isChecked(): # noinspection PyTypeChecker @@ -145,9 +166,13 @@ class InstallDialog(QDialog, Ui_InstallDialog): def verify_clicked(self): message = self.tr("Updating...") self.download_size_info_label.setText(message) - self.download_size_info_label.setStyleSheet("font-style: italic; font-weight: normal") + self.download_size_info_label.setStyleSheet( + "font-style: italic; font-weight: normal" + ) self.install_size_info_label.setText(message) - self.install_size_info_label.setStyleSheet("font-style: italic; font-weight: normal") + self.install_size_info_label.setStyleSheet( + "font-style: italic; font-weight: normal" + ) self.cancel_button.setEnabled(False) self.verify_button.setEnabled(False) self.install_button.setEnabled(False) @@ -180,13 +205,19 @@ class InstallDialog(QDialog, Ui_InstallDialog): install_size = self.dl_item.download.analysis.install_size if download_size: self.download_size_info_label.setText("{}".format(get_size(download_size))) - self.download_size_info_label.setStyleSheet("font-style: normal; font-weight: bold") + self.download_size_info_label.setStyleSheet( + "font-style: normal; font-weight: bold" + ) self.install_button.setEnabled(not self.options_changed) else: self.install_size_info_label.setText(self.tr("Game already installed")) - self.install_size_info_label.setStyleSheet("font-style: italics; font-weight: normal") + self.install_size_info_label.setStyleSheet( + "font-style: italics; font-weight: normal" + ) self.install_size_info_label.setText("{}".format(get_size(install_size))) - self.install_size_info_label.setStyleSheet("font-style: normal; font-weight: bold") + self.install_size_info_label.setStyleSheet( + "font-style: normal; font-weight: bold" + ) self.verify_button.setEnabled(self.options_changed) self.cancel_button.setEnabled(True) if self.silent: @@ -225,7 +256,6 @@ class InstallInfoWorkerSignals(QObject): class InstallInfoWorker(QRunnable): - def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel): super(InstallInfoWorker, self).__init__() self.signals = InstallInfoWorkerSignals() @@ -235,44 +265,47 @@ class InstallInfoWorker(QRunnable): @pyqtSlot() def run(self): try: - download = InstallDownloadModel(*self.core.prepare_download( - app_name=self.dl_item.options.app_name, - base_path=self.dl_item.options.base_path, - force=self.dl_item.options.force, - no_install=self.dl_item.options.no_install, - status_q=self.dl_item.status_q, - # max_shm=, - max_workers=self.dl_item.options.max_workers, - # game_folder=, - # disable_patching=, - # override_manifest=, - # override_old_manifest=, - # override_base_url=, - platform=self.dl_item.options.platform, - # file_prefix_filter=, - # file_exclude_filter=, - # file_install_tag=, - # dl_optimizations=, - # dl_timeout=, - repair=self.dl_item.options.repair, - # repair_use_latest=, - ignore_space_req=self.dl_item.options.ignore_space_req, - # disable_delta=, - # override_delta_manifest=, - # reset_sdl=, - sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list - )) + download = InstallDownloadModel( + *self.core.prepare_download( + app_name=self.dl_item.options.app_name, + base_path=self.dl_item.options.base_path, + force=self.dl_item.options.force, + no_install=self.dl_item.options.no_install, + status_q=self.dl_item.status_q, + # max_shm=, + max_workers=self.dl_item.options.max_workers, + # game_folder=, + # disable_patching=, + # override_manifest=, + # override_old_manifest=, + # override_base_url=, + platform=self.dl_item.options.platform, + # file_prefix_filter=, + # file_exclude_filter=, + # file_install_tag=, + # dl_optimizations=, + # dl_timeout=, + repair=self.dl_item.options.repair, + # repair_use_latest=, + ignore_space_req=self.dl_item.options.ignore_space_req, + # disable_delta=, + # override_delta_manifest=, + # reset_sdl=, + sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list, + ) + ) if not download.res.failures: self.signals.result.emit(download) else: - self.signals.failed.emit("\n".join(str(i) for i in download.res.failures)) + self.signals.failed.emit( + "\n".join(str(i) for i in download.res.failures) + ) except Exception as e: self.signals.failed.emit(str(e)) self.signals.finished.emit() class QDataCheckBox(QCheckBox): - def __init__(self, text, data=None, parent=None): super(QDataCheckBox, self).__init__(parent) self.setText(text) diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 8152733d..98b4e470 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -75,8 +75,8 @@ class AssetWorker(QRunnable): if not shared.core.egs.user: return [] assets = [ - GameAsset.from_egs_json(a) for a in - shared.core.egs.get_game_assets(platform=p) + GameAsset.from_egs_json(a) + for a in shared.core.egs.get_game_assets(platform=p) ] return assets @@ -144,15 +144,24 @@ class LaunchDialog(QDialog, Ui_LaunchDialog): # cloud save from another worker, because it is used in cloud_save_utils too cloud_worker = CloudWorker() - cloud_worker.signals.result_ready.connect(lambda x: self.handle_api_worker_result(x, "saves")) + cloud_worker.signals.result_ready.connect( + lambda x: self.handle_api_worker_result(x, "saves") + ) self.thread_pool.start(cloud_worker) else: self.finished = 2 if self.core.lgd.assets: - self.api_results.game_list, self.api_results.dlcs = self.core.get_game_and_dlc_list(False) - self.api_results.bit32_games = list(map(lambda i: i.app_name, self.core.get_game_list(False, "Win32"))) - self.api_results.mac_games = list(map(lambda i: i.app_name, self.core.get_game_list(False, "Mac"))) + ( + self.api_results.game_list, + self.api_results.dlcs, + ) = self.core.get_game_and_dlc_list(False) + self.api_results.bit32_games = list( + map(lambda i: i.app_name, self.core.get_game_list(False, "Win32")) + ) + self.api_results.mac_games = list( + map(lambda i: i.app_name, self.core.get_game_list(False, "Mac")) + ) else: logger.warning("No assets found. Falling back to empty game lists") self.api_results.game_list, self.api_results.dlcs = [], {} @@ -165,11 +174,18 @@ class LaunchDialog(QDialog, Ui_LaunchDialog): if result: self.api_results.game_list, self.api_results.dlcs = result else: - self.api_results.game_list, self.api_results.dlcs = self.core.get_game_and_dlc_list(False) + ( + self.api_results.game_list, + self.api_results.dlcs, + ) = self.core.get_game_and_dlc_list(False) elif text == "32bit": - self.api_results.bit32_games = [i.app_name for i in result[0]] if result else [] + self.api_results.bit32_games = ( + [i.app_name for i in result[0]] if result else [] + ) elif text == "mac": - self.api_results.mac_games = [i.app_name for i in result[0]] if result else [] + self.api_results.mac_games = ( + [i.app_name for i in result[0]] if result else [] + ) elif text == "no_assets": self.api_results.no_asset_games = result if result else [] diff --git a/rare/components/dialogs/login/__init__.py b/rare/components/dialogs/login/__init__.py index 9312c793..3bca4d59 100644 --- a/rare/components/dialogs/login/__init__.py +++ b/rare/components/dialogs/login/__init__.py @@ -48,8 +48,12 @@ class LoginDialog(QDialog, Ui_LoginDialog): self.next_button.setEnabled(False) self.back_button.setEnabled(False) - self.login_browser_radio.clicked.connect(lambda: self.next_button.setEnabled(True)) - self.login_import_radio.clicked.connect(lambda: self.next_button.setEnabled(True)) + self.login_browser_radio.clicked.connect( + lambda: self.next_button.setEnabled(True) + ) + self.login_import_radio.clicked.connect( + lambda: self.next_button.setEnabled(True) + ) self.exit_button.clicked.connect(self.close) self.back_button.clicked.connect(self.back_clicked) self.next_button.clicked.connect(self.next_clicked) @@ -97,4 +101,3 @@ class LoginDialog(QDialog, Ui_LoginDialog): self.next_button.setEnabled(False) self.logged_in = False QMessageBox.warning(self, "Error", str(e)) - diff --git a/rare/components/dialogs/login/browser_login.py b/rare/components/dialogs/login/browser_login.py index 66ff8d68..46b4192f 100644 --- a/rare/components/dialogs/login/browser_login.py +++ b/rare/components/dialogs/login/browser_login.py @@ -25,9 +25,7 @@ class BrowserLogin(QWidget, Ui_BrowserLogin): self.core = core self.sid_edit = IndicatorLineEdit( - ph_text=self.tr("Insert SID here"), - edit_func=self.text_changed, - parent=self + ph_text=self.tr("Insert SID here"), edit_func=self.text_changed, parent=self ) self.sid_layout.addWidget(self.sid_edit) @@ -58,7 +56,9 @@ class BrowserLogin(QWidget, Ui_BrowserLogin): try: token = self.core.auth_sid(sid) if self.core.auth_code(token): - logger.info(f"Successfully logged in as {self.core.lgd.userdata['displayName']}") + logger.info( + f"Successfully logged in as {self.core.lgd.userdata['displayName']}" + ) self.success.emit() else: self.status_label.setText(self.tr("Login failed.")) diff --git a/rare/components/dialogs/login/import_login.py b/rare/components/dialogs/login/import_login.py index 20d5c1bc..087a1070 100644 --- a/rare/components/dialogs/login/import_login.py +++ b/rare/components/dialogs/login/import_login.py @@ -17,7 +17,9 @@ class ImportLogin(QWidget, Ui_ImportLogin): if os.name == "nt": localappdata = os.path.expandvars("%LOCALAPPDATA%") else: - localappdata = os.path.join("drive_c/users", getuser(), "Local Settings/Application Data") + localappdata = os.path.join( + "drive_c/users", getuser(), "Local Settings/Application Data" + ) appdata_path = os.path.join(localappdata, "EpicGamesLauncher/Saved/Config/Windows") found = False @@ -27,7 +29,9 @@ class ImportLogin(QWidget, Ui_ImportLogin): self.core = core - self.text_egl_found = self.tr("Found EGL Program Data. Click 'Next' to import them.") + self.text_egl_found = self.tr( + "Found EGL Program Data. Click 'Next' to import them." + ) self.text_egl_notfound = self.tr("Could not find EGL Program Data. ") if os.name == "nt": @@ -39,14 +43,19 @@ class ImportLogin(QWidget, Ui_ImportLogin): self.status_label.setText(self.text_egl_found) self.found = True else: - self.info_label.setText(self.tr( - "Please select the Wine prefix" - " where Epic Games Launcher is installed. ") + self.info_label.text() - ) + self.info_label.setText( + self.tr( + "Please select the Wine prefix" + " where Epic Games Launcher is installed. " + ) + + self.info_label.text() + ) prefixes = self.get_wine_prefixes() if len(prefixes): self.prefix_combo.addItems(prefixes) - self.status_label.setText(self.tr("Select the Wine prefix you want to import.")) + self.status_label.setText( + self.tr("Select the Wine prefix you want to import.") + ) else: self.status_label.setText(self.text_egl_notfound) @@ -65,7 +74,9 @@ class ImportLogin(QWidget, Ui_ImportLogin): return prefixes def prefix_path(self): - prefix_dialog = QFileDialog(self, self.tr("Choose path"), os.path.expanduser("~/")) + prefix_dialog = QFileDialog( + self, self.tr("Choose path"), os.path.expanduser("~/") + ) prefix_dialog.setFileMode(QFileDialog.DirectoryOnly) if prefix_dialog.exec_(): names = prefix_dialog.selectedFiles() @@ -75,14 +86,18 @@ class ImportLogin(QWidget, Ui_ImportLogin): if os.name == "nt": return self.found else: - return os.path.exists(os.path.join(self.prefix_combo.currentText(), self.appdata_path)) + return os.path.exists( + os.path.join(self.prefix_combo.currentText(), self.appdata_path) + ) def do_login(self): self.status_label.setText(self.tr("Loading...")) if os.name == "nt": pass else: - self.core.egl.appdata_path = os.path.join(self.prefix_combo.currentText(), self.appdata_path) + self.core.egl.appdata_path = os.path.join( + self.prefix_combo.currentText(), self.appdata_path + ) try: if self.core.auth_import(): logger.info(f"Logged in as {self.core.lgd.userdata['displayName']}") diff --git a/rare/components/dialogs/path_input_dialog.py b/rare/components/dialogs/path_input_dialog.py index 1437b9ef..d3f4d93b 100644 --- a/rare/components/dialogs/path_input_dialog.py +++ b/rare/components/dialogs/path_input_dialog.py @@ -1,5 +1,12 @@ from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QLabel, QDialog, QFileDialog +from PyQt5.QtWidgets import ( + QHBoxLayout, + QPushButton, + QVBoxLayout, + QLabel, + QDialog, + QFileDialog, +) from rare.utils.extra_widgets import PathEdit diff --git a/rare/components/dialogs/uninstall_dialog.py b/rare/components/dialogs/uninstall_dialog.py index 7bbce917..9a9afb9c 100644 --- a/rare/components/dialogs/uninstall_dialog.py +++ b/rare/components/dialogs/uninstall_dialog.py @@ -1,5 +1,13 @@ from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QDialog, QLabel, QVBoxLayout, QCheckBox, QFormLayout, QHBoxLayout, QPushButton +from PyQt5.QtWidgets import ( + QDialog, + QLabel, + QVBoxLayout, + QCheckBox, + QFormLayout, + QHBoxLayout, + QPushButton, +) from qtawesome import icon from legendary.models.game import Game @@ -12,7 +20,9 @@ class UninstallDialog(QDialog): self.info = 0 self.setAttribute(Qt.WA_DeleteOnClose, True) self.layout = QVBoxLayout() - self.info_text = QLabel(self.tr("Do you really want to uninstall {}").format(game.app_title)) + self.info_text = QLabel( + self.tr("Do you really want to uninstall {}").format(game.app_title) + ) self.layout.addWidget(self.info_text) self.keep_files = QCheckBox(self.tr("Keep Files")) self.form = QFormLayout() @@ -21,7 +31,9 @@ class UninstallDialog(QDialog): self.layout.addLayout(self.form) self.button_layout = QHBoxLayout() - self.ok_button = QPushButton(icon("ei.remove-circle", color="red"), self.tr("Uninstall")) + self.ok_button = QPushButton( + icon("ei.remove-circle", color="red"), self.tr("Uninstall") + ) self.ok_button.clicked.connect(self.ok) self.cancel_button = QPushButton(self.tr("Cancel")) diff --git a/rare/components/extra/console.py b/rare/components/extra/console.py index 14bc2f83..70884748 100644 --- a/rare/components/extra/console.py +++ b/rare/components/extra/console.py @@ -1,5 +1,11 @@ from PyQt5.QtGui import QTextCursor, QFont -from PyQt5.QtWidgets import QPlainTextEdit, QWidget, QPushButton, QFileDialog, QVBoxLayout +from PyQt5.QtWidgets import ( + QPlainTextEdit, + QWidget, + QPushButton, + QFileDialog, + QVBoxLayout, +) class ConsoleWindow(QWidget): @@ -22,7 +28,9 @@ class ConsoleWindow(QWidget): self.setLayout(self.layout) def save(self): - file, ok = QFileDialog.getSaveFileName(self, "Save output", "", "Log Files (*.log);;All Files (*)") + file, ok = QFileDialog.getSaveFileName( + self, "Save output", "", "Log Files (*.log);;All Files (*)" + ) if ok: if "." not in file: file += ".log" @@ -39,7 +47,6 @@ class ConsoleWindow(QWidget): class Console(QPlainTextEdit): - def __init__(self): super().__init__() self.setReadOnly(True) @@ -51,12 +58,13 @@ class Console(QPlainTextEdit): self.scroll_to_last_line() def error(self, text): - self._cursor_output.insertHtml(f"{text}") + self._cursor_output.insertHtml(f'{text}') self.scroll_to_last_line() def scroll_to_last_line(self): cursor = self.textCursor() cursor.movePosition(QTextCursor.End) - cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else - QTextCursor.StartOfLine) + cursor.movePosition( + QTextCursor.Up if cursor.atBlockStart() else QTextCursor.StartOfLine + ) self.setTextCursor(cursor) diff --git a/rare/components/main_window.py b/rare/components/main_window.py index 4a190745..de98e52c 100644 --- a/rare/components/main_window.py +++ b/rare/components/main_window.py @@ -13,7 +13,6 @@ logger = getLogger("Window") class MainWindow(QMainWindow): - def __init__(self): super(MainWindow, self).__init__() self.setAttribute(Qt.WA_DeleteOnClose) @@ -54,10 +53,14 @@ class MainWindow(QMainWindow): decor_width = margins.left() + margins.right() decor_height = margins.top() + margins.bottom() window_size = QSize(self.width(), self.height()).boundedTo( - screen_rect.size() - QSize(decor_width, decor_height)) + screen_rect.size() - QSize(decor_width, decor_height) + ) self.resize(window_size) - self.move(screen_rect.center() - self.rect().adjusted(0, 0, decor_width, decor_height).center()) + self.move( + screen_rect.center() + - self.rect().adjusted(0, 0, decor_width, decor_height).center() + ) def timer_finished(self): file_path = os.path.join(data_dir, "lockfile") @@ -67,8 +70,12 @@ class MainWindow(QMainWindow): 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=shared.args.offline) + 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=shared.args.offline + ) else: logger.info(f"Could not find {game} in Games") elif action.startswith("start"): diff --git a/rare/components/tabs/__init__.py b/rare/components/tabs/__init__.py index 22b02797..bf48e3c5 100644 --- a/rare/components/tabs/__init__.py +++ b/rare/components/tabs/__init__.py @@ -29,8 +29,15 @@ class TabWidget(QTabWidget): if not shared.args.offline: # updates = self.games_tab.default_widget.game_list.updates self.downloadTab = DownloadsTab(self.games_tab.updates) - self.addTab(self.downloadTab, "Downloads" + ( - " (" + str(len(self.games_tab.updates)) + ")" if len(self.games_tab.updates) != 0 else "")) + self.addTab( + self.downloadTab, + "Downloads" + + ( + " (" + str(len(self.games_tab.updates)) + ")" + if len(self.games_tab.updates) != 0 + else "" + ), + ) self.store = Shop(self.core) self.addTab(self.store, self.tr("Store (Beta)")) @@ -49,14 +56,18 @@ class TabWidget(QTabWidget): self.mini_widget = MiniWidget() account_action = QWidgetAction(self) account_action.setDefaultWidget(self.mini_widget) - account_button = TabButtonWidget('mdi.account-circle', 'Account') + account_button = TabButtonWidget("mdi.account-circle", "Account") account_button.setMenu(QMenu()) account_button.menu().addAction(account_action) - self.tabBar().setTabButton(disabled_tab + 1, self.tabBar().RightSide, account_button) + self.tabBar().setTabButton( + disabled_tab + 1, self.tabBar().RightSide, account_button + ) self.addTab(self.settings, icon("fa.gear"), "") - self.settings.about.update_available_ready.connect(lambda: self.tabBar().setTabText(5, "(!)")) + self.settings.about.update_available_ready.connect( + lambda: self.tabBar().setTabText(5, "(!)") + ) # Signals # set current index self.signals.set_main_tab_index.connect(self.setCurrentIndex) @@ -75,8 +86,12 @@ class TabWidget(QTabWidget): QShortcut("Alt+4", self).activated.connect(lambda: self.setCurrentIndex(5)) def update_dl_tab_text(self): - num_downloads = len(set([i.options.app_name for i in self.downloadTab.dl_queue] + [i for i in - self.downloadTab.update_widgets.keys()])) + num_downloads = len( + set( + [i.options.app_name for i in self.downloadTab.dl_queue] + + [i for i in self.downloadTab.update_widgets.keys()] + ) + ) if num_downloads != 0: self.setTabText(1, f"Downloads ({num_downloads})") diff --git a/rare/components/tabs/account/__init__.py b/rare/components/tabs/account/__init__.py index fb57e5f3..d7cb2794 100644 --- a/rare/components/tabs/account/__init__.py +++ b/rare/components/tabs/account/__init__.py @@ -20,7 +20,10 @@ class MiniWidget(QWidget): self.open_browser = QPushButton(self.tr("Account settings")) self.open_browser.clicked.connect( - lambda: webbrowser.open("https://www.epicgames.com/account/personal?productName=epicgames")) + lambda: webbrowser.open( + "https://www.epicgames.com/account/personal?productName=epicgames" + ) + ) self.layout.addWidget(self.open_browser) self.logout_button = QPushButton(self.tr("Logout")) @@ -29,9 +32,13 @@ class MiniWidget(QWidget): self.setLayout(self.layout) def logout(self): - reply = QMessageBox.question(self.parent().parent(), 'Message', - self.tr("Do you really want to logout"), QMessageBox.Yes | - QMessageBox.No, QMessageBox.No) + reply = QMessageBox.question( + self.parent().parent(), + "Message", + self.tr("Do you really want to logout"), + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No, + ) if reply == QMessageBox.Yes: self.core.lgd.invalidate_userdata() diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py index f1bf47d0..362b73ad 100644 --- a/rare/components/tabs/downloads/__init__.py +++ b/rare/components/tabs/downloads/__init__.py @@ -3,8 +3,14 @@ from logging import getLogger from typing import List, Dict from PyQt5.QtCore import QThread, pyqtSignal, QSettings -from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QPushButton, \ - QGroupBox +from PyQt5.QtWidgets import ( + QWidget, + QMessageBox, + QVBoxLayout, + QLabel, + QPushButton, + QGroupBox, +) from legendary.core import LegendaryCore from legendary.models.downloading import UIUpdate @@ -59,7 +65,9 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.signals.game_uninstalled.connect(self.queue_item_removed) self.signals.game_uninstalled.connect(self.remove_update) - self.signals.add_download.connect(lambda app_name: self.add_update(self.core.get_installed_game(app_name))) + self.signals.add_download.connect( + lambda app_name: self.add_update(self.core.get_installed_game(app_name)) + ) shared.signals.game_uninstalled.connect(self.game_uninstalled) self.reset_infos() @@ -75,7 +83,9 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.update_widgets[igame.app_name] = widget widget.update_signal.connect(self.get_install_options) if QSettings().value("auto_update", False, bool): - self.get_install_options(InstallOptionsModel(app_name=igame.app_name, update=True, silent=True)) + self.get_install_options( + InstallOptionsModel(app_name=igame.app_name, update=True, silent=True) + ) widget.update_button.setDisabled(True) self.update_text.setVisible(False) @@ -148,7 +158,12 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): if game.app_name in self.update_widgets.keys(): igame = self.core.get_installed_game(game.app_name) - if self.core.get_asset(game.app_name, igame.platform, False).build_version == igame.version: + if ( + self.core.get_asset( + game.app_name, igame.platform, False + ).build_version + == igame.version + ): self.remove_update(game.app_name) self.signals.send_notification.emit(game.app_title) @@ -188,12 +203,20 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.analysis = None def statistics(self, ui_update: UIUpdate): - self.progress_bar.setValue(100 * ui_update.total_downloaded // self.analysis.dl_size) + self.progress_bar.setValue( + 100 * ui_update.total_downloaded // self.analysis.dl_size + ) self.dl_speed.setText(f"{get_size(ui_update.download_speed)}/s") - self.cache_used.setText(f"{get_size(ui_update.cache_usage) if ui_update.cache_usage > 1023 else '0KB'}") - self.downloaded.setText(f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}") + self.cache_used.setText( + f"{get_size(ui_update.cache_usage) if ui_update.cache_usage > 1023 else '0KB'}" + ) + self.downloaded.setText( + f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}" + ) self.time_left.setText(self.get_time(ui_update.estimated_time_left)) - self.signals.dl_progress.emit(100 * ui_update.total_downloaded // self.analysis.dl_size) + self.signals.dl_progress.emit( + 100 * ui_update.total_downloaded // self.analysis.dl_size + ) def get_time(self, seconds: int) -> str: return str(datetime.timedelta(seconds=seconds)) @@ -209,14 +232,24 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): def get_install_options(self, options: InstallOptionsModel): - install_dialog = InstallDialog(InstallQueueItemModel(options=options), - update=options.update, silent=options.silent, parent=self) + install_dialog = InstallDialog( + InstallQueueItemModel(options=options), + update=options.update, + silent=options.silent, + parent=self, + ) install_dialog.result_ready.connect(self.on_install_dialog_closed) install_dialog.execute() def start_download(self, download_item: InstallQueueItemModel): - downloads = len(self.downloadTab.dl_queue) + len(self.downloadTab.update_widgets.keys()) + 1 - self.setTabText(1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else "")) + downloads = ( + len(self.downloadTab.dl_queue) + + len(self.downloadTab.update_widgets.keys()) + + 1 + ) + self.setTabText( + 1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else "") + ) self.setCurrentIndex(1) self.downloadTab.install_game(download_item) self.games_tab.start_download(download_item.options.app_name) @@ -244,13 +277,22 @@ class UpdateWidget(QWidget): self.update_with_settings.clicked.connect(lambda: self.update_game(False)) self.layout.addWidget(self.update_button) self.layout.addWidget(self.update_with_settings) - self.layout.addWidget(QLabel( - self.tr("Version: ") + self.game.version + " -> " + - self.core.get_asset(self.game.app_name, self.game.platform, True).build_version)) + self.layout.addWidget( + QLabel( + self.tr("Version: ") + + self.game.version + + " -> " + + self.core.get_asset( + self.game.app_name, self.game.platform, True + ).build_version + ) + ) self.setLayout(self.layout) def update_game(self, auto: bool): self.update_button.setDisabled(True) self.update_with_settings.setDisabled(True) - self.update_signal.emit(InstallOptionsModel(app_name=self.game.app_name, silent=auto)) # True if settings + self.update_signal.emit( + InstallOptionsModel(app_name=self.game.app_name, silent=auto) + ) # True if settings diff --git a/rare/components/tabs/downloads/dl_queue_widget.py b/rare/components/tabs/downloads/dl_queue_widget.py index c9da6b6e..8b3d823d 100644 --- a/rare/components/tabs/downloads/dl_queue_widget.py +++ b/rare/components/tabs/downloads/dl_queue_widget.py @@ -1,7 +1,14 @@ from logging import getLogger from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QLabel, QHBoxLayout, QPushButton, QWidget +from PyQt5.QtWidgets import ( + QGroupBox, + QVBoxLayout, + QLabel, + QHBoxLayout, + QPushButton, + QWidget, +) from qtawesome import icon from rare.utils.models import InstallQueueItemModel @@ -28,7 +35,9 @@ class DlWidget(QWidget): self.left_layout.addWidget(self.move_up_button) self.move_down_buttton = QPushButton(icon("fa.arrow-down"), "") - self.move_down_buttton.clicked.connect(lambda: self.move_down.emit(self.app_name)) + self.move_down_buttton.clicked.connect( + lambda: self.move_down.emit(self.app_name) + ) self.left_layout.addWidget(self.move_down_buttton) self.move_down_buttton.setFixedWidth(20) @@ -43,8 +52,18 @@ class DlWidget(QWidget): self.size = QHBoxLayout() - self.size.addWidget(QLabel(self.tr("Download size: {} GB").format(round(dl_size / 1024 ** 3, 2)))) - self.size.addWidget(QLabel(self.tr("Install size: {} GB").format(round(install_size / 1024 ** 3, 2)))) + self.size.addWidget( + QLabel( + self.tr("Download size: {} GB").format(round(dl_size / 1024 ** 3, 2)) + ) + ) + self.size.addWidget( + QLabel( + self.tr("Install size: {} GB").format( + round(install_size / 1024 ** 3, 2) + ) + ) + ) self.right_layout.addLayout(self.size) self.delete = QPushButton(self.tr("Remove Download")) @@ -69,7 +88,9 @@ class DlQueueWidget(QGroupBox): self.layout().addWidget(self.text) def update_queue(self, dl_queue: list): - logger.debug("Update Queue " + ", ".join(i.download.game.app_title for i in dl_queue)) + logger.debug( + "Update Queue " + ", ".join(i.download.game.app_title for i in dl_queue) + ) self.dl_queue = dl_queue for item in (self.layout().itemAt(i) for i in range(self.layout().count())): diff --git a/rare/components/tabs/downloads/download_thread.py b/rare/components/tabs/downloads/download_thread.py index d35ad5fa..74169560 100644 --- a/rare/components/tabs/downloads/download_thread.py +++ b/rare/components/tabs/downloads/download_thread.py @@ -59,12 +59,23 @@ class DownloadThread(QThread): for t in self.dlm.threads: t.join(timeout=5.0) if t.is_alive(): - logger.warning(f'Thread did not terminate! {repr(t)}') + logger.warning(f"Thread did not terminate! {repr(t)}") # clean up all the queues, otherwise this process won't terminate properly - for name, q in zip(('Download jobs', 'Writer jobs', 'Download results', 'Writer results'), - (self.dlm.dl_worker_queue, self.dlm.writer_queue, self.dlm.dl_result_q, - self.dlm.writer_result_q)): + for name, q in zip( + ( + "Download jobs", + "Writer jobs", + "Download results", + "Writer results", + ), + ( + self.dlm.dl_worker_queue, + self.dlm.writer_queue, + self.dlm.dl_result_q, + self.dlm.writer_result_q, + ), + ): logger.debug(f'Cleaning up queue "{name}"') try: while True: @@ -73,11 +84,11 @@ class DownloadThread(QThread): q.close() q.join_thread() except AttributeError: - logger.warning(f'Queue {name} did not close') + logger.warning(f"Queue {name} did not close") if self.dlm.writer_queue: # cancel installation - self.dlm.writer_queue.put_nowait(WriterTask('', kill=True)) + self.dlm.writer_queue.put_nowait(WriterTask("", kill=True)) # forcibly kill DL workers that are not actually dead yet for child in self.dlm.children: @@ -95,9 +106,16 @@ class DownloadThread(QThread): # force kill any threads that are somehow still alive for proc in psutil.process_iter(): # check whether the process name matches - if sys.platform in ['linux', 'darwin'] and proc.name() == 'DownloadThread': + if ( + sys.platform in ["linux", "darwin"] + and proc.name() == "DownloadThread" + ): proc.kill() - elif sys.platform == 'win32' and proc.name() == 'python.exe' and proc.create_time() >= start_time: + elif ( + sys.platform == "win32" + and proc.name() == "python.exe" + and proc.create_time() >= start_time + ): proc.kill() logger.info("Download stopped. It can be continued later.") @@ -111,7 +129,9 @@ class DownloadThread(QThread): self.dlm.join() except Exception as e: - logger.error(f"Installation failed after {time.time() - start_time:.02f} seconds: {e}") + logger.error( + f"Installation failed after {time.time() - start_time:.02f} seconds: {e}" + ) self.status.emit("error " + str(e)) return @@ -130,48 +150,65 @@ class DownloadThread(QThread): dlcs = self.core.get_dlc_for_game(self.igame.app_name) if dlcs: - print('The following DLCs are available for this game:') + print("The following DLCs are available for this game:") for dlc in dlcs: - print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})') - print('Manually installing DLCs works the same; just use the DLC app name instead.') + print( + f" - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})" + ) + print( + "Manually installing DLCs works the same; just use the DLC app name instead." + ) # install_dlcs = QMessageBox.question(self, "", "Do you want to install the prequisites", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes # TODO if game.supports_cloud_saves and not game.is_dlc: - logger.info('This game supports cloud saves, syncing is handled by the "sync-saves" command.') - logger.info(f'To download saves for this game run "legendary sync-saves {game.app_name}"') + logger.info( + 'This game supports cloud saves, syncing is handled by the "sync-saves" command.' + ) + logger.info( + f'To download saves for this game run "legendary sync-saves {game.app_name}"' + ) old_igame = self.core.get_installed_game(game.app_name) if old_igame and self.repair and os.path.exists(self.repair_file): if old_igame.needs_verification: old_igame.needs_verification = False self.core.install_game(old_igame) - logger.debug('Removing repair file.') + logger.debug("Removing repair file.") os.remove(self.repair_file) if old_igame and old_igame.install_tags != self.igame.install_tags: old_igame.install_tags = self.igame.install_tags - self.logger.info('Deleting now untagged files.') + self.logger.info("Deleting now untagged files.") self.core.uninstall_tag(old_igame) self.core.install_game(old_igame) self.status.emit("finish") def _handle_postinstall(self, postinstall, igame): - print('This game lists the following prequisites to be installed:') - print(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}') + print("This game lists the following prequisites to be installed:") + print( + f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}' + ) if platform.system() == "Windows": - if QMessageBox.question(self, "", "Do you want to install the prequisites", - QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: + if ( + QMessageBox.question( + self, + "", + "Do you want to install the prequisites", + QMessageBox.Yes | QMessageBox.No, + ) + == QMessageBox.Yes + ): self.core.prereq_installed(igame.app_name) - req_path, req_exec = os.path.split(postinstall['path']) + req_path, req_exec = os.path.split(postinstall["path"]) work_dir = os.path.join(igame.install_path, req_path) fullpath = os.path.join(work_dir, req_exec) - subprocess.call([fullpath, postinstall['args']], cwd=work_dir) + subprocess.call([fullpath, postinstall["args"]], cwd=work_dir) else: self.core.prereq_installed(self.igame.app_name) else: - logger.info('Automatic installation not available on Linux.') + logger.info("Automatic installation not available on Linux.") def kill(self): self._kill = True diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index 94f7e25c..698272a6 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -58,7 +58,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab): self.game_info_tabs.back_clicked.connect(lambda: self.setCurrentIndex(0)) self.addWidget(self.game_info_tabs) - self.game_info_tabs.info.verification_finished.connect(self.verification_finished) + self.game_info_tabs.info.verification_finished.connect( + self.verification_finished + ) self.game_info_tabs.info.uninstalled.connect(lambda x: self.setCurrentIndex(0)) self.import_sync_tabs = ImportSyncTabs(self) @@ -102,7 +104,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab): self.signals.dl_progress.connect(self.installing_widget.set_status) self.signals.installation_started.connect(self.installation_started) self.signals.update_gamelist.connect(self.update_list) - self.signals.installation_finished.connect(lambda x: self.installing_widget.setVisible(False)) + self.signals.installation_finished.connect( + lambda x: self.installing_widget.setVisible(False) + ) self.signals.game_uninstalled.connect(lambda name: self.update_list([name])) self.game_utils.update_list.connect(self.update_list) @@ -174,9 +178,11 @@ class GamesTab(QStackedWidget, Ui_GamesTab): self.list_view.layout().addWidget(list_widget) def update_count_games_label(self): - self.count_games_label.setText(self.tr("Installed Games: {} Available Games: {}").format( - len(self.core.get_installed_list()), - len(self.game_list))) + self.count_games_label.setText( + self.tr("Installed Games: {} Available Games: {}").format( + len(self.core.get_installed_list()), len(self.game_list) + ) + ) def add_installed_widget(self, app_name): pixmap = get_pixmap(app_name) @@ -249,7 +255,10 @@ class GamesTab(QStackedWidget, Ui_GamesTab): visible = True else: visible = True - if search_text.lower() not in w.game.app_name.lower() and search_text.lower() not in w.game.app_title.lower(): + if ( + search_text.lower() not in w.game.app_name.lower() + and search_text.lower() not in w.game.app_title.lower() + ): visible = False w.setVisible(visible) @@ -261,16 +270,24 @@ class GamesTab(QStackedWidget, Ui_GamesTab): if widgets := self.widgets.get(app_name): # from update - if self.core.is_installed(widgets[0].game.app_name) and isinstance(widgets[0], BaseInstalledWidget): + if self.core.is_installed(widgets[0].game.app_name) and isinstance( + widgets[0], BaseInstalledWidget + ): logger.debug("Update Gamelist: Updated: " + app_name) igame = self.core.get_installed_game(app_name) for w in widgets: w.igame = igame - w.update_available = self.core.get_asset(w.game.app_name, w.igame.platform, - True).build_version != igame.version + w.update_available = ( + self.core.get_asset( + w.game.app_name, w.igame.platform, True + ).build_version + != igame.version + ) widgets[0].leaveEvent(None) # new installed - elif self.core.is_installed(app_name) and isinstance(widgets[0], BaseUninstalledWidget): + elif self.core.is_installed(app_name) and isinstance( + widgets[0], BaseUninstalledWidget + ): logger.debug("Update Gamelist: New installed " + app_name) self.widgets[app_name][0].deleteLater() self.widgets[app_name][1].deleteLater() @@ -280,8 +297,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab): update_list = True # uninstalled - elif not self.core.is_installed(widgets[0].game.app_name) and isinstance(widgets[0], - BaseInstalledWidget): + elif not self.core.is_installed( + widgets[0].game.app_name + ) and isinstance(widgets[0], BaseInstalledWidget): logger.debug("Update list: Uninstalled: " + app_name) self.widgets[app_name][0].deleteLater() self.widgets[app_name][1].deleteLater() @@ -305,8 +323,13 @@ class GamesTab(QStackedWidget, Ui_GamesTab): if not game.app_name in installed_names: uninstalled_names.append(game.app_name) - new_installed_games = list(set(installed_names) - set([i.app_name for i in self.installed])) - new_uninstalled_games = list(set(uninstalled_names) - set([i.app_name for i in self.uninstalled_games])) + new_installed_games = list( + set(installed_names) - set([i.app_name for i in self.installed]) + ) + new_uninstalled_games = list( + set(uninstalled_names) + - set([i.app_name for i in self.uninstalled_games]) + ) if (not new_uninstalled_games) and (not new_installed_games): return @@ -332,7 +355,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab): game = self.core.get_game(name, False) self.add_uninstalled_widget(game) - for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title): + for igame in sorted( + self.core.get_installed_list(), key=lambda x: x.title + ): i_widget, list_widget = self.widgets[igame.app_name] self.icon_view.layout().addWidget(i_widget) @@ -348,7 +373,10 @@ class GamesTab(QStackedWidget, Ui_GamesTab): self.uninstalled_games = [] games, self.dlcs = self.core.get_game_and_dlc_list() for game in sorted(games, key=lambda x: x.app_title): - if not self.core.is_installed(game.app_name) and game.app_name not in self.no_asset_names: + if ( + not self.core.is_installed(game.app_name) + and game.app_name not in self.no_asset_names + ): i_widget, list_widget = self.widgets[game.app_name] self.icon_view.layout().addWidget(i_widget) self.list_view.layout().addWidget(list_widget) @@ -391,8 +419,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab): self.list_view.setParent(None) # insert widget in layout - self.scroll_widget.layout().insertWidget(1, - self.icon_view if self.head_bar.view.isChecked() else self.list_view) + self.scroll_widget.layout().insertWidget( + 1, self.icon_view if self.head_bar.view.isChecked() else self.list_view + ) def toggle_view(self): self.settings.setValue("icon_view", not self.head_bar.view.isChecked()) diff --git a/rare/components/tabs/games/cloud_save_utils.py b/rare/components/tabs/games/cloud_save_utils.py index 567f413b..a2da20aa 100644 --- a/rare/components/tabs/games/cloud_save_utils.py +++ b/rare/components/tabs/games/cloud_save_utils.py @@ -44,9 +44,15 @@ class SaveWorker(QRunnable): def run(self) -> None: try: if isinstance(self.model, DownloadModel): - shared.core.download_saves(self.model.app_name, self.model.latest_save.manifest_name, self.model.path) + shared.core.download_saves( + self.model.app_name, + self.model.latest_save.manifest_name, + self.model.path, + ) else: - shared.core.upload_save(self.model.app_name, self.model.path, self.model.date_time) + shared.core.upload_save( + self.model.app_name, self.model.path, self.model.date_time + ) except Exception as e: self.signals.finished.emit(str(e), self.model.app_name) logger.error(str(e)) @@ -69,7 +75,13 @@ class CloudSaveDialog(QDialog, Ui_SyncSaveDialog): UPLOAD = 1 CANCEL = 0 - def __init__(self, igame: InstalledGame, dt_local: datetime.datetime, dt_remote: datetime.datetime, newer: str): + def __init__( + self, + igame: InstalledGame, + dt_local: datetime.datetime, + dt_remote: datetime.datetime, + newer: str, + ): super(CloudSaveDialog, self).__init__() self.setupUi(self) @@ -155,18 +167,34 @@ class CloudSaveUtils(QObject): self.core.lgd.set_installed_game(app_name, igame) logger.info(f"Set save path of {igame.title} to {savepath}") elif not ignore_settings: # sync on startup - if QMessageBox.question(None, "Warning", self.tr( - "Could not compute cloud save path. Please set it in Game settings manually. \nDo you want to launch {} anyway?").format( - igame.title), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes: + if ( + QMessageBox.question( + None, + "Warning", + self.tr( + "Could not compute cloud save path. Please set it in Game settings manually. \nDo you want to launch {} anyway?" + ).format(igame.title), + QMessageBox.Yes | QMessageBox.No, + QMessageBox.No, + ) + == QMessageBox.Yes + ): return False else: raise ValueError("No savepath detected") else: - QMessageBox.warning(None, "Warning", - self.tr("No savepath found. Please set it in Game Settings manually")) + QMessageBox.warning( + None, + "Warning", + self.tr( + "No savepath found. Please set it in Game Settings manually" + ), + ) return False - res, (dt_local, dt_remote) = self.core.check_savegame_state(igame.save_path, self.latest_saves.get(app_name)) + res, (dt_local, dt_remote) = self.core.check_savegame_state( + igame.save_path, self.latest_saves.get(app_name) + ) if res == SaveGameStatus.NO_SAVE: return False @@ -217,18 +245,27 @@ class CloudSaveUtils(QObject): self.core.lgd.set_installed_game(app_name, igame) logger.info(f"Set save path of {igame.title} to {savepath}") else: - QMessageBox.warning(None, "Warning", self.tr("No savepath set. Skip syncing with cloud")) + QMessageBox.warning( + None, "Warning", self.tr("No savepath set. Skip syncing with cloud") + ) return False - res, (dt_local, dt_remote) = self.core.check_savegame_state(igame.save_path, self.latest_saves.get(app_name)) + res, (dt_local, dt_remote) = self.core.check_savegame_state( + igame.save_path, self.latest_saves.get(app_name) + ) if res == SaveGameStatus.LOCAL_NEWER and not always_ask: self.upload_saves(igame, dt_local) return elif res == SaveGameStatus.NO_SAVE and not always_ask: - QMessageBox.warning(None, "No saves", self.tr( - "There are no saves local and online. Maybe you have to change save path of {}").format(igame.title)) + QMessageBox.warning( + None, + "No saves", + self.tr( + "There are no saves local and online. Maybe you have to change save path of {}" + ).format(igame.title), + ) self.sync_finished.emit(app_name) return @@ -256,7 +293,11 @@ class CloudSaveUtils(QObject): def download_saves(self, igame): logger.info("Downloading saves for " + igame.title) - w = SaveWorker(DownloadModel(igame.app_name, self.latest_saves.get(igame.app_name), igame.save_path)) + w = SaveWorker( + DownloadModel( + igame.app_name, self.latest_saves.get(igame.app_name), igame.save_path + ) + ) w.signals.finished.connect(self.worker_finished) self.thread_pool.start(w) @@ -266,5 +307,9 @@ class CloudSaveUtils(QObject): self.sync_finished.emit(app_name) self.latest_saves = self.get_latest_saves(shared.api_results.saves) else: - QMessageBox.warning(None, "Warning", self.tr("Syncing with cloud failed: \n ") + error_message) + QMessageBox.warning( + None, + "Warning", + self.tr("Syncing with cloud failed: \n ") + error_message, + ) self.sync_finished.emit(app_name) diff --git a/rare/components/tabs/games/game_info/__init__.py b/rare/components/tabs/games/game_info/__init__.py index 801240e6..33c5ea19 100644 --- a/rare/components/tabs/games/game_info/__init__.py +++ b/rare/components/tabs/games/game_info/__init__.py @@ -33,7 +33,10 @@ class GameInfoTabs(SideTabWidget): self.settings.update_game(app_name) # DLC Tab: Disable if no dlcs available - if len(self.dlc_list.get(self.core.get_game(app_name).catalog_item_id, [])) == 0: + if ( + len(self.dlc_list.get(self.core.get_game(app_name).catalog_item_id, [])) + == 0 + ): self.setTabEnabled(3, False) else: self.setTabEnabled(3, True) diff --git a/rare/components/tabs/games/game_info/game_dlc.py b/rare/components/tabs/games/game_info/game_dlc.py index 6a857d1f..7d442b07 100644 --- a/rare/components/tabs/games/game_info/game_dlc.py +++ b/rare/components/tabs/games/game_info/game_dlc.py @@ -69,11 +69,18 @@ class GameDlc(QWidget, Ui_GameDlc): def install(self, app_name): if not self.core.is_installed(self.game.app_name): - QMessageBox.warning(self, "Error", self.tr("Base Game is not installed. Please install {} first").format( - self.game.app_title)) + QMessageBox.warning( + self, + "Error", + self.tr("Base Game is not installed. Please install {} first").format( + self.game.app_title + ), + ) return - self.signals.install_game.emit(InstallOptionsModel(app_name=app_name, update=True)) + self.signals.install_game.emit( + InstallOptionsModel(app_name=app_name, update=True) + ) class GameDlcWidget(QFrame, Ui_GameDlcWidget): @@ -109,10 +116,15 @@ class GameDlcWidget(QFrame, Ui_GameDlcWidget): self.pixmap = a0 self.image.setPixmap( self.pixmap.scaledToHeight( - self.dlc_info.size().height() - (self.image.contentsMargins().top() + - self.image.contentsMargins().bottom() + - self.image.lineWidth()*2), - Qt.SmoothTransformation)) + self.dlc_info.size().height() + - ( + self.image.contentsMargins().top() + + self.image.contentsMargins().bottom() + + self.image.lineWidth() * 2 + ), + Qt.SmoothTransformation, + ) + ) self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) def uninstall_dlc(self): diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index c5a42c71..ea440978 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -58,20 +58,32 @@ class GameInfo(QWidget, Ui_GameInfo): self.uninstalled.emit(self.game.app_name) def repair(self): - repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{self.game.app_name}.repair') + repair_file = os.path.join( + self.core.lgd.get_tmp_path(), f"{self.game.app_name}.repair" + ) if not os.path.exists(repair_file): - QMessageBox.warning(self, "Warning", self.tr( - "Repair file does not exist or game does not need a repair. Please verify game first")) + QMessageBox.warning( + self, + "Warning", + self.tr( + "Repair file does not exist or game does not need a repair. Please verify game first" + ), + ) return - self.signals.install_game.emit(InstallOptionsModel(app_name=self.game.app_name, repair=True, - update=True)) + self.signals.install_game.emit( + InstallOptionsModel(app_name=self.game.app_name, repair=True, update=True) + ) def verify(self): if not os.path.exists(self.igame.install_path): logger.error("Path does not exist") - QMessageBox.warning(self, "Warning", - self.tr("Installation path of {} does not exist. Cannot verify").format( - self.igame.title)) + QMessageBox.warning( + self, + "Warning", + self.tr("Installation path of {} does not exist. Cannot verify").format( + self.igame.title + ), + ) return self.verify_widget.setCurrentIndex(1) verify_worker = VerifyWorker(self.core, self.game.app_name) @@ -88,8 +100,11 @@ class GameInfo(QWidget, Ui_GameInfo): def finish_verify(self, failed, missing, app_name): if failed == missing == 0: - QMessageBox.information(self, "Summary", - "Game was verified successfully. No missing or corrupt files found") + QMessageBox.information( + self, + "Summary", + "Game was verified successfully. No missing or corrupt files found", + ) igame = self.core.get_installed_game(app_name) if igame.needs_verification: igame.needs_verification = False @@ -99,19 +114,28 @@ class GameInfo(QWidget, Ui_GameInfo): QMessageBox.warning(self, "Warning", self.tr("Something went wrong")) else: - ans = QMessageBox.question(self, "Summary", self.tr( - 'Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?').format( - failed, missing), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) + ans = QMessageBox.question( + self, + "Summary", + self.tr( + "Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?" + ).format(failed, missing), + QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes, + ) if ans == QMessageBox.Yes: - self.signals.install_game.emit(InstallOptionsModel(app_name=self.game.app_name, repair=True, - update=True)) + self.signals.install_game.emit( + InstallOptionsModel( + app_name=self.game.app_name, repair=True, update=True + ) + ) self.verify_widget.setCurrentIndex(0) self.verify_threads.pop(app_name) def update_game(self, app_name: str): self.game = self.core.get_game(app_name) self.igame = self.core.get_installed_game(self.game.app_name) - self.game_title.setText(f'

{self.game.app_title}

') + self.game_title.setText(f"

{self.game.app_title}

") pixmap = get_pixmap(self.game.app_name) w = 200 @@ -122,7 +146,9 @@ class GameInfo(QWidget, Ui_GameInfo): if self.igame: self.version.setText(self.igame.version) else: - self.version.setText(self.game.app_version(self.igame.platform if self.igame else "Windows")) + self.version.setText( + self.game.app_version(self.igame.platform if self.igame else "Windows") + ) self.dev.setText(self.game.metadata["developer"]) if self.igame: @@ -154,10 +180,14 @@ class GameInfo(QWidget, Ui_GameInfo): self.steam_worker.set_app_name(self.game.app_name) QThreadPool.globalInstance().start(self.steam_worker) - if len(self.verify_threads.keys()) == 0 or not self.verify_threads.get(self.game.app_name): + if len(self.verify_threads.keys()) == 0 or not self.verify_threads.get( + self.game.app_name + ): self.verify_widget.setCurrentIndex(0) elif self.verify_threads.get(self.game.app_name): self.verify_widget.setCurrentIndex(1) self.verify_progress.setValue( - self.verify_threads[self.game.app_name].num / self.verify_threads[self.game.app_name].total * 100 + self.verify_threads[self.game.app_name].num + / self.verify_threads[self.game.app_name].total + * 100 ) diff --git a/rare/components/tabs/games/game_info/game_settings.py b/rare/components/tabs/games/game_info/game_settings.py index 0b3fead8..e051e929 100644 --- a/rare/components/tabs/games/game_info/game_settings.py +++ b/rare/components/tabs/games/game_info/game_settings.py @@ -4,7 +4,14 @@ from logging import getLogger from typing import Tuple from PyQt5.QtCore import QSettings, QThreadPool, Qt -from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox, QLabel, QPushButton, QSizePolicy +from PyQt5.QtWidgets import ( + QWidget, + QFileDialog, + QMessageBox, + QLabel, + QPushButton, + QSizePolicy, +) from qtawesome import icon from legendary.core import LegendaryCore @@ -23,7 +30,7 @@ def find_proton_wrappers(): os.path.expanduser("~/.steam/steam/steamapps/common"), "/usr/share/steam/compatibilitytools.d", os.path.expanduser("~/.steam/compatibilitytools.d"), - os.path.expanduser("~/.steam/root/compatibilitytools.d") + os.path.expanduser("~/.steam/root/compatibilitytools.d"), ] for c in compatibilitytools_dirs: if os.path.exists(c): @@ -31,7 +38,9 @@ def find_proton_wrappers(): proton = os.path.join(c, i, "proton") compatibilitytool = os.path.join(c, i, "compatibilitytool.vdf") toolmanifest = os.path.join(c, i, "toolmanifest.vdf") - if os.path.exists(proton) and (os.path.exists(compatibilitytool) or os.path.exists(toolmanifest)): + if os.path.exists(proton) and ( + os.path.exists(compatibilitytool) or os.path.exists(toolmanifest) + ): wrapper = '"' + proton + '" run' possible_proton_wrappers.append(wrapper) if not possible_proton_wrappers: @@ -53,14 +62,23 @@ class GameSettings(QWidget, Ui_GameSettings): self.core = core self.settings = QSettings() - self.cloud_save_path_edit = PathEdit("", file_type=QFileDialog.DirectoryOnly, - ph_text=self.tr("Cloud save path"), - edit_func=lambda text: (os.path.exists(text), text), - save_func=self.save_save_path) - self.cloud_gb.layout().addRow(QLabel(self.tr("Save path")), self.cloud_save_path_edit) + self.cloud_save_path_edit = PathEdit( + "", + file_type=QFileDialog.DirectoryOnly, + ph_text=self.tr("Cloud save path"), + edit_func=lambda text: (os.path.exists(text), text), + save_func=self.save_save_path, + ) + self.cloud_gb.layout().addRow( + QLabel(self.tr("Save path")), self.cloud_save_path_edit + ) - self.compute_save_path_button = QPushButton(icon("fa.magic"), self.tr("Auto compute save path")) - self.compute_save_path_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed) + self.compute_save_path_button = QPushButton( + icon("fa.magic"), self.tr("Auto compute save path") + ) + self.compute_save_path_button.setSizePolicy( + QSizePolicy.Maximum, QSizePolicy.Fixed + ) self.compute_save_path_button.clicked.connect(self.compute_save_path) self.cloud_gb.layout().addRow(None, self.compute_save_path_button) @@ -71,14 +89,14 @@ class GameSettings(QWidget, Ui_GameSettings): lambda x: self.update_combobox(x, "skip_update_check") ) self.cloud_sync.stateChanged.connect( - lambda: self.settings.setValue(f"{self.game.app_name}/auto_sync_cloud", self.cloud_sync.isChecked()) + lambda: self.settings.setValue( + f"{self.game.app_name}/auto_sync_cloud", self.cloud_sync.isChecked() + ) ) self.launch_params.textChanged.connect( lambda x: self.save_line_edit("start_params", x) ) - self.wrapper.textChanged.connect( - lambda x: self.save_line_edit("wrapper", x) - ) + self.wrapper.textChanged.connect(lambda x: self.save_line_edit("wrapper", x)) self.override_exe_edit.textChanged.connect( lambda x: self.save_line_edit("override_exe", x) ) @@ -92,7 +110,7 @@ class GameSettings(QWidget, Ui_GameSettings): self.proton_prefix = PathEdit( file_type=QFileDialog.DirectoryOnly, edit_func=self.proton_prefix_edit, - save_func=self.proton_prefix_save + save_func=self.proton_prefix_save, ) self.proton_prefix_layout.addWidget(self.proton_prefix) @@ -100,8 +118,10 @@ class GameSettings(QWidget, Ui_GameSettings): # FIXME: Remove the spacerItem and margins from the linux settings # FIXME: This should be handled differently at soem point in the future self.linux_settings.layout().setContentsMargins(0, 0, 0, 0) - for item in [self.linux_settings.layout().itemAt(idx) for idx in - range(self.linux_settings.layout().count())]: + for item in [ + self.linux_settings.layout().itemAt(idx) + for idx in range(self.linux_settings.layout().count()) + ]: if item.spacerItem(): self.linux_settings.layout().removeItem(item) del item @@ -113,7 +133,10 @@ class GameSettings(QWidget, Ui_GameSettings): self.game_settings_layout.setAlignment(Qt.AlignTop) def compute_save_path(self): - if self.core.is_installed(self.game.app_name) and self.game.supports_cloud_saves: + if ( + self.core.is_installed(self.game.app_name) + and self.game.supports_cloud_saves + ): try: new_path = self.core.get_save_path(self.game.app_name) except Exception as e: @@ -121,16 +144,22 @@ class GameSettings(QWidget, Ui_GameSettings): self.cloud_save_path_edit.setText(self.tr("Loading")) self.cloud_save_path_edit.setDisabled(True) self.compute_save_path_button.setDisabled(True) - resolver = WineResolver(get_raw_save_path(self.game), self.game.app_name, self.core) + resolver = WineResolver( + get_raw_save_path(self.game), self.game.app_name, self.core + ) app_name = self.game.app_name[:] - resolver.signals.result_ready.connect(lambda x: self.wine_resolver_finished(x, app_name)) + resolver.signals.result_ready.connect( + lambda x: self.wine_resolver_finished(x, app_name) + ) QThreadPool.globalInstance().start(resolver) return else: self.cloud_save_path_edit.setText(new_path) def wine_resolver_finished(self, path, app_name): - logger.info(f"Wine resolver finished for {app_name}. Computed save path: {path}") + logger.info( + f"Wine resolver finished for {app_name}. Computed save path: {path}" + ) if app_name == self.game.app_name: self.cloud_save_path_edit.setDisabled(False) self.compute_save_path_button.setDisabled(False) @@ -139,9 +168,13 @@ class GameSettings(QWidget, Ui_GameSettings): os.makedirs(path) except PermissionError: self.cloud_save_path_edit.setText("") - QMessageBox.warning(None, "Error", self.tr( - "Error while launching {}. No permission to create {}").format( - self.game.app_title, path)) + QMessageBox.warning( + None, + "Error", + self.tr( + "Error while launching {}. No permission to create {}" + ).format(self.game.app_title, path), + ) return if not path: self.cloud_save_path_edit.setText("") @@ -163,8 +196,13 @@ class GameSettings(QWidget, Ui_GameSettings): self.core.lgd.config.add_section(self.game.app_name) self.core.lgd.config.set(self.game.app_name, option, value) else: - if self.core.lgd.config.has_section(self.game.app_name) and self.core.lgd.config.get( - f"{self.game.app_name}", option, fallback=None) is not None: + if ( + self.core.lgd.config.has_section(self.game.app_name) + and self.core.lgd.config.get( + f"{self.game.app_name}", option, fallback=None + ) + is not None + ): self.core.lgd.config.remove_option(self.game.app_name, option) if not self.core.lgd.config[self.game.app_name]: self.core.lgd.config.remove_section(self.game.app_name) @@ -186,7 +224,9 @@ class GameSettings(QWidget, Ui_GameSettings): self.core.lgd.config.set(self.game.app_name, option, "false") else: if self.game.app_name in self.core.lgd.config.sections(): - if self.core.lgd.config.get(f"{self.game.app_name}", option, fallback=False): + if self.core.lgd.config.get( + f"{self.game.app_name}", option, fallback=False + ): self.core.lgd.config.remove_option(self.game.app_name, option) if not self.core.lgd.config[self.game.app_name]: self.core.lgd.config.remove_section(self.game.app_name) @@ -197,21 +237,39 @@ class GameSettings(QWidget, Ui_GameSettings): # Dont use Proton if i == 0: if f"{self.game.app_name}" in self.core.lgd.config.sections(): - if self.core.lgd.config.get(f"{self.game.app_name}", "wrapper", fallback=False): - self.core.lgd.config.remove_option(self.game.app_name, "wrapper") - if self.core.lgd.config.get(f"{self.game.app_name}", "no_wine", fallback=False): - self.core.lgd.config.remove_option(self.game.app_name, "no_wine") + if self.core.lgd.config.get( + f"{self.game.app_name}", "wrapper", fallback=False + ): + self.core.lgd.config.remove_option( + self.game.app_name, "wrapper" + ) + if self.core.lgd.config.get( + f"{self.game.app_name}", "no_wine", fallback=False + ): + self.core.lgd.config.remove_option( + self.game.app_name, "no_wine" + ) if not self.core.lgd.config[self.game.app_name]: self.core.lgd.config.remove_section(self.game.app_name) if f"{self.game.app_name}.env" in self.core.lgd.config.sections(): - if self.core.lgd.config.get(f"{self.game.app_name}.env", "STEAM_COMPAT_DATA_PATH", fallback=False): - self.core.lgd.config.remove_option(f"{self.game.app_name}.env", "STEAM_COMPAT_DATA_PATH") + if self.core.lgd.config.get( + f"{self.game.app_name}.env", + "STEAM_COMPAT_DATA_PATH", + fallback=False, + ): + self.core.lgd.config.remove_option( + f"{self.game.app_name}.env", "STEAM_COMPAT_DATA_PATH" + ) if not self.core.lgd.config[self.game.app_name + ".env"]: self.core.lgd.config.remove_section(self.game.app_name + ".env") self.proton_prefix.setEnabled(False) # lk: TODO: This has to be fixed properly. # lk: It happens because of the widget update. Mask it for now behind disabling the save button - self.wrapper.setText(self.core.lgd.config.get(f"{self.game.app_name}", "wrapper", fallback="")) + self.wrapper.setText( + self.core.lgd.config.get( + f"{self.game.app_name}", "wrapper", fallback="" + ) + ) self.wrapper.setEnabled(True) self.linux_settings.wine_groupbox.setEnabled(True) else: @@ -225,8 +283,11 @@ class GameSettings(QWidget, Ui_GameSettings): self.core.lgd.config[self.game.app_name + ".env"] = {} self.core.lgd.config.set(self.game.app_name, "wrapper", wrapper) self.core.lgd.config.set(self.game.app_name, "no_wine", "true") - self.core.lgd.config.set(self.game.app_name + ".env", "STEAM_COMPAT_DATA_PATH", - os.path.expanduser("~/.proton")) + self.core.lgd.config.set( + self.game.app_name + ".env", + "STEAM_COMPAT_DATA_PATH", + os.path.expanduser("~/.proton"), + ) self.proton_prefix.setText(os.path.expanduser("~/.proton")) # Dont use Wine @@ -243,7 +304,9 @@ class GameSettings(QWidget, Ui_GameSettings): return os.path.exists(parent), text def proton_prefix_save(self, text: str): - self.core.lgd.config.set(self.game.app_name + ".env", "STEAM_COMPAT_DATA_PATH", text) + self.core.lgd.config.set( + self.game.app_name + ".env", "STEAM_COMPAT_DATA_PATH", text + ) self.core.lgd.save_config() def update_game(self, app_name: str): @@ -252,7 +315,9 @@ class GameSettings(QWidget, Ui_GameSettings): self.igame = self.core.get_installed_game(self.game.app_name) if self.igame: if self.igame.can_run_offline: - offline = self.core.lgd.config.get(self.game.app_name, "offline", fallback="unset") + offline = self.core.lgd.config.get( + self.game.app_name, "offline", fallback="unset" + ) if offline == "true": self.offline.setCurrentIndex(1) elif offline == "false": @@ -266,7 +331,9 @@ class GameSettings(QWidget, Ui_GameSettings): else: self.offline.setEnabled(False) - skip_update = self.core.lgd.config.get(self.game.app_name, "skip_update_check", fallback="unset") + skip_update = self.core.lgd.config.get( + self.game.app_name, "skip_update_check", fallback="unset" + ) if skip_update == "true": self.skip_update.setCurrentIndex(1) elif skip_update == "false": @@ -286,13 +353,19 @@ class GameSettings(QWidget, Ui_GameSettings): else: self.linux_settings_scroll.setVisible(True) - proton = self.core.lgd.config.get(f"{app_name}", "wrapper", fallback="").replace('"', "") + proton = self.core.lgd.config.get( + f"{app_name}", "wrapper", fallback="" + ).replace('"', "") if proton and "proton" in proton: self.proton_prefix.setEnabled(True) - self.proton_wrapper.setCurrentText(f'"{proton.replace(" run", "")}" run') - proton_prefix = self.core.lgd.config.get(f"{app_name}.env", "STEAM_COMPAT_DATA_PATH", - fallback=self.tr( - "Please select path for proton prefix")) + self.proton_wrapper.setCurrentText( + f'"{proton.replace(" run", "")}" run' + ) + proton_prefix = self.core.lgd.config.get( + f"{app_name}.env", + "STEAM_COMPAT_DATA_PATH", + fallback=self.tr("Please select path for proton prefix"), + ) self.proton_prefix.setText(proton_prefix) self.wrapper.setEnabled(False) else: @@ -305,15 +378,21 @@ class GameSettings(QWidget, Ui_GameSettings): self.cloud_save_path_edit.setText("") else: self.cloud_gb.setEnabled(True) - sync_cloud = self.settings.value(f"{self.game.app_name}/auto_sync_cloud", True, bool) + sync_cloud = self.settings.value( + f"{self.game.app_name}/auto_sync_cloud", True, bool + ) self.cloud_sync.setChecked(sync_cloud) if self.igame.save_path: self.cloud_save_path_edit.setText(self.igame.save_path) else: self.cloud_save_path_edit.setText("") - self.launch_params.setText(self.core.lgd.config.get(self.game.app_name, "start_params", fallback="")) - self.override_exe_edit.setText(self.core.lgd.config.get(self.game.app_name, "override_exe", fallback="")) + self.launch_params.setText( + self.core.lgd.config.get(self.game.app_name, "start_params", fallback="") + ) + self.override_exe_edit.setText( + self.core.lgd.config.get(self.game.app_name, "override_exe", fallback="") + ) self.change = True diff --git a/rare/components/tabs/games/game_utils.py b/rare/components/tabs/games/game_utils.py index c5a16a85..8868ab31 100644 --- a/rare/components/tabs/games/game_utils.py +++ b/rare/components/tabs/games/game_utils.py @@ -56,10 +56,15 @@ class GameUtils(QObject): game = self.core.get_game(app_name) igame = self.core.get_installed_game(app_name) if not os.path.exists(igame.install_path): - if QMessageBox.Yes == QMessageBox.question(None, "Uninstall", self.tr( - "Game files of {} do not exist. Remove it from installed games?").format(igame.title), - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes): + if QMessageBox.Yes == QMessageBox.question( + None, + "Uninstall", + self.tr( + "Game files of {} do not exist. Remove it from installed games?" + ).format(igame.title), + QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes, + ): self.core.lgd.remove_installed_game(app_name) return True else: @@ -72,7 +77,9 @@ class GameUtils(QObject): shared.signals.game_uninstalled.emit(app_name) return True - def prepare_launch(self, app_name, offline: bool = False, skip_update_check: bool = False): + def prepare_launch( + self, app_name, offline: bool = False, skip_update_check: bool = False + ): game = self.core.get_game(app_name) dont_sync_after_finish = False @@ -90,10 +97,19 @@ class GameUtils(QObject): return self.sync_finished(app_name) - self.launch_game(app_name, offline, skip_update_check, ask_always_sync=dont_sync_after_finish) + self.launch_game( + app_name, offline, skip_update_check, ask_always_sync=dont_sync_after_finish + ) - def launch_game(self, app_name: str, offline: bool = False, skip_update_check: bool = False, wine_bin: str = None, - wine_pfx: str = None, ask_always_sync: bool = False): + def launch_game( + self, + app_name: str, + offline: bool = False, + skip_update_check: bool = False, + wine_bin: str = None, + wine_pfx: str = None, + ask_always_sync: bool = False, + ): if shared.args.offline: offline = True game = self.core.get_game(app_name) @@ -105,8 +121,15 @@ class GameUtils(QObject): return if QSettings().value("confirm_start", False, bool): - if not QMessageBox.question(None, "Launch", self.tr("Do you want to launch {}").format(game.app_title), - QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: + if ( + not QMessageBox.question( + None, + "Launch", + self.tr("Do you want to launch {}").format(game.app_title), + QMessageBox.Yes | QMessageBox.No, + ) + == QMessageBox.Yes + ): logger.info("Cancel Startup") self.finished.emit(app_name, "") return @@ -119,12 +142,18 @@ class GameUtils(QObject): logger.error(f"{app_name} is not installed") if game.is_dlc: logger.error("Game is dlc") - self.finished.emit(app_name, self.tr("Game is a DLC. Please launch base game instead")) + self.finished.emit( + app_name, self.tr("Game is a DLC. Please launch base game instead") + ) return if not os.path.exists(igame.install_path): logger.error("Game doesn't exist") - self.finished.emit(app_name, - self.tr("Game files of {} do not exist. Please install game").format(game.app_title)) + self.finished.emit( + app_name, + self.tr( + "Game files of {} do not exist. Please install game" + ).format(game.app_title), + ) return process = GameProcess(app_name) @@ -135,7 +164,9 @@ class GameUtils(QObject): if not skip_update_check and not self.core.is_noupdate_game(app_name): # check updates try: - latest = self.core.get_asset(app_name, igame.platform, update=False) + latest = self.core.get_asset( + app_name, igame.platform, update=False + ) except ValueError: self.finished.emit(app_name, self.tr("Metadata doesn't exist")) return @@ -144,12 +175,15 @@ class GameUtils(QObject): self.finished.emit(app_name, self.tr("Please update game")) return - params: LaunchParameters = self.core.get_launch_parameters(app_name=app_name, offline=offline, - wine_bin=wine_bin, wine_pfx=wine_pfx) + params: LaunchParameters = self.core.get_launch_parameters( + app_name=app_name, offline=offline, wine_bin=wine_bin, wine_pfx=wine_pfx + ) full_params = list() full_params.extend(params.launch_command) - full_params.append(os.path.join(params.game_directory, params.game_executable)) + full_params.append( + os.path.join(params.game_directory, params.game_executable) + ) full_params.extend(params.game_parameters) full_params.extend(params.egl_parameters) full_params.extend(params.user_parameters) @@ -170,23 +204,33 @@ class GameUtils(QObject): os.makedirs(val) except PermissionError as e: logger.error(str(e)) - QMessageBox.warning(None, "Error", - self.tr( - "Error while launching {}. No permission to create {} for {}").format( - game.app_title, val, env)) + QMessageBox.warning( + None, + "Error", + self.tr( + "Error while launching {}. No permission to create {} for {}" + ).format(game.app_title, val, env), + ) process.deleteLater() return # check wine executable if shutil.which(full_params[0]) is None: # wine binary does not exist - QMessageBox.warning(None, "Warning", self.tr( - "Wine executable '{}' does not exist. Please change it in Settings").format(full_params[0])) + QMessageBox.warning( + None, + "Warning", + self.tr( + "Wine executable '{}' does not exist. Please change it in Settings" + ).format(full_params[0]), + ) process.deleteLater() return process.setProcessEnvironment(environment) process.game_finished.connect(self.game_finished) - running_game = RunningGameModel(process=process, app_name=app_name, always_ask_sync=ask_always_sync) + running_game = RunningGameModel( + process=process, app_name=app_name, always_ask_sync=ask_always_sync + ) process.start(full_params[0], full_params[1:]) self.game_launched.emit(app_name) @@ -201,25 +245,37 @@ class GameUtils(QObject): webbrowser.open(origin_uri) self.finished.emit(app_name, "") return - wine_pfx = self.core.lgd.config.get(game.app_name, 'wine_prefix', - fallback=os.path.expanduser("~/.wine")) + wine_pfx = self.core.lgd.config.get( + game.app_name, "wine_prefix", fallback=os.path.expanduser("~/.wine") + ) if not wine_bin: - wine_bin = self.core.lgd.config.get(game.app_name, 'wine_executable', fallback="/usr/bin/wine") + wine_bin = self.core.lgd.config.get( + game.app_name, "wine_executable", fallback="/usr/bin/wine" + ) if shutil.which(wine_bin) is None: # wine binary does not exist - QMessageBox.warning(None, "Warning", - self.tr("Wine executable '{}' does not exist. Please change it in Settings").format( - wine_bin)) + QMessageBox.warning( + None, + "Warning", + self.tr( + "Wine executable '{}' does not exist. Please change it in Settings" + ).format(wine_bin), + ) process.deleteLater() return env = self.core.get_app_environment(game.app_name, wine_pfx=wine_pfx) - if not env.get('WINEPREFIX') and not os.path.exists("/usr/bin/wine"): - logger.error(f'In order to launch Origin correctly you must specify the wine binary and prefix ' - f'to use in the configuration file or command line. See the README for details.') - self.finished.emit(app_name, self.tr("No wine executable selected. Please set it in settings")) + if not env.get("WINEPREFIX") and not os.path.exists("/usr/bin/wine"): + logger.error( + f"In order to launch Origin correctly you must specify the wine binary and prefix " + f"to use in the configuration file or command line. See the README for details." + ) + self.finished.emit( + app_name, + self.tr("No wine executable selected. Please set it in settings"), + ) return environment = QProcessEnvironment() @@ -231,17 +287,27 @@ class GameUtils(QObject): if QSettings().value("show_console", False, bool): self.console.show() - process.readyReadStandardOutput.connect(lambda: self.console.log( - str(process.readAllStandardOutput().data(), "utf-8", "ignore"))) - process.readyReadStandardError.connect(lambda: self.console.error( - str(process.readAllStandardError().data(), "utf-8", "ignore"))) + process.readyReadStandardOutput.connect( + lambda: self.console.log( + str(process.readAllStandardOutput().data(), "utf-8", "ignore") + ) + ) + process.readyReadStandardError.connect( + lambda: self.console.error( + str(process.readAllStandardError().data(), "utf-8", "ignore") + ) + ) def game_finished(self, exit_code, app_name): logger.info("Game exited with exit code: " + str(exit_code)) is_origin = self.core.get_game(app_name).third_party_store == "Origin" if exit_code == 53 and is_origin: msg_box = QMessageBox() - msg_box.setText(self.tr("Origin is not installed. Do you want to download installer file? ")) + msg_box.setText( + self.tr( + "Origin is not installed. Do you want to download installer file? " + ) + ) msg_box.addButton(QPushButton("Download"), QMessageBox.YesRole) msg_box.addButton(QPushButton("Cancel"), QMessageBox.RejectRole) resp = msg_box.exec() @@ -249,8 +315,13 @@ class GameUtils(QObject): if resp == 0: webbrowser.open("https://www.dm.origin.com/download") if is_origin and exit_code == 1: - QMessageBox.warning(None, "Warning", - self.tr("Failed to launch {}").format(self.core.get_game(app_name).app_title)) + QMessageBox.warning( + None, + "Warning", + self.tr("Failed to launch {}").format( + self.core.get_game(app_name).app_title + ), + ) game: RunningGameModel = self.running_games.get(app_name, None) if app_name in self.running_games.keys(): @@ -262,10 +333,15 @@ class GameUtils(QObject): if self.core.get_game(app_name).supports_cloud_saves: if exit_code != 0: - r = QMessageBox.question(None, "Question", self.tr( - "Game exited with code {}, which is not a normal code. It could be caused by a crash. Do you want to sync cloud saves").format( - exit_code), - buttons=QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.Yes) + r = QMessageBox.question( + None, + "Question", + self.tr( + "Game exited with code {}, which is not a normal code. It could be caused by a crash. Do you want to sync cloud saves" + ).format(exit_code), + buttons=QMessageBox.Yes | QMessageBox.No, + defaultButton=QMessageBox.Yes, + ) if r != QMessageBox.Yes: return self.cloud_save_utils.game_finished(app_name, game.always_ask_sync) 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 3570ba06..f9704dc5 100644 --- a/rare/components/tabs/games/game_widgets/base_installed_widget.py +++ b/rare/components/tabs/games/game_widgets/base_installed_widget.py @@ -33,27 +33,30 @@ class BaseInstalledWidget(QGroupBox): "update_available": self.tr("Start game without version check"), "launch": self.tr("Launch Game"), "launch_origin": self.tr("Launch/Link"), - "running": self.tr("Game running") + "running": self.tr("Game running"), }, "default": { "running": self.tr("Game running"), "syncing": self.tr("Syncing cloud saves"), - "update_available": self.tr("Update available") - } + "update_available": self.tr("Update available"), + }, } self.game = self.core.get_game(app_name) self.igame = self.core.get_installed_game(app_name) # None if origin self.image = QLabel() - self.image.setPixmap(pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)) + self.image.setPixmap( + pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation) + ) self.game_running = False self.offline = shared.args.offline self.update_available = False if self.igame and self.core.lgd.assets: try: - remote_version = self.core.get_asset(self.game.app_name, platform=self.igame.platform, - update=False).build_version + remote_version = self.core.get_asset( + self.game.app_name, platform=self.igame.platform, update=False + ).build_version except ValueError: logger.error("Asset error for " + self.game.app_title) self.update_available = False @@ -75,19 +78,26 @@ class BaseInstalledWidget(QGroupBox): sync.triggered.connect(self.sync_game) self.addAction(sync) - if os.path.exists(os.path.expanduser(f"~/Desktop/{self.game.app_title}.desktop")) \ - or os.path.exists(os.path.expanduser(f"~/Desktop/{self.game.app_title}.lnk")): + if os.path.exists( + os.path.expanduser(f"~/Desktop/{self.game.app_title}.desktop") + ) or os.path.exists(os.path.expanduser(f"~/Desktop/{self.game.app_title}.lnk")): self.create_desktop = QAction(self.tr("Remove Desktop link")) else: self.create_desktop = QAction(self.tr("Create Desktop link")) if self.igame: - self.create_desktop.triggered.connect(lambda: self.create_desktop_link("desktop")) + self.create_desktop.triggered.connect( + lambda: self.create_desktop_link("desktop") + ) self.addAction(self.create_desktop) if platform.system() == "Linux": - start_menu_file = os.path.expanduser(f"~/.local/share/applications/{self.game.app_title}.desktop") + start_menu_file = os.path.expanduser( + f"~/.local/share/applications/{self.game.app_title}.desktop" + ) elif platform.system() == "Windows": - start_menu_file = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu") + start_menu_file = os.path.expandvars( + "%appdata%/Microsoft/Windows/Start Menu" + ) else: start_menu_file = "" if platform.system() in ["Windows", "Linux"]: @@ -96,7 +106,9 @@ class BaseInstalledWidget(QGroupBox): else: self.create_start_menu = QAction(self.tr("Create start menu link")) if self.igame: - self.create_start_menu.triggered.connect(lambda: self.create_desktop_link("start_menu")) + self.create_start_menu.triggered.connect( + lambda: self.create_desktop_link("start_menu") + ) self.addAction(self.create_start_menu) reload_image = QAction(self.tr("Reload Image"), self) @@ -105,19 +117,26 @@ class BaseInstalledWidget(QGroupBox): uninstall = QAction(self.tr("Uninstall"), self) uninstall.triggered.connect( - lambda: shared.signals.update_gamelist.emit([self.game.app_name]) if self.game_utils.uninstall_game( - self.game.app_name) else None) + lambda: shared.signals.update_gamelist.emit([self.game.app_name]) + if self.game_utils.uninstall_game(self.game.app_name) + else None + ) self.addAction(uninstall) def reload_image(self): utils.download_image(self.game, True) pm = utils.get_pixmap(self.game.app_name) - self.image.setPixmap(pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)) + self.image.setPixmap( + pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation) + ) def create_desktop_link(self, type_of_link): if platform.system() not in ["Windows", "Linux"]: - QMessageBox.warning(self, "Warning", - f"Create a Desktop link is currently not supported on {platform.system()}") + QMessageBox.warning( + self, + "Warning", + f"Create a Desktop link is currently not supported on {platform.system()}", + ) return if type_of_link == "desktop": path = os.path.expanduser(f"~/Desktop/") @@ -125,19 +144,25 @@ class BaseInstalledWidget(QGroupBox): path = os.path.expanduser("~/.local/share/applications/") else: return - if not (os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.desktop")) - or os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.lnk"))): + if not ( + os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.desktop")) + or os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.lnk")) + ): try: if not create_desktop_link(self.game.app_name, self.core, type_of_link): return except PermissionError: - QMessageBox.warning(self, "Error", "Permission error. Cannot create Desktop Link") + QMessageBox.warning( + self, "Error", "Permission error. Cannot create Desktop Link" + ) if type_of_link == "desktop": self.create_desktop.setText(self.tr("Remove Desktop link")) elif type_of_link == "start_menu": self.create_start_menu.setText(self.tr("Remove Start menu link")) else: - if os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.desktop")): + if os.path.exists( + os.path.expanduser(f"{path}{self.game.app_title}.desktop") + ): os.remove(os.path.expanduser(f"{path}{self.game.app_title}.desktop")) elif os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.lnk")): os.remove(os.path.expanduser(f"{path}{self.game.app_title}.lnk")) @@ -151,14 +176,18 @@ class BaseInstalledWidget(QGroupBox): if not self.game_running: if self.game.supports_cloud_saves: self.syncing_cloud_saves = True - self.game_utils.prepare_launch(self.game.app_name, offline, skip_version_check) + self.game_utils.prepare_launch( + self.game.app_name, offline, skip_version_check + ) def sync_finished(self, app_name): self.syncing_cloud_saves = False def sync_game(self): - if self.game_utils.cloud_save_utils.sync_before_launch_game(self.game.app_name, True): + if self.game_utils.cloud_save_utils.sync_before_launch_game( + self.game.app_name, True + ): self.syncing_cloud_saves = True def game_finished(self, app_name, error): 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 5c15e6b8..e819f09a 100644 --- a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py +++ b/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py @@ -17,7 +17,9 @@ class BaseUninstalledWidget(QGroupBox): self.game = game self.core = core self.image = QLabel() - self.image.setPixmap(pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)) + self.image.setPixmap( + pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation) + ) self.installing = False self.setContextMenuPolicy(Qt.ActionsContextMenu) self.setContentsMargins(0, 0, 0, 0) @@ -29,7 +31,9 @@ class BaseUninstalledWidget(QGroupBox): def reload_image(self): utils.download_image(self.game, True) pm = utils.get_uninstalled_pixmap(self.game.app_name) - self.image.setPixmap(pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)) + self.image.setPixmap( + pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation) + ) def install(self): self.show_uninstalled_info.emit(self.game) diff --git a/rare/components/tabs/games/game_widgets/installed_icon_widget.py b/rare/components/tabs/games/game_widgets/installed_icon_widget.py index 3095e42c..fa3068b6 100644 --- a/rare/components/tabs/games/game_widgets/installed_icon_widget.py +++ b/rare/components/tabs/games/game_widgets/installed_icon_widget.py @@ -6,7 +6,9 @@ from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QLabel from qtawesome import icon from rare import shared -from rare.components.tabs.games.game_widgets.base_installed_widget import BaseInstalledWidget +from rare.components.tabs.games.game_widgets.base_installed_widget import ( + BaseInstalledWidget, +) logger = getLogger("GameWidgetInstalled") @@ -43,7 +45,9 @@ class InstalledIconWidget(BaseInstalledWidget): self.menu_btn.setIcon(icon("ei.info-circle")) # self.menu_btn.setObjectName("installed_menu_button") self.menu_btn.setIconSize(QSize(18, 18)) - self.menu_btn.enterEvent = lambda x: self.info_label.setText(self.tr("Information")) + self.menu_btn.enterEvent = lambda x: self.info_label.setText( + self.tr("Information") + ) self.menu_btn.leaveEvent = lambda x: self.enterEvent(None) # remove Border @@ -77,7 +81,9 @@ class InstalledIconWidget(BaseInstalledWidget): elif self.update_available: self.info_label.setText(self.texts["hover"]["update_available"]) else: - self.info_label.setText(self.texts["hover"]["launch" if self.igame else "launch_origin"]) + self.info_label.setText( + self.texts["hover"]["launch" if self.igame else "launch_origin"] + ) def leaveEvent(self, a0: QEvent = None) -> None: if self.game_running: diff --git a/rare/components/tabs/games/game_widgets/installed_list_widget.py b/rare/components/tabs/games/game_widgets/installed_list_widget.py index ea4c3a3d..d252a592 100644 --- a/rare/components/tabs/games/game_widgets/installed_list_widget.py +++ b/rare/components/tabs/games/game_widgets/installed_list_widget.py @@ -4,7 +4,9 @@ from PyQt5.QtCore import QProcess, pyqtSignal, Qt from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout from qtawesome import icon -from rare.components.tabs.games.game_widgets.base_installed_widget import BaseInstalledWidget +from rare.components.tabs.games.game_widgets.base_installed_widget import ( + BaseInstalledWidget, +) from rare.utils.utils import get_size logger = getLogger("GameWidget") @@ -36,7 +38,9 @@ class InstalledListWidget(BaseInstalledWidget): self.title_label.setWordWrap(True) self.childLayout.addWidget(self.title_label) self.app_name_label = QLabel(self.game.app_name) - self.launch_button = QPushButton(play_icon, self.tr("Launch") if self.igame else self.tr("Link/Play")) + self.launch_button = QPushButton( + play_icon, self.tr("Launch") if self.igame else self.tr("Link/Play") + ) self.launch_button.setObjectName("launch_game_button") self.launch_button.setFixedWidth(150) @@ -57,7 +61,9 @@ class InstalledListWidget(BaseInstalledWidget): self.childLayout.addWidget(self.developer_label) if self.igame: self.version_label = QLabel("Version: " + str(self.igame.version)) - self.size_label = QLabel(f"{self.tr('Installed size')}: {get_size(self.size)}") + self.size_label = QLabel( + f"{self.tr('Installed size')}: {get_size(self.size)}" + ) self.childLayout.addWidget(self.version_label) self.childLayout.addWidget(self.size_label) 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 e3712a6a..5f77a5c3 100644 --- a/rare/components/tabs/games/game_widgets/installing_game_widget.py +++ b/rare/components/tabs/games/game_widgets/installing_game_widget.py @@ -3,7 +3,12 @@ from PyQt5.QtGui import QPaintEvent, QPainter, QPixmap, QPen, QFont, QColor from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QWidget from rare import shared -from rare.utils.utils import get_pixmap, optimal_text_background, text_color_for_background, get_uninstalled_pixmap +from rare.utils.utils import ( + get_pixmap, + optimal_text_background, + text_color_for_background, + get_uninstalled_pixmap, +) class InstallingGameWidget(QWidget): @@ -53,17 +58,23 @@ class PaintWidget(QWidget): game = shared.core.get_game(app_name, False) self.color_image = get_pixmap(game.app_name) w = 200 - self.color_image = self.color_image.scaled(w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation) + self.color_image = self.color_image.scaled( + w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation + ) self.setFixedSize(self.color_image.size()) self.bw_image = get_uninstalled_pixmap(app_name) - self.bw_image = self.bw_image.scaled(w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation) + self.bw_image = self.bw_image.scaled( + w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation + ) self.progress = 0 pixel_list = [] for x in range(self.color_image.width()): for y in range(self.color_image.height()): # convert pixmap to qimage, get pixel and remove alpha channel - pixel_list.append(self.color_image.toImage().pixelColor(x, y).getRgb()[:-1]) + pixel_list.append( + self.color_image.toImage().pixelColor(x, y).getRgb()[:-1] + ) self.rgb_tuple = optimal_text_background(pixel_list) @@ -74,14 +85,21 @@ class PaintWidget(QWidget): w = self.bw_image.width() * self.progress // 100 - painter.drawPixmap(0, 0, w, self.color_image.height(), - self.color_image.copy(QRect(0, 0, w, self.color_image.height()))) + painter.drawPixmap( + 0, + 0, + w, + self.color_image.height(), + self.color_image.copy(QRect(0, 0, w, self.color_image.height())), + ) # Draw Circle pen = QPen(QColor(*self.rgb_tuple), 3) painter.setPen(pen) painter.setBrush(QColor(*self.rgb_tuple)) - painter.drawEllipse(int(self.width() / 2) - 20, int(self.height() / 2) - 20, 40, 40) + painter.drawEllipse( + int(self.width() / 2) - 20, int(self.height() / 2) - 20, 40, 40 + ) # draw text painter.setPen(QColor(*text_color_for_background(self.rgb_tuple))) diff --git a/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py b/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py index f23dd0a0..4a653828 100644 --- a/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py +++ b/rare/components/tabs/games/game_widgets/uninstalled_icon_widget.py @@ -4,13 +4,14 @@ from PyQt5.QtWidgets import QVBoxLayout, QLabel from legendary.core import LegendaryCore from legendary.models.game import Game -from rare.components.tabs.games.game_widgets.base_uninstalled_widget import BaseUninstalledWidget +from rare.components.tabs.games.game_widgets.base_uninstalled_widget import ( + BaseUninstalledWidget, +) logger = getLogger("Uninstalled") class IconWidgetUninstalled(BaseUninstalledWidget): - def __init__(self, game: Game, core: LegendaryCore, pixmap): super(IconWidgetUninstalled, self).__init__(game, core, pixmap) self.layout = QVBoxLayout() diff --git a/rare/components/tabs/games/game_widgets/uninstalled_list_widget.py b/rare/components/tabs/games/game_widgets/uninstalled_list_widget.py index 50e42406..870b6184 100644 --- a/rare/components/tabs/games/game_widgets/uninstalled_list_widget.py +++ b/rare/components/tabs/games/game_widgets/uninstalled_list_widget.py @@ -4,13 +4,14 @@ from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QPushButton from legendary.core import LegendaryCore -from rare.components.tabs.games.game_widgets.base_uninstalled_widget import BaseUninstalledWidget +from rare.components.tabs.games.game_widgets.base_uninstalled_widget import ( + BaseUninstalledWidget, +) logger = getLogger("Game") class ListWidgetUninstalled(BaseUninstalledWidget): - def __init__(self, core: LegendaryCore, game, pixmap): super(ListWidgetUninstalled, self).__init__(game, core, pixmap) self.layout = QHBoxLayout() diff --git a/rare/components/tabs/games/head_bar.py b/rare/components/tabs/games/head_bar.py index 9d50d3e9..1f879e94 100644 --- a/rare/components/tabs/games/head_bar.py +++ b/rare/components/tabs/games/head_bar.py @@ -1,5 +1,12 @@ from PyQt5.QtCore import QSize, QSettings, pyqtSignal -from PyQt5.QtWidgets import QLineEdit, QLabel, QPushButton, QWidget, QHBoxLayout, QComboBox +from PyQt5.QtWidgets import ( + QLineEdit, + QLabel, + QPushButton, + QWidget, + QHBoxLayout, + QComboBox, +) from qtawesome import icon from rare.utils.extra_widgets import SelectViewWidget @@ -17,15 +24,26 @@ class GameListHeadBar(QWidget): # self.layout.addWidget(self.installed_only) self.filter = QComboBox() - self.filter.addItems([self.tr("All"), - self.tr("Installed only"), - self.tr("Offline Games"), - self.tr("32 Bit Games"), - self.tr("Mac games"), - self.tr("Exclude Origin")]) + self.filter.addItems( + [ + self.tr("All"), + self.tr("Installed only"), + self.tr("Offline Games"), + self.tr("32 Bit Games"), + self.tr("Mac games"), + self.tr("Exclude Origin"), + ] + ) self.layout().addWidget(self.filter) - self.available_filters = ["all", "installed", "offline", "32bit", "mac", "installable"] + self.available_filters = [ + "all", + "installed", + "offline", + "32bit", + "mac", + "installable", + ] try: self.filter.setCurrentIndex(self.settings.value("filter", 0, int)) diff --git a/rare/components/tabs/games/import_sync/__init__.py b/rare/components/tabs/games/import_sync/__init__.py index f7e0d7b2..f9555d0f 100644 --- a/rare/components/tabs/games/import_sync/__init__.py +++ b/rare/components/tabs/games/import_sync/__init__.py @@ -6,22 +6,21 @@ from .import_group import ImportGroup class ImportSyncTabs(SideTabWidget): - def __init__(self, parent=None): super(ImportSyncTabs, self).__init__(show_back=True, parent=parent) self.import_widget = ImportSyncWidget( ImportGroup(self), - self.tr('Import Game'), - self.tr('To import games from Epic Games Store, please enable EGL Sync.'), - self + self.tr("Import Game"), + self.tr("To import games from Epic Games Store, please enable EGL Sync."), + self, ) self.addTab(self.import_widget, self.tr("Import Games")) self.egl_sync_widget = ImportSyncWidget( EGLSyncGroup(self), - self.tr('Sync with EGL'), - self.tr('To import EGL games from directories, please use Import Game.'), - self + self.tr("Sync with EGL"), + self.tr("To import EGL games from directories, please use Import Game."), + self, ) self.addTab(self.egl_sync_widget, self.tr("Sync with EGL")) # FIXME: Until it is ready @@ -37,7 +36,6 @@ class ImportSyncTabs(SideTabWidget): class ImportSyncWidget(QWidget): - def __init__(self, widget: QWidget, title: str, info: str, parent=None): super(ImportSyncWidget, self).__init__(parent=parent) self.layout = QVBoxLayout() @@ -48,5 +46,7 @@ class ImportSyncWidget(QWidget): self.layout.addWidget(self.group) self.info = QLabel(f"

{info}

") self.layout.addWidget(self.info) - self.layout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) + self.layout.addItem( + QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + ) self.setLayout(self.layout) diff --git a/rare/components/tabs/games/import_sync/egl_sync_group.py b/rare/components/tabs/games/import_sync/egl_sync_group.py index c27025c8..56824b25 100644 --- a/rare/components/tabs/games/import_sync/egl_sync_group.py +++ b/rare/components/tabs/games/import_sync/egl_sync_group.py @@ -8,7 +8,9 @@ from PyQt5.QtWidgets import QGroupBox, QListWidgetItem, QFileDialog, QMessageBox import rare.shared as shared from rare.ui.components.tabs.games.import_sync.egl_sync_group import Ui_EGLSyncGroup -from rare.ui.components.tabs.games.import_sync.egl_sync_list_group import Ui_EGLSyncListGroup +from rare.ui.components.tabs.games.import_sync.egl_sync_list_group import ( + Ui_EGLSyncListGroup, +) from rare.utils.extra_widgets import PathEdit from rare.utils.models import PathSpec from rare.utils.utils import WineResolver @@ -17,33 +19,36 @@ logger = getLogger("EGLSync") class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): - def __init__(self, parent=None): super(EGLSyncGroup, self).__init__(parent=parent) self.setupUi(self) - self.egl_path_info.setProperty('infoLabel', 1) + self.egl_path_info.setProperty("infoLabel", 1) self.thread_pool = QThreadPool.globalInstance() - if platform.system() == 'Windows': + if platform.system() == "Windows": self.egl_path_edit_label.setVisible(False) self.egl_path_info_label.setVisible(False) self.egl_path_info.setVisible(False) else: self.egl_path_edit = PathEdit( path=shared.core.egl.programdata_path, - ph_text=self.tr('Path to the Wine prefix where EGL is installed, or the Manifests folder'), + ph_text=self.tr( + "Path to the Wine prefix where EGL is installed, or the Manifests folder" + ), file_type=QFileDialog.DirectoryOnly, edit_func=self.egl_path_edit_edit_cb, save_func=self.egl_path_edit_save_cb, - parent=self + parent=self, ) self.egl_path_edit.textChanged.connect(self.egl_path_changed) self.egl_path_edit_layout.addWidget(self.egl_path_edit) if not shared.core.egl.programdata_path: - self.egl_path_info.setText(self.tr('Updating...')) - wine_resolver = WineResolver(PathSpec.egl_programdata, 'default', shared.core) + self.egl_path_info.setText(self.tr("Updating...")) + wine_resolver = WineResolver( + PathSpec.egl_programdata, "default", shared.core + ) wine_resolver.signals.result_ready.connect(self.wine_resolver_cb) self.thread_pool.start(wine_resolver) else: @@ -67,12 +72,18 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): self.egl_path_info.setText(path) if not path: self.egl_path_info.setText( - self.tr('Default Wine prefix is unset, or path does not exist. ' - 'Create it or configure it in Settings -> Linux.')) + self.tr( + "Default Wine prefix is unset, or path does not exist. " + "Create it or configure it in Settings -> Linux." + ) + ) elif not os.path.exists(path): self.egl_path_info.setText( - self.tr('Default Wine prefix is set but EGL manifests path does not exist. ' - 'Your configured default Wine prefix might not be where EGL is installed.')) + self.tr( + "Default Wine prefix is set but EGL manifests path does not exist. " + "Your configured default Wine prefix might not be where EGL is installed." + ) + ) else: self.egl_path_edit.setText(path) @@ -80,10 +91,18 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): def egl_path_edit_edit_cb(path) -> Tuple[bool, str]: if not path: return True, path - if os.path.exists(os.path.join(path, 'system.reg')) and os.path.exists(os.path.join(path, 'dosdevices/c:')): + if os.path.exists(os.path.join(path, "system.reg")) and os.path.exists( + os.path.join(path, "dosdevices/c:") + ): # path is a wine prefix - path = os.path.join(path, 'dosdevices/c:', 'ProgramData/Epic/EpicGamesLauncher/Data/Manifests') - elif not path.rstrip('/').endswith('ProgramData/Epic/EpicGamesLauncher/Data/Manifests'): + path = os.path.join( + path, + "dosdevices/c:", + "ProgramData/Epic/EpicGamesLauncher/Data/Manifests", + ) + elif not path.rstrip("/").endswith( + "ProgramData/Epic/EpicGamesLauncher/Data/Manifests" + ): # lower() might or might not be needed in the check return False, path if os.path.exists(path): @@ -95,11 +114,11 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): if not path or not os.path.exists(path): # This is the same as "--unlink" shared.core.egl.programdata_path = None - shared.core.lgd.config.remove_option('Legendary', 'egl_programdata') - shared.core.lgd.config.remove_option('Legendary', 'egl_sync') + shared.core.lgd.config.remove_option("Legendary", "egl_programdata") + shared.core.lgd.config.remove_option("Legendary", "egl_sync") # remove EGL GUIDs from all games, DO NOT remove .egstore folders because that would fuck things up. for igame in shared.core.get_installed_list(): - igame.egl_guid = '' + igame.egl_guid = "" shared.core.install_game(igame) else: shared.core.egl.programdata_path = path @@ -119,9 +138,9 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): if state == Qt.Unchecked: self.import_list.setEnabled(bool(self.import_list.items)) self.export_list.setEnabled(bool(self.export_list.items)) - shared.core.lgd.config.remove_option('Legendary', 'egl_sync') + shared.core.lgd.config.remove_option("Legendary", "egl_sync") else: - shared.core.lgd.config.set('Legendary', 'egl_sync', str(True)) + shared.core.lgd.config.set("Legendary", "egl_sync", str(True)) # lk: do import/export here since automatic sync was selected self.import_list.mark(Qt.Checked) self.export_list.mark(Qt.Checked) @@ -134,7 +153,9 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): def update_lists(self): # self.egl_watcher.blockSignals(True) - if have_path := bool(shared.core.egl.programdata_path) and os.path.exists(shared.core.egl.programdata_path): + if have_path := bool(shared.core.egl.programdata_path) and os.path.exists( + shared.core.egl.programdata_path + ): # NOTE: need to clear known manifests to force refresh shared.core.egl.manifests.clear() self.egl_sync_check_label.setEnabled(have_path) @@ -178,7 +199,6 @@ class EGLSyncListItem(QListWidgetItem): class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup): - def __init__(self, export: bool, parent=None): super(EGLSyncListGroup, self).__init__(parent=parent) self.setupUi(self) @@ -187,19 +207,20 @@ class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup): self.export = export if export: - self.setTitle(self.tr('Exportable games')) - self.label.setText(self.tr('No games to export to EGL')) - self.action_button.setText(self.tr('Export')) + self.setTitle(self.tr("Exportable games")) + self.label.setText(self.tr("No games to export to EGL")) + self.action_button.setText(self.tr("Export")) self.list_func = shared.core.egl_get_exportable else: - self.setTitle(self.tr('Importable games')) - self.label.setText(self.tr('No games to import from EGL')) - self.action_button.setText(self.tr('Import')) + self.setTitle(self.tr("Importable games")) + self.label.setText(self.tr("No games to import from EGL")) + self.action_button.setText(self.tr("Import")) self.list_func = shared.core.egl_get_importable self.list.itemDoubleClicked.connect( - lambda item: - item.setCheckState(Qt.Unchecked) if item.checkState() != Qt.Unchecked else item.setCheckState(Qt.Checked) + lambda item: item.setCheckState(Qt.Unchecked) + if item.checkState() != Qt.Unchecked + else item.setCheckState(Qt.Checked) ) self.list.itemChanged.connect(self.has_selected) @@ -244,9 +265,10 @@ class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup): if errors: QMessageBox.warning( self.parent(), - self.tr('The following errors occurred while {}.').format( - self.tr('exporting') if self.export else self.tr('importing')), - '\n'.join(errors) + self.tr("The following errors occurred while {}.").format( + self.tr("exporting") if self.export else self.tr("importing") + ), + "\n".join(errors), ) @property @@ -269,7 +291,7 @@ class EGLSyncWorker(QRunnable): self.export_list.action() -''' +""" from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QCheckBox, QPushButton, QDialog @@ -348,4 +370,4 @@ class EGLSyncItemWidget(QGroupBox): # FIXME: on update_egl_widget this is going to crash because # FIXME: the item is not removed from the list in the python's side self.deleteLater() -''' +""" diff --git a/rare/components/tabs/games/import_sync/import_group.py b/rare/components/tabs/games/import_sync/import_group.py index 369de5c7..344f1ab1 100644 --- a/rare/components/tabs/games/import_sync/import_group.py +++ b/rare/components/tabs/games/import_sync/import_group.py @@ -21,7 +21,9 @@ class AppNameCompleter(QCompleter): def __init__(self, app_names: List, parent=None): super(AppNameCompleter, self).__init__(parent) # pylint: disable=E1136 - super(AppNameCompleter, self).activated[QModelIndex].connect(self.__activated_idx) + super(AppNameCompleter, self).activated[QModelIndex].connect( + self.__activated_idx + ) model = QStandardItemModel(len(app_names), 2) for idx, game in enumerate(app_names): @@ -51,9 +53,7 @@ class AppNameCompleter(QCompleter): # lk: Getting the index from the popup and trying to use it in the completer will return invalid results if isinstance(idx, QModelIndex): self.activated.emit( - self.popup().model().data( - self.popup().model().index(idx.row(), 1) - ) + self.popup().model().data(self.popup().model().index(idx.row(), 1)) ) # TODO: implement conversion from app_name to app_title (signal loop here) # if isinstance(idx_str, str): @@ -67,23 +67,31 @@ class ImportGroup(QGroupBox, Ui_ImportGroup): self.core = shared.core self.app_name_list = [game.app_name for game in shared.api_results.game_list] self.install_dir_list = [ - game.metadata.get('customAttributes', {}).get('FolderName', {}).get('value', game.app_name) - for game in shared.api_results.game_list if not game.is_dlc] + game.metadata.get("customAttributes", {}) + .get("FolderName", {}) + .get("value", game.app_name) + for game in shared.api_results.game_list + if not game.is_dlc + ] self.path_edit = PathEdit( self.core.get_default_install_dir(), QFileDialog.DirectoryOnly, edit_func=self.path_edit_cb, - parent=self + parent=self, ) self.path_edit.textChanged.connect(self.path_changed) self.path_edit_layout.addWidget(self.path_edit) self.app_name = IndicatorLineEdit( ph_text=self.tr("Use in case the app name was not found automatically"), - completer=AppNameCompleter(app_names=[(i.app_name, i.app_title) for i in shared.api_results.game_list]), + completer=AppNameCompleter( + app_names=[ + (i.app_name, i.app_title) for i in shared.api_results.game_list + ] + ), edit_func=self.app_name_edit_cb, - parent=self + parent=self, ) self.app_name.textChanged.connect(self.app_name_changed) self.app_name_layout.addWidget(self.app_name) @@ -144,18 +152,27 @@ class ImportGroup(QGroupBox, Ui_ImportGroup): self.info_label.setText(self.tr("Could not find app name")) return - if not (err := legendary_utils.import_game(self.core, app_name=app_name, path=path)): + if not ( + err := legendary_utils.import_game(self.core, app_name=app_name, path=path) + ): igame = self.core.get_installed_game(app_name) - self.info_label.setText(self.tr("Successfully imported {}").format(igame.title)) + self.info_label.setText( + self.tr("Successfully imported {}").format(igame.title) + ) self.app_name.setText(str()) shared.signals.update_gamelist.emit([app_name]) - if igame.version != self.core.get_asset(app_name, igame.platform, False).build_version: + if ( + igame.version + != self.core.get_asset(app_name, igame.platform, False).build_version + ): # update available shared.signals.add_download.emit(igame.app_name) shared.signals.update_download_tab_text.emit() else: logger.warning(f'Failed to import "{app_name}"') - self.info_label.setText(self.tr("Could not import {}: ").format(app_name) + err) + self.info_label.setText( + self.tr("Could not import {}: ").format(app_name) + err + ) return diff --git a/rare/components/tabs/settings/__init__.py b/rare/components/tabs/settings/__init__.py index 7fa940dd..7d444467 100644 --- a/rare/components/tabs/settings/__init__.py +++ b/rare/components/tabs/settings/__init__.py @@ -22,6 +22,8 @@ class SettingsTab(SideTabWidget): self.about = About() self.addTab(self.about, "About") - self.about.update_available_ready.connect(lambda: self.tabBar().setTabText(about_tab, "About (!)")) + self.about.update_available_ready.connect( + lambda: self.tabBar().setTabText(about_tab, "About (!)") + ) self.setCurrentIndex(0) diff --git a/rare/components/tabs/settings/about.py b/rare/components/tabs/settings/about.py index 8355f2af..f6678f16 100644 --- a/rare/components/tabs/settings/about.py +++ b/rare/components/tabs/settings/about.py @@ -33,10 +33,14 @@ class About(QWidget, Ui_About): self.open_browser.setVisible(False) self.manager = QtRequestManager("json") - self.manager.get("https://api.github.com/repos/Dummerle/Rare/releases/latest", self.update_available_finished) + self.manager.get( + "https://api.github.com/repos/Dummerle/Rare/releases/latest", + self.update_available_finished, + ) self.open_browser.clicked.connect( - lambda: webbrowser.open("https://github.com/Dummerle/Rare/releases/latest")) + lambda: webbrowser.open("https://github.com/Dummerle/Rare/releases/latest") + ) def update_available_finished(self, data: dict): if latest_tag := data.get("tag_name"): @@ -46,7 +50,9 @@ class About(QWidget, Ui_About): if self.update_available: logger.info(f"Update available: {__version__} -> {latest_tag}") - self.update_lbl.setText(self.tr("Update available: ") + f"{__version__} -> {latest_tag}") + self.update_lbl.setText( + self.tr("Update available: ") + f"{__version__} -> {latest_tag}" + ) self.update_label.setVisible(True) self.update_lbl.setVisible(True) self.open_browser.setVisible(True) diff --git a/rare/components/tabs/settings/dxvk.py b/rare/components/tabs/settings/dxvk.py index 701fc444..13cb3007 100644 --- a/rare/components/tabs/settings/dxvk.py +++ b/rare/components/tabs/settings/dxvk.py @@ -9,7 +9,6 @@ logger = getLogger("DXVK Settings") class DxvkSettings(QGroupBox, Ui_DxvkSettings): - def __init__(self, name=None): super(DxvkSettings, self).__init__() self.setupUi(self) @@ -39,7 +38,9 @@ class DxvkSettings(QGroupBox, Ui_DxvkSettings): # Custom Options, index 3, adds DXVK_HUD=devinfo,fps and enables the customization panel def load_settings(self): - dxvk_options = self.core.lgd.config.get(f"{self.name}.env", "DXVK_HUD", fallback=None) + dxvk_options = self.core.lgd.config.get( + f"{self.name}.env", "DXVK_HUD", fallback=None + ) self.gb_dxvk_options.setDisabled(True) if dxvk_options is not None: if dxvk_options == "0": @@ -74,16 +75,23 @@ class DxvkSettings(QGroupBox, Ui_DxvkSettings): dxvk_options.append(opt) if not dxvk_options: # Check if this is the first activation - stored = self.core.lgd.config.get(f"{self.name}.env", "DXVK_HUD", fallback=None) + stored = self.core.lgd.config.get( + f"{self.name}.env", "DXVK_HUD", fallback=None + ) if stored not in [None, "0", "1"]: self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = "0" else: dxvk_options = ["devinfo", "fps"] # Check again if dxvk_options changed due to first activation if dxvk_options: - self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = ",".join(dxvk_options) + self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = ",".join( + dxvk_options + ) else: - if self.core.lgd.config.get(f"{self.name}.env", "DXVK_HUD", fallback=None) is not None: + if ( + self.core.lgd.config.get(f"{self.name}.env", "DXVK_HUD", fallback=None) + is not None + ): self.core.lgd.config.remove_option(f"{self.name}.env", "DXVK_HUD") if not self.core.lgd.config[f"{self.name}.env"]: self.core.lgd.config.remove_section(f"{self.name}.env") diff --git a/rare/components/tabs/settings/legendary.py b/rare/components/tabs/settings/legendary.py index faadb13d..d80da319 100644 --- a/rare/components/tabs/settings/legendary.py +++ b/rare/components/tabs/settings/legendary.py @@ -21,42 +21,46 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): self.core = shared.core # Default installation directory - self.install_dir = PathEdit(self.core.get_default_install_dir(), - file_type=QFileDialog.DirectoryOnly, - save_func=self.path_save) + self.install_dir = PathEdit( + self.core.get_default_install_dir(), + file_type=QFileDialog.DirectoryOnly, + save_func=self.path_save, + ) self.install_dir_layout.addWidget(self.install_dir) # Max Workers - max_workers = self.core.lgd.config['Legendary'].getint('max_workers', fallback=0) + max_workers = self.core.lgd.config["Legendary"].getint( + "max_workers", fallback=0 + ) self.max_worker_spin.setValue(max_workers) self.max_worker_spin.valueChanged.connect(self.max_worker_save) # Max memory - max_memory = self.core.lgd.config['Legendary'].getint('max_memory', fallback=0) + max_memory = self.core.lgd.config["Legendary"].getint("max_memory", fallback=0) self.max_memory_spin.setValue(max_memory) self.max_memory_spin.valueChanged.connect(self.max_memory_save) # Preferred CDN - preferred_cdn = self.core.lgd.config['Legendary'].get('preferred_cdn', fallback="") + preferred_cdn = self.core.lgd.config["Legendary"].get( + "preferred_cdn", fallback="" + ) self.preferred_cdn_line.setText(preferred_cdn) self.preferred_cdn_line.textChanged.connect(self.preferred_cdn_save) # Disable HTTPS - disable_https = self.core.lgd.config['Legendary'].getboolean('disable_https', fallback=False) + disable_https = self.core.lgd.config["Legendary"].getboolean( + "disable_https", fallback=False + ) self.disable_https_check.setChecked(disable_https) self.disable_https_check.stateChanged.connect(self.disable_https_save) # Cleanup - self.clean_button.clicked.connect( - lambda: self.cleanup(False) - ) - self.clean_keep_manifests_button.clicked.connect( - lambda: self.cleanup(True) - ) + self.clean_button.clicked.connect(lambda: self.cleanup(False)) + self.clean_keep_manifests_button.clicked.connect(lambda: self.cleanup(True)) self.locale_edit = IndicatorLineEdit( f"{self.core.language_code}-{self.core.country_code}", edit_func=self.locale_edit_cb, save_func=self.locale_save_cb, horiz_policy=QSizePolicy.Minimum, - parent=self + parent=self, ) self.locale_layout.addWidget(self.locale_edit) @@ -111,28 +115,41 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): self.core.lgd.save_config() def disable_https_save(self, checked: int): - self.core.lgd.config.set("Legendary", "disable_https", str(bool(checked)).lower()) + self.core.lgd.config.set( + "Legendary", "disable_https", str(bool(checked)).lower() + ) self.core.lgd.save_config() def cleanup(self, keep_manifests: bool): before = self.core.lgd.get_dir_size() - logger.debug('Removing app metadata...') + logger.debug("Removing app metadata...") app_names = set(g.app_name for g in self.core.get_assets(update_assets=False)) self.core.lgd.clean_metadata(app_names) if not keep_manifests: - logger.debug('Removing manifests...') - installed = [(ig.app_name, ig.version) for ig in self.core.get_installed_list()] - installed.extend((ig.app_name, ig.version) for ig in self.core.get_installed_dlc_list()) + logger.debug("Removing manifests...") + installed = [ + (ig.app_name, ig.version) for ig in self.core.get_installed_list() + ] + installed.extend( + (ig.app_name, ig.version) for ig in self.core.get_installed_dlc_list() + ) self.core.lgd.clean_manifests(installed) - logger.debug('Removing tmp data') + logger.debug("Removing tmp data") self.core.lgd.clean_tmp_data() after = self.core.lgd.get_dir_size() - logger.info(f'Cleanup complete! Removed {(before - after) / 1024 / 1024:.02f} MiB.') + logger.info( + f"Cleanup complete! Removed {(before - after) / 1024 / 1024:.02f} MiB." + ) if (before - after) > 0: - QMessageBox.information(self, "Cleanup", self.tr("Cleanup complete! Successfully removed {}").format( - get_size(before - after))) + QMessageBox.information( + self, + "Cleanup", + self.tr("Cleanup complete! Successfully removed {}").format( + get_size(before - after) + ), + ) else: QMessageBox.information(self, "Cleanup", "Nothing to clean") diff --git a/rare/components/tabs/settings/linux.py b/rare/components/tabs/settings/linux.py index 815a2de1..a50f91a2 100644 --- a/rare/components/tabs/settings/linux.py +++ b/rare/components/tabs/settings/linux.py @@ -21,7 +21,8 @@ class LinuxSettings(QWidget, Ui_LinuxSettings): self.wine_prefix = PathEdit( self.load_prefix(), file_type=QFileDialog.DirectoryOnly, - save_func=self.save_prefix) + save_func=self.save_prefix, + ) self.prefix_layout.addWidget(self.wine_prefix) # Wine executable @@ -29,7 +30,10 @@ class LinuxSettings(QWidget, Ui_LinuxSettings): self.load_setting(self.name, "wine_executable"), file_type=QFileDialog.ExistingFile, name_filter="Wine executable (wine wine64)", - save_func=lambda text: self.save_setting(text, section=self.name, setting="wine_executable")) + save_func=lambda text: self.save_setting( + text, section=self.name, setting="wine_executable" + ), + ) self.exec_layout.addWidget(self.wine_exec) # dxvk @@ -37,13 +41,15 @@ class LinuxSettings(QWidget, Ui_LinuxSettings): self.dxvk_layout.addWidget(self.dxvk) def load_prefix(self) -> str: - return self.load_setting(f'{self.name}.env', - 'WINEPREFIX', - fallback=self.load_setting(self.name, 'wine_prefix')) + return self.load_setting( + f"{self.name}.env", + "WINEPREFIX", + fallback=self.load_setting(self.name, "wine_prefix"), + ) def save_prefix(self, text: str): - self.save_setting(text, f'{self.name}.env', 'WINEPREFIX') - self.save_setting(text, self.name, 'wine_prefix') + self.save_setting(text, f"{self.name}.env", "WINEPREFIX") + self.save_setting(text, self.name, "wine_prefix") @staticmethod def load_setting(section: str, setting: str, fallback: str = str()): @@ -59,7 +65,9 @@ class LinuxSettings(QWidget, Ui_LinuxSettings): logger.debug(f"Set {setting} in {f'[{section}]'} to {text}") else: - if shared.core.lgd.config.has_section(section) and shared.core.lgd.config.has_option(section, setting): + if shared.core.lgd.config.has_section( + section + ) and shared.core.lgd.config.has_option(section, setting): shared.core.lgd.config.remove_option(section, setting) logger.debug(f"Unset {setting} from {f'[{section}]'}") if not shared.core.lgd.config[section]: diff --git a/rare/components/tabs/settings/rare.py b/rare/components/tabs/settings/rare.py index 4f18c47e..ddb42e6e 100644 --- a/rare/components/tabs/settings/rare.py +++ b/rare/components/tabs/settings/rare.py @@ -11,15 +11,17 @@ from rare import cache_dir, shared from rare.components.tabs.settings.rpc import RPCSettings from rare.ui.components.tabs.settings.rare import Ui_RareSettings from rare.utils import utils -from rare.utils.utils import get_translations, get_color_schemes, set_color_pallete, get_style_sheets, set_style_sheet +from rare.utils.utils import ( + get_translations, + get_color_schemes, + set_color_pallete, + get_style_sheets, + set_style_sheet, +) logger = getLogger("RareSettings") -languages = [ - ("en", "English"), - ("de", "Deutsch"), - ("fr", "Français") -] +languages = [("en", "English"), ("de", "Deutsch"), ("fr", "Français")] class RareSettings(QWidget, Ui_RareSettings): @@ -84,27 +86,35 @@ class RareSettings(QWidget, Ui_RareSettings): lambda: self.settings.setValue("auto_update", self.auto_update.isChecked()) ) self.confirm_start.stateChanged.connect( - lambda: self.settings.setValue("confirm_start", self.confirm_start.isChecked()) + lambda: self.settings.setValue( + "confirm_start", self.confirm_start.isChecked() + ) ) self.auto_sync_cloud.stateChanged.connect( - lambda: self.settings.setValue("auto_sync_cloud", self.auto_sync_cloud.isChecked()) + lambda: self.settings.setValue( + "auto_sync_cloud", self.auto_sync_cloud.isChecked() + ) ) self.notification.stateChanged.connect( - lambda: self.settings.setValue("notification", self.notification.isChecked()) - ) - self.save_size.stateChanged.connect( - self.save_window_size + lambda: self.settings.setValue( + "notification", self.notification.isChecked() + ) ) + self.save_size.stateChanged.connect(self.save_window_size) self.log_games.stateChanged.connect( lambda: self.settings.setValue("show_console", self.log_games.isChecked()) ) if platform.system() == "Linux": self.desktop_file = os.path.expanduser("~/Desktop/Rare.desktop") - self.start_menu_link = os.path.expanduser("~/.local/share/applications/Rare.desktop") + self.start_menu_link = os.path.expanduser( + "~/.local/share/applications/Rare.desktop" + ) elif platform.system() == "Windows": self.desktop_file = os.path.expanduser("~/Desktop/Rare.lnk") - self.start_menu_link = os.path.expandvars("%appdata%\\Microsoft\\Windows\\Start Menu\\Rare.lnk") + self.start_menu_link = os.path.expandvars( + "%appdata%\\Microsoft\\Windows\\Start Menu\\Rare.lnk" + ) else: self.desktop_link_btn.setText(self.tr("Not supported")) self.desktop_link_btn.setDisabled(True) @@ -151,7 +161,11 @@ class RareSettings(QWidget, Ui_RareSettings): self.startmenu_link_btn.setText(self.tr("Create start menu link")) except PermissionError as e: logger.error(str(e)) - QMessageBox.warning(self, "Error", "Permission error, cannot remove " + str(self.start_menu_link)) + QMessageBox.warning( + self, + "Error", + "Permission error, cannot remove " + str(self.start_menu_link), + ) def create_desktop_link(self): try: @@ -162,7 +176,11 @@ class RareSettings(QWidget, Ui_RareSettings): os.remove(self.desktop_file) self.desktop_link_btn.setText(self.tr("Create desktop link")) except PermissionError as e: - logger.warning(self, "Error", "Permission error, cannot remove " + str(self.desktop_file)) + logger.warning( + self, + "Error", + "Permission error, cannot remove " + str(self.desktop_file), + ) def on_color_select_changed(self, color): if color: diff --git a/rare/components/tabs/settings/ubisoft_activation.py b/rare/components/tabs/settings/ubisoft_activation.py index 5e81420a..203b50aa 100644 --- a/rare/components/tabs/settings/ubisoft_activation.py +++ b/rare/components/tabs/settings/ubisoft_activation.py @@ -27,20 +27,20 @@ class UbiGetInfoWorker(QRunnable): try: external_auths = shared.core.egs.get_external_auths() for ext_auth in external_auths: - if ext_auth['type'] != 'ubisoft': + if ext_auth["type"] != "ubisoft": continue - ubi_account_id = ext_auth['externalAuthId'] + ubi_account_id = ext_auth["externalAuthId"] break else: self.signals.worker_finished.emit(set(), set(), "") return uplay_keys = shared.core.egs.store_get_uplay_codes() - key_list = uplay_keys['data']['PartnerIntegration']['accountUplayCodes'] - redeemed = {k['gameId'] for k in key_list if k['redeemedOnUplay']} + key_list = uplay_keys["data"]["PartnerIntegration"]["accountUplayCodes"] + redeemed = {k["gameId"] for k in key_list if k["redeemedOnUplay"]} entitlements = shared.core.egs.get_user_entitlements() - entitlements = {i['entitlementName'] for i in entitlements} + entitlements = {i["entitlementName"] for i in entitlements} self.signals.worker_finished.emit(redeemed, entitlements, ubi_account_id) except Exception as e: logger.error(str(e)) @@ -61,7 +61,9 @@ class UbiConnectWorker(QRunnable): self.signals.linked.emit("") return try: - shared.core.egs.store_claim_uplay_code(self.ubi_account_id, self.partner_link_id) + shared.core.egs.store_claim_uplay_code( + self.ubi_account_id, self.partner_link_id + ) shared.core.egs.store_redeem_uplay_codes(self.ubi_account_id) except Exception as e: self.signals.linked.emit(str(e)) @@ -85,14 +87,18 @@ class UbiLinkWidget(QWidget): self.ok_indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) self.layout().addWidget(self.ok_indicator) - self.link_button = QPushButton(self.tr("Redeem to Ubisoft") + ": Test" if shared.args.debug else "") + self.link_button = QPushButton( + self.tr("Redeem to Ubisoft") + ": Test" if shared.args.debug else "" + ) self.layout().addWidget(self.link_button) self.link_button.clicked.connect(self.activate) def activate(self): self.link_button.setDisabled(True) # self.ok_indicator.setPixmap(icon("mdi.loading", color="grey").pixmap(20, 20)) - self.ok_indicator.setPixmap(icon("mdi.transit-connection-horizontal", color="grey").pixmap(20, 20)) + self.ok_indicator.setPixmap( + icon("mdi.transit-connection-horizontal", color="grey").pixmap(20, 20) + ) if shared.args.debug: worker = UbiConnectWorker(None, None) @@ -103,11 +109,15 @@ class UbiLinkWidget(QWidget): def worker_finished(self, error): if not error: - self.ok_indicator.setPixmap(icon("ei.ok-circle", color="green").pixmap(QSize(20, 20))) + self.ok_indicator.setPixmap( + icon("ei.ok-circle", color="green").pixmap(QSize(20, 20)) + ) self.link_button.setDisabled(True) self.link_button.setText(self.tr("Already activated")) else: - self.ok_indicator.setPixmap(icon("fa.info-circle", color="red").pixmap(QSize(20, 20))) + self.ok_indicator.setPixmap( + icon("fa.info-circle", color="red").pixmap(QSize(20, 20)) + ) self.ok_indicator.setToolTip(error) self.link_button.setText(self.tr("Try again")) self.link_button.setDisabled(False) @@ -126,11 +136,20 @@ class UbiActivationHelper(QObject): def show_ubi_games(self, redeemed: set, entitlements: set, ubi_account_id: str): if not redeemed and ubi_account_id != "error": - logger.error('No linked ubisoft account found! Link your accounts via your browser and try again.') + logger.error( + "No linked ubisoft account found! Link your accounts via your browser and try again." + ) self.widget.layout().addWidget( - QLabel(self.tr("Your account is not linked with Ubisoft. Please link your account first"))) + QLabel( + self.tr( + "Your account is not linked with Ubisoft. Please link your account first" + ) + ) + ) open_browser_button = QPushButton(self.tr("Open link page")) - open_browser_button.clicked.connect(lambda: webbrowser.open("https://www.epicgames.com/id/link/ubisoft")) + open_browser_button.clicked.connect( + lambda: webbrowser.open("https://www.epicgames.com/id/link/ubisoft") + ) self.widget.layout().addWidget(open_browser_button) return elif ubi_account_id == "error": @@ -141,17 +160,19 @@ class UbiActivationHelper(QObject): uplay_games = [] activated = 0 for game in games: - for dlc_data in game.metadata.get('dlcItemList', []): - if dlc_data['entitlementName'] not in entitlements: + for dlc_data in game.metadata.get("dlcItemList", []): + if dlc_data["entitlementName"] not in entitlements: continue try: - app_name = dlc_data['releaseInfo'][0]['appId'] + app_name = dlc_data["releaseInfo"][0]["appId"] except (IndexError, KeyError): - app_name = 'unknown' + app_name = "unknown" - dlc_game = Game(app_name=app_name, app_title=dlc_data['title'], metadata=dlc_data) - if dlc_game.partner_link_type != 'ubisoft': + dlc_game = Game( + app_name=app_name, app_title=dlc_data["title"], metadata=dlc_data + ) + if dlc_game.partner_link_type != "ubisoft": continue if dlc_game.partner_link_id in redeemed: continue @@ -167,14 +188,22 @@ class UbiActivationHelper(QObject): if not uplay_games: if activated >= 1: self.widget.layout().addWidget( - QLabel(self.tr("All your Ubisoft games have already been activated"))) + QLabel( + self.tr("All your Ubisoft games have already been activated") + ) + ) else: - self.widget.layout().addWidget(QLabel(self.tr("You don't own any Ubisoft games"))) + self.widget.layout().addWidget( + QLabel(self.tr("You don't own any Ubisoft games")) + ) if shared.args.debug: - widget = UbiLinkWidget(Game(app_name="Test", app_title="This is a test game"), ubi_account_id) + widget = UbiLinkWidget( + Game(app_name="Test", app_title="This is a test game"), + ubi_account_id, + ) self.widget.layout().addWidget(widget) return - logger.info(f'Found {len(uplay_games)} game(s) to redeem') + logger.info(f"Found {len(uplay_games)} game(s) to redeem") for game in uplay_games: widget = UbiLinkWidget(game, ubi_account_id) diff --git a/rare/components/tabs/shop/__init__.py b/rare/components/tabs/shop/__init__.py index 4df21471..e803c0ea 100644 --- a/rare/components/tabs/shop/__init__.py +++ b/rare/components/tabs/shop/__init__.py @@ -15,8 +15,11 @@ class Shop(QStackedWidget): def __init__(self, core: LegendaryCore): super(Shop, self).__init__() self.core = core - self.api_core = ShopApiCore(self.core.egs.session.headers["Authorization"], self.core.language_code, - self.core.country_code) + self.api_core = ShopApiCore( + self.core.egs.session.headers["Authorization"], + self.core.language_code, + self.core.country_code, + ) self.shop = ShopWidget(cache_dir, core, self.api_core) self.wishlist_widget = Wishlist(self.api_core) @@ -30,7 +33,10 @@ class Shop(QStackedWidget): self.search_results = SearchResults(self.api_core) self.addWidget(self.search_results) self.search_results.show_info.connect(self.show_game_info) - self.info = ShopGameInfo([i.asset_infos["Windows"].namespace for i in shared.api_results.game_list], self.api_core) + self.info = ShopGameInfo( + [i.asset_infos["Windows"].namespace for i in shared.api_results.game_list], + self.api_core, + ) self.addWidget(self.info) self.info.back_button.clicked.connect(lambda: self.setCurrentIndex(0)) diff --git a/rare/components/tabs/shop/constants.py b/rare/components/tabs/shop/constants.py index b9f108d9..246729ef 100644 --- a/rare/components/tabs/shop/constants.py +++ b/rare/components/tabs/shop/constants.py @@ -5,21 +5,24 @@ from PyQt5.QtCore import QObject class Constants(QObject): def __init__(self): super(Constants, self).__init__() - self.categories = sorted([ - (self.tr("Action"), "1216"), - (self.tr("Adventure"), "1117"), - (self.tr("Puzzle"), "1298"), - (self.tr("Open world"), "1307"), - (self.tr("Racing"), "1212"), - (self.tr("RPG"), "1367"), - (self.tr("Shooter"), "1210"), - (self.tr("Strategy"), "1115"), - (self.tr("Survival"), "1080"), - (self.tr("First Person"), "1294"), - (self.tr("Indie"), "1263"), - (self.tr("Simulation"), "1393"), - (self.tr("Sport"), "1283") - ], key=lambda x: x[0]) + self.categories = sorted( + [ + (self.tr("Action"), "1216"), + (self.tr("Adventure"), "1117"), + (self.tr("Puzzle"), "1298"), + (self.tr("Open world"), "1307"), + (self.tr("Racing"), "1212"), + (self.tr("RPG"), "1367"), + (self.tr("Shooter"), "1210"), + (self.tr("Strategy"), "1115"), + (self.tr("Survival"), "1080"), + (self.tr("First Person"), "1294"), + (self.tr("Indie"), "1263"), + (self.tr("Simulation"), "1393"), + (self.tr("Sport"), "1283"), + ], + key=lambda x: x[0], + ) self.platforms = [ ("MacOS", "9548"), @@ -37,74 +40,78 @@ class Constants(QObject): (self.tr("Game"), "games/edition/base"), (self.tr("Bundle"), "bundles/games"), (self.tr("Add-on"), "addons"), - (self.tr("Apps"), "software/edition/base") + (self.tr("Apps"), "software/edition/base"), ] -game_query = "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " \ - "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \ - "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean " \ - "= false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " \ - "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " \ - "category: $category\n count: $count\n country: $country\n keywords: $keywords\n " \ - "locale: $locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n " \ - "sortDir: $sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n " \ - "priceRange: $priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: " \ - "$effectiveDate\n ) {\n elements {\n title\n id\n namespace\n " \ - "description\n effectiveDate\n keyImages {\n type\n url\n }\n " \ - " currentPrice\n seller {\n id\n name\n }\n productSlug\n " \ - " urlSlug\n url\n tags {\n id\n }\n items {\n id\n " \ - " namespace\n }\n customAttributes {\n key\n value\n }\n " \ - "categories {\n path\n }\n catalogNs @include(if: $withMapping) {\n " \ - "mappings(pageType: \"productHome\") {\n pageSlug\n pageType\n }\n " \ - "}\n offerMappings @include(if: $withMapping) {\n pageSlug\n pageType\n " \ - "}\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n " \ - "discountPrice\n originalPrice\n voucherDiscount\n discount\n " \ - " currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice(" \ - "locale: $locale) {\n originalPrice\n discountPrice\n " \ - "intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n " \ - " id\n endDate\n discountSetting {\n discountType\n " \ - " }\n }\n }\n }\n promotions(category: $category) @include(if: " \ - "$withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " \ - "startDate\n endDate\n discountSetting {\n discountType\n " \ - " discountPercentage\n }\n }\n }\n " \ - "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " \ - "endDate\n discountSetting {\n discountType\n " \ - "discountPercentage\n }\n }\n }\n }\n }\n paging {\n " \ - " count\n total\n }\n }\n }\n}\n " +game_query = ( + "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " + "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " + "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean " + "= false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " + "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " + "category: $category\n count: $count\n country: $country\n keywords: $keywords\n " + "locale: $locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n " + "sortDir: $sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n " + "priceRange: $priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: " + "$effectiveDate\n ) {\n elements {\n title\n id\n namespace\n " + "description\n effectiveDate\n keyImages {\n type\n url\n }\n " + " currentPrice\n seller {\n id\n name\n }\n productSlug\n " + " urlSlug\n url\n tags {\n id\n }\n items {\n id\n " + " namespace\n }\n customAttributes {\n key\n value\n }\n " + "categories {\n path\n }\n catalogNs @include(if: $withMapping) {\n " + 'mappings(pageType: "productHome") {\n pageSlug\n pageType\n }\n ' + "}\n offerMappings @include(if: $withMapping) {\n pageSlug\n pageType\n " + "}\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n " + "discountPrice\n originalPrice\n voucherDiscount\n discount\n " + " currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice(" + "locale: $locale) {\n originalPrice\n discountPrice\n " + "intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n " + " id\n endDate\n discountSetting {\n discountType\n " + " }\n }\n }\n }\n promotions(category: $category) @include(if: " + "$withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " + "startDate\n endDate\n discountSetting {\n discountType\n " + " discountPercentage\n }\n }\n }\n " + "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " + "endDate\n discountSetting {\n discountType\n " + "discountPercentage\n }\n }\n }\n }\n }\n paging {\n " + " count\n total\n }\n }\n }\n}\n " +) -search_query = "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " \ - "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \ - "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = " \ - "false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " \ - "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " \ - "category: $category\n count: $count\n country: $country\n keywords: $keywords\n locale: " \ - "$locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n sortDir: " \ - "$sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n priceRange: " \ - "$priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: $effectiveDate\n ) {" \ - "\n elements {\n title\n id\n namespace\n description\n " \ - "effectiveDate\n keyImages {\n type\n url\n }\n currentPrice\n " \ - "seller {\n id\n name\n }\n productSlug\n urlSlug\n url\n " \ - " tags {\n id\n }\n items {\n id\n namespace\n }\n " \ - "customAttributes {\n key\n value\n }\n categories {\n path\n " \ - "}\n catalogNs @include(if: $withMapping) {\n mappings(pageType: \"productHome\") {\n " \ - " pageSlug\n pageType\n }\n }\n offerMappings @include(if: $withMapping) " \ - "{\n pageSlug\n pageType\n }\n price(country: $country) @include(if: " \ - "$withPrice) {\n totalPrice {\n discountPrice\n originalPrice\n " \ - "voucherDiscount\n discount\n currencyCode\n currencyInfo {\n " \ - "decimals\n }\n fmtPrice(locale: $locale) {\n originalPrice\n " \ - "discountPrice\n intermediatePrice\n }\n }\n lineOffers {\n " \ - " appliedRules {\n id\n endDate\n discountSetting {\n " \ - "discountType\n }\n }\n }\n }\n promotions(category: " \ - "$category) @include(if: $withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " \ - " startDate\n endDate\n discountSetting {\n " \ - "discountType\n discountPercentage\n }\n }\n }\n " \ - "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " \ - "endDate\n discountSetting {\n discountType\n discountPercentage\n " \ - " }\n }\n }\n }\n }\n paging {\n count\n " \ - "total\n }\n }\n }\n}\n " +search_query = ( + "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " + "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " + "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = " + "false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " + "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " + "category: $category\n count: $count\n country: $country\n keywords: $keywords\n locale: " + "$locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n sortDir: " + "$sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n priceRange: " + "$priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: $effectiveDate\n ) {" + "\n elements {\n title\n id\n namespace\n description\n " + "effectiveDate\n keyImages {\n type\n url\n }\n currentPrice\n " + "seller {\n id\n name\n }\n productSlug\n urlSlug\n url\n " + " tags {\n id\n }\n items {\n id\n namespace\n }\n " + "customAttributes {\n key\n value\n }\n categories {\n path\n " + '}\n catalogNs @include(if: $withMapping) {\n mappings(pageType: "productHome") {\n ' + " pageSlug\n pageType\n }\n }\n offerMappings @include(if: $withMapping) " + "{\n pageSlug\n pageType\n }\n price(country: $country) @include(if: " + "$withPrice) {\n totalPrice {\n discountPrice\n originalPrice\n " + "voucherDiscount\n discount\n currencyCode\n currencyInfo {\n " + "decimals\n }\n fmtPrice(locale: $locale) {\n originalPrice\n " + "discountPrice\n intermediatePrice\n }\n }\n lineOffers {\n " + " appliedRules {\n id\n endDate\n discountSetting {\n " + "discountType\n }\n }\n }\n }\n promotions(category: " + "$category) @include(if: $withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " + " startDate\n endDate\n discountSetting {\n " + "discountType\n discountPercentage\n }\n }\n }\n " + "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " + "endDate\n discountSetting {\n discountType\n discountPercentage\n " + " }\n }\n }\n }\n }\n paging {\n count\n " + "total\n }\n }\n }\n}\n " +) -wishlist_query = "\n query wishlistQuery($country:String!, $locale:String) {\n Wishlist {\n wishlistItems {\n elements {\n id\n order\n created\n offerId\n updated\n namespace\n \n offer {\n productSlug\n urlSlug\n title\n id\n namespace\n offerType\n expiryDate\n status\n isCodeRedemptionOnly\n description\n effectiveDate\n keyImages {\n type\n url\n }\n seller {\n id\n name\n }\n productSlug\n urlSlug\n items {\n id\n namespace\n }\n customAttributes {\n key\n value\n }\n catalogNs {\n mappings(pageType: \"productHome\") {\n pageSlug\n pageType\n }\n }\n offerMappings {\n pageSlug\n pageType\n }\n categories {\n path\n }\n price(country: $country) {\n totalPrice {\n discountPrice\n originalPrice\n voucherDiscount\n discount\n fmtPrice(locale: $locale) {\n originalPrice\n discountPrice\n intermediatePrice\n }\n currencyCode\n currencyInfo {\n decimals\n symbol\n }\n }\n lineOffers {\n appliedRules {\n id\n endDate\n }\n }\n }\n }\n\n }\n }\n }\n }\n" +wishlist_query = '\n query wishlistQuery($country:String!, $locale:String) {\n Wishlist {\n wishlistItems {\n elements {\n id\n order\n created\n offerId\n updated\n namespace\n \n offer {\n productSlug\n urlSlug\n title\n id\n namespace\n offerType\n expiryDate\n status\n isCodeRedemptionOnly\n description\n effectiveDate\n keyImages {\n type\n url\n }\n seller {\n id\n name\n }\n productSlug\n urlSlug\n items {\n id\n namespace\n }\n customAttributes {\n key\n value\n }\n catalogNs {\n mappings(pageType: "productHome") {\n pageSlug\n pageType\n }\n }\n offerMappings {\n pageSlug\n pageType\n }\n categories {\n path\n }\n price(country: $country) {\n totalPrice {\n discountPrice\n originalPrice\n voucherDiscount\n discount\n fmtPrice(locale: $locale) {\n originalPrice\n discountPrice\n intermediatePrice\n }\n currencyCode\n currencyInfo {\n decimals\n symbol\n }\n }\n lineOffers {\n appliedRules {\n id\n endDate\n }\n }\n }\n }\n\n }\n }\n }\n }\n' add_to_wishlist_query = "\n mutation removeFromWishlistMutation($namespace: String!, $offerId: String!, $operation: RemoveOperation!) {\n Wishlist {\n removeFromWishlist(namespace: $namespace, offerId: $offerId, operation: $operation) {\n success\n }\n }\n }\n" remove_from_wishlist_query = "\n mutation removeFromWishlistMutation($namespace: String!, $offerId: String!, $operation: RemoveOperation!) {\n Wishlist {\n removeFromWishlist(namespace: $namespace, offerId: $offerId, operation: $operation) {\n success\n }\n }\n }\n" coupon_query = "\n query getCoupons($currencyCountry: String!, $identityId: String!, $locale: String) {\n CodeRedemption {\n coupons(currencyCountry: $currencyCountry, identityId: $identityId, includeSalesEventInfo: true) {\n code\n codeStatus\n codeType\n consumptionMetadata {\n amountDisplay {\n amount\n currency\n placement\n symbol\n }\n minSalesPriceDisplay {\n amount\n currency\n placement\n symbol\n }\n }\n endDate\n namespace\n salesEvent(locale: $locale) {\n eventName\n eventSlug\n voucherImages {\n type\n url\n }\n voucherLink\n }\n startDate\n }\n }\n }\n" diff --git a/rare/components/tabs/shop/game_info.py b/rare/components/tabs/shop/game_info.py index 9ed2e3c7..c3201f2f 100644 --- a/rare/components/tabs/shop/game_info.py +++ b/rare/components/tabs/shop/game_info.py @@ -3,7 +3,16 @@ import webbrowser from PyQt5.QtCore import Qt from PyQt5.QtGui import QPixmap, QFont -from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QHBoxLayout, QSpacerItem, QGroupBox, QTabWidget, QGridLayout +from PyQt5.QtWidgets import ( + QWidget, + QLabel, + QPushButton, + QHBoxLayout, + QSpacerItem, + QGroupBox, + QTabWidget, + QGridLayout, +) from qtawesome import icon from rare import shared @@ -29,7 +38,9 @@ class ShopGameInfo(QWidget, Ui_shop_info): self.image_stack.addWidget(self.image) self.image_stack.addWidget(WaitingSpinner()) warn_label = QLabel() - warn_label.setPixmap(icon("fa.warning").pixmap(160, 160).scaled(240, 320, Qt.IgnoreAspectRatio)) + warn_label.setPixmap( + icon("fa.warning").pixmap(160, 160).scaled(240, 320, Qt.IgnoreAspectRatio) + ) self.image_stack.addWidget(warn_label) self.wishlist_button.clicked.connect(self.add_to_wishlist) @@ -101,9 +112,13 @@ class ShopGameInfo(QWidget, Ui_shop_info): # lambda success: self.wishlist_button.setText(self.tr("Remove from wishlist")) # if success else self.wishlist_button.setText("Something goes wrong")) else: - self.api_core.remove_from_wishlist(self.game.namespace, self.game.offer_id, - lambda success: self.wishlist_button.setVisible(False) - if success else self.wishlist_button.setText("Something goes wrong")) + self.api_core.remove_from_wishlist( + self.game.namespace, + self.game.offer_id, + lambda success: self.wishlist_button.setVisible(False) + if success + else self.wishlist_button.setText("Something goes wrong"), + ) def data_received(self, game): try: @@ -113,7 +128,12 @@ class ShopGameInfo(QWidget, Ui_shop_info): self.price.setText("Error") self.req_group_box.setVisible(False) for img in self.data.get("keyImages"): - if img["type"] in ["DieselStoreFrontWide", "OfferImageTall", "VaultClosed", "ProductLogo"]: + if img["type"] in [ + "DieselStoreFrontWide", + "OfferImageTall", + "VaultClosed", + "ProductLogo", + ]: self.image.update_image(img["url"], size=(240, 320)) self.image_stack.setCurrentIndex(0) break @@ -137,7 +157,10 @@ class ShopGameInfo(QWidget, Ui_shop_info): font.setStrikeOut(True) self.price.setFont(font) self.discount_price.setText( - self.game.discount_price if self.game.discount_price != "0" else self.tr("Free")) + self.game.discount_price + if self.game.discount_price != "0" + else self.tr("Free") + ) self.discount_price.setVisible(True) else: self.discount_price.setVisible(False) @@ -156,7 +179,9 @@ class ShopGameInfo(QWidget, Ui_shop_info): req_widget.setLayout(QGridLayout()) req_widget.layout().addWidget(min_label, 0, 1) req_widget.layout().addWidget(rec_label, 0, 2) - for i, (key, value) in enumerate(self.game.reqs.get(system, {}).items()): + for i, (key, value) in enumerate( + self.game.reqs.get(system, {}).items() + ): req_widget.layout().addWidget(QLabel(key), i + 1, 0) min_label = QLabel(value[0]) min_label.setWordWrap(True) @@ -167,7 +192,9 @@ class ShopGameInfo(QWidget, Ui_shop_info): req_tabs.addTab(req_widget, system) self.req_group_box.layout().addWidget(req_tabs) else: - self.req_group_box.layout().addWidget(QLabel(self.tr("Could not get requirements"))) + self.req_group_box.layout().addWidget( + QLabel(self.tr("Could not get requirements")) + ) self.req_group_box.setVisible(True) if self.game.image_urls.front_tall: img_url = self.game.image_urls.front_tall @@ -190,7 +217,10 @@ class ShopGameInfo(QWidget, Ui_shop_info): self.tags.setText(", ".join(self.game.tags)) # clear Layout - for widget in (self.social_link_gb.layout().itemAt(i) for i in range(self.social_link_gb.layout().count())): + for widget in ( + self.social_link_gb.layout().itemAt(i) + for i in range(self.social_link_gb.layout().count()) + ): if not isinstance(widget, QSpacerItem): widget.widget().deleteLater() self.social_link_gb.deleteLater() @@ -229,7 +259,10 @@ class ShopGameInfo(QWidget, Ui_shop_info): self.wishlist.append(game["offer"]["title"]) def button_clicked(self): - webbrowser.open(f"https://www.epicgames.com/store/{shared.core.language_code}/p/" + self.slug) + webbrowser.open( + f"https://www.epicgames.com/store/{shared.core.language_code}/p/" + + self.slug + ) class SocialButton(QPushButton): diff --git a/rare/components/tabs/shop/game_widgets.py b/rare/components/tabs/shop/game_widgets.py index 8c26f896..eaa719be 100644 --- a/rare/components/tabs/shop/game_widgets.py +++ b/rare/components/tabs/shop/game_widgets.py @@ -42,14 +42,16 @@ class GameWidget(QWidget): mini_layout.addWidget(self.title_label) mini_layout.addStretch(1) - price = json_info['price']['totalPrice']['fmtPrice']['originalPrice'] - discount_price = json_info['price']['totalPrice']['fmtPrice']['discountPrice'] + price = json_info["price"]["totalPrice"]["fmtPrice"]["originalPrice"] + discount_price = json_info["price"]["totalPrice"]["fmtPrice"]["discountPrice"] price_label = QLabel(price) if price != discount_price: font = QFont() font.setStrikeOut(True) price_label.setFont(font) - mini_layout.addWidget(QLabel(discount_price if discount_price != "0" else self.tr("Free"))) + mini_layout.addWidget( + QLabel(discount_price if discount_price != "0" else self.tr("Free")) + ) mini_layout.addWidget(price_label) else: if price == "0": @@ -64,10 +66,19 @@ class GameWidget(QWidget): self.title = json_info["title"] for img in json_info["keyImages"]: - if img["type"] in ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo"]: + if img["type"] in [ + "DieselStoreFrontWide", + "OfferImageWide", + "VaultClosed", + "ProductLogo", + ]: if img["type"] == "VaultClosed" and self.title != "Mystery Game": continue - self.image.update_image(img["url"], json_info["title"], (self.width, int(self.width * 9 / 16))) + self.image.update_image( + img["url"], + json_info["title"], + (self.width, int(self.width * 9 / 16)), + ) break else: logger.info(", ".join([img["type"] for img in json_info["keyImages"]])) @@ -95,8 +106,8 @@ class WishlistWidget(QWidget, Ui_WishlistWidget): break else: self.developer.setText(game["seller"]["name"]) - original_price = game['price']['totalPrice']['fmtPrice']['originalPrice'] - discount_price = game['price']['totalPrice']['fmtPrice']['discountPrice'] + original_price = game["price"]["totalPrice"]["fmtPrice"]["originalPrice"] + discount_price = game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] self.price.setText(original_price if original_price != "0" else self.tr("Free")) # if discount @@ -118,7 +129,9 @@ class WishlistWidget(QWidget, Ui_WishlistWidget): url = image_model.offer_image_wide self.image.update_image(url, game.get("title"), (240, 135)) self.delete_button.setIcon(icon("mdi.delete", color="white")) - self.delete_button.clicked.connect(lambda: self.delete_from_wishlist.emit(self.game)) + self.delete_button.clicked.connect( + lambda: self.delete_from_wishlist.emit(self.game) + ) def mousePressEvent(self, e: QtGui.QMouseEvent) -> None: # left button diff --git a/rare/components/tabs/shop/search_results.py b/rare/components/tabs/shop/search_results.py index f97180c9..9ea66114 100644 --- a/rare/components/tabs/shop/search_results.py +++ b/rare/components/tabs/shop/search_results.py @@ -1,8 +1,16 @@ from PyQt5 import QtGui from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtGui import QFont -from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QScrollArea, QGroupBox, QPushButton, \ - QStackedWidget +from PyQt5.QtWidgets import ( + QWidget, + QVBoxLayout, + QHBoxLayout, + QLabel, + QScrollArea, + QGroupBox, + QPushButton, + QStackedWidget, +) from rare.utils.extra_widgets import ImageLabel, FlowLayout, WaitingSpinner @@ -79,8 +87,8 @@ class _SearchResultItem(QGroupBox): self.title.setFont(title_font) self.title.setWordWrap(True) self.layout.addWidget(self.title) - price = result['price']['totalPrice']['fmtPrice']['originalPrice'] - discount_price = result['price']['totalPrice']['fmtPrice']['discountPrice'] + price = result["price"]["totalPrice"]["fmtPrice"]["originalPrice"] + discount_price = result["price"]["totalPrice"]["fmtPrice"]["discountPrice"] price_layout = QHBoxLayout() price_label = QLabel(price if price != "0" else self.tr("Free")) price_layout.addWidget(price_label) diff --git a/rare/components/tabs/shop/shop_api_core.py b/rare/components/tabs/shop/shop_api_core.py index b3453ff0..1d4e39b4 100644 --- a/rare/components/tabs/shop/shop_api_core.py +++ b/rare/components/tabs/shop/shop_api_core.py @@ -3,8 +3,12 @@ from logging import getLogger from PyQt5.QtCore import pyqtSignal, QObject -from rare.components.tabs.shop.constants import wishlist_query, search_query, add_to_wishlist_query, \ - remove_from_wishlist_query +from rare.components.tabs.shop.constants import ( + wishlist_query, + search_query, + add_to_wishlist_query, + remove_from_wishlist_query, +) from rare.components.tabs.shop.shop_models import BrowseModel from rare.utils.qt_requests import QtRequestManager @@ -46,13 +50,17 @@ class ShopApiCore(QObject): handle_func(results) def get_wishlist(self, handle_func): - self.auth_manager.post(graphql_url, { - "query": wishlist_query, - "variables": { - "country": self.country_code, - "locale": self.language_code + "-" + self.country_code - } - }, lambda data: self._handle_wishlist(data, handle_func)) + self.auth_manager.post( + graphql_url, + { + "query": wishlist_query, + "variables": { + "country": self.country_code, + "locale": self.language_code + "-" + self.country_code, + }, + }, + lambda data: self._handle_wishlist(data, handle_func), + ) def _handle_wishlist(self, data, handle_func): try: @@ -71,13 +79,24 @@ class ShopApiCore(QObject): def search_game(self, name, handle_func): payload = { "query": search_query, - "variables": {"category": "games/edition/base|bundles/games|editors|software/edition/base", "count": 10, - "country": self.country_code, "keywords": name, "locale": self.locale, "sortDir": "DESC", - "allowCountries": self.country_code, - "start": 0, "tag": "", "withMapping": False, "withPrice": True} + "variables": { + "category": "games/edition/base|bundles/games|editors|software/edition/base", + "count": 10, + "country": self.country_code, + "keywords": name, + "locale": self.locale, + "sortDir": "DESC", + "allowCountries": self.country_code, + "start": 0, + "tag": "", + "withMapping": False, + "withPrice": True, + }, } - self.manager.post(graphql_url, payload, lambda data: self._handle_search(data, handle_func)) + self.manager.post( + graphql_url, payload, lambda data: self._handle_search(data, handle_func) + ) def _handle_search(self, data, handle_func): try: @@ -98,13 +117,26 @@ class ShopApiCore(QObject): url = "https://www.epicgames.com/graphql?operationName=searchStoreQuery&variables=" args = urllib.parse.quote_plus(str(browse_model.__dict__)) - for old, new in [("%27", "%22"), ("+", ""), ("%3A", ":"), ("%2C", ","), ("%5B", "["), ("%5D", "]"), - ("True", "true")]: + for old, new in [ + ("%27", "%22"), + ("+", ""), + ("%3A", ":"), + ("%2C", ","), + ("%5B", "["), + ("%5D", "]"), + ("True", "true"), + ]: args = args.replace(old, new) - url = url + args + "&extensions=%7B%22persistedQuery%22:%7B%22version%22:1,%22sha256Hash%22:%220304d711e653a2914f3213a6d9163cc17153c60aef0ef52279731b02779231d2%22%7D%7D" + url = ( + url + + args + + "&extensions=%7B%22persistedQuery%22:%7B%22version%22:1,%22sha256Hash%22:%220304d711e653a2914f3213a6d9163cc17153c60aef0ef52279731b02779231d2%22%7D%7D" + ) - self.auth_manager.get(url, lambda data: self._handle_browse_games(data, handle_func)) + self.auth_manager.get( + url, lambda data: self._handle_browse_games(data, handle_func) + ) def _handle_browse_games(self, data, handle_func): self.browse_active = False @@ -143,11 +175,15 @@ class ShopApiCore(QObject): "offerId": offer_id, "namespace": namespace, "country": self.country_code, - "locale": self.locale + "locale": self.locale, }, - "query": add_to_wishlist_query + "query": add_to_wishlist_query, } - self.auth_manager.post(graphql_url, payload, lambda data: self._handle_add_to_wishlist(data, handle_func)) + self.auth_manager.post( + graphql_url, + payload, + lambda data: self._handle_add_to_wishlist(data, handle_func), + ) def _handle_add_to_wishlist(self, data, handle_func): try: @@ -166,11 +202,15 @@ class ShopApiCore(QObject): "variables": { "offerId": offer_id, "namespace": namespace, - "operation": "REMOVE" + "operation": "REMOVE", }, - "query": remove_from_wishlist_query + "query": remove_from_wishlist_query, } - self.auth_manager.post(graphql_url, payload, lambda data: self._handle_remove_from_wishlist(data, handle_func)) + self.auth_manager.post( + graphql_url, + payload, + lambda data: self._handle_remove_from_wishlist(data, handle_func), + ) def _handle_remove_from_wishlist(self, data, handle_func): try: diff --git a/rare/components/tabs/shop/shop_models.py b/rare/components/tabs/shop/shop_models.py index c78e28e3..ef65cc95 100644 --- a/rare/components/tabs/shop/shop_models.py +++ b/rare/components/tabs/shop/shop_models.py @@ -3,9 +3,15 @@ from dataclasses import dataclass class ImageUrlModel: - def __init__(self, front_tall: str = "", offer_image_tall: str = "", - thumbnail: str = "", front_wide: str = "", offer_image_wide: str = "", - product_logo: str = ""): + def __init__( + self, + front_tall: str = "", + offer_image_tall: str = "", + thumbnail: str = "", + front_wide: str = "", + offer_image_wide: str = "", + product_logo: str = "", + ): self.front_tall = front_tall self.offer_image_tall = offer_image_tall self.thumbnail = thumbnail @@ -34,17 +40,30 @@ class ImageUrlModel: class ShopGame: # TODO: Copyrights etc - def __init__(self, title: str = "", image_urls: ImageUrlModel = None, social_links: dict = None, - langs: list = None, reqs: dict = None, publisher: str = "", developer: str = "", - original_price: str = "", discount_price: str = "", tags: list = None, namespace: str = "", - offer_id: str = ""): + def __init__( + self, + title: str = "", + image_urls: ImageUrlModel = None, + social_links: dict = None, + langs: list = None, + reqs: dict = None, + publisher: str = "", + developer: str = "", + original_price: str = "", + discount_price: str = "", + tags: list = None, + namespace: str = "", + offer_id: str = "", + ): self.title = title self.image_urls = image_urls self.links = [] if social_links: for item in social_links: if item.startswith("link"): - self.links.append(tuple((item.replace("link", ""), social_links[item]))) + self.links.append( + tuple((item.replace("link", ""), social_links[item])) + ) else: self.links = [] self.languages = langs @@ -77,7 +96,9 @@ class ShopGame: for item in links: if item.startswith("link"): tmp.links.append(tuple((item.replace("link", ""), links[item]))) - tmp.available_voice_langs = api_data["data"]["requirements"].get("languages", "Failed") + tmp.available_voice_langs = api_data["data"]["requirements"].get( + "languages", "Failed" + ) tmp.reqs = {} for i, system in enumerate(api_data["data"]["requirements"].get("systems", [])): try: @@ -86,7 +107,10 @@ class ShopGame: continue for req in system["details"]: try: - tmp.reqs[system["systemType"]][req["title"]] = (req["minimum"], req["recommended"]) + tmp.reqs[system["systemType"]][req["title"]] = ( + req["minimum"], + req["recommended"], + ) except KeyError: pass tmp.publisher = api_data["data"]["meta"].get("publisher", "") @@ -95,9 +119,14 @@ class ShopGame: for i in search_data["customAttributes"]: if i["key"] == "developerName": tmp.developer = i["value"] - tmp.price = search_data['price']['totalPrice']['fmtPrice']['originalPrice'] - tmp.discount_price = search_data['price']['totalPrice']['fmtPrice']['discountPrice'] - tmp.tags = [i.replace("_", " ").capitalize() for i in api_data["data"]["meta"].get("tags", [])] + tmp.price = search_data["price"]["totalPrice"]["fmtPrice"]["originalPrice"] + tmp.discount_price = search_data["price"]["totalPrice"]["fmtPrice"][ + "discountPrice" + ] + tmp.tags = [ + i.replace("_", " ").capitalize() + for i in api_data["data"]["meta"].get("tags", []) + ] tmp.namespace = search_data["namespace"] tmp.offer_id = search_data["id"] @@ -116,7 +145,9 @@ class BrowseModel: tag: str = "" withMapping: bool = True withPrice: bool = True - date: str = f"[,{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%X')}.999Z]" + date: str = ( + f"[,{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%X')}.999Z]" + ) price: str = "" onSale: bool = False diff --git a/rare/components/tabs/shop/shop_widget.py b/rare/components/tabs/shop/shop_widget.py index cefefd2f..39b0986d 100644 --- a/rare/components/tabs/shop/shop_widget.py +++ b/rare/components/tabs/shop/shop_widget.py @@ -3,7 +3,14 @@ import logging import random from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QGroupBox, QScrollArea, QCheckBox, QLabel, QPushButton, QHBoxLayout +from PyQt5.QtWidgets import ( + QGroupBox, + QScrollArea, + QCheckBox, + QLabel, + QPushButton, + QHBoxLayout, +) from legendary.core import LegendaryCore from rare.ui.components.tabs.store.store import Ui_ShopWidget @@ -49,7 +56,9 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.game_stack.addWidget(WaitingSpinner()) self.game_stack.setCurrentIndex(1) - self.search_bar = ButtonLineEdit("fa.search", placeholder_text=self.tr("Search Games")) + self.search_bar = ButtonLineEdit( + "fa.search", placeholder_text=self.tr("Search Games") + ) self.layout().insertWidget(0, self.search_bar) # self.search_bar.textChanged.connect(self.search_games) @@ -78,10 +87,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): if item: item.widget().deleteLater() if wishlist and wishlist[0] == "error": - self.discount_widget.layout().addWidget(QLabel(self.tr("Failed to get wishlist: ") + wishlist[1])) + self.discount_widget.layout().addWidget( + QLabel(self.tr("Failed to get wishlist: ") + wishlist[1]) + ) btn = QPushButton(self.tr("Reload")) self.discount_widget.layout().addWidget(btn) - btn.clicked.connect(lambda: self.api_core.get_wishlist(self.add_wishlist_items)) + btn.clicked.connect( + lambda: self.api_core.get_wishlist(self.add_wishlist_items) + ) self.discount_stack.setCurrentIndex(0) return @@ -109,10 +122,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): item.widget().deleteLater() if free_games and free_games[0] == "error": - self.free_widget.layout().addWidget(QLabel(self.tr("Failed to fetch free games: ") + free_games[1])) + self.free_widget.layout().addWidget( + QLabel(self.tr("Failed to fetch free games: ") + free_games[1]) + ) btn = QPushButton(self.tr("Reload")) self.free_widget.layout().addWidget(btn) - btn.clicked.connect(lambda: self.api_core.get_free_games(self.add_free_games)) + btn.clicked.connect( + lambda: self.api_core.get_free_games(self.add_free_games) + ) self.free_stack.setCurrentIndex(0) return @@ -129,9 +146,11 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): coming_free_games = [] for game in free_games: try: - if game['price']['totalPrice']['fmtPrice']['discountPrice'] == "0" and \ - game['price']['totalPrice']['fmtPrice']['originalPrice'] != \ - game['price']['totalPrice']['fmtPrice']['discountPrice']: + if ( + game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] == "0" + and game["price"]["totalPrice"]["fmtPrice"]["originalPrice"] + != game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] + ): free_games_now.append(game) continue @@ -145,13 +164,19 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): # parse datetime to check if game is next week or now try: start_date = datetime.datetime.strptime( - game["promotions"]["upcomingPromotionalOffers"][0]["promotionalOffers"][0]["startDate"], - '%Y-%m-%dT%H:%M:%S.%fZ') + game["promotions"]["upcomingPromotionalOffers"][0][ + "promotionalOffers" + ][0]["startDate"], + "%Y-%m-%dT%H:%M:%S.%fZ", + ) except Exception: try: start_date = datetime.datetime.strptime( - game["promotions"]["promotionalOffers"][0]["promotionalOffers"][0]["startDate"], - '%Y-%m-%dT%H:%M:%S.%fZ') + game["promotions"]["promotionalOffers"][0][ + "promotionalOffers" + ][0]["startDate"], + "%Y-%m-%dT%H:%M:%S.%fZ", + ) except Exception as e: continue @@ -171,7 +196,9 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.free_game_widgets.append(w) now_free += 1 if now_free == 0: - self.free_games_now.layout().addWidget(QLabel(self.tr("Could not find current free game"))) + self.free_games_now.layout().addWidget( + QLabel(self.tr("Could not find current free game")) + ) # free games next week for free_game in coming_free_games: @@ -187,30 +214,53 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): def init_filter(self): - self.none_price.toggled.connect(lambda: self.prepare_request("") if self.none_price.isChecked() else None) - self.free_button.toggled.connect(lambda: self.prepare_request("free") if self.free_button.isChecked() else None) + self.none_price.toggled.connect( + lambda: self.prepare_request("") if self.none_price.isChecked() else None + ) + self.free_button.toggled.connect( + lambda: self.prepare_request("free") + if self.free_button.isChecked() + else None + ) self.under10.toggled.connect( - lambda: self.prepare_request("[0, 1000)") if self.under10.isChecked() else None) + lambda: self.prepare_request("[0, 1000)") + if self.under10.isChecked() + else None + ) self.under20.toggled.connect( - lambda: self.prepare_request("[0, 2000)") if self.under20.isChecked() else None) + lambda: self.prepare_request("[0, 2000)") + if self.under20.isChecked() + else None + ) self.under30.toggled.connect( - lambda: self.prepare_request("[0, 3000)") if self.under30.isChecked() else None) - self.above.toggled.connect(lambda: self.prepare_request("[1499,]") if self.above.isChecked() else None) + lambda: self.prepare_request("[0, 3000)") + if self.under30.isChecked() + else None + ) + self.above.toggled.connect( + lambda: self.prepare_request("[1499,]") + if self.above.isChecked() + else None + ) # self.on_discount.toggled.connect(lambda: self.prepare_request("sale") if self.on_discount.isChecked() else None) self.on_discount.toggled.connect(lambda: self.prepare_request()) constants = Constants() self.checkboxes = [] - for groupbox, variables in [(self.genre_gb, constants.categories), - (self.platform_gb, constants.platforms), - (self.others_gb, constants.others), - (self.type_gb, constants.types)]: + for groupbox, variables in [ + (self.genre_gb, constants.categories), + (self.platform_gb, constants.platforms), + (self.others_gb, constants.others), + (self.type_gb, constants.types), + ]: for text, tag in variables: checkbox = CheckBox(text, tag) checkbox.activated.connect(lambda x: self.prepare_request(added_tag=x)) - checkbox.deactivated.connect(lambda x: self.prepare_request(removed_tag=x)) + checkbox.deactivated.connect( + lambda x: self.prepare_request(removed_tag=x) + ) groupbox.layout().addWidget(checkbox) self.checkboxes.append(checkbox) self.reset_button.clicked.connect(self.reset_filters) @@ -228,8 +278,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.on_discount.setChecked(False) - def prepare_request(self, price: str = None, added_tag: int = 0, removed_tag: int = 0, - added_type: str = "", removed_type: str = ""): + def prepare_request( + self, + price: str = None, + added_tag: int = 0, + removed_tag: int = 0, + added_type: str = "", + removed_type: str = "", + ): if not self.update_games_allowed: return if price is not None: @@ -255,9 +311,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.game_stack.setCurrentIndex(1) date = f"[{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')},]" - browse_model = BrowseModel(language_code=self.core.language_code, country_code=self.core.country_code, - date=date, count=20, price=self.price, - onSale=self.on_discount.isChecked()) + browse_model = BrowseModel( + language_code=self.core.language_code, + country_code=self.core.country_code, + date=date, + count=20, + price=self.price, + onSale=self.on_discount.isChecked(), + ) browse_model.tag = "|".join(self.tags) if self.types: @@ -265,7 +326,10 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): self.api_core.browse_games(browse_model, self.show_games) def show_games(self, data): - for item in (self.game_widget.layout().itemAt(i) for i in range(self.game_widget.layout().count())): + for item in ( + self.game_widget.layout().itemAt(i) + for i in range(self.game_widget.layout().count()) + ): item.widget().deleteLater() if data: for game in data: @@ -275,7 +339,8 @@ class ShopWidget(QScrollArea, Ui_ShopWidget): else: self.game_widget.layout().addWidget( - QLabel(self.tr("Could not get games matching the filter"))) + QLabel(self.tr("Could not get games matching the filter")) + ) self.game_stack.setCurrentIndex(0) self.game_widget.layout().update() diff --git a/rare/components/tabs/shop/wishlist.py b/rare/components/tabs/shop/wishlist.py index 34d64062..d49b2a78 100644 --- a/rare/components/tabs/shop/wishlist.py +++ b/rare/components/tabs/shop/wishlist.py @@ -21,22 +21,31 @@ class Wishlist(QStackedWidget, Ui_Wishlist): self.wishlist = [] self.widgets = [] - self.sort_cb.currentIndexChanged.connect(lambda i: self.set_wishlist(self.wishlist, i)) + self.sort_cb.currentIndexChanged.connect( + lambda i: self.set_wishlist(self.wishlist, i) + ) self.filter_cb.currentIndexChanged.connect(self.set_filter) self.reload_button.clicked.connect(self.update_wishlist) self.reload_button.setIcon(icon("fa.refresh", color="white")) - self.reverse.stateChanged.connect(lambda: self.set_wishlist(sort=self.sort_cb.currentIndex())) + self.reverse.stateChanged.connect( + lambda: self.set_wishlist(sort=self.sort_cb.currentIndex()) + ) def update_wishlist(self): self.setCurrentIndex(1) self.api_core.get_wishlist(self.set_wishlist) def delete_from_wishlist(self, game): - self.api_core.remove_from_wishlist(game["namespace"], game["id"], - lambda success: self.update_wishlist() if success else - QMessageBox.warning(self, "Error", - self.tr("Could not remove game from wishlist"))) + self.api_core.remove_from_wishlist( + game["namespace"], + game["id"], + lambda success: self.update_wishlist() + if success + else QMessageBox.warning( + self, "Error", self.tr("Could not remove game from wishlist") + ), + ) self.update_wishlist_signal.emit() def set_filter(self, i): @@ -69,14 +78,26 @@ class Wishlist(QStackedWidget, Ui_Wishlist): if sort == 0: sorted_list = sorted(self.wishlist, key=lambda x: x["offer"]["title"]) elif sort == 1: - sorted_list = sorted(self.wishlist, - key=lambda x: x["offer"]['price']['totalPrice']['fmtPrice']['discountPrice']) + sorted_list = sorted( + self.wishlist, + key=lambda x: x["offer"]["price"]["totalPrice"]["fmtPrice"][ + "discountPrice" + ], + ) elif sort == 2: - sorted_list = sorted(self.wishlist, key=lambda x: x["offer"]["seller"]["name"]) + sorted_list = sorted( + self.wishlist, key=lambda x: x["offer"]["seller"]["name"] + ) elif sort == 3: - sorted_list = sorted(self.wishlist, reverse=True, key=lambda x: 1 - ( - x["offer"]["price"]["totalPrice"]["discountPrice"] / x["offer"]["price"]["totalPrice"][ - "originalPrice"])) + sorted_list = sorted( + self.wishlist, + reverse=True, + key=lambda x: 1 + - ( + x["offer"]["price"]["totalPrice"]["discountPrice"] + / x["offer"]["price"]["totalPrice"]["originalPrice"] + ), + ) else: sorted_list = self.wishlist self.widgets.clear() diff --git a/rare/components/tabs/tab_utils.py b/rare/components/tabs/tab_utils.py index fa5462b8..788dc859 100644 --- a/rare/components/tabs/tab_utils.py +++ b/rare/components/tabs/tab_utils.py @@ -9,7 +9,7 @@ class MainTabBar(QTabBar): self._expanded = expanded self.setObjectName("MainTabBar") font = self.font() - font.setPointSize(font.pointSize()+2) + font.setPointSize(font.pointSize() + 2) font.setBold(True) self.setFont(font) # self.setContentsMargins(0,10,0,10) diff --git a/rare/utils/extra_widgets.py b/rare/utils/extra_widgets.py index 2928c82d..3ded2fba 100644 --- a/rare/utils/extra_widgets.py +++ b/rare/utils/extra_widgets.py @@ -2,11 +2,36 @@ import os from logging import getLogger from typing import Callable, Tuple -from PyQt5.QtCore import Qt, QCoreApplication, QRect, QSize, QPoint, pyqtSignal, QFileInfo +from PyQt5.QtCore import ( + Qt, + QCoreApplication, + QRect, + QSize, + QPoint, + pyqtSignal, + QFileInfo, +) from PyQt5.QtGui import QMovie, QPixmap, QFontMetrics, QImage -from PyQt5.QtWidgets import QLayout, QStyle, QSizePolicy, QLabel, QFileDialog, QHBoxLayout, QWidget, QPushButton, \ - QStyleOptionTab, QStylePainter, QTabBar, QLineEdit, QToolButton, QTabWidget, QCompleter, QFileSystemModel, \ - QStyledItemDelegate, QFileIconProvider +from PyQt5.QtWidgets import ( + QLayout, + QStyle, + QSizePolicy, + QLabel, + QFileDialog, + QHBoxLayout, + QWidget, + QPushButton, + QStyleOptionTab, + QStylePainter, + QTabBar, + QLineEdit, + QToolButton, + QTabWidget, + QCompleter, + QFileSystemModel, + QStyledItemDelegate, + QFileIconProvider, +) from qtawesome import icon as qta_icon from rare import cache_dir @@ -33,15 +58,13 @@ class FlowLayout(QLayout): if self._hspacing >= 0: return self._hspacing else: - return self.smartSpacing( - QStyle.PM_LayoutHorizontalSpacing) + return self.smartSpacing(QStyle.PM_LayoutHorizontalSpacing) def verticalSpacing(self): if self._vspacing >= 0: return self._vspacing else: - return self.smartSpacing( - QStyle.PM_LayoutVerticalSpacing) + return self.smartSpacing(QStyle.PM_LayoutVerticalSpacing) def count(self): return len(self._items) @@ -91,13 +114,13 @@ class FlowLayout(QLayout): hspace = self.horizontalSpacing() if hspace == -1: hspace = widget.style().layoutSpacing( - QSizePolicy.PushButton, - QSizePolicy.PushButton, Qt.Horizontal) + QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal + ) vspace = self.verticalSpacing() if vspace == -1: vspace = widget.style().layoutSpacing( - QSizePolicy.PushButton, - QSizePolicy.PushButton, Qt.Vertical) + QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical + ) nextX = x + item.sizeHint().width() + hspace if nextX - hspace > effective.right() and lineheight > 0: x = effective.x() @@ -105,8 +128,7 @@ class FlowLayout(QLayout): nextX = x + item.sizeHint().width() + hspace lineheight = 0 if not testonly: - item.setGeometry( - QRect(QPoint(x, y), item.sizeHint())) + item.setGeometry(QRect(QPoint(x, y), item.sizeHint())) x = nextX lineheight = max(lineheight, item.sizeHint().height()) return y + lineheight - rect.y() + bottom @@ -125,14 +147,16 @@ class IndicatorLineEdit(QWidget): textChanged = pyqtSignal(str) is_valid = False - def __init__(self, - text: str = "", - ph_text: str = "", - completer: QCompleter = None, - edit_func: Callable[[str], Tuple[bool, str]] = None, - save_func: Callable[[str], None] = None, - horiz_policy: QSizePolicy = QSizePolicy.Expanding, - parent=None): + def __init__( + self, + text: str = "", + ph_text: str = "", + completer: QCompleter = None, + edit_func: Callable[[str], Tuple[bool, str]] = None, + save_func: Callable[[str], None] = None, + horiz_policy: QSizePolicy = QSizePolicy.Expanding, + parent=None, + ): super(IndicatorLineEdit, self).__init__(parent=parent) self.setObjectName("IndicatorLineEdit") self.layout = QHBoxLayout(self) @@ -146,7 +170,7 @@ class IndicatorLineEdit(QWidget): # Add hint_label to line_edit self.line_edit.setLayout(QHBoxLayout()) self.hint_label = QLabel() - self.hint_label.setObjectName('HintLabel') + self.hint_label.setObjectName("HintLabel") self.hint_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.line_edit.layout().setContentsMargins(0, 0, 10, 0) self.line_edit.layout().addWidget(self.hint_label) @@ -158,13 +182,17 @@ class IndicatorLineEdit(QWidget): self.layout.addWidget(self.line_edit) if edit_func is not None: self.indicator_label = QLabel() - self.indicator_label.setPixmap(qta_icon("ei.info-circle", color="gray").pixmap(16, 16)) + self.indicator_label.setPixmap( + qta_icon("ei.info-circle", color="gray").pixmap(16, 16) + ) self.indicator_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.layout.addWidget(self.indicator_label) if not ph_text: _translate = QCoreApplication.translate - self.line_edit.setPlaceholderText(_translate(self.__class__.__name__, "Default")) + self.line_edit.setPlaceholderText( + _translate(self.__class__.__name__, "Default") + ) self.edit_func = edit_func self.save_func = save_func @@ -193,7 +221,9 @@ class IndicatorLineEdit(QWidget): def __indicator(self, res): color = "green" if res else "red" - self.indicator_label.setPixmap(qta_icon("ei.info-circle", color=color).pixmap(16, 16)) + self.indicator_label.setPixmap( + qta_icon("ei.info-circle", color=color).pixmap(16, 16) + ) def __edit(self, text): if self.edit_func is not None: @@ -214,22 +244,22 @@ class IndicatorLineEdit(QWidget): class PathEditIconProvider(QFileIconProvider): icons = [ - 'mdi.file-cancel', # Unknown - 'mdi.desktop-classic', # Computer - 'mdi.desktop-mac', # Desktop - 'mdi.trash-can', # Trashcan - 'mdi.server-network', # Network - 'mdi.harddisk', # Drive - 'mdi.folder', # Folder - 'mdi.file', # File - 'mdi.cog', # Executable + "mdi.file-cancel", # Unknown + "mdi.desktop-classic", # Computer + "mdi.desktop-mac", # Desktop + "mdi.trash-can", # Trashcan + "mdi.server-network", # Network + "mdi.harddisk", # Drive + "mdi.folder", # Folder + "mdi.file", # File + "mdi.cog", # Executable ] def __init__(self): super(PathEditIconProvider, self).__init__() self.icon_types = dict() for idx, icn in enumerate(self.icons): - self.icon_types.update({idx-1: qta_icon(icn, color='#eeeeee')}) + self.icon_types.update({idx - 1: qta_icon(icn, color="#eeeeee")}) def icon(self, info_type): if isinstance(info_type, QFileInfo): @@ -249,25 +279,35 @@ class PathEdit(IndicatorLineEdit): completer = QCompleter() compl_model = QFileSystemModel() - def __init__(self, - path: str = "", - file_type: QFileDialog.FileType = QFileDialog.AnyFile, - type_filter: str = "", - name_filter: str = "", - ph_text: str = "", - edit_func: Callable[[str], Tuple[bool, str]] = None, - save_func: Callable[[str], None] = None, - horiz_policy: QSizePolicy = QSizePolicy.Expanding, - parent=None): - self.compl_model.setOptions(QFileSystemModel.DontWatchForChanges | - QFileSystemModel.DontResolveSymlinks | - QFileSystemModel.DontUseCustomDirectoryIcons) + def __init__( + self, + path: str = "", + file_type: QFileDialog.FileType = QFileDialog.AnyFile, + type_filter: str = "", + name_filter: str = "", + ph_text: str = "", + edit_func: Callable[[str], Tuple[bool, str]] = None, + save_func: Callable[[str], None] = None, + horiz_policy: QSizePolicy = QSizePolicy.Expanding, + parent=None, + ): + self.compl_model.setOptions( + QFileSystemModel.DontWatchForChanges + | QFileSystemModel.DontResolveSymlinks + | QFileSystemModel.DontUseCustomDirectoryIcons + ) self.compl_model.setIconProvider(PathEditIconProvider()) self.compl_model.setRootPath(path) self.completer.setModel(self.compl_model) - super(PathEdit, self).__init__(text=path, ph_text=ph_text, completer=self.completer, - edit_func=edit_func, save_func=save_func, - horiz_policy=horiz_policy, parent=parent) + super(PathEdit, self).__init__( + text=path, + ph_text=ph_text, + completer=self.completer, + edit_func=edit_func, + save_func=save_func, + horiz_policy=horiz_policy, + parent=parent, + ) self.setObjectName("PathEdit") self.line_edit.setMinimumSize(QSize(300, 0)) self.path_select = QToolButton(self) @@ -353,10 +393,12 @@ class SideTabWidget(QTabWidget): class WaitingSpinner(QLabel): def __init__(self): super(WaitingSpinner, self).__init__() - self.setStyleSheet(""" + self.setStyleSheet( + """ margin-left: auto; margin-right: auto; - """) + """ + ) self.movie = QMovie(":/images/loader.gif") self.setMovie(self.movie) self.movie.start() @@ -368,11 +410,15 @@ class SelectViewWidget(QWidget): def __init__(self, icon_view: bool): super(SelectViewWidget, self).__init__() self.icon_view = icon_view - self.setStyleSheet("""QPushButton{border: none; background-color: transparent}""") + self.setStyleSheet( + """QPushButton{border: none; background-color: transparent}""" + ) self.icon_view_button = QPushButton() self.list_view = QPushButton() if icon_view: - self.icon_view_button.setIcon(qta_icon("mdi.view-grid-outline", color="orange")) + self.icon_view_button.setIcon( + qta_icon("mdi.view-grid-outline", color="orange") + ) self.list_view.setIcon(qta_icon("fa5s.list")) else: self.icon_view_button.setIcon(qta_icon("mdi.view-grid-outline")) @@ -438,14 +484,19 @@ class ImageLabel(QLabel): return image = QImage() image.loadFromData(data) - image = image.scaled(*self.img_size[:2], Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation) + image = image.scaled( + *self.img_size[:2], + Qt.KeepAspectRatio, + transformMode=Qt.SmoothTransformation, + ) pixmap = QPixmap().fromImage(image) self.setPixmap(pixmap) def show_image(self): - self.image = QPixmap(os.path.join(self.path, self.name)).scaled(*self.img_size, - transformMode=Qt.SmoothTransformation) + self.image = QPixmap(os.path.join(self.path, self.name)).scaled( + *self.img_size, transformMode=Qt.SmoothTransformation + ) self.setPixmap(self.image) @@ -457,20 +508,31 @@ class ButtonLineEdit(QLineEdit): self.button = QToolButton(self) self.button.setIcon(qta_icon(icon_name, color="white")) - self.button.setStyleSheet('border: 0px; padding: 0px;') + self.button.setStyleSheet("border: 0px; padding: 0px;") self.button.setCursor(Qt.ArrowCursor) self.button.clicked.connect(self.buttonClicked.emit) self.setPlaceholderText(placeholder_text) frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) buttonSize = self.button.sizeHint() - self.setStyleSheet('QLineEdit {padding-right: %dpx; }' % (buttonSize.width() + frameWidth + 1)) - self.setMinimumSize(max(self.minimumSizeHint().width(), buttonSize.width() + frameWidth * 2 + 2), - max(self.minimumSizeHint().height(), buttonSize.height() + frameWidth * 2 + 2)) + self.setStyleSheet( + "QLineEdit {padding-right: %dpx; }" % (buttonSize.width() + frameWidth + 1) + ) + self.setMinimumSize( + max( + self.minimumSizeHint().width(), buttonSize.width() + frameWidth * 2 + 2 + ), + max( + self.minimumSizeHint().height(), + buttonSize.height() + frameWidth * 2 + 2, + ), + ) def resizeEvent(self, event): buttonSize = self.button.sizeHint() frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) - self.button.move(self.rect().right() - frameWidth - buttonSize.width(), - (self.rect().bottom() - buttonSize.height() + 1) // 2) + self.button.move( + self.rect().right() - frameWidth - buttonSize.width(), + (self.rect().bottom() - buttonSize.height() + 1) // 2, + ) super(ButtonLineEdit, self).resizeEvent(event) diff --git a/rare/utils/json_formatter.py b/rare/utils/json_formatter.py index 22168309..5ddc1778 100644 --- a/rare/utils/json_formatter.py +++ b/rare/utils/json_formatter.py @@ -64,10 +64,7 @@ class QJsonTreeItem(object): return len(self._children) def row(self): - return ( - self._parent._children.index(self) - if self._parent else 0 - ) + return self._parent._children.index(self) if self._parent else 0 @property def key(self): @@ -99,10 +96,7 @@ class QJsonTreeItem(object): rootItem.key = "root" if isinstance(value, dict): - items = ( - sorted(value.items()) - if sort else value.items() - ) + items = sorted(value.items()) if sort else value.items() for key, value in items: child = self.load(value, rootItem) @@ -142,10 +136,9 @@ class QJsonModel(QtCore.QAbstractItemModel): """ - assert isinstance(document, (dict, list, tuple)), ( - "`document` must be of dict, list or tuple, " - "not %s" % type(document) - ) + assert isinstance( + document, (dict, list, tuple) + ), "`document` must be of dict, list or tuple, " "not %s" % type(document) self.beginResetModel() @@ -277,7 +270,7 @@ class QJsonModel(QtCore.QAbstractItemModel): return item.value -if __name__ == '__main__': +if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) @@ -286,7 +279,8 @@ if __name__ == '__main__': view.setModel(model) - document = json.loads("""\ + document = json.loads( + """\ { "firstName": "John", "lastName": "Smith", @@ -308,16 +302,16 @@ if __name__ == '__main__': } ] } - """) + """ + ) model.load(document) model.clear() model.load(document) # Sanity check - assert ( - json.dumps(model.json(), sort_keys=True) == - json.dumps(document, sort_keys=True) + assert json.dumps(model.json(), sort_keys=True) == json.dumps( + document, sort_keys=True ) view.show() diff --git a/rare/utils/legendary_utils.py b/rare/utils/legendary_utils.py index 759998f6..2e196bbc 100644 --- a/rare/utils/legendary_utils.py +++ b/rare/utils/legendary_utils.py @@ -22,15 +22,28 @@ def uninstall(app_name: str, core: LegendaryCore, options=None): if platform.system() == "Linux": if os.path.exists(os.path.expanduser(f"~/Desktop/{igame.title}.desktop")): os.remove(os.path.expanduser(f"~/Desktop/{igame.title}.desktop")) - if os.path.exists(os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop")): - os.remove(os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop")) + if os.path.exists( + os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop") + ): + os.remove( + os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop") + ) elif platform.system() == "Windows": - if os.path.exists(os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk")): + if os.path.exists( + os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk") + ): os.remove(os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk")) elif os.path.exists( - os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk")): - os.remove(os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk")) + os.path.expandvars( + f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk" + ) + ): + os.remove( + os.path.expandvars( + f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk" + ) + ) try: # Remove DLC first so directory is empty when game uninstall runs @@ -41,13 +54,17 @@ def uninstall(app_name: str, core: LegendaryCore, options=None): core.uninstall_game(idlc, delete_files=not options["keep_files"]) logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...') - core.uninstall_game(igame, delete_files=not options["keep_files"], delete_root_directory=True) - logger.info('Game has been uninstalled.') + core.uninstall_game( + igame, delete_files=not options["keep_files"], delete_root_directory=True + ) + logger.info("Game has been uninstalled.") if not options["keep_files"]: shutil.rmtree(igame.install_path) except Exception as e: - logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.') + logger.warning( + f"Removing game failed: {e!r}, please remove {igame.install_path} manually." + ) logger.info("Removing sections in config file") if core.lgd.config.has_section(app_name): @@ -59,7 +76,7 @@ def uninstall(app_name: str, core: LegendaryCore, options=None): def update_manifest(app_name: str, core: LegendaryCore): game = core.get_game(app_name) - logger.info('Reloading game manifest of ' + game.app_title) + logger.info("Reloading game manifest of " + game.app_title) new_manifest_data, base_urls = core.get_cdn_manifest(game) # overwrite base urls in metadata with current ones to avoid using old/dead CDNs game.base_urls = base_urls @@ -67,10 +84,11 @@ def update_manifest(app_name: str, core: LegendaryCore): core.lgd.set_game_meta(game.app_name, game) new_manifest = core.load_manifest(new_manifest_data) - logger.debug(f'Base urls: {base_urls}') + logger.debug(f"Base urls: {base_urls}") # save manifest with version name as well for testing/downgrading/etc. - core.lgd.save_manifest(game.app_name, new_manifest_data, - version=new_manifest.meta.build_version) + core.lgd.save_manifest( + game.app_name, new_manifest_data, version=new_manifest.meta.build_version + ) class VerifySignals(QObject): @@ -105,8 +123,9 @@ class VerifyWorker(QRunnable): manifest = self.core.load_manifest(manifest_data) - files = sorted(manifest.file_manifest_list.elements, - key=lambda a: a.filename.lower()) + files = sorted( + manifest.file_manifest_list.elements, key=lambda a: a.filename.lower() + ) # build list of hashes file_list = [(f.filename, f.sha_hash.hex()) for f in files] @@ -117,46 +136,60 @@ class VerifyWorker(QRunnable): _translate = QCoreApplication.translate - logger.info(f'Verifying "{igame.title}" version "{manifest.meta.build_version}"') + logger.info( + f'Verifying "{igame.title}" version "{manifest.meta.build_version}"' + ) repair_file = [] try: - for result, path, result_hash in validate_files(igame.install_path, file_list): + for result, path, result_hash in validate_files( + igame.install_path, file_list + ): self.signals.status.emit((self.num, self.total, self.app_name)) self.num += 1 if result == VerifyResult.HASH_MATCH: - repair_file.append(f'{result_hash}:{path}') + repair_file.append(f"{result_hash}:{path}") continue elif result == VerifyResult.HASH_MISMATCH: logger.error(f'File does not match hash: "{path}"') - repair_file.append(f'{result_hash}:{path}') + repair_file.append(f"{result_hash}:{path}") failed.append(path) elif result == VerifyResult.FILE_MISSING: logger.error(f'File is missing: "{path}"') missing.append(path) else: - logger.error(f'Other failure (see log), treating file as missing: "{path}"') + logger.error( + f'Other failure (see log), treating file as missing: "{path}"' + ) missing.append(path) except OSError as e: - QMessageBox.warning(None, "Error", _translate("VerifyWorker", "Path does not exist")) + QMessageBox.warning( + None, "Error", _translate("VerifyWorker", "Path does not exist") + ) logger.error(str(e)) except ValueError as e: - QMessageBox.warning(None, "Error", _translate("VerifyWorker", "No files to validate")) + QMessageBox.warning( + None, "Error", _translate("VerifyWorker", "No files to validate") + ) logger.error(str(e)) # always write repair file, even if all match if repair_file: - repair_filename = os.path.join(self.core.lgd.get_tmp_path(), f'{self.app_name}.repair') - with open(repair_filename, 'w') as f: - f.write('\n'.join(repair_file)) + repair_filename = os.path.join( + self.core.lgd.get_tmp_path(), f"{self.app_name}.repair" + ) + with open(repair_filename, "w") as f: + f.write("\n".join(repair_file)) logger.debug(f'Written repair file to "{repair_filename}"') if not missing and not failed: - logger.info('Verification finished successfully.') + logger.info("Verification finished successfully.") self.signals.summary.emit(0, 0, self.app_name) else: - logger.error(f'Verification finished, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.') + logger.error( + f"Verification finished, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing." + ) self.signals.summary.emit(len(failed), len(missing), self.app_name) @@ -176,30 +209,37 @@ def import_game(core: LegendaryCore, app_name: str, path: str) -> str: return _tr("LgdUtils", "Path does not exist") manifest, igame = core.import_game(game, path) - exe_path = os.path.join(path, manifest.meta.launch_exe.lstrip('/')) + exe_path = os.path.join(path, manifest.meta.launch_exe.lstrip("/")) if not os.path.exists(exe_path): logger.error(f"Launch Executable of {game.app_title} does not exist") - return _tr("LgdUtils", "Launch executable of {} does not exist").format(game.app_title) + return _tr("LgdUtils", "Launch executable of {} does not exist").format( + game.app_title + ) if game.is_dlc: - release_info = game.metadata.get('mainGameItem', {}).get('releaseInfo') + release_info = game.metadata.get("mainGameItem", {}).get("releaseInfo") if release_info: - main_game_appname = release_info[0]['appId'] - main_game_title = game.metadata['mainGameItem']['title'] + main_game_appname = release_info[0]["appId"] + main_game_title = game.metadata["mainGameItem"]["title"] if not core.is_installed(main_game_appname): - return _tr("LgdUtils", "Game is a DLC, but {} is not installed").format(main_game_title) + return _tr("LgdUtils", "Game is a DLC, but {} is not installed").format( + main_game_title + ) else: return _tr("LgdUtils", "Unable to get base game information for DLC") total = len(manifest.file_manifest_list.elements) - found = sum(os.path.exists(os.path.join(path, f.filename)) - for f in manifest.file_manifest_list.elements) + found = sum( + os.path.exists(os.path.join(path, f.filename)) + for f in manifest.file_manifest_list.elements + ) ratio = found / total if ratio < 0.9: logger.warning( - "Game files are missing. It may be not the latest version or it is corrupt") + "Game files are missing. It may be not the latest version or it is corrupt" + ) # return False core.install_game(igame) if igame.needs_verification: diff --git a/rare/utils/models.py b/rare/utils/models.py index 76b6c3e1..b8552a2c 100644 --- a/rare/utils/models.py +++ b/rare/utils/models.py @@ -20,7 +20,7 @@ class InstallOptionsModel: no_install: bool = False ignore_space_req: bool = False force: bool = False - sdl_list: list = field(default_factory=lambda: ['']) + sdl_list: list = field(default_factory=lambda: [""]) update: bool = False silent: bool = False platform: str = "" @@ -47,32 +47,38 @@ class InstallQueueItemModel: options: InstallOptionsModel = None def __bool__(self): - return (self.status_q is not None) and (self.download is not None) and (self.options is not None) + return ( + (self.status_q is not None) + and (self.download is not None) + and (self.options is not None) + ) class PathSpec: __egl_path_vars = { - '{appdata}': os.path.expandvars('%LOCALAPPDATA%'), - '{userdir}': os.path.expandvars('%USERPROFILE%/Documents'), + "{appdata}": os.path.expandvars("%LOCALAPPDATA%"), + "{userdir}": os.path.expandvars("%USERPROFILE%/Documents"), # '{userprofile}': os.path.expandvars('%userprofile%'), # possibly wrong - '{usersavedgames}': os.path.expandvars('%USERPROFILE%/Saved Games'), + "{usersavedgames}": os.path.expandvars("%USERPROFILE%/Saved Games"), } - egl_appdata: str = r'%LOCALAPPDATA%\EpicGamesLauncher\Saved\Config\Windows' - egl_programdata: str = r'%PROGRAMDATA%\Epic\EpicGamesLauncher\Data\Manifests' - wine_programdata: str = r'dosdevices/c:/ProgramData' + egl_appdata: str = r"%LOCALAPPDATA%\EpicGamesLauncher\Saved\Config\Windows" + egl_programdata: str = r"%PROGRAMDATA%\Epic\EpicGamesLauncher\Data\Manifests" + wine_programdata: str = r"dosdevices/c:/ProgramData" - def __init__(self, core: LegendaryCore = None, app_name: str = 'default'): + def __init__(self, core: LegendaryCore = None, app_name: str = "default"): if core is not None: - self.__egl_path_vars.update({'{epicid}': core.lgd.userdata['account_id']}) + self.__egl_path_vars.update({"{epicid}": core.lgd.userdata["account_id"]}) self.app_name = app_name def cook(self, path: str) -> str: - cooked_path = [self.__egl_path_vars.get(p.lower(), p) for p in path.split('/')] + cooked_path = [self.__egl_path_vars.get(p.lower(), p) for p in path.split("/")] return os.path.join(*cooked_path) @property def wine_egl_programdata(self): - return self.egl_programdata.replace('\\', '/').replace('%PROGRAMDATA%', self.wine_programdata) + return self.egl_programdata.replace("\\", "/").replace( + "%PROGRAMDATA%", self.wine_programdata + ) def wine_egl_prefixes(self, results: int = 0) -> Union[List[str], str]: possible_prefixes = [ @@ -103,12 +109,14 @@ class ApiResults: saves: list = None def __bool__(self): - return self.game_list is not None \ - and self.dlcs is not None \ - and self.bit32_games is not None \ - and self.mac_games is not None \ - and self.no_asset_games is not None \ - and self.saves is not None + return ( + self.game_list is not None + and self.dlcs is not None + and self.bit32_games is not None + and self.mac_games is not None + and self.no_asset_games is not None + and self.saves is not None + ) class Signals(QObject): diff --git a/rare/utils/qt_requests.py b/rare/utils/qt_requests.py index 1fb319ab..b5eaa188 100644 --- a/rare/utils/qt_requests.py +++ b/rare/utils/qt_requests.py @@ -29,30 +29,41 @@ class QtRequestManager(QObject): self.request_active = RequestQueueItem(handle_func=handle_func) payload = json.dumps(payload).encode("utf-8") - request.setHeader(QNetworkRequest.UserAgentHeader, - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36") + request.setHeader( + QNetworkRequest.UserAgentHeader, + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", + ) if self.authorization_token is not None: - request.setRawHeader(b"Authorization", self.authorization_token.encode()) + request.setRawHeader( + b"Authorization", self.authorization_token.encode() + ) self.request = self.manager.post(request, payload) self.request.finished.connect(self.prepare_data) else: self.request_queue.append( - RequestQueueItem(method="post", url=url, payload=payload, handle_func=handle_func)) + RequestQueueItem( + method="post", url=url, payload=payload, handle_func=handle_func + ) + ) def get(self, url: str, handle_func: Callable[[Union[dict, bytes]], None]): if not self.request_active: request = QNetworkRequest(QUrl(url)) - request.setHeader(QNetworkRequest.UserAgentHeader, - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36") + request.setHeader( + QNetworkRequest.UserAgentHeader, + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", + ) self.request_active = RequestQueueItem(handle_func=handle_func) self.request = self.manager.get(request) self.request.finished.connect(self.prepare_data) else: - self.request_queue.append(RequestQueueItem(method="get", url=url, handle_func=handle_func)) + self.request_queue.append( + RequestQueueItem(method="get", url=url, handle_func=handle_func) + ) def prepare_data(self): # self.request_active = False @@ -62,7 +73,9 @@ class QtRequestManager(QObject): if self.request.error() == QNetworkReply.NoError: if self.type == "json": error = QJsonParseError() - json_data = QJsonDocument.fromJson(self.request.readAll().data(), error) + json_data = QJsonDocument.fromJson( + self.request.readAll().data(), error + ) if QJsonParseError.NoError == error.error: data = json.loads(json_data.toJson().data().decode()) else: @@ -78,7 +91,11 @@ class QtRequestManager(QObject): if self.request_queue: if self.request_queue[0].method == "post": - self.post(self.request_queue[0].url, self.request_queue[0].payload, self.request_queue[0].handle_func) + self.post( + self.request_queue[0].url, + self.request_queue[0].payload, + self.request_queue[0].handle_func, + ) else: self.get(self.request_queue[0].url, self.request_queue[0].handle_func) self.request_queue.pop(0) diff --git a/rare/utils/rpc.py b/rare/utils/rpc.py index 4b16638b..a1d80578 100644 --- a/rare/utils/rpc.py +++ b/rare/utils/rpc.py @@ -61,7 +61,9 @@ class DiscordRPC(QObject): def set_discord_rpc(self, app_name=None): if not self.RPC: try: - self.RPC = Presence(client_id) # Rare app: https://discord.com/developers/applications + self.RPC = Presence( + client_id + ) # Rare app: https://discord.com/developers/applications self.RPC.connect() except ConnectionRefusedError as e: logger.warning("Discord is not active\n" + str(e)) @@ -83,13 +85,15 @@ class DiscordRPC(QObject): def update_rpc(self, app_name=None): if self.settings.value("rpc_enable", 0, int) == 2 or ( - app_name is None and self.settings.value("rpc_enable", 0, int) == 0): + app_name is None and self.settings.value("rpc_enable", 0, int) == 0 + ): self.remove_rpc() return title = None if not app_name: - self.RPC.update(large_image="logo", - details="https://github.com/Dummerle/Rare") + self.RPC.update( + large_image="logo", details="https://github.com/Dummerle/Rare" + ) return if self.settings.value("rpc_name", True, bool): try: @@ -104,9 +108,7 @@ class DiscordRPC(QObject): if self.settings.value("rpc_os", True, bool): os = "via Rare on " + platform.system() - self.RPC.update(large_image="logo", - details=title, - large_text=title, - state=os, - start=start) + self.RPC.update( + large_image="logo", details=title, large_text=title, state=os, start=start + ) self.state = 0 diff --git a/rare/utils/singleton.py b/rare/utils/singleton.py index 493cb9bd..a9be16ab 100644 --- a/rare/utils/singleton.py +++ b/rare/utils/singleton.py @@ -34,36 +34,38 @@ class SingleInstance(object): if lockfile: self.lockfile = lockfile else: - basename = os.path.splitext(os.path.abspath(sys.argv[0]))[0].replace( - "/", "-").replace(":", "").replace("\\", "-") + '-%s' % flavor_id + '.lock' - self.lockfile = os.path.normpath( - tempfile.gettempdir() + '/' + basename) + basename = ( + os.path.splitext(os.path.abspath(sys.argv[0]))[0] + .replace("/", "-") + .replace(":", "") + .replace("\\", "-") + + "-%s" % flavor_id + + ".lock" + ) + self.lockfile = os.path.normpath(tempfile.gettempdir() + "/" + basename) logger.debug("SingleInstance lockfile: " + self.lockfile) - if sys.platform == 'win32': + if sys.platform == "win32": try: # file already exists, we try to remove (in case previous # execution was interrupted) if os.path.exists(self.lockfile): os.unlink(self.lockfile) - self.fd = os.open( - self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR) + self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR) except OSError: type, e, tb = sys.exc_info() if e.errno == 13: - logger.error( - "Another instance is already running, quitting.") + logger.error("Another instance is already running, quitting.") raise SingleInstanceException() print(e.errno) raise else: # non Windows - self.fp = open(self.lockfile, 'w') + self.fp = open(self.lockfile, "w") self.fp.flush() try: fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: - logger.warning( - "Another instance is already running, quitting.") + logger.warning("Another instance is already running, quitting.") raise SingleInstanceException() self.initialized = True @@ -71,8 +73,8 @@ class SingleInstance(object): if not self.initialized: return try: - if sys.platform == 'win32': - if hasattr(self, 'fd'): + if sys.platform == "win32": + if hasattr(self, "fd"): os.close(self.fd) os.unlink(self.lockfile) else: diff --git a/rare/utils/steam_grades.py b/rare/utils/steam_grades.py index 80d19df3..1d998602 100644 --- a/rare/utils/steam_grades.py +++ b/rare/utils/steam_grades.py @@ -34,14 +34,16 @@ class SteamWorker(QRunnable): "bronze": _tr("SteamWorker", "Bronze"), "fail": _tr("SteamWorker", "Could not get grade"), "borked": _tr("SteamWorker", "unplayable"), - "pending": _tr("SteamWorker", "Could not get grade") + "pending": _tr("SteamWorker", "Could not get grade"), } def set_app_name(self, app_name: str): self.app_name = app_name def run(self) -> None: - self.signals.rating_signal.emit(self.ratings.get(get_rating(self.app_name), self.ratings["fail"])) + self.signals.rating_signal.emit( + self.ratings.get(get_rating(self.app_name), self.ratings["fail"]) + ) def get_rating(app_name: str): @@ -57,10 +59,7 @@ def get_rating(app_name: str): steam_id = get_steam_id(game.app_title) grade = get_grade(steam_id) - grades[app_name] = { - "steam_id": steam_id, - "grade": grade - } + grades[app_name] = {"steam_id": steam_id, "grade": grade} with open(os.path.join(data_dir, "steam_ids.json"), "w") as f: f.write(json.dumps(grades)) f.close() @@ -74,14 +73,14 @@ def get_grade(steam_code): if steam_code == 0: return "fail" steam_code = str(steam_code) - url = 'https://www.protondb.com/api/v1/reports/summaries/' - res = requests.get(url + steam_code + '.json') + url = "https://www.protondb.com/api/v1/reports/summaries/" + res = requests.get(url + steam_code + ".json") try: lista = json.loads(res.text) except json.decoder.JSONDecodeError: return "fail" - return lista['tier'] + return lista["tier"] def load_json() -> dict: @@ -129,18 +128,18 @@ def get_steam_id(title: str): def check_time(): # this function check if it's time to update global file - text = open(file, 'r') + text = open(file, "r") json_table = json.load(text) text.close() today = date.today() day = 0 # it controls how many days it's necessary for an update - for i in 'ymd': - if i == 'd': + for i in "ymd": + if i == "d": day = 7 else: day = 0 - if int(today.strftime('%' + i)) > int(json_table['data'][i]) + day: + if int(today.strftime("%" + i)) > int(json_table["data"][i]) + day: return 1 else: return 0 diff --git a/rare/utils/utils.py b/rare/utils/utils.py index c2f6328f..c1cff184 100644 --- a/rare/utils/utils.py +++ b/rare/utils/utils.py @@ -10,7 +10,16 @@ from typing import Tuple, List import qtawesome import requests -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QRunnable, QSettings, Qt, QFile, QDir +from PyQt5.QtCore import ( + pyqtSignal, + pyqtSlot, + QObject, + QRunnable, + QSettings, + Qt, + QFile, + QDir, +) from PyQt5.QtGui import QPalette, QColor, QPixmap, QImage from PyQt5.QtWidgets import QApplication, QStyleFactory from requests.exceptions import HTTPError @@ -25,6 +34,7 @@ if platform.system() == "Windows": from win32com.client import Dispatch # pylint: disable=E0401 from rare import image_dir, shared, resources_path + # Mac not supported from legendary.core import LegendaryCore @@ -66,27 +76,46 @@ def download_image(game, force=False): # to get picture updates if not os.path.isfile(f"{image_dir}/{game.app_name}/image.json"): - json_data = {"DieselGameBoxTall": None, "DieselGameBoxLogo": None, "Thumbnail": None} + json_data = { + "DieselGameBoxTall": None, + "DieselGameBoxLogo": None, + "Thumbnail": None, + } else: json_data = json.load(open(f"{image_dir}/{game.app_name}/image.json", "r")) # Download for image in game.metadata["keyImages"]: - if image["type"] == "DieselGameBoxTall" or image["type"] == "DieselGameBoxLogo" or image["type"] == "Thumbnail": + if ( + image["type"] == "DieselGameBoxTall" + or image["type"] == "DieselGameBoxLogo" + or image["type"] == "Thumbnail" + ): if image["type"] not in json_data.keys(): json_data[image["type"]] = None if json_data[image["type"]] != image["md5"] or not os.path.isfile( - f"{image_dir}/{game.app_name}/{image['type']}.png"): + f"{image_dir}/{game.app_name}/{image['type']}.png" + ): # Download json_data[image["type"]] = image["md5"] # os.remove(f"{image_dir}/{game.app_name}/{image['type']}.png") - json.dump(json_data, open(f"{image_dir}/{game.app_name}/image.json", "w")) + json.dump( + json_data, open(f"{image_dir}/{game.app_name}/image.json", "w") + ) logger.info(f"Download Image for Game: {game.app_title}") url = image["url"] resp = requests.get(url) img = QImage() img.loadFromData(resp.content) - img = img.scaled(200, 200 * 4 // 3, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation) - img.save(os.path.join(image_dir, game.app_name, image["type"] + ".png"), format="PNG") + img = img.scaled( + 200, + 200 * 4 // 3, + Qt.KeepAspectRatio, + transformMode=Qt.SmoothTransformation, + ) + img.save( + os.path.join(image_dir, game.app_name, image["type"] + ".png"), + format="PNG", + ) color_role_map = { @@ -145,9 +174,13 @@ def load_color_scheme(path: str) -> QPalette: def set_color_pallete(color_scheme: str): if not color_scheme: - QApplication.instance().setStyle(QStyleFactory.create(QApplication.instance().property('rareDefaultQtStyle'))) + QApplication.instance().setStyle( + QStyleFactory.create(QApplication.instance().property("rareDefaultQtStyle")) + ) QApplication.instance().setStyleSheet("") - QApplication.instance().setPalette(QApplication.instance().style().standardPalette()) + QApplication.instance().setPalette( + QApplication.instance().style().standardPalette() + ) return QApplication.instance().setStyle(QStyleFactory.create("Fusion")) custom_palette = load_color_scheme(f":/schemes/{color_scheme}") @@ -166,7 +199,9 @@ def get_color_schemes() -> List[str]: def set_style_sheet(style_sheet: str): if not style_sheet: - QApplication.instance().setStyle(QStyleFactory.create(QApplication.instance().property('rareDefaultQtStyle'))) + QApplication.instance().setStyle( + QStyleFactory.create(QApplication.instance().property("rareDefaultQtStyle")) + ) QApplication.instance().setStyleSheet("") return QApplication.instance().setStyle(QStyleFactory.create("Fusion")) @@ -195,7 +230,9 @@ def get_translations(): def get_latest_version(): try: - resp = requests.get("https://api.github.com/repos/Dummerle/Rare/releases/latest") + resp = requests.get( + "https://api.github.com/repos/Dummerle/Rare/releases/latest" + ) tag = resp.json()["tag_name"] return tag except requests.exceptions.ConnectionError: @@ -203,7 +240,7 @@ def get_latest_version(): def get_size(b: int) -> str: - for i in ['', 'K', 'M', 'G', 'T', 'P', 'E']: + for i in ["", "K", "M", "G", "T", "P", "E"]: if b < 1024: return f"{b:.2f}{i}B" b /= 1024 @@ -226,21 +263,22 @@ def create_rare_desktop_link(type_of_link): else: executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}" with open(os.path.join(path, "Rare.desktop"), "w") as desktop_file: - desktop_file.write("[Desktop Entry]\n" - f"Name=Rare\n" - f"Type=Application\n" - f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n" - f"Exec={executable}\n" - "Terminal=false\n" - "StartupWMClass=rare\n" - ) + desktop_file.write( + "[Desktop Entry]\n" + f"Name=Rare\n" + f"Type=Application\n" + f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n" + f"Exec={executable}\n" + "Terminal=false\n" + "StartupWMClass=rare\n" + ) desktop_file.close() os.chmod(os.path.expanduser(os.path.join(path, "Rare.desktop")), 0o755) elif platform.system() == "Windows": # Target of shortcut if type_of_link == "desktop": - target_folder = os.path.expanduser('~/Desktop/') + target_folder = os.path.expanduser("~/Desktop/") elif type_of_link == "start_menu": target_folder = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu") else: @@ -255,7 +293,7 @@ def create_rare_desktop_link(type_of_link): executable = executable.replace("python.exe", "pythonw.exe") logger.debug(executable) # Add shortcut - shell = Dispatch('WScript.Shell') + shell = Dispatch("WScript.Shell") shortcut = shell.CreateShortCut(pathLink) shortcut.Targetpath = executable if not sys.executable.endswith("Rare.exe"): @@ -270,12 +308,16 @@ def create_rare_desktop_link(type_of_link): def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") -> bool: igame = core.get_installed_game(app_name) - if os.path.exists(p := os.path.join(image_dir, igame.app_name, 'Thumbnail.png')): + if os.path.exists(p := os.path.join(image_dir, igame.app_name, "Thumbnail.png")): icon = p - elif os.path.exists(p := os.path.join(image_dir, igame.app_name, "DieselGameBoxLogo.png")): + elif os.path.exists( + p := os.path.join(image_dir, igame.app_name, "DieselGameBoxLogo.png") + ): icon = p else: - icon = os.path.join(os.path.join(image_dir, igame.app_name, "DieselGameBoxTall.png")) + icon = os.path.join( + os.path.join(image_dir, igame.app_name, "DieselGameBoxTall.png") + ) icon = icon.replace(".png", "") # Linux @@ -293,14 +335,15 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") - else: executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}" with open(f"{path}{igame.title}.desktop", "w") as desktop_file: - desktop_file.write("[Desktop Entry]\n" - f"Name={igame.title}\n" - f"Type=Application\n" - f"Icon={icon}.png\n" - f"Exec={executable} launch {app_name}\n" - "Terminal=false\n" - "StartupWMClass=rare-game\n" - ) + desktop_file.write( + "[Desktop Entry]\n" + f"Name={igame.title}\n" + f"Type=Application\n" + f"Icon={icon}.png\n" + f"Exec={executable} launch {app_name}\n" + "Terminal=false\n" + "StartupWMClass=rare-game\n" + ) desktop_file.close() os.chmod(os.path.expanduser(f"{path}{igame.title}.desktop"), 0o755) @@ -308,7 +351,7 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") - elif platform.system() == "Windows": # Target of shortcut if type_of_link == "desktop": - target_folder = os.path.expanduser('~/Desktop/') + target_folder = os.path.expanduser("~/Desktop/") elif type_of_link == "start_menu": target_folder = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu") else: @@ -322,20 +365,20 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") - for c in r'<>?":|\/*': linkName.replace(c, "") - linkName = linkName.strip() + '.lnk' + linkName = linkName.strip() + ".lnk" # Path to location of link file pathLink = os.path.join(target_folder, linkName) # Add shortcut - shell = Dispatch('WScript.Shell') + shell = Dispatch("WScript.Shell") shortcut = shell.CreateShortCut(pathLink) if sys.executable.endswith("Rare.exe"): executable = sys.executable else: executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}" shortcut.Targetpath = executable - shortcut.Arguments = f'launch {app_name}' + shortcut.Arguments = f"launch {app_name}" shortcut.WorkingDirectory = os.getcwd() # Icon @@ -383,9 +426,7 @@ def optimal_text_background(image: list) -> Tuple[int, int, int]: return tuple(inverted) -def text_color_for_background(background: Tuple[int, int, int]) -> Tuple[int, - int, - int]: +def text_color_for_background(background: Tuple[int, int, int]) -> Tuple[int, int, int]: """ Calculates whether a black or white text color would fit better for the given background, and returns that color. This is done by calculating the @@ -393,10 +434,7 @@ def text_color_for_background(background: Tuple[int, int, int]) -> Tuple[int, """ # see https://alienryderflex.com/hsp.html (red, green, blue) = background - luminance = math.sqrt( - 0.299 * red ** 2 + - 0.587 * green ** 2 + - 0.114 * blue ** 2) + luminance = math.sqrt(0.299 * red ** 2 + 0.587 * green ** 2 + 0.114 * blue ** 2) if luminance < 127: return 255, 255, 255 else: @@ -408,45 +446,62 @@ class WineResolverSignals(QObject): class WineResolver(QRunnable): - def __init__(self, path: str, app_name: str, core: LegendaryCore): super(WineResolver, self).__init__() self.setAutoDelete(True) self.signals = WineResolverSignals() self.wine_env = os.environ.copy() self.wine_env.update(core.get_app_environment(app_name)) - self.wine_env['WINEDLLOVERRIDES'] = 'winemenubuilder=d;mscoree=d;mshtml=d;' - self.wine_env['DISPLAY'] = '' + self.wine_env["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;" + self.wine_env["DISPLAY"] = "" self.wine_binary = core.lgd.config.get( - app_name, 'wine_executable', - fallback=core.lgd.config.get('default', 'wine_executable', fallback='wine')) - self.winepath_binary = os.path.join(os.path.dirname(self.wine_binary), 'winepath') + app_name, + "wine_executable", + fallback=core.lgd.config.get("default", "wine_executable", fallback="wine"), + ) + self.winepath_binary = os.path.join( + os.path.dirname(self.wine_binary), "winepath" + ) self.path = PathSpec(core, app_name).cook(path) @pyqtSlot() def run(self): - if 'WINEPREFIX' not in self.wine_env or not os.path.exists(self.wine_env['WINEPREFIX']): + if "WINEPREFIX" not in self.wine_env or not os.path.exists( + self.wine_env["WINEPREFIX"] + ): # pylint: disable=E1136 self.signals.result_ready[str].emit(str()) return - if not os.path.exists(self.wine_binary) or not os.path.exists(self.winepath_binary): + if not os.path.exists(self.wine_binary) or not os.path.exists( + self.winepath_binary + ): # pylint: disable=E1136 self.signals.result_ready[str].emit(str()) return - path = self.path.strip().replace('/', '\\') + path = self.path.strip().replace("/", "\\") # lk: if path does not exist form - cmd = [self.wine_binary, 'cmd', '/c', 'echo', path] + cmd = [self.wine_binary, "cmd", "/c", "echo", path] # lk: if path exists and needs a case sensitive interpretation form # cmd = [self.wine_binary, 'cmd', '/c', f'cd {path} & cd'] - proc = subprocess.Popen(cmd, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env=self.wine_env, shell=False, text=True) + proc = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=self.wine_env, + shell=False, + text=True, + ) out, err = proc.communicate() # Clean wine output out = out.strip().strip('"') - proc = subprocess.Popen([self.winepath_binary, '-u', out], - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env=self.wine_env, shell=False, text=True) + proc = subprocess.Popen( + [self.winepath_binary, "-u", out], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=self.wine_env, + shell=False, + text=True, + ) out, err = proc.communicate() real_path = os.path.realpath(out.strip()) # pylint: disable=E1136 @@ -474,7 +529,11 @@ class CloudWorker(QRunnable): def get_raw_save_path(game: Game): if game.supports_cloud_saves: - return game.metadata.get("customAttributes", {}).get("CloudSaveFolder", {}).get("value") + return ( + game.metadata.get("customAttributes", {}) + .get("CloudSaveFolder", {}) + .get("value") + ) def get_default_platform(app_name):