baea6ccdd1
Merlin Studio v2: in-progress + completed transfer journal (duration, throughput, retry failed) alongside the routines tab.
6.8 KiB
6.8 KiB
Merlin Studio — Visualisation des transferts (design)
- Date : 2026-06-21
- Composants : Tower
lisael-content(lisael_content.py,lisael_server.py) + UI webmerlin_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_batch→push_file(POSTurllibbloquant vershttp://<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_fileest 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" }
status∈pending | sending | done | failed.- Lot actif (en mémoire, un seul à la fois —
try_pushest 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 enregistrementsdone|failedterminé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 enpending+ leur taille viaos.path.getsize),transfers_mark(name, status, error=None)(passesendingpuisdone/failed, calculeduration_setrate_bpsà la fin),transfers_end()(vide le lot actif, flush les enregistrements terminés danstransfers.jsonborné à 200),transfers_active()/transfers_history(limit)(lecture pour l'API). push_batchappelletransfers_beginau début,transfers_mark(name,"sending")avant chaquepush_file,transfers_mark(name,"done"/"failed",err)après,transfers_endà la fin.push_filemesure le temps (monotone) et lève/retourne l'info de succès pour le mark.- Retry :
retry_failed(box_ip)= collecter lesfaileddu 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 fichiersfailedconnus pour la box enregistrée (box-state.json→ ip),202+{started, count};409si pas d'IP / rien à relancer.
8. UI (merlin_ui.html) — onglet « 📤 Transferts »
- 4ᵉ onglet (bouton + panneau + entrée dans
TABS+ brancheshowTab), suivant le style exact des onglets existants (helperapi(), rendu de liste, bannière d'erreur). - En cours : si
active→ carte « Vers <box_ip> — fichier / » + barre de progression (X/N) + nom du fichiersending; sinon « Aucun transfert en cours ». - Effectués : table (fichier · dossier · taille · durée · débit · statut ✓/✗ · heure),
ligne
faileden rouge avec la raison ; bouton « 🔁 Relancer les échecs » (POST /api/transfers/retry) actif s'il existe ≥1failed. - Auto-refresh :
setInterval~2 s qui appelleloadTransfers()tant queactive != null; arrêt quand plus de lot actif (et un dernier refresh pour figer l'historique).
9. Gestion d'erreurs
transfers.jsonabsent/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
registerde la box re-déclenche le delta. - Retry sans box enregistrée →
409+ message UI clair. - Double-push évité :
try_pushgarde déjà l'anti-concurrence par IP (_busy).
10. Tests
- Hôte (Python, pattern
test_delta.py, asserts purs) :transfers_begin/mark/end(statuts, calculduration_s/rate_bps, troncature à 200),retry_failed(sélection desfailed). Pas de réseau réel : injecter une fonction de push factice / utiliser un fichiertransfers.jsontemporaire.
11. Critères d'acceptation
- 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.
- L'historique affiche fichier/taille/durée/débit/statut/heure et survit à un
restart du service
lisael-content. - Un fichier en échec apparaît en rouge avec sa raison ; « Relancer les échecs » le re-pousse et il repasse ✓.
- L'onglet s'auto-rafraîchit pendant un transfert et se calme une fois fini.
- Test hôte vert (journal + retry).