selene_core/library/album/
trait_impls.rs1use std::convert::Infallible;
2
3use lunar_lib::{formatter::Taggable, paths::sys::sanitize_str};
4use sled::{Db, Tree};
5
6use crate::{
7 database::{
8 CompareAndSwapTransaction, Createable, DatabaseEntry, DatabaseError, Mergeable, Patchable,
9 album_tree, patch_option_replace, patch_vec_merge,
10 },
11 library::{
12 album::{Album, AlbumId, TrackReference},
13 artist::{Artist, ArtistGroup},
14 track::{Track, TrackId},
15 },
16};
17
18impl DatabaseEntry for Album {
19 const VERSION_NUMBER: u32 = 1;
20 type Id = AlbumId;
21
22 fn tree(db: &Db) -> Tree {
23 album_tree(db)
24 }
25
26 fn id(&self) -> Self::Id {
27 self.id
28 }
29
30 fn pre_upsert(&mut self, _cas_tx: &CompareAndSwapTransaction) -> Result<(), DatabaseError> {
31 self.tracks.sort_by(|a, b| {
32 let a_disc = a.disc_num.unwrap_or(u16::MAX);
33 let a_track = a.track_num.unwrap_or(u16::MAX);
34 let b_disc = b.disc_num.unwrap_or(u16::MAX);
35 let b_track = b.track_num.unwrap_or(u16::MAX);
36
37 a_disc.cmp(&b_disc).then(a_track.cmp(&b_track))
38 });
39
40 Ok(())
41 }
42}
43
44impl Patchable<Self> for Album {
45 fn patch(&mut self, patch: Album) {
46 let Album {
47 id: _,
48 name,
49 cover_art,
50 tracks,
51 artist_group,
52 disc_total,
53 genre,
54 track_total,
55 date,
56 version: _,
57 } = patch;
58
59 self.name = name;
60 patch_option_replace(&mut self.cover_art, cover_art);
61 self.artist_group.patch(artist_group);
62 patch_vec_merge(&mut self.tracks, tracks);
63 patch_option_replace(&mut self.disc_total, disc_total);
64 patch_option_replace(&mut self.track_total, track_total);
65 patch_option_replace(&mut self.genre, genre);
66 patch_option_replace(&mut self.date, date);
67 }
68}
69
70impl Taggable for Album {
71 type Err = Infallible;
72
73 fn fill_table(&self, table: &mut lunar_lib::formatter::FormatTable) -> Result<(), Self::Err> {
74 if let Some(value) = self.disc_total {
75 table.add_entry("disc_total", value.to_string());
76 }
77 if let Some(value) = &self.genre {
78 table.add_entry("album_genre", sanitize_str(value));
79 }
80
81 table.add_entry("album", &self.name);
82
83 if let Some(value) = self.track_total {
84 table.add_entry("track_total", value.to_string());
85 }
86 if let Some(value) = self.date {
87 table.add_entry("album_year", value.to_string());
88 }
89
90 Ok(())
91 }
92}
93
94impl Mergeable for Album {
95 fn tx_merge(
96 &self,
97 merge_into: Self::Id,
98 cas_tx: &mut CompareAndSwapTransaction,
99 ) -> Result<(), DatabaseError> {
100 let mut album = cas_tx
102 .tx_get(merge_into)?
103 .expect("Album was deleted in this transaction");
104
105 album.patch(self.clone());
106
107 cas_tx.tracks_set_album(Some(merge_into), self.tracks.iter().map(|t| &t.id))?;
109
110 cas_tx.artists_remove_album(self.id(), self.artist_group.artist_ids())?;
112 cas_tx.artists_add_album(merge_into, self.artist_group.artist_ids())?;
113
114 cas_tx.tx_upsert(album.id(), Some(album))?;
116 if self.id() != merge_into {
117 cas_tx.tx_remove(self.id())?
118 }
119
120 Ok(())
121 }
122}
123
124pub struct AlbumCreateArgs {
125 pub name: String,
126 pub artists: Vec<Artist>,
127 pub tracks: Vec<Track>,
128}
129
130impl AlbumCreateArgs {
131 #[must_use]
132 pub fn new(name: String, artists: Vec<Artist>, tracks: Vec<Track>) -> Self {
133 Self {
134 name,
135 artists,
136 tracks,
137 }
138 }
139}
140
141#[derive(Debug, thiserror::Error)]
142pub enum AlbumCreationError {
143 #[error("A database already exists with the same identifier: {0}")]
144 AlreadyExists(String),
145
146 #[error("{0}")]
147 Database(#[from] DatabaseError),
148}
149
150impl Createable for Album {
151 type CreateArgs = AlbumCreateArgs;
152
153 fn tx_create(
154 cas_tx: &mut CompareAndSwapTransaction,
155 mut args: Self::CreateArgs,
156 ) -> Result<Self::Id, DatabaseError> {
157 let track_ids: Vec<TrackId> = args.tracks.iter().map(|i| i.id()).collect();
158 for artist in &mut args.artists {
159 artist.tracks.retain(|t| !track_ids.contains(t));
160 }
161
162 let artists = ArtistGroup::from_artists(&args.artists);
163
164 let tracks = (1u16..)
165 .zip(args.tracks.iter())
166 .map(|(i, t)| TrackReference {
167 id: t.id(),
168 track_num: Some(i),
169 disc_num: None,
170 });
171
172 let album = Album::new(args.name, artists, tracks.collect());
174 if Self::db_check(album.id())? {
175 return Err(DatabaseError::AlreadyInDatabase);
176 }
177
178 args.tracks
180 .into_iter()
181 .try_for_each(|mut track| -> Result<(), DatabaseError> {
182 track.metadata.album = Some(album.id());
183
184 cas_tx.tx_upsert(track.id(), Some(track))?;
185
186 Ok(())
187 })?;
188
189 let album_id = album.id();
191 cas_tx.tx_insert(album)?;
192
193 Ok(album_id)
194 }
195}
196
197impl PartialEq for Album {
198 fn eq(&self, other: &Self) -> bool {
199 self.id == other.id
200 }
201}
202
203impl Eq for Album {}