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
//! 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)) } }