Skip to main content

lastfm_client/api/
top_albums.rs

1use crate::client::HttpClient;
2use crate::config::Config;
3use crate::error::Result;
4use crate::file_handler::{FileFormat, FileHandler};
5use crate::types::{TopAlbum, TrackLimit, TrackList, UserTopAlbums};
6use crate::url_builder::QueryParams;
7
8use serde::de::DeserializeOwned;
9use std::fmt;
10use std::sync::Arc;
11
12use super::fetch_utils::{Period, ResourceContainer, fetch};
13
14/// Client for fetching top albums
15pub struct TopAlbumsClient {
16    http: Arc<dyn HttpClient>,
17    config: Arc<Config>,
18}
19
20impl fmt::Debug for TopAlbumsClient {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        f.debug_struct("TopAlbumsClient")
23            .field("config", &self.config)
24            .finish_non_exhaustive()
25    }
26}
27
28impl TopAlbumsClient {
29    /// Create a new top albums client
30    pub fn new(http: Arc<dyn HttpClient>, config: Arc<Config>) -> Self {
31        Self { http, config }
32    }
33
34    /// Create a builder for top albums requests
35    pub fn builder(&self, username: impl Into<String>) -> TopAlbumsRequestBuilder {
36        TopAlbumsRequestBuilder::new(self.http.clone(), self.config.clone(), username.into())
37    }
38}
39
40/// Builder for top albums requests
41pub struct TopAlbumsRequestBuilder {
42    http: Arc<dyn HttpClient>,
43    config: Arc<Config>,
44    username: String,
45    limit: Option<u32>,
46    period: Option<Period>,
47}
48
49impl fmt::Debug for TopAlbumsRequestBuilder {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.debug_struct("TopAlbumsRequestBuilder")
52            .field("username", &self.username)
53            .field("limit", &self.limit)
54            .field("period", &self.period)
55            .finish_non_exhaustive()
56    }
57}
58
59impl TopAlbumsRequestBuilder {
60    fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
61        Self {
62            http,
63            config,
64            username,
65            limit: None,
66            period: None,
67        }
68    }
69
70    /// Set the maximum number of albums to fetch
71    ///
72    /// # Arguments
73    /// * `limit` - Maximum number of albums to fetch. The Last.fm API supports fetching up to thousands of albums.
74    ///   If you need all albums, use `unlimited()` instead.
75    #[must_use]
76    pub const fn limit(mut self, limit: u32) -> Self {
77        self.limit = Some(limit);
78        self
79    }
80
81    /// Fetch all available albums (no limit)
82    #[must_use]
83    pub const fn unlimited(mut self) -> Self {
84        self.limit = None;
85        self
86    }
87
88    /// Set the time period for top albums
89    ///
90    /// # Arguments
91    /// * `period` - The time range to calculate top albums over. Use `Period::Overall` for all-time,
92    ///   `Period::Week` for last 7 days, `Period::Month` for last 30 days, etc.
93    ///   If not set, defaults to the Last.fm API's default behavior (typically overall).
94    #[must_use]
95    pub const fn period(mut self, period: Period) -> Self {
96        self.period = Some(period);
97        self
98    }
99
100    /// Fetch the albums
101    ///
102    /// # Errors
103    /// Returns an error if the HTTP request fails or the response cannot be parsed.
104    pub async fn fetch(self) -> Result<TrackList<TopAlbum>> {
105        let mut params = QueryParams::new();
106
107        if let Some(period) = self.period {
108            params.insert("period".to_string(), period.as_api_str().to_string());
109        }
110
111        let limit = self
112            .limit
113            .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
114
115        self.fetch_albums::<UserTopAlbums>(limit, params)
116            .await
117            .map(TrackList::from)
118    }
119
120    /// Fetch albums and save them to a file
121    ///
122    /// # Arguments
123    /// * `format` - The file format to save the albums in
124    /// * `filename_prefix` - Prefix for the generated filename
125    ///
126    /// # Errors
127    /// Returns an error if the HTTP request fails, response cannot be parsed, or file cannot be saved.
128    ///
129    /// # Returns
130    /// * `Result<String>` - The filename of the saved file
131    pub async fn fetch_and_save(self, format: FileFormat, filename_prefix: &str) -> Result<String> {
132        let albums = self.fetch().await?;
133        tracing::info!("Saving {} top albums to file", albums.len());
134        let filename = FileHandler::save(&albums, &format, filename_prefix)
135            .map_err(crate::error::LastFmError::Io)?;
136        Ok(filename)
137    }
138
139    /// Fetch albums and save them to a new `SQLite` database file.
140    ///
141    /// # Arguments
142    /// * `filename_prefix` - Prefix for the generated filename
143    ///
144    /// # Errors
145    /// Returns an error if the HTTP request fails, the response cannot be parsed, or the database cannot be saved.
146    ///
147    /// # Returns
148    /// * `Result<String>` - Path to the saved database file
149    #[cfg(feature = "sqlite")]
150    pub async fn fetch_and_save_sqlite(self, filename_prefix: &str) -> Result<String> {
151        let albums = self.fetch().await?;
152        tracing::info!("Saving {} top albums to SQLite", albums.len());
153        crate::file_handler::FileHandler::save_sqlite(&albums, filename_prefix)
154            .map_err(crate::error::LastFmError::Io)
155    }
156
157    async fn fetch_albums<T>(
158        &self,
159        limit: TrackLimit,
160        additional_params: QueryParams,
161    ) -> Result<Vec<TopAlbum>>
162    where
163        T: DeserializeOwned + ResourceContainer<ItemType = TopAlbum>,
164    {
165        fetch::<TopAlbum, T>(
166            self.http.clone(),
167            self.config.clone(),
168            self.username.clone(),
169            "user.gettopalbums",
170            limit,
171            additional_params,
172            None,
173        )
174        .await
175    }
176}
177
178impl ResourceContainer for UserTopAlbums {
179    type ItemType = TopAlbum;
180
181    fn total(&self) -> u32 {
182        self.topalbums.attr.total
183    }
184
185    fn items(self) -> Vec<Self::ItemType> {
186        self.topalbums.album
187    }
188}