use lunar_lib::database::{
CompareAndSwapTransaction, CustomTransactionError, Database, DatabaseEntry, DatabaseError,
TransactionError,
};
use crate::{
database::{Createable, LibraryDb, Mergeable, Patchable},
library::{
album::{Album, AlbumId, TrackReference},
artist::{ArtistGroup, ArtistId},
track::TrackId,
},
};
pub trait CasTxExtensions<CasDb: Database, T: DatabaseEntry<EntryDb = CasDb>> {
fn tx_patch(&mut self, item: T) -> Result<(), TransactionError>
where
T: Patchable<T>;
fn tx_merge(&mut self, from: &T, into: T::Id) -> Result<(), CustomTransactionError<T::Err>>
where
T: Mergeable;
fn tx_create(&mut self, args: T::CreateArgs) -> Result<T, CustomTransactionError<T::Err>>
where
T: Createable;
}
impl<CasDb: Database, T: DatabaseEntry<EntryDb = CasDb>> CasTxExtensions<CasDb, T>
for CompareAndSwapTransaction<CasDb>
{
fn tx_patch(&mut self, item: T) -> Result<(), TransactionError>
where
T: Patchable<T>,
{
if let Some(mut old_item) = self.tx_get(item.id())? {
let item_id = item.id();
old_item.patch(item);
self.tx_upsert(item_id, Some(old_item))?;
} else {
self.tx_upsert(item.id(), Some(item))?;
}
Ok(())
}
fn tx_merge(&mut self, from: &T, into: T::Id) -> Result<(), CustomTransactionError<T::Err>>
where
T: Mergeable,
{
T::tx_merge(from, into, self)
}
fn tx_create(&mut self, args: T::CreateArgs) -> Result<T, CustomTransactionError<T::Err>>
where
T: Createable,
{
T::tx_create(self, args)
}
}
pub trait CasTxLibraryExtensions {
fn album_set_and_relink_tracks(
&mut self,
album_id: AlbumId,
tracks: &[TrackId],
) -> Result<bool, TransactionError>;
fn album_set_and_relink_artists(
&mut self,
album_id: AlbumId,
artists: &[ArtistId],
) -> Result<bool, 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;
self.tx_upsert(track.id(), Some(track.clone()))?;
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.id(), Some(old_album))?;
}
if let Some(new_album_id) = album {
let mut new_album = self
.tx_get(new_album_id)?
.ok_or(DatabaseError::MissingEntry)?;
new_album.tracks.push(TrackReference {
id: track_id,
track_num: None,
disc_num: None,
});
self.tx_upsert(new_album.id(), Some(new_album))?;
}
Ok(true)
}
fn album_set_and_relink_artists(
&mut self,
album_id: AlbumId,
artists: &[ArtistId],
) -> Result<bool, TransactionError> {
let mut album = self.tx_get(album_id)?.ok_or(DatabaseError::MissingEntry)?;
let old_artists: Vec<ArtistId> = album.artist_group.artist_ids().to_vec();
album.artist_group = ArtistGroup::from_artist_ids(artists.iter().copied());
let removed_artists: Vec<ArtistId> = old_artists
.into_iter()
.filter(|old_artist| !artists.contains(old_artist))
.collect();
self.artists_add_album(album_id, artists)?;
self.artists_remove_album(album_id, &removed_artists)?;
self.tx_upsert(album_id, Some(album))?;
Ok(true)
}
fn album_set_and_relink_tracks(
&mut self,
album_id: AlbumId,
tracks: &[TrackId],
) -> Result<bool, TransactionError> {
let album = self.tx_get(album_id)?.ok_or(DatabaseError::MissingEntry)?;
let old_tracks: Vec<TrackId> = album.tracks.iter().map(|t| t.id).collect();
let removed_tracks: Vec<TrackId> = old_tracks
.iter()
.filter(|old_track| !tracks.contains(old_track))
.copied()
.collect();
self.album_set_tracks(album, tracks)?;
self.tracks_set_album(Some(album_id), tracks)?;
self.tracks_set_album(None, &removed_tracks)?;
Ok(true)
}
}
pub(crate) trait CasTxUnsafeLibraryExtensions {
fn album_set_tracks(
&mut self,
album: Album,
tracks: &[TrackId],
) -> Result<(), TransactionError>;
fn tracks_set_album<'a>(
&mut self,
album_id: Option<AlbumId>,
tracks: impl IntoIterator<Item = &'a TrackId>,
) -> Result<(), TransactionError>;
fn artists_remove_album(
&mut self,
album_id: AlbumId,
artists: &[ArtistId],
) -> Result<(), TransactionError>;
fn artists_add_album(
&mut self,
album_id: AlbumId,
artists: &[ArtistId],
) -> Result<(), TransactionError>;
fn artist_add_tracks(
&mut self,
artist_id: ArtistId,
tracks: &[TrackId],
) -> Result<(), TransactionError>;
}
impl CasTxUnsafeLibraryExtensions for CompareAndSwapTransaction<LibraryDb> {
fn album_set_tracks(
&mut self,
mut album: Album,
tracks: &[TrackId],
) -> Result<(), TransactionError> {
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();
self.tx_upsert(album.id(), Some(album))?;
Ok(())
}
fn tracks_set_album<'a>(
&mut self,
album_id: Option<AlbumId>,
tracks: impl IntoIterator<Item = &'a TrackId>,
) -> Result<(), TransactionError> {
for track_id in tracks {
let Some(mut track) = self.tx_get(*track_id)? else {
return Err(DatabaseError::MissingEntry.into());
};
track.metadata.album = album_id;
for artist_id in track.metadata.artists.artist_ids() {
if let Some(album_id) = album_id {
let Some(mut artist) = self.tx_get(*artist_id)? else {
return Err(DatabaseError::MissingEntry.into());
};
if artist.albums.contains(&album_id) {
artist.tracks.retain(|t| t != track_id);
self.tx_upsert(*artist_id, Some(artist))?;
} else {
self.artist_add_tracks(*artist_id, &[*track_id])?;
}
} else {
self.artist_add_tracks(*artist_id, &[*track_id])?;
}
}
self.tx_upsert(*track_id, Some(track))?;
}
Ok(())
}
fn artists_remove_album(
&mut self,
album_id: AlbumId,
artists: &[ArtistId],
) -> Result<(), TransactionError> {
for artist_id in artists {
let Some(mut artist) = self.tx_get(*artist_id)? else {
return Err(DatabaseError::MissingEntry.into());
};
artist.albums.retain(|a| *a != album_id);
self.tx_upsert(*artist_id, Some(artist))?;
}
Ok(())
}
fn artists_add_album(
&mut self,
album_id: AlbumId,
artists: &[ArtistId],
) -> Result<(), TransactionError> {
for artist_id in artists {
let Some(mut artist) = self.tx_get(*artist_id)? else {
return Err(DatabaseError::MissingEntry.into());
};
if !artist.albums.contains(&album_id) {
artist.albums.push(album_id);
}
self.tx_upsert(*artist_id, Some(artist))?;
}
Ok(())
}
fn artist_add_tracks(
&mut self,
artist_id: ArtistId,
tracks: &[TrackId],
) -> Result<(), TransactionError> {
let Some(mut artist) = self.tx_get(artist_id)? else {
return Err(TransactionError::Database(DatabaseError::MissingEntry));
};
for track_id in tracks {
let Some(track) = self.tx_get(*track_id)? else {
return Err(TransactionError::Database(DatabaseError::MissingEntry));
};
if let Some(album_id) = track.metadata.album
&& artist.albums.contains(&album_id)
{
continue;
}
if !artist.tracks.contains(track_id) {
artist.tracks.push(*track_id);
}
}
self.tx_upsert(artist_id, Some(artist))?;
Ok(())
}
}