diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5df27713..7da7f6ba 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,8 +2,6 @@
## What you can do
-To Contribute first fork the repository
-
### Add translations
1. Execute ```pylupdate5 $(find -name "*.py") -ts Rare/languages/{your lang (two letters)}.ts``` in project directrory
@@ -21,4 +19,7 @@ exmples:
Select one Card of the project and implement it or make other changes
-If you made your changes, create a pull request
+##Git crash-course
+To contribute fork the repository and clone **your** repo. Then make your changes, add it to git with `git add .` and upload it to Github with `git commit -m "message"` and `git push` or with your IDE.
+
+If you uploaded your changes, create a pull request
diff --git a/README.md b/README.md
index bf56a771..f8d3df82 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,8 @@ There are more options to contribute.
- If you are a designer, you can add Stylesheets or create a logo or a banner
- You can translate the application
+**Note:** Pull Requests please to "dev" branch
+
More Information is in CONTRIBUTING.md
## Images
diff --git a/Rare/Components/Dialogs/Login/ImportWidget.py b/Rare/Components/Dialogs/Login/ImportWidget.py
index a526c39a..a54c3a46 100644
--- a/Rare/Components/Dialogs/Login/ImportWidget.py
+++ b/Rare/Components/Dialogs/Login/ImportWidget.py
@@ -88,12 +88,15 @@ class ImportWidget(QWidget):
if os.name != "nt":
self.core.egl.appdata_path = os.path.join(self.data_path,
f"drive_c/users/{getuser()}/Local Settings/Application Data/EpicGamesLauncher/Saved/Config/Windows")
+ try:
+ if self.core.auth_import():
+ logger.info(f"Logged in as {self.core.lgd.userdata['displayName']}")
+ self.success.emit()
+ else:
+ logger.warning("Failed to import existing session")
+ except Exception as e:
+ logger.warning(e)
- if self.core.auth_import():
- logger.info(f"Logged in as {self.core.lgd.userdata['displayName']}")
- self.success.emit()
- else:
- print("Lol")
logger.warning("Error: No valid session found")
self.login_text.setText(self.tr("Error: No valid session found"))
self.import_button.setText(self.tr("Import"))
diff --git a/Rare/Components/Dialogs/UninstallDialog.py b/Rare/Components/Dialogs/UninstallDialog.py
new file mode 100644
index 00000000..3a18248d
--- /dev/null
+++ b/Rare/Components/Dialogs/UninstallDialog.py
@@ -0,0 +1,44 @@
+from PyQt5.QtWidgets import QDialog, QLabel, QVBoxLayout, QCheckBox, QFormLayout, QHBoxLayout, QPushButton
+from qtawesome import icon
+
+from custom_legendary.models.game import Game
+
+
+class UninstallDialog(QDialog):
+ def __init__(self, game: Game):
+ super(UninstallDialog, self).__init__()
+ self.setWindowTitle("Uninstall Game")
+ self.info = 0
+ self.layout = QVBoxLayout()
+ self.info_text = QLabel(self.tr("Do you really want to uninstall {}").format(game.app_title))
+ self.layout.addWidget(self.info_text)
+ self.keep_files = QCheckBox(self.tr("Keep Files"))
+ self.form = QFormLayout()
+ self.form.setContentsMargins(0, 10, 0, 10)
+ self.form.addRow(QLabel(self.tr("Do you want to keep files?")), self.keep_files)
+ self.layout.addLayout(self.form)
+
+ self.button_layout = QHBoxLayout()
+ self.ok_button = QPushButton(icon("ei.remove-circle", color="red"), self.tr("Uninstall"))
+ self.ok_button.clicked.connect(self.ok)
+
+ self.cancel_button = QPushButton(self.tr("Cancel"))
+ self.cancel_button.clicked.connect(self.cancel)
+
+ self.button_layout.addStretch(1)
+ self.button_layout.addWidget(self.ok_button)
+ self.button_layout.addWidget(self.cancel_button)
+ self.layout.addLayout(self.button_layout)
+ self.setLayout(self.layout)
+
+ def get_information(self):
+ self.exec_()
+ return self.info
+
+ def ok(self):
+ self.info = {"keep_files": self.keep_files.isChecked()}
+ self.close()
+
+ def cancel(self):
+ self.info = 0
+ self.close()
diff --git a/Rare/Components/MainWindow.py b/Rare/Components/MainWindow.py
index a0116212..d22c9ea9 100644
--- a/Rare/Components/MainWindow.py
+++ b/Rare/Components/MainWindow.py
@@ -1,4 +1,6 @@
-from PyQt5.QtWidgets import QMainWindow
+from PyQt5.QtCore import QSettings
+from PyQt5.QtGui import QCloseEvent
+from PyQt5.QtWidgets import QMainWindow, QMessageBox
from Rare.Components.TabWidget import TabWidget
@@ -8,5 +10,15 @@ class MainWindow(QMainWindow):
super(MainWindow, self).__init__()
self.setGeometry(0, 0, 1200, 800)
self.setWindowTitle("Rare - GUI for legendary")
- self.setCentralWidget(TabWidget(core))
+ self.tab_widget = TabWidget(core)
+ self.setCentralWidget(self.tab_widget)
self.show()
+
+ def closeEvent(self, e: QCloseEvent):
+ if QSettings().value("sys_tray", True, bool):
+ self.hide()
+ e.ignore()
+ elif self.tab_widget.downloadTab.active_game is not None and QMessageBox.question(self, "Close", self.tr("There is a download active. Do you really want to exit app?"), QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes:
+ e.accept()
+ else:
+ e.accept()
diff --git a/Rare/Components/TabWidget.py b/Rare/Components/TabWidget.py
index 3be3189e..26b5adab 100644
--- a/Rare/Components/TabWidget.py
+++ b/Rare/Components/TabWidget.py
@@ -3,10 +3,10 @@ from PyQt5.QtWidgets import QTabWidget, QWidget
from qtawesome import icon
from Rare.Components.TabUtils import TabBar, TabButtonWidget
-from Rare.Components.Tabs.CloudSaves.CloudSaves import SyncSaves
+from Rare.Components.Tabs.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
+from Rare.Components.Tabs.Games import GameTab
+from Rare.Components.Tabs.Settings import SettingsTab
from Rare.utils.Models import InstallOptions
from custom_legendary.core import LegendaryCore
@@ -23,7 +23,7 @@ class TabWidget(QTabWidget):
self.addTab(self.game_list, self.tr("Games"))
self.downloadTab = DownloadTab(core, updates)
self.addTab(self.downloadTab, "Downloads" + (" (" + str(len(updates)) + ")" if len(updates) != 0 else ""))
- self.downloadTab.finished.connect(self.game_list.default_widget.game_list.update_list)
+ self.downloadTab.finished.connect(self.dl_finished)
self.game_list.default_widget.game_list.install_game.connect(lambda x: self.downloadTab.install_game(x))
self.game_list.game_info.info.verify_game.connect(lambda app_name: self.downloadTab.install_game(
@@ -31,7 +31,6 @@ class TabWidget(QTabWidget):
self.tabBarClicked.connect(lambda x: self.game_list.layout.setCurrentIndex(0) if x == 0 else None)
- # Commented, because it is not finished
self.cloud_saves = SyncSaves(core)
self.addTab(self.cloud_saves, "Cloud Saves")
@@ -43,10 +42,15 @@ class TabWidget(QTabWidget):
self.addTab(self.account, "")
self.setTabEnabled(disabled_tab + 1, False)
# self.settings = SettingsTab(core)
- self.addTab(self.settings, icon("fa.gear", color='white'), None)
+
+ self.addTab(self.settings, icon("fa.gear", color='white'), "(!)" if self.settings.about.update_available else "")
self.setIconSize(QSize(25, 25))
self.tabBar().setTabButton(3, self.tabBar().RightSide, TabButtonWidget(core))
+ def dl_finished(self):
+ self.game_list.default_widget.game_list.update_list()
+ self.setTabText(1, "Downloads")
+
def resizeEvent(self, event):
self.tabBar().setMinimumWidth(self.width())
super(TabWidget, self).resizeEvent(event)
diff --git a/Rare/Components/Tabs/Account/AccountWidget.py b/Rare/Components/Tabs/Account/AccountWidget.py
index 9d16b589..8aff5de0 100644
--- a/Rare/Components/Tabs/Account/AccountWidget.py
+++ b/Rare/Components/Tabs/Account/AccountWidget.py
@@ -12,15 +12,16 @@ class MiniWidget(QWidget):
self.layout = QVBoxLayout()
self.core = core
self.layout.addWidget(QLabel("Account"))
- username = str(self.core.lgd.userdata.get("display_name"))
+ username = self.core.lgd.userdata.get("display_name")
if not username:
self.core.login()
- username = str(self.core.lgd.userdata.get("display_name"))
+ username = self.core.lgd.userdata.get("display_name")
- self.layout.addWidget(QLabel(self.tr("Logged in as ") + username))
+ self.layout.addWidget(QLabel(self.tr("Logged in as ") + str(username)))
self.open_browser = QPushButton(self.tr("Account settings"))
- self.open_browser.clicked.connect(self.open_account)
+ self.open_browser.clicked.connect(
+ lambda: webbrowser.open("https://www.epicgames.com/account/personal?productName=epicgames"))
self.layout.addWidget(self.open_browser)
self.logout_button = QPushButton(self.tr("Logout"))
@@ -35,7 +36,5 @@ class MiniWidget(QWidget):
if reply == QMessageBox.Yes:
self.core.lgd.invalidate_userdata()
- QCoreApplication.exit()
-
- def open_account(self):
- webbrowser.open("https://www.epicgames.com/account/personal?productName=epicgames")
+ # restart app
+ QCoreApplication.instance().exit_action(-133742) # restart exit code
diff --git a/Rare/Components/Tabs/CloudSaves/CloudSaves.py b/Rare/Components/Tabs/CloudSaves/CloudSaves.py
deleted file mode 100644
index 72b52b6f..00000000
--- a/Rare/Components/Tabs/CloudSaves/CloudSaves.py
+++ /dev/null
@@ -1,123 +0,0 @@
-from logging import getLogger
-
-from PyQt5.QtCore import QThread, pyqtSignal, Qt
-from PyQt5.QtWidgets import *
-
-from Rare.Components.Dialogs.PathInputDialog import PathInputDialog
-from Rare.Components.Tabs.CloudSaves.SyncWidget import SyncWidget
-from Rare.utils.QtExtensions import WaitingSpinner
-from custom_legendary.core import LegendaryCore
-from custom_legendary.models.game import SaveGameStatus
-
-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.load_saves()
-
- def load_saves(self):
- self.widget = QWidget()
- layout = QVBoxLayout()
- layout.addWidget(WaitingSpinner())
- layout.addWidget(QLabel("
Loading Cloud Saves "))
- 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"" + self.tr("Cloud Saves") + " \n" + self.tr("Found Saves for folowing Games"))
-
- self.main_layout.addWidget(self.title)
-
- saves_games = []
- for i in saves:
- if not i.app_name in saves_games and self.core.is_installed(i.app_name):
- saves_games.append(i.app_name)
- if len(saves_games) == 0:
- # QMessageBox.information(self.tr("No Games Found"), self.tr("Your games don't support cloud save"))
- self.title.setText(
- f"" + self.tr("Cloud Saves") + " \n" + self.tr("Your games does not support Cloud Saves"))
- self.setWidget(self.title)
- return
-
- self.sync_all_button = QPushButton(self.tr("Sync all games"))
- self.sync_all_button.clicked.connect(self.sync_all)
- self.main_layout.addWidget(self.sync_all_button)
-
- latest_save = {}
- for i in sorted(saves, key=lambda a: a.datetime):
- latest_save[i.app_name] = i
-
- logger.info(f'Got {len(latest_save)} remote save game(s)')
-
- 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
- sync_widget.reload.connect(self.reload)
- 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 reload(self):
- self.setWidget(QWidget())
- self.load_saves()
- self.update()
-
- def sync_all(self):
- logger.info("Sync all Games")
- 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_label("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:
- logger.info("Download")
- w.download()
- elif w.res == SaveGameStatus.LOCAL_NEWER:
- logger.info("Upload")
- w.upload()
diff --git a/Rare/Components/Tabs/CloudSaves/SyncWidget.py b/Rare/Components/Tabs/CloudSaves/SyncWidget.py
index e19ce92b..1f24795c 100644
--- a/Rare/Components/Tabs/CloudSaves/SyncWidget.py
+++ b/Rare/Components/Tabs/CloudSaves/SyncWidget.py
@@ -1,8 +1,8 @@
import os
from logging import getLogger
-from PyQt5.QtCore import QThread, pyqtSignal
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QHBoxLayout, QLabel
+from PyQt5.QtCore import QThread, pyqtSignal, Qt
+from PyQt5.QtWidgets import QVBoxLayout, QPushButton, QHBoxLayout, QLabel, QGroupBox
from Rare.Components.Dialogs.PathInputDialog import PathInputDialog
from custom_legendary.core import LegendaryCore
@@ -37,12 +37,14 @@ class _DownloadThread(QThread):
self.core.download_saves(self.app_name, self.latest_save.manifest_name, self.save_path, clean_dir=True)
-class SyncWidget(QWidget):
+class SyncWidget(QGroupBox):
reload = pyqtSignal()
def __init__(self, igame: InstalledGame, save, core: LegendaryCore):
- super(SyncWidget, self).__init__()
+ super(SyncWidget, self).__init__(igame.title)
+ self.setObjectName("group")
self.layout = QVBoxLayout()
+ self.setContentsMargins(10, 20, 10, 20)
self.thr = None
self.core = core
self.save = save
@@ -70,7 +72,7 @@ class SyncWidget(QWidget):
self.logger.info('No cloud or local savegame found.')
return
- game_title = QLabel(f"{igame.title} ")
+ # game_title = QLabel(f"{igame.title} ")
if self.dt_local:
local_save_date = QLabel(
@@ -124,14 +126,16 @@ class SyncWidget(QWidget):
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(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.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.save_path_text.setWordWrap(True)
self.change_save_path = QPushButton(self.tr("Change path"))
+ self.change_save_path.setFixedWidth(100)
self.change_save_path.clicked.connect(self.change_path)
save_path_layout.addWidget(self.save_path_text)
save_path_layout.addWidget(self.change_save_path)
@@ -141,7 +145,7 @@ class SyncWidget(QWidget):
button_layout.addWidget(self.upload_button)
button_layout.addWidget(self.download_button)
self.layout.addLayout(button_layout)
-
+ self.layout.addStretch(1)
self.setLayout(self.layout)
def change_path(self):
diff --git a/Rare/Components/Tabs/CloudSaves/__init__.py b/Rare/Components/Tabs/CloudSaves/__init__.py
index e69de29b..9dc45abb 100644
--- a/Rare/Components/Tabs/CloudSaves/__init__.py
+++ b/Rare/Components/Tabs/CloudSaves/__init__.py
@@ -0,0 +1,121 @@
+from logging import getLogger
+
+from PyQt5.QtCore import QThread, pyqtSignal, Qt
+from PyQt5.QtWidgets import *
+
+from Rare.Components.Dialogs.PathInputDialog import PathInputDialog
+from Rare.Components.Tabs.CloudSaves.SyncWidget import SyncWidget
+from Rare.utils.QtExtensions import WaitingSpinner
+from custom_legendary.core import LegendaryCore
+from custom_legendary.models.game import SaveGameStatus
+
+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.load_saves()
+
+ def load_saves(self):
+ self.widget = QWidget()
+ layout = QVBoxLayout()
+ layout.addWidget(WaitingSpinner())
+ layout.addWidget(QLabel("Loading Cloud Saves "))
+ 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"" + self.tr("Cloud Saves") + " \n" + self.tr("Found Saves for folowing Games"))
+ self.main_layout.addWidget(self.title)
+
+ saves_games = []
+ for i in saves:
+ if not i.app_name in saves_games and self.core.is_installed(i.app_name):
+ saves_games.append(i.app_name)
+ if len(saves_games) == 0:
+ # QMessageBox.information(self.tr("No Games Found"), self.tr("Your games don't support cloud save"))
+ self.title.setText(
+ f"" + self.tr("Cloud Saves") + " \n" + self.tr("Your games does not support Cloud Saves"))
+ self.setWidget(self.title)
+ return
+
+ self.sync_all_button = QPushButton(self.tr("Sync all games"))
+ self.sync_all_button.clicked.connect(self.sync_all)
+ self.main_layout.addWidget(self.sync_all_button)
+
+ latest_save = {}
+ for i in sorted(saves, key=lambda a: a.datetime):
+ latest_save[i.app_name] = i
+
+ logger.info(f'Got {len(latest_save)} remote save game(s)')
+
+ 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
+ sync_widget.reload.connect(self.reload)
+ self.main_layout.addWidget(sync_widget)
+ self.widgets.append(sync_widget)
+
+ self.widget = QWidget()
+ self.main_layout.addStretch(1)
+ self.widget.setLayout(self.main_layout)
+ self.setWidgetResizable(True)
+ self.setWidget(self.widget)
+
+ def reload(self):
+ self.setWidget(QWidget())
+ self.load_saves()
+ self.update()
+
+ def sync_all(self):
+ logger.info("Sync all Games")
+ 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_label("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:
+ logger.info("Download")
+ w.download()
+ elif w.res == SaveGameStatus.LOCAL_NEWER:
+ logger.info("Upload")
+ w.upload()
diff --git a/Rare/Components/Tabs/Downloads/DlQueueWidget.py b/Rare/Components/Tabs/Downloads/DlQueueWidget.py
new file mode 100644
index 00000000..943a48b2
--- /dev/null
+++ b/Rare/Components/Tabs/Downloads/DlQueueWidget.py
@@ -0,0 +1,131 @@
+from logging import getLogger
+
+from PyQt5.QtCore import pyqtSignal
+from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QLabel, QHBoxLayout, QPushButton, QWidget
+from qtawesome import icon
+
+logger = getLogger("QueueWidget")
+
+
+class DlWidget(QWidget):
+ move_up = pyqtSignal(str) # app_name
+ move_down = pyqtSignal(str) # app_name
+ remove = pyqtSignal(str) # app_name
+
+ def __init__(self, index, item):
+ super(DlWidget, self).__init__()
+ self.app_name = item[1].app_name
+ self.layout = QHBoxLayout()
+
+ self.left_layout = QVBoxLayout()
+ self.move_up_button = QPushButton(icon("fa.arrow-up", color="white"), "")
+ if index == 0:
+ self.move_up_button.setDisabled(True)
+ self.move_up_button.clicked.connect(lambda: self.move_up.emit(self.app_name))
+ self.move_up_button.setFixedWidth(20)
+ self.left_layout.addWidget(self.move_up_button)
+
+ self.move_down_buttton = QPushButton(icon("fa.arrow-down", color="white"), "")
+ self.move_down_buttton.clicked.connect(lambda: self.move_down.emit(self.app_name))
+ self.left_layout.addWidget(self.move_down_buttton)
+ self.move_down_buttton.setFixedWidth(20)
+
+ self.layout.addLayout(self.left_layout)
+
+ self.right_layout = QVBoxLayout()
+ self.title = QLabel(item[1].app_title)
+ self.right_layout.addWidget(self.title)
+
+ dl_size = item[-1].dl_size
+ install_size = item[-1].install_size
+
+ self.size = QHBoxLayout()
+
+ self.size.addWidget(QLabel(self.tr("Download size: {} GB").format(dl_size / 1024 ** 3)))
+ self.size.addWidget(QLabel(self.tr("Install size: {} GB").format(install_size / 1024 ** 3)))
+ self.right_layout.addLayout(self.size)
+
+ self.delete = QPushButton(self.tr("Remove Download"))
+ self.delete.clicked.connect(lambda: self.remove.emit(self.app_name))
+ self.right_layout.addWidget(self.delete)
+
+ self.layout.addLayout(self.right_layout)
+ self.setLayout(self.layout)
+
+
+class DlQueueWidget(QGroupBox):
+ update_list = pyqtSignal(list)
+ dl_queue = []
+
+ def __init__(self):
+
+ super(DlQueueWidget, self).__init__()
+ self.setTitle(self.tr("Download Queue"))
+ self.layout = QVBoxLayout()
+ self.setObjectName("group")
+ self.text = QLabel(self.tr("No downloads in queue"))
+ self.layout.addWidget(self.text)
+
+ self.setLayout(self.layout)
+
+ def update_queue(self, dl_queue: list):
+ logger.info("Update Queue " + ", ".join(i[1].app_title for i in dl_queue))
+ self.dl_queue = dl_queue
+ self.setLayout(QVBoxLayout())
+ QWidget().setLayout(self.layout)
+ self.layout = QVBoxLayout()
+
+ if len(dl_queue) == 0:
+ self.layout.addWidget(QLabel(self.tr("No downloads in queue")))
+ self.setLayout(self.layout)
+ return
+
+ for index, item in enumerate(dl_queue):
+ widget = DlWidget(index, item)
+ widget.remove.connect(self.remove)
+ widget.move_up.connect(self.move_up)
+ widget.move_down.connect(self.move_down)
+ self.layout.addWidget(widget)
+ if index + 1 == len(dl_queue):
+ widget.move_down_buttton.setDisabled(True)
+
+ self.setLayout(self.layout)
+
+ def remove(self, app_name):
+ for index, i in enumerate(self.dl_queue):
+ if i[1].app_name == app_name:
+ self.dl_queue.pop(index)
+ break
+ else:
+ logger.warning("BUG! ", app_name)
+ return
+ self.update_list.emit(self.dl_queue)
+ self.update_queue(self.dl_queue)
+
+ def move_up(self, app_name):
+ index: int
+
+ for i, item in enumerate(self.dl_queue):
+ if item[1].app_name == app_name:
+ index = i
+ break
+ else:
+ logger.warning("Could not find appname" + app_name)
+ return
+ self.dl_queue.insert(index - 1, self.dl_queue.pop(index))
+ self.update_list.emit(self.dl_queue)
+ self.update_queue(self.dl_queue)
+
+ def move_down(self, app_name):
+ index: int
+
+ for i, item in enumerate(self.dl_queue):
+ if item[1].app_name == app_name:
+ index = i
+ break
+ else:
+ logger.warning("Could not find appname" + app_name)
+ return
+ self.dl_queue.insert(index + 1, self.dl_queue.pop(index))
+ self.update_list.emit(self.dl_queue)
+ self.update_queue(self.dl_queue)
diff --git a/Rare/Components/Tabs/Downloads/DownloadTab.py b/Rare/Components/Tabs/Downloads/DownloadTab.py
index bad38222..5da62057 100644
--- a/Rare/Components/Tabs/Downloads/DownloadTab.py
+++ b/Rare/Components/Tabs/Downloads/DownloadTab.py
@@ -1,19 +1,16 @@
import datetime
-import os
-import queue
-import subprocess
-import time
from logging import getLogger
from multiprocessing import Queue as MPQueue
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton, QDialog, \
- QListWidget, QHBoxLayout
+ QListWidget, QHBoxLayout, QGroupBox
from Rare.Components.Dialogs.InstallDialog import InstallInfoDialog, InstallDialog
-from Rare.utils.Models import InstallOptions, KillDownloadException
+from Rare.Components.Tabs.Downloads.DlQueueWidget import DlQueueWidget
+from Rare.Components.Tabs.Downloads.DownloadThread import DownloadThread
+from Rare.utils.Models import InstallOptions
from custom_legendary.core import LegendaryCore
-from custom_legendary.downloader.manager import DLManager
from custom_legendary.models.downloading import UIUpdate
from custom_legendary.models.game import Game, InstalledGame
from custom_legendary.utils.selective_dl import games
@@ -21,108 +18,10 @@ from custom_legendary.utils.selective_dl import games
logger = getLogger("Download")
-class DownloadThread(QThread):
- status = pyqtSignal(str)
- statistics = pyqtSignal(UIUpdate)
- kill = False
-
- def __init__(self, dlm: DLManager, core: LegendaryCore, status_queue: MPQueue, igame, repair=False,
- repair_file=None):
- super(DownloadThread, self).__init__()
- self.dlm = dlm
- self.core = core
- self.status_queue = status_queue
- self.igame = igame
- self.repair = repair
- self.repair_file = repair_file
-
- def run(self):
- start_time = time.time()
- try:
-
- self.dlm.start()
- time.sleep(1)
- while self.dlm.is_alive():
- if self.kill:
- # raise KillDownloadException()
- # TODO kill download queue, workers
- pass
- try:
- self.statistics.emit(self.status_queue.get(timeout=1))
- except queue.Empty:
- pass
-
- self.dlm.join()
-
- except KillDownloadException:
- self.status.emit("stop")
- logger.info("Downlaod can be continued later")
- self.dlm.kill()
- return
-
- except Exception as e:
- logger.error(f"Installation failed after {time.time() - start_time:.02f} seconds: {e}")
- self.status.emit("error")
- return
-
- else:
- self.status.emit("dl_finished")
- end_t = time.time()
-
- game = self.core.get_game(self.igame.app_name)
- postinstall = self.core.install_game(self.igame)
- if postinstall:
- self._handle_postinstall(postinstall, self.igame)
-
- dlcs = self.core.get_dlc_for_game(self.igame.app_name)
- if dlcs:
- print('The following DLCs are available for this game:')
- for dlc in dlcs:
- print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})')
- print('Manually installing DLCs works the same; just use the DLC app name instead.')
-
- # install_dlcs = QMessageBox.question(self, "", "Do you want to install the prequisites", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes
- # TODO
- if game.supports_cloud_saves and not game.is_dlc:
- logger.info('This game supports cloud saves, syncing is handled by the "sync-saves" command.')
- logger.info(f'To download saves for this game run "legendary sync-saves {game.app_name}"')
- old_igame = self.core.get_installed_game(game.app_name)
- if old_igame and self.repair and os.path.exists(self.repair_file):
- if old_igame.needs_verification:
- old_igame.needs_verification = False
- self.core.install_game(old_igame)
-
- logger.debug('Removing repair file.')
- os.remove(self.repair_file)
- if old_igame and old_igame.install_tags != self.igame.install_tags:
- old_igame.install_tags = self.igame.install_tags
- self.logger.info('Deleting now untagged files.')
- self.core.uninstall_tag(old_igame)
- self.core.install_game(old_igame)
-
- self.status.emit("finish")
-
- def _handle_postinstall(self, postinstall, igame):
- print('This game lists the following prequisites to be installed:')
- print(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}')
- if os.name == 'nt':
- if QMessageBox.question(self, "", "Do you want to install the prequisites",
- QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
- self.core.prereq_installed(igame.app_name)
- req_path, req_exec = os.path.split(postinstall['path'])
- work_dir = os.path.join(igame.install_path, req_path)
- fullpath = os.path.join(work_dir, req_exec)
- subprocess.call([fullpath, postinstall['args']], cwd=work_dir)
- else:
- self.core.prereq_installed(self.igame.app_name)
-
- else:
- logger.info('Automatic installation not available on Linux.')
-
-
class DownloadTab(QWidget):
finished = pyqtSignal()
thread: QThread
+ dl_queue = []
def __init__(self, core: LegendaryCore, updates: list):
super(DownloadTab, self).__init__()
@@ -146,7 +45,6 @@ class DownloadTab(QWidget):
self.layout.addLayout(self.info_layout)
self.mini_layout = QHBoxLayout()
-
self.prog_bar = QProgressBar()
self.prog_bar.setMaximum(100)
self.mini_layout.addWidget(self.prog_bar)
@@ -158,33 +56,39 @@ class DownloadTab(QWidget):
self.layout.addLayout(self.mini_layout)
- self.update_title = QLabel(f"{self.tr('Updates')} ")
- self.update_title.setStyleSheet("""
- QLabel{
- margin-top: 20px;
- }
- """)
- self.layout.addWidget(self.update_title)
- self.update_widgets = {}
- if not updates:
- self.update_text = QLabel(self.tr("No updates available"))
- self.layout.addWidget(self.update_text)
- else:
- for igame in updates:
- widget = UpdateWidget(core, igame)
- self.update_widgets[igame.app_name] = widget
- self.layout.addWidget(widget)
- widget.update.connect(self.update_game)
+ self.queue_widget = DlQueueWidget()
+ self.layout.addWidget(self.queue_widget)
+ self.queue_widget.update_list.connect(self.update_dl_queue)
+ self.updates = QGroupBox(self.tr("Updates"))
+ self.updates.setObjectName("group")
+ self.update_layout = QVBoxLayout()
+ self.update_widgets = {}
+
+ self.update_text = QLabel(self.tr("No updates available"))
+ self.update_text.setVisible(len(updates) == 0)
+ self.update_layout.addWidget(self.update_text)
+
+ for igame in updates:
+ widget = UpdateWidget(core, igame)
+ self.update_widgets[igame.app_name] = widget
+ self.update_layout.addWidget(widget)
+ widget.update.connect(self.update_game)
+
+ self.updates.setLayout(self.update_layout)
+ self.layout.addWidget(self.updates)
self.layout.addStretch(1)
self.setLayout(self.layout)
+ def update_dl_queue(self, dl_queue):
+ self.dl_queue = dl_queue
+
def stop_download(self):
self.thread.kill = True
def install_game(self, options: InstallOptions):
- game = self.core.get_game(options.app_name, update_meta=True)
+
status_queue = MPQueue()
try:
dlm, analysis, game, igame, repair, repair_file = self.core.prepare_download(
@@ -221,21 +125,27 @@ class DownloadTab(QWidget):
if not analysis.dl_size:
QMessageBox.information(self, "Warning", self.tr("Download size is 0. Game already exists"))
return
+
# Information
+
if not InstallInfoDialog(dl_size=analysis.dl_size, install_size=analysis.install_size).get_accept():
return
+ if self.active_game is None:
+ self.start_installation(dlm, game, status_queue, igame, repair_file, options, analysis)
+ else:
+ self.dl_queue.append((dlm, game, status_queue, igame, repair_file, options, analysis))
+ self.queue_widget.update_queue(self.dl_queue)
+
+ def start_installation(self, dlm, game, status_queue, igame, repair_file, options: InstallOptions, analysis):
+ print("start installation", game.app_title)
self.active_game = game
- self.installing_game_widget.setText(self.tr("Installing game: ")+self.active_game.app_title)
self.thread = DownloadThread(dlm, self.core, status_queue, igame, options.repair, repair_file)
self.thread.status.connect(self.status)
self.thread.statistics.connect(self.statistics)
self.thread.start()
self.kill_button.setDisabled(False)
- self.installing_game.setText("Installing Game: " + self.active_game.app_title)
-
- for i in self.update_widgets.values():
- i.update_button.setDisabled(True)
+ self.installing_game.setText(self.tr("Installing Game: ") + self.active_game.app_title)
def sdl_prompt(self, app_name: str = '', title: str = '') -> list:
sdl = QDialog()
@@ -279,6 +189,8 @@ class DownloadTab(QWidget):
if text == "dl_finished":
pass
elif text == "finish":
+ self.installing_game.setText(self.tr("Download finished. Reload library"))
+
try:
from notifypy import Notify
except ModuleNotFoundError:
@@ -290,30 +202,43 @@ class DownloadTab(QWidget):
notification.send()
# QMessageBox.information(self, "Info", "Download finished")
logger.info("Download finished: " + self.active_game.app_title)
+
+ if self.dl_queue:
+ if self.dl_queue[0][1] == self.active_game.app_name:
+ self.dl_queue.pop(0)
+ self.queue_widget.update_queue(self.dl_queue)
+
if self.active_game.app_name in self.update_widgets.keys():
self.update_widgets[self.active_game.app_name].setVisible(False)
self.update_widgets.pop(self.active_game.app_name)
+ self.active_game = None
+
for i in self.update_widgets.values():
i.update_button.setDisabled(False)
self.finished.emit()
- self.installing_game.setText(self.tr("Installing Game: No active download"))
- self.prog_bar.setValue(0)
+ self.reset_infos()
+
+ if len(self.dl_queue) != 0:
+ self.start_installation(*self.dl_queue[0])
+ else:
+ self.queue_widget.update_queue(self.dl_queue)
- self.dl_speed.setText("")
- self.cache_used.setText("")
- self.downloaded.setText("")
elif text == "error":
QMessageBox.warning(self, "warn", "Download error")
elif text == "stop":
- self.kill_button.setDisabled(True)
- self.installing_game.setText(self.tr("Installing Game: No active download"))
- self.prog_bar.setValue(0)
- self.dl_speed.setText("")
- self.cache_used.setText("")
- self.downloaded.setText("")
+ self.reset_infos()
+
+ def reset_infos(self):
+ self.kill_button.setDisabled(True)
+ self.installing_game.setText(self.tr("Installing Game: No active download"))
+ self.prog_bar.setValue(0)
+ self.dl_speed.setText("")
+ self.time_left.setText("")
+ self.cache_used.setText("")
+ self.downloaded.setText("")
def statistics(self, ui_update: UIUpdate):
self.prog_bar.setValue(ui_update.progress)
@@ -326,23 +251,19 @@ class DownloadTab(QWidget):
return str(datetime.timedelta(seconds=seconds))
def update_game(self, app_name: str):
- print("Update ", app_name)
+ logger.info("Update " + app_name)
infos = InstallDialog(app_name, self.core, True).get_information()
if infos != 0:
path, max_workers, force, ignore_free_space = infos
self.install_game(InstallOptions(app_name=app_name, max_workers=max_workers, path=path,
force=force, ignore_free_space=ignore_free_space))
- def repair(self):
- pass
-
class UpdateWidget(QWidget):
update = pyqtSignal(str)
def __init__(self, core: LegendaryCore, game: InstalledGame):
super(UpdateWidget, self).__init__()
- print(game)
self.core = core
self.game = game
diff --git a/Rare/Components/Tabs/Downloads/DownloadThread.py b/Rare/Components/Tabs/Downloads/DownloadThread.py
new file mode 100644
index 00000000..3fce81ad
--- /dev/null
+++ b/Rare/Components/Tabs/Downloads/DownloadThread.py
@@ -0,0 +1,117 @@
+import os
+import queue
+import subprocess
+import time
+from logging import getLogger
+from multiprocessing import Queue as MPQueue
+
+from PyQt5.QtCore import QThread, pyqtSignal
+from PyQt5.QtWidgets import QMessageBox
+
+from Rare.utils.Models import KillDownloadException
+
+from custom_legendary.core import LegendaryCore
+from custom_legendary.downloader.manager import DLManager
+from custom_legendary.models.downloading import UIUpdate
+
+logger = getLogger("Download")
+
+
+class DownloadThread(QThread):
+ status = pyqtSignal(str)
+ statistics = pyqtSignal(UIUpdate)
+ kill = False
+
+ def __init__(self, dlm: DLManager, core: LegendaryCore, status_queue: MPQueue, igame, repair=False,
+ repair_file=None):
+ super(DownloadThread, self).__init__()
+ self.dlm = dlm
+ self.core = core
+ self.status_queue = status_queue
+ self.igame = igame
+ self.repair = repair
+ self.repair_file = repair_file
+
+ def run(self):
+ start_time = time.time()
+ try:
+
+ self.dlm.start()
+ time.sleep(1)
+ while self.dlm.is_alive():
+ if self.kill:
+ # raise KillDownloadException()
+ # TODO kill download queue, workers
+ pass
+ try:
+ self.statistics.emit(self.status_queue.get(timeout=1))
+ except queue.Empty:
+ pass
+
+ self.dlm.join()
+
+ except KillDownloadException:
+ self.status.emit("stop")
+ logger.info("Downlaod can be continued later")
+ self.dlm.kill()
+ return
+
+ except Exception as e:
+ logger.error(f"Installation failed after {time.time() - start_time:.02f} seconds: {e}")
+ self.status.emit("error")
+ return
+
+ else:
+ self.status.emit("dl_finished")
+ end_t = time.time()
+
+ game = self.core.get_game(self.igame.app_name)
+ postinstall = self.core.install_game(self.igame)
+ if postinstall:
+ self._handle_postinstall(postinstall, self.igame)
+
+ dlcs = self.core.get_dlc_for_game(self.igame.app_name)
+ if dlcs:
+ print('The following DLCs are available for this game:')
+ for dlc in dlcs:
+ print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})')
+ print('Manually installing DLCs works the same; just use the DLC app name instead.')
+
+ # install_dlcs = QMessageBox.question(self, "", "Do you want to install the prequisites", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes
+ # TODO
+ if game.supports_cloud_saves and not game.is_dlc:
+ logger.info('This game supports cloud saves, syncing is handled by the "sync-saves" command.')
+ logger.info(f'To download saves for this game run "legendary sync-saves {game.app_name}"')
+ old_igame = self.core.get_installed_game(game.app_name)
+ if old_igame and self.repair and os.path.exists(self.repair_file):
+ if old_igame.needs_verification:
+ old_igame.needs_verification = False
+ self.core.install_game(old_igame)
+
+ logger.debug('Removing repair file.')
+ os.remove(self.repair_file)
+ if old_igame and old_igame.install_tags != self.igame.install_tags:
+ old_igame.install_tags = self.igame.install_tags
+ self.logger.info('Deleting now untagged files.')
+ self.core.uninstall_tag(old_igame)
+ self.core.install_game(old_igame)
+
+ self.status.emit("finish")
+
+ def _handle_postinstall(self, postinstall, igame):
+ print('This game lists the following prequisites to be installed:')
+ print(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}')
+ if os.name == 'nt':
+ if QMessageBox.question(self, "", "Do you want to install the prequisites",
+ QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
+ self.core.prereq_installed(igame.app_name)
+ req_path, req_exec = os.path.split(postinstall['path'])
+ work_dir = os.path.join(igame.install_path, req_path)
+ fullpath = os.path.join(work_dir, req_exec)
+ subprocess.call([fullpath, postinstall['args']], cwd=work_dir)
+ else:
+ self.core.prereq_installed(self.igame.app_name)
+
+ else:
+ logger.info('Automatic installation not available on Linux.')
+
diff --git a/Rare/Components/Tabs/Games/GameInfo/GameInfo.py b/Rare/Components/Tabs/Games/GameInfo/GameInfo.py
index 159ee834..b67c64e8 100644
--- a/Rare/Components/Tabs/Games/GameInfo/GameInfo.py
+++ b/Rare/Components/Tabs/Games/GameInfo/GameInfo.py
@@ -6,6 +6,7 @@ from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout, QLabel, QHBoxLayo
QProgressBar, QStackedWidget, QGroupBox
from qtawesome import icon
+from Rare.Components.Dialogs.UninstallDialog import UninstallDialog
from Rare.Components.Tabs.Games.GameInfo.GameSettings import GameSettings
from Rare.utils import LegendaryApi
from Rare.utils.LegendaryApi import VerifyThread
@@ -65,6 +66,7 @@ class GameInfo(QWidget):
right_layout.addWidget(self.game_title)
self.dev = QLabel("Error")
+ self.dev.setTextInteractionFlags(Qt.TextSelectableByMouse)
right_layout.addWidget(self.dev)
self.app_name = QLabel("Error")
@@ -72,12 +74,14 @@ class GameInfo(QWidget):
right_layout.addWidget(self.app_name)
self.version = QLabel("Error")
+ self.version.setTextInteractionFlags(Qt.TextSelectableByMouse)
right_layout.addWidget(self.version)
self.install_size = QLabel("Error")
right_layout.addWidget(self.install_size)
self.install_path = QLabel("Error")
+ self.install_path.setTextInteractionFlags(Qt.TextSelectableByMouse)
right_layout.addWidget(self.install_path)
top_layout.addLayout(right_layout)
@@ -95,10 +99,13 @@ class GameInfo(QWidget):
self.setLayout(self.layout)
def uninstall(self):
- if QMessageBox.question(self, "Uninstall", self.tr("Are you sure to uninstall {}").format(self.game.app_title),
- QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
- LegendaryApi.uninstall(self.game.app_name, self.core)
- self.update_list.emit()
+ infos = UninstallDialog(self.game).get_information()
+ if infos == 0:
+ print("Cancel Uninstall")
+ return
+
+ LegendaryApi.uninstall(self.game.app_name, self.core, infos)
+ self.update_list.emit()
def repair(self):
repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{self.game.app_name}.repair')
diff --git a/Rare/Components/Tabs/Games/GameList.py b/Rare/Components/Tabs/Games/GameList.py
index 183dc193..884e3788 100644
--- a/Rare/Components/Tabs/Games/GameList.py
+++ b/Rare/Components/Tabs/Games/GameList.py
@@ -40,6 +40,7 @@ class GameList(QStackedWidget):
self.icon_scrollarea.setWidgetResizable(True)
self.icon_scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
+ self.list_scrollarea.setWidgetResizable(True)
self.list_scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.info_text = self.tr("Installed Games: {} Available Games: {}").format(
@@ -53,7 +54,7 @@ class GameList(QStackedWidget):
self.list_layout = QVBoxLayout()
self.list_layout.addWidget(QLabel(self.info_text))
- IMAGE_DIR = self.settings.value("img_dir", os.path.expanduser("~/.cache/rare"), str)
+ IMAGE_DIR = self.settings.value("img_dir", os.path.expanduser("~/.cache/rare/images"), str)
self.updates = []
self.widgets = {}
@@ -80,7 +81,7 @@ class GameList(QStackedWidget):
icon_widget.show_info.connect(self.show_game_info.emit)
list_widget.show_info.connect(self.show_game_info.emit)
- icon_widget.launch_signal.connect(self.launch)
+ icon_widget.finish_signal.connect(self.launch)
icon_widget.finish_signal.connect(self.finished)
list_widget.launch_signal.connect(self.launch)
list_widget.finish_signal.connect(self.finished)
@@ -100,6 +101,7 @@ class GameList(QStackedWidget):
if not game.app_name in installed:
uninstalled_games.append(game)
+ # add uninstalled games
for game in uninstalled_games:
if os.path.exists(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png"):
pixmap = QPixmap(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png")
@@ -110,15 +112,15 @@ class GameList(QStackedWidget):
pixmap = QPixmap(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png")
else:
- logger.warning(f"No Image found: {self.game.app_title}")
+ logger.warning(f"No Image found: {game.app_title}")
download_image(game, force=True)
pixmap = QPixmap(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png")
icon_widget = IconWidgetUninstalled(game, self.core, pixmap)
- icon_widget.install_game.connect(self.install_game.emit)
+ icon_widget.install_game.connect(self.install)
list_widget = ListWidgetUninstalled(self.core, game, pixmap)
- list_widget.install_game.connect(self.install_game.emit)
+ list_widget.install_game.connect(self.install)
self.icon_layout.addWidget(icon_widget)
self.list_layout.addWidget(list_widget)
@@ -143,6 +145,14 @@ class GameList(QStackedWidget):
if self.settings.value("installed_only", False, bool):
self.installed_only(True)
+ def install(self, options: InstallOptions):
+ icon_widget, list_widget = self.widgets[options.app_name]
+ icon_widget.mousePressEvent = lambda e: None
+ icon_widget.installing = True
+ list_widget.install_button.setDisabled(True)
+ list_widget.installing = True
+ self.install_game.emit(options)
+
def finished(self, app_name):
self.widgets[app_name][0].info_text = ""
self.widgets[app_name][0].info_label.setText("")
diff --git a/Rare/Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py b/Rare/Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py
index 0b1b5224..3a654610 100644
--- a/Rare/Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py
+++ b/Rare/Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py
@@ -25,6 +25,8 @@ class BaseInstalledWidget(QGroupBox):
self.game_running = False
self.update_available = self.core.get_asset(self.game.app_name, True).build_version != igame.version
+
+
self.setContentsMargins(0, 0, 0, 0)
# self.setStyleSheet("border-radius: 5px")
diff --git a/Rare/Components/Tabs/Games/GameWidgets/BaseUninstalledWidget.py b/Rare/Components/Tabs/Games/GameWidgets/BaseUninstalledWidget.py
index 80b48177..74804e51 100644
--- a/Rare/Components/Tabs/Games/GameWidgets/BaseUninstalledWidget.py
+++ b/Rare/Components/Tabs/Games/GameWidgets/BaseUninstalledWidget.py
@@ -17,6 +17,7 @@ class BaseUninstalledWidget(QGroupBox):
self.game = game
self.core = core
self.pixmap = pixmap
+ self.installing = False
self.setContentsMargins(0, 0, 0, 0)
diff --git a/Rare/Components/Tabs/Games/GameWidgets/InstalledIconWidget.py b/Rare/Components/Tabs/Games/GameWidgets/InstalledIconWidget.py
index b2dda22a..85f511fe 100644
--- a/Rare/Components/Tabs/Games/GameWidgets/InstalledIconWidget.py
+++ b/Rare/Components/Tabs/Games/GameWidgets/InstalledIconWidget.py
@@ -37,7 +37,7 @@ class GameWidgetInstalled(BaseInstalledWidget):
if self.pixmap:
w = 200
- self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3))
+ self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3), transformMode=Qt.SmoothTransformation)
self.image = ClickableLabel()
self.image.setObjectName("game_widget")
self.image.setPixmap(self.pixmap)
diff --git a/Rare/Components/Tabs/Games/GameWidgets/InstalledListWidget.py b/Rare/Components/Tabs/Games/GameWidgets/InstalledListWidget.py
index 1a30231d..c25b634f 100644
--- a/Rare/Components/Tabs/Games/GameWidgets/InstalledListWidget.py
+++ b/Rare/Components/Tabs/Games/GameWidgets/InstalledListWidget.py
@@ -1,7 +1,7 @@
import os
from logging import getLogger
-from PyQt5.QtCore import QProcess, pyqtSignal
+from PyQt5.QtCore import QProcess, pyqtSignal, Qt
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QStyle, QVBoxLayout
from qtawesome import icon
@@ -30,7 +30,7 @@ class InstalledListWidget(BaseInstalledWidget):
self.childLayout = QVBoxLayout()
if self.pixmap:
- self.pixmap = self.pixmap.scaled(180, 240)
+ self.pixmap = self.pixmap.scaled(180, 240, transformMode=Qt.SmoothTransformation)
self.image = QLabel()
self.image.setPixmap(self.pixmap)
self.layout.addWidget(self.image)
diff --git a/Rare/Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py b/Rare/Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py
index a5eec79b..32131581 100644
--- a/Rare/Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py
+++ b/Rare/Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py
@@ -18,7 +18,7 @@ class IconWidgetUninstalled(BaseUninstalledWidget):
def __init__(self, game: Game, core: LegendaryCore, pixmap):
super(IconWidgetUninstalled, self).__init__(game, core, pixmap)
self.layout = QVBoxLayout()
-
+ self.setObjectName("game_widget_icon")
if self.pixmap:
w = 200
self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3))
@@ -37,10 +37,17 @@ class IconWidgetUninstalled(BaseUninstalledWidget):
self.setFixedWidth(self.sizeHint().width())
def mousePressEvent(self, e) -> None:
- self.install()
+ if not self.installing:
+ self.install()
def enterEvent(self, e):
- self.info_label.setText(self.tr("Install Game"))
+ if not self.installing:
+ self.info_label.setText(self.tr("Install Game"))
+ else:
+ self.info_label.setText(self.tr("Installation running"))
def leaveEvent(self, e):
- self.info_label.setText("")
+ if self.installing:
+ self.info_label.setText("Installation...")
+ else:
+ self.info_label.setText("")
diff --git a/Rare/Components/Tabs/Games/GamesTab.py b/Rare/Components/Tabs/Games/GamesTab.py
deleted file mode 100644
index 93c27038..00000000
--- a/Rare/Components/Tabs/Games/GamesTab.py
+++ /dev/null
@@ -1,107 +0,0 @@
-from PyQt5.QtCore import QSettings, QSize
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QLineEdit, QPushButton, QStackedLayout, QLabel
-from qtawesome import icon
-
-from Rare.Components.Tabs.Games.GameInfo.GameInfo import InfoTabs
-from Rare.Components.Tabs.Games.GameList import GameList
-from Rare.Components.Tabs.Games.ImportWidget import ImportWidget
-from Rare.utils.QtExtensions import SelectViewWidget
-
-
-class GameTab(QWidget):
- def __init__(self, core):
- super(GameTab, self).__init__()
- self.layout = QStackedLayout()
- self.default_widget = Games(core)
- self.default_widget.game_list.show_game_info.connect(self.show_info)
- self.default_widget.head_bar.import_game.clicked.connect(lambda: self.layout.setCurrentIndex(2))
- self.layout.addWidget(self.default_widget)
- self.game_info = InfoTabs(core)
- self.game_info.info.update_list.connect(self.update_list)
- self.layout.addWidget(self.game_info)
-
- self.default_widget.head_bar.refresh_list.clicked.connect(self.update_list)
-
- self.import_widget = ImportWidget(core)
- self.layout.addWidget(self.import_widget)
- self.import_widget.back_button.clicked.connect(lambda: self.layout.setCurrentIndex(0))
- self.import_widget.update_list.connect(self.update_list)
- self.setLayout(self.layout)
-
- def update_list(self):
- self.default_widget.game_list.update_list(not self.default_widget.head_bar.view.isChecked())
- self.layout.setCurrentIndex(0)
-
- def show_info(self, app_name):
- self.game_info.update_game(app_name)
- self.game_info.setCurrentIndex(1)
- self.layout.setCurrentIndex(1)
-
-
-class Games(QWidget):
- def __init__(self, core):
- super(Games, self).__init__()
- self.layout = QVBoxLayout()
-
- self.head_bar = GameListHeadBar()
- self.head_bar.setObjectName("head_bar")
-
- self.game_list = GameList(core)
-
- self.head_bar.search_bar.textChanged.connect(
- lambda: self.game_list.filter(self.head_bar.search_bar.text()))
-
- self.head_bar.installed_only.stateChanged.connect(lambda:
- self.game_list.installed_only(
- self.head_bar.installed_only.isChecked()))
- self.layout.addWidget(self.head_bar)
- self.layout.addWidget(self.game_list)
- # self.layout.addStretch(1)
- self.head_bar.view.toggled.connect(self.toggle_view)
-
- self.setLayout(self.layout)
-
- def toggle_view(self):
- self.game_list.setCurrentIndex(1 if self.head_bar.view.isChecked() else 0)
- settings = QSettings()
- settings.setValue("icon_view", not self.head_bar.view.isChecked())
-
-
-class GameListHeadBar(QWidget):
- def __init__(self):
- super(GameListHeadBar, self).__init__()
- self.layout = QHBoxLayout()
- self.installed_only = QCheckBox(self.tr("Installed only"))
- self.settings = QSettings()
- self.installed_only.setChecked(self.settings.value("installed_only", False, bool))
- self.layout.addWidget(self.installed_only)
-
- self.layout.addStretch(1)
-
- self.import_game = QPushButton(icon("mdi.import", color="white"), self.tr("Import Game"))
- self.layout.addWidget(self.import_game)
-
- self.layout.addStretch(1)
-
- self.search_bar = QLineEdit()
- self.search_bar.setObjectName("search_bar")
- self.search_bar.setFrame(False)
- icon_label = QLabel()
- icon_label.setPixmap(icon("fa.search", color="white").pixmap(QSize(20, 20)))
- self.layout.addWidget(icon_label)
- self.search_bar.setMinimumWidth(200)
- self.search_bar.setPlaceholderText(self.tr("Search Game"))
- self.layout.addWidget(self.search_bar)
-
- self.layout.addStretch(2)
-
- checked = QSettings().value("icon_view", True, bool)
-
- self.view = SelectViewWidget(checked)
- self.layout.addWidget(self.view)
- self.layout.addStretch(1)
- self.refresh_list = QPushButton()
- self.refresh_list.setIcon(icon("fa.refresh", color="white")) # Reload icon
- self.layout.addWidget(self.refresh_list)
-
- self.setLayout(self.layout)
diff --git a/Rare/Components/Tabs/Games/ImportWidget.py b/Rare/Components/Tabs/Games/ImportWidget.py
index 1a5bd4b6..a4c149a6 100644
--- a/Rare/Components/Tabs/Games/ImportWidget.py
+++ b/Rare/Components/Tabs/Games/ImportWidget.py
@@ -4,7 +4,8 @@ import string
from logging import getLogger
from PyQt5.QtCore import pyqtSignal
-from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, QPushButton, QVBoxLayout, QFileDialog, QMessageBox, QLineEdit
+from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, QPushButton, QVBoxLayout, QFileDialog, QMessageBox, QLineEdit, \
+ QGroupBox
from qtawesome import icon
from Rare.utils import LegendaryApi
@@ -34,11 +35,13 @@ class ImportWidget(QWidget):
self.title = QLabel("Import Game {self.tr('Import existing game from Epic Games Launcher')}")
- self.layout.addWidget(self.import_one_game)
+ # self.import_one_game = QLabel(f"{self.tr('Import existing game from Epic Games Launcher')} ")
+ self.import_one_game = QGroupBox(self.tr('Import existing game from Epic Games Launcher'))
+ self.import_one_game.setObjectName("group")
+ self.gb_layout = QVBoxLayout()
self.import_game_info = QLabel(self.tr("Select path to game"))
- self.layout.addWidget(self.import_game_info)
+ self.gb_layout.addWidget(self.import_game_info)
self.override_app_name_label = QLabel(self.tr("Override app name (Only if imported game from legendary or the app could not find the app name)"))
self.app_name_input = QLineEdit()
@@ -52,17 +55,21 @@ class ImportWidget(QWidget):
self.path_edit = PathEdit(os.path.expanduser("~"), QFileDialog.DirectoryOnly)
self.path_edit.text_edit.textChanged.connect(self.path_changed)
- self.layout.addWidget(self.path_edit)
+ self.gb_layout.addWidget(self.path_edit)
- self.layout.addWidget(self.override_app_name_label)
- self.layout.addWidget(self.app_name_input)
+ self.gb_layout.addWidget(self.override_app_name_label)
+ self.gb_layout.addWidget(self.app_name_input)
self.info_label = QLabel("")
- self.layout.addWidget(self.info_label)
+ self.gb_layout.addWidget(self.info_label)
self.import_button = QPushButton(self.tr("Import Game"))
- self.layout.addWidget(self.import_button)
+ self.gb_layout.addWidget(self.import_button)
self.import_button.clicked.connect(self.import_game)
+ self.import_one_game.setLayout(self.gb_layout)
+
+ self.layout.addWidget(self.import_one_game)
+
self.layout.addStretch(1)
self.auto_import = QLabel(f"{self.tr('Auto import all existing games')} ")
@@ -104,6 +111,7 @@ class ImportWidget(QWidget):
if not path:
path = self.path_edit.text()
if not app_name:
+ # try to find app name
if a_n := self.find_app_name(path):
app_name = a_n
else:
@@ -134,7 +142,7 @@ class ImportWidget(QWidget):
continue
app_name = self.find_app_name(json_path)
if not app_name:
- logger.warning("Could not find app name")
+ logger.warning("Could not find app name at " + game_path)
continue
if LegendaryApi.import_game(self.core, app_name, game_path + path):
@@ -159,4 +167,4 @@ class ImportWidget(QWidget):
QMessageBox.information(self, "Imported Games", self.tr("Successfully imported {} Games. Reloading Library").format(imported))
self.update_list.emit()
else:
- QMessageBox.information(self, "Imported Games", "No Games were found")
+ QMessageBox.information(self, "Imported Games", self.tr("No Games were found"))
diff --git a/Rare/Components/Tabs/Games/__init__.py b/Rare/Components/Tabs/Games/__init__.py
index e69de29b..08ab89cf 100644
--- a/Rare/Components/Tabs/Games/__init__.py
+++ b/Rare/Components/Tabs/Games/__init__.py
@@ -0,0 +1,107 @@
+from PyQt5.QtCore import QSettings, QSize
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QLineEdit, QPushButton, QStackedLayout, QLabel
+from qtawesome import icon
+
+from Rare.Components.Tabs.Games.GameInfo.GameInfo import InfoTabs
+from Rare.Components.Tabs.Games.GameList import GameList
+from Rare.Components.Tabs.Games.ImportWidget import ImportWidget
+from Rare.utils.QtExtensions import SelectViewWidget
+
+
+class GameTab(QWidget):
+ def __init__(self, core):
+ super(GameTab, self).__init__()
+ self.layout = QStackedLayout()
+ self.default_widget = Games(core)
+ self.default_widget.game_list.show_game_info.connect(self.show_info)
+ self.default_widget.head_bar.import_game.clicked.connect(lambda: self.layout.setCurrentIndex(2))
+ self.layout.addWidget(self.default_widget)
+ self.game_info = InfoTabs(core)
+ self.game_info.info.update_list.connect(self.update_list)
+ self.layout.addWidget(self.game_info)
+
+ self.default_widget.head_bar.refresh_list.clicked.connect(self.update_list)
+
+ self.import_widget = ImportWidget(core)
+ self.layout.addWidget(self.import_widget)
+ self.import_widget.back_button.clicked.connect(lambda: self.layout.setCurrentIndex(0))
+ self.import_widget.update_list.connect(self.update_list)
+ self.setLayout(self.layout)
+
+ def update_list(self):
+ self.default_widget.game_list.update_list(self.default_widget.head_bar.view.isChecked())
+ self.layout.setCurrentIndex(0)
+
+ def show_info(self, app_name):
+ self.game_info.update_game(app_name)
+ self.game_info.setCurrentIndex(1)
+ self.layout.setCurrentIndex(1)
+
+
+class Games(QWidget):
+ def __init__(self, core):
+ super(Games, self).__init__()
+ self.layout = QVBoxLayout()
+
+ self.head_bar = GameListHeadBar()
+ self.head_bar.setObjectName("head_bar")
+
+ self.game_list = GameList(core)
+
+ self.head_bar.search_bar.textChanged.connect(
+ lambda: self.game_list.filter(self.head_bar.search_bar.text()))
+
+ self.head_bar.installed_only.stateChanged.connect(lambda:
+ self.game_list.installed_only(
+ self.head_bar.installed_only.isChecked()))
+ self.layout.addWidget(self.head_bar)
+ self.layout.addWidget(self.game_list)
+ # self.layout.addStretch(1)
+ self.head_bar.view.toggled.connect(self.toggle_view)
+
+ self.setLayout(self.layout)
+
+ def toggle_view(self):
+ self.game_list.setCurrentIndex(1 if self.head_bar.view.isChecked() else 0)
+ settings = QSettings()
+ settings.setValue("icon_view", not self.head_bar.view.isChecked())
+
+
+class GameListHeadBar(QWidget):
+ def __init__(self):
+ super(GameListHeadBar, self).__init__()
+ self.layout = QHBoxLayout()
+ self.installed_only = QCheckBox(self.tr("Installed only"))
+ self.settings = QSettings()
+ self.installed_only.setChecked(self.settings.value("installed_only", False, bool))
+ self.layout.addWidget(self.installed_only)
+
+ self.layout.addStretch(1)
+
+ self.import_game = QPushButton(icon("mdi.import", color="white"), self.tr("Import Game"))
+ self.layout.addWidget(self.import_game)
+
+ self.layout.addStretch(1)
+
+ self.search_bar = QLineEdit()
+ self.search_bar.setObjectName("search_bar")
+ self.search_bar.setFrame(False)
+ icon_label = QLabel()
+ icon_label.setPixmap(icon("fa.search", color="white").pixmap(QSize(20, 20)))
+ self.layout.addWidget(icon_label)
+ self.search_bar.setMinimumWidth(200)
+ self.search_bar.setPlaceholderText(self.tr("Search Game"))
+ self.layout.addWidget(self.search_bar)
+
+ self.layout.addStretch(2)
+
+ checked = QSettings().value("icon_view", True, bool)
+
+ self.view = SelectViewWidget(checked)
+ self.layout.addWidget(self.view)
+ self.layout.addStretch(1)
+ self.refresh_list = QPushButton()
+ self.refresh_list.setIcon(icon("fa.refresh", color="white")) # Reload icon
+ self.layout.addWidget(self.refresh_list)
+
+ self.setLayout(self.layout)
diff --git a/Rare/Components/Tabs/Settings/About.py b/Rare/Components/Tabs/Settings/About.py
index ab766115..4e9592da 100644
--- a/Rare/Components/Tabs/Settings/About.py
+++ b/Rare/Components/Tabs/Settings/About.py
@@ -1,6 +1,9 @@
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
+import webbrowser
+
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton
from Rare import __version__
+from Rare.utils.utils import get_latest_version
class About(QWidget):
@@ -13,6 +16,15 @@ class About(QWidget):
self.version = QLabel("Version: " + __version__)
self.layout.addWidget(self.version)
+ latest_tag = get_latest_version()
+ self.update_available = latest_tag != __version__
+ if latest_tag != __version__:
+ print(f"Update available: {__version__} -> {latest_tag}")
+ self.update_available = QLabel(self.tr("Update available: {} -> {}").format(__version__, latest_tag))
+ self.layout.addWidget(self.update_available)
+ self.open_browser = QPushButton(self.tr("Download latest release"))
+ self.layout.addWidget(self.open_browser)
+ self.open_browser.clicked.connect(lambda: webbrowser.open("https://github.com/Dummerle/Rare/releases/latest"))
self.dev = QLabel(self.tr("Developer:") + "Dummerle ")
self.dev.setToolTip("Github")
diff --git a/Rare/Components/Tabs/Settings/Dxvk.py b/Rare/Components/Tabs/Settings/Dxvk.py
index 993d3ba9..14cdbcc7 100644
--- a/Rare/Components/Tabs/Settings/Dxvk.py
+++ b/Rare/Components/Tabs/Settings/Dxvk.py
@@ -41,8 +41,10 @@ class DxvkWidget(QGroupBox):
self.more_settings.setPopupMode(QToolButton.InstantPopup)
self.more_settings.setMenu(QMenu())
self.more_settings.setText("More DXVK settings")
+
action = QWidgetAction(self)
self.more_settings_widget = DxvkMoreSettingsWidget(self.dxvk_settings, self.core)
+ self.more_settings_widget.show_dxvk.connect(lambda x: self.show_dxvk.setChecked(x))
action.setDefaultWidget(self.more_settings_widget)
self.more_settings.menu().addAction(action)
@@ -71,7 +73,8 @@ class DxvkWidget(QGroupBox):
def update_dxvk_active(self):
if self.show_dxvk.isChecked():
if not f"{self.name}.env" in self.core.lgd.config.sections():
- self.core.lgd.config[f"{self.name}.env"] = {}
+ print("add section dxvk")
+ self.core.lgd.config.add_section(f"{self.name}.env")
self.more_settings.setDisabled(False)
for i in self.more_settings_widget.settings:
@@ -93,11 +96,11 @@ class DxvkWidget(QGroupBox):
self.core.lgd.config.remove_option(f"{self.name}.env", "DXVK_HUD")
if not self.core.lgd.config[f"{self.name}.env"]:
self.core.lgd.config.remove_section(f"{self.name}.env")
- print("Remove Section DXVK_HUD")
self.core.lgd.save_config()
class DxvkMoreSettingsWidget(QWidget):
+ show_dxvk = pyqtSignal(bool)
def __init__(self, settings: dict, core: LegendaryCore):
super(DxvkMoreSettingsWidget, self).__init__()
self.layout = QVBoxLayout()
@@ -118,16 +121,22 @@ class DxvkMoreSettingsWidget(QWidget):
y = list(self.settings[tag])
y[0] = checked
self.settings[tag] = tuple(y)
- # print(self.settings)
+
sett = []
logger.debug(self.settings)
for i in self.settings:
check, _ = self.settings[i]
if check:
sett.append(i)
- if sett:
- self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = ",".join(sett)
- self.core.lgd.save_config()
+ if len(sett) != 0:
+ self.core.lgd.config.set(f"{self.name}.env", "DXVK_HUD", ",".join(sett))
+
+ else:
+ self.core.lgd.config.remove_option(f"{self.name}.env", "DXVK_HUD")
+ self.show_dxvk.emit(False)
+ if not self.core.lgd.config.options(f"{self.name}.env"):
+ self.core.lgd.config.remove_section(f"{self.name}.env")
+ self.core.lgd.save_config()
class CheckBox(QCheckBox):
diff --git a/Rare/Components/Tabs/Settings/Legendary.py b/Rare/Components/Tabs/Settings/Legendary.py
index fcc2c428..6046e677 100644
--- a/Rare/Components/Tabs/Settings/Legendary.py
+++ b/Rare/Components/Tabs/Settings/Legendary.py
@@ -10,14 +10,15 @@ from custom_legendary.core import LegendaryCore
logger = getLogger("LegendarySettings")
-class LegendarySettings(QWidget):
+class LegendarySettings(QGroupBox):
def __init__(self, core: LegendaryCore):
super(LegendarySettings, self).__init__()
+ self.setTitle(self.tr("Legendary settings"))
self.layout = QVBoxLayout()
self.core = core
- self.title = QLabel("" + self.tr("Legendary settings") + " ")
- self.layout.addWidget(self.title)
-
+ #self.title = QLabel("" + self.tr("Legendary settings") + " ")
+ #self.layout.addWidget(self.title)
+ self.setObjectName("group")
# Default installation directory
self.select_path = PathEdit(core.get_default_install_dir(), type_of_file=QFileDialog.DirectoryOnly,
infotext="Default")
@@ -39,8 +40,7 @@ class LegendarySettings(QWidget):
#cleanup
self.clean_layout = QVBoxLayout()
- self.cleanup_widget = QGroupBox()
- self.cleanup_widget.setTitle(self.tr("Cleanup"))
+ self.cleanup_widget = QGroupBox(self.tr("Cleanup"))
self.clean_button = QPushButton(self.tr("Remove everything"))
self.clean_button.clicked.connect(lambda: self.cleanup(False))
self.clean_layout.addWidget(self.clean_button)
@@ -59,20 +59,20 @@ class LegendarySettings(QWidget):
self.core.lgd.config["Legendary"]["install_dir"] = self.select_path.text()
if self.select_path.text() == "" and "install_dir" in self.core.lgd.config["Legendary"].keys():
self.core.lgd.config["Legendary"].pop("install_dir")
- logger.info("Remove install_dir section")
else:
logger.info("Set config install_dir to " + self.select_path.text())
self.core.lgd.save_config()
def max_worker_save(self, num_workers: str):
- self.core.lgd.config["Legendary"]["max_workers"] = num_workers
if num_workers == "":
- self.core.lgd.config["Legendary"].pop("max_workers")
+ self.core.lgd.config.remove_option("Legendary", "max_workers")
+ self.core.lgd.save_config()
return
num_workers = int(num_workers)
if num_workers == 0:
- self.core.lgd.config["Legendary"].pop("max_workers")
- logger.info("Updating config for max_workers")
+ self.core.lgd.config.remove_option("Legendary", "max_workers")
+ else:
+ self.core.lgd.config.set("Legendary", "max_workers", str(num_workers))
self.core.lgd.save_config()
def cleanup(self, keep_manifests):
@@ -92,7 +92,7 @@ class LegendarySettings(QWidget):
after = self.core.lgd.get_dir_size()
logger.info(f'Cleanup complete! Removed {(before - after) / 1024 / 1024:.02f} MiB.')
- if cleaned := (before-after) != 0:
+ if cleaned := (before-after) > 0:
QMessageBox.information(self, "Cleanup", self.tr("Cleanup complete! Successfully removed {} MB").format(round(cleaned / 1024 / 1024, 3)))
else:
QMessageBox.information(self, "Cleanup", "Nothing to clean")
diff --git a/Rare/Components/Tabs/Settings/Rare.py b/Rare/Components/Tabs/Settings/Rare.py
index 6587ab1b..b586f531 100644
--- a/Rare/Components/Tabs/Settings/Rare.py
+++ b/Rare/Components/Tabs/Settings/Rare.py
@@ -3,7 +3,7 @@ import shutil
from logging import getLogger
from PyQt5.QtCore import QSettings
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QFileDialog, QComboBox, QPushButton, QCheckBox
+from PyQt5.QtWidgets import QVBoxLayout, QFileDialog, QComboBox, QPushButton, QCheckBox, QGroupBox
from Rare.Components.Tabs.Settings.SettingsWidget import SettingsWidget
from Rare.utils.QtExtensions import PathEdit
@@ -12,21 +12,16 @@ from Rare.utils.utils import get_lang, get_possible_langs
logger = getLogger("RareSettings")
-class RareSettings(QWidget):
+class RareSettings(QGroupBox):
def __init__(self):
super(RareSettings, self).__init__()
+ self.setTitle(self.tr("Rare settings"))
+ self.setObjectName("group")
self.layout = QVBoxLayout()
- self.title = QLabel("" + self.tr("Rare settings") + " ")
- self.layout.addWidget(self.title)
settings = QSettings()
- img_dir = settings.value("img_dir", type=str)
- language = settings.value("language", type=str)
- # default settings
- if not img_dir:
- settings.setValue("img_dir", os.path.expanduser("~/.cache/rare/"))
- if not language:
- settings.setValue("language", get_lang())
- del settings
+ img_dir = settings.value("img_dir", os.path.expanduser("~/.cache/rare/images/"), type=str)
+ language = settings.value("language", get_lang(), type=str)
+
# select Image dir
self.select_path = PathEdit(img_dir, type_of_file=QFileDialog.DirectoryOnly)
self.select_path.text_edit.textChanged.connect(lambda t: self.save_path_button.setDisabled(False))
@@ -37,19 +32,27 @@ class RareSettings(QWidget):
# Select lang
self.select_lang = QComboBox()
- languages = ["English", "Deutsch"]
+ languages = ["English", "Deutsch", "Français"]
self.select_lang.addItems(languages)
if language in get_possible_langs():
if language == "de":
self.select_lang.setCurrentIndex(1)
elif language == "en":
self.select_lang.setCurrentIndex(0)
+ elif language == "fr":
+ self.select_lang.setCurrentIndex(2)
else:
self.select_lang.setCurrentIndex(0)
self.lang_widget = SettingsWidget(self.tr("Language"), self.select_lang)
self.select_lang.currentIndexChanged.connect(self.update_lang)
self.layout.addWidget(self.lang_widget)
+ self.exit_to_sys_tray = QCheckBox(self.tr("Hide to System Tray Icon"))
+ self.exit_to_sys_tray.setChecked(settings.value("sys_tray", True, bool))
+ self.exit_to_sys_tray.stateChanged.connect(self.update_sys_tray)
+ self.sys_tray_widget = SettingsWidget(self.tr("Exit to System Tray Icon"), self.exit_to_sys_tray)
+ self.layout.addWidget(self.sys_tray_widget)
+
self.game_start_accept = QCheckBox(self.tr("Confirm launch of game"))
self.game_start_accept.stateChanged.connect(self.update_start_confirm)
self.game_start_accept_widget = SettingsWidget(self.tr("Confirm launch of game"), self.game_start_accept)
@@ -59,6 +62,10 @@ class RareSettings(QWidget):
self.setLayout(self.layout)
+ def update_sys_tray(self):
+ settings = QSettings()
+ settings.setValue("sys_tray", self.exit_to_sys_tray.isChecked())
+
def update_start_confirm(self):
settings = QSettings()
settings.setValue("confirm_start", self.game_start_accept.isChecked())
@@ -73,7 +80,8 @@ class RareSettings(QWidget):
settings.setValue("language", "en")
elif i == 1:
settings.setValue("language", "de")
- del settings
+ elif i == 2:
+ settings.setValue("language", "fr")
self.lang_widget.info_text.setText(self.tr("Restart Application to activate changes"))
def update_path(self):
diff --git a/Rare/Components/Tabs/Settings/SettingsTab.py b/Rare/Components/Tabs/Settings/SettingsTab.py
deleted file mode 100644
index dfe5443b..00000000
--- a/Rare/Components/Tabs/Settings/SettingsTab.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import os
-
-from PyQt5.QtWidgets import QTabWidget
-
-from Rare.Components.Tabs.Settings.About import About
-from Rare.Components.Tabs.Settings.Legendary import LegendarySettings
-from Rare.Components.Tabs.Settings.Linux import LinuxSettings
-from Rare.Components.Tabs.Settings.Rare import RareSettings
-from Rare.utils.QtExtensions import SideTabBar
-
-
-class SettingsTab(QTabWidget):
- def __init__(self, core):
- super(SettingsTab, self).__init__()
- self.core = core
- self.setTabBar(SideTabBar())
- self.setTabPosition(QTabWidget.West)
- self.addTab(RareSettings(), "Rare")
- self.addTab(LegendarySettings(core), "Legendary")
- if os.name != "nt":
- self.addTab(LinuxSettings(core), "Linux")
- self.addTab(About(), "About")
diff --git a/Rare/Components/Tabs/Settings/__init__.py b/Rare/Components/Tabs/Settings/__init__.py
index e69de29b..6674fb52 100644
--- a/Rare/Components/Tabs/Settings/__init__.py
+++ b/Rare/Components/Tabs/Settings/__init__.py
@@ -0,0 +1,24 @@
+import os
+
+from PyQt5.QtWidgets import QTabWidget
+
+from Rare.Components.Tabs.Settings.About import About
+from Rare.Components.Tabs.Settings.Legendary import LegendarySettings
+from Rare.Components.Tabs.Settings.Linux import LinuxSettings
+from Rare.Components.Tabs.Settings.Rare import RareSettings
+from Rare.utils.QtExtensions import SideTabBar
+
+
+class SettingsTab(QTabWidget):
+ def __init__(self, core):
+ super(SettingsTab, self).__init__()
+ self.core = core
+ self.setTabBar(SideTabBar())
+ self.setTabPosition(QTabWidget.West)
+ self.addTab(RareSettings(), "Rare")
+ self.addTab(LegendarySettings(core), "Legendary")
+ if os.name != "nt":
+ self.addTab(LinuxSettings(core), "Linux")
+ self.about = About()
+
+ self.addTab(self.about, "About (!)" if self.about.update_available else "About")
diff --git a/Rare/Components/TrayIcon.py b/Rare/Components/TrayIcon.py
new file mode 100644
index 00000000..3b80f061
--- /dev/null
+++ b/Rare/Components/TrayIcon.py
@@ -0,0 +1,22 @@
+from PyQt5.QtCore import QCoreApplication
+from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QAction
+from qtawesome import icon
+
+
+class TrayIcon(QSystemTrayIcon):
+ def __init__(self, parent):
+ super(TrayIcon, self).__init__(parent)
+ self.setIcon(icon("ei.cogs", color="white"))
+ self.setVisible(True)
+ self.setToolTip("Rare")
+
+ self.menu = QMenu()
+
+ self.start_rare = QAction("Rare")
+ self.menu.addAction(self.start_rare)
+
+ self.exit_action = QAction(self.tr("Exit"))
+ self.menu.addSeparator()
+ self.menu.addAction(self.exit_action)
+
+ self.setContextMenu(self.menu)
diff --git a/Rare/Main.py b/Rare/Main.py
index bb3b1566..a4283de4 100644
--- a/Rare/Main.py
+++ b/Rare/Main.py
@@ -2,21 +2,30 @@ import configparser
import logging
import os
import sys
+import time
from PyQt5.QtCore import QSettings, QTranslator
from PyQt5.QtGui import QIcon
-from PyQt5.QtWidgets import QApplication
+from PyQt5.QtWidgets import QApplication, QSystemTrayIcon
from Rare import lang_path, style_path
from Rare.Components.Launch.LaunchDialog import LaunchDialog
from Rare.Components.MainWindow import MainWindow
+from Rare.Components.TrayIcon import TrayIcon
from Rare.utils.utils import get_lang
from custom_legendary.core import LegendaryCore
+start_time = time.strftime('%y-%m-%d--%H:%M') # year-month-day-hour-minute
+file_name = os.path.expanduser(f"~/.cache/rare/logs/Rare_{start_time}.log")
+if not os.path.exists(os.path.dirname(file_name)):
+ os.makedirs(os.path.dirname(file_name))
+
logging.basicConfig(
format='[%(name)s] %(levelname)s: %(message)s',
- level=logging.INFO
-)
+ level=logging.INFO,
+ filename=file_name,
+ filemode="w"
+ )
logger = logging.getLogger("Rare")
@@ -40,6 +49,7 @@ class App(QApplication):
self.core.lgd.save_config()
# set Application name for settings
+ self.mainwindow = None
self.setApplicationName("Rare")
self.setOrganizationName("Rare")
settings = QSettings()
@@ -58,6 +68,8 @@ class App(QApplication):
self.setStyleSheet(open(style_path + "RareStyle.qss").read())
self.setWindowIcon(QIcon(style_path + "Logo.png"))
+ # tray icon
+
# launch app
self.launch_dialog = LaunchDialog(self.core)
self.launch_dialog.start_app.connect(self.start_app)
@@ -65,10 +77,27 @@ class App(QApplication):
def start_app(self):
self.mainwindow = MainWindow(self.core)
- # close launch dialog after app widgets were created
+ self.tray_icon = TrayIcon(self)
+ self.tray_icon.exit_action.triggered.connect(lambda: exit(0))
+ self.tray_icon.start_rare.triggered.connect(self.mainwindow.show)
+ self.tray_icon.activated.connect(self.tray)
+ self.mainwindow.tab_widget.downloadTab.finished.connect(lambda: self.tray_icon.showMessage(
+ self.tr("Download finished"), self.tr("Download finished. Game is playable now"),
+ QSystemTrayIcon.Information, 4000))
self.launch_dialog.close()
+ def tray(self, reason):
+ if reason == QSystemTrayIcon.DoubleClick:
+ self.mainwindow.show()
+ logger.info("Show App")
+
def start():
- app = App()
- app.exec_()
+ while True:
+ app = App()
+ exit_code = app.exec_()
+ # if not restart
+ if exit_code != -133742:
+ break
+ # restart app
+ del app
diff --git a/Rare/Styles/RareStyle.qss b/Rare/Styles/RareStyle.qss
index d6670020..672096d1 100644
--- a/Rare/Styles/RareStyle.qss
+++ b/Rare/Styles/RareStyle.qss
@@ -30,8 +30,23 @@ QTabBar::tab:hover#main_tab_bar {
}
+QGroupBox{
+ padding: 4px;
+ margin: 8px;
+}
+
+QGroupBox#game_widget_icon{
+ border: none;
+ padding: 0;
+ margin: 0;
+}
+
QGroupBox#group{
font-size: 15px;
+ font-weight: bold;
+ border: 1px solid white;
+ margin-top: 10px;
+ padding: 8px;
}
QTabBar::tab:disabled {
diff --git a/Rare/__init__.py b/Rare/__init__.py
index b9ef77b6..4b051139 100644
--- a/Rare/__init__.py
+++ b/Rare/__init__.py
@@ -1,5 +1,5 @@
import os
-__version__ = "0.9.8.3"
+__version__ = "0.9.9"
style_path = os.path.join(os.path.dirname(__file__), "Styles/")
lang_path = os.path.join(os.path.dirname(__file__), "languages/")
diff --git a/Rare/__main__.py b/Rare/__main__.py
index 77a3c6b8..209f7c10 100644
--- a/Rare/__main__.py
+++ b/Rare/__main__.py
@@ -14,20 +14,3 @@ def main():
if __name__ == '__main__':
main()
-
-"""
- tray = QSystemTrayIcon()
- tray.setIcon(icon("fa.gamepad", color="white"))
- tray.setVisible(True)
- menu = QMenu()
- option1 = QAction("Geeks for Geeks")
- option1.triggered.connect(lambda: app.exec_())
- option2 = QAction("GFG")
- menu.addAction(option1)
- menu.addAction(option2)
- # To quit the app
- quit = QAction("Quit")
- quit.triggered.connect(app.quit)
- menu.addAction(quit)
- # Adding options to the System Tray
- tray.setContextMenu(menu)"""
diff --git a/Rare/languages/de.qm b/Rare/languages/de.qm
index c3cc547d..73a052a2 100644
Binary files a/Rare/languages/de.qm and b/Rare/languages/de.qm differ
diff --git a/Rare/languages/de.ts b/Rare/languages/de.ts
index 04d4deed..445511c8 100644
--- a/Rare/languages/de.ts
+++ b/Rare/languages/de.ts
@@ -4,12 +4,12 @@
About
-
+
Developer:
Entwickler:
-
+
Legendary developer:
Legendary Entwickler:
@@ -19,15 +19,38 @@
Dies ist eine beta version, also können Bugs entstehen. Wenn du einen Bug bemerkst, kontaktiere mich, indem du einen Issue auf <a href='https://github.com/Dummerle/Rare/issues'>Github</a> erstellst oder mir auf Discord eine Nachricht schickst. Ebenso bei einem Wunsch für Features
-
+
This is a beta version, so you can get bugs. If you get a bug, please report it by creating a Issue on <a href='https://github.com/Dummerle/Rare/issues'>Github</a>. You can also contact me on Discord (Dummerle#7419). Or you can join the <a href='https://discord.gg/YvmABK9YSk'>Discord server</a>
Dies ist eine Betaversion, also können Bugs und andere Unschönheiten auftreten. Falls ein Bug auftritt, bitte auf <a href='https://github.com/Dummerle/Rare/issues'>Github</a> melden, indem du einen Issue erstellst oder auf Discord. (Dummerle#7419). Ein Rare <a href='https://discord.gg/YvmABK9YSk'>Discord server</a> existiert ebenfalls
+
+
+ Update available: {} -> {}
+ Update verfügbar: {} -> {}
+
+
+
+ Download latest release
+ Neueste Version herunterladen
+
+
+
+ App
+
+
+ Download finished
+ Download abgeschlossen
+
+
+
+ Download finished. Game is playable now
+ Downlaod abgeschlossen. Spiel kann jetzt gespielt werden
+
BaseInstalledWidget
-
+
Do you want to launch {}
Möchtest du {} starten
@@ -55,68 +78,129 @@
Laden...
+
+ DlQueueWidget
+
+
+ Download Queue
+ Eingereihte Downloads
+
+
+
+ No downloads in queue
+ Keine eingereihten Downloads
+
+
+
+ DlWidget
+
+
+ Download size: {} GB
+ Download Größe: {} GB
+
+
+
+ Install size: {} GB
+ Installierte Größe: {} GB
+
+
+
+ Remove Download
+ Download löschen
+
+
DownloadTab
-
+
No active Download
Kein aktiver Download
-
+
Stop Download
Download anhalten
-
+
No updates available
Keine Updates verfügbar
-
+
Error preparing download
Fehler beim Vorbereiten des Downloads
-
+
Download size is 0. Game already exists
Die Größe des Downloads ist 0. Spiel existiert bereits
-
+
Installation finished
Installation abgeschlossen
-
+
Installing Game: No active download
Installierendes Spiel: Kein aktiver Download
-
+
Download speed
Geschwindigkeit
-
+
Cache used
Benutzter Cache
-
+
Downloaded
Runtergeladen
-
+
Time left:
Zeit übrig:
-
+
Finished Download of game {}
Downlaod von {} abgeschlossen
+
+
+ Download finished. Reload library
+ Download abgeschlossen. Spiele neu laden
+
+
+
+ Download queue: Empty
+ Anschließende Downloads: Keine
+
+
+
+ Download queue:
+ Anschließende Downloads:
+
+
+
+ Empty
+ Keine
+
+
+
+ Installing Game:
+ Installierendes Spiel:
+
+
+
+ Updates
+ Updates
+
DxvkWidget
@@ -159,32 +243,32 @@
GameActions
-
+
Uninstall game
Spiel deinstallieren
-
+
Uninstall
Deinstallieren
-
+
Verify Game
Spieldateien überprüfen
-
+
Verify
Überprüfen
-
+
Repair Game
Spiel reparieren
-
+
Repair
Reparieren
@@ -192,50 +276,50 @@
GameInfo
-
+
Repair file does not exist or game does not need a repair. Please verify game first
Reparationsdatei existiert nicht oder das Spiel braucht keine Reperatur. Bitte das spiel zuerst überprüfen
-
+
Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?
Überprüfung fehlgeschlagen, {} Datei(en) fehlerhaft, {} Datei(en) fehlen. Willst du das Spiel reparieren?
-
+
Developer:
Entwickler:
-
+
Install size:
Größe:
-
+
Install path:
Installationsordner:
Are you sure to uninstall {}
- Möchtest du {} wirklich deinstallieren
+ Möchtest du {} wirklich deinstallieren
GameList
-
+
Launch
Starten
-
+
Game running
Spiel läuft
-
+
Installed Games: {} Available Games: {}
Installierte Spiele: {} Verfügbare Spiele: {}
@@ -243,17 +327,17 @@
GameListHeadBar
-
+
Installed only
Nur Installierte
-
+
Import Game
Spiel importieren
-
+
Search Game
Spiel suchen
@@ -322,15 +406,20 @@
IconWidgetUninstalled
-
+
Install Game
Spiel installieren
+
+
+ Installation running
+ Installation läuft
+
ImportWidget
-
+
Import
Importieren
@@ -355,27 +444,27 @@
Laden...
-
+
Error: No valid session found
Keine valide Session gefunden
-
+
Back
Zurück
-
+
Select path to game
Wähle den Pfad zum Spiel
-
+
Import Game
Spiel importieren
-
+
Import all games from Epic Games Launcher
Alle Spiele aus dem Epic Games Launcher importieren
@@ -385,45 +474,55 @@
{} Spiele erfolgreich importiert
-
+
Override app name (Only if imported game from legendary or the app could not find the app name)
App Name überschreiben (Nur falls das Spiel von Legendary importiert wird oder der App Name nicht gefunden wird
-
+
Could not find app name
Konnte den Appnamen nicht finden
-
+
Successfully imported {}. Reload library
Erfolgreich {} importiert. Spiele neu laden
-
+
Failed to import {}
{} Konnte nicht importiert werden
-
+
Successfully imported {} Games. Reloading Library
Erfolgreich {} Spiele importiert. Spiele neu laden
+
+
+ Import existing game from Epic Games Launcher
+ Ein bereits existierendes Spiel aus dem Epic Games Launcher importieren
+
+
+
+ No Games were found
+ Keine Spiele wurden gefunden
+
InfoTabs
-
+
Back
Zurück
-
+
Game Info
Spielinfo
-
+
Settings
Einstellungen
@@ -431,27 +530,42 @@
InstallDialog
-
+
Max workers (0: Default)
Maximale Anzahl Downloadprozessen(Standard: 0)
+
+
+ <h3>Install {}</h3>
+ <h3>Installiere {}</h3>
+
+
+
+ Force download
+ Download erzwingen
+
+
+
+ Ignore free space (Warning!)
+ Freien Speicherplatz ignorieren (Achtung!)
+
InstallInfoDialog
-
+
Download size: {}GB
Install size: {}GB
Downloadgröße: {}GB
Installationsgröße: {} GB
-
+
Install
Installieren
-
+
Cancel
Abbruch
@@ -503,17 +617,17 @@ Installationsgröße: {} GB
LegendarySettings
-
+
Legendary settings
Legendary Einstellungen
-
+
Default installation directory
Standardordner für Installationen
-
+
Max workers for Download (Less: slower download)(0: Default)
Maximale Anzahl Downloadprozesse (Weniger: langsamer)(Standard: 0)
@@ -582,6 +696,14 @@ Installationsgröße: {} GB
Dies öffnet den Browser, Einloggen und den Text kopieren
+
+ MainWindow
+
+
+ There is a download active. Do you really want to exit app?
+ Ein Download läuft noch. Möchtest du die App wirklich beenden?
+
+
MiniWidget
@@ -595,12 +717,12 @@ Installationsgröße: {} GB
Accounteinstellungen
-
+
Logout
Ausloggen
-
+
Do you really want to logout
Willst du dich wirklich abmelden
@@ -629,65 +751,75 @@ Installationsgröße: {} GB
RareSettings
-
+
Rare settings
Rare Einstellungen
-
+
Save
Speichern
-
+
Image Directory
Ordner für Bilder
-
+
Language
Sprache
-
+
Restart Application to activate changes
Starte die App neu um die Änderungen zu aktivieren
-
+
Confirm launch of game
Start des Spiels bestätigen
+
+
+ Exit to System Tray Icon
+ Beim verlassen auf das System Tray Icon minimieren
+
+
+
+ Hide to System Tray Icon
+ In das System Tray Icon minimieren
+
SyncSaves
-
+
Cloud Saves
Cloud Speicherstände
-
+
Found Saves for folowing Games
Spielstände für folgende Spiele gefunden
-
+
Your games does not support Cloud Saves
Deine Spiele unterstützen keine Online Speicherstände
-
+
Sync all games
Alle Spiele synchronisieren
-
+
Found no savepath
Kein Speicherort gefunden
-
+
No save path was found. Please select path or skip
Kein Speicherort wurde gefunden. Wähle einen Ordner oder überspringe
@@ -695,97 +827,97 @@ Installationsgröße: {} GB
SyncWidget
-
+
Path not found
Ordner nicht gefunden
-
+
Local Save date:
Lokales Speicherdatum:
-
+
No Local Save files
Keine Lokalen Dateien
-
+
Cloud save date:
Online Speicherdatum:
-
+
No Cloud saves
Keine Online Speicherstände
-
+
Game is up to date
Spiel ist aktuell
-
+
Upload anyway
Trotzdem hochladen
-
+
Download anyway
Trotzdem herunterladen
-
+
Cloud save is newer
Online Speicherstand ist aktueller
-
+
Download Cloud saves
Online Speicherstand herunterladen
-
+
Upload Saves
Spielstände hochladen
-
+
Local save is newer
Lokaler Speicher ist aktueller
-
+
Upload saves
Spielstände hochladen
-
+
Download saves
Spielstand herunterladen
-
+
Change path
Pfad ändern
-
+
Uploading...
Hochladen...
-
+
Upload finished
Hochladen abgeschlossen
-
+
Downloading...
Runterladen...
-
+
Download finished
Download abgeschlossen
@@ -798,10 +930,46 @@ Installationsgröße: {} GB
Spiele
+
+ TrayIcon
+
+
+ Exit
+ Schließen
+
+
+
+ UninstallDialog
+
+
+ Do you really want to uninstall {}
+ Möchtest du wirklich {} deinstallieren
+
+
+
+ Keep Files
+ Dateien behalten
+
+
+
+ Do you want to keep files?
+ Willst du die Dateien behalten?
+
+
+
+ Uninstall
+ Deinstallieren
+
+
+
+ Cancel
+ Abbruch
+
+
UpdateWidget
-
+
Update Game
Spiel updaten
diff --git a/Rare/languages/fr.qm b/Rare/languages/fr.qm
new file mode 100644
index 00000000..d5ae2341
Binary files /dev/null and b/Rare/languages/fr.qm differ
diff --git a/Rare/languages/fr.ts b/Rare/languages/fr.ts
new file mode 100644
index 00000000..2aa8f3d8
--- /dev/null
+++ b/Rare/languages/fr.ts
@@ -0,0 +1,818 @@
+
+
+
+ About
+
+
+ Developer:
+ Développeur
+
+
+
+ Legendary developer:
+ Legendary Développeur
+
+
+
+ This is a beta version, so you can get bugs. If you get a bug, please report it by creating a Issue on <a href='https://github.com/Dummerle/Rare/issues'>Github</a>. You can also contact me on Discord (Dummerle#7419). Or you can join the <a href='https://discord.gg/YvmABK9YSk'>Discord server</a>
+ Il s'agit d'une version bêta, vous pouvez donc rencontrer des bogues. Si vous rencontrez un bug, veuillez le signaler en créant un Issue sur <a href='https://github.com/Dummerle/Rare/issues'>Github</a>. Vous pouvez également me contacter sur Discord (Dummerle#7419). Ou vous pouvez rejoindre le <a href='https://discord.gg/YvmABK9YSk'>serveur Discord</a>
+
+
+
+ BaseInstalledWidget
+
+
+ Do you want to launch {}
+ Voulez-vous lancer {}
+
+
+
+ BrowserLogin
+
+
+ Opens a browser. You login and copy the json code in the field below. Click <a href='{}'>here</a> to open Browser
+ Ouvre un navigateur. Vous vous connectez et copiez le code json dans le champ ci-dessous. Cliquez <a href='{}'>here</a> pour ouvrir un navigateur
+
+
+
+ Insert SID here
+ Insérer le SID ici
+
+
+
+ Login
+ Login
+
+
+
+ Loading...
+ charge...
+
+
+
+ DownloadTab
+
+
+ No active Download
+ Aucun téléchargement actif
+
+
+
+ Stop Download
+ Stop Télécharger
+
+
+
+ No updates available
+ Aucune mise à jour disponible
+
+
+
+ Error preparing download
+ Erreur lors de la préparation du téléchargement
+
+
+
+ Download size is 0. Game already exists
+ La taille du téléchargement est de 0. Le jeu existe déjà
+
+
+
+ Installing game:
+ Installation du jeu:
+
+
+
+ Installation finished
+ Installation terminée
+
+
+
+ Finished Download of game {}
+ Fin du téléchargement du jeu {}
+
+
+
+ Installing Game: No active download
+ Installation du jeu: Aucun téléchargement actif
+
+
+
+ Download speed
+ Vitesse de téléchargement
+
+
+
+ Cache used
+ Cache utilisé
+
+
+
+ Downloaded
+ Téléchargé
+
+
+
+ Time left:
+ Il reste du temps:
+
+
+
+ DxvkWidget
+
+
+ GPU usage
+ GPU Utilisation
+
+
+
+ Used Memory
+ utilisé Memory
+
+
+
+ Device info
+ Info sur le dispositif
+
+
+
+ DXVK version
+ DXVK version
+
+
+
+ D3D Level of application
+ D3D Niveau d'application
+
+
+
+ Frame time graph
+ Graphique de temps de trame
+
+
+
+ dxvk settings
+ dxvk paramètres
+
+
+
+ GameActions
+
+
+ Uninstall game
+ Désinstaller le jeu
+
+
+
+ Uninstall
+ Désinstaller
+
+
+
+ Verify Game
+ Vérifier le jeu
+
+
+
+ Verify
+ Vérifier
+
+
+
+ Repair Game
+ Jeu de réparation
+
+
+
+ Repair
+ Réparation
+
+
+
+ GameInfo
+
+
+ Are you sure to uninstall {}
+ Etes-vous sûr de désinstaller {}
+
+
+
+ Repair file does not exist or game does not need a repair. Please verify game first
+ Le fichier de réparation n'existe pas ou le jeu ne nécessite pas de réparation. Veuillez d'abord vérifier le jeu.
+
+
+
+ Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?
+ La vérification a échoué, {} fichier(s) corrompu(s), {} fichier(s) manquant(s). Voulez-vous les réparer ?
+
+
+
+ Developer:
+ Développeur:
+
+
+
+ Install size:
+ Taille d'installation:
+
+
+
+ Install path:
+ Chemin d'installation:
+
+
+
+ GameList
+
+
+ Installed Games: {} Available Games: {}
+ Jeux installés: {} Jeux disponibles: {}
+
+
+
+ Launch
+ Lancer
+
+
+
+ Game running
+ Jeu en cours
+
+
+
+ GameListHeadBar
+
+
+ Installed only
+ Installé uniquement
+
+
+
+ Import Game
+ Jeu d'importation
+
+
+
+ Search Game
+ Rechercher un jeu
+
+
+
+ GameSettings
+
+
+ Launch Game offline
+ Lancer le jeu hors ligne
+
+
+
+ Skip update check before launching
+ Sauter la vérification de la mise à jour avant le lancement
+
+
+
+ Save
+ Sauvez
+
+
+
+ Wrapper (e.g. optirun)
+ Wrapper (p.e. optirun)
+
+
+
+ Proton Wrapper
+ Enveloppeur de Proton
+
+
+
+ Proton prefix
+ Préfixe du proton
+
+
+
+ No permission to create folder
+ Pas de permission pour créer un dossier
+
+
+
+ Please select path for proton prefix
+ Veuillez sélectionner le chemin pour le préfixe proton
+
+
+
+ GameWidgetInstalled
+
+
+ Update available
+ Mise à jour disponible
+
+
+
+ Start game without version check
+ Démarrer le jeu sans vérifier la version
+
+
+
+ Game running
+ Jeu en cours
+
+
+
+ IconWidgetUninstalled
+
+
+ Install Game
+ Installer le jeu
+
+
+
+ ImportWidget
+
+
+ Import
+ Importer
+
+
+
+ Could not find EGL program data
+ Impossible de trouver les données du programme EGL
+
+
+
+ Found EGL program Data. Do you want to import them?
+ Les données du programme EGL ont été trouvées. Voulez-vous les importer ?
+
+
+
+ Could not find any Epic Games login data
+ Impossible de trouver les données de connexion d'Epic Games
+
+
+
+ Loading...
+ Chargement...
+
+
+
+ Error: No valid session found
+ Erreur : Aucune session valide n'a été trouvée
+
+
+
+ Back
+ Dos
+
+
+
+ Select path to game
+ Sélectionnez le chemin vers le jeu
+
+
+
+ Override app name (Only if imported game from legendary or the app could not find the app name)
+ Remplacer le nom de l'application (uniquement si le jeu a été importé depuis le légendaire ou si l'application n'a pas pu trouver le nom de l'application)
+
+
+
+ Import Game
+ Import jeu
+
+
+
+ Import all games from Epic Games Launcher
+ Importer tous les jeux du Epic Games Launcher
+
+
+
+ Could not find app name
+ Impossible de trouver le nom de l'application
+
+
+
+ Successfully imported {}. Reload library
+ Importation réussie de {}. Recharger la bibliothèque
+
+
+
+ Failed to import {}
+ Impossible d'importer {}
+
+
+
+ Successfully imported {} Games. Reloading Library
+ Importation réussie de {} Jeux. Bibliothèque de rechargement
+
+
+
+ InfoTabs
+
+
+ Back
+ Dos
+
+
+
+ Game Info
+ Info de jeu
+
+
+
+ Settings
+ Paramètres
+
+
+
+ InstallDialog
+
+
+ <h3>Install {}</h3>
+ <h3>Installer {}</h3>
+
+
+
+ Max workers (0: Default)
+ Travailleurs maximum (0: Par défaut)
+
+
+
+ Force download
+ Téléchargement forcé
+
+
+
+ Ignore free space (Warning!)
+ Ignorer l'espace libre (Attention!)
+
+
+
+ InstallInfoDialog
+
+
+ Download size: {}GB
+Install size: {}GB
+ Taille du téléchargement: {}GB
+Taille de l'installation: {}GB
+
+
+
+ Install
+ Installer
+
+
+
+ Cancel
+ Annuler
+
+
+
+ InstalledListWidget
+
+
+ Launch
+ Lancer
+
+
+
+ Developer:
+ Développeur:
+
+
+
+ LaunchDialog
+
+
+ Launching Rare
+ Lancer Rare
+
+
+
+ Logging in
+ Se connecter
+
+
+
+ Downloading Images
+ Téléchargement d'images
+
+
+
+ Starting...
+ Démarrage...
+
+
+
+ LaunchThread
+
+
+ Downloading Images
+ Téléchargement d'images
+
+
+
+ LegendarySettings
+
+
+ Legendary settings
+ Legendary paramètres
+
+
+
+ Default installation directory
+ Répertoire d'installation par défaut
+
+
+
+ Max workers for Download (Less: slower download)(0: Default)
+ Nombre maximum de travailleurs pour le téléchargement (Moins: téléchargement plus lent)(0: Défaut)
+
+
+
+ Cleanup
+ Nettoyage
+
+
+
+ Remove everything
+ Enlever tout
+
+
+
+ Clean, but keep manifests
+ Nettoyer, mais garder les manifestes
+
+
+
+ Cleanup complete! Successfully removed {} MB
+ Nettoyage terminé ! J'ai réussi à supprimer {} MB
+
+
+
+ LinuxSettings
+
+
+ Linux settings
+ Linux paramètres
+
+
+
+ Default Wine Prefix
+ Défaut Wine Prefix
+
+
+
+ Default Wine executable
+ Défaut Wine exécutable
+
+
+
+ ListWidgetUninstalled
+
+
+ Install
+ Installer
+
+
+
+ LoginDialog
+
+
+ Select one option to Login
+ Sélectionnez une option pour vous connecter
+
+
+
+ Use Browser
+ Utiliser le navigateur
+
+
+
+ This opens your default browser. Login and copy the text
+ Cela ouvre votre navigateur par défaut. Connectez-vous et copiez le texte
+
+
+
+ MiniWidget
+
+
+ Logged in as
+ Connecté en tant que
+
+
+
+ Account settings
+ paramètres du compte
+
+
+
+ Logout
+ Déconnexion
+
+
+
+ Do you really want to logout
+ Voulez-vous vraiment vous déconnecter?
+
+
+
+ PathEdit
+
+
+ Select Path
+ Sélectionner le chemin
+
+
+
+ Choose Path
+ Choisir le chemin
+
+
+
+ PathInputDialog
+
+
+ Cancel
+ Annuler
+
+
+
+ RareSettings
+
+
+ Rare settings
+ Rare paramètres
+
+
+
+ Save
+ Sauvez
+
+
+
+ Image Directory
+ Répertoire d'images
+
+
+
+ Language
+ Langue
+
+
+
+ Confirm launch of game
+ Confirmation du lancement du jeu
+
+
+
+ Restart Application to activate changes
+ Redémarrez l'application pour activer les changements
+
+
+
+ SyncSaves
+
+
+ Cloud Saves
+ Cloud Saves
+
+
+
+ Found Saves for folowing Games
+ Sauvegardes trouvées pour les jeux suivants
+
+
+
+ Your games does not support Cloud Saves
+ Vos jeux ne prennent pas en charge les sauvegardes en nuage
+
+
+
+ Sync all games
+ Sync tous les jeux
+
+
+
+ Found no savepath
+ Pas de chemin de sauvegarde trouvé
+
+
+
+ No save path was found. Please select path or skip
+ Aucun chemin de sauvegarde n'a été trouvé. Veuillez sélectionner le chemin ou passer
+
+
+
+ SyncWidget
+
+
+ Path not found
+ Chemin non trouvé
+
+
+
+ Local Save date:
+ Local Save date:
+
+
+
+ No Local Save files
+ Pas de fichiers de sauvegarde locaux
+
+
+
+ Cloud save date:
+ Cloud save date:
+
+
+
+ No Cloud saves
+ Pas Cloud saves
+
+
+
+ Game is up to date
+ Le jeu est à jour
+
+
+
+ Upload anyway
+ Télécharger quand même
+
+
+
+ Download anyway
+ Télécharger en tout cas
+
+
+
+ Cloud save is newer
+ La sauvegarde en nuage est plus récente
+
+
+
+ Download Cloud saves
+ Télécharger Cloud saves
+
+
+
+ Upload Saves
+ Upload Saves
+
+
+
+ Local save is newer
+ La sauvegarde locale est plus récente
+
+
+
+ Upload saves
+ Upload Saves
+
+
+
+ Download saves
+ Télécharger les sauvegardes
+
+
+
+ Change path
+ Changement de trajectoire
+
+
+
+ Uploading...
+ Téléchargement...
+
+
+
+ Upload finished
+ Téléchargement terminé
+
+
+
+ Downloading...
+ Téléchargement...
+
+
+
+ Download finished
+ Téléchargement terminé
+
+
+
+ TabWidget
+
+
+ Games
+ Jeus
+
+
+
+ UpdateWidget
+
+
+ Update Game
+ Jeu de mise à jour
+
+
+
diff --git a/Rare/utils/LegendaryApi.py b/Rare/utils/LegendaryApi.py
index e22baf89..0f5ce4cd 100644
--- a/Rare/utils/LegendaryApi.py
+++ b/Rare/utils/LegendaryApi.py
@@ -47,7 +47,9 @@ def launch_game(core, app_name: str, offline: bool = False, skip_version_check:
return process, params
-def uninstall(app_name: str, core):
+def uninstall(app_name: str, core, options=None):
+ if not options:
+ options = {"keep_files": False}
igame = core.get_installed_game(app_name)
try:
# Remove DLC first so directory is empty when game uninstall runs
@@ -55,12 +57,13 @@ def uninstall(app_name: str, core):
for dlc in dlcs:
if (idlc := core.get_installed_game(dlc.app_name)) is not None:
logger.info(f'Uninstalling DLC "{dlc.app_name}"...')
- core.uninstall_game(idlc, delete_files=True)
+ core.uninstall_game(idlc, delete_files=not options["keep_files"])
logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...')
- core.uninstall_game(igame, delete_files=True, delete_root_directory=True)
+ core.uninstall_game(igame, delete_files=not options["keep_files"], delete_root_directory=True)
logger.info('Game has been uninstalled.')
- shutil.rmtree(igame.install_path)
+ if not options["keep_files"]:
+ shutil.rmtree(igame.install_path)
except Exception as e:
logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.')
diff --git a/Rare/utils/utils.py b/Rare/utils/utils.py
index d25436bf..517e8796 100644
--- a/Rare/utils/utils.py
+++ b/Rare/utils/utils.py
@@ -4,15 +4,15 @@ import shutil
from logging import getLogger
import requests
-from PIL import Image
+from PIL import Image, UnidentifiedImageError
from PyQt5.QtCore import pyqtSignal, QLocale, QSettings
-from Rare import lang_path
+from Rare import lang_path, __version__
from custom_legendary.core import LegendaryCore
logger = getLogger("Utils")
s = QSettings("Rare", "Rare")
-IMAGE_DIR = s.value("img_dir", os.path.expanduser("~/.cache/rare"), type=str)
+IMAGE_DIR = s.value("img_dir", os.path.expanduser("~/.cache/rare/images"), type=str)
logger.info("IMAGE DIRECTORY: " + IMAGE_DIR)
@@ -38,6 +38,7 @@ def download_image(game, force=False):
if not os.path.isdir(f"{IMAGE_DIR}/" + game.app_name):
os.mkdir(f"{IMAGE_DIR}/" + game.app_name)
+ # to git picture updates
if not os.path.isfile(f"{IMAGE_DIR}/{game.app_name}/image.json"):
json_data = {"DieselGameBoxTall": None, "DieselGameBoxLogo": None}
else:
@@ -56,7 +57,13 @@ def download_image(game, force=False):
url = image["url"]
with open(f"{IMAGE_DIR}/{game.app_name}/{image['type']}.png", "wb") as f:
f.write(requests.get(url).content)
- f.close()
+ try:
+ img = Image.open(f"{IMAGE_DIR}/{game.app_name}/{image['type']}.png")
+ img = img.resize((200, int(200*4/3)))
+ img.save(f"{IMAGE_DIR}/{game.app_name}/{image['type']}.png")
+ except UnidentifiedImageError as e:
+ logger.warning(e)
+
# scale and grey
if not os.path.isfile(f'{IMAGE_DIR}/' + game.app_name + '/UninstalledArt.png'):
@@ -67,6 +74,7 @@ def download_image(game, force=False):
bg = Image.open(f"{IMAGE_DIR}/{game.app_name}/DieselGameBoxTall.png")
uninstalledArt = bg.convert('L')
+ uninstalledArt = uninstalledArt.resize((200, int(200*4/3)))
uninstalledArt.save(f'{IMAGE_DIR}/{game.app_name}/UninstalledArt.png')
elif os.path.isfile(f"{IMAGE_DIR}/{game.app_name}/DieselGameBoxLogo.png"):
bg: Image.Image = Image.open(f"{IMAGE_DIR}/{game.app_name}/DieselGameBoxLogo.png")
@@ -91,7 +99,7 @@ def download_image(game, force=False):
uninstalledArt = uninstalledArt.convert('L')
uninstalledArt.save(f'{IMAGE_DIR}/' + game.app_name + '/UninstalledArt.png')
else:
- logger.warning(f"File {IMAGE_DIR}/{game.app_name}/DieselGameBoxTall.png dowsn't exist")
+ logger.warning(f"File {IMAGE_DIR}/{game.app_name}/DieselGameBoxTall.png doesn't exist")
def get_lang():
@@ -110,3 +118,9 @@ def get_possible_langs():
if i.endswith(".qm"):
langs.append(i.split(".")[0])
return langs
+
+
+def get_latest_version():
+ resp = requests.get("https://api.github.com/repos/Dummerle/Rare/releases/latest")
+ tag = json.loads(resp.content.decode("utf-8"))["tag_name"]
+ return tag