Skip to main content

selene_core/library/artist/
trait_impls.rs

1use chrono::{DateTime, Utc};
2use sled::{Db, Tree};
3
4use crate::{
5    database::{
6        CompareAndSwapTransaction, Createable, DatabaseEntry, DatabaseError, Patchable,
7        artist_tree, patch_option_replace, patch_replace, patch_vec,
8        validator::DatabaseReferenceError,
9    },
10    library::{
11        album::Album,
12        artist::{Artist, ArtistId},
13        track::Track,
14    },
15};
16
17impl std::hash::Hash for Artist {
18    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
19        state.write(self.id.as_bytes());
20    }
21}
22
23impl PartialEq for Artist {
24    fn eq(&self, other: &Self) -> bool {
25        self.id() == other.id()
26    }
27}
28
29impl Eq for Artist {}
30
31impl DatabaseEntry for Artist {
32    type Id = ArtistId;
33    const VERSION_NUMBER: u32 = 1;
34
35    fn tree(db: &Db) -> Tree {
36        artist_tree(db)
37    }
38
39    fn id(&self) -> Self::Id {
40        self.id
41    }
42
43    fn pre_upsert(&mut self, cas_tx: &CompareAndSwapTransaction) -> Result<(), DatabaseError> {
44        let mut albums = self.tx_albums(cas_tx)?;
45
46        albums.sort_unstable_by(|a, b| {
47            let a_date = a.date.unwrap_or(DateTime::<Utc>::MAX_UTC);
48            let b_date = b.date.unwrap_or(DateTime::<Utc>::MAX_UTC);
49            a_date.cmp(&b_date).then(a.name.cmp(&b.name))
50        });
51
52        self.albums = albums.iter().map(Album::id).collect();
53
54        Ok(())
55    }
56}
57
58impl Patchable<Self> for Artist {
59    fn patch(&mut self, patch: Self) {
60        let Artist {
61            id: _,
62            name,
63            cover_art,
64            description,
65            tracks,
66            albums,
67            version: _,
68        } = patch;
69
70        patch_option_replace(&mut self.cover_art, cover_art);
71        patch_option_replace(&mut self.description, description);
72        patch_replace(&mut self.name, Some(name));
73        patch_vec(&mut self.tracks, tracks);
74        patch_vec(&mut self.albums, albums);
75    }
76}
77
78impl std::ops::Deref for Artist {
79    type Target = ArtistId;
80
81    fn deref(&self) -> &Self::Target {
82        &self.id
83    }
84}
85
86pub struct ArtistCreateArgs {
87    pub name: String,
88    pub albums: Vec<Album>,
89    pub tracks: Vec<Track>,
90}
91
92impl ArtistCreateArgs {
93    #[must_use]
94    pub fn new(name: String, albums: Vec<Album>, tracks: Vec<Track>) -> Self {
95        Self {
96            name,
97            albums,
98            tracks,
99        }
100    }
101}
102
103impl Createable for Artist {
104    type CreateArgs = ArtistCreateArgs;
105
106    fn tx_create(
107        cas_tx: &mut CompareAndSwapTransaction,
108        args: Self::CreateArgs,
109    ) -> Result<Self::Id, DatabaseError> {
110        let mut artist = Artist::new(args.name).ok_or(DatabaseError::InvalidInput(
111            "Name field must not be empty".to_owned(),
112        ))?;
113        let artist_id = artist.id();
114
115        for track in &args.tracks {
116            if let Some(album) = args.albums.iter().find(|album| {
117                album
118                    .tracks
119                    .iter()
120                    .any(|track_ref| track_ref.id == track.id())
121            }) {
122                return Err(DatabaseError::ValidationError(
123                    DatabaseReferenceError::ArtistInvalidTrackRef {
124                        artist: artist.id(),
125                        track: track.id(),
126                        album: album.id(),
127                    },
128                ));
129            }
130
131            if !artist.tracks.contains(&track.id()) {
132                artist.tracks.push(track.id());
133            }
134        }
135
136        cas_tx.tx_insert(artist)?;
137
138        for mut track in args.tracks {
139            track.metadata.artists.add_artist(artist_id);
140            cas_tx.tx_patch(track)?;
141        }
142
143        Ok(artist_id)
144    }
145}