use std::{collections::HashMap, fmt};
use serde::Serialize;
use zbus::zvariant::{Error, Result, Type, Value};
use crate::{Time, TrackId, Uri};
pub type DateTime = String;
#[derive(PartialEq, Serialize, Type)]
#[serde(transparent)]
#[zvariant(signature = "a{sv}")]
#[doc(alias = "Metadata_Map")]
pub struct Metadata(HashMap<String, Value<'static>>);
impl Clone for Metadata {
fn clone(&self) -> Self {
Self(
self.0
.iter()
.map(|(k, v)| (k.clone(), v.try_clone().expect("metadata contained an fd")))
.collect::<HashMap<_, _>>(),
)
}
}
impl fmt::Debug for Metadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl Default for Metadata {
fn default() -> Self {
Self::new()
}
}
impl Metadata {
pub fn new() -> Self {
Self(HashMap::new())
}
pub fn builder() -> MetadataBuilder {
MetadataBuilder { m: Metadata::new() }
}
pub fn get<'v, V>(&'v self, key: &str) -> Option<Result<&'v V>>
where
&'v V: TryFrom<&'v Value<'v>>,
<&'v V as TryFrom<&'v Value<'v>>>::Error: Into<Error>,
{
self.get_value(key).map(|v| v.downcast_ref())
}
pub fn get_value(&self, key: &str) -> Option<&Value<'_>> {
self.0.get(key)
}
pub fn set(
&mut self,
key: &str,
value: Option<impl Into<Value<'static>>>,
) -> Option<Value<'static>> {
self.set_value(key, value.map(|value| value.into()))
}
pub fn set_value(
&mut self,
key: &str,
value: Option<Value<'static>>,
) -> Option<Value<'static>> {
if let Some(value) = value {
self.0.insert(key.into(), value)
} else {
self.0.remove(key)
}
}
pub fn trackid(&self) -> Option<TrackId> {
self.get_value("mpris:trackid")?.downcast_ref().ok()
}
pub fn set_trackid(&mut self, trackid: Option<impl Into<TrackId>>) {
self.set("mpris:trackid", trackid.map(|trackid| trackid.into()));
}
pub fn length(&self) -> Option<Time> {
self.get_value("mpris:length")?.downcast_ref().ok()
}
pub fn set_length(&mut self, length: Option<Time>) {
self.set("mpris:length", length);
}
pub fn art_url(&self) -> Option<Uri> {
self.get_value("mpris:artUrl")?.downcast_ref().ok()
}
pub fn set_art_url(&mut self, art_url: Option<impl Into<Uri>>) {
self.set("mpris:artUrl", art_url.map(|art_url| art_url.into()));
}
pub fn album(&self) -> Option<&str> {
self.get_value("xesam:album")?.downcast_ref().ok()
}
pub fn set_album(&mut self, album: Option<impl Into<String>>) {
self.set("xesam:album", album.map(|album| album.into()));
}
pub fn album_artist(&self) -> Option<Vec<String>> {
self.get_value("xesam:albumArtist")?
.try_clone()
.ok()
.and_then(|v| v.downcast().ok())
}
pub fn set_album_artist(
&mut self,
album_artist: Option<impl IntoIterator<Item = impl Into<String>>>,
) {
self.set(
"xesam:albumArtist",
album_artist.map(|album_artist| {
album_artist
.into_iter()
.map(|i| i.into())
.collect::<Vec<_>>()
}),
);
}
pub fn artist(&self) -> Option<Vec<String>> {
self.get_value("xesam:artist")?
.try_clone()
.ok()
.and_then(|v| v.downcast().ok())
}
pub fn set_artist(&mut self, artist: Option<impl IntoIterator<Item = impl Into<String>>>) {
self.set(
"xesam:artist",
artist.map(|artist| artist.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
);
}
pub fn lyrics(&self) -> Option<&str> {
self.get_value("xesam:asText")?.downcast_ref().ok()
}
pub fn set_lyrics(&mut self, lyrics: Option<impl Into<String>>) {
self.set("xesam:asText", lyrics.map(|lyrics| lyrics.into()));
}
pub fn audio_bpm(&self) -> Option<i32> {
self.get_value("xesam:audioBPM")?.downcast_ref().ok()
}
pub fn set_audio_bpm(&mut self, audio_bpm: Option<i32>) {
self.set("xesam:audioBPM", audio_bpm);
}
pub fn auto_rating(&self) -> Option<f64> {
self.get_value("xesam:autoRating")?.downcast_ref().ok()
}
pub fn set_auto_rating(&mut self, auto_rating: Option<f64>) {
self.set("xesam:autoRating", auto_rating);
}
pub fn comment(&self) -> Option<Vec<String>> {
self.get_value("xesam:comment")?
.try_clone()
.ok()
.and_then(|v| v.downcast().ok())
}
pub fn set_comment(&mut self, comment: Option<impl IntoIterator<Item = impl Into<String>>>) {
self.set(
"xesam:comment",
comment.map(|comment| comment.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
);
}
pub fn composer(&self) -> Option<Vec<String>> {
self.get_value("xesam:composer")?
.try_clone()
.ok()
.and_then(|v| v.downcast().ok())
}
pub fn set_composer(&mut self, composer: Option<impl IntoIterator<Item = impl Into<String>>>) {
self.set(
"xesam:composer",
composer.map(|composer| composer.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
);
}
pub fn content_created(&self) -> Option<DateTime> {
self.get_value("xesam:contentCreated")?.downcast_ref().ok()
}
pub fn set_content_created(&mut self, content_created: Option<impl Into<DateTime>>) {
self.set(
"xesam:contentCreated",
content_created.map(|content_created| content_created.into()),
);
}
pub fn disc_number(&self) -> Option<i32> {
self.get_value("xesam:discNumber")?.downcast_ref().ok()
}
pub fn set_disc_number(&mut self, disc_number: Option<i32>) {
self.set("xesam:discNumber", disc_number);
}
pub fn first_used(&self) -> Option<DateTime> {
self.get_value("xesam:firstUsed")?.downcast_ref().ok()
}
pub fn set_first_used(&mut self, first_used: Option<impl Into<DateTime>>) {
self.set(
"xesam:firstUsed",
first_used.map(|first_used| first_used.into()),
);
}
pub fn genre(&self) -> Option<Vec<String>> {
self.get_value("xesam:genre")?
.try_clone()
.ok()
.and_then(|v| v.downcast().ok())
}
pub fn set_genre(&mut self, genre: Option<impl IntoIterator<Item = impl Into<String>>>) {
self.set(
"xesam:genre",
genre.map(|genre| genre.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
);
}
pub fn last_used(&self) -> Option<DateTime> {
self.get_value("xesam:lastUsed")?.downcast_ref().ok()
}
pub fn set_last_used(&mut self, last_used: Option<impl Into<DateTime>>) {
self.set(
"xesam:lastUsed",
last_used.map(|last_used| last_used.into()),
);
}
pub fn lyricist(&self) -> Option<Vec<String>> {
self.get_value("xesam:lyricist")?
.try_clone()
.ok()
.and_then(|v| v.downcast().ok())
}
pub fn set_lyricist(&mut self, lyricist: Option<impl IntoIterator<Item = impl Into<String>>>) {
self.set(
"xesam:lyricist",
lyricist.map(|lyricist| lyricist.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
);
}
pub fn title(&self) -> Option<&str> {
self.get_value("xesam:title")?.downcast_ref().ok()
}
pub fn set_title(&mut self, title: Option<impl Into<String>>) {
self.set("xesam:title", title.map(|title| title.into()));
}
pub fn track_number(&self) -> Option<i32> {
self.get_value("xesam:trackNumber")?.downcast_ref().ok()
}
pub fn set_track_number(&mut self, track_number: Option<i32>) {
self.set("xesam:trackNumber", track_number);
}
pub fn url(&self) -> Option<Uri> {
self.get_value("xesam:url")?.downcast_ref().ok()
}
pub fn set_url(&mut self, url: Option<impl Into<Uri>>) {
self.set("xesam:url", url.map(|url| url.into()));
}
pub fn use_count(&self) -> Option<i32> {
self.get_value("xesam:useCount")?.downcast_ref().ok()
}
pub fn set_use_count(&mut self, use_count: Option<i32>) {
self.set("xesam:useCount", use_count);
}
pub fn user_rating(&self) -> Option<f64> {
self.get_value("xesam:userRating")?.downcast_ref().ok()
}
pub fn set_user_rating(&mut self, user_rating: Option<f64>) {
self.set("xesam:userRating", user_rating);
}
}
#[derive(Debug, Default, Clone)]
#[must_use = "must call `build()` to finish building the metadata"]
pub struct MetadataBuilder {
m: Metadata,
}
impl MetadataBuilder {
pub fn other(mut self, key: &str, value: impl Into<Value<'static>>) -> Self {
self.m.set(key, Some(value));
self
}
pub fn trackid(mut self, trackid: impl Into<TrackId>) -> Self {
self.m.set_trackid(Some(trackid));
self
}
pub fn length(mut self, length: Time) -> Self {
self.m.set_length(Some(length));
self
}
pub fn art_url(mut self, art_url: impl Into<Uri>) -> Self {
self.m.set_art_url(Some(art_url));
self
}
pub fn album(mut self, album: impl Into<String>) -> Self {
self.m.set_album(Some(album));
self
}
pub fn album_artist(
mut self,
album_artist: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
self.m.set_album_artist(Some(album_artist));
self
}
pub fn artist(mut self, artist: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.m.set_artist(Some(artist));
self
}
pub fn lyrics(mut self, lyrics: impl Into<String>) -> Self {
self.m.set_lyrics(Some(lyrics));
self
}
pub fn audio_bpm(mut self, audio_bpm: i32) -> Self {
self.m.set_audio_bpm(Some(audio_bpm));
self
}
pub fn auto_rating(mut self, auto_rating: f64) -> Self {
self.m.set_auto_rating(Some(auto_rating));
self
}
pub fn comment(mut self, comment: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.m.set_comment(Some(comment));
self
}
pub fn composer(mut self, composer: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.m.set_composer(Some(composer));
self
}
pub fn content_created(mut self, content_created: impl Into<DateTime>) -> Self {
self.m.set_content_created(Some(content_created));
self
}
pub fn disc_number(mut self, disc_number: i32) -> Self {
self.m.set_disc_number(Some(disc_number));
self
}
pub fn first_used(mut self, first_used: impl Into<DateTime>) -> Self {
self.m.set_first_used(Some(first_used));
self
}
pub fn genre(mut self, genre: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.m.set_genre(Some(genre));
self
}
pub fn last_used(mut self, last_used: impl Into<DateTime>) -> Self {
self.m.set_last_used(Some(last_used));
self
}
pub fn lyricist(mut self, lyricist: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.m.set_lyricist(Some(lyricist));
self
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.m.set_title(Some(title));
self
}
pub fn track_number(mut self, track_number: i32) -> Self {
self.m.set_track_number(Some(track_number));
self
}
pub fn url(mut self, url: impl Into<Uri>) -> Self {
self.m.set_url(Some(url));
self
}
pub fn use_count(mut self, use_count: i32) -> Self {
self.m.set_use_count(Some(use_count));
self
}
pub fn user_rating(mut self, user_rating: f64) -> Self {
self.m.set_user_rating(Some(user_rating));
self
}
#[must_use = "building metadata is usually expensive and is not expected to have side effects"]
pub fn build(self) -> Metadata {
self.m
}
}
impl<'a> From<Metadata> for Value<'a> {
fn from(metainfo: Metadata) -> Self {
Value::new(metainfo.0)
}
}
#[cfg(test)]
mod tests {
use zbus::zvariant::Str;
use super::*;
#[test]
fn clone() {
let original = Metadata::builder().trackid(TrackId::NO_TRACK).build();
assert_eq!(original, original.clone());
}
#[test]
fn builder_and_getter() {
let m = Metadata::builder()
.other("other", "value")
.trackid(TrackId::try_from("/io/github/seadve/Player/Track123").unwrap())
.length(Time::from_millis(2))
.art_url("file:///tmp/cover.jpg")
.album("The Album")
.album_artist(vec!["The Album Artist".to_string()])
.artist(vec!["The Artist".to_string()])
.lyrics("The lyrics")
.audio_bpm(120)
.auto_rating(0.5)
.comment(vec!["The comment".to_string()])
.composer(vec!["The Composer".to_string()])
.content_created("2021-01-01T00:00:00".to_string())
.disc_number(3)
.first_used("2021-01-01T00:00:00".to_string())
.genre(vec!["The Genre".to_string()])
.last_used("2021-01-01T00:00:00".to_string())
.lyricist(vec!["The Lyricist".to_string()])
.title("The Title")
.track_number(2)
.url("file:///tmp/track.mp3")
.use_count(1)
.user_rating(0.5)
.build();
assert_eq!(
m.get::<Str<'_>>("other"),
Some(Ok(&Str::from_static("value")))
);
assert_eq!(
m.trackid(),
Some(TrackId::try_from("/io/github/seadve/Player/Track123").unwrap())
);
assert_eq!(m.length(), Some(Time::from_millis(2)));
assert_eq!(m.art_url(), Some("file:///tmp/cover.jpg".into()));
assert_eq!(m.album(), Some("The Album"));
assert_eq!(m.album_artist(), Some(vec!["The Album Artist".to_string()]));
assert_eq!(m.artist(), Some(vec!["The Artist".to_string()]));
assert_eq!(m.lyrics(), Some("The lyrics"));
assert_eq!(m.audio_bpm(), Some(120));
assert_eq!(m.auto_rating(), Some(0.5));
assert_eq!(m.comment(), Some(vec!["The comment".to_string()]));
assert_eq!(m.composer(), Some(vec!["The Composer".to_string()]));
assert_eq!(m.content_created(), Some("2021-01-01T00:00:00".to_string()));
assert_eq!(m.disc_number(), Some(3));
assert_eq!(m.first_used(), Some("2021-01-01T00:00:00".to_string()));
assert_eq!(m.genre(), Some(vec!["The Genre".to_string()]));
assert_eq!(m.last_used(), Some("2021-01-01T00:00:00".to_string()));
assert_eq!(m.lyricist(), Some(vec!["The Lyricist".to_string()]));
assert_eq!(m.title(), Some("The Title"));
assert_eq!(m.track_number(), Some(2));
assert_eq!(m.url(), Some("file:///tmp/track.mp3".into()));
assert_eq!(m.use_count(), Some(1));
assert_eq!(m.user_rating(), Some(0.5));
}
}