Skip to main content

selene_core/library/album/
trait_impls.rs

1use 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        // Get and patch album
101        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        // Relink tracks
108        cas_tx.tracks_set_album(Some(merge_into), self.tracks.iter().map(|t| &t.id))?;
109
110        // Relink artists
111        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        // Upsert album
115        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        // Create album
173        let album = Album::new(args.name, artists, tracks.collect());
174        if Self::db_check(album.id())? {
175            return Err(DatabaseError::AlreadyInDatabase);
176        }
177
178        // Relink tracks
179        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        // Upsert album
190        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 {}