use crate::clients::Client;
use crate::commands::{spawn, PinnedTaggedCmdFuture, TaggedCommandFuture};
use crate::error_from;
use snafu::{Backtrace, GenerateBacktrace, OptionExt, ResultExt, Snafu};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Snafu)]
pub enum Error {
#[snafu(display("{}", cause))]
Other {
#[snafu(source(true))]
cause: Box<dyn std::error::Error>,
#[snafu(backtrace(true))]
back: Backtrace,
},
#[snafu(display("Couldn't interpret `{}' as a rating", text))]
RatingError {
#[snafu(source(from(std::num::ParseIntError, Box::new)))]
cause: Box<dyn std::error::Error>,
text: String,
},
#[snafu(display("Can't rate the current track when the player is stopped"))]
PlayerStopped,
#[snafu(display("`{}' is not implemented, yet", feature))]
NotImplemented { feature: String },
#[snafu(display("Path `{}' cannot be converted to a String", pth.display()))]
BadPath {
pth: PathBuf,
#[snafu(backtrace(true))]
back: Backtrace,
},
}
error_from!(crate::clients::Error);
error_from!(crate::commands::Error);
error_from!(std::num::ParseIntError);
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.len() == 0 {
0u8
} else {
match rating {
"*" => 1,
"**" => 64,
"***" => 128,
"****" => 196,
"*****" => 255,
_ => rating.parse::<u8>().context(RatingError {
text: String::from(rating),
})?,
}
};
let track = if track.len() == 0 {
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? {
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?;
if cmd.len() == 0 {
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()
.context(BadPath {
pth: full_path.clone(),
})?
.to_string(),
);
params.insert("rating".to_string(), format!("{}", rating));
Ok(Some(TaggedCommandFuture::pin(
spawn(cmd, args, ¶ms)?,
None,
)))
}