lastfm_client/api/user/recent_tracks/
extended.rs1use crate::api::constants::METHOD_RECENT_TRACKS;
4use crate::api::fetch_utils::{ResourceContainer, fetch};
5use crate::error::Result;
6use crate::file_handler::FileHandler;
7use crate::types::{
8 RecentTrackExtended, Timestamped, TrackLimit, TrackList, UserRecentTracksExtended,
9};
10use crate::url_builder::QueryParams;
11
12use serde::de::DeserializeOwned;
13
14use super::builder::{RecentTracksRequestBuilder, validate_date_range};
15
16impl RecentTracksRequestBuilder {
17 pub async fn fetch_extended(self) -> Result<TrackList<RecentTrackExtended>> {
24 validate_date_range(self.from, self.to)?;
25
26 let mut params = self.build_params();
27 params.insert("extended".to_string(), "1".to_string());
28
29 let limit = self
30 .limit
31 .map_or(TrackLimit::Unlimited, TrackLimit::Limited);
32
33 fetch_tracks_extended::<UserRecentTracksExtended>(&self, limit, params)
34 .await
35 .map(TrackList::from)
36 }
37
38 pub async fn fetch_extended_and_save(
50 self,
51 format: crate::file_handler::FileFormat,
52 filename_prefix: &str,
53 ) -> Result<String> {
54 let tracks = self.fetch_extended().await?;
55 tracing::info!("Saving {} recent tracks (extended) to file", tracks.len());
56
57 let filename = FileHandler::save(&tracks, &format, filename_prefix)
58 .map_err(crate::error::LastFmError::Io)?;
59
60 if let Some(latest_ts) = tracks.first().and_then(Timestamped::get_timestamp) {
61 FileHandler::write_sidecar_timestamp(&filename, latest_ts)
62 .map_err(crate::error::LastFmError::Io)?;
63 }
64 Ok(filename)
65 }
66
67 pub async fn fetch_extended_and_update(self, file_path: &str) -> Result<usize> {
81 let ext = std::path::Path::new(file_path)
82 .extension()
83 .and_then(|e| e.to_str())
84 .map(str::to_ascii_lowercase);
85 let is_csv = ext.as_deref() == Some("csv");
86 let is_ndjson = ext.as_deref() == Some("ndjson");
87
88 let since_timestamp = if let Some(ts) = FileHandler::read_sidecar_timestamp(file_path) {
89 Some(ts)
90 } else if !is_csv && !is_ndjson && std::path::Path::new(file_path).exists() {
91 let existing: Vec<RecentTrackExtended> =
92 FileHandler::load(file_path).map_err(crate::error::LastFmError::Io)?;
93 let ts = existing.iter().filter_map(Timestamped::get_timestamp).max();
94
95 if let Some(t) = ts {
96 FileHandler::write_sidecar_timestamp(file_path, t)
97 .map_err(crate::error::LastFmError::Io)?;
98 }
99
100 ts
101 } else {
102 None
103 };
104
105 let builder = match since_timestamp {
106 Some(ts) => self.since(i64::from(ts) + 1),
107 None => self,
108 };
109
110 let new_tracks = builder.fetch_extended().await?;
111 let count = new_tracks.len();
112
113 if !new_tracks.is_empty() {
114 if let Some(latest_ts) = new_tracks.first().and_then(Timestamped::get_timestamp) {
115 FileHandler::write_sidecar_timestamp(file_path, latest_ts)
116 .map_err(crate::error::LastFmError::Io)?;
117 }
118
119 if is_csv {
120 FileHandler::append_or_create_csv(&new_tracks, file_path)
121 .map_err(crate::error::LastFmError::Io)?;
122 } else if is_ndjson {
123 FileHandler::append_or_create_ndjson(&new_tracks, file_path)
124 .map_err(crate::error::LastFmError::Io)?;
125 } else {
126 FileHandler::prepend_json(&new_tracks, file_path)
127 .map_err(crate::error::LastFmError::Io)?;
128 }
129 }
130
131 Ok(count)
132 }
133
134 #[cfg(feature = "sqlite")]
145 pub async fn fetch_extended_and_save_sqlite(
146 self,
147 filename_prefix: &str,
148 ) -> crate::error::Result<String> {
149 let tracks = self.fetch_extended().await?;
150
151 tracing::info!("Saving {} extended recent tracks to SQLite", tracks.len());
152
153 crate::file_handler::FileHandler::save_sqlite(&tracks, filename_prefix)
154 .map_err(crate::error::LastFmError::Io)
155 }
156
157 #[cfg(feature = "sqlite")]
170 pub async fn fetch_extended_and_update_sqlite(
171 self,
172 db_path: &str,
173 ) -> crate::error::Result<usize> {
174 let since_timestamp = crate::file_handler::FileHandler::read_sqlite_max_timestamp(
175 db_path,
176 <RecentTrackExtended as crate::sqlite::SqliteExportable>::table_name(),
177 );
178
179 let builder = match since_timestamp {
180 Some(ts) => self.since(i64::from(ts) + 1),
181 None => self,
182 };
183
184 let new_tracks = builder.fetch_extended().await?;
185 let count = new_tracks.len();
186
187 if !new_tracks.is_empty() {
188 crate::file_handler::FileHandler::append_or_create_sqlite(&new_tracks, db_path)
189 .map_err(crate::error::LastFmError::Io)?;
190 }
191
192 Ok(count)
193 }
194}
195
196async fn fetch_tracks_extended<T>(
197 builder: &RecentTracksRequestBuilder,
198 limit: TrackLimit,
199 additional_params: QueryParams,
200) -> Result<Vec<RecentTrackExtended>>
201where
202 T: DeserializeOwned + ResourceContainer<ItemType = RecentTrackExtended>,
203{
204 fetch::<RecentTrackExtended, T>(
205 builder.http.clone(),
206 builder.config.clone(),
207 builder.username.clone(),
208 METHOD_RECENT_TRACKS,
209 limit,
210 additional_params,
211 builder.progress_callback.as_ref(),
212 )
213 .await
214}