selene-core 0.4.2

selene-core is the backend for Selene, a local-first music player
Documentation
use std::collections::HashMap;

use lunar_lib::database::{DatabaseEntry, DatabaseError};
use thiserror::Error;

use crate::library::{
    album::{Album, AlbumId},
    artist::{Artist, ArtistId},
    track::{Track, TrackId},
};

#[derive(Debug, Error)]
pub enum DatabaseReferenceError {
    #[error("Track points to an album, but the album does not point back")]
    TrackDetatchedAlbumRef { track: TrackId, album: AlbumId },

    #[error("Track points to an album, but the album does not exist")]
    TrackDanglingAlbumRef { track: TrackId, album: AlbumId },

    #[error("Track points to an artist, but the artist does not point back")]
    TrackDetatchedArtistRef { track: TrackId, artist: ArtistId },

    #[error("Track points to an artist, but the artist does not exist")]
    TrackDanglingArtistRef { track: TrackId, artist: ArtistId },

    #[error("Album points to a track, but the track does not point back")]
    AlbumDetatchedTrackRef { album: AlbumId, track: TrackId },

    #[error("Album points to a track, but the track does not exist")]
    AlbumDanglingTrackRef { album: AlbumId, track: TrackId },

    #[error("Album points to an artist, but the artist does not point back")]
    AlbumDetatchedArtistRef { album: AlbumId, artist: ArtistId },

    #[error("Album points to an artist, but the artist does not exist")]
    AlbumDanglingArtistRef { album: AlbumId, artist: ArtistId },

    #[error("Artist points to a track, but the track does not exist")]
    ArtistDanglingTrackRef { artist: ArtistId, track: TrackId },

    #[error("Artist points to a track, but the track does not point back")]
    ArtistDetachedTrackRef { artist: ArtistId, track: TrackId },

    #[error("Artist points to an album, but the album does not exist")]
    ArtistDanglingAlbumRef { artist: ArtistId, album: AlbumId },

    #[error("Artist points to an album, but the album does not point back")]
    ArtistDetachedAlbumRef { artist: ArtistId, album: AlbumId },

    #[error("Tried to add track to an artist, but the track is part of the artist's albums")]
    ArtistInvalidTrackRef {
        artist: ArtistId,
        track: TrackId,
        album: AlbumId,
    },
}

pub fn validate() -> Result<Vec<DatabaseReferenceError>, DatabaseError> {
    let mut errors = Vec::new();

    let tracks: HashMap<TrackId, Track> = Track::db_get_all()?
        .into_iter()
        .map(|t| (t.id(), t))
        .collect();

    let albums: HashMap<AlbumId, Album> = Album::db_get_all()?
        .into_iter()
        .map(|a| (a.id(), a))
        .collect();

    let artists: HashMap<ArtistId, Artist> = Artist::db_get_all()?
        .into_iter()
        .map(|a| (a.id(), a))
        .collect();

    for track in tracks.values() {
        if let Some(album_id) = track.metadata.album {
            if let Some(album) = albums.get(&album_id) {
                if !album.track_refs().iter().any(|t| t.id == track.id()) {
                    errors.push(DatabaseReferenceError::TrackDetatchedAlbumRef {
                        track: track.id(),
                        album: album.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::TrackDanglingAlbumRef {
                    track: track.id(),
                    album: album_id,
                });
            }
        }

        for artist_id in track.metadata.artists.artist_ids() {
            if let Some(artist) = artists.get(artist_id) {
                if !artist.tracks.iter().any(|t| *t == track.id()) {
                    if let Some(album_id) = track.metadata.album
                        && artist.albums.contains(&album_id)
                    {
                        continue;
                    }
                    errors.push(DatabaseReferenceError::TrackDetatchedArtistRef {
                        track: track.id(),
                        artist: artist.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::TrackDanglingArtistRef {
                    track: track.id(),
                    artist: *artist_id,
                });
            }
        }
    }

    for album in albums.values() {
        for track in &album.tracks {
            if let Some(track) = tracks.get(&track.id) {
                if !(track.metadata.album == Some(album.id())) {
                    errors.push(DatabaseReferenceError::AlbumDetatchedTrackRef {
                        album: album.id(),
                        track: track.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::AlbumDanglingTrackRef {
                    album: album.id(),
                    track: track.id,
                });
            }
        }

        for artist_id in album.artist_group.artist_ids() {
            if let Some(artist) = artists.get(artist_id) {
                if !artist.albums.iter().any(|t| *t == album.id()) {
                    errors.push(DatabaseReferenceError::AlbumDetatchedArtistRef {
                        album: album.id(),
                        artist: artist.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::AlbumDanglingArtistRef {
                    album: album.id(),
                    artist: *artist_id,
                });
            }
        }
    }

    for artist in artists.values() {
        for track in &artist.tracks {
            if let Some(track) = tracks.get(track) {
                if !track
                    .metadata
                    .artists
                    .artist_ids()
                    .iter()
                    .any(|a| *a == artist.id())
                {
                    errors.push(DatabaseReferenceError::ArtistDetachedTrackRef {
                        artist: artist.id(),
                        track: track.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::ArtistDanglingTrackRef {
                    artist: artist.id(),
                    track: *track,
                });
            }
        }

        for album in &artist.albums {
            if let Some(album) = albums.get(album) {
                if !album
                    .artists()
                    .artist_ids()
                    .iter()
                    .any(|a| *a == artist.id())
                {
                    errors.push(DatabaseReferenceError::ArtistDetachedAlbumRef {
                        artist: artist.id(),
                        album: album.id(),
                    });
                }
            } else {
                errors.push(DatabaseReferenceError::ArtistDanglingAlbumRef {
                    artist: artist.id(),
                    album: *album,
                });
            }
        }
    }

    Ok(errors)
}