la crate des playlists
This commit is contained in:
111
pmoplaylist/src/playlist/core.rs
Normal file
111
pmoplaylist/src/playlist/core.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! PlaylistCore : structure FIFO avec éviction automatique
|
||||
|
||||
use super::record::Record;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Configuration d'une playlist
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlaylistConfig {
|
||||
pub max_size: Option<usize>,
|
||||
pub default_ttl: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Default for PlaylistConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_size: None,
|
||||
default_ttl: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Noyau de la playlist (structure interne protégée par RwLock)
|
||||
pub struct PlaylistCore {
|
||||
pub tracks: VecDeque<Arc<Record>>,
|
||||
pub config: PlaylistConfig,
|
||||
}
|
||||
|
||||
impl PlaylistCore {
|
||||
/// Crée un nouveau core
|
||||
pub fn new(config: PlaylistConfig) -> Self {
|
||||
Self {
|
||||
tracks: VecDeque::new(),
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
/// Ajoute un record et applique l'éviction
|
||||
pub fn push(&mut self, record: Record) {
|
||||
self.tracks.push_back(Arc::new(record));
|
||||
self.evict();
|
||||
}
|
||||
|
||||
/// Ajoute plusieurs records de manière atomique
|
||||
pub fn push_all(&mut self, records: Vec<Record>) {
|
||||
for record in records {
|
||||
self.tracks.push_back(Arc::new(record));
|
||||
}
|
||||
self.evict();
|
||||
}
|
||||
|
||||
/// Nettoie les morceaux expirés et applique la limite de taille
|
||||
pub fn evict(&mut self) {
|
||||
// 1. Supprimer les morceaux périmés par TTL
|
||||
self.tracks.retain(|record| {
|
||||
!record.is_expired(self.config.default_ttl)
|
||||
});
|
||||
|
||||
// 2. Appliquer la limite de taille (FIFO)
|
||||
if let Some(max) = self.config.max_size {
|
||||
while self.tracks.len() > max {
|
||||
self.tracks.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Vide complètement la playlist
|
||||
pub fn clear(&mut self) {
|
||||
self.tracks.clear();
|
||||
}
|
||||
|
||||
/// Nombre de morceaux
|
||||
pub fn len(&self) -> usize {
|
||||
self.tracks.len()
|
||||
}
|
||||
|
||||
/// Vérifie si la playlist est vide
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.tracks.is_empty()
|
||||
}
|
||||
|
||||
/// Récupère un record par index
|
||||
pub fn get(&self, index: usize) -> Option<Arc<Record>> {
|
||||
self.tracks.get(index).cloned()
|
||||
}
|
||||
|
||||
/// Snapshot de tous les records
|
||||
pub fn snapshot(&self) -> Vec<Arc<Record>> {
|
||||
self.tracks.iter().cloned().collect()
|
||||
}
|
||||
|
||||
/// Supprime un record par cache_pk (retourne true si supprimé)
|
||||
pub fn remove_by_cache_pk(&mut self, cache_pk: &str) -> bool {
|
||||
let initial_len = self.tracks.len();
|
||||
self.tracks.retain(|r| r.cache_pk != cache_pk);
|
||||
self.tracks.len() != initial_len
|
||||
}
|
||||
|
||||
/// Met à jour la capacité maximale
|
||||
pub fn set_capacity(&mut self, max_size: Option<usize>) {
|
||||
self.config.max_size = max_size;
|
||||
self.evict();
|
||||
}
|
||||
|
||||
/// Met à jour le TTL par défaut
|
||||
pub fn set_default_ttl(&mut self, ttl: Option<Duration>) {
|
||||
self.config.default_ttl = ttl;
|
||||
self.evict();
|
||||
}
|
||||
}
|
||||
102
pmoplaylist/src/playlist/mod.rs
Normal file
102
pmoplaylist/src/playlist/mod.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
//! Playlist interne (non exposée publiquement)
|
||||
|
||||
pub mod core;
|
||||
pub mod record;
|
||||
|
||||
use self::core::{PlaylistConfig, PlaylistCore};
|
||||
use self::record::Record;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::SystemTime;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
/// État d'une playlist
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
enum PlaylistState {
|
||||
Active = 0,
|
||||
Deleted = 1,
|
||||
}
|
||||
|
||||
impl From<u8> for PlaylistState {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => PlaylistState::Deleted,
|
||||
_ => PlaylistState::Active,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Playlist interne (gérée par le PlaylistManager)
|
||||
pub struct Playlist {
|
||||
pub id: String,
|
||||
title: RwLock<String>,
|
||||
state: Arc<AtomicU8>,
|
||||
pub core: Arc<RwLock<PlaylistCore>>,
|
||||
pub persistent: bool,
|
||||
last_change: RwLock<SystemTime>,
|
||||
writer_lock: RwLock<Option<Weak<()>>>,
|
||||
}
|
||||
|
||||
impl Playlist {
|
||||
/// Crée une nouvelle playlist
|
||||
pub fn new(id: String, title: String, config: PlaylistConfig, persistent: bool) -> Self {
|
||||
Self {
|
||||
id,
|
||||
title: RwLock::new(title),
|
||||
state: Arc::new(AtomicU8::new(PlaylistState::Active as u8)),
|
||||
core: Arc::new(RwLock::new(PlaylistCore::new(config))),
|
||||
persistent,
|
||||
last_change: RwLock::new(SystemTime::now()),
|
||||
writer_lock: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si la playlist est active
|
||||
pub fn is_alive(&self) -> bool {
|
||||
PlaylistState::from(self.state.load(Ordering::SeqCst)) == PlaylistState::Active
|
||||
}
|
||||
|
||||
/// Marque la playlist comme supprimée
|
||||
pub fn mark_deleted(&self) {
|
||||
self.state.store(PlaylistState::Deleted as u8, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Met à jour le timestamp de dernière modification
|
||||
pub async fn touch(&self) {
|
||||
*self.last_change.write().await = SystemTime::now();
|
||||
}
|
||||
|
||||
/// Récupère le titre
|
||||
pub async fn title(&self) -> String {
|
||||
self.title.read().await.clone()
|
||||
}
|
||||
|
||||
/// Change le titre
|
||||
pub async fn set_title(&self, title: String) {
|
||||
*self.title.write().await = title;
|
||||
self.touch().await;
|
||||
}
|
||||
|
||||
/// Timestamp du dernier changement
|
||||
pub async fn last_change(&self) -> SystemTime {
|
||||
*self.last_change.read().await
|
||||
}
|
||||
|
||||
/// Tente d'acquérir le write lock
|
||||
pub async fn acquire_write_lock(&self) -> Result<Arc<()>, ()> {
|
||||
let mut guard = self.writer_lock.write().await;
|
||||
|
||||
// Vérifier si un writer existe déjà
|
||||
if let Some(weak) = guard.as_ref() {
|
||||
if weak.strong_count() > 0 {
|
||||
return Err(()); // Lock déjà pris
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un nouveau token
|
||||
let token = Arc::new(());
|
||||
*guard = Some(Arc::downgrade(&token));
|
||||
Ok(token)
|
||||
}
|
||||
}
|
||||
61
pmoplaylist/src/playlist/record.rs
Normal file
61
pmoplaylist/src/playlist/record.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
//! Record : entrée dans la playlist pointant vers le cache audio
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
/// Un enregistrement dans la playlist
|
||||
///
|
||||
/// Contient uniquement une référence (pk) vers une entrée dans pmoaudiocache
|
||||
/// et des informations de gestion (timestamp, TTL).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Record {
|
||||
/// Clé primaire dans pmoaudiocache
|
||||
pub cache_pk: String,
|
||||
|
||||
/// Timestamp d'ajout à la playlist (en nanosecondes depuis epoch)
|
||||
pub added_at: SystemTime,
|
||||
|
||||
/// Durée de vie optionnelle (surcharge le TTL par défaut)
|
||||
pub ttl: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Record {
|
||||
/// Crée un nouveau record
|
||||
pub fn new(cache_pk: String) -> Self {
|
||||
Self {
|
||||
cache_pk,
|
||||
added_at: SystemTime::now(),
|
||||
ttl: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Crée un record avec un TTL personnalisé
|
||||
pub fn with_ttl(cache_pk: String, ttl: Duration) -> Self {
|
||||
Self {
|
||||
cache_pk,
|
||||
added_at: SystemTime::now(),
|
||||
ttl: Some(ttl),
|
||||
}
|
||||
}
|
||||
|
||||
/// Vérifie si le record est expiré
|
||||
pub fn is_expired(&self, default_ttl: Option<Duration>) -> bool {
|
||||
let now = SystemTime::now();
|
||||
let age = now.duration_since(self.added_at).unwrap_or_default();
|
||||
|
||||
if let Some(ttl) = self.ttl {
|
||||
age >= ttl
|
||||
} else if let Some(default_ttl) = default_ttl {
|
||||
age >= default_ttl
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Retourne le timestamp en nanosecondes depuis epoch
|
||||
pub fn added_at_nanos(&self) -> i64 {
|
||||
self.added_at
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos() as i64
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user