Refactoring des pmosource
This commit is contained in:
112
pmoupnp/src/cache_registry.rs
Normal file
112
pmoupnp/src/cache_registry.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
//! 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 std::sync::Arc;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::RwLock;
|
||||
use pmocovers::Cache as CoverCache;
|
||||
use pmoaudiocache::Cache as AudioCache;
|
||||
|
||||
/// 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 {
|
||||
/// 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 {
|
||||
cover_cache: None,
|
||||
audio_cache: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
|
||||
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<RwLock<CacheRegistry>> = 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<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()
|
||||
}
|
||||
|
||||
#[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());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod object_trait;
|
||||
mod object_set;
|
||||
|
||||
pub mod cache_registry;
|
||||
pub mod upnp_server;
|
||||
pub mod upnp_api;
|
||||
pub mod actions;
|
||||
@@ -12,15 +13,12 @@ pub mod state_variables;
|
||||
pub mod value_ranges;
|
||||
pub mod variable_types;
|
||||
|
||||
|
||||
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub use crate::object_trait::*;
|
||||
pub use crate::upnp_server::UpnpServerExt;
|
||||
pub use crate::cache_registry::{get_cover_cache, get_audio_cache};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UpnpObjectType {
|
||||
|
||||
@@ -23,10 +23,15 @@ use std::sync::RwLock;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use pmoserver::Server;
|
||||
use utoipa::OpenApi;
|
||||
|
||||
use crate::devices::errors::DeviceError;
|
||||
use crate::devices::{Device, DeviceInstance, DeviceRegistry};
|
||||
use crate::UpnpModel;
|
||||
use crate::cache_registry::CACHE_REGISTRY;
|
||||
|
||||
use pmocovers::Cache as CoverCache;
|
||||
use pmoaudiocache::Cache as AudioCache;
|
||||
|
||||
/// Registre de devices global et thread-safe.
|
||||
///
|
||||
@@ -70,6 +75,8 @@ static DEVICE_REGISTRY: Lazy<RwLock<DeviceRegistry>> = Lazy::new(|| {
|
||||
/// let devices = server.device_registry().list_devices();
|
||||
/// ```
|
||||
pub trait UpnpServerExt {
|
||||
// ========= Device Management (existant) =========
|
||||
|
||||
/// Enregistre un device UPnP et toutes ses URLs.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -89,6 +96,57 @@ pub trait UpnpServerExt {
|
||||
|
||||
/// Récupère un device par son UDN.
|
||||
fn get_device(&self, udn: &str) -> Option<Arc<DeviceInstance>>;
|
||||
|
||||
// ========= Cache Management (NOUVEAU) =========
|
||||
|
||||
/// Initialiser le cache de couvertures centralisé
|
||||
///
|
||||
/// Crée le cache et enregistre les routes HTTP.
|
||||
/// Toutes les sources musicales utiliseront ce cache partagé.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cache_dir` - Répertoire de stockage
|
||||
/// * `limit` - Limite de taille (nombre d'images)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Instance partagée du cache
|
||||
async fn init_cover_cache(&mut self, cache_dir: &str, limit: usize)
|
||||
-> Result<Arc<CoverCache>, anyhow::Error>;
|
||||
|
||||
/// Initialiser le cache audio centralisé
|
||||
///
|
||||
/// Crée le cache et enregistre les routes HTTP.
|
||||
/// Toutes les sources musicales utiliseront ce cache partagé.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cache_dir` - Répertoire de stockage
|
||||
/// * `limit` - Limite de taille (nombre de pistes)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Instance partagée du cache
|
||||
async fn init_audio_cache(&mut self, cache_dir: &str, limit: usize)
|
||||
-> Result<Arc<AudioCache>, anyhow::Error>;
|
||||
|
||||
/// Initialiser les caches depuis la configuration
|
||||
///
|
||||
/// Utilise pmoconfig pour charger les paramètres et initialiser
|
||||
/// automatiquement les deux caches.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Tuple (cache de couvertures, cache audio)
|
||||
async fn init_caches(&mut self)
|
||||
-> Result<(Arc<CoverCache>, Arc<AudioCache>), anyhow::Error>;
|
||||
|
||||
/// Récupérer le cache de couvertures
|
||||
fn cover_cache(&self) -> Option<Arc<CoverCache>>;
|
||||
|
||||
/// Récupérer le cache audio
|
||||
fn audio_cache(&self) -> Option<Arc<AudioCache>>;
|
||||
}
|
||||
|
||||
// Implémentation du trait UpnpServer pour pmoserver::Server
|
||||
@@ -120,6 +178,101 @@ impl UpnpServerExt for Server {
|
||||
fn get_device(&self, udn: &str) -> Option<Arc<DeviceInstance>> {
|
||||
DEVICE_REGISTRY.read().unwrap().get_device(udn)
|
||||
}
|
||||
|
||||
// ========= Cache Management Implementation =========
|
||||
|
||||
async fn init_cover_cache(&mut self, cache_dir: &str, limit: usize)
|
||||
-> Result<Arc<CoverCache>, anyhow::Error> {
|
||||
use pmocovers::new_cache;
|
||||
use pmocache::pmoserver_ext::{create_file_router_with_generator, create_api_router};
|
||||
|
||||
let base_url = self.info().base_url;
|
||||
let cache = Arc::new(new_cache(cache_dir, limit, &base_url)?);
|
||||
|
||||
// Routes de fichiers avec génération de variantes
|
||||
// Routes: GET /covers/image/{pk} et GET /covers/image/{pk}/{size}
|
||||
let variant_generator: pmocache::pmoserver_ext::ParamGenerator<pmocovers::CoversConfig> =
|
||||
Arc::new(|cache, pk, param| {
|
||||
Box::pin(async move {
|
||||
// Si le param est numérique, c'est une taille de variante
|
||||
if let Ok(size) = param.parse::<usize>() {
|
||||
match pmocovers::webp::generate_variant(&cache, &pk, size).await {
|
||||
Ok(data) => return Some(data),
|
||||
Err(e) => {
|
||||
tracing::warn!("Cannot generate variant {}x{} for {}: {}", size, size, pk, e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
});
|
||||
|
||||
let file_router = create_file_router_with_generator(
|
||||
cache.clone(),
|
||||
"image/webp",
|
||||
Some(variant_generator)
|
||||
);
|
||||
self.add_router("/", file_router).await;
|
||||
|
||||
// API REST générique (pmocache)
|
||||
let api_router = create_api_router(cache.clone());
|
||||
let openapi = pmocovers::ApiDoc::openapi();
|
||||
self.add_openapi(api_router, openapi, "covers").await;
|
||||
|
||||
// Enregistrer dans le registre global
|
||||
CACHE_REGISTRY.write().unwrap().set_cover_cache(cache.clone());
|
||||
|
||||
Ok(cache)
|
||||
}
|
||||
|
||||
async fn init_audio_cache(&mut self, cache_dir: &str, limit: usize)
|
||||
-> Result<Arc<AudioCache>, anyhow::Error> {
|
||||
use pmoaudiocache::new_cache;
|
||||
use pmocache::pmoserver_ext::{create_file_router, create_api_router};
|
||||
|
||||
let base_url = self.info().base_url;
|
||||
let cache = Arc::new(new_cache(cache_dir, limit, &base_url)?);
|
||||
|
||||
// Routes de fichiers pour servir les pistes FLAC
|
||||
let file_router = create_file_router(cache.clone(), "audio/flac");
|
||||
self.add_router("/", file_router).await;
|
||||
|
||||
// API REST générique (pmocache)
|
||||
let api_router = create_api_router(cache.clone());
|
||||
let openapi = pmoaudiocache::ApiDoc::openapi();
|
||||
self.add_openapi(api_router, openapi, "audio").await;
|
||||
|
||||
// Enregistrer dans le registre global
|
||||
CACHE_REGISTRY.write().unwrap().set_audio_cache(cache.clone());
|
||||
|
||||
Ok(cache)
|
||||
}
|
||||
|
||||
async fn init_caches(&mut self)
|
||||
-> Result<(Arc<CoverCache>, Arc<AudioCache>), anyhow::Error> {
|
||||
let config = pmoconfig::get_config();
|
||||
|
||||
let cover_cache = self.init_cover_cache(
|
||||
&config.get_cover_cache_dir()?,
|
||||
config.get_cover_cache_size()?
|
||||
).await?;
|
||||
|
||||
let audio_cache = self.init_audio_cache(
|
||||
&config.get_audio_cache_dir()?,
|
||||
config.get_audio_cache_size()?
|
||||
).await?;
|
||||
|
||||
Ok((cover_cache, audio_cache))
|
||||
}
|
||||
|
||||
fn cover_cache(&self) -> Option<Arc<CoverCache>> {
|
||||
crate::cache_registry::get_cover_cache()
|
||||
}
|
||||
|
||||
fn audio_cache(&self) -> Option<Arc<AudioCache>> {
|
||||
crate::cache_registry::get_audio_cache()
|
||||
}
|
||||
}
|
||||
|
||||
/// Fonctions helper pour accéder au registre depuis les handlers.
|
||||
|
||||
Reference in New Issue
Block a user