use futures::{
future::{select, Either, FutureExt},
sink::SinkExt,
stream::StreamExt,
};
use mpd_protocol::{MpdCodec, Response as RawResponse};
use tokio::{
io::{AsyncRead, AsyncWrite},
net::{TcpStream, ToSocketAddrs},
sync::{
mpsc::{self, Receiver, Sender, UnboundedSender},
oneshot,
},
};
use tokio_util::codec::Framed;
use tracing::{debug, error, span, trace, warn, Level, Span};
use tracing_futures::Instrument;
#[cfg(unix)]
use tokio::net::UnixStream;
use std::fmt::Debug;
#[cfg(unix)]
use std::path::Path;
use std::sync::Arc;
use crate::commands::{self as cmds, responses::Response, Command, CommandList};
use crate::errors::{CommandError, StateChangeError};
use crate::raw::{Frame, ProtocolError, RawCommand, RawCommandList};
use crate::state_changes::{StateChanges, Subsystem};
type CommandResponder = oneshot::Sender<Result<RawResponse, CommandError>>;
type StateChangesSender = UnboundedSender<Result<Subsystem, StateChangeError>>;
pub type ConnectResult = Result<(Client, StateChanges), ProtocolError>;
#[derive(Clone, Debug)]
pub struct Client {
commands_sender: Sender<(RawCommandList, CommandResponder)>,
protocol_version: Arc<str>,
span: Span,
}
impl Client {
pub async fn connect_to<A: ToSocketAddrs + Debug>(address: A) -> ConnectResult {
let span = span!(Level::DEBUG, "client connection", tcp_addr = ?address);
let connection = TcpStream::connect(address).await?;
Self::do_connect(connection, span).await
}
#[cfg(unix)]
pub async fn connect_unix<P: AsRef<Path>>(path: P) -> ConnectResult {
let span = span!(Level::DEBUG, "client connection", unix_addr = ?path.as_ref());
let connection = UnixStream::connect(path).await?;
Self::do_connect(connection, span).await
}
pub async fn connect<C>(connection: C) -> ConnectResult
where
C: AsyncRead + AsyncWrite + Send + Unpin + 'static,
{
Self::do_connect(connection, span!(Level::DEBUG, "client connection")).await
}
pub async fn command<C>(&self, cmd: C) -> Result<C::Response, CommandError>
where
C: Command,
{
let command = cmd.to_command();
let frame = self.raw_command(command).await?;
Ok(Response::from_frame(frame)?)
}
pub async fn command_list<L>(&self, list: L) -> Result<L::Response, CommandError>
where
L: CommandList,
{
let frames = match list.to_raw_command_list() {
Some(cmds) => self.raw_command_list(cmds).await?,
None => Vec::new(),
};
<L as CommandList>::parse_responses(frames).map_err(Into::into)
}
pub async fn password(&self, password: Option<String>) -> Result<(), CommandError> {
if let Some(pw) = password {
self.command(cmds::Password(pw)).await
} else {
Ok(())
}
}
pub async fn raw_command(&self, command: RawCommand) -> Result<Frame, CommandError> {
self.do_send(RawCommandList::new(command))
.await?
.single_frame()
.map_err(Into::into)
}
pub async fn raw_command_list(
&self,
commands: RawCommandList,
) -> Result<Vec<Frame>, CommandError> {
debug!(?commands, "sending command");
let res = self.do_send(commands).await?;
let mut frames = Vec::with_capacity(res.len());
for frame in res {
match frame {
Ok(f) => frames.push(f),
Err(error) => {
return Err(CommandError::ErrorResponse {
error,
succesful_frames: frames,
});
}
}
}
Ok(frames)
}
pub fn protocol_version(&self) -> &str {
self.protocol_version.as_ref()
}
async fn do_send(&self, commands: RawCommandList) -> Result<RawResponse, CommandError> {
let (tx, rx) = oneshot::channel();
self.commands_sender.send((commands, tx)).await?;
rx.await?
}
async fn do_connect<C>(connection: C, span: Span) -> Result<(Self, StateChanges), ProtocolError>
where
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
let (state_changes_sender, state_changes) = mpsc::unbounded_channel();
let (commands_sender, commands_receiver) = mpsc::channel(2);
let mut connection = MpdCodec::connect(connection).await?;
let protocol_version = connection.codec().protocol_version().into();
trace!("sending initial idle command");
if let Err(e) = connection.send(idle()).await {
error!(error = ?e, "failed to send initial idle command");
return Err(e);
}
debug!("connected succesfully");
let run_loop = run_loop(connection, commands_receiver, state_changes_sender)
.instrument(span!(parent: &span, Level::TRACE, "run loop"));
tokio::spawn(run_loop);
let client = Self {
commands_sender,
protocol_version,
span,
};
let state_changes = StateChanges { rx: state_changes };
Ok((client, state_changes))
}
}
struct State<C> {
loop_state: LoopState,
connection: Framed<C, MpdCodec>,
commands: Receiver<(RawCommandList, CommandResponder)>,
state_changes: StateChangesSender,
}
#[derive(Debug)]
enum LoopState {
Idling,
WaitingForCommandReply(CommandResponder),
}
fn idle() -> RawCommand {
RawCommand::new("idle")
}
fn cancel_idle() -> RawCommand {
RawCommand::new("noidle")
}
async fn run_loop<C>(
connection: Framed<C, MpdCodec>,
commands: Receiver<(RawCommandList, CommandResponder)>,
state_changes: StateChangesSender,
) where
C: AsyncRead + AsyncWrite + Unpin,
{
trace!("entering run loop");
let mut state = State {
loop_state: LoopState::Idling,
connection,
commands,
state_changes,
};
loop {
trace!(state = ?state.loop_state, "loop iteration");
match run_loop_iteration(state).await {
Some(new_state) => state = new_state,
None => break,
}
}
trace!("exited run_loop");
}
async fn run_loop_iteration<C>(mut state: State<C>) -> Option<State<C>>
where
C: AsyncRead + AsyncWrite + Unpin,
{
match state.loop_state {
LoopState::Idling => {
let next_command = state.commands.recv();
tokio::pin!(next_command);
let event = select(state.connection.next(), next_command).await;
match event {
Either::Left((response, _)) => {
match response {
Some(Ok(res)) => {
if let Some(state_change) = response_to_subsystem(res).transpose() {
trace!(?state_change);
let _ = state.state_changes.send(state_change);
}
if let Err(e) = state.connection.send(idle()).await {
error!(error = ?e, "failed to start idling after state change");
let _ = state.state_changes.send(Err(e.into()));
return None;
}
}
Some(Err(e)) => {
error!(error = ?e, "state change error");
let _ = state.state_changes.send(Err(e.into()));
return None;
}
None => return None, }
}
Either::Right((command, _)) => {
let (command, responder) = command?;
trace!(?command, "command received");
if let Err(e) = state.connection.send(cancel_idle()).await {
error!(error = ?e, "failed to cancel idle prior to sending command");
let _ = responder.send(Err(e.into()));
return None;
}
match state.connection.next().await {
None => return None,
Some(Ok(res)) => {
if let Some(state_change) = response_to_subsystem(res).transpose() {
trace!(?state_change);
let _ = state.state_changes.send(state_change);
}
}
Some(Err(e)) => {
error!(error = ?e, "state change error prior to sending command");
let _ = responder.send(Err(e.into()));
return None;
}
}
match state.connection.send(command).await {
Ok(_) => state.loop_state = LoopState::WaitingForCommandReply(responder),
Err(e) => {
error!(error = ?e, "failed to send command");
let _ = responder.send(Err(e.into()));
return None;
}
}
trace!("command sent succesfully");
}
}
}
LoopState::WaitingForCommandReply(responder) => {
let response = state.connection.next().await?;
trace!(?response, "response to command received");
let _ = responder.send(response.map_err(Into::into));
match state.commands.recv().now_or_never()? {
Some((command, responder)) => {
trace!(?command, "next command immediately available");
match state.connection.send(command).await {
Ok(_) => state.loop_state = LoopState::WaitingForCommandReply(responder),
Err(e) => {
error!(error = ?e, "failed to send command");
let _ = responder.send(Err(e.into()));
return None;
}
}
}
None => {
trace!("no next command immediately available, idling");
state.loop_state = LoopState::Idling;
if let Err(e) = state.connection.send(idle()).await {
error!(error = ?e, "failed to start idling after receiving command response");
let _ = state.state_changes.send(Err(e.into()));
return None;
}
}
}
}
}
Some(state)
}
fn response_to_subsystem(res: RawResponse) -> Result<Option<Subsystem>, StateChangeError> {
let mut frame = res.single_frame()?;
Ok(match frame.get("changed") {
Some(raw) => Some(Subsystem::from_raw_string(raw)),
None => {
if frame.fields_len() != 0 {
warn!("state change response was not empty but did not contain `changed` key");
}
None
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use tokio_test::{assert_ok, io::Builder as MockBuilder};
static GREETING: &[u8] = b"OK MPD 0.21.11\n";
#[tokio::test]
async fn single_state_change() {
let io = MockBuilder::new()
.read(GREETING)
.write(b"idle\n")
.read(b"changed: player\nOK\n")
.write(b"idle\n")
.build();
let (_client, mut state_changes) = Client::connect(io).await.expect("connect failed");
assert_eq!(
assert_ok!(state_changes.next().await.expect("no state change")),
Subsystem::Player
);
}
#[tokio::test]
async fn command() {
let io = MockBuilder::new()
.read(GREETING)
.write(b"idle\n")
.write(b"noidle\n")
.read(b"changed: playlist\nOK\n")
.write(b"hello\n")
.read(b"foo: bar\nOK\n")
.write(b"idle\n")
.build();
let (client, mut state_changes) = Client::connect(io).await.expect("connect failed");
let response = client
.raw_command(RawCommand::new("hello"))
.await
.expect("command failed");
assert_eq!(response.find("foo"), Some("bar"));
assert_eq!(
assert_ok!(state_changes.next().await.expect("no state change")),
Subsystem::Queue
);
assert!(state_changes.next().await.is_none());
}
#[tokio::test]
async fn incomplete_response() {
let io = MockBuilder::new()
.read(GREETING)
.write(b"idle\n")
.write(b"noidle\n")
.read(b"OK\n")
.write(b"hello\n")
.read(b"foo: bar\n")
.read(b"baz: qux\nOK\n")
.write(b"idle\n")
.build();
let (client, _state_changes) = Client::connect(io).await.expect("connect failed");
let response = client
.raw_command(RawCommand::new("hello"))
.await
.expect("command failed");
assert_eq!(response.find("foo"), Some("bar"));
}
#[tokio::test]
async fn command_list() {
let io = MockBuilder::new()
.read(GREETING)
.write(b"idle\n")
.write(b"noidle\n")
.read(b"OK\n")
.write(b"command_list_ok_begin\nfoo\nbar\ncommand_list_end\n")
.read(b"foo: asdf\nlist_OK\n")
.read(b"baz: qux\nlist_OK\nOK\n")
.write(b"idle\n")
.build();
let (client, _state_changes) = Client::connect(io).await.expect("connect failed");
let mut commands = RawCommandList::new(RawCommand::new("foo"));
commands.add(RawCommand::new("bar"));
let responses = client
.raw_command_list(commands)
.await
.expect("command failed");
assert_eq!(responses.len(), 2);
assert_eq!(responses[0].find("foo"), Some("asdf"));
}
#[tokio::test]
async fn dropping_client() {
let io = MockBuilder::new().read(GREETING).write(b"idle\n").build();
let (client, mut state_changes) = Client::connect(io).await.expect("connect failed");
drop(client);
assert!(state_changes.next().await.is_none());
}
#[tokio::test]
async fn protocol_version() {
let io = MockBuilder::new().read(GREETING).write(b"idle\n").build();
let (client, _state_changes) = Client::connect(io).await.expect("connect failed");
assert_eq!(client.protocol_version(), "0.21.11");
}
}