use crate::api::constants::METHOD_RECENT_TRACKS;
use crate::api::fetch_utils::{ResourceContainer, fetch};
use crate::error::Result;
use crate::file_handler::FileHandler;
use crate::types::{
RecentTrackExtended, Timestamped, TrackLimit, TrackList, UserRecentTracksExtended,
};
use crate::url_builder::QueryParams;
use serde::de::DeserializeOwned;
use super::builder::{RecentTracksRequestBuilder, validate_date_range};
impl RecentTracksRequestBuilder {
pub async fn fetch_extended(self) -> Result<TrackList<RecentTrackExtended>> {
validate_date_range(self.from, self.to)?;
let mut params = self.build_params();
params.insert("extended".to_string(), "1".to_string());
let limit = self
.limit
.map_or(TrackLimit::Unlimited, TrackLimit::Limited);
fetch_tracks_extended::<UserRecentTracksExtended>(&self, limit, params)
.await
.map(TrackList::from)
}
pub async fn fetch_extended_and_save(
self,
format: crate::file_handler::FileFormat,
filename_prefix: &str,
) -> Result<String> {
let tracks = self.fetch_extended().await?;
tracing::info!("Saving {} recent tracks (extended) to file", tracks.len());
let filename = FileHandler::save(&tracks, &format, filename_prefix)
.map_err(crate::error::LastFmError::Io)?;
if let Some(latest_ts) = tracks.first().and_then(Timestamped::get_timestamp) {
FileHandler::write_sidecar_timestamp(&filename, latest_ts)
.map_err(crate::error::LastFmError::Io)?;
}
Ok(filename)
}
pub async fn fetch_extended_and_update(self, file_path: &str) -> Result<usize> {
let ext = std::path::Path::new(file_path)
.extension()
.and_then(|e| e.to_str())
.map(str::to_ascii_lowercase);
let is_csv = ext.as_deref() == Some("csv");
let is_ndjson = ext.as_deref() == Some("ndjson");
let since_timestamp = if let Some(ts) = FileHandler::read_sidecar_timestamp(file_path) {
Some(ts)
} else if !is_csv && !is_ndjson && std::path::Path::new(file_path).exists() {
let existing: Vec<RecentTrackExtended> =
FileHandler::load(file_path).map_err(crate::error::LastFmError::Io)?;
let ts = existing.iter().filter_map(Timestamped::get_timestamp).max();
if let Some(t) = ts {
FileHandler::write_sidecar_timestamp(file_path, t)
.map_err(crate::error::LastFmError::Io)?;
}
ts
} else {
None
};
let builder = match since_timestamp {
Some(ts) => self.since(i64::from(ts) + 1),
None => self,
};
let new_tracks = builder.fetch_extended().await?;
let count = new_tracks.len();
if !new_tracks.is_empty() {
if let Some(latest_ts) = new_tracks.first().and_then(Timestamped::get_timestamp) {
FileHandler::write_sidecar_timestamp(file_path, latest_ts)
.map_err(crate::error::LastFmError::Io)?;
}
if is_csv {
FileHandler::append_or_create_csv(&new_tracks, file_path)
.map_err(crate::error::LastFmError::Io)?;
} else if is_ndjson {
FileHandler::append_or_create_ndjson(&new_tracks, file_path)
.map_err(crate::error::LastFmError::Io)?;
} else {
FileHandler::prepend_json(&new_tracks, file_path)
.map_err(crate::error::LastFmError::Io)?;
}
}
Ok(count)
}
#[cfg(feature = "sqlite")]
pub async fn fetch_extended_and_save_sqlite(
self,
filename_prefix: &str,
) -> crate::error::Result<String> {
let tracks = self.fetch_extended().await?;
tracing::info!("Saving {} extended recent tracks to SQLite", tracks.len());
crate::file_handler::FileHandler::save_sqlite(&tracks, filename_prefix)
.map_err(crate::error::LastFmError::Io)
}
#[cfg(feature = "sqlite")]
pub async fn fetch_extended_and_update_sqlite(
self,
db_path: &str,
) -> crate::error::Result<usize> {
let since_timestamp = crate::file_handler::FileHandler::read_sqlite_max_timestamp(
db_path,
<RecentTrackExtended as crate::sqlite::SqliteExportable>::table_name(),
);
let builder = match since_timestamp {
Some(ts) => self.since(i64::from(ts) + 1),
None => self,
};
let new_tracks = builder.fetch_extended().await?;
let count = new_tracks.len();
if !new_tracks.is_empty() {
crate::file_handler::FileHandler::append_or_create_sqlite(&new_tracks, db_path)
.map_err(crate::error::LastFmError::Io)?;
}
Ok(count)
}
}
async fn fetch_tracks_extended<T>(
builder: &RecentTracksRequestBuilder,
limit: TrackLimit,
additional_params: QueryParams,
) -> Result<Vec<RecentTrackExtended>>
where
T: DeserializeOwned + ResourceContainer<ItemType = RecentTrackExtended>,
{
fetch::<RecentTrackExtended, T>(
builder.http.clone(),
builder.config.clone(),
builder.username.clone(),
METHOD_RECENT_TRACKS,
limit,
additional_params,
builder.progress_callback.as_ref(),
)
.await
}