selene_core/database/
validator.rs1use 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}