Skip to main content

selene_core/library/artist/
trait_impls.rs

1use 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        // Patch the album we are merging into with the data from self
88        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        // Relink tracks
95        let mut tracks: Vec<Track> = cas_tx.tx_get_batch(&artist.tracks)?;
96        tracks_add_artist(&mut tracks, merge_into);
97
98        // Upsert artist
99        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        // Upsert tracks
106        for track in tracks {
107            cas_tx.tx_upsert(track)?;
108        }
109
110        Ok(artist)
111    }
112}
113
114// impl Patchable<Self> for Artist {
115//     fn patch(&mut self, patch: Self) {
116//         let Artist {
117//             id: _,
118//             name,
119//             cover_art,
120//             description,
121//             tracks,
122//             albums,
123//             version: _,
124//         } = patch;
125
126//         patch_option_replace(&mut self.cover_art, cover_art);
127//         patch_option_replace(&mut self.description, description);
128//         patch_replace(&mut self.name, Some(name));
129//         patch_vec(&mut self.tracks, tracks);
130//         patch_vec(&mut self.albums, albums);
131//     }
132// }
133
134impl 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}