2025-10-26 13:44:41 +01:00
|
|
|
|
//! # pmocache – Système de cache générique pour PMOMusic
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! 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.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-12-17 10:10:56 +01:00
|
|
|
|
//! 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.
|
|
|
|
|
|
//!
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//! ## Vue d'ensemble
|
|
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! `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.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! 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…).
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Exemple basique
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! ```rust,no_run
|
2025-10-17 14:36:39 +02:00
|
|
|
|
//! use pmocache::{Cache, CacheConfig};
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! struct MyConfig;
|
|
|
|
|
|
//! impl CacheConfig for MyConfig {
|
|
|
|
|
|
//! fn file_extension() -> &'static str { "dat" }
|
|
|
|
|
|
//! }
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! #[tokio::main]
|
|
|
|
|
|
//! async fn main() -> anyhow::Result<()> {
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! let cache = Cache::<MyConfig>::new("./cache", 1000)?;
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! // 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}");
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! // Récupération du fichier local
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//! let path = cache.get(&pk).await?;
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! println!("Fichier disponible à {path:?}");
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! Ok(())
|
|
|
|
|
|
//! }
|
|
|
|
|
|
//! ```
|
|
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Collections
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! ```rust,no_run
|
2025-10-17 14:36:39 +02:00
|
|
|
|
//! use pmocache::{Cache, CacheConfig};
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! struct AudioConfig;
|
|
|
|
|
|
//! impl CacheConfig for AudioConfig {
|
|
|
|
|
|
//! fn file_extension() -> &'static str { "flac" }
|
|
|
|
|
|
//! fn cache_type() -> &'static str { "audio" }
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! fn cache_name() -> &'static str { "tracks" }
|
2025-10-17 14:36:39 +02:00
|
|
|
|
//! }
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! #[tokio::main]
|
|
|
|
|
|
//! async fn main() -> anyhow::Result<()> {
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! let cache = Cache::<AudioConfig>::new("./audio-cache", 200)?;
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! 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?;
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! let files = cache.get_collection(album).await?;
|
|
|
|
|
|
//! println!("Album {album} : {} fichiers en cache", files.len());
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! Ok(())
|
|
|
|
|
|
//! }
|
|
|
|
|
|
//! ```
|
|
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Structure sur disque
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! ```text
|
|
|
|
|
|
//! cache/
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ├── cache.db # Base SQLite
|
|
|
|
|
|
//! ├── 1a2b3c4d.orig.dat # Fichier original
|
|
|
|
|
|
//! └── 1a2b3c4d.thumb.dat # Variante (qualifier différent)
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//! ```
|
|
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! Les métadonnées sont conservées dans deux tables :
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
|
|
|
|
|
//! ```sql
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! 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
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//! );
|
|
|
|
|
|
//! ```
|
|
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Modules principaux
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! - [`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.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! ## Crates associées
|
2025-10-12 21:39:20 +02:00
|
|
|
|
//!
|
2025-10-26 13:44:41 +01:00
|
|
|
|
//! - [`pmocovers`] : cache d'images reposant sur `pmocache` ;
|
|
|
|
|
|
//! - [`pmoaudiocache`] : spécialisation audio avec extraction de métadonnées.
|
2025-10-12 21:39:20 +02:00
|
|
|
|
|
|
|
|
|
|
pub mod cache;
|
2025-10-17 14:36:39 +02:00
|
|
|
|
pub mod cache_trait;
|
2025-10-19 13:42:29 +02:00
|
|
|
|
pub mod db;
|
2025-10-17 19:48:53 +02:00
|
|
|
|
pub mod download;
|
2025-12-15 15:05:35 +01:00
|
|
|
|
pub mod lazy;
|
2025-10-27 12:32:31 +01:00
|
|
|
|
pub mod metadata_macros;
|
2025-10-17 14:36:39 +02:00
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
|
pub mod pmoserver_ext;
|
2025-10-12 21:39:20 +02:00
|
|
|
|
|
2025-10-17 19:48:53 +02:00
|
|
|
|
#[cfg(feature = "pmoserver")]
|
|
|
|
|
|
pub mod api;
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "openapi")]
|
|
|
|
|
|
pub mod openapi;
|
|
|
|
|
|
|
2025-10-25 17:14:24 +02:00
|
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
|
|
|
|
pub mod config_ext;
|
|
|
|
|
|
|
2025-12-12 16:36:31 +00:00
|
|
|
|
pub use cache::{
|
|
|
|
|
|
generate_lazy_pk, is_lazy_pk, Cache, CacheBroadcastEvent, CacheConfig, CacheEvent,
|
|
|
|
|
|
CacheSubscription,
|
|
|
|
|
|
};
|
2025-10-27 12:32:31 +01:00
|
|
|
|
pub use cache_trait::{pk_from_content_header, FileCache};
|
2025-10-19 13:42:29 +02:00
|
|
|
|
pub use db::{CacheEntry, DB};
|
2025-10-19 17:26:06 +02:00
|
|
|
|
pub use download::{
|
2025-10-26 09:18:48 +01:00
|
|
|
|
download, download_with_transformer, ingest_with_transformer, peek_header, peek_reader_header,
|
2025-10-28 00:10:51 +01:00
|
|
|
|
Download, StreamTransformer, TransformContextHandle, TransformMetadata,
|
2025-10-19 17:26:06 +02:00
|
|
|
|
};
|
2025-12-17 10:10:56 +01:00
|
|
|
|
pub use lazy::{lazy_prefix_from_pk, LazyEntryRemoteData, LazyProvider};
|
2025-10-17 19:48:53 +02:00
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "pmoserver")]
|
2025-10-19 13:42:29 +02:00
|
|
|
|
pub use pmoserver_ext::{create_api_router, create_file_router, GenericCacheExt};
|
2025-10-17 19:48:53 +02:00
|
|
|
|
|
|
|
|
|
|
#[cfg(all(feature = "pmoserver", feature = "openapi"))]
|
2025-10-19 13:42:29 +02:00
|
|
|
|
pub use api::{AddItemRequest, AddItemResponse, DeleteItemResponse, DownloadStatus, ErrorResponse};
|
2025-10-25 17:14:24 +02:00
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "pmoconfig")]
|
|
|
|
|
|
pub use config_ext::CacheConfigExt;
|