BlueProject
Published on

Media Ingest — rendimiento: no hashear todo, no pintar de más

Authors
  • avatar
    Name
    Mateo Londoño Toro
    Twitter

Después de Temporal → Media el cuello de botella no era copiar: era escanear miles de fotos y repintar Qt en cada señal del worker. El working tree de Media Ingest cierra eso en tres frentes.

Este corte es bitácora del proyecto Media Ingest (Windows USB + macOS Fase 1: carpeta local, Organizar, Historial, Limpieza).

1) Hash solo cuando el tamaño choca

scan_media_folder (domain/folder_cleanup.py) recorre con os.walk, agrupa por tamaño y solo entonces llama a hash_file.

walk (stat) → reglas de nombre (IMG_ vs IMG_E, $I*, caché)
            → _hash_needed: SHA-256 si size count ≥ 2
            → clasificar ($R*, staging, hash dup)

Un HEIC único de 2.4 MiB no se lee entero. Dos archivos del mismo byte-count sí: ahí el hash decide si es Recuva $R*, staging ya archivado, o duplicado canónico.

Quién se conserva (_keep_score): archivo sobre staging, ruta con YYYY/MM/DD, no Recuva, original sobre IMG_E, no IMG_1234_2, path más corto. Nunca ambos original y editado: política por defecto conservar IMG_, cuarentenar IMG_E.

Progreso en dos fases (walk cada 200 ficheros, hash con bytes_hashed) para no fingir un % sobre trabajo que no se va a hacer.

Reglas baratas primero (sin I/O de contenido): Recuva $I*, node_modules / .pnpm-store / .cache, IMG_E#### si existe IMG_#### misma carpeta + extensión. Live Photo .MOV (tope 20 MiB) y sidecars .AAE se listan sin selección por defecto: calidad / sidecar, no duplicado.

4K → 1080p está fuera de alcance: eso es calidad, no duplicado.

2) El hilo GUI no es un sink de cada archivo

UiCoalesce / UiBatchQueue (presentation/widgets/ui_coalesce.py): el worker no aplica cada señal al instante. Se queda el último estado (barra, caption) o una cola corta de tiles, y un QTimer drena. Qt puede interpolar paint e input. Patrón de la propia doc de Qt: no pintar cada tick del worker.

Animación decorativa apagada por flag:

# presentation/widgets/__init__.py
ANIMATIONS_ENABLED = False

El canvas de transferencia, los fades de preview y el barrido del scan siguen en el código; no arrancan timers ni QGraphicsOpacityEffect hasta que el flag vuelva a True. En un Mac con discos Mounty (~/.mounty/Disco C_C) eso se nota más que el Fluent en sí.

Limpieza no usa TransferStage. Spec: la tabla permanece; progreso inline + DiskScanIndicator. Inspeccionar ≠ transferir. Peer-busy solo durante scan / cuarentena / purge / restore; no dejar la vista “ocupada” entre escanear y Limpiar.

Tope de tabla: 400 filas en carpeta, 80 en disco. El plan completo vive en el resultado; la UI no materializa decenas de miles de QTableWidgetItem.

3) No trabajar placeholders ni cuota inventada

  • iCloud: icloud_skip_reason omite stubs .icloud en Sync y Organizar (warning en log, no copia silenciosa). Prefiere origen/destino ya descargados.
  • Papelera macOS: no hay API de cuota tipo BitBucket; el preflight marca quota_checked=False y trata el vaciado como riesgo. Vaciar sigue siendo la operación que el incidente 2026-07-31 enseñó a no hacer a la ligera.
  • Volúmenes Darwin: list_volumes incluye /, /Volumes y mounts Mounty, con disk_usage — sin enumerar el árbol de media para pintar el home.
Limpieza — escanear carpeta, tabla visible

Captura regenerada del módulo Limpieza (docs/assets/ui-cleanup.png). Modo carpeta: Temporal vs archivo, no auditoría de todo el disco.

Qué es producto (además del perf)

Dos modos en la misma vista:

ModoAlcance
DiscoAuditoría de volumen (hints de caché / basura)
CarpetaDuplicados de dump iPhone vs archivo (folder_cleanup)

Invalidar el audit al cambiar disco o modo. send2trash en el stack para no inventar un recycle propio en Mac.

Empaquetado macOS (Homebrew Python 3.12 + Nuitka, ruta ASCII ~/MediaIngestBuild): existe en el working tree; Gatekeeper sin firmar = clic derecho → Abrir. Windows sigue siendo el ZIP en packaged-releases.

Esto no estaba en HEAD publicado al escribir el post (working tree vs e0111ab). Describe el corte que está en disco.

Serie · proyecto