selene_core/library/artist/
trait_impls.rs1use std::convert::Infallible;
2
3use chrono::{DateTime, Utc};
4use lunar_lib::database::{
5 CompareAndSwapTransaction, CustomTransactionError, DatabaseEntry, TransactionError,
6 caching::Cacheable,
7};
8
9use crate::{
10 database::{
11 ARTIST_CACHE, Createable, LibraryDb, Mergeable, artist_add_tracks, patch_option_replace,
12 patch_vec, tracks_add_artist, tracks_remove_artist,
13 },
14 library::{
15 album::Album,
16 artist::{Artist, ArtistGroup, ArtistId},
17 track::Track,
18 },
19};
20
21impl std::hash::Hash for Artist {
22 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
23 state.write(&*self.id);
24 }
25}
26
27impl PartialEq for Artist {
28 fn eq(&self, other: &Self) -> bool {
29 self.id() == other.id()
30 }
31}
32
33impl Eq for Artist {}
34
35impl DatabaseEntry for Artist {
36 type Id = ArtistId;
37 type Db = LibraryDb;
38 const VERSION_NUMBER: u32 = 1;
39 const TREE_NAME: &str = "artist";
40
41 fn id(&self) -> Self::Id {
42 self.id
43 }
44
45 fn pre_upsert(
46 &mut self,
47 cas_tx: &CompareAndSwapTransaction<Self::Db>,
48 ) -> Result<(), TransactionError> {
49 let mut albums = self.tx_albums(cas_tx)?;
50
51 albums.sort_unstable_by(|a, b| {
52 let a_date = a.date.unwrap_or(DateTime::<Utc>::MAX_UTC);
53 let b_date = b.date.unwrap_or(DateTime::<Utc>::MAX_UTC);
54 a_date.cmp(&b_date).then(a.name.cmp(&b.name))
55 });
56
57 self.albums = albums.iter().map(Album::id).collect();
58
59 Ok(())
60 }
61}
62
63impl Cacheable for Artist {
64 fn cache() -> &'static std::sync::Mutex<lunar_lib::database::caching::DbCache<Self>> {
65 &ARTIST_CACHE
66 }
67}
68
69impl Mergeable for Artist {
70 type Err = Infallible;
71
72 fn tx_merge(
73 self,
74 merge_into: Self::Id,
75 cas_tx: &mut CompareAndSwapTransaction<Self::Db>,
76 ) -> Result<Self, CustomTransactionError<Self::Err>> {
77 let self_id = self.id();
78
79 let Some(mut artist) = cas_tx.tx_get(merge_into)? else {
80 if self_id == merge_into {
81 cas_tx.tx_upsert(self.clone())?;
82 return Ok(self);
83 }
84 return Err(TransactionError::MissingEntry.into());
85 };
86
87 artist.name = self.name;
89 patch_option_replace(&mut artist.cover_art, self.cover_art);
90 patch_option_replace(&mut artist.description, self.description);
91 patch_vec(&mut artist.tracks, self.tracks);
92 patch_vec(&mut artist.albums, self.albums);
93
94 let mut tracks: Vec<Track> = cas_tx.tx_get_batch(&artist.tracks)?;
96 tracks_add_artist(&mut tracks, merge_into);
97
98 cas_tx.tx_upsert(artist.clone())?;
100 if self_id != merge_into {
101 tracks_remove_artist(&mut tracks, self_id);
102 cas_tx.tx_remove(self_id)?;
103 }
104
105 for track in tracks {
107 cas_tx.tx_upsert(track)?;
108 }
109
110 Ok(artist)
111 }
112}
113
114impl std::ops::Deref for Artist {
135 type Target = ArtistId;
136
137 fn deref(&self) -> &Self::Target {
138 &self.id
139 }
140}
141
142#[derive(Debug, Clone)]
143pub struct ArtistCreateArgs {
144 pub name: String,
145 pub albums: Vec<Album>,
146 pub tracks: Vec<Track>,
147}
148
149impl ArtistCreateArgs {
150 #[must_use]
151 pub fn new(name: String, albums: Vec<Album>, tracks: Vec<Track>) -> Self {
152 Self {
153 name,
154 albums,
155 tracks,
156 }
157 }
158}
159
160#[derive(Debug, thiserror::Error)]
161pub enum ArtistCreationError {
162 #[error("Artist name resolved to an empty string")]
163 EmptyName,
164}
165
166impl Createable for Artist {
167 type CreateArgs = ArtistCreateArgs;
168 type Err = ArtistCreationError;
169
170 fn tx_create(
171 cas_tx: &mut CompareAndSwapTransaction<Self::Db>,
172 args: Self::CreateArgs,
173 ) -> Result<Self, CustomTransactionError<Self::Err>> {
174 let mut artist = Artist::new(args.name).ok_or(CustomTransactionError::Closure(
175 ArtistCreationError::EmptyName,
176 ))?;
177 let artist_id = artist.id();
178
179 artist_add_tracks(&mut artist, args.tracks.iter().map(Track::id));
180
181 cas_tx.tx_insert(artist.clone())?;
182
183 for mut track in args.tracks {
184 track.metadata.artists.add_artist(artist_id);
185 cas_tx.tx_upsert(track)?;
186 }
187
188 Ok(artist)
189 }
190}