Files
lisael-box/docs/superpowers/specs/2026-06-21-merlin-transfers-viz-design.md
L'électron rare baea6ccdd1 docs(merlin): transfers viz spec + routines note
Merlin Studio v2: in-progress + completed transfer journal (duration, throughput, retry failed) alongside the routines tab.
2026-06-21 08:48:59 +02:00

6.8 KiB

Merlin Studio — Visualisation des transferts (design)

  • Date : 2026-06-21
  • Composants : Tower lisael-content (lisael_content.py, lisael_server.py) + UI web merlin_ui.html
  • Statut : design validé (variante « plus complète »), prêt pour le plan
  • Lié à : 2026-06-21-lisael-box-routines-design.md — livré dans le même chantier Merlin Studio v2 (onglet Routines + onglet Transferts).

1. But

Sur merlin.saillant.cc, voir les transferts en cours et ceux effectués : quand Tower pousse du contenu vers la box (au boot/register ou via « Pousser maintenant »), on veut un retour visuel — quel fichier part maintenant, combien restent, et l'historique des push passés (réussis/échoués) avec durée et débit moyen, plus un bouton pour relancer les transferts échoués.

2. Contexte (état actuel)

  • Le push tourne dans un thread de fond : try_push(ip, delta)push_batchpush_file (POST urllib bloquant vers http://<box>:8080/put?name=podcasts/<f>, timeout 300 s).
  • Aucun journal n'est exposé : l'onglet Box montre l'état (box-state.json) et le delta restant, mais pas le déroulé des transferts ni l'historique.
  • Contrainte : push_file est un POST bloquant → pas de % intra-fichier réaliste. On expose donc le statut par fichier + l'avancement du lot (X/N).

3. Décisions (verrouillées)

Sujet Décision
Contenu Transferts en cours + effectués
Granularité Par fichier, groupés par lot de push (un register ou un /api/push)
Métriques taille, durée par fichier, débit moyen (taille/durée), statut, horodatage
Échecs raison courte + bouton « relancer les échecs » (re-push des fichiers failed)
Historique persistant, ~200 derniers fichiers (transfers.json, écriture atomique)
Emplacement onglet dédié « 📤 Transferts » dans Merlin
Rafraîchissement auto-refresh ~2 s tant qu'un transfert est actif, sinon au clic
Pas de % intra-fichier (limite POST bloquant)

4. Architecture

push_batch/push_file  ──maj──▶  journal (mémoire: lot actif) + transfers.json (historique)
        (lisael_content.py)                         │
                                          GET /api/transfers ──▶  onglet "Transferts"
                                          POST /api/transfers/retry      (auto-refresh 2s)
        (lisael_server.py)                                        (merlin_ui.html)

5. Modèle de données

Un enregistrement de transfert (par fichier) :

{ "name":"matin_xx_a48.mp3", "dir":"routines", "size":428111,
  "status":"done", "started":"2026-06-21T09:12:03", "ended":"2026-06-21T09:12:07",
  "duration_s":4.1, "rate_bps":104417, "error":null, "box_ip":"192.168.0.250" }
  • statuspending | sending | done | failed.
  • Lot actif (en mémoire, un seul à la fois — try_push est déjà sérialisé par IP via _busy) :
{ "box_ip":"192.168.0.250", "started":"...", "total":15, "current":3,
  "files":[ {name,dir,size,status,...}, ... ] }
  • Historique : transfers.json = liste des enregistrements done|failed terminés, tronquée aux ~200 derniers, écrite via _atomic_write_json (pattern existant).

6. Tower — instrumentation (lisael_content.py)

  • Un module d'état transfers : transfers_begin(box_ip, files) (initialise le lot actif avec tous les fichiers en pending + leur taille via os.path.getsize), transfers_mark(name, status, error=None) (passe sending puis done/failed, calcule duration_s et rate_bps à la fin), transfers_end() (vide le lot actif, flush les enregistrements terminés dans transfers.json borné à 200), transfers_active() / transfers_history(limit) (lecture pour l'API).
  • push_batch appelle transfers_begin au début, transfers_mark(name,"sending") avant chaque push_file, transfers_mark(name,"done"/"failed",err) après, transfers_end à la fin.
  • push_file mesure le temps (monotone) et lève/retourne l'info de succès pour le mark.
  • Retry : retry_failed(box_ip) = collecter les failed du dernier lot (ou de l'historique récent pour cette box) → try_push(ip, names).

7. API (lisael_server.py)

  • GET /api/transfers{ "active": <lot actif|null>, "history": [<~200 derniers>] } (sous la Basic Auth existante).
  • POST /api/transfers/retry → relance les fichiers failed connus pour la box enregistrée (box-state.json → ip), 202 + {started, count} ; 409 si pas d'IP / rien à relancer.

8. UI (merlin_ui.html) — onglet « 📤 Transferts »

  • 4ᵉ onglet (bouton + panneau + entrée dans TABS + branche showTab), suivant le style exact des onglets existants (helper api(), rendu de liste, bannière d'erreur).
  • En cours : si active → carte « Vers <box_ip> — fichier / » + barre de progression (X/N) + nom du fichier sending ; sinon « Aucun transfert en cours ».
  • Effectués : table (fichier · dossier · taille · durée · débit · statut ✓/✗ · heure), ligne failed en rouge avec la raison ; bouton « 🔁 Relancer les échecs » (POST /api/transfers/retry) actif s'il existe ≥1 failed.
  • Auto-refresh : setInterval ~2 s qui appelle loadTransfers() tant que active != null ; arrêt quand plus de lot actif (et un dernier refresh pour figer l'historique).

9. Gestion d'erreurs

  • transfers.json absent/corrompu → historique vide, on repart proprement (try/except).
  • Restart du service en plein push → le lot actif (mémoire) est perdu ; l'historique persistant garde les fichiers déjà terminés ; le prochain register de la box re-déclenche le delta.
  • Retry sans box enregistrée → 409 + message UI clair.
  • Double-push évité : try_push garde déjà l'anti-concurrence par IP (_busy).

10. Tests

  • Hôte (Python, pattern test_delta.py, asserts purs) : transfers_begin/mark/end (statuts, calcul duration_s/rate_bps, troncature à 200), retry_failed (sélection des failed). Pas de réseau réel : injecter une fonction de push factice / utiliser un fichier transfers.json temporaire.

11. Critères d'acceptation

  1. Lancer un push (bouton « Pousser maintenant » ou boot box) → l'onglet Transferts montre le lot en cours (box, X/N, fichier courant) qui progresse, puis bascule en historique.
  2. L'historique affiche fichier/taille/durée/débit/statut/heure et survit à un restart du service lisael-content.
  3. Un fichier en échec apparaît en rouge avec sa raison ; « Relancer les échecs » le re-pousse et il repasse ✓.
  4. L'onglet s'auto-rafraîchit pendant un transfert et se calme une fois fini.
  5. Test hôte vert (journal + retry).