Files
pmomusic/pmoradiofrance/src/config_ext.rs

351 lines
12 KiB
Rust
Raw Normal View History

//! Extension pour intégrer Radio France dans pmoconfig
//!
//! Ce module fournit le trait `RadioFranceConfigExt` qui permet d'ajouter
//! des méthodes de gestion de la configuration Radio France à pmoconfig::Config.
//!
//! # Fonctionnalités
//!
//! - Activation/désactivation de la source
//! - Cache de la liste des stations (TTL configurable, défaut 7 jours)
//! - Configuration minimale (pas de sur-configuration)
//!
//! # Exemple
//!
//! ```no_run
//! use pmoconfig::get_config;
//! use pmoradiofrance::RadioFranceConfigExt;
//!
//! # fn main() -> anyhow::Result<()> {
//! let config = get_config();
//!
//! // Check if enabled
//! if !config.get_radiofrance_enabled()? {
//! println!("Radio France is disabled");
//! return Ok(());
//! }
//!
//! // Get cached stations (or None if cache expired/empty)
//! if let Some(cached) = config.get_radiofrance_cached_stations()? {
//! println!("Found {} cached stations", cached.stations.len());
//! }
//! # Ok(())
//! # }
//! ```
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
use crate::models::Station;
use anyhow::Result;
use pmoconfig::Config;
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
use serde::{Deserialize, Serialize};
use serde_yaml::Value;
use std::collections::HashMap;
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
use std::time::{SystemTime, UNIX_EPOCH};
/// Default TTL for station list cache (7 days in seconds)
pub const DEFAULT_STATION_CACHE_TTL_SECS: u64 = 7 * 24 * 3600;
/// Default TTL for station mapping cache (30 days — IDs and stream URLs are very stable)
pub const DEFAULT_STATION_MAPPING_TTL_SECS: u64 = 30 * 24 * 3600;
/// Informations d'une station : ID numérique et URL du stream HiFi
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct StationInfo {
/// ID numérique pour l'API livemeta/pull
pub station_id: u32,
/// URL du stream HiFi (icecast AAC ou MP3)
pub stream_url: String,
}
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
/// Cached station list (simplifié)
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CachedStations {
stations: Vec<Station>,
last_updated: u64, // Unix timestamp
}
/// Mapping slug → StationInfo avec timestamp de mise à jour
#[derive(Debug, Clone, Serialize, Deserialize)]
struct CachedStationMapping {
/// slug → { station_id, stream_url }
mapping: HashMap<String, StationInfo>,
last_updated: u64, // Unix timestamp
}
/// Trait d'extension pour gérer la configuration Radio France dans pmoconfig
///
/// Ce trait étend `pmoconfig::Config` avec des méthodes spécifiques
/// à la gestion de Radio France, incluant :
///
/// - Activation/désactivation
/// - Cache de la liste des stations
///
/// # Auto-persist des valeurs par défaut
///
/// Les getters persistent automatiquement les valeurs par défaut dans la
/// configuration si elles n'existent pas encore.
pub trait RadioFranceConfigExt {
// ========================================================================
// Enable/Disable
// ========================================================================
/// Vérifie si Radio France est activé
///
/// # Returns
///
/// `true` si la source est activée (default), `false` sinon.
fn get_radiofrance_enabled(&self) -> Result<bool>;
/// Active ou désactive Radio France
fn set_radiofrance_enabled(&self, enabled: bool) -> Result<()>;
// ========================================================================
// Station Cache
// ========================================================================
/// Récupère la liste des stations en cache
///
/// # Returns
///
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
/// - `Some(Vec<Station>)` si le cache existe et est valide
/// - `None` si le cache n'existe pas ou est expiré
///
/// # Cache Validation
///
/// Le cache est considéré invalide si :
/// - Il n'existe pas
/// - Son TTL est dépassé (configurable, défaut 7 jours)
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
fn get_radiofrance_stations_cached(&self) -> Result<Option<Vec<Station>>>;
/// Enregistre la liste des stations en cache
///
/// # Arguments
///
/// * `stations` - Liste des stations découvertes
fn set_radiofrance_cached_stations(&self, stations: &[Station]) -> Result<()>;
/// Récupère le TTL du cache des stations (en secondes)
///
/// # Returns
///
/// Le TTL en secondes (default: 7 jours)
fn get_radiofrance_station_cache_ttl(&self) -> Result<u64>;
/// Définit le TTL du cache des stations (en secondes)
fn set_radiofrance_station_cache_ttl(&self, ttl_secs: u64) -> Result<()>;
/// Efface le cache des stations (force re-découverte)
fn clear_radiofrance_station_cache(&self) -> Result<()>;
// ========================================================================
// Station Mapping Cache (slug → station_id + stream_url)
// ========================================================================
/// Récupère le mapping slug → StationInfo depuis le cache (si valide)
///
/// # Returns
///
/// - `Some(HashMap)` si le cache existe et n'est pas expiré
/// - `None` si absent ou TTL dépassé → il faut re-découvrir
fn get_radiofrance_station_mapping(&self) -> Result<Option<HashMap<String, StationInfo>>>;
/// Enregistre le mapping slug → StationInfo en cache
fn set_radiofrance_station_mapping(&self, mapping: &HashMap<String, StationInfo>)
-> Result<()>;
/// Récupère le TTL du mapping (en secondes, défaut 30 jours)
fn get_radiofrance_mapping_ttl(&self) -> Result<u64>;
/// Définit le TTL du mapping (en secondes)
fn set_radiofrance_mapping_ttl(&self, ttl_secs: u64) -> Result<()>;
/// Efface le cache du mapping (force re-découverte au prochain accès)
fn clear_radiofrance_station_mapping(&self) -> Result<()>;
/// Met à jour une seule entrée dans le mapping (sans invalider tout le cache)
///
/// Utilisé pour la mise à jour incrémentale lors d'une re-découverte unitaire.
fn upsert_radiofrance_station_info(&self, slug: &str, info: StationInfo) -> Result<()>;
}
impl RadioFranceConfigExt for Config {
fn get_radiofrance_enabled(&self) -> Result<bool> {
match self.get_value(&["sources", "radiofrance", "enabled"]) {
Ok(Value::Bool(b)) => Ok(b),
_ => {
// Default: enabled
self.set_radiofrance_enabled(true)?;
Ok(true)
}
}
}
fn set_radiofrance_enabled(&self, enabled: bool) -> Result<()> {
self.set_value(&["sources", "radiofrance", "enabled"], Value::Bool(enabled))
}
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
fn get_radiofrance_stations_cached(&self) -> Result<Option<Vec<Station>>> {
let ttl = self.get_radiofrance_station_cache_ttl()?;
match self.get_value(&["sources", "radiofrance", "station_cache"]) {
Ok(value) => {
// Try to deserialize the cached data
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
let cached: CachedStations = serde_yaml::from_value(value)?;
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
// Check validity (TTL)
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
if now - cached.last_updated < ttl {
Ok(Some(cached.stations))
} else {
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
// Cache expired
Ok(None)
}
}
Err(_) => Ok(None),
}
}
fn set_radiofrance_cached_stations(&self, stations: &[Station]) -> Result<()> {
Refactorisation complète de pmoradiofrance : cache centralisé avec système d'événements Refactorisation complète de la crate `pmoradiofrance` pour simplifier l'architecture autour d'un cache de métadonnées centralisé avec système d'événements. ## Objectifs 1. Simplifier les structures de stations (supprimer StationType) 2. Créer un cache de métadonnées in-memory avec TTL basé sur `end_time` 3. Maintenir le cache de stations persistant (pmoconfig, TTL 1 semaine) 4. Implémenter un système d'événements pour la synchronisation GENA 5. Unifier les méthodes `to_didl()` pour retourner des Containers DIDL 6. Gérer automatiquement le cache des covers via pmocovers ## Changements architecturaux majeurs ### 1. Nouveau fichier: metadata_cache.rs **Créé**: `pmoradiofrance/src/metadata_cache.rs` Contient deux structures principales: - **CachedMetadata**: Stocke uniquement les données nécessaires au DIDL (titre, artiste, album, cover, stream URL, etc.) - **MetadataCache**: Gère le cache in-memory avec TTL + cache persistant des stations + système d'événements **Fonctionnalités**: - TTL basé sur `end_time` de l'API Radio France - Gestion automatique du cache de covers via pmocovers - Système subscribe/notify pour les événements - Graceful degradation si API Radio France down - Méthode `to_didl()` retournant une playlist à un item avec métadonnées identiques ### 2. Suppression: stateful_client.rs **Supprimé**: `pmoradiofrance/src/stateful_client.rs` Raison: Complètement redondant avec `MetadataCache`. Toute la logique a été déplacée dans le nouveau module. ### 3. Simplification: models.rs **Modifications**: - Supprimé `StationType` enum - Simplifié `Station` struct (juste `slug` + `name`) - Supprimé méthodes `is_main()`, `is_webradio()`, `is_local_radio()`, `base_station()` - Conservé structures d'API (`LiveResponse`, `ShowMetadata`, etc.) ### 4. Simplification: playlist.rs **Modifications**: - Supprimé `StationPlaylist` complètement - Simplifié `StationGroup` et `StationGroups` - **Important**: `to_didl()` retourne `Container` (pas `Vec<Container>`) - Logique unifiée: ICI fonctionne comme FIP (plus de traitement spécial) - Préservé les règles de mapping RF → UPnP existantes ### 5. Refactoring: source.rs **Modifications**: - Utilise uniquement `MetadataCache` (plus de `stateful_client`) - Simplifié `browse()` en 3 cas simples - Abonnement aux événements du cache pour GENA - Retourne des `Container` (cohérence avec to_didl) ### 6. Adaptation: config_ext.rs **Modifications**: - Format simplifié: `Vec<Station>` au lieu de `CachedStationList` - TTL reste à 7 jours (1 semaine) ### 7. Mise à jour: lib.rs **Modifications**: - Ajouté `pub mod metadata_cache;` - Supprimé export de `stateful_client` - Ajouté exports: `MetadataCache`, `CachedMetadata` ## Hiérarchie de browse **Niveau 0**: `radiofrance` - Retourne UN Container contenant les containers de groupes - Exemple: Container "FIP", Container "France Culture", Container "ICI" **Niveau 1**: `radiofrance:group:fip` ou `radiofrance:ici` - Si 1 station: retourne directement la playlist (Container playlistContainer) - Si plusieurs stations: retourne un container contenant les playlists **Niveau 2**: `radiofrance:fip` - Retourne Container playlistContainer avec 1 item - Métadonnées identiques entre playlist et item ## Règles de mapping préservées Les règles existantes de transformation RF → UPnP ont été préservées: - Radio musicale avec song → métadonnées du morceau - Radio parlée → agrégation émission/producteur - Éviter duplications du nom de station - Calcul de duration depuis end_time ## Système d'événements **Flux**: 1. `MetadataCache` rafraîchit les métadonnées d'un slug 2. Notifie tous les abonnés via `notify(slug)` 3. `RadioFranceSource` reçoit l'événement 4. Émet un événement GENA UPnP pour la playlist `radiofrance:{slug}` 5. Le Control Point reçoit la notification et peut se mettre à jour ## Fichiers modifiés ### Créés - `pmoradiofrance/src/metadata_cache.rs` ### Supprimés - `pmoradiofrance/src/stateful_client.rs` ### Modifiés - `pmoradiofrance/src/models.rs` - `pmoradiofrance/src/playlist.rs` - `pmoradiofrance/src/source.rs` - `pmoradiofrance/src/config_ext.rs` - `pmoradiofrance/src/lib.rs` ### Inchangés - `pmoradiofrance/src/client.rs` - `pmoradiofrance/src/error.rs` ## Points de vigilance 1. **Migration**: Le cache pmoconfig existant sera invalidé (nouveau format) 2. **Covers**: Nécessite que pmocovers soit initialisé via cache_registry 3. **Thread safety**: Utilisation d'Arc<RwLock> pour la sécurité thread 4. **Graceful degradation**: Retourne cache expiré si API Radio France down ## Prochaines étapes 1. Tester le cache de métadonnées (TTL, refresh, graceful degradation) 2. Tester le système d'événements 3. Tester le browse sur les 3 niveaux 4. Vérifier les événements GENA 5. Vérifier que les covers sont correctement cachées
2026-01-24 14:15:26 +01:00
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let cached = CachedStations {
stations: stations.to_vec(),
last_updated: now,
};
let value = serde_yaml::to_value(&cached)?;
self.set_value(&["sources", "radiofrance", "station_cache"], value)
}
fn get_radiofrance_station_cache_ttl(&self) -> Result<u64> {
match self.get_value(&["sources", "radiofrance", "station_cache_ttl_secs"]) {
Ok(Value::Number(n)) => {
if let Some(ttl) = n.as_u64() {
Ok(ttl)
} else {
// Invalid number, use default
self.set_radiofrance_station_cache_ttl(DEFAULT_STATION_CACHE_TTL_SECS)?;
Ok(DEFAULT_STATION_CACHE_TTL_SECS)
}
}
_ => {
// Not set, use default and persist
self.set_radiofrance_station_cache_ttl(DEFAULT_STATION_CACHE_TTL_SECS)?;
Ok(DEFAULT_STATION_CACHE_TTL_SECS)
}
}
}
fn set_radiofrance_station_cache_ttl(&self, ttl_secs: u64) -> Result<()> {
self.set_value(
&["sources", "radiofrance", "station_cache_ttl_secs"],
Value::Number(serde_yaml::Number::from(ttl_secs)),
)
}
fn clear_radiofrance_station_cache(&self) -> Result<()> {
// Set to null to clear
self.set_value(&["sources", "radiofrance", "station_cache"], Value::Null)
}
fn get_radiofrance_station_mapping(&self) -> Result<Option<HashMap<String, StationInfo>>> {
let ttl = self.get_radiofrance_mapping_ttl()?;
match self.get_value(&["sources", "radiofrance", "station_mapping"]) {
Ok(value) => {
let cached: CachedStationMapping = serde_yaml::from_value(value)?;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
if now - cached.last_updated < ttl {
Ok(Some(cached.mapping))
} else {
Ok(None) // Expiré
}
}
Err(_) => Ok(None), // Absent
}
}
fn set_radiofrance_station_mapping(
&self,
mapping: &HashMap<String, StationInfo>,
) -> Result<()> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let cached = CachedStationMapping {
mapping: mapping.clone(),
last_updated: now,
};
let value = serde_yaml::to_value(&cached)?;
self.set_value(&["sources", "radiofrance", "station_mapping"], value)
}
fn get_radiofrance_mapping_ttl(&self) -> Result<u64> {
match self.get_value(&["sources", "radiofrance", "station_mapping_ttl_secs"]) {
Ok(Value::Number(n)) => {
if let Some(ttl) = n.as_u64() {
Ok(ttl)
} else {
self.set_radiofrance_mapping_ttl(DEFAULT_STATION_MAPPING_TTL_SECS)?;
Ok(DEFAULT_STATION_MAPPING_TTL_SECS)
}
}
_ => {
self.set_radiofrance_mapping_ttl(DEFAULT_STATION_MAPPING_TTL_SECS)?;
Ok(DEFAULT_STATION_MAPPING_TTL_SECS)
}
}
}
fn set_radiofrance_mapping_ttl(&self, ttl_secs: u64) -> Result<()> {
self.set_value(
&["sources", "radiofrance", "station_mapping_ttl_secs"],
Value::Number(serde_yaml::Number::from(ttl_secs)),
)
}
fn clear_radiofrance_station_mapping(&self) -> Result<()> {
self.set_value(
&["sources", "radiofrance", "station_mapping"],
Value::Null,
)
}
fn upsert_radiofrance_station_info(&self, slug: &str, info: StationInfo) -> Result<()> {
// Charger le mapping existant (même expiré) pour mise à jour incrémentale
let mut mapping = match self.get_value(&["sources", "radiofrance", "station_mapping"]) {
Ok(value) => serde_yaml::from_value::<CachedStationMapping>(value)
.map(|c| c.mapping)
.unwrap_or_default(),
Err(_) => HashMap::new(),
};
mapping.insert(slug.to_string(), info);
self.set_radiofrance_station_mapping(&mapping)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_ttl() {
// 7 days in seconds
assert_eq!(DEFAULT_STATION_CACHE_TTL_SECS, 7 * 24 * 3600);
}
}