Fix is_valid_pk to support progressive caching properly

Changes:
1. pmocache/cache_trait.rs - Fixed is_valid_pk() logic:
   - Accept files WITH completion markers (complete downloads)
   - Accept files WITHOUT markers but recent (< 60s) (downloads in progress)
   - Reject files WITHOUT markers and old (>= 60s) (failed downloads)

   This preserves progressive caching: files are valid as soon as prebuffer
   completes, without waiting for completion marker.

2. pmoupnp/cache_registry.rs - Added compatibility layer:
   - Re-exports get_audio_cache/get_cover_cache from singletons
   - Provides build_audio_url/build_cover_url for pmosource
   - Uses PMO_SERVER_URL env var for base URL

3. pmoupnp/lib.rs - Added cache_registry module to public API

This fixes "Cache entry not found" errors while maintaining progressive
caching functionality for play_and_cache example.
This commit is contained in:
Claude
2025-11-07 08:09:20 +00:00
parent a5a2ea1181
commit ff859372cf
3 changed files with 126 additions and 10 deletions

View File

@@ -138,8 +138,12 @@ pub trait FileCache<C: CacheConfig>: Send + Sync {
/// ///
/// # Returns /// # Returns
/// ///
/// `true` si l'entrée existe en base de données et que le fichier est présent ET complet /// `true` si l'entrée existe en base de données et que le fichier est présent ET:
/// (avec marker .complete) OU en cours de download /// - SOIT le fichier est complet (marker .complete existe)
/// - SOIT le download est en cours (fichier récent sans marker)
///
/// Ceci permet le progressive caching: les fichiers en cours de download sont acceptés
/// dès que le prebuffer est atteint, sans attendre le marker de completion.
fn is_valid_pk(&self, pk: &str) -> bool { fn is_valid_pk(&self, pk: &str) -> bool {
if self.get_database().get(pk, false).is_err() { if self.get_database().get(pk, false).is_err() {
tracing::debug!("is_valid_pk({}): DB entry not found", pk); tracing::debug!("is_valid_pk({}): DB entry not found", pk);
@@ -152,22 +156,36 @@ pub trait FileCache<C: CacheConfig>: Send + Sync {
return false; return false;
} }
// Vérifier si le fichier est récent (modifié dans les 60 dernières secondes) // Vérifier d'abord si le marker de completion existe
// Ceci détecte les downloads en cours même sans marker .complete let completion_marker = file_path.with_extension(
// Le marker sera vérifié plus tard lors de la lecture effective format!("{}.complete", C::file_extension())
);
if completion_marker.exists() {
tracing::debug!("is_valid_pk({}): Completion marker found, file is complete", pk);
return true;
}
// Pas de marker - vérifier si le download est en cours (fichier récent)
// Un fichier en cours de download aura une modification récente
if let Ok(metadata) = file_path.metadata() { if let Ok(metadata) = file_path.metadata() {
if let Ok(modified) = metadata.modified() { if let Ok(modified) = metadata.modified() {
if let Ok(elapsed) = modified.elapsed() { if let Ok(elapsed) = modified.elapsed() {
let age_secs = elapsed.as_secs(); let age_secs = elapsed.as_secs();
let is_recent = age_secs < 60; if age_secs < 60 {
tracing::debug!("is_valid_pk({}): File age={}s, is_recent={}", pk, age_secs, is_recent); tracing::debug!("is_valid_pk({}): No marker but file is recent ({}s), download in progress", pk, age_secs);
return is_recent; return true;
} else {
tracing::debug!("is_valid_pk({}): No marker and file is old ({}s), incomplete download", pk, age_secs);
return false;
}
} }
} }
} }
tracing::debug!("is_valid_pk({}): Could not check file age, accepting by default", pk); // Ne peut pas vérifier le statut - rejeter par sécurité
true // Si on ne peut pas vérifier la date, on accepter par défaut tracing::debug!("is_valid_pk({}): Could not check file status, rejecting", pk);
false
} }
} }

View File

@@ -0,0 +1,97 @@
//! Registre centralisé des caches pour le serveur UPnP (couche de compatibilité)
//!
//! Ce module fournit une couche de compatibilité pour pmosource qui utilise
//! les singletons de pmoaudiocache et pmocovers pour accéder aux caches.
use pmoaudiocache::Cache as AudioCache;
use pmocache::FileCache;
use pmocovers::Cache as CoverCache;
use std::sync::Arc;
/// 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>> {
pmocovers::get_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>> {
pmoaudiocache::get_audio_cache()
}
/// Construit l'URL complète pour une couverture
///
/// # 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> {
// Récupérer l'URL de base depuis la variable d'environnement ou une config
let base_url = std::env::var("PMO_SERVER_URL")
.unwrap_or_else(|_| "http://localhost:8080".to_string());
let cache = get_cover_cache()
.ok_or_else(|| anyhow::anyhow!("No registered 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", "stream")
///
/// # Examples
///
/// ```rust,ignore
/// use pmoupnp::cache_registry::build_audio_url;
///
/// let url = build_audio_url("abc123", Some("stream"))?;
/// // url = "http://localhost:8080/audio/tracks/abc123/stream"
/// ```
pub fn build_audio_url(pk: &str, param: Option<&str>) -> anyhow::Result<String> {
// Récupérer l'URL de base depuis la variable d'environnement ou une config
let base_url = std::env::var("PMO_SERVER_URL")
.unwrap_or_else(|_| "http://localhost:8080".to_string());
let cache = get_audio_cache()
.ok_or_else(|| anyhow::anyhow!("No registered audio cache"))?;
let route = cache.route_for(pk, param);
Ok(format!("{}{}", base_url, route))
}

View File

@@ -2,6 +2,7 @@ mod object_set;
mod object_trait; mod object_trait;
pub mod actions; pub mod actions;
pub mod cache_registry;
pub mod devices; pub mod devices;
pub mod services; pub mod services;
pub mod soap; pub mod soap;