1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::{utils, Result};
use roxmltree::Node;

/// A [Track](struct.Track.html) with some metadata like the track number, its duration and the
/// elapsed time.
#[derive(Debug)]
pub struct TrackInfo {
    track: Track,
    metadata: String,
    track_no: u32,
    duration: u32,
    elapsed: u32,
}

#[allow(missing_docs)]
impl TrackInfo {
    pub(crate) fn new(
        track: Track,
        metadata: String,
        track_no: u32,
        duration: u32,
        elapsed: u32,
    ) -> Self {
        Self {
            track,
            metadata,
            track_no,
            duration,
            elapsed,
        }
    }

    pub fn track(&self) -> &Track {
        &self.track
    }
    pub fn metadata(&self) -> &str {
        &self.metadata
    }
    pub fn track_no(&self) -> u32 {
        self.track_no
    }
    pub fn duration(&self) -> u32 {
        self.duration
    }
    pub fn elapsed(&self) -> u32 {
        self.elapsed
    }
}

/// The track struct contains information about the music in UPnP music players.
/// It always has a title and an URI, but sometimes there is a creator, album or duration specified
/// too.
#[derive(Debug)]
pub struct Track {
    title: String,
    creator: Option<String>,
    album: Option<String>,
    duration: Option<u32>,
    uri: String,
}

#[allow(missing_docs)]
impl Track {
    pub fn title(&self) -> &str {
        &self.title
    }
    pub fn creator(&self) -> Option<&str> {
        self.creator.as_deref()
    }
    pub fn album(&self) -> Option<&str> {
        self.album.as_deref()
    }
    pub fn duration(&self) -> Option<u32> {
        self.duration
    }
    pub fn uri(&self) -> &str {
        &self.uri
    }
}

impl std::fmt::Display for Track {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", &self.title)?;
        if let Some(creator) = &self.creator {
            write!(f, " - {}", creator)?;
        }
        if let Some(album) = &self.album {
            write!(f, " ({})", album)?;
        }
        Ok(())
    }
}

impl Track {
    pub(crate) fn from_xml(node: Node<'_, '_>) -> Result<Self> {
        let mut title = None;
        let mut creator = None;
        let mut album = None;
        let mut res = None;

        for child in node.children() {
            match child.tag_name().name() {
                "title" => title = Some(child.text().unwrap_or_default().to_string()),
                "creator" => creator = Some(child.text().unwrap_or_default().to_string()),
                "album" => album = Some(child.text().unwrap_or_default().to_string()),
                "res" => res = Some(child),
                _ => (),
            }
        }

        let title = title.ok_or_else(|| {
            rupnp::Error::XmlMissingElement(node.tag_name().name().to_string(), "title".to_string())
        })?;
        let res = res.ok_or_else(|| {
            rupnp::Error::XmlMissingElement(node.tag_name().name().to_string(), "res".to_string())
        })?;
        let duration = res
            .attributes()
            .iter()
            .find(|a| a.name().eq_ignore_ascii_case("duration"))
            .map(|a| utils::seconds_from_str(a.value()))
            .transpose()?;

        let uri = res.text().unwrap_or_default().to_string();

        Ok(Self {
            title,
            creator,
            album,
            duration,
            uri,
        })
    }
}