use crate::file_handler::{FileFormat, FileHandler};
use crate::types::Timestamped;
pub trait LimitBuilder: Sized {
#[doc(hidden)]
fn limit_mut(&mut self) -> &mut Option<u32>;
#[must_use]
fn limit(mut self, n: u32) -> Self {
*self.limit_mut() = Some(n);
self
}
#[must_use]
fn unlimited(mut self) -> Self {
*self.limit_mut() = None;
self
}
}
#[allow(async_fn_in_trait)]
pub trait FetchAndSave: Sized {
type Item: serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static;
fn resource_label() -> &'static str;
#[doc(hidden)]
async fn do_fetch(self) -> crate::error::Result<Vec<Self::Item>>;
async fn fetch_and_save(
self,
format: FileFormat,
filename_prefix: &str,
) -> crate::error::Result<String> {
let items = self.do_fetch().await?;
tracing::info!("Saving {} {} to file", items.len(), Self::resource_label());
FileHandler::save(&items, &format, filename_prefix)
.map_err(crate::error::LastFmError::Io)
}
#[cfg(feature = "sqlite")]
async fn fetch_and_save_sqlite(
self,
filename_prefix: &str,
) -> crate::error::Result<String>
where
Self::Item: crate::sqlite::SqliteExportable,
{
let items = self.do_fetch().await?;
tracing::info!("Saving {} {} to SQLite", items.len(), Self::resource_label());
FileHandler::save_sqlite(&items, filename_prefix)
.map_err(crate::error::LastFmError::Io)
}
}
#[allow(async_fn_in_trait)]
pub trait FetchAndUpdate: Sized {
type Item: serde::Serialize
+ serde::de::DeserializeOwned
+ Timestamped
+ Clone
+ Send
+ Sync
+ 'static;
async fn fetch_since(self, max_ts: Option<u32>) -> crate::error::Result<Vec<Self::Item>>;
async fn fetch_and_update(self, file_path: &str) -> crate::error::Result<usize> {
let is_csv = std::path::Path::new(file_path)
.extension()
.is_some_and(|e| e.eq_ignore_ascii_case("csv"));
let max_ts = if let Some(ts) = FileHandler::read_sidecar_timestamp(file_path) {
Some(ts)
} else if !is_csv && std::path::Path::new(file_path).exists() {
let existing: Vec<Self::Item> =
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 new_items = self.fetch_since(max_ts).await?;
let count = new_items.len();
if !new_items.is_empty() {
if let Some(latest_ts) = new_items.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_items, file_path)
.map_err(crate::error::LastFmError::Io)?;
} else {
FileHandler::prepend_json(&new_items, file_path)
.map_err(crate::error::LastFmError::Io)?;
}
}
Ok(count)
}
#[cfg(feature = "sqlite")]
async fn fetch_and_update_sqlite(self, db_path: &str) -> crate::error::Result<usize>
where
Self::Item: crate::sqlite::SqliteExportable,
{
let max_ts = FileHandler::read_sqlite_max_timestamp(
db_path,
<Self::Item as crate::sqlite::SqliteExportable>::table_name(),
);
let new_items = self.fetch_since(max_ts).await?;
let count = new_items.len();
if !new_items.is_empty() {
FileHandler::append_or_create_sqlite(&new_items, db_path)
.map_err(crate::error::LastFmError::Io)?;
}
Ok(count)
}
}