lastfm_client/api/
loved_tracks.rs1use crate::analytics::AnalysisHandler;
2use crate::client::HttpClient;
3use crate::config::Config;
4use crate::error::Result;
5use crate::file_handler::{FileFormat, FileHandler};
6use crate::types::{LovedTrack, TrackLimit, UserLovedTracks};
7
8use serde::de::DeserializeOwned;
9use std::sync::Arc;
10
11use super::fetch_utils::{TrackContainer, fetch_tracks};
12
13pub struct LovedTracksClient {
15 http: Arc<dyn HttpClient>,
16 config: Arc<Config>,
17}
18
19impl LovedTracksClient {
20 pub fn new(http: Arc<dyn HttpClient>, config: Arc<Config>) -> Self {
21 Self { http, config }
22 }
23
24 pub fn builder(&self, username: impl Into<String>) -> LovedTracksRequestBuilder {
26 LovedTracksRequestBuilder::new(self.http.clone(), self.config.clone(), username.into())
27 }
28}
29
30pub struct LovedTracksRequestBuilder {
32 http: Arc<dyn HttpClient>,
33 config: Arc<Config>,
34 username: String,
35 limit: Option<u32>,
36}
37
38impl LovedTracksRequestBuilder {
39 fn new(http: Arc<dyn HttpClient>, config: Arc<Config>, username: String) -> Self {
40 Self {
41 http,
42 config,
43 username,
44 limit: None,
45 }
46 }
47
48 #[must_use]
50 pub fn limit(mut self, limit: u32) -> Self {
51 self.limit = Some(limit);
52 self
53 }
54
55 #[must_use]
57 pub fn unlimited(mut self) -> Self {
58 self.limit = None;
59 self
60 }
61
62 pub async fn fetch(self) -> Result<Vec<LovedTrack>> {
67 let limit = self
68 .limit
69 .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
70
71 self.fetch_tracks::<UserLovedTracks>(limit).await
72 }
73
74 pub async fn fetch_and_save(self, format: FileFormat, filename_prefix: &str) -> Result<String> {
86 let tracks = self.fetch().await?;
87 tracing::info!("Saving {} loved tracks to file", tracks.len());
88 let filename = FileHandler::save(&tracks, &format, filename_prefix)
89 .map_err(crate::error::LastFmError::Io)?;
90 Ok(filename)
91 }
92
93 pub async fn analyze(self, threshold: usize) -> Result<crate::analytics::TrackStats> {
104 let tracks = self.fetch().await?;
105 Ok(AnalysisHandler::analyze_tracks(&tracks, threshold))
106 }
107
108 pub async fn analyze_and_print(self, threshold: usize) -> Result<()> {
116 let stats = self.analyze(threshold).await?;
117 AnalysisHandler::print_analysis(&stats);
118 Ok(())
119 }
120
121 async fn fetch_tracks<T>(&self, limit: TrackLimit) -> Result<Vec<LovedTrack>>
122 where
123 T: DeserializeOwned + TrackContainer<TrackType = LovedTrack>,
124 {
125 use crate::url_builder::QueryParams;
126
127 fetch_tracks::<LovedTrack, T>(
128 self.http.clone(),
129 self.config.clone(),
130 self.username.clone(),
131 "user.getlovedtracks",
132 limit,
133 QueryParams::new(),
134 )
135 .await
136 }
137}
138
139impl TrackContainer for UserLovedTracks {
140 type TrackType = LovedTrack;
141
142 fn total_tracks(&self) -> u32 {
143 self.lovedtracks.attr.total
144 }
145
146 fn tracks(self) -> Vec<Self::TrackType> {
147 self.lovedtracks.track
148 }
149}