MoveWorker: Move worker into rare/shared/workers
This commit is contained in:
parent
f0b81c7038
commit
28e68cad97
4 changed files with 142 additions and 114 deletions
|
@ -28,11 +28,12 @@ from rare.shared import (
|
||||||
)
|
)
|
||||||
from rare.shared.image_manager import ImageSize
|
from rare.shared.image_manager import ImageSize
|
||||||
from rare.shared.workers.verify import VerifyWorker
|
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.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
||||||
from rare.utils.misc import get_size
|
from rare.utils.misc import get_size
|
||||||
from rare.utils.steam_grades import SteamWorker
|
from rare.utils.steam_grades import SteamWorker
|
||||||
from rare.widgets.image_widget import ImageWidget
|
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")
|
logger = getLogger("GameInfo")
|
||||||
|
|
||||||
|
@ -157,12 +158,12 @@ class GameInfo(QWidget):
|
||||||
verify_worker.signals.result.connect(self.__on_verify_result)
|
verify_worker.signals.result.connect(self.__on_verify_result)
|
||||||
verify_worker.signals.error.connect(self.__on_verify_error)
|
verify_worker.signals.error.connect(self.__on_verify_error)
|
||||||
self.ui.verify_progress.setValue(0)
|
self.ui.verify_progress.setValue(0)
|
||||||
self.rgame.active_worker = verify_worker
|
self.rgame.__worker = verify_worker
|
||||||
self.verify_pool.start(verify_worker)
|
self.verify_pool.start(verify_worker)
|
||||||
self.ui.move_button.setEnabled(False)
|
self.ui.move_button.setEnabled(False)
|
||||||
|
|
||||||
def verify_cleanup(self, rgame: RareGame):
|
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.verify_stack.setCurrentWidget(self.ui.verify_button_page)
|
||||||
self.ui.move_button.setEnabled(True)
|
self.ui.move_button.setEnabled(True)
|
||||||
self.ui.verify_button.setEnabled(True)
|
self.ui.verify_button.setEnabled(True)
|
||||||
|
@ -257,7 +258,8 @@ class GameInfo(QWidget):
|
||||||
self.ui.move_progress.setValue(progress_int)
|
self.ui.move_progress.setValue(progress_int)
|
||||||
|
|
||||||
def start_copy_diff_drive(self):
|
def start_copy_diff_drive(self):
|
||||||
copy_worker = CopyGameInstallation(
|
copy_worker = MoveWorker(
|
||||||
|
self.core,
|
||||||
install_path=self.rgame.igame.install_path,
|
install_path=self.rgame.igame.install_path,
|
||||||
dest_path=self.dest_path_with_suffix,
|
dest_path=self.dest_path_with_suffix,
|
||||||
is_existing_dir=self.existing_game_dir,
|
is_existing_dir=self.existing_game_dir,
|
||||||
|
@ -305,7 +307,7 @@ class GameInfo(QWidget):
|
||||||
rgame = self.rcore.get_game(rgame)
|
rgame = self.rcore.get_game(rgame)
|
||||||
|
|
||||||
if self.rgame is not None:
|
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):
|
if isinstance(worker, VerifyWorker):
|
||||||
try:
|
try:
|
||||||
worker.signals.progress.disconnect(self.__on_verify_progress)
|
worker.signals.progress.disconnect(self.__on_verify_progress)
|
||||||
|
@ -316,7 +318,7 @@ class GameInfo(QWidget):
|
||||||
self.rgame = rgame
|
self.rgame = rgame
|
||||||
self.rgame.signals.game.installed.connect(self.update_game)
|
self.rgame.signals.game.installed.connect(self.update_game)
|
||||||
self.rgame.signals.game.uninstalled.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):
|
if isinstance(worker, VerifyWorker):
|
||||||
self.ui.verify_stack.setCurrentWidget(self.ui.verify_progress_page)
|
self.ui.verify_stack.setCurrentWidget(self.ui.verify_progress_page)
|
||||||
self.ui.verify_progress.setValue(self.rgame.progress)
|
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)
|
self.ui.move_stack.setCurrentWidget(self.ui.move_button_page)
|
||||||
|
|
||||||
# If a game is verifying or moving, disable both verify and moving buttons.
|
# 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.verify_button.setEnabled(False)
|
||||||
self.ui.move_button.setEnabled(False)
|
self.ui.move_button.setEnabled(False)
|
||||||
if self.is_moving:
|
if self.is_moving:
|
||||||
|
|
|
@ -4,10 +4,8 @@ from logging import getLogger
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Tuple
|
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 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.shared import LegendaryCoreSingleton
|
||||||
from rare.utils.extra_widgets import PathEdit
|
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):
|
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
|
# This iterates over the destination dir, then iterates over the current install dir and if the file names
|
||||||
# matches, we have an exisiting dir
|
# 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.models.install import InstallOptionsModel, UninstallOptionsModel
|
||||||
from rare.shared.game_process import GameProcess
|
from rare.shared.game_process import GameProcess
|
||||||
from rare.shared.image_manager import ImageManager
|
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.misc import get_rare_executable
|
||||||
from rare.utils.paths import data_dir
|
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})")
|
logger.info(f"Update available for game: {self.app_name} ({self.app_title})")
|
||||||
|
|
||||||
self.progress: int = 0
|
self.progress: int = 0
|
||||||
self.active_worker: Optional[QRunnable] = None
|
self.__worker: Optional[QRunnable] = None
|
||||||
|
|
||||||
self.__state = RareGame.State.IDLE
|
self.__state = RareGame.State.IDLE
|
||||||
|
|
||||||
|
@ -128,6 +130,20 @@ class RareGame(QObject):
|
||||||
self.game_process.connect_to_server(on_startup=True)
|
self.game_process.connect_to_server(on_startup=True)
|
||||||
# self.grant_date(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
|
@property
|
||||||
def state(self) -> 'RareGame.State':
|
def state(self) -> 'RareGame.State':
|
||||||
return self.__state
|
return self.__state
|
||||||
|
@ -138,6 +154,10 @@ class RareGame(QObject):
|
||||||
self.signals.widget.update.emit()
|
self.signals.widget.update.emit()
|
||||||
self.__state = state
|
self.__state = state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_idle(self):
|
||||||
|
return self.state == RareGame.State.IDLE
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def __game_launched(self, code: int):
|
def __game_launched(self, code: int):
|
||||||
if code == GameProcess.Code.ON_STARTUP:
|
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