use lunar_lib::{
database::{
CompareAndSwapTransaction, DatabaseEntry, Db, DbIdExt, DbIdIterExt, Deleteable, Entry,
TransactionError, caching::Cacheable,
},
id::{ID_SIZE, Id},
};
use crate::{
database::{GenreIndexable, Searchable, Selene, album_remove_track, artist_remove_track},
library::{artist::Artist, track::Track},
};
impl DatabaseEntry for Track {
type DbInner = Selene;
const VERSION_NUMBER: u32 = 1;
const TREE_NAME: &str = "track";
fn pre_upsert(
entry: &mut Entry<Self>,
is_new: bool,
cas_tx: &mut CompareAndSwapTransaction<Self::DbInner>,
) -> Result<(), TransactionError> {
if is_new {
Self::update_search_index(entry, cas_tx)?;
Self::add_to_genres(entry, &entry.metadata.genres, cas_tx)?;
Self::update_to_singles(entry, &entry.metadata.artists, cas_tx)?;
}
Ok(())
}
}
impl Cacheable for Track {}
impl Deleteable for Track {
fn unlink_references(
track: Entry<Self>,
cas_tx: &mut CompareAndSwapTransaction<Self::DbInner>,
) -> Result<(), TransactionError> {
Self::remove_search_index(&track, cas_tx)?;
Self::remove_from_genres(&track, &track.metadata.genres, cas_tx)?;
Self::remove_from_artists(&track, &track.metadata.artists, cas_tx)?;
if let Some(album_id) = track.metadata.album {
album_id.tx_fetch_and_update(
|old, _| {
let mut album = old.expect("Dangling ref");
album_remove_track(&mut album, track.id());
Ok(Some(album))
},
cas_tx,
)?;
}
track.metadata.artists().tx_fetch_and_update(
|old, _| {
let mut artist = old.expect("Dangling ref");
artist_remove_track(&mut artist, track.id());
Ok(Some(artist))
},
cas_tx,
)?;
Ok(())
}
}
impl Searchable for Track {
const SEARCH_INDEX: &'static str = "track_search";
fn search_name(&self) -> Option<&str> {
self.metadata.title.as_deref()
}
}
impl GenreIndexable for Track {
const GENRE_INDEX: &'static str = "track_genres";
}
impl Track {
const SINGLES_INDEX: &'static str = "artist_singles";
pub fn get_singles_for_artist(
artist: Id<Artist>,
db: &Db<Selene>,
) -> Result<Vec<Id<Track>>, TransactionError> {
let Some(value) = db.index(Self::SINGLES_INDEX).get(artist)? else {
return Ok(Vec::new());
};
Ok(value
.as_chunks::<{ ID_SIZE }>()
.0
.iter()
.copied()
.map(Id::from)
.collect())
}
fn update_to_singles(
entry: &Entry<Self>,
artists: &[Id<Artist>],
cas_tx: &mut CompareAndSwapTransaction<Selene>,
) -> Result<(), TransactionError> {
if !entry.is_single() {
return Ok(());
}
let request = cas_tx.get_or_new_index(Self::SINGLES_INDEX);
for id in artists {
request.fetch_and_update(&**id, |old| {
let existing = old.as_deref().unwrap_or_default();
if existing.as_chunks::<{ ID_SIZE }>().0.contains(&*entry.id()) {
return old;
}
let mut new = Vec::with_capacity(existing.len() + ID_SIZE);
new.extend_from_slice(existing);
new.extend_from_slice(&*entry.id());
Some(new.into())
})?;
}
Ok(())
}
fn remove_from_artists(
entry: &Entry<Self>,
artists: &[Id<Artist>],
cas_tx: &mut CompareAndSwapTransaction<Selene>,
) -> Result<(), TransactionError> {
let request = cas_tx.get_or_new_index(Self::SINGLES_INDEX);
for id in artists {
request.fetch_and_update(&**id, |old| {
let new = old.as_deref().map(|old| {
let chunks = old.as_chunks::<{ ID_SIZE }>().0;
if let Some(idx) = chunks.iter().position(|c| *c == *entry.id()) {
let mut v = Vec::with_capacity((chunks.len() - 1) * { ID_SIZE });
v.extend_from_slice(&old[..idx * { ID_SIZE }]);
v.extend_from_slice(&old[(idx + 1) * { ID_SIZE }..]);
v
} else {
old.to_vec()
}
});
if let Some(new) = new
&& !new.is_empty()
{
Some(new.into())
} else {
None
}
})?;
}
Ok(())
}
}