//! Registre centralisé des caches pour le serveur UPnP //! //! Ce module gère les caches partagés entre toutes les sources musicales : //! - Cache de couvertures d'albums (WebP) //! - Cache de pistes audio (FLAC) //! //! Les caches supportent les collections, permettant à chaque source //! d'avoir sa propre collection dans le cache partagé. use once_cell::sync::Lazy; use pmoaudiocache::Cache as AudioCache; use pmocache::FileCache; use pmocovers::Cache as CoverCache; use std::sync::Arc; use std::sync::RwLock; /// Registre global des caches /// /// Contient les instances partagées des caches de couvertures et audio. /// Ces caches sont uniques et partagés entre toutes les sources musicales. pub struct CacheRegistry { /// URL de base du serveur (ex: "http://localhost:8080") base_url: Option, /// Cache de couvertures (WebP) cover_cache: Option>, /// Cache audio (FLAC) audio_cache: Option>, } impl CacheRegistry { /// Créer un nouveau registre vide pub fn new() -> Self { Self { base_url: None, cover_cache: None, audio_cache: None, } } /// Définir l'URL de base du serveur pub fn set_base_url(&mut self, url: String) { self.base_url = Some(url); } /// Récupérer l'URL de base du serveur pub fn base_url(&self) -> Option<&str> { self.base_url.as_deref() } /// Enregistrer le cache de couvertures pub fn set_cover_cache(&mut self, cache: Arc) { self.cover_cache = Some(cache); } /// Récupérer le cache de couvertures pub fn cover_cache(&self) -> Option> { self.cover_cache.clone() } /// Enregistrer le cache audio pub fn set_audio_cache(&mut self, cache: Arc) { self.audio_cache = Some(cache); } /// Récupérer le cache audio pub fn audio_cache(&self) -> Option> { self.audio_cache.clone() } /// Construit l'URL complète pour une couverture /// /// # Arguments /// /// * `pk` - Clé primaire de la couverture /// * `size` - Taille optionnelle de l'image /// /// # Returns /// /// URL complète (ex: "http://localhost:8080/covers/images/abc123/300") pub fn build_cover_url(&self, pk: &str, size: Option) -> anyhow::Result { let base_url = self .base_url .as_ref() .ok_or_else(|| anyhow::anyhow!("Base URL not set in CacheRegistry"))?; let cache = get_cover_cache().ok_or_else(|| anyhow::anyhow!("No registred cover cache"))?; let param = match size { Some(size_) => Some(size_.to_string()), None => None, }; let route = cache.route_for(pk, param.as_deref()); Ok(format!("{}{}", base_url, route)) } /// Construit l'URL complète pour une piste audio /// /// # Arguments /// /// * `pk` - Clé primaire de la piste /// * `param` - Paramètre optionnel (ex: "orig", "128k") /// /// # Returns /// /// URL complète (ex: "http://localhost:8080/audio/tracks/abc123/orig") pub fn build_audio_url(&self, pk: &str, param: Option<&str>) -> anyhow::Result { let base_url = self .base_url .as_ref() .ok_or_else(|| anyhow::anyhow!("Base URL not set in CacheRegistry"))?; let cache = get_audio_cache().ok_or_else(|| anyhow::anyhow!("No registred audio cache"))?; let route = cache.route_for(pk, param); Ok(format!("{}{}", base_url, route)) } } impl Default for CacheRegistry { fn default() -> Self { Self::new() } } /// Registre global thread-safe /// /// Utilise Lazy pour une initialisation paresseuse et RwLock pour le partage entre threads. /// Permet aux handlers et aux sources d'accéder aux caches depuis n'importe où. pub(crate) static CACHE_REGISTRY: Lazy> = Lazy::new(|| RwLock::new(CacheRegistry::new())); /// Accès global au cache de couvertures /// /// # Examples /// /// ```rust,ignore /// use pmoupnp::cache_registry::get_cover_cache; /// /// if let Some(cache) = get_cover_cache() { /// let pk = cache.add_from_url("http://example.com/cover.jpg").await?; /// } /// ``` pub fn get_cover_cache() -> Option> { CACHE_REGISTRY.read().unwrap().cover_cache() } /// Accès global au cache audio /// /// # Examples /// /// ```rust,ignore /// use pmoupnp::cache_registry::get_audio_cache; /// /// if let Some(cache) = get_audio_cache() { /// let (pk, _) = cache.add_from_url("http://example.com/track.flac", None).await?; /// } /// ``` pub fn get_audio_cache() -> Option> { CACHE_REGISTRY.read().unwrap().audio_cache() } /// Enregistre le cache audio global /// /// Cette fonction doit être appelée au démarrage de l'application /// pour rendre le cache audio disponible globalement. /// /// # Examples /// /// ```rust,ignore /// use pmoupnp::cache_registry::register_audio_cache; /// use pmoaudiocache::Cache as AudioCache; /// use std::sync::Arc; /// /// let audio_cache = Arc::new(AudioCache::new("./cache", 1000)?); /// register_audio_cache(audio_cache); /// ``` pub fn register_audio_cache(cache: Arc) { CACHE_REGISTRY.write().unwrap().set_audio_cache(cache); } /// Enregistre le cache de couvertures global /// /// Cette fonction doit être appelée au démarrage de l'application /// pour rendre le cache de couvertures disponible globalement. /// /// # Examples /// /// ```rust,ignore /// use pmoupnp::cache_registry::register_cover_cache; /// use pmocovers::Cache as CoverCache; /// use std::sync::Arc; /// /// let cover_cache = Arc::new(CoverCache::new("./covers", 100)?); /// register_cover_cache(cover_cache); /// ``` pub fn register_cover_cache(cache: Arc) { CACHE_REGISTRY.write().unwrap().set_cover_cache(cache); } /// Construit l'URL complète pour une couverture /// /// Fonction globale qui utilise le registre de caches pour construire l'URL. /// /// # Arguments /// /// * `pk` - Clé primaire de la couverture /// * `size` - Taille optionnelle de l'image /// /// # Examples /// /// ```rust,ignore /// use pmoupnp::cache_registry::build_cover_url; /// /// let url = build_cover_url("abc123", Some(300))?; /// // url = "http://localhost:8080/covers/images/abc123/300" /// ``` pub fn build_cover_url(pk: &str, size: Option) -> anyhow::Result { CACHE_REGISTRY.read().unwrap().build_cover_url(pk, size) } /// Construit l'URL complète pour une piste audio /// /// Fonction globale qui utilise le registre de caches pour construire l'URL. /// /// # Arguments /// /// * `pk` - Clé primaire de la piste /// * `param` - Paramètre optionnel (ex: "orig", "128k") /// /// # Examples /// /// ```rust,ignore /// use pmoupnp::cache_registry::build_audio_url; /// /// let url = build_audio_url("abc123", Some("orig"))?; /// // url = "http://localhost:8080/audio/tracks/abc123/orig" /// ``` pub fn build_audio_url(pk: &str, param: Option<&str>) -> anyhow::Result { CACHE_REGISTRY.read().unwrap().build_audio_url(pk, param) } #[cfg(test)] mod tests { use super::*; #[test] fn test_cache_registry_empty() { let registry = CacheRegistry::new(); assert!(registry.cover_cache().is_none()); assert!(registry.audio_cache().is_none()); } }