summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2022-08-07 20:47:54 -0400
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2022-08-07 20:47:54 -0400
commitea77a8cf9c012fcd877d842fec85b0b5a442952c (patch)
tree7f209c030b97a957497f152d161a9d7035311132
parent0de7e9ac43f88aae3750ecfe25b5454fdd1ad015 (diff)
Import modifications to `mpd` crateHEADmaster
-rw-r--r--vendored/mpd/.gitignore2
-rw-r--r--vendored/mpd/.travis.yml43
-rw-r--r--vendored/mpd/Cargo.toml19
-rw-r--r--vendored/mpd/LICENSE-APACHE201
-rw-r--r--vendored/mpd/LICENSE-MIT25
-rw-r--r--vendored/mpd/README.md44
-rw-r--r--vendored/mpd/benches/options.rs15
-rw-r--r--vendored/mpd/examples/example.rs24
-rw-r--r--vendored/mpd/rustfmt.toml12
-rw-r--r--vendored/mpd/src/client.rs689
-rw-r--r--vendored/mpd/src/convert.rs316
-rw-r--r--vendored/mpd/src/error.rs394
-rw-r--r--vendored/mpd/src/idle.rs176
-rw-r--r--vendored/mpd/src/lib.rs66
-rw-r--r--vendored/mpd/src/macros.rs27
-rw-r--r--vendored/mpd/src/message.rs73
-rw-r--r--vendored/mpd/src/mount.rs51
-rw-r--r--vendored/mpd/src/output.rs28
-rw-r--r--vendored/mpd/src/playlist.rs33
-rw-r--r--vendored/mpd/src/plugin.rs45
-rw-r--r--vendored/mpd/src/proto.rs260
-rw-r--r--vendored/mpd/src/reply.rs36
-rw-r--r--vendored/mpd/src/search.rs141
-rw-r--r--vendored/mpd/src/song.rs210
-rw-r--r--vendored/mpd/src/stats.rs78
-rw-r--r--vendored/mpd/src/status.rs310
-rw-r--r--vendored/mpd/src/sticker.rs23
-rw-r--r--vendored/mpd/src/version.rs25
-rw-r--r--vendored/mpd/tests/data/empty.flacbin0 -> 8282 bytes
-rw-r--r--vendored/mpd/tests/helpers/daemon.rs140
-rw-r--r--vendored/mpd/tests/helpers/mod.rs36
-rw-r--r--vendored/mpd/tests/idle.rs20
-rw-r--r--vendored/mpd/tests/options.rs73
-rw-r--r--vendored/mpd/tests/outputs.rs25
-rw-r--r--vendored/mpd/tests/playback.rs10
-rw-r--r--vendored/mpd/tests/playlist.rs15
-rw-r--r--vendored/mpd/tests/reflect.rs28
-rw-r--r--vendored/mpd/tests/search.rs15
-rw-r--r--vendored/mpd/tests/song.rs28
-rw-r--r--vendored/mpd/tests/stickers.rs17
40 files changed, 3773 insertions, 0 deletions
diff --git a/vendored/mpd/.gitignore b/vendored/mpd/.gitignore
new file mode 100644
index 0000000..4fffb2f
--- /dev/null
+++ b/vendored/mpd/.gitignore
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/vendored/mpd/.travis.yml b/vendored/mpd/.travis.yml
new file mode 100644
index 0000000..5e221d0
--- /dev/null
+++ b/vendored/mpd/.travis.yml
@@ -0,0 +1,43 @@
+sudo: required
+language: rust
+cache: cargo
+addons:
+ apt:
+ packages:
+ - libcurl4-openssl-dev
+ - libelf-dev
+ - libdw-dev
+ - binutils-dev
+rust:
+ - stable
+ - nightly-2017-02-05
+env:
+ global:
+ # override the default `--features unstable` used for the nightly branch
+ - TRAVIS_CARGO_NIGHTLY_FEATURE=""
+ # Version of clippy known to work with pinned nightly.
+ - CLIPPY_VERSION=0.0.113
+before_script:
+ - sudo apt-get update -qq
+ - sudo apt-get install -y mpd
+ # Stop mpd service to ensure we use the test-started ones.
+ - sudo /etc/init.d/mpd stop
+ - /usr/bin/mpd --version
+ - export PATH=$HOME/.cargo/bin:$HOME/.local/bin:$PATH
+ - |
+ pip install 'travis-cargo<0.2' --user &&
+ travis-cargo --only nightly install -- --force clippy --vers CLIPPY_VERSION
+ - |
+ cargo install --force rustfmt
+script:
+ - |
+ RUSTFLAGS=-Dwarnings travis-cargo build &&
+ travis-cargo test
+ - |
+ cargo fmt -- --write-mode diff
+ - |
+ travis-cargo --only nightly clippy
+ - |
+ travis-cargo --only stable doc
+after_success:
+ - travis-cargo coveralls --no-sudo --verify
diff --git a/vendored/mpd/Cargo.toml b/vendored/mpd/Cargo.toml
new file mode 100644
index 0000000..e3a104f
--- /dev/null
+++ b/vendored/mpd/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+authors = ["Konstantin Stepanov <me@kstep.me>"]
+description = "A client library for MPD (music player daemon), like libmpdclient but in Rust"
+documentation = "http://kstep.me/rust-mpd/mpd/index.html"
+homepage = "https://github.com/kstep/rust-mpd"
+license = "MIT/Apache-2.0"
+name = "mpd"
+repository = "https://github.com/kstep/rust-mpd.git"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+bufstream = "0.1.1"
+rustc-serialize = "0.3.16"
+time = "0.2"
+
+[dev-dependencies]
+tempdir = "0.3.5"
+unix_socket = "0.5.0"
diff --git a/vendored/mpd/LICENSE-APACHE b/vendored/mpd/LICENSE-APACHE
new file mode 100644
index 0000000..16fe87b
--- /dev/null
+++ b/vendored/mpd/LICENSE-APACHE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/vendored/mpd/LICENSE-MIT b/vendored/mpd/LICENSE-MIT
new file mode 100644
index 0000000..fddc895
--- /dev/null
+++ b/vendored/mpd/LICENSE-MIT
@@ -0,0 +1,25 @@
+Copyright (c) 2015-2016 Konstantin Stepanov <me@kstep.me>
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/vendored/mpd/README.md b/vendored/mpd/README.md
new file mode 100644
index 0000000..0fae261
--- /dev/null
+++ b/vendored/mpd/README.md
@@ -0,0 +1,44 @@
+# rust-mpd <a href="https://travis-ci.org/kstep/rust-mpd"><img src="https://img.shields.io/travis/kstep/rust-mpd.png?style=flat-square" /></a> <a href="https://crates.io/crates/mpd"><img src="https://img.shields.io/crates/d/mpd.png?style=flat-square" /></a> <a href="https://crates.io/crates/mpd"><img src="https://img.shields.io/crates/v/mpd.png?style=flat-square" /></a> <a href="https://crates.io/crates/mpd"><img src="https://img.shields.io/crates/l/mpd.png?style=flat-square" /></a><a href=http://docs.rs/mpd/><img src="https://docs.rs/mpd/badge.svg" /></a>
+
+Pure Rust version of [libmpdclient](http://www.musicpd.org/libs/libmpdclient/).
+
+[Full documentation](http://docs.rs/mpd/)
+
+## Example
+
+Add to `Cargo.toml`:
+
+```toml
+[dependencies]
+mpd = "*"
+```
+
+Then just use:
+
+```rust
+extern crate mpd;
+
+use mpd::Client;
+use std::net::TcpStream;
+
+let mut conn = Client::connect("127.0.0.1:6600").unwrap();
+conn.volume(100).unwrap();
+conn.load("My Lounge Playlist", ..).unwrap();
+conn.play().unwrap();
+println!("Status: {:?}", conn.status());
+```
+
+## License
+
+Licensed under either of
+
+ * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
+
+at your option.
+
+### Contribution
+
+Unless you explicitly state otherwise, any contribution intentionally submitted
+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
+additional terms or conditions.
diff --git a/vendored/mpd/benches/options.rs b/vendored/mpd/benches/options.rs
new file mode 100644
index 0000000..cd04418
--- /dev/null
+++ b/vendored/mpd/benches/options.rs
@@ -0,0 +1,15 @@
+#![feature(test)]
+
+extern crate mpd;
+extern crate time;
+extern crate test;
+extern crate unix_socket;
+
+use test::{Bencher, black_box};
+use unix_socket::UnixStream;
+
+#[bench]
+fn status(b: &mut Bencher) {
+ let mut mpd = mpd::Client::<UnixStream>::new(UnixStream::connect("/var/run/mpd/socket").unwrap()).unwrap();
+ b.iter(|| { black_box(mpd.status()).unwrap(); });
+}
diff --git a/vendored/mpd/examples/example.rs b/vendored/mpd/examples/example.rs
new file mode 100644
index 0000000..f4ae079
--- /dev/null
+++ b/vendored/mpd/examples/example.rs
@@ -0,0 +1,24 @@
+extern crate mpd;
+
+use mpd::{Client, Query};
+use std::net::TcpStream;
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let mut c = Client::new(TcpStream::connect("127.0.0.1:6600").unwrap()).unwrap();
+ println!("version: {:?}", c.version);
+ println!("status: {:?}", c.status());
+ println!("stuff: {:?}", c.find(&Query::new(), (1, 2)));
+
+ let now_playing = c.currentsong()?;
+ if let Some(song) = now_playing {
+ println!("Metadata:");
+ for row in c.readcomments(song)? {
+ if let Ok((k, v)) = row {
+ println!("{}: {}", k, v);
+ }
+ }
+ } else {
+ println!("No song playing.");
+ }
+ Ok(())
+}
diff --git a/vendored/mpd/rustfmt.toml b/vendored/mpd/rustfmt.toml
new file mode 100644
index 0000000..1091732
--- /dev/null
+++ b/vendored/mpd/rustfmt.toml
@@ -0,0 +1,12 @@
+newline_style = "Unix"
+max_width = 140
+fn_call_width = 120
+ideal_width = 120
+tab_spaces = 4
+fn_args_density = "Compressed"
+fn_arg_indent = "Tabbed"
+single_line_if_else = true
+reorder_imports = true
+chain_base_indent = "Tabbed"
+chain_indent = "Block"
+chain_one_line_max = 160
diff --git a/vendored/mpd/src/client.rs b/vendored/mpd/src/client.rs
new file mode 100644
index 0000000..ac5c834
--- /dev/null
+++ b/vendored/mpd/src/client.rs
@@ -0,0 +1,689 @@
+//! This module defines client data structure — the main entry point to MPD communication
+//!
+//! Almost every method of the `Client` structure corresponds to some command in [MPD protocol][proto].
+//!
+//! [proto]: http://www.musicpd.org/doc/protocol/
+
+
+use bufstream::BufStream;
+
+use crate::convert::*;
+use crate::error::{Error, ParseError, ProtoError, Result};
+use crate::message::{Channel, Message};
+use crate::mount::{Mount, Neighbor};
+use crate::output::Output;
+use crate::playlist::Playlist;
+use crate::plugin::Plugin;
+use crate::proto::*;
+use crate::search::{Query, Window, Term};
+use crate::song::{Id, Song};
+use crate::stats::Stats;
+use crate::status::{ReplayGain, Status};
+use crate::sticker::Sticker;
+use crate::version::Version;
+
+use std::convert::From;
+use std::io::{BufRead, Lines, Read, Write};
+use std::net::{TcpStream, ToSocketAddrs};
+use std::collections::HashMap;
+
+// Client {{{
+
+/// Client connection
+#[derive(Debug)]
+pub struct Client<S = TcpStream>
+ where S: Read + Write
+{
+ socket: BufStream<S>,
+ /// MPD version
+ pub version: Version,
+}
+
+impl Default for Client<TcpStream> {
+ fn default() -> Client<TcpStream> {
+ Client::<TcpStream>::connect("127.0.0.1:6600").unwrap()
+ }
+}
+
+impl Client<TcpStream> {
+ /// Connect client to some IP address
+ pub fn connect<A: ToSocketAddrs>(addr: A) -> Result<Client<TcpStream>> {
+ TcpStream::connect(addr).map_err(Error::Io).and_then(Client::new)
+ }
+}
+
+impl<S: Read + Write> Client<S> {
+ // Constructors {{{
+ /// Create client from some arbitrary pre-connected socket
+ pub fn new(socket: S) -> Result<Client<S>> {
+ let mut socket = BufStream::new(socket);
+
+ let mut banner = String::new();
+ socket.read_line(&mut banner)?;
+
+ if !banner.starts_with("OK MPD ") {
+ return Err(From::from(ProtoError::BadBanner));
+ }
+
+ let version = banner[7..].trim().parse::<Version>()?;
+
+ Ok(Client {
+ socket: socket,
+ version: version,
+ })
+ }
+ // }}}
+
+ // Playback options & status {{{
+ /// Get MPD status
+ pub fn status(&mut self) -> Result<Status> {
+ self.run_command("command_list_begin", ())
+ .and_then(|_| self.run_command("status", ()))
+ .and_then(|_| self.run_command("replay_gain_status", ()))
+ .and_then(|_| self.run_command("command_list_end", ()))
+ .and_then(|_| self.read_struct())
+ }
+
+ /// Get MPD playing statistics
+ pub fn stats(&mut self) -> Result<Stats> {
+ self.run_command("stats", ()).and_then(|_| self.read_struct())
+ }
+
+ /// Clear error state
+ pub fn clearerror(&mut self) -> Result<()> {
+ self.run_command("clearerror", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// Set volume
+ pub fn volume(&mut self, volume: i8) -> Result<()> {
+ self.run_command("setvol", volume).and_then(|_| self.expect_ok())
+ }
+
+ /// Set repeat state
+ pub fn repeat(&mut self, value: bool) -> Result<()> {
+ self.run_command("repeat", value as u8).and_then(|_| self.expect_ok())
+ }
+
+ /// Set random state
+ pub fn random(&mut self, value: bool) -> Result<()> {
+ self.run_command("random", value as u8).and_then(|_| self.expect_ok())
+ }
+
+ /// Set single state
+ pub fn single(&mut self, value: bool) -> Result<()> {
+ self.run_command("single", value as u8).and_then(|_| self.expect_ok())
+ }
+
+ /// Set consume state
+ pub fn consume(&mut self, value: bool) -> Result<()> {
+ self.run_command("consume", value as u8).and_then(|_| self.expect_ok())
+ }
+
+ /// Set crossfade time in seconds
+ pub fn crossfade<T: ToSeconds>(&mut self, value: T) -> Result<()> {
+ self.run_command("crossfade", value.to_seconds()).and_then(|_| self.expect_ok())
+ }
+
+ /// Set mixramp level in dB
+ pub fn mixrampdb(&mut self, value: f32) -> Result<()> {
+ self.run_command("mixrampdb", value).and_then(|_| self.expect_ok())
+ }
+
+ /// Set mixramp delay in seconds
+ pub fn mixrampdelay<T: ToSeconds>(&mut self, value: T) -> Result<()> {
+ self.run_command("mixrampdelay", value.to_seconds()).and_then(|_| self.expect_ok())
+ }
+
+ /// Set replay gain mode
+ pub fn replaygain(&mut self, gain: ReplayGain) -> Result<()> {
+ self.run_command("replay_gain_mode", gain).and_then(|_| self.expect_ok())
+ }
+ // }}}
+
+ // Playback control {{{
+ /// Start playback
+ pub fn play(&mut self) -> Result<()> {
+ self.run_command("play", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// Start playback from given song in a queue
+ pub fn switch<T: ToQueuePlace>(&mut self, place: T) -> Result<()> {
+ let command = if T::is_id() { "playid" } else { "play" };
+ self.run_command(command, place.to_place()).and_then(|_| self.expect_ok())
+ }
+
+ /// Switch to a next song in queue
+ #[cfg_attr(feature = "cargo-clippy", allow(should_implement_trait))]
+ pub fn next(&mut self) -> Result<()> {
+ self.run_command("next", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// Switch to a previous song in queue
+ pub fn prev(&mut self) -> Result<()> {
+ self.run_command("previous", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// Stop playback
+ pub fn stop(&mut self) -> Result<()> {
+ self.run_command("stop", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// Toggle pause state
+ pub fn toggle_pause(&mut self) -> Result<()> {
+ self.run_command("pause", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// Set pause state
+ pub fn pause(&mut self, value: bool) -> Result<()> {
+ self.run_command("pause", value as u8).and_then(|_| self.expect_ok())
+ }
+
+ /// Seek to a given place (in seconds) in a given song
+ pub fn seek<T: ToSeconds, P: ToQueuePlace>(&mut self, place: P, pos: T) -> Result<()> {
+ let command = if P::is_id() { "seekid" } else { "seek" };
+ self.run_command(command, (place.to_place(), pos.to_seconds())).and_then(|_| self.expect_ok())
+ }
+
+ /// Seek to a given place (in seconds) in the current song
+ pub fn rewind<T: ToSeconds>(&mut self, pos: T) -> Result<()> {
+ self.run_command("seekcur", pos.to_seconds()).and_then(|_| self.expect_ok())
+ }
+ // }}}
+
+ // Queue control {{{
+ /// List given song or range of songs in a play queue
+ pub fn songs<T: ToQueueRangeOrPlace>(&mut self, pos: T) -> Result<Vec<Song>> {
+ let command = if T::is_id() {
+ "playlistid"
+ } else {
+ "playlistinfo"
+ };
+ self.run_command(command, pos.to_range()).and_then(|_| self.read_structs("file"))
+ }
+
+ /// List all songs in a play queue
+ pub fn queue(&mut self) -> Result<Vec<Song>> {
+ self.run_command("playlistinfo", ()).and_then(|_| self.read_structs("file"))
+ }
+
+ /// Get current playing song
+ pub fn currentsong(&mut self) -> Result<Option<Song>> {
+ self.run_command("currentsong", ()).and_then(|_| self.read_struct::<Song>()).map(|s| if s.place.is_none() { None } else { Some(s) })
+ }
+
+ /// Clear current queue
+ pub fn clear(&mut self) -> Result<()> {
+ self.run_command("clear", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// List all changes in a queue since given version
+ pub fn changes(&mut self, version: u32) -> Result<Vec<Song>> {
+ self.run_command("plchanges", version).and_then(|_| self.read_structs("file"))
+ }
+
+ /// Append a song into a queue
+ pub fn push_str(&mut self, path: String) -> Result<Id> {
+ self.run_command("addid", path).and_then(|_| self.read_field("Id")).map(Id)
+ }
+
+ /// Append a song into a queue
+ pub fn push<P: ToSongPath>(&mut self, path: P) -> Result<Id> {
+ self.run_command("addid", path).and_then(|_| self.read_field("Id")).map(Id)
+ }
+
+ /// Insert a song into a given position in a queue
+ pub fn insert<P: ToSongPath>(&mut self, path: P, pos: usize) -> Result<usize> {
+ self.run_command("addid", (path, pos)).and_then(|_| self.read_field("Id"))
+ }
+
+ /// Delete a song (at some position) or several songs (in a range) from a queue
+ pub fn delete<T: ToQueueRangeOrPlace>(&mut self, pos: T) -> Result<()> {
+ let command = if T::is_id() { "deleteid" } else { "delete" };
+ self.run_command(command, pos.to_range()).and_then(|_| self.expect_ok())
+ }
+
+ /// Move a song (at a some position) or several songs (in a range) to other position in queue
+ pub fn shift<T: ToQueueRangeOrPlace>(&mut self, from: T, to: usize) -> Result<()> {
+ let command = if T::is_id() { "moveid" } else { "move" };
+ self.run_command(command, (from.to_range(), to)).and_then(|_| self.expect_ok())
+ }
+
+ /// Swap to songs in a queue
+ pub fn swap<T: ToQueuePlace>(&mut self, one: T, two: T) -> Result<()> {
+ let command = if T::is_id() { "swapid" } else { "swap" };
+ self.run_command(command, (one.to_place(), two.to_place())).and_then(|_| self.expect_ok())
+ }
+
+ /// Shuffle queue in a given range (use `..` to shuffle full queue)
+ pub fn shuffle<T: ToQueueRange>(&mut self, range: T) -> Result<()> {
+ self.run_command("shuffle", range.to_range()).and_then(|_| self.expect_ok())
+ }
+
+ /// Set song priority in a queue
+ pub fn priority<T: ToQueueRangeOrPlace>(&mut self, pos: T, prio: u8) -> Result<()> {
+ let command = if T::is_id() { "prioid" } else { "prio" };
+ self.run_command(command, (prio, pos.to_range())).and_then(|_| self.expect_ok())
+ }
+
+ /// Set song range (in seconds) to play
+ ///
+ /// Doesn't work for currently playing song.
+ pub fn range<T: ToSongId, R: ToSongRange>(&mut self, song: T, range: R) -> Result<()> {
+ self.run_command("rangeid", (song.to_song_id(), range.to_range())).and_then(|_| self.expect_ok())
+ }
+
+ /// Add tag to a song
+ pub fn tag<T: ToSongId>(&mut self, song: T, tag: &str, value: &str) -> Result<()> {
+ self.run_command("addtagid", (song.to_song_id(), tag, value)).and_then(|_| self.expect_ok())
+ }
+
+ /// Delete tag from a song
+ pub fn untag<T: ToSongId>(&mut self, song: T, tag: &str) -> Result<()> {
+ self.run_command("cleartagid", (song.to_song_id(), tag)).and_then(|_| self.expect_ok())
+ }
+ // }}}
+
+ // Connection settings {{{
+ /// Just pings MPD server, does nothing
+ pub fn ping(&mut self) -> Result<()> {
+ self.run_command("ping", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// Close MPD connection
+ pub fn close(&mut self) -> Result<()> {
+ self.run_command("close", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// Kill MPD server
+ pub fn kill(&mut self) -> Result<()> {
+ self.run_command("kill", ()).and_then(|_| self.expect_ok())
+ }
+
+ /// Login to MPD server with given password
+ pub fn login(&mut self, password: &str) -> Result<()> {
+ self.run_command("password", password).and_then(|_| self.expect_ok())
+ }
+ // }}}
+
+ // Playlist methods {{{
+ /// List all playlists
+ pub fn playlists(&mut self) -> Result<Vec<Playlist>> {
+ self.run_command("listplaylists", ()).and_then(|_| self.read_structs("playlist"))
+ }
+
+ /// List all songs in a playlist
+ pub fn playlist<N: ToPlaylistName>(&mut self, name: N) -> Result<Vec<Song>> {
+ self.run_command("listplaylistinfo", name.to_name()).and_then(|_| self.read_structs("file"))
+ }
+
+ /// Load playlist into queue
+ ///
+ /// You can give either full range (`..`) to load all songs in a playlist,
+ /// or some partial range to load only part of playlist.
+ pub fn load<T: ToQueueRange, N: ToPlaylistName>(&mut self, name: N, range: T) -> Result<()> {
+ self.run_command("load", (name.to_name(), range.to_range())).and_then(|_| self.expect_ok())
+ }
+
+ /// Save current queue into playlist
+ ///
+ /// If playlist with given name doesn't exist, create new one.
+ pub fn save<N: ToPlaylistName>(&mut self, name: N) -> Result<()> {
+ self.run_command("save", name.to_name()).and_then(|_| self.expect_ok())
+ }
+
+ /// Rename playlist
+ pub fn pl_rename<N: ToPlaylistName>(&mut self, name: N, newname: &str) -> Result<()> {
+ self.run_command("rename", (name.to_name(), newname)).and_then(|_| self.expect_ok())
+ }
+
+ /// Clear playlist
+ pub fn pl_clear<N: ToPlaylistName>(&mut self, name: N) -> Result<()> {
+ self.run_command("playlistclear", name.to_name()).and_then(|_| self.expect_ok())
+ }
+
+ /// Delete playlist
+ pub fn pl_remove<N: ToPlaylistName>(&mut self, name: N) -> Result<()> {
+ self.run_command("rm", name.to_name()).and_then(|_| self.expect_ok())
+ }
+
+ /// Add new songs to a playlist
+ pub fn pl_push<N: ToPlaylistName, P: ToSongPath>(&mut self, name: N, path: P) -> Result<()> {
+ self.run_command("playlistadd", (name.to_name(), path)).and_then(|_| self.expect_ok())
+ }
+
+ /// Delete a song at a given position in a playlist
+ pub fn pl_delete<N: ToPlaylistName>(&mut self, name: N, pos: u32) -> Result<()> {
+ self.run_command("playlistdelete", (name.to_name(), pos)).and_then(|_| self.expect_ok())
+ }
+
+ /// Move song in a playlist from one position into another
+ pub fn pl_shift<N: ToPlaylistName>(&mut self, name: N, from: u32, to: u32) -> Result<()> {
+ self.run_command("playlistmove", (name.to_name(), from, to)).and_then(|_| self.expect_ok())
+ }
+ // }}}
+
+ // Database methods {{{
+ /// Run database rescan, i.e. remove non-existing files from DB
+ /// as well as add new files to DB
+ pub fn rescan(&mut self) -> Result<u32> {
+ self.run_command("rescan", ()).and_then(|_| self.read_field("updating_db"))
+ }
+
+ /// Run database update, i.e. remove non-existing files from DB
+ pub fn update(&mut self) -> Result<u32> {
+ self.run_command("update", ()).and_then(|_| self.read_field("updating_db"))
+ }
+ // }}}
+
+ // Database search {{{
+ // TODO: count tag needle [...] [group] [grouptag], find type what [...] [window start:end]
+ // TODO: search type what [...] [window start:end], searchadd type what [...]
+ // TODO: listallinfo [uri], listfiles [uri]
+ // TODO: list type [filtertype] [filterwhat] [...] [group] [grouptype] [...]
+ // TODO: searchaddpl name type what [...]
+
+ /// Find songs matching Query conditions.
+ pub fn find<W>(&mut self, query: &Query, window: W) -> Result<Vec<Song>>
+ where W: Into<Window>
+ {
+ self.find_generic("find", query, window.into())
+ }
+
+ /// Find album art for file
+ pub fn albumart<P: ToSongPath>(&mut self, path: &P) -> Result<Vec<u8>> {
+ let mut buf = vec![];
+ loop {
+ self.run_command("albumart", (path, &*format!("{}", buf.len())))?;
+ let (_, size) = self.read_pair()?;
+ let (_, bytes) = self.read_pair()?;
+ let mut chunk = self.read_bytes(bytes.parse()?)?;
+ buf.append(&mut chunk);
+ // Read empty newline
+ let _ = self.read_line()?;
+ let result = self.read_line()?;
+ if result != "OK" {
+ return Err(ProtoError::NotOk)?;
+ }
+
+ if size.parse::<usize>()? == buf.len() {
+ break;
+ }
+ }
+ Ok(buf)
+ }
+
+ /// Case-insensitively search for songs matching Query conditions.
+ pub fn search<W>(&mut self, query: &Query, window: W) -> Result<Vec<Song>>
+ where W: Into<Window>
+ {
+ self.find_generic("search", query, window.into())
+ }
+
+ fn find_generic(&mut self, cmd: &str, query: &Query, window: Window) -> Result<Vec<Song>> {
+ self.run_command(cmd, (query, window)).and_then(|_| self.read_structs("file"))
+ }
+
+ /// Lists unique tags values of the specified type for songs matching the given query.
+ // TODO: list type [filtertype] [filterwhat] [...] [group] [grouptype] [...]
+ // It isn't clear if or how `group` works
+ pub fn list(&mut self, term: &Term, query: &Query) -> Result<Vec<String>> {
+ self.run_command("list", (term, query)).and_then(|_| self.read_pairs().map(|p| p.map(|p| p.1)).collect())
+ }
+
+ /// Find all songs in the db that match query and adds them to current playlist.
+ pub fn findadd(&mut self, query: &Query) -> Result<()> {
+ self.run_command("findadd", query).and_then(|_| self.expect_ok())
+ }
+
+ /// Lists the contents of a directory.
+ pub fn lsinfo<P: ToSongPath>(&mut self, path: P) -> Result<Song> {
+ self.run_command("lsinfo", path).and_then(|_| self.read_struct())
+ }
+
+ /// Returns raw metadata for file
+ pub fn readcomments<'a, P: ToSongPath>(&'a mut self, path: P) -> Result<impl Iterator<Item = Result<(String, String)>> + 'a> {
+ self.run_command("readcomments", path)?;
+ Ok(self.read_pairs())
+ }
+
+ // }}}
+
+ // Output methods {{{
+ /// List all outputs
+ pub fn outputs(&mut self) -> Result<Vec<Output>> {
+ self.run_command("outputs", ()).and_then(|_| self.read_structs("outputid"))
+ }
+
+ /// Set given output enabled state
+ pub fn output<T: ToOutputId>(&mut self, id: T, state: bool) -> Result<()> {
+ if state {
+ self.out_enable(id)
+ } else {
+ self.out_disable(id)
+ }
+ }
+
+ /// Disable given output
+ pub fn out_disable<T: ToOutputId>(&mut self, id: T) -> Result<()> {
+ self.run_command("disableoutput", id.to_output_id()).and_then(|_| self.expect_ok())
+ }
+
+ /// Enable given output
+ pub fn out_enable<T: ToOutputId>(&mut self, id: T) -> Result<()> {
+ self.run_command("enableoutput", id.to_output_id()).and_then(|_| self.expect_ok())
+ }
+
+ /// Toggle given output
+ pub fn out_toggle<T: ToOutputId>(&mut self, id: T) -> Result<()> {
+ self.run_command("toggleoutput", id.to_output_id()).and_then(|_| self.expect_ok())
+ }
+ // }}}
+
+ // Reflection methods {{{
+ /// Get current music directory
+ pub fn music_directory(&mut self) -> Result<String> {
+ self.run_command("config", ()).and_then(|_| self.read_field("music_directory"))
+ }
+
+ /// List all available commands
+ pub fn commands(&mut self) -> Result<Vec<String>> {
+ self.run_command("commands", ()).and_then(|_| self.read_list("command"))
+ }
+
+ /// List all forbidden commands
+ pub fn notcommands(&mut self) -> Result<Vec<String>> {
+ self.run_command("notcommands", ()).and_then(|_| self.read_list("command"))
+ }
+
+ /// List all available URL handlers
+ pub fn urlhandlers(&mut self) -> Result<Vec<String>> {
+ self.run_command("urlhandlers", ()).and_then(|_| self.read_list("handler"))
+ }
+
+ /// List all supported tag types
+ pub fn tagtypes(&mut self) -> Result<Vec<String>> {
+ self.run_command("tagtypes", ()).and_then(|_| self.read_list("tagtype"))
+ }
+
+ /// List all available decoder plugins
+ pub fn decoders(&mut self) -> Result<Vec<Plugin>> {
+ self.run_command("decoders", ()).and_then(|_| self.read_struct())
+ }
+ // }}}
+
+ // Messaging {{{
+ /// List all channels available for current connection
+ pub fn channels(&mut self) -> Result<Vec<Channel>> {
+ self.run_command("channels", ()).and_then(|_| self.read_list("channel")).map(|v| {
+ v.into_iter()
+ .map(|b| unsafe {
+ Channel::new_unchecked(b)
+ })
+ .collect()
+ })
+ }
+
+ /// Read queued messages from subscribed channels
+ pub fn readmessages(&mut self) -> Result<Vec<Message>> {
+ self.run_command("readmessages", ()).and_then(|_| self.read_structs("channel"))
+ }
+
+ /// Send a message to a channel
+ pub fn sendmessage(&mut self, channel: Channel, message: &str) -> Result<()> {
+ self.run_command("sendmessage", (channel, message)).and_then(|_| self.expect_ok())
+ }
+
+ /// Subscribe to a channel
+ pub fn subscribe(&mut self, channel: Channel) -> Result<()> {
+ self.run_command("subscribe", channel).and_then(|_| self.expect_ok())
+ }
+
+ /// Unsubscribe to a channel
+ pub fn unsubscribe(&mut self, channel: Channel) -> Result<()> {
+ self.run_command("unsubscribe", channel).and_then(|_| self.expect_ok())
+ }
+ // }}}
+
+ // Mount methods {{{
+ /// List all (virtual) mounts
+ ///
+ /// These mounts exist inside MPD process only, thus they can work without root permissions.
+ pub fn mounts(&mut self) -> Result<Vec<Mount>> {
+ self.run_command("listmounts", ()).and_then(|_| self.read_structs("mount"))
+ }
+
+ /// List all network neighbors, which can be potentially mounted
+ pub fn neighbors(&mut self) -> Result<Vec<Neighbor>> {
+ self.run_command("listneighbors", ()).and_then(|_| self.read_structs("neighbor"))
+ }
+
+ /// Mount given neighbor to a mount point
+ ///
+ /// The mount exists inside MPD process only, thus it can work without root permissions.
+ pub fn mount(&mut self, path: &str, uri: &str) -> Result<()> {
+ self.run_command("mount", (path, uri)).and_then(|_| self.expect_ok())
+ }
+
+ /// Unmount given active (virtual) mount
+ ///
+ /// The mount exists inside MPD process only, thus it can work without root permissions.
+ pub fn unmount(&mut self, path: &str) -> Result<()> {
+ self.run_command("unmount", path).and_then(|_| self.expect_ok())
+ }
+ // }}}
+
+ // Sticker methods {{{
+ /// Show sticker value for a given object, identified by type and uri
+ pub fn sticker(&mut self, typ: &str, uri: &str, name: &str) -> Result<String> {
+ self.run_command("sticker get", (typ, uri, name))
+ // TODO: This should parse to a `Sticker` type.
+ .and_then(|_| self.read_field::<Sticker>("sticker"))
+ .map(|s| s.value)
+ }
+
+ /// Set sticker value for a given object, identified by type and uri
+ pub fn set_sticker(&mut self, typ: &str, uri: &str, name: &str, value: &str) -> Result<()> {
+ self.run_command("sticker set", (typ, uri, name, value)).and_then(|_| self.expect_ok())
+ }
+
+ /// Delete sticker from a given object, identified by type and uri
+ pub fn delete_sticker(&mut self, typ: &str, uri: &str, name: &str) -> Result<()> {
+ self.run_command("sticker delete", (typ, uri, name)).and_then(|_| self.expect_ok())
+ }
+
+ /// Remove all stickers from a given object, identified by type and uri
+ pub fn clear_stickers(&mut self, typ: &str, uri: &str) -> Result<()> {
+ self.run_command("sticker delete", (typ, uri)).and_then(|_| self.expect_ok())
+ }
+
+ /// List all stickers from a given object, identified by type and uri
+ pub fn stickers(&mut self, typ: &str, uri: &str) -> Result<Vec<String>> {
+ self.run_command("sticker list", (typ, uri))
+ .and_then(|_| self.read_list("sticker"))
+ .map(|v| v.into_iter().map(|b| b.splitn(2, '=').nth(1).map(|s| s.to_owned()).unwrap()).collect())
+ }
+
+ /// List all stickers from a given object in a map, identified by type and uri
+ pub fn stickers_map(&mut self, typ: &str, uri: &str) -> Result<HashMap<String, String>> {
+ self.run_command("sticker list", (typ, uri))
+ .and_then(|_| self.read_list("sticker"))
+ .map(|v| v.into_iter().map(|b| {
+ let mut iter = b.splitn(2, '=');
+
+ (iter.next().unwrap().to_owned(), iter.next().unwrap().to_owned())
+ }).collect())
+ }
+
+ /// List all (file, sticker) pairs for sticker name and objects of given type
+ /// from given directory (identified by uri)
+ pub fn find_sticker(&mut self, typ: &str, uri: &str, name: &str) -> Result<Vec<(String, String)>> {
+ self.run_command("sticker find", (typ, uri, name))
+ .and_then(|_| {
+ self.read_pairs()
+ .split("file")
+ .map(|rmap| {
+ rmap.map(|mut map| {
+ (map.remove("file").unwrap(),
+ map.remove("sticker").and_then(|s| s.splitn(2, '=').nth(1).map(|s| s.to_owned())).unwrap())
+ })
+ })
+ .collect()
+ })
+ }
+
+ /// List all files of a given type under given directory (identified by uri)
+ /// with a tag set to given value
+ pub fn find_sticker_eq(&mut self, typ: &str, uri: &str, name: &str, value: &str) -> Result<Vec<String>> {
+ self.run_command("sticker find", (typ, uri, name, value)).and_then(|_| self.read_list("file"))
+ }
+ // }}}
+}
+
+// Helper methods {{{
+impl<S: Read + Write> Proto for Client<S> {
+ type Stream = S;
+
+ fn read_bytes(&mut self, bytes: usize) -> Result<Vec<u8>> {
+ let mut buf = Vec::with_capacity(bytes);
+ let mut chunk = (&mut self.socket).take(bytes as u64);
+ chunk.read_to_end(&mut buf)?;
+ Ok(buf)
+ }
+
+ fn read_line(&mut self) -> Result<String> {
+ let mut buf = Vec::new();
+ self.socket.read_until(b'\n', &mut buf)?;
+ if buf.ends_with(&[b'\n']) {
+ buf.pop();
+ }
+ let str = String::from_utf8(buf)
+ .map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "stream did not contain valid UTF-8"))?;
+ Ok(str)
+ }
+
+ fn read_pairs(&mut self) -> Pairs<Lines<&mut BufStream<S>>> {
+ Pairs((&mut self.socket).lines())
+ }
+
+ fn read_pair(&mut self) -> Result<(String, String)> {
+ let line = self.read_line()?;
+ let mut split = line.split(": ");
+ let key = split.next().ok_or(ParseError::BadPair)?;
+ let val = split.next().ok_or(ParseError::BadPair)?;
+ Ok((key.to_string(), val.to_string()))
+ }
+
+ fn run_command<I>(&mut self, command: &str, arguments: I) -> Result<()>
+ where I: ToArguments
+ {
+ self.socket
+ .write_all(command.as_bytes())
+ .and_then(|_| arguments.to_arguments(&mut |arg| write!(self.socket, " {}", Quoted(arg))))
+ .and_then(|_| self.socket.write(&[0x0a]))
+ .and_then(|_| self.socket.flush())
+ .map_err(From::from)
+ }
+}
+// }}}
+
+// }}}
diff --git a/vendored/mpd/src/convert.rs b/vendored/mpd/src/convert.rs
new file mode 100644
index 0000000..7e554bb
--- /dev/null
+++ b/vendored/mpd/src/convert.rs
@@ -0,0 +1,316 @@
+#![allow(missing_docs)]
+//! These are inner traits to support methods overloading for the `Client`
+
+use crate::error::Error;
+use crate::output::Output;
+use crate::playlist::Playlist;
+use crate::proto::ToArguments;
+use crate::song::{self, Id, Song};
+use std::collections::BTreeMap;
+use std::ops::{Range, RangeFrom, RangeFull, RangeTo};
+
+use std::time::Duration;
+
+#[doc(hidden)]
+pub trait FromMap: Sized {
+ fn from_map(map: BTreeMap<String, String>) -> Result<Self, Error>;
+}
+
+#[doc(hidden)]
+pub trait FromIter: Sized {
+ fn from_iter<I: Iterator<Item = Result<(String, String), Error>>>(iter: I) -> Result<Self, Error>;
+}
+
+impl<T: FromIter> FromMap for T {
+ fn from_map(map: BTreeMap<String, String>) -> Result<Self, Error> {
+ FromIter::from_iter(map.into_iter().map(Ok))
+ }
+}
+
+// Playlist name polymorphisms {{{
+pub trait ToPlaylistName {
+ fn to_name(&self) -> &str;
+}
+
+impl ToPlaylistName for Playlist {
+ fn to_name(&self) -> &str {
+ &*self.name
+ }
+}
+
+impl<'a> ToPlaylistName for &'a Playlist {
+ fn to_name(&self) -> &str {
+ &*self.name
+ }
+}
+
+impl<'a> ToPlaylistName for &'a String {
+ fn to_name(&self) -> &str {
+ self
+ }
+}
+
+impl<'a> ToPlaylistName for &'a str {
+ fn to_name(&self) -> &str {
+ *self
+ }
+}
+
+impl ToPlaylistName for str {
+ fn to_name(&self) -> &str {
+ self
+ }
+}
+
+impl ToPlaylistName for String {
+ fn to_name(&self) -> &str {
+ &*self
+ }
+}
+// }}}
+
+// Seconds polymorphisms {{{
+pub trait ToSeconds {
+ fn to_seconds(self) -> f64;
+}
+
+impl ToSeconds for i64 {
+ fn to_seconds(self) -> f64 {
+ self as f64
+ }
+}
+
+impl ToSeconds for f64 {
+ fn to_seconds(self) -> f64 {
+ self
+ }
+}
+
+impl ToSeconds for Duration {
+ fn to_seconds(self) -> f64 {
+ self.as_secs_f64()
+ }
+}
+// }}}
+
+// Queue place polymorphisms {{{
+
+pub trait IsId {
+ fn is_id() -> bool {
+ false
+ }
+}
+
+pub trait ToQueueRangeOrPlace: IsId {
+ fn to_range(self) -> String;
+}
+
+pub trait ToQueueRange {
+ fn to_range(self) -> String;
+}
+
+impl<T: ToQueuePlace> ToQueueRangeOrPlace for T {
+ fn to_range(self) -> String {
+ format!("{}", self.to_place())
+ }
+}
+
+impl ToQueueRange for Range<u32> {
+ fn to_range(self) -> String {
+ format!("{}:{}", self.start, self.end)
+ }
+}
+
+impl ToQueueRangeOrPlace for Range<u32> {
+ fn to_range(self) -> String {
+ ToQueueRange::to_range(self)
+ }
+}
+
+impl ToQueueRange for RangeTo<u32> {
+ fn to_range(self) -> String {
+ format!(":{}", self.end)
+ }
+}
+
+impl ToQueueRangeOrPlace for RangeTo<u32> {
+ fn to_range(self) -> String {
+ ToQueueRange::to_range(self)
+ }
+}
+
+impl ToQueueRange for RangeFrom<u32> {
+ fn to_range(self) -> String {
+ format!("{}:", self.start)
+ }
+}
+
+impl ToQueueRangeOrPlace for RangeFrom<u32> {
+ fn to_range(self) -> String {
+ ToQueueRange::to_range(self)
+ }
+}
+
+impl ToQueueRange for RangeFull {
+ fn to_range(self) -> String {
+ ToQueueRange::to_range(0..)
+ }
+}
+
+impl ToQueueRangeOrPlace for RangeFull {
+ fn to_range(self) -> String {
+ ToQueueRange::to_range(self)
+ }
+}
+
+pub trait ToQueuePlace: IsId {
+ fn to_place(self) -> u32;
+}
+
+impl ToQueuePlace for Id {
+ fn to_place(self) -> u32 {
+ self.0
+ }
+}
+
+impl ToQueuePlace for u32 {
+ fn to_place(self) -> u32 {
+ self
+ }
+}
+
+impl IsId for u32 {}
+impl IsId for Range<u32> {}
+impl IsId for RangeTo<u32> {}
+impl IsId for RangeFrom<u32> {}
+impl IsId for RangeFull {}
+impl IsId for Id {
+ fn is_id() -> bool {
+ true
+ }
+}
+
+pub trait ToSongId {
+ fn to_song_id(&self) -> Id;
+}
+
+impl ToSongId for Song {
+ fn to_song_id(&self) -> Id {
+ self.place.unwrap().id
+ }
+}
+
+impl ToSongId for u32 {
+ fn to_song_id(&self) -> Id {
+ Id(*self)
+ }
+}
+
+impl ToSongId for Id {
+ fn to_song_id(&self) -> Id {
+ *self
+ }
+}
+// }}}
+
+// Output id polymorphisms {{{
+pub trait ToOutputId {
+ fn to_output_id(self) -> u32;
+}
+
+impl ToOutputId for u32 {
+ fn to_output_id(self) -> u32 {
+ self
+ }
+}
+impl ToOutputId for Output {
+ fn to_output_id(self) -> u32 {
+ self.id
+ }
+}
+// }}}
+
+// Song play range polymorphisms {{{
+pub trait ToSongRange {
+ fn to_range(self) -> song::Range;
+}
+
+impl ToSongRange for Range<Duration> {
+ fn to_range(self) -> song::Range {
+ song::Range(self.start, Some(self.end))
+ }
+}
+
+impl ToSongRange for Range<u32> {
+ fn to_range(self) -> song::Range {
+ song::Range(Duration::from_secs(self.start as u64), Some(Duration::from_secs(self.end as u64)))
+ }
+}
+
+impl ToSongRange for RangeFrom<Duration> {
+ fn to_range(self) -> song::Range {
+ song::Range(self.start, None)
+ }
+}
+
+impl ToSongRange for RangeFrom<u32> {
+ fn to_range(self) -> song::Range {
+ song::Range(Duration::from_secs(self.start as u64), None)
+ }
+}
+
+impl ToSongRange for RangeTo<Duration> {
+ fn to_range(self) -> song::Range {
+ song::Range(Duration::from_secs(0), Some(self.end))
+ }
+}
+
+impl ToSongRange for RangeTo<u32> {
+ fn to_range(self) -> song::Range {
+ song::Range(Duration::from_secs(0), Some(Duration::from_secs(self.end as u64)))
+ }
+}
+
+impl ToSongRange for RangeFull {
+ fn to_range(self) -> song::Range {
+ song::Range(Duration::from_secs(0), None)
+ }
+}
+
+impl ToSongRange for song::Range {
+ fn to_range(self) -> song::Range {
+ self
+ }
+}
+
+// }}}
+
+pub trait ToSongPath {
+ fn to_path(&self) -> &str;
+}
+
+impl ToSongPath for Song {
+ fn to_path(&self) -> &str {
+ &self.file
+ }
+}
+
+impl<'a, T: ToSongPath> ToSongPath for &'a T {
+ fn to_path(&self) -> &str {
+ (*self).to_path()
+ }
+}
+
+impl ToSongPath for dyn AsRef<str> {
+ fn to_path(&self) -> &str {
+ self.as_ref()
+ }
+}
+
+impl<T: ToSongPath> ToArguments for T {
+ fn to_arguments<F, E>(&self, f: &mut F) -> Result<(), E>
+ where F: FnMut(&str) -> Result<(), E>
+ {
+ self.to_path().to_arguments(f)
+ }
+}
diff --git a/vendored/mpd/src/error.rs b/vendored/mpd/src/error.rs
new file mode 100644
index 0000000..b58f8dc
--- /dev/null
+++ b/vendored/mpd/src/error.rs
@@ -0,0 +1,394 @@
+//! This module defines different errors occurring during communication with MPD.
+//!
+//! There're following kinds of possible errors:
+//!
+//! - IO errors (due to network communication failures),
+//! - parsing errors (because of bugs in parsing server response),
+//! - protocol errors (happen when we get unexpected data from server,
+//! mostly because protocol version mismatch, network data corruption
+//! or just bugs in the client),
+//! - server errors (run-time errors coming from MPD due to some MPD
+//! errors, like database failures or sound problems)
+//!
+//! This module defines all necessary infrastructure to represent these kinds or errors.
+
+use std::convert::From;
+use std::error::Error as StdError;
+use std::fmt;
+use std::io::Error as IoError;
+use std::num::{ParseFloatError, ParseIntError};
+use std::result;
+use std::str::FromStr;
+use std::string::ParseError as StringParseError;
+use time::ParseError as TimeParseError;
+use time::ConversionRangeError as TimeConversionRangeError;
+
+// Server errors {{{
+/// Server error codes, as defined in [libmpdclient](http://www.musicpd.org/doc/libmpdclient/protocol_8h_source.html)
+#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
+pub enum ErrorCode {
+ /// not a list
+ NotList = 1,
+ /// bad command arguments
+ Argument = 2,
+ /// invalid password
+ Password = 3,
+ /// insufficient permissions
+ Permission = 4,
+ /// unknown command
+ UnknownCmd = 5,
+ /// object doesn't exist
+ NoExist = 50,
+ /// maximum playlist size exceeded
+ PlaylistMax = 51,
+ /// general system error
+ System = 52,
+ /// error loading playlist
+ PlaylistLoad = 53,
+ /// update database is already in progress
+ UpdateAlready = 54,
+ /// player synchronization error
+ PlayerSync = 55,
+ /// object already exists
+ Exist = 56,
+}
+
+impl FromStr for ErrorCode {
+ type Err = ParseError;
+ fn from_str(s: &str) -> result::Result<ErrorCode, ParseError> {
+ use self::ErrorCode::*;
+ match s.parse()? {
+ 1 => Ok(NotList),
+ 2 => Ok(Argument),
+ 3 => Ok(Password),
+ 4 => Ok(Permission),
+ 5 => Ok(UnknownCmd),
+
+ 50 => Ok(NoExist),
+ 51 => Ok(PlaylistMax),
+ 52 => Ok(System),
+ 53 => Ok(PlaylistLoad),
+ 54 => Ok(UpdateAlready),
+ 55 => Ok(PlayerSync),
+ 56 => Ok(Exist),
+
+ v => Err(ParseError::BadErrorCode(v)),
+ }
+ }
+}
+
+impl StdError for ErrorCode { }
+
+impl fmt::Display for ErrorCode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::ErrorCode::*;
+
+ let desc = match *self {
+ NotList => "not a list",
+ Argument => "invalid argument",
+ Password => "invalid password",
+ Permission => "permission",
+ UnknownCmd => "unknown command",
+
+ NoExist => "item not found",
+ PlaylistMax => "playlist overflow",
+ System => "system",
+ PlaylistLoad => "playload load",
+ UpdateAlready => "already updating",
+ PlayerSync => "player syncing",
+ Exist => "already exists",
+ };
+
+ f.write_str(desc)
+ }
+}
+
+/// Server error
+#[derive(Debug, Clone, PartialEq)]
+pub struct ServerError {
+ /// server error code
+ pub code: ErrorCode,
+ /// command position in command list
+ pub pos: u16,
+ /// command name, which caused the error
+ pub command: String,
+ /// detailed error description
+ pub detail: String,
+}
+
+impl StdError for ServerError { }
+
+impl fmt::Display for ServerError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{} error (`{}') at {}", self.code, self.detail, self.pos)
+ }
+}
+
+impl FromStr for ServerError {
+ type Err = ParseError;
+ fn from_str(s: &str) -> result::Result<ServerError, ParseError> {
+ // ACK [<code>@<index>] {<command>} <description>
+ if s.starts_with("ACK [") {
+ let s = &s[5..];
+ if let (Some(atsign), Some(right_bracket)) = (s.find('@'), s.find(']')) {
+ match (s[..atsign].parse(), s[atsign + 1..right_bracket].parse()) {
+ (Ok(code), Ok(pos)) => {
+ let s = &s[right_bracket + 1..];
+ if let (Some(left_brace), Some(right_brace)) = (s.find('{'), s.find('}')) {
+ let command = s[left_brace + 1..right_brace].to_string();
+ let detail = s[right_brace + 1..].trim().to_string();
+ Ok(ServerError {
+ code: code,
+ pos: pos,
+ command: command,
+ detail: detail,
+ })
+ } else {
+ Err(ParseError::NoMessage)
+ }
+ }
+ (Err(_), _) => Err(ParseError::BadCode),
+ (_, Err(_)) => Err(ParseError::BadPos),
+ }
+ } else {
+ Err(ParseError::NoCodePos)
+ }
+ } else {
+ Err(ParseError::NotAck)
+ }
+ }
+}
+// }}}
+
+// Error {{{
+/// Main error type, describing all possible error classes for the crate
+#[derive(Debug)]
+pub enum Error {
+ /// IO errors (low-level network communication failures)
+ Io(IoError),
+ /// parsing errors (unknown data came from server)
+ Parse(ParseError),
+ /// protocol errors (e.g. missing required fields in server response, no handshake message etc.)
+ Proto(ProtoError),
+ /// server errors (a.k.a. `ACK` responses from server)
+ Server(ServerError),
+}
+
+/// Shortcut type for MPD results
+pub type Result<T> = result::Result<T, Error>;
+
+impl StdError for Error {
+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
+ match *self {
+ Error::Io(ref err) => Some(err),
+ Error::Parse(ref err) => Some(err),
+ Error::Proto(ref err) => Some(err),
+ Error::Server(ref err) => Some(err),
+ }
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Error::Io(ref err) => err.fmt(f),
+ Error::Parse(ref err) => err.fmt(f),
+ Error::Proto(ref err) => err.fmt(f),
+ Error::Server(ref err) => err.fmt(f),
+ }
+ }
+}
+
+impl From<IoError> for Error {
+ fn from(e: IoError) -> Error {
+ Error::Io(e)
+ }
+}
+impl From<ParseError> for Error {
+ fn from(e: ParseError) -> Error {
+ Error::Parse(e)
+ }
+}
+impl From<ProtoError> for Error {
+ fn from(e: ProtoError) -> Error {
+ Error::Proto(e)
+ }
+}
+impl From<ParseIntError> for Error {
+ fn from(e: ParseIntError) -> Error {
+ Error::Parse(ParseError::BadInteger(e))
+ }
+}
+impl From<ParseFloatError> for Error {
+ fn from(e: ParseFloatError) -> Error {
+ Error::Parse(ParseError::BadFloat(e))
+ }
+}
+impl From<TimeParseError> for Error {
+ fn from(e: TimeParseError) -> Error {
+ Error::Parse(ParseError::BadTime(e))
+ }
+}
+
+impl From<ServerError> for Error {
+ fn from(e: ServerError) -> Error {
+ Error::Server(e)
+ }
+}
+
+impl From<TimeConversionRangeError> for Error {
+ fn from(e: TimeConversionRangeError) -> Error {
+ Error::Parse(ParseError::BadTimeConversion(e))
+ }
+}
+// }}}
+
+// Parse errors {{{
+/// Parsing error kinds
+#[derive(Debug, Clone, PartialEq)]
+pub enum ParseError {
+ /// invalid integer
+ BadInteger(ParseIntError),
+ /// invalid float
+ BadFloat(ParseFloatError),
+ /// some other invalid value
+ BadValue(String),
+ /// date/time parsing error
+ BadTime(TimeParseError),
+ /// date/time to duration (Unix time) conversion error
+ BadTimeConversion(TimeConversionRangeError),
+ /// invalid version format (should be x.y.z)
+ BadVersion,
+ /// the response is not an `ACK` (not an error)
+ /// (this is not actually an error, just a marker
+ /// to try to parse the response as some other type,
+ /// like a pair)
+ NotAck,
+ /// invalid pair
+ BadPair,
+ /// invalid error code in `ACK` response
+ BadCode,
+ /// invalid command position in `ACK` response
+ BadPos,
+ /// missing command position and/or error code in `ACK` response
+ NoCodePos,
+ /// missing error message in `ACK` response
+ NoMessage,
+ /// missing bitrate in audio format field
+ NoRate,
+ /// missing bits in audio format field
+ NoBits,
+ /// missing channels in audio format field
+ NoChans,
+ /// invalid bitrate in audio format field
+ BadRate(ParseIntError),
+ /// invalid bits in audio format field
+ BadBits(ParseIntError),
+ /// invalid channels in audio format field
+ BadChans(ParseIntError),
+ /// unknown state in state status field
+ BadState(String),
+ /// unknown error code in `ACK` response
+ BadErrorCode(usize),
+}
+
+impl StdError for ParseError { }
+
+impl fmt::Display for ParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::ParseError::*;
+
+ let desc = match *self {
+ BadInteger(_) => "invalid integer",
+ BadFloat(_) => "invalid float",
+ BadValue(_) => "invalid value",
+ BadTime(_) => "invalid date/time",
+ BadTimeConversion(_) => "invalid date/time conversion",
+ BadVersion => "invalid version",
+ NotAck => "not an ACK",
+ BadPair => "invalid pair",
+ BadCode => "invalid code",
+ BadPos => "invalid position",
+ NoCodePos => "missing code and position",
+ NoMessage => "missing position",
+ NoRate => "missing audio format rate",
+ NoBits => "missing audio format bits",
+ NoChans => "missing audio format channels",
+ BadRate(_) => "invalid audio format rate",
+ BadBits(_) => "invalid audio format bits",
+ BadChans(_) => "invalid audio format channels",
+ BadState(_) => "invalid playing state",
+ BadErrorCode(_) => "unknown error code",
+ };
+
+ write!(f, "{}", desc)
+ }
+}
+
+impl From<TimeParseError> for ParseError {
+ fn from(e: TimeParseError) -> ParseError {
+ ParseError::BadTime(e)
+ }
+}
+
+impl From<TimeConversionRangeError> for ParseError {
+ fn from(e: TimeConversionRangeError) -> ParseError {
+ ParseError::BadTimeConversion(e)
+ }
+}
+
+impl From<ParseIntError> for ParseError {
+ fn from(e: ParseIntError) -> ParseError {
+ ParseError::BadInteger(e)
+ }
+}
+
+impl From<ParseFloatError> for ParseError {
+ fn from(e: ParseFloatError) -> ParseError {
+ ParseError::BadFloat(e)
+ }
+}
+
+impl From<StringParseError> for ParseError {
+ fn from(e: StringParseError) -> ParseError {
+ match e {}
+ }
+}
+// }}}
+
+// Protocol errors {{{
+/// Protocol errors
+///
+/// They usually occur when server violate expected command response format,
+/// like missing fields in answer to some command, missing closing `OK`
+/// line after data stream etc.
+#[derive(Debug, Clone, PartialEq)]
+pub enum ProtoError {
+ /// `OK` was expected, but it was missing
+ NotOk,
+ /// a data pair was expected
+ NotPair,
+ /// invalid handshake banner received
+ BadBanner,
+ /// expected some field, but it was missing
+ NoField(&'static str),
+ /// expected sticker value, but didn't find it
+ BadSticker,
+}
+
+impl StdError for ProtoError { }
+
+impl fmt::Display for ProtoError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let desc = match *self {
+ ProtoError::NotOk => "OK expected",
+ ProtoError::NotPair => "pair expected",
+ ProtoError::BadBanner => "banner error",
+ ProtoError::NoField(_) => "missing field",
+ ProtoError::BadSticker => "sticker error",
+ };
+
+ write!(f, "{}", desc)
+ }
+}
+// }}}
diff --git a/vendored/mpd/src/idle.rs b/vendored/mpd/src/idle.rs
new file mode 100644
index 0000000..6cb3881
--- /dev/null
+++ b/vendored/mpd/src/idle.rs
@@ -0,0 +1,176 @@
+//! 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 crate::client::Client;
+use crate::error::{Error, ParseError};
+use crate::proto::Proto;
+
+use std::fmt;
+use std::io::{Read, Write};
+use std::mem::forget;
+use std::str::FromStr;
+
+/// 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 Subsystem {
+ fn to_str(self) -> &'static str {
+ use self::Subsystem::*;
+ 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",
+ }
+ }
+}
+
+impl fmt::Display for Subsystem {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(self.to_str())
+ }
+}
+
+use std::result::Result as StdResult;
+impl<'a> crate::proto::ToArguments for Subsystem {
+ fn to_arguments<F, E>(&self, f: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ f(self.to_str())
+ }
+}
+
+/// "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_list("changed").and_then(|v| v.into_iter().map(|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> {
+ self.run_command("idle", subsystems)?;
+ Ok(IdleGuard(self))
+ }
+}
diff --git a/vendored/mpd/src/lib.rs b/vendored/mpd/src/lib.rs
new file mode 100644
index 0000000..ccc9c01
--- /dev/null
+++ b/vendored/mpd/src/lib.rs
@@ -0,0 +1,66 @@
+#![warn(missing_docs)]
+
+//! MPD client for Rust
+//!
+//! This crate tries to provide idiomatic Rust API for [Music Player Daemon][mpd].
+//! The main entry point to the API is [`Client`](client/struct.Client.html) struct,
+//! and inherent methods of the struct follow [MPD protocol][proto] for most part,
+//! making use of traits to overload different parameters for convenience.
+//!
+//! [mpd]: http://www.musicpd.org/
+//! [proto]: http://www.musicpd.org/doc/protocol/
+//!
+//! # Usage
+//!
+//! ```text
+//! [dependencies]
+//! mpd = "*"
+//! ```
+//!
+//! ```rust,no_run
+//! extern crate mpd;
+//!
+//! use mpd::Client;
+//! use std::net::TcpStream;
+//!
+//! # fn main() {
+//! let mut conn = Client::connect("127.0.0.1:6600").unwrap();
+//! conn.volume(100).unwrap();
+//! conn.load("My Lounge Playlist", ..).unwrap();
+//! conn.play().unwrap();
+//! println!("Status: {:?}", conn.status());
+//! # }
+//! ```
+
+mod macros;
+mod convert;
+pub mod error;
+pub mod version;
+pub mod reply;
+pub mod status;
+pub mod song;
+pub mod output;
+pub mod playlist;
+pub mod plugin;
+pub mod stats;
+pub mod search;
+pub mod message;
+pub mod idle;
+pub mod mount;
+mod sticker;
+
+mod proto;
+pub mod client;
+
+pub use client::Client;
+pub use idle::{Idle, Subsystem};
+pub use message::{Channel, Message};
+pub use mount::{Mount, Neighbor};
+pub use output::Output;
+pub use playlist::Playlist;
+pub use plugin::Plugin;
+pub use search::{Query, Term};
+pub use song::{Id, Song};
+pub use stats::Stats;
+pub use status::{ReplayGain, State, Status};
+pub use version::Version;
diff --git a/vendored/mpd/src/macros.rs b/vendored/mpd/src/macros.rs
new file mode 100644
index 0000000..76690db
--- /dev/null
+++ b/vendored/mpd/src/macros.rs
@@ -0,0 +1,27 @@
+#![macro_use]
+
+macro_rules! get_field_impl {
+ ($op:ident, $map:expr, bool $name:expr) => {
+ $map.$op($name).ok_or(Error::Proto(ProtoError::NoField($name)))
+ .map(|v| v == "1")?
+ };
+ ($op:ident, $map:expr, opt $name:expr) => {
+ $map.$op($name).map(|v| v.parse().map(Some)).unwrap_or(Ok(None))?
+ };
+ ($op:ident, $map:expr, $name:expr) => {
+ $map.$op($name).ok_or(Error::Proto(ProtoError::NoField($name)))
+ .and_then(|v| v.parse().map_err(|e| Error::Parse(From::from(e))))?
+ };
+}
+
+macro_rules! get_field {
+ ($map:expr, bool $name:expr) => { get_field_impl!(get, $map, bool $name) };
+ ($map:expr, opt $name:expr) => { get_field_impl!(get, $map, opt $name) };
+ ($map:expr, $name:expr) => { get_field_impl!(get, $map, $name) }
+}
+
+macro_rules! pop_field {
+ ($map:expr, bool $name:expr) => { get_field_impl!(remove, $map, bool $name) };
+ ($map:expr, opt $name:expr) => { get_field_impl!(remove, $map, opt $name) };
+ ($map:expr, $name:expr) => { get_field_impl!(remove, $map, $name) }
+}
diff --git a/vendored/mpd/src/message.rs b/vendored/mpd/src/message.rs
new file mode 100644
index 0000000..8be122a
--- /dev/null
+++ b/vendored/mpd/src/message.rs
@@ -0,0 +1,73 @@
+//! The module defines structures for MPD client-to-client messaging/subscription protocol
+//!
+//! The MPD client-to-client messaging protocol is fairly easy one, and is based on channels.
+//! Any client can subscribe to arbitrary number of channels, and some other client
+//! can send messages to a channel by name. Then, at some point of time, subscribed
+//! client can read all queued messages for all channels, it was subscribed to.
+//!
+//! Also client can get asynchronous notifications about new messages from subscribed
+//! channels with `idle` command, by waiting for `message` subsystem events.
+
+use crate::convert::FromMap;
+use crate::error::{Error, ProtoError};
+
+use std::collections::BTreeMap;
+use std::fmt;
+
+/// Message
+#[derive(Debug, PartialEq, Clone, RustcEncodable)]
+pub struct Message {
+ /// channel
+ pub channel: Channel,
+ /// message payload
+ pub message: String,
+}
+
+impl FromMap for Message {
+ fn from_map(map: BTreeMap<String, String>) -> Result<Message, Error> {
+ Ok(Message {
+ channel: Channel(map.get("channel").map(|v| v.to_owned()).ok_or(Error::Proto(ProtoError::NoField("channel")))?),
+ message: map.get("message").map(|v| v.to_owned()).ok_or(Error::Proto(ProtoError::NoField("message")))?,
+ })
+ }
+}
+
+/// Channel
+#[derive(Debug, PartialEq, PartialOrd, Clone, RustcEncodable)]
+pub struct Channel(String);
+
+impl fmt::Display for Channel {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(&self.0, f)
+ }
+}
+
+impl Channel {
+ /// Create channel with given name
+ pub fn new(name: &str) -> Option<Channel> {
+ if Channel::is_valid_name(name) {
+ Some(Channel(name.to_owned()))
+ } else {
+ None
+ }
+ }
+
+ /// Create channel with arbitrary name, bypassing name validity checks
+ ///
+ /// Not recommened! Use `new()` method above instead.
+ pub unsafe fn new_unchecked(name: String) -> Channel {
+ Channel(name)
+ }
+
+ /// Check if given name is a valid channel name
+ ///
+ /// Valid channel name can contain only English letters (`A`-`Z`, `a`-`z`),
+ /// numbers (`0`-`9`), underscore, forward slash, dot and colon (`_`, `/`, `.`, `:`)
+ pub fn is_valid_name(name: &str) -> bool {
+ name.bytes()
+ .all(|b| {
+ (0x61 <= b && b <= 0x7a) || (0x41 <= b && b <= 0x5a) || (0x30 <= b && b <= 0x39) ||
+ (b == 0x5f || b == 0x2f || b == 0x2e || b == 0x3a)
+ })
+ }
+}
diff --git a/vendored/mpd/src/mount.rs b/vendored/mpd/src/mount.rs
new file mode 100644
index 0000000..2c9558e
--- /dev/null
+++ b/vendored/mpd/src/mount.rs
@@ -0,0 +1,51 @@
+//! The module describes data structures for MPD (virtual) mounts system
+//!
+//! This mounts has nothing to do with system-wide Unix mounts, as they are
+//! implemented inside MPD only, so they doesn't require root access.
+//!
+//! The MPD mounts are plugin-based, so MPD can mount any resource as
+//! a source of songs for its database (like network shares).
+//!
+//! Possible, but inactive, mounts are named "neighbors" and can be
+//! listed with `neighbors()` method.
+
+use crate::convert::FromMap;
+use crate::error::{Error, ProtoError};
+
+use std::collections::BTreeMap;
+
+/// Mount point
+#[derive(Clone, Debug, PartialEq, RustcEncodable)]
+pub struct Mount {
+ /// mount point name
+ pub name: String,
+ /// mount storage URI
+ pub storage: String,
+}
+
+impl FromMap for Mount {
+ fn from_map(map: BTreeMap<String, String>) -> Result<Mount, Error> {
+ Ok(Mount {
+ name: map.get("mount").map(|s| s.to_owned()).ok_or(Error::Proto(ProtoError::NoField("mount")))?,
+ storage: map.get("storage").map(|s| s.to_owned()).ok_or(Error::Proto(ProtoError::NoField("storage")))?,
+ })
+ }
+}
+
+/// Neighbor
+#[derive(Clone, Debug, PartialEq, RustcEncodable)]
+pub struct Neighbor {
+ /// neighbor name
+ pub name: String,
+ /// neighbor storage URI
+ pub storage: String,
+}
+
+impl FromMap for Neighbor {
+ fn from_map(map: BTreeMap<String, String>) -> Result<Neighbor, Error> {
+ Ok(Neighbor {
+ name: map.get("name").map(|s| s.to_owned()).ok_or(Error::Proto(ProtoError::NoField("name")))?,
+ storage: map.get("neighbor").map(|s| s.to_owned()).ok_or(Error::Proto(ProtoError::NoField("neighbor")))?,
+ })
+ }
+}
diff --git a/vendored/mpd/src/output.rs b/vendored/mpd/src/output.rs
new file mode 100644
index 0000000..d781511
--- /dev/null
+++ b/vendored/mpd/src/output.rs
@@ -0,0 +1,28 @@
+//! The module describes output
+
+
+use crate::convert::FromMap;
+use crate::error::{Error, ProtoError};
+use std::collections::BTreeMap;
+use std::convert::From;
+
+/// Sound output
+#[derive(Clone, Debug, PartialEq, RustcEncodable)]
+pub struct Output {
+ /// id
+ pub id: u32,
+ /// name
+ pub name: String,
+ /// enabled state
+ pub enabled: bool,
+}
+
+impl FromMap for Output {
+ fn from_map(map: BTreeMap<String, String>) -> Result<Output, Error> {
+ Ok(Output {
+ id: get_field!(map, "outputid"),
+ name: map.get("outputname").map(|v| v.to_owned()).ok_or(Error::Proto(ProtoError::NoField("outputname")))?,
+ enabled: get_field!(map, bool "outputenabled"),
+ })
+ }
+}
diff --git a/vendored/mpd/src/playlist.rs b/vendored/mpd/src/playlist.rs
new file mode 100644
index 0000000..3d246c9
--- /dev/null
+++ b/vendored/mpd/src/playlist.rs
@@ -0,0 +1,33 @@
+//! The module defines playlist data structures
+
+use crate::convert::FromMap;
+use crate::error::{Error, ProtoError, ParseError};
+
+use std::collections::BTreeMap;
+use std::convert::TryFrom;
+use std::time::Duration;
+
+/// Playlist
+#[derive(Clone, Debug, PartialEq)]
+pub struct Playlist {
+ /// name
+ pub name: String,
+ /// last modified
+ pub last_mod: Duration,
+}
+
+impl FromMap for Playlist {
+ fn from_map(map: BTreeMap<String, String>) -> Result<Playlist, Error> {
+ Ok(Playlist {
+ name: map.get("playlist").map(|v| v.to_owned()).ok_or(Error::Proto(ProtoError::NoField("playlist")))?,
+ last_mod: map.get("Last-Modified")
+ .ok_or(Error::Proto(ProtoError::NoField("Last-Modified")))
+ .and_then(|v| {
+ let parsed: time::Date = time::parse(&*v, "%Y-%m-%dT%H:%M:%SZ")
+ .map_err(ParseError::BadTime)?;
+ Ok(std::time::Duration::try_from(parsed - time::date!(1970-01-01))
+ .map_err(ParseError::BadTimeConversion)?)
+ })?
+ })
+ }
+}
diff --git a/vendored/mpd/src/plugin.rs b/vendored/mpd/src/plugin.rs
new file mode 100644
index 0000000..c490d02
--- /dev/null
+++ b/vendored/mpd/src/plugin.rs
@@ -0,0 +1,45 @@
+//! The module defines decoder plugin data structures
+
+use crate::convert::FromIter;
+use crate::error::Error;
+
+/// Decoder plugin
+#[derive(Clone, Debug, PartialEq, RustcEncodable)]
+pub struct Plugin {
+ /// name
+ pub name: String,
+ /// supported file suffixes (extensions)
+ pub suffixes: Vec<String>,
+ /// supported MIME-types
+ pub mime_types: Vec<String>,
+}
+
+impl FromIter for Vec<Plugin> {
+ fn from_iter<I: Iterator<Item = Result<(String, String), Error>>>(iter: I) -> Result<Self, Error> {
+ let mut result = Vec::new();
+ let mut plugin: Option<Plugin> = None;
+ for reply in iter {
+ let (a, b) = reply?;
+ match &*a {
+ "plugin" => {
+ plugin.map(|p| result.push(p));
+
+ plugin = Some(Plugin {
+ name: b,
+ suffixes: Vec::new(),
+ mime_types: Vec::new(),
+ });
+ }
+ "mime_type" => {
+ plugin.as_mut().map(|p| p.mime_types.push(b));
+ }
+ "suffix" => {
+ plugin.as_mut().map(|p| p.suffixes.push(b));
+ }
+ _ => unreachable!(),
+ }
+ }
+ plugin.map(|p| result.push(p));
+ Ok(result)
+ }
+}
diff --git a/vendored/mpd/src/proto.rs b/vendored/mpd/src/proto.rs
new file mode 100644
index 0000000..af97d58
--- /dev/null
+++ b/vendored/mpd/src/proto.rs
@@ -0,0 +1,260 @@
+// Hidden internal interface
+#![allow(missing_docs)]
+
+use bufstream::BufStream;
+
+use crate::convert::{FromIter, FromMap};
+use crate::error::{Error, ProtoError, Result, ParseError};
+use crate::reply::Reply;
+
+use std::collections::BTreeMap;
+use std::fmt;
+use std::io::{self, Lines, Read, Write};
+use std::result::Result as StdResult;
+use std::str::FromStr;
+
+pub struct Pairs<I>(pub I);
+
+impl<I> Iterator for Pairs<I>
+ where I: Iterator<Item = io::Result<String>>
+{
+ type Item = Result<(String, String)>;
+ fn next(&mut self) -> Option<Result<(String, String)>> {
+ let reply: Option<Result<Reply>> =
+ self.0.next().map(|v| v.map_err(Error::Io).and_then(|s| s.parse::<Reply>().map_err(Error::Parse)));
+ match reply {
+ Some(Ok(Reply::Pair(a, b))) => Some(Ok((a, b))),
+ None |
+ Some(Ok(Reply::Ok)) => None,
+ Some(Ok(Reply::Ack(e))) => Some(Err(Error::Server(e))),
+ Some(Err(e)) => Some(Err(e)),
+ }
+ }
+}
+
+pub struct Maps<'a, I: 'a> {
+ pairs: &'a mut Pairs<I>,
+ sep: &'a str,
+ value: Option<String>,
+ done: bool,
+ first: bool,
+}
+
+impl<'a, I> Iterator for Maps<'a, I>
+ where I: Iterator<Item = io::Result<String>>
+{
+ type Item = Result<BTreeMap<String, String>>;
+ fn next(&mut self) -> Option<Result<BTreeMap<String, String>>> {
+ if self.done {
+ return None;
+ }
+
+ let mut map = BTreeMap::new();
+
+ if let Some(b) = self.value.take() {
+ map.insert(self.sep.to_owned(), b);
+ }
+
+ loop {
+ match self.pairs.next() {
+ Some(Ok((a, b))) => {
+ if &*a == self.sep {
+ self.value = Some(b);
+ if self.first {
+ self.first = false;
+ return self.next();
+ }
+ break;
+ } else {
+ map.insert(a, b);
+ }
+ }
+ Some(Err(e)) => return Some(Err(e)),
+ None => {
+ self.done = true;
+ break;
+ }
+ }
+ }
+
+ if map.is_empty() { None } else { Some(Ok(map)) }
+ }
+}
+
+impl<I> Pairs<I>
+ where I: Iterator<Item = io::Result<String>>
+{
+ pub fn split<'a, 'b: 'a>(&'a mut self, f: &'b str) -> Maps<'a, I> {
+ Maps {
+ pairs: self,
+ sep: f,
+ value: None,
+ done: false,
+ first: true,
+ }
+ }
+}
+
+// Client inner communication methods {{{
+#[doc(hidden)]
+pub trait Proto {
+ type Stream: Read + Write;
+
+ fn read_bytes(&mut self, bytes: usize) -> Result<Vec<u8>>;
+ fn read_line(&mut self) -> Result<String>;
+ fn read_pairs(&mut self) -> Pairs<Lines<&mut BufStream<Self::Stream>>>;
+
+ fn run_command<I>(&mut self, command: &str, arguments: I) -> Result<()> where I: ToArguments;
+
+ fn read_structs<'a, T>(&'a mut self, key: &'static str) -> Result<Vec<T>>
+ where T: 'a + FromMap
+ {
+ self.read_pairs().split(key).map(|v| v.and_then(FromMap::from_map)).collect()
+ }
+
+ fn read_list(&mut self, key: &'static str) -> Result<Vec<String>> {
+ self.read_pairs().filter(|r| r.as_ref().map(|&(ref a, _)| *a == key).unwrap_or(true)).map(|r| r.map(|(_, b)| b)).collect()
+ }
+
+ fn read_struct<'a, T>(&'a mut self) -> Result<T>
+ where T: 'a + FromIter,
+ Self::Stream: 'a
+ {
+ FromIter::from_iter(self.read_pairs())
+ }
+
+ fn drain(&mut self) -> Result<()> {
+ loop {
+ let reply = self.read_line()?;
+ match &*reply {
+ "OK" | "list_OK" => break,
+ _ => (),
+ }
+ }
+ Ok(())
+ }
+
+ fn expect_ok(&mut self) -> Result<()> {
+ let line = self.read_line()?;
+
+ match line.parse::<Reply>() {
+ Ok(Reply::Ok) => Ok(()),
+ Ok(Reply::Ack(e)) => Err(Error::Server(e)),
+ Ok(_) => Err(Error::Proto(ProtoError::NotOk)),
+ Err(e) => Err(From::from(e)),
+ }
+ }
+
+ fn read_pair(&mut self) -> Result<(String, String)> {
+ let line = self.read_line()?;
+
+ match line.parse::<Reply>() {
+ Ok(Reply::Pair(a, b)) => Ok((a, b)),
+ Ok(Reply::Ok) => Err(Error::Proto(ProtoError::NotPair)),
+ Ok(Reply::Ack(e)) => Err(Error::Server(e)),
+ Err(e) => Err(Error::Parse(e)),
+ }
+ }
+
+ fn read_field<T: FromStr>(&mut self, field: &'static str) -> Result<T>
+ where ParseError: From<T::Err>
+ {
+ let (a, b) = self.read_pair()?;
+ self.expect_ok()?;
+ if &*a == field {
+ Ok(b.parse::<T>().map_err(Into::<ParseError>::into)?)
+ } else {
+ Err(Error::Proto(ProtoError::NoField(field)))
+ }
+ }
+}
+
+
+pub trait ToArguments {
+ fn to_arguments<F, E>(&self, _: &mut F) -> StdResult<(), E> where F: FnMut(&str) -> StdResult<(), E>;
+}
+
+impl ToArguments for () {
+ fn to_arguments<F, E>(&self, _: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ Ok(())
+ }
+}
+
+impl<'a> ToArguments for &'a str {
+ fn to_arguments<F, E>(&self, f: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ f(self)
+ }
+}
+
+macro_rules! argument_for_display {
+ ( $x:path ) => {
+ impl ToArguments for $x {
+ fn to_arguments<F, E>(&self, f: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ f(&self.to_string())
+ }
+ }
+ };
+}
+argument_for_display!{i8}
+argument_for_display!{u8}
+argument_for_display!{u32}
+argument_for_display!{f32}
+argument_for_display!{f64}
+argument_for_display!{usize}
+argument_for_display!{crate::status::ReplayGain}
+argument_for_display!{String}
+argument_for_display!{crate::song::Id}
+argument_for_display!{crate::song::Range}
+argument_for_display!{crate::message::Channel}
+
+macro_rules! argument_for_tuple {
+ ( $($t:ident: $T: ident),+ ) => {
+ impl<$($T : ToArguments,)*> ToArguments for ($($T,)*) {
+ fn to_arguments<F, E>(&self, f: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ let ($(ref $t,)*) = *self;
+ $(
+ $t.to_arguments(f)?;
+ )*
+ Ok(())
+ }
+ }
+ };
+}
+argument_for_tuple!{t0: T0}
+argument_for_tuple!{t0: T0, t1: T1}
+argument_for_tuple!{t0: T0, t1: T1, t2: T2}
+argument_for_tuple!{t0: T0, t1: T1, t2: T2, t3:T3}
+
+impl<'a, T: ToArguments> ToArguments for &'a [T] {
+ fn to_arguments<F, E>(&self, f: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ for arg in *self {
+ arg.to_arguments(f)?
+ }
+ Ok(())
+ }
+}
+
+pub struct Quoted<'a, D: fmt::Display + 'a + ?Sized>(pub &'a D);
+
+impl<'a, D: fmt::Display + 'a + ?Sized> fmt::Display for Quoted<'a, D> {
+ fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+ let unquoted = format!("{}", self.0);
+ if &unquoted == "" {
+ // return Ok(());
+ }
+ let quoted = unquoted.replace('\\', r"\\").replace('"', r#"\""#);
+ formatter.write_fmt(format_args!("\"{}\"", &quoted))
+ }
+}
+
+// }}}
diff --git a/vendored/mpd/src/reply.rs b/vendored/mpd/src/reply.rs
new file mode 100644
index 0000000..b288259
--- /dev/null
+++ b/vendored/mpd/src/reply.rs
@@ -0,0 +1,36 @@
+//! The module describes all possible replies from MPD server.
+//!
+//! Also it contains most generic parser, which can handle
+//! all possible server replies.
+
+
+use crate::error::{ParseError, ServerError};
+use std::str::FromStr;
+
+/// All possible MPD server replies
+#[derive(Debug, Clone, PartialEq)]
+pub enum Reply {
+ /// `OK` and `list_OK` replies
+ Ok,
+ /// `ACK` reply (server error)
+ Ack(ServerError),
+ /// a data pair reply (in `field: value` format)
+ Pair(String, String),
+}
+
+impl FromStr for Reply {
+ type Err = ParseError;
+ fn from_str(s: &str) -> Result<Reply, ParseError> {
+ if s == "OK" || s == "list_OK" {
+ Ok(Reply::Ok)
+ } else if let Ok(ack) = s.parse::<ServerError>() {
+ Ok(Reply::Ack(ack))
+ } else {
+ let mut splits = s.splitn(2, ':');
+ match (splits.next(), splits.next()) {
+ (Some(a), Some(b)) => Ok(Reply::Pair(a.to_owned(), b.trim().to_owned())),
+ _ => Err(ParseError::BadPair),
+ }
+ }
+ }
+}
diff --git a/vendored/mpd/src/search.rs b/vendored/mpd/src/search.rs
new file mode 100644
index 0000000..c800c38
--- /dev/null
+++ b/vendored/mpd/src/search.rs
@@ -0,0 +1,141 @@
+#![allow(missing_docs)]
+// TODO: unfinished functionality
+
+use crate::proto::ToArguments;
+use std::borrow::Cow;
+use std::convert::Into;
+use std::fmt;
+use std::result::Result as StdResult;
+
+pub enum Term<'a> {
+ Any,
+ File,
+ Base,
+ LastMod,
+ Tag(Cow<'a, str>),
+}
+
+pub struct Filter<'a> {
+ typ: Term<'a>,
+ what: Cow<'a, str>,
+}
+
+impl<'a> Filter<'a> {
+ fn new<W>(typ: Term<'a>, what: W) -> Filter
+ where W: 'a + Into<Cow<'a, str>>
+ {
+ Filter {
+ typ: typ,
+ what: what.into(),
+ }
+ }
+}
+
+pub struct Window(Option<(u32, u32)>);
+
+impl From<(u32, u32)> for Window {
+ fn from(window: (u32, u32)) -> Window {
+ Window(Some(window))
+ }
+}
+
+impl From<Option<(u32, u32)>> for Window {
+ fn from(window: Option<(u32, u32)>) -> Window {
+ Window(window)
+ }
+}
+
+#[derive(Default)]
+pub struct Query<'a> {
+ filters: Vec<Filter<'a>>,
+}
+
+impl<'a> Query<'a> {
+ pub fn new() -> Query<'a> {
+ Query { filters: Vec::new() }
+ }
+
+ pub fn and<'b: 'a, V: 'b + Into<Cow<'b, str>>>(&mut self, term: Term<'b>, value: V) -> &mut Query<'a> {
+ self.filters.push(Filter::new(term, value));
+ self
+ }
+}
+
+impl<'a> fmt::Display for Term<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(match *self {
+ Term::Any => "any",
+ Term::File => "file",
+ Term::Base => "base",
+ Term::LastMod => "modified-since",
+ Term::Tag(ref tag) => &*tag,
+ })
+ }
+}
+
+impl<'a> ToArguments for &'a Term<'a> {
+ fn to_arguments<F, E>(&self, f: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ f(&self.to_string())
+ }
+}
+
+impl<'a> ToArguments for &'a Filter<'a> {
+ fn to_arguments<F, E>(&self, f: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ (&self.typ).to_arguments(f)?;
+ f(&self.what)
+ }
+}
+
+impl<'a> ToArguments for &'a Query<'a> {
+ fn to_arguments<F, E>(&self, f: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ for filter in &self.filters {
+ filter.to_arguments(f)?
+ }
+ Ok(())
+ }
+}
+
+impl ToArguments for Window {
+ fn to_arguments<F, E>(&self, f: &mut F) -> StdResult<(), E>
+ where F: FnMut(&str) -> StdResult<(), E>
+ {
+ if let Some(window) = self.0 {
+ f("window")?;
+ f(&format!{"{}:{}", window.0, window.1})?;
+ }
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use crate::proto::ToArguments;
+
+ fn collect<I: ToArguments>(arguments: I) -> Vec<String> {
+ let mut output = Vec::<String>::new();
+ arguments.to_arguments::<_, ()>(&mut |arg| Ok(output.push(arg.to_string()))).unwrap();
+ output
+ }
+
+ #[test]
+ fn find_window_format() {
+ let window: Window = (0, 2).into();
+ let output = collect(window);
+ assert_eq!(output, vec!["window", "0:2"]);
+ }
+
+ #[test]
+ fn find_query_format() {
+ let mut query = Query::new();
+ let finished = query.and(Term::Tag("albumartist".into()), "Mac DeMarco").and(Term::Tag("album".into()), "Salad Days");
+ let output = collect(&*finished);
+ assert_eq!(output, vec!["albumartist", "Mac DeMarco", "album", "Salad Days"]);
+ }
+}
diff --git a/vendored/mpd/src/song.rs b/vendored/mpd/src/song.rs
new file mode 100644
index 0000000..8730025
--- /dev/null
+++ b/vendored/mpd/src/song.rs
@@ -0,0 +1,210 @@
+//! The module defines song structs and methods.
+
+use crate::convert::FromIter;
+use crate::error::{Error, ParseError};
+
+use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
+
+use std::collections::BTreeMap;
+use std::fmt;
+use std::str::FromStr;
+use std::time::Duration;
+use std::convert::TryFrom;
+
+/// Song ID
+#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Default)]
+pub struct Id(pub u32);
+
+impl Encodable for Id {
+ fn encode<S: Encoder>(&self, e: &mut S) -> Result<(), S::Error> {
+ self.0.encode(e)
+ }
+}
+
+impl Decodable for Id {
+ fn decode<S: Decoder>(d: &mut S) -> Result<Id, S::Error> {
+ d.read_u32().map(Id)
+ }
+}
+
+impl fmt::Display for Id {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+/// Song place in the queue
+#[derive(Debug, Copy, Clone, PartialEq, Default, RustcEncodable)]
+pub struct QueuePlace {
+ /// song ID
+ pub id: Id,
+ /// absolute zero-based song position
+ pub pos: u32,
+ /// song priority, if present, defaults to 0
+ pub prio: u8,
+}
+
+/// Song range
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct Range(pub Duration, pub Option<Duration>);
+
+impl Encodable for Range {
+ fn encode<S: Encoder>(&self, e: &mut S) -> Result<(), S::Error> {
+ e.emit_tuple(2, |e| {
+ e.emit_tuple_arg(0, |e| e.emit_u64(self.0.as_secs()))?;
+ e.emit_tuple_arg(1, |e| {
+ e.emit_option(|e| match self.1 {
+ Some(d) => e.emit_option_some(|e| d.as_secs().encode(e)),
+ None => e.emit_option_none(),
+ })
+ })
+ })
+ }
+}
+
+impl Default for Range {
+ fn default() -> Range {
+ Range(Duration::from_secs(0), None)
+ }
+}
+
+impl fmt::Display for Range {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ self.0.as_secs().fmt(f)?;
+ f.write_str(":")?;
+ if let Some(v) = self.1 {
+ v.as_secs().fmt(f)?;
+ }
+ Ok(())
+ }
+}
+
+impl FromStr for Range {
+ type Err = ParseError;
+ fn from_str(s: &str) -> Result<Range, ParseError> {
+ let mut splits = s.split('-').flat_map(|v| v.parse().into_iter());
+ match (splits.next(), splits.next()) {
+ (Some(s), Some(e)) => Ok(Range(Duration::from_secs(s), Some(Duration::from_secs(e)))),
+ (None, Some(e)) => Ok(Range(Duration::from_secs(0), Some(Duration::from_secs(e)))),
+ (Some(s), None) => Ok(Range(Duration::from_secs(s), None)),
+ (None, None) => Ok(Range(Duration::from_secs(0), None)),
+ }
+ }
+}
+
+/// Song data
+#[derive(Debug, Clone, PartialEq, Default)]
+pub struct Song {
+ /// filename
+ pub file: String,
+ /// name (for streams)
+ pub name: Option<String>,
+ /// title
+ pub title: Option<String>,
+ /// last modification time
+ pub last_mod: Option<Duration>,
+ /// artist
+ pub artist: Option<String>,
+ /// duration (in seconds resolution)
+ pub duration: Option<Duration>,
+ /// place in the queue (if queued for playback)
+ pub place: Option<QueuePlace>,
+ /// range to play (if queued for playback and range was set)
+ pub range: Option<Range>,
+ /// arbitrary tags, like album, artist etc
+ pub tags: BTreeMap<String, String>,
+}
+
+impl Encodable for Song {
+ fn encode<S: Encoder>(&self, e: &mut S) -> Result<(), S::Error> {
+ e.emit_struct("Song", 8, |e| {
+ e.emit_struct_field("file", 0, |e| self.file.encode(e))?;
+ e.emit_struct_field("name", 1, |e| self.name.encode(e))?;
+ e.emit_struct_field("title", 2, |e| self.title.encode(e))?;
+ e.emit_struct_field("last_mod", 3, |e| {
+ e.emit_option(|e| match self.last_mod {
+ Some(m) => e.emit_option_some(|e| m.as_secs().encode(e)),
+ None => e.emit_option_none(),
+ })
+ })?;
+ e.emit_struct_field("artist", 4, |e| self.artist.encode(e))?;
+ e.emit_struct_field("duration", 5, |e| {
+ e.emit_option(|e| match self.duration {
+ Some(d) => e.emit_option_some(|e| d.as_secs().encode(e)),
+ None => e.emit_option_none(),
+ })
+ })?;
+ e.emit_struct_field("place", 6, |e| self.place.encode(e))?;
+ e.emit_struct_field("range", 7, |e| self.range.encode(e))?;
+ e.emit_struct_field("tags", 8, |e| self.tags.encode(e))?;
+ Ok(())
+ })
+ }
+}
+
+impl FromIter for Song {
+ /// build song from map
+ fn from_iter<I: Iterator<Item = Result<(String, String), Error>>>(iter: I) -> Result<Song, Error> {
+ let mut result = Song::default();
+
+ for res in iter {
+ let line = res?;
+ match &*line.0 {
+ "file" => result.file = line.1.to_owned(),
+ "Title" => result.title = Some(line.1.to_owned()),
+ "Last-Modified" => {
+ let parsed: time::Date = time::parse(&*line.1, "%Y-%m-%dT%H:%M:%SZ")
+ .map_err(ParseError::BadTime)?;
+ let stamp = std::time::Duration::try_from(parsed - time::date!(1970-01-01))?;
+
+ result.last_mod = Some(stamp);
+ },
+ "Artist" => result.artist = Some(line.1.to_owned()),
+ "Name" => result.name = Some(line.1.to_owned()),
+ "Time" => result.duration = Some(Duration::from_secs(line.1.parse()?)),
+ "Range" => result.range = Some(line.1.parse()?),
+ "Id" => {
+ match result.place {
+ None => {
+ result.place = Some(QueuePlace {
+ id: Id(line.1.parse()?),
+ pos: 0,
+ prio: 0,
+ })
+ }
+ Some(ref mut place) => place.id = Id(line.1.parse()?),
+ }
+ }
+ "Pos" => {
+ match result.place {
+ None => {
+ result.place = Some(QueuePlace {
+ pos: line.1.parse()?,
+ id: Id(0),
+ prio: 0,
+ })
+ }
+ Some(ref mut place) => place.pos = line.1.parse()?,
+ }
+ }
+ "Prio" => {
+ match result.place {
+ None => {
+ result.place = Some(QueuePlace {
+ prio: line.1.parse()?,
+ id: Id(0),
+ pos: 0,
+ })
+ }
+ Some(ref mut place) => place.prio = line.1.parse()?,
+ }
+ }
+ _ => {
+ result.tags.insert(line.0, line.1);
+ }
+ }
+ }
+
+ Ok(result)
+ }
+}
diff --git a/vendored/mpd/src/stats.rs b/vendored/mpd/src/stats.rs
new file mode 100644
index 0000000..3829381
--- /dev/null
+++ b/vendored/mpd/src/stats.rs
@@ -0,0 +1,78 @@
+//! The module describes DB and playback statistics
+
+use crate::convert::FromIter;
+use crate::error::Error;
+
+use rustc_serialize::{Encodable, Encoder};
+use std::time::Duration;
+
+/// DB and playback statistics
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Stats {
+ /// number of artists in DB
+ pub artists: u32,
+ /// number of albums in DB
+ pub albums: u32,
+ /// number of songs in DB
+ pub songs: u32,
+ /// total MPD uptime, seconds resolution
+ pub uptime: Duration,
+ /// total playback time, seconds resolution
+ pub playtime: Duration,
+ /// total playback time for all songs in DB, seconds resolution
+ pub db_playtime: Duration,
+ /// last DB update timestamp in seconds since Epoch, seconds resolution
+ pub db_update: Duration,
+}
+
+impl Encodable for Stats {
+ fn encode<S: Encoder>(&self, e: &mut S) -> Result<(), S::Error> {
+ e.emit_struct("Stats", 7, |e| {
+ e.emit_struct_field("artists", 0, |e| self.artists.encode(e))?;
+ e.emit_struct_field("albums", 1, |e| self.albums.encode(e))?;
+ e.emit_struct_field("songs", 2, |e| self.songs.encode(e))?;
+ e.emit_struct_field("uptime", 3, |e| self.uptime.as_secs().encode(e))?;
+ e.emit_struct_field("playtime", 4, |e| self.playtime.as_secs().encode(e))?;
+ e.emit_struct_field("db_playtime", 5, |e| self.db_playtime.as_secs().encode(e))?;
+ e.emit_struct_field("db_update", 6, |e| self.db_update.as_secs().encode(e))?;
+ Ok(())
+ })
+ }
+}
+
+impl Default for Stats {
+ fn default() -> Stats {
+ Stats {
+ artists: 0,
+ albums: 0,
+ songs: 0,
+ uptime: Duration::from_secs(0),
+ playtime: Duration::from_secs(0),
+ db_playtime: Duration::from_secs(0),
+ db_update: Duration::from_secs(0),
+ }
+ }
+}
+
+impl FromIter for Stats {
+ /// build stats from iterator
+ fn from_iter<I: Iterator<Item = Result<(String, String), Error>>>(iter: I) -> Result<Stats, Error> {
+ let mut result = Stats::default();
+
+ for res in iter {
+ let line = res?;
+ match &*line.0 {
+ "artists" => result.artists = line.1.parse()?,
+ "albums" => result.albums = line.1.parse()?,
+ "songs" => result.songs = line.1.parse()?,
+ "uptime" => result.uptime = Duration::from_secs(line.1.parse()?),
+ "playtime" => result.playtime = Duration::from_secs(line.1.parse()?),
+ "db_playtime" => result.db_playtime = Duration::from_secs(line.1.parse()?),
+ "db_update" => result.db_update = Duration::from_secs(line.1.parse()?),
+ _ => (),
+ }
+ }
+
+ Ok(result)
+ }
+}
diff --git a/vendored/mpd/src/status.rs b/vendored/mpd/src/status.rs
new file mode 100644
index 0000000..08c7278
--- /dev/null
+++ b/vendored/mpd/src/status.rs
@@ -0,0 +1,310 @@
+//! The module defines MPD status data structures
+
+use crate::convert::FromIter;
+use crate::error::{Error, ParseError};
+use crate::song::{Id, QueuePlace};
+
+use rustc_serialize::{Encodable, Encoder};
+use std::fmt;
+use std::str::FromStr;
+use std::time::Duration;
+
+/// MPD status
+#[derive(Debug, PartialEq, Clone, Default)]
+pub struct Status {
+ /// volume (0-100, or -1 if volume is unavailable (e.g. for HTTPD output type)
+ pub volume: i8,
+ /// repeat mode
+ pub repeat: bool,
+ /// random mode
+ pub random: bool,
+ /// single mode
+ pub single: bool,
+ /// consume mode
+ pub consume: bool,
+ /// queue version number
+ pub queue_version: u32,
+ /// queue length
+ pub queue_len: u32,
+ /// playback state
+ pub state: State,
+ /// currently playing song place in the queue
+ pub song: Option<QueuePlace>,
+ /// next song to play place in the queue
+ pub nextsong: Option<QueuePlace>,
+ /// time current song played, and total song duration (in seconds resolution)
+ pub time: Option<(Duration, Duration)>,
+ /// elapsed play time current song played (in milliseconds resolution)
+ pub elapsed: Option<Duration>,
+ /// current song duration
+ pub duration: Option<Duration>,
+ /// current song bitrate, kbps
+ pub bitrate: Option<u32>,
+ /// crossfade timeout, seconds
+ pub crossfade: Option<Duration>,
+ /// mixramp threshold, dB
+ pub mixrampdb: f32,
+ /// mixramp duration, seconds
+ pub mixrampdelay: Option<Duration>,
+ /// current audio playback format
+ pub audio: Option<AudioFormat>,
+ /// current DB updating job number (if DB updating is in progress)
+ pub updating_db: Option<u32>,
+ /// last player error (if happened, can be reset with `clearerror()` method)
+ pub error: Option<String>,
+ /// replay gain mode
+ pub replaygain: Option<ReplayGain>,
+}
+
+impl Encodable for Status {
+ fn encode<S: Encoder>(&self, e: &mut S) -> Result<(), S::Error> {
+ e.emit_struct("Status", 21, |e| {
+ e.emit_struct_field("volume", 0, |e| self.volume.encode(e))?;
+ e.emit_struct_field("repeat", 1, |e| self.repeat.encode(e))?;
+ e.emit_struct_field("random", 2, |e| self.random.encode(e))?;
+ e.emit_struct_field("single", 3, |e| self.single.encode(e))?;
+ e.emit_struct_field("consume", 4, |e| self.consume.encode(e))?;
+ e.emit_struct_field("queue_version", 5, |e| self.queue_version.encode(e))?;
+ e.emit_struct_field("queue_len", 6, |e| self.queue_len.encode(e))?;
+ e.emit_struct_field("state", 7, |e| self.state.encode(e))?;
+ e.emit_struct_field("song", 8, |e| self.song.encode(e))?;
+ e.emit_struct_field("nextsong", 9, |e| self.nextsong.encode(e))?;
+ e.emit_struct_field("time", 10, |e| {
+ e.emit_option(|e| match self.time {
+ Some(p) => {
+ e.emit_option_some(|e| {
+ e.emit_tuple(2, |e| {
+ e.emit_tuple_arg(0, |e| p.0.as_secs().encode(e))?;
+ e.emit_tuple_arg(1, |e| p.1.as_secs().encode(e))?;
+ Ok(())
+ })
+ })
+ }
+ None => e.emit_option_none(),
+ })
+ })?;
+ e.emit_struct_field("elapsed", 11, |e| {
+ e.emit_option(|e| match self.elapsed {
+ Some(d) => e.emit_option_some(|e| d.as_secs().encode(e)),
+ None => e.emit_option_none(),
+ })
+
+ })?;
+ e.emit_struct_field("duration", 12, |e| {
+ e.emit_option(|e| match self.duration {
+ Some(d) => e.emit_option_some(|e| d.as_secs().encode(e)),
+ None => e.emit_option_none(),
+ })
+ })?;
+ e.emit_struct_field("bitrate", 13, |e| self.bitrate.encode(e))?;
+ e.emit_struct_field("crossfade", 14, |e| {
+ e.emit_option(|e| match self.crossfade {
+ Some(d) => e.emit_option_some(|e| d.as_secs().encode(e)),
+ None => e.emit_option_none(),
+ })
+ })?;
+ e.emit_struct_field("mixrampdb", 15, |e| self.mixrampdb.encode(e))?;
+ e.emit_struct_field("mixrampdelay", 16, |e| {
+ e.emit_option(|e| match self.mixrampdelay {
+ Some(d) => e.emit_option_some(|e| d.as_secs().encode(e)),
+ None => e.emit_option_none(),
+ })
+ })?;
+ e.emit_struct_field("audio", 17, |e| self.audio.encode(e))?;
+ e.emit_struct_field("updating_db", 18, |e| self.updating_db.encode(e))?;
+ e.emit_struct_field("error", 19, |e| self.error.encode(e))?;
+ e.emit_struct_field("replaygain", 20, |e| self.replaygain.encode(e))?;
+ Ok(())
+ })
+
+ }
+}
+
+impl FromIter for Status {
+ fn from_iter<I: Iterator<Item = Result<(String, String), Error>>>(iter: I) -> Result<Status, Error> {
+ let mut result = Status::default();
+
+ for res in iter {
+ let line = res?;
+ match &*line.0 {
+ "volume" => result.volume = line.1.parse()?,
+
+ "repeat" => result.repeat = &*line.1 == "1",
+ "random" => result.random = &*line.1 == "1",
+ "single" => result.single = &*line.1 == "1",
+ "consume" => result.consume = &*line.1 == "1",
+
+ "playlist" => result.queue_version = line.1.parse()?,
+ "playlistlength" => result.queue_len = line.1.parse()?,
+ "state" => result.state = line.1.parse()?,
+ "songid" => {
+ match result.song {
+ None => {
+ result.song = Some(QueuePlace {
+ id: Id(line.1.parse()?),
+ pos: 0,
+ prio: 0,
+ })
+ }
+ Some(ref mut place) => place.id = Id(line.1.parse()?),
+ }
+ }
+ "song" => {
+ match result.song {
+ None => {
+ result.song = Some(QueuePlace {
+ pos: line.1.parse()?,
+ id: Id(0),
+ prio: 0,
+ })
+ }
+ Some(ref mut place) => place.pos = line.1.parse()?,
+ }
+ }
+ "nextsongid" => {
+ match result.nextsong {
+ None => {
+ result.nextsong = Some(QueuePlace {
+ id: Id(line.1.parse()?),
+ pos: 0,
+ prio: 0,
+ })
+ }
+ Some(ref mut place) => place.id = Id(line.1.parse()?),
+ }
+ }
+ "nextsong" => {
+ match result.nextsong {
+ None => {
+ result.nextsong = Some(QueuePlace {
+ pos: line.1.parse()?,
+ id: Id(0),
+ prio: 0,
+ })
+ }
+ Some(ref mut place) => place.pos = line.1.parse()?,
+ }
+ }
+ "time" => {
+ let mut splits = line.1.splitn(2, ':').map(|v| v.parse().map_err(ParseError::BadInteger).map(Duration::from_secs));
+ result.time = match (splits.next(), splits.next()) {
+ (Some(Ok(a)), Some(Ok(b))) => Ok(Some((a, b))),
+ (Some(Err(e)), _) |
+ (_, Some(Err(e))) => Err(e),
+ _ => Ok(None),
+ }?;
+ }
+ // TODO" => float errors don't work on stable
+ "elapsed" => result.elapsed = line.1.parse::<f32>().ok().map(|v| Duration::from_millis((v * 1000.0) as u64)),
+ "duration" => result.duration = line.1.parse::<f32>().ok().map(|v| Duration::from_millis((v * 1000.0) as u64)),
+ "bitrate" => result.bitrate = Some(line.1.parse()?),
+ "xfade" => result.crossfade = Some(Duration::from_secs(line.1.parse()?)),
+ // "mixrampdb" => 0.0, //get_field!(map, "mixrampdb"),
+ // "mixrampdelay" => None, //get_field!(map, opt "mixrampdelay").map(|v: f64| Duration::milliseconds((v * 1000.0) as i64)),
+ "audio" => result.audio = Some(line.1.parse()?),
+ "updating_db" => result.updating_db = Some(line.1.parse()?),
+ "error" => result.error = Some(line.1.to_owned()),
+ "replay_gain_mode" => result.replaygain = Some(line.1.parse()?),
+ _ => (),
+ }
+ }
+
+ Ok(result)
+ }
+}
+
+/// Audio playback format
+#[derive(Debug, Copy, Clone, PartialEq, RustcEncodable)]
+pub struct AudioFormat {
+ /// sample rate, kbps
+ pub rate: u32,
+ /// sample resolution in bits, can be 0 for floating point resolution
+ pub bits: u8,
+ /// number of channels
+ pub chans: u8,
+}
+
+impl FromStr for AudioFormat {
+ type Err = ParseError;
+ fn from_str(s: &str) -> Result<AudioFormat, ParseError> {
+ let mut it = s.split(':');
+ Ok(AudioFormat {
+ rate: it.next().ok_or(ParseError::NoRate).and_then(|v| v.parse().map_err(ParseError::BadRate))?,
+ bits: it.next().ok_or(ParseError::NoBits)
+ .and_then(|v| if &*v == "f" {
+ Ok(0)
+ } else {
+ v.parse().map_err(ParseError::BadBits)
+ })?,
+ chans: it.next().ok_or(ParseError::NoChans).and_then(|v| v.parse().map_err(ParseError::BadChans))?,
+ })
+ }
+}
+
+/// Playback state
+#[derive(Debug, Copy, Clone, PartialEq, RustcEncodable, RustcDecodable)]
+pub enum State {
+ /// player stopped
+ Stop,
+ /// player is playing
+ Play,
+ /// player paused
+ Pause,
+}
+
+impl Default for State {
+ fn default() -> State {
+ State::Stop
+ }
+}
+
+impl FromStr for State {
+ type Err = ParseError;
+ fn from_str(s: &str) -> Result<State, ParseError> {
+ match s {
+ "stop" => Ok(State::Stop),
+ "play" => Ok(State::Play),
+ "pause" => Ok(State::Pause),
+ _ => Err(ParseError::BadState(s.to_owned())),
+ }
+ }
+}
+
+/// Replay gain mode
+#[derive(Debug, Clone, Copy, PartialEq, RustcEncodable, RustcDecodable)]
+pub enum ReplayGain {
+ /// off
+ Off,
+ /// track
+ Track,
+ /// album
+ Album,
+ /// auto
+ Auto,
+}
+
+impl FromStr for ReplayGain {
+ type Err = ParseError;
+ fn from_str(s: &str) -> Result<ReplayGain, ParseError> {
+ use self::ReplayGain::*;
+ match s {
+ "off" => Ok(Off),
+ "track" => Ok(Track),
+ "album" => Ok(Album),
+ "auto" => Ok(Auto),
+ _ => Err(ParseError::BadValue(s.to_owned())),
+ }
+ }
+}
+
+impl fmt::Display for ReplayGain {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::ReplayGain::*;
+ f.write_str(match *self {
+ Off => "off",
+ Track => "track",
+ Album => "album",
+ Auto => "auto",
+ })
+ }
+}
diff --git a/vendored/mpd/src/sticker.rs b/vendored/mpd/src/sticker.rs
new file mode 100644
index 0000000..b1a2dc3
--- /dev/null
+++ b/vendored/mpd/src/sticker.rs
@@ -0,0 +1,23 @@
+use crate::error::ParseError;
+use std::str::FromStr;
+
+pub struct Sticker {
+ pub name: String,
+ pub value: String,
+}
+
+impl FromStr for Sticker {
+ type Err = ParseError;
+ fn from_str(s: &str) -> Result<Sticker, ParseError> {
+ let mut parts = s.splitn(2, '=');
+ match (parts.next(), parts.next()) {
+ (Some(name), Some(value)) => {
+ Ok(Sticker {
+ name: name.to_owned(),
+ value: value.to_owned(),
+ })
+ }
+ _ => Err(ParseError::BadValue(s.to_owned())),
+ }
+ }
+}
diff --git a/vendored/mpd/src/version.rs b/vendored/mpd/src/version.rs
new file mode 100644
index 0000000..7a62d6a
--- /dev/null
+++ b/vendored/mpd/src/version.rs
@@ -0,0 +1,25 @@
+//! This module defines MPD version type and parsing code
+
+
+use crate::error::ParseError;
+use std::str::FromStr;
+
+// Version {{{
+/// MPD version
+#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, RustcEncodable)]
+pub struct Version(pub u16, pub u16, pub u16);
+
+impl FromStr for Version {
+ type Err = ParseError;
+ fn from_str(s: &str) -> Result<Version, ParseError> {
+ let mut splits = s.splitn(3, '.').map(FromStr::from_str);
+ match (splits.next(), splits.next(), splits.next()) {
+ (Some(Ok(a)), Some(Ok(b)), Some(Ok(c))) => Ok(Version(a, b, c)),
+ (Some(Err(e)), _, _) |
+ (_, Some(Err(e)), _) |
+ (_, _, Some(Err(e))) => Err(ParseError::BadInteger(e)),
+ _ => Err(ParseError::BadVersion),
+ }
+ }
+}
+// }}}
diff --git a/vendored/mpd/tests/data/empty.flac b/vendored/mpd/tests/data/empty.flac
new file mode 100644
index 0000000..4407f5b
--- /dev/null
+++ b/vendored/mpd/tests/data/empty.flac
Binary files differ
diff --git a/vendored/mpd/tests/helpers/daemon.rs b/vendored/mpd/tests/helpers/daemon.rs
new file mode 100644
index 0000000..a8e8b6c
--- /dev/null
+++ b/vendored/mpd/tests/helpers/daemon.rs
@@ -0,0 +1,140 @@
+extern crate tempdir;
+
+use self::tempdir::TempDir;
+use super::mpd;
+use std::fs::{File, create_dir};
+use std::io::{Write, Read};
+use std::os::unix::net::UnixStream;
+use std::path::{Path, PathBuf};
+use std::process::{Command, Child, Stdio};
+
+struct MpdConfig {
+ db_file: PathBuf,
+ music_directory: PathBuf,
+ playlist_directory: PathBuf,
+ sticker_file: PathBuf,
+ config_path: PathBuf,
+ sock_path: PathBuf,
+}
+
+impl MpdConfig {
+ pub fn new<P>(base: P) -> MpdConfig
+ where P: AsRef<Path>
+ {
+ let base = base.as_ref();
+ MpdConfig {
+ db_file: base.join("db"),
+ music_directory: base.join("music"),
+ playlist_directory: base.join("playlists"),
+ sticker_file: base.join("sticker_file"),
+ config_path: base.join("config"),
+ sock_path: base.join("sock"),
+ }
+ }
+
+ fn config_text(&self) -> String {
+ format!(r#"
+db_file "{db_file}"
+log_file "/dev/null"
+music_directory "{music_directory}"
+playlist_directory "{playlist_directory}"
+sticker_file "{sticker_file}"
+bind_to_address "{sock_path}"
+audio_output {{
+ type "null"
+ name "null"
+}}
+"#,
+ db_file=self.db_file.display(),
+ music_directory=self.music_directory.display(),
+ playlist_directory=self.playlist_directory.display(),
+ sticker_file=self.sticker_file.display(),
+ sock_path=self.sock_path.display(),
+ )
+ }
+
+ fn generate(&self) {
+ create_dir(&self.music_directory).expect("Could not create music directory.");
+ create_dir(&self.playlist_directory).expect("Could not create playlist directory.");
+ let mut file = File::create(&self.config_path).expect("Could not create config file.");
+ file.write_all(self.config_text().as_bytes()).expect("Could not write config file.");
+ }
+}
+
+pub struct Daemon {
+ // Saved here so it gets dropped when this does.
+ _temp_dir: TempDir,
+ config: MpdConfig,
+ process: Child,
+}
+
+impl Drop for Daemon {
+ fn drop(&mut self) {
+ self.process.kill().expect("Could not kill mpd daemon.");
+ self.process.wait().expect("Could not wait for mpd daemon to shutdown.");
+ if let Some(ref mut stderr) = self.process.stderr {
+ let mut output = String::new();
+ stderr.read_to_string(&mut output).expect("Could not collect output from mpd.");
+ println!{"Output from mpd:"}
+ println!{"{}", output};
+ }
+ }
+}
+
+fn sleep() {
+ use std::{thread, time};
+ let ten_millis = time::Duration::from_millis(10);
+ thread::sleep(ten_millis);
+}
+
+static EMPTY_FLAC_BYTES: &'static [u8] = include_bytes!("../data/empty.flac");
+
+impl Daemon {
+ pub fn start() -> Daemon {
+ let temp_dir = TempDir::new("mpd-test").unwrap();
+ let config = MpdConfig::new(&temp_dir);
+ config.generate();
+
+ // TODO: Factor out putting files in the music directory.
+ File::create(config.music_directory.join("empty.flac")).unwrap().write_all(EMPTY_FLAC_BYTES).unwrap();
+
+ let process = Command::new("mpd")
+ .arg("--no-daemon")
+ .arg(&config.config_path)
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::piped())
+ .spawn()
+ .expect("Could not create mpd daemon.");
+
+ let daemon = Daemon {
+ _temp_dir: temp_dir,
+ config: config,
+ process: process,
+ };
+
+ // Wait until we can connect to the daemon
+ let mut client;
+ loop {
+ if let Ok(c) = daemon.maybe_connect() {
+ client = c;
+ break;
+ }
+ sleep()
+ }
+ while let Some(_) = client.status().expect("Couldn't get status.").updating_db {
+ sleep()
+ }
+
+ daemon
+ }
+
+ fn maybe_connect(&self) -> Result<mpd::Client<UnixStream>, mpd::error::Error> {
+ let stream = UnixStream::connect(&self.config.sock_path)?;
+ mpd::Client::new(stream)
+ }
+
+ pub fn connect(&self) -> mpd::Client<UnixStream> {
+ self.maybe_connect().expect("Could not connect to daemon.")
+ }
+}
diff --git a/vendored/mpd/tests/helpers/mod.rs b/vendored/mpd/tests/helpers/mod.rs
new file mode 100644
index 0000000..e331433
--- /dev/null
+++ b/vendored/mpd/tests/helpers/mod.rs
@@ -0,0 +1,36 @@
+extern crate mpd;
+
+mod daemon;
+
+pub use self::daemon::Daemon;
+use std::os::unix::net::UnixStream;
+
+pub struct DaemonClient {
+ _daemon: Daemon,
+ client: mpd::Client<UnixStream>,
+}
+
+use std::ops::{Deref, DerefMut};
+
+impl Deref for DaemonClient {
+ type Target = mpd::Client<UnixStream>;
+ fn deref(&self) -> &Self::Target {
+ &self.client
+ }
+}
+
+impl DerefMut for DaemonClient {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.client
+ }
+}
+
+#[allow(dead_code)]
+pub fn connect() -> DaemonClient {
+ let daemon = Daemon::start();
+ let client = daemon.connect();
+ DaemonClient {
+ _daemon: daemon,
+ client: client,
+ }
+}
diff --git a/vendored/mpd/tests/idle.rs b/vendored/mpd/tests/idle.rs
new file mode 100644
index 0000000..d510aa2
--- /dev/null
+++ b/vendored/mpd/tests/idle.rs
@@ -0,0 +1,20 @@
+extern crate mpd;
+
+mod helpers;
+use helpers::Daemon;
+
+use mpd::Idle;
+
+#[test]
+fn idle() {
+ let daemon = Daemon::start();
+ let mut mpd = daemon.connect();
+ let idle = mpd.idle(&[]).unwrap();
+
+ let mut mpd1 = daemon.connect();
+ mpd1.consume(true).unwrap();
+ mpd1.consume(false).unwrap();
+
+ let sys = idle.get().unwrap();
+ assert_eq!(&*sys, &[mpd::Subsystem::Options]);
+}
diff --git a/vendored/mpd/tests/options.rs b/vendored/mpd/tests/options.rs
new file mode 100644
index 0000000..1bfa861
--- /dev/null
+++ b/vendored/mpd/tests/options.rs
@@ -0,0 +1,73 @@
+extern crate mpd;
+
+mod helpers;
+use helpers::connect;
+use std::time::Duration;
+
+#[test]
+fn status() {
+ let mut mpd = connect();
+ let status = mpd.status().unwrap();
+ println!("{:?}", status);
+}
+
+#[test]
+fn stats() {
+ let mut mpd = connect();
+ let stats = mpd.stats().unwrap();
+ println!("{:?}", stats);
+}
+
+macro_rules! test_options_impl {
+ ($name:ident, $val1:expr, $tval1:expr, $val2:expr, $tval2:expr) => {
+ #[test]
+ fn $name() {
+ let mut mpd = connect();
+ mpd.$name($val1).unwrap();
+ assert_eq!(mpd.status().unwrap().$name, $tval1);
+ mpd.$name($val2).unwrap();
+ assert_eq!(mpd.status().unwrap().$name, $tval2);
+ }
+ }
+}
+
+macro_rules! test_option {
+ ($name:ident, $val1:expr, $val2:expr) => {
+ test_options_impl!($name, $val1, $val1, $val2, $val2);
+ };
+ ($name:ident, $val1:expr => $tval1:expr, $val2:expr => $tval2:expr) => {
+ test_options_impl!($name, $val1, $tval1, $val2, $tval2);
+ };
+}
+
+test_option!(consume, true, false);
+test_option!(single, true, false);
+test_option!(random, true, false);
+test_option!(repeat, true, false);
+// test_option!(mixrampdb, 1.0f32, 0.0f32);
+// test_option!(mixrampdelay, 1 => Some(Duration::from_secs(1)), 0 => None);
+
+#[test]
+fn volume() {
+ let mut mpd = connect();
+ if mpd.status().unwrap().volume >= 0 {
+ mpd.volume(100).unwrap();
+ assert_eq!(mpd.status().unwrap().volume, 100);
+ mpd.volume(0).unwrap();
+ assert_eq!(mpd.status().unwrap().volume, 0);
+ }
+}
+
+#[test]
+fn crossfade() {
+ let mut mpd = connect();
+ mpd.crossfade(1000).unwrap();
+ assert_eq!(mpd.status().unwrap().crossfade, Some(Duration::from_secs(1000)));
+ mpd.crossfade(0).unwrap();
+ assert_eq!(mpd.status().unwrap().crossfade,
+ if mpd.version >= mpd::Version(0, 19, 0) {
+ None
+ } else {
+ Some(Duration::from_secs(0))
+ });
+}
diff --git a/vendored/mpd/tests/outputs.rs b/vendored/mpd/tests/outputs.rs
new file mode 100644
index 0000000..078aa94
--- /dev/null
+++ b/vendored/mpd/tests/outputs.rs
@@ -0,0 +1,25 @@
+extern crate mpd;
+extern crate time;
+
+mod helpers;
+use helpers::connect;
+
+#[test]
+fn outputs() {
+ let mut mpd = connect();
+ println!("{:?}", mpd.outputs());
+}
+
+#[test]
+fn out_toggle() {
+ let mut mpd = connect();
+
+ mpd.out_disable(0).unwrap();
+ mpd.out_enable(0).unwrap();
+
+ if mpd.version >= mpd::Version(0, 17, 0) {
+ mpd.out_toggle(0).unwrap();
+ }
+
+ mpd.output(0, true).unwrap();
+}
diff --git a/vendored/mpd/tests/playback.rs b/vendored/mpd/tests/playback.rs
new file mode 100644
index 0000000..4a51eb8
--- /dev/null
+++ b/vendored/mpd/tests/playback.rs
@@ -0,0 +1,10 @@
+extern crate mpd;
+extern crate time;
+
+mod helpers;
+
+#[test]
+fn playback() {
+ let mut mpd = helpers::connect();
+ mpd.play().unwrap();
+}
diff --git a/vendored/mpd/tests/playlist.rs b/vendored/mpd/tests/playlist.rs
new file mode 100644
index 0000000..c8442c2
--- /dev/null
+++ b/vendored/mpd/tests/playlist.rs
@@ -0,0 +1,15 @@
+extern crate mpd;
+
+mod helpers;
+use helpers::connect;
+
+#[test]
+fn playlists() {
+ let mut mpd = connect();
+ let pls = mpd.playlists().unwrap();
+ println!("{:?}", pls);
+
+ for pl in &pls {
+ println!("{}: {:?}", pl.name, mpd.playlist(&pl.name).unwrap());
+ }
+}
diff --git a/vendored/mpd/tests/reflect.rs b/vendored/mpd/tests/reflect.rs
new file mode 100644
index 0000000..f0d0858
--- /dev/null
+++ b/vendored/mpd/tests/reflect.rs
@@ -0,0 +1,28 @@
+extern crate mpd;
+
+mod helpers;
+use helpers::connect;
+
+#[test]
+fn commands() {
+ let mut mpd = connect();
+ println!("{:?}", mpd.commands().unwrap());
+}
+
+#[test]
+fn urlhandlers() {
+ let mut mpd = connect();
+ println!("{:?}", mpd.urlhandlers().unwrap());
+}
+
+#[test]
+fn decoders() {
+ let mut mpd = connect();
+ println!("{:?}", mpd.decoders().unwrap());
+}
+
+#[test]
+fn tagtypes() {
+ let mut mpd = connect();
+ println!("{:?}", mpd.tagtypes().unwrap());
+}
diff --git a/vendored/mpd/tests/search.rs b/vendored/mpd/tests/search.rs
new file mode 100644
index 0000000..56c544d
--- /dev/null
+++ b/vendored/mpd/tests/search.rs
@@ -0,0 +1,15 @@
+extern crate mpd;
+
+mod helpers;
+use helpers::connect;
+use mpd::Query;
+
+#[test]
+fn search() {
+ let mut mpd = connect();
+ let mut query = Query::new();
+ let query = query.and(mpd::Term::Any, "Soul");
+ let songs = mpd.find(query, None);
+ println!("{:?}", songs);
+ assert!(songs.is_ok());
+}
diff --git a/vendored/mpd/tests/song.rs b/vendored/mpd/tests/song.rs
new file mode 100644
index 0000000..3510f99
--- /dev/null
+++ b/vendored/mpd/tests/song.rs
@@ -0,0 +1,28 @@
+extern crate mpd;
+
+mod helpers;
+use helpers::connect;
+
+#[test]
+fn currentsong() {
+ let mut mpd = connect();
+ let song = mpd.currentsong().unwrap();
+ println!("{:?}", song);
+}
+
+#[test]
+fn queue() {
+ let mut mpd = connect();
+ let queue = mpd.queue().unwrap();
+ println!("{:?}", queue);
+
+ let songs = mpd.songs(..).unwrap();
+ assert_eq!(songs, queue);
+}
+
+#[test]
+fn rescan_update() {
+ let mut mpd = connect();
+ println!("update: {:?}", mpd.update());
+ println!("rescan: {:?}", mpd.rescan());
+}
diff --git a/vendored/mpd/tests/stickers.rs b/vendored/mpd/tests/stickers.rs
new file mode 100644
index 0000000..61aead6
--- /dev/null
+++ b/vendored/mpd/tests/stickers.rs
@@ -0,0 +1,17 @@
+extern crate mpd;
+
+mod helpers;
+use helpers::connect;
+
+#[test]
+/// Creating a sticker and then getting that sticker returns the value that was set.
+fn set_sticker() {
+ let mut mpd = connect();
+
+ static VALUE: &'static str = "value";
+
+ mpd.set_sticker("song", "empty.flac", "test_sticker", VALUE).unwrap();
+
+ let sticker = mpd.sticker("song", "empty.flac", "test_sticker").unwrap();
+ assert_eq!(sticker, VALUE);
+}