1
0
Fork 0
mirror of synced 2024-06-17 10:04:43 +12:00

Merge pull request #26 from Dummerle/dev

Added many features
This commit is contained in:
Dummerle 2021-04-07 21:09:52 +02:00 committed by GitHub
commit 85b9fac065
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1966 additions and 612 deletions

View file

@ -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

View file

@ -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

View file

@ -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"))

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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("<h4>Loading Cloud Saves</h4>"))
layout.addStretch()
self.widget.setLayout(layout)
self.setWidget(self.widget)
self.start_thread = LoadThread(self.core)
self.start_thread.signal.connect(self.setup_ui)
self.start_thread.start()
self.igames = self.core.get_installed_list()
def setup_ui(self, saves: list):
self.start_thread.disconnect()
self.main_layout = QVBoxLayout()
self.title = QLabel(
f"<h1>" + self.tr("Cloud Saves") + "</h1>\n" + self.tr("Found Saves for folowing Games"))
self.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"<h1>" + self.tr("Cloud Saves") + "</h1>\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()

View file

@ -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"<h2>{igame.title}</h2>")
# game_title = QLabel(f"<h2>{igame.title}</h2>")
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):

View file

@ -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("<h4>Loading Cloud Saves</h4>"))
layout.addStretch()
self.widget.setLayout(layout)
self.setWidget(self.widget)
self.start_thread = LoadThread(self.core)
self.start_thread.signal.connect(self.setup_ui)
self.start_thread.start()
self.igames = self.core.get_installed_list()
def setup_ui(self, saves: list):
self.start_thread.disconnect()
self.main_layout = QVBoxLayout()
self.title = QLabel(
f"<h1>" + self.tr("Cloud Saves") + "</h1>\n" + self.tr("Found Saves for folowing Games"))
self.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"<h1>" + self.tr("Cloud Saves") + "</h1>\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()

View file

@ -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)

View file

@ -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"<h2>{self.tr('Updates')}</h2>")
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

View file

@ -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.')

View file

@ -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')

View file

@ -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("")

View file

@ -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")

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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("")

View file

@ -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)

View file

@ -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("<h2>Import Game</h2")
self.layout.addWidget(self.title)
self.import_one_game = QLabel(f"<h3>{self.tr('Import existing game from Epic Games Launcher')}</h3>")
self.layout.addWidget(self.import_one_game)
# self.import_one_game = QLabel(f"<h3>{self.tr('Import existing game from Epic Games Launcher')}</h3>")
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"<h3>{self.tr('Auto import all existing games')}</h3>")
@ -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"))

View file

@ -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)

View file

@ -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:") + "<a href='https://github.com/Dummerle'>Dummerle</a>")
self.dev.setToolTip("Github")

View file

@ -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):

View file

@ -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("<h2>" + self.tr("Legendary settings") + "</h2>")
self.layout.addWidget(self.title)
#self.title = QLabel("<h2>" + self.tr("Legendary settings") + "</h2>")
#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")

View file

@ -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("<h2>" + self.tr("Rare settings") + "</h2>")
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):

View file

@ -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")

View file

@ -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")

View file

@ -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)

View file

@ -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

View file

@ -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 {

View file

@ -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/")

View file

@ -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)"""

Binary file not shown.

View file

@ -4,12 +4,12 @@
<context>
<name>About</name>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="17"/>
<location filename="../Components/Tabs/Settings/About.py" line="29"/>
<source>Developer:</source>
<translation>Entwickler:</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="22"/>
<location filename="../Components/Tabs/Settings/About.py" line="34"/>
<source>Legendary developer:</source>
<translation>Legendary Entwickler:</translation>
</message>
@ -19,15 +19,38 @@
<translation type="obsolete">Dies ist eine beta version, also können Bugs entstehen. Wenn du einen Bug bemerkst, kontaktiere mich, indem du einen Issue auf &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt; erstellst oder mir auf Discord eine Nachricht schickst. Ebenso bei einem Wunsch für Features</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="28"/>
<location filename="../Components/Tabs/Settings/About.py" line="40"/>
<source>This is a beta version, so you can get bugs. If you get a bug, please report it by creating a Issue on &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt;. You can also contact me on Discord (Dummerle#7419). Or you can join the &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;Discord server&lt;/a&gt;</source>
<translation>Dies ist eine Betaversion, also können Bugs und andere Unschönheiten auftreten. Falls ein Bug auftritt, bitte auf &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt; melden, indem du einen Issue erstellst oder auf Discord. (Dummerle#7419). Ein Rare &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;Discord server&lt;/a&gt; existiert ebenfalls</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="23"/>
<source>Update available: {} -&gt; {}</source>
<translation>Update verfügbar: {} -&gt; {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="25"/>
<source>Download latest release</source>
<translation>Neueste Version herunterladen</translation>
</message>
</context>
<context>
<name>App</name>
<message>
<location filename="../Main.py" line="84"/>
<source>Download finished</source>
<translation>Download abgeschlossen</translation>
</message>
<message>
<location filename="../Main.py" line="84"/>
<source>Download finished. Game is playable now</source>
<translation>Downlaod abgeschlossen. Spiel kann jetzt gespielt werden</translation>
</message>
</context>
<context>
<name>BaseInstalledWidget</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py" line="34"/>
<location filename="../Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py" line="36"/>
<source>Do you want to launch {}</source>
<translation>Möchtest du {} starten</translation>
</message>
@ -55,68 +78,129 @@
<translation>Laden...</translation>
</message>
</context>
<context>
<name>DlQueueWidget</name>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="63"/>
<source>Download Queue</source>
<translation>Eingereihte Downloads</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="79"/>
<source>No downloads in queue</source>
<translation>Keine eingereihten Downloads</translation>
</message>
</context>
<context>
<name>DlWidget</name>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="44"/>
<source>Download size: {} GB</source>
<translation>Download Größe: {} GB</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="45"/>
<source>Install size: {} GB</source>
<translation>Installierte Größe: {} GB</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="48"/>
<source>Remove Download</source>
<translation>Download löschen</translation>
</message>
</context>
<context>
<name>DownloadTab</name>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="133"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="32"/>
<source>No active Download</source>
<translation>Kein aktiver Download</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="154"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="53"/>
<source>Stop Download</source>
<translation>Download anhalten</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="170"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="69"/>
<source>No updates available</source>
<translation>Keine Updates verfügbar</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="217"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="122"/>
<source>Error preparing download</source>
<translation>Fehler beim Vorbereiten des Downloads</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="222"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="127"/>
<source>Download size is 0. Game already exists</source>
<translation>Die Größe des Downloads ist 0. Spiel existiert bereits</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="287"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="201"/>
<source>Installation finished</source>
<translation>Installation abgeschlossen</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="311"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="240"/>
<source>Installing Game: No active download</source>
<translation>Installierendes Spiel: Kein aktiver Download</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="319"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="248"/>
<source>Download speed</source>
<translation>Geschwindigkeit</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="320"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="249"/>
<source>Cache used</source>
<translation>Benutzter Cache</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="321"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="250"/>
<source>Downloaded</source>
<translation>Runtergeladen</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="322"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="251"/>
<source>Time left: </source>
<translation>Zeit übrig: </translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="288"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="202"/>
<source>Finished Download of game {}</source>
<translation>Downlaod von {} abgeschlossen</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="193"/>
<source>Download finished. Reload library</source>
<translation>Download abgeschlossen. Spiele neu laden</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="332"/>
<source>Download queue: Empty</source>
<translation type="obsolete">Anschließende Downloads: Keine</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="238"/>
<source>Download queue: </source>
<translation type="obsolete">Anschließende Downloads: </translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="238"/>
<source>Empty</source>
<translation type="obsolete">Keine</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="149"/>
<source>Installing Game: </source>
<translation>Installierendes Spiel: </translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="64"/>
<source>Updates</source>
<translation>Updates</translation>
</message>
</context>
<context>
<name>DxvkWidget</name>
@ -159,32 +243,32 @@
<context>
<name>GameActions</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="182"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="189"/>
<source>Uninstall game</source>
<translation>Spiel deinstallieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="184"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="191"/>
<source>Uninstall</source>
<translation>Deinstallieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="190"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="197"/>
<source>Verify Game</source>
<translation>Spieldateien überprüfen</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="195"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="202"/>
<source>Verify</source>
<translation>Überprüfen</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="204"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="211"/>
<source>Repair Game</source>
<translation>Spiel reparieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="206"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="213"/>
<source>Repair</source>
<translation>Reparieren</translation>
</message>
@ -192,50 +276,50 @@
<context>
<name>GameInfo</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="106"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="113"/>
<source>Repair file does not exist or game does not need a repair. Please verify game first</source>
<translation>Reparationsdatei existiert nicht oder das Spiel braucht keine Reperatur. Bitte das spiel zuerst überprüfen</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="131"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="138"/>
<source>Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?</source>
<translation>Überprüfung fehlgeschlagen, {} Datei(en) fehlerhaft, {} Datei(en) fehlen. Willst du das Spiel reparieren?</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="160"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="167"/>
<source>Developer: </source>
<translation>Entwickler: </translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="161"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="168"/>
<source>Install size: </source>
<translation>Größe: </translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="163"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="170"/>
<source>Install path: </source>
<translation>Installationsordner: </translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="98"/>
<source>Are you sure to uninstall {}</source>
<translation>Möchtest du {} wirklich deinstallieren</translation>
<translation type="obsolete">Möchtest du {} wirklich deinstallieren</translation>
</message>
</context>
<context>
<name>GameList</name>
<message>
<location filename="../Components/Tabs/Games/GameList.py" line="148"/>
<location filename="../Components/Tabs/Games/GameList.py" line="160"/>
<source>Launch</source>
<translation>Starten</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameList.py" line="153"/>
<location filename="../Components/Tabs/Games/GameList.py" line="165"/>
<source>Game running</source>
<translation>Spiel läuft</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameList.py" line="45"/>
<location filename="../Components/Tabs/Games/GameList.py" line="46"/>
<source>Installed Games: {} Available Games: {}</source>
<translation>Installierte Spiele: {} Verfügbare Spiele: {}</translation>
</message>
@ -243,17 +327,17 @@
<context>
<name>GameListHeadBar</name>
<message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="74"/>
<location filename="../Components/Tabs/Games/__init__.py" line="74"/>
<source>Installed only</source>
<translation>Nur Installierte</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="81"/>
<location filename="../Components/Tabs/Games/__init__.py" line="81"/>
<source>Import Game</source>
<translation>Spiel importieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="93"/>
<location filename="../Components/Tabs/Games/__init__.py" line="93"/>
<source>Search Game</source>
<translation>Spiel suchen</translation>
</message>
@ -322,15 +406,20 @@
<context>
<name>IconWidgetUninstalled</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py" line="43"/>
<location filename="../Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py" line="45"/>
<source>Install Game</source>
<translation>Spiel installieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py" line="47"/>
<source>Installation running</source>
<translation>Installation läuft</translation>
</message>
</context>
<context>
<name>ImportWidget</name>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="99"/>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="102"/>
<source>Import</source>
<translation>Importieren</translation>
</message>
@ -355,27 +444,27 @@
<translation>Laden...</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="98"/>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="101"/>
<source>Error: No valid session found</source>
<translation>Keine valide Session gefunden</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="26"/>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="27"/>
<source>Back</source>
<translation>Zurück</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="40"/>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="43"/>
<source>Select path to game</source>
<translation>Wähle den Pfad zum Spiel</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="62"/>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="65"/>
<source>Import Game</source>
<translation>Spiel importieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="70"/>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="77"/>
<source>Import all games from Epic Games Launcher</source>
<translation>Alle Spiele aus dem Epic Games Launcher importieren</translation>
</message>
@ -385,45 +474,55 @@
<translation type="obsolete">{} Spiele erfolgreich importiert</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="43"/>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="46"/>
<source>Override app name (Only if imported game from legendary or the app could not find the app name)</source>
<translation>App Name überschreiben (Nur falls das Spiel von Legendary importiert wird oder der App Name nicht gefunden wird</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="110"/>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="118"/>
<source>Could not find app name</source>
<translation>Konnte den Appnamen nicht finden</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="114"/>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="122"/>
<source>Successfully imported {}. Reload library</source>
<translation>Erfolgreich {} importiert. Spiele neu laden</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="120"/>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="128"/>
<source>Failed to import {}</source>
<translation>{} Konnte nicht importiert werden</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="159"/>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="167"/>
<source>Successfully imported {} Games. Reloading Library</source>
<translation>Erfolgreich {} Spiele importiert. Spiele neu laden</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="39"/>
<source>Import existing game from Epic Games Launcher</source>
<translation>Ein bereits existierendes Spiel aus dem Epic Games Launcher importieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="170"/>
<source>No Games were found</source>
<translation>Keine Spiele wurden gefunden</translation>
</message>
</context>
<context>
<name>InfoTabs</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="26"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="27"/>
<source>Back</source>
<translation>Zurück</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="30"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="31"/>
<source>Game Info</source>
<translation>Spielinfo</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="32"/>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="33"/>
<source>Settings</source>
<translation>Einstellungen</translation>
</message>
@ -431,27 +530,42 @@
<context>
<name>InstallDialog</name>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="22"/>
<location filename="../Components/Dialogs/InstallDialog.py" line="29"/>
<source>Max workers (0: Default)</source>
<translation>Maximale Anzahl Downloadprozessen(Standard: 0)</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="20"/>
<source>&lt;h3&gt;Install {}&lt;/h3&gt;</source>
<translation>&lt;h3&gt;Installiere {}&lt;/h3&gt;</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="33"/>
<source>Force download</source>
<translation>Download erzwingen</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="37"/>
<source>Ignore free space (Warning!)</source>
<translation>Freien Speicherplatz ignorieren (Achtung!)</translation>
</message>
</context>
<context>
<name>InstallInfoDialog</name>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="55"/>
<location filename="../Components/Dialogs/InstallDialog.py" line="70"/>
<source>Download size: {}GB
Install size: {}GB</source>
<translation>Downloadgröße: {}GB
Installationsgröße: {} GB</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="61"/>
<location filename="../Components/Dialogs/InstallDialog.py" line="76"/>
<source>Install</source>
<translation>Installieren</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="63"/>
<location filename="../Components/Dialogs/InstallDialog.py" line="78"/>
<source>Cancel</source>
<translation>Abbruch</translation>
</message>
@ -503,17 +617,17 @@ Installationsgröße: {} GB</translation>
<context>
<name>LegendarySettings</name>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="18"/>
<location filename="../Components/Tabs/Settings/Legendary.py" line="16"/>
<source>Legendary settings</source>
<translation>Legendary Einstellungen</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="27"/>
<location filename="../Components/Tabs/Settings/Legendary.py" line="28"/>
<source>Default installation directory</source>
<translation>Standardordner für Installationen</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="36"/>
<location filename="../Components/Tabs/Settings/Legendary.py" line="37"/>
<source>Max workers for Download (Less: slower download)(0: Default)</source>
<translation>Maximale Anzahl Downloadprozesse (Weniger: langsamer)(Standard: 0)</translation>
</message>
@ -582,6 +696,14 @@ Installationsgröße: {} GB</translation>
<translation>Dies öffnet den Browser, Einloggen und den Text kopieren</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../Components/MainWindow.py" line="21"/>
<source>There is a download active. Do you really want to exit app?</source>
<translation>Ein Download läuft noch. Möchtest du die App wirklich beenden?</translation>
</message>
</context>
<context>
<name>MiniWidget</name>
<message>
@ -595,12 +717,12 @@ Installationsgröße: {} GB</translation>
<translation>Accounteinstellungen</translation>
</message>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="26"/>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="27"/>
<source>Logout</source>
<translation>Ausloggen</translation>
</message>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="32"/>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="33"/>
<source>Do you really want to logout</source>
<translation>Willst du dich wirklich abmelden</translation>
</message>
@ -629,65 +751,75 @@ Installationsgröße: {} GB</translation>
<context>
<name>RareSettings</name>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="19"/>
<location filename="../Components/Tabs/Settings/Rare.py" line="18"/>
<source>Rare settings</source>
<translation>Rare Einstellungen</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="33"/>
<location filename="../Components/Tabs/Settings/Rare.py" line="28"/>
<source>Save</source>
<translation>Speichern</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="35"/>
<location filename="../Components/Tabs/Settings/Rare.py" line="30"/>
<source>Image Directory</source>
<translation>Ordner für Bilder</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="49"/>
<location filename="../Components/Tabs/Settings/Rare.py" line="44"/>
<source>Language</source>
<translation>Sprache</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="77"/>
<location filename="../Components/Tabs/Settings/Rare.py" line="82"/>
<source>Restart Application to activate changes</source>
<translation>Starte die App neu um die Änderungen zu aktivieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="55"/>
<location filename="../Components/Tabs/Settings/Rare.py" line="56"/>
<source>Confirm launch of game</source>
<translation>Start des Spiels bestätigen</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="51"/>
<source>Exit to System Tray Icon</source>
<translation>Beim verlassen auf das System Tray Icon minimieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="48"/>
<source>Hide to System Tray Icon</source>
<translation>In das System Tray Icon minimieren</translation>
</message>
</context>
<context>
<name>SyncSaves</name>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="65"/>
<location filename="../Components/Tabs/CloudSaves/__init__.py" line="62"/>
<source>Cloud Saves</source>
<translation>Cloud Speicherstände</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="54"/>
<location filename="../Components/Tabs/CloudSaves/__init__.py" line="52"/>
<source>Found Saves for folowing Games</source>
<translation>Spielstände für folgende Spiele gefunden</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="65"/>
<location filename="../Components/Tabs/CloudSaves/__init__.py" line="62"/>
<source>Your games does not support Cloud Saves</source>
<translation>Deine Spiele unterstützen keine Online Speicherstände</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="70"/>
<location filename="../Components/Tabs/CloudSaves/__init__.py" line="67"/>
<source>Sync all games</source>
<translation>Alle Spiele synchronisieren</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="110"/>
<location filename="../Components/Tabs/CloudSaves/__init__.py" line="108"/>
<source>Found no savepath</source>
<translation>Kein Speicherort gefunden</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="110"/>
<location filename="../Components/Tabs/CloudSaves/__init__.py" line="108"/>
<source>No save path was found. Please select path or skip</source>
<translation>Kein Speicherort wurde gefunden. Wähle einen Ordner oder überspringe</translation>
</message>
@ -695,97 +827,97 @@ Installationsgröße: {} GB</translation>
<context>
<name>SyncWidget</name>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="60"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="62"/>
<source>Path not found</source>
<translation>Ordner nicht gefunden</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="76"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="78"/>
<source>Local Save date: </source>
<translation>Lokales Speicherdatum: </translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="79"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="81"/>
<source>No Local Save files</source>
<translation>Keine Lokalen Dateien</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="81"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="83"/>
<source>Cloud save date: </source>
<translation>Online Speicherdatum: </translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="83"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="85"/>
<source>No Cloud saves</source>
<translation>Keine Online Speicherstände</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="87"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="89"/>
<source>Game is up to date</source>
<translation>Spiel ist aktuell</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="88"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="90"/>
<source>Upload anyway</source>
<translation>Trotzdem hochladen</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="89"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="91"/>
<source>Download anyway</source>
<translation>Trotzdem herunterladen</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="91"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="93"/>
<source>Cloud save is newer</source>
<translation>Online Speicherstand ist aktueller</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="92"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="94"/>
<source>Download Cloud saves</source>
<translation>Online Speicherstand herunterladen</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="96"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="98"/>
<source>Upload Saves</source>
<translation>Spielstände hochladen</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="107"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="109"/>
<source>Local save is newer</source>
<translation>Lokaler Speicher ist aktueller</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="108"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="110"/>
<source>Upload saves</source>
<translation>Spielstände hochladen</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="112"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="114"/>
<source>Download saves</source>
<translation>Spielstand herunterladen</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="134"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="137"/>
<source>Change path</source>
<translation>Pfad ändern</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="159"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="162"/>
<source>Uploading...</source>
<translation>Hochladen...</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="167"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="170"/>
<source>Upload finished</source>
<translation>Hochladen abgeschlossen</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="177"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="180"/>
<source>Downloading...</source>
<translation>Runterladen...</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="183"/>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="186"/>
<source>Download finished</source>
<translation>Download abgeschlossen</translation>
</message>
@ -798,10 +930,46 @@ Installationsgröße: {} GB</translation>
<translation>Spiele</translation>
</message>
</context>
<context>
<name>TrayIcon</name>
<message>
<location filename="../Components/TrayIcon.py" line="18"/>
<source>Exit</source>
<translation>Schließen</translation>
</message>
</context>
<context>
<name>UninstallDialog</name>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="13"/>
<source>Do you really want to uninstall {}</source>
<translation>Möchtest du wirklich {} deinstallieren</translation>
</message>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="15"/>
<source>Keep Files</source>
<translation>Dateien behalten</translation>
</message>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="18"/>
<source>Do you want to keep files?</source>
<translation>Willst du die Dateien behalten?</translation>
</message>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="22"/>
<source>Uninstall</source>
<translation>Deinstallieren</translation>
</message>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="25"/>
<source>Cancel</source>
<translation>Abbruch</translation>
</message>
</context>
<context>
<name>UpdateWidget</name>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="349"/>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="277"/>
<source>Update Game</source>
<translation>Spiel updaten</translation>
</message>

BIN
Rare/languages/fr.qm Normal file

Binary file not shown.

818
Rare/languages/fr.ts Normal file
View file

@ -0,0 +1,818 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0">
<context>
<name>About</name>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="17"/>
<source>Developer:</source>
<translation>Développeur</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="22"/>
<source>Legendary developer:</source>
<translation>Legendary Développeur</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="28"/>
<source>This is a beta version, so you can get bugs. If you get a bug, please report it by creating a Issue on &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt;. You can also contact me on Discord (Dummerle#7419). Or you can join the &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;Discord server&lt;/a&gt;</source>
<translation>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 &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt;. Vous pouvez également me contacter sur Discord (Dummerle#7419). Ou vous pouvez rejoindre le &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;serveur Discord&lt;/a&gt;</translation>
</message>
</context>
<context>
<name>BaseInstalledWidget</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py" line="34"/>
<source>Do you want to launch {}</source>
<translation>Voulez-vous lancer {}</translation>
</message>
</context>
<context>
<name>BrowserLogin</name>
<message>
<location filename="../Components/Dialogs/Login/BrowserLogin.py" line="24"/>
<source>Opens a browser. You login and copy the json code in the field below. Click &lt;a href=&apos;{}&apos;&gt;here&lt;/a&gt; to open Browser</source>
<translation>Ouvre un navigateur. Vous vous connectez et copiez le code json dans le champ ci-dessous. Cliquez &lt;a href=&apos;{}&apos;&gt;here&lt;/a&gt; pour ouvrir un navigateur</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/BrowserLogin.py" line="32"/>
<source>Insert SID here</source>
<translation>Insérer le SID ici</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/BrowserLogin.py" line="37"/>
<source>Login</source>
<translation>Login</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/BrowserLogin.py" line="44"/>
<source>Loading...</source>
<translation>charge...</translation>
</message>
</context>
<context>
<name>DownloadTab</name>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="133"/>
<source>No active Download</source>
<translation>Aucun téléchargement actif</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="154"/>
<source>Stop Download</source>
<translation>Stop Télécharger</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="170"/>
<source>No updates available</source>
<translation>Aucune mise à jour disponible</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="217"/>
<source>Error preparing download</source>
<translation>Erreur lors de la préparation du téléchargement</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="222"/>
<source>Download size is 0. Game already exists</source>
<translation>La taille du téléchargement est de 0. Le jeu existe déjà</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="229"/>
<source>Installing game: </source>
<translation>Installation du jeu: </translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="288"/>
<source>Installation finished</source>
<translation>Installation terminée</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="289"/>
<source>Finished Download of game {}</source>
<translation>Fin du téléchargement du jeu {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="312"/>
<source>Installing Game: No active download</source>
<translation>Installation du jeu: Aucun téléchargement actif</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="320"/>
<source>Download speed</source>
<translation>Vitesse de téléchargement</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="321"/>
<source>Cache used</source>
<translation>Cache utilisé</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="322"/>
<source>Downloaded</source>
<translation>Téléchargé</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="323"/>
<source>Time left: </source>
<translation>Il reste du temps: </translation>
</message>
</context>
<context>
<name>DxvkWidget</name>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="18"/>
<source>GPU usage</source>
<translation>GPU Utilisation</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="19"/>
<source>Used Memory</source>
<translation>utilisé Memory</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="20"/>
<source>Device info</source>
<translation>Info sur le dispositif</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="21"/>
<source>DXVK version</source>
<translation>DXVK version</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="22"/>
<source>D3D Level of application</source>
<translation>D3D Niveau d'application</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="23"/>
<source>Frame time graph</source>
<translation>Graphique de temps de trame</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="28"/>
<source>dxvk settings</source>
<translation>dxvk paramètres</translation>
</message>
</context>
<context>
<name>GameActions</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="182"/>
<source>Uninstall game</source>
<translation>Désinstaller le jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="184"/>
<source>Uninstall</source>
<translation>Désinstaller</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="190"/>
<source>Verify Game</source>
<translation>Vérifier le jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="195"/>
<source>Verify</source>
<translation>Vérifier</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="204"/>
<source>Repair Game</source>
<translation>Jeu de réparation</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="206"/>
<source>Repair</source>
<translation>Réparation</translation>
</message>
</context>
<context>
<name>GameInfo</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="98"/>
<source>Are you sure to uninstall {}</source>
<translation>Etes-vous sûr de désinstaller {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="106"/>
<source>Repair file does not exist or game does not need a repair. Please verify game first</source>
<translation>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.</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="131"/>
<source>Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?</source>
<translation>La vérification a échoué, {} fichier(s) corrompu(s), {} fichier(s) manquant(s). Voulez-vous les réparer ?</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="160"/>
<source>Developer: </source>
<translation>Développeur: </translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="161"/>
<source>Install size: </source>
<translation>Taille d'installation: </translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="163"/>
<source>Install path: </source>
<translation>Chemin d'installation:</translation>
</message>
</context>
<context>
<name>GameList</name>
<message>
<location filename="../Components/Tabs/Games/GameList.py" line="45"/>
<source>Installed Games: {} Available Games: {}</source>
<translation>Jeux installés: {} Jeux disponibles: {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameList.py" line="150"/>
<source>Launch</source>
<translation>Lancer</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameList.py" line="155"/>
<source>Game running</source>
<translation>Jeu en cours</translation>
</message>
</context>
<context>
<name>GameListHeadBar</name>
<message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="74"/>
<source>Installed only</source>
<translation>Installé uniquement</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="81"/>
<source>Import Game</source>
<translation>Jeu d'importation</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="93"/>
<source>Search Game</source>
<translation>Rechercher un jeu</translation>
</message>
</context>
<context>
<name>GameSettings</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="27"/>
<source>Launch Game offline</source>
<translation>Lancer le jeu hors ligne</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="32"/>
<source>Skip update check before launching</source>
<translation>Sauter la vérification de la mise à jour avant le lancement</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="65"/>
<source>Save</source>
<translation>Sauvez</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="41"/>
<source>Wrapper (e.g. optirun)</source>
<translation>Wrapper (p.e. optirun)</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="61"/>
<source>Proton Wrapper</source>
<translation>Enveloppeur de Proton</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="67"/>
<source>Proton prefix</source>
<translation>Préfixe du proton</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="173"/>
<source>No permission to create folder</source>
<translation>Pas de permission pour créer un dossier</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="218"/>
<source>Please select path for proton prefix</source>
<translation>Veuillez sélectionner le chemin pour le préfixe proton</translation>
</message>
</context>
<context>
<name>GameWidgetInstalled</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledIconWidget.py" line="36"/>
<source>Update available</source>
<translation>Mise à jour disponible</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledIconWidget.py" line="86"/>
<source>Start game without version check</source>
<translation>Démarrer le jeu sans vérifier la version</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledIconWidget.py" line="94"/>
<source>Game running</source>
<translation>Jeu en cours</translation>
</message>
</context>
<context>
<name>IconWidgetUninstalled</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py" line="43"/>
<source>Install Game</source>
<translation>Installer le jeu</translation>
</message>
</context>
<context>
<name>ImportWidget</name>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="99"/>
<source>Import</source>
<translation>Importer</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="39"/>
<source>Could not find EGL program data</source>
<translation>Impossible de trouver les données du programme EGL</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="42"/>
<source>Found EGL program Data. Do you want to import them?</source>
<translation>Les données du programme EGL ont é trouvées. Voulez-vous les importer ?</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="51"/>
<source>Could not find any Epic Games login data</source>
<translation>Impossible de trouver les données de connexion d'Epic Games</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="86"/>
<source>Loading...</source>
<translation>Chargement...</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="98"/>
<source>Error: No valid session found</source>
<translation>Erreur : Aucune session valide n'a é trouvée</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="26"/>
<source>Back</source>
<translation>Dos</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="40"/>
<source>Select path to game</source>
<translation>Sélectionnez le chemin vers le jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="43"/>
<source>Override app name (Only if imported game from legendary or the app could not find the app name)</source>
<translation>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)</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="62"/>
<source>Import Game</source>
<translation>Import jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="70"/>
<source>Import all games from Epic Games Launcher</source>
<translation>Importer tous les jeux du Epic Games Launcher</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="110"/>
<source>Could not find app name</source>
<translation>Impossible de trouver le nom de l'application</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="114"/>
<source>Successfully imported {}. Reload library</source>
<translation>Importation réussie de {}. Recharger la bibliothèque</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="120"/>
<source>Failed to import {}</source>
<translation>Impossible d'importer {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="159"/>
<source>Successfully imported {} Games. Reloading Library</source>
<translation>Importation réussie de {} Jeux. Bibliothèque de rechargement</translation>
</message>
</context>
<context>
<name>InfoTabs</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="26"/>
<source>Back</source>
<translation>Dos</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="30"/>
<source>Game Info</source>
<translation>Info de jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="32"/>
<source>Settings</source>
<translation>Paramètres</translation>
</message>
</context>
<context>
<name>InstallDialog</name>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="20"/>
<source>&lt;h3&gt;Install {}&lt;/h3&gt;</source>
<translation>&lt;h3&gt;Installer {}&lt;/h3&gt;</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="29"/>
<source>Max workers (0: Default)</source>
<translation>Travailleurs maximum (0: Par défaut)</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="33"/>
<source>Force download</source>
<translation>Téléchargement forcé</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="37"/>
<source>Ignore free space (Warning!)</source>
<translation>Ignorer l'espace libre (Attention!)</translation>
</message>
</context>
<context>
<name>InstallInfoDialog</name>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="70"/>
<source>Download size: {}GB
Install size: {}GB</source>
<translation>Taille du téléchargement: {}GB
Taille de l'installation: {}GB</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="76"/>
<source>Install</source>
<translation>Installer</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="78"/>
<source>Cancel</source>
<translation>Annuler</translation>
</message>
</context>
<context>
<name>InstalledListWidget</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledListWidget.py" line="41"/>
<source>Launch</source>
<translation>Lancer</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledListWidget.py" line="52"/>
<source>Developer: </source>
<translation>Développeur: </translation>
</message>
</context>
<context>
<name>LaunchDialog</name>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="60"/>
<source>Launching Rare</source>
<translation>Lancer Rare</translation>
</message>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="62"/>
<source>Logging in</source>
<translation>Se connecter</translation>
</message>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="80"/>
<source>Downloading Images</source>
<translation>Téléchargement d'images</translation>
</message>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="91"/>
<source>Starting...</source>
<translation>Démarrage...</translation>
</message>
</context>
<context>
<name>LaunchThread</name>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="23"/>
<source>Downloading Images</source>
<translation>Téléchargement d'images</translation>
</message>
</context>
<context>
<name>LegendarySettings</name>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="18"/>
<source>Legendary settings</source>
<translation>Legendary paramètres</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="27"/>
<source>Default installation directory</source>
<translation>Répertoire d'installation par défaut</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="36"/>
<source>Max workers for Download (Less: slower download)(0: Default)</source>
<translation>Nombre maximum de travailleurs pour le téléchargement (Moins: téléchargement plus lent)(0: Défaut)</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="43"/>
<source>Cleanup</source>
<translation>Nettoyage</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="44"/>
<source>Remove everything</source>
<translation>Enlever tout</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="48"/>
<source>Clean, but keep manifests</source>
<translation>Nettoyer, mais garder les manifestes</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="96"/>
<source>Cleanup complete! Successfully removed {} MB</source>
<translation>Nettoyage terminé ! J'ai réussi à supprimer {} MB</translation>
</message>
</context>
<context>
<name>LinuxSettings</name>
<message>
<location filename="../Components/Tabs/Settings/Linux.py" line="19"/>
<source>Linux settings</source>
<translation>Linux paramètres</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Linux.py" line="28"/>
<source>Default Wine Prefix</source>
<translation>Défaut Wine Prefix</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Linux.py" line="36"/>
<source>Default Wine executable</source>
<translation>Défaut Wine exécutable</translation>
</message>
</context>
<context>
<name>ListWidgetUninstalled</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/UninstalledListWidget.py" line="29"/>
<source>Install</source>
<translation>Installer</translation>
</message>
</context>
<context>
<name>LoginDialog</name>
<message>
<location filename="../Components/Dialogs/Login/LoginDialog.py" line="30"/>
<source>Select one option to Login</source>
<translation>Sélectionnez une option pour vous connecter</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/LoginDialog.py" line="33"/>
<source>Use Browser</source>
<translation>Utiliser le navigateur</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/LoginDialog.py" line="33"/>
<source>This opens your default browser. Login and copy the text</source>
<translation>Cela ouvre votre navigateur par défaut. Connectez-vous et copiez le texte</translation>
</message>
</context>
<context>
<name>MiniWidget</name>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="20"/>
<source>Logged in as </source>
<translation>Connecté en tant que </translation>
</message>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="22"/>
<source>Account settings</source>
<translation>paramètres du compte</translation>
</message>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="26"/>
<source>Logout</source>
<translation>Déconnexion</translation>
</message>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="32"/>
<source>Do you really want to logout</source>
<translation>Voulez-vous vraiment vous déconnecter?</translation>
</message>
</context>
<context>
<name>PathEdit</name>
<message>
<location filename="../utils/QtExtensions.py" line="133"/>
<source>Select Path</source>
<translation>Sélectionner le chemin</translation>
</message>
<message>
<location filename="../utils/QtExtensions.py" line="146"/>
<source>Choose Path</source>
<translation>Choisir le chemin</translation>
</message>
</context>
<context>
<name>PathInputDialog</name>
<message>
<location filename="../Components/Dialogs/PathInputDialog.py" line="24"/>
<source>Cancel</source>
<translation>Annuler</translation>
</message>
</context>
<context>
<name>RareSettings</name>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="19"/>
<source>Rare settings</source>
<translation>Rare paramètres</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="33"/>
<source>Save</source>
<translation>Sauvez</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="35"/>
<source>Image Directory</source>
<translation>Répertoire d'images</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="49"/>
<source>Language</source>
<translation>Langue</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="55"/>
<source>Confirm launch of game</source>
<translation>Confirmation du lancement du jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="77"/>
<source>Restart Application to activate changes</source>
<translation>Redémarrez l'application pour activer les changements</translation>
</message>
</context>
<context>
<name>SyncSaves</name>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="65"/>
<source>Cloud Saves</source>
<translation>Cloud Saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="54"/>
<source>Found Saves for folowing Games</source>
<translation>Sauvegardes trouvées pour les jeux suivants</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="65"/>
<source>Your games does not support Cloud Saves</source>
<translation>Vos jeux ne prennent pas en charge les sauvegardes en nuage</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="70"/>
<source>Sync all games</source>
<translation>Sync tous les jeux</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="110"/>
<source>Found no savepath</source>
<translation>Pas de chemin de sauvegarde trouvé</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="110"/>
<source>No save path was found. Please select path or skip</source>
<translation>Aucun chemin de sauvegarde n'a é trouvé. Veuillez sélectionner le chemin ou passer</translation>
</message>
</context>
<context>
<name>SyncWidget</name>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="60"/>
<source>Path not found</source>
<translation>Chemin non trouvé</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="76"/>
<source>Local Save date: </source>
<translation>Local Save date: </translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="79"/>
<source>No Local Save files</source>
<translation>Pas de fichiers de sauvegarde locaux</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="81"/>
<source>Cloud save date: </source>
<translation>Cloud save date: </translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="83"/>
<source>No Cloud saves</source>
<translation>Pas Cloud saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="87"/>
<source>Game is up to date</source>
<translation>Le jeu est à jour</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="88"/>
<source>Upload anyway</source>
<translation>Télécharger quand même</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="89"/>
<source>Download anyway</source>
<translation>Télécharger en tout cas</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="91"/>
<source>Cloud save is newer</source>
<translation>La sauvegarde en nuage est plus récente</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="92"/>
<source>Download Cloud saves</source>
<translation>Télécharger Cloud saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="96"/>
<source>Upload Saves</source>
<translation>Upload Saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="107"/>
<source>Local save is newer</source>
<translation>La sauvegarde locale est plus récente</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="108"/>
<source>Upload saves</source>
<translation>Upload Saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="112"/>
<source>Download saves</source>
<translation>Télécharger les sauvegardes</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="134"/>
<source>Change path</source>
<translation>Changement de trajectoire</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="158"/>
<source>Uploading...</source>
<translation>Téléchargement...</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="166"/>
<source>Upload finished</source>
<translation>Téléchargement terminé</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="176"/>
<source>Downloading...</source>
<translation>Téléchargement...</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="182"/>
<source>Download finished</source>
<translation>Téléchargement terminé</translation>
</message>
</context>
<context>
<name>TabWidget</name>
<message>
<location filename="../Components/TabWidget.py" line="23"/>
<source>Games</source>
<translation>Jeus</translation>
</message>
</context>
<context>
<name>UpdateWidget</name>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="353"/>
<source>Update Game</source>
<translation>Jeu de mise à jour</translation>
</message>
</context>
</TS>

View file

@ -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.')

View file

@ -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