Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@ license = "Apache-2.0/MIT"
edition = "2021"

[dependencies]
failure = "0.1.3"
log = "0.4.6"
md5 = "0.6.0"
rand = "0.6.1"
thiserror = "1.0"
log = "0.4"
md5 = "0.7"
rand = "0.8"
readonly = "0.2"
serde = "1.0.80"
serde_derive = "1.0.80"
serde_json = "1.0.33"
reqwest = "0.9.5"
serde = "1"
serde_derive = "1"
serde_json = "1"
reqwest = { version = "0.11", features = ["json"] }
async-trait = "0.1.67"
url = "2.3.1"

[dev-dependencies]
tokio-test = "0.4.2"
81 changes: 47 additions & 34 deletions src/annotate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ use crate::query::Query;
use crate::{Album, Artist, Client, Error, Result, Song};

/// Allows starring, rating, and scrobbling media.
#[async_trait::async_trait]
pub trait Annotatable {
/// Attaches a star to the content.
fn star(&self, client: &Client) -> Result<()>;
async fn star(&self, client: &Client) -> Result<()>;

/// Removes a star from the content.
fn unstar(&self, client: &Client) -> Result<()>;
async fn unstar(&self, client: &Client) -> Result<()>;

/// Sets the rating for the content.
fn set_rating(&self, client: &Client, rating: u8) -> Result<()>;
async fn set_rating(&self, client: &Client, rating: u8) -> Result<()>;

/// Registers the local playback of the content. Typically used when playing
/// media that is cached on the client. This operation includes the
Expand All @@ -29,113 +30,125 @@ pub trait Annotatable {
///
/// `time` should be a valid ISO8601 timestamp. In the future, this will be
/// validated.
fn scrobble<'a, B, T>(&self, client: &Client, time: T, now_playing: B) -> Result<()>
async fn scrobble<'a, B: Send, T: Send>(
&self,
client: &Client,
time: T,
now_playing: B,
) -> Result<()>
where
B: Into<Option<bool>>,
T: Into<Option<&'a str>>;
}

#[async_trait::async_trait]
impl Annotatable for Artist {
fn star(&self, client: &Client) -> Result<()> {
client.get("star", Query::with("artistId", self.id))?;
async fn star(&self, client: &Client) -> Result<()> {
client.get("star", Query::with("artistId", self.id)).await?;
Ok(())
}

fn unstar(&self, client: &Client) -> Result<()> {
client.get("unstar", Query::with("artistId", self.id))?;
async fn unstar(&self, client: &Client) -> Result<()> {
client
.get("unstar", Query::with("artistId", self.id))
.await?;
Ok(())
}

fn set_rating(&self, client: &Client, rating: u8) -> Result<()> {
async fn set_rating(&self, client: &Client, rating: u8) -> Result<()> {
if rating > 5 {
return Err(Error::Other("rating must be between 0 and 5 inclusive"));
}

let args = Query::with("id", self.id).arg("rating", rating).build();
client.get("setRating", args)?;
client.get("setRating", args).await?;
Ok(())
}

fn scrobble<'a, B, T>(&self, client: &Client, time: T, now_playing: B) -> Result<()>
async fn scrobble<'a, B, T>(&self, client: &Client, time: T, now_playing: B) -> Result<()>
where
B: Into<Option<bool>>,
T: Into<Option<&'a str>>,
B: Into<Option<bool>> + Send,
T: Into<Option<&'a str>> + Send,
{
let args = Query::with("id", self.id)
.arg("time", time.into())
.arg("submission", now_playing.into().map(|b| !b))
.build();
client.get("scrobble", args)?;
client.get("scrobble", args).await?;
Ok(())
}
}

#[async_trait::async_trait]
impl Annotatable for Album {
fn star(&self, client: &Client) -> Result<()> {
client.get("star", Query::with("albumId", self.id))?;
async fn star(&self, client: &Client) -> Result<()> {
client.get("star", Query::with("albumId", self.id)).await?;
Ok(())
}

fn unstar(&self, client: &Client) -> Result<()> {
client.get("unstar", Query::with("albumId", self.id))?;
async fn unstar(&self, client: &Client) -> Result<()> {
client
.get("unstar", Query::with("albumId", self.id))
.await?;
Ok(())
}

fn set_rating(&self, client: &Client, rating: u8) -> Result<()> {
async fn set_rating(&self, client: &Client, rating: u8) -> Result<()> {
if rating > 5 {
return Err(Error::Other("rating must be between 0 and 5 inclusive"));
}

let args = Query::with("id", self.id).arg("rating", rating).build();
client.get("setRating", args)?;
client.get("setRating", args).await?;
Ok(())
}

fn scrobble<'a, B, T>(&self, client: &Client, time: T, now_playing: B) -> Result<()>
async fn scrobble<'a, B, T>(&self, client: &Client, time: T, now_playing: B) -> Result<()>
where
B: Into<Option<bool>>,
T: Into<Option<&'a str>>,
B: Into<Option<bool>> + Send,
T: Into<Option<&'a str>> + Send,
{
let args = Query::with("id", self.id)
.arg("time", time.into())
.arg("submission", now_playing.into().map(|b| !b))
.build();
client.get("scrobble", args)?;
client.get("scrobble", args).await?;
Ok(())
}
}

#[async_trait::async_trait]
impl Annotatable for Song {
fn star(&self, client: &Client) -> Result<()> {
client.get("star", Query::with("id", self.id))?;
async fn star(&self, client: &Client) -> Result<()> {
client.get("star", Query::with("id", self.id)).await?;
Ok(())
}

fn unstar(&self, client: &Client) -> Result<()> {
client.get("unstar", Query::with("id", self.id))?;
async fn unstar(&self, client: &Client) -> Result<()> {
client.get("unstar", Query::with("id", self.id)).await?;
Ok(())
}

fn set_rating(&self, client: &Client, rating: u8) -> Result<()> {
async fn set_rating(&self, client: &Client, rating: u8) -> Result<()> {
if rating > 5 {
return Err(Error::Other("rating must be between 0 and 5 inclusive"));
}

let args = Query::with("id", self.id).arg("rating", rating).build();
client.get("setRating", args)?;
client.get("setRating", args).await?;
Ok(())
}

fn scrobble<'a, B, T>(&self, client: &Client, time: T, now_playing: B) -> Result<()>
async fn scrobble<'a, B, T>(&self, client: &Client, time: T, now_playing: B) -> Result<()>
where
B: Into<Option<bool>>,
T: Into<Option<&'a str>>,
B: Into<Option<bool>> + Send,
T: Into<Option<&'a str>> + Send,
{
let args = Query::with("id", self.id)
.arg("time", time.into())
.arg("submission", now_playing.into().map(|b| !b))
.build();
client.get("scrobble", args)?;
client.get("scrobble", args).await?;
Ok(())
}
}
Loading