From 6b3a841378eeac787b9dcaebfe0516466bbb5abc Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Fri, 3 Feb 2023 10:55:56 +0200 Subject: [PATCH] Refine QueueWorker and implement `worker_info` for `VerifyWorker` and `MoveWorker` --- .../tabs/games/game_info/game_info.py | 112 ++++++++---------- rare/models/game.py | 6 +- rare/shared/rare_core.py | 12 +- rare/shared/workers/move.py | 8 +- rare/shared/workers/verify.py | 13 +- rare/shared/workers/worker.py | 31 +++-- rare/utils/steam_grades.py | 13 +- 7 files changed, 102 insertions(+), 93 deletions(-) diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index 37a703ca..2955801c 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -61,10 +61,6 @@ class GameInfo(QWidget): if platform.system() == "Windows": self.ui.lbl_grade.setVisible(False) self.ui.grade.setVisible(False) - else: - self.steam_worker: SteamWorker = SteamWorker(self.core) - self.steam_worker.signals.rating.connect(self.ui.grade.setText) - self.steam_worker.setAutoDelete(False) self.ui.game_actions_stack.setCurrentWidget(self.ui.installed_page) @@ -151,18 +147,18 @@ class GameInfo(QWidget): self.verify_game(self.rgame) def verify_game(self, rgame: RareGame): + worker = VerifyWorker(self.core, self.args, rgame) + worker.signals.progress.connect(self.__on_verify_progress) + worker.signals.result.connect(self.__on_verify_result) + worker.signals.error.connect(self.__on_verify_error) self.ui.verify_stack.setCurrentWidget(self.ui.verify_progress_page) - verify_worker = VerifyWorker(self.core, self.args, rgame) - verify_worker.signals.progress.connect(self.__on_verify_progress) - verify_worker.signals.result.connect(self.__on_verify_result) - verify_worker.signals.error.connect(self.__on_verify_error) self.ui.verify_progress.setValue(0) - self.rgame.__worker = verify_worker - self.verify_pool.start(verify_worker) self.ui.move_button.setEnabled(False) + self.rcore.enqueue_worker(rgame, worker) def verify_cleanup(self, rgame: RareGame): - rgame.__worker = None + if rgame is not self.rgame: + return self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page) self.ui.move_button.setEnabled(True) self.ui.verify_button.setEnabled(True) @@ -257,7 +253,7 @@ class GameInfo(QWidget): self.ui.move_progress.setValue(progress_int) def start_copy_diff_drive(self): - copy_worker = MoveWorker( + worker = MoveWorker( self.core, install_path=self.rgame.igame.install_path, dest_path=self.dest_path_with_suffix, @@ -265,10 +261,10 @@ class GameInfo(QWidget): igame=self.rgame.igame, ) - copy_worker.signals.progress.connect(self.__on_move_progress) - copy_worker.signals.result.connect(self.set_new_game) - copy_worker.signals.error.connect(self.warn_no_space_left) - QThreadPool.globalInstance().start(copy_worker) + worker.signals.progress.connect(self.__on_move_progress) + worker.signals.result.connect(self.set_new_game) + worker.signals.error.connect(self.warn_no_space_left) + self.rcore.enqueue_worker(self.rgame, worker) def move_helper_clean_up(self): self.ui.move_stack.setCurrentWidget(self.ui.move_button_page) @@ -310,67 +306,69 @@ class GameInfo(QWidget): rgame = self.rcore.get_game(rgame) if self.rgame is not None: - if (worker := self.rgame.worker) is not None: + if (worker := self.rgame.worker()) is not None: if isinstance(worker, VerifyWorker): try: worker.signals.progress.disconnect(self.__on_verify_progress) + self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page) except TypeError as e: logger.warning(f"{self.rgame.app_name} verify worker: {e}") if isinstance(worker, MoveWorker): try: worker.signals.progress.disconnect(self.__on_move_progress) + self.ui.move_stack.setCurrentWidget(self.ui.move_button_page) except TypeError as e: logger.warning(f"{self.rgame.app_name} move worker: {e}") self.rgame.signals.widget.update.disconnect(self.__update_ui) self.rgame.signals.game.installed.disconnect(self.update_game) self.rgame.signals.game.uninstalled.disconnect(self.update_game) - self.rgame = rgame + self.rgame = None - self.rgame.signals.widget.update.disconnect(self.__update_ui) - self.rgame.signals.game.installed.connect(self.update_game) - self.rgame.signals.game.uninstalled.connect(self.update_game) - if (worker := self.rgame.worker) is not None: + rgame.signals.widget.update.connect(self.__update_ui) + rgame.signals.game.installed.connect(self.update_game) + rgame.signals.game.uninstalled.connect(self.update_game) + if (worker := rgame.worker()) is not None: if isinstance(worker, VerifyWorker): self.ui.verify_stack.setCurrentWidget(self.ui.verify_progress_page) - self.ui.verify_progress.setValue(self.rgame.progress) + self.ui.verify_progress.setValue(rgame.progress) worker.signals.progress.connect(self.__on_verify_progress) else: self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page) if isinstance(worker, MoveWorker): self.ui.move_stack.setCurrentWidget(self.ui.move_progress_page) - self.ui.move_progress.setValue(self.rgame.progress) + self.ui.move_progress.setValue(rgame.progress) worker.signals.progress.connect(self.__on_move_progress) else: self.ui.move_stack.setCurrentWidget(self.ui.move_button_page) - self.title.setTitle(self.rgame.app_title) + self.title.setTitle(rgame.app_title) self.image.setPixmap(rgame.pixmap) - self.ui.app_name.setText(self.rgame.app_name) - self.ui.version.setText(self.rgame.version) - self.ui.dev.setText(self.rgame.developer) + self.ui.app_name.setText(rgame.app_name) + self.ui.version.setText(rgame.version) + self.ui.dev.setText(rgame.developer) - if self.rgame.igame: - self.ui.install_size.setText(get_size(self.rgame.igame.install_size)) - self.ui.install_path.setText(self.rgame.igame.install_path) - self.ui.platform.setText(self.rgame.igame.platform) + if rgame.igame: + self.ui.install_size.setText(get_size(rgame.igame.install_size)) + self.ui.install_path.setText(rgame.igame.install_path) + self.ui.platform.setText(rgame.igame.platform) else: self.ui.install_size.setText("N/A") self.ui.install_path.setText("N/A") self.ui.platform.setText("Windows") - self.ui.install_size.setEnabled(bool(self.rgame.igame)) - self.ui.lbl_install_size.setEnabled(bool(self.rgame.igame)) - self.ui.install_path.setEnabled(bool(self.rgame.igame)) - self.ui.lbl_install_path.setEnabled(bool(self.rgame.igame)) + self.ui.install_size.setEnabled(bool(rgame.igame)) + self.ui.lbl_install_size.setEnabled(bool(rgame.igame)) + self.ui.install_path.setEnabled(bool(rgame.igame)) + self.ui.lbl_install_path.setEnabled(bool(rgame.igame)) - self.ui.uninstall_button.setEnabled(bool(self.rgame.igame)) - self.ui.verify_button.setEnabled(bool(self.rgame.igame)) - self.ui.repair_button.setEnabled(bool(self.rgame.igame)) + self.ui.uninstall_button.setEnabled(bool(rgame.igame)) + self.ui.verify_button.setEnabled(bool(rgame.igame)) + self.ui.repair_button.setEnabled(bool(rgame.igame)) - if not self.rgame.is_installed or self.rgame.is_origin: + if not rgame.is_installed or rgame.is_origin: self.ui.game_actions_stack.setCurrentWidget(self.ui.uninstalled_page) - if self.rgame.is_origin: + if rgame.is_origin: self.ui.version.setText("N/A") self.ui.version.setEnabled(False) self.ui.install_button.setText(self.tr("Link to Origin/Launch")) @@ -379,34 +377,26 @@ class GameInfo(QWidget): else: if not self.args.offline: self.ui.repair_button.setDisabled( - not os.path.exists(os.path.join(self.core.lgd.get_tmp_path(), f"{self.rgame.app_name}.repair")) + not os.path.exists(os.path.join(self.core.lgd.get_tmp_path(), f"{rgame.app_name}.repair")) ) self.ui.game_actions_stack.setCurrentWidget(self.ui.installed_page) - grade_visible = not self.rgame.is_unreal and platform.system() != "Windows" + grade_visible = not rgame.is_unreal and platform.system() != "Windows" self.ui.grade.setVisible(grade_visible) self.ui.lbl_grade.setVisible(grade_visible) - if platform.system() != "Windows" and not self.rgame.is_unreal: + if platform.system() != "Windows" and not rgame.is_unreal: self.ui.grade.setText(self.tr("Loading")) - self.steam_worker.set_app_name(self.rgame.app_name) - QThreadPool.globalInstance().start(self.steam_worker) + # TODO: Handle result emitted after quickly changing between game information + steam_worker: SteamWorker = SteamWorker(self.core, rgame.app_name) + steam_worker.signals.rating.connect(self.ui.grade.setText) + QThreadPool.globalInstance().start(steam_worker) - # If the game that is currently moving matches with the current app_name, we show the progressbar. - # Otherwhise, we show the move tool button. - if self.rgame.igame is not None: - if self.game_moving == self.rgame.app_name: - self.ui.move_stack.setCurrentWidget(self.ui.move_progress_page) - else: - self.ui.move_stack.setCurrentWidget(self.ui.move_button_page) - - # If a game is verifying or moving, disable both verify and moving buttons. - if rgame.worker is not None: - self.ui.verify_button.setEnabled(False) - self.ui.move_button.setEnabled(False) - if self.is_moving: - self.ui.move_button.setEnabled(False) - self.ui.verify_button.setEnabled(False) + self.ui.verify_button.setEnabled(rgame.is_idle) + self.ui.move_button.setEnabled(rgame.is_idle) self.move_game_pop_up.update_game(rgame.app_name) + self.rgame = rgame + + diff --git a/rare/models/game.py b/rare/models/game.py index 5def46a5..284487e4 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -132,13 +132,13 @@ class RareGame(QObject): def __on_progress_update(self, progress: int): self.progress = progress - @property def worker(self) -> Optional[QRunnable]: return self.__worker - @worker.setter - def worker(self, worker: Optional[QRunnable]): + def set_worker(self, worker: Optional[QRunnable]): self.__worker = worker + if worker is None: + self.state = RareGame.State.IDLE @property def state(self) -> 'RareGame.State': diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index 1a512d56..20e4e54e 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -15,6 +15,7 @@ from rare.models.game import RareGame, RareEosOverlay from rare.models.signals import GlobalSignals from .workers import QueueWorker, VerifyWorker, MoveWorker from .image_manager import ImageManager +from .workers.worker import QueueWorkerInfo logger = getLogger("RareCore") @@ -52,13 +53,16 @@ class RareCore(QObject): rgame.state = RareGame.State.VERIFYING if isinstance(worker, MoveWorker): rgame.state = RareGame.State.MOVING - rgame.worker = worker - worker.feedback.started.connect(self.__signals.application.update_statubar) + rgame.set_worker(worker) + worker.feedback.started.connect(self.__signals.application.update_statusbar) + worker.feedback.finished.connect(lambda: rgame.set_worker(None)) worker.feedback.finished.connect(lambda: self.queue_workers.remove(worker)) + worker.feedback.finished.connect(self.__signals.application.update_statusbar) self.queue_workers.append(worker) - self.queue_threadpool.start(worker, 0) + self.queue_threadpool.start(worker, priority=0) + self.__signals.application.update_statusbar.emit() - def queue_info(self) -> List: + def queue_info(self) -> List[QueueWorkerInfo]: return [w.worker_info() for w in self.queue_workers] @staticmethod diff --git a/rare/shared/workers/move.py b/rare/shared/workers/move.py index d9896dc6..78613650 100644 --- a/rare/shared/workers/move.py +++ b/rare/shared/workers/move.py @@ -7,7 +7,7 @@ from legendary.lfs.utils import validate_files from legendary.models.game import VerifyResult, InstalledGame from rare.lgndr.core import LegendaryCore -from .worker import QueueWorker +from .worker import QueueWorker, QueueWorkerInfo # noinspection PyUnresolvedReferences @@ -37,8 +37,10 @@ class MoveWorker(QueueWorker): self.file_list = None self.total: int = 0 - def worker_info(self): - return None + def worker_info(self) -> QueueWorkerInfo: + return QueueWorkerInfo( + app_name=self.rgame.app_name, app_title=self.rgame.app_title, worker_type="Move", state=self.state + ) def run_real(self): root_directory = Path(self.install_path) diff --git a/rare/shared/workers/verify.py b/rare/shared/workers/verify.py index db4f3f17..fede5e08 100644 --- a/rare/shared/workers/verify.py +++ b/rare/shared/workers/verify.py @@ -1,5 +1,6 @@ import os import sys +import time from argparse import Namespace from logging import getLogger @@ -10,7 +11,7 @@ from rare.lgndr.core import LegendaryCore from rare.lgndr.glue.arguments import LgndrVerifyGameArgs from rare.lgndr.glue.monkeys import LgndrIndirectStatus from rare.models.game import RareGame -from .worker import QueueWorker +from .worker import QueueWorker, QueueWorkerInfo logger = getLogger("VerifyWorker") @@ -21,8 +22,8 @@ class VerifyWorker(QueueWorker): result = pyqtSignal(RareGame, bool, int, int) error = pyqtSignal(RareGame, str) - num: int = 0 - total: int = 1 # set default to 1 to avoid DivisionByZero before it is initialized + # num: int = 0 + # total: int = 1 # set default to 1 to avoid DivisionByZero before it is initialized def __init__(self, core: LegendaryCore, args: Namespace, rgame: RareGame): super(VerifyWorker, self).__init__() @@ -35,8 +36,10 @@ class VerifyWorker(QueueWorker): self.rgame.signals.progress.update.emit(num * 100 // total) self.signals.progress.emit(self.rgame, num, total, percentage, speed) - def worker_info(self): - return None + def worker_info(self) -> QueueWorkerInfo: + return QueueWorkerInfo( + app_name=self.rgame.app_name, app_title=self.rgame.app_title, worker_type="Verify", state=self.state + ) def run_real(self): self.rgame.signals.progress.start.emit() diff --git a/rare/shared/workers/worker.py b/rare/shared/workers/worker.py index fdb1d535..11b2a7c3 100644 --- a/rare/shared/workers/worker.py +++ b/rare/shared/workers/worker.py @@ -1,5 +1,6 @@ import sys from abc import abstractmethod +from dataclasses import dataclass from enum import IntEnum from typing import Optional @@ -42,6 +43,21 @@ class Worker(QRunnable): self.signals.deleteLater() +class QueueWorkerState(IntEnum): + UNDEFINED = 0 + QUEUED = 1 + ACTIVE = 2 + + +@dataclass +class QueueWorkerInfo: + app_name: str + app_title: str + worker_type: str + state: QueueWorkerState + progress: int = 0 + + class QueueWorker(Worker): """ Base queueable worker class @@ -53,11 +69,6 @@ class QueueWorker(Worker): to the `QueueWorker.signals` attribute, implement `QueueWorker.run_real()` and `QueueWorker.worker_info()` """ - class State(IntEnum): - UNDEFINED = 0 - QUEUED = 1 - ACTIVE = 2 - class Signals(QObject): started = pyqtSignal() finished = pyqtSignal() @@ -65,16 +76,18 @@ class QueueWorker(Worker): def __init__(self): super(QueueWorker, self).__init__() self.feedback = QueueWorker.Signals() - self.state = QueueWorker.State.QUEUED + self.state = QueueWorkerState.QUEUED @pyqtSlot() def run(self): - self.state = QueueWorker.State.ACTIVE + self.state = QueueWorkerState.ACTIVE self.feedback.started.emit() super(QueueWorker, self).run() self.feedback.finished.emit() self.feedback.deleteLater() @abstractmethod - def worker_info(self): - pass \ No newline at end of file + def worker_info(self) -> QueueWorkerInfo: + pass + + diff --git a/rare/utils/steam_grades.py b/rare/utils/steam_grades.py index 57ba3e5f..9e665778 100644 --- a/rare/utils/steam_grades.py +++ b/rare/utils/steam_grades.py @@ -8,22 +8,22 @@ from PyQt5.QtCore import pyqtSignal, QRunnable, QObject, QCoreApplication from rare.lgndr.core import LegendaryCore from rare.shared import LegendaryCoreSingleton, ArgumentsSingleton +from rare.shared.workers import Worker from rare.utils.paths import data_dir, cache_dir replace_chars = ",;.:-_ " url = "https://api.steampowered.com/ISteamApps/GetAppList/v2/" -class SteamWorker(QRunnable): +class SteamWorker(Worker): class Signals(QObject): rating = pyqtSignal(str) - app_name: str = "" - - def __init__(self, core: LegendaryCore): + def __init__(self, core: LegendaryCore, app_name: str): super(SteamWorker, self).__init__() self.signals = SteamWorker.Signals() self.core = core + self.app_name = app_name _tr = QCoreApplication.translate self.ratings = { "platinum": _tr("SteamWorker", "Platinum"), @@ -35,10 +35,7 @@ class SteamWorker(QRunnable): "pending": _tr("SteamWorker", "Could not get grade"), } - def set_app_name(self, app_name: str): - self.app_name = app_name - - def run(self) -> None: + def run_real(self) -> None: self.signals.rating.emit( self.ratings.get(get_rating(self.app_name), self.ratings["fail"]) )