Merge pull request #19 from ChemicalXandco/download_progress
Download progress
This commit is contained in:
commit
7e0627bbe1
33 changed files with 115 additions and 90 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@
|
|||
__pycache__
|
||||
|
||||
/.idea
|
||||
/.vscode
|
||||
/build
|
||||
/dist
|
||||
/Rare.egg-info/
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -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))
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,4 +1,4 @@
|
|||
legendary-gl
|
||||
git+https://github.com/ChemicalXandco/legendary.git@c1db2b9#egg=legendary-gl
|
||||
requests
|
||||
Pillow
|
||||
PyQt5
|
||||
|
|
2
setup.py
2
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",
|
||||
|
|
2
start.sh
Normal file
2
start.sh
Normal file
|
@ -0,0 +1,2 @@
|
|||
export PYTHONPATH=$PWD
|
||||
python3 Rare
|
Loading…
Reference in a new issue