2025-10-07 08:33:06 +02:00
|
|
|
//! # pmocovers - Service de cache d'images de couvertures pour PMOMusic
|
|
|
|
|
//!
|
|
|
|
|
//! Cette crate fournit un système de cache d'images optimisé pour les couvertures d'albums,
|
|
|
|
|
//! avec conversion automatique en WebP et génération de variantes de tailles.
|
|
|
|
|
//!
|
2025-10-17 22:15:00 +02:00
|
|
|
//! ## Fonctionnalités
|
2025-10-07 08:33:06 +02:00
|
|
|
//!
|
|
|
|
|
//! - Conversion automatique en WebP pour réduire la taille
|
|
|
|
|
//! - Génération de variantes de tailles à la demande
|
|
|
|
|
//! - Cache persistant avec base de données SQLite
|
2025-10-17 22:15:00 +02:00
|
|
|
//! - API HTTP complète (fournie par `pmocache`)
|
2025-10-07 08:33:06 +02:00
|
|
|
//!
|
|
|
|
|
//! ## Architecture
|
|
|
|
|
//!
|
2025-10-17 22:15:00 +02:00
|
|
|
//! `pmocovers` est une spécialisation minimale de `pmocache` qui ajoute :
|
|
|
|
|
//! 1. La conversion WebP automatique lors du téléchargement (via transformer)
|
|
|
|
|
//! 2. La génération de variantes redimensionnées à la demande (via param generator)
|
2025-10-07 08:33:06 +02:00
|
|
|
//!
|
2025-10-17 22:15:00 +02:00
|
|
|
//! Tout le reste (API REST, serveur de fichiers, DB) est fourni par `pmocache`.
|
2025-10-07 08:33:06 +02:00
|
|
|
//!
|
|
|
|
|
//! ## Utilisation
|
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
//! ### Exemple minimal
|
|
|
|
|
//!
|
|
|
|
|
//! ```rust,no_run
|
|
|
|
|
//! use pmocovers::cache;
|
|
|
|
|
//!
|
|
|
|
|
//! #[tokio::main]
|
|
|
|
|
//! async fn main() -> anyhow::Result<()> {
|
|
|
|
|
//! let cache = cache::new_cache("./covers_cache", 200)?;
|
|
|
|
|
//! let pk = cache.add_from_url("https://example.com/cover.jpg", None).await?;
|
|
|
|
|
//! let path = cache.get(&pk).await?;
|
|
|
|
|
//! println!("Image convertie en WebP: {path:?}");
|
|
|
|
|
//! Ok(())
|
|
|
|
|
//! }
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
2025-10-17 22:15:00 +02:00
|
|
|
//! ### Exemple avec configuration automatique
|
2025-10-07 08:33:06 +02:00
|
|
|
//!
|
|
|
|
|
//! ```rust,no_run
|
|
|
|
|
//! use pmocovers::CoverCacheExt;
|
|
|
|
|
//! use pmoserver::ServerBuilder;
|
|
|
|
|
//!
|
|
|
|
|
//! #[tokio::main]
|
|
|
|
|
//! async fn main() -> anyhow::Result<()> {
|
|
|
|
|
//! let mut server = ServerBuilder::new_configured().build();
|
|
|
|
|
//! server.init_cover_cache_configured().await?;
|
|
|
|
|
//! server.start().await;
|
|
|
|
|
//! server.wait().await;
|
|
|
|
|
//! Ok(())
|
|
|
|
|
//! }
|
|
|
|
|
//! ```
|
|
|
|
|
|
|
|
|
|
pub mod cache;
|
|
|
|
|
pub mod webp;
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
pub mod openapi;
|
|
|
|
|
|
2025-10-25 17:14:24 +02:00
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
|
|
|
pub mod config_ext;
|
|
|
|
|
|
2025-10-19 13:42:29 +02:00
|
|
|
pub use cache::{new_cache, Cache, CoversConfig};
|
2025-10-07 08:33:06 +02:00
|
|
|
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
pub use openapi::ApiDoc;
|
|
|
|
|
|
2025-10-25 17:14:24 +02:00
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
|
|
|
pub use config_ext::CoverCacheConfigExt;
|
|
|
|
|
|
2025-10-17 22:15:00 +02:00
|
|
|
#[cfg(feature = "pmoserver")]
|
2025-10-07 08:33:06 +02:00
|
|
|
use std::sync::Arc;
|
2025-10-19 13:42:29 +02:00
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
use utoipa::OpenApi;
|
2025-10-07 08:33:06 +02:00
|
|
|
|
2025-10-17 22:15:00 +02:00
|
|
|
/// Générateur de variantes d'images
|
2025-10-07 08:33:06 +02:00
|
|
|
///
|
2025-10-17 22:15:00 +02:00
|
|
|
/// Si param est numérique, génère une variante redimensionnée
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
fn create_variant_generator() -> pmocache::pmoserver_ext::ParamGenerator<CoversConfig> {
|
|
|
|
|
Arc::new(|cache, pk, param| {
|
|
|
|
|
Box::pin(async move {
|
|
|
|
|
// Si le param est numérique, c'est une taille de variante
|
|
|
|
|
if let Ok(size) = param.parse::<usize>() {
|
|
|
|
|
match webp::generate_variant(&cache, &pk, size).await {
|
|
|
|
|
Ok(data) => return Some(data),
|
|
|
|
|
Err(e) => {
|
2025-10-19 13:42:29 +02:00
|
|
|
tracing::warn!(
|
|
|
|
|
"Cannot generate variant {}x{} for {}: {}",
|
|
|
|
|
size,
|
|
|
|
|
size,
|
|
|
|
|
pk,
|
|
|
|
|
e
|
|
|
|
|
);
|
2025-10-17 22:15:00 +02:00
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Param non reconnu
|
|
|
|
|
None
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Trait d'extension pour ajouter le cache de couvertures à pmoserver
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
2025-10-07 08:33:06 +02:00
|
|
|
pub trait CoverCacheExt {
|
2025-10-17 22:15:00 +02:00
|
|
|
/// Initialise le cache d'images et enregistre les routes HTTP
|
2025-10-07 08:33:06 +02:00
|
|
|
///
|
|
|
|
|
/// # Arguments
|
|
|
|
|
///
|
|
|
|
|
/// * `cache_dir` - Répertoire de stockage du cache
|
|
|
|
|
/// * `limit` - Limite de taille du cache (en nombre d'images)
|
|
|
|
|
///
|
|
|
|
|
/// # Returns
|
|
|
|
|
///
|
2025-10-17 22:15:00 +02:00
|
|
|
/// Instance partagée du cache
|
2025-10-07 08:33:06 +02:00
|
|
|
///
|
|
|
|
|
/// # Routes enregistrées
|
|
|
|
|
///
|
2025-10-17 22:15:00 +02:00
|
|
|
/// - `GET /covers/image/{pk}` - Image originale
|
|
|
|
|
/// - `GET /covers/image/{pk}/{size}` - Variante de taille (ex: 256, 512)
|
2025-10-07 08:33:06 +02:00
|
|
|
/// - `GET /api/covers` - Liste des images (API REST)
|
|
|
|
|
/// - `POST /api/covers` - Ajouter une image (API REST)
|
|
|
|
|
/// - `DELETE /api/covers/{pk}` - Supprimer une image (API REST)
|
2025-10-17 22:15:00 +02:00
|
|
|
/// - `GET /api/covers/{pk}/status` - Statut du téléchargement
|
|
|
|
|
/// - `GET /swagger-ui/covers` - Documentation interactive
|
2025-10-19 13:42:29 +02:00
|
|
|
async fn init_cover_cache(
|
|
|
|
|
&mut self,
|
|
|
|
|
cache_dir: &str,
|
|
|
|
|
limit: usize,
|
|
|
|
|
) -> anyhow::Result<Arc<Cache>>;
|
2025-10-07 08:33:06 +02:00
|
|
|
|
2025-10-17 22:15:00 +02:00
|
|
|
/// Initialise le cache d'images avec la configuration par défaut
|
2025-10-07 08:33:06 +02:00
|
|
|
///
|
2025-10-17 22:15:00 +02:00
|
|
|
/// Utilise automatiquement les paramètres de `pmoconfig::Config`
|
2025-10-19 13:42:29 +02:00
|
|
|
async fn init_cover_cache_configured(&mut self) -> anyhow::Result<Arc<Cache>>;
|
2025-10-07 08:33:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
2025-10-17 22:15:00 +02:00
|
|
|
impl CoverCacheExt for pmoserver::Server {
|
2025-10-19 13:42:29 +02:00
|
|
|
async fn init_cover_cache(
|
|
|
|
|
&mut self,
|
|
|
|
|
cache_dir: &str,
|
|
|
|
|
limit: usize,
|
|
|
|
|
) -> anyhow::Result<Arc<Cache>> {
|
|
|
|
|
use pmocache::pmoserver_ext::{create_api_router, create_file_router_with_generator};
|
2025-10-17 22:15:00 +02:00
|
|
|
|
2025-10-18 09:58:39 +02:00
|
|
|
let cache = Arc::new(cache::new_cache(cache_dir, limit)?);
|
2025-10-17 22:15:00 +02:00
|
|
|
|
|
|
|
|
// Router de fichiers avec génération de variantes
|
|
|
|
|
// Routes: GET /covers/image/{pk} et GET /covers/image/{pk}/{size}
|
|
|
|
|
let file_router = create_file_router_with_generator(
|
|
|
|
|
cache.clone(),
|
|
|
|
|
"image/webp",
|
2025-10-19 13:42:29 +02:00
|
|
|
Some(create_variant_generator()),
|
2025-10-17 22:15:00 +02:00
|
|
|
);
|
|
|
|
|
self.add_router("/", file_router).await;
|
|
|
|
|
|
|
|
|
|
// API REST générique (pmocache)
|
|
|
|
|
// Routes: GET/POST/DELETE /api/covers, etc.
|
|
|
|
|
let api_router = create_api_router(cache.clone());
|
|
|
|
|
let openapi = crate::ApiDoc::openapi();
|
|
|
|
|
self.add_openapi(api_router, openapi, "covers").await;
|
|
|
|
|
|
|
|
|
|
Ok(cache)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-19 13:42:29 +02:00
|
|
|
async fn init_cover_cache_configured(&mut self) -> anyhow::Result<Arc<Cache>> {
|
2025-10-25 17:14:24 +02:00
|
|
|
use crate::CoverCacheConfigExt;
|
2025-10-17 22:15:00 +02:00
|
|
|
let config = pmoconfig::get_config();
|
2025-10-25 17:14:24 +02:00
|
|
|
let cache_dir = config.get_covers_dir()?;
|
|
|
|
|
let limit = config.get_covers_size()?;
|
2025-10-17 22:15:00 +02:00
|
|
|
self.init_cover_cache(&cache_dir, limit).await
|
|
|
|
|
}
|
|
|
|
|
}
|