144 lines
4.4 KiB
Python
144 lines
4.4 KiB
Python
#!/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()
|