Ajout de la capacité à épingler des items pour les protéger de l'éviction LRU et à définir un TTL pour l'expiration automatique des items temporaires. Cette implémentation inclut : - Ajout de colonnes `pinned` et `ttl_expires_at` dans la base de données - Nouvelles méthodes dans DB et Cache pour gérer le pinning et le TTL - Modification de la politique d'éviction pour exclure les items épinglés - Implémentation d'une règle métier interdisant le pinning et le TTL simultanément - API REST complète avec endpoints GET/POST/DELETE pour gérer le pinning et le TTL - Documentation OpenAPI automatique - Tests complets couvrant tous les cas d'usage Les items épinglés ne comptent pas dans la limite du cache et ne peuvent jamais être supprimés automatiquement, tandis que les items avec TTL sont supprimés automatiquement à l'expiration.
165 lines
5.5 KiB
Rust
165 lines
5.5 KiB
Rust
//! # pmocache – Système de cache générique pour PMOMusic
|
||
//!
|
||
//! Cette crate fournit les briques communes utilisées par les caches de PMOMusic.
|
||
//! Elle gère l'association entre fichiers stockés sur disque et métadonnées
|
||
//! conservées dans une base SQLite, ainsi que les opérations de téléchargement,
|
||
//! d'éviction et de mise à jour.
|
||
//!
|
||
//! Les fonctionnalités clés incluent :
|
||
//! - **lazy caching** : génération de clés « lazy » (préfixées `L:`) permettant de publier des
|
||
//! URLs stables avant le téléchargement. Le cache résout automatiquement un lazy PK lors du
|
||
//! premier accès, commute l'entrée vers le PK réel et notifie les abonnés ;
|
||
//! - **local caching** : possibilité d’enregistrer un fichier déjà présent sur le disque via
|
||
//! [`Cache::register_local_file_reference`] sans duplication physique. Cette méthode crée un
|
||
//! lien (symlink ou hard link suivant la plateforme), marque l’élément comme complet et laisse
|
||
//! la gestion métier (audio, images…) décider des métadonnées à persister.
|
||
//!
|
||
//! ## Vue d'ensemble
|
||
//!
|
||
//! `pmocache` met à disposition :
|
||
//! - un modèle `Cache` asynchrone pour stocker des fichiers et leurs métadonnées ;
|
||
//! - un module `db` encapsulant l'accès SQLite (table `asset` + table `metadata`) ;
|
||
//! - des utilitaires de téléchargement (`download`) réutilisables par les caches spécialisés ;
|
||
//! - un trait `CacheConfig` permettant de paramétrer l'extension, le nom et le type du cache.
|
||
//!
|
||
//! Les crates `pmocovers` (images) et `pmoaudiocache` (pistes audio) s'appuient sur ces
|
||
//! composants et ajoutent leurs propres contraintes métier (conversion WebP, métadonnées audio…).
|
||
//!
|
||
//! ## Exemple basique
|
||
//!
|
||
//! ```rust,no_run
|
||
//! use pmocache::{Cache, CacheConfig};
|
||
//!
|
||
//! struct MyConfig;
|
||
//! impl CacheConfig for MyConfig {
|
||
//! fn file_extension() -> &'static str { "dat" }
|
||
//! }
|
||
//!
|
||
//! #[tokio::main]
|
||
//! async fn main() -> anyhow::Result<()> {
|
||
//! let cache = Cache::<MyConfig>::new("./cache", 1000)?;
|
||
//!
|
||
//! // Ajout d'un fichier depuis une URL
|
||
//! let pk = cache.add_from_url("https://example.com/file.dat", None).await?;
|
||
//! println!("Fichier ajouté avec la clé {pk}");
|
||
//!
|
||
//! // Récupération du fichier local
|
||
//! let path = cache.get(&pk).await?;
|
||
//! println!("Fichier disponible à {path:?}");
|
||
//!
|
||
//! Ok(())
|
||
//! }
|
||
//! ```
|
||
//!
|
||
//! ## Collections
|
||
//!
|
||
//! ```rust,no_run
|
||
//! use pmocache::{Cache, CacheConfig};
|
||
//!
|
||
//! struct AudioConfig;
|
||
//! impl CacheConfig for AudioConfig {
|
||
//! fn file_extension() -> &'static str { "flac" }
|
||
//! fn cache_type() -> &'static str { "audio" }
|
||
//! fn cache_name() -> &'static str { "tracks" }
|
||
//! }
|
||
//!
|
||
//! #[tokio::main]
|
||
//! async fn main() -> anyhow::Result<()> {
|
||
//! let cache = Cache::<AudioConfig>::new("./audio-cache", 200)?;
|
||
//!
|
||
//! let album = "album:the_wall";
|
||
//! cache.add_from_url("https://example.com/track1.flac", Some(album)).await?;
|
||
//! cache.add_from_url("https://example.com/track2.flac", Some(album)).await?;
|
||
//!
|
||
//! let files = cache.get_collection(album).await?;
|
||
//! println!("Album {album} : {} fichiers en cache", files.len());
|
||
//!
|
||
//! Ok(())
|
||
//! }
|
||
//! ```
|
||
//!
|
||
//! ## Structure sur disque
|
||
//!
|
||
//! ```text
|
||
//! cache/
|
||
//! ├── cache.db # Base SQLite
|
||
//! ├── 1a2b3c4d.orig.dat # Fichier original
|
||
//! └── 1a2b3c4d.thumb.dat # Variante (qualifier différent)
|
||
//! ```
|
||
//!
|
||
//! Les métadonnées sont conservées dans deux tables :
|
||
//!
|
||
//! ```sql
|
||
//! CREATE TABLE asset (
|
||
//! pk TEXT PRIMARY KEY,
|
||
//! collection TEXT,
|
||
//! id TEXT,
|
||
//! hits INTEGER DEFAULT 0,
|
||
//! last_used TEXT
|
||
//! );
|
||
//!
|
||
//! CREATE TABLE metadata (
|
||
//! pk TEXT,
|
||
//! key TEXT,
|
||
//! value_type TEXT CHECK(value_type IN ('string','number','boolean','null')),
|
||
//! value TEXT,
|
||
//! PRIMARY KEY (pk, key),
|
||
//! FOREIGN KEY (pk) REFERENCES asset(pk) ON DELETE CASCADE
|
||
//! );
|
||
//! ```
|
||
//!
|
||
//! ## Modules principaux
|
||
//!
|
||
//! - [`cache`] : gestion du cache sur disque + opérations asynchrones ;
|
||
//! - [`db`] : accès SQLite, contraintes et helpers métadonnées ;
|
||
//! - [`download`] : primitives de téléchargement et de transformation ;
|
||
//! - [`cache_trait`] : trait partagé entre implémentations spécialisées.
|
||
//!
|
||
//! ## Crates associées
|
||
//!
|
||
//! - [`pmocovers`] : cache d'images reposant sur `pmocache` ;
|
||
//! - [`pmoaudiocache`] : spécialisation audio avec extraction de métadonnées.
|
||
|
||
pub mod cache;
|
||
pub mod cache_trait;
|
||
pub mod db;
|
||
pub mod download;
|
||
pub mod lazy;
|
||
pub mod metadata_macros;
|
||
|
||
#[cfg(feature = "pmoserver")]
|
||
pub mod pmoserver_ext;
|
||
|
||
#[cfg(feature = "pmoserver")]
|
||
pub mod api;
|
||
|
||
#[cfg(feature = "openapi")]
|
||
pub mod openapi;
|
||
|
||
#[cfg(feature = "pmoconfig")]
|
||
pub mod config_ext;
|
||
|
||
pub use cache::{
|
||
generate_lazy_pk, is_lazy_pk, Cache, CacheBroadcastEvent, CacheConfig, CacheEvent,
|
||
CacheSubscription,
|
||
};
|
||
pub use cache_trait::{pk_from_content_header, FileCache};
|
||
pub use db::{CacheEntry, DB};
|
||
pub use download::{
|
||
download, download_with_transformer, ingest_with_transformer, peek_header, peek_reader_header,
|
||
Download, StreamTransformer, TransformContextHandle, TransformMetadata,
|
||
};
|
||
pub use lazy::{lazy_prefix_from_pk, LazyEntryRemoteData, LazyProvider};
|
||
|
||
#[cfg(feature = "pmoserver")]
|
||
pub use pmoserver_ext::{create_api_router, create_file_router, GenericCacheExt};
|
||
|
||
#[cfg(all(feature = "pmoserver", feature = "openapi"))]
|
||
pub use api::{
|
||
AddItemRequest, AddItemResponse, DeleteItemResponse, DownloadStatus, ErrorResponse,
|
||
PinResponse, PinStatus, SetTtlRequest,
|
||
};
|
||
|
||
#[cfg(feature = "pmoconfig")]
|
||
pub use config_ext::CacheConfigExt;
|