selene_core/database/
tx_extensions.rs1use lunar_lib::{
2 database::{CompareAndSwapTransaction, TransactionError},
3 iterator_ext::IteratorExtensions,
4};
5
6use crate::{
7 database::LibraryDb,
8 library::{
9 album::{Album, AlbumId, TrackReference},
10 artist::{Artist, ArtistGroup, ArtistId},
11 track::{Track, TrackId},
12 },
13};
14
15pub trait CasTxLibraryExtensions {
16 fn album_set_and_relink_tracks(
17 &mut self,
18 album: Album,
19 tracks: Vec<Track>,
20 ) -> Result<(Album, Vec<Track>), TransactionError>;
21
22 fn album_set_and_relink_artists(
23 &mut self,
24 album_id: Album,
25 artists: impl IntoIterator<Item = Artist>,
26 ) -> Result<(Album, Vec<Artist>), TransactionError>;
27
28 fn relink_track_to_album(
29 &mut self,
30 track_id: TrackId,
31 album: Option<AlbumId>,
32 ) -> Result<bool, TransactionError>;
33}
34
35impl CasTxLibraryExtensions for CompareAndSwapTransaction<LibraryDb> {
36 fn relink_track_to_album(
38 &mut self,
39 track_id: TrackId,
40 album: Option<AlbumId>,
41 ) -> Result<bool, TransactionError> {
42 let Some(mut track) = self.tx_get(track_id)? else {
43 return Ok(false);
44 };
45
46 if track.metadata().album == album {
47 return Ok(false);
48 }
49
50 let old_album_id = track.metadata.album;
51
52 track.metadata.album = album;
53
54 if let Some(old_album_id) = old_album_id {
55 let mut old_album = self.tx_get(old_album_id)?.unwrap_or_else(|| {
56 panic!(
57 "Track '{}' contains a reference to an album that doesnt exist",
58 track.metadata.safe_title()
59 )
60 });
61
62 old_album.tracks.retain(|t| t.id != track_id);
63
64 self.tx_upsert(old_album)?;
65 }
66
67 if let Some(new_album_id) = album {
68 let mut new_album = self
69 .tx_get(new_album_id)?
70 .ok_or(TransactionError::MissingEntry)?;
71
72 new_album.tracks.push(TrackReference {
73 id: track_id,
74 track_num: None,
75 disc_num: None,
76 });
77
78 self.tx_upsert(new_album)?;
79 }
80
81 self.tx_upsert(track)?;
82
83 Ok(true)
84 }
85
86 fn album_set_and_relink_artists(
88 &mut self,
89 mut album: Album,
90 artists: impl IntoIterator<Item = Artist>,
91 ) -> Result<(Album, Vec<Artist>), TransactionError> {
92 let mut artists = artists.into_iter().to_vec();
93 let artist_ids = artists.iter().map(Artist::id).to_vec();
94
95 let old_artists = album.artists.artist_ids().to_vec();
96
97 let mut removed_artists = old_artists
98 .iter()
99 .filter_map(|a| -> Option<Result<Artist, TransactionError>> {
100 (!artist_ids.contains(a)).then_some(
101 self.tx_get(*a)
102 .transpose()
103 .ok_or(TransactionError::MissingEntry)
104 .flatten(),
105 )
106 })
107 .collect::<Result<Vec<Artist>, _>>()?;
108
109 album.artists = artist_ids;
110
111 let album_id = album.id();
112 artists_add_album(&mut artists, album_id);
113 artists_remove_album(&mut removed_artists, album_id);
114
115 self.tx_upsert(album.clone())?;
116 for artist in artists.iter().chain(removed_artists.iter()) {
117 self.tx_upsert(artist.clone())?;
118 }
119
120 Ok((album, artists))
121 }
122
123 fn album_set_and_relink_tracks(
125 &mut self,
126 mut album: Album,
127 mut tracks: Vec<Track>,
128 ) -> Result<(Album, Vec<Track>), TransactionError> {
129 let track_ids: Vec<TrackId> = tracks.iter().map(Track::id).collect();
130 let old_tracks: Vec<TrackId> = album.tracks.iter().map(|t| t.id).collect();
131
132 let mut removed_tracks: Vec<Track> = old_tracks
133 .into_iter()
134 .filter(|old_track| !track_ids.contains(old_track))
135 .try_map(|id| {
136 self.tx_get(id)
137 .transpose()
138 .ok_or(TransactionError::MissingEntry)
139 .flatten()
140 })?
141 .collect();
142
143 album_set_tracks(&mut album, &track_ids);
144 tracks_set_album(&mut tracks, Some(album.id()));
145 tracks_set_album(&mut removed_tracks, None);
146
147 Ok((album, tracks))
148 }
149}
150
151pub(crate) fn album_set_tracks(album: &mut Album, tracks: &[TrackId]) {
153 album.tracks = tracks
154 .iter()
155 .map(|t| {
156 album
157 .tracks
158 .iter()
159 .find(|old| old.id == *t)
160 .copied()
161 .unwrap_or(TrackReference {
162 id: *t,
163 track_num: None,
164 disc_num: None,
165 })
166 })
167 .collect();
168}
169
170pub(crate) fn album_add_track(album: &mut Album, track: TrackId) {
171 album.tracks.push(TrackReference {
172 id: track,
173 track_num: None,
174 disc_num: None,
175 });
176}
177
178pub(crate) fn tracks_set_album<'a>(
179 tracks: impl IntoIterator<Item = &'a mut Track>,
180 album_id: Option<AlbumId>,
181) {
182 for track in tracks {
183 track.metadata.album = album_id;
184 }
185}
186
187pub(crate) fn tracks_add_artist<'a>(
188 tracks: impl IntoIterator<Item = &'a mut Track>,
189 artist_id: ArtistId,
190) {
191 for track in tracks {
192 if !track.metadata.artists.contains(&artist_id) {
193 track.metadata.artists.push(artist_id);
194 }
195 }
196}
197
198pub(crate) fn tracks_remove_artist<'a>(
199 tracks: impl IntoIterator<Item = &'a mut Track>,
200 artist_id: ArtistId,
201) {
202 for track in tracks {
203 track.metadata.artists.retain(|a| *a != artist_id);
204 }
205}
206
207pub(crate) fn artists_remove_album<'a>(
208 artists: impl IntoIterator<Item = &'a mut Artist>,
209 album_id: AlbumId,
210) {
211 for artist in artists {
212 artist.albums.retain(|a| *a != album_id);
213 }
214}
215
216pub(crate) fn artists_add_album<'a>(
217 artists: impl IntoIterator<Item = &'a mut Artist>,
218 album_id: AlbumId,
219) {
220 for artist in artists {
221 if !artist.albums.contains(&album_id) {
222 artist.albums.push(album_id);
223 }
224 }
225}
226
227pub(crate) fn artist_add_tracks<'a>(
228 artist: &mut Artist,
229 track_ids: impl IntoIterator<Item = TrackId>,
230) {
231 for track in track_ids {
232 if !artist.tracks.contains(&track) {
233 artist.tracks.push(track);
234 }
235 }
236}