import shutil from pathlib import Path from typing import Optional import os from ..shared.logger import ProgressLogger class CopyMigrationStrategy: def __init__(self, logger: Optional[ProgressLogger]=None, preserve_metadata: bool=True, verify_checksums: bool=True): self.logger = logger self.preserve_metadata = preserve_metadata self.verify_checksums = verify_checksums def migrate(self, source: Path, destination: Path, verify: bool=True) -> bool: if not source.exists(): if self.logger: self.logger.error(f'Source file does not exist: {source}') return False destination.parent.mkdir(parents=True, exist_ok=True) try: if self.preserve_metadata: shutil.copy2(source, destination) else: shutil.copy(source, destination) if verify and self.verify_checksums: if not self._verify_copy(source, destination): if self.logger: self.logger.error(f'Verification failed: {source} -> {destination}') destination.unlink() return False return True except Exception as e: if self.logger: self.logger.error(f'Copy failed: {source} -> {destination}: {e}') return False def _verify_copy(self, source: Path, destination: Path) -> bool: source_size = source.stat().st_size dest_size = destination.stat().st_size if source_size != dest_size: return False if source_size > 1024 * 1024: from ..deduplication.chunker import hash_file source_hash = hash_file(source) dest_hash = hash_file(destination) return source_hash == dest_hash with open(source, 'rb') as f1, open(destination, 'rb') as f2: return f1.read() == f2.read() def can_migrate(self, source: Path, destination: Path) -> bool: if not source.exists(): return False dest_dir = destination.parent if dest_dir.exists(): return os.access(dest_dir, os.W_OK) parent = dest_dir.parent while not parent.exists() and parent != parent.parent: parent = parent.parent return parent.exists() and os.access(parent, os.W_OK) def estimate_time(self, source: Path) -> float: if not source.exists(): return 0.0 size = source.stat().st_size typical_speed = 100 * 1024 * 1024 return size / typical_speed def cleanup(self, source: Path) -> bool: try: if source.exists(): source.unlink() return True except Exception as e: if self.logger: self.logger.warning(f'Failed to cleanup {source}: {e}') return False class FastCopyStrategy(CopyMigrationStrategy): def __init__(self, logger: Optional[ProgressLogger]=None): super().__init__(logger=logger, preserve_metadata=True, verify_checksums=False) class SafeCopyStrategy(CopyMigrationStrategy): def __init__(self, logger: Optional[ProgressLogger]=None): super().__init__(logger=logger, preserve_metadata=True, verify_checksums=True) class ReferenceCopyStrategy: def __init__(self, logger: Optional[ProgressLogger]=None): self.logger = logger def migrate(self, source: Path, destination: Path, verify: bool=True) -> bool: if not source.exists(): if self.logger: self.logger.error(f'Source file does not exist: {source}') return False destination.parent.mkdir(parents=True, exist_ok=True) try: import subprocess result = subprocess.run(['cp', '--reflink=auto', str(source), str(destination)], capture_output=True, check=False) if result.returncode != 0: shutil.copy2(source, destination) return True except Exception as e: if self.logger: self.logger.error(f'Reflink copy failed: {source} -> {destination}: {e}') return False def can_migrate(self, source: Path, destination: Path) -> bool: if not source.exists(): return False dest_dir = destination.parent if dest_dir.exists(): return os.access(dest_dir, os.W_OK) return True def estimate_time(self, source: Path) -> float: return 0.1 def cleanup(self, source: Path) -> bool: try: if source.exists(): source.unlink() return True except Exception as e: if self.logger: self.logger.warning(f'Failed to cleanup {source}: {e}') return False