From 88e8022b9e5e4964677affc4ba7c11b01a8e48fb Mon Sep 17 00:00:00 2001 From: Dummerle Date: Thu, 14 Jan 2021 13:01:03 +0100 Subject: [PATCH] Sync Saves Update --- README.md | 1 + Rare/Styles/Icons/loader.gif | Bin 0 -> 1924 bytes Rare/Tabs/GamesInstalled/InstalledList.py | 2 +- Rare/utils/Dialogs/PathInputDialog.py | 48 +++++ .../Dialogs/SyncSaves/SyncSavesDialog.py | 111 ++++++++++++ Rare/utils/Dialogs/SyncSaves/SyncWidget.py | 167 ++++++++++++++++++ Rare/utils/Dialogs/SyncSaves/__init__.py | 0 Rare/utils/Dialogs/SyncSavesDialog.py | 161 ----------------- 8 files changed, 328 insertions(+), 162 deletions(-) create mode 100644 Rare/Styles/Icons/loader.gif create mode 100644 Rare/utils/Dialogs/PathInputDialog.py create mode 100644 Rare/utils/Dialogs/SyncSaves/SyncSavesDialog.py create mode 100644 Rare/utils/Dialogs/SyncSaves/SyncWidget.py create mode 100644 Rare/utils/Dialogs/SyncSaves/__init__.py delete mode 100644 Rare/utils/Dialogs/SyncSavesDialog.py diff --git a/README.md b/README.md index 7d42d740..825be8e7 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Using the exe-file could cause an error with the stylesheets - Translations (English and German) - Design (Pretty Stylesheets, Icons, Icon view for installed Games) - Download Progressbar +- Offline mode ## Images diff --git a/Rare/Styles/Icons/loader.gif b/Rare/Styles/Icons/loader.gif new file mode 100644 index 0000000000000000000000000000000000000000..289295082db046757a845e9536e51653a1efa85e GIT binary patch literal 1924 zcmciDSx{3~90u?RH#dv4Do7F-ElDm(pn_5{QHEf^1cX*x0E>eQgrb3fAPCifEFw$T z5)-q4F@TXh5G6o_f|eygmVlx~6i|vqWUw>T+Rn5u+7O>w>C~A{`+q%m?zzA3n|sdg z0KXj`Y#MAk>@^Jb);w?ieqetd!rRq*eM5qoKKp&QZg#U=Wm+Y?v1cY7XD1(z!k>vr zj>faO?6YwR?5NE#@h1{sgx>)a+Vqsm@_Uvyg#pI30|Dw=J6?`-zKw7aM8%Yo#A{jE zm9@R2ITn|Yf#05KUuy(Ri0--K<6q|@#xcm&VI5<%g{IU*9klKnDyY1w7P6=nGbVZQ9D zeXfgYcMVXtz7R;0X>MS(@-|#UqgO|D>^ID+irzlGq5sZ+qDOGsu`4MVx=DI~AHSbT zyqhxy4JVBZmd_Zb3=@3|z4Om!pDlTi8mY!70FG&ips0;#q#4E`<%@s-t~ZbeR^hKvt4V8 zmloS}eQRf3mAIx$Wd;roQF@(6qD~pdh&s~vxpvBg!)@{t;$36+aQW<=nf@vLqH3Xk ze(n|JLy4M3H!F+7XjY(JyW^4m;!FC>8dl3}o&(mZY!i-~XA!`2wEbFlep^W6LEn9! z^uUal^}3`$|6LQTCkkgH)E6E;wyJZdn$XaOhY5Nd=)?gwU7Q4+v@`}Wf_gnmX;@~!C;V4!zpeQV)#&S3|vUchg z*5YR>JwKDG2)S3^Xd9ojyT0q104Ul1FpFtIACVZq@tH+* zL0OPLf`dr4K@@>i)#}D34aFQoBU+4xvLU1#XbbTpY2_%wMaSFJJJP`+!evLjxc^=y z>6W~kceA*|ztgt4Wr{JT9#|MrzxZ+D`cIoy6b}CvLi$d@T==7~PqXYe{<*k(Mgism zvjmsH5T#0_%Rk~CI7kKI@j(0XMHBa&$=pOQh)g#iLz8Az5eeWGK(GCB;!GG|<%wH}; z)2V0)Qbnt94yoMI;LujSyjuM(`Z^UaIYBcrnYT{K@71o@Hze3q>3+LRI z+*N_n))|d-n*LN`3P6UK1{cem{x=|zrILAnziAneX^qULWiS#kX?y@x#Kj0{3=URG zmHS`?e33_7SiZ1O6=ZilJ0~|!kgn0Tw6?t~w*}7WI`9RtBOBa%2`?p=5{AN+1U&=u~>Vp)u>(2%(u=xTUz;;`QQdK_$=4+_u3 None: + saves = self.core.get_save_games() + self.signal.emit(saves) + + +class SyncGame(QWidget): + def __init__(self): + super(SyncGame, self).__init__() + + +# noinspection PyAttributeOutsideInit +class SyncSavesDialog(QDialog): + def __init__(self, core: LegendaryCore): + super(SyncSavesDialog, self).__init__() + self.core = core + layout = QVBoxLayout() + layout.addWidget(WaitingSpinner()) + layout.addWidget(QLabel("

Loading Cloud Saves

")) + self.setLayout(layout) + + self.start_thread = LoadThread(self.core) + self.start_thread.signal.connect(self.setup_ui) + self.start_thread.start() + self.igames = self.core.get_installed_list() + + def setup_ui(self, saves: list): + self.start_thread.disconnect() + QObjectCleanupHandler().add(self.layout()) + self.main_layout = QVBoxLayout() + self.title = CustomQLabel("

Cloud Saves

\nFound Saves for folowing Games") + self.sync_all_button = QPushButton("Sync all games") + self.sync_all_button.clicked.connect(self.sync_all) + self.main_layout.addWidget(self.title) + self.main_layout.addWidget(self.sync_all_button) + + latest_save = {} + for i in saves: + latest_save[i.app_name] = i + logger.info(f'Got {len(latest_save)} remote save game(s)') + if len(latest_save) == 0: + QMessageBox.information("No Games Found", "Your games don't support cloud save") + self.close() + return + self.widgets = [] + + for igame in self.igames: + game = self.core.get_game(igame.app_name) + if not game.supports_cloud_saves: + continue + sync_widget = SyncWidget(igame, latest_save[igame.app_name], self.core) + self.main_layout.addWidget(sync_widget) + self.widgets.append(sync_widget) + + self.ok_button = QPushButton("Ok") + self.ok_button.clicked.connect(lambda: self.close()) + self.main_layout.addWidget(self.ok_button) + + self.setLayout(self.main_layout) + + def sync_all(self): + for w in self.widgets: + if not w.igame.save_path: + save_path = self.core.get_save_path(w.igame.app_name) + if '%' in save_path or '{' in save_path: + self.logger.info("Could not find save_path") + save_path = PathInputDialog("Found no savepath", + "No save path was found. Please select path or skip") + if save_path == "": + continue + else: + w.igame.save_path = save_path + if w.res == SaveGameStatus.SAME_AGE: + continue + if w.res == SaveGameStatus.REMOTE_NEWER: + w.download() + elif w.res == SaveGameStatus.LOCAL_NEWER: + w.upload() diff --git a/Rare/utils/Dialogs/SyncSaves/SyncWidget.py b/Rare/utils/Dialogs/SyncSaves/SyncWidget.py new file mode 100644 index 00000000..477c3ec2 --- /dev/null +++ b/Rare/utils/Dialogs/SyncSaves/SyncWidget.py @@ -0,0 +1,167 @@ +import os +from logging import getLogger + +from PyQt5.QtCore import QThread, pyqtSignal +from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QHBoxLayout +from legendary.core import LegendaryCore +from legendary.models.game import InstalledGame, SaveGameStatus + +from Rare.ext.QtExtensions import CustomQLabel + + +class _UploadThread(QThread): + signal = pyqtSignal() + + def __init__(self, app_name, latest_save, save_path, core: LegendaryCore): + super(_UploadThread, self).__init__() + self.core = core + self.app_name = app_name + self.latest_save = latest_save + self.save_path = save_path + + def run(self) -> None: + self.core.upload_save(self.app_name, self.latest_save.manifest_nam, self.save_path) + + +class _DownloadThread(QThread): + signal = pyqtSignal() + + def __init__(self, app_name, latest_save, save_path, core: LegendaryCore): + super(_DownloadThread, self).__init__() + self.core = core + self.app_name = app_name + self.latest_save = latest_save + self.save_path = save_path + + def run(self) -> None: + self.core.download_saves(self.app_name, self.latest_save.manifest_name, self.save_path, clean_dir=True) + + +class SyncWidget(QWidget): + def __init__(self, igame: InstalledGame, save, core: LegendaryCore): + super(SyncWidget, self).__init__() + self.layout = QVBoxLayout() + self.setObjectName("syncwidget") + self.setStyleSheet(""" + QWidget#syncwidget{ + border: 2px solid white; + } + """) + self.core = core + self.save = save + self.logger = getLogger("Sync " + igame.app_name) + self.game = self.core.get_game(igame.app_name) + self.igame = igame + self.has_save_path = True + if not igame.save_path: + save_path = self.core.get_save_path(igame.app_name) + if '%' in save_path or '{' in save_path: + status = "PathNotFound" + self.logger.info("Could not find save_path") + else: + igame.save_path = save_path + + if not os.path.exists(igame.save_path): + os.makedirs(igame.save_path) + + self.res, (dt_local, dt_remote) = self.core.check_savegame_state(igame.save_path, save) + if self.res == SaveGameStatus.NO_SAVE: + self.logger.info('No cloud or local savegame found.') + return + game_title = CustomQLabel(f"

{igame.title}

") + if dt_local: + local_save_date = CustomQLabel(f"Local Save date: {dt_local.strftime('%Y-%m-%d %H:%M:%S')}") + else: + local_save_date = CustomQLabel("No Local Save files") + if dt_remote: + cloud_save_date = CustomQLabel(f"Cloud save date: {dt_remote.strftime('%Y-%m-%d %H:%M:%S')}") + else: + cloud_save_date = CustomQLabel(f"No Cloud saves") + + if self.res == SaveGameStatus.SAME_AGE: + self.logger.info(f'Save game for "{igame.title}" is up to date') + status = "Game is up to date" + self.upload_button = QPushButton("Upload anyway") + self.download_button = QPushButton("Download anyway") + if self.res == SaveGameStatus.REMOTE_NEWER: + status = "Cloud save is newer" + self.download_button = QPushButton("Download Cloud saves") + self.download_button.setStyleSheet(""" + QPushButton{ background-color: lime} + """) + self.upload_button = QPushButton("Upload Saves") + self.logger.info(f'Cloud save for "{igame.title}" is newer:') + self.logger.info(f'- Cloud save date: {dt_remote.strftime("%Y-%m-%d %H:%M:%S")}') + if dt_local: + self.logger.info(f'- Local save date: {dt_local.strftime("%Y-%m-%d %H:%M:%S")}') + else: + self.logger.info('- Local save date: N/A') + self.upload_button.setDisabled(True) + self.upload_button.setToolTip("No local save") + + elif self.res == SaveGameStatus.LOCAL_NEWER: + status = "Local save is newer" + self.upload_button = QPushButton("Upload saves") + self.upload_button.setStyleSheet(""" + QPushButton{ background-color: lime} + """) + self.download_button = QPushButton("Download saves") + self.logger.info(f'Local save for "{igame.title}" is newer') + if dt_remote: + self.logger.info(f'- Cloud save date: {dt_remote.strftime("%Y-%m-%d %H:%M:%S")}') + else: + self.logger.info('- Cloud save date: N/A') + self.download_button.setDisabled(True) + self.logger.info(f'- Local save date: {dt_local.strftime("%Y-%m-%d %H:%M:%S")}') + else: + self.logger.error("Error") + return + + self.upload_button.clicked.connect(self.upload) + self.download_button.clicked.connect(self.download) + self.info_text = CustomQLabel(status) + self.layout.addWidget(game_title) + self.layout.addWidget(local_save_date) + self.layout.addWidget(cloud_save_date) + + save_path_layout = QHBoxLayout() + self.save_path_text = CustomQLabel(igame.save_path) + self.change_save_path = QPushButton("Change path") + save_path_layout.addWidget(self.save_path_text) + save_path_layout.addWidget(self.change_save_path) + self.layout.addWidget(self.info_text) + button_layout = QHBoxLayout() + button_layout.addWidget(self.upload_button) + button_layout.addWidget(self.download_button) + self.layout.addLayout(button_layout) + + self.setLayout(self.layout) + + def upload(self): + self.logger.info("Uploading Saves for game " + self.igame.title) + self.info_text.setText("Uploading...") + self.upload_button.setDisabled(True) + self.download_button.setDisabled(True) + thr = _UploadThread(self.igame.app_name, self.save, self.igame.save_path, self.core) + thr.finished.connect(self.uploaded) + thr.start() + + def uploaded(self): + self.info_text.setText("Upload finished") + + def download(self): + if not os.path.exists(self.igame.save_path): + os.makedirs(self.igame.save_path) + self.upload_button.setDisabled(True) + self.download_button.setDisabled(True) + self.logger.info("Downloading Saves for game " + self.igame.title) + self.info_text.setText("Downloading...") + thr = _DownloadThread(self.igame.app_name, self.save, self.igame.save_path, self.core) + thr.finished.connect(self.downloaded) + thr.start() + + def downloaded(self): + self.info_text.setText("Download finished") + self.upload_button.setDisabled(True) + self.download_button.setDisabled(True) + self.download_button.setStyleSheet("QPushButton{background-color: black}") diff --git a/Rare/utils/Dialogs/SyncSaves/__init__.py b/Rare/utils/Dialogs/SyncSaves/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/Rare/utils/Dialogs/SyncSavesDialog.py b/Rare/utils/Dialogs/SyncSavesDialog.py deleted file mode 100644 index 54320048..00000000 --- a/Rare/utils/Dialogs/SyncSavesDialog.py +++ /dev/null @@ -1,161 +0,0 @@ -import os -from logging import getLogger - -from PyQt5.QtCore import QThread, pyqtSignal, QObjectCleanupHandler -from PyQt5.QtWidgets import * -from legendary.core import LegendaryCore -from legendary.models.game import SaveGameStatus - -from Rare.ext.QtExtensions import CustomQLabel, WaitingSpinner - -logger = getLogger("Sync Saves") - - -class LoadThread(QThread): - signal = pyqtSignal(list) - - def __init__(self, core: LegendaryCore): - super(LoadThread, self).__init__() - self.core = core - - def run(self) -> None: - saves = self.core.get_save_games() - self.signal.emit(saves) - - -class SyncGame(QWidget): - def __init__(self): - super(SyncGame, self).__init__() - - -class SyncSavesDialog(QDialog): - def __init__(self, core: LegendaryCore): - super(SyncSavesDialog, self).__init__() - self.core = core - layout = QVBoxLayout() - layout.addWidget(WaitingSpinner()) - layout.addWidget(QLabel("

Loading Cloud Saves

")) - self.setLayout(layout) - - self.start_thread = LoadThread(self.core) - self.start_thread.signal.connect(self.setup_ui) - self.start_thread.start() - self.igames = self.core.get_installed_list() - # self.igames = self.core.get_installed_list() - # self.saves = self.core.get_save_games() - # latest_save = dict() - - # for save in sorted(self.saves, key=lambda a: a.datetime): - # latest_save[save.app_name] = save - - # self.setup_ui() - - def setup_ui(self, saves: list): - self.start_thread.disconnect() - QObjectCleanupHandler().add(self.layout()) - self.main_layout = QVBoxLayout() - self.title = CustomQLabel("

Cloud Saves

\nFound Saves for folowing Games") - self.sync_all_button = QPushButton("Sync all games") - self.main_layout.addWidget(self.title) - self.main_layout.addWidget(self.sync_all_button) - self.status = {} - - latest_save = {} - for i in saves: - latest_save[i.app_name] = i - logger.info(f'Got {len(latest_save)} remote save game(s)') - - if len(latest_save) == 0: - QMessageBox.information("No Games Found", "Your games don't support cloud save") - self.close() - return - for igame in self.igames: - game = self.core.get_game(igame.app_name) - if not game.supports_cloud_saves: - continue - logger.info(f'Checking "{igame.title}" ({igame.app_name})') - - if not igame.save_path: - save_path = self.core.get_save_path(igame.app_name) - if '%' in save_path or '{' in save_path: - status = "PathNotFound" - logger.info("Could not find save_path") - else: - igame.save_path = save_path - - if not os.path.exists(igame.save_path): - os.makedirs(igame.save_path) - - res, (dt_local, dt_remote) = self.core.check_savegame_state(igame.save_path, - latest_save.get(igame.app_name)) - if res == SaveGameStatus.NO_SAVE: - logger.info('No cloud or local savegame found.') - continue - widget = QWidget() - layout = QVBoxLayout() - game_title = CustomQLabel(f"

{igame.title}

") - local_save_date = CustomQLabel(f"Local Save date: {dt_local.strftime('%Y-%m-%d %H:%M:%S')}") - cloud_save_date = CustomQLabel(f"Cloud save date: {dt_remote.strftime('%Y-%m-%d %H:%M:%S')}") - - if res == SaveGameStatus.SAME_AGE: - logger.info(f'Save game for "{igame.title}" is up to date') - status = "Game is up to date" - upload_button = QPushButton("Upload anyway") - download_button = QPushButton("Download anyway") - if res == SaveGameStatus.REMOTE_NEWER: - status = "Cloud save is newer" - download_button = QPushButton("Download Cloud saves") - download_button.setStyleSheet(""" - QPushButton{ background-color: lime} - """) - upload_button = QPushButton("Upload Saves") - logger.info(f'Cloud save for "{igame.title}" is newer:') - logger.info(f'- Cloud save date: {dt_remote.strftime("%Y-%m-%d %H:%M:%S")}') - if dt_local: - logger.info(f'- Local save date: {dt_local.strftime("%Y-%m-%d %H:%M:%S")}') - else: - logger.info('- Local save date: N/A') - upload_button.setDisabled(True) - upload_button.setToolTip("No local save") - - elif res == SaveGameStatus.LOCAL_NEWER: - status = "local is newer" - upload_button = QPushButton("Upload saves") - upload_button.setStyleSheet(""" - QPushButton{ background-color: lime} - """) - download_button = QPushButton("Download saves") - logger.info(f'Local save for "{igame.title}" is newer') - if dt_remote: - logger.info(f'- Cloud save date: {dt_remote.strftime("%Y-%m-%d %H:%M:%S")}') - else: - logger.info('- Cloud save date: N/A') - download_button.setDisabled(True) - logger.info(f'- Local save date: {dt_local.strftime("%Y-%m-%d %H:%M:%S")}') - upload_button.app_name = igame.app_name - download_button.app_name = igame.app_name - upload_button.clicked.connect(lambda: self.upload(upload_button.app_name)) - download_button.clicked.connect(lambda: self.download_save(download_button.app_name)) - mini_layout = QHBoxLayout() - mini_layout.addWidget(upload_button) - mini_layout.addWidget(download_button) - layout.addWidget(game_title) - layout.addWidget(local_save_date) - layout.addWidget(cloud_save_date) - layout.addWidget(CustomQLabel(status)) - layout.addLayout(mini_layout) - self.main_layout.addLayout(layout) - self.status[igame.app_name] = res, dt_remote, dt_local, igame.save_path, latest_save[igame.app_name] - - self.setLayout(self.main_layout) - - def download_save(self, app_name): - logger.info('Downloading saves ') - res, dt_remote, dt_local, save_path, latest_save = self.status[app_name] - self.core.download_saves(app_name, latest_save.manifest_name, save_path) - # todo Threading - - def upload(self, app_name): - logger.info("Uplouding saves") - res, dt_remote, dt_local, save_path, latest_save = self.status[app_name] - self.core.upload_save(app_name, save_path, dt_local)