use chrono::{DateTime, Utc};
use sled::{Db, Tree};
use crate::{
database::{
CompareAndSwapTransaction, Createable, DatabaseEntry, DatabaseError, Patchable,
artist_tree, patch_option_replace, patch_replace, patch_vec,
validator::DatabaseReferenceError,
},
library::{
album::Album,
artist::{Artist, ArtistId},
track::Track,
},
};
impl std::hash::Hash for Artist {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write(self.id.as_bytes());
}
}
impl PartialEq for Artist {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Eq for Artist {}
impl DatabaseEntry for Artist {
type Id = ArtistId;
const VERSION_NUMBER: u32 = 1;
fn tree(db: &Db) -> Tree {
artist_tree(db)
}
fn id(&self) -> Self::Id {
self.id
}
fn pre_upsert(&mut self, cas_tx: &CompareAndSwapTransaction) -> Result<(), DatabaseError> {
let mut albums = self.tx_albums(cas_tx)?;
albums.sort_unstable_by(|a, b| {
let a_date = a.date.unwrap_or(DateTime::<Utc>::MAX_UTC);
let b_date = b.date.unwrap_or(DateTime::<Utc>::MAX_UTC);
a_date.cmp(&b_date).then(a.name.cmp(&b.name))
});
self.albums = albums.iter().map(Album::id).collect();
Ok(())
}
}
impl Patchable<Self> for Artist {
fn patch(&mut self, patch: Self) {
let Artist {
id: _,
name,
cover_art,
description,
tracks,
albums,
version: _,
} = patch;
patch_option_replace(&mut self.cover_art, cover_art);
patch_option_replace(&mut self.description, description);
patch_replace(&mut self.name, Some(name));
patch_vec(&mut self.tracks, tracks);
patch_vec(&mut self.albums, albums);
}
}
impl std::ops::Deref for Artist {
type Target = ArtistId;
fn deref(&self) -> &Self::Target {
&self.id
}
}
pub struct ArtistCreateArgs {
pub name: String,
pub albums: Vec<Album>,
pub tracks: Vec<Track>,
}
impl ArtistCreateArgs {
#[must_use]
pub fn new(name: String, albums: Vec<Album>, tracks: Vec<Track>) -> Self {
Self {
name,
albums,
tracks,
}
}
}
impl Createable for Artist {
type CreateArgs = ArtistCreateArgs;
fn tx_create(
cas_tx: &mut CompareAndSwapTransaction,
args: Self::CreateArgs,
) -> Result<Self::Id, DatabaseError> {
let mut artist = Artist::new(args.name).ok_or(DatabaseError::InvalidInput(
"Name field must not be empty".to_owned(),
))?;
let artist_id = artist.id();
for track in &args.tracks {
if let Some(album) = args.albums.iter().find(|album| {
album
.tracks
.iter()
.any(|track_ref| track_ref.id == track.id())
}) {
return Err(DatabaseError::ValidationError(
DatabaseReferenceError::ArtistInvalidTrackRef {
artist: artist.id(),
track: track.id(),
album: album.id(),
},
));
}
if !artist.tracks.contains(&track.id()) {
artist.tracks.push(track.id());
}
}
cas_tx.tx_insert(artist)?;
for mut track in args.tracks {
track.metadata.artists.add_artist(artist_id);
cas_tx.tx_patch(track)?;
}
Ok(artist_id)
}
}