selene_core/library/
album.rs1use std::{ops::Deref, str::FromStr, sync::Arc};
2
3use blake3::{Hash, hash};
4use chrono::{DateTime, Utc};
5use lunar_lib::{
6 database::{EntryId, EntryIdIteratorExt, TransactionError},
7 iterator_ext::IteratorExtensions,
8};
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 database::{LibraryDb, Resolveable},
13 library::{
14 artist::{Artist, ArtistId},
15 image_art::ImageArt,
16 track::{Track, TrackId},
17 },
18};
19
20pub mod frontend_impls;
21pub mod trait_impls;
22
23pub const UNKNOWN_ALBUM: &str = "UNKNOWN ALBUM";
24
25#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct AlbumId(Hash);
27
28impl EntryId for AlbumId {
29 type Entry = Album;
30 type IdDb = LibraryDb;
31}
32
33impl Deref for AlbumId {
34 type Target = [u8; 32];
35
36 fn deref(&self) -> &Self::Target {
37 self.0.as_bytes()
38 }
39}
40
41impl FromStr for AlbumId {
42 type Err = <Hash as FromStr>::Err;
43
44 fn from_str(s: &str) -> Result<Self, Self::Err> {
45 Ok(Self(Hash::from_str(s)?))
46 }
47}
48
49impl AlbumId {
50 fn new(name: &str) -> Self {
51 Self(hash(name.as_bytes()))
52 }
53
54 #[must_use]
55 pub fn to_hash(&self) -> Hash {
56 self.0
57 }
58
59 #[must_use]
60 pub fn to_selene_id(&self) -> String {
61 format!("album:{}", self.0)
62 }
63}
64
65#[derive(Debug, Clone, Deserialize, Serialize)]
66pub struct Album {
67 id: AlbumId,
68
69 pub name: String,
70 pub art: Option<ImageArt>,
71
72 pub(crate) tracks: Vec<TrackReference>,
74
75 pub(crate) artists: Vec<ArtistId>,
76 pub disc_total: Option<u32>,
77 pub genre: Vec<String>,
78 pub track_total: Option<u32>,
79 pub date: Option<DateTime<Utc>>,
80}
81
82impl Album {
84 pub(crate) fn new(name: String, artists: Vec<ArtistId>, tracks: Vec<TrackReference>) -> Self {
85 let hash = AlbumId::new(&name);
86
87 Self {
88 artists,
89 art: None,
90 disc_total: None,
91 genre: Vec::new(),
92 id: hash,
93 name,
94 track_total: None,
95 date: None,
96 tracks,
97 }
98 }
99}
100
101impl Album {
103 #[must_use]
104 pub fn id(&self) -> AlbumId {
105 self.id
106 }
107
108 #[must_use]
109 pub fn name(&self) -> &str {
110 &self.name
111 }
112
113 pub fn tracks(&self, db: &LibraryDb) -> Result<Vec<Track>, TransactionError> {
114 self.track_refs().iter().map(|t| t.id).db_get_batch(db)
115 }
116
117 pub fn tracks_cache(&self, db: &LibraryDb) -> Result<Vec<Arc<Track>>, TransactionError> {
118 self.track_refs().iter().map(|t| t.id).cache_get_batch(db)
119 }
120
121 #[must_use]
122 pub fn artists_raw(&self) -> &[ArtistId] {
123 &self.artists
124 }
125
126 #[must_use]
127 pub fn artists(&self, db: &LibraryDb) -> Result<Vec<Artist>, TransactionError> {
128 self.artists.iter().copied().db_get_batch(db)
129 }
130
131 #[must_use]
132 pub fn artists_cache(&self, db: &LibraryDb) -> Result<Vec<Arc<Artist>>, TransactionError> {
133 self.artists.iter().copied().cache_get_batch(db)
134 }
135
136 #[must_use]
137 pub fn track_refs(&self) -> &[TrackReference] {
138 &self.tracks
139 }
140}
141
142#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
143pub struct TrackReference {
144 pub id: TrackId,
145 pub track_num: Option<u32>,
146 pub disc_num: Option<u32>,
147}
148
149impl PartialEq for TrackReference {
150 fn eq(&self, other: &Self) -> bool {
151 self.id == other.id
152 }
153}
154
155impl Eq for TrackReference {}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct ResolvedAlbum {
159 pub album: Arc<Album>,
160 pub artists: Vec<Arc<Artist>>,
161
162 pub tracks: Vec<(Arc<Track>, Vec<Arc<Artist>>)>,
163}
164
165impl Deref for ResolvedAlbum {
166 type Target = Album;
167
168 fn deref(&self) -> &Self::Target {
169 &self.album
170 }
171}
172
173impl Resolveable for Album {
174 type Resolved = ResolvedAlbum;
175
176 fn resolve(album: Arc<Self>, db: &Self::Db) -> Result<Self::Resolved, TransactionError> {
177 let artists = album.artists_cache(db)?;
178
179 let tracks = album.tracks_cache(db)?;
180 let track_artists = tracks
181 .iter()
182 .try_map(|t| t.metadata.artists_cache(db))?
183 .to_vec();
184
185 Ok(ResolvedAlbum {
186 album,
187 artists,
188 tracks: tracks.into_iter().zip(track_artists).collect(),
189 })
190 }
191}