"""Dynamic progress logger with formatting utilities""" import sys import logging from typing import Optional from datetime import datetime from pathlib import Path def format_size(bytes_size: int) -> str: """Format bytes to human-readable size string Args: bytes_size: Size in bytes Returns: Human-readable size string (e.g., "1.5 GB", "234.5 MB") """ for unit in ['B', 'KB', 'MB', 'GB', 'TB', 'PB']: if bytes_size < 1024.0: return f"{bytes_size:.1f} {unit}" bytes_size /= 1024.0 return f"{bytes_size:.1f} EB" def format_rate(bytes_per_second: float) -> str: """Format transfer rate to human-readable string Args: bytes_per_second: Transfer rate in bytes per second Returns: Human-readable rate string (e.g., "125.3 MB/s") """ return f"{format_size(int(bytes_per_second))}/s" def format_time(seconds: float) -> str: """Format seconds to human-readable time string Args: seconds: Time in seconds Returns: Human-readable time string (e.g., "2h 34m 12s", "45m 23s", "12s") """ if seconds < 60: return f"{int(seconds)}s" elif seconds < 3600: minutes = int(seconds // 60) secs = int(seconds % 60) return f"{minutes}m {secs}s" else: hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = int(seconds % 60) return f"{hours}h {minutes}m {secs}s" class ProgressLogger: """Dynamic progress logger with real-time statistics""" def __init__( self, name: str = "defrag", level: int = logging.INFO, log_file: Optional[Path] = None, console_output: bool = True ): """Initialize progress logger Args: name: Logger name level: Logging level log_file: Optional log file path console_output: Whether to output to console """ self.logger = logging.getLogger(name) self.logger.setLevel(level) self.logger.handlers.clear() # Create formatter formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # Add console handler if console_output: console_handler = logging.StreamHandler(sys.stdout) console_handler.setLevel(level) console_handler.setFormatter(formatter) self.logger.addHandler(console_handler) # Add file handler if log_file: log_file.parent.mkdir(parents=True, exist_ok=True) file_handler = logging.FileHandler(log_file) file_handler.setLevel(level) file_handler.setFormatter(formatter) self.logger.addHandler(file_handler) self._last_progress_line = "" def info(self, message: str) -> None: """Log info message""" self.logger.info(message) def warning(self, message: str) -> None: """Log warning message""" self.logger.warning(message) def error(self, message: str) -> None: """Log error message""" self.logger.error(message) def debug(self, message: str) -> None: """Log debug message""" self.logger.debug(message) def critical(self, message: str) -> None: """Log critical message""" self.logger.critical(message) def progress( self, current: int, total: int, prefix: str = "", suffix: str = "", bytes_processed: Optional[int] = None, elapsed_seconds: Optional[float] = None ) -> None: """Log progress with dynamic statistics Args: current: Current progress count total: Total count prefix: Prefix message suffix: Suffix message bytes_processed: Optional bytes processed for rate calculation elapsed_seconds: Optional elapsed time for rate calculation """ if total == 0: percent = 0.0 else: percent = (current / total) * 100 progress_msg = f"{prefix} [{current}/{total}] {percent:.1f}%" if bytes_processed is not None and elapsed_seconds is not None and elapsed_seconds > 0: rate = bytes_per_second = bytes_processed / elapsed_seconds progress_msg += f" | {format_size(bytes_processed)} @ {format_rate(rate)}" # Estimate time remaining if current > 0: estimated_total_seconds = (elapsed_seconds / current) * total remaining_seconds = estimated_total_seconds - elapsed_seconds progress_msg += f" | ETA: {format_time(remaining_seconds)}" if suffix: progress_msg += f" | {suffix}" self.info(progress_msg) def section(self, title: str) -> None: """Log section header Args: title: Section title """ separator = "=" * 60 self.info(separator) self.info(f" {title}") self.info(separator) def subsection(self, title: str) -> None: """Log subsection header Args: title: Subsection title """ self.info(f"\n--- {title} ---") def create_logger( name: str = "defrag", level: str = "INFO", log_file: Optional[Path] = None, console_output: bool = True ) -> ProgressLogger: """Create and configure a progress logger Args: name: Logger name level: Logging level as string log_file: Optional log file path console_output: Whether to output to console Returns: Configured ProgressLogger instance """ level_map = { 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL } log_level = level_map.get(level.upper(), logging.INFO) return ProgressLogger( name=name, level=log_level, log_file=log_file, console_output=console_output )