use chrono::{DateTime, Utc};
use lunar_lib::{
database::{
CompareAndSwapTransaction, Createable, CustomTransactionError, DatabaseEntry, DbIdExt,
DbIdIterExt, Entry, Mergeable, TransactionError, caching::Cacheable,
},
id::Id,
log::warn,
vec_ext::VecExtensions,
};
use crate::{
SeleneIdExt,
database::{Library, Searchable},
library::artist::{Artist, ArtistCreateArgs, ArtistCreationError},
};
impl DatabaseEntry for Artist {
type DbInner = Library;
const VERSION_NUMBER: u32 = 1;
const TREE_NAME: &str = "artist";
fn pre_upsert(
entry: &mut Entry<Self>,
cas_tx: &mut CompareAndSwapTransaction<Library>,
) -> Result<(), TransactionError> {
let mut albums = entry.albums.iter().tx_get(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.title.cmp(&b.title))
});
entry.albums = albums.iter().map(Entry::id).collect();
Ok(())
}
}
impl Cacheable for Artist {}
impl Searchable for Artist {
const SEARCH_INDEX: &'static str = "artist_search";
fn search_name(&self) -> Option<&str> {
Some(&*self.name)
}
}
impl Mergeable for Artist {
fn merge_data(&mut self, from: Self) {
self.name = from.name;
self.art = self.art.take().or(from.art);
self.description = self.description.take().or(from.description);
self.tracks.extend_unique(from.tracks);
self.albums.extend_unique(from.albums);
}
fn relink_references(
from: &Entry<Self>,
to: Id<Self>,
cas_tx: &mut CompareAndSwapTransaction<Self::DbInner>,
) -> Result<(), TransactionError> {
from.tracks.iter().tx_fetch_and_update(
|old| {
let Some(mut track) = old else {
warn!(
"Artist '{}' attempted to relink to a track which does not exist yet",
from.name()
);
return None;
};
track.metadata.artists.push_unique(to);
Some(track)
},
cas_tx,
)?;
from.albums.iter().tx_fetch_and_update(
|old| {
let Some(mut album) = old else {
warn!(
"Artist '{}' attempted to relink to a album which does not exist yet",
from.name()
);
return None;
};
album.artists.push_unique(to);
Some(album)
},
cas_tx,
)?;
Ok(())
}
}
impl Createable for Artist {
type CreateArgs = ArtistCreateArgs;
type Err = ArtistCreationError;
fn create(
args: Self::CreateArgs,
cas_tx: &mut CompareAndSwapTransaction<Self::DbInner>,
) -> Result<Entry<Self>, CustomTransactionError<Self::Err>> {
let id = Id::from_string_hash(&args.name);
if id.tx_check(cas_tx)? {
return Err(CustomTransactionError::Transaction(
TransactionError::AlreadyInDatabase,
));
}
args.tracks.tx_fetch_and_update(
|old| {
let mut track = old.expect("Invalid ref");
track.metadata.artists.push_unique(id);
Some(track)
},
cas_tx,
)?;
args.albums.tx_fetch_and_update(
|old| {
let mut album = old.expect("Invalid ref");
album.artists.push_unique(id);
Some(album)
},
cas_tx,
)?;
let artist = Artist::new(args.name)
.map_err(CustomTransactionError::Closure)?
.to_entry(id);
Ok(artist)
}
}