From 76cd33054a7069bd88c9e506ec3a771b5e549847 Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sat, 2 Jul 2022 11:48:33 +0300
Subject: [PATCH 1/2] EGLSyncListGroup: Move message box outside of thread to
prevent "parent in other thread" crash
---
.../tabs/games/import_sync/egl_sync_group.py | 32 ++++++++++++-------
1 file changed, 21 insertions(+), 11 deletions(-)
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 eb096f5d..56df606c 100644
--- a/rare/components/tabs/games/import_sync/egl_sync_group.py
+++ b/rare/components/tabs/games/import_sync/egl_sync_group.py
@@ -1,9 +1,9 @@
import os
import platform
from logging import getLogger
-from typing import Tuple, Iterable
+from typing import Tuple, Iterable, List
-from PyQt5.QtCore import Qt, QThreadPool, QRunnable, pyqtSlot
+from PyQt5.QtCore import Qt, QThreadPool, QRunnable, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QGroupBox, QListWidgetItem, QFileDialog, QMessageBox
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
@@ -200,12 +200,16 @@ class EGLSyncListItem(QListWidgetItem):
class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
+ action_errors = pyqtSignal(list)
+
def __init__(self, export: bool, parent=None):
super(EGLSyncListGroup, self).__init__(parent=parent)
self.setupUi(self)
self.core = LegendaryCoreSingleton()
self.signals = GlobalSignalsSingleton()
self.list.setProperty("noBorder", 1)
+ # TODO: Convert the CSS and the code to adhere to NoFrame
+ # self.list.setFrameShape(self.list.NoFrame)
self.export = export
@@ -232,6 +236,8 @@ class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
self.action_button.clicked.connect(self.action)
+ self.action_errors.connect(self.show_errors)
+
def has_selected(self):
for item in self.items:
if item.is_checked():
@@ -258,8 +264,8 @@ class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
self.buttons_widget.setVisible(enabled and bool(self.list.count()))
def action(self):
- imported = list()
- errors = list()
+ imported: List = []
+ errors: List = []
for item in self.items:
if item.is_checked():
if e := item.action():
@@ -271,13 +277,17 @@ class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
self.signals.update_gamelist.emit(imported)
self.populate(True)
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.action_errors.emit(errors)
+
+ @pyqtSlot(list)
+ def show_errors(self, errors: List):
+ 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),
+ )
@property
def items(self) -> Iterable[EGLSyncListItem]:
From 0d6c9a850569ca39d2c0e1987961faa9c1fa4b5c Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Sat, 2 Jul 2022 19:22:12 +0300
Subject: [PATCH 2/2] ImportGroup: Show messagebox when importing multiple
games (scanning install folder)
I moved around a few things because the separate lists felt like they
would make the handling a bit complicated.
---
.../tabs/games/import_sync/import_group.py | 147 +++++++++++-------
.../tabs/games/import_sync/import_group.py | 46 +++---
.../tabs/games/import_sync/import_group.ui | 51 +++---
3 files changed, 138 insertions(+), 106 deletions(-)
diff --git a/rare/components/tabs/games/import_sync/import_group.py b/rare/components/tabs/games/import_sync/import_group.py
index f606094a..18ca268a 100644
--- a/rare/components/tabs/games/import_sync/import_group.py
+++ b/rare/components/tabs/games/import_sync/import_group.py
@@ -1,13 +1,14 @@
import json
import os
from dataclasses import dataclass
+from enum import IntEnum
from logging import getLogger
from pathlib import Path
from typing import List, Tuple, Optional
-from PyQt5.QtCore import Qt, QModelIndex, pyqtSignal, QRunnable, QObject, QThreadPool
+from PyQt5.QtCore import Qt, QModelIndex, pyqtSignal, QRunnable, QObject, QThreadPool, pyqtSlot
from PyQt5.QtGui import QStandardItemModel
-from PyQt5.QtWidgets import QFileDialog, QGroupBox, QCompleter, QTreeView, QHeaderView, QApplication, QMessageBox
+from PyQt5.QtWidgets import QFileDialog, QGroupBox, QCompleter, QTreeView, QHeaderView, qApp, QMessageBox
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ApiResultsSingleton
from rare.ui.components.tabs.games.import_sync.import_group import Ui_ImportGroup
@@ -34,22 +35,22 @@ def find_app_name(path: str, core) -> Optional[str]:
return None
-@dataclass
-class ResultGame:
- app_name: str
- error: Optional[str] = None
+class ImportResult(IntEnum):
+ ERROR = 0
+ FAILED = 1
+ SUCCESS = 2
@dataclass
-class Result:
- successful_games: List[ResultGame] = None
- failed_games: List[ResultGame] = None
- error_messages: List[str] = None
+class ImportedGame:
+ result: ImportResult
+ app_name: Optional[str] = None
+ message: Optional[str] = None
class ImportWorker(QRunnable):
class Signals(QObject):
- finished = pyqtSignal(Result)
+ finished = pyqtSignal(list)
progress = pyqtSignal(int)
def __init__(self, path: str, import_folder: bool = False, app_name: str = None):
@@ -59,39 +60,37 @@ class ImportWorker(QRunnable):
self.path = Path(path)
self.import_folder = import_folder
self.app_name = app_name
- self.tr = lambda message: QApplication.translate("ImportThread", message)
+ self.tr = lambda message: qApp.translate("ImportThread", message)
def run(self) -> None:
- result = Result([], [], [])
+ result_list: List = []
if self.import_folder:
- number_of_folders = len(list(self.path.iterdir()))
- for i, child in enumerate(self.path.iterdir()):
+ folders = [i for i in self.path.iterdir() if i.is_dir()]
+ number_of_folders = len(folders)
+ for i, child in enumerate(folders):
if not child.is_dir():
continue
- if (app_name := find_app_name(str(child), self.core)) is not None:
- logger.debug(f"Found app_name {app_name} for {child}")
- err = self.__import_game(app_name, child)
- if err:
- result.failed_games.append(ResultGame(app_name, err))
- else:
- result.successful_games.append(ResultGame(app_name))
- else:
- result.error_messages.append(self.tr("Could not find AppName for {}").format(child))
+ result = self.__try_import(child, None)
+ result_list.append(result)
self.signals.progress.emit(int(100 * i // number_of_folders))
else:
- if not self.app_name:
- # try to find app name
- if a_n := find_app_name(str(self.path), self.core):
- self.app_name = a_n
- else:
- result.error_messages.append(self.tr("Could not find AppName for {}").format(str(self.path)))
- return
- err = self.__import_game(self.app_name, self.path)
+ result = self.__try_import(self.path, self.app_name)
+ result_list.append(result)
+ self.signals.finished.emit(result_list)
+
+ def __try_import(self, path: Path, app_name: str = None) -> ImportedGame:
+ result = ImportedGame(ImportResult.ERROR, None, None)
+ if app_name or (app_name := find_app_name(str(path), self.core)):
+ result.app_name = app_name
+ err = self.__import_game(app_name, path)
if err:
- result.failed_games.append(ResultGame(self.app_name, err))
+ result.result = ImportResult.FAILED
+ result.message = err
else:
- result.successful_games.append(ResultGame(self.app_name))
- self.signals.finished.emit(result)
+ result.result = ImportResult.SUCCESS
+ else:
+ result.message = self.tr("Could not find AppName for {}").format(str(path))
+ return result
def __import_game(self, app_name: str, path: Path) -> str:
if not (err := legendary_utils.import_game(self.core, app_name=app_name, path=str(path))):
@@ -205,12 +204,12 @@ class ImportGroup(QGroupBox):
return False, path, ""
def path_changed(self, path):
- self.ui.info_label.setText(str())
+ self.ui.info_label.setText("")
self.ui.import_folder_check.setChecked(False)
if self.path_edit.is_valid:
self.app_name_edit.setText(find_app_name(path, self.core))
else:
- self.app_name_edit.setText(str())
+ self.app_name_edit.setText("")
def app_name_edit_cb(self, text) -> Tuple[bool, str, str]:
if not text:
@@ -221,7 +220,7 @@ class ImportGroup(QGroupBox):
return False, text, IndicatorLineEdit.reasons.game_not_installed
def app_name_changed(self, text):
- self.ui.info_label.setText(str())
+ self.ui.info_label.setText("")
if self.app_name_edit.is_valid:
self.ui.import_button.setEnabled(True)
else:
@@ -235,33 +234,63 @@ class ImportGroup(QGroupBox):
worker.signals.progress.connect(self.import_progress)
self.threadpool.start(worker)
- def import_finished(self, result: Result):
- logger.info(f"Import finished: {result.__dict__}")
- if result.successful_games:
- self.signals.update_gamelist.emit([i.app_name for i in result.successful_games])
- if len(result.successful_games) == 1:
+ @pyqtSlot(list)
+ def import_finished(self, result: List):
+ logger.info(f"Import finished: {result}")
+
+ self.signals.update_gamelist.emit([r.app_name for r in result if r.result == ImportResult.SUCCESS])
+
+ for failed in (f for f in result if f.result == ImportResult.FAILED):
+ igame = self.core.get_installed_game(failed.app_name)
+ if igame and igame.version != self.core.get_asset(igame.app_name, igame.platform, False).build_version:
+ # update available
+ self.signals.add_download.emit(igame.app_name)
+ self.signals.update_download_tab_text.emit()
+
+ if len(result) == 1:
+ res = result[0]
+ if res.result == ImportResult.SUCCESS:
self.ui.info_label.setText(
- self.tr(f"{self.core.get_game(result.successful_games[0].app_name).app_title} imported successfully: ")
+ self.tr("{} was imported successfully").format(self.core.get_game(res.app_name).app_title)
+ )
+ elif res.result == ImportResult.FAILED:
+ self.ui.info_label.setText(
+ self.tr("Failed: {}").format(res.message)
)
else:
self.ui.info_label.setText(
- self.tr("Imported {} games successfully".format(len(result.successful_games)))
+ self.tr("Error: {}").format(res.message)
)
- for res_game in result.failed_games:
- igame = self.core.get_installed_game(res_game.app_name)
- if igame.version != self.core.get_asset(igame.app_name, igame.platform, False).build_version:
- # update available
- self.signals.add_download.emit(igame.app_name)
- self.signals.update_download_tab_text.emit()
-
- if result.failed_games:
- self.ui.info_label.setText(
- self.tr(f"Failed to import: "
- f"{', '.join([self.core.get_game(i.app_name).app_title for i in result.failed_games])}")
+ else:
+ success = [r for r in result if r.result == ImportResult.SUCCESS]
+ failure = [r for r in result if r.result == ImportResult.FAILED]
+ errored = [r for r in result if r.result == ImportResult.ERROR]
+ messagebox = QMessageBox(
+ QMessageBox.Information,
+ self.tr("Import summary"),
+ self.tr(
+ "Tried to import {} folders.\n\n"
+ "Successfully imported {} games, failed to import {} games and {} errors occurred"
+ ).format(len(success) + len(failure) + len(errored), len(success), len(failure), len(errored)),
+ buttons=QMessageBox.StandardButton.Close,
+ parent=self,
)
- if result.error_messages:
- QMessageBox.warning(self, self.tr("Error"), self.tr("\n".join(result.error_messages)))
-
+ messagebox.setWindowModality(Qt.NonModal)
+ details: List = []
+ for res in success:
+ details.append(
+ self.tr("{} was imported successfully").format(self.core.get_game(res.app_name).app_title)
+ )
+ for res in failure:
+ details.append(
+ self.tr("Failed: {}").format(res.message)
+ )
+ for res in errored:
+ details.append(
+ self.tr("Error: {}").format(res.message)
+ )
+ messagebox.setDetailedText("\n".join(details))
+ messagebox.show()
def import_progress(self, progress: int):
pass
diff --git a/rare/ui/components/tabs/games/import_sync/import_group.py b/rare/ui/components/tabs/games/import_sync/import_group.py
index f1e8222e..47fa08c2 100644
--- a/rare/ui/components/tabs/games/import_sync/import_group.py
+++ b/rare/ui/components/tabs/games/import_sync/import_group.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'rare/ui/components/tabs/games/import_sync/import_group.ui'
#
-# Created by: PyQt5 UI code generator 5.15.6
+# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
@@ -14,31 +14,34 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_ImportGroup(object):
def setupUi(self, ImportGroup):
ImportGroup.setObjectName("ImportGroup")
- ImportGroup.resize(501, 154)
+ ImportGroup.resize(501, 136)
ImportGroup.setWindowTitle("ImportGroup")
ImportGroup.setWindowFilePath("")
- self.import_layout = QtWidgets.QFormLayout(ImportGroup)
- self.import_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.import_layout.setObjectName("import_layout")
+ self.formLayout = QtWidgets.QFormLayout(ImportGroup)
+ self.formLayout.setObjectName("formLayout")
self.path_edit_label = QtWidgets.QLabel(ImportGroup)
self.path_edit_label.setObjectName("path_edit_label")
- self.import_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.path_edit_label)
+ self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.path_edit_label)
self.path_edit_layout = QtWidgets.QHBoxLayout()
self.path_edit_layout.setObjectName("path_edit_layout")
- self.import_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.path_edit_layout)
+ self.formLayout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.path_edit_layout)
self.app_name_label = QtWidgets.QLabel(ImportGroup)
self.app_name_label.setObjectName("app_name_label")
- self.import_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.app_name_label)
+ self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.app_name_label)
self.app_name_layout = QtWidgets.QHBoxLayout()
self.app_name_layout.setObjectName("app_name_layout")
- self.import_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.app_name_layout)
+ self.formLayout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.app_name_layout)
self.import_folder_label = QtWidgets.QLabel(ImportGroup)
self.import_folder_label.setObjectName("import_folder_label")
- self.import_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.import_folder_label)
- self.info_label = QtWidgets.QLabel(ImportGroup)
- self.info_label.setText("")
- self.info_label.setObjectName("info_label")
- self.import_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.info_label)
+ self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.import_folder_label)
+ self.import_folder_check = QtWidgets.QCheckBox(ImportGroup)
+ font = QtGui.QFont()
+ font.setItalic(True)
+ self.import_folder_check.setFont(font)
+ self.import_folder_check.setObjectName("import_folder_check")
+ self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.import_folder_check)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
self.import_button = QtWidgets.QPushButton(ImportGroup)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -46,13 +49,12 @@ class Ui_ImportGroup(object):
sizePolicy.setHeightForWidth(self.import_button.sizePolicy().hasHeightForWidth())
self.import_button.setSizePolicy(sizePolicy)
self.import_button.setObjectName("import_button")
- self.import_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.import_button)
- self.import_folder_check = QtWidgets.QCheckBox(ImportGroup)
- font = QtGui.QFont()
- font.setItalic(True)
- self.import_folder_check.setFont(font)
- self.import_folder_check.setObjectName("import_folder_check")
- self.import_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.import_folder_check)
+ self.horizontalLayout.addWidget(self.import_button)
+ self.info_label = QtWidgets.QLabel(ImportGroup)
+ self.info_label.setText("")
+ self.info_label.setObjectName("info_label")
+ self.horizontalLayout.addWidget(self.info_label)
+ self.formLayout.setLayout(3, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout)
self.retranslateUi(ImportGroup)
QtCore.QMetaObject.connectSlotsByName(ImportGroup)
@@ -63,8 +65,8 @@ class Ui_ImportGroup(object):
self.path_edit_label.setText(_translate("ImportGroup", "Installation path"))
self.app_name_label.setText(_translate("ImportGroup", "Override app name"))
self.import_folder_label.setText(_translate("ImportGroup", "Import all folders"))
- self.import_button.setText(_translate("ImportGroup", "Import Game"))
self.import_folder_check.setText(_translate("ImportGroup", "Scan the installation path for game folders and import them"))
+ self.import_button.setText(_translate("ImportGroup", "Import Game"))
if __name__ == "__main__":
diff --git a/rare/ui/components/tabs/games/import_sync/import_group.ui b/rare/ui/components/tabs/games/import_sync/import_group.ui
index 0545451f..1f149d1f 100644
--- a/rare/ui/components/tabs/games/import_sync/import_group.ui
+++ b/rare/ui/components/tabs/games/import_sync/import_group.ui
@@ -7,7 +7,7 @@
0
0
501
- 154
+ 136
@@ -19,10 +19,7 @@
Import EGL game from a directory
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
+
-
@@ -50,26 +47,6 @@
- -
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Import Game
-
-
-
-
@@ -82,6 +59,30 @@
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Import Game
+
+
+
+ -
+
+
+
+
+
+
+
+