mpdclient 0.2.0

Rust interface to MPD using libmpdclient
Documentation
use std::ffi::{CStr, CString};

use mpdclient_sys::{
    mpd_recv_pair_tag, mpd_return_pair, mpd_tag_name, mpd_tag_name_iparse,
    mpd_tag_type_MPD_TAG_ALBUM, mpd_tag_type_MPD_TAG_ALBUM_ARTIST,
    mpd_tag_type_MPD_TAG_ALBUM_ARTIST_SORT, mpd_tag_type_MPD_TAG_ALBUM_SORT,
    mpd_tag_type_MPD_TAG_ARTIST, mpd_tag_type_MPD_TAG_ARTIST_SORT, mpd_tag_type_MPD_TAG_COMMENT,
    mpd_tag_type_MPD_TAG_COMPOSER, mpd_tag_type_MPD_TAG_COMPOSER_SORT,
    mpd_tag_type_MPD_TAG_CONDUCTOR, mpd_tag_type_MPD_TAG_DATE, mpd_tag_type_MPD_TAG_DISC,
    mpd_tag_type_MPD_TAG_ENSEMBLE, mpd_tag_type_MPD_TAG_GENRE, mpd_tag_type_MPD_TAG_GROUPING,
    mpd_tag_type_MPD_TAG_LABEL, mpd_tag_type_MPD_TAG_LOCATION, mpd_tag_type_MPD_TAG_MOOD,
    mpd_tag_type_MPD_TAG_MOVEMENT, mpd_tag_type_MPD_TAG_MOVEMENTNUMBER,
    mpd_tag_type_MPD_TAG_MUSICBRAINZ_ALBUMARTISTID, mpd_tag_type_MPD_TAG_MUSICBRAINZ_ALBUMID,
    mpd_tag_type_MPD_TAG_MUSICBRAINZ_ARTISTID, mpd_tag_type_MPD_TAG_MUSICBRAINZ_RELEASEGROUPID,
    mpd_tag_type_MPD_TAG_MUSICBRAINZ_RELEASETRACKID, mpd_tag_type_MPD_TAG_MUSICBRAINZ_TRACKID,
    mpd_tag_type_MPD_TAG_MUSICBRAINZ_WORKID, mpd_tag_type_MPD_TAG_NAME,
    mpd_tag_type_MPD_TAG_ORIGINAL_DATE, mpd_tag_type_MPD_TAG_PERFORMER, mpd_tag_type_MPD_TAG_TITLE,
    mpd_tag_type_MPD_TAG_TITLE_SORT, mpd_tag_type_MPD_TAG_TRACK, mpd_tag_type_MPD_TAG_UNKNOWN,
    mpd_tag_type_MPD_TAG_WORK,
};

use crate::{
    Connection,
    error::{Error, Result},
};

/// Supported tags for music files by MPD.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tag {
    /// Generic artist name.
    ///
    /// Exact meaning not well defined.
    Artist = mpd_tag_type_MPD_TAG_ARTIST as isize,

    /// Album name
    Album = mpd_tag_type_MPD_TAG_ALBUM as isize,

    /// For albums with multiple artists, who should be used for the whole album.
    ///
    /// Exact meaning not well defined.
    AlbumArtist = mpd_tag_type_MPD_TAG_ALBUM_ARTIST as isize,

    /// Song title.
    Title = mpd_tag_type_MPD_TAG_TITLE as isize,

    /// Track number on the album.
    Track = mpd_tag_type_MPD_TAG_TRACK as isize,

    /// Song name.
    ///
    /// Exact meaning not well defined.
    Name = mpd_tag_type_MPD_TAG_NAME as isize,

    /// Music genre.
    Genre = mpd_tag_type_MPD_TAG_GENRE as isize,

    /// Release date. Usually a year, sometimes a full date.
    Date = mpd_tag_type_MPD_TAG_DATE as isize,

    /// Artist who composed the song.
    Composer = mpd_tag_type_MPD_TAG_COMPOSER as isize,

    /// Artist who performed the song.
    Performer = mpd_tag_type_MPD_TAG_PERFORMER as isize,

    /// Human-readable comment about the song.
    ///
    /// Exact meaning not well defined.
    Comment = mpd_tag_type_MPD_TAG_COMMENT as isize,

    /// Disc number on a multi-disc album.
    Disc = mpd_tag_type_MPD_TAG_DISC as isize,

    /// Artist id from [MusicBrainz](https://musicbrainz.org/).
    MusicbrainzArtistid = mpd_tag_type_MPD_TAG_MUSICBRAINZ_ARTISTID as isize,

    /// Album id from [MusicBrainz](https://musicbrainz.org/).
    MusicbrainzAlbumid = mpd_tag_type_MPD_TAG_MUSICBRAINZ_ALBUMID as isize,

    /// Album artist id from [MusicBrainz](https://musicbrainz.org/).
    MusicbrainzAlbumartistid = mpd_tag_type_MPD_TAG_MUSICBRAINZ_ALBUMARTISTID as isize,

    /// Track id from [MusicBrainz](https://musicbrainz.org/).
    MusicbrainzTrackid = mpd_tag_type_MPD_TAG_MUSICBRAINZ_TRACKID as isize,

    /// Release track id from [MusicBrainz](https://musicbrainz.org/).
    MusicbrainzReleasetrackid = mpd_tag_type_MPD_TAG_MUSICBRAINZ_RELEASETRACKID as isize,

    /// Original release date of the song.
    OriginalDate = mpd_tag_type_MPD_TAG_ORIGINAL_DATE as isize,

    /// Artist used for sorting.
    ///
    /// Usually omits prefixes.
    ArtistSort = mpd_tag_type_MPD_TAG_ARTIST_SORT as isize,

    /// Album artist used for sorting.
    ///
    /// Usually omits prefixes.
    AlbumArtistSort = mpd_tag_type_MPD_TAG_ALBUM_ARTIST_SORT as isize,

    /// Album name used for sorting.
    Albumsort = mpd_tag_type_MPD_TAG_ALBUM_SORT as isize,

    /// Name of the label or publisher.
    Label = mpd_tag_type_MPD_TAG_LABEL as isize,

    /// Work id from [MusicBrainz](https://musicbrainz.org/).
    MusicbrainzWorkid = mpd_tag_type_MPD_TAG_MUSICBRAINZ_WORKID as isize,

    /// "used if the sound belongs to a larger category of sounds/music" from
    /// [IDv2.4.0 TIT1](http://id3.org/id3v2.4.0-frames).
    Grouping = mpd_tag_type_MPD_TAG_GROUPING as isize,

    /// "distinct intellectial or artistic creation, [...] in the form of one or more audio
    /// recordings" from the [MusicBrainz definition for Work](https://musicbrainz.org/doc/Work).
    Work = mpd_tag_type_MPD_TAG_WORK as isize,

    /// Person which conducted the song.
    ///
    /// Mostly used for classical music.
    Conductor = mpd_tag_type_MPD_TAG_CONDUCTOR as isize,

    /// Person which conducted the song used for sorting.
    ///
    /// Mostly used for classical music.
    ///
    /// Usually omits prefixes.
    ComposerSort = mpd_tag_type_MPD_TAG_COMPOSER_SORT as isize,

    /// Ensemble performing the song.
    ///
    /// Mostly used for classical music.
    Ensemble = mpd_tag_type_MPD_TAG_ENSEMBLE as isize,

    /// Name of the movement.
    ///
    /// Mostly used for classical music.
    Movement = mpd_tag_type_MPD_TAG_MOVEMENT as isize,

    /// Number of the movement.
    ///
    /// Possible roman numeration.
    ///
    /// Mostly used for classical music.
    Movementnumber = mpd_tag_type_MPD_TAG_MOVEMENTNUMBER as isize,

    /// Location of recording.
    Location = mpd_tag_type_MPD_TAG_LOCATION as isize,

    /// Mood of audio in keywords.
    Mood = mpd_tag_type_MPD_TAG_MOOD as isize,

    /// Song title used for sorting.
    TitleSort = mpd_tag_type_MPD_TAG_TITLE_SORT as isize,

    /// Release group id from [MusicBrainz](https://musicbrainz.org/).
    MusicbrainzReleasegroupid = mpd_tag_type_MPD_TAG_MUSICBRAINZ_RELEASEGROUPID as isize,
}

impl Tag {
    /// Returns the tag name as `String`.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Unknown`] if MPD doesn't know the tag.
    #[must_use]
    pub fn name(&self) -> String {
        unsafe {
            let name = mpd_tag_name(*self as i32);
            if name.is_null() {
                unreachable!("All possible tag inputs should are valid");
            }
            CStr::from_ptr(name).to_string_lossy().to_string()
        }
    }

    /// Returns a valid tag.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Unknown`] if MPD doesn't know the tag.
    pub fn parse(value: &str) -> Result<Self> {
        let tag = unsafe { mpd_tag_name_iparse(CString::new(value)?.as_ptr()) };
        #[allow(non_upper_case_globals)]
        match tag {
            mpd_tag_type_MPD_TAG_UNKNOWN => Err(Error::Unknown("Tag".to_string())),

            mpd_tag_type_MPD_TAG_ARTIST => Ok(Self::Artist),
            mpd_tag_type_MPD_TAG_ALBUM => Ok(Self::Album),
            mpd_tag_type_MPD_TAG_ALBUM_ARTIST => Ok(Self::AlbumArtist),
            mpd_tag_type_MPD_TAG_TITLE => Ok(Self::Title),
            mpd_tag_type_MPD_TAG_TRACK => Ok(Self::Track),
            mpd_tag_type_MPD_TAG_NAME => Ok(Self::Name),
            mpd_tag_type_MPD_TAG_GENRE => Ok(Self::Genre),
            mpd_tag_type_MPD_TAG_DATE => Ok(Self::Date),
            mpd_tag_type_MPD_TAG_COMPOSER => Ok(Self::Composer),
            mpd_tag_type_MPD_TAG_PERFORMER => Ok(Self::Performer),
            mpd_tag_type_MPD_TAG_COMMENT => Ok(Self::Comment),
            mpd_tag_type_MPD_TAG_DISC => Ok(Self::Disc),

            mpd_tag_type_MPD_TAG_MUSICBRAINZ_ARTISTID => Ok(Self::MusicbrainzArtistid),
            mpd_tag_type_MPD_TAG_MUSICBRAINZ_ALBUMID => Ok(Self::MusicbrainzAlbumid),
            mpd_tag_type_MPD_TAG_MUSICBRAINZ_ALBUMARTISTID => Ok(Self::MusicbrainzAlbumartistid),
            mpd_tag_type_MPD_TAG_MUSICBRAINZ_TRACKID => Ok(Self::MusicbrainzTrackid),
            mpd_tag_type_MPD_TAG_MUSICBRAINZ_RELEASETRACKID => Ok(Self::MusicbrainzReleasetrackid),

            mpd_tag_type_MPD_TAG_ORIGINAL_DATE => Ok(Self::OriginalDate),

            mpd_tag_type_MPD_TAG_ARTIST_SORT => Ok(Self::ArtistSort),
            mpd_tag_type_MPD_TAG_ALBUM_ARTIST_SORT => Ok(Self::AlbumArtistSort),

            mpd_tag_type_MPD_TAG_ALBUM_SORT => Ok(Self::Albumsort),
            mpd_tag_type_MPD_TAG_LABEL => Ok(Self::Label),
            mpd_tag_type_MPD_TAG_MUSICBRAINZ_WORKID => Ok(Self::MusicbrainzWorkid),

            mpd_tag_type_MPD_TAG_GROUPING => Ok(Self::Grouping),
            mpd_tag_type_MPD_TAG_WORK => Ok(Self::Work),
            mpd_tag_type_MPD_TAG_CONDUCTOR => Ok(Self::Conductor),

            mpd_tag_type_MPD_TAG_COMPOSER_SORT => Ok(Self::ComposerSort),
            mpd_tag_type_MPD_TAG_ENSEMBLE => Ok(Self::Ensemble),
            mpd_tag_type_MPD_TAG_MOVEMENT => Ok(Self::Movement),
            mpd_tag_type_MPD_TAG_MOVEMENTNUMBER => Ok(Self::Movementnumber),
            mpd_tag_type_MPD_TAG_LOCATION => Ok(Self::Location),
            mpd_tag_type_MPD_TAG_MOOD => Ok(Self::Mood),
            mpd_tag_type_MPD_TAG_TITLE_SORT => Ok(Self::TitleSort),
            mpd_tag_type_MPD_TAG_MUSICBRAINZ_RELEASEGROUPID => Ok(Self::MusicbrainzReleasegroupid),
            _ => unreachable!(),
        }
    }

    /// Used to receive tag value from server with `mpd_recv_pair_tag` and returning it.
    pub(super) fn receive(&self, connection: &Connection) -> Result<Option<String>> {
        unsafe {
            let pair = connection
                .get_error(|| mpd_recv_pair_tag(connection.connection(), *self as i32))?;
            Ok(if pair.is_null() {
                None
            } else {
                let value = CStr::from_ptr((*pair).value).to_string_lossy().to_string();
                mpd_return_pair(connection.connection(), pair);
                Some(value)
            })
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::Tag;

    #[test]
    fn name() {
        assert_eq!(Tag::Composer.name(), "Composer");
    }

    #[test]
    fn from() -> eyre::Result<()> {
        assert_eq!(Tag::parse("composer")?, Tag::Composer);
        Ok(())
    }

    #[test]
    #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: Unknown(\"Tag\")")]
    fn from_error() {
        Tag::parse("aaa").unwrap();
    }
}