//! Minimal metadata abstraction shared between PMO crates. #![allow(async_fn_in_trait)] use std::{ collections::HashMap, time::{Duration, SystemTime}, }; use tokio::sync::RwLock; use std::sync::Arc; use async_trait::async_trait; /// Convenience alias for metadata operations that can fail or return no value. pub type MetadataResult = Result, MetadataError>; /// Errors that can occur when manipulating metadata. #[derive(Debug, thiserror::Error)] pub enum MetadataError { #[error("metadata field is not implemented")] NotImplemented, #[error("metadata field is read-only")] ReadOnly, #[error("backend error: {0}")] Backend(String), } impl MetadataError { fn is_transient(&self) -> bool { matches!( self, MetadataError::NotImplemented | MetadataError::ReadOnly ) } } /// Trait implemented by metadata providers. /// /// Only the fields supported by the implementation have to be overridden. #[async_trait] pub trait TrackMetadata: Send + Sync { async fn get_title(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_title(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_artist(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_artist(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_album(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_album(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_year(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_year(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_duration(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_duration(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_track_id(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_track_id(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_channel_id(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_channel_id(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_event(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_event(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_rating(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_rating(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_cover_url(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_cover_url(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_cover_pk(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn set_cover_pk(&mut self, _value: Option) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_extra(&self) -> MetadataResult> { Err(MetadataError::NotImplemented) } async fn set_extra(&mut self, _value: Option>) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } async fn get_updated_at(&self) -> MetadataResult { Err(MetadataError::NotImplemented) } async fn touch(&mut self) -> MetadataResult<()> { Err(MetadataError::NotImplemented) } } pub async fn copy_metadata_into( src: &Arc>, dest: &Arc>, ) -> Result<(), MetadataError> where S: TrackMetadata + ?Sized, D: TrackMetadata + ?Sized, { let src_guard = src.read().await; // Expansion de copy_metadata! pour toutes les propriétés match src_guard.get_title().await { Ok(Some(value)) => { dest.write().await.set_title(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_title(None).await.ok(); } } match src_guard.get_artist().await { Ok(Some(value)) => { dest.write().await.set_artist(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_artist(None).await.ok(); } } match src_guard.get_album().await { Ok(Some(value)) => { dest.write().await.set_album(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_album(None).await.ok(); } } match src_guard.get_year().await { Ok(Some(value)) => { dest.write().await.set_year(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_year(None).await.ok(); } } match src_guard.get_duration().await { Ok(Some(value)) => { dest.write().await.set_duration(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_duration(None).await.ok(); } } match src_guard.get_track_id().await { Ok(Some(value)) => { dest.write().await.set_track_id(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_track_id(None).await.ok(); } } match src_guard.get_channel_id().await { Ok(Some(value)) => { dest.write().await.set_channel_id(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_channel_id(None).await.ok(); } } match src_guard.get_event().await { Ok(Some(value)) => { dest.write().await.set_event(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_event(None).await.ok(); } } match src_guard.get_rating().await { Ok(Some(value)) => { dest.write().await.set_rating(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_rating(None).await.ok(); } } match src_guard.get_cover_url().await { Ok(Some(value)) => { dest.write().await.set_cover_url(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_cover_url(None).await.ok(); } } match src_guard.get_cover_pk().await { Ok(Some(value)) => { dest.write().await.set_cover_pk(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_cover_pk(None).await.ok(); } } match src_guard.get_extra().await { Ok(Some(value)) => { dest.write().await.set_extra(Some(value)).await?; }, Ok(None) | Err(_) => { dest.write().await.set_extra(None).await.ok(); } } dest.write().await.touch().await?; Ok(()) } /// In-memory metadata implementation with full read/write support. #[derive(Clone, Debug, Default, PartialEq)] pub struct MemoryTrackMetadata { title: Option, artist: Option, album: Option, year: Option, duration: Option, elapsed: Option, track_id: Option, channel_id: Option, event: Option, rating: Option, cover_url: Option, cover_pk: Option, updated_at: Option, extra: Option>, } impl MemoryTrackMetadata { pub fn new() -> Self { Self::default() } } #[async_trait] impl TrackMetadata for MemoryTrackMetadata { async fn get_title(&self) -> MetadataResult { Ok(self.title.clone()) } async fn set_title(&mut self, value: Option) -> MetadataResult<()> { self.title = value; self.touch().await?; Ok(Some(())) } async fn get_artist(&self) -> MetadataResult { Ok(self.artist.clone()) } async fn set_artist(&mut self, value: Option) -> MetadataResult<()> { self.artist = value; self.touch().await?; Ok(Some(())) } async fn get_album(&self) -> MetadataResult { Ok(self.album.clone()) } async fn set_album(&mut self, value: Option) -> MetadataResult<()> { self.album = value; self.touch().await?; Ok(Some(())) } async fn get_year(&self) -> MetadataResult { Ok(self.year) } async fn set_year(&mut self, value: Option) -> MetadataResult<()> { self.year = value; self.touch().await?; Ok(Some(())) } async fn get_duration(&self) -> MetadataResult { Ok(self.duration) } async fn set_duration(&mut self, value: Option) -> MetadataResult<()> { self.duration = value; self.touch().await?; Ok(Some(())) } async fn get_track_id(&self) -> MetadataResult { Ok(self.track_id.clone()) } async fn set_track_id(&mut self, value: Option) -> MetadataResult<()> { self.track_id = value; self.touch().await?; Ok(Some(())) } async fn get_channel_id(&self) -> MetadataResult { Ok(self.channel_id.clone()) } async fn set_channel_id(&mut self, value: Option) -> MetadataResult<()> { self.channel_id = value; self.touch().await?; Ok(Some(())) } async fn get_event(&self) -> MetadataResult { Ok(self.event.clone()) } async fn set_event(&mut self, value: Option) -> MetadataResult<()> { self.event = value; self.touch().await?; Ok(Some(())) } async fn get_rating(&self) -> MetadataResult { Ok(self.rating) } async fn set_rating(&mut self, value: Option) -> MetadataResult<()> { self.rating = value; self.touch().await?; Ok(Some(())) } async fn get_cover_url(&self) -> MetadataResult { Ok(self.cover_url.clone()) } async fn set_cover_url(&mut self, value: Option) -> MetadataResult<()> { self.cover_url = value; self.touch().await?; Ok(Some(())) } async fn get_cover_pk(&self) -> MetadataResult { Ok(self.cover_pk.clone()) } async fn set_cover_pk(&mut self, value: Option) -> MetadataResult<()> { self.cover_pk = value; self.touch().await?; Ok(Some(())) } async fn get_extra(&self) -> MetadataResult> { Ok(self.extra.clone()) } async fn set_extra(&mut self, value: Option>) -> MetadataResult<()> { self.extra = value; self.touch().await?; Ok(Some(())) } async fn get_updated_at(&self) -> MetadataResult { Ok(self.updated_at) } async fn touch(&mut self) -> MetadataResult<()> { self.updated_at = Some(SystemTime::now()); Ok(Some(())) } }