1
0
Fork 0
mirror of synced 2024-06-17 18:14:44 +12:00
Rare/rare/shared/workers/move.py

138 lines
5.5 KiB
Python

import os
import shutil
from logging import getLogger
from PyQt5.QtCore import pyqtSignal, QObject
from legendary.lfs.utils import validate_files
from legendary.models.game import VerifyResult
from rare.models.game import RareGame
from rare.lgndr.core import LegendaryCore
from .worker import QueueWorker, QueueWorkerInfo
logger = getLogger("MoveWorker")
class MoveWorker(QueueWorker):
class Signals(QObject):
# int: percentage, object: source size, object: dest size
progress = pyqtSignal(RareGame, int, object, object)
# str: destination path
result = pyqtSignal(RareGame, str)
# str: error message
error = pyqtSignal(RareGame, str)
def __init__(self, core: LegendaryCore, rgame: RareGame, dst_path: str, dst_exists: bool):
super(MoveWorker, self).__init__()
self.signals = MoveWorker.Signals()
self.core = core
self.rgame = rgame
self.dst_path = dst_path
self.dst_exists = dst_exists
def worker_info(self) -> QueueWorkerInfo:
return QueueWorkerInfo(
app_name=self.rgame.app_name, app_title=self.rgame.app_title, worker_type="Move", state=self.state
)
def progress(self, src_size, dst_size):
progress = dst_size * 100 // src_size
self.rgame.signals.progress.update.emit(progress)
self.signals.progress.emit(self.rgame, progress, src_size, dst_size)
def run_real(self):
self.rgame.signals.progress.start.emit()
if os.stat(self.rgame.install_path).st_dev == os.stat(os.path.dirname(self.dst_path)).st_dev:
shutil.move(self.rgame.install_path, self.dst_path)
elif not self.dst_exists:
src_size = sum(
os.stat(os.path.join(dp, f)).st_size
for dp, dn, filenames in os.walk(self.rgame.install_path)
for f in filenames
)
dst_size = 0
def copy_with_progress(src, dst):
nonlocal dst_size
shutil.copy2(src, dst)
dst_size += os.stat(src).st_size
self.progress(src_size, dst_size)
shutil.copytree(
self.rgame.install_path,
self.dst_path,
copy_function=copy_with_progress,
dirs_exist_ok=True,
)
shutil.rmtree(self.rgame.install_path)
else:
manifest_data, _ = self.core.get_installed_manifest(self.rgame.app_name)
manifest = self.core.load_manifest(manifest_data)
files = sorted(
manifest.file_manifest_list.elements,
key=lambda a: a.filename.lower(),
)
if config_tags := self.core.lgd.config.get(self.rgame.app_name, 'install_tags', fallback=None):
install_tags = set(i.strip() for i in config_tags.split(','))
file_list = [
(f.filename, f.sha_hash.hex())
for f in files
if any(it in install_tags for it in f.install_tags) or not f.install_tags
]
else:
file_list = [(f.filename, f.sha_hash.hex()) for f in files]
total_size = sum(manifest.file_manifest_list.get_file_by_path(fm[0]).file_size
for fm in file_list)
dst_size = 0
# 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 structure.
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
# recreate dir structure
shutil.copytree(
self.rgame.install_path,
self.dst_path,
copy_function=copy_dir_structure,
dirs_exist_ok=True,
)
for result, path, _, _ in validate_files(self.dst_path, file_list):
dst_path = os.path.join(self.dst_path, path)
src_path = os.path.join(self.rgame.install_path, path)
if os.path.isfile(src_path):
if result == VerifyResult.HASH_MATCH:
dst_size += os.stat(dst_path).st_size
if result == VerifyResult.HASH_MISMATCH or result == VerifyResult.FILE_MISSING:
try:
shutil.copy2(src_path, dst_path)
dst_size += os.stat(dst_path).st_size
except (IOError, OSError) as e:
self.rgame.signals.progress.finish.emit(True)
self.signals.error.emit(self.rgame, str(e))
return
else:
logger.warning(f"Copying file {src_path} to {dst_path} failed")
self.progress(total_size, dst_size)
else:
logger.warning(
f"Source dir does not have file {src_path}. File will be missing in the destination dir."
)
self.rgame.needs_verification = True
shutil.rmtree(self.rgame.install_path)
self.rgame.install_path = self.dst_path
self.rgame.signals.progress.finish.emit(False)
self.signals.result.emit(self.rgame, self.dst_path)