diff --git a/backend/destinations.py b/backend/destinations.py
index 65366c4..5d37e31 100644
--- a/backend/destinations.py
+++ b/backend/destinations.py
@@ -1,4 +1,5 @@
import ftplib
+import json
import logging
import shutil
import threading
@@ -6,10 +7,13 @@ from pathlib import Path
from urllib.request import Request, urlopen
from urllib.error import URLError
-from backend.config import charger_config, mettre_a_jour_config
+from backend.config import charger_config, mettre_a_jour_config, RACINE
log = logging.getLogger("photobooth.destinations")
+FICHIER_BOOTH_SPOOL = RACINE / "data" / "booth_spool.json"
+_booth_spool_lock = threading.Lock()
+
def distribuer_photo(chemin_photo: Path, imprimee: bool = False, copies: int = 1, format_papier: str | None = None):
"""Copie la photo vers toutes les destinations activees."""
@@ -118,7 +122,7 @@ def _upload_booth(url: str, api_key: str, event_id: str, chemin_photo: Path) ->
def envoyer_booth(chemin_photo: Path, config_booth: dict):
- """Envoie une photo vers la galerie live booth."""
+ """Envoie une photo vers la galerie live booth. En cas d'echec, met en spool."""
url = config_booth.get("url", "").rstrip("/")
url_tunnel = config_booth.get("url_tunnel", "").rstrip("/")
api_key = config_booth.get("api_key", "")
@@ -137,9 +141,93 @@ def envoyer_booth(chemin_photo: Path, config_booth: dict):
log.error(f"Booth erreur HTTP via {tentative_url}")
except (URLError, OSError) as e:
log.warning(f"Echec envoi booth via {tentative_url} : {e}")
+
+ _ajouter_booth_spool(str(chemin_photo))
return False
+# --- Spool galerie booth ---
+
+def _ajouter_booth_spool(chemin: str):
+ with _booth_spool_lock:
+ spool = _charger_booth_spool()
+ if chemin not in spool:
+ spool.append(chemin)
+ _sauver_booth_spool(spool)
+ log.info(f"Photo mise en spool galerie ({len(spool)} en attente)")
+
+
+def _charger_booth_spool() -> list:
+ if not FICHIER_BOOTH_SPOOL.exists():
+ return []
+ try:
+ with open(FICHIER_BOOTH_SPOOL, "r") as f:
+ return json.load(f)
+ except (json.JSONDecodeError, OSError):
+ return []
+
+
+def _sauver_booth_spool(spool: list):
+ try:
+ with open(FICHIER_BOOTH_SPOOL, "w") as f:
+ json.dump(spool, f)
+ except OSError:
+ pass
+
+
+def taille_booth_spool() -> int:
+ with _booth_spool_lock:
+ return len(_charger_booth_spool())
+
+
+def traiter_booth_spool() -> int:
+ """Retente l'envoi des photos en spool. Retourne le nombre envoyees."""
+ with _booth_spool_lock:
+ spool = _charger_booth_spool()
+ if not spool:
+ return 0
+
+ config = charger_config()
+ booth = config.get("booth", {})
+ if not booth.get("actif", False):
+ return 0
+
+ envoyes = 0
+ restants = []
+ for chemin_str in spool:
+ chemin = Path(chemin_str)
+ if not chemin.exists():
+ log.warning(f"Spool booth: photo introuvable {chemin}, ignoree")
+ continue
+ url = booth.get("url", "").rstrip("/")
+ url_tunnel = booth.get("url_tunnel", "").rstrip("/")
+ api_key = booth.get("api_key", "")
+ event_id = config.get("evenement", {}).get("event_id") or booth.get("event_id", "default")
+ ok = False
+ for tentative_url in [u for u in (url_tunnel, url) if u]:
+ try:
+ if _upload_booth(tentative_url, api_key, event_id, chemin):
+ log.info(f"Spool booth: {chemin.name} envoyee via {tentative_url}")
+ ok = True
+ break
+ except (URLError, OSError):
+ pass
+ if ok:
+ envoyes += 1
+ else:
+ restants.append(chemin_str)
+ break
+
+ if envoyes > 0 or len(restants) < len(spool):
+ idx = envoyes + len(restants)
+ restants.extend(spool[idx:])
+ with _booth_spool_lock:
+ _sauver_booth_spool(restants)
+ log.info(f"Spool booth: {envoyes} envoyee(s), {len(restants)} en attente")
+
+ return envoyes
+
+
def recuperer_booth_password() -> dict:
"""Recupere le mot de passe et l'info de la session booth."""
config = charger_config()
diff --git a/backend/mailer.py b/backend/mailer.py
index d2fb0aa..fe7999b 100644
--- a/backend/mailer.py
+++ b/backend/mailer.py
@@ -178,18 +178,34 @@ def envoyer_rapport_spool(nb_envoyes: int, destinataires: list[str], nb_echoues:
log.error(f"Erreur envoi rapport spool : {e}")
+RETRY_INTERVAL = 300 # 5 minutes
+
+
+async def tache_spool_periodique():
+ """Retente emails + galerie en spool toutes les 5 min."""
+ from backend.destinations import traiter_booth_spool, taille_booth_spool
+ await asyncio.sleep(15)
+ while True:
+ loop = asyncio.get_event_loop()
+ # Emails
+ total_mail = taille_spool()
+ if total_mail > 0:
+ log.info(f"Spool email: retry ({total_mail} en attente)")
+ nb, dests = await loop.run_in_executor(None, traiter_spool)
+ if nb > 0:
+ echoues = total_mail - nb
+ await loop.run_in_executor(None, envoyer_rapport_spool, nb, dests, echoues)
+ # Galerie booth
+ total_booth = taille_booth_spool()
+ if total_booth > 0:
+ log.info(f"Spool galerie: retry ({total_booth} en attente)")
+ await loop.run_in_executor(None, traiter_booth_spool)
+ await asyncio.sleep(RETRY_INTERVAL)
+
+
async def tache_spool_demarrage():
- """Tente de vider le spool une seule fois au demarrage du serveur."""
- await asyncio.sleep(10)
- total = taille_spool()
- if total > 0:
- log.info(f"Spool: tentative d'envoi au demarrage ({total} en attente)")
- nb, dests = await asyncio.get_event_loop().run_in_executor(None, traiter_spool)
- if nb > 0:
- echoues = total - nb
- await asyncio.get_event_loop().run_in_executor(
- None, envoyer_rapport_spool, nb, dests, echoues
- )
+ """Alias pour compatibilite — lance la boucle periodique."""
+ await tache_spool_periodique()
FICHIER_EMAILS = RACINE / "data" / "emails_history.json"
diff --git a/frontend/index.html b/frontend/index.html
index 2c96f0a..597aa7e 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1149,7 +1149,7 @@
-
+