adaptation de la crate pmocovers
This commit is contained in:
@@ -3,62 +3,24 @@
|
||||
//! 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.
|
||||
//!
|
||||
//! ## Vue d'ensemble
|
||||
//! ## Fonctionnalités
|
||||
//!
|
||||
//! `pmocovers` gère le téléchargement, la conversion, le stockage et la distribution
|
||||
//! d'images de couvertures d'albums, avec :
|
||||
//! - 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
|
||||
//! - API HTTP pour récupérer les images
|
||||
//!
|
||||
//! ## Fonctionnalités
|
||||
//!
|
||||
//! ### 📦 Gestion du cache
|
||||
//! - Téléchargement automatique depuis des URLs
|
||||
//! - Conversion des images en WebP (format optimisé)
|
||||
//! - Stockage persistant sur disque
|
||||
//! - Base de données SQLite pour le tracking
|
||||
//!
|
||||
//! ### 🎨 Génération de variantes
|
||||
//! - Redimensionnement automatique à la demande
|
||||
//! - Création d'images carrées avec centrage
|
||||
//! - Cache des variantes générées
|
||||
//! - Support de multiples tailles
|
||||
//!
|
||||
//! ### 📊 Statistiques d'utilisation
|
||||
//! - Comptage des accès (hits)
|
||||
//! - Suivi de la dernière utilisation
|
||||
//! - API de statistiques complètes
|
||||
//! - API HTTP complète (fournie par `pmocache`)
|
||||
//!
|
||||
//! ## Architecture
|
||||
//!
|
||||
//! `pmocovers` suit le pattern d'extension des autres crates PMO :
|
||||
//! `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)
|
||||
//!
|
||||
//! - `pmoserver` définit un serveur HTTP générique
|
||||
//! - `pmocovers` étend ce serveur avec des méthodes de cache via un trait
|
||||
//! - Le serveur n'a pas besoin de connaître `pmocovers`
|
||||
//!
|
||||
//! ## Structure des fichiers
|
||||
//!
|
||||
//! ```text
|
||||
//! pmocovers/
|
||||
//! ├── Cargo.toml
|
||||
//! ├── src/
|
||||
//! │ ├── lib.rs # Module principal (ce fichier)
|
||||
//! │ ├── cache.rs # Gestion du cache
|
||||
//! │ ├── db.rs # Base de données SQLite
|
||||
//! │ ├── webp.rs # Conversion et redimensionnement WebP
|
||||
//! │ └── pmoserver_impl.rs # Extension de pmoserver::Server
|
||||
//! └── cache/ # Répertoire de cache (généré)
|
||||
//! ├── cache.db # Base SQLite
|
||||
//! ├── *.orig.webp # Images originales
|
||||
//! └── *.{size}.webp # Variantes de tailles
|
||||
//! ```
|
||||
//! Tout le reste (API REST, serveur de fichiers, DB) est fourni par `pmocache`.
|
||||
//!
|
||||
//! ## Utilisation
|
||||
//!
|
||||
//! ### Exemple basique avec configuration automatique
|
||||
//! ### Exemple avec configuration automatique
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use pmocovers::CoverCacheExt;
|
||||
@@ -67,161 +29,56 @@
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> anyhow::Result<()> {
|
||||
//! let mut server = ServerBuilder::new_configured().build();
|
||||
//!
|
||||
//! // Utilise automatiquement la config (pmoconfig)
|
||||
//! server.init_cover_cache_configured().await?;
|
||||
//!
|
||||
//! server.start().await;
|
||||
//! server.wait().await;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Exemple avec paramètres personnalisés
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use pmocovers::CoverCacheExt;
|
||||
//! use pmoserver::ServerBuilder;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> anyhow::Result<()> {
|
||||
//! let mut server = ServerBuilder::new("MyApp", "http://localhost:3000", 3000).build();
|
||||
//!
|
||||
//! // Paramètres personnalisés
|
||||
//! server.init_cover_cache("./cache", 1000).await?;
|
||||
//!
|
||||
//! server.start().await;
|
||||
//! server.wait().await;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Utilisation du cache directement
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use pmocovers::Cache;
|
||||
//! use pmocache::FileCache;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> anyhow::Result<()> {
|
||||
//! let cache = Cache::new("./cache", 1000, "http://localhost:8080")?;
|
||||
//!
|
||||
//! // Ajouter une image depuis une URL (avec conversion WebP automatique)
|
||||
//! let pk = cache.add_from_url("http://example.com/cover.jpg", None).await?;
|
||||
//! println!("Image ajoutée avec clé: {}", pk);
|
||||
//!
|
||||
//! // Récupérer l'image originale
|
||||
//! let path = cache.get(&pk).await?;
|
||||
//! println!("Image stockée à: {:?}", path);
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## API HTTP
|
||||
//!
|
||||
//! Une fois enregistré sur un serveur via `CoverCacheExt`, les endpoints suivants sont disponibles :
|
||||
//!
|
||||
//! ### GET /covers/images/{pk}
|
||||
//! Récupère l'image originale en WebP
|
||||
//!
|
||||
//! ### GET /covers/images/{pk}/{size}
|
||||
//! Récupère une variante de taille spécifique (ex: `/covers/images/abc123/256`)
|
||||
//!
|
||||
//! ### GET /covers/stats
|
||||
//! Récupère les statistiques du cache (JSON)
|
||||
//!
|
||||
//! ## Format des clés (pk)
|
||||
//!
|
||||
//! Les images sont identifiées par une clé (pk) dérivée de l'URL source :
|
||||
//! - Hash SHA1 de l'URL
|
||||
//! - Encodé en hexadécimal (8 premiers octets)
|
||||
//! - Exemple: `"1a2b3c4d5e6f7a8b"`
|
||||
//!
|
||||
//! ## Stockage
|
||||
//!
|
||||
//! Les fichiers sont organisés comme suit :
|
||||
//!
|
||||
//! ```text
|
||||
//! cache/
|
||||
//! ├── cache.db # Base SQLite
|
||||
//! ├── 1a2b3c4d.orig.webp # Image originale
|
||||
//! ├── 1a2b3c4d.256.webp # Variante 256x256
|
||||
//! └── 1a2b3c4d.512.webp # Variante 512x512
|
||||
//! ```
|
||||
//!
|
||||
//! ## Opérations de maintenance
|
||||
//!
|
||||
//! ### Purge du cache
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # use pmocovers::Cache;
|
||||
//! # async fn example(cache: &Cache) -> anyhow::Result<()> {
|
||||
//! // Supprimer tous les fichiers et entrées DB
|
||||
//! cache.purge().await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Consolidation du cache
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! # use pmocovers::Cache;
|
||||
//! # async fn example(cache: &Cache) -> anyhow::Result<()> {
|
||||
//! // Re-télécharger les images manquantes et supprimer les orphelins
|
||||
//! cache.consolidate().await?;
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Dépendances principales
|
||||
//!
|
||||
//! - `image` : Chargement et manipulation d'images
|
||||
//! - `webp` : Encodage WebP
|
||||
//! - `rusqlite` : Base de données SQLite
|
||||
//! - `reqwest` : Téléchargement HTTP
|
||||
//! - `sha1` : Génération de clés
|
||||
//!
|
||||
//! ## Voir aussi
|
||||
//!
|
||||
//! - [`pmoserver`] : Serveur HTTP Axum
|
||||
//! - [`pmoapp`] : Application web frontend
|
||||
//! - [`pmoupnp`] : Bibliothèque UPnP MediaRenderer
|
||||
|
||||
pub mod cache;
|
||||
pub mod db;
|
||||
pub mod webp;
|
||||
|
||||
#[cfg(feature = "pmoserver")]
|
||||
pub mod api;
|
||||
|
||||
#[cfg(feature = "pmoserver")]
|
||||
pub mod openapi;
|
||||
|
||||
pub use cache::{Cache, CoversConfig};
|
||||
pub use db::{CacheEntry, DB};
|
||||
pub use cache::{Cache, CoversConfig, new_cache};
|
||||
|
||||
#[cfg(feature = "pmoserver")]
|
||||
pub use openapi::ApiDoc;
|
||||
|
||||
use anyhow::Result;
|
||||
#[cfg(feature = "pmoserver")]
|
||||
use utoipa::OpenApi;
|
||||
#[cfg(feature = "pmoserver")]
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Trait pour étendre un serveur HTTP avec des fonctionnalités de cache d'images.
|
||||
/// Générateur de variantes d'images
|
||||
///
|
||||
/// Ce trait permet à `pmocovers` d'ajouter des méthodes d'extension sur des types
|
||||
/// de serveurs externes (comme `pmoserver::Server`) sans que ces crates dépendent de `pmocovers`.
|
||||
///
|
||||
/// # Architecture
|
||||
///
|
||||
/// Similaire au pattern utilisé par `pmoapp` pour `WebAppExt`, ce trait permet
|
||||
/// une extension propre et découplée :
|
||||
///
|
||||
/// - `pmoserver` définit un serveur HTTP générique
|
||||
/// - `pmocovers` étend ce serveur avec des méthodes de cache via ce trait
|
||||
/// - Le serveur n'a pas besoin de connaître `pmocovers`
|
||||
/// 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) => {
|
||||
tracing::warn!("Cannot generate variant {}x{} for {}: {}", size, size, pk, e);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Param non reconnu
|
||||
None
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Trait d'extension pour ajouter le cache de couvertures à pmoserver
|
||||
#[cfg(feature = "pmoserver")]
|
||||
pub trait CoverCacheExt {
|
||||
/// Initialise le cache d'images et enregistre les routes HTTP.
|
||||
/// Initialise le cache d'images et enregistre les routes HTTP
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -230,49 +87,59 @@ pub trait CoverCacheExt {
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Arc<Cache>` - Instance partagée du cache
|
||||
/// Instance partagée du cache
|
||||
///
|
||||
/// # Routes enregistrées
|
||||
///
|
||||
/// - `GET /covers/images/{pk}` - Image originale
|
||||
/// - `GET /covers/images/{pk}/{size}` - Variante de taille
|
||||
/// - `GET /covers/stats` - Statistiques
|
||||
/// - `GET /covers/image/{pk}` - Image originale
|
||||
/// - `GET /covers/image/{pk}/{size}` - Variante de taille (ex: 256, 512)
|
||||
/// - `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)
|
||||
/// - `GET /swagger-ui` - Documentation interactive
|
||||
async fn init_cover_cache(&mut self, cache_dir: &str, limit: usize) -> Result<Arc<Cache>>;
|
||||
/// - `GET /api/covers/{pk}/status` - Statut du téléchargement
|
||||
/// - `GET /swagger-ui/covers` - Documentation interactive
|
||||
async fn init_cover_cache(&mut self, cache_dir: &str, limit: usize)
|
||||
-> anyhow::Result<Arc<Cache>>;
|
||||
|
||||
/// Initialise le cache d'images avec la configuration par défaut.
|
||||
/// Initialise le cache d'images avec la configuration par défaut
|
||||
///
|
||||
/// Utilise automatiquement les paramètres de `pmoconfig::Config` :
|
||||
/// - `host.cover_cache.directory` pour le répertoire
|
||||
/// - `host.cover_cache.size` pour la limite de taille
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Arc<Cache>` - Instance partagée du cache
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pmocovers::CoverCacheExt;
|
||||
/// use pmoserver::ServerBuilder;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> anyhow::Result<()> {
|
||||
/// let mut server = ServerBuilder::new_configured().build();
|
||||
///
|
||||
/// // Utilise automatiquement la config
|
||||
/// server.init_cover_cache_configured().await?;
|
||||
///
|
||||
/// server.start().await;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
async fn init_cover_cache_configured(&mut self) -> Result<Arc<Cache>>;
|
||||
/// Utilise automatiquement les paramètres de `pmoconfig::Config`
|
||||
async fn init_cover_cache_configured(&mut self)
|
||||
-> anyhow::Result<Arc<Cache>>;
|
||||
}
|
||||
|
||||
// Implémentation du trait pour pmoserver::Server (feature-gated)
|
||||
#[cfg(feature = "pmoserver")]
|
||||
mod pmoserver_impl;
|
||||
impl CoverCacheExt for pmoserver::Server {
|
||||
async fn init_cover_cache(&mut self, cache_dir: &str, limit: usize)
|
||||
-> anyhow::Result<Arc<Cache>> {
|
||||
use pmocache::pmoserver_ext::{create_file_router_with_generator, create_api_router};
|
||||
|
||||
let base_url = self.info().base_url;
|
||||
let cache = Arc::new(cache::new_cache(cache_dir, limit, &base_url)?);
|
||||
|
||||
// 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",
|
||||
Some(create_variant_generator())
|
||||
);
|
||||
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)
|
||||
}
|
||||
|
||||
async fn init_cover_cache_configured(&mut self)
|
||||
-> anyhow::Result<Arc<Cache>> {
|
||||
let config = pmoconfig::get_config();
|
||||
let cache_dir = config.get_cover_cache_dir()?;
|
||||
let limit = config.get_cover_cache_size()?;
|
||||
self.init_cover_cache(&cache_dir, limit).await
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user