mpdclient 0.2.0

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

use mpdclient_sys::{
    mpd_message_get_channel, mpd_message_get_text, mpd_recv_message, mpd_recv_pair_named,
    mpd_return_pair, mpd_run_send_message, mpd_run_subscribe, mpd_run_unsubscribe,
    mpd_send_channels, mpd_send_read_messages,
};

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

/// Intermediate to bundle access to the mpd messaging infrastructure.
#[derive(Debug)]
pub struct Channel<'a> {
    connection: &'a Connection,
}

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

    /// Subscribes to a channel.
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::Mpd`] if MPD returns an error.
    pub fn subscribe(&self, channel: &str) -> Result<()> {
        let channel = CString::new(channel)?;
        self.connection.get_bool_error(|| unsafe {
            mpd_run_subscribe(self.connection.connection(), channel.as_ptr())
        })?;

        Ok(())
    }

    /// Unsubscribes from a channel.
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::Mpd`] if MPD returns an error.
    pub fn unsubscribe(&self, channel: &str) -> Result<()> {
        let channel = CString::new(channel)?;
        self.connection.get_bool_error(|| unsafe {
            mpd_run_unsubscribe(self.connection.connection(), channel.as_ptr())
        })?;

        Ok(())
    }

    /// Get [Message]s from all subscribed channels.
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::Mpd`] if MPD returns an error.
    pub fn read_subscriptions(&self) -> Result<MessageIter<'_>> {
        self.connection
            .get_bool_error(|| unsafe { mpd_send_read_messages(self.connection.connection()) })?;

        Ok(MessageIter {
            connection: self.connection,
        })
    }

    /// Write message to a channel.
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::Mpd`] if MPD returns an error.
    pub fn channel_write(&self, channel: &str, msg: &str) -> Result<()> {
        let channel = CString::new(channel)?;
        let msg = CString::new(msg)?;
        self.connection.get_bool_error(|| unsafe {
            mpd_run_send_message(self.connection.connection(), channel.as_ptr(), msg.as_ptr())
        })
    }

    /// List all existing channels on the server.
    ///
    /// # Errors
    ///
    /// Returns [`crate::Error::Mpd`] if MPD returns an error.
    pub fn list(&self) -> Result<Vec<String>> {
        self.connection
            .get_bool_error(|| unsafe { mpd_send_channels(self.connection.connection()) })?;

        let channel_str = CString::new("channel")?;

        let mut channel_pair = self.connection.get_error(|| unsafe {
            mpd_recv_pair_named(self.connection.connection(), channel_str.as_ptr())
        })?;

        let mut channels = Vec::new();
        while !channel_pair.is_null() {
            channels.push(
                unsafe { CStr::from_ptr((*channel_pair).value) }
                    .to_string_lossy()
                    .to_string(),
            );

            unsafe { mpd_return_pair(self.connection.connection(), channel_pair) };
            channel_pair = self.connection.get_error(|| unsafe {
                mpd_recv_pair_named(self.connection.connection(), channel_str.as_ptr())
            })?;
        }

        Ok(channels)
    }
}

/// Struct representing a complete through a channel received message.
#[derive(Debug, PartialEq, Eq)]
pub struct Message {
    /// Channel the message originated from
    pub channel: String,

    /// Text content of the message
    pub text: String,
}

/// Iterator to get all available [`Message`]s.
pub struct MessageIter<'a> {
    connection: &'a Connection,
}

impl Iterator for MessageIter<'_> {
    type Item = Result<Message>;

    fn next(&mut self) -> Option<Self::Item> {
        let msg = self
            .connection
            .get_error(|| unsafe { mpd_recv_message(self.connection.connection()) });

        match msg {
            Ok(msg) => {
                if msg.is_null() {
                    return None;
                }
                let channel = unsafe { CStr::from_ptr(mpd_message_get_channel(msg)) }
                    .to_string_lossy()
                    .to_string();

                let text = unsafe { CStr::from_ptr(mpd_message_get_text(msg)) }
                    .to_string_lossy()
                    .to_string();

                Some(Ok(Message { channel, text }))
            }
            Err(err) => Some(Err(err)),
        }
    }
}