2025-11-25 20:09:33 -08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""Text-based progress bar utilities for consistent display across all downloads."""
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
|
2025-12-13 12:09:50 -08:00
|
|
|
from SYS.logger import log
|
2025-11-25 20:09:33 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_progress_bar(current: int, total: int, width: int = 40, label: str = "") -> str:
|
|
|
|
|
"""Create a text-based progress bar.
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
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
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
Returns:
|
|
|
|
|
Formatted progress bar string
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
Examples:
|
|
|
|
|
format_progress_bar(50, 100)
|
|
|
|
|
# Returns: "[████████████████░░░░░░░░░░░░░░░░░░░░] 50.0%"
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
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)
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
bar = "█" * filled + "░" * (width - filled)
|
|
|
|
|
pct_str = f"{percentage:.1f}%"
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
if label:
|
|
|
|
|
result = f"{label}: [{bar}] {pct_str}"
|
|
|
|
|
else:
|
|
|
|
|
result = f"[{bar}] {pct_str}"
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_size(bytes_val: float) -> str:
|
2025-12-11 19:04:02 -08:00
|
|
|
"""Format bytes to human-readable size."""
|
2025-11-25 20:09:33 -08:00
|
|
|
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:
|
2025-12-11 19:04:02 -08:00
|
|
|
"""Format download status with progress bar and details."""
|
2025-11-25 20:09:33 -08:00
|
|
|
bar = format_progress_bar(current, total, width=30)
|
|
|
|
|
size_current = format_size(current)
|
|
|
|
|
size_total = format_size(total)
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
if speed > 0:
|
|
|
|
|
speed_str = f" @ {format_size(speed)}/s"
|
|
|
|
|
else:
|
|
|
|
|
speed_str = ""
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
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:
|
2025-12-11 19:04:02 -08:00
|
|
|
"""Print download progress to stderr (doesn't interfere with piped output)."""
|
2025-11-25 20:09:33 -08:00
|
|
|
status = format_download_status(filename, current, total, speed)
|
2025-12-13 12:09:50 -08:00
|
|
|
print(status, file=sys.stderr, end=end, flush=True)
|
2025-11-25 20:09:33 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_final_progress(filename: str, total: int, elapsed: float) -> None:
|
2025-12-11 19:04:02 -08:00
|
|
|
"""Print final progress line (100%) with time elapsed."""
|
2025-11-25 20:09:33 -08:00
|
|
|
bar = format_progress_bar(total, total, width=30)
|
|
|
|
|
size_str = format_size(total)
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
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"
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-12-13 12:09:50 -08:00
|
|
|
print(f"{bar} ({size_str}) - {time_str}", file=sys.stderr, flush=True)
|
2025-11-25 20:09:33 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
import time
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
log("Progress Bar Demo:", file=sys.stderr)
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
for i in range(101):
|
|
|
|
|
print_progress("demo.bin", i * 10 * 1024 * 1024, 1024 * 1024 * 1024)
|
|
|
|
|
time.sleep(0.02)
|
2025-12-11 19:04:02 -08:00
|
|
|
|
2025-11-25 20:09:33 -08:00
|
|
|
print_final_progress("demo.bin", 1024 * 1024 * 1024, 2.0)
|
|
|
|
|
log()
|