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

MoveWorker: Move worker into rare/shared/workers

This commit is contained in:
loathingKernel 2023-01-30 16:00:27 +02:00
parent f0b81c7038
commit 28e68cad97
4 changed files with 142 additions and 114 deletions

View file

@ -28,11 +28,12 @@ from rare.shared import (
)
from rare.shared.image_manager import ImageSize
from rare.shared.workers.verify import VerifyWorker
from rare.shared.workers.move import 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
from rare.widgets.image_widget import ImageWidget
from .move_game import CopyGameInstallation, MoveGamePopUp, is_game_dir
from .move_game import MoveGamePopUp, is_game_dir
logger = getLogger("GameInfo")
@ -157,12 +158,12 @@ class GameInfo(QWidget):
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.active_worker = verify_worker
self.rgame.__worker = verify_worker
self.verify_pool.start(verify_worker)
self.ui.move_button.setEnabled(False)
def verify_cleanup(self, rgame: RareGame):
rgame.active_worker = None
rgame.__worker = None
self.ui.verify_stack.setCurrentWidget(self.ui.verify_button_page)
self.ui.move_button.setEnabled(True)
self.ui.verify_button.setEnabled(True)
@ -257,7 +258,8 @@ class GameInfo(QWidget):
self.ui.move_progress.setValue(progress_int)
def start_copy_diff_drive(self):
copy_worker = CopyGameInstallation(
copy_worker = MoveWorker(
self.core,
install_path=self.rgame.igame.install_path,
dest_path=self.dest_path_with_suffix,
is_existing_dir=self.existing_game_dir,
@ -305,7 +307,7 @@ class GameInfo(QWidget):
rgame = self.rcore.get_game(rgame)
if self.rgame is not None:
if (worker := self.rgame.active_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)
@ -316,7 +318,7 @@ class GameInfo(QWidget):
self.rgame = rgame
self.rgame.signals.game.installed.connect(self.update_game)
self.rgame.signals.game.uninstalled.connect(self.update_game)
if (worker := self.rgame.active_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)
@ -381,7 +383,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.active_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

@ -4,10 +4,8 @@ from logging import getLogger
from pathlib import Path
from typing import Tuple
from PyQt5.QtCore import pyqtSignal, QRunnable, QObject, Qt
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog
from legendary.models.game import VerifyResult, InstalledGame
from legendary.lfs.utils import validate_files
from rare.shared import LegendaryCoreSingleton
from rare.utils.extra_widgets import PathEdit
@ -142,109 +140,6 @@ class MoveGamePopUp(QWidget):
)
# noinspection PyUnresolvedReferences
class CopyGameInstallation(QRunnable):
class Signals(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal(str)
no_space_left = pyqtSignal()
def __init__(
self,
install_path: str,
dest_path: Path,
is_existing_dir: bool,
igame: InstalledGame,
):
super(CopyGameInstallation, self).__init__()
self.signals = CopyGameInstallation.Signals()
self.install_path = install_path
self.dest_path = dest_path
self.source_size = 0
self.dest_size = 0
self.is_existing_dir = is_existing_dir
self.core = LegendaryCoreSingleton()
self.igame = igame
self.file_list = None
self.total: int = 0
def run(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())
# if game dir is not existing, just copying:
if not self.is_existing_dir:
shutil.copytree(
self.install_path,
self.dest_path,
copy_function=self.copy_each_file_with_progress,
dirs_exist_ok=True,
)
else:
manifest_data, _ = self.core.get_installed_manifest(self.igame.app_name)
manifest = self.core.load_manifest(manifest_data)
files = sorted(
manifest.file_manifest_list.elements,
key=lambda a: a.filename.lower(),
)
self.file_list = [(f.filename, f.sha_hash.hex()) for f in files]
self.total = len(self.file_list)
# recreate dir structure
shutil.copytree(
self.install_path,
self.dest_path,
copy_function=self.copy_dir_structure,
dirs_exist_ok=True,
)
for i, (result, relative_path, _, _) in enumerate(
validate_files(str(self.dest_path), self.file_list)
):
dst_path = f"{self.dest_path}/{relative_path}"
src_path = f"{self.install_path}/{relative_path}"
if Path(src_path).is_file():
if result == VerifyResult.HASH_MISMATCH:
try:
shutil.copy(src_path, dst_path)
except IOError:
self.signals.no_space_left.emit()
return
elif result == VerifyResult.FILE_MISSING:
try:
shutil.copy(src_path, dst_path)
except (IOError, OSError):
self.signals.no_space_left.emit()
return
elif result == VerifyResult.OTHER_ERROR:
logger.warning(f"Copying file {src_path} to {dst_path} failed")
self.signals.progress.emit(int(i * 10 / self.total * 10))
else:
logger.warning(
f"Source dir does not have file {src_path}. File will be missing in the destination "
f"dir. "
)
shutil.rmtree(self.install_path)
self.signals.finished.emit(str(self.dest_path))
def copy_each_file_with_progress(self, src, dst):
shutil.copy(src, dst)
self.dest_size += Path(src).stat().st_size
self.signals.progress.emit(int(self.dest_size * 10 / self.source_size * 10))
# This method is a copy_func, and only copies the src if it's a dir.
# Thus, it can be used to re-create the dir strucute.
@staticmethod
def copy_dir_structure(src, dst):
if os.path.isdir(dst):
dst = os.path.join(dst, os.path.basename(src))
if os.path.isdir(src):
shutil.copyfile(src, dst)
shutil.copystat(src, dst)
return dst
def is_game_dir(install_path: Path, dest_path: Path):
# This iterates over the destination dir, then iterates over the current install dir and if the file names
# matches, we have an exisiting dir

View file

@ -14,6 +14,8 @@ 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
@ -117,7 +119,7 @@ class RareGame(QObject):
logger.info(f"Update available for game: {self.app_name} ({self.app_title})")
self.progress: int = 0
self.active_worker: Optional[QRunnable] = None
self.__worker: Optional[QRunnable] = None
self.__state = RareGame.State.IDLE
@ -128,6 +130,20 @@ class RareGame(QObject):
self.game_process.connect_to_server(on_startup=True)
# self.grant_date(True)
@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
def state(self) -> 'RareGame.State':
return self.__state
@ -138,6 +154,10 @@ class RareGame(QObject):
self.signals.widget.update.emit()
self.__state = state
@property
def is_idle(self):
return self.state == RareGame.State.IDLE
@pyqtSlot(int)
def __game_launched(self, code: int):
if code == GameProcess.Code.ON_STARTUP:

111
rare/shared/workers/move.py Normal file
View file

@ -0,0 +1,111 @@
import os
import shutil
from pathlib import Path
from PyQt5.QtCore import pyqtSignal, QRunnable, QObject
from legendary.lfs.utils import validate_files
from legendary.models.game import VerifyResult, InstalledGame
# noinspection PyUnresolvedReferences
class MoveWorker(QRunnable):
class Signals(QObject):
progress = pyqtSignal(int)
finished = pyqtSignal(str)
no_space_left = pyqtSignal()
def __init__(
self,
core: LegendaryCore,
install_path: str,
dest_path: Path,
is_existing_dir: bool,
igame: InstalledGame,
):
super(MoveWorker, self).__init__()
self.signals = MoveWorker.Signals()
self.core = core
self.install_path = install_path
self.dest_path = dest_path
self.source_size = 0
self.dest_size = 0
self.is_existing_dir = is_existing_dir
self.igame = igame
self.file_list = None
self.total: int = 0
def run(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())
# if game dir is not existing, just copying:
if not self.is_existing_dir:
shutil.copytree(
self.install_path,
self.dest_path,
copy_function=self.copy_each_file_with_progress,
dirs_exist_ok=True,
)
else:
manifest_data, _ = self.core.get_installed_manifest(self.igame.app_name)
manifest = self.core.load_manifest(manifest_data)
files = sorted(
manifest.file_manifest_list.elements,
key=lambda a: a.filename.lower(),
)
self.file_list = [(f.filename, f.sha_hash.hex()) for f in files]
self.total = len(self.file_list)
# recreate dir structure
shutil.copytree(
self.install_path,
self.dest_path,
copy_function=self.copy_dir_structure,
dirs_exist_ok=True,
)
for i, (result, relative_path, _, _) in enumerate(
validate_files(str(self.dest_path), self.file_list)
):
dst_path = f"{self.dest_path}/{relative_path}"
src_path = f"{self.install_path}/{relative_path}"
if Path(src_path).is_file():
if result == VerifyResult.HASH_MISMATCH:
try:
shutil.copy(src_path, dst_path)
except IOError:
self.signals.no_space_left.emit()
return
elif result == VerifyResult.FILE_MISSING:
try:
shutil.copy(src_path, dst_path)
except (IOError, OSError):
self.signals.no_space_left.emit()
return
elif result == VerifyResult.OTHER_ERROR:
logger.warning(f"Copying file {src_path} to {dst_path} failed")
self.signals.progress.emit(int(i * 10 / self.total * 10))
else:
logger.warning(
f"Source dir does not have file {src_path}. File will be missing in the destination "
f"dir. "
)
shutil.rmtree(self.install_path)
self.signals.finished.emit(str(self.dest_path))
def copy_each_file_with_progress(self, src, dst):
shutil.copy(src, dst)
self.dest_size += Path(src).stat().st_size
self.signals.progress.emit(int(self.dest_size * 10 / self.source_size * 10))
# This method is a copy_func, and only copies the src if it's a dir.
# Thus, it can be used to re-create the dir strucute.
@staticmethod
def copy_dir_structure(src, dst):
if os.path.isdir(dst):
dst = os.path.join(dst, os.path.basename(src))
if os.path.isdir(src):
shutil.copyfile(src, dst)
shutil.copystat(src, dst)
return dst