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)]
15pub enum Period {
16 Overall,
17 Week,
18 Month,
19 ThreeMonth,
20 SixMonth,
21 TwelveMonth,
22}
23
24impl Period {
25 #[must_use]
26 pub fn as_api_str(self) -> &'static str {
27 match self {
28 Period::Overall => "overall",
29 Period::Week => "7day",
30 Period::Month => "1month",
31 Period::ThreeMonth => "3month",
32 Period::SixMonth => "6month",
33 Period::TwelveMonth => "12month",
34 }
35 }
36}
37
38pub struct TopTracksClient {
40 http: Arc<dyn HttpClient>,
41 config: Arc<Config>,
42}
43
44impl TopTracksClient {
45 pub fn new(http: Arc<dyn HttpClient>, config: Arc<Config>) -> Self {
46 Self { http, config }
47 }
48
49 pub fn builder(&self, username: impl Into<String>) -> TopTracksRequestBuilder {
51 TopTracksRequestBuilder::new(self.http.clone(), self.config.clone(), username.into())
52 }
53}
54
55pub struct TopTracksRequestBuilder {
57 http: Arc<dyn HttpClient>,
58 config: Arc<Config>,
59 username: String,
60 limit: Option<u32>,
61 period: Option<Period>,
62}
63
64impl TopTracksRequestBuilder {
65 fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
66 Self {
67 http,
68 config,
69 username,
70 limit: None,
71 period: None,
72 }
73 }
74
75 #[must_use]
77 pub fn limit(mut self, limit: u32) -> Self {
78 self.limit = Some(limit);
79 self
80 }
81
82 #[must_use]
84 pub fn unlimited(mut self) -> Self {
85 self.limit = None;
86 self
87 }
88
89 #[must_use]
91 pub fn period(mut self, period: Period) -> Self {
92 self.period = Some(period);
93 self
94 }
95
96 pub async fn fetch(self) -> Result<Vec<TopTrack>> {
101 let mut params = QueryParams::new();
102
103 if let Some(period) = self.period {
104 params.insert("period".to_string(), period.as_api_str().to_string());
105 }
106
107 let limit = self
108 .limit
109 .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
110
111 self.fetch_tracks::<UserTopTracks>(limit, params).await
112 }
113
114 pub async fn fetch_and_save(self, format: FileFormat, filename_prefix: &str) -> Result<String> {
126 let tracks = self.fetch().await?;
127 tracing::info!("Saving {} top tracks to file", tracks.len());
128 let filename = FileHandler::save(&tracks, &format, filename_prefix)
129 .map_err(crate::error::LastFmError::Io)?;
130 Ok(filename)
131 }
132
133 async fn fetch_tracks<T>(
134 &self,
135 limit: TrackLimit,
136 additional_params: QueryParams,
137 ) -> Result<Vec<TopTrack>>
138 where
139 T: DeserializeOwned + TrackContainer<TrackType = TopTrack>,
140 {
141 fetch_tracks::<TopTrack, T>(
142 self.http.clone(),
143 self.config.clone(),
144 self.username.clone(),
145 "user.gettoptracks",
146 limit,
147 additional_params,
148 )
149 .await
150 }
151}
152
153impl TrackContainer for UserTopTracks {
154 type TrackType = TopTrack;
155
156 fn total_tracks(&self) -> u32 {
157 self.toptracks.attr.total
158 }
159
160 fn tracks(self) -> Vec<Self::TrackType> {
161 self.toptracks.track
162 }
163}