On complète la gestion du cache pour les métadonnées
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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??;
|
||||
|
||||
Reference in New Issue
Block a user