From b0ec5c5fcbf8b7b35ac33bad728e4c310e64e67a Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Sun, 23 May 2021 15:15:36 +0300 Subject: [PATCH] Move all download preparations inside InstallDialog. InstallDialog now returns a InstallQueueItemModel ready to be downloaded or queued. Renamed a few model attributes to match legendary's names. InstallDialog can be run silently for auto-downloads. --- rare/components/dialogs/install_dialog.py | 123 +++++++++++------- rare/components/tab_widget.py | 11 +- rare/components/tabs/downloads/__init__.py | 55 +------- .../tabs/downloads/download_thread.py | 6 +- rare/utils/models.py | 13 +- 5 files changed, 100 insertions(+), 108 deletions(-) diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py index db3f715c..8033b610 100644 --- a/rare/components/dialogs/install_dialog.py +++ b/rare/components/dialogs/install_dialog.py @@ -1,4 +1,5 @@ import os +from multiprocessing import Queue as MPQueue from PyQt5.QtCore import Qt, QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import QDialog, QFileDialog, QCheckBox @@ -7,21 +8,22 @@ from custom_legendary.core import LegendaryCore from custom_legendary.utils.selective_dl import games from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog from rare.utils.extra_widgets import PathEdit -from rare.utils.models import InstallOptionsModel +from rare.utils.models import InstallDownloadModel, InstallQueueItemModel from rare.utils.utils import get_size class InstallDialog(QDialog, Ui_InstallDialog): - options = False - def __init__(self, app_name, core: LegendaryCore, update=False, parent=None): + def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel, update=False, parent=None): super(InstallDialog, self).__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setupUi(self) self.core = core - self.app_name = app_name - self.game = self.core.get_game(app_name) + self.dl_item = dl_item + self.dl_item.status_q = MPQueue() + self.app_name = self.dl_item.options.app_name + self.game = self.core.get_game(self.app_name) self.update_game = update self.threadpool = QThreadPool(self) @@ -54,12 +56,11 @@ class InstallDialog(QDialog, Ui_InstallDialog): self.max_workers_spin.setValue(int(max_workers)) self.sdl_list_checks = list() - self.tags = [''] try: - for key, info in games[app_name].items(): + for key, info in games[self.app_name].items(): cb = QDataCheckBox(info['name'], info['tags']) if key == '__required': - self.tags.extend(info['tags']) + self.dl_item.options.sdl_list.extend(info['tags']) cb.setChecked(True) cb.setDisabled(True) self.sdl_list_layout.addWidget(cb) @@ -71,7 +72,8 @@ class InstallDialog(QDialog, Ui_InstallDialog): self.sdl_list_frame.setVisible(False) self.sdl_list_label.setVisible(False) - self.get_install_info(app_name, default_path, self.tags) + self.get_options() + self.get_download_info() self.install_button.clicked.connect(self.on_install_button_clicked) self.cancel_button.clicked.connect(self.on_cancel_button_clicked) @@ -79,13 +81,27 @@ class InstallDialog(QDialog, Ui_InstallDialog): self.resize(self.minimumSize()) self.setFixedSize(self.size()) - def get_install_options(self, path=None): + def get_options(self): + self.dl_item.options.path = self.install_dir_edit.text() if not self.update_game else None + self.dl_item.options.max_workers = self.max_workers_spin.value() + self.dl_item.options.force = self.force_download_check.isChecked() + self.dl_item.options.ignore_space_req = self.ignore_space_check.isChecked() + self.dl_item.options.dl_only = self.download_only_check.isChecked() + self.dl_item.options.sdl_list = [''] + for cb in self.sdl_list_checks: + if data := cb.isChecked(): + self.dl_item.options.sdl_list.extend(data) + + def get_download_item(self, path=None, silent=False): if path: self.install_dir_edit.setText(path) - self.exec_() - return self.options + if silent: + self.threadpool.waitForDone() + else: + self.exec_() + return self.dl_item - def get_install_info(self, app_name, path, tags): + def get_download_info(self): message = self.tr("Updating...") self.download_size_info_label.setText(message) self.download_size_info_label.setStyleSheet("font-style: italic; font-weight: normal") @@ -93,44 +109,36 @@ class InstallDialog(QDialog, Ui_InstallDialog): self.install_size_info_label.setStyleSheet("font-style: italic; font-weight: normal") self.install_button.setEnabled(False) self.sdl_list_frame.setEnabled(False) - info_worker = InstallInfoWorker(app_name, path, tags, self.core) + info_worker = InstallInfoWorker(self.core, self.dl_item) info_worker.setAutoDelete(True) info_worker.signals.finished.connect(self.on_worker_finished) self.threadpool.start(info_worker) def on_sdl_checkbox_changed(self): - self.tags = [''] - for cb in self.sdl_list_checks: - if data := cb.isChecked(): - self.tags.extend(data) - self.get_install_info(self.app_name, self.install_dir_edit.text(), self.tags) + self.get_options() + self.get_download_info() - def on_install_dir_text_changed(self, path: str): - self.get_install_info(self.app_name, path, self.tags) + def on_install_dir_text_changed(self): + self.get_options() + self.get_download_info() def on_install_button_clicked(self): - self.options = InstallOptionsModel( - app_name=self.app_name, - path=self.install_dir_edit.text() if not self.update_game else None, - max_workers=self.max_workers_spin.value(), - force=self.force_download_check.isChecked(), - ignore_free_space=self.ignore_space_check.isChecked(), - dl_only=self.download_only_check.isChecked(), - sdl_list=self.tags - ) self.threadpool.clear() self.close() def on_cancel_button_clicked(self): + self.dl_item = False self.threadpool.clear() self.close() - def on_worker_finished(self, info: tuple): - download_size, install_size = info + def on_worker_finished(self, dl_item: InstallQueueItemModel): # TODO: Check available size and act accordingly # TODO: (show message in label | color it | disable install unless ignore) # TODO: Find a way to get the installation size delta and show it - if download_size is not None and install_size is not None: + if dl_item: + self.dl_item = dl_item + download_size = self.dl_item.download.analysis.dl_size + install_size = self.dl_item.download.analysis.install_size if download_size: self.download_size_info_label.setText("{}".format(get_size(download_size))) self.download_size_info_label.setStyleSheet("font-style: normal; font-weight: bold") @@ -147,37 +155,58 @@ class InstallDialog(QDialog, Ui_InstallDialog): class InstallInfoWorkerSignals(QObject): - finished = pyqtSignal(tuple) + finished = pyqtSignal(InstallQueueItemModel) class InstallInfoWorker(QRunnable): - def __init__(self, app_name, path, tags, core: LegendaryCore): + def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel): super(InstallInfoWorker, self).__init__() self.signals = InstallInfoWorkerSignals() self.core = core - self.app_name = app_name - self.path = path - self.tags = tags + self.dl_item = dl_item @pyqtSlot() def run(self): try: - dlm, analysis, game, igame, repair, repair_file = self.core.prepare_download( - app_name=self.app_name, - base_path=self.path, - sdl_prompt=lambda app_name, title: self.tags - ) - self.signals.finished.emit((analysis.dl_size, analysis.install_size)) + download = InstallDownloadModel(*self.core.prepare_download( + app_name=self.dl_item.options.app_name, + base_path=self.dl_item.options.path, + force=self.dl_item.options.force, + no_install=self.dl_item.options.dl_only, + status_q=self.dl_item.status_q, + # max_shm=, + max_workers=self.dl_item.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=self.dl_item.options.repair, + # repair_use_latest=, + ignore_space_req=self.dl_item.options.ignore_space_req, + # disable_delta=, + # override_delta_manifest=, + # reset_sdl=, + sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list + )) + self.dl_item.download = download except: - self.signals.finished.emit((None, None)) + self.dl_item.download = None + self.signals.finished.emit(self.dl_item) return class QDataCheckBox(QCheckBox): - def __init__(self, text, data=None): - super(QDataCheckBox, self).__init__() + def __init__(self, text, data=None, parent=None): + super(QDataCheckBox, self).__init__(parent) self.setText(text) self.data = data diff --git a/rare/components/tab_widget.py b/rare/components/tab_widget.py index e5931e76..3f4851c4 100644 --- a/rare/components/tab_widget.py +++ b/rare/components/tab_widget.py @@ -14,7 +14,7 @@ from rare.components.tabs.downloads import DownloadTab from rare.components.tabs.games import GameTab from rare.components.tabs.settings import SettingsTab from rare.utils import legendary_utils -from rare.utils.models import InstallOptionsModel +from rare.utils.models import InstallQueueItemModel, InstallOptionsModel class TabWidget(QTabWidget): @@ -96,11 +96,12 @@ class TabWidget(QTabWidget): self.setIconSize(QSize(25, 25)) def install_game(self, app_name, disable_path=False): - dialog = InstallDialog(app_name, self.core, disable_path, parent=self) - options = dialog.get_install_options() - if options: + download_item = InstallQueueItemModel(options=InstallOptionsModel(app_name=app_name)) + dialog = InstallDialog(self.core, download_item, update=disable_path, parent=self) + download_item = dialog.get_download_item() + if download_item: self.setCurrentIndex(1) - self.start_download(options) + self.start_download(download_item) def start_download(self, options): downloads = len(self.downloadTab.dl_queue) + len(self.downloadTab.update_widgets.keys()) + 1 diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py index 415fc12a..266d0cf2 100644 --- a/rare/components/tabs/downloads/__init__.py +++ b/rare/components/tabs/downloads/__init__.py @@ -1,6 +1,5 @@ import datetime from logging import getLogger -from multiprocessing import Queue as MPQueue from PyQt5.QtCore import QThread, pyqtSignal, QSettings from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton, \ @@ -12,7 +11,7 @@ from custom_legendary.models.game import Game, InstalledGame from rare.components.dialogs.install_dialog import InstallDialog from rare.components.tabs.downloads.dl_queue_widget import DlQueueWidget from rare.components.tabs.downloads.download_thread import DownloadThread -from rare.utils.models import InstallOptionsModel, InstallDownloadModel, InstallQueueItemModel +from rare.utils.models import InstallOptionsModel, InstallQueueItemModel from rare.utils.utils import get_size logger = getLogger("Download") @@ -96,44 +95,7 @@ class DownloadTab(QWidget): def stop_download(self): self.thread.kill() - def install_game(self, options: InstallOptionsModel): - - status_queue = MPQueue() - try: - download = InstallDownloadModel(*self.core.prepare_download( - app_name=options.app_name, - base_path=options.path, - force=options.force, - no_install=options.dl_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=options.ignore_free_space, - # disable_delta=, - # override_delta_manifest=, - # reset_sdl=, - sdl_prompt=lambda app_name, title: options.sdl_list - )) - except Exception as e: - QMessageBox.warning(self, self.tr("Error preparing download"), - str(e)) - return - - queue_item = InstallQueueItemModel(status_queue, download, options) - + def install_game(self, queue_item: InstallQueueItemModel): if self.active_game is None: self.start_installation(queue_item) else: @@ -231,14 +193,11 @@ class DownloadTab(QWidget): def update_game(self, app_name: str, auto=False): logger.info("Update " + app_name) - if not auto: - dialog = InstallDialog(app_name, self.core, update=True, parent=self) - options = dialog.get_install_options() - else: - self.install_game(InstallOptionsModel(app_name=app_name)) - return - if options: - self.install_game(options) + download_item = InstallQueueItemModel(options=InstallOptionsModel(app_name=app_name)) + dialog = InstallDialog(self.core, download_item, update=True, parent=self) + download_item = dialog.get_download_item(silent=auto) + if download_item: + self.install_game(download_item) else: self.update_widgets[app_name].update_button.setDisabled(False) self.update_widgets[app_name].update_with_settings.setDisabled(False) diff --git a/rare/components/tabs/downloads/download_thread.py b/rare/components/tabs/downloads/download_thread.py index 94c4f7c8..e3b6b5aa 100644 --- a/rare/components/tabs/downloads/download_thread.py +++ b/rare/components/tabs/downloads/download_thread.py @@ -23,10 +23,10 @@ class DownloadThread(QThread): def __init__(self, core: LegendaryCore, queue_item: InstallQueueItemModel): super(DownloadThread, self).__init__() - self.dlm = queue_item.download.dlmanager self.core = core + self.dlm = queue_item.download.dlmanager self.dl_only = queue_item.options.dl_only - self.status_queue = queue_item.queue + self.status_q = queue_item.status_q self.igame = queue_item.download.igame self.repair = queue_item.download.repair self.repair_file = queue_item.download.repair_file @@ -103,7 +103,7 @@ class DownloadThread(QThread): dl_stopped = True try: if not dl_stopped: - self.statistics.emit(self.status_queue.get(timeout=1)) + self.statistics.emit(self.status_q.get(timeout=1)) except queue.Empty: pass diff --git a/rare/utils/models.py b/rare/utils/models.py index 5dd814d5..7d752f7f 100644 --- a/rare/utils/models.py +++ b/rare/utils/models.py @@ -4,20 +4,20 @@ import os class InstallOptionsModel: def __init__(self, app_name: str, path: str = os.path.expanduser("~/legendary"), max_workers: int = os.cpu_count() * 2, repair: bool = False, dl_only: bool = False, - ignore_free_space: bool = False, force: bool = False, sdl_list: list = [''] + ignore_space_req: bool = False, force: bool = False, sdl_list: list = [''] ): self.app_name = app_name self.path = path self.max_workers = max_workers self.repair = repair self.dl_only = dl_only - self.ignore_free_space = ignore_free_space + self.ignore_space_req = ignore_space_req self.force = force self.sdl_list = sdl_list class InstallDownloadModel: - def __init__(self, dlmanager, analysis, game, igame, repair: bool = False, repair_file: str = None): + def __init__(self, dlmanager, analysis, game, igame, repair: bool, repair_file: str): self.dlmanager = dlmanager self.analysis = analysis self.game = game @@ -27,7 +27,10 @@ class InstallDownloadModel: class InstallQueueItemModel: - def __init__(self, queue, download: InstallDownloadModel, options: InstallOptionsModel): - self.queue = queue + def __init__(self, status_q=None, download: InstallDownloadModel = None, options: InstallOptionsModel = None): + self.status_q = status_q self.download = download self.options = options + + def __bool__(self): + return (self.status_q is not None) and (self.download is not None) and (self.options is not None)