lastfm_client/api/user/recent_tracks/
builder.rs1use crate::api::builder_ext::{FetchAndSave, FetchAndUpdate, LimitBuilder};
4use crate::api::constants::METHOD_RECENT_TRACKS;
5use crate::api::fetch_utils::{ProgressCallback, ResourceContainer, fetch};
6use crate::client::HttpClient;
7use crate::config::Config;
8use crate::error::Result;
9use crate::types::{RecentTrack, Timestamped, TrackLimit, TrackList, UserRecentTracks};
10use crate::url_builder::QueryParams;
11
12use serde::de::DeserializeOwned;
13use std::fmt;
14use std::sync::Arc;
15
16pub(in crate::api::user::recent_tracks) fn validate_date_range(
21 from: Option<i64>,
22 to: Option<i64>,
23) -> crate::error::Result<()> {
24 if let (Some(from), Some(to)) = (from, to)
25 && to <= from
26 {
27 return Err(crate::error::LastFmError::Config(format!(
28 "Invalid date range: 'to' timestamp ({to}) must be greater than 'from' timestamp ({from})"
29 )));
30 }
31 Ok(())
32}
33
34pub struct RecentTracksRequestBuilder {
36 pub(in crate::api::user::recent_tracks) http: Arc<dyn HttpClient>,
37 pub(in crate::api::user::recent_tracks) config: Arc<Config>,
38 pub(in crate::api::user::recent_tracks) username: String,
39 pub(in crate::api::user::recent_tracks) limit: Option<u32>,
40 pub(in crate::api::user::recent_tracks) from: Option<i64>,
41 pub(in crate::api::user::recent_tracks) to: Option<i64>,
42 pub(in crate::api::user::recent_tracks) progress_callback: Option<ProgressCallback>,
43}
44
45impl fmt::Debug for RecentTracksRequestBuilder {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.debug_struct("RecentTracksRequestBuilder")
48 .field("username", &self.username)
49 .field("limit", &self.limit)
50 .field("from", &self.from)
51 .field("to", &self.to)
52 .finish_non_exhaustive()
53 }
54}
55
56impl RecentTracksRequestBuilder {
57 pub(crate) fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
59 Self {
60 http,
61 config,
62 username,
63 limit: None,
64 from: None,
65 to: None,
66 progress_callback: None,
67 }
68 }
69
70 #[must_use]
85 pub const fn since(mut self, timestamp: i64) -> Self {
86 self.from = Some(timestamp);
87
88 self
89 }
90
91 #[must_use]
107 pub const fn between(mut self, from: i64, to: i64) -> Self {
108 self.from = Some(from);
109 self.to = Some(to);
110
111 self
112 }
113
114 #[must_use]
116 pub fn on_progress(mut self, callback: impl Fn(u32, u32) + Send + Sync + 'static) -> Self {
117 self.progress_callback = Some(Arc::new(callback));
118
119 self
120 }
121
122 #[cfg(feature = "progress")]
124 #[must_use]
125 pub fn with_progress(self) -> Self {
126 self.on_progress(crate::api::progress::make_progress_callback())
127 }
128
129 pub async fn fetch(self) -> Result<TrackList<RecentTrack>> {
136 validate_date_range(self.from, self.to)?;
137
138 let params = self.build_params();
139
140 let limit = self
141 .limit
142 .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
143
144 self.fetch_tracks::<UserRecentTracks>(limit, params)
145 .await
146 .map(TrackList::from)
147 }
148
149 pub async fn check_currently_playing(self) -> Result<Option<RecentTrack>> {
157 let tracks = self.limit(1).fetch().await?;
158
159 Ok(tracks.first().and_then(|track| {
160 if track
161 .attr
162 .as_ref()
163 .is_some_and(|val| val.nowplaying == "true")
164 {
165 Some(track.clone())
166 } else {
167 None
168 }
169 }))
170 }
171
172 pub(in crate::api::user::recent_tracks) fn build_params(&self) -> QueryParams {
174 let mut params = QueryParams::new();
175
176 if let Some(from_timestamp) = self.from {
177 params.insert("from".to_string(), from_timestamp.to_string());
178 }
179
180 if let Some(to_timestamp) = self.to {
181 params.insert("to".to_string(), to_timestamp.to_string());
182 }
183
184 params
185 }
186
187 async fn fetch_tracks<T>(
188 &self,
189 limit: TrackLimit,
190 additional_params: QueryParams,
191 ) -> Result<Vec<RecentTrack>>
192 where
193 T: DeserializeOwned + ResourceContainer<ItemType = RecentTrack>,
194 {
195 fetch::<RecentTrack, T>(
196 self.http.clone(),
197 self.config.clone(),
198 self.username.clone(),
199 METHOD_RECENT_TRACKS,
200 limit,
201 additional_params,
202 self.progress_callback.as_ref(),
203 )
204 .await
205 }
206}
207
208impl LimitBuilder for RecentTracksRequestBuilder {
209 fn limit_mut(&mut self) -> &mut Option<u32> {
210 &mut self.limit
211 }
212}
213
214impl FetchAndSave for RecentTracksRequestBuilder {
215 type Item = RecentTrack;
216
217 fn resource_label() -> &'static str {
218 "recent tracks"
219 }
220
221 fn latest_timestamp(items: &[Self::Item]) -> Option<u32> {
222 items.first().and_then(Timestamped::get_timestamp)
223 }
224
225 async fn do_fetch(self) -> crate::error::Result<Vec<Self::Item>> {
226 Ok(Vec::from(self.fetch().await?))
227 }
228}
229
230impl FetchAndUpdate for RecentTracksRequestBuilder {
231 type Item = RecentTrack;
232
233 async fn fetch_since(self, max_ts: Option<u32>) -> crate::error::Result<Vec<Self::Item>> {
235 let builder = match max_ts {
236 Some(ts) => self.since(i64::from(ts) + 1),
237 None => self,
238 };
239 Ok(Vec::from(builder.fetch().await?))
240 }
241}