use std::{collections::HashSet, fmt::Debug, }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; pub mod bandcamp; pub mod mixxx; pub mod beets; pub mod musicbrainz; pub mod archive; pub mod tools; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum SourceID { Bandcamp, Musicbrainz, Mixxx, Beets } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct Artist { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub bio: Option, #[serde(skip_serializing_if = "Option::is_none")] pub location: Option, } impl PartialEq for Artist { fn eq(&self, other: &Self) -> bool { if self.name != other.name { return false; } true } fn ne(&self, other: &Self) -> bool { !self.eq(other) } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct Album { pub title: String, pub artist: String, #[serde(skip_serializing_if = "Option::is_none")] pub about: Option, #[serde(skip_serializing_if = "Option::is_none")] pub credits: Option, #[serde(skip_serializing_if = "Option::is_none")] pub release_date: Option> } impl PartialEq for Album { fn eq(&self, other: &Self) -> bool { if self.title != other.title || self.artist != other.artist { return false; } true } fn ne(&self, other: &Self) -> bool { !self.eq(other) } } #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct Track { pub title: String, #[serde(skip_serializing_if = "Option::is_none")] pub label: Option, #[serde(skip_serializing_if = "Option::is_none")] pub year: Option, #[serde(skip_serializing_if = "Vec::is_empty")] #[serde(default)] pub genres: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub album: Option, #[serde(skip_serializing_if = "Option::is_none")] pub artist: Option, #[serde(skip_serializing_if = "Option::is_none")] pub bpm: Option } impl PartialEq for Track { fn eq(&self, other: &Self) -> bool { if self.title != other.title { return false; } if other.artist.is_some() && self.artist.is_some() && self.artist != other.artist { return false; } if other.album.is_some() && self.album.is_some() && self.album != other.album { return false; } true } fn ne(&self, other: &Self) -> bool { !self.eq(other) } } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(tag = "type")] pub enum Contents { Artist(Artist), Album(Album), Track(Track) } impl From for Contents { fn from(value: Artist) -> Self { Self::Artist(value) } } impl From for Contents { fn from(value: Album) -> Self { Self::Album(value) } } impl From for Contents { fn from(value: Track) -> Self { Self::Track(value) } } impl Merge for Vec { fn merge(&mut self, other: Self) { for artifact in other { if let Some(merge_target) = self.iter_mut().find(|a| { **a == artifact }) { merge_target.merge(artifact); } else { self.push(artifact); } } } } pub trait Merge { fn merge(&mut self, other: Self); } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct Artifact { #[serde(skip_serializing_if = "Option::is_none")] mbid: Option, #[serde(flatten)] contents: Contents, sources: HashSet, } #[derive(Debug)] pub struct ArtifactBuilder { contents: Option, mbid: Option, source: SourceID, } impl ArtifactBuilder { pub fn new(source: SourceID) -> Self { Self { contents: None, mbid: None, source, } } pub fn contents>(mut self, contents: T) -> Self { self.contents = Some(contents.into()); self } pub fn mbid>(mut self, mbid: T) -> Self { self.mbid = Some(mbid.into()); self } pub fn build(self) -> Artifact { Artifact { mbid: self.mbid, contents: self.contents.unwrap(), sources: HashSet::from_iter([self.source]), } } } impl Artifact { pub fn contents(&self) -> &Contents { &self.contents } } impl Merge for Artifact { fn merge(&mut self, other: Self) { self.contents.merge(other.contents); if self.mbid.is_none() { self.mbid = other.mbid; } for src in other.sources { self.sources.insert(src); } } } macro_rules! merge_fields { ($this:expr, $that:expr, $field:tt) => { if $this.$field.is_none() { $this.$field = $that.$field; } }; ($this:tt, $that:tt, $($fields:tt),+) => { $( merge_fields!($this, $that, $fields); )+ } } impl Merge for Contents { fn merge(&mut self, other: Self) { if *self != other { return; } match (self, other) { (Self::Track(this_track), Self::Track(that_track)) => { this_track.merge(that_track); }, (Self::Album(this_album), Self::Album(that_album)) => { this_album.merge(that_album); }, (Self::Artist(this_artist), Self::Artist(that_artist)) => { this_artist.merge(that_artist); }, _ => () } } } impl Merge for Track { fn merge(&mut self, other: Self) { merge_fields!(self, other, album, label, year, artist, bpm); } } impl Merge for Artist { fn merge(&mut self, other: Self) { merge_fields!(self, other, bio, location); } } impl Merge for Album { fn merge(&mut self, other: Self) { merge_fields!(self, other, about, credits, release_date); } }