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]
54 pub fn limit(mut self, limit: u32) -> Self {
55 self.limit = Some(limit);
56 self
57 }
58
59 #[must_use]
61 pub fn unlimited(mut self) -> Self {
62 self.limit = None;
63 self
64 }
65
66 pub async fn fetch(self) -> Result<Vec<LovedTrack>> {
71 let limit = self
72 .limit
73 .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
74
75 self.fetch_tracks::<UserLovedTracks>(limit).await
76 }
77
78 pub async fn fetch_and_save(self, format: FileFormat, filename_prefix: &str) -> Result<String> {
90 let tracks = self.fetch().await?;
91 tracing::info!("Saving {} loved tracks to file", tracks.len());
92 let filename = FileHandler::save(&tracks, &format, filename_prefix)
93 .map_err(crate::error::LastFmError::Io)?;
94 Ok(filename)
95 }
96
97 pub async fn analyze(self, threshold: usize) -> Result<crate::analytics::TrackStats> {
110 let tracks = self.fetch().await?;
111 Ok(AnalysisHandler::analyze_tracks(&tracks, threshold))
112 }
113
114 pub async fn analyze_and_print(self, threshold: usize) -> Result<()> {
123 let stats = self.analyze(threshold).await?;
124 AnalysisHandler::print_analysis(&stats);
125 Ok(())
126 }
127
128 async fn fetch_tracks<T>(&self, limit: TrackLimit) -> Result<Vec<LovedTrack>>
129 where
130 T: DeserializeOwned + TrackContainer<TrackType = LovedTrack>,
131 {
132 use crate::url_builder::QueryParams;
133
134 fetch_tracks::<LovedTrack, T>(
135 self.http.clone(),
136 self.config.clone(),
137 self.username.clone(),
138 "user.getlovedtracks",
139 limit,
140 QueryParams::new(),
141 )
142 .await
143 }
144}
145
146impl TrackContainer for UserLovedTracks {
147 type TrackType = LovedTrack;
148
149 fn total_tracks(&self) -> u32 {
150 self.lovedtracks.attr.total
151 }
152
153 fn tracks(self) -> Vec<Self::TrackType> {
154 self.lovedtracks.track
155 }
156}