267 lines
6.1 KiB
Rust
267 lines
6.1 KiB
Rust
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<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub location: Option<String>,
|
|
}
|
|
|
|
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<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub credits: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub release_date: Option<DateTime<Utc>>
|
|
}
|
|
|
|
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<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub year: Option<u32>,
|
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
|
#[serde(default)]
|
|
pub genres: Vec<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub album: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub artist: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub bpm: Option<f64>
|
|
}
|
|
|
|
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<Artist> for Contents {
|
|
fn from(value: Artist) -> Self {
|
|
Self::Artist(value)
|
|
}
|
|
}
|
|
|
|
impl From<Album> for Contents {
|
|
fn from(value: Album) -> Self {
|
|
Self::Album(value)
|
|
}
|
|
}
|
|
|
|
impl From<Track> for Contents {
|
|
fn from(value: Track) -> Self {
|
|
Self::Track(value)
|
|
}
|
|
}
|
|
|
|
impl<M: Merge + PartialEq> Merge for Vec<M> {
|
|
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<Uuid>,
|
|
#[serde(flatten)]
|
|
contents: Contents,
|
|
|
|
sources: HashSet<SourceID>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ArtifactBuilder {
|
|
contents: Option<Contents>,
|
|
mbid: Option<Uuid>,
|
|
source: SourceID,
|
|
}
|
|
|
|
impl ArtifactBuilder {
|
|
pub fn new(source: SourceID) -> Self {
|
|
Self {
|
|
contents: None,
|
|
mbid: None,
|
|
source,
|
|
}
|
|
}
|
|
|
|
pub fn contents<T: Into<Contents>>(mut self, contents: T) -> Self {
|
|
self.contents = Some(contents.into());
|
|
self
|
|
}
|
|
|
|
pub fn mbid<T: Into<Uuid>>(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);
|
|
}
|
|
} |