Workers: Implement wrapper QueueWorker class prototype for queueable workers
RareCore: Impelement base worker queue
This commit is contained in:
parent
07ef43b13e
commit
6800b7e9ab
8 changed files with 135 additions and 45 deletions
|
@ -27,8 +27,7 @@ from rare.shared import (
|
|||
ImageManagerSingleton,
|
||||
)
|
||||
from rare.shared.image_manager import ImageSize
|
||||
from rare.shared.workers.verify import VerifyWorker
|
||||
from rare.shared.workers.move import MoveWorker
|
||||
from rare.shared.workers import VerifyWorker, MoveWorker
|
||||
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
||||
from rare.utils.misc import get_size
|
||||
from rare.utils.steam_grades import SteamWorker
|
||||
|
@ -267,8 +266,8 @@ class GameInfo(QWidget):
|
|||
)
|
||||
|
||||
copy_worker.signals.progress.connect(self.__on_move_progress)
|
||||
copy_worker.signals.finished.connect(self.set_new_game)
|
||||
copy_worker.signals.no_space_left.connect(self.warn_no_space_left)
|
||||
copy_worker.signals.result.connect(self.set_new_game)
|
||||
copy_worker.signals.error.connect(self.warn_no_space_left)
|
||||
QThreadPool.globalInstance().start(copy_worker)
|
||||
|
||||
def move_helper_clean_up(self):
|
||||
|
@ -300,6 +299,10 @@ class GameInfo(QWidget):
|
|||
def show_menu_after_browse(self):
|
||||
self.ui.move_button.showMenu()
|
||||
|
||||
@pyqtSlot()
|
||||
def __update_ui(self):
|
||||
pass
|
||||
|
||||
@pyqtSlot(str)
|
||||
@pyqtSlot(RareGame)
|
||||
def update_game(self, rgame: Union[RareGame, str]):
|
||||
|
@ -307,24 +310,39 @@ 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)
|
||||
except TypeError as e:
|
||||
logger.warning(f"{self.rgame.app_title} verify worker: {e}")
|
||||
logger.warning(f"{self.rgame.app_name} verify worker: {e}")
|
||||
if isinstance(worker, MoveWorker):
|
||||
try:
|
||||
worker.signals.progress.disconnect(self.__on_move_progress)
|
||||
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.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:
|
||||
if (worker := self.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)
|
||||
worker.signals.progress.connect(self.__on_verify_progress)
|
||||
else:
|
||||
self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page)
|
||||
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)
|
||||
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.image.setPixmap(rgame.pixmap)
|
||||
|
@ -383,7 +401,7 @@ class GameInfo(QWidget):
|
|||
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:
|
||||
if rgame.worker is not None:
|
||||
self.ui.verify_button.setEnabled(False)
|
||||
self.ui.move_button.setEnabled(False)
|
||||
if self.is_moving:
|
||||
|
|
|
@ -14,8 +14,6 @@ from rare.lgndr.core import LegendaryCore
|
|||
from rare.models.install import InstallOptionsModel, UninstallOptionsModel
|
||||
from rare.shared.game_process import GameProcess
|
||||
from rare.shared.image_manager import ImageManager
|
||||
from rare.shared.workers.move import MoveWorker
|
||||
from rare.shared.workers.verify import VerifyWorker
|
||||
from rare.utils.misc import get_rare_executable
|
||||
from rare.utils.paths import data_dir
|
||||
|
||||
|
@ -118,10 +116,11 @@ class RareGame(QObject):
|
|||
if self.has_update:
|
||||
logger.info(f"Update available for game: {self.app_name} ({self.app_title})")
|
||||
|
||||
self.progress: int = 0
|
||||
self.__worker: Optional[QRunnable] = None
|
||||
|
||||
self.__state = RareGame.State.IDLE
|
||||
self.progress: int = 0
|
||||
self.signals.progress.start.connect(lambda: self.__on_progress_update(0))
|
||||
self.signals.progress.update.connect(self.__on_progress_update)
|
||||
|
||||
self.game_process = GameProcess(self.game)
|
||||
self.game_process.launched.connect(self.__game_launched)
|
||||
|
@ -130,18 +129,15 @@ class RareGame(QObject):
|
|||
self.game_process.connect_to_server(on_startup=True)
|
||||
# self.grant_date(True)
|
||||
|
||||
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]):
|
||||
if worker is None:
|
||||
self.state = RareGame.State.IDLE
|
||||
if isinstance(worker, VerifyWorker):
|
||||
self.state = RareGame.State.VERIFYING
|
||||
if isinstance(worker, MoveWorker):
|
||||
self.state = RareGame.State.MOVING
|
||||
self.__worker = worker
|
||||
|
||||
@property
|
||||
|
@ -497,21 +493,13 @@ class RareGame(QObject):
|
|||
def refresh_pixmap(self):
|
||||
self.image_manager.download_image(self.game, self.set_pixmap, 0, True)
|
||||
|
||||
def start_progress(self):
|
||||
self.signals.progress.start.emit()
|
||||
|
||||
def update_progress(self, progress: int):
|
||||
self.progress = progress
|
||||
self.signals.progress.update.emit(progress)
|
||||
|
||||
def finish_progress(self, fail: bool, miss: int, app: str):
|
||||
self.set_installed(True)
|
||||
self.signals.progress.finish.emit(fail)
|
||||
|
||||
def install(self):
|
||||
def install(self) -> bool:
|
||||
if not self.is_idle:
|
||||
return False
|
||||
self.signals.game.install.emit(
|
||||
InstallOptionsModel(app_name=self.app_name)
|
||||
)
|
||||
return True
|
||||
|
||||
def repair(self, repair_and_update):
|
||||
self.signals.game.install.emit(
|
||||
|
@ -520,10 +508,13 @@ class RareGame(QObject):
|
|||
)
|
||||
)
|
||||
|
||||
def uninstall(self):
|
||||
def uninstall(self) -> bool:
|
||||
if not self.is_idle:
|
||||
return False
|
||||
self.signals.game.uninstall.emit(
|
||||
UninstallOptionsModel(app_name=self.app_name)
|
||||
)
|
||||
return True
|
||||
|
||||
def launch(
|
||||
self,
|
||||
|
@ -532,9 +523,9 @@ class RareGame(QObject):
|
|||
wine_bin: Optional[str] = None,
|
||||
wine_pfx: Optional[str] = None,
|
||||
ask_sync_saves: bool = False,
|
||||
):
|
||||
) -> bool:
|
||||
if not self.can_launch:
|
||||
return
|
||||
return False
|
||||
|
||||
cmd_line = get_rare_executable()
|
||||
executable, args = cmd_line[0], cmd_line[1:]
|
||||
|
@ -554,6 +545,7 @@ class RareGame(QObject):
|
|||
QProcess.startDetached(executable, args)
|
||||
logger.info(f"Start new Process: ({executable} {' '.join(args)})")
|
||||
self.game_process.connect_to_server(on_startup=False)
|
||||
return True
|
||||
|
||||
|
||||
class RareEosOverlay(QObject):
|
||||
|
|
|
@ -19,6 +19,8 @@ class GlobalSignals:
|
|||
overlay_installed = pyqtSignal()
|
||||
# none
|
||||
update_tray = pyqtSignal()
|
||||
# none
|
||||
update_statusbar = pyqtSignal()
|
||||
|
||||
class GameSignals(QObject):
|
||||
# model
|
||||
|
|
|
@ -3,9 +3,9 @@ import os
|
|||
from argparse import Namespace
|
||||
from itertools import chain
|
||||
from logging import getLogger
|
||||
from typing import Optional, Dict, Iterator, Callable
|
||||
from typing import Optional, Dict, Iterator, Callable, List
|
||||
|
||||
from PyQt5.QtCore import QObject
|
||||
from PyQt5.QtCore import QObject, QThreadPool
|
||||
from legendary.lfs.eos import EOSOverlayApp
|
||||
from legendary.models.game import Game, SaveGameFile
|
||||
|
||||
|
@ -13,6 +13,7 @@ from rare.lgndr.core import LegendaryCore
|
|||
from rare.models.apiresults import ApiResults
|
||||
from rare.models.game import RareGame, RareEosOverlay
|
||||
from rare.models.signals import GlobalSignals
|
||||
from .workers import QueueWorker, VerifyWorker, MoveWorker
|
||||
from .image_manager import ImageManager
|
||||
|
||||
logger = getLogger("RareCore")
|
||||
|
@ -36,12 +37,30 @@ class RareCore(QObject):
|
|||
self.core(init=True)
|
||||
self.image_manager(init=True)
|
||||
|
||||
self.queue_workers: List[QueueWorker] = []
|
||||
self.queue_threadpool = QThreadPool()
|
||||
self.queue_threadpool.setMaxThreadCount(2)
|
||||
|
||||
self.__games: Dict[str, RareGame] = {}
|
||||
|
||||
self.__eos_overlay_rgame = RareEosOverlay(self.__core, self.__image_manager, EOSOverlayApp)
|
||||
|
||||
RareCore._instance = self
|
||||
|
||||
def enqueue_worker(self, rgame: RareGame, worker: QueueWorker):
|
||||
if isinstance(worker, VerifyWorker):
|
||||
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)
|
||||
worker.feedback.finished.connect(lambda: self.queue_workers.remove(worker))
|
||||
self.queue_workers.append(worker)
|
||||
self.queue_threadpool.start(worker, 0)
|
||||
|
||||
def queue_info(self) -> List:
|
||||
return [w.worker_info() for w in self.queue_workers]
|
||||
|
||||
@staticmethod
|
||||
def instance() -> 'RareCore':
|
||||
if RareCore._instance is None:
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
from .install_info import InstallInfoWorker
|
||||
from .move import MoveWorker
|
||||
from .uninstall import UninstallWorker
|
||||
from .verify import VerifyWorker
|
||||
from .worker import Worker, QueueWorker
|
|
@ -2,20 +2,20 @@ import os
|
|||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QRunnable, QObject
|
||||
from PyQt5.QtCore import pyqtSignal, QObject
|
||||
from legendary.lfs.utils import validate_files
|
||||
from legendary.models.game import VerifyResult, InstalledGame
|
||||
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from .worker import Worker
|
||||
from .worker import QueueWorker
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
class MoveWorker(Worker):
|
||||
class MoveWorker(QueueWorker):
|
||||
class Signals(QObject):
|
||||
progress = pyqtSignal(int)
|
||||
finished = pyqtSignal(str)
|
||||
no_space_left = pyqtSignal()
|
||||
result = pyqtSignal(str)
|
||||
error = pyqtSignal()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -37,6 +37,9 @@ class MoveWorker(Worker):
|
|||
self.file_list = None
|
||||
self.total: int = 0
|
||||
|
||||
def worker_info(self):
|
||||
return None
|
||||
|
||||
def run_real(self):
|
||||
root_directory = Path(self.install_path)
|
||||
self.source_size = sum(f.stat().st_size for f in root_directory.glob("**/*") if f.is_file())
|
||||
|
|
|
@ -10,12 +10,12 @@ 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 Worker
|
||||
from .worker import QueueWorker
|
||||
|
||||
logger = getLogger("VerifyWorker")
|
||||
|
||||
|
||||
class VerifyWorker(Worker):
|
||||
class VerifyWorker(QueueWorker):
|
||||
class Signals(QObject):
|
||||
progress = pyqtSignal(RareGame, int, int, float, float)
|
||||
result = pyqtSignal(RareGame, bool, int, int)
|
||||
|
@ -35,6 +35,9 @@ class VerifyWorker(Worker):
|
|||
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 run_real(self):
|
||||
self.rgame.signals.progress.start.emit()
|
||||
cli = LegendaryCLI(self.core)
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
import sys
|
||||
from abc import abstractmethod
|
||||
from enum import IntEnum
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import QRunnable, QObject, pyqtSlot
|
||||
from PyQt5.QtCore import QRunnable, QObject, pyqtSlot, pyqtSignal
|
||||
|
||||
|
||||
class Worker(QRunnable):
|
||||
"""
|
||||
Base QRunnable class.
|
||||
|
||||
This class provides a base for QRunnables with signals that are automatically deleted.
|
||||
|
||||
To use this class you have to assign the signals object of your concrete implementation
|
||||
to the `Worker.signals` attribute and implement `Worker.run_real()`
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
sys.excepthook = sys.__excepthook__
|
||||
super(Worker, self).__init__()
|
||||
|
@ -30,3 +40,41 @@ class Worker(QRunnable):
|
|||
def run(self):
|
||||
self.run_real()
|
||||
self.signals.deleteLater()
|
||||
|
||||
|
||||
class QueueWorker(Worker):
|
||||
"""
|
||||
Base queueable worker class
|
||||
|
||||
This class is a specialization of the `Worker` class. It provides feedback signals to know
|
||||
if a worker has started or finished.
|
||||
|
||||
To use this class you have to assign the signals object of your concrete implementation
|
||||
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()
|
||||
|
||||
def __init__(self):
|
||||
super(QueueWorker, self).__init__()
|
||||
self.feedback = QueueWorker.Signals()
|
||||
self.state = QueueWorker.State.QUEUED
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
self.state = QueueWorker.State.ACTIVE
|
||||
self.feedback.started.emit()
|
||||
super(QueueWorker, self).run()
|
||||
self.feedback.finished.emit()
|
||||
self.feedback.deleteLater()
|
||||
|
||||
@abstractmethod
|
||||
def worker_info(self):
|
||||
pass
|
Loading…
Reference in a new issue