From da07d6b7910c255ea95ef16503ad6a73ee7acb8f Mon Sep 17 00:00:00 2001
From: Dummerle <44114474+Dummerle@users.noreply.github.com>
Date: Sun, 31 Jul 2022 23:55:21 +0200
Subject: [PATCH 1/5] Move some options in install dialog to advanced options
---
rare/components/dialogs/install_dialog.py | 14 +-
rare/ui/components/dialogs/install_dialog.py | 202 +++++-----
rare/ui/components/dialogs/install_dialog.ui | 366 ++++++++++---------
rare/widgets/collabsible_widget.py | 86 +++++
4 files changed, 401 insertions(+), 267 deletions(-)
create mode 100644 rare/widgets/collabsible_widget.py
diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py
index 65501956..683bed1c 100644
--- a/rare/components/dialogs/install_dialog.py
+++ b/rare/components/dialogs/install_dialog.py
@@ -10,17 +10,18 @@ from legendary.models.downloading import ConditionCheckResult
from legendary.models.game import Game
from legendary.utils.selective_dl import get_sdl_appname
-from rare.lgndr.cli import LegendaryCLI
from rare.lgndr.api_arguments import LgndrInstallGameArgs
from rare.lgndr.api_exception import LgndrException
from rare.lgndr.api_monkeys import LgndrIndirectStatus
+from rare.lgndr.cli import LegendaryCLI
from rare.lgndr.core import LegendaryCore
+from rare.models.install import InstallDownloadModel, InstallQueueItemModel
from rare.shared import LegendaryCoreSingleton, ApiResultsSingleton, ArgumentsSingleton
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
-from rare.utils.extra_widgets import PathEdit
-from rare.models.install import InstallDownloadModel, InstallQueueItemModel
-from rare.utils.misc import get_size
from rare.utils import config_helper
+from rare.utils.extra_widgets import PathEdit
+from rare.utils.misc import get_size
+from rare.widgets.collabsible_widget import CollabsibleWidget
class InstallDialog(QDialog, Ui_InstallDialog):
@@ -41,6 +42,11 @@ class InstallDialog(QDialog, Ui_InstallDialog):
if not self.dl_item.options.overlay
else Game(app_name=self.app_name, app_title="Epic Overlay")
)
+ self.advanced_layout.setParent(None)
+ self.advanced_widget = CollabsibleWidget(
+ self.advanced_layout, self.tr("Advanced options"), parent=self
+ )
+ self.advanced_placeholder_layout.addWidget(self.advanced_widget)
self.game_path = self.game.metadata.get("customAttributes", {}).get("FolderName", {}).get("value", "")
diff --git a/rare/ui/components/dialogs/install_dialog.py b/rare/ui/components/dialogs/install_dialog.py
index dd4baa58..3696b8ed 100644
--- a/rare/ui/components/dialogs/install_dialog.py
+++ b/rare/ui/components/dialogs/install_dialog.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'rare/ui/components/dialogs/install_dialog.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,11 +14,13 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_InstallDialog(object):
def setupUi(self, InstallDialog):
InstallDialog.setObjectName("InstallDialog")
- InstallDialog.resize(406, 447)
+ InstallDialog.resize(526, 483)
InstallDialog.setWindowTitle("Rare")
self.install_dialog_layout = QtWidgets.QFormLayout(InstallDialog)
self.install_dialog_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
- self.install_dialog_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.install_dialog_layout.setLabelAlignment(
+ QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter
+ )
self.install_dialog_layout.setObjectName("install_dialog_layout")
self.install_dialog_label = QtWidgets.QLabel(InstallDialog)
self.install_dialog_label.setObjectName("install_dialog_label")
@@ -29,38 +31,6 @@ class Ui_InstallDialog(object):
self.install_dir_layout = QtWidgets.QHBoxLayout()
self.install_dir_layout.setObjectName("install_dir_layout")
self.install_dialog_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.install_dir_layout)
- self.max_workers_label = QtWidgets.QLabel(InstallDialog)
- self.max_workers_label.setObjectName("max_workers_label")
- self.install_dialog_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.max_workers_label)
- self.max_workers_layout = QtWidgets.QHBoxLayout()
- self.max_workers_layout.setObjectName("max_workers_layout")
- self.max_workers_spin = QtWidgets.QSpinBox(InstallDialog)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.max_workers_spin.sizePolicy().hasHeightForWidth())
- self.max_workers_spin.setSizePolicy(sizePolicy)
- self.max_workers_spin.setObjectName("max_workers_spin")
- self.max_workers_layout.addWidget(self.max_workers_spin)
- self.max_workers_info_label = QtWidgets.QLabel(InstallDialog)
- font = QtGui.QFont()
- font.setItalic(True)
- self.max_workers_info_label.setFont(font)
- self.max_workers_info_label.setObjectName("max_workers_info_label")
- self.max_workers_layout.addWidget(self.max_workers_info_label)
- self.install_dialog_layout.setLayout(3, QtWidgets.QFormLayout.FieldRole, self.max_workers_layout)
- self.force_download_label = QtWidgets.QLabel(InstallDialog)
- self.force_download_label.setObjectName("force_download_label")
- self.install_dialog_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.force_download_label)
- self.force_download_check = QtWidgets.QCheckBox(InstallDialog)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.force_download_check.sizePolicy().hasHeightForWidth())
- self.force_download_check.setSizePolicy(sizePolicy)
- self.force_download_check.setText("")
- self.force_download_check.setObjectName("force_download_check")
- self.install_dialog_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.force_download_check)
self.platform_label = QtWidgets.QLabel(InstallDialog)
self.platform_label.setObjectName("platform_label")
self.install_dialog_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.platform_label)
@@ -72,12 +42,6 @@ class Ui_InstallDialog(object):
self.platform_combo_box.setSizePolicy(sizePolicy)
self.platform_combo_box.setObjectName("platform_combo_box")
self.install_dialog_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.platform_combo_box)
- self.ignore_space_label = QtWidgets.QLabel(InstallDialog)
- self.ignore_space_label.setObjectName("ignore_space_label")
- self.install_dialog_layout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.ignore_space_label)
- self.download_only_label = QtWidgets.QLabel(InstallDialog)
- self.download_only_label.setObjectName("download_only_label")
- self.install_dialog_layout.setWidget(8, QtWidgets.QFormLayout.LabelRole, self.download_only_label)
self.shortcut_lbl = QtWidgets.QLabel(InstallDialog)
self.shortcut_lbl.setObjectName("shortcut_lbl")
self.install_dialog_layout.setWidget(9, QtWidgets.QFormLayout.LabelRole, self.shortcut_lbl)
@@ -85,6 +49,16 @@ class Ui_InstallDialog(object):
self.shortcut_cb.setText("")
self.shortcut_cb.setObjectName("shortcut_cb")
self.install_dialog_layout.setWidget(9, QtWidgets.QFormLayout.FieldRole, self.shortcut_cb)
+ self.install_preqs_lbl = QtWidgets.QLabel(InstallDialog)
+ self.install_preqs_lbl.setObjectName("install_preqs_lbl")
+ self.install_dialog_layout.setWidget(10, QtWidgets.QFormLayout.LabelRole, self.install_preqs_lbl)
+ self.install_preqs_check = QtWidgets.QCheckBox(InstallDialog)
+ font = QtGui.QFont()
+ font.setItalic(True)
+ self.install_preqs_check.setFont(font)
+ self.install_preqs_check.setText("")
+ self.install_preqs_check.setObjectName("install_preqs_check")
+ self.install_dialog_layout.setWidget(10, QtWidgets.QFormLayout.FieldRole, self.install_preqs_check)
self.sdl_list_label = QtWidgets.QLabel(InstallDialog)
self.sdl_list_label.setObjectName("sdl_list_label")
self.install_dialog_layout.setWidget(11, QtWidgets.QFormLayout.LabelRole, self.sdl_list_label)
@@ -104,7 +78,9 @@ class Ui_InstallDialog(object):
font.setItalic(True)
self.download_size_info_label.setFont(font)
self.download_size_info_label.setObjectName("download_size_info_label")
- self.install_dialog_layout.setWidget(13, QtWidgets.QFormLayout.FieldRole, self.download_size_info_label)
+ self.install_dialog_layout.setWidget(
+ 13, QtWidgets.QFormLayout.FieldRole, self.download_size_info_label
+ )
self.install_size_label = QtWidgets.QLabel(InstallDialog)
self.install_size_label.setObjectName("install_size_label")
self.install_dialog_layout.setWidget(14, QtWidgets.QFormLayout.LabelRole, self.install_size_label)
@@ -114,7 +90,12 @@ class Ui_InstallDialog(object):
self.install_size_info_label.setFont(font)
self.install_size_info_label.setWordWrap(True)
self.install_size_info_label.setObjectName("install_size_info_label")
- self.install_dialog_layout.setWidget(14, QtWidgets.QFormLayout.FieldRole, self.install_size_info_label)
+ self.install_dialog_layout.setWidget(
+ 14, QtWidgets.QFormLayout.FieldRole, self.install_size_info_label
+ )
+ self.warn_label = QtWidgets.QLabel(InstallDialog)
+ self.warn_label.setObjectName("warn_label")
+ self.install_dialog_layout.setWidget(15, QtWidgets.QFormLayout.LabelRole, self.warn_label)
self.warn_message = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont()
font.setItalic(True)
@@ -124,12 +105,11 @@ class Ui_InstallDialog(object):
self.warn_message.setWordWrap(True)
self.warn_message.setObjectName("warn_message")
self.install_dialog_layout.setWidget(15, QtWidgets.QFormLayout.FieldRole, self.warn_message)
- self.warn_label = QtWidgets.QLabel(InstallDialog)
- self.warn_label.setObjectName("warn_label")
- self.install_dialog_layout.setWidget(15, QtWidgets.QFormLayout.LabelRole, self.warn_label)
self.button_layout = QtWidgets.QHBoxLayout()
self.button_layout.setObjectName("button_layout")
- spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ spacerItem = QtWidgets.QSpacerItem(
+ 40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
+ )
self.button_layout.addItem(spacerItem)
self.cancel_button = QtWidgets.QPushButton(InstallDialog)
self.cancel_button.setObjectName("cancel_button")
@@ -140,42 +120,41 @@ class Ui_InstallDialog(object):
self.install_button = QtWidgets.QPushButton(InstallDialog)
self.install_button.setObjectName("install_button")
self.button_layout.addWidget(self.install_button)
- self.install_dialog_layout.setLayout(16, QtWidgets.QFormLayout.SpanningRole, self.button_layout)
- self.install_preqs_lbl = QtWidgets.QLabel(InstallDialog)
- self.install_preqs_lbl.setObjectName("install_preqs_lbl")
- self.install_dialog_layout.setWidget(10, QtWidgets.QFormLayout.LabelRole, self.install_preqs_lbl)
- self.install_preqs_check = QtWidgets.QCheckBox(InstallDialog)
- font = QtGui.QFont()
- font.setItalic(True)
- self.install_preqs_check.setFont(font)
- self.install_preqs_check.setText("")
- self.install_preqs_check.setObjectName("install_preqs_check")
- self.install_dialog_layout.setWidget(10, QtWidgets.QFormLayout.FieldRole, self.install_preqs_check)
- self.ignore_space_check = QtWidgets.QCheckBox(InstallDialog)
+ self.install_dialog_layout.setLayout(17, QtWidgets.QFormLayout.SpanningRole, self.button_layout)
+ self.advanced_layout = QtWidgets.QFormLayout()
+ self.advanced_layout.setLabelAlignment(
+ QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter
+ )
+ self.advanced_layout.setObjectName("advanced_layout")
+ self.max_workers_label = QtWidgets.QLabel(InstallDialog)
+ self.max_workers_label.setAlignment(
+ QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
+ )
+ self.max_workers_label.setObjectName("max_workers_label")
+ self.advanced_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.max_workers_label)
+ self.max_workers_layout = QtWidgets.QHBoxLayout()
+ self.max_workers_layout.setObjectName("max_workers_layout")
+ self.max_workers_spin = QtWidgets.QSpinBox(InstallDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.ignore_space_check.sizePolicy().hasHeightForWidth())
- self.ignore_space_check.setSizePolicy(sizePolicy)
+ sizePolicy.setHeightForWidth(self.max_workers_spin.sizePolicy().hasHeightForWidth())
+ self.max_workers_spin.setSizePolicy(sizePolicy)
+ self.max_workers_spin.setObjectName("max_workers_spin")
+ self.max_workers_layout.addWidget(self.max_workers_spin)
+ self.max_workers_info_label = QtWidgets.QLabel(InstallDialog)
font = QtGui.QFont()
font.setItalic(True)
- self.ignore_space_check.setFont(font)
- self.ignore_space_check.setObjectName("ignore_space_check")
- self.install_dialog_layout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.ignore_space_check)
- self.download_only_check = QtWidgets.QCheckBox(InstallDialog)
- sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
- sizePolicy.setHorizontalStretch(0)
- sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.download_only_check.sizePolicy().hasHeightForWidth())
- self.download_only_check.setSizePolicy(sizePolicy)
- font = QtGui.QFont()
- font.setItalic(True)
- self.download_only_check.setFont(font)
- self.download_only_check.setObjectName("download_only_check")
- self.install_dialog_layout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.download_only_check)
+ self.max_workers_info_label.setFont(font)
+ self.max_workers_info_label.setObjectName("max_workers_info_label")
+ self.max_workers_layout.addWidget(self.max_workers_info_label)
+ self.advanced_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.max_workers_layout)
self.max_memory_label = QtWidgets.QLabel(InstallDialog)
+ self.max_memory_label.setAlignment(
+ QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
+ )
self.max_memory_label.setObjectName("max_memory_label")
- self.install_dialog_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.max_memory_label)
+ self.advanced_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.max_memory_label)
self.max_memory_layout = QtWidgets.QHBoxLayout()
self.max_memory_layout.setObjectName("max_memory_layout")
self.max_memory_spin = QtWidgets.QSpinBox(InstallDialog)
@@ -196,15 +175,61 @@ class Ui_InstallDialog(object):
self.max_memory_info_label.setFont(font)
self.max_memory_info_label.setObjectName("max_memory_info_label")
self.max_memory_layout.addWidget(self.max_memory_info_label)
- self.install_dialog_layout.setLayout(4, QtWidgets.QFormLayout.FieldRole, self.max_memory_layout)
+ self.advanced_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.max_memory_layout)
self.dl_optimizations_label = QtWidgets.QLabel(InstallDialog)
self.dl_optimizations_label.setObjectName("dl_optimizations_label")
- self.install_dialog_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.dl_optimizations_label)
+ self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.dl_optimizations_label)
self.dl_optimizations_check = QtWidgets.QCheckBox(InstallDialog)
self.dl_optimizations_check.setText("")
self.dl_optimizations_check.setChecked(False)
self.dl_optimizations_check.setObjectName("dl_optimizations_check")
- self.install_dialog_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.dl_optimizations_check)
+ self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.dl_optimizations_check)
+ self.force_download_label = QtWidgets.QLabel(InstallDialog)
+ self.force_download_label.setObjectName("force_download_label")
+ self.advanced_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.force_download_label)
+ self.force_download_check = QtWidgets.QCheckBox(InstallDialog)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.force_download_check.sizePolicy().hasHeightForWidth())
+ self.force_download_check.setSizePolicy(sizePolicy)
+ self.force_download_check.setText("")
+ self.force_download_check.setObjectName("force_download_check")
+ self.advanced_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.force_download_check)
+ self.ignore_space_label = QtWidgets.QLabel(InstallDialog)
+ self.ignore_space_label.setObjectName("ignore_space_label")
+ self.advanced_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.ignore_space_label)
+ self.ignore_space_check = QtWidgets.QCheckBox(InstallDialog)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.ignore_space_check.sizePolicy().hasHeightForWidth())
+ self.ignore_space_check.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setItalic(True)
+ self.ignore_space_check.setFont(font)
+ self.ignore_space_check.setObjectName("ignore_space_check")
+ self.advanced_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.ignore_space_check)
+ self.download_only_label = QtWidgets.QLabel(InstallDialog)
+ self.download_only_label.setObjectName("download_only_label")
+ self.advanced_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.download_only_label)
+ self.download_only_check = QtWidgets.QCheckBox(InstallDialog)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.download_only_check.sizePolicy().hasHeightForWidth())
+ self.download_only_check.setSizePolicy(sizePolicy)
+ font = QtGui.QFont()
+ font.setItalic(True)
+ self.download_only_check.setFont(font)
+ self.download_only_check.setObjectName("download_only_check")
+ self.advanced_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.download_only_check)
+ self.install_dialog_layout.setLayout(16, QtWidgets.QFormLayout.SpanningRole, self.advanced_layout)
+ self.advanced_placeholder_layout = QtWidgets.QVBoxLayout()
+ self.advanced_placeholder_layout.setObjectName("advanced_placeholder_layout")
+ self.install_dialog_layout.setLayout(
+ 12, QtWidgets.QFormLayout.SpanningRole, self.advanced_placeholder_layout
+ )
self.retranslateUi(InstallDialog)
QtCore.QMetaObject.connectSlotsByName(InstallDialog)
@@ -213,34 +238,35 @@ class Ui_InstallDialog(object):
_translate = QtCore.QCoreApplication.translate
self.install_dialog_label.setText(_translate("InstallDialog", "error"))
self.install_dir_label.setText(_translate("InstallDialog", "Install directory"))
- self.max_workers_label.setText(_translate("InstallDialog", "Max workers"))
- self.max_workers_info_label.setText(_translate("InstallDialog", "Less is slower. (0: Default)"))
- self.force_download_label.setText(_translate("InstallDialog", "Force redownload"))
self.platform_label.setText(_translate("InstallDialog", "Platform"))
- self.ignore_space_label.setText(_translate("InstallDialog", "Ignore free space"))
- self.download_only_label.setText(_translate("InstallDialog", "Download only"))
self.shortcut_lbl.setText(_translate("InstallDialog", "Create shortcut"))
+ self.install_preqs_lbl.setText(_translate("InstallDialog", "Install prerequisites"))
self.sdl_list_label.setText(_translate("InstallDialog", "Optional packs"))
self.download_size_label.setText(_translate("InstallDialog", "Download size"))
self.download_size_info_label.setText(_translate("InstallDialog", "Click verify..."))
self.install_size_label.setText(_translate("InstallDialog", "Total install size"))
self.install_size_info_label.setText(_translate("InstallDialog", "Click verify..."))
- self.warn_message.setText(_translate("InstallDialog", "None"))
self.warn_label.setText(_translate("InstallDialog", "Warning"))
+ self.warn_message.setText(_translate("InstallDialog", "None"))
self.cancel_button.setText(_translate("InstallDialog", "Cancel"))
self.verify_button.setText(_translate("InstallDialog", "Verify"))
self.install_button.setText(_translate("InstallDialog", "Install"))
- self.install_preqs_lbl.setText(_translate("InstallDialog", "Install prerequisites"))
- self.ignore_space_check.setText(_translate("InstallDialog", "Use with caution!"))
- self.download_only_check.setText(_translate("InstallDialog", "Do not try to install."))
+ self.max_workers_label.setText(_translate("InstallDialog", "Max workers"))
+ self.max_workers_info_label.setText(_translate("InstallDialog", "Less is slower. (0: Default)"))
self.max_memory_label.setText(_translate("InstallDialog", "Max shared memory"))
self.max_memory_spin.setSuffix(_translate("InstallDialog", "MiB"))
self.max_memory_info_label.setText(_translate("InstallDialog", "Less is slower (0: Default)"))
self.dl_optimizations_label.setText(_translate("InstallDialog", "Enable reordering"))
+ self.force_download_label.setText(_translate("InstallDialog", "Force redownload"))
+ self.ignore_space_label.setText(_translate("InstallDialog", "Ignore free space"))
+ self.ignore_space_check.setText(_translate("InstallDialog", "Use with caution!"))
+ self.download_only_label.setText(_translate("InstallDialog", "Download only"))
+ self.download_only_check.setText(_translate("InstallDialog", "Do not try to install."))
if __name__ == "__main__":
import sys
+
app = QtWidgets.QApplication(sys.argv)
InstallDialog = QtWidgets.QDialog()
ui = Ui_InstallDialog()
diff --git a/rare/ui/components/dialogs/install_dialog.ui b/rare/ui/components/dialogs/install_dialog.ui
index fdfd2aa3..03875d60 100644
--- a/rare/ui/components/dialogs/install_dialog.ui
+++ b/rare/ui/components/dialogs/install_dialog.ui
@@ -6,8 +6,8 @@
0
0
- 406
- 447
+ 526
+ 483
@@ -37,59 +37,6 @@
-
- -
-
-
- Max workers
-
-
-
- -
-
-
-
-
-
-
- 0
- 0
-
-
-
-
- -
-
-
-
- true
-
-
-
- Less is slower. (0: Default)
-
-
-
-
-
- -
-
-
- Force redownload
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
-
-
-
-
@@ -107,20 +54,6 @@
- -
-
-
- Ignore free space
-
-
-
- -
-
-
- Download only
-
-
-
-
@@ -135,6 +68,25 @@
+ -
+
+
+ Install prerequisites
+
+
+
+ -
+
+
+
+ true
+
+
+
+
+
+
+
-
@@ -198,6 +150,13 @@
+ -
+
+
+ Warning
+
+
+
-
@@ -219,14 +178,7 @@
- -
-
-
- Warning
-
-
-
- -
+
-
-
@@ -264,125 +216,189 @@
- -
-
-
- Install prerequisites
+
-
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
- -
-
-
-
- true
-
-
-
-
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- true
-
-
-
- Use with caution!
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
- true
-
-
-
- Do not try to install.
-
-
-
- -
-
-
- Max shared memory
-
-
-
- -
-
-
-
-
+
-
+
+
+ Max workers
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+
+ true
+
+
+
+ Less is slower. (0: Default)
+
+
+
+
+
+ -
+
+
+ Max shared memory
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+
+ -
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ MiB
+
+
+ 0
+
+
+ 10240
+
+
+ 128
+
+
+ 1024
+
+
+
+ -
+
+
+
+ true
+
+
+
+ Less is slower (0: Default)
+
+
+
+
+
+ -
+
+
+ Enable reordering
+
+
+
+ -
+
+
+
+
+
+ false
+
+
+
+ -
+
+
+ Force redownload
+
+
+
+ -
+
0
0
-
- MiB
-
-
- 0
-
-
- 10240
-
-
- 128
-
-
- 1024
+
+
- -
-
+
-
+
+
+ Ignore free space
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
true
- Less is slower (0: Default)
+ Use with caution!
+
+
+
+ -
+
+
+ Download only
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ true
+
+
+
+ Do not try to install.
- -
-
-
- Enable reordering
-
-
-
- -
-
-
-
-
-
- false
-
-
+
-
+
diff --git a/rare/widgets/collabsible_widget.py b/rare/widgets/collabsible_widget.py
new file mode 100644
index 00000000..79726b0b
--- /dev/null
+++ b/rare/widgets/collabsible_widget.py
@@ -0,0 +1,86 @@
+from PyQt5.QtCore import QParallelAnimationGroup, Qt, QPropertyAnimation, QAbstractAnimation
+from PyQt5.QtWidgets import QWidget, QFrame, QToolButton, QGridLayout, QSizePolicy, QLayout
+
+
+# https://newbedev.com/how-to-make-an-expandable-collapsable-section-widget-in-qt
+
+
+class CollabsibleWidget(QWidget):
+ def __init__(
+ self, child_layout: QLayout = None, title: str = "", animation_duration: int = 200, parent=None
+ ):
+ """
+ References:
+ # Adapted from c++ version
+ https://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt
+ """
+ super(CollabsibleWidget, self).__init__(parent=parent)
+
+ self.animationDuration = animation_duration
+ self.toggleAnimation = QParallelAnimationGroup()
+ self.contentArea = QWidget()
+ self.headerLine = QFrame()
+ self.toggleButton = QToolButton()
+ self.mainLayout = QGridLayout()
+
+ toggleButton = self.toggleButton
+ toggleButton.setStyleSheet("QToolButton { border: none; }")
+ toggleButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
+ toggleButton.setArrowType(Qt.RightArrow)
+ toggleButton.setText(str(title))
+ toggleButton.setCheckable(True)
+ toggleButton.setChecked(False)
+
+ headerLine = self.headerLine
+ headerLine.setFrameShape(QFrame.HLine)
+ headerLine.setFrameShadow(QFrame.Sunken)
+ headerLine.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum)
+
+ self.contentArea.setStyleSheet("QScrollArea { background-color: white; border: none; }")
+ self.contentArea.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
+ # start out collapsed
+ self.contentArea.setMaximumHeight(0)
+ self.contentArea.setMinimumHeight(0)
+ # let the entire widget grow and shrink with its content
+ toggleAnimation = self.toggleAnimation
+ toggleAnimation.addAnimation(QPropertyAnimation(self, b"minimumHeight"))
+ toggleAnimation.addAnimation(QPropertyAnimation(self, b"maximumHeight"))
+ toggleAnimation.addAnimation(QPropertyAnimation(self.contentArea, b"maximumHeight"))
+ # don't waste space
+ mainLayout = self.mainLayout
+ mainLayout.setVerticalSpacing(0)
+ mainLayout.setContentsMargins(0, 0, 0, 0)
+ row = 0
+ mainLayout.addWidget(self.toggleButton, row, 0, 1, 1, Qt.AlignLeft)
+ mainLayout.addWidget(self.headerLine, row, 2, 1, 1)
+ row += 1
+ mainLayout.addWidget(self.contentArea, row, 0, 1, 3)
+ self.setLayout(self.mainLayout)
+
+ def start_animation(checked):
+ arrow_type = Qt.DownArrow if checked else Qt.RightArrow
+ direction = QAbstractAnimation.Forward if checked else QAbstractAnimation.Backward
+ toggleButton.setArrowType(arrow_type)
+ self.toggleAnimation.setDirection(direction)
+ self.toggleAnimation.start()
+
+ self.toggleButton.clicked.connect(start_animation)
+
+ if child_layout:
+ self.setContentLayout(child_layout)
+
+ def setContentLayout(self, content_layout: QLayout):
+ # Not sure if this is equivalent to self.contentArea.destroy()
+ self.contentArea.destroy()
+ self.contentArea.setLayout(content_layout)
+ collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
+ contentHeight = content_layout.sizeHint().height()
+ for i in range(self.toggleAnimation.animationCount() - 1):
+ spoilerAnimation = self.toggleAnimation.animationAt(i)
+ spoilerAnimation.setDuration(self.animationDuration)
+ spoilerAnimation.setStartValue(collapsedHeight)
+ spoilerAnimation.setEndValue(collapsedHeight + contentHeight)
+ contentAnimation = self.toggleAnimation.animationAt(self.toggleAnimation.animationCount() - 1)
+ contentAnimation.setDuration(self.animationDuration)
+ contentAnimation.setStartValue(0)
+ contentAnimation.setEndValue(contentHeight)
From 2797485e6994ca278f43bde6d3ad11196ca3fdd3 Mon Sep 17 00:00:00 2001
From: Dummerle <44114474+Dummerle@users.noreply.github.com>
Date: Sun, 31 Jul 2022 23:56:02 +0200
Subject: [PATCH 2/5] Fix reason for cloud save edit, if nothing wrong
---
.../tabs/games/game_info/game_settings.py | 71 ++++++-------------
1 file changed, 21 insertions(+), 50 deletions(-)
diff --git a/rare/components/tabs/games/game_info/game_settings.py b/rare/components/tabs/games/game_info/game_settings.py
index 3808b9cc..c3725258 100644
--- a/rare/components/tabs/games/game_info/game_settings.py
+++ b/rare/components/tabs/games/game_info/game_settings.py
@@ -30,54 +30,37 @@ class GameSettings(DefaultGameSettings):
"",
file_type=QFileDialog.DirectoryOnly,
placeholder=self.tr("Cloud save path"),
- edit_func=lambda text: (os.path.exists(text), text, PathEdit.reasons.dir_not_exist),
+ edit_func=lambda text: (True, text, None)
+ if os.path.exists(text)
+ else (False, text, PathEdit.reasons.dir_not_exist),
save_func=self.save_save_path,
)
- self.cloud_layout.addRow(
- QLabel(self.tr("Save path")), self.cloud_save_path_edit
- )
+ self.cloud_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_layout.addRow(None, self.compute_save_path_button)
- self.offline.currentIndexChanged.connect(
- lambda x: self.update_combobox(x, "offline")
- )
- self.skip_update.currentIndexChanged.connect(
- lambda x: self.update_combobox(x, "skip_update_check")
- )
+ self.offline.currentIndexChanged.connect(lambda x: self.update_combobox(x, "offline"))
+ self.skip_update.currentIndexChanged.connect(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()
)
)
- self.override_exe_edit.textChanged.connect(
- lambda text: self.save_line_edit("override_exe", text)
- )
- self.launch_params.textChanged.connect(
- lambda x: self.save_line_edit("start_params", x)
- )
+ self.override_exe_edit.textChanged.connect(lambda text: self.save_line_edit("override_exe", text))
+ self.launch_params.textChanged.connect(lambda x: self.save_line_edit("start_params", x))
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:
logger.warning(str(e))
- resolver = WineResolver(
- get_raw_save_path(self.game), self.game.app_name
- )
+ resolver = WineResolver(get_raw_save_path(self.game), self.game.app_name)
if not resolver.wine_env.get("WINEPREFIX"):
self.cloud_save_path_edit.setText("")
QMessageBox.warning(self, "Warning", "No wine prefix selected. Please set it in settings")
@@ -87,18 +70,14 @@ class GameSettings(DefaultGameSettings):
self.compute_save_path_button.setDisabled(True)
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)
@@ -110,9 +89,9 @@ class GameSettings(DefaultGameSettings):
QMessageBox.warning(
None,
"Error",
- self.tr(
- "Error while launching {}. No permission to create {}"
- ).format(self.game.app_title, path),
+ self.tr("Error while launching {}. No permission to create {}").format(
+ self.game.app_title, path
+ ),
)
return
if not path:
@@ -160,9 +139,7 @@ class GameSettings(DefaultGameSettings):
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":
@@ -176,9 +153,7 @@ class GameSettings(DefaultGameSettings):
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":
@@ -198,18 +173,14 @@ class GameSettings(DefaultGameSettings):
self.cloud_save_path_edit.setText("")
else:
self.cloud_group.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.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="")
)
From 3469946ee941a0df4e68dc586b272d97a996a9af Mon Sep 17 00:00:00 2001
From: Dummerle <44114474+Dummerle@users.noreply.github.com>
Date: Thu, 11 Aug 2022 00:47:55 +0200
Subject: [PATCH 3/5] Install prereqs by default on windows
---
rare/components/dialogs/install_dialog.py | 5 ++
rare/ui/components/dialogs/install_dialog.py | 75 ++++++++------------
rare/ui/components/dialogs/install_dialog.ui | 65 +++++++++--------
3 files changed, 73 insertions(+), 72 deletions(-)
diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py
index 683bed1c..0400b958 100644
--- a/rare/components/dialogs/install_dialog.py
+++ b/rare/components/dialogs/install_dialog.py
@@ -153,6 +153,8 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.verify_button.clicked.connect(self.verify_clicked)
self.install_button.clicked.connect(self.install_clicked)
+ self.install_preqs_check.setChecked(self.dl_item.options.install_preqs)
+
self.install_dialog_layout.setSizeConstraint(self.install_dialog_layout.SetFixedSize)
def execute(self):
@@ -205,6 +207,8 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.dl_item.options.ignore_space = 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.install_preqs = self.install_preqs_check.isChecked()
+ self.dl_item.options.create_shortcut = self.shortcut_cb.isChecked()
if self.sdl_list_cbs:
self.dl_item.options.install_tag = [""]
for cb in self.sdl_list_cbs:
@@ -286,6 +290,7 @@ class InstallDialog(QDialog, Ui_InstallDialog):
if dl_item.igame.prereq_info and not dl_item.igame.prereq_info.get("installed", False):
self.install_preqs_check.setVisible(True)
self.install_preqs_lbl.setVisible(True)
+ self.install_preqs_check.setChecked(True)
self.install_preqs_check.setText(
self.tr("Also install: {}").format(dl_item.igame.prereq_info.get("name", ""))
)
diff --git a/rare/ui/components/dialogs/install_dialog.py b/rare/ui/components/dialogs/install_dialog.py
index 3696b8ed..1f8f4823 100644
--- a/rare/ui/components/dialogs/install_dialog.py
+++ b/rare/ui/components/dialogs/install_dialog.py
@@ -14,13 +14,12 @@ from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_InstallDialog(object):
def setupUi(self, InstallDialog):
InstallDialog.setObjectName("InstallDialog")
- InstallDialog.resize(526, 483)
+ InstallDialog.resize(530, 489)
InstallDialog.setWindowTitle("Rare")
self.install_dialog_layout = QtWidgets.QFormLayout(InstallDialog)
self.install_dialog_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize)
self.install_dialog_layout.setLabelAlignment(
- QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter
- )
+ QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
self.install_dialog_layout.setObjectName("install_dialog_layout")
self.install_dialog_label = QtWidgets.QLabel(InstallDialog)
self.install_dialog_label.setObjectName("install_dialog_label")
@@ -43,22 +42,14 @@ class Ui_InstallDialog(object):
self.platform_combo_box.setObjectName("platform_combo_box")
self.install_dialog_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.platform_combo_box)
self.shortcut_lbl = QtWidgets.QLabel(InstallDialog)
+ self.shortcut_lbl.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.shortcut_lbl.setObjectName("shortcut_lbl")
self.install_dialog_layout.setWidget(9, QtWidgets.QFormLayout.LabelRole, self.shortcut_lbl)
self.shortcut_cb = QtWidgets.QCheckBox(InstallDialog)
self.shortcut_cb.setText("")
+ self.shortcut_cb.setChecked(True)
self.shortcut_cb.setObjectName("shortcut_cb")
self.install_dialog_layout.setWidget(9, QtWidgets.QFormLayout.FieldRole, self.shortcut_cb)
- self.install_preqs_lbl = QtWidgets.QLabel(InstallDialog)
- self.install_preqs_lbl.setObjectName("install_preqs_lbl")
- self.install_dialog_layout.setWidget(10, QtWidgets.QFormLayout.LabelRole, self.install_preqs_lbl)
- self.install_preqs_check = QtWidgets.QCheckBox(InstallDialog)
- font = QtGui.QFont()
- font.setItalic(True)
- self.install_preqs_check.setFont(font)
- self.install_preqs_check.setText("")
- self.install_preqs_check.setObjectName("install_preqs_check")
- self.install_dialog_layout.setWidget(10, QtWidgets.QFormLayout.FieldRole, self.install_preqs_check)
self.sdl_list_label = QtWidgets.QLabel(InstallDialog)
self.sdl_list_label.setObjectName("sdl_list_label")
self.install_dialog_layout.setWidget(11, QtWidgets.QFormLayout.LabelRole, self.sdl_list_label)
@@ -78,9 +69,7 @@ class Ui_InstallDialog(object):
font.setItalic(True)
self.download_size_info_label.setFont(font)
self.download_size_info_label.setObjectName("download_size_info_label")
- self.install_dialog_layout.setWidget(
- 13, QtWidgets.QFormLayout.FieldRole, self.download_size_info_label
- )
+ self.install_dialog_layout.setWidget(13, QtWidgets.QFormLayout.FieldRole, self.download_size_info_label)
self.install_size_label = QtWidgets.QLabel(InstallDialog)
self.install_size_label.setObjectName("install_size_label")
self.install_dialog_layout.setWidget(14, QtWidgets.QFormLayout.LabelRole, self.install_size_label)
@@ -90,9 +79,7 @@ class Ui_InstallDialog(object):
self.install_size_info_label.setFont(font)
self.install_size_info_label.setWordWrap(True)
self.install_size_info_label.setObjectName("install_size_info_label")
- self.install_dialog_layout.setWidget(
- 14, QtWidgets.QFormLayout.FieldRole, self.install_size_info_label
- )
+ self.install_dialog_layout.setWidget(14, QtWidgets.QFormLayout.FieldRole, self.install_size_info_label)
self.warn_label = QtWidgets.QLabel(InstallDialog)
self.warn_label.setObjectName("warn_label")
self.install_dialog_layout.setWidget(15, QtWidgets.QFormLayout.LabelRole, self.warn_label)
@@ -107,9 +94,7 @@ class Ui_InstallDialog(object):
self.install_dialog_layout.setWidget(15, QtWidgets.QFormLayout.FieldRole, self.warn_message)
self.button_layout = QtWidgets.QHBoxLayout()
self.button_layout.setObjectName("button_layout")
- spacerItem = QtWidgets.QSpacerItem(
- 40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
- )
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
self.button_layout.addItem(spacerItem)
self.cancel_button = QtWidgets.QPushButton(InstallDialog)
self.cancel_button.setObjectName("cancel_button")
@@ -122,14 +107,10 @@ class Ui_InstallDialog(object):
self.button_layout.addWidget(self.install_button)
self.install_dialog_layout.setLayout(17, QtWidgets.QFormLayout.SpanningRole, self.button_layout)
self.advanced_layout = QtWidgets.QFormLayout()
- self.advanced_layout.setLabelAlignment(
- QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter
- )
+ self.advanced_layout.setLabelAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter)
self.advanced_layout.setObjectName("advanced_layout")
self.max_workers_label = QtWidgets.QLabel(InstallDialog)
- self.max_workers_label.setAlignment(
- QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
- )
+ self.max_workers_label.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.max_workers_label.setObjectName("max_workers_label")
self.advanced_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.max_workers_label)
self.max_workers_layout = QtWidgets.QHBoxLayout()
@@ -150,9 +131,7 @@ class Ui_InstallDialog(object):
self.max_workers_layout.addWidget(self.max_workers_info_label)
self.advanced_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.max_workers_layout)
self.max_memory_label = QtWidgets.QLabel(InstallDialog)
- self.max_memory_label.setAlignment(
- QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
- )
+ self.max_memory_label.setAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
self.max_memory_label.setObjectName("max_memory_label")
self.advanced_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.max_memory_label)
self.max_memory_layout = QtWidgets.QHBoxLayout()
@@ -178,15 +157,15 @@ class Ui_InstallDialog(object):
self.advanced_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.max_memory_layout)
self.dl_optimizations_label = QtWidgets.QLabel(InstallDialog)
self.dl_optimizations_label.setObjectName("dl_optimizations_label")
- self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.dl_optimizations_label)
+ self.advanced_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.dl_optimizations_label)
self.dl_optimizations_check = QtWidgets.QCheckBox(InstallDialog)
self.dl_optimizations_check.setText("")
self.dl_optimizations_check.setChecked(False)
self.dl_optimizations_check.setObjectName("dl_optimizations_check")
- self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.dl_optimizations_check)
+ self.advanced_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.dl_optimizations_check)
self.force_download_label = QtWidgets.QLabel(InstallDialog)
self.force_download_label.setObjectName("force_download_label")
- self.advanced_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.force_download_label)
+ self.advanced_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.force_download_label)
self.force_download_check = QtWidgets.QCheckBox(InstallDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -195,10 +174,10 @@ class Ui_InstallDialog(object):
self.force_download_check.setSizePolicy(sizePolicy)
self.force_download_check.setText("")
self.force_download_check.setObjectName("force_download_check")
- self.advanced_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.force_download_check)
+ self.advanced_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.force_download_check)
self.ignore_space_label = QtWidgets.QLabel(InstallDialog)
self.ignore_space_label.setObjectName("ignore_space_label")
- self.advanced_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.ignore_space_label)
+ self.advanced_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.ignore_space_label)
self.ignore_space_check = QtWidgets.QCheckBox(InstallDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -209,10 +188,10 @@ class Ui_InstallDialog(object):
font.setItalic(True)
self.ignore_space_check.setFont(font)
self.ignore_space_check.setObjectName("ignore_space_check")
- self.advanced_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.ignore_space_check)
+ self.advanced_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.ignore_space_check)
self.download_only_label = QtWidgets.QLabel(InstallDialog)
self.download_only_label.setObjectName("download_only_label")
- self.advanced_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.download_only_label)
+ self.advanced_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.download_only_label)
self.download_only_check = QtWidgets.QCheckBox(InstallDialog)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -223,13 +202,22 @@ class Ui_InstallDialog(object):
font.setItalic(True)
self.download_only_check.setFont(font)
self.download_only_check.setObjectName("download_only_check")
- self.advanced_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.download_only_check)
+ self.advanced_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.download_only_check)
+ self.install_preqs_lbl = QtWidgets.QLabel(InstallDialog)
+ self.install_preqs_lbl.setObjectName("install_preqs_lbl")
+ self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.install_preqs_lbl)
+ self.install_preqs_check = QtWidgets.QCheckBox(InstallDialog)
+ font = QtGui.QFont()
+ font.setItalic(True)
+ self.install_preqs_check.setFont(font)
+ self.install_preqs_check.setText("")
+ self.install_preqs_check.setChecked(False)
+ self.install_preqs_check.setObjectName("install_preqs_check")
+ self.advanced_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.install_preqs_check)
self.install_dialog_layout.setLayout(16, QtWidgets.QFormLayout.SpanningRole, self.advanced_layout)
self.advanced_placeholder_layout = QtWidgets.QVBoxLayout()
self.advanced_placeholder_layout.setObjectName("advanced_placeholder_layout")
- self.install_dialog_layout.setLayout(
- 12, QtWidgets.QFormLayout.SpanningRole, self.advanced_placeholder_layout
- )
+ self.install_dialog_layout.setLayout(12, QtWidgets.QFormLayout.SpanningRole, self.advanced_placeholder_layout)
self.retranslateUi(InstallDialog)
QtCore.QMetaObject.connectSlotsByName(InstallDialog)
@@ -240,7 +228,6 @@ class Ui_InstallDialog(object):
self.install_dir_label.setText(_translate("InstallDialog", "Install directory"))
self.platform_label.setText(_translate("InstallDialog", "Platform"))
self.shortcut_lbl.setText(_translate("InstallDialog", "Create shortcut"))
- self.install_preqs_lbl.setText(_translate("InstallDialog", "Install prerequisites"))
self.sdl_list_label.setText(_translate("InstallDialog", "Optional packs"))
self.download_size_label.setText(_translate("InstallDialog", "Download size"))
self.download_size_info_label.setText(_translate("InstallDialog", "Click verify..."))
@@ -262,11 +249,11 @@ class Ui_InstallDialog(object):
self.ignore_space_check.setText(_translate("InstallDialog", "Use with caution!"))
self.download_only_label.setText(_translate("InstallDialog", "Download only"))
self.download_only_check.setText(_translate("InstallDialog", "Do not try to install."))
+ self.install_preqs_lbl.setText(_translate("InstallDialog", "Install prerequisites"))
if __name__ == "__main__":
import sys
-
app = QtWidgets.QApplication(sys.argv)
InstallDialog = QtWidgets.QDialog()
ui = Ui_InstallDialog()
diff --git a/rare/ui/components/dialogs/install_dialog.ui b/rare/ui/components/dialogs/install_dialog.ui
index 03875d60..0542d5a1 100644
--- a/rare/ui/components/dialogs/install_dialog.ui
+++ b/rare/ui/components/dialogs/install_dialog.ui
@@ -6,8 +6,8 @@
0
0
- 526
- 483
+ 530
+ 489
@@ -59,6 +59,9 @@
Create shortcut
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
-
@@ -66,24 +69,8 @@
-
-
- -
-
-
- Install prerequisites
-
-
-
- -
-
-
-
- true
-
-
-
-
+
+ true
@@ -308,14 +295,14 @@
- -
+
-
Enable reordering
- -
+
-
@@ -325,14 +312,14 @@
- -
+
-
Force redownload
- -
+
-
@@ -345,14 +332,14 @@
- -
+
-
Ignore free space
- -
+
-
@@ -370,14 +357,14 @@
- -
+
-
Download only
- -
+
-
@@ -395,6 +382,28 @@
+ -
+
+
+ Install prerequisites
+
+
+
+ -
+
+
+
+ true
+
+
+
+
+
+
+ false
+
+
+
-
From 625066d2da3313e3eb3ec11ca717c57b67ae52ba Mon Sep 17 00:00:00 2001
From: Dummerle <44114474+Dummerle@users.noreply.github.com>
Date: Thu, 11 Aug 2022 21:09:21 +0200
Subject: [PATCH 4/5] Fix two small errors in game launch helper
---
rare/game_launch_helper/__init__.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/rare/game_launch_helper/__init__.py b/rare/game_launch_helper/__init__.py
index 6b26435d..6773b866 100644
--- a/rare/game_launch_helper/__init__.py
+++ b/rare/game_launch_helper/__init__.py
@@ -40,7 +40,7 @@ class PreLaunchThread(QRunnable):
def prepare_launch(self, app_name) -> Union[LaunchArgs, None]:
try:
args = get_launch_args(self.core, InitArgs(app_name))
- except GameArgsError as e:
+ except Exception as e:
self.signals.error_occurred.emit(str(e))
return None
if not args:
@@ -196,8 +196,11 @@ class GameProcessApp(RareApp):
def stop(self):
self.logger.info("Stopping server")
- self.server.close()
- self.server.deleteLater()
+ try:
+ self.server.close()
+ self.server.deleteLater()
+ except RuntimeError:
+ pass
self.exit_app.emit()
From 70a51902d23219e1c761fdda697f02af7120e5eb Mon Sep 17 00:00:00 2001
From: Dummerle <44114474+Dummerle@users.noreply.github.com>
Date: Thu, 11 Aug 2022 21:43:11 +0200
Subject: [PATCH 5/5] Move move game stuff to external file I know it's weird
---
.../tabs/games/game_info/game_info.py | 256 +----------------
.../tabs/games/game_info/move_game.py | 258 ++++++++++++++++++
2 files changed, 261 insertions(+), 253 deletions(-)
create mode 100644 rare/components/tabs/games/game_info/move_game.py
diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py
index 9f8fa904..d4808378 100644
--- a/rare/components/tabs/games/game_info/game_info.py
+++ b/rare/components/tabs/games/game_info/game_info.py
@@ -3,31 +3,25 @@ import platform
import shutil
from logging import getLogger
from pathlib import Path
-from typing import Tuple
from PyQt5.QtCore import (
- QObject,
- QRunnable,
Qt,
pyqtSignal,
QThreadPool,
pyqtSlot,
)
from PyQt5.QtWidgets import (
- QFileDialog,
QHBoxLayout,
- QLabel,
QMenu,
QProgressBar,
QPushButton,
- QVBoxLayout,
QWidget,
QMessageBox,
QWidgetAction,
)
-from legendary.models.game import Game, InstalledGame, VerifyResult
-from legendary.utils.lfs import validate_files
+from legendary.models.game import Game, InstalledGame
+from rare.models.install import InstallOptionsModel
from rare.shared import (
LegendaryCoreSingleton,
GlobalSignalsSingleton,
@@ -35,11 +29,9 @@ from rare.shared import (
)
from rare.shared.image_manager import ImageManagerSingleton, ImageSize
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
-from rare.utils.extra_widgets import PathEdit
from rare.utils.legendary_utils import VerifyWorker
-from rare.models.install import InstallOptionsModel
-from rare.utils.steam_grades import SteamWorker
from rare.utils.misc import get_size
+from rare.utils.steam_grades import SteamWorker
from rare.widgets.image_widget import ImageWidget
logger = getLogger("GameInfo")
@@ -401,245 +393,3 @@ class GameInfo(QWidget, Ui_GameInfo):
self.move_game_pop_up.update_game(app_name)
-
-class MoveGamePopUp(QWidget):
- move_clicked = pyqtSignal(str)
- browse_done = pyqtSignal()
-
- def __init__(self):
- super(MoveGamePopUp, self).__init__()
- layout: QVBoxLayout = QVBoxLayout()
- self.install_path = str()
- self.core = LegendaryCoreSingleton()
- self.move_path_edit = PathEdit(str(), QFileDialog.Directory, edit_func=self.edit_func_move_game)
- self.move_path_edit.path_select.clicked.connect(self.emit_browse_done_signal)
-
- self.move_game = QPushButton(self.tr("Move"))
- self.move_game.setMaximumWidth(50)
- self.move_game.clicked.connect(self.emit_move_game_signal)
-
- self.warn_overwriting = QLabel()
-
- middle_layout = QHBoxLayout()
- middle_layout.setAlignment(Qt.AlignRight)
- middle_layout.addWidget(self.warn_overwriting, stretch=1)
- middle_layout.addWidget(self.move_game)
-
- bottom_layout = QVBoxLayout()
- self.aval_space_label = QLabel()
- self.req_space_label = QLabel()
- bottom_layout.addWidget(self.aval_space_label)
- bottom_layout.addWidget(self.req_space_label)
-
- layout.addWidget(self.move_path_edit)
- layout.addLayout(middle_layout)
- layout.addLayout(bottom_layout)
-
- self.setLayout(layout)
-
- def emit_move_game_signal(self):
- self.move_clicked.emit(self.move_path_edit.text())
-
- def emit_browse_done_signal(self):
- self.browse_done.emit()
-
- def refresh_indicator(self):
- # needed so the edit_func gets run again
- text = self.move_path_edit.text()
- self.move_path_edit.setText(str())
- self.move_path_edit.setText(text)
-
- # Thanks to lk.
- @staticmethod
- def find_mount(path):
- mount_point = path
- while path != path.anchor:
- if path.is_mount():
- return path
- else:
- path = path.parent
- return mount_point
-
- def edit_func_move_game(self, dir_selected):
- self.move_game.setEnabled(True)
- self.warn_overwriting.setHidden(True)
-
- def helper_func(reason: str) -> Tuple[bool, str, str]:
- self.move_game.setEnabled(False)
- return False, dir_selected, self.tr(reason)
-
- if not self.install_path or not dir_selected:
- return helper_func("You need to provide a directory.")
-
- install_path = Path(self.install_path).resolve()
- dest_path = Path(dir_selected).resolve()
- dest_path_with_suffix = dest_path.joinpath(install_path.stem).resolve()
-
- if not dest_path.is_dir():
- return helper_func("Directory doesn't exist or file selected.")
-
- # Get free space on drive and size of game folder
- _, _, free_space = shutil.disk_usage(dest_path)
- source_size = sum(f.stat().st_size for f in install_path.glob("**/*") if f.is_file())
-
- # Calculate from bytes to gigabytes
- free_space_dest_drive = round(free_space / 1000**3, 2)
- source_size = round(source_size / 1000**3, 2)
- self.aval_space_label.setText(self.tr("Available space: {}GB".format(free_space_dest_drive)))
- self.req_space_label.setText(self.tr("Required space: {}GB").format(source_size))
-
- if not os.access(dir_selected, os.W_OK) or not os.access(self.install_path, os.W_OK):
- return helper_func("No write permission on destination path/current install path.")
-
- if install_path == dest_path or install_path == dest_path_with_suffix:
- return helper_func("Same directory or parent directory selected.")
-
- if str(install_path) in str(dest_path):
- return helper_func("You can't select a directory that is inside the current install path.")
-
- if str(dest_path_with_suffix) in str(install_path):
- return helper_func("You can't select a directory which contains the game installation.")
-
- for game in self.core.get_installed_list():
- if game.install_path in dir_selected:
- return helper_func("Game installations cannot be nested due to unintended sideeffects.")
-
- is_existing_dir = is_game_dir(install_path, dest_path_with_suffix)
-
- for i in dest_path.iterdir():
- if install_path.stem in i.stem:
- if dest_path_with_suffix.is_dir():
- if not is_existing_dir:
- self.warn_overwriting.setHidden(False)
- elif dest_path_with_suffix.is_file():
- self.warn_overwriting.setHidden(False)
-
- if free_space_dest_drive <= source_size and not is_existing_dir:
- return helper_func("Not enough space available on drive.")
-
- # Fallback
- self.move_game.setEnabled(True)
- return True, dir_selected, str()
-
- def update_game(self, app_name):
- igame = self.core.get_installed_game(app_name, False)
- if igame is None:
- return
- self.install_path = igame.install_path
- self.move_path_edit.setText(igame.install_path)
- self.warn_overwriting.setText(
- self.tr("Moving here will overwrite the dir/file {}/").format(Path(self.install_path).stem)
- )
-
-
-class CopyGameInstallation(QRunnable):
- class Signals(QObject):
- progress = pyqtSignal(int)
- finished = pyqtSignal(str)
- no_space_left = pyqtSignal()
-
- def __init__(
- self,
- install_path: str,
- dest_path: Path,
- is_existing_dir: bool,
- igame: InstalledGame,
- ):
- super(CopyGameInstallation, self).__init__()
- self.signals = self.Signals()
- self.install_path = install_path
- self.dest_path = dest_path
- self.source_size = 0
- self.dest_size = 0
- self.is_existing_dir = is_existing_dir
- self.core = LegendaryCoreSingleton()
- self.igame = igame
- self.file_list = None
- self.total: int = 0
-
- def run(self):
- root_directory = Path(self.install_path)
- self.source_size = sum(f.stat().st_size for f in root_directory.glob("**/*") if f.is_file())
-
- # if game dir is not existing, just copying:
- if not self.is_existing_dir:
- shutil.copytree(
- self.install_path,
- self.dest_path,
- copy_function=self.copy_each_file_with_progress,
- dirs_exist_ok=True,
- )
- else:
- manifest_data, _ = self.core.get_installed_manifest(self.igame.app_name)
- manifest = self.core.load_manifest(manifest_data)
- files = sorted(
- manifest.file_manifest_list.elements,
- key=lambda a: a.filename.lower(),
- )
- self.file_list = [(f.filename, f.sha_hash.hex()) for f in files]
- self.total = len(self.file_list)
-
- # recreate dir structure
- shutil.copytree(
- self.install_path,
- self.dest_path,
- copy_function=self.copy_dir_structure,
- dirs_exist_ok=True,
- )
-
- for i, (result, relative_path, _, _) in enumerate(
- validate_files(str(self.dest_path), self.file_list)
- ):
- dst_path = f"{self.dest_path}/{relative_path}"
- src_path = f"{self.install_path}/{relative_path}"
- if Path(src_path).is_file():
- if result == VerifyResult.HASH_MISMATCH:
- try:
- shutil.copy(src_path, dst_path)
- except IOError:
- self.signals.no_space_left.emit()
- return
- elif result == VerifyResult.FILE_MISSING:
- try:
- shutil.copy(src_path, dst_path)
- except (IOError, OSError):
- self.signals.no_space_left.emit()
- return
- elif result == VerifyResult.OTHER_ERROR:
- logger.warning(f"Copying file {src_path} to {dst_path} failed")
- self.signals.progress.emit(int(i * 10 / self.total * 10))
- else:
- logger.warning(
- f"Source dir does not have file {src_path}. File will be missing in the destination "
- f"dir. "
- )
-
- shutil.rmtree(self.install_path)
- self.signals.finished.emit(str(self.dest_path))
-
- def copy_each_file_with_progress(self, src, dst):
- shutil.copy(src, dst)
- self.dest_size += Path(src).stat().st_size
- self.signals.progress.emit(int(self.dest_size * 10 / self.source_size * 10))
-
- # This method is a copy_func, and only copies the src if it's a dir.
- # Thus, it can be used to re-create the dir strucute.
- @staticmethod
- def copy_dir_structure(src, dst):
- if os.path.isdir(dst):
- dst = os.path.join(dst, os.path.basename(src))
- if os.path.isdir(src):
- shutil.copyfile(src, dst)
- shutil.copystat(src, dst)
- return dst
-
-
-def is_game_dir(install_path: Path, dest_path: Path):
- # This iterates over the destination dir, then iterates over the current install dir and if the file names
- # matches, we have an exisiting dir
- if dest_path.is_dir():
- for file in dest_path.iterdir():
- for install_file in install_path.iterdir():
- if file.name == install_file.name:
- return True
- return False
diff --git a/rare/components/tabs/games/game_info/move_game.py b/rare/components/tabs/games/game_info/move_game.py
new file mode 100644
index 00000000..e4a71d3e
--- /dev/null
+++ b/rare/components/tabs/games/game_info/move_game.py
@@ -0,0 +1,258 @@
+import os
+import shutil
+from logging import getLogger
+from pathlib import Path
+from typing import Tuple
+
+from PyQt5.QtCore import pyqtSignal, QRunnable, QObject, Qt
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog
+from legendary.models.game import VerifyResult, InstalledGame
+from legendary.utils.lfs import validate_files
+
+from rare.shared import LegendaryCoreSingleton
+from rare.utils.extra_widgets import PathEdit
+
+logger = getLogger("MoveGame")
+
+
+class MoveGamePopUp(QWidget):
+ move_clicked = pyqtSignal(str)
+ browse_done = pyqtSignal()
+
+ def __init__(self):
+ super(MoveGamePopUp, self).__init__()
+ layout: QVBoxLayout = QVBoxLayout()
+ self.install_path = str()
+ self.core = LegendaryCoreSingleton()
+ self.move_path_edit = PathEdit(str(), QFileDialog.Directory, edit_func=self.edit_func_move_game)
+ self.move_path_edit.path_select.clicked.connect(self.emit_browse_done_signal)
+
+ self.move_game = QPushButton(self.tr("Move"))
+ self.move_game.setMaximumWidth(50)
+ self.move_game.clicked.connect(self.emit_move_game_signal)
+
+ self.warn_overwriting = QLabel()
+
+ middle_layout = QHBoxLayout()
+ middle_layout.setAlignment(Qt.AlignRight)
+ middle_layout.addWidget(self.warn_overwriting, stretch=1)
+ middle_layout.addWidget(self.move_game)
+
+ bottom_layout = QVBoxLayout()
+ self.aval_space_label = QLabel()
+ self.req_space_label = QLabel()
+ bottom_layout.addWidget(self.aval_space_label)
+ bottom_layout.addWidget(self.req_space_label)
+
+ layout.addWidget(self.move_path_edit)
+ layout.addLayout(middle_layout)
+ layout.addLayout(bottom_layout)
+
+ self.setLayout(layout)
+
+ def emit_move_game_signal(self):
+ self.move_clicked.emit(self.move_path_edit.text())
+
+ def emit_browse_done_signal(self):
+ self.browse_done.emit()
+
+ def refresh_indicator(self):
+ # needed so the edit_func gets run again
+ text = self.move_path_edit.text()
+ self.move_path_edit.setText(str())
+ self.move_path_edit.setText(text)
+
+ # Thanks to lk.
+ @staticmethod
+ def find_mount(path):
+ mount_point = path
+ while path != path.anchor:
+ if path.is_mount():
+ return path
+ else:
+ path = path.parent
+ return mount_point
+
+ def edit_func_move_game(self, dir_selected):
+ self.move_game.setEnabled(True)
+ self.warn_overwriting.setHidden(True)
+
+ def helper_func(reason: str) -> Tuple[bool, str, str]:
+ self.move_game.setEnabled(False)
+ return False, dir_selected, self.tr(reason)
+
+ if not self.install_path or not dir_selected:
+ return helper_func("You need to provide a directory.")
+
+ install_path = Path(self.install_path).resolve()
+ dest_path = Path(dir_selected).resolve()
+ dest_path_with_suffix = dest_path.joinpath(install_path.stem).resolve()
+
+ if not dest_path.is_dir():
+ return helper_func("Directory doesn't exist or file selected.")
+
+ # Get free space on drive and size of game folder
+ _, _, free_space = shutil.disk_usage(dest_path)
+ source_size = sum(f.stat().st_size for f in install_path.glob("**/*") if f.is_file())
+
+ # Calculate from bytes to gigabytes
+ free_space_dest_drive = round(free_space / 1000 ** 3, 2)
+ source_size = round(source_size / 1000 ** 3, 2)
+ self.aval_space_label.setText(self.tr("Available space: {}GB".format(free_space_dest_drive)))
+ self.req_space_label.setText(self.tr("Required space: {}GB").format(source_size))
+
+ if not os.access(dir_selected, os.W_OK) or not os.access(self.install_path, os.W_OK):
+ return helper_func("No write permission on destination path/current install path.")
+
+ if install_path == dest_path or install_path == dest_path_with_suffix:
+ return helper_func("Same directory or parent directory selected.")
+
+ if str(install_path) in str(dest_path):
+ return helper_func("You can't select a directory that is inside the current install path.")
+
+ if str(dest_path_with_suffix) in str(install_path):
+ return helper_func("You can't select a directory which contains the game installation.")
+
+ for game in self.core.get_installed_list():
+ if game.install_path in dir_selected:
+ return helper_func("Game installations cannot be nested due to unintended sideeffects.")
+
+ is_existing_dir = is_game_dir(install_path, dest_path_with_suffix)
+
+ for i in dest_path.iterdir():
+ if install_path.stem in i.stem:
+ if dest_path_with_suffix.is_dir():
+ if not is_existing_dir:
+ self.warn_overwriting.setHidden(False)
+ elif dest_path_with_suffix.is_file():
+ self.warn_overwriting.setHidden(False)
+
+ if free_space_dest_drive <= source_size and not is_existing_dir:
+ return helper_func("Not enough space available on drive.")
+
+ # Fallback
+ self.move_game.setEnabled(True)
+ return True, dir_selected, str()
+
+ def update_game(self, app_name):
+ igame = self.core.get_installed_game(app_name, False)
+ if igame is None:
+ return
+ self.install_path = igame.install_path
+ self.move_path_edit.setText(igame.install_path)
+ self.warn_overwriting.setText(
+ self.tr("Moving here will overwrite the dir/file {}/").format(Path(self.install_path).stem)
+ )
+
+
+class CopyGameInstallation(QRunnable):
+ class Signals(QObject):
+ progress = pyqtSignal(int)
+ finished = pyqtSignal(str)
+ no_space_left = pyqtSignal()
+
+ def __init__(
+ self,
+ install_path: str,
+ dest_path: Path,
+ is_existing_dir: bool,
+ igame: InstalledGame,
+ ):
+ super(CopyGameInstallation, self).__init__()
+ self.signals = CopyGameInstallation.Signals()
+ self.install_path = install_path
+ self.dest_path = dest_path
+ self.source_size = 0
+ self.dest_size = 0
+ self.is_existing_dir = is_existing_dir
+ self.core = LegendaryCoreSingleton()
+ self.igame = igame
+ self.file_list = None
+ self.total: int = 0
+
+ def run(self):
+ root_directory = Path(self.install_path)
+ self.source_size = sum(f.stat().st_size for f in root_directory.glob("**/*") if f.is_file())
+
+ # if game dir is not existing, just copying:
+ if not self.is_existing_dir:
+ shutil.copytree(
+ self.install_path,
+ self.dest_path,
+ copy_function=self.copy_each_file_with_progress,
+ dirs_exist_ok=True,
+ )
+ else:
+ manifest_data, _ = self.core.get_installed_manifest(self.igame.app_name)
+ manifest = self.core.load_manifest(manifest_data)
+ files = sorted(
+ manifest.file_manifest_list.elements,
+ key=lambda a: a.filename.lower(),
+ )
+ self.file_list = [(f.filename, f.sha_hash.hex()) for f in files]
+ self.total = len(self.file_list)
+
+ # recreate dir structure
+ shutil.copytree(
+ self.install_path,
+ self.dest_path,
+ copy_function=self.copy_dir_structure,
+ dirs_exist_ok=True,
+ )
+
+ for i, (result, relative_path, _, _) in enumerate(
+ validate_files(str(self.dest_path), self.file_list)
+ ):
+ dst_path = f"{self.dest_path}/{relative_path}"
+ src_path = f"{self.install_path}/{relative_path}"
+ if Path(src_path).is_file():
+ if result == VerifyResult.HASH_MISMATCH:
+ try:
+ shutil.copy(src_path, dst_path)
+ except IOError:
+ self.signals.no_space_left.emit()
+ return
+ elif result == VerifyResult.FILE_MISSING:
+ try:
+ shutil.copy(src_path, dst_path)
+ except (IOError, OSError):
+ self.signals.no_space_left.emit()
+ return
+ elif result == VerifyResult.OTHER_ERROR:
+ logger.warning(f"Copying file {src_path} to {dst_path} failed")
+ self.signals.progress.emit(int(i * 10 / self.total * 10))
+ else:
+ logger.warning(
+ f"Source dir does not have file {src_path}. File will be missing in the destination "
+ f"dir. "
+ )
+
+ shutil.rmtree(self.install_path)
+ self.signals.finished.emit(str(self.dest_path))
+
+ def copy_each_file_with_progress(self, src, dst):
+ shutil.copy(src, dst)
+ self.dest_size += Path(src).stat().st_size
+ self.signals.progress.emit(int(self.dest_size * 10 / self.source_size * 10))
+
+ # This method is a copy_func, and only copies the src if it's a dir.
+ # Thus, it can be used to re-create the dir strucute.
+ @staticmethod
+ def copy_dir_structure(src, dst):
+ if os.path.isdir(dst):
+ dst = os.path.join(dst, os.path.basename(src))
+ if os.path.isdir(src):
+ shutil.copyfile(src, dst)
+ shutil.copystat(src, dst)
+ return dst
+
+
+def is_game_dir(install_path: Path, dest_path: Path):
+ # This iterates over the destination dir, then iterates over the current install dir and if the file names
+ # matches, we have an exisiting dir
+ if dest_path.is_dir():
+ for file in dest_path.iterdir():
+ for install_file in install_path.iterdir():
+ if file.name == install_file.name:
+ return True
+ return False