2025-10-17 23:45:01 +02:00
|
|
|
//! 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;
|
2025-10-19 13:42:29 +02:00
|
|
|
use pmoaudiocache::Cache as AudioCache;
|
2025-10-18 23:51:36 +02:00
|
|
|
use pmocache::FileCache;
|
2025-10-17 23:45:01 +02:00
|
|
|
use pmocovers::Cache as CoverCache;
|
2025-10-19 13:42:29 +02:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
use std::sync::RwLock;
|
2025-10-17 23:45:01 +02:00
|
|
|
|
|
|
|
|
/// 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 {
|
2025-10-18 09:58:39 +02:00
|
|
|
/// URL de base du serveur (ex: "http://localhost:8080")
|
|
|
|
|
base_url: Option<String>,
|
|
|
|
|
|
2025-10-17 23:45:01 +02:00
|
|
|
/// Cache de couvertures (WebP)
|
|
|
|
|
cover_cache: Option<Arc<CoverCache>>,
|
|
|
|
|
|
|
|
|
|
/// Cache audio (FLAC)
|
|
|
|
|
audio_cache: Option<Arc<AudioCache>>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CacheRegistry {
|
|
|
|
|
/// Créer un nouveau registre vide
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
2025-10-18 09:58:39 +02:00
|
|
|
base_url: None,
|
2025-10-17 23:45:01 +02:00
|
|
|
cover_cache: None,
|
|
|
|
|
audio_cache: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-18 09:58:39 +02:00
|
|
|
/// 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()
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 23:45:01 +02:00
|
|
|
/// Enregistrer le cache de couvertures
|
|
|
|
|
pub fn set_cover_cache(&mut self, cache: Arc<CoverCache>) {
|
|
|
|
|
self.cover_cache = Some(cache);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Récupérer le cache de couvertures
|
|
|
|
|
pub fn cover_cache(&self) -> Option<Arc<CoverCache>> {
|
|
|
|
|
self.cover_cache.clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Enregistrer le cache audio
|
|
|
|
|
pub fn set_audio_cache(&mut self, cache: Arc<AudioCache>) {
|
|
|
|
|
self.audio_cache = Some(cache);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Récupérer le cache audio
|
|
|
|
|
pub fn audio_cache(&self) -> Option<Arc<AudioCache>> {
|
|
|
|
|
self.audio_cache.clone()
|
|
|
|
|
}
|
2025-10-18 09:58:39 +02:00
|
|
|
|
|
|
|
|
/// 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<usize>) -> anyhow::Result<String> {
|
2025-10-19 13:42:29 +02:00
|
|
|
let base_url = self
|
|
|
|
|
.base_url
|
2025-10-18 09:58:39 +02:00
|
|
|
.as_ref()
|
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("Base URL not set in CacheRegistry"))?;
|
2025-10-19 13:42:29 +02:00
|
|
|
let cache = get_cover_cache().ok_or_else(|| anyhow::anyhow!("No registred cover cache"))?;
|
|
|
|
|
let param = match size {
|
2025-10-18 23:51:36 +02:00
|
|
|
Some(size_) => Some(size_.to_string()),
|
2025-10-19 13:42:29 +02:00
|
|
|
None => None,
|
2025-10-18 23:51:36 +02:00
|
|
|
};
|
2025-10-19 13:42:29 +02:00
|
|
|
let route = cache.route_for(pk, param.as_deref());
|
2025-10-18 09:58:39 +02:00
|
|
|
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<String> {
|
2025-10-19 13:42:29 +02:00
|
|
|
let base_url = self
|
|
|
|
|
.base_url
|
2025-10-18 09:58:39 +02:00
|
|
|
.as_ref()
|
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("Base URL not set in CacheRegistry"))?;
|
2025-10-19 13:42:29 +02:00
|
|
|
let cache = get_audio_cache().ok_or_else(|| anyhow::anyhow!("No registred audio cache"))?;
|
|
|
|
|
let route = cache.route_for(pk, param);
|
2025-10-18 09:58:39 +02:00
|
|
|
Ok(format!("{}{}", base_url, route))
|
|
|
|
|
}
|
2025-10-17 23:45:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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ù.
|
2025-10-19 13:42:29 +02:00
|
|
|
pub(crate) static CACHE_REGISTRY: Lazy<RwLock<CacheRegistry>> =
|
|
|
|
|
Lazy::new(|| RwLock::new(CacheRegistry::new()));
|
2025-10-17 23:45:01 +02:00
|
|
|
|
|
|
|
|
/// 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<Arc<CoverCache>> {
|
|
|
|
|
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<Arc<AudioCache>> {
|
|
|
|
|
CACHE_REGISTRY.read().unwrap().audio_cache()
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 19:49:59 +00:00
|
|
|
/// 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<AudioCache>) {
|
|
|
|
|
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<CoverCache>) {
|
|
|
|
|
CACHE_REGISTRY.write().unwrap().set_cover_cache(cache);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-18 09:58:39 +02:00
|
|
|
/// 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<usize>) -> anyhow::Result<String> {
|
|
|
|
|
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<String> {
|
|
|
|
|
CACHE_REGISTRY.read().unwrap().build_audio_url(pk, param)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 23:45:01 +02:00
|
|
|
#[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());
|
|
|
|
|
}
|
|
|
|
|
}
|