1
0
Fork 0
mirror of synced 2024-06-26 18:20:50 +12:00

Workers: Implement wrapper QueueWorker class prototype for queueable workers

RareCore: Impelement base worker queue
This commit is contained in:
loathingKernel 2023-01-31 15:43:26 +02:00
parent 07ef43b13e
commit 6800b7e9ab
8 changed files with 135 additions and 45 deletions

View file

@ -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:

View file

@ -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):

View file

@ -19,6 +19,8 @@ class GlobalSignals:
overlay_installed = pyqtSignal()
# none
update_tray = pyqtSignal()
# none
update_statusbar = pyqtSignal()
class GameSignals(QObject):
# model

View file

@ -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:

View file

@ -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

View file

@ -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())

View 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)

View file

@ -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