diff --git a/.gitignore b/.gitignore index 69e93893..2547711d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__ /.idea +/.vscode /build /dist /Rare.egg-info/ diff --git a/README.md b/README.md index b787120b..4be4fec8 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,12 @@ Using the exe-file could cause an error with the stylesheets - Launch, install and uninstall games - Authentication(Import from existing installation and via Browser) -- In-app Browser to buy games +- Download progress bar - Settings (Legendary and games) - Translations (English and German) ## Planned - Sync Cloud Saves -- Download Progressbar - Offline mode - More Translations diff --git a/Rare/Components/Tabs/Account/__pycache__/AccountWidget.cpython-38.pyc b/Rare/Components/Tabs/Account/__pycache__/AccountWidget.cpython-38.pyc deleted file mode 100644 index 524aba18..00000000 Binary files a/Rare/Components/Tabs/Account/__pycache__/AccountWidget.cpython-38.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/Account/__pycache__/AccountWidget.cpython-39.pyc b/Rare/Components/Tabs/Account/__pycache__/AccountWidget.cpython-39.pyc deleted file mode 100644 index 756e24aa..00000000 Binary files a/Rare/Components/Tabs/Account/__pycache__/AccountWidget.cpython-39.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/Downloads/DownloadTab.py b/Rare/Components/Tabs/Downloads/DownloadTab.py index 14d8b2dd..c99be7a3 100644 --- a/Rare/Components/Tabs/Downloads/DownloadTab.py +++ b/Rare/Components/Tabs/Downloads/DownloadTab.py @@ -1,13 +1,17 @@ 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 QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton +from PyQt5.QtCore import QThread, pyqtSignal, Qt, QVariant +from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton, QDialog, QListWidget +from notifypy import Notify from legendary.core import LegendaryCore from legendary.models.game import Game -from notifypy import Notify +from legendary.models.downloading import UIUpdate +from legendary.utils.selective_dl import games from Rare.Components.Dialogs.InstallDialog import InstallInfoDialog from Rare.utils.LegendaryApi import VerifyThread @@ -18,11 +22,13 @@ logger = getLogger("Download") class DownloadThread(QThread): status = pyqtSignal(str) + statistics = pyqtSignal(UIUpdate) - def __init__(self, dlm, core: LegendaryCore, igame, repair=False, repair_file=None): + def __init__(self, dlm, 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 @@ -32,9 +38,15 @@ class DownloadThread(QThread): try: self.dlm.start() + time.sleep(1) + while self.dlm.is_alive(): + try: + self.statistics.emit(self.status_queue.get(timeout=0.1)) + except queue.Empty: + pass self.dlm.join() - except: - logger.error(f"Installation failed after{time.time() - start_time:.02f} seconds.") + except Exception as e: + logger.error(f"Installation failed after {time.time() - start_time:.02f} seconds: {e}") self.status.emit("error") return @@ -104,9 +116,9 @@ class DownloadTab(QWidget): self.active_game: Game = None self.installing_game = QLabel(self.tr("No active Download")) - self.dl_speed = QLabel(self.tr("Download speed") + ": 0MB/s") - self.cache_used = QLabel(self.tr("Cache used") + ": 0MB") - self.downloaded = QLabel(self.tr("Downloaded") + ": 0MB") + self.dl_speed = QLabel() + self.cache_used = QLabel() + self.downloaded = QLabel() self.info_layout = QGridLayout() @@ -114,16 +126,11 @@ class DownloadTab(QWidget): self.info_layout.addWidget(self.dl_speed, 0, 1) self.info_layout.addWidget(self.cache_used, 1, 0) self.info_layout.addWidget(self.downloaded, 1, 1) - self.layout.addLayout(self.info_layout) + self.prog_bar = QProgressBar() + self.prog_bar.setMaximum(100) self.layout.addWidget(self.prog_bar) - label = QLabel( - self.tr("WARNING: The progress bar is not implemented. It is normal, if there is no progress. The " - "progress is visible in console, because Legendary prints output to console. A pull request is " - "active to get output")) - label.setWordWrap(True) - self.layout.addWidget(label) self.installing_game_widget = QLabel(self.tr("No active Download")) self.layout.addWidget(self.installing_game_widget) @@ -150,55 +157,39 @@ class DownloadTab(QWidget): def install_game(self, options: InstallOptions): game = self.core.get_game(options.app_name, update_meta=True) - if self.core.is_installed(options.app_name): - igame = self.core.get_installed_game(options.app_name) - if igame.needs_verification and not options.repair: - options.repair = True - repair_file = None - if options.repair: - repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{options.app_name}.repair') - - if not game: - QMessageBox.warning(self, "Error", self.tr("Could not find Game in your library")) + status_queue = MPQueue() + try: + dlm, analysis, game, igame, repair, repair_file = self.core.prepare_download( + app_name=options.app_name, + base_path=options.path, + force=False, # TODO allow overwrite + no_install=options.download_only, + status_q=status_queue, + #max_shm=, + max_workers=options.max_workers, + #game_folder=, + #disable_patching=, + #override_manifest=, + #override_old_manifest=, + #override_base_url=, + #platform_override=, + #file_prefix_filter=, + #file_exclude_filter=, + #file_install_tag=, + #dl_optimizations=, + #dl_timeout=, + repair=options.repair, + #repair_use_latest=, + #ignore_space_req=, + #disable_delta=, + #override_delta_manifest=, + #reset_sdl=, + sdl_prompt=self.sdl_prompt) + except Exception as e: + QMessageBox.warning(self, self.tr("Error preparing download"), + str(e)) return - if game.is_dlc: - logger.info("DLCs are currently not supported") - return - - if game.is_dlc: - logger.info('Install candidate is DLC') - app_name = game.metadata['mainGameItem']['releaseInfo'][0]['appId'] - base_game = self.core.get_game(app_name) - # check if base_game is actually installed - if not self.core.is_installed(app_name): - # download mode doesn't care about whether or not something's installed - logger.error("Base Game is not installed") - return - else: - base_game = None - - if options.repair: - repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{options.app_name}.repair') - if not self.core.is_installed(game.app_name): - return - - if not os.path.exists(repair_file): - logger.info("Game has not been verified yet") - if QMessageBox.question(self, "Verify", self.tr("Game has not been verified yet. Do you want to verify first?"), - QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes: - self.verify_thread = VerifyThread(self.core, game.app_name) - self.verify_thread.finished.connect( - lambda: self.prepare_download(game, options, base_game, repair_file)) - self.verify_thread.start() - return - self.prepare_download(game, options, base_game, repair_file) - - def prepare_download(self, game, options, base_game, repair_file): - dlm, analysis, igame = self.core.prepare_download( - game=game, - base_path=options.path, - max_workers=options.max_workers, base_game=base_game, repair=options.repair) if not analysis.dl_size: QMessageBox.information(self, "Warning", self.tr("Download size is 0. Game already exists")) return @@ -206,26 +197,50 @@ class DownloadTab(QWidget): if not InstallInfoDialog(dl_size=analysis.dl_size, install_size=analysis.install_size).get_accept(): return - self.installing_game_widget.setText("") - self.installing_game.setText(self.tr("Installing Game: ") + game.app_title) - res = self.core.check_installation_conditions(analysis=analysis, install=igame, game=game, - updating=self.core.is_installed(options.app_name), - ) - if res.warnings: - for w in sorted(res.warnings): - logger.warning(w) - if res.failures: - for msg in sorted(res.failures): - logger.error(msg) - logger.error('Installation cannot proceed, exiting.') - QMessageBox.warning(self, "Installation failed", - self.tr("Installation failed. See logs for more information")) - return self.active_game = game - self.thread = DownloadThread(dlm, self.core, igame, options.repair, repair_file) + 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() + def sdl_prompt(self, app_name: str = '', title: str = '') -> list: + sdl = QDialog() + sdl.setWindowTitle('Select Additional Downloads') + + layout = QVBoxLayout(sdl) + sdl.setLayout(layout) + + pack_list = QListWidget() + layout.addWidget(pack_list) + + done = QPushButton(text='Done') + done.clicked.connect(sdl.accept) + layout.addWidget(done) + + tags = [''] + if '__required' in games[app_name]: + tags.extend(games[app_name]['__required']['tags']) + + # add available additional downloads to list + pack_list.addItems([ tag + ': ' + info['name'] for tag, info in games[app_name].items() if tag != '__required' ]) + + # enable checkboxes + for i in range(len(pack_list)): + item = pack_list.item(i) + item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) + item.setCheckState(Qt.Unchecked) + + sdl.exec_() + + # read checkboxes states + for i in range(len(pack_list)): + item = pack_list.item(i) + if item.checkState() == Qt.Checked: + tag = item.text().split(':')[0] + tags.extend(games[app_name][tag]['tags']) + + return tags + def status(self, text): if text == "dl_finished": pass @@ -240,6 +255,12 @@ class DownloadTab(QWidget): elif text == "error": QMessageBox.warning(self, "warn", "Download error") + def statistics(self, ui_update: UIUpdate): + self.prog_bar.setValue(ui_update.progress) + self.dl_speed.setText(self.tr("Download speed") + f": {ui_update.download_speed/1024/1024:.02f}MB/s") + self.cache_used.setText(self.tr("Cache used") + f": {ui_update.cache_usage/1024/1024:.02f}MB") + self.downloaded.setText(self.tr("Downloaded") + f": {ui_update.total_downloaded/1024/1024:.02f}MB") + def update_game(self, app_name: str): print("Update ", app_name) self.install_game(InstallOptions(app_name)) diff --git a/Rare/Components/Tabs/Games/GameInfo/GameInfo.py b/Rare/Components/Tabs/Games/GameInfo/GameInfo.py index d747949b..e71c6d4a 100644 --- a/Rare/Components/Tabs/Games/GameInfo/GameInfo.py +++ b/Rare/Components/Tabs/Games/GameInfo/GameInfo.py @@ -98,7 +98,6 @@ class GameInfo(QWidget): QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: LegendaryApi.uninstall(self.game.app_name, self.core) self.update_list.emit() - self.back_button.click() def repair(self): repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{self.game.app_name}.repair') diff --git a/Rare/Components/Tabs/Games/GameInfo/GameSettings.py b/Rare/Components/Tabs/Games/GameInfo/GameSettings.py index c1bbd28e..8235b169 100644 --- a/Rare/Components/Tabs/Games/GameInfo/GameSettings.py +++ b/Rare/Components/Tabs/Games/GameInfo/GameSettings.py @@ -40,11 +40,14 @@ class GameSettings(QWidget): self.layout.addWidget(self.linux_settings) self.possible_proton_wrappers = [] - for i in os.listdir(os.path.expanduser("~/.steam/steam/steamapps/common")): - if i.startswith("Proton"): - wrapper = '"' + os.path.join(os.path.expanduser("~/.steam/steam/steamapps/common"), i, - "proton") + '" run' - self.possible_proton_wrappers.append(wrapper) + try: + for i in os.listdir(os.path.expanduser("~/.steam/steam/steamapps/common")): + if i.startswith("Proton"): + wrapper = '"' + os.path.join(os.path.expanduser("~/.steam/steam/steamapps/common"), i, + "proton") + '" run' + self.possible_proton_wrappers.append(wrapper) + except FileNotFoundError as e: + print("Unable to find Proton:", e) self.select_proton = QComboBox() self.select_proton.addItems(["Don't use Proton"] + self.possible_proton_wrappers) self.select_proton.currentIndexChanged.connect(self.change_proton) diff --git a/Rare/Components/Tabs/Games/__pycache__/GameList.cpython-38.pyc b/Rare/Components/Tabs/Games/__pycache__/GameList.cpython-38.pyc deleted file mode 100644 index 24165df8..00000000 Binary files a/Rare/Components/Tabs/Games/__pycache__/GameList.cpython-38.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/Games/__pycache__/GameList.cpython-39.pyc b/Rare/Components/Tabs/Games/__pycache__/GameList.cpython-39.pyc deleted file mode 100644 index 2c762c19..00000000 Binary files a/Rare/Components/Tabs/Games/__pycache__/GameList.cpython-39.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/Games/__pycache__/GameWidgetInstalled.cpython-39.pyc b/Rare/Components/Tabs/Games/__pycache__/GameWidgetInstalled.cpython-39.pyc deleted file mode 100644 index 12665593..00000000 Binary files a/Rare/Components/Tabs/Games/__pycache__/GameWidgetInstalled.cpython-39.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/Games/__pycache__/GameWidgetUninstalled.cpython-38.pyc b/Rare/Components/Tabs/Games/__pycache__/GameWidgetUninstalled.cpython-38.pyc deleted file mode 100644 index c338885d..00000000 Binary files a/Rare/Components/Tabs/Games/__pycache__/GameWidgetUninstalled.cpython-38.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/Games/__pycache__/GameWidgetUninstalled.cpython-39.pyc b/Rare/Components/Tabs/Games/__pycache__/GameWidgetUninstalled.cpython-39.pyc deleted file mode 100644 index 47884c73..00000000 Binary files a/Rare/Components/Tabs/Games/__pycache__/GameWidgetUninstalled.cpython-39.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/Games/__pycache__/__init__.cpython-38.pyc b/Rare/Components/Tabs/Games/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 3c0b23f5..00000000 Binary files a/Rare/Components/Tabs/Games/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/Games/__pycache__/__init__.cpython-39.pyc b/Rare/Components/Tabs/Games/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 433835e2..00000000 Binary files a/Rare/Components/Tabs/Games/__pycache__/__init__.cpython-39.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/__pycache__/__init__.cpython-38.pyc b/Rare/Components/Tabs/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 567bfa6d..00000000 Binary files a/Rare/Components/Tabs/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/Rare/Components/Tabs/__pycache__/__init__.cpython-39.pyc b/Rare/Components/Tabs/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 18a578ef..00000000 Binary files a/Rare/Components/Tabs/__pycache__/__init__.cpython-39.pyc and /dev/null differ diff --git a/Rare/Components/__pycache__/MainWindow.cpython-38.pyc b/Rare/Components/__pycache__/MainWindow.cpython-38.pyc deleted file mode 100644 index 32ac6a89..00000000 Binary files a/Rare/Components/__pycache__/MainWindow.cpython-38.pyc and /dev/null differ diff --git a/Rare/Components/__pycache__/MainWindow.cpython-39.pyc b/Rare/Components/__pycache__/MainWindow.cpython-39.pyc deleted file mode 100644 index 0fb0b5f8..00000000 Binary files a/Rare/Components/__pycache__/MainWindow.cpython-39.pyc and /dev/null differ diff --git a/Rare/Components/__pycache__/TabWidget.cpython-38.pyc b/Rare/Components/__pycache__/TabWidget.cpython-38.pyc deleted file mode 100644 index 29c02f81..00000000 Binary files a/Rare/Components/__pycache__/TabWidget.cpython-38.pyc and /dev/null differ diff --git a/Rare/Components/__pycache__/TabWidget.cpython-39.pyc b/Rare/Components/__pycache__/TabWidget.cpython-39.pyc deleted file mode 100644 index c2ba93ed..00000000 Binary files a/Rare/Components/__pycache__/TabWidget.cpython-39.pyc and /dev/null differ diff --git a/Rare/Components/__pycache__/__init__.cpython-38.pyc b/Rare/Components/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index c7c64683..00000000 Binary files a/Rare/Components/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/Rare/Components/__pycache__/__init__.cpython-39.pyc b/Rare/Components/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 629a852f..00000000 Binary files a/Rare/Components/__pycache__/__init__.cpython-39.pyc and /dev/null differ diff --git a/Rare/Main.py b/Rare/__main__.py similarity index 100% rename from Rare/Main.py rename to Rare/__main__.py diff --git a/Rare/__pycache__/__init__.cpython-38.pyc b/Rare/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index eac0adec..00000000 Binary files a/Rare/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/Rare/__pycache__/__init__.cpython-39.pyc b/Rare/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 048e31aa..00000000 Binary files a/Rare/__pycache__/__init__.cpython-39.pyc and /dev/null differ diff --git a/Rare/utils/__pycache__/LegendaryApi.cpython-39.pyc b/Rare/utils/__pycache__/LegendaryApi.cpython-39.pyc deleted file mode 100644 index 51a67440..00000000 Binary files a/Rare/utils/__pycache__/LegendaryApi.cpython-39.pyc and /dev/null differ diff --git a/Rare/utils/__pycache__/QtExtensions.cpython-38.pyc b/Rare/utils/__pycache__/QtExtensions.cpython-38.pyc deleted file mode 100644 index 9da2b837..00000000 Binary files a/Rare/utils/__pycache__/QtExtensions.cpython-38.pyc and /dev/null differ diff --git a/Rare/utils/__pycache__/QtExtensions.cpython-39.pyc b/Rare/utils/__pycache__/QtExtensions.cpython-39.pyc deleted file mode 100644 index ca7ec4f6..00000000 Binary files a/Rare/utils/__pycache__/QtExtensions.cpython-39.pyc and /dev/null differ diff --git a/Rare/utils/__pycache__/__init__.cpython-38.pyc b/Rare/utils/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 278ce053..00000000 Binary files a/Rare/utils/__pycache__/__init__.cpython-38.pyc and /dev/null differ diff --git a/Rare/utils/__pycache__/__init__.cpython-39.pyc b/Rare/utils/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index bcb49365..00000000 Binary files a/Rare/utils/__pycache__/__init__.cpython-39.pyc and /dev/null differ diff --git a/requirements.txt b/requirements.txt index 7ed2416f..11e0ebc0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -legendary-gl +git+https://github.com/ChemicalXandco/legendary.git@c1db2b9#egg=legendary-gl requests Pillow PyQt5 QtAwesome -notify-py \ No newline at end of file +notify-py diff --git a/setup.py b/setup.py index 8785917e..d4d4f55a 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ setuptools.setup( "Operating System :: OS Independent" ], python_requires=">=3.8", - entry_points=dict(console_scripts=["rare=Rare.Main:main"]), + entry_points=dict(console_scripts=["rare=Rare.__main__:main"]), install_requires=[ "legendary-gl", "requests<3.0", diff --git a/start.sh b/start.sh new file mode 100644 index 00000000..f6fb2402 --- /dev/null +++ b/start.sh @@ -0,0 +1,2 @@ +export PYTHONPATH=$PWD +python3 Rare