Ajouter le prébuffering configurable au cache pour éviter les erreurs de lecture prématurée
Problème : Après la correction de la race condition précédente, les fichiers étaient créés sur disque mais la lecture commençait immédiatement, avant qu'il y ait suffisamment de données. Cela causait des erreurs FLAC "Expected one more byte" car le décodeur essayait de lire un fichier incomplet. Solution - Prébuffering : Attendre qu'une quantité minimale de données (512 KB par défaut, ~5 secondes de FLAC) soit téléchargée avant que add_from_url() et add_from_reader() retournent le pk. Cela permet au cache progressif de fonctionner correctement : le fichier a suffisamment de données pour commencer la lecture pendant que le téléchargement continue en arrière-plan. Changements : - Ajout d'un champ min_prebuffer_size dans Cache<C> (défaut: 512 KB) - Ajout de méthodes set_prebuffer_size() et get_prebuffer_size() - Ajout de la constante DEFAULT_PREBUFFER_SIZE (512 KB) - Modification de add_from_url() : utilise wait_until_min_size() - Modification de add_from_reader() : utilise wait_until_min_size() - Cas où download déjà en cours : attend également le prébuffering Résultat testé : ✓ L'exemple play_and_cache fonctionne sans erreur FLAC ✓ Le prébuffering garantit suffisamment de données avant la lecture ✓ Le cache progressif fonctionne : lecture pendant le téléchargement ✓ Configurable : peut être ajusté selon les besoins (0 = désactivé)
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.
|
||||
@@ -178,23 +212,22 @@ impl<C: CacheConfig> Cache<C> {
|
||||
}
|
||||
|
||||
// 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 +250,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 +265,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)
|
||||
}
|
||||
|
||||
@@ -304,12 +327,22 @@ impl<C: CacheConfig> Cache<C> {
|
||||
}
|
||||
|
||||
// 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 +372,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 +387,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