diff --git a/pmoaudiocache/examples/test_flac_debug.rs b/pmoaudiocache/examples/test_flac_debug.rs index f62ade3b..b18d43cc 100644 --- a/pmoaudiocache/examples/test_flac_debug.rs +++ b/pmoaudiocache/examples/test_flac_debug.rs @@ -21,7 +21,7 @@ async fn main() -> anyhow::Result<()> { let pk = cache::add_with_metadata_extraction(&cache, test_url, Some("test")).await?; println!("\nPK: {}", pk); - let file_path = cache.file_path(&pk); + let file_path = cache.get_file_path(&pk); println!("File path: {}", file_path.display()); // Vérifier le format diff --git a/pmoaudiocache/examples/test_streaming_flac.rs b/pmoaudiocache/examples/test_streaming_flac.rs index 8f22451d..045d0380 100644 --- a/pmoaudiocache/examples/test_streaming_flac.rs +++ b/pmoaudiocache/examples/test_streaming_flac.rs @@ -33,7 +33,7 @@ async fn main() -> anyhow::Result<()> { println!("✓ Fichier converti avec succès!"); println!(" Clé primaire: {}", pk); - let file_path = cache.file_path(&pk); + let file_path = cache.get_file_path(&pk); println!(" Chemin: {}", file_path.display()); if let Ok(metadata) = std::fs::metadata(&file_path) { diff --git a/pmoaudiocache/src/cache.rs b/pmoaudiocache/src/cache.rs index e6827341..f53d2f37 100644 --- a/pmoaudiocache/src/cache.rs +++ b/pmoaudiocache/src/cache.rs @@ -384,7 +384,7 @@ pub async fn add_with_metadata_extraction( cache.wait_until_finished(&pk).await?; // Lire le fichier FLAC pour extraire les métadonnées - let file_path = cache.file_path(&pk); + let file_path = cache.get_file_path(&pk); let flac_bytes = tokio::fs::read(&file_path).await?; // Extraire les métadonnées diff --git a/pmocache/src/cache.rs b/pmocache/src/cache.rs index 7e3dc101..f8a6d1cb 100644 --- a/pmocache/src/cache.rs +++ b/pmocache/src/cache.rs @@ -8,7 +8,8 @@ use crate::db::DB; use crate::download::{ download_with_transformer, ingest_with_transformer, Download, StreamTransformer, }; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; +use serde_json::{Number, Value}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -168,7 +169,7 @@ impl Cache { // 3. Vérifier si le fichier est déjà en cache if self.db.get(&pk, false).is_ok() { - let file_path = self.file_path(&pk); + 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); @@ -189,7 +190,7 @@ impl Cache { // 5. Lancer le téléchargement complet avec transformer tracing::debug!("Starting full download for pk {} from URL {}", pk, url); - let file_path = self.file_path(&pk); + let file_path = self.get_file_path(&pk); let transformer = self.transformer_factory.as_ref().map(|f| f()); let download = download_with_transformer(&file_path, url, transformer); @@ -263,7 +264,7 @@ impl Cache { // 3. Vérifier si le fichier est déjà en cache if self.db.get(&pk, false).is_ok() { - let file_path = self.file_path(&pk); + 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); @@ -290,7 +291,7 @@ impl Cache { // 6. Lancer l'ingestion avec transformer tracing::debug!("Starting ingestion for pk {} from reader", pk); - let file_path = self.file_path(&pk); + let file_path = self.get_file_path(&pk); let transformer = self.transformer_factory.as_ref().map(|factory| factory()); let download = ingest_with_transformer(&file_path, full_reader, length, transformer); @@ -413,7 +414,7 @@ impl Cache { self.db.get(pk, false)?; self.db.update_hit(pk)?; - let file_path = self.file_path(pk); + let file_path = self.get_file_path(pk); if file_path.exists() { Ok(file_path) } else { @@ -421,6 +422,37 @@ impl Cache { } } + /// Récupère une métadonnée précise pour une entrée du cache. + pub async fn get_a_metadata(&self, pk: &str, key: &str) -> Result> { + Ok(self.db.get_metadata_value(pk, key)?) + } + + /// Récupère une métadonnée en tant que chaîne, si disponible. + pub async fn get_a_metadata_as_string(&self, pk: &str, key: &str) -> Result> { + match self.get_a_metadata(pk, key).await? { + Some(Value::String(s)) => Ok(Some(s)), + Some(Value::Null) | None => Ok(None), + Some(other) => bail!("metadata '{key}' for pk '{pk}' is not a string (found {other})"), + } + } + + /// Récupère une métadonnée en tant que nombre JSON (`serde_json::Number`). + pub async fn get_a_metadata_as_number(&self, pk: &str, key: &str) -> Result> { + match self.get_a_metadata(pk, key).await? { + Some(Value::Number(n)) => Ok(Some(n)), + Some(Value::Null) | None => Ok(None), + Some(other) => bail!("metadata '{key}' for pk '{pk}' is not a number (found {other})"), + } + } + + /// Récupère une métadonnée en tant que booléen. + pub async fn get_a_metadata_as_bool(&self, pk: &str, key: &str) -> Result> { + match self.get_a_metadata(pk, key).await? { + Some(Value::Bool(b)) => Ok(Some(b)), + Some(Value::Null) | None => Ok(None), + Some(other) => bail!("metadata '{key}' for pk '{pk}' is not a boolean (found {other})"), + } + } pub async fn touch(&self, pk: &str) -> Result<()> { self.db.update_hit(pk)?; Ok(()) @@ -436,7 +468,7 @@ impl Cache { let mut paths = Vec::new(); for entry in entries { - let path = self.file_path(&entry.pk); + let path = self.get_file_path(&entry.pk); if path.exists() { paths.push(path); } @@ -466,7 +498,7 @@ impl Cache { // Supprimer les entrées sans fichiers correspondants for entry in entries { - let file_path = self.file_path(&entry.pk); + let file_path = self.get_file_path(&entry.pk); if !file_path.exists() { match self.db.get_origin_url(&entry.pk)? { @@ -535,7 +567,7 @@ impl Cache { Some(download.current_size().await) } else { // Fichier terminé, lire la taille du fichier - let file_path = self.file_path(pk); + let file_path = self.get_file_path(pk); if file_path.exists() { std::fs::metadata(file_path).ok().map(|m| m.len()) } else { @@ -557,7 +589,7 @@ impl Cache { Some(download.transformed_size().await) } else { // Fichier terminé, lire la taille du fichier - let file_path = self.file_path(pk); + let file_path = self.get_file_path(pk); if file_path.exists() { std::fs::metadata(file_path).ok().map(|m| m.len()) } else { @@ -590,7 +622,7 @@ impl Cache { download.finished().await } else { // Pas dans la map = terminé (ou n'existe pas) - self.file_path(pk).exists() + self.get_file_path(pk).exists() } } @@ -608,7 +640,7 @@ impl Cache { .map_err(|e| anyhow!("Download error: {}", e)) } else { // Déjà terminé ou n'existe pas - if self.file_path(pk).exists() { + if self.get_file_path(pk).exists() { Ok(()) } else { Err(anyhow!("File not found")) @@ -629,7 +661,7 @@ impl Cache { .map_err(|e| anyhow!("Download error: {}", e)) } else { // Déjà terminé ou n'existe pas - if self.file_path(pk).exists() { + if self.get_file_path(pk).exists() { Ok(()) } else { Err(anyhow!("File not found")) @@ -645,14 +677,14 @@ impl Cache { /// Construit le chemin complet d'un fichier dans le cache avec le param par défaut /// /// Format: `{pk}.{default_param}.{extension}` - pub fn file_path(&self, pk: &str) -> PathBuf { - self.file_path_with_qualifier(pk, C::default_param()) + pub fn get_file_path(&self, pk: &str) -> PathBuf { + self.get_file_path_with_qualifier(pk, C::default_param()) } /// Construit le chemin d'un fichier dans le cache avec un qualificatif /// /// Format: `{pk}.{qualifier}.{extension}` - pub fn file_path_with_qualifier(&self, pk: &str, qualifier: &str) -> PathBuf { + pub fn get_file_path_with_qualifier(&self, pk: &str, qualifier: &str) -> PathBuf { self.dir .join(format!("{}.{}.{}", pk, qualifier, C::file_extension())) } diff --git a/pmocache/src/db.rs b/pmocache/src/db.rs index aa53bf84..3e4f8a47 100644 --- a/pmocache/src/db.rs +++ b/pmocache/src/db.rs @@ -334,7 +334,7 @@ impl DB { /// Récupère l'URL d'origine précédemment stockée pour un élément. /// - /// Retourne `Ok(None)` si aucune URL n'est définie. + /// Retourne `Ok(None)` lorsqu'aucune URL n'a été définie. pub fn get_origin_url(&self, pk: &str) -> rusqlite::Result> { match self.get_metadata_value(pk, "origin_url")? { Some(Value::String(url)) => Ok(Some(url)), @@ -447,6 +447,34 @@ impl DB { Ok(entry) } + /// Indique si une collection contient un identifiant donné. + /// + /// Retourne `false` si l'enregistrement n'existe pas ou si la requête échoue. + pub fn does_collection_contain_id(&self, collection: &str, id: &str) -> bool { + let conn = self.conn.lock().unwrap(); + conn.query_row( + "SELECT EXISTS(SELECT 1 FROM asset WHERE collection = ?1 AND id = ?2)", + params![collection, id], + |row| row.get::<_, i64>(0), + ) + .map(|flag| flag != 0) + .unwrap_or(false) + } + + /// Retourne la clé primaire associée à la paire `(collection, id)`. + /// + /// # Errors + /// + /// Retourne `QueryReturnedNoRows` si aucun enregistrement ne correspond. + pub fn get_pk_from_id(&self, collection: &str, id: &str) -> rusqlite::Result { + let conn = self.conn.lock().unwrap(); + conn.query_row( + "SELECT pk FROM asset WHERE collection = ?1 AND id = ?2", + params![collection, id], + |row| row.get(0), + ) + } + /// Définit ou remplace l'identifiant logique (`id`) d'une entrée. /// /// Retourne `QueryReturnedNoRows` si la clé primaire est inconnue. diff --git a/pmocache/src/pmoserver_ext.rs b/pmocache/src/pmoserver_ext.rs index afab1e87..e6dab594 100644 --- a/pmocache/src/pmoserver_ext.rs +++ b/pmocache/src/pmoserver_ext.rs @@ -123,7 +123,7 @@ async fn serve_file_with_streaming( content_type: &'static str, param_generator: Option>, ) -> Response { - let file_path = cache.file_path_with_qualifier(pk, param); + let file_path = cache.get_file_path_with_qualifier(pk, param); // Si le fichier n'existe pas et qu'on a un générateur, l'utiliser if !file_path.exists() { diff --git a/pmocovers/src/webp.rs b/pmocovers/src/webp.rs index 05988347..c6a246b0 100644 --- a/pmocovers/src/webp.rs +++ b/pmocovers/src/webp.rs @@ -118,13 +118,13 @@ pub async fn generate_variant( size: usize, ) -> Result> { // Utiliser file_path_with_qualifier pour obtenir le chemin - let variant_path = cache.file_path_with_qualifier(pk, &size.to_string()); + let variant_path = cache.get_file_path_with_qualifier(pk, &size.to_string()); if variant_path.exists() { return Ok(tokio::fs::read(variant_path).await?); } - let orig_path = cache.file_path_with_qualifier(pk, "orig"); + let orig_path = cache.get_file_path_with_qualifier(pk, "orig"); // Charger l'image de manière synchrone (image::open n'est pas async) let img = tokio::task::spawn_blocking(move || image::open(orig_path)).await??;