use crate::clients::Client;
use crate::commands::{PinnedTaggedCmdFuture, TaggedCommandFuture, spawn};
use backtrace::Backtrace;
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug)]
pub enum Error {
Rating {
source: std::num::ParseIntError,
text: String,
},
PlayerStopped,
NotImplemented {
feature: String,
},
BadPath {
pth: PathBuf,
back: Backtrace,
},
Client {
source: crate::clients::Error,
back: Backtrace,
},
Command {
source: crate::commands::Error,
back: Backtrace,
},
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::Rating { source, text } => write!(
f,
"Unable to interpret ``{}'' as a rating: {}",
text, source
),
Error::PlayerStopped => write!(f, "Player stopped"),
Error::NotImplemented { feature } => write!(f, "{} not implemented", feature),
Error::BadPath { pth, back: _ } => write!(f, "Bad path: {:?}", pth),
Error::Client { source, back: _ } => write!(f, "Client error: {}", source),
Error::Command { source, back: _ } => write!(f, "Command error: {}", source),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self {
Error::Rating {
text: _,
ref source,
} => Some(source),
Error::Client {
ref source,
back: _,
} => Some(source),
_ => None,
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, PartialEq)]
pub enum RatedTrack {
Current,
File(std::path::PathBuf),
Relative(i8),
}
#[derive(Debug)]
pub struct RatingRequest {
pub rating: u8,
pub track: RatedTrack,
}
impl std::convert::TryFrom<&str> for RatingRequest {
type Error = Error;
fn try_from(text: &str) -> std::result::Result<Self, Self::Error> {
let text = text.trim();
let (rating, track) = match text.find(char::is_whitespace) {
Some(idx) => (&text[..idx], &text[idx + 1..]),
None => (text, ""),
};
let rating = if rating.is_empty() {
0u8
} else {
match rating {
"*" => 1,
"**" => 64,
"***" => 128,
"****" => 196,
"*****" => 255,
_ => rating.parse::<u8>().map_err(|err| Error::Rating {
source: err,
text: String::from(rating),
})?,
}
};
let track = if track.is_empty() {
RatedTrack::Current
} else {
match text.parse::<i8>() {
Ok(i) => RatedTrack::Relative(i),
Err(_) => RatedTrack::File(std::path::PathBuf::from(&track)),
}
};
Ok(RatingRequest {
rating: rating,
track: track,
})
}
}
#[cfg(test)]
mod rating_request_tests {
use super::*;
use std::convert::TryFrom;
#[test]
fn rating_request_smoke() {
let req = RatingRequest::try_from("*** foo bar splat.mp3").unwrap();
assert_eq!(req.rating, 128);
assert_eq!(
req.track,
RatedTrack::File(PathBuf::from("foo bar splat.mp3"))
);
let req = RatingRequest::try_from("255").unwrap();
assert_eq!(req.rating, 255);
assert_eq!(req.track, RatedTrack::Current);
let _req = RatingRequest::try_from("******").unwrap_err();
}
}
pub async fn get_rating(client: &mut Client, sticker: &str, file: &str) -> Result<u8> {
match client
.get_sticker::<u8>(file, sticker)
.await
.map_err(|err| Error::Client {
source: err,
back: Backtrace::new(),
})? {
Some(x) => Ok(x),
None => Ok(0u8),
}
}
pub async fn set_rating<I: Iterator<Item = String>>(
client: &mut Client,
sticker: &str,
file: &str,
rating: u8,
cmd: &str,
args: I,
music_dir: &str,
) -> Result<Option<PinnedTaggedCmdFuture>> {
client
.set_sticker(file, sticker, &format!("{}", rating))
.await
.map_err(|err| Error::Client {
source: err,
back: Backtrace::new(),
})?;
if cmd.is_empty() {
return Ok(None);
}
let mut params = HashMap::<String, String>::new();
let full_path: PathBuf = [music_dir, file].iter().collect();
params.insert(
"full-file".to_string(),
full_path
.to_str()
.ok_or_else(|| Error::BadPath {
pth: full_path.clone(),
back: Backtrace::new(),
})?
.to_string(),
);
params.insert("rating".to_string(), format!("{}", rating));
Ok(Some(TaggedCommandFuture::pin(
spawn(cmd, args, ¶ms).map_err(|err| Error::Command {
source: err,
back: Backtrace::new(),
})?,
None,
)))
}