artifacts: rewrite the entire artifact querying layer to create modular 'tools' and 'datasource's

This commit is contained in:
2026-06-17 11:09:50 +02:00
parent 33e0b1768f
commit 3a8130d785
11 changed files with 672 additions and 257 deletions
+126 -44
View File
@@ -1,17 +1,21 @@
use std::collections::{HashMap, HashSet};
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(u64),
Musicbrainz(String),
Bandcamp,
Musicbrainz,
Mixxx,
Beets
}
@@ -23,8 +27,6 @@ pub struct Artist {
pub bio: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
pub sources: HashSet<SourceID>
}
impl PartialEq for Artist {
@@ -50,9 +52,7 @@ pub struct Album {
#[serde(skip_serializing_if = "Option::is_none")]
pub credits: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub release_date: Option<DateTime<Utc>>,
pub sources: HashSet<SourceID>
pub release_date: Option<DateTime<Utc>>
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
@@ -70,9 +70,7 @@ pub struct Track {
#[serde(skip_serializing_if = "Option::is_none")]
pub artist: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bpm: Option<f64>,
pub sources: HashSet<SourceID>
pub bpm: Option<f64>
}
impl PartialEq for Track {
@@ -97,48 +95,29 @@ impl PartialEq for Track {
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum Artifact {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "type")]
pub enum Contents {
Artist(Artist),
Album(Album),
Track(Track)
}
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);
for src in &$that.sources {
$this.sources.insert(src.clone());
}
)+
impl From<Artist> for Contents {
fn from(value: Artist) -> Self {
Self::Artist(value)
}
}
impl Merge for Artifact {
fn merge(&mut self, other: Self) {
if *self != other {
return;
}
impl From<Album> for Contents {
fn from(value: Album) -> Self {
Self::Album(value)
}
}
match (self, other) {
(Self::Track(this_track), Self::Track(that_track)) => {
merge_fields!(this_track, that_track, album, label, year, artist, bpm);
},
(Self::Album(this_album), Self::Album(that_album)) => {
merge_fields!(this_album, that_album, about, credits, release_date);
},
(Self::Artist(this_artist), Self::Artist(that_artist)) => {
merge_fields!(this_artist, that_artist, bio, location);
},
_ => ()
}
impl From<Track> for Contents {
fn from(value: Track) -> Self {
Self::Track(value)
}
}
@@ -156,4 +135,107 @@ impl<M: Merge + PartialEq> Merge for Vec<M> {
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)) => {
merge_fields!(this_album, that_album, about, credits, release_date);
},
(Self::Artist(this_artist), Self::Artist(that_artist)) => {
merge_fields!(this_artist, that_artist, bio, location);
},
_ => ()
}
}
}
impl Merge for Track {
fn merge(&mut self, other: Self) {
merge_fields!(self, other, album, label, year, artist, bpm);
}
}