1
0
Fork 0
mirror of synced 2024-06-29 19:51:02 +12:00

Merge pull request #19 from ChemicalXandco/download_progress

Download progress
This commit is contained in:
Dummerle 2021-03-15 10:52:23 +01:00 committed by GitHub
commit 7e0627bbe1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 115 additions and 90 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
__pycache__ __pycache__
/.idea /.idea
/.vscode
/build /build
/dist /dist
/Rare.egg-info/ /Rare.egg-info/

View file

@ -36,13 +36,12 @@ Using the exe-file could cause an error with the stylesheets
- Launch, install and uninstall games - Launch, install and uninstall games
- Authentication(Import from existing installation and via Browser) - Authentication(Import from existing installation and via Browser)
- In-app Browser to buy games - Download progress bar
- Settings (Legendary and games) - Settings (Legendary and games)
- Translations (English and German) - Translations (English and German)
## Planned ## Planned
- Sync Cloud Saves - Sync Cloud Saves
- Download Progressbar
- Offline mode - Offline mode
- More Translations - More Translations

View file

@ -1,13 +1,17 @@
import os import os
import queue
import subprocess import subprocess
import time import time
from logging import getLogger from logging import getLogger
from multiprocessing import Queue as MPQueue
from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtCore import QThread, pyqtSignal, Qt, QVariant
from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton, QDialog, QListWidget
from notifypy import Notify
from legendary.core import LegendaryCore from legendary.core import LegendaryCore
from legendary.models.game import Game 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.Components.Dialogs.InstallDialog import InstallInfoDialog
from Rare.utils.LegendaryApi import VerifyThread from Rare.utils.LegendaryApi import VerifyThread
@ -18,11 +22,13 @@ logger = getLogger("Download")
class DownloadThread(QThread): class DownloadThread(QThread):
status = pyqtSignal(str) 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__() super(DownloadThread, self).__init__()
self.dlm = dlm self.dlm = dlm
self.core = core self.core = core
self.status_queue = status_queue
self.igame = igame self.igame = igame
self.repair = repair self.repair = repair
self.repair_file = repair_file self.repair_file = repair_file
@ -32,9 +38,15 @@ class DownloadThread(QThread):
try: try:
self.dlm.start() 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() self.dlm.join()
except: except Exception as e:
logger.error(f"Installation failed after{time.time() - start_time:.02f} seconds.") logger.error(f"Installation failed after {time.time() - start_time:.02f} seconds: {e}")
self.status.emit("error") self.status.emit("error")
return return
@ -104,9 +116,9 @@ class DownloadTab(QWidget):
self.active_game: Game = None self.active_game: Game = None
self.installing_game = QLabel(self.tr("No active Download")) self.installing_game = QLabel(self.tr("No active Download"))
self.dl_speed = QLabel(self.tr("Download speed") + ": 0MB/s") self.dl_speed = QLabel()
self.cache_used = QLabel(self.tr("Cache used") + ": 0MB") self.cache_used = QLabel()
self.downloaded = QLabel(self.tr("Downloaded") + ": 0MB") self.downloaded = QLabel()
self.info_layout = QGridLayout() 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.dl_speed, 0, 1)
self.info_layout.addWidget(self.cache_used, 1, 0) self.info_layout.addWidget(self.cache_used, 1, 0)
self.info_layout.addWidget(self.downloaded, 1, 1) self.info_layout.addWidget(self.downloaded, 1, 1)
self.layout.addLayout(self.info_layout) self.layout.addLayout(self.info_layout)
self.prog_bar = QProgressBar() self.prog_bar = QProgressBar()
self.prog_bar.setMaximum(100)
self.layout.addWidget(self.prog_bar) self.layout.addWidget(self.prog_bar)
label = QLabel(
self.tr("<b>WARNING</b>: 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.installing_game_widget = QLabel(self.tr("No active Download"))
self.layout.addWidget(self.installing_game_widget) self.layout.addWidget(self.installing_game_widget)
@ -150,55 +157,39 @@ class DownloadTab(QWidget):
def install_game(self, options: InstallOptions): def install_game(self, options: InstallOptions):
game = self.core.get_game(options.app_name, update_meta=True) game = self.core.get_game(options.app_name, update_meta=True)
if self.core.is_installed(options.app_name): status_queue = MPQueue()
igame = self.core.get_installed_game(options.app_name) try:
if igame.needs_verification and not options.repair: dlm, analysis, game, igame, repair, repair_file = self.core.prepare_download(
options.repair = True app_name=options.app_name,
repair_file = None base_path=options.path,
if options.repair: force=False, # TODO allow overwrite
repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{options.app_name}.repair') no_install=options.download_only,
status_q=status_queue,
if not game: #max_shm=,
QMessageBox.warning(self, "Error", self.tr("Could not find Game in your library")) 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 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: if not analysis.dl_size:
QMessageBox.information(self, "Warning", self.tr("Download size is 0. Game already exists")) QMessageBox.information(self, "Warning", self.tr("Download size is 0. Game already exists"))
return return
@ -206,26 +197,50 @@ class DownloadTab(QWidget):
if not InstallInfoDialog(dl_size=analysis.dl_size, install_size=analysis.install_size).get_accept(): if not InstallInfoDialog(dl_size=analysis.dl_size, install_size=analysis.install_size).get_accept():
return 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.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.status.connect(self.status)
self.thread.statistics.connect(self.statistics)
self.thread.start() 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): def status(self, text):
if text == "dl_finished": if text == "dl_finished":
pass pass
@ -240,6 +255,12 @@ class DownloadTab(QWidget):
elif text == "error": elif text == "error":
QMessageBox.warning(self, "warn", "Download 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): def update_game(self, app_name: str):
print("Update ", app_name) print("Update ", app_name)
self.install_game(InstallOptions(app_name)) self.install_game(InstallOptions(app_name))

View file

@ -98,7 +98,6 @@ class GameInfo(QWidget):
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
LegendaryApi.uninstall(self.game.app_name, self.core) LegendaryApi.uninstall(self.game.app_name, self.core)
self.update_list.emit() self.update_list.emit()
self.back_button.click()
def repair(self): def repair(self):
repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{self.game.app_name}.repair') repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{self.game.app_name}.repair')

View file

@ -40,11 +40,14 @@ class GameSettings(QWidget):
self.layout.addWidget(self.linux_settings) self.layout.addWidget(self.linux_settings)
self.possible_proton_wrappers = [] self.possible_proton_wrappers = []
for i in os.listdir(os.path.expanduser("~/.steam/steam/steamapps/common")): try:
if i.startswith("Proton"): for i in os.listdir(os.path.expanduser("~/.steam/steam/steamapps/common")):
wrapper = '"' + os.path.join(os.path.expanduser("~/.steam/steam/steamapps/common"), i, if i.startswith("Proton"):
"proton") + '" run' wrapper = '"' + os.path.join(os.path.expanduser("~/.steam/steam/steamapps/common"), i,
self.possible_proton_wrappers.append(wrapper) "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 = QComboBox()
self.select_proton.addItems(["Don't use Proton"] + self.possible_proton_wrappers) self.select_proton.addItems(["Don't use Proton"] + self.possible_proton_wrappers)
self.select_proton.currentIndexChanged.connect(self.change_proton) self.select_proton.currentIndexChanged.connect(self.change_proton)

View file

@ -1,6 +1,6 @@
legendary-gl git+https://github.com/ChemicalXandco/legendary.git@c1db2b9#egg=legendary-gl
requests requests
Pillow Pillow
PyQt5 PyQt5
QtAwesome QtAwesome
notify-py notify-py

View file

@ -24,7 +24,7 @@ setuptools.setup(
"Operating System :: OS Independent" "Operating System :: OS Independent"
], ],
python_requires=">=3.8", python_requires=">=3.8",
entry_points=dict(console_scripts=["rare=Rare.Main:main"]), entry_points=dict(console_scripts=["rare=Rare.__main__:main"]),
install_requires=[ install_requires=[
"legendary-gl", "legendary-gl",
"requests<3.0", "requests<3.0",

2
start.sh Normal file
View file

@ -0,0 +1,2 @@
export PYTHONPATH=$PWD
python3 Rare