use crate::client::HttpClient;
use crate::config::Config;
use crate::error::Result;
use crate::file_handler::{FileFormat, FileHandler};
use crate::types::{TopAlbum, TrackLimit, UserTopAlbums};
use crate::url_builder::QueryParams;
use serde::de::DeserializeOwned;
use std::fmt;
use std::sync::Arc;
use super::fetch_utils::{Period, ResourceContainer, fetch};
pub struct TopAlbumsClient {
http: Arc<dyn HttpClient>,
config: Arc<Config>,
}
impl fmt::Debug for TopAlbumsClient {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TopAlbumsClient")
.field("config", &self.config)
.finish_non_exhaustive()
}
}
impl TopAlbumsClient {
pub fn new(http: Arc<dyn HttpClient>, config: Arc<Config>) -> Self {
Self { http, config }
}
pub fn builder(&self, username: impl Into<String>) -> TopAlbumsRequestBuilder {
TopAlbumsRequestBuilder::new(self.http.clone(), self.config.clone(), username.into())
}
}
pub struct TopAlbumsRequestBuilder {
http: Arc<dyn HttpClient>,
config: Arc<Config>,
username: String,
limit: Option<u32>,
period: Option<Period>,
}
impl fmt::Debug for TopAlbumsRequestBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TopAlbumsRequestBuilder")
.field("username", &self.username)
.field("limit", &self.limit)
.field("period", &self.period)
.finish_non_exhaustive()
}
}
impl TopAlbumsRequestBuilder {
fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
Self {
http,
config,
username,
limit: None,
period: None,
}
}
#[must_use]
pub const fn limit(mut self, limit: u32) -> Self {
self.limit = Some(limit);
self
}
#[must_use]
pub const fn unlimited(mut self) -> Self {
self.limit = None;
self
}
#[must_use]
pub const fn period(mut self, period: Period) -> Self {
self.period = Some(period);
self
}
pub async fn fetch(self) -> Result<Vec<TopAlbum>> {
let mut params = QueryParams::new();
if let Some(period) = self.period {
params.insert("period".to_string(), period.as_api_str().to_string());
}
let limit = self
.limit
.map_or(TrackLimit::Unlimited, TrackLimit::Limited);
self.fetch_albums::<UserTopAlbums>(limit, params).await
}
pub async fn fetch_and_save(self, format: FileFormat, filename_prefix: &str) -> Result<String> {
let albums = self.fetch().await?;
tracing::info!("Saving {} top albums to file", albums.len());
let filename = FileHandler::save(&albums, &format, filename_prefix)
.map_err(crate::error::LastFmError::Io)?;
Ok(filename)
}
#[cfg(feature = "sqlite")]
pub async fn fetch_and_save_sqlite(self, filename_prefix: &str) -> Result<String> {
let albums = self.fetch().await?;
tracing::info!("Saving {} top albums to SQLite", albums.len());
crate::file_handler::FileHandler::save_sqlite(&albums, filename_prefix)
.map_err(crate::error::LastFmError::Io)
}
async fn fetch_albums<T>(
&self,
limit: TrackLimit,
additional_params: QueryParams,
) -> Result<Vec<TopAlbum>>
where
T: DeserializeOwned + ResourceContainer<ItemType = TopAlbum>,
{
fetch::<TopAlbum, T>(
self.http.clone(),
self.config.clone(),
self.username.clone(),
"user.gettopalbums",
limit,
additional_params,
None,
)
.await
}
}
impl ResourceContainer for UserTopAlbums {
type ItemType = TopAlbum;
fn total(&self) -> u32 {
self.topalbums.attr.total
}
fn items(self) -> Vec<Self::ItemType> {
self.topalbums.album
}
}