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