From d8f39857db90d4585fa1c6cfeb5ad3ce1bef3a97 Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Sat, 16 Oct 2021 23:21:36 +0300 Subject: [PATCH] Add directory watcher to monitor `egl_programdata` folder for changes. Clear known manifests when changing directory Run egl_sync() after importing or exporting Arrange Importable and Exportable lists vertically Signed-off-by: Stelios Tsampas --- .../tabs/games/import_sync/egl_sync_widget.py | 132 ++++++----- .../stylesheets/RareStyle/stylesheet.qss | 6 +- .../tabs/games/import_sync/egl_sync_widget.py | 106 ++++----- .../tabs/games/import_sync/egl_sync_widget.ui | 218 ++++++++---------- rare/utils/extra_widgets.py | 13 +- 5 files changed, 241 insertions(+), 234 deletions(-) diff --git a/rare/components/tabs/games/import_sync/egl_sync_widget.py b/rare/components/tabs/games/import_sync/egl_sync_widget.py index fc0e221d..b03c8a04 100644 --- a/rare/components/tabs/games/import_sync/egl_sync_widget.py +++ b/rare/components/tabs/games/import_sync/egl_sync_widget.py @@ -4,7 +4,7 @@ from glob import glob from typing import Tuple from logging import getLogger -from PyQt5.QtCore import Qt, QThread +from PyQt5.QtCore import Qt, QThread, QFileSystemWatcher from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QGroupBox, \ QCheckBox, QPushButton, QListWidgetItem, QDialog, QFileDialog, QSizePolicy @@ -62,22 +62,28 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): # if os.path.exists(p := os.path.join(i, "drive_c/ProgramData/Epic/EpicGamesLauncher/Data/Manifests")): # egl_path = p - if platform.system() != "Windows": - egl_path = self.core.egl.programdata_path - if egl_path is None: - egl_path = str() - self.egl_path_edit = PathEdit( - path=egl_path, - ph_text=estimated_path, - file_type=QFileDialog.DirectoryOnly, - edit_func=self.egl_path_edit_cb, - save_func=self.egl_path_save_cb, - parent=self - ) - self.egl_path_edit.textChanged.connect(self.egl_path_changed) - self.egl_path_layout.addWidget(self.egl_path_edit) - else: + egl_path = shared.core.egl.programdata_path + if egl_path is None: + egl_path = str() + self.egl_path_edit = PathEdit( + path=egl_path, + ph_text=estimated_path, + file_type=QFileDialog.DirectoryOnly, + edit_func=self.egl_path_edit_cb, + save_func=self.egl_path_save_cb, + parent=self + ) + self.egl_path_edit.textChanged.connect(self.egl_path_changed) + self.egl_path_layout.addWidget(self.egl_path_edit) + + self.egl_watcher = QFileSystemWatcher([self.egl_path_edit.text()], self) + self.egl_watcher.directoryChanged.connect(self.egl_items_changed) + + if platform.system() == "Windows": self.egl_path_label.setVisible(False) + self.egl_path_edit.setVisible(False) + self.egl_path_info_label.setVisible(False) + self.egl_path_info.setVisible(False) self.egl_sync_check.setChecked(shared.core.egl_sync_enabled) self.egl_sync_check.stateChanged.connect(self.egl_sync_changed) @@ -118,69 +124,70 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): return True, path return False, path - def egl_path_save_cb(self, path): + @staticmethod + def egl_path_save_cb(path): if not path: # This is the same as "--unlink" - self.core.egl.programdata_path = None - self.core.lgd.config.remove_option('Legendary', 'egl_programdata') - self.core.lgd.config.remove_option('Legendary', 'egl_sync') + shared.core.egl.programdata_path = None + 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 self.core.get_installed_list(): + for igame in shared.core.get_installed_list(): igame.egl_guid = '' - self.core.install_game(igame) + shared.core.install_game(igame) else: - self.core.egl.programdata_path = path - self.core.lgd.config.set("Legendary", "egl_programdata", path) - self.core.lgd.save_config() + # NOTE: need to clear known manifests to force refresh + shared.core.egl.manifests.clear() + shared.core.egl.programdata_path = path + shared.core.lgd.config.set("Legendary", "egl_programdata", path) + shared.core.lgd.save_config() def egl_path_changed(self, path): if self.egl_path_edit.is_valid: self.egl_sync_check.setEnabled(bool(path)) - self.egl_sync_check.setCheckState(Qt.Unchecked) - self.update_lists() + self.egl_sync_check.setCheckState(Qt.Unchecked) + self.egl_watcher.removePaths([p for p in self.egl_watcher.directories()]) + self.egl_watcher.addPaths([path]) + self.update_lists() def egl_sync_changed(self, state): if state == Qt.Unchecked: self.core.lgd.config.remove_option('Legendary', 'egl_sync') else: self.core.lgd.config.set('Legendary', 'egl_sync', str(True)) - # self.core.egl_sync() - # self.update_lists() + # lk: not sure if this should be done here + # self.select_items(self.importable_items, Qt.Checked) + # self.import_selected() + # self.select_items(self.exportable_items, Qt.Checked) + # self.export_selected() self.core.lgd.save_config() + def egl_items_changed(self, path: str): + if path == shared.core.egl.programdata_path and self.egl_path_edit.is_valid: + shared.core.egl.manifests.clear() + self.update_import_list() + self.update_export_list() + def update_lists(self): have_path = bool(shared.core.egl.programdata_path) and self.egl_path_edit.is_valid self.egl_sync_label.setEnabled(have_path) self.egl_sync_check.setEnabled(have_path) - self.export_import_widget.setEnabled(have_path) - - self.export_label.setVisible(not have_path) - self.export_list.setVisible(have_path) - self.export_buttons_widget.setVisible(have_path) + self.import_export_widget.setEnabled(have_path) self.import_label.setVisible(not have_path) self.import_list.setVisible(have_path) self.import_buttons_widget.setVisible(have_path) + self.export_label.setVisible(not have_path) + self.export_list.setVisible(have_path) + self.export_buttons_widget.setVisible(have_path) + if not have_path: return - self.update_export_list() self.update_import_list() - - def update_export_list(self): - self.export_list.clear() - self.exportable_items.clear() - exportable_games = shared.core.egl_get_exportable() - for igame in exportable_games: - ew = EGLSyncItem(igame, True, self.export_list) - self.exportable_items.append(ew) - self.export_list.addItem(ew) - have_exportable = bool(exportable_games) - self.export_label.setVisible(not have_exportable) - self.export_list.setVisible(have_exportable) - self.export_buttons_widget.setVisible(have_exportable) + self.update_export_list() def update_import_list(self): self.import_list.clear() @@ -195,25 +202,40 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): self.import_list.setVisible(have_importable) self.import_buttons_widget.setVisible(have_importable) + def update_export_list(self): + self.export_list.clear() + self.exportable_items.clear() + exportable_games = shared.core.egl_get_exportable() + for igame in exportable_games: + ew = EGLSyncItem(igame, True, self.export_list) + self.exportable_items.append(ew) + self.export_list.addItem(ew) + have_exportable = bool(exportable_games) + self.export_label.setVisible(not have_exportable) + self.export_list.setVisible(have_exportable) + self.export_buttons_widget.setVisible(have_exportable) + @staticmethod def select_items(item_list, state): for w in item_list: w.setCheckState(state) - def export_selected(self): - for ew in self.exportable_items: - if ew.is_checked(): - ew.export_game() - self.export_list.takeItem(self.export_list.row(ew)) - self.update_export_list() - def import_selected(self): for iw in self.importable_items: if iw.is_checked: iw.import_game() self.import_list.takeItem(self.import_list.row(iw)) + self.core.egl_sync() self.update_import_list() + def export_selected(self): + for ew in self.exportable_items: + if ew.is_checked(): + ew.export_game() + self.export_list.takeItem(self.export_list.row(ew)) + self.core.egl_sync() + self.update_export_list() + class EGLSyncItem(QListWidgetItem): def __init__(self, game, export: bool, parent=None): diff --git a/rare/resources/stylesheets/RareStyle/stylesheet.qss b/rare/resources/stylesheets/RareStyle/stylesheet.qss index caeb60b8..2e3dea96 100644 --- a/rare/resources/stylesheets/RareStyle/stylesheet.qss +++ b/rare/resources/stylesheets/RareStyle/stylesheet.qss @@ -12,9 +12,13 @@ } QLabel { - border-width: 0px; + border-width: 1px; + border-style: solid; + border-radius: 2px; + border-color: transparent; background-color: transparent; padding: 0px; + selection-background-color: #2f4f4f; } QMenu, diff --git a/rare/ui/components/tabs/games/import_sync/egl_sync_widget.py b/rare/ui/components/tabs/games/import_sync/egl_sync_widget.py index 1fca3a66..583d17fe 100644 --- a/rare/ui/components/tabs/games/import_sync/egl_sync_widget.py +++ b/rare/ui/components/tabs/games/import_sync/egl_sync_widget.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_EGLSyncGroup(object): def setupUi(self, EGLSyncGroup): EGLSyncGroup.setObjectName("EGLSyncGroup") - EGLSyncGroup.resize(772, 368) + EGLSyncGroup.resize(814, 702) EGLSyncGroup.setCheckable(False) EGLSyncGroup.setChecked(False) self.egl_sync_layout = QtWidgets.QFormLayout(EGLSyncGroup) @@ -23,6 +23,9 @@ class Ui_EGLSyncGroup(object): self.egl_path_label = QtWidgets.QLabel(EGLSyncGroup) self.egl_path_label.setObjectName("egl_path_label") self.egl_sync_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.egl_path_label) + self.egl_path_layout = QtWidgets.QHBoxLayout() + self.egl_path_layout.setObjectName("egl_path_layout") + self.egl_sync_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.egl_path_layout) self.egl_path_info_label = QtWidgets.QLabel(EGLSyncGroup) self.egl_path_info_label.setObjectName("egl_path_info_label") self.egl_sync_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.egl_path_info_label) @@ -31,22 +34,48 @@ class Ui_EGLSyncGroup(object): self.egl_path_info.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse) self.egl_path_info.setObjectName("egl_path_info") self.egl_sync_layout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.egl_path_info) + self.egl_sync_label = QtWidgets.QLabel(EGLSyncGroup) + self.egl_sync_label.setObjectName("egl_sync_label") + self.egl_sync_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.egl_sync_label) self.egl_sync_check = QtWidgets.QCheckBox(EGLSyncGroup) self.egl_sync_check.setText("") self.egl_sync_check.setObjectName("egl_sync_check") self.egl_sync_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.egl_sync_check) - self.egl_path_layout = QtWidgets.QHBoxLayout() - self.egl_path_layout.setObjectName("egl_path_layout") - self.egl_sync_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.egl_path_layout) - self.egl_sync_label = QtWidgets.QLabel(EGLSyncGroup) - self.egl_sync_label.setObjectName("egl_sync_label") - self.egl_sync_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.egl_sync_label) - self.export_import_widget = QtWidgets.QWidget(EGLSyncGroup) - self.export_import_widget.setObjectName("export_import_widget") - self.export_import_layout = QtWidgets.QHBoxLayout(self.export_import_widget) - self.export_import_layout.setContentsMargins(0, 0, 0, 0) - self.export_import_layout.setObjectName("export_import_layout") - self.export_group = QtWidgets.QGroupBox(self.export_import_widget) + self.import_export_widget = QtWidgets.QWidget(EGLSyncGroup) + self.import_export_widget.setObjectName("import_export_widget") + self.import_export_layout = QtWidgets.QVBoxLayout(self.import_export_widget) + self.import_export_layout.setContentsMargins(0, 0, 0, 0) + self.import_export_layout.setObjectName("import_export_layout") + self.import_group = QtWidgets.QGroupBox(self.import_export_widget) + self.import_group.setObjectName("import_group") + self.import_layout = QtWidgets.QVBoxLayout(self.import_group) + self.import_layout.setObjectName("import_layout") + self.import_label = QtWidgets.QLabel(self.import_group) + self.import_label.setObjectName("import_label") + self.import_layout.addWidget(self.import_label) + self.import_list = QtWidgets.QListWidget(self.import_group) + self.import_list.setAlternatingRowColors(True) + self.import_list.setObjectName("import_list") + self.import_layout.addWidget(self.import_list) + self.import_buttons_widget = QtWidgets.QWidget(self.import_group) + self.import_buttons_widget.setObjectName("import_buttons_widget") + self.import_buttons_layout = QtWidgets.QHBoxLayout(self.import_buttons_widget) + self.import_buttons_layout.setContentsMargins(0, 0, 0, 0) + self.import_buttons_layout.setObjectName("import_buttons_layout") + self.import_select_all_button = QtWidgets.QPushButton(self.import_buttons_widget) + self.import_select_all_button.setObjectName("import_select_all_button") + self.import_buttons_layout.addWidget(self.import_select_all_button) + self.import_select_none_button = QtWidgets.QPushButton(self.import_buttons_widget) + self.import_select_none_button.setObjectName("import_select_none_button") + self.import_buttons_layout.addWidget(self.import_select_none_button) + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.import_buttons_layout.addItem(spacerItem) + self.import_button = QtWidgets.QPushButton(self.import_buttons_widget) + self.import_button.setObjectName("import_button") + self.import_buttons_layout.addWidget(self.import_button) + self.import_layout.addWidget(self.import_buttons_widget, 0, QtCore.Qt.AlignLeft) + self.import_export_layout.addWidget(self.import_group) + self.export_group = QtWidgets.QGroupBox(self.import_export_widget) self.export_group.setObjectName("export_group") self.export_layout = QtWidgets.QVBoxLayout(self.export_group) self.export_layout.setObjectName("export_layout") @@ -57,8 +86,6 @@ class Ui_EGLSyncGroup(object): self.export_list.setAlternatingRowColors(True) self.export_list.setObjectName("export_list") self.export_layout.addWidget(self.export_list) - spacerItem = QtWidgets.QSpacerItem(40, 4, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.export_layout.addItem(spacerItem) self.export_buttons_widget = QtWidgets.QWidget(self.export_group) self.export_buttons_widget.setObjectName("export_buttons_widget") self.export_buttons_layout = QtWidgets.QHBoxLayout(self.export_buttons_widget) @@ -75,40 +102,9 @@ class Ui_EGLSyncGroup(object): self.export_button = QtWidgets.QPushButton(self.export_buttons_widget) self.export_button.setObjectName("export_button") self.export_buttons_layout.addWidget(self.export_button) - self.export_layout.addWidget(self.export_buttons_widget) - self.export_import_layout.addWidget(self.export_group) - self.import_group = QtWidgets.QGroupBox(self.export_import_widget) - self.import_group.setObjectName("import_group") - self.import_layout = QtWidgets.QVBoxLayout(self.import_group) - self.import_layout.setObjectName("import_layout") - self.import_label = QtWidgets.QLabel(self.import_group) - self.import_label.setObjectName("import_label") - self.import_layout.addWidget(self.import_label) - self.import_list = QtWidgets.QListWidget(self.import_group) - self.import_list.setAlternatingRowColors(True) - self.import_list.setObjectName("import_list") - self.import_layout.addWidget(self.import_list) - spacerItem2 = QtWidgets.QSpacerItem(40, 4, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.import_layout.addItem(spacerItem2) - self.import_buttons_widget = QtWidgets.QWidget(self.import_group) - self.import_buttons_widget.setObjectName("import_buttons_widget") - self.import_buttons_layout = QtWidgets.QHBoxLayout(self.import_buttons_widget) - self.import_buttons_layout.setContentsMargins(0, 0, 0, 0) - self.import_buttons_layout.setObjectName("import_buttons_layout") - self.import_select_all_button = QtWidgets.QPushButton(self.import_buttons_widget) - self.import_select_all_button.setObjectName("import_select_all_button") - self.import_buttons_layout.addWidget(self.import_select_all_button) - self.import_select_none_button = QtWidgets.QPushButton(self.import_buttons_widget) - self.import_select_none_button.setObjectName("import_select_none_button") - self.import_buttons_layout.addWidget(self.import_select_none_button) - spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) - self.import_buttons_layout.addItem(spacerItem3) - self.import_button = QtWidgets.QPushButton(self.import_buttons_widget) - self.import_button.setObjectName("import_button") - self.import_buttons_layout.addWidget(self.import_button) - self.import_layout.addWidget(self.import_buttons_widget) - self.export_import_layout.addWidget(self.import_group) - self.egl_sync_layout.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.export_import_widget) + self.export_layout.addWidget(self.export_buttons_widget, 0, QtCore.Qt.AlignLeft) + self.import_export_layout.addWidget(self.export_group) + self.egl_sync_layout.setWidget(3, QtWidgets.QFormLayout.SpanningRole, self.import_export_widget) self.retranslateUi(EGLSyncGroup) QtCore.QMetaObject.connectSlotsByName(EGLSyncGroup) @@ -120,18 +116,18 @@ class Ui_EGLSyncGroup(object): self.egl_path_label.setText(_translate("EGLSyncGroup", "Prefix/Manifest path")) self.egl_path_info_label.setText(_translate("EGLSyncGroup", "Estimated path")) self.egl_sync_label.setText(_translate("EGLSyncGroup", "Enable automatic sync")) - self.export_group.setTitle(_translate("EGLSyncGroup", "Exportable games")) - self.export_label.setText(_translate("EGLSyncGroup", "No games to export to EGS")) - self.export_list.setSortingEnabled(True) - self.export_select_all_button.setText(_translate("EGLSyncGroup", "Select all")) - self.export_select_none_button.setText(_translate("EGLSyncGroup", "Select none")) - self.export_button.setText(_translate("EGLSyncGroup", "Export")) self.import_group.setTitle(_translate("EGLSyncGroup", "Importable games")) self.import_label.setText(_translate("EGLSyncGroup", "No games to import from EGS")) self.import_list.setSortingEnabled(True) self.import_select_all_button.setText(_translate("EGLSyncGroup", "Select all")) self.import_select_none_button.setText(_translate("EGLSyncGroup", "Select none")) self.import_button.setText(_translate("EGLSyncGroup", "Import")) + self.export_group.setTitle(_translate("EGLSyncGroup", "Exportable games")) + self.export_label.setText(_translate("EGLSyncGroup", "No games to export to EGS")) + self.export_list.setSortingEnabled(True) + self.export_select_all_button.setText(_translate("EGLSyncGroup", "Select all")) + self.export_select_none_button.setText(_translate("EGLSyncGroup", "Select none")) + self.export_button.setText(_translate("EGLSyncGroup", "Export")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/games/import_sync/egl_sync_widget.ui b/rare/ui/components/tabs/games/import_sync/egl_sync_widget.ui index 56f643e1..828d3c4b 100644 --- a/rare/ui/components/tabs/games/import_sync/egl_sync_widget.ui +++ b/rare/ui/components/tabs/games/import_sync/egl_sync_widget.ui @@ -6,8 +6,8 @@ 0 0 - 772 - 368 + 814 + 702 @@ -33,6 +33,9 @@ + + + @@ -50,16 +53,6 @@ - - - - - - - - - - @@ -67,9 +60,16 @@ + + + + + + + - - + + 0 @@ -82,97 +82,6 @@ 0 - - - - Exportable games - - - - - - No games to export to EGS - - - - - - - true - - - true - - - - - - - Qt::Horizontal - - - - 40 - 4 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Select all - - - - - - - Select none - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Export - - - - - - - - - @@ -191,25 +100,15 @@ true + + 1 + true - - - - Qt::Horizontal - - - - 40 - 4 - - - - - + @@ -264,6 +163,87 @@ + + + + Exportable games + + + + + + No games to export to EGS + + + + + + + true + + + 1 + + + true + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Select all + + + + + + + Select none + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Export + + + + + + + + + diff --git a/rare/utils/extra_widgets.py b/rare/utils/extra_widgets.py index afce7a4f..9fa9a453 100644 --- a/rare/utils/extra_widgets.py +++ b/rare/utils/extra_widgets.py @@ -148,22 +148,27 @@ class IndicatorLineEdit(QWidget): self.indicator_label = QLabel() self.indicator_label.setPixmap(icon("ei.info-circle", color="gray").pixmap(16, 16)) self.indicator_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.__indicator(edit_func(text)[0]) self.layout.addWidget(self.indicator_label) if not ph_text: _translate = QCoreApplication.translate self.line_edit.setPlaceholderText(_translate(self.__class__.__name__, "Default")) - if text: - self.line_edit.setText(text) - self.edit_func = edit_func self.save_func = save_func self.line_edit.textChanged.connect(self.__edit) if self.edit_func is None: self.line_edit.textChanged.connect(self.__save) + # lk: this can be placed here to trigger __edit + # lk: it going to save the input again if it is valid which + # lk: is ok to do given the checks don't misbehave (they shouldn't) + # lk: however it is going to edit any "understood" bad input to good input + # lk: and we might not want that (but the validity check reports on the edited string) + # lk: it is also going to trigger this widget's textChanged signal but that gets lost + if text: + self.line_edit.setText(text) + def text(self) -> str: return self.line_edit.text()