//! WriteHandle : accès exclusif en écriture à une playlist use crate::playlist::record::Record; use crate::playlist::Playlist; use crate::Result; use pmocache::cache_trait::FileCache; use std::sync::Arc; use std::time::{Duration, SystemTime}; /// Handle d'écriture sur une playlist (exclusif) pub struct WriteHandle { playlist: Arc, _write_token: Arc<()>, } impl WriteHandle { /// Crée un nouveau handle d'écriture pub(crate) fn new(playlist: Arc, write_token: Arc<()>) -> Self { Self { playlist, _write_token: write_token, } } /// Ajoute un morceau à la playlist pub async fn push(&self, cache_pk: String) -> Result<()> { if !self.playlist.is_alive() { return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone())); } // Vérifier que le pk existe dans le cache let cache = crate::manager::audio_cache()?; if !cache.is_valid_pk(&cache_pk) { return Err(crate::Error::CacheEntryNotFound(cache_pk)); } // Ajouter à la playlist let record = Record::new(cache_pk); let mut core = self.playlist.core.write().await; core.push(record); drop(core); self.playlist.touch().await; // Sauvegarder si persistante if self.playlist.persistent { self.save_to_db().await?; } Ok(()) } /// Ajoute plusieurs morceaux de manière atomique pub async fn push_set(&self, cache_pks: Vec) -> Result<()> { if !self.playlist.is_alive() { return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone())); } // Vérifier tous les pks d'abord let cache = crate::manager::audio_cache()?; for pk in &cache_pks { if !cache.is_valid_pk(pk) { return Err(crate::Error::CacheEntryNotFound(pk.clone())); } } // Créer tous les records let records: Vec = cache_pks.into_iter().map(Record::new).collect(); // Ajouter atomiquement let mut core = self.playlist.core.write().await; core.push_all(records); drop(core); self.playlist.touch().await; // Une seule sauvegarde pour tout le batch if self.playlist.persistent { self.save_to_db().await?; } Ok(()) } /// Vide la playlist pub async fn flush(&self) -> Result<()> { if !self.playlist.is_alive() { return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone())); } let mut core = self.playlist.core.write().await; core.clear(); drop(core); self.playlist.touch().await; if self.playlist.persistent { self.save_to_db().await?; } Ok(()) } /// Supprime la playlist définitivement pub async fn delete(self) -> Result<()> { if !self.playlist.is_alive() { return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone())); } // Marquer comme supprimée self.playlist.mark_deleted(); // Supprimer du manager crate::manager::delete_playlist_internal(&self.playlist.id).await?; Ok(()) } /// Change le titre pub async fn set_title(&self, title: String) -> Result<()> { if !self.playlist.is_alive() { return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone())); } self.playlist.set_title(title).await; if self.playlist.persistent { self.save_to_db().await?; } Ok(()) } /// Change la capacité maximale pub async fn set_capacity(&self, max_size: Option) -> Result<()> { if !self.playlist.is_alive() { return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone())); } let mut core = self.playlist.core.write().await; core.set_capacity(max_size); drop(core); self.playlist.touch().await; if self.playlist.persistent { self.save_to_db().await?; } Ok(()) } /// Change le TTL par défaut pub async fn set_default_ttl(&self, ttl: Option) -> Result<()> { if !self.playlist.is_alive() { return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone())); } let mut core = self.playlist.core.write().await; core.set_default_ttl(ttl); drop(core); self.playlist.touch().await; if self.playlist.persistent { self.save_to_db().await?; } Ok(()) } /// Clone vers une nouvelle playlist persistante pub async fn clone_as_persistent(&self, new_id: String) -> Result { if !self.playlist.is_alive() { return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone())); } // Récupérer les données actuelles let title = self.playlist.title().await; let core = self.playlist.core.read().await; let config = core.config.clone(); let tracks = core.snapshot(); drop(core); // Créer la nouvelle playlist persistante let manager = crate::manager::PlaylistManager(); let new_handle = manager.create_persistent_playlist(new_id).await?; // Copier le titre et la config new_handle.set_title(title).await?; new_handle.set_capacity(config.max_size).await?; new_handle.set_default_ttl(config.default_ttl).await?; // Copier tous les morceaux let pks: Vec = tracks.iter().map(|r| r.cache_pk.clone()).collect(); new_handle.push_set(pks).await?; Ok(new_handle) } // Métadonnées pub fn id(&self) -> &str { &self.playlist.id } pub async fn title(&self) -> String { self.playlist.title().await } pub fn is_persistent(&self) -> bool { self.playlist.persistent } pub async fn capacity(&self) -> Option { let core = self.playlist.core.read().await; core.config.max_size } pub async fn default_ttl(&self) -> Option { let core = self.playlist.core.read().await; core.config.default_ttl } pub async fn len(&self) -> usize { let core = self.playlist.core.read().await; core.len() } pub async fn is_empty(&self) -> bool { let core = self.playlist.core.read().await; core.is_empty() } pub async fn last_change(&self) -> SystemTime { self.playlist.last_change().await } // Helpers internes async fn save_to_db(&self) -> Result<()> { let manager = crate::manager::PlaylistManager(); let persistence = manager .persistence() .ok_or_else(|| crate::Error::PersistenceError("No persistence manager".into()))?; let title = self.playlist.title().await; let core = self.playlist.core.read().await; let config = &core.config; let tracks = &core.tracks; persistence .save_playlist(&self.playlist.id, &title, config, tracks) .await } }