use librespot_core::session::Session;
use maybe_async::maybe_async;
use rspotify::{
clients::{BaseClient, OAuthClient},
http::HttpClient,
sync::Mutex,
AuthCodePkceSpotify, ClientResult, Config, Credentials, OAuth, Token,
};
use std::{fmt, sync::Arc};
use crate::token;
#[derive(Clone, Default)]
pub struct Spotify {
creds: Credentials,
oauth: OAuth,
config: Config,
token: Arc<Mutex<Option<Token>>>,
http: HttpClient,
session: Arc<tokio::sync::Mutex<Option<Session>>>,
}
#[allow(clippy::missing_fields_in_debug)] impl fmt::Debug for Spotify {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Spotify")
.field("creds", &self.creds)
.field("oauth", &self.oauth)
.field("config", &self.config)
.field("token", &self.token)
.finish()
}
}
impl Spotify {
pub fn new() -> Spotify {
Self {
creds: Credentials::default(),
oauth: OAuth::default(),
config: Config {
token_refreshing: true,
..Default::default()
},
token: Arc::new(Mutex::new(None)),
http: HttpClient::default(),
session: Arc::new(tokio::sync::Mutex::new(None)),
}
}
pub async fn set_session(&self, session: Session) {
*self.session.lock().await = Some(session);
}
pub async fn session(&self) -> Session {
self.session
.lock()
.await
.clone()
.expect("non-empty Spotify session")
}
}
#[maybe_async]
impl BaseClient for Spotify {
fn get_http(&self) -> &HttpClient {
&self.http
}
fn get_token(&self) -> Arc<Mutex<Option<Token>>> {
Arc::clone(&self.token)
}
fn get_creds(&self) -> &Credentials {
&self.creds
}
fn get_config(&self) -> &Config {
&self.config
}
async fn refetch_token(&self) -> ClientResult<Option<Token>> {
let session = self.session().await;
let old_token = self.token.lock().await.unwrap().clone();
if session.is_invalid() {
tracing::error!("Failed to get a new token: invalid session");
return Ok(old_token);
}
match token::get_token_rspotify(&session).await {
Ok(token) => Ok(Some(token)),
Err(err) => {
tracing::error!("Failed to get a new token: {err:#}");
Ok(old_token)
}
}
}
}
#[maybe_async]
impl OAuthClient for Spotify {
fn get_oauth(&self) -> &OAuth {
panic!("`OAuthClient::get_oauth` should never be called!")
}
async fn request_token(&self, _code: &str) -> ClientResult<()> {
panic!("`OAuthClient::request_token` should never be called!")
}
}
#[derive(Clone, Debug, Default)]
pub struct WebApiClient(AuthCodePkceSpotify);
impl WebApiClient {
pub fn new(inner: AuthCodePkceSpotify) -> Self {
Self(inner)
}
pub fn get_authorize_url(&mut self, verifier_bytes: Option<usize>) -> ClientResult<String> {
self.0.get_authorize_url(verifier_bytes)
}
}
#[maybe_async]
impl BaseClient for WebApiClient {
fn get_http(&self) -> &HttpClient {
self.0.get_http()
}
fn get_token(&self) -> Arc<Mutex<Option<Token>>> {
self.0.get_token()
}
fn get_creds(&self) -> &Credentials {
self.0.get_creds()
}
fn get_config(&self) -> &Config {
self.0.get_config()
}
async fn refetch_token(&self) -> ClientResult<Option<Token>> {
let previous_refresh_token = self
.0
.get_token()
.lock()
.await
.unwrap()
.as_ref()
.and_then(|token| token.refresh_token.clone());
let refreshed = self.0.refetch_token().await?;
Ok(refreshed.map(|mut token| {
if token.refresh_token.is_none() {
token.refresh_token = previous_refresh_token;
}
token
}))
}
}
#[maybe_async]
impl OAuthClient for WebApiClient {
fn get_oauth(&self) -> &OAuth {
self.0.get_oauth()
}
async fn request_token(&self, code: &str) -> ClientResult<()> {
self.0.request_token(code).await
}
}