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, UserTopTracks};
6use crate::url_builder::QueryParams;
7
8use serde::de::DeserializeOwned;
9use std::sync::Arc;
10
11use super::fetch_utils::{TrackContainer, fetch_tracks};
12
13#[derive(Debug, Clone, Copy)]
17pub enum Period {
18 Overall,
20 Week,
22 Month,
24 ThreeMonth,
26 SixMonth,
28 TwelveMonth,
30}
31
32impl Period {
33 #[must_use]
34 pub fn as_api_str(self) -> &'static str {
35 match self {
36 Period::Overall => "overall",
37 Period::Week => "7day",
38 Period::Month => "1month",
39 Period::ThreeMonth => "3month",
40 Period::SixMonth => "6month",
41 Period::TwelveMonth => "12month",
42 }
43 }
44}
45
46pub struct TopTracksClient {
48 http: Arc<dyn HttpClient>,
49 config: Arc<Config>,
50}
51
52impl TopTracksClient {
53 pub fn new(http: Arc<dyn HttpClient>, config: Arc<Config>) -> Self {
54 Self { http, config }
55 }
56
57 pub fn builder(&self, username: impl Into<String>) -> TopTracksRequestBuilder {
59 TopTracksRequestBuilder::new(self.http.clone(), self.config.clone(), username.into())
60 }
61}
62
63pub struct TopTracksRequestBuilder {
65 http: Arc<dyn HttpClient>,
66 config: Arc<Config>,
67 username: String,
68 limit: Option<u32>,
69 period: Option<Period>,
70}
71
72impl TopTracksRequestBuilder {
73 fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
74 Self {
75 http,
76 config,
77 username,
78 limit: None,
79 period: None,
80 }
81 }
82
83 #[must_use]
89 pub fn limit(mut self, limit: u32) -> Self {
90 self.limit = Some(limit);
91 self
92 }
93
94 #[must_use]
96 pub fn unlimited(mut self) -> Self {
97 self.limit = None;
98 self
99 }
100
101 #[must_use]
119 pub fn period(mut self, period: Period) -> Self {
120 self.period = Some(period);
121 self
122 }
123
124 pub async fn fetch(self) -> Result<Vec<TopTrack>> {
129 let mut params = QueryParams::new();
130
131 if let Some(period) = self.period {
132 params.insert("period".to_string(), period.as_api_str().to_string());
133 }
134
135 let limit = self
136 .limit
137 .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
138
139 self.fetch_tracks::<UserTopTracks>(limit, params).await
140 }
141
142 pub async fn fetch_and_save(self, format: FileFormat, filename_prefix: &str) -> Result<String> {
154 let tracks = self.fetch().await?;
155 tracing::info!("Saving {} top tracks to file", tracks.len());
156 let filename = FileHandler::save(&tracks, &format, filename_prefix)
157 .map_err(crate::error::LastFmError::Io)?;
158 Ok(filename)
159 }
160
161 async fn fetch_tracks<T>(
162 &self,
163 limit: TrackLimit,
164 additional_params: QueryParams,
165 ) -> Result<Vec<TopTrack>>
166 where
167 T: DeserializeOwned + TrackContainer<TrackType = TopTrack>,
168 {
169 fetch_tracks::<TopTrack, T>(
170 self.http.clone(),
171 self.config.clone(),
172 self.username.clone(),
173 "user.gettoptracks",
174 limit,
175 additional_params,
176 )
177 .await
178 }
179}
180
181impl TrackContainer for UserTopTracks {
182 type TrackType = TopTrack;
183
184 fn total_tracks(&self) -> u32 {
185 self.toptracks.attr.total
186 }
187
188 fn tracks(self) -> Vec<Self::TrackType> {
189 self.toptracks.track
190 }
191}