selene_core/library/artist/
trait_impls.rs1use 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}