1
0
Fork 0
mirror of synced 2024-06-09 22:24:40 +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__
/.idea
/.vscode
/build
/dist
/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
- 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

View file

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

View file

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

View file

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

View file

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

View file

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

2
start.sh Normal file
View file

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