use lunar_lib::{
database::{CompareAndSwapTransaction, TransactionError},
iterator_ext::IteratorExtensions,
};
use crate::{
database::LibraryDb,
library::{
album::{Album, AlbumId, TrackReference},
artist::{Artist, ArtistGroup, ArtistId},
track::{Track, TrackId},
},
};
pub trait CasTxLibraryExtensions {
fn album_set_and_relink_tracks(
&mut self,
album: Album,
tracks: Vec<Track>,
) -> Result<(Album, Vec<Track>), TransactionError>;
fn album_set_and_relink_artists(
&mut self,
album_id: Album,
artists: impl IntoIterator<Item = Artist>,
) -> Result<(Album, Vec<Artist>), TransactionError>;
fn relink_track_to_album(
&mut self,
track_id: TrackId,
album: Option<AlbumId>,
) -> Result<bool, TransactionError>;
}
impl CasTxLibraryExtensions for CompareAndSwapTransaction<LibraryDb> {
fn relink_track_to_album(
&mut self,
track_id: TrackId,
album: Option<AlbumId>,
) -> Result<bool, TransactionError> {
let Some(mut track) = self.tx_get(track_id)? else {
return Ok(false);
};
if track.metadata().album == album {
return Ok(false);
}
let old_album_id = track.metadata.album;
track.metadata.album = album;
if let Some(old_album_id) = old_album_id {
let mut old_album = self.tx_get(old_album_id)?.unwrap_or_else(|| {
panic!(
"Track '{}' contains a reference to an album that doesnt exist",
track.metadata.safe_title()
)
});
old_album.tracks.retain(|t| t.id != track_id);
self.tx_upsert(old_album)?;
}
if let Some(new_album_id) = album {
let mut new_album = self
.tx_get(new_album_id)?
.ok_or(TransactionError::MissingEntry)?;
new_album.tracks.push(TrackReference {
id: track_id,
track_num: None,
disc_num: None,
});
self.tx_upsert(new_album)?;
}
self.tx_upsert(track)?;
Ok(true)
}
fn album_set_and_relink_artists(
&mut self,
mut album: Album,
artists: impl IntoIterator<Item = Artist>,
) -> Result<(Album, Vec<Artist>), TransactionError> {
let mut artists = artists.into_iter().to_vec();
let artist_ids = artists.iter().map(Artist::id).to_vec();
let old_artists = album.artists.artist_ids().to_vec();
let mut removed_artists = old_artists
.iter()
.filter_map(|a| -> Option<Result<Artist, TransactionError>> {
(!artist_ids.contains(a)).then_some(
self.tx_get(*a)
.transpose()
.ok_or(TransactionError::MissingEntry)
.flatten(),
)
})
.collect::<Result<Vec<Artist>, _>>()?;
album.artists = artist_ids;
let album_id = album.id();
artists_add_album(&mut artists, album_id);
artists_remove_album(&mut removed_artists, album_id);
self.tx_upsert(album.clone())?;
for artist in artists.iter().chain(removed_artists.iter()) {
self.tx_upsert(artist.clone())?;
}
Ok((album, artists))
}
fn album_set_and_relink_tracks(
&mut self,
mut album: Album,
mut tracks: Vec<Track>,
) -> Result<(Album, Vec<Track>), TransactionError> {
let track_ids: Vec<TrackId> = tracks.iter().map(Track::id).collect();
let old_tracks: Vec<TrackId> = album.tracks.iter().map(|t| t.id).collect();
let mut removed_tracks: Vec<Track> = old_tracks
.into_iter()
.filter(|old_track| !track_ids.contains(old_track))
.try_map(|id| {
self.tx_get(id)
.transpose()
.ok_or(TransactionError::MissingEntry)
.flatten()
})?
.collect();
album_set_tracks(&mut album, &track_ids);
tracks_set_album(&mut tracks, Some(album.id()));
tracks_set_album(&mut removed_tracks, None);
Ok((album, tracks))
}
}
pub(crate) fn album_set_tracks(album: &mut Album, tracks: &[TrackId]) {
album.tracks = tracks
.iter()
.map(|t| {
album
.tracks
.iter()
.find(|old| old.id == *t)
.copied()
.unwrap_or(TrackReference {
id: *t,
track_num: None,
disc_num: None,
})
})
.collect();
}
pub(crate) fn album_add_track(album: &mut Album, track: TrackId) {
album.tracks.push(TrackReference {
id: track,
track_num: None,
disc_num: None,
});
}
pub(crate) fn tracks_set_album<'a>(
tracks: impl IntoIterator<Item = &'a mut Track>,
album_id: Option<AlbumId>,
) {
for track in tracks {
track.metadata.album = album_id;
}
}
pub(crate) fn tracks_add_artist<'a>(
tracks: impl IntoIterator<Item = &'a mut Track>,
artist_id: ArtistId,
) {
for track in tracks {
if !track.metadata.artists.contains(&artist_id) {
track.metadata.artists.push(artist_id);
}
}
}
pub(crate) fn tracks_remove_artist<'a>(
tracks: impl IntoIterator<Item = &'a mut Track>,
artist_id: ArtistId,
) {
for track in tracks {
track.metadata.artists.retain(|a| *a != artist_id);
}
}
pub(crate) fn artists_remove_album<'a>(
artists: impl IntoIterator<Item = &'a mut Artist>,
album_id: AlbumId,
) {
for artist in artists {
artist.albums.retain(|a| *a != album_id);
}
}
pub(crate) fn artists_add_album<'a>(
artists: impl IntoIterator<Item = &'a mut Artist>,
album_id: AlbumId,
) {
for artist in artists {
if !artist.albums.contains(&album_id) {
artist.albums.push(album_id);
}
}
}
pub(crate) fn artist_add_tracks<'a>(
artist: &mut Artist,
track_ids: impl IntoIterator<Item = TrackId>,
) {
for track in track_ids {
if !artist.tracks.contains(&track) {
artist.tracks.push(track);
}
}
}