2021-02-18 05:46:03 +13:00
|
|
|
import os
|
2021-03-15 10:11:03 +13:00
|
|
|
import queue
|
2021-02-18 05:46:03 +13:00
|
|
|
import subprocess
|
|
|
|
import time
|
|
|
|
from logging import getLogger
|
2021-03-15 10:11:03 +13:00
|
|
|
from multiprocessing import Queue as MPQueue
|
2021-02-18 05:46:03 +13:00
|
|
|
|
2021-03-17 03:12:37 +13:00
|
|
|
from PyQt5.QtCore import QThread, pyqtSignal, Qt
|
|
|
|
from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton, QDialog, \
|
|
|
|
QListWidget, QHBoxLayout
|
2021-03-16 00:03:03 +13:00
|
|
|
|
2021-03-17 03:12:37 +13:00
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
from custom_legendary.utils.selective_dl import games
|
2021-02-18 05:46:03 +13:00
|
|
|
|
2021-02-18 22:22:15 +13:00
|
|
|
from Rare.Components.Dialogs.InstallDialog import InstallInfoDialog
|
2021-03-17 03:12:37 +13:00
|
|
|
from Rare.utils.Models import InstallOptions, KillDownloadException
|
2021-02-18 05:46:03 +13:00
|
|
|
|
|
|
|
logger = getLogger("Download")
|
|
|
|
|
|
|
|
|
|
|
|
class DownloadThread(QThread):
|
|
|
|
status = pyqtSignal(str)
|
2021-03-15 10:11:03 +13:00
|
|
|
statistics = pyqtSignal(UIUpdate)
|
2021-03-17 03:12:37 +13:00
|
|
|
kill = False
|
2021-02-18 05:46:03 +13:00
|
|
|
|
2021-03-17 03:12:37 +13:00
|
|
|
def __init__(self, dlm: DLManager, core: LegendaryCore, status_queue: MPQueue, igame, repair=False,
|
|
|
|
repair_file=None):
|
2021-02-18 05:46:03 +13:00
|
|
|
super(DownloadThread, self).__init__()
|
|
|
|
self.dlm = dlm
|
|
|
|
self.core = core
|
2021-03-15 10:11:03 +13:00
|
|
|
self.status_queue = status_queue
|
2021-02-18 05:46:03 +13:00
|
|
|
self.igame = igame
|
2021-03-10 04:52:51 +13:00
|
|
|
self.repair = repair
|
|
|
|
self.repair_file = repair_file
|
2021-02-18 05:46:03 +13:00
|
|
|
|
|
|
|
def run(self):
|
|
|
|
start_time = time.time()
|
|
|
|
try:
|
|
|
|
|
|
|
|
self.dlm.start()
|
2021-03-15 10:11:03 +13:00
|
|
|
time.sleep(1)
|
|
|
|
while self.dlm.is_alive():
|
2021-03-17 03:12:37 +13:00
|
|
|
if self.kill:
|
|
|
|
#raise KillDownloadException()
|
|
|
|
# TODO kill download queue, workers
|
|
|
|
pass
|
2021-03-15 10:11:03 +13:00
|
|
|
try:
|
2021-03-17 03:12:37 +13:00
|
|
|
self.statistics.emit(self.status_queue.get(timeout=1))
|
2021-03-15 10:11:03 +13:00
|
|
|
except queue.Empty:
|
|
|
|
pass
|
2021-03-17 03:12:37 +13:00
|
|
|
|
2021-02-18 05:46:03 +13:00
|
|
|
self.dlm.join()
|
2021-03-17 03:12:37 +13:00
|
|
|
|
|
|
|
except KillDownloadException:
|
|
|
|
self.status.emit("stop")
|
|
|
|
logger.info("Downlaod can be continued later")
|
|
|
|
self.dlm.kill()
|
|
|
|
return
|
|
|
|
|
2021-03-15 10:11:03 +13:00
|
|
|
except Exception as e:
|
|
|
|
logger.error(f"Installation failed after {time.time() - start_time:.02f} seconds: {e}")
|
2021-02-18 05:46:03 +13:00
|
|
|
self.status.emit("error")
|
|
|
|
return
|
|
|
|
|
|
|
|
else:
|
|
|
|
self.status.emit("dl_finished")
|
|
|
|
end_t = time.time()
|
2021-03-09 05:20:28 +13:00
|
|
|
|
2021-02-18 05:46:03 +13:00
|
|
|
game = self.core.get_game(self.igame.app_name)
|
|
|
|
postinstall = self.core.install_game(self.igame)
|
|
|
|
if postinstall:
|
|
|
|
self._handle_postinstall(postinstall, self.igame)
|
2021-03-10 04:52:51 +13:00
|
|
|
|
2021-02-18 05:46:03 +13:00
|
|
|
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}"')
|
2021-03-10 04:52:51 +13:00
|
|
|
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)
|
2021-02-18 05:46:03 +13:00
|
|
|
|
|
|
|
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.')
|
2021-02-10 23:48:25 +13:00
|
|
|
|
|
|
|
|
|
|
|
class DownloadTab(QWidget):
|
2021-02-18 05:46:03 +13:00
|
|
|
finished = pyqtSignal()
|
|
|
|
thread: QThread
|
|
|
|
|
2021-03-01 00:06:33 +13:00
|
|
|
def __init__(self, core: LegendaryCore, updates: list):
|
2021-02-10 23:48:25 +13:00
|
|
|
super(DownloadTab, self).__init__()
|
2021-02-18 05:46:03 +13:00
|
|
|
self.core = core
|
|
|
|
self.layout = QVBoxLayout()
|
2021-02-20 00:57:55 +13:00
|
|
|
self.active_game: Game = None
|
2021-02-18 05:46:03 +13:00
|
|
|
|
2021-03-12 00:56:38 +13:00
|
|
|
self.installing_game = QLabel(self.tr("No active Download"))
|
2021-03-15 10:11:03 +13:00
|
|
|
self.dl_speed = QLabel()
|
|
|
|
self.cache_used = QLabel()
|
|
|
|
self.downloaded = QLabel()
|
2021-02-18 05:46:03 +13:00
|
|
|
|
|
|
|
self.info_layout = QGridLayout()
|
|
|
|
|
|
|
|
self.info_layout.addWidget(self.installing_game, 0, 0)
|
|
|
|
self.info_layout.addWidget(self.dl_speed, 0, 1)
|
2021-02-27 07:28:54 +13:00
|
|
|
self.info_layout.addWidget(self.cache_used, 1, 0)
|
|
|
|
self.info_layout.addWidget(self.downloaded, 1, 1)
|
2021-02-18 05:46:03 +13:00
|
|
|
self.layout.addLayout(self.info_layout)
|
2021-03-15 10:11:03 +13:00
|
|
|
|
2021-03-17 03:12:37 +13:00
|
|
|
self.mini_layout = QHBoxLayout()
|
|
|
|
|
2021-02-18 05:46:03 +13:00
|
|
|
self.prog_bar = QProgressBar()
|
2021-03-15 10:11:03 +13:00
|
|
|
self.prog_bar.setMaximum(100)
|
2021-03-17 03:12:37 +13:00
|
|
|
self.mini_layout.addWidget(self.prog_bar)
|
|
|
|
|
|
|
|
self.kill_button = QPushButton(self.tr("Stop Download"))
|
|
|
|
# self.mini_layout.addWidget(self.kill_button)
|
|
|
|
self.kill_button.setDisabled(True)
|
|
|
|
self.kill_button.clicked.connect(self.stop_download)
|
|
|
|
|
|
|
|
self.layout.addLayout(self.mini_layout)
|
2021-02-18 05:46:03 +13:00
|
|
|
|
2021-03-01 08:01:15 +13:00
|
|
|
self.installing_game_widget = QLabel(self.tr("No active Download"))
|
2021-02-18 05:46:03 +13:00
|
|
|
self.layout.addWidget(self.installing_game_widget)
|
|
|
|
|
2021-03-12 00:56:38 +13:00
|
|
|
self.update_title = QLabel(f"<h2>{self.tr('Updates')}</h2>")
|
2021-03-01 00:06:33 +13:00
|
|
|
self.update_title.setStyleSheet("""
|
|
|
|
QLabel{
|
|
|
|
margin-top: 20px;
|
|
|
|
}
|
|
|
|
""")
|
|
|
|
self.layout.addWidget(self.update_title)
|
2021-03-17 09:25:07 +13:00
|
|
|
self.update_widgets = {}
|
2021-03-01 00:06:33 +13:00
|
|
|
if not updates:
|
2021-03-01 08:01:15 +13:00
|
|
|
self.update_text = QLabel(self.tr("No updates available"))
|
2021-03-01 00:06:33 +13:00
|
|
|
self.layout.addWidget(self.update_text)
|
|
|
|
else:
|
|
|
|
for i in updates:
|
|
|
|
widget = UpdateWidget(core, i)
|
2021-03-17 09:25:07 +13:00
|
|
|
self.update_widgets[i] = widget
|
2021-03-01 00:06:33 +13:00
|
|
|
self.layout.addWidget(widget)
|
|
|
|
widget.update.connect(self.update_game)
|
|
|
|
|
2021-02-18 05:46:03 +13:00
|
|
|
self.layout.addStretch(1)
|
|
|
|
|
|
|
|
self.setLayout(self.layout)
|
|
|
|
|
2021-03-17 03:12:37 +13:00
|
|
|
def stop_download(self):
|
|
|
|
self.thread.kill = True
|
|
|
|
|
2021-03-09 05:20:28 +13:00
|
|
|
def install_game(self, options: InstallOptions):
|
|
|
|
game = self.core.get_game(options.app_name, update_meta=True)
|
2021-03-15 10:11:03 +13:00
|
|
|
status_queue = MPQueue()
|
|
|
|
try:
|
|
|
|
dlm, analysis, game, igame, repair, repair_file = self.core.prepare_download(
|
|
|
|
app_name=options.app_name,
|
|
|
|
base_path=options.path,
|
2021-03-17 03:12:37 +13:00
|
|
|
force=False, # TODO allow overwrite
|
2021-03-15 10:11:03 +13:00
|
|
|
no_install=options.download_only,
|
|
|
|
status_q=status_queue,
|
2021-03-17 03:12:37 +13:00
|
|
|
# max_shm=,
|
2021-03-15 10:11:03 +13:00
|
|
|
max_workers=options.max_workers,
|
2021-03-17 03:12:37 +13:00
|
|
|
# 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=,
|
2021-03-15 10:11:03 +13:00
|
|
|
repair=options.repair,
|
2021-03-17 03:12:37 +13:00
|
|
|
# repair_use_latest=,
|
|
|
|
# ignore_space_req=,
|
|
|
|
# disable_delta=,
|
|
|
|
# override_delta_manifest=,
|
|
|
|
# reset_sdl=,
|
2021-03-15 10:11:03 +13:00
|
|
|
sdl_prompt=self.sdl_prompt)
|
|
|
|
except Exception as e:
|
|
|
|
QMessageBox.warning(self, self.tr("Error preparing download"),
|
|
|
|
str(e))
|
2021-03-09 05:20:28 +13:00
|
|
|
return
|
|
|
|
|
2021-02-18 05:46:03 +13:00
|
|
|
if not analysis.dl_size:
|
2021-03-01 08:01:15 +13:00
|
|
|
QMessageBox.information(self, "Warning", self.tr("Download size is 0. Game already exists"))
|
2021-02-18 05:46:03 +13:00
|
|
|
return
|
|
|
|
# Information
|
|
|
|
if not InstallInfoDialog(dl_size=analysis.dl_size, install_size=analysis.install_size).get_accept():
|
|
|
|
return
|
|
|
|
|
2021-02-20 00:57:55 +13:00
|
|
|
self.active_game = game
|
2021-03-15 10:11:03 +13:00
|
|
|
self.thread = DownloadThread(dlm, self.core, status_queue, igame, options.repair, repair_file)
|
2021-02-18 05:46:03 +13:00
|
|
|
self.thread.status.connect(self.status)
|
2021-03-15 10:11:03 +13:00
|
|
|
self.thread.statistics.connect(self.statistics)
|
2021-02-18 05:46:03 +13:00
|
|
|
self.thread.start()
|
2021-03-17 03:12:37 +13:00
|
|
|
self.kill_button.setDisabled(False)
|
2021-03-16 04:20:07 +13:00
|
|
|
self.installing_game.setText("Installing Game: " + self.active_game.app_title)
|
2021-02-18 05:46:03 +13:00
|
|
|
|
2021-03-17 09:25:07 +13:00
|
|
|
for i in self.update_widgets.values():
|
|
|
|
i.update_button.setDisabled(True)
|
|
|
|
|
2021-03-15 10:11:03 +13:00
|
|
|
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
|
2021-03-17 03:12:37 +13:00
|
|
|
pack_list.addItems([tag + ': ' + info['name'] for tag, info in games[app_name].items() if tag != '__required'])
|
2021-03-15 10:11:03 +13:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2021-02-18 05:46:03 +13:00
|
|
|
def status(self, text):
|
|
|
|
if text == "dl_finished":
|
|
|
|
pass
|
|
|
|
elif text == "finish":
|
2021-03-16 00:03:03 +13:00
|
|
|
try:
|
|
|
|
from notifypy import Notify
|
|
|
|
except ModuleNotFoundError:
|
|
|
|
logger.warning("No Notification Module found")
|
|
|
|
else:
|
|
|
|
notification = Notify()
|
|
|
|
notification.title = self.tr("Installation finished")
|
|
|
|
notification.message = self.tr("Download of game ") + self.active_game.app_title
|
|
|
|
notification.send()
|
2021-02-27 07:28:54 +13:00
|
|
|
# QMessageBox.information(self, "Info", "Download finished")
|
2021-03-17 09:25:07 +13:00
|
|
|
self.update_widgets[self.active_game.app_name].setVisible(False)
|
|
|
|
self.update_widgets.pop(self.active_game.app_name)
|
|
|
|
|
|
|
|
for i in self.update_widgets.values():
|
|
|
|
i.update_button.setDisabled(False)
|
|
|
|
|
2021-02-18 05:46:03 +13:00
|
|
|
self.finished.emit()
|
2021-03-01 08:01:15 +13:00
|
|
|
self.installing_game.setText(self.tr("Installing Game: No active download"))
|
2021-03-16 04:20:07 +13:00
|
|
|
self.prog_bar.setValue(0)
|
2021-03-17 09:25:07 +13:00
|
|
|
|
2021-03-16 04:20:07 +13:00
|
|
|
self.dl_speed.setText("")
|
|
|
|
self.cache_used.setText("")
|
|
|
|
self.downloaded.setText("")
|
2021-02-18 05:46:03 +13:00
|
|
|
elif text == "error":
|
|
|
|
QMessageBox.warning(self, "warn", "Download error")
|
|
|
|
|
2021-03-17 03:12:37 +13:00
|
|
|
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("")
|
|
|
|
|
2021-03-15 10:11:03 +13:00
|
|
|
def statistics(self, ui_update: UIUpdate):
|
|
|
|
self.prog_bar.setValue(ui_update.progress)
|
2021-03-17 03:12:37 +13:00
|
|
|
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")
|
2021-03-15 10:11:03 +13:00
|
|
|
|
2021-02-18 05:46:03 +13:00
|
|
|
def update_game(self, app_name: str):
|
|
|
|
print("Update ", app_name)
|
2021-03-09 05:20:28 +13:00
|
|
|
self.install_game(InstallOptions(app_name))
|
|
|
|
|
|
|
|
def repair(self):
|
|
|
|
pass
|
2021-03-01 00:06:33 +13:00
|
|
|
|
|
|
|
|
|
|
|
class UpdateWidget(QWidget):
|
|
|
|
update = pyqtSignal(str)
|
|
|
|
|
|
|
|
def __init__(self, core: LegendaryCore, app_name):
|
|
|
|
super(UpdateWidget, self).__init__()
|
|
|
|
self.core = core
|
|
|
|
self.game = core.get_installed_game(app_name)
|
|
|
|
|
|
|
|
self.layout = QVBoxLayout()
|
|
|
|
self.title = QLabel(self.game.title)
|
|
|
|
self.layout.addWidget(self.title)
|
|
|
|
|
2021-03-01 08:01:15 +13:00
|
|
|
self.update_button = QPushButton(self.tr("Update Game"))
|
|
|
|
self.update_button.clicked.connect(lambda: self.update.emit(app_name))
|
2021-03-01 00:06:33 +13:00
|
|
|
self.layout.addWidget(self.update_button)
|
|
|
|
|
|
|
|
self.setLayout(self.layout)
|