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:
parent
0830c17ee5
commit
523391d166
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)))
|
||||
|
|
69
rare/shared/workers/install_info.py
Normal file
69
rare/shared/workers/install_info.py
Normal 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()
|
Loading…
Reference in a new issue