#!/usr/bin/env python3 """Text-based progress bar utilities for consistent display across all downloads.""" import sys from helper.logger import log, debug def format_progress_bar(current: int, total: int, width: int = 40, label: str = "") -> str: """Create a text-based progress bar. Args: current: Current progress (bytes/items) total: Total to complete (bytes/items) width: Width of the bar in characters (default 40) label: Optional label prefix Returns: Formatted progress bar string Examples: format_progress_bar(50, 100) # Returns: "[████████████████░░░░░░░░░░░░░░░░░░░░] 50.0%" format_progress_bar(256*1024*1024, 1024*1024*1024, label="download.zip") # Returns: "download.zip: [████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 25.0%" """ if total <= 0: percentage = 0 filled = 0 else: percentage = (current / total) * 100 filled = int((current / total) * width) # Create bar: filled blocks + empty blocks bar = "█" * filled + "░" * (width - filled) # Format percentage pct_str = f"{percentage:.1f}%" # Build result if label: result = f"{label}: [{bar}] {pct_str}" else: result = f"[{bar}] {pct_str}" return result def format_size(bytes_val: float) -> str: """Format bytes to human-readable size. Examples: format_size(1024) -> "1.00 KB" format_size(1024*1024) -> "1.00 MB" format_size(1024*1024*1024) -> "1.00 GB" """ for unit in ['B', 'KB', 'MB', 'GB', 'TB']: if bytes_val < 1024: return f"{bytes_val:.2f} {unit}" bytes_val /= 1024 return f"{bytes_val:.2f} PB" def format_download_status(filename: str, current: int, total: int, speed: float = 0) -> str: """Format download status with progress bar and details. Args: filename: Name of file being downloaded current: Current bytes downloaded total: Total file size speed: Download speed in bytes/sec Returns: Formatted status line Examples: format_download_status("movie.mkv", 512*1024*1024, 2*1024*1024*1024, 10*1024*1024) # Returns: "movie.mkv: [████████████░░░░░░░░░░░░░░░░░░░░░░░░░░] 25.0% (512.00 MB / 2.00 GB @ 10.00 MB/s)" """ bar = format_progress_bar(current, total, width=30) size_current = format_size(current) size_total = format_size(total) if speed > 0: speed_str = f" @ {format_size(speed)}/s" else: speed_str = "" return f"{bar} ({size_current} / {size_total}{speed_str})" def print_progress(filename: str, current: int, total: int, speed: float = 0, end: str = "\r") -> None: """Print download progress to stderr (doesn't interfere with piped output). Args: filename: File being downloaded current: Current bytes total: Total bytes speed: Speed in bytes/sec end: Line ending (default "\r" for overwriting, use "\n" for final) """ status = format_download_status(filename, current, total, speed) debug(status, end=end, flush=True) def print_final_progress(filename: str, total: int, elapsed: float) -> None: """Print final progress line (100%) with time elapsed. Args: filename: File that was downloaded total: Total size elapsed: Time elapsed in seconds """ bar = format_progress_bar(total, total, width=30) size_str = format_size(total) # Format elapsed time if elapsed < 60: time_str = f"{elapsed:.1f}s" elif elapsed < 3600: minutes = elapsed / 60 time_str = f"{minutes:.1f}m" else: hours = elapsed / 3600 time_str = f"{hours:.2f}h" debug(f"{bar} ({size_str}) - {time_str}") if __name__ == "__main__": # Demo import time log("Progress Bar Demo:", file=sys.stderr) # Demo 1: Simple progress for i in range(101): print_progress("demo.bin", i * 10 * 1024 * 1024, 1024 * 1024 * 1024) time.sleep(0.02) print_final_progress("demo.bin", 1024 * 1024 * 1024, 2.0) log()