lastfm_client/api/
top_artists.rs1use crate::client::HttpClient;
2use crate::config::Config;
3use crate::error::Result;
4use crate::file_handler::{FileFormat, FileHandler};
5use crate::types::{TopArtist, TrackLimit, TrackList, UserTopArtists};
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 TopArtistsClient {
16 http: Arc<dyn HttpClient>,
17 config: Arc<Config>,
18}
19
20impl fmt::Debug for TopArtistsClient {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 f.debug_struct("TopArtistsClient")
23 .field("config", &self.config)
24 .finish_non_exhaustive()
25 }
26}
27
28impl TopArtistsClient {
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>) -> TopArtistsRequestBuilder {
36 TopArtistsRequestBuilder::new(self.http.clone(), self.config.clone(), username.into())
37 }
38}
39
40pub struct TopArtistsRequestBuilder {
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 TopArtistsRequestBuilder {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.debug_struct("TopArtistsRequestBuilder")
52 .field("username", &self.username)
53 .field("limit", &self.limit)
54 .field("period", &self.period)
55 .finish_non_exhaustive()
56 }
57}
58
59impl TopArtistsRequestBuilder {
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]
95 pub const fn period(mut self, period: Period) -> Self {
96 self.period = Some(period);
97 self
98 }
99
100 pub async fn fetch(self) -> Result<TrackList<TopArtist>> {
105 let mut params = QueryParams::new();
106
107 if let Some(period) = self.period {
108 params.insert("period".to_string(), period.as_api_str().to_string());
109 }
110
111 let limit = self
112 .limit
113 .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
114
115 self.fetch_artists::<UserTopArtists>(limit, params)
116 .await
117 .map(TrackList::from)
118 }
119
120 pub async fn fetch_and_save(self, format: FileFormat, filename_prefix: &str) -> Result<String> {
132 let artists = self.fetch().await?;
133 tracing::info!("Saving {} top artists to file", artists.len());
134 let filename = FileHandler::save(&artists, &format, filename_prefix)
135 .map_err(crate::error::LastFmError::Io)?;
136 Ok(filename)
137 }
138
139 #[cfg(feature = "sqlite")]
150 pub async fn fetch_and_save_sqlite(self, filename_prefix: &str) -> Result<String> {
151 let artists = self.fetch().await?;
152 tracing::info!("Saving {} top artists to SQLite", artists.len());
153 crate::file_handler::FileHandler::save_sqlite(&artists, filename_prefix)
154 .map_err(crate::error::LastFmError::Io)
155 }
156
157 async fn fetch_artists<T>(
158 &self,
159 limit: TrackLimit,
160 additional_params: QueryParams,
161 ) -> Result<Vec<TopArtist>>
162 where
163 T: DeserializeOwned + ResourceContainer<ItemType = TopArtist>,
164 {
165 fetch::<TopArtist, T>(
166 self.http.clone(),
167 self.config.clone(),
168 self.username.clone(),
169 "user.gettopartists",
170 limit,
171 additional_params,
172 None,
173 )
174 .await
175 }
176}
177
178impl ResourceContainer for UserTopArtists {
179 type ItemType = TopArtist;
180
181 fn total(&self) -> u32 {
182 self.topartists.attr.total
183 }
184
185 fn items(self) -> Vec<Self::ItemType> {
186 self.topartists.artist
187 }
188}