MoveWorker: Move worker into rare/shared/workers
This commit is contained in:
parent
f0b81c7038
commit
28e68cad97
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
111
rare/shared/workers/move.py
Normal 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
|
Loading…
Reference in a new issue