1
0
Fork 0
mirror of synced 2024-06-23 08:40:45 +12:00

QueueWidget: Prepare requeued downloads after adding it.

By preparing the download inside the widget, the delay after stopping
the running download and visual feedback of adding the widget is
reduced. The widget will now appear containing the basic information
and will be populated with the information about the download
when it is ready. The widget is disabled in the meantime.

Move `InstallInfoWorker` to `rare.shared.workers` module and
revert it to emitting a `InstallDownloadItem` model only
instead of a `InstallQueueItemModel.`
This commit is contained in:
loathingKernel 2023-01-26 22:03:38 +02:00
parent 0830c17ee5
commit 523391d166
5 changed files with 159 additions and 124 deletions

View file

@ -1,24 +1,17 @@
import os
import platform as pf
import sys
from typing import Tuple, List, Union, Optional
from PyQt5.QtCore import QObject, QRunnable, pyqtSignal, pyqtSlot
from PyQt5.QtCore import Qt, QThreadPool, QSettings
from PyQt5.QtCore import pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QKeyEvent
from PyQt5.QtWidgets import QDialog, QFileDialog, QCheckBox, QLayout, QWidget, QVBoxLayout, QApplication
from legendary.lfs.eos import EOSOverlayApp
from legendary.models.downloading import ConditionCheckResult
from legendary.utils.selective_dl import get_sdl_appname
from rare.lgndr.cli import LegendaryCLI
from rare.lgndr.core import LegendaryCore
from rare.lgndr.glue.arguments import LgndrInstallGameArgs
from rare.lgndr.glue.exception import LgndrException
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
from rare.models.game import RareGame
from rare.models.install import InstallDownloadModel, InstallQueueItemModel, InstallOptionsModel
from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton
from rare.shared.workers.install_info import InstallInfoWorker
from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
from rare.utils import config_helper
@ -291,11 +284,11 @@ class InstallDialog(QDialog):
self.reject_close = False
self.close()
@pyqtSlot(InstallQueueItemModel)
def on_worker_result(self, dl_item: InstallQueueItemModel):
self.__download = dl_item.download
download_size = dl_item.download.analysis.dl_size
install_size = dl_item.download.analysis.install_size
@pyqtSlot(InstallDownloadModel)
def on_worker_result(self, download: InstallDownloadModel):
self.__download = download
download_size = download.analysis.dl_size
install_size = download.analysis.install_size
# install_size = self.dl_item.download.analysis.disk_space_delta
if download_size:
self.ui.download_size_text.setText(get_size(download_size))
@ -309,12 +302,12 @@ class InstallDialog(QDialog):
self.ui.verify_button.setEnabled(self.options_changed)
self.ui.cancel_button.setEnabled(True)
if pf.system() == "Windows" or ArgumentsSingleton().debug:
if dl_item.download.igame.prereq_info and not dl_item.download.igame.prereq_info.get("installed", False):
if download.igame.prereq_info and not download.igame.prereq_info.get("installed", False):
self.advanced.ui.install_prereqs_check.setEnabled(True)
self.advanced.ui.install_prereqs_label.setEnabled(True)
self.advanced.ui.install_prereqs_check.setChecked(True)
prereq_name = dl_item.download.igame.prereq_info.get("name", "")
prereq_path = os.path.split(dl_item.download.igame.prereq_info.get("path", ""))[-1]
prereq_name = download.igame.prereq_info.get("name", "")
prereq_path = os.path.split(download.igame.prereq_info.get("path", ""))[-1]
prereq_desc = prereq_name if prereq_name else prereq_path
self.advanced.ui.install_prereqs_check.setText(
self.tr("Also install: {}").format(prereq_desc)
@ -358,62 +351,6 @@ class InstallDialog(QDialog):
self.cancel_clicked()
class InstallInfoWorker(QRunnable):
class Signals(QObject):
result = pyqtSignal(InstallQueueItemModel)
failed = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, core: LegendaryCore, options: InstallOptionsModel):
sys.excepthook = sys.__excepthook__
super(InstallInfoWorker, self).__init__()
self.setAutoDelete(True)
self.signals = InstallInfoWorker.Signals()
self.core = core
self.options = options
@pyqtSlot()
def run(self):
try:
if not self.options.overlay:
cli = LegendaryCLI(self.core)
status = LgndrIndirectStatus()
result = cli.install_game(
LgndrInstallGameArgs(**self.options.as_install_kwargs(), indirect_status=status)
)
if result:
download = InstallDownloadModel(*result)
else:
raise LgndrException(status.message)
else:
if not os.path.exists(path := self.options.base_path):
os.makedirs(path)
dlm, analysis, igame = self.core.prepare_overlay_install(
path=self.options.base_path
)
download = InstallDownloadModel(
dlm=dlm,
analysis=analysis,
igame=igame,
game=EOSOverlayApp,
repair=False,
repair_file="",
res=ConditionCheckResult(), # empty
)
if not download.res or not download.res.failures:
self.signals.result.emit(InstallQueueItemModel(options=self.options, download=download))
else:
self.signals.failed.emit("\n".join(str(i) for i in download.res.failures))
except LgndrException as ret:
self.signals.failed.emit(ret.message)
except Exception as e:
self.signals.failed.emit(str(e))
self.signals.finished.emit()
class TagCheckBox(QCheckBox):
def __init__(self, text, tags: List[str], parent=None):
super(TagCheckBox, self).__init__(parent)

View file

@ -9,7 +9,7 @@ from PyQt5.QtWidgets import (
QMessageBox,
)
from rare.components.dialogs.install_dialog import InstallDialog, InstallInfoWorker
from rare.components.dialogs.install_dialog import InstallDialog
from rare.components.dialogs.uninstall_dialog import UninstallDialog
from rare.lgndr.models.downloading import UIUpdate
from rare.models.game import RareGame
@ -68,11 +68,7 @@ class DownloadsTab(QWidget):
self.__reset_download()
self.__forced_item: Optional[InstallQueueItemModel] = None
self.__omit_queue = False
def __check_updates(self):
for rgame in self.rcore.updates:
self.__add_update(rgame)
self.__omit_requeue = False
@pyqtSlot()
@pyqtSlot(int)
@ -80,6 +76,14 @@ class DownloadsTab(QWidget):
count = self.updates_group.count() + self.queue_group.count()
self.update_title.emit(count)
@property
def is_download_active(self):
return self.thread is not None
def __check_updates(self):
for rgame in self.rcore.updates:
self.__add_update(rgame)
@pyqtSlot(str)
@pyqtSlot(RareGame)
def __add_update(self, update: Union[str,RareGame]):
@ -140,7 +144,7 @@ class DownloadsTab(QWidget):
# `self.on_exit` control whether we try to add the download
# back in the queue. If we are on exit we wait for the thread
# to finish, we do not care about handling the result really
self.__omit_queue = omit_queue
self.__omit_requeue = omit_queue
if omit_queue:
self.thread.wait()
@ -154,10 +158,6 @@ class DownloadsTab(QWidget):
self.ui.kill_button.setDisabled(False)
self.ui.dl_name.setText(item.download.game.app_title)
@property
def is_download_active(self):
return self.thread is not None
@pyqtSlot(UIUpdate, c_ulonglong)
def __on_download_progress(self, ui_update: UIUpdate, dl_size: c_ulonglong):
self.ui.progress_bar.setValue(int(ui_update.progress))
@ -170,19 +170,10 @@ class DownloadsTab(QWidget):
)
self.ui.time_left.setText(get_time(ui_update.estimated_time_left))
@pyqtSlot(InstallQueueItemModel)
def __on_info_worker_result(self, item: InstallQueueItemModel):
def __requeue_download(self, item: InstallQueueItemModel):
rgame = self.rcore.get_game(item.options.app_name)
self.queue_group.push_front(item, rgame.igame)
logger.info(f"Re-queued download for {item.download.game.app_name} ({item.download.game.app_title})")
@pyqtSlot(str)
def __on_info_worker_failed(self, message: str):
logger.error(f"Failed to re-queue stopped download with error: {message}")
@pyqtSlot()
def __on_info_worker_finished(self):
logger.info("Download re-queue worker finished")
logger.info(f"Re-queued download for {rgame.app_name} ({rgame.app_title})")
@pyqtSlot(DlResultModel)
def __on_download_result(self, result: DlResultModel):
@ -211,12 +202,8 @@ class DownloadsTab(QWidget):
elif result.code == DlResultCode.STOPPED:
logger.info(f"Download stopped: {result.item.download.game.app_title}")
if not self.__omit_queue:
worker = InstallInfoWorker(self.core, result.item.options)
worker.signals.result.connect(self.__on_info_worker_result)
worker.signals.failed.connect(self.__on_info_worker_failed)
worker.signals.finished.connect(self.__on_info_worker_finished)
self.threadpool.start(worker)
if not self.__omit_requeue:
self.__requeue_download(InstallQueueItemModel(options=result.item.options))
else:
return

View file

@ -1,7 +1,7 @@
from collections import deque
from enum import IntEnum
from logging import getLogger
from typing import Optional, List, Deque
from typing import Optional, Deque, Union
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
from PyQt5.QtWidgets import (
@ -125,7 +125,7 @@ class QueueGroup(QGroupBox):
def __create_widget(self, item: InstallQueueItemModel, old_igame: InstalledGame) -> QueueWidget:
widget: QueueWidget = QueueWidget(item, old_igame, parent=self.__container)
widget.toggle_arrows(self.__queue.index(item.download.game.app_name), len(self.__queue))
widget.toggle_arrows(self.__queue.index(item.options.app_name), len(self.__queue))
widget.destroyed.connect(self.__update_group)
widget.remove.connect(self.remove)
widget.force.connect(self.__on_force)
@ -136,7 +136,7 @@ class QueueGroup(QGroupBox):
def push_front(self, item: InstallQueueItemModel, old_igame: InstalledGame):
self.__text.setVisible(False)
self.__container.setVisible(True)
self.__queue.appendleft(item.download.game.app_name)
self.__queue.appendleft(item.options.app_name)
widget = self.__create_widget(item, old_igame)
self.__container.layout().insertWidget(0, widget)
if self.count() > 1:

View file

@ -1,24 +1,28 @@
from logging import getLogger
from typing import Optional
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtCore import pyqtSignal, Qt, QThreadPool, pyqtSlot
from PyQt5.QtWidgets import QWidget, QFrame
from legendary.models.downloading import AnalysisResult
from legendary.models.game import Game, InstalledGame
from qtawesome import icon
from rare.models.install import InstallQueueItemModel, InstallOptionsModel
from rare.shared import ImageManagerSingleton
from rare.models.install import InstallQueueItemModel, InstallOptionsModel, InstallDownloadModel
from rare.shared import RareCore, ImageManagerSingleton
from rare.shared.workers.install_info import InstallInfoWorker
from rare.ui.components.tabs.downloads.download_widget import Ui_DownloadWidget
from rare.ui.components.tabs.downloads.info_widget import Ui_InfoWidget
from rare.utils.misc import get_size, widget_object_name, elide_text
from rare.widgets.image_widget import ImageWidget, ImageSize
logger = getLogger("DownloadWidgets")
class InfoWidget(QWidget):
def __init__(
self,
game: Game,
igame: InstalledGame,
game: Optional[Game],
igame: Optional[InstalledGame],
analysis: Optional[AnalysisResult] = None,
old_igame: Optional[InstalledGame] = None,
parent=None,
@ -29,6 +33,23 @@ class InfoWidget(QWidget):
self.image_manager = ImageManagerSingleton()
self.image = ImageWidget(self)
self.image.setFixedSize(ImageSize.Icon)
self.ui.image_layout.addWidget(self.image)
self.ui.info_widget_layout.setAlignment(Qt.AlignTop)
if game and igame:
self.update_information(game, igame, analysis, old_igame)
elif old_igame:
self.ui.title.setText(old_igame.title)
self.ui.remote_version.setText("...")
self.ui.local_version.setText("...")
self.ui.dl_size.setText("...")
self.ui.install_size.setText("...")
self.image.setPixmap(self.image_manager.get_pixmap(old_igame.app_name, color=True))
def update_information(self, game, igame, analysis, old_igame):
self.ui.title.setText(game.app_title)
self.ui.remote_version.setText(
elide_text(self.ui.remote_version, old_igame.version if old_igame else game.app_version(igame.platform))
@ -36,13 +57,7 @@ class InfoWidget(QWidget):
self.ui.local_version.setText(elide_text(self.ui.local_version, igame.version))
self.ui.dl_size.setText(get_size(analysis.dl_size) if analysis else "")
self.ui.install_size.setText(get_size(analysis.install_size) if analysis else "")
self.image = ImageWidget(self)
self.image.setFixedSize(ImageSize.Icon)
self.image.setPixmap(self.image_manager.get_pixmap(game.app_name, color=True))
self.ui.image_layout.addWidget(self.image)
self.ui.info_widget_layout.setAlignment(Qt.AlignTop)
class UpdateWidget(QFrame):
@ -98,28 +113,55 @@ class QueueWidget(QFrame):
# lk: setObjectName has to be after `setupUi` because it is also set in that function
self.setObjectName(widget_object_name(self, item.options.app_name))
self.item = item
self.threadpool = QThreadPool.globalInstance()
if not item:
self.ui.queue_buttons.setEnabled(False)
worker = InstallInfoWorker(RareCore.instance().core(), item.options)
worker.signals.result.connect(self.add_info_widget)
worker.signals.failed.connect(self.__on_info_worker_failed)
worker.signals.finished.connect(self.__on_info_worker_finished)
self.threadpool.start(worker)
self.info_widget = InfoWidget(None, None, None, old_igame, parent=self)
else:
self.info_widget = InfoWidget(
item.download.game, item.download.igame, item.download.analysis, old_igame, parent=self
)
self.ui.info_layout.addWidget(self.info_widget)
self.ui.update_buttons.setVisible(False)
self.old_igame = old_igame
self.item = item
self.ui.move_up_button.setIcon(icon("fa.arrow-up"))
self.ui.move_up_button.clicked.connect(
lambda: self.move_up.emit(self.item.download.game.app_name)
lambda: self.move_up.emit(self.item.options.app_name)
)
self.ui.move_down_button.setIcon(icon("fa.arrow-down"))
self.ui.move_down_button.clicked.connect(
lambda: self.move_down.emit(self.item.download.game.app_name)
lambda: self.move_down.emit(self.item.options.app_name)
)
self.info_widget = InfoWidget(
item.download.game, item.download.igame, item.download.analysis, old_igame, parent=self
)
self.ui.info_layout.addWidget(self.info_widget)
self.ui.remove_button.clicked.connect(lambda: self.remove.emit(self.item.download.game.app_name))
self.ui.remove_button.clicked.connect(lambda: self.remove.emit(self.item.options.app_name))
self.ui.force_button.clicked.connect(lambda: self.force.emit(self.item))
@pyqtSlot(str)
def __on_info_worker_failed(self, message: str):
logger.error(f"Failed to requeue download for {self.item.options.app_name} with error: {message}")
self.remove.emit(self.item.options.app_name)
@pyqtSlot()
def __on_info_worker_finished(self):
logger.info(f"Download requeue worker finished for {self.item.options.app_name}")
@pyqtSlot(InstallDownloadModel)
def add_info_widget(self, download: InstallDownloadModel):
self.item.download = download
if self.item:
self.info_widget.update_information(download.game, download.igame, download.analysis, self.old_igame)
self.ui.queue_buttons.setEnabled(bool(self.item))
def toggle_arrows(self, index: int, length: int):
self.ui.move_up_button.setEnabled(bool(index))
self.ui.move_down_button.setEnabled(bool(length - (index + 1)))

View file

@ -0,0 +1,69 @@
import os
import sys
from PyQt5.QtCore import QObject, QRunnable, pyqtSignal, pyqtSlot
from legendary.lfs.eos import EOSOverlayApp
from legendary.models.downloading import ConditionCheckResult
from rare.lgndr.cli import LegendaryCLI
from rare.lgndr.core import LegendaryCore
from rare.lgndr.glue.arguments import LgndrInstallGameArgs
from rare.lgndr.glue.exception import LgndrException
from rare.lgndr.glue.monkeys import LgndrIndirectStatus
from rare.models.install import InstallDownloadModel, InstallOptionsModel
class InstallInfoWorker(QRunnable):
class Signals(QObject):
result = pyqtSignal(InstallDownloadModel)
failed = pyqtSignal(str)
finished = pyqtSignal()
def __init__(self, core: LegendaryCore, options: InstallOptionsModel):
sys.excepthook = sys.__excepthook__
super(InstallInfoWorker, self).__init__()
self.setAutoDelete(True)
self.signals = InstallInfoWorker.Signals()
self.core = core
self.options = options
@pyqtSlot()
def run(self):
try:
if not self.options.overlay:
cli = LegendaryCLI(self.core)
status = LgndrIndirectStatus()
result = cli.install_game(
LgndrInstallGameArgs(**self.options.as_install_kwargs(), indirect_status=status)
)
if result:
download = InstallDownloadModel(*result)
else:
raise LgndrException(status.message)
else:
if not os.path.exists(path := self.options.base_path):
os.makedirs(path)
dlm, analysis, igame = self.core.prepare_overlay_install(
path=self.options.base_path
)
download = InstallDownloadModel(
dlm=dlm,
analysis=analysis,
igame=igame,
game=EOSOverlayApp,
repair=False,
repair_file="",
res=ConditionCheckResult(), # empty
)
if not download.res or not download.res.failures:
self.signals.result.emit(download)
else:
self.signals.failed.emit("\n".join(str(i) for i in download.res.failures))
except LgndrException as ret:
self.signals.failed.emit(ret.message)
except Exception as e:
self.signals.failed.emit(str(e))
self.signals.finished.emit()