Skip to main content

selene_core/database/
validator.rs

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