mpdclient 0.2.0

Rust interface to MPD using libmpdclient
Documentation
use std::ffi::CStr;

use mpdclient_sys::{
    mpd_idle_name, mpd_recv_idle, mpd_run_idle, mpd_run_idle_mask, mpd_run_noidle, mpd_send_idle,
    mpd_send_idle_mask,
};

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

/// Intermediate to bundle idle functions with MPD.
pub struct Idle<'a> {
    connection: &'a Connection,
}

impl<'a> Idle<'a> {
    pub(crate) fn new(connection: &'a Connection) -> Self {
        Self { connection }
    }

    #[doc(alias = "mpd_run_idle")]
    /// Idle mode. Blocks without timeout until an event on mpd happens and returns it.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`](crate::Error::Mpd) if MPD returns an error.
    pub fn idle(&self) -> Result<Vec<IdleReturn>> {
        let idle = self
            .connection
            .get_error(|| unsafe { mpd_run_idle(self.connection.connection()) })?;
        Ok(IdleReturn::from_u32(idle))
    }

    #[doc(alias = "mpd_run_idle_mask")]
    /// Idle mode with mask. See [`Self::idle`]. The masks sets which types of updates are received.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`](crate::Error::Mpd) if MPD returns an error.
    pub fn idle_mask(&self, mask: &[IdleReturn]) -> Result<Vec<IdleReturn>> {
        let idle = self.connection.get_error(|| unsafe {
            mpd_run_idle_mask(self.connection.connection(), IdleReturn::to_u32(mask))
        })?;
        Ok(IdleReturn::from_u32(idle))
    }

    #[doc(alias = "mpd_send_idle")]
    /// Idle mode set. Only sets the mode, doesn't directly receive updates. Needs [`Self::unset`]
    /// or [`Self::receive`] to receive updates.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`](crate::Error::Mpd) if MPD returns an error.
    pub fn set(&self) -> Result<()> {
        self.connection
            .get_bool_error(|| unsafe { mpd_send_idle(self.connection.connection()) })
    }

    #[doc(alias = "mpd_send_idle_mask")]
    /// Idle mode set with mask. See [`Self::set`]. The masks sets which types of updates are received.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`](crate::Error::Mpd) if MPD returns an error.
    pub fn set_mask(&self, mask: &[IdleReturn]) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_send_idle_mask(self.connection.connection(), IdleReturn::to_u32(mask))
        })
    }

    #[doc(alias = "mpd_send_noidle", alias = "mpd_run_noidle")]
    /// Idle mode unset. Returns all updates since [`Self::set`] or [`Self::set_mask`].
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`](crate::Error::Mpd) if MPD returns an error.
    pub fn unset(&self) -> Result<Vec<IdleReturn>> {
        let idle = self
            .connection
            .get_error(|| unsafe { mpd_run_noidle(self.connection.connection()) })?;
        Ok(IdleReturn::from_u32(idle))
    }

    #[doc(alias = "mpd_recv_idle")]
    /// Returns all updates since [`Self::set`] or [`Self::set_mask`], or blocks until an update
    /// happens.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`](crate::Error::Mpd) if MPD returns an error.
    pub fn receive(&self, disable_timeout: bool) -> Result<Vec<IdleReturn>> {
        let idle = self.connection.get_error(|| unsafe {
            mpd_recv_idle(self.connection.connection(), disable_timeout)
        })?;
        Ok(IdleReturn::from_u32(idle))
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
/// Enum of all possible return options of an [`Idle`] connection
pub enum IdleReturn {
    /// Database updated
    Database = 0x1,

    /// Playlist has been edited
    StoredPlaylist = 0x2,

    /// Queue has been modified
    Queue = 0x4,

    /// Player state changed
    Player = 0x8,

    /// Mixer settings changed
    Mixer = 0x10,

    /// Output device was en-/disabled
    Output = 0x20,

    /// Options have changed
    Options = 0x40,

    /// Database update started/finished
    Update = 0x80,

    /// Sticker has been modified
    Sticker = 0x100,

    /// Client (un)subscribed from a channel
    Subscription = 0x200,

    /// Message on a subscribed channel was received
    Message = 0x400,

    /// Partition added or changed
    Partition = 0x800,

    /// Neighbour found or lost
    Neighbor = 0x1000,

    /// Mount list changed
    Mount = 0x2000,
}

impl IdleReturn {
    fn from_u32(value: u32) -> Vec<Self> {
        let mut ret: Vec<Self> = Vec::new();

        Self::check_value(&mut ret, value, Self::Database);
        Self::check_value(&mut ret, value, Self::StoredPlaylist);
        Self::check_value(&mut ret, value, Self::Queue);
        Self::check_value(&mut ret, value, Self::Player);
        Self::check_value(&mut ret, value, Self::Mixer);
        Self::check_value(&mut ret, value, Self::Output);
        Self::check_value(&mut ret, value, Self::Options);
        Self::check_value(&mut ret, value, Self::Update);
        Self::check_value(&mut ret, value, Self::Sticker);
        Self::check_value(&mut ret, value, Self::Subscription);
        Self::check_value(&mut ret, value, Self::Message);
        Self::check_value(&mut ret, value, Self::Partition);
        Self::check_value(&mut ret, value, Self::Neighbor);
        Self::check_value(&mut ret, value, Self::Mount);

        ret
    }

    fn check_value(vec: &mut Vec<Self>, value: u32, idle: IdleReturn) {
        if value & idle as u32 != 0 {
            vec.push(idle);
        }
    }

    fn to_u32(value: &[Self]) -> u32 {
        let mut ret = 0;

        for v in value {
            ret |= *v as u32;
        }

        ret
    }

    /// Get the name of a return value as String
    #[must_use]
    pub fn name(&self) -> String {
        unsafe {
            CStr::from_ptr(mpd_idle_name(*self as u32))
                .to_string_lossy()
                .to_string()
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::connection::idle::IdleReturn;

    #[test]
    fn idle_return_single_database() {
        assert_eq!(IdleReturn::from_u32(0b1), vec![IdleReturn::Database]);
    }

    #[test]
    fn idle_return_single_queue() {
        assert_eq!(IdleReturn::from_u32(0b100), vec![IdleReturn::Queue]);
    }

    #[test]
    fn idle_return_single_mixer() {
        assert_eq!(IdleReturn::from_u32(0b10000), vec![IdleReturn::Mixer]);
    }

    #[test]
    fn idle_return_multiple_sp_p() {
        assert_eq!(
            IdleReturn::from_u32(0b1010),
            vec![IdleReturn::StoredPlaylist, IdleReturn::Player]
        );
    }

    #[test]
    fn idle_return_multiple_o_o() {
        assert_eq!(
            IdleReturn::from_u32(0b110_0000),
            vec![IdleReturn::Output, IdleReturn::Options]
        );
    }

    #[test]
    fn idle_to_u32_database() {
        assert_eq!(IdleReturn::to_u32(&[IdleReturn::Database]), 0b1);
    }

    #[test]
    fn idle_to_u32_queue() {
        assert_eq!(IdleReturn::to_u32(&[IdleReturn::Queue]), 0b100);
    }

    #[test]
    fn idle_to_mixer() {
        assert_eq!(IdleReturn::to_u32(&[IdleReturn::Mixer]), 0b10000);
    }

    #[test]
    fn idle_to_sp_p() {
        assert_eq!(
            IdleReturn::to_u32(&[IdleReturn::StoredPlaylist, IdleReturn::Player]),
            0b1010
        );
    }

    #[test]
    fn idle_to_o_o() {
        assert_eq!(
            IdleReturn::to_u32(&[IdleReturn::Output, IdleReturn::Options]),
            0b110_0000
        );
    }
}