use lunar_lib::{
database::{Db, DbIdExt, TransactionError},
id::Id,
log::{error, trace},
};
use thiserror::Error;
use crate::{
database::Library,
library::{Entry, album::Album, artist::Artist, track::Track},
};
#[derive(Debug, Error, Hash, PartialEq, Eq)]
pub enum DatabaseReferenceError {
#[error("Track points to an album, but the album does not point back")]
TrackDetatchedAlbumRef { track: Id<Track>, album: Id<Album> },
#[error("Track points to an album, but the album does not exist")]
TrackDanglingAlbumRef { track: Id<Track>, album: Id<Album> },
#[error("Track points to an artist, but the artist does not point back")]
TrackDetatchedArtistRef {
track: Id<Track>,
artist: Id<Artist>,
},
#[error("Track points to an artist, but the artist does not exist")]
TrackDanglingArtistRef {
track: Id<Track>,
artist: Id<Artist>,
},
#[error("Album points to a track, but the track does not point back")]
AlbumDetatchedTrackRef { album: Id<Album>, track: Id<Track> },
#[error("Album points to a track, but the track does not exist")]
AlbumDanglingTrackRef { album: Id<Album>, track: Id<Track> },
#[error("Album points to an artist, but the artist does not point back")]
AlbumDetatchedArtistRef {
album: Id<Album>,
artist: Id<Artist>,
},
#[error("Album points to an artist, but the artist does not exist")]
AlbumDanglingArtistRef {
album: Id<Album>,
artist: Id<Artist>,
},
#[error("Artist points to a track, but the track does not exist")]
ArtistDanglingTrackRef {
artist: Id<Artist>,
track: Id<Track>,
},
#[error("Artist points to a track, but the track does not point back")]
ArtistDetachedTrackRef {
artist: Id<Artist>,
track: Id<Track>,
},
#[error("Artist points to an album, but the album does not exist")]
ArtistDanglingAlbumRef {
artist: Id<Artist>,
album: Id<Album>,
},
#[error("Artist points to an album, but the album does not point back")]
ArtistDetachedAlbumRef {
artist: Id<Artist>,
album: Id<Album>,
},
#[error("Tried to add track to an artist, but the track is part of the artist's albums")]
ArtistInvalidTrackRef {
artist: Id<Artist>,
track: Id<Track>,
album: Id<Album>,
},
}
pub fn validate(db: &Db<Library>) -> Result<(), TransactionError> {
let mut was_error = false;
for track in db.iter_entries::<Track>() {
let track = Entry::from(track?);
if let Some(album_id) = track.metadata.album {
if let Some(album) = album_id.db_get(db)? {
if album.track_refs().iter().any(|t| *t.id == *track.id()) {
trace!(
"Track '{tr}' is correctly linked to album '{al}'",
tr = track.metadata.safe_title(),
al = album.name()
);
} else {
error!(
"Track '{tr}' points to album '{al}', but {al} does not point back",
tr = track.metadata.safe_title(),
al = album.name()
);
was_error = true;
}
} else {
error!(
"Track '{tr}' points to album '{al}' which does not exist",
tr = track.metadata.safe_title(),
al = album_id
);
was_error = true;
}
}
for artist_id in &track.metadata.artists {
if let Some(artist) = artist_id.db_get(db)? {
if artist.tracks.iter().any(|t| **t == *track.id()) {
trace!(
"Track '{tr}' is correctly linked to artist '{ar}'",
tr = track.metadata.safe_title(),
ar = artist.name()
);
} else {
error!(
"Track '{tr}' points to artist '{ar}', but {ar} does not point back",
tr = track.metadata.safe_title(),
ar = artist.name()
);
was_error = true;
}
} else {
error!(
"Track '{tr}' points to artist '{ar}' which does not exist",
tr = track.metadata.safe_title(),
ar = artist_id
);
was_error = true;
}
}
}
for album in db.iter_entries::<Album>() {
let album = album?;
for track_id in album.tracks.iter().map(|t| t.id) {
if let Some(track) = track_id.db_get(db)? {
if track.metadata.album.as_deref() == Some(&*album.id()) {
trace!(
"Album '{al}' is correctly linked to track '{tr}'",
al = album.name(),
tr = track.metadata.safe_title()
);
} else {
error!(
"Album '{al}' points to track '{tr}', but {tr} does not point back",
al = album.name(),
tr = track.metadata.safe_title()
);
was_error = true;
}
} else {
error!(
"Album '{al}' points to track '{tr}' which does not exist",
al = album.name(),
tr = track_id
);
was_error = true;
}
}
for artist_id in &album.artists {
if let Some(artist) = artist_id.db_get(db)? {
if artist.albums.iter().any(|t| **t == *album.id()) {
trace!(
"Album '{al}' is correctly linked to artist '{ar}'",
al = album.name(),
ar = artist.name()
);
} else {
error!(
"Album '{al}' points to artist '{ar}', but {ar} does not point back",
al = album.name(),
ar = artist.name()
);
was_error = true;
}
} else {
error!(
"Album '{al}' points to artist '{ar}' which does not exist",
al = album.name(),
ar = artist_id
);
was_error = true;
}
}
}
for artist in db.iter_entries::<Artist>() {
let artist = artist?;
for track_id in &artist.tracks {
if let Some(track) = track_id.db_get(db)? {
if track.metadata.artists.iter().any(|a| **a == *artist.id()) {
trace!(
"Artist '{ar}' is correctly linked to track '{tr}'",
ar = artist.name(),
tr = track.metadata.safe_title()
);
} else {
error!(
"Artist '{ar}' points to track '{tr}', but {tr} does not point back",
ar = artist.name(),
tr = track.metadata.safe_title()
);
was_error = true;
}
} else {
error!(
"Artist '{ar}' points to track '{tr}' which does not exist",
ar = artist.name(),
tr = track_id
);
was_error = true;
}
}
for album_id in &artist.albums {
if let Some(album) = album_id.db_get(db)? {
if album.artists().iter().any(|a| **a == *artist.id()) {
trace!(
"Artist '{ar}' is correctly linked to album '{al}'",
ar = artist.name(),
al = album.name()
);
} else {
error!(
"Artist '{ar}' points to album '{al}', but {al} does not point back",
ar = artist.name(),
al = album.name()
);
was_error = true;
}
} else {
error!(
"Artist '{ar}' points to album '{al}' which does not exist",
ar = artist.name(),
al = album_id
);
was_error = true;
}
}
}
assert!(!was_error);
Ok(())
}