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?;
|
let pk = cache::add_with_metadata_extraction(&cache, test_url, Some("test")).await?;
|
||||||
|
|
||||||
println!("\nPK: {}", pk);
|
println!("\nPK: {}", pk);
|
||||||
let file_path = cache.file_path(&pk);
|
let file_path = cache.get_file_path(&pk);
|
||||||
println!("File path: {}", file_path.display());
|
println!("File path: {}", file_path.display());
|
||||||
|
|
||||||
// Vérifier le format
|
// Vérifier le format
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
println!("✓ Fichier converti avec succès!");
|
println!("✓ Fichier converti avec succès!");
|
||||||
println!(" Clé primaire: {}", pk);
|
println!(" Clé primaire: {}", pk);
|
||||||
|
|
||||||
let file_path = cache.file_path(&pk);
|
let file_path = cache.get_file_path(&pk);
|
||||||
println!(" Chemin: {}", file_path.display());
|
println!(" Chemin: {}", file_path.display());
|
||||||
|
|
||||||
if let Ok(metadata) = std::fs::metadata(&file_path) {
|
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?;
|
cache.wait_until_finished(&pk).await?;
|
||||||
|
|
||||||
// Lire le fichier FLAC pour extraire les métadonnées
|
// 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?;
|
let flac_bytes = tokio::fs::read(&file_path).await?;
|
||||||
|
|
||||||
// Extraire les métadonnées
|
// Extraire les métadonnées
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ use crate::db::DB;
|
|||||||
use crate::download::{
|
use crate::download::{
|
||||||
download_with_transformer, ingest_with_transformer, Download, StreamTransformer,
|
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::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -168,7 +169,7 @@ impl<C: CacheConfig> Cache<C> {
|
|||||||
|
|
||||||
// 3. Vérifier si le fichier est déjà en cache
|
// 3. Vérifier si le fichier est déjà en cache
|
||||||
if self.db.get(&pk, false).is_ok() {
|
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() {
|
if file_path.exists() {
|
||||||
// Déjà en cache, update timestamp et retour rapide
|
// Déjà en cache, update timestamp et retour rapide
|
||||||
tracing::debug!("File with pk {} already in cache, updating timestamp", pk);
|
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
|
// 5. Lancer le téléchargement complet avec transformer
|
||||||
tracing::debug!("Starting full download for pk {} from URL {}", pk, url);
|
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 transformer = self.transformer_factory.as_ref().map(|f| f());
|
||||||
let download = download_with_transformer(&file_path, url, transformer);
|
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
|
// 3. Vérifier si le fichier est déjà en cache
|
||||||
if self.db.get(&pk, false).is_ok() {
|
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() {
|
if file_path.exists() {
|
||||||
// Déjà en cache, update timestamp et retour rapide
|
// Déjà en cache, update timestamp et retour rapide
|
||||||
tracing::debug!("File with pk {} already in cache, updating timestamp", pk);
|
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
|
// 6. Lancer l'ingestion avec transformer
|
||||||
tracing::debug!("Starting ingestion for pk {} from reader", pk);
|
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 transformer = self.transformer_factory.as_ref().map(|factory| factory());
|
||||||
let download = ingest_with_transformer(&file_path, full_reader, length, transformer);
|
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.get(pk, false)?;
|
||||||
self.db.update_hit(pk)?;
|
self.db.update_hit(pk)?;
|
||||||
|
|
||||||
let file_path = self.file_path(pk);
|
let file_path = self.get_file_path(pk);
|
||||||
if file_path.exists() {
|
if file_path.exists() {
|
||||||
Ok(file_path)
|
Ok(file_path)
|
||||||
} else {
|
} 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<()> {
|
pub async fn touch(&self, pk: &str) -> Result<()> {
|
||||||
self.db.update_hit(pk)?;
|
self.db.update_hit(pk)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -436,7 +468,7 @@ impl<C: CacheConfig> Cache<C> {
|
|||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
|
|
||||||
for entry in entries {
|
for entry in entries {
|
||||||
let path = self.file_path(&entry.pk);
|
let path = self.get_file_path(&entry.pk);
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
paths.push(path);
|
paths.push(path);
|
||||||
}
|
}
|
||||||
@@ -466,7 +498,7 @@ impl<C: CacheConfig> Cache<C> {
|
|||||||
|
|
||||||
// Supprimer les entrées sans fichiers correspondants
|
// Supprimer les entrées sans fichiers correspondants
|
||||||
for entry in entries {
|
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() {
|
if !file_path.exists() {
|
||||||
match self.db.get_origin_url(&entry.pk)? {
|
match self.db.get_origin_url(&entry.pk)? {
|
||||||
@@ -535,7 +567,7 @@ impl<C: CacheConfig> Cache<C> {
|
|||||||
Some(download.current_size().await)
|
Some(download.current_size().await)
|
||||||
} else {
|
} else {
|
||||||
// Fichier terminé, lire la taille du fichier
|
// 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() {
|
if file_path.exists() {
|
||||||
std::fs::metadata(file_path).ok().map(|m| m.len())
|
std::fs::metadata(file_path).ok().map(|m| m.len())
|
||||||
} else {
|
} else {
|
||||||
@@ -557,7 +589,7 @@ impl<C: CacheConfig> Cache<C> {
|
|||||||
Some(download.transformed_size().await)
|
Some(download.transformed_size().await)
|
||||||
} else {
|
} else {
|
||||||
// Fichier terminé, lire la taille du fichier
|
// 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() {
|
if file_path.exists() {
|
||||||
std::fs::metadata(file_path).ok().map(|m| m.len())
|
std::fs::metadata(file_path).ok().map(|m| m.len())
|
||||||
} else {
|
} else {
|
||||||
@@ -590,7 +622,7 @@ impl<C: CacheConfig> Cache<C> {
|
|||||||
download.finished().await
|
download.finished().await
|
||||||
} else {
|
} else {
|
||||||
// Pas dans la map = terminé (ou n'existe pas)
|
// 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))
|
.map_err(|e| anyhow!("Download error: {}", e))
|
||||||
} else {
|
} else {
|
||||||
// Déjà terminé ou n'existe pas
|
// Déjà terminé ou n'existe pas
|
||||||
if self.file_path(pk).exists() {
|
if self.get_file_path(pk).exists() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("File not found"))
|
Err(anyhow!("File not found"))
|
||||||
@@ -629,7 +661,7 @@ impl<C: CacheConfig> Cache<C> {
|
|||||||
.map_err(|e| anyhow!("Download error: {}", e))
|
.map_err(|e| anyhow!("Download error: {}", e))
|
||||||
} else {
|
} else {
|
||||||
// Déjà terminé ou n'existe pas
|
// Déjà terminé ou n'existe pas
|
||||||
if self.file_path(pk).exists() {
|
if self.get_file_path(pk).exists() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(anyhow!("File not found"))
|
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
|
/// Construit le chemin complet d'un fichier dans le cache avec le param par défaut
|
||||||
///
|
///
|
||||||
/// Format: `{pk}.{default_param}.{extension}`
|
/// Format: `{pk}.{default_param}.{extension}`
|
||||||
pub fn file_path(&self, pk: &str) -> PathBuf {
|
pub fn get_file_path(&self, pk: &str) -> PathBuf {
|
||||||
self.file_path_with_qualifier(pk, C::default_param())
|
self.get_file_path_with_qualifier(pk, C::default_param())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construit le chemin d'un fichier dans le cache avec un qualificatif
|
/// Construit le chemin d'un fichier dans le cache avec un qualificatif
|
||||||
///
|
///
|
||||||
/// Format: `{pk}.{qualifier}.{extension}`
|
/// 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
|
self.dir
|
||||||
.join(format!("{}.{}.{}", pk, qualifier, C::file_extension()))
|
.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.
|
/// 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>> {
|
pub fn get_origin_url(&self, pk: &str) -> rusqlite::Result<Option<String>> {
|
||||||
match self.get_metadata_value(pk, "origin_url")? {
|
match self.get_metadata_value(pk, "origin_url")? {
|
||||||
Some(Value::String(url)) => Ok(Some(url)),
|
Some(Value::String(url)) => Ok(Some(url)),
|
||||||
@@ -447,6 +447,34 @@ impl DB {
|
|||||||
Ok(entry)
|
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.
|
/// Définit ou remplace l'identifiant logique (`id`) d'une entrée.
|
||||||
///
|
///
|
||||||
/// Retourne `QueryReturnedNoRows` si la clé primaire est inconnue.
|
/// Retourne `QueryReturnedNoRows` si la clé primaire est inconnue.
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ async fn serve_file_with_streaming<C: CacheConfig>(
|
|||||||
content_type: &'static str,
|
content_type: &'static str,
|
||||||
param_generator: Option<ParamGenerator<C>>,
|
param_generator: Option<ParamGenerator<C>>,
|
||||||
) -> Response {
|
) -> 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
|
// Si le fichier n'existe pas et qu'on a un générateur, l'utiliser
|
||||||
if !file_path.exists() {
|
if !file_path.exists() {
|
||||||
|
|||||||
@@ -118,13 +118,13 @@ pub async fn generate_variant(
|
|||||||
size: usize,
|
size: usize,
|
||||||
) -> Result<Vec<u8>> {
|
) -> Result<Vec<u8>> {
|
||||||
// Utiliser file_path_with_qualifier pour obtenir le chemin
|
// 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() {
|
if variant_path.exists() {
|
||||||
return Ok(tokio::fs::read(variant_path).await?);
|
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)
|
// 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??;
|
let img = tokio::task::spawn_blocking(move || image::open(orig_path)).await??;
|
||||||
|
|||||||
Reference in New Issue
Block a user