1use std::{collections::HashMap, fmt};
2
3use serde::Serialize;
4use zbus::zvariant::{Error, Result, Type, Value};
5
6use crate::{Time, TrackId, Uri};
7
8pub type DateTime = String;
18
19#[derive(PartialEq, Serialize, Type)]
33#[serde(transparent)]
34#[zvariant(signature = "a{sv}")]
35#[doc(alias = "Metadata_Map")]
36pub struct Metadata(HashMap<String, Value<'static>>);
37
38impl Clone for Metadata {
39 fn clone(&self) -> Self {
40 Self(
42 self.0
43 .iter()
44 .map(|(k, v)| (k.clone(), v.try_clone().expect("metadata contained an fd")))
45 .collect::<HashMap<_, _>>(),
46 )
47 }
48}
49
50impl fmt::Debug for Metadata {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 fmt::Debug::fmt(&self.0, f)
53 }
54}
55
56impl Default for Metadata {
57 fn default() -> Self {
58 Self::new()
59 }
60}
61
62impl Metadata {
63 pub fn new() -> Self {
65 Self(HashMap::new())
66 }
67
68 pub fn builder() -> MetadataBuilder {
70 MetadataBuilder { m: Metadata::new() }
71 }
72
73 pub fn get<'v, V>(&'v self, key: &str) -> Option<Result<&'v V>>
75 where
76 &'v V: TryFrom<&'v Value<'v>>,
77 <&'v V as TryFrom<&'v Value<'v>>>::Error: Into<Error>,
78 {
79 self.get_value(key).map(|v| v.downcast_ref())
80 }
81
82 pub fn get_value(&self, key: &str) -> Option<&Value<'_>> {
84 self.0.get(key)
85 }
86
87 pub fn set(
92 &mut self,
93 key: &str,
94 value: Option<impl Into<Value<'static>>>,
95 ) -> Option<Value<'static>> {
96 self.set_value(key, value.map(|value| value.into()))
97 }
98
99 pub fn set_value(
107 &mut self,
108 key: &str,
109 value: Option<Value<'static>>,
110 ) -> Option<Value<'static>> {
111 if let Some(value) = value {
112 self.0.insert(key.into(), value)
113 } else {
114 self.0.remove(key)
115 }
116 }
117
118 pub fn trackid(&self) -> Option<TrackId> {
126 self.get_value("mpris:trackid")?.downcast_ref().ok()
127 }
128
129 pub fn set_trackid(&mut self, trackid: Option<impl Into<TrackId>>) {
137 self.set("mpris:trackid", trackid.map(|trackid| trackid.into()));
138 }
139
140 pub fn length(&self) -> Option<Time> {
142 self.get_value("mpris:length")?.downcast_ref().ok()
143 }
144
145 pub fn set_length(&mut self, length: Option<Time>) {
147 self.set("mpris:length", length);
148 }
149
150 pub fn art_url(&self) -> Option<Uri> {
155 self.get_value("mpris:artUrl")?.downcast_ref().ok()
156 }
157
158 pub fn set_art_url(&mut self, art_url: Option<impl Into<Uri>>) {
163 self.set("mpris:artUrl", art_url.map(|art_url| art_url.into()));
164 }
165
166 pub fn album(&self) -> Option<&str> {
168 self.get_value("xesam:album")?.downcast_ref().ok()
169 }
170
171 pub fn set_album(&mut self, album: Option<impl Into<String>>) {
173 self.set("xesam:album", album.map(|album| album.into()));
174 }
175
176 pub fn album_artist(&self) -> Option<Vec<String>> {
178 self.get_value("xesam:albumArtist")?
179 .try_clone()
180 .ok()
181 .and_then(|v| v.downcast().ok())
182 }
183
184 pub fn set_album_artist(
186 &mut self,
187 album_artist: Option<impl IntoIterator<Item = impl Into<String>>>,
188 ) {
189 self.set(
190 "xesam:albumArtist",
191 album_artist.map(|album_artist| {
192 album_artist
193 .into_iter()
194 .map(|i| i.into())
195 .collect::<Vec<_>>()
196 }),
197 );
198 }
199
200 pub fn artist(&self) -> Option<Vec<String>> {
202 self.get_value("xesam:artist")?
203 .try_clone()
204 .ok()
205 .and_then(|v| v.downcast().ok())
206 }
207
208 pub fn set_artist(&mut self, artist: Option<impl IntoIterator<Item = impl Into<String>>>) {
210 self.set(
211 "xesam:artist",
212 artist.map(|artist| artist.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
213 );
214 }
215
216 pub fn lyrics(&self) -> Option<&str> {
218 self.get_value("xesam:asText")?.downcast_ref().ok()
219 }
220
221 pub fn set_lyrics(&mut self, lyrics: Option<impl Into<String>>) {
223 self.set("xesam:asText", lyrics.map(|lyrics| lyrics.into()));
224 }
225
226 pub fn audio_bpm(&self) -> Option<i32> {
228 self.get_value("xesam:audioBPM")?.downcast_ref().ok()
229 }
230
231 pub fn set_audio_bpm(&mut self, audio_bpm: Option<i32>) {
233 self.set("xesam:audioBPM", audio_bpm);
234 }
235
236 pub fn auto_rating(&self) -> Option<f64> {
240 self.get_value("xesam:autoRating")?.downcast_ref().ok()
241 }
242
243 pub fn set_auto_rating(&mut self, auto_rating: Option<f64>) {
247 self.set("xesam:autoRating", auto_rating);
248 }
249
250 pub fn comment(&self) -> Option<Vec<String>> {
252 self.get_value("xesam:comment")?
253 .try_clone()
254 .ok()
255 .and_then(|v| v.downcast().ok())
256 }
257
258 pub fn set_comment(&mut self, comment: Option<impl IntoIterator<Item = impl Into<String>>>) {
260 self.set(
261 "xesam:comment",
262 comment.map(|comment| comment.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
263 );
264 }
265
266 pub fn composer(&self) -> Option<Vec<String>> {
268 self.get_value("xesam:composer")?
269 .try_clone()
270 .ok()
271 .and_then(|v| v.downcast().ok())
272 }
273
274 pub fn set_composer(&mut self, composer: Option<impl IntoIterator<Item = impl Into<String>>>) {
276 self.set(
277 "xesam:composer",
278 composer.map(|composer| composer.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
279 );
280 }
281
282 pub fn content_created(&self) -> Option<DateTime> {
285 self.get_value("xesam:contentCreated")?.downcast_ref().ok()
286 }
287
288 pub fn set_content_created(&mut self, content_created: Option<impl Into<DateTime>>) {
291 self.set(
292 "xesam:contentCreated",
293 content_created.map(|content_created| content_created.into()),
294 );
295 }
296
297 pub fn disc_number(&self) -> Option<i32> {
299 self.get_value("xesam:discNumber")?.downcast_ref().ok()
300 }
301
302 pub fn set_disc_number(&mut self, disc_number: Option<i32>) {
304 self.set("xesam:discNumber", disc_number);
305 }
306
307 pub fn first_used(&self) -> Option<DateTime> {
309 self.get_value("xesam:firstUsed")?.downcast_ref().ok()
310 }
311
312 pub fn set_first_used(&mut self, first_used: Option<impl Into<DateTime>>) {
314 self.set(
315 "xesam:firstUsed",
316 first_used.map(|first_used| first_used.into()),
317 );
318 }
319
320 pub fn genre(&self) -> Option<Vec<String>> {
322 self.get_value("xesam:genre")?
323 .try_clone()
324 .ok()
325 .and_then(|v| v.downcast().ok())
326 }
327
328 pub fn set_genre(&mut self, genre: Option<impl IntoIterator<Item = impl Into<String>>>) {
330 self.set(
331 "xesam:genre",
332 genre.map(|genre| genre.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
333 );
334 }
335
336 pub fn last_used(&self) -> Option<DateTime> {
338 self.get_value("xesam:lastUsed")?.downcast_ref().ok()
339 }
340
341 pub fn set_last_used(&mut self, last_used: Option<impl Into<DateTime>>) {
343 self.set(
344 "xesam:lastUsed",
345 last_used.map(|last_used| last_used.into()),
346 );
347 }
348
349 pub fn lyricist(&self) -> Option<Vec<String>> {
351 self.get_value("xesam:lyricist")?
352 .try_clone()
353 .ok()
354 .and_then(|v| v.downcast().ok())
355 }
356
357 pub fn set_lyricist(&mut self, lyricist: Option<impl IntoIterator<Item = impl Into<String>>>) {
359 self.set(
360 "xesam:lyricist",
361 lyricist.map(|lyricist| lyricist.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
362 );
363 }
364
365 pub fn title(&self) -> Option<&str> {
367 self.get_value("xesam:title")?.downcast_ref().ok()
368 }
369
370 pub fn set_title(&mut self, title: Option<impl Into<String>>) {
372 self.set("xesam:title", title.map(|title| title.into()));
373 }
374
375 pub fn track_number(&self) -> Option<i32> {
377 self.get_value("xesam:trackNumber")?.downcast_ref().ok()
378 }
379
380 pub fn set_track_number(&mut self, track_number: Option<i32>) {
382 self.set("xesam:trackNumber", track_number);
383 }
384
385 pub fn url(&self) -> Option<Uri> {
387 self.get_value("xesam:url")?.downcast_ref().ok()
388 }
389
390 pub fn set_url(&mut self, url: Option<impl Into<Uri>>) {
392 self.set("xesam:url", url.map(|url| url.into()));
393 }
394
395 pub fn use_count(&self) -> Option<i32> {
397 self.get_value("xesam:useCount")?.downcast_ref().ok()
398 }
399
400 pub fn set_use_count(&mut self, use_count: Option<i32>) {
402 self.set("xesam:useCount", use_count);
403 }
404
405 pub fn user_rating(&self) -> Option<f64> {
407 self.get_value("xesam:userRating")?.downcast_ref().ok()
408 }
409
410 pub fn set_user_rating(&mut self, user_rating: Option<f64>) {
412 self.set("xesam:userRating", user_rating);
413 }
414}
415
416#[derive(Debug, Default, Clone)]
418#[must_use = "must call `build()` to finish building the metadata"]
419pub struct MetadataBuilder {
420 m: Metadata,
421}
422
423impl MetadataBuilder {
424 pub fn other(mut self, key: &str, value: impl Into<Value<'static>>) -> Self {
426 self.m.set(key, Some(value));
427 self
428 }
429
430 pub fn trackid(mut self, trackid: impl Into<TrackId>) -> Self {
438 self.m.set_trackid(Some(trackid));
439 self
440 }
441
442 pub fn length(mut self, length: Time) -> Self {
444 self.m.set_length(Some(length));
445 self
446 }
447
448 pub fn art_url(mut self, art_url: impl Into<Uri>) -> Self {
453 self.m.set_art_url(Some(art_url));
454 self
455 }
456
457 pub fn album(mut self, album: impl Into<String>) -> Self {
459 self.m.set_album(Some(album));
460 self
461 }
462
463 pub fn album_artist(
465 mut self,
466 album_artist: impl IntoIterator<Item = impl Into<String>>,
467 ) -> Self {
468 self.m.set_album_artist(Some(album_artist));
469 self
470 }
471
472 pub fn artist(mut self, artist: impl IntoIterator<Item = impl Into<String>>) -> Self {
474 self.m.set_artist(Some(artist));
475 self
476 }
477
478 pub fn lyrics(mut self, lyrics: impl Into<String>) -> Self {
480 self.m.set_lyrics(Some(lyrics));
481 self
482 }
483
484 pub fn audio_bpm(mut self, audio_bpm: i32) -> Self {
486 self.m.set_audio_bpm(Some(audio_bpm));
487 self
488 }
489
490 pub fn auto_rating(mut self, auto_rating: f64) -> Self {
494 self.m.set_auto_rating(Some(auto_rating));
495 self
496 }
497
498 pub fn comment(mut self, comment: impl IntoIterator<Item = impl Into<String>>) -> Self {
500 self.m.set_comment(Some(comment));
501 self
502 }
503
504 pub fn composer(mut self, composer: impl IntoIterator<Item = impl Into<String>>) -> Self {
506 self.m.set_composer(Some(composer));
507 self
508 }
509
510 pub fn content_created(mut self, content_created: impl Into<DateTime>) -> Self {
513 self.m.set_content_created(Some(content_created));
514 self
515 }
516
517 pub fn disc_number(mut self, disc_number: i32) -> Self {
519 self.m.set_disc_number(Some(disc_number));
520 self
521 }
522
523 pub fn first_used(mut self, first_used: impl Into<DateTime>) -> Self {
525 self.m.set_first_used(Some(first_used));
526 self
527 }
528
529 pub fn genre(mut self, genre: impl IntoIterator<Item = impl Into<String>>) -> Self {
531 self.m.set_genre(Some(genre));
532 self
533 }
534
535 pub fn last_used(mut self, last_used: impl Into<DateTime>) -> Self {
537 self.m.set_last_used(Some(last_used));
538 self
539 }
540
541 pub fn lyricist(mut self, lyricist: impl IntoIterator<Item = impl Into<String>>) -> Self {
543 self.m.set_lyricist(Some(lyricist));
544 self
545 }
546
547 pub fn title(mut self, title: impl Into<String>) -> Self {
549 self.m.set_title(Some(title));
550 self
551 }
552
553 pub fn track_number(mut self, track_number: i32) -> Self {
555 self.m.set_track_number(Some(track_number));
556 self
557 }
558
559 pub fn url(mut self, url: impl Into<Uri>) -> Self {
561 self.m.set_url(Some(url));
562 self
563 }
564
565 pub fn use_count(mut self, use_count: i32) -> Self {
567 self.m.set_use_count(Some(use_count));
568 self
569 }
570
571 pub fn user_rating(mut self, user_rating: f64) -> Self {
573 self.m.set_user_rating(Some(user_rating));
574 self
575 }
576
577 #[must_use = "building metadata is usually expensive and is not expected to have side effects"]
579 pub fn build(self) -> Metadata {
580 self.m
581 }
582}
583
584impl From<Metadata> for Value<'_> {
585 fn from(metainfo: Metadata) -> Self {
586 Value::new(metainfo.0)
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use zbus::zvariant::Str;
593
594 use super::*;
595
596 #[test]
597 fn clone() {
598 let original = Metadata::builder().trackid(TrackId::NO_TRACK).build();
599 assert_eq!(original, original.clone());
600 }
601
602 #[test]
603 fn builder_and_getter() {
604 let m = Metadata::builder()
605 .other("other", "value")
606 .trackid(TrackId::try_from("/io/github/seadve/Player/Track123").unwrap())
607 .length(Time::from_millis(2))
608 .art_url("file:///tmp/cover.jpg")
609 .album("The Album")
610 .album_artist(vec!["The Album Artist".to_string()])
611 .artist(vec!["The Artist".to_string()])
612 .lyrics("The lyrics")
613 .audio_bpm(120)
614 .auto_rating(0.5)
615 .comment(vec!["The comment".to_string()])
616 .composer(vec!["The Composer".to_string()])
617 .content_created("2021-01-01T00:00:00".to_string())
618 .disc_number(3)
619 .first_used("2021-01-01T00:00:00".to_string())
620 .genre(vec!["The Genre".to_string()])
621 .last_used("2021-01-01T00:00:00".to_string())
622 .lyricist(vec!["The Lyricist".to_string()])
623 .title("The Title")
624 .track_number(2)
625 .url("file:///tmp/track.mp3")
626 .use_count(1)
627 .user_rating(0.5)
628 .build();
629
630 assert_eq!(
631 m.get::<Str<'_>>("other"),
632 Some(Ok(&Str::from_static("value")))
633 );
634 assert_eq!(
635 m.trackid(),
636 Some(TrackId::try_from("/io/github/seadve/Player/Track123").unwrap())
637 );
638 assert_eq!(m.length(), Some(Time::from_millis(2)));
639 assert_eq!(m.art_url(), Some("file:///tmp/cover.jpg".into()));
640 assert_eq!(m.album(), Some("The Album"));
641 assert_eq!(m.album_artist(), Some(vec!["The Album Artist".to_string()]));
642 assert_eq!(m.artist(), Some(vec!["The Artist".to_string()]));
643 assert_eq!(m.lyrics(), Some("The lyrics"));
644 assert_eq!(m.audio_bpm(), Some(120));
645 assert_eq!(m.auto_rating(), Some(0.5));
646 assert_eq!(m.comment(), Some(vec!["The comment".to_string()]));
647 assert_eq!(m.composer(), Some(vec!["The Composer".to_string()]));
648 assert_eq!(m.content_created(), Some("2021-01-01T00:00:00".to_string()));
649 assert_eq!(m.disc_number(), Some(3));
650 assert_eq!(m.first_used(), Some("2021-01-01T00:00:00".to_string()));
651 assert_eq!(m.genre(), Some(vec!["The Genre".to_string()]));
652 assert_eq!(m.last_used(), Some("2021-01-01T00:00:00".to_string()));
653 assert_eq!(m.lyricist(), Some(vec!["The Lyricist".to_string()]));
654 assert_eq!(m.title(), Some("The Title"));
655 assert_eq!(m.track_number(), Some(2));
656 assert_eq!(m.url(), Some("file:///tmp/track.mp3".into()));
657 assert_eq!(m.use_count(), Some(1));
658 assert_eq!(m.user_rating(), Some(0.5));
659 }
660}