157 lines
4.8 KiB
Python
157 lines
4.8 KiB
Python
"""Seguimiento y presentación del progreso de la migración en consola."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import time
|
|
|
|
|
|
def format_bytes(n: float) -> str:
|
|
units = ["B", "KB", "MB", "GB", "TB"]
|
|
size = float(max(n, 0))
|
|
for unit in units[:-1]:
|
|
if size < 1024:
|
|
return f"{size:.1f} {unit}"
|
|
size /= 1024
|
|
return f"{size:.1f} {units[-1]}"
|
|
|
|
|
|
def format_duration(seconds: float) -> str:
|
|
seconds = int(max(0, seconds))
|
|
h, rem = divmod(seconds, 3600)
|
|
m, s = divmod(rem, 60)
|
|
return f"{h:02d}:{m:02d}:{s:02d}"
|
|
|
|
|
|
def compute_eta(elapsed: float, done: int, total: int) -> float:
|
|
"""Tiempo estimado restante en segundos, dado lo hecho hasta ahora."""
|
|
if done <= 0 or total <= 0 or elapsed <= 0:
|
|
return 0.0
|
|
rate = done / elapsed
|
|
if rate <= 0:
|
|
return 0.0
|
|
remaining = max(total - done, 0)
|
|
return remaining / rate
|
|
|
|
|
|
class ProgressTracker:
|
|
def __init__(self, stream=None):
|
|
self.stream = stream or sys.stdout
|
|
self.total_folders = 0
|
|
self.folders_done = 0
|
|
self.current_folder = ""
|
|
self.total_messages = 0
|
|
self.messages_processed = 0
|
|
self.messages_copied = 0
|
|
self.messages_skipped = 0
|
|
self.messages_errors = 0
|
|
self.total_bytes = 0
|
|
self.bytes_transferred = 0
|
|
self.start_time = None
|
|
self._last_render = 0.0
|
|
|
|
def set_total_folders(self, n: int) -> None:
|
|
self.total_folders = n
|
|
|
|
def set_total_messages(self, n: int) -> None:
|
|
self.total_messages = n
|
|
|
|
def set_total_bytes(self, n: int) -> None:
|
|
self.total_bytes = n
|
|
|
|
def set_current_folder(self, name: str) -> None:
|
|
self.current_folder = name
|
|
|
|
def folder_done(self) -> None:
|
|
self.folders_done += 1
|
|
|
|
def start(self) -> None:
|
|
self.start_time = time.monotonic()
|
|
|
|
def message_copied(self, size: int) -> None:
|
|
self.messages_processed += 1
|
|
self.messages_copied += 1
|
|
self.bytes_transferred += size
|
|
self._maybe_render()
|
|
|
|
def message_error(self) -> None:
|
|
self.messages_processed += 1
|
|
self.messages_errors += 1
|
|
self._maybe_render()
|
|
|
|
def elapsed(self) -> float:
|
|
if self.start_time is None:
|
|
return 0.0
|
|
return time.monotonic() - self.start_time
|
|
|
|
def speed_bps(self) -> float:
|
|
e = self.elapsed()
|
|
return self.bytes_transferred / e if e > 0 else 0.0
|
|
|
|
def eta_seconds(self) -> float:
|
|
return compute_eta(self.elapsed(), self.messages_processed, self.total_messages)
|
|
|
|
def _maybe_render(self, force: bool = False) -> None:
|
|
now = time.monotonic()
|
|
if not force and (now - self._last_render) < 0.5:
|
|
return
|
|
self._last_render = now
|
|
self.render()
|
|
|
|
def render(self) -> None:
|
|
pct = (self.messages_processed / self.total_messages * 100) if self.total_messages else 100.0
|
|
pct = min(pct, 100.0)
|
|
bar_width = 24
|
|
filled = int(bar_width * pct / 100)
|
|
bar = "█" * filled + "░" * (bar_width - filled)
|
|
folder = (self.current_folder or "")[:20]
|
|
line = (
|
|
f"\rCarpeta: {folder:<20} [{bar}] {pct:5.1f}% "
|
|
f"Msjs: {self.messages_processed}/{self.total_messages} "
|
|
f"Copiados: {self.messages_copied} Omitidos: {self.messages_skipped} "
|
|
f"Errores: {self.messages_errors} "
|
|
f"{format_bytes(self.bytes_transferred)}/{format_bytes(self.total_bytes)} "
|
|
f"{format_bytes(self.speed_bps())}/s "
|
|
f"T: {format_duration(self.elapsed())} ETA: {format_duration(self.eta_seconds())}"
|
|
)
|
|
self.stream.write(line)
|
|
self.stream.flush()
|
|
|
|
def stop(self) -> None:
|
|
self.render()
|
|
self.stream.write("\n")
|
|
self.stream.flush()
|
|
|
|
def print_summary(self, host_a: str, host_b: str, logger) -> None:
|
|
lines = [
|
|
"=" * 40,
|
|
"MIGRACIÓN TERMINADA",
|
|
"=" * 40,
|
|
"",
|
|
"Servidor origen:",
|
|
host_a,
|
|
"",
|
|
"Servidor destino:",
|
|
host_b,
|
|
"",
|
|
f"Carpetas procesadas: {self.folders_done}/{self.total_folders}",
|
|
"",
|
|
f"Mensajes nuevos detectados: {self.total_messages}",
|
|
f"Mensajes copiados: {self.messages_copied}",
|
|
f"Mensajes existentes: {self.messages_skipped}",
|
|
f"Mensajes con error: {self.messages_errors}",
|
|
"",
|
|
"Datos transferidos:",
|
|
format_bytes(self.bytes_transferred),
|
|
"",
|
|
"Tiempo total:",
|
|
format_duration(self.elapsed()),
|
|
"",
|
|
"Velocidad promedio:",
|
|
f"{format_bytes(self.speed_bps())}/s",
|
|
"=" * 40,
|
|
]
|
|
text = "\n".join(lines)
|
|
print(text)
|
|
logger.info("Resumen final:\n" + text)
|