lastfm_client/api/
top_tracks.rs1use crate::client::HttpClient;
2use crate::config::Config;
3use crate::error::Result;
4use crate::file_handler::{FileFormat, FileHandler};
5use crate::types::{TopTrack, TrackLimit, TrackList, UserTopTracks};
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
14pub struct TopTracksClient {
16 http: Arc<dyn HttpClient>,
17 config: Arc<Config>,
18}
19
20impl fmt::Debug for TopTracksClient {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 f.debug_struct("TopTracksClient")
23 .field("config", &self.config)
24 .finish_non_exhaustive()
25 }
26}
27
28impl TopTracksClient {
29 pub fn new(http: Arc<dyn HttpClient>, config: Arc<Config>) -> Self {
31 Self { http, config }
32 }
33
34 pub fn builder(&self, username: impl Into<String>) -> TopTracksRequestBuilder {
36 TopTracksRequestBuilder::new(self.http.clone(), self.config.clone(), username.into())
37 }
38}
39
40pub struct TopTracksRequestBuilder {
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 TopTracksRequestBuilder {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.debug_struct("TopTracksRequestBuilder")
52 .field("username", &self.username)
53 .field("limit", &self.limit)
54 .field("period", &self.period)
55 .finish_non_exhaustive()
56 }
57}
58
59impl TopTracksRequestBuilder {
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 #[must_use]
76 pub const fn limit(mut self, limit: u32) -> Self {
77 self.limit = Some(limit);
78 self
79 }
80
81 #[must_use]
83 pub const fn unlimited(mut self) -> Self {
84 self.limit = None;
85 self
86 }
87
88 #[must_use]
106 pub const fn period(mut self, period: Period) -> Self {
107 self.period = Some(period);
108 self
109 }
110
111 pub async fn fetch(self) -> Result<TrackList<TopTrack>> {
116 let mut params = QueryParams::new();
117
118 if let Some(period) = self.period {
119 params.insert("period".to_string(), period.as_api_str().to_string());
120 }
121
122 let limit = self
123 .limit
124 .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
125
126 self.fetch_tracks::<UserTopTracks>(limit, params)
127 .await
128 .map(TrackList::from)
129 }
130
131 pub async fn fetch_and_save(self, format: FileFormat, filename_prefix: &str) -> Result<String> {
143 let tracks = self.fetch().await?;
144 tracing::info!("Saving {} top tracks to file", tracks.len());
145 let filename = FileHandler::save(&tracks, &format, filename_prefix)
146 .map_err(crate::error::LastFmError::Io)?;
147 Ok(filename)
148 }
149
150 #[cfg(feature = "sqlite")]
161 pub async fn fetch_and_save_sqlite(self, filename_prefix: &str) -> Result<String> {
162 let tracks = self.fetch().await?;
163 tracing::info!("Saving {} top tracks to SQLite", tracks.len());
164 crate::file_handler::FileHandler::save_sqlite(&tracks, filename_prefix)
165 .map_err(crate::error::LastFmError::Io)
166 }
167
168 async fn fetch_tracks<T>(
169 &self,
170 limit: TrackLimit,
171 additional_params: QueryParams,
172 ) -> Result<Vec<TopTrack>>
173 where
174 T: DeserializeOwned + ResourceContainer<ItemType = TopTrack>,
175 {
176 fetch::<TopTrack, T>(
177 self.http.clone(),
178 self.config.clone(),
179 self.username.clone(),
180 "user.gettoptracks",
181 limit,
182 additional_params,
183 None,
184 )
185 .await
186 }
187}
188
189impl ResourceContainer for UserTopTracks {
190 type ItemType = TopTrack;
191
192 fn total(&self) -> u32 {
193 self.toptracks.attr.total
194 }
195
196 fn items(self) -> Vec<Self::ItemType> {
197 self.toptracks.track
198 }
199}