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(
91 &mut self,
92 key: &str,
93 value: Option<impl Into<Value<'static>>>,
94 ) -> Option<Value<'static>> {
95 self.set_value(key, value.map(|value| value.into()))
96 }
97
98 pub fn set_value(
105 &mut self,
106 key: &str,
107 value: Option<Value<'static>>,
108 ) -> Option<Value<'static>> {
109 if let Some(value) = value {
110 self.0.insert(key.into(), value)
111 } else {
112 self.0.remove(key)
113 }
114 }
115
116 pub fn trackid(&self) -> Option<TrackId> {
124 self.get_value("mpris:trackid")?.downcast_ref().ok()
125 }
126
127 pub fn set_trackid(&mut self, trackid: Option<impl Into<TrackId>>) {
135 self.set("mpris:trackid", trackid.map(|trackid| trackid.into()));
136 }
137
138 pub fn length(&self) -> Option<Time> {
140 self.get_value("mpris:length")?.downcast_ref().ok()
141 }
142
143 pub fn set_length(&mut self, length: Option<Time>) {
145 self.set("mpris:length", length);
146 }
147
148 pub fn art_url(&self) -> Option<Uri> {
153 self.get_value("mpris:artUrl")?.downcast_ref().ok()
154 }
155
156 pub fn set_art_url(&mut self, art_url: Option<impl Into<Uri>>) {
161 self.set("mpris:artUrl", art_url.map(|art_url| art_url.into()));
162 }
163
164 pub fn album(&self) -> Option<&str> {
166 self.get_value("xesam:album")?.downcast_ref().ok()
167 }
168
169 pub fn set_album(&mut self, album: Option<impl Into<String>>) {
171 self.set("xesam:album", album.map(|album| album.into()));
172 }
173
174 pub fn album_artist(&self) -> Option<Vec<String>> {
176 self.get_value("xesam:albumArtist")?
177 .try_clone()
178 .ok()
179 .and_then(|v| v.downcast().ok())
180 }
181
182 pub fn set_album_artist(
184 &mut self,
185 album_artist: Option<impl IntoIterator<Item = impl Into<String>>>,
186 ) {
187 self.set(
188 "xesam:albumArtist",
189 album_artist.map(|album_artist| {
190 album_artist
191 .into_iter()
192 .map(|i| i.into())
193 .collect::<Vec<_>>()
194 }),
195 );
196 }
197
198 pub fn artist(&self) -> Option<Vec<String>> {
200 self.get_value("xesam:artist")?
201 .try_clone()
202 .ok()
203 .and_then(|v| v.downcast().ok())
204 }
205
206 pub fn set_artist(&mut self, artist: Option<impl IntoIterator<Item = impl Into<String>>>) {
208 self.set(
209 "xesam:artist",
210 artist.map(|artist| artist.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
211 );
212 }
213
214 pub fn lyrics(&self) -> Option<&str> {
216 self.get_value("xesam:asText")?.downcast_ref().ok()
217 }
218
219 pub fn set_lyrics(&mut self, lyrics: Option<impl Into<String>>) {
221 self.set("xesam:asText", lyrics.map(|lyrics| lyrics.into()));
222 }
223
224 pub fn audio_bpm(&self) -> Option<i32> {
226 self.get_value("xesam:audioBPM")?.downcast_ref().ok()
227 }
228
229 pub fn set_audio_bpm(&mut self, audio_bpm: Option<i32>) {
231 self.set("xesam:audioBPM", audio_bpm);
232 }
233
234 pub fn auto_rating(&self) -> Option<f64> {
238 self.get_value("xesam:autoRating")?.downcast_ref().ok()
239 }
240
241 pub fn set_auto_rating(&mut self, auto_rating: Option<f64>) {
245 self.set("xesam:autoRating", auto_rating);
246 }
247
248 pub fn comment(&self) -> Option<Vec<String>> {
250 self.get_value("xesam:comment")?
251 .try_clone()
252 .ok()
253 .and_then(|v| v.downcast().ok())
254 }
255
256 pub fn set_comment(&mut self, comment: Option<impl IntoIterator<Item = impl Into<String>>>) {
258 self.set(
259 "xesam:comment",
260 comment.map(|comment| comment.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
261 );
262 }
263
264 pub fn composer(&self) -> Option<Vec<String>> {
266 self.get_value("xesam:composer")?
267 .try_clone()
268 .ok()
269 .and_then(|v| v.downcast().ok())
270 }
271
272 pub fn set_composer(&mut self, composer: Option<impl IntoIterator<Item = impl Into<String>>>) {
274 self.set(
275 "xesam:composer",
276 composer.map(|composer| composer.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
277 );
278 }
279
280 pub fn content_created(&self) -> Option<DateTime> {
283 self.get_value("xesam:contentCreated")?.downcast_ref().ok()
284 }
285
286 pub fn set_content_created(&mut self, content_created: Option<impl Into<DateTime>>) {
289 self.set(
290 "xesam:contentCreated",
291 content_created.map(|content_created| content_created.into()),
292 );
293 }
294
295 pub fn disc_number(&self) -> Option<i32> {
297 self.get_value("xesam:discNumber")?.downcast_ref().ok()
298 }
299
300 pub fn set_disc_number(&mut self, disc_number: Option<i32>) {
302 self.set("xesam:discNumber", disc_number);
303 }
304
305 pub fn first_used(&self) -> Option<DateTime> {
307 self.get_value("xesam:firstUsed")?.downcast_ref().ok()
308 }
309
310 pub fn set_first_used(&mut self, first_used: Option<impl Into<DateTime>>) {
312 self.set(
313 "xesam:firstUsed",
314 first_used.map(|first_used| first_used.into()),
315 );
316 }
317
318 pub fn genre(&self) -> Option<Vec<String>> {
320 self.get_value("xesam:genre")?
321 .try_clone()
322 .ok()
323 .and_then(|v| v.downcast().ok())
324 }
325
326 pub fn set_genre(&mut self, genre: Option<impl IntoIterator<Item = impl Into<String>>>) {
328 self.set(
329 "xesam:genre",
330 genre.map(|genre| genre.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
331 );
332 }
333
334 pub fn last_used(&self) -> Option<DateTime> {
336 self.get_value("xesam:lastUsed")?.downcast_ref().ok()
337 }
338
339 pub fn set_last_used(&mut self, last_used: Option<impl Into<DateTime>>) {
341 self.set(
342 "xesam:lastUsed",
343 last_used.map(|last_used| last_used.into()),
344 );
345 }
346
347 pub fn lyricist(&self) -> Option<Vec<String>> {
349 self.get_value("xesam:lyricist")?
350 .try_clone()
351 .ok()
352 .and_then(|v| v.downcast().ok())
353 }
354
355 pub fn set_lyricist(&mut self, lyricist: Option<impl IntoIterator<Item = impl Into<String>>>) {
357 self.set(
358 "xesam:lyricist",
359 lyricist.map(|lyricist| lyricist.into_iter().map(|i| i.into()).collect::<Vec<_>>()),
360 );
361 }
362
363 pub fn title(&self) -> Option<&str> {
365 self.get_value("xesam:title")?.downcast_ref().ok()
366 }
367
368 pub fn set_title(&mut self, title: Option<impl Into<String>>) {
370 self.set("xesam:title", title.map(|title| title.into()));
371 }
372
373 pub fn track_number(&self) -> Option<i32> {
375 self.get_value("xesam:trackNumber")?.downcast_ref().ok()
376 }
377
378 pub fn set_track_number(&mut self, track_number: Option<i32>) {
380 self.set("xesam:trackNumber", track_number);
381 }
382
383 pub fn url(&self) -> Option<Uri> {
385 self.get_value("xesam:url")?.downcast_ref().ok()
386 }
387
388 pub fn set_url(&mut self, url: Option<impl Into<Uri>>) {
390 self.set("xesam:url", url.map(|url| url.into()));
391 }
392
393 pub fn use_count(&self) -> Option<i32> {
395 self.get_value("xesam:useCount")?.downcast_ref().ok()
396 }
397
398 pub fn set_use_count(&mut self, use_count: Option<i32>) {
400 self.set("xesam:useCount", use_count);
401 }
402
403 pub fn user_rating(&self) -> Option<f64> {
405 self.get_value("xesam:userRating")?.downcast_ref().ok()
406 }
407
408 pub fn set_user_rating(&mut self, user_rating: Option<f64>) {
410 self.set("xesam:userRating", user_rating);
411 }
412}
413
414#[derive(Debug, Default, Clone)]
416#[must_use = "must call `build()` to finish building the metadata"]
417pub struct MetadataBuilder {
418 m: Metadata,
419}
420
421impl MetadataBuilder {
422 pub fn other(mut self, key: &str, value: impl Into<Value<'static>>) -> Self {
424 self.m.set(key, Some(value));
425 self
426 }
427
428 pub fn trackid(mut self, trackid: impl Into<TrackId>) -> Self {
436 self.m.set_trackid(Some(trackid));
437 self
438 }
439
440 pub fn length(mut self, length: Time) -> Self {
442 self.m.set_length(Some(length));
443 self
444 }
445
446 pub fn art_url(mut self, art_url: impl Into<Uri>) -> Self {
451 self.m.set_art_url(Some(art_url));
452 self
453 }
454
455 pub fn album(mut self, album: impl Into<String>) -> Self {
457 self.m.set_album(Some(album));
458 self
459 }
460
461 pub fn album_artist(
463 mut self,
464 album_artist: impl IntoIterator<Item = impl Into<String>>,
465 ) -> Self {
466 self.m.set_album_artist(Some(album_artist));
467 self
468 }
469
470 pub fn artist(mut self, artist: impl IntoIterator<Item = impl Into<String>>) -> Self {
472 self.m.set_artist(Some(artist));
473 self
474 }
475
476 pub fn lyrics(mut self, lyrics: impl Into<String>) -> Self {
478 self.m.set_lyrics(Some(lyrics));
479 self
480 }
481
482 pub fn audio_bpm(mut self, audio_bpm: i32) -> Self {
484 self.m.set_audio_bpm(Some(audio_bpm));
485 self
486 }
487
488 pub fn auto_rating(mut self, auto_rating: f64) -> Self {
492 self.m.set_auto_rating(Some(auto_rating));
493 self
494 }
495
496 pub fn comment(mut self, comment: impl IntoIterator<Item = impl Into<String>>) -> Self {
498 self.m.set_comment(Some(comment));
499 self
500 }
501
502 pub fn composer(mut self, composer: impl IntoIterator<Item = impl Into<String>>) -> Self {
504 self.m.set_composer(Some(composer));
505 self
506 }
507
508 pub fn content_created(mut self, content_created: impl Into<DateTime>) -> Self {
511 self.m.set_content_created(Some(content_created));
512 self
513 }
514
515 pub fn disc_number(mut self, disc_number: i32) -> Self {
517 self.m.set_disc_number(Some(disc_number));
518 self
519 }
520
521 pub fn first_used(mut self, first_used: impl Into<DateTime>) -> Self {
523 self.m.set_first_used(Some(first_used));
524 self
525 }
526
527 pub fn genre(mut self, genre: impl IntoIterator<Item = impl Into<String>>) -> Self {
529 self.m.set_genre(Some(genre));
530 self
531 }
532
533 pub fn last_used(mut self, last_used: impl Into<DateTime>) -> Self {
535 self.m.set_last_used(Some(last_used));
536 self
537 }
538
539 pub fn lyricist(mut self, lyricist: impl IntoIterator<Item = impl Into<String>>) -> Self {
541 self.m.set_lyricist(Some(lyricist));
542 self
543 }
544
545 pub fn title(mut self, title: impl Into<String>) -> Self {
547 self.m.set_title(Some(title));
548 self
549 }
550
551 pub fn track_number(mut self, track_number: i32) -> Self {
553 self.m.set_track_number(Some(track_number));
554 self
555 }
556
557 pub fn url(mut self, url: impl Into<Uri>) -> Self {
559 self.m.set_url(Some(url));
560 self
561 }
562
563 pub fn use_count(mut self, use_count: i32) -> Self {
565 self.m.set_use_count(Some(use_count));
566 self
567 }
568
569 pub fn user_rating(mut self, user_rating: f64) -> Self {
571 self.m.set_user_rating(Some(user_rating));
572 self
573 }
574
575 #[must_use = "building metadata is usually expensive and is not expected to have side effects"]
577 pub fn build(self) -> Metadata {
578 self.m
579 }
580}
581
582impl<'a> From<Metadata> for Value<'a> {
583 fn from(metainfo: Metadata) -> Self {
584 Value::new(metainfo.0)
585 }
586}
587
588#[cfg(test)]
589mod tests {
590 use zbus::zvariant::Str;
591
592 use super::*;
593
594 #[test]
595 fn clone() {
596 let original = Metadata::builder().trackid(TrackId::NO_TRACK).build();
597 assert_eq!(original, original.clone());
598 }
599
600 #[test]
601 fn builder_and_getter() {
602 let m = Metadata::builder()
603 .other("other", "value")
604 .trackid(TrackId::try_from("/io/github/seadve/Player/Track123").unwrap())
605 .length(Time::from_millis(2))
606 .art_url("file:///tmp/cover.jpg")
607 .album("The Album")
608 .album_artist(vec!["The Album Artist".to_string()])
609 .artist(vec!["The Artist".to_string()])
610 .lyrics("The lyrics")
611 .audio_bpm(120)
612 .auto_rating(0.5)
613 .comment(vec!["The comment".to_string()])
614 .composer(vec!["The Composer".to_string()])
615 .content_created("2021-01-01T00:00:00".to_string())
616 .disc_number(3)
617 .first_used("2021-01-01T00:00:00".to_string())
618 .genre(vec!["The Genre".to_string()])
619 .last_used("2021-01-01T00:00:00".to_string())
620 .lyricist(vec!["The Lyricist".to_string()])
621 .title("The Title")
622 .track_number(2)
623 .url("file:///tmp/track.mp3")
624 .use_count(1)
625 .user_rating(0.5)
626 .build();
627
628 assert_eq!(
629 m.get::<Str<'_>>("other"),
630 Some(Ok(&Str::from_static("value")))
631 );
632 assert_eq!(
633 m.trackid(),
634 Some(TrackId::try_from("/io/github/seadve/Player/Track123").unwrap())
635 );
636 assert_eq!(m.length(), Some(Time::from_millis(2)));
637 assert_eq!(m.art_url(), Some("file:///tmp/cover.jpg".into()));
638 assert_eq!(m.album(), Some("The Album"));
639 assert_eq!(m.album_artist(), Some(vec!["The Album Artist".to_string()]));
640 assert_eq!(m.artist(), Some(vec!["The Artist".to_string()]));
641 assert_eq!(m.lyrics(), Some("The lyrics"));
642 assert_eq!(m.audio_bpm(), Some(120));
643 assert_eq!(m.auto_rating(), Some(0.5));
644 assert_eq!(m.comment(), Some(vec!["The comment".to_string()]));
645 assert_eq!(m.composer(), Some(vec!["The Composer".to_string()]));
646 assert_eq!(m.content_created(), Some("2021-01-01T00:00:00".to_string()));
647 assert_eq!(m.disc_number(), Some(3));
648 assert_eq!(m.first_used(), Some("2021-01-01T00:00:00".to_string()));
649 assert_eq!(m.genre(), Some(vec!["The Genre".to_string()]));
650 assert_eq!(m.last_used(), Some("2021-01-01T00:00:00".to_string()));
651 assert_eq!(m.lyricist(), Some(vec!["The Lyricist".to_string()]));
652 assert_eq!(m.title(), Some("The Title"));
653 assert_eq!(m.track_number(), Some(2));
654 assert_eq!(m.url(), Some("file:///tmp/track.mp3".into()));
655 assert_eq!(m.use_count(), Some(1));
656 assert_eq!(m.user_rating(), Some(0.5));
657 }
658}