diff options
Diffstat (limited to '')
| -rw-r--r-- | pkgs/by-name/mp/mpdpopm/src/clients.rs | 309 |
1 files changed, 47 insertions, 262 deletions
diff --git a/pkgs/by-name/mp/mpdpopm/src/clients.rs b/pkgs/by-name/mp/mpdpopm/src/clients.rs index 587063b2..b88e4041 100644 --- a/pkgs/by-name/mp/mpdpopm/src/clients.rs +++ b/pkgs/by-name/mp/mpdpopm/src/clients.rs @@ -31,9 +31,9 @@ //! re-issue the "idle" command. This crate however takes the approach of two channels (like //! [mpdfav](https://github.com/vincent-petithory/mpdfav)). +use anyhow::{Context, Error, Result, anyhow, bail, ensure}; use async_trait::async_trait; use regex::Regex; -use snafu::{Backtrace, IntoError, OptionExt, ResultExt, prelude::*}; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio::net::{TcpStream, ToSocketAddrs, UnixStream}; use tracing::{debug, info}; @@ -49,92 +49,9 @@ use std::{ str::FromStr, }; -// The Protocol error, below, gets used a *lot*; anywhere we receive a message from the MPD server -// that "should" never happen. To help give a bit of context beyond a stack trace, I use this -// enumeration of "operations" -/// Enumerated list of MPD operations; used in Error::Protocol to distinguish which operation it was -/// that elicited the protocol error. -#[derive(Debug)] -#[non_exhaustive] -pub enum Operation { - Connect, - Status, - GetSticker, - SetSticker, - SendToPlaylist, - SendMessage, - Update, - GetStoredPlaylists, - RspToUris, - GetStickers, - GetAllSongs, - Add, - Idle, - GetMessages, -} - -impl std::fmt::Display for Operation { - #[allow(unreachable_patterns)] // the _ arm is *currently* unreachable - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Operation::Connect => write!(f, "Connect"), - Operation::Status => write!(f, "Status"), - Operation::GetSticker => write!(f, "GetSticker"), - Operation::SetSticker => write!(f, "SetSticker"), - Operation::SendToPlaylist => write!(f, "SendToPlaylist"), - Operation::SendMessage => write!(f, "SendMessage"), - Operation::Update => write!(f, "Update"), - Operation::GetStoredPlaylists => write!(f, "GetStoredPlaylists"), - Operation::RspToUris => write!(f, "RspToUris"), - Operation::GetStickers => write!(f, "GetStickers"), - Operation::GetAllSongs => write!(f, "GetAllSongs"), - Operation::Add => write!(f, "Add"), - Operation::Idle => write!(f, "Idle"), - Operation::GetMessages => write!(f, "GetMessages"), - _ => write!(f, "Unknown client operation"), - } - } -} - -/// An MPD client error -#[derive(Debug, Snafu)] -#[non_exhaustive] -pub enum Error { - #[snafu(display("Protocol error ({}): {}", op, msg))] - Protocol { - op: Operation, - msg: String, - backtrace: Backtrace, - }, - #[snafu(display("Protocol errror ({}): {}", op, source))] - ProtocolConv { - op: Operation, - source: Box<dyn std::error::Error>, - backtrace: Backtrace, - }, - #[snafu(display("I/O error: {}", source))] - Io { - source: std::io::Error, - backtrace: Backtrace, - }, - #[snafu(display("Encoding error: {}", source))] - Encoding { - source: std::string::FromUtf8Error, - backtrace: Backtrace, - }, - #[snafu(display("While converting sticker ``{}'': {}", sticker, source))] - StickerConversion { - sticker: String, - source: Box<dyn std::error::Error>, - backtrace: Backtrace, - }, - #[snafu(display("``{}'' is not a recognized Idle subsystem", text))] - IdleSubSystem { text: String, backtrace: Backtrace }, -} - -pub type Result<T> = std::result::Result<T, Error>; - -//////////////////////////////////////////////////////////////////////////////////////////////////// +// Some default error context messages +const ENCODING_SNAFU: &str = "Failed to interpete text as utf8"; +const IO_SNAFU: &str = "Failed read from mpd socket"; /// A description of the current track, suitable for our purposes (as in, it only tracks the /// attributes needed for this module's functionality). @@ -187,10 +104,6 @@ impl PlayerStatus { } } -//////////////////////////////////////////////////////////////////////////////////////////////////// -// Connection // -//////////////////////////////////////////////////////////////////////////////////////////////////// - /// A trait representing a simple, textual request/response protocol like that /// [employed](https://www.musicpd.org/doc/html/protocol.html) by [MPD](https://www.musicpd.org/): /// the caller sends a textual command & the server responds with a (perhaps multi-line) textual @@ -310,7 +223,7 @@ where self.sock .write_all(format!("{}\n", msg).as_bytes()) .await - .context(IoSnafu)?; + .context(IO_SNAFU)?; let mut buf = Vec::with_capacity(hint); // Given the request/response nature of the MPD protocol, our callers expect a complete @@ -319,7 +232,7 @@ where let mut cb = 0; // # bytes read so far let mut more = true; // true as long as there is more to read while more { - cb += self.sock.read_buf(&mut buf).await.context(IoSnafu)?; + cb += self.sock.read_buf(&mut buf).await.context(IO_SNAFU)?; // The shortest complete response has three bytes. If the final byte in `buf' is not a // newline, then don't bother looking further. @@ -347,7 +260,7 @@ where } // Only doing this to trouble-shoot issue 11 - String::from_utf8(buf.clone()).context(EncodingSnafu) + String::from_utf8(buf.clone()).context(ENCODING_SNAFU) } } @@ -357,15 +270,15 @@ where T: AsyncReadExt + AsyncWriteExt + Send + Unpin, { let mut buf = Vec::with_capacity(32); - let _cb = sock.read_buf(&mut buf).await.context(IoSnafu)?; + let _cb = sock.read_buf(&mut buf).await.context(IO_SNAFU)?; + // Only doing this to trouble-shoot issue 11 - let text = String::from_utf8(buf.clone()).context(EncodingSnafu)?; + let text = String::from_utf8(buf.clone()).context(ENCODING_SNAFU)?; + ensure!( text.starts_with("OK MPD "), - ProtocolSnafu { - op: Operation::Connect, - msg: text.trim() - } + "failed to connect: {}", + text.trim() ); info!("Connected {}.", text[7..].trim()); Ok(text[7..].trim().to_string()) @@ -373,7 +286,7 @@ where impl MpdConnection<TcpStream> { pub async fn connect<A: ToSocketAddrs>(addr: A) -> Result<Box<dyn RequestResponse>> { - let mut sock = TcpStream::connect(addr).await.context(IoSnafu)?; + let mut sock = TcpStream::connect(addr).await.context(IO_SNAFU)?; let proto_ver = parse_connect_rsp(&mut sock).await?; Ok(Box::new(MpdConnection::<TcpStream> { sock, @@ -385,7 +298,7 @@ impl MpdConnection<TcpStream> { impl MpdConnection<UnixStream> { // NTS: we have to box the return value because a `dyn RequestResponse` isn't Sized. pub async fn connect<P: AsRef<Path>>(pth: P) -> Result<Box<dyn RequestResponse>> { - let mut sock = UnixStream::connect(pth).await.context(IoSnafu)?; + let mut sock = UnixStream::connect(pth).await.context(IO_SNAFU)?; let proto_ver = parse_connect_rsp(&mut sock).await?; Ok(Box::new(MpdConnection::<UnixStream> { sock, @@ -478,13 +391,7 @@ impl Client { // also don't want to depend on the order. let text = self.stream.req("status").await?; - let proto = || -> Error { - ProtocolSnafu { - op: Operation::Status, - msg: text.to_owned(), - } - .build() - }; + let proto = || -> Error { anyhow!("Failed to parse mpd status output (with regexes)") }; // I first thought to avoid the use (and cost) of regular expressions by just doing // sub-string searching on "state: ", but when I realized I needed to only match at the @@ -507,12 +414,7 @@ impl Client { .ok_or_else(proto)? .as_str() .parse::<u64>() - .map_err(|err| { - ProtocolConvSnafu { - op: Operation::Status, - } - .into_error(Box::new(err)) - })?; + .context("Failed to parse songid as u64")?; let elapsed = RE_ELAPSED .captures(&text) @@ -521,12 +423,7 @@ impl Client { .ok_or_else(proto)? .as_str() .parse::<f64>() - .map_err(|err| { - ProtocolConvSnafu { - op: Operation::Status, - } - .into_error(Box::new(err)) - })?; + .context("failed to parse `elapsed` as f64")?; // navigate from `songid'-- don't send a "currentsong" message-- the current song // could have changed @@ -545,12 +442,7 @@ impl Client { .ok_or_else(proto)? .as_str() .parse::<f64>() - .map_err(|err| { - ProtocolConvSnafu { - op: Operation::Status, - } - .into_error(Box::new(err)) - })?; + .context("Failed to parse `duration` as f64")?; let curr = CurrentSong::new(songid, PathBuf::from(file), elapsed, duration); @@ -560,11 +452,7 @@ impl Client { Ok(PlayerStatus::Pause(curr)) } } - _ => ProtocolSnafu { - op: Operation::Status, - msg: state.to_owned(), - } - .fail(), + _ => bail!("Encountered unknow state `{}`", state), } } @@ -586,24 +474,18 @@ impl Client { let s = text[prefix.len()..] .split('\n') .next() - .context(ProtocolSnafu { - op: Operation::GetSticker, - msg, - })?; - Ok(Some(T::from_str(s).map_err(|err| { - StickerConversionSnafu { - sticker: sticker_name.to_owned(), - } - .into_error(Box::new(err)) + .with_context(|| format!("Failed to parse `{}` as get_sticker response", text))?; + Ok(Some(T::from_str(s).with_context(|| { + format!( + "Failed to parse sticker value as correct type: `{}`", + sticker_name + ) })?)) } else { // ACK_ERROR_NO_EXIST = 50 (Ack.hxx:17) ensure!( text.starts_with("ACK [50@0]"), - ProtocolSnafu { - op: Operation::GetSticker, - msg, - } + "Missing no sticker response" ); Ok(None) } @@ -626,13 +508,7 @@ impl Client { let text = self.stream.req(&msg).await?; debug!("Sent `{}'; got `{}'", &msg, &text); - ensure!( - text.starts_with("OK"), - ProtocolSnafu { - op: Operation::SetSticker, - msg - } - ); + ensure!(text.starts_with("OK"), "Set sticker, not acknowledged"); Ok(()) } @@ -641,13 +517,7 @@ impl Client { let msg = format!("playlistadd {} {}", quote(pl), quote(file)); let text = self.stream.req(&msg).await?; debug!("Sent `{}'; got `{}'.", &msg, &text); - ensure!( - text.starts_with("OK"), - ProtocolSnafu { - op: Operation::SendToPlaylist, - msg - } - ); + ensure!(text.starts_with("OK"), "send_to_playlist not acknowledged"); Ok(()) } @@ -657,13 +527,7 @@ impl Client { let text = self.stream.req(&msg).await?; debug!("Sent `{}'; got `{}'.", &msg, &text); - ensure!( - text.starts_with("OK"), - ProtocolSnafu { - op: Operation::SendMessage, - msg: text - } - ); + ensure!(text.starts_with("OK"), "Send_message not acknowledged"); Ok(()) } @@ -683,20 +547,12 @@ impl Client { let prefix = "updating_db: "; ensure!( text.starts_with(prefix), - ProtocolSnafu { - op: Operation::Update, - msg: &text - } + "update response doesn't start with correct prefix" ); text[prefix.len()..].split('\n').collect::<Vec<&str>>()[0] .to_string() .parse::<u64>() - .map_err(|err| { - ProtocolConvSnafu { - op: Operation::Update, - } - .into_error(Box::new(err)) - }) + .context("Failed to treat update job id as u64") } /// Get the list of stored playlists @@ -717,10 +573,7 @@ impl Client { // ACK... ensure!( !text.starts_with("ACK"), - ProtocolSnafu { - op: Operation::GetStoredPlaylists, - msg: text - } + "get_stored_playlists response not acknowledged" ); Ok(text .lines() @@ -742,13 +595,7 @@ impl Client { // or // // ACK... - ensure!( - !text.starts_with("ACK"), - ProtocolSnafu { - op: Operation::RspToUris, - msg: text.to_owned() - } - ); + ensure!(!text.starts_with("ACK"), "rsp_to_uris not acknowledged"); Ok(text .lines() .filter_map(|x| x.strip_prefix("file: ").map(String::from)) @@ -814,27 +661,15 @@ impl Client { // or // // ACK ... - ensure!( - !text.starts_with("ACK"), - ProtocolSnafu { - op: Operation::GetStickers, - msg: text, - } - ); + ensure!(!text.starts_with("ACK"), "get_stickers not ACKed"); let mut m = HashMap::new(); let mut lines = text.lines(); loop { - let file = lines.next().context(ProtocolSnafu { - op: Operation::GetStickers, - msg: text.to_owned(), - })?; + let file = lines.next().context("get_stickers no new line")?; if "OK" == file { break; } - let val = lines.next().context(ProtocolSnafu { - op: Operation::GetStickers, - msg: text.to_owned(), - })?; + let val = lines.next().context("get_stickers no val")?; m.insert( String::from(&file[6..]), @@ -863,13 +698,7 @@ impl Client { // OK // // or "ACK..." - ensure!( - !text.starts_with("ACK"), - ProtocolSnafu { - op: Operation::GetAllSongs, - msg: text, - } - ); + ensure!(!text.starts_with("ACK"), "get_all_songs not ACKed"); Ok(text .lines() .filter_map(|x| x.strip_prefix("file: ").map(String::from)) @@ -881,13 +710,7 @@ impl Client { let text = self.stream.req(&msg).await?; debug!("Sent `{}'; got `{}'.", &msg, &text); - ensure!( - text.starts_with("OK"), - ProtocolSnafu { - op: Operation::Add, - msg: &text - } - ); + ensure!(text.starts_with("OK"), "add not Oked"); Ok(()) } } @@ -1161,10 +984,7 @@ impl TryFrom<&str> for IdleSubSystem { } else if x == "message" { Ok(IdleSubSystem::Message) } else { - Err(IdleSubSystemSnafu { - text: String::from(text), - } - .build()) + bail!("{}", text) } } } @@ -1227,13 +1047,7 @@ impl IdleClient { pub async fn subscribe(&mut self, chan: &str) -> Result<()> { let text = self.conn.req(&format!("subscribe {}", chan)).await?; debug!("Sent subscribe message for {}; got `{}'.", chan, text); - ensure!( - text.starts_with("OK"), - ProtocolSnafu { - op: Operation::Connect, - msg: &text - } - ); + ensure!(text.starts_with("OK"), "subscribe not Ok: `{}`", text); debug!("Subscribed to {}.", chan); Ok(()) } @@ -1255,27 +1069,12 @@ impl IdleClient { // // We remain subscribed, but we need to send a new idle message. - ensure!( - text.starts_with("changed: "), - ProtocolSnafu { - op: Operation::Idle, - msg: &text - } - ); - let idx = text.find('\n').context(ProtocolSnafu { - op: Operation::Idle, - msg: text.to_owned(), - })?; + ensure!(text.starts_with("changed: "), "idle not OK: `{}`", text); + let idx = text.find('\n').context("idle has no newline")?; let result = IdleSubSystem::try_from(&text[9..idx])?; let text = text[idx + 1..].to_string(); - ensure!( - text.starts_with("OK"), - ProtocolSnafu { - op: Operation::Idle, - msg: &text - } - ); + ensure!(text.starts_with("OK"), "idle not OKed"); Ok(result) } @@ -1308,13 +1107,7 @@ impl IdleClient { for line in text.lines() { match state { State::Init => { - ensure!( - line.starts_with("channel: "), - ProtocolSnafu { - op: Operation::GetMessages, - msg: line.to_owned() - } - ); + ensure!(line.starts_with("channel: "), "no `channel: ` given"); chan = String::from(&line[9..]); state = State::Running; } @@ -1339,20 +1132,12 @@ impl IdleClient { } state = State::Finished; } else { - return Err(ProtocolSnafu { - op: Operation::GetMessages, - msg: text, - } - .build()); + bail!("Failed to get messages: `{}`", text) } } State::Finished => { // Should never be here! - return Err(ProtocolSnafu { - op: Operation::GetMessages, - msg: String::from(line), - } - .build()); + bail!("Failed to get messages: `{}`", text) } } } |
