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 @@ - + diff --git a/frontend/js/share.js b/frontend/js/share.js index 2579555..d2cbc38 100644 --- a/frontend/js/share.js +++ b/frontend/js/share.js @@ -185,7 +185,7 @@ async function envoyerEmail() { afficherStatut('Envoi en cours...', 'succes'); const resultat = await apiPost('/api/email', { email, photo: photoFinale }); if (resultat.spool) { - afficherStatut('Vous recevrez votre photo par email dans la semaine', 'succes'); + afficherStatut('Votre photo sera envoyee par email des que possible', 'succes'); } else if (resultat.succes) { afficherStatut('Email envoyé !', 'succes'); } else {