Skip to main content

lastfm_client/api/user/top/
tracks.rs

1use crate::api::builder_ext::{FetchAndSave, LimitBuilder};
2use crate::api::constants::METHOD_TOP_TRACKS;
3use crate::api::fetch_utils::{Period, ProgressCallback, ResourceContainer, fetch};
4use crate::client::HttpClient;
5use crate::config::Config;
6use crate::error::Result;
7use crate::types::{TopTrack, TrackLimit, TrackList, UserTopTracks};
8use crate::url_builder::QueryParams;
9
10use serde::de::DeserializeOwned;
11use std::fmt;
12use std::sync::Arc;
13
14/// Builder for top tracks requests
15pub struct TopTracksRequestBuilder {
16    http: Arc<dyn HttpClient>,
17    config: Arc<Config>,
18    username: String,
19    limit: Option<u32>,
20    period: Option<Period>,
21    progress_callback: Option<ProgressCallback>,
22}
23
24impl fmt::Debug for TopTracksRequestBuilder {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        f.debug_struct("TopTracksRequestBuilder")
27            .field("username", &self.username)
28            .field("limit", &self.limit)
29            .field("period", &self.period)
30            .finish_non_exhaustive()
31    }
32}
33
34impl TopTracksRequestBuilder {
35    pub(crate) fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
36        Self {
37            http,
38            config,
39            username,
40            limit: None,
41            period: None,
42            progress_callback: None,
43        }
44    }
45
46    /// Register a progress callback invoked with `(fetched, total)` after each batch.
47    #[must_use]
48    pub fn on_progress(mut self, callback: impl Fn(u32, u32) + Send + Sync + 'static) -> Self {
49        self.progress_callback = Some(Arc::new(callback));
50        self
51    }
52
53    /// Display a terminal progress bar while fetching (requires `progress` feature).
54    #[cfg(feature = "progress")]
55    #[must_use]
56    pub fn with_progress(self) -> Self {
57        self.on_progress(crate::api::progress::make_progress_callback())
58    }
59
60    /// Set the time period for top tracks
61    ///
62    /// # Arguments
63    /// * `period` - The time range to calculate top tracks over. Use `Period::Overall` for all-time,
64    ///   `Period::Week` for last 7 days, `Period::Month` for last 30 days, etc.
65    ///   If not set, defaults to the Last.fm API's default behavior (typically overall).
66    ///
67    /// # Example
68    /// ```ignore
69    /// use lastfm_client::api::Period;
70    ///
71    /// let tracks = client.top_tracks("username")
72    ///     .period(Period::Month)
73    ///     .limit(50)
74    ///     .fetch()
75    ///     .await?;
76    /// ```
77    #[must_use]
78    pub const fn period(mut self, period: Period) -> Self {
79        self.period = Some(period);
80        self
81    }
82
83    /// Fetch the tracks
84    ///
85    /// # Errors
86    /// Returns an error if the HTTP request fails or the response cannot be parsed.
87    pub async fn fetch(self) -> Result<TrackList<TopTrack>> {
88        let mut params = QueryParams::new();
89
90        if let Some(period) = self.period {
91            params.insert("period".to_string(), period.as_api_str().to_string());
92        }
93
94        let limit = self
95            .limit
96            .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
97
98        self.fetch_tracks::<UserTopTracks>(limit, params)
99            .await
100            .map(TrackList::from)
101    }
102
103    async fn fetch_tracks<T>(
104        &self,
105        limit: TrackLimit,
106        additional_params: QueryParams,
107    ) -> Result<Vec<TopTrack>>
108    where
109        T: DeserializeOwned + ResourceContainer<ItemType = TopTrack>,
110    {
111        fetch::<TopTrack, T>(
112            self.http.clone(),
113            self.config.clone(),
114            self.username.clone(),
115            METHOD_TOP_TRACKS,
116            limit,
117            additional_params,
118            self.progress_callback.as_ref(),
119        )
120        .await
121    }
122}
123
124impl LimitBuilder for TopTracksRequestBuilder {
125    fn limit_mut(&mut self) -> &mut Option<u32> {
126        &mut self.limit
127    }
128}
129
130impl FetchAndSave for TopTracksRequestBuilder {
131    type Item = TopTrack;
132
133    fn resource_label() -> &'static str {
134        "top tracks"
135    }
136
137    async fn do_fetch(self) -> crate::error::Result<Vec<Self::Item>> {
138        Ok(Vec::from(self.fetch().await?))
139    }
140}
141
142impl ResourceContainer for UserTopTracks {
143    type ItemType = TopTrack;
144
145    fn total(&self) -> u32 {
146        self.toptracks.attr.total
147    }
148
149    fn items(self) -> Vec<Self::ItemType> {
150        self.toptracks.track
151    }
152}