"""Copy-based migration strategy""" import shutil from pathlib import Path from typing import Optional import os from ..shared.logger import ProgressLogger class CopyMigrationStrategy: """Copy files to destination with verification""" def __init__( self, logger: Optional[ProgressLogger] = None, preserve_metadata: bool = True, verify_checksums: bool = True ): """Initialize copy migration strategy Args: logger: Optional progress logger preserve_metadata: Whether to preserve file metadata verify_checksums: Whether to verify checksums after copy """ self.logger = logger self.preserve_metadata = preserve_metadata self.verify_checksums = verify_checksums def migrate( self, source: Path, destination: Path, verify: bool = True ) -> bool: """Migrate file by copying Args: source: Source file path destination: Destination file path verify: Whether to verify the operation Returns: True if migration successful """ if not source.exists(): if self.logger: self.logger.error(f"Source file does not exist: {source}") return False # Create destination directory destination.parent.mkdir(parents=True, exist_ok=True) try: # Copy file if self.preserve_metadata: shutil.copy2(source, destination) else: shutil.copy(source, destination) # Verify if requested 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: """Verify copied file Args: source: Source file path destination: Destination file path Returns: True if verification successful """ # Check size source_size = source.stat().st_size dest_size = destination.stat().st_size if source_size != dest_size: return False # Compare checksums for files larger than 1MB 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 # For small files, compare content directly 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: """Check if migration is possible Args: source: Source file path destination: Destination file path Returns: True if migration is possible """ if not source.exists(): return False # Check if destination directory is writable dest_dir = destination.parent if dest_dir.exists(): return os.access(dest_dir, os.W_OK) # Check if parent directory exists and is writable 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: """Estimate migration time in seconds Args: source: Source file path Returns: Estimated time in seconds """ if not source.exists(): return 0.0 size = source.stat().st_size # Estimate based on typical copy speed (100 MB/s) typical_speed = 100 * 1024 * 1024 # bytes per second return size / typical_speed def cleanup(self, source: Path) -> bool: """Cleanup source file after successful migration Args: source: Source file path Returns: True if cleanup successful """ 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): """Fast copy strategy without verification""" def __init__(self, logger: Optional[ProgressLogger] = None): """Initialize fast copy strategy""" super().__init__( logger=logger, preserve_metadata=True, verify_checksums=False ) class SafeCopyStrategy(CopyMigrationStrategy): """Safe copy strategy with full verification""" def __init__(self, logger: Optional[ProgressLogger] = None): """Initialize safe copy strategy""" super().__init__( logger=logger, preserve_metadata=True, verify_checksums=True ) class ReferenceCopyStrategy: """Create reference copy using reflinks (CoW) if supported""" def __init__(self, logger: Optional[ProgressLogger] = None): """Initialize reflink copy strategy""" self.logger = logger def migrate( self, source: Path, destination: Path, verify: bool = True ) -> bool: """Migrate using reflink (copy-on-write) Args: source: Source file path destination: Destination file path verify: Whether to verify the operation Returns: True if migration successful """ if not source.exists(): if self.logger: self.logger.error(f"Source file does not exist: {source}") return False # Create destination directory destination.parent.mkdir(parents=True, exist_ok=True) try: # Try reflink copy (works on btrfs, xfs, etc.) import subprocess result = subprocess.run( ['cp', '--reflink=auto', str(source), str(destination)], capture_output=True, check=False ) if result.returncode != 0: # Fallback to regular copy 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: """Check if migration is possible""" 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: """Estimate migration time (reflinks are fast)""" return 0.1 # Reflinks are nearly instant def cleanup(self, source: Path) -> bool: """Cleanup source file""" 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