use std::path::Path;
use {tokio::runtime::Runtime, tracing::info};
use crate::{
api::http_client::{HttpClient, ReqwestClient},
credentials::{load_app_credentials, save_app_credentials, web::extract_from_web_player},
errors::QobuzApiError::{self, AuthenticationError, InitializationError},
};
const BASE_URL: &str = "https://www.qobuz.com/api.json/0.2";
pub struct QobuzApiService {
pub base_url: String,
pub app_id: String,
pub app_secret: String,
pub user_auth_token: Option<String>,
pub client: Box<dyn HttpClient>,
pub credentials_refreshed: bool,
}
impl QobuzApiService {
fn build_service(app_id: String, app_secret: String) -> Result<Self, QobuzApiError> {
if app_id.is_empty() || app_secret.is_empty() {
return Err(InitializationError {
message: "app_id and app_secret must be non-empty".to_string(),
});
}
let client = ReqwestClient::new(&app_id)?;
Ok(Self {
base_url: BASE_URL.to_string(),
app_id,
app_secret,
user_auth_token: None,
client: Box::new(client),
credentials_refreshed: false,
})
}
pub fn new() -> Result<Self, QobuzApiError> {
let env_path = Path::new(".env");
let (app_id, app_secret) = if let Some(creds) = load_app_credentials(env_path)? {
creds
} else {
info!(path = %env_path.display(), "No .env credentials found, extracting from web player");
let rt = Runtime::new()?;
let (id, secret) = rt.block_on(extract_from_web_player())?;
save_app_credentials(env_path, &id, &secret)?;
(id, secret)
};
Self::build_service(app_id, app_secret)
}
pub fn with_credentials(app_id: &str, app_secret: &str) -> Result<Self, QobuzApiError> {
Self::build_service(app_id.to_string(), app_secret.to_string())
}
#[must_use]
pub fn base_url(&self) -> &str {
&self.base_url
}
pub fn require_auth_token(&self) -> Result<&str, QobuzApiError> {
self.user_auth_token
.as_deref()
.ok_or_else(|| AuthenticationError {
message: "Not authenticated. Call authenticate_with_env() or login() first."
.to_string(),
})
}
pub fn set_auth_token(&mut self, token: String) {
self.user_auth_token = Some(token);
}
#[must_use]
pub fn http_client(&self) -> &dyn HttpClient {
&*self.client
}
#[must_use]
pub const fn is_credentials_refreshed(&self) -> bool {
self.credentials_refreshed
}
pub const fn mark_credentials_refreshed(&mut self) {
self.credentials_refreshed = true;
}
#[must_use]
pub fn app_secret(&self) -> &str {
&self.app_secret
}
pub fn rebuild_http_client(&mut self) -> Result<(), QobuzApiError> {
self.client = ReqwestClient::new(&self.app_id)?.into_boxed();
Ok(())
}
pub fn http_client_ref(app_id: &str) -> Result<Box<dyn HttpClient>, QobuzApiError> {
let client = ReqwestClient::new(app_id)?;
Ok(client.into_boxed())
}
}
#[cfg(test)]
impl QobuzApiService {
#[must_use]
pub fn new_test(client: Box<dyn HttpClient>, base_url: &str) -> Self {
Self {
base_url: base_url.to_string(),
app_id: "test-app-id".to_string(),
app_secret: "test-app-secret".to_string(),
user_auth_token: None,
client,
credentials_refreshed: false,
}
}
}