Ajout de fonctionnalité de download asynchrone au pmocache
This commit is contained in:
205
pmocache/examples/README_EXAMPLES.md
Normal file
205
pmocache/examples/README_EXAMPLES.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# Exemples du module Download
|
||||
|
||||
Ce répertoire contient des exemples d'utilisation du module `download` de pmocache.
|
||||
|
||||
## Fichiers
|
||||
|
||||
### `test_download.rs`
|
||||
Exemple basique de téléchargement sans transformation.
|
||||
|
||||
**Utilisation:**
|
||||
```bash
|
||||
cargo run --example test_download
|
||||
```
|
||||
|
||||
### `test_download_transformer.rs`
|
||||
Exemples complets de transformers :
|
||||
- Transformation en majuscules
|
||||
- Suppression de header (skip N bytes)
|
||||
- Numérotation des lignes
|
||||
- Compression GZIP (commenté, nécessite async-compression)
|
||||
|
||||
**Utilisation:**
|
||||
```bash
|
||||
cargo run --example test_download_transformer
|
||||
```
|
||||
|
||||
### `simple_transformer.rs`
|
||||
Exemple de documentation montrant la syntaxe et l'API.
|
||||
|
||||
**Utilisation:**
|
||||
```bash
|
||||
cargo run --example simple_transformer
|
||||
```
|
||||
|
||||
## Concepts clés
|
||||
|
||||
### 1. Téléchargement simple
|
||||
|
||||
```rust
|
||||
use pmocache::download::download;
|
||||
|
||||
let dl = download("/tmp/file.dat", "https://example.com/file.dat");
|
||||
dl.wait_until_finished().await?;
|
||||
```
|
||||
|
||||
### 2. Téléchargement avec transformer
|
||||
|
||||
Un transformer est une fonction qui :
|
||||
1. Reçoit le stream de réponse HTTP
|
||||
2. Reçoit un fichier ouvert en écriture
|
||||
3. Reçoit un callback de progression
|
||||
4. Traite les données à la volée
|
||||
5. Écrit le résultat transformé dans le fichier
|
||||
|
||||
```rust
|
||||
let transformer: StreamTransformer = Box::new(|response, mut file, update_progress| {
|
||||
Box::pin(async move {
|
||||
let mut stream = response.bytes_stream();
|
||||
let mut total = 0u64;
|
||||
|
||||
while let Some(chunk_result) = stream.next().await {
|
||||
let chunk = chunk_result.map_err(|e| e.to_string())?;
|
||||
|
||||
// Transformer les données
|
||||
let transformed = your_transformation(&chunk);
|
||||
|
||||
// Écrire le résultat
|
||||
file.write_all(&transformed).await.map_err(|e| e.to_string())?;
|
||||
|
||||
// Mettre à jour la progression
|
||||
total += transformed.len() as u64;
|
||||
update_progress(total);
|
||||
}
|
||||
|
||||
file.flush().await.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
let dl = download_with_transformer("/tmp/output.dat", "https://example.com/input.dat", Some(transformer));
|
||||
```
|
||||
|
||||
### 3. Suivi de progression
|
||||
|
||||
```rust
|
||||
let dl = download("/tmp/file.dat", "https://example.com/file.dat");
|
||||
|
||||
// Attendre au moins 1MB
|
||||
dl.wait_until_min_size(1024 * 1024).await?;
|
||||
println!("Au moins 1MB téléchargés");
|
||||
|
||||
// Voir la progression
|
||||
loop {
|
||||
let current = dl.current_size().await;
|
||||
let expected = dl.expected_size().await;
|
||||
|
||||
if let Some(total) = expected {
|
||||
println!("Progression: {}/{} bytes ({:.1}%)",
|
||||
current, total, 100.0 * current as f64 / total as f64);
|
||||
} else {
|
||||
println!("Téléchargés: {} bytes", current);
|
||||
}
|
||||
|
||||
if dl.finished().await {
|
||||
break;
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
```
|
||||
|
||||
## Cas d'usage pour PMOMusic
|
||||
|
||||
### Conversion d'images pour le cache
|
||||
|
||||
```rust
|
||||
// Télécharger une couverture d'album et la convertir en WebP
|
||||
fn webp_transformer() -> StreamTransformer {
|
||||
Box::new(|response, mut file, update_progress| {
|
||||
Box::pin(async move {
|
||||
let bytes = response.bytes().await.map_err(|e| e.to_string())?;
|
||||
let img = image::load_from_memory(&bytes)
|
||||
.map_err(|e| format!("Decode error: {}", e))?;
|
||||
|
||||
let encoder = webp::Encoder::from_image(&img)
|
||||
.map_err(|e| format!("Encode error: {}", e))?;
|
||||
let webp = encoder.encode(75.0);
|
||||
|
||||
file.write_all(&*webp).await.map_err(|e| e.to_string())?;
|
||||
file.flush().await.map_err(|e| e.to_string())?;
|
||||
|
||||
update_progress(webp.len() as u64);
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Utilisation dans pmocovers
|
||||
let transformer = webp_transformer();
|
||||
let dl = download_with_transformer(cache_path, cover_url, Some(transformer));
|
||||
```
|
||||
|
||||
### Conversion audio pour le cache
|
||||
|
||||
```rust
|
||||
// Télécharger du MP3 et le convertir en FLAC
|
||||
fn mp3_to_flac_transformer() -> StreamTransformer {
|
||||
Box::new(|response, mut file, update_progress| {
|
||||
Box::pin(async move {
|
||||
let mp3_bytes = response.bytes().await.map_err(|e| e.to_string())?;
|
||||
|
||||
// Décoder MP3
|
||||
let decoded = decode_mp3(&mp3_bytes)?;
|
||||
|
||||
// Encoder FLAC
|
||||
let flac_bytes = encode_flac(&decoded)?;
|
||||
|
||||
file.write_all(&flac_bytes).await.map_err(|e| e.to_string())?;
|
||||
file.flush().await.map_err(|e| e.to_string())?;
|
||||
|
||||
update_progress(flac_bytes.len() as u64);
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Utilisation dans pmoaudiocache
|
||||
let transformer = mp3_to_flac_transformer();
|
||||
let dl = download_with_transformer(cache_path, audio_url, Some(transformer));
|
||||
```
|
||||
|
||||
### Streaming progressif
|
||||
|
||||
```rust
|
||||
// Commencer à lire pendant le téléchargement
|
||||
let dl = download(audio_path, stream_url);
|
||||
|
||||
// Attendre le buffer minimal (256KB)
|
||||
dl.wait_until_min_size(256 * 1024).await?;
|
||||
|
||||
// Ouvrir et commencer à lire
|
||||
let mut file = dl.open()?;
|
||||
let mut buffer = [0u8; 4096];
|
||||
|
||||
loop {
|
||||
// Lire ce qui est disponible
|
||||
match file.read(&mut buffer) {
|
||||
Ok(0) if dl.finished().await => break, // EOF
|
||||
Ok(0) => {
|
||||
// Pas encore de données, attendre un peu
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
Ok(n) => {
|
||||
// Traiter les données lues
|
||||
process_audio_chunk(&buffer[..n]);
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Voir aussi
|
||||
|
||||
- [DOWNLOAD_MODULE.md](../DOWNLOAD_MODULE.md) - Documentation complète du module
|
||||
- [src/download.rs](../src/download.rs) - Code source
|
||||
53
pmocache/examples/simple_transformer.rs
Normal file
53
pmocache/examples/simple_transformer.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
/// Exemple minimal de transformer sans dépendances externes complexes
|
||||
|
||||
// Import direct du type depuis le module
|
||||
// Note: Cet exemple montre comment utiliser l'API de transformation
|
||||
|
||||
fn main() {
|
||||
println!("Exemple d'utilisation du module download avec transformers\n");
|
||||
|
||||
println!("1. Téléchargement simple:");
|
||||
println!(" let dl = download(\"/tmp/file.dat\", \"https://example.com/file.dat\");");
|
||||
println!(" dl.wait_until_finished().await?;\n");
|
||||
|
||||
println!("2. Téléchargement avec transformation:");
|
||||
println!(" let transformer: StreamTransformer = Box::new(|response, mut file, update_progress| {{");
|
||||
println!(" Box::pin(async move {{");
|
||||
println!(" let mut stream = response.bytes_stream();");
|
||||
println!(" let mut total = 0u64;");
|
||||
println!();
|
||||
println!(" while let Some(chunk_result) = stream.next().await {{");
|
||||
println!(" let chunk = chunk_result.map_err(|e| e.to_string())?;");
|
||||
println!();
|
||||
println!(" // Transformation ici (ex: compression, conversion)");
|
||||
println!(" let transformed = process(chunk);");
|
||||
println!();
|
||||
println!(" file.write_all(&transformed).await.map_err(|e| e.to_string())?;");
|
||||
println!(" total += transformed.len() as u64;");
|
||||
println!(" update_progress(total);");
|
||||
println!(" }}");
|
||||
println!();
|
||||
println!(" file.flush().await.map_err(|e| e.to_string())?;");
|
||||
println!(" Ok(())");
|
||||
println!(" }})");
|
||||
println!(" }});\n");
|
||||
|
||||
println!(" let dl = download_with_transformer(\"/tmp/out.dat\", \"https://example.com/in.dat\", Some(transformer));");
|
||||
println!(" dl.wait_until_finished().await?;\n");
|
||||
|
||||
println!("3. Méthodes disponibles sur Download:");
|
||||
println!(" - filename() : Chemin du fichier");
|
||||
println!(" - current_size() : Taille téléchargée (source)");
|
||||
println!(" - transformed_size() : Taille transformée (destination)");
|
||||
println!(" - expected_size() : Taille attendue (Content-Length)");
|
||||
println!(" - finished() : Téléchargement terminé?");
|
||||
println!(" - error() : Erreur éventuelle");
|
||||
println!(" - wait_until_min_size(n) : Attend au moins n bytes");
|
||||
println!(" - wait_until_finished() : Attend la fin");
|
||||
println!(" - open() : Ouvre le fichier pour lecture");
|
||||
println!(" - pos() / set_pos() : Position de lecture\n");
|
||||
|
||||
println!("Pour des exemples complets, voir:");
|
||||
println!(" - examples/test_download_transformer.rs");
|
||||
println!(" - DOWNLOAD_MODULE.md");
|
||||
}
|
||||
26
pmocache/examples/test_download.rs
Normal file
26
pmocache/examples/test_download.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
// Simple test pour vérifier la compilation du module download
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("Module download compilé avec succès!");
|
||||
|
||||
// Test basique (commenté pour ne pas vraiment télécharger)
|
||||
/*
|
||||
let dl = download::download("/tmp/test.html", "https://www.rust-lang.org/");
|
||||
|
||||
println!("Téléchargement démarré...");
|
||||
|
||||
match dl.wait_until_min_size(100).await {
|
||||
Ok(_) => println!("Au moins 100 bytes téléchargés"),
|
||||
Err(e) => eprintln!("Erreur: {}", e),
|
||||
}
|
||||
|
||||
match dl.wait_until_finished().await {
|
||||
Ok(_) => {
|
||||
println!("Téléchargement terminé!");
|
||||
println!("Taille finale: {} bytes", dl.current_size().await);
|
||||
}
|
||||
Err(e) => eprintln!("Erreur: {}", e),
|
||||
}
|
||||
*/
|
||||
}
|
||||
293
pmocache/examples/test_download_transformer.rs
Normal file
293
pmocache/examples/test_download_transformer.rs
Normal file
@@ -0,0 +1,293 @@
|
||||
// Exemple d'utilisation du module download avec transformations
|
||||
|
||||
use pmocache::download::{download_with_transformer, StreamTransformer};
|
||||
use futures_util::StreamExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
/// Exemple de transformer qui compresse les données en gzip
|
||||
///
|
||||
/// Note: Cette fonction nécessite la dépendance `async-compression`
|
||||
/// Pour l'utiliser, ajoutez à Cargo.toml:
|
||||
/// ```toml
|
||||
/// [dev-dependencies]
|
||||
/// async-compression = { version = "0.4", features = ["tokio", "gzip"] }
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
fn create_gzip_transformer() -> StreamTransformer {
|
||||
// Commenté car nécessite async-compression
|
||||
// Décommentez si vous ajoutez la dépendance
|
||||
unimplemented!("Cette fonction nécessite la dépendance async-compression")
|
||||
|
||||
/*
|
||||
Box::new(|response, mut file, update_progress| {
|
||||
Box::pin(async move {
|
||||
use async_compression::tokio::write::GzipEncoder;
|
||||
|
||||
let mut encoder = GzipEncoder::new(&mut file);
|
||||
let mut stream = response.bytes_stream();
|
||||
let mut total_written = 0u64;
|
||||
|
||||
while let Some(chunk_result) = stream.next().await {
|
||||
let chunk = chunk_result.map_err(|e| format!("Failed to read chunk: {}", e))?;
|
||||
|
||||
encoder
|
||||
.write_all(&chunk)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write compressed data: {}", e))?;
|
||||
|
||||
total_written += chunk.len() as u64;
|
||||
update_progress(total_written);
|
||||
}
|
||||
|
||||
encoder
|
||||
.shutdown()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to finalize compression: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
/// Exemple de transformer qui convertit les données en majuscules (exemple simple)
|
||||
fn create_uppercase_transformer() -> StreamTransformer {
|
||||
Box::new(|response, mut file, update_progress| {
|
||||
Box::pin(async move {
|
||||
let mut stream = response.bytes_stream();
|
||||
let mut total_written = 0u64;
|
||||
|
||||
while let Some(chunk_result) = stream.next().await {
|
||||
let chunk = chunk_result.map_err(|e| format!("Failed to read chunk: {}", e))?;
|
||||
|
||||
// Transformer en majuscules (seulement pour texte ASCII)
|
||||
let transformed: Vec<u8> = chunk
|
||||
.iter()
|
||||
.map(|&b| if b.is_ascii_lowercase() { b.to_ascii_uppercase() } else { b })
|
||||
.collect();
|
||||
|
||||
file.write_all(&transformed)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write: {}", e))?;
|
||||
|
||||
total_written += transformed.len() as u64;
|
||||
update_progress(total_written);
|
||||
}
|
||||
|
||||
file.flush()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to flush: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Exemple de transformer qui saute les N premiers bytes (utile pour enlever des headers)
|
||||
fn create_skip_header_transformer(skip_bytes: usize) -> StreamTransformer {
|
||||
Box::new(move |response, mut file, update_progress| {
|
||||
Box::pin(async move {
|
||||
let mut stream = response.bytes_stream();
|
||||
let mut skipped = 0usize;
|
||||
let mut total_written = 0u64;
|
||||
|
||||
while let Some(chunk_result) = stream.next().await {
|
||||
let chunk = chunk_result.map_err(|e| format!("Failed to read chunk: {}", e))?;
|
||||
|
||||
let to_write = if skipped < skip_bytes {
|
||||
let remaining_to_skip = skip_bytes - skipped;
|
||||
if chunk.len() <= remaining_to_skip {
|
||||
skipped += chunk.len();
|
||||
continue;
|
||||
} else {
|
||||
skipped = skip_bytes;
|
||||
&chunk[remaining_to_skip..]
|
||||
}
|
||||
} else {
|
||||
&chunk[..]
|
||||
};
|
||||
|
||||
file.write_all(to_write)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write: {}", e))?;
|
||||
|
||||
total_written += to_write.len() as u64;
|
||||
update_progress(total_written);
|
||||
}
|
||||
|
||||
file.flush()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to flush: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Exemple de transformer qui compte les lignes et ajoute des numéros
|
||||
fn create_line_number_transformer() -> StreamTransformer {
|
||||
Box::new(|response, mut file, update_progress| {
|
||||
Box::pin(async move {
|
||||
let mut stream = response.bytes_stream();
|
||||
let mut line_number = 1u32;
|
||||
let mut buffer = Vec::new();
|
||||
let mut total_written = 0u64;
|
||||
|
||||
while let Some(chunk_result) = stream.next().await {
|
||||
let chunk = chunk_result.map_err(|e| format!("Failed to read chunk: {}", e))?;
|
||||
buffer.extend_from_slice(&chunk);
|
||||
|
||||
// Traiter les lignes complètes dans le buffer
|
||||
while let Some(newline_pos) = buffer.iter().position(|&b| b == b'\n') {
|
||||
let line = &buffer[..newline_pos];
|
||||
|
||||
// Écrire le numéro de ligne et la ligne
|
||||
let numbered_line = format!("{:6}: ", line_number);
|
||||
file.write_all(numbered_line.as_bytes())
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write: {}", e))?;
|
||||
|
||||
file.write_all(line)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write: {}", e))?;
|
||||
|
||||
file.write_all(b"\n")
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write: {}", e))?;
|
||||
|
||||
total_written += numbered_line.len() as u64 + line.len() as u64 + 1;
|
||||
update_progress(total_written);
|
||||
|
||||
line_number += 1;
|
||||
buffer.drain(..=newline_pos);
|
||||
}
|
||||
}
|
||||
|
||||
// Traiter la dernière ligne si elle n'a pas de newline
|
||||
if !buffer.is_empty() {
|
||||
let numbered_line = format!("{:6}: ", line_number);
|
||||
file.write_all(numbered_line.as_bytes())
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write: {}", e))?;
|
||||
|
||||
file.write_all(&buffer)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to write: {}", e))?;
|
||||
|
||||
total_written += numbered_line.len() as u64 + buffer.len() as u64;
|
||||
update_progress(total_written);
|
||||
}
|
||||
|
||||
file.flush()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to flush: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Exemples de transformers pour le module download ===\n");
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
|
||||
// Exemple 1: Téléchargement avec transformation en majuscules
|
||||
println!("1. Téléchargement avec transformation en MAJUSCULES");
|
||||
let uppercase_file = temp_dir.join("uppercase_example.txt");
|
||||
let _ = std::fs::remove_file(&uppercase_file);
|
||||
|
||||
let transformer = create_uppercase_transformer();
|
||||
let dl = download_with_transformer(
|
||||
&uppercase_file,
|
||||
"https://www.rust-lang.org/",
|
||||
Some(transformer),
|
||||
);
|
||||
|
||||
println!(" Téléchargement démarré...");
|
||||
match dl.wait_until_finished().await {
|
||||
Ok(_) => {
|
||||
println!(" ✓ Téléchargement terminé!");
|
||||
println!(" - Taille source: {} bytes", dl.current_size().await);
|
||||
println!(" - Taille transformée: {} bytes", dl.transformed_size().await);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(" ✗ Erreur: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Exemple 2: Skip header
|
||||
println!("\n2. Téléchargement en sautant les 100 premiers bytes");
|
||||
let skip_file = temp_dir.join("skip_header_example.txt");
|
||||
let _ = std::fs::remove_file(&skip_file);
|
||||
|
||||
let transformer = create_skip_header_transformer(100);
|
||||
let dl = download_with_transformer(
|
||||
&skip_file,
|
||||
"https://www.rust-lang.org/",
|
||||
Some(transformer),
|
||||
);
|
||||
|
||||
match dl.wait_until_finished().await {
|
||||
Ok(_) => {
|
||||
println!(" ✓ Téléchargement terminé!");
|
||||
println!(" - Taille transformée: {} bytes", dl.transformed_size().await);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(" ✗ Erreur: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Exemple 3: Numérotation des lignes
|
||||
println!("\n3. Téléchargement avec numérotation des lignes");
|
||||
let numbered_file = temp_dir.join("numbered_example.txt");
|
||||
let _ = std::fs::remove_file(&numbered_file);
|
||||
|
||||
let transformer = create_line_number_transformer();
|
||||
let dl = download_with_transformer(
|
||||
&numbered_file,
|
||||
"https://www.rust-lang.org/",
|
||||
Some(transformer),
|
||||
);
|
||||
|
||||
match dl.wait_until_finished().await {
|
||||
Ok(_) => {
|
||||
println!(" ✓ Téléchargement terminé!");
|
||||
println!(" - Taille transformée: {} bytes", dl.transformed_size().await);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(" ✗ Erreur: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n=== Exemples terminés ===");
|
||||
println!("Fichiers créés dans: {:?}", temp_dir);
|
||||
|
||||
// Note: Commenté car nécessite la dépendance async-compression
|
||||
/*
|
||||
println!("\n4. Téléchargement avec compression GZIP");
|
||||
let gzip_file = temp_dir.join("compressed_example.gz");
|
||||
let _ = std::fs::remove_file(&gzip_file);
|
||||
|
||||
let transformer = create_gzip_transformer();
|
||||
let dl = download_with_transformer(
|
||||
&gzip_file,
|
||||
"https://www.rust-lang.org/",
|
||||
Some(transformer),
|
||||
);
|
||||
|
||||
match dl.wait_until_finished().await {
|
||||
Ok(_) => {
|
||||
println!(" ✓ Téléchargement terminé!");
|
||||
println!(" - Taille source: {} bytes", dl.current_size().await);
|
||||
println!(" - Taille compressée: {} bytes", dl.transformed_size().await);
|
||||
let ratio = 100.0 * dl.transformed_size().await as f64 / dl.current_size().await as f64;
|
||||
println!(" - Ratio de compression: {:.1}%", ratio);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!(" ✗ Erreur: {}", e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
Reference in New Issue
Block a user