2023-01-31 03:00:27 +13:00
|
|
|
import os
|
|
|
|
import shutil
|
2023-02-04 04:25:02 +13:00
|
|
|
from logging import getLogger
|
2023-01-31 03:00:27 +13:00
|
|
|
|
2023-02-01 02:43:26 +13:00
|
|
|
from PyQt5.QtCore import pyqtSignal, QObject
|
2023-01-31 03:00:27 +13:00
|
|
|
from legendary.lfs.utils import validate_files
|
2023-02-16 03:59:33 +13:00
|
|
|
from legendary.models.game import VerifyResult
|
2023-01-31 03:00:27 +13:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
from rare.models.game import RareGame
|
2023-02-01 00:59:22 +13:00
|
|
|
from rare.lgndr.core import LegendaryCore
|
2023-02-03 21:55:56 +13:00
|
|
|
from .worker import QueueWorker, QueueWorkerInfo
|
2023-02-01 00:59:22 +13:00
|
|
|
|
2023-02-04 04:25:02 +13:00
|
|
|
logger = getLogger("MoveWorker")
|
2023-01-31 03:00:27 +13:00
|
|
|
|
2023-03-17 01:04:12 +13:00
|
|
|
|
2023-02-01 02:43:26 +13:00
|
|
|
class MoveWorker(QueueWorker):
|
2023-01-31 03:00:27 +13:00
|
|
|
class Signals(QObject):
|
2023-03-17 01:04:12 +13:00
|
|
|
# int: percentage, object: source size, object: dest size
|
|
|
|
progress = pyqtSignal(RareGame, int, object, object)
|
2023-02-16 03:59:33 +13:00
|
|
|
# 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):
|
2023-01-31 03:00:27 +13:00
|
|
|
super(MoveWorker, self).__init__()
|
|
|
|
self.signals = MoveWorker.Signals()
|
|
|
|
self.core = core
|
2023-02-16 03:59:33 +13:00
|
|
|
self.rgame = rgame
|
|
|
|
self.dst_path = dst_path
|
|
|
|
self.dst_exists = dst_exists
|
2023-01-31 03:00:27 +13:00
|
|
|
|
2023-02-03 21:55:56 +13:00
|
|
|
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
|
|
|
|
)
|
2023-02-01 02:43:26 +13:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
def progress(self, src_size, dst_size):
|
|
|
|
progress = dst_size * 100 // src_size
|
|
|
|
self.rgame.signals.progress.update.emit(progress)
|
2023-03-17 01:04:12 +13:00
|
|
|
self.signals.progress.emit(self.rgame, progress, src_size, dst_size)
|
2023-02-16 03:59:33 +13:00
|
|
|
|
2023-02-01 00:59:22 +13:00
|
|
|
def run_real(self):
|
2023-02-16 03:59:33 +13:00
|
|
|
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)
|
2023-01-31 03:00:27 +13:00
|
|
|
|
|
|
|
shutil.copytree(
|
2023-02-16 03:59:33 +13:00
|
|
|
self.rgame.install_path,
|
|
|
|
self.dst_path,
|
|
|
|
copy_function=copy_with_progress,
|
2023-01-31 03:00:27 +13:00
|
|
|
dirs_exist_ok=True,
|
|
|
|
)
|
2023-02-16 03:59:33 +13:00
|
|
|
shutil.rmtree(self.rgame.install_path)
|
|
|
|
|
2023-01-31 03:00:27 +13:00
|
|
|
else:
|
2023-02-16 03:59:33 +13:00
|
|
|
manifest_data, _ = self.core.get_installed_manifest(self.rgame.app_name)
|
2023-01-31 03:00:27 +13:00
|
|
|
manifest = self.core.load_manifest(manifest_data)
|
|
|
|
files = sorted(
|
|
|
|
manifest.file_manifest_list.elements,
|
|
|
|
key=lambda a: a.filename.lower(),
|
|
|
|
)
|
2023-02-16 03:59:33 +13:00
|
|
|
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
|
2023-01-31 03:00:27 +13:00
|
|
|
|
|
|
|
# recreate dir structure
|
|
|
|
shutil.copytree(
|
2023-02-16 03:59:33 +13:00
|
|
|
self.rgame.install_path,
|
|
|
|
self.dst_path,
|
|
|
|
copy_function=copy_dir_structure,
|
2023-01-31 03:00:27 +13:00
|
|
|
dirs_exist_ok=True,
|
|
|
|
)
|
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
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:
|
2023-01-31 03:00:27 +13:00
|
|
|
try:
|
2023-02-16 03:59:33 +13:00
|
|
|
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))
|
2023-01-31 03:00:27 +13:00
|
|
|
return
|
2023-02-16 03:59:33 +13:00
|
|
|
else:
|
2023-01-31 03:00:27 +13:00
|
|
|
logger.warning(f"Copying file {src_path} to {dst_path} failed")
|
2023-02-16 03:59:33 +13:00
|
|
|
self.progress(total_size, dst_size)
|
2023-01-31 03:00:27 +13:00
|
|
|
else:
|
|
|
|
logger.warning(
|
2023-02-16 03:59:33 +13:00
|
|
|
f"Source dir does not have file {src_path}. File will be missing in the destination dir."
|
2023-01-31 03:00:27 +13:00
|
|
|
)
|
2023-02-16 03:59:33 +13:00
|
|
|
self.rgame.needs_verification = True
|
|
|
|
shutil.rmtree(self.rgame.install_path)
|
|
|
|
|
|
|
|
self.rgame.install_path = self.dst_path
|
2023-01-31 03:00:27 +13:00
|
|
|
|
2023-02-16 03:59:33 +13:00
|
|
|
self.rgame.signals.progress.finish.emit(False)
|
|
|
|
self.signals.result.emit(self.rgame, self.dst_path)
|