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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! The module defines structures and protocols for asynchronous MPD communication
//!
//! The MPD supports very simple protocol for asynchronous client notifications about
//! different player events. First user issues `idle` command with optional argument
//! to filter events by source subsystem (like "database", "player", "mixer" etc.)
//!
//! Once in "idle" mode, client connection timeout is disabled, and MPD will notify
//! client about next event when one occurs (if originated from one of designated
//! subsystems, if specified).
//!
//! (Actually MPD notifies only about general subsystem source of event, e.g.
//! if user changed volume, client will get `mixer` event in idle mode, so
//! it should issue `status` command then and check for any mixer-related field
//! changes.)
//!
//! Once some such event occurs, and client is notified about it, idle mode is interrupted,
//! and client must issue another `idle` command to continue listening for interesting
//! events.
//!
//! While in "idle" mode, client can't issue any commands, except for special `noidle`
//! command, which interrupts "idle" mode, and provides a list queued events
//! since last `idle` command, if they occurred.
//!
//! The module describes subsystems enum only, but the main workflow is determined by
//! [`IdleGuard`](struct.IdleGuard.html) struct, which catches mutable reference
//! to original `Client` struct, thus enforcing MPD contract in regards of (im)possibility
//! to send commands while in "idle" mode.

use std::fmt;
use std::str::FromStr;
use std::io::{Read, Write};
use std::mem::forget;

use error::{Error, ParseError};
use client::Client;
use proto::Proto;

/// Subsystems for `idle` command
#[derive(Clone, Copy, Debug, PartialEq, RustcEncodable)]
pub enum Subsystem {
    /// database: the song database has been modified after update.
    Database,
    /// update: a database update has started or finished.
    /// If the database was modified during the update, the database event is also emitted.
    Update,
    /// stored_playlist: a stored playlist has been modified, renamed, created or deleted
    Playlist,
    /// playlist: the current playlist has been modified
    Queue,
    /// player: the player has been started, stopped or seeked
    Player,
    /// mixer: the volume has been changed
    Mixer,
    /// output: an audio output has been enabled or disabled
    Output,
    /// options: options like repeat, random, crossfade, replay gain
    Options,
    /// sticker: the sticker database has been modified.
    Sticker,
    /// subscription: a client has subscribed or unsubscribed to a channel
    Subscription,
    /// message: a message was received on a channel this client is subscribed to; this event is only emitted when the queue is empty
    Message,
}

impl FromStr for Subsystem {
    type Err = ParseError;
    fn from_str(s: &str) -> Result<Subsystem, ParseError> {
        use self::Subsystem::*;
        match s {
            "database" => Ok(Database),
            "update" => Ok(Update),
            "stored_playlist" => Ok(Playlist),
            "playlist" => Ok(Queue),
            "player" => Ok(Player),
            "mixer" => Ok(Mixer),
            "output" => Ok(Output),
            "options" => Ok(Options),
            "sticker" => Ok(Sticker),
            "subscription" => Ok(Subscription),
            "message" => Ok(Message),
            _ => Err(ParseError::BadValue(s.to_owned())),
        }
    }
}

impl fmt::Display for Subsystem {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::Subsystem::*;
        f.write_str(match *self {
            Database => "database",
            Update => "update",
            Playlist => "stored_playlist",
            Queue => "playlist",
            Player => "player",
            Mixer => "mixer",
            Output => "output",
            Options => "options",
            Sticker => "sticker",
            Subscription => "subscription",
            Message => "message",
        })
    }
}


/// "Idle" mode guard enforcing MPD asynchronous events protocol
pub struct IdleGuard<'a, S: 'a + Read + Write>(&'a mut Client<S>);

impl<'a, S: 'a + Read + Write> IdleGuard<'a, S> {
    /// Get list of subsystems with new events, interrupting idle mode in process
    pub fn get(self) -> Result<Vec<Subsystem>, Error> {
        let result = self.0
                         .read_pairs()
                         .filter(|r| {
                             r.as_ref()
                              .map(|&(ref a, _)| *a == "changed")
                              .unwrap_or(true)
                         })
                         .map(|r| r.and_then(|(_, b)| b.parse().map_err(From::from)))
                         .collect();
        forget(self);
        result
    }
}

impl<'a, S: 'a + Read + Write> Drop for IdleGuard<'a, S> {
    fn drop(&mut self) {
        let _ = self.0.run_command("noidle").map(|_| self.0.drain());
    }
}

/// This trait implements `idle` command of MPD protocol
///
/// See module's documentation for details.
pub trait Idle {
    /// Stream type of a client
    type Stream: Read + Write;

    /// Start listening for events from a set of subsystems
    ///
    /// If empty subsystems slice is given, wait for all event from any subsystem.
    ///
    /// This method returns `IdleGuard`, which takes mutable reference of an initial client,
    /// thus disallowing any operations on this mpd connection.
    ///
    /// You can call `.get()` method of this struct to stop waiting and get all queued events
    /// matching given subsystems filter. This call consumes a guard, stops waiting
    /// and releases client object.
    ///
    /// If the guard goes out of scope, wait lock is released as well, but all queued events
    /// will be silently ignored.
    fn idle<'a>(&'a mut self, subsystems: &[Subsystem]) -> Result<IdleGuard<'a, Self::Stream>, Error>;

    /// Wait for events from a set of subsystems and return list of affected subsystems
    ///
    /// This is a blocking operation. If empty subsystems slice is given,
    /// wait for all event from any subsystem.
    fn wait(&mut self, subsystems: &[Subsystem]) -> Result<Vec<Subsystem>, Error> {
        self.idle(subsystems).and_then(IdleGuard::get)
    }
}

impl<S: Read + Write> Idle for Client<S> {
    type Stream = S;
    fn idle<'a>(&'a mut self, subsystems: &[Subsystem]) -> Result<IdleGuard<'a, S>, Error> {
        let subsystems = subsystems.iter()
                                   .map(|v| v.to_string())
                                   .collect::<Vec<String>>()
                                   .join(" ");
        try!(self.run_command_fmt(format_args!("idle {}", subsystems)));
        Ok(IdleGuard(self))
    }
}