Merge pull request #16 from coissac/claude/investigate-playlist-manager-011CUrEiQDobxYc8ejQWcixb
Claude/investigate playlist manager 011 c ur ei q dobx yc8ej q wcixb
This commit is contained in:
@@ -17,6 +17,9 @@ use tokio::io::AsyncRead;
|
||||
use tokio::sync::RwLock;
|
||||
use tracing;
|
||||
|
||||
/// Taille minimale de prébuffering par défaut (512 KB = ~5 secondes de FLAC)
|
||||
pub const DEFAULT_PREBUFFER_SIZE: u64 = 512 * 1024;
|
||||
|
||||
/// Paramètres statiques d'un cache spécialisé.
|
||||
pub trait CacheConfig: Send + Sync {
|
||||
/// Extension des fichiers générés (ex: `"webp"`, `"flac"`).
|
||||
@@ -58,6 +61,8 @@ pub struct Cache<C: CacheConfig> {
|
||||
downloads: Arc<RwLock<HashMap<String, Arc<Download>>>>,
|
||||
/// Factory pour créer des transformers (optionnel)
|
||||
transformer_factory: Option<Arc<dyn Fn() -> StreamTransformer + Send + Sync>>,
|
||||
/// Taille minimale de prébuffering en octets (0 = désactivé)
|
||||
min_prebuffer_size: u64,
|
||||
/// Phantom data pour le type de configuration
|
||||
_phantom: std::marker::PhantomData<C>,
|
||||
}
|
||||
@@ -124,10 +129,39 @@ impl<C: CacheConfig> Cache<C> {
|
||||
db: Arc::new(db),
|
||||
downloads: Arc::new(RwLock::new(HashMap::new())),
|
||||
transformer_factory,
|
||||
min_prebuffer_size: DEFAULT_PREBUFFER_SIZE,
|
||||
_phantom: std::marker::PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Configure la taille minimale de prébuffering
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `size` - Taille minimale en octets (0 = désactivé)
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pmocache::{Cache, CacheConfig};
|
||||
///
|
||||
/// struct MyConfig;
|
||||
/// impl CacheConfig for MyConfig {
|
||||
/// fn file_extension() -> &'static str { "dat" }
|
||||
/// }
|
||||
///
|
||||
/// let mut cache = Cache::<MyConfig>::new("./cache", 1000).unwrap();
|
||||
/// cache.set_prebuffer_size(1024 * 1024); // 1 MB de prébuffering
|
||||
/// ```
|
||||
pub fn set_prebuffer_size(&mut self, size: u64) {
|
||||
self.min_prebuffer_size = size;
|
||||
}
|
||||
|
||||
/// Retourne la taille minimale de prébuffering configurée
|
||||
pub fn get_prebuffer_size(&self) -> u64 {
|
||||
self.min_prebuffer_size
|
||||
}
|
||||
|
||||
/// Télécharge un fichier depuis une URL et l'ajoute au cache
|
||||
///
|
||||
/// Cette méthode utilise un système d'identifiants basé sur le contenu plutôt que sur l'URL.
|
||||
@@ -166,35 +200,48 @@ impl<C: CacheConfig> Cache<C> {
|
||||
let pk = crate::cache_trait::pk_from_content_header(&header);
|
||||
tracing::debug!("Computed pk {} for URL {}", pk, url);
|
||||
|
||||
// 3. Vérifier si le fichier est déjà en cache
|
||||
// 3. Vérifier si le fichier est déjà en cache ET complet
|
||||
if self.db.get(&pk, false).is_ok() {
|
||||
let file_path = self.get_file_path(&pk);
|
||||
if file_path.exists() {
|
||||
// Déjà en cache, update timestamp et retour rapide
|
||||
tracing::debug!("File with pk {} already in cache, updating timestamp", pk);
|
||||
self.db.update_hit(&pk)?;
|
||||
return Ok(pk);
|
||||
// Vérifier si le fichier semble complet (taille >= min_prebuffer_size)
|
||||
if let Ok(metadata) = std::fs::metadata(&file_path) {
|
||||
let file_size = metadata.len();
|
||||
if self.min_prebuffer_size > 0 && file_size < self.min_prebuffer_size {
|
||||
tracing::warn!(
|
||||
"File with pk {} in cache is too small ({} bytes < {} bytes), will re-download/re-ingest",
|
||||
pk, file_size, self.min_prebuffer_size
|
||||
);
|
||||
// Supprimer le fichier incomplet
|
||||
let _ = std::fs::remove_file(&file_path);
|
||||
// Continuer avec le téléchargement/ingestion
|
||||
} else {
|
||||
// Déjà en cache et complet, update timestamp et retour rapide
|
||||
tracing::debug!("File with pk {} already in cache, updating timestamp", pk);
|
||||
self.db.update_hit(&pk)?;
|
||||
return Ok(pk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Vérifier si un download est déjà en cours pour ce pk
|
||||
{
|
||||
let download_handle = {
|
||||
let downloads = self.downloads.read().await;
|
||||
if downloads.contains_key(&pk) {
|
||||
// Download déjà en cours pour ce contenu, attendre que le fichier soit créé
|
||||
tracing::debug!("Download already in progress for pk {}", pk);
|
||||
drop(downloads); // Libérer le lock avant la boucle d'attente
|
||||
downloads.get(&pk).cloned()
|
||||
};
|
||||
|
||||
// Attendre que le fichier soit créé (pour le cache progressif)
|
||||
let file_path = self.get_file_path(&pk);
|
||||
let mut attempts = 0;
|
||||
while !file_path.exists() && attempts < 100 {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
attempts += 1;
|
||||
}
|
||||
if let Some(download) = download_handle {
|
||||
// Download déjà en cours pour ce contenu, attendre le prébuffering
|
||||
tracing::debug!("Download already in progress for pk {}, waiting for prebuffering", pk);
|
||||
|
||||
return Ok(pk);
|
||||
if self.min_prebuffer_size > 0 {
|
||||
download.wait_until_min_size(self.min_prebuffer_size).await
|
||||
.map_err(|e| anyhow!("Prebuffering failed: {}", e))?;
|
||||
tracing::debug!("Prebuffering complete for pk {}", pk);
|
||||
}
|
||||
|
||||
return Ok(pk);
|
||||
}
|
||||
|
||||
// 5. Lancer le téléchargement complet avec transformer
|
||||
@@ -217,6 +264,13 @@ impl<C: CacheConfig> Cache<C> {
|
||||
tracing::warn!("Error enforcing cache limit: {}", e);
|
||||
}
|
||||
|
||||
// Attendre le prébuffering (pour le cache progressif)
|
||||
if self.min_prebuffer_size > 0 {
|
||||
download.wait_until_min_size(self.min_prebuffer_size).await
|
||||
.map_err(|e| anyhow!("Prebuffering failed: {}", e))?;
|
||||
tracing::debug!("Prebuffering complete for pk {} ({} bytes)", pk, self.min_prebuffer_size);
|
||||
}
|
||||
|
||||
// Lancer une tâche de nettoyage en background
|
||||
let downloads_clone = self.downloads.clone();
|
||||
let pk_clone = pk.clone();
|
||||
@@ -225,23 +279,6 @@ impl<C: CacheConfig> Cache<C> {
|
||||
downloads_clone.write().await.remove(&pk_clone);
|
||||
});
|
||||
|
||||
// Attendre que le fichier soit créé sur disque (pour le cache progressif)
|
||||
// On attend jusqu'à 5 secondes maximum
|
||||
let file_path = self.get_file_path(&pk);
|
||||
let mut attempts = 0;
|
||||
while !file_path.exists() && attempts < 100 {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
if !file_path.exists() {
|
||||
tracing::warn!(
|
||||
"File {} not created after waiting 5 seconds, pk={}",
|
||||
file_path.display(),
|
||||
pk
|
||||
);
|
||||
}
|
||||
|
||||
Ok(pk)
|
||||
}
|
||||
|
||||
@@ -292,24 +329,48 @@ impl<C: CacheConfig> Cache<C> {
|
||||
tracing::debug!("Computed pk {} from reader", pk);
|
||||
}
|
||||
|
||||
// 3. Vérifier si le fichier est déjà en cache
|
||||
// 3. Vérifier si le fichier est déjà en cache ET complet
|
||||
if self.db.get(&pk, false).is_ok() {
|
||||
let file_path = self.get_file_path(&pk);
|
||||
if file_path.exists() {
|
||||
// Déjà en cache, update timestamp et retour rapide
|
||||
tracing::debug!("File with pk {} already in cache, updating timestamp", pk);
|
||||
self.db.update_hit(&pk)?;
|
||||
return Ok(pk);
|
||||
// Vérifier si le fichier semble complet (taille >= min_prebuffer_size)
|
||||
if let Ok(metadata) = std::fs::metadata(&file_path) {
|
||||
let file_size = metadata.len();
|
||||
if self.min_prebuffer_size > 0 && file_size < self.min_prebuffer_size {
|
||||
tracing::warn!(
|
||||
"File with pk {} in cache is too small ({} bytes < {} bytes), will re-download/re-ingest",
|
||||
pk, file_size, self.min_prebuffer_size
|
||||
);
|
||||
// Supprimer le fichier incomplet
|
||||
let _ = std::fs::remove_file(&file_path);
|
||||
// Continuer avec le téléchargement/ingestion
|
||||
} else {
|
||||
// Déjà en cache et complet, update timestamp et retour rapide
|
||||
tracing::debug!("File with pk {} already in cache, updating timestamp", pk);
|
||||
self.db.update_hit(&pk)?;
|
||||
return Ok(pk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Vérifier si un download est déjà en cours pour ce pk
|
||||
{
|
||||
let download_handle = {
|
||||
let downloads = self.downloads.read().await;
|
||||
if downloads.contains_key(&pk) {
|
||||
tracing::debug!("Download already in progress for pk {}", pk);
|
||||
return Ok(pk);
|
||||
downloads.get(&pk).cloned()
|
||||
};
|
||||
|
||||
if let Some(download) = download_handle {
|
||||
// Download déjà en cours pour ce contenu, attendre le prébuffering
|
||||
tracing::debug!("Download already in progress for pk {}, waiting for prebuffering", pk);
|
||||
|
||||
if self.min_prebuffer_size > 0 {
|
||||
download.wait_until_min_size(self.min_prebuffer_size).await
|
||||
.map_err(|e| anyhow!("Prebuffering failed: {}", e))?;
|
||||
tracing::debug!("Prebuffering complete for pk {}", pk);
|
||||
}
|
||||
|
||||
return Ok(pk);
|
||||
}
|
||||
|
||||
// 5. Reconstituer le reader complet (header + reste)
|
||||
@@ -339,6 +400,14 @@ impl<C: CacheConfig> Cache<C> {
|
||||
tracing::warn!("Error enforcing cache limit: {}", e);
|
||||
}
|
||||
|
||||
// Attendre le prébuffering (pour le cache progressif)
|
||||
if self.min_prebuffer_size > 0 {
|
||||
download.wait_until_min_size(self.min_prebuffer_size).await
|
||||
.map_err(|e| anyhow!("Prebuffering failed: {}", e))?;
|
||||
tracing::debug!("Prebuffering complete for pk {} ({} bytes)", pk, self.min_prebuffer_size);
|
||||
}
|
||||
|
||||
// Lancer une tâche de nettoyage en background
|
||||
let downloads_clone = self.downloads.clone();
|
||||
let pk_clone = pk.clone();
|
||||
tokio::spawn(async move {
|
||||
@@ -346,23 +415,6 @@ impl<C: CacheConfig> Cache<C> {
|
||||
downloads_clone.write().await.remove(&pk_clone);
|
||||
});
|
||||
|
||||
// Attendre que le fichier soit créé sur disque (pour le cache progressif)
|
||||
// On attend jusqu'à 5 secondes maximum
|
||||
let file_path = self.get_file_path(&pk);
|
||||
let mut attempts = 0;
|
||||
while !file_path.exists() && attempts < 100 {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
attempts += 1;
|
||||
}
|
||||
|
||||
if !file_path.exists() {
|
||||
tracing::warn!(
|
||||
"File {} not created after waiting 5 seconds, pk={}",
|
||||
file_path.display(),
|
||||
pk
|
||||
);
|
||||
}
|
||||
|
||||
Ok(pk)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user