1use std::{borrow::Cow, collections::HashSet, convert::Infallible, ops::Deref, str::FromStr};
2
3use crate::{
4 config::common::common_config,
5 database::{
6 CompareAndSwapTransaction, DatabaseEntry, DatabaseError, EntryId, Patchable, patch_vec,
7 },
8 library::{
9 album::{Album, AlbumId},
10 cover_art::CoverArt,
11 track::{Track, TrackId},
12 },
13};
14use blake3::{Hash, hash};
15use lunar_lib::{formatter::FormatTable, paths::sys::sanitize_str};
16use regex::Regex;
17use serde::{Deserialize, Serialize};
18
19pub mod accessors;
20pub mod mutators;
21
22pub mod frontend_impls;
23pub mod trait_impls;
24
25pub const UNKNOWN_ARTIST: &str = "UNKNOWN ARTIST";
26
27#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)]
28pub struct ArtistId {
29 id: Hash,
30}
31
32impl EntryId for ArtistId {
33 type Entry = Artist;
34}
35
36impl Deref for ArtistId {
37 type Target = Hash;
38
39 fn deref(&self) -> &Self::Target {
40 &self.id
41 }
42}
43
44impl ArtistId {
45 fn new(name: &str) -> Self {
46 Self {
47 id: hash(name.to_ascii_lowercase().as_bytes()),
48 }
49 }
50}
51
52impl FromStr for ArtistId {
53 type Err = Infallible;
54
55 fn from_str(s: &str) -> Result<Self, Self::Err> {
56 Ok(Self::new(s))
57 }
58}
59
60impl<T> From<T> for ArtistId
61where
62 T: AsRef<str>,
63{
64 fn from(value: T) -> Self {
65 Self::new(value.as_ref())
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct Artist {
71 id: ArtistId,
72 name: String,
73
74 pub cover_art: Option<CoverArt>,
75 pub description: Option<String>,
76
77 pub(crate) tracks: Vec<TrackId>,
78 pub(crate) albums: Vec<AlbumId>,
79
80 version: usize,
81}
82
83impl Artist {
84 #[must_use]
90 pub fn new(name: impl AsRef<str>) -> Option<Self> {
91 let name = name.as_ref().trim();
92 if name.is_empty() {
93 return None;
94 }
95
96 Some(Self {
97 version: 1,
98 id: ArtistId::new(name),
99 name: name.to_owned(),
100 description: None,
101 cover_art: None,
102 tracks: Vec::new(),
103 albums: Vec::new(),
104 })
105 }
106
107 pub fn set_name(&mut self, name: impl AsRef<str>) -> bool {
113 let name = name.as_ref().trim();
114 if name.is_empty() {
115 return false;
116 }
117
118 name.clone_into(&mut self.name);
119 true
120 }
121
122 pub fn albums(&self) -> Result<Vec<Album>, DatabaseError> {
123 Album::db_get_batch(&self.albums)
124 }
125
126 pub fn all_tracks(&self) -> Result<Vec<Track>, DatabaseError> {
127 Track::db_get_batch(&self.tracks)
128 }
129
130 fn tx_albums(&self, cas_tx: &CompareAndSwapTransaction) -> Result<Vec<Album>, DatabaseError> {
131 cas_tx.tx_get_batch(&self.albums)
132 }
133
134 pub fn albums_raw(&self) -> &[AlbumId] {
135 &self.albums
136 }
137}
138
139#[must_use]
140pub fn join_artists(artists: &[Artist]) -> String {
141 if artists.is_empty() {
142 return String::new();
143 }
144
145 let main_sep = &common_config().track_name_config.artist_separator;
146 let alt_sep = &common_config().track_name_config.alt_artist_separator;
147
148 let names: Vec<&str> = artists.iter().map(Artist::name).collect();
149
150 match names.len() {
151 1 => names[0].to_owned(),
152 _ => {
153 format!(
154 "{}{alt_sep}{}",
155 names[..names.len() - 1].join(main_sep),
156 names.last().unwrap(),
157 )
158 }
159 }
160}
161
162pub fn add_from_artists(format_table: &mut FormatTable, artists: &[Artist], artist_type: &str) {
163 if artists.is_empty() {
164 return;
165 }
166
167 let (main_sep, alt_sep) = {
168 let common_config = common_config();
169 (
170 common_config.track_name_config.artist_separator.clone(),
171 common_config.track_name_config.alt_artist_separator.clone(),
172 )
173 };
174
175 let names: Vec<String> = artists.iter().map(|a| sanitize_str(a.name())).collect();
176
177 let main = &names[0];
178 format_table.add_entry(format!("main_{artist_type}_artist"), main);
179
180 if names.len() == 1 {
181 format_table.add_entry(format!("all_{artist_type}_artists"), &names[0]);
182 } else {
183 let all = format!(
184 "{}{alt_sep}{}",
185 names[..names.len() - 1].join(&main_sep),
186 names.last().unwrap(),
187 );
188
189 format_table.add_entry(format!("all_{artist_type}_artists"), all);
190 }
191
192 let featuring = &names[1..];
193 match featuring.len() {
194 0 => (),
195 1 => format_table.add_entry(format!("feat_{artist_type}_artists"), &featuring[0]),
196 _ => {
197 let feat = format!(
198 "{}{alt_sep}{}",
199 featuring[..featuring.len() - 1].join(&main_sep),
200 featuring.last().unwrap(),
201 );
202
203 format_table.add_entry(format!("feat_{artist_type}_artists"), feat);
204 }
205 }
206}
207
208#[derive(Debug, Deserialize, Serialize, Clone, Default, PartialEq, Eq)]
209pub struct ArtistGroup {
210 artists: Vec<ArtistId>,
211}
212
213impl ArtistGroup {
214 #[must_use]
215 pub fn artist_ids(&self) -> &[ArtistId] {
216 &self.artists
217 }
218
219 pub fn artists(&self) -> Result<Vec<Artist>, DatabaseError> {
220 Artist::db_get_batch(&self.artists)
221 }
222
223 #[must_use]
224 pub fn main_artist_id(&self) -> Option<ArtistId> {
225 self.artists.first().copied()
226 }
227
228 pub fn main_artist(&self) -> Result<Option<Artist>, DatabaseError> {
229 if let Some(first) = self.artists.first() {
230 let artist = Artist::db_get(*first)?
231 .expect("ArtistGroup main artist did not exist in the database");
232 Ok(Some(artist))
233 } else {
234 Ok(None)
235 }
236 }
237
238 #[must_use]
239 pub fn featuring_artist_ids(&self) -> &[ArtistId] {
240 self.artists.get(1..).unwrap_or_default()
241 }
242
243 pub fn featuring_artists(&self) -> Result<Vec<Artist>, DatabaseError> {
244 Artist::db_get_batch(self.featuring_artist_ids())
245 }
246
247 pub fn add_artist(&mut self, artist: ArtistId) {
248 if !self.artists.contains(&artist) {
249 self.artists.push(artist);
250 }
251 }
252
253 pub fn add_artists(&mut self, artists: impl IntoIterator<Item = ArtistId>) {
254 artists.into_iter().for_each(|artist| {
255 self.add_artist(artist);
256 });
257 }
258
259 pub fn remove_artist(&mut self, artist: ArtistId) {
260 self.artists.retain(|a| *a != artist);
261 }
262
263 pub fn remove_artists(&mut self, artists: impl IntoIterator<Item = ArtistId>) {
264 let artists: Vec<ArtistId> = artists.into_iter().collect();
265 self.artists.retain(|a| !artists.contains(a));
266 }
267
268 pub fn from_artist_ids<I>(value: I) -> ArtistGroup
269 where
270 I: IntoIterator<Item = ArtistId>,
271 {
272 Self {
273 artists: value
274 .into_iter()
275 .collect::<HashSet<_>>()
276 .into_iter()
277 .collect(),
278 }
279 }
280
281 pub fn from_artists<'a, I>(value: I) -> ArtistGroup
282 where
283 I: IntoIterator<Item = &'a Artist>,
284 {
285 Self {
286 artists: value
287 .into_iter()
288 .collect::<HashSet<_>>()
289 .into_iter()
290 .map(Artist::id)
291 .collect(),
292 }
293 }
294
295 #[must_use]
296 pub fn new() -> Self {
297 Self {
298 artists: Vec::new(),
299 }
300 }
301}
302
303impl std::ops::Deref for ArtistGroup {
304 type Target = [ArtistId];
305
306 fn deref(&self) -> &Self::Target {
307 &self.artists
308 }
309}
310
311impl Patchable<ArtistGroup> for ArtistGroup {
312 fn patch(&mut self, patch: ArtistGroup) {
313 patch_vec(&mut self.artists, patch.to_vec());
314 }
315}
316
317pub fn artists_from_string(artists: impl AsRef<str>) -> Vec<Artist> {
318 let split_regex =
319 Regex::new(r"(?i)\s*(?:,|;|\band\b)\s*").expect("Invalid regex string for 'split_regex'");
320
321 split_regex
322 .split(artists.as_ref())
323 .map(str::trim)
324 .filter_map(Artist::new)
325 .collect()
326}
327
328#[must_use]
329pub fn extract_from_featuring(str: &str) -> (Cow<'_, str>, Vec<Artist>) {
330 let find_regex = Regex::new(r"(?i)\((?:feat|ft|featuring|with)(?:\.|:)?\s+([^)]*?)\)")
331 .expect("Invalid regex string for 'find_regex");
332
333 let Some(artists) = find_regex.captures(str) else {
334 return (Cow::Borrowed(str), Vec::new());
335 };
336
337 let returned_str = find_regex.replace(str, "");
338 let artists = artists_from_string(artists.get(1).unwrap().as_str());
339
340 (returned_str, artists)
341}