On complète la gestion du cache pour les métadonnées

This commit is contained in:
2025-10-26 13:05:46 +01:00
parent 33761f1cef
commit 1268c24faf
7 changed files with 83 additions and 23 deletions

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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<C: CacheConfig> Cache<C> {
// 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<C: CacheConfig> Cache<C> {
// 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<C: CacheConfig> Cache<C> {
// 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<C: CacheConfig> Cache<C> {
// 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<C: CacheConfig> Cache<C> {
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<C: CacheConfig> Cache<C> {
}
}
/// 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<Option<Value>> {
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<Option<String>> {
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<Option<Number>> {
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<Option<bool>> {
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<C: CacheConfig> Cache<C> {
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<C: CacheConfig> Cache<C> {
// 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<C: CacheConfig> Cache<C> {
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<C: CacheConfig> Cache<C> {
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<C: CacheConfig> Cache<C> {
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<C: CacheConfig> Cache<C> {
.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<C: CacheConfig> Cache<C> {
.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<C: CacheConfig> Cache<C> {
/// 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()))
}

View File

@@ -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<Option<String>> {
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<String> {
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.

View File

@@ -123,7 +123,7 @@ async fn serve_file_with_streaming<C: CacheConfig>(
content_type: &'static str,
param_generator: Option<ParamGenerator<C>>,
) -> 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() {

View File

@@ -118,13 +118,13 @@ pub async fn generate_variant(
size: usize,
) -> Result<Vec<u8>> {
// 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??;