diff --git a/Cargo.lock b/Cargo.lock index 1c60e85e..b4fc4f12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2890,6 +2890,7 @@ dependencies = [ "chrono", "futures-util", "lofty", + "once_cell", "paste", "pmocache", "pmoconfig", @@ -2959,6 +2960,7 @@ dependencies = [ "anyhow", "axum 0.8.6", "image", + "once_cell", "pmocache", "pmoconfig", "pmoserver", diff --git a/pmoaudiocache/Cargo.toml b/pmoaudiocache/Cargo.toml index c04f343a..49dc679e 100644 --- a/pmoaudiocache/Cargo.toml +++ b/pmoaudiocache/Cargo.toml @@ -36,6 +36,9 @@ async-trait = "0.1" # Async tokio = { version = "1.0", features = ["full"] } +# Singleton +once_cell = "1.20" + # Serveur HTTP (optionnel pour l'extension) pmoserver = { path = "../pmoserver", optional = true } pmoconfig = { path = "../pmoconfig", optional = true } diff --git a/pmoaudiocache/src/lib.rs b/pmoaudiocache/src/lib.rs index 16e8888a..5b65f018 100755 --- a/pmoaudiocache/src/lib.rs +++ b/pmoaudiocache/src/lib.rs @@ -100,6 +100,48 @@ pub use config_ext::AudioCacheConfigExt; #[cfg(feature = "pmoserver")] pub use openapi::ApiDoc; +// ============================================================================ +// Registre global singleton +// ============================================================================ + +use once_cell::sync::OnceCell; +use std::sync::Arc; + +static AUDIO_CACHE: OnceCell> = OnceCell::new(); + +/// 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 pmoaudiocache::{new_cache, register_audio_cache}; +/// use std::sync::Arc; +/// +/// let cache = Arc::new(new_cache("./cache", 1000)?); +/// register_audio_cache(cache); +/// ``` +pub fn register_audio_cache(cache: Arc) { + let _ = AUDIO_CACHE.set(cache); +} + +/// Accès global au cache audio +/// +/// # Examples +/// +/// ```rust,ignore +/// use pmoaudiocache::get_audio_cache; +/// +/// if let Some(cache) = get_audio_cache() { +/// // Utiliser le cache +/// } +/// ``` +pub fn get_audio_cache() -> Option> { + AUDIO_CACHE.get().cloned() +} + // ============================================================================ // Extension pmoserver (inline comme pmocovers) // ============================================================================ @@ -132,8 +174,6 @@ pub trait AudioCacheExt { #[cfg(feature = "pmoserver")] use pmocache::pmoserver_ext::{create_api_router, create_file_router}; #[cfg(feature = "pmoserver")] -use std::sync::Arc; -#[cfg(feature = "pmoserver")] use utoipa::OpenApi; #[cfg(feature = "pmoserver")] diff --git a/pmocovers/Cargo.toml b/pmocovers/Cargo.toml index a9a237bc..eea54228 100644 --- a/pmocovers/Cargo.toml +++ b/pmocovers/Cargo.toml @@ -21,6 +21,9 @@ serde = { version = "1.0", features = ["derive"] } # Async tokio = { version = "1.0", features = ["full"] } +# Singleton +once_cell = "1.20" + # Serveur HTTP (optionnel pour l'extension) pmoserver = { path = "../pmoserver", optional = true } pmoconfig = { path = "../pmoconfig", optional = true } diff --git a/pmocovers/src/lib.rs b/pmocovers/src/lib.rs index 104371a1..a9e2cd5b 100644 --- a/pmocovers/src/lib.rs +++ b/pmocovers/src/lib.rs @@ -68,8 +68,52 @@ pub use openapi::ApiDoc; #[cfg(feature = "pmoconfig")] pub use config_ext::CoverCacheConfigExt; -#[cfg(feature = "pmoserver")] +// ============================================================================ +// Registre global singleton +// ============================================================================ + +use once_cell::sync::OnceCell; use std::sync::Arc; + +static COVER_CACHE: OnceCell> = OnceCell::new(); + +/// 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 pmocovers::{new_cache, register_cover_cache}; +/// use std::sync::Arc; +/// +/// let cache = Arc::new(new_cache("./covers", 100)?); +/// register_cover_cache(cache); +/// ``` +pub fn register_cover_cache(cache: Arc) { + let _ = COVER_CACHE.set(cache); +} + +/// Accès global au cache de couvertures +/// +/// # Examples +/// +/// ```rust,ignore +/// use pmocovers::get_cover_cache; +/// +/// if let Some(cache) = get_cover_cache() { +/// // Utiliser le cache +/// } +/// ``` +pub fn get_cover_cache() -> Option> { + COVER_CACHE.get().cloned() +} + +// ============================================================================ +// Extension pmoserver +// ============================================================================ + #[cfg(feature = "pmoserver")] use utoipa::OpenApi; diff --git a/pmoplaylist/src/manager.rs b/pmoplaylist/src/manager.rs index 7a4e148c..2daf75f8 100644 --- a/pmoplaylist/src/manager.rs +++ b/pmoplaylist/src/manager.rs @@ -312,8 +312,8 @@ pub(crate) fn audio_cache() -> Result> { .get() .cloned() .or_else(|| { - // Fallback: essayer pmoupnp si disponible - pmoupnp::get_audio_cache() + // Fallback: essayer le registre global de pmoaudiocache + pmoaudiocache::get_audio_cache() }) .ok_or_else(|| crate::Error::ManagerNotInitialized) } diff --git a/pmoupnp/src/cache_registry.rs b/pmoupnp/src/cache_registry.rs deleted file mode 100644 index 8c4d87da..00000000 --- a/pmoupnp/src/cache_registry.rs +++ /dev/null @@ -1,250 +0,0 @@ -//! 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()); - } -} diff --git a/pmoupnp/src/lib.rs b/pmoupnp/src/lib.rs index 4442472a..28e0cb93 100644 --- a/pmoupnp/src/lib.rs +++ b/pmoupnp/src/lib.rs @@ -2,7 +2,6 @@ mod object_set; mod object_trait; pub mod actions; -pub mod cache_registry; pub mod devices; pub mod services; pub mod soap; @@ -16,9 +15,10 @@ pub mod variable_types; use std::sync::RwLock; use std::{collections::HashMap, sync::Arc}; -pub use crate::cache_registry::{ - get_audio_cache, get_cover_cache, register_audio_cache, register_cover_cache, -}; +// Réexports pour compatibilité (seulement get_*, pas register_*) +pub use pmoaudiocache::get_audio_cache; +pub use pmocovers::get_cover_cache; + pub use crate::object_trait::*; pub use crate::upnp_server::UpnpServerExt; diff --git a/pmoupnp/src/upnp_server.rs b/pmoupnp/src/upnp_server.rs index f4538193..881b7e9c 100644 --- a/pmoupnp/src/upnp_server.rs +++ b/pmoupnp/src/upnp_server.rs @@ -26,7 +26,6 @@ use pmoserver::Server; use utoipa::OpenApi; use crate::UpnpModel; -use crate::cache_registry::CACHE_REGISTRY; use crate::devices::errors::DeviceError; use crate::devices::{Device, DeviceInstance, DeviceRegistry}; use crate::ssdp::SsdpServer; @@ -330,12 +329,8 @@ impl UpnpServerExt for Server { let openapi = pmocovers::ApiDoc::openapi(); self.add_openapi(api_router, openapi, "covers").await; - // Enregistrer base_url et cache dans le registre global - { - let mut registry = CACHE_REGISTRY.write().unwrap(); - registry.set_base_url(base_url); - registry.set_cover_cache(cache.clone()); - } + // Enregistrer le cache dans le registre global + pmocovers::register_cover_cache(cache.clone()); Ok(cache) } @@ -360,12 +355,8 @@ impl UpnpServerExt for Server { let openapi = pmoaudiocache::ApiDoc::openapi(); self.add_openapi(api_router, openapi, "audio").await; - // Enregistrer base_url et cache dans le registre global - { - let mut registry = CACHE_REGISTRY.write().unwrap(); - registry.set_base_url(base_url); - registry.set_audio_cache(cache.clone()); - } + // Enregistrer le cache dans le registre global + pmoaudiocache::register_audio_cache(cache.clone()); Ok(cache) } @@ -388,11 +379,11 @@ impl UpnpServerExt for Server { } fn cover_cache(&self) -> Option> { - crate::cache_registry::get_cover_cache() + pmocovers::get_cover_cache() } fn audio_cache(&self) -> Option> { - crate::cache_registry::get_audio_cache() + pmoaudiocache::get_audio_cache() } // ========= SSDP Management Implementation =========