mpdclient 0.2.0

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

use mpdclient_sys::{
    mpd_parse_queue_save_mode, mpd_queue_save_mode_MPD_QUEUE_SAVE_MODE_APPEND,
    mpd_queue_save_mode_MPD_QUEUE_SAVE_MODE_CREATE,
    mpd_queue_save_mode_MPD_QUEUE_SAVE_MODE_REPLACE, mpd_run_load, mpd_run_load_range,
    mpd_run_load_range_to, mpd_run_playlist_add, mpd_run_playlist_add_to, mpd_run_playlist_clear,
    mpd_run_playlist_delete, mpd_run_playlist_delete_range, mpd_run_playlist_move, mpd_run_rename,
    mpd_run_rm, mpd_send_list_playlist, mpd_send_list_playlist_meta,
};
#[cfg(feature = "protocol_0_24")]
use mpdclient_sys::{mpd_run_playlist_move_range, mpd_run_save_queue};

use crate::Error;

use crate::{
    entity::{EntityReceiver, Song},
    error::Result,
};

use super::{Connection, Position};

/// Intermediate to bundle MPD playlist functions.
pub struct StoredPlaylist<'a> {
    connection: &'a Connection,
    name: CString,
}

impl<'a> StoredPlaylist<'a> {
    pub(super) fn new(connection: &'a Connection, name: &str) -> Result<Self> {
        let c_name = CString::new(name)?;
        Ok(Self {
            connection,
            name: c_name,
        })
    }

    /// List all songs in the playlist **without** metadata.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn list(&self) -> Result<Vec<Song>> {
        self.connection.get_bool_error(|| unsafe {
            mpd_send_list_playlist(self.connection.connection(), self.name.as_ptr())
        })?;
        Song::extract_all(EntityReceiver::new(self.connection))
    }

    /// List all songs in the playlist **with** metadata.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn list_meta(&self) -> Result<Vec<Song>> {
        self.connection.get_bool_error(|| unsafe {
            mpd_send_list_playlist_meta(self.connection.connection(), self.name.as_ptr())
        })?;
        Song::extract_all(EntityReceiver::new(self.connection))
    }

    /// Clear the contents of the playlist.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn clear(&self) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_playlist_clear(self.connection.connection(), self.name.as_ptr())
        })
    }

    /// Add a song with the `path` to the playlist.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn add(&self, path: &str) -> Result<()> {
        let c_path = CString::new(path)?;
        self.connection.get_bool_error(|| unsafe {
            mpd_run_playlist_add(
                self.connection.connection(),
                self.name.as_ptr(),
                c_path.as_ptr(),
            )
        })
    }

    /// Add a song with the `path` to the playlist at a specific position.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn add_to(&self, path: &str, to: u32) -> Result<()> {
        let c_path = CString::new(path)?;
        self.connection.get_bool_error(|| unsafe {
            mpd_run_playlist_add_to(
                self.connection.connection(),
                self.name.as_ptr(),
                c_path.as_ptr(),
                to,
            )
        })
    }

    /// Move the position of a song within the playlist.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn move_single(&self, from: u32, to: u32) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_playlist_move(self.connection.connection(), self.name.as_ptr(), from, to)
        })
    }

    /// Move all songs in a range within the playlist.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    #[cfg(feature = "protocol_0_24")]
    pub fn move_range(&self, start: u32, end: u32, to: u32) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_playlist_move_range(
                self.connection.connection(),
                self.name.as_ptr(),
                start,
                end,
                to,
            )
        })
    }

    /// Delete a song at `position`.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn delete(&self, pos: u32) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_playlist_delete(self.connection.connection(), self.name.as_ptr(), pos)
        })
    }

    /// Delete all songs in the range.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn delete_range(&self, start: u32, end: u32) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_playlist_delete_range(
                self.connection.connection(),
                self.name.as_ptr(),
                start,
                end,
            )
        })
    }

    /// Save the current queue to the playlist. The `mode` changes how the current playlist is
    /// handled through [`QueueSaveMode`].
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    #[cfg(feature = "protocol_0_24")]
    pub fn save_queue(&self, mode: QueueSaveMode) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_save_queue(
                self.connection.connection(),
                self.name.as_ptr(),
                mode as u32,
            )
        })
    }

    /// Loads the playlist into the queue, replacing the current queue.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn load(&self) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_load(self.connection.connection(), self.name.as_ptr())
        })
    }

    /// Loads a part of the playlist into the queue, replacing the current queue.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn load_range(&self, start: u32, end: Option<u32>) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_load_range(
                self.connection.connection(),
                self.name.as_ptr(),
                start,
                end.unwrap_or(u32::MAX),
            )
        })
    }

    /// Loads a part of the playlist into a position within the queue.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn load_range_to(
        &self,
        start: u32,
        end: Option<u32>,
        to: u32,
        whence: Position,
    ) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_load_range_to(
                self.connection.connection(),
                self.name.as_ptr(),
                start,
                end.unwrap_or(u32::MAX),
                to,
                whence as u32,
            )
        })
    }

    /// Renames the playlist.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn rename(&self, to: &str) -> Result<()> {
        let c_to = CString::new(to)?;
        self.connection.get_bool_error(|| unsafe {
            mpd_run_rename(
                self.connection.connection(),
                self.name.as_ptr(),
                c_to.as_ptr(),
            )
        })
    }

    /// Removes the playlist, includes deleting the file.
    ///
    /// # Errors
    ///
    /// Returns [`Error::Mpd`] if MPD returns an error.
    pub fn rm(&self) -> Result<()> {
        self.connection.get_bool_error(|| unsafe {
            mpd_run_rm(self.connection.connection(), self.name.as_ptr())
        })
    }
}

// --- Mode ---
/// Queue save mode, deceides how the queue should be saved as a playlist.
#[allow(clippy::cast_possible_wrap)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QueueSaveMode {
    /// Create a new playlist
    Create = mpd_queue_save_mode_MPD_QUEUE_SAVE_MODE_CREATE as isize,

    /// Replace an existing playlist
    Replace = mpd_queue_save_mode_MPD_QUEUE_SAVE_MODE_REPLACE as isize,

    /// Append to an existing playlist
    Append = mpd_queue_save_mode_MPD_QUEUE_SAVE_MODE_APPEND as isize,
}

impl TryFrom<&str> for QueueSaveMode {
    type Error = Error;

    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
        let c_value = CString::new(value)?;
        unsafe {
            #[allow(non_upper_case_globals)]
            Ok(match mpd_parse_queue_save_mode(c_value.as_ptr()) {
                mpd_queue_save_mode_MPD_QUEUE_SAVE_MODE_CREATE => Self::Create,
                mpd_queue_save_mode_MPD_QUEUE_SAVE_MODE_REPLACE => Self::Replace,
                mpd_queue_save_mode_MPD_QUEUE_SAVE_MODE_APPEND => Self::Append,
                _ => unreachable!(),
            })
        }
    }
}