Refactoring de pmoflac -factorisation de code ogg et opus

This commit is contained in:
2025-10-27 22:06:24 +01:00
parent e82979561a
commit 5b60fdbe1d
24 changed files with 751 additions and 793 deletions

View File

@@ -22,7 +22,7 @@ impl ReadHandle {
cursor: AtomicUsize::new(0),
}
}
/// Pop le prochain morceau (avance le curseur)
///
/// Skip automatiquement les entrées invalides dans le cache.
@@ -31,24 +31,24 @@ impl ReadHandle {
if !self.playlist.is_alive() {
return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone()));
}
let pos = self.cursor.load(Ordering::SeqCst);
let core = self.playlist.core.read().await;
// Fin de playlist ?
if pos >= core.len() {
return Ok(None);
}
let record = match core.get(pos) {
Some(r) => r,
None => return Ok(None),
};
let cache_pk = record.cache_pk.clone();
drop(core);
// Vérifier validité dans le cache
let cache = crate::manager::audio_cache()?;
if cache.is_valid_pk(&cache_pk) {
@@ -61,31 +61,33 @@ impl ReadHandle {
let mut core = self.playlist.core.write().await;
core.remove_by_cache_pk(&cache_pk);
drop(core);
// Sauvegarder si persistante
if self.playlist.persistent {
if let Some(persistence) = crate::manager::PlaylistManager().persistence() {
let title = self.playlist.title().await;
let core = self.playlist.core.read().await;
let _ = persistence.save_playlist(&self.playlist.id, &title, &core.config, &core.tracks).await;
let _ = persistence
.save_playlist(&self.playlist.id, &title, &core.config, &core.tracks)
.await;
}
}
// Ne pas avancer le curseur, continuer avec la position actuelle
continue;
}
}
}
/// Peek le prochain morceau sans avancer le curseur
pub async fn peek(&self) -> Result<Option<PlaylistTrack>> {
if !self.playlist.is_alive() {
return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone()));
}
let pos = self.cursor.load(Ordering::SeqCst);
let core = self.playlist.core.read().await;
match core.get(pos) {
Some(record) => {
// Vérifier validité
@@ -99,28 +101,28 @@ impl ReadHandle {
None => Ok(None),
}
}
/// Position actuelle du curseur
pub fn position(&self) -> usize {
self.cursor.load(Ordering::SeqCst)
}
/// Nombre de morceaux restants (compte uniquement les valides)
pub async fn remaining(&self) -> Result<usize> {
if !self.playlist.is_alive() {
return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone()));
}
let pos = self.cursor.load(Ordering::SeqCst);
let core = self.playlist.core.read().await;
if pos >= core.len() {
return Ok(0);
}
let cache = crate::manager::audio_cache()?;
let mut count = 0;
for i in pos..core.len() {
if let Some(record) = core.get(i) {
if cache.is_valid_pk(&record.cache_pk) {
@@ -128,38 +130,38 @@ impl ReadHandle {
}
}
}
Ok(count)
}
/// Crée un nouveau handle avec cursor à 0
pub fn get_new_handle(&self) -> Result<ReadHandle> {
if !self.playlist.is_alive() {
return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone()));
}
Ok(ReadHandle::new(self.playlist.clone()))
}
/// Vérifie si la playlist est vivante
pub fn is_alive(&self) -> bool {
self.playlist.is_alive()
}
/// ID de la playlist
pub fn id(&self) -> &str {
&self.playlist.id
}
/// Génère un Container DIDL-Lite
pub async fn to_container(&self) -> Result<Container> {
if !self.playlist.is_alive() {
return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone()));
}
let title = self.playlist.title().await;
let remaining = self.remaining().await?;
Ok(Container {
id: self.playlist.id.clone(),
parent_id: "0".to_string(),
@@ -172,48 +174,48 @@ impl ReadHandle {
items: vec![],
})
}
/// Génère des Items DIDL-Lite depuis la position actuelle
pub async fn to_items(&self, limit: usize) -> Result<Vec<Item>> {
if !self.playlist.is_alive() {
return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone()));
}
let pos = self.cursor.load(Ordering::SeqCst);
let core = self.playlist.core.read().await;
let cache = crate::manager::audio_cache()?;
// Récupérer base_url depuis le cache (via route_for)
let mut items = Vec::new();
let mut idx = 0;
for i in pos..core.len() {
if items.len() >= limit {
break;
}
let record = match core.get(i) {
Some(r) => r,
None => continue,
};
// Vérifier validité
if !cache.is_valid_pk(&record.cache_pk) {
continue;
}
// Charger métadonnées
let metadata = match pmoaudiocache::get_metadata(&*cache, &record.cache_pk) {
Ok(m) => m,
Err(_) => continue,
};
// Construire l'URL via route_for
let url = cache.route_for(&record.cache_pk, None);
// Créer le Resource DIDL
let resource = metadata.to_didl_resource(url);
// Créer l'Item
let item = Item {
id: format!("{}:{}", self.playlist.id, pos + idx),
@@ -232,11 +234,11 @@ impl ReadHandle {
resources: vec![resource],
descriptions: vec![],
};
items.push(item);
idx += 1;
}
Ok(items)
}
}

View File

@@ -22,41 +22,41 @@ impl WriteHandle {
_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<String>) -> 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 {
@@ -64,195 +64,194 @@ impl WriteHandle {
return Err(crate::Error::CacheEntryNotFound(pk.clone()));
}
}
// Créer tous les records
let records: Vec<Record> = cache_pks.into_iter()
.map(Record::new)
.collect();
let records: Vec<Record> = 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<usize>) -> 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<Duration>) -> 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<WriteHandle> {
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 mut 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<String> = tracks.iter()
.map(|r| r.cache_pk.clone())
.collect();
let pks: Vec<String> = 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<usize> {
let core = self.playlist.core.read().await;
core.config.max_size
}
pub async fn default_ttl(&self) -> Option<Duration> {
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()
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
persistence
.save_playlist(&self.playlist.id, &title, config, tracks)
.await
}
}