Sync Saves
This commit is contained in:
parent
e04b36498b
commit
d77808ee05
7 changed files with 328 additions and 4 deletions
44
Rare/Components/Dialogs/PathInputDialog.py
Normal file
44
Rare/Components/Dialogs/PathInputDialog.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QLabel, QDialog
|
||||
|
||||
from Rare.utils.QtExtensions import PathEdit
|
||||
|
||||
|
||||
class PathInputDialog(QDialog):
|
||||
def __init__(self, title_text, text):
|
||||
super().__init__()
|
||||
self.path = ""
|
||||
|
||||
self.setWindowTitle(title_text)
|
||||
self.info_label = QLabel(text)
|
||||
self.info_label.setWordWrap(True)
|
||||
|
||||
self.input = PathEdit(self.tr("Select directory"), QFileDialog.DirectoryOnly)
|
||||
|
||||
self.layout = QVBoxLayout()
|
||||
self.layout.addWidget(self.info_label)
|
||||
self.layout.addWidget(self.input)
|
||||
|
||||
self.child_layout = QHBoxLayout()
|
||||
self.ok_button = QPushButton("Ok")
|
||||
self.ok_button.clicked.connect(self.ok)
|
||||
self.cancel_button = QPushButton("Cancel")
|
||||
self.cancel_button.clicked.connect(self.cancel)
|
||||
self.child_layout.addStretch()
|
||||
self.child_layout.addWidget(self.ok_button)
|
||||
self.child_layout.addWidget(self.cancel_button)
|
||||
|
||||
self.layout.addLayout(self.child_layout)
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def get_path(self):
|
||||
self.exec_()
|
||||
return self.path
|
||||
|
||||
def cancel(self):
|
||||
self.path = ""
|
||||
self.close()
|
||||
|
||||
def ok(self):
|
||||
self.path = self.input.text()
|
||||
self.close()
|
|
@ -4,6 +4,7 @@ from legendary.core import LegendaryCore
|
|||
|
||||
from Rare import style_path
|
||||
from Rare.Components.Tabs.Account.AccountWidget import MiniWidget
|
||||
from Rare.Components.Tabs.CloudSaves.CloudSaves import SyncSaves
|
||||
from Rare.Components.Tabs.Downloads.DownloadTab import DownloadTab
|
||||
from Rare.Components.Tabs.Games.GamesTab import GameTab
|
||||
from Rare.Components.Tabs.Settings.SettingsTab import SettingsTab
|
||||
|
@ -12,7 +13,7 @@ from Rare.Components.Tabs.Settings.SettingsTab import SettingsTab
|
|||
class TabWidget(QTabWidget):
|
||||
def __init__(self, core: LegendaryCore):
|
||||
super(TabWidget, self).__init__()
|
||||
self.setTabBar(TabBar(2))
|
||||
self.setTabBar(TabBar(3))
|
||||
self.settings = SettingsTab(core)
|
||||
self.game_list = GameTab(core)
|
||||
self.addTab(self.game_list, self.tr("Games"))
|
||||
|
@ -20,13 +21,16 @@ class TabWidget(QTabWidget):
|
|||
self.addTab(self.downloadTab, "Downloads")
|
||||
self.downloadTab.finished.connect(self.game_list.default_widget.game_list.update_list)
|
||||
self.game_list.default_widget.game_list.install_game.connect(lambda x: self.downloadTab.install_game(x))
|
||||
|
||||
self.cloud_saves = SyncSaves(core)
|
||||
self.addTab(self.cloud_saves, "Cloud Saves")
|
||||
# Space Tab
|
||||
self.addTab(QWidget(), "")
|
||||
self.setTabEnabled(2, False)
|
||||
self.setTabEnabled(3, False)
|
||||
|
||||
self.account = QWidget()
|
||||
self.addTab(self.account, "")
|
||||
self.setTabEnabled(3, False)
|
||||
self.setTabEnabled(4, False)
|
||||
# self.settings = SettingsTab(core)
|
||||
|
||||
self.addTab(self.settings, QIcon(style_path + "/Icons/settings.png"), None)
|
||||
|
|
99
Rare/Components/Tabs/CloudSaves/CloudSaves.py
Normal file
99
Rare/Components/Tabs/CloudSaves/CloudSaves.py
Normal file
|
@ -0,0 +1,99 @@
|
|||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QObjectCleanupHandler, Qt
|
||||
from PyQt5.QtWidgets import *
|
||||
from legendary.core import LegendaryCore
|
||||
from legendary.models.game import SaveGameStatus
|
||||
|
||||
from Rare.Components.Dialogs.PathInputDialog import PathInputDialog
|
||||
from Rare.Components.Tabs.CloudSaves.SyncWidget import SyncWidget
|
||||
from Rare.utils.QtExtensions import 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 SyncSaves(QScrollArea):
|
||||
|
||||
def __init__(self, core: LegendaryCore):
|
||||
super(SyncSaves, self).__init__()
|
||||
self.core = core
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.widget = QWidget()
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(WaitingSpinner())
|
||||
layout.addWidget(QLabel("<h4>Loading Cloud Saves</h4>"))
|
||||
layout.addStretch()
|
||||
self.widget.setLayout(layout)
|
||||
self.setWidget(self.widget)
|
||||
|
||||
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()
|
||||
|
||||
self.main_layout = QVBoxLayout()
|
||||
self.title = QLabel(
|
||||
f"<h1>" + self.tr("Cloud Saves") + "</h1>\n" + self.tr("Found Saves for folowing Games"))
|
||||
self.sync_all_button = QPushButton(self.tr("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(self.tr("No Games Found"), self.tr("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
|
||||
if latest_save.get(igame.app_name):
|
||||
sync_widget = SyncWidget(igame, latest_save[igame.app_name], self.core)
|
||||
else:
|
||||
continue
|
||||
self.main_layout.addWidget(sync_widget)
|
||||
self.widgets.append(sync_widget)
|
||||
|
||||
self.widget = QWidget()
|
||||
self.widget.setLayout(self.main_layout)
|
||||
self.setWidget(self.widget)
|
||||
|
||||
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(self.tr("Found no savepath"),
|
||||
self.tr("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()
|
164
Rare/Components/Tabs/CloudSaves/SyncWidget.py
Normal file
164
Rare/Components/Tabs/CloudSaves/SyncWidget.py
Normal file
|
@ -0,0 +1,164 @@
|
|||
import os
|
||||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QHBoxLayout, QLabel
|
||||
from legendary.core import LegendaryCore
|
||||
from legendary.models.game import InstalledGame, SaveGameStatus
|
||||
|
||||
|
||||
|
||||
class _UploadThread(QThread):
|
||||
signal = pyqtSignal()
|
||||
|
||||
def __init__(self, app_name, date_time, save_path, core: LegendaryCore):
|
||||
super(_UploadThread, self).__init__()
|
||||
self.core = core
|
||||
self.app_name = app_name
|
||||
self.date_time = date_time
|
||||
self.save_path = save_path
|
||||
|
||||
def run(self) -> None:
|
||||
self.core.upload_save(self.app_name, self.save_path, self.date_time)
|
||||
|
||||
|
||||
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.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 = self.tr("Path not found")
|
||||
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, (self.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 = QLabel(f"<h2>{igame.title}</h2>")
|
||||
if self.dt_local:
|
||||
local_save_date = QLabel(
|
||||
self.tr("Local Save date: ") + str(self.dt_local.strftime('%Y-%m-%d %H:%M:%S')))
|
||||
else:
|
||||
local_save_date = QLabel(self.tr("No Local Save files"))
|
||||
if dt_remote:
|
||||
cloud_save_date = QLabel(self.tr("Cloud save date: ") + str(dt_remote.strftime('%Y-%m-%d %H:%M:%S')))
|
||||
else:
|
||||
cloud_save_date = QLabel(self.tr("No Cloud saves"))
|
||||
|
||||
if self.res == SaveGameStatus.SAME_AGE:
|
||||
self.logger.info(f'Save game for "{igame.title}" is up to date')
|
||||
status = self.tr("Game is up to date")
|
||||
self.upload_button = QPushButton(self.tr("Upload anyway"))
|
||||
self.download_button = QPushButton(self.tr("Download anyway"))
|
||||
elif self.res == SaveGameStatus.REMOTE_NEWER:
|
||||
status = self.tr("Cloud save is newer")
|
||||
self.download_button = QPushButton(self.tr("Download Cloud saves"))
|
||||
self.download_button.setStyleSheet("""
|
||||
QPushButton{ background-color: lime}
|
||||
""")
|
||||
self.upload_button = QPushButton(self.tr("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 self.dt_local:
|
||||
self.logger.info(f'- Local save date: {self.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 = self.tr("Local save is newer")
|
||||
self.upload_button = QPushButton(self.tr("Upload saves"))
|
||||
self.upload_button.setStyleSheet("""
|
||||
QPushButton{ background-color: lime}
|
||||
""")
|
||||
self.download_button = QPushButton(self.tr("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: {self.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 = QLabel(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 = QLabel(igame.save_path)
|
||||
self.save_path_text.setWordWrap(True)
|
||||
self.change_save_path = QPushButton(self.tr("Change path"))
|
||||
save_path_layout.addWidget(self.save_path_text)
|
||||
save_path_layout.addWidget(self.change_save_path)
|
||||
self.layout.addLayout(save_path_layout)
|
||||
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(self.tr("Uploading..."))
|
||||
self.upload_button.setDisabled(True)
|
||||
self.download_button.setDisabled(True)
|
||||
self.thr = _UploadThread(self.igame.app_name, self.dt_local, self.igame.save_path, self.core)
|
||||
self.thr.finished.connect(self.uploaded)
|
||||
self.thr.start()
|
||||
|
||||
def uploaded(self):
|
||||
self.info_text.setText(self.tr("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(self.tr("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(self.tr("Download finished"))
|
||||
self.upload_button.setDisabled(True)
|
||||
self.download_button.setDisabled(True)
|
||||
self.download_button.setStyleSheet("QPushButton{background-color: black}")
|
0
Rare/Components/Tabs/CloudSaves/__init__.py
Normal file
0
Rare/Components/Tabs/CloudSaves/__init__.py
Normal file
|
@ -18,7 +18,6 @@ class InfoTabs(QTabWidget):
|
|||
|
||||
self.info = GameInfo(core)
|
||||
self.addTab(self.info, "Game Info")
|
||||
self.addTab(QLabel("Coming soon"), "Cloud Saves")
|
||||
self.addTab(QLabel("Coming soon"), "Settings")
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import os
|
||||
|
||||
from PyQt5.QtCore import Qt, QRect, QSize, QPoint, pyqtSignal
|
||||
from PyQt5.QtGui import QMovie
|
||||
from PyQt5.QtWidgets import QLayout, QStyle, QSizePolicy, QLabel, QFileDialog, QHBoxLayout, QWidget, QLineEdit, \
|
||||
QPushButton, QStyleOptionTab, QStylePainter, QTabBar
|
||||
|
||||
from Rare import style_path
|
||||
|
||||
|
||||
class FlowLayout(QLayout):
|
||||
def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1):
|
||||
|
@ -177,3 +180,14 @@ class SideTabBar(QTabBar):
|
|||
painter.translate(-c)
|
||||
painter.drawControl(QStyle.CE_TabBarTabLabel, opt);
|
||||
painter.restore()
|
||||
|
||||
class WaitingSpinner(QLabel):
|
||||
def __init__(self):
|
||||
super(WaitingSpinner, self).__init__()
|
||||
self.setStyleSheet("""
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
""")
|
||||
self.movie = QMovie(style_path+"Icons/loader.gif")
|
||||
self.setMovie(self.movie)
|
||||
self.movie.start()
|
||||
|
|
Loading…
Reference in a new issue