Skip to main content

selene_core/database/
validator.rs

1use std::collections::HashMap;
2
3use lunar_lib::database::{DatabaseEntry, DatabaseError};
4use thiserror::Error;
5
6use crate::library::{
7    album::{Album, AlbumId},
8    artist::{Artist, ArtistId},
9    track::{Track, TrackId},
10};
11
12#[derive(Debug, Error)]
13pub enum DatabaseReferenceError {
14    #[error("Track points to an album, but the album does not point back")]
15    TrackDetatchedAlbumRef { track: TrackId, album: AlbumId },
16
17    #[error("Track points to an album, but the album does not exist")]
18    TrackDanglingAlbumRef { track: TrackId, album: AlbumId },
19
20    #[error("Track points to an artist, but the artist does not point back")]
21    TrackDetatchedArtistRef { track: TrackId, artist: ArtistId },
22
23    #[error("Track points to an artist, but the artist does not exist")]
24    TrackDanglingArtistRef { track: TrackId, artist: ArtistId },
25
26    #[error("Album points to a track, but the track does not point back")]
27    AlbumDetatchedTrackRef { album: AlbumId, track: TrackId },
28
29    #[error("Album points to a track, but the track does not exist")]
30    AlbumDanglingTrackRef { album: AlbumId, track: TrackId },
31
32    #[error("Album points to an artist, but the artist does not point back")]
33    AlbumDetatchedArtistRef { album: AlbumId, artist: ArtistId },
34
35    #[error("Album points to an artist, but the artist does not exist")]
36    AlbumDanglingArtistRef { album: AlbumId, artist: ArtistId },
37
38    #[error("Artist points to a track, but the track does not exist")]
39    ArtistDanglingTrackRef { artist: ArtistId, track: TrackId },
40
41    #[error("Artist points to a track, but the track does not point back")]
42    ArtistDetachedTrackRef { artist: ArtistId, track: TrackId },
43
44    #[error("Artist points to an album, but the album does not exist")]
45    ArtistDanglingAlbumRef { artist: ArtistId, album: AlbumId },
46
47    #[error("Artist points to an album, but the album does not point back")]
48    ArtistDetachedAlbumRef { artist: ArtistId, album: AlbumId },
49
50    #[error("Tried to add track to an artist, but the track is part of the artist's albums")]
51    ArtistInvalidTrackRef {
52        artist: ArtistId,
53        track: TrackId,
54        album: AlbumId,
55    },
56}
57
58pub fn validate() -> Result<Vec<DatabaseReferenceError>, DatabaseError> {
59    let mut errors = Vec::new();
60
61    let tracks: HashMap<TrackId, Track> = Track::db_get_all()?
62        .into_iter()
63        .map(|t| (t.id(), t))
64        .collect();
65
66    let albums: HashMap<AlbumId, Album> = Album::db_get_all()?
67        .into_iter()
68        .map(|a| (a.id(), a))
69        .collect();
70
71    let artists: HashMap<ArtistId, Artist> = Artist::db_get_all()?
72        .into_iter()
73        .map(|a| (a.id(), a))
74        .collect();
75
76    for track in tracks.values() {
77        if let Some(album_id) = track.metadata.album {
78            if let Some(album) = albums.get(&album_id) {
79                if !album.track_refs().iter().any(|t| t.id == track.id()) {
80                    errors.push(DatabaseReferenceError::TrackDetatchedAlbumRef {
81                        track: track.id(),
82                        album: album.id(),
83                    });
84                }
85            } else {
86                errors.push(DatabaseReferenceError::TrackDanglingAlbumRef {
87                    track: track.id(),
88                    album: album_id,
89                });
90            }
91        }
92
93        for artist_id in track.metadata.artists.artist_ids() {
94            if let Some(artist) = artists.get(artist_id) {
95                if !artist.tracks.iter().any(|t| *t == track.id()) {
96                    if let Some(album_id) = track.metadata.album
97                        && artist.albums.contains(&album_id)
98                    {
99                        continue;
100                    }
101                    errors.push(DatabaseReferenceError::TrackDetatchedArtistRef {
102                        track: track.id(),
103                        artist: artist.id(),
104                    });
105                }
106            } else {
107                errors.push(DatabaseReferenceError::TrackDanglingArtistRef {
108                    track: track.id(),
109                    artist: *artist_id,
110                });
111            }
112        }
113    }
114
115    for album in albums.values() {
116        for track in &album.tracks {
117            if let Some(track) = tracks.get(&track.id) {
118                if !(track.metadata.album == Some(album.id())) {
119                    errors.push(DatabaseReferenceError::AlbumDetatchedTrackRef {
120                        album: album.id(),
121                        track: track.id(),
122                    });
123                }
124            } else {
125                errors.push(DatabaseReferenceError::AlbumDanglingTrackRef {
126                    album: album.id(),
127                    track: track.id,
128                });
129            }
130        }
131
132        for artist_id in album.artist_group.artist_ids() {
133            if let Some(artist) = artists.get(artist_id) {
134                if !artist.albums.iter().any(|t| *t == album.id()) {
135                    errors.push(DatabaseReferenceError::AlbumDetatchedArtistRef {
136                        album: album.id(),
137                        artist: artist.id(),
138                    });
139                }
140            } else {
141                errors.push(DatabaseReferenceError::AlbumDanglingArtistRef {
142                    album: album.id(),
143                    artist: *artist_id,
144                });
145            }
146        }
147    }
148
149    for artist in artists.values() {
150        for track in &artist.tracks {
151            if let Some(track) = tracks.get(track) {
152                if !track
153                    .metadata
154                    .artists
155                    .artist_ids()
156                    .iter()
157                    .any(|a| *a == artist.id())
158                {
159                    errors.push(DatabaseReferenceError::ArtistDetachedTrackRef {
160                        artist: artist.id(),
161                        track: track.id(),
162                    });
163                }
164            } else {
165                errors.push(DatabaseReferenceError::ArtistDanglingTrackRef {
166                    artist: artist.id(),
167                    track: *track,
168                });
169            }
170        }
171
172        for album in &artist.albums {
173            if let Some(album) = albums.get(album) {
174                if !album
175                    .artists()
176                    .artist_ids()
177                    .iter()
178                    .any(|a| *a == artist.id())
179                {
180                    errors.push(DatabaseReferenceError::ArtistDetachedAlbumRef {
181                        artist: artist.id(),
182                        album: album.id(),
183                    });
184                }
185            } else {
186                errors.push(DatabaseReferenceError::ArtistDanglingAlbumRef {
187                    artist: artist.id(),
188                    album: *album,
189                });
190            }
191        }
192    }
193
194    Ok(errors)
195}