Files
pmomusic/pmoqobuz/src/cache.rs

325 lines
10 KiB
Rust

//! Système de cache en mémoire pour les données Qobuz
//!
//! Ce module fournit un cache en mémoire avec TTL pour minimiser les requêtes à l'API Qobuz.
use crate::models::{Album, Artist, Playlist, SearchResult, StreamInfo, Track};
use moka::future::Cache as MokaCache;
use std::sync::Arc;
use std::time::Duration;
/// Cache principal pour les données Qobuz
#[derive(Clone)]
pub struct QobuzCache {
/// Cache des albums (TTL: 1 heure)
albums: Arc<MokaCache<String, Album>>,
/// Cache des tracks (TTL: 1 heure)
tracks: Arc<MokaCache<String, Track>>,
/// Cache des tracks d'un album complet (TTL: 1 heure)
album_tracks: Arc<MokaCache<String, Vec<Track>>>,
/// Cache des artistes (TTL: 1 heure)
artists: Arc<MokaCache<String, Artist>>,
/// Cache des playlists (TTL: 30 minutes)
playlists: Arc<MokaCache<String, Playlist>>,
/// Cache des résultats de recherche (TTL: 15 minutes)
searches: Arc<MokaCache<String, SearchResult>>,
/// Cache des URLs de streaming (TTL: 5 minutes)
stream_urls: Arc<MokaCache<String, StreamInfo>>,
}
impl QobuzCache {
/// Crée un nouveau cache avec les paramètres par défaut
pub fn new() -> Self {
Self::with_capacity(1000)
}
/// Crée un nouveau cache avec une capacité spécifique
pub fn with_capacity(max_capacity: u64) -> Self {
Self {
albums: Arc::new(
MokaCache::builder()
.max_capacity(max_capacity)
.time_to_live(Duration::from_secs(3600)) // 1 heure
.build(),
),
tracks: Arc::new(
MokaCache::builder()
.max_capacity(max_capacity * 2)
.time_to_live(Duration::from_secs(3600)) // 1 heure
.build(),
),
album_tracks: Arc::new(
MokaCache::builder()
.max_capacity(max_capacity)
.time_to_live(Duration::from_secs(3600)) // 1 heure
.build(),
),
artists: Arc::new(
MokaCache::builder()
.max_capacity(max_capacity / 2)
.time_to_live(Duration::from_secs(3600)) // 1 heure
.build(),
),
playlists: Arc::new(
MokaCache::builder()
.max_capacity(max_capacity / 4)
.time_to_live(Duration::from_secs(1800)) // 30 minutes
.build(),
),
searches: Arc::new(
MokaCache::builder()
.max_capacity(max_capacity / 2)
.time_to_live(Duration::from_secs(900)) // 15 minutes
.build(),
),
stream_urls: Arc::new(
MokaCache::builder()
.max_capacity(max_capacity / 4)
.time_to_live(Duration::from_secs(300)) // 5 minutes
.build(),
),
}
}
// ============ Albums ============
/// Récupère un album depuis le cache
pub async fn get_album(&self, id: &str) -> Option<Album> {
self.albums.get(id).await
}
/// Ajoute un album au cache
pub async fn put_album(&self, id: String, album: Album) {
self.albums.insert(id, album).await;
}
/// Invalide un album du cache
pub async fn invalidate_album(&self, id: &str) {
self.albums.invalidate(id).await;
}
// ============ Tracks ============
/// Récupère une track depuis le cache
pub async fn get_track(&self, id: &str) -> Option<Track> {
self.tracks.get(id).await
}
/// Ajoute une track au cache
pub async fn put_track(&self, id: String, track: Track) {
self.tracks.insert(id, track).await;
}
/// Invalide une track du cache
pub async fn invalidate_track(&self, id: &str) {
self.tracks.invalidate(id).await;
}
// ============ Album Tracks (liste complète des tracks d'un album) ============
/// Récupère la liste complète des tracks d'un album depuis le cache
pub async fn get_album_tracks(&self, album_id: &str) -> Option<Vec<Track>> {
self.album_tracks.get(album_id).await
}
/// Ajoute la liste complète des tracks d'un album au cache
pub async fn put_album_tracks(&self, album_id: String, tracks: Vec<Track>) {
self.album_tracks.insert(album_id, tracks).await;
}
/// Invalide la liste des tracks d'un album du cache
pub async fn invalidate_album_tracks(&self, album_id: &str) {
self.album_tracks.invalidate(album_id).await;
}
// ============ Artists ============
/// Récupère un artiste depuis le cache
pub async fn get_artist(&self, id: &str) -> Option<Artist> {
self.artists.get(id).await
}
/// Ajoute un artiste au cache
pub async fn put_artist(&self, id: String, artist: Artist) {
self.artists.insert(id, artist).await;
}
/// Invalide un artiste du cache
pub async fn invalidate_artist(&self, id: &str) {
self.artists.invalidate(id).await;
}
// ============ Playlists ============
/// Récupère une playlist depuis le cache
pub async fn get_playlist(&self, id: &str) -> Option<Playlist> {
self.playlists.get(id).await
}
/// Ajoute une playlist au cache
pub async fn put_playlist(&self, id: String, playlist: Playlist) {
self.playlists.insert(id, playlist).await;
}
/// Invalide une playlist du cache
pub async fn invalidate_playlist(&self, id: &str) {
self.playlists.invalidate(id).await;
}
// ============ Recherches ============
/// Récupère un résultat de recherche depuis le cache
pub async fn get_search(&self, query: &str) -> Option<SearchResult> {
self.searches.get(query).await
}
/// Ajoute un résultat de recherche au cache
pub async fn put_search(&self, query: String, result: SearchResult) {
self.searches.insert(query, result).await;
}
/// Invalide un résultat de recherche du cache
pub async fn invalidate_search(&self, query: &str) {
self.searches.invalidate(query).await;
}
// ============ URLs de streaming ============
/// Récupère une URL de streaming depuis le cache
pub async fn get_stream_url(&self, track_id: &str) -> Option<StreamInfo> {
self.stream_urls.get(track_id).await
}
/// Ajoute une URL de streaming au cache
pub async fn put_stream_url(&self, track_id: String, info: StreamInfo) {
self.stream_urls.insert(track_id, info).await;
}
/// Invalide une URL de streaming du cache
pub async fn invalidate_stream_url(&self, track_id: &str) {
self.stream_urls.invalidate(track_id).await;
}
// ============ Maintenance ============
/// Vide tous les caches
pub async fn clear_all(&self) {
self.albums.invalidate_all();
self.tracks.invalidate_all();
self.album_tracks.invalidate_all();
self.artists.invalidate_all();
self.playlists.invalidate_all();
self.searches.invalidate_all();
self.stream_urls.invalidate_all();
}
/// Retourne des statistiques sur le cache
pub async fn stats(&self) -> CacheStats {
self.albums.run_pending_tasks().await;
self.tracks.run_pending_tasks().await;
self.album_tracks.run_pending_tasks().await;
self.artists.run_pending_tasks().await;
self.playlists.run_pending_tasks().await;
self.searches.run_pending_tasks().await;
self.stream_urls.run_pending_tasks().await;
CacheStats {
albums_count: self.albums.entry_count(),
tracks_count: self.tracks.entry_count(),
album_tracks_count: self.album_tracks.entry_count(),
artists_count: self.artists.entry_count(),
playlists_count: self.playlists.entry_count(),
searches_count: self.searches.entry_count(),
stream_urls_count: self.stream_urls.entry_count(),
}
}
}
impl Default for QobuzCache {
fn default() -> Self {
Self::new()
}
}
/// Statistiques du cache
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CacheStats {
/// Nombre d'albums en cache
pub albums_count: u64,
/// Nombre de tracks en cache
pub tracks_count: u64,
/// Nombre de listes complètes de tracks d'albums en cache
pub album_tracks_count: u64,
/// Nombre d'artistes en cache
pub artists_count: u64,
/// Nombre de playlists en cache
pub playlists_count: u64,
/// Nombre de recherches en cache
pub searches_count: u64,
/// Nombre d'URLs de streaming en cache
pub stream_urls_count: u64,
}
impl CacheStats {
/// Retourne le nombre total d'entrées en cache
pub fn total_count(&self) -> u64 {
self.albums_count
+ self.tracks_count
+ self.album_tracks_count
+ self.artists_count
+ self.playlists_count
+ self.searches_count
+ self.stream_urls_count
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::Artist;
#[tokio::test]
async fn test_cache_basic_operations() {
let cache = QobuzCache::new();
let artist = Artist::new("123", "Test Artist");
// Test insertion
cache.put_artist("123".to_string(), artist.clone()).await;
// Test récupération
let retrieved = cache.get_artist("123").await;
assert!(retrieved.is_some());
assert_eq!(retrieved.unwrap().name, "Test Artist");
// Test invalidation
cache.invalidate_artist("123").await;
let after_invalidation = cache.get_artist("123").await;
assert!(after_invalidation.is_none());
}
#[tokio::test]
async fn test_cache_stats() {
let cache = QobuzCache::new();
let artist = Artist::new("123", "Test Artist");
cache.put_artist("123".to_string(), artist).await;
let stats = cache.stats().await;
assert_eq!(stats.artists_count, 1);
assert_eq!(stats.albums_count, 0);
}
#[tokio::test]
async fn test_cache_clear_all() {
let cache = QobuzCache::new();
let artist = Artist::new("123", "Test Artist");
cache.put_artist("123".to_string(), artist).await;
cache.clear_all().await;
let stats = cache.stats().await;
assert_eq!(stats.total_count(), 0);
}
}