Commit inicial
This commit is contained in:
+156
@@ -0,0 +1,156 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user