use std::convert::Infallible;
use chrono::{DateTime, Utc};
use lunar_lib::database::{
CompareAndSwapTransaction, CustomTransactionError, DatabaseEntry, TransactionError,
caching::Cacheable,
};
use crate::{
database::{
ARTIST_CACHE, Createable, LibraryDb, Mergeable, artist_add_tracks, patch_option_replace,
patch_vec, tracks_add_artist, tracks_remove_artist,
},
library::{
album::Album,
artist::{Artist, ArtistGroup, ArtistId},
track::Track,
},
};
impl std::hash::Hash for Artist {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write(&*self.id);
}
}
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;
type Db = LibraryDb;
const VERSION_NUMBER: u32 = 1;
const TREE_NAME: &str = "artist";
fn id(&self) -> Self::Id {
self.id
}
fn pre_upsert(
&mut self,
cas_tx: &CompareAndSwapTransaction<Self::Db>,
) -> Result<(), TransactionError> {
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 Cacheable for Artist {
fn cache() -> &'static std::sync::Mutex<lunar_lib::database::caching::DbCache<Self>> {
&ARTIST_CACHE
}
}
impl Mergeable for Artist {
type Err = Infallible;
fn tx_merge(
self,
merge_into: Self::Id,
cas_tx: &mut CompareAndSwapTransaction<Self::Db>,
) -> Result<Self, CustomTransactionError<Self::Err>> {
let self_id = self.id();
let Some(mut artist) = cas_tx.tx_get(merge_into)? else {
if self_id == merge_into {
cas_tx.tx_upsert(self.clone())?;
return Ok(self);
}
return Err(TransactionError::MissingEntry.into());
};
artist.name = self.name;
patch_option_replace(&mut artist.cover_art, self.cover_art);
patch_option_replace(&mut artist.description, self.description);
patch_vec(&mut artist.tracks, self.tracks);
patch_vec(&mut artist.albums, self.albums);
let mut tracks: Vec<Track> = cas_tx.tx_get_batch(&artist.tracks)?;
tracks_add_artist(&mut tracks, merge_into);
cas_tx.tx_upsert(artist.clone())?;
if self_id != merge_into {
tracks_remove_artist(&mut tracks, self_id);
cas_tx.tx_remove(self_id)?;
}
for track in tracks {
cas_tx.tx_upsert(track)?;
}
Ok(artist)
}
}
impl std::ops::Deref for Artist {
type Target = ArtistId;
fn deref(&self) -> &Self::Target {
&self.id
}
}
#[derive(Debug, Clone)]
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,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum ArtistCreationError {
#[error("Artist name resolved to an empty string")]
EmptyName,
}
impl Createable for Artist {
type CreateArgs = ArtistCreateArgs;
type Err = ArtistCreationError;
fn tx_create(
cas_tx: &mut CompareAndSwapTransaction<Self::Db>,
args: Self::CreateArgs,
) -> Result<Self, CustomTransactionError<Self::Err>> {
let mut artist = Artist::new(args.name).ok_or(CustomTransactionError::Closure(
ArtistCreationError::EmptyName,
))?;
let artist_id = artist.id();
artist_add_tracks(&mut artist, args.tracks.iter().map(Track::id));
cas_tx.tx_insert(artist.clone())?;
for mut track in args.tracks {
track.metadata.artists.add_artist(artist_id);
cas_tx.tx_upsert(track)?;
}
Ok(artist)
}
}