diff --git a/core/summary.py b/core/summary.py index 827ecb7..aae921b 100644 --- a/core/summary.py +++ b/core/summary.py @@ -1,5 +1,8 @@ # core/summary.py +import datetime +import os + from core.result import ComicResult _CORRUPT_PATTERNS = ("BadRarFile", "BadZipFile", "corrupto", "Failed to read") @@ -21,7 +24,7 @@ class SummaryCollector: for pattern in _CORRUPT_PATTERNS ) - def render(self) -> str: + def render(self, _max=10) -> str: results = self._results total = len(results) if total == 0: @@ -93,4 +96,67 @@ class SummaryCollector: all_errs = [e for s in r.steps for e in s.errors] lines.append(f" {r.original_path} — {'; '.join(all_errs)}") + warn_categories = self._warning_categories(results) + if warn_categories: + lines.append("") + lines.append("Advertencias por categoría:") + for label, entries in warn_categories: + lines.append(f"\n {label} ({len(entries)}):") + shown = entries[:_max] if _max is not None else entries + for path, msg, annotation in shown: + suffix = f" ({annotation})" if annotation else "" + if msg: + lines.append(f" {path} — {msg}{suffix}") + else: + lines.append(f" {path}{suffix}") + if _max is not None and len(entries) > _max: + lines.append(f" ... y {len(entries) - _max} más") + return "\n".join(lines) + + def full_log(self) -> str: + parts = [self.render(_max=None)] + issues = [r for r in self._results if r.has_issues()] + if issues: + parts.append("") + parts.append("") + parts.append("Detalle por fichero:") + parts.append(_BORDER) + for r in issues: + parts.append(r.full_report()) + parts.append("") + return "\n".join(parts) + + def _warning_categories(self, results): + categories = [ + ("Extensión incorrecta", "validate", lambda w: "Extensión incorrecta" in w, ["convert"], "convertido"), + ("Basura detectada", "check_trash", lambda w: True, ["clean"], "limpiado"), + ("Numeración de páginas", "check_page_numbering", lambda w: True, ["normalize_pages"], "renumerado"), + ("Imágenes mezcladas", "check_image_extensions", lambda w: True, ["normalize_images", "convert_images"], "normalizado"), + ("Sin ComicInfo.xml", "check_comicinfo", lambda w: True, [], None), + ] + output = [] + for label, step_name, predicate, resolver_steps, fix_label in categories: + entries = [] + for r in results: + msgs = [ + w for s in r.steps + if s.step == step_name + for w in s.warnings + if predicate(w) + ] + if msgs: + resolved = bool(resolver_steps) and any( + s.step in resolver_steps and s.changed for s in r.steps + ) + annotation = fix_label if resolved else "" + if step_name == "check_comicinfo": + entries.append((r.original_path, "", annotation)) + elif step_name == "check_trash": + items = [w.removeprefix("Basura detectada: ") for w in msgs] + entries.append((r.original_path, ", ".join(items), annotation)) + else: + entries.append((r.original_path, msgs[0], annotation)) + if entries: + output.append((label, entries)) + return output diff --git a/main.py b/main.py index 7ba4b01..4431e79 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,9 @@ # main.py import argparse +import datetime +import os +import shutil from core.scanner import find_comic_files from core.pipeline import Pipeline from core.summary import SummaryCollector @@ -8,6 +11,26 @@ from core.collision import CollisionPolicy _COL_W = 30 _SEP = "─" * 44 +_BOLD = "\033[1m" +_DIM = "\033[2m" +_RST = "\033[0m" + + +def _human_size(size: int) -> str: + if size >= 1_073_741_824: + return f"{size / 1_073_741_824:.1f} GB" + if size >= 1_048_576: + return f"{size / 1_048_576:.1f} MB" + return f"{size / 1024:.1f} KB" + + +def _print_file_header(path: str, i: int = 0, total: int = 0) -> None: + name = os.path.basename(path) + size_str = _human_size(os.path.getsize(path)) + counter = f"{_DIM}[{i}/{total}]{_RST} " if i and total else "" + term_w = shutil.get_terminal_size(fallback=(80, 24)).columns + print(f"\n{'─' * term_w}") + print(f" {counter}{_BOLD}{name}{_RST} {_DIM}({size_str}){_RST}") def _print_preview(step: str, preview: dict, formato: str) -> None: @@ -44,6 +67,16 @@ def _print_preview(step: str, preview: dict, formato: str) -> None: print(f"Formato final del archivo: {fmt}") +def _save_log(collector, script_dir): + log_dir = os.path.join(script_dir, "validaciones") + os.makedirs(log_dir, exist_ok=True) + ts = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + log_path = os.path.join(log_dir, f"{ts}.log") + with open(log_path, "w", encoding="utf-8") as fh: + fh.write(collector.full_log()) + return log_path + + def make_confirm_fn(args): def confirm_fn(step: str, preview: dict) -> bool: if step == "convert": @@ -93,13 +126,24 @@ def main(): if args.validar: pipeline = Pipeline(steps=[]) collector = SummaryCollector() - for f in comic_files: - result = pipeline.run(f) - collector.add(result) - if result.has_issues(): - print(result.full_report()) + total = len(comic_files) + try: + for i, f in enumerate(comic_files, 1): + size_str = _human_size(os.path.getsize(f)) + name = os.path.basename(f) + term_w = shutil.get_terminal_size(fallback=(80, 24)).columns + line = f" [{i}/{total}] {name} ({size_str})" + print(f"\r{line[:term_w]:<{term_w}}", end="", flush=True) + result = pipeline.run(f) + collector.add(result) + except KeyboardInterrupt: + print("\n (interrumpido)") + finally: + if collector._results: print() - print(collector.render()) + print(collector.render()) + log_path = _save_log(collector, os.path.dirname(os.path.abspath(__file__))) + print(f" Log completo: {log_path}") return # --- Construir steps --- @@ -125,12 +169,20 @@ def main(): collision_policy=collision, ) collector = SummaryCollector() - for f in comic_files: - print(f"\n=== {f} ===") - result = pipeline.run(f, confirm_fn=confirm_fn) - print(result.summary()) - collector.add(result) - print(f"\n{collector.render()}") + try: + total = len(comic_files) + for i, f in enumerate(comic_files, 1): + _print_file_header(f, i, total) + result = pipeline.run(f, confirm_fn=confirm_fn) + print(result.summary()) + collector.add(result) + except KeyboardInterrupt: + print("\n (interrumpido)") + finally: + if collector._results: + print(f"\n{collector.render()}") + log_path = _save_log(collector, os.path.dirname(os.path.abspath(__file__))) + print(f" Log completo: {log_path}") if __name__ == "__main__":