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
+127 -41
View File
@@ -1,50 +1,136 @@
use std::collections::HashSet;
use musicbrainz_rs::entity::recording::Recording;
use musicbrainz_rs::{ApiEndpointError, entity::recording::Recording};
use musicbrainz_rs::prelude::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::artifacts::{Album, Artifact, Artist, SourceID, Track};
use crate::artifacts::tools::{DataSource, ToolDescription};
use crate::artifacts::{Album, Artifact, ArtifactBuilder, Artist, Contents, Merge, SourceID, Track};
pub struct MBQuery;
impl DataSource for MBQuery {
type Error = ApiEndpointError;
type Args = MusicbrainzQueryArgs;
async fn synchronize(&mut self, artifact: &mut Artifact) -> Result<Vec<Artifact>, Self::Error> {
let mut new_artifacts = vec![];
if artifact.mbid.is_none() {
return Ok(new_artifacts);
}
let artifact_id = artifact.mbid.clone().unwrap();
log::debug!("Synchronizing {} with musicbrainz", artifact_id);
match artifact.contents {
Contents::Track(ref mut target_track) => {
let mb_track = Recording::fetch()
.id(&artifact_id.to_string())
.with_releases().with_artists().with_annotations().execute_async().await;
let track = match mb_track {
Ok(track) => track,
Err(err) => {
log::error!("Failed to grab musicbrainz data: {:?}", err);
return Err(err);
}
};
artifact.sources.insert(SourceID::Musicbrainz);
for release in track.releases.unwrap_or_default() {
log::debug!("Found new release: {:?}", release);
let first_artist = release.artist_credit.unwrap_or_default().first().unwrap().clone();
new_artifacts.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Album {
title: release.title.clone(),
artist: first_artist.name.clone(),
about: release.annotation,
..Default::default()
})
.mbid(Uuid::parse_str(&release.id).unwrap()).build());
target_track.merge(Track {
album: Some(release.title),
title: track.title.clone(),
artist: Some(first_artist.artist.name.clone()),
..Default::default()
});
new_artifacts.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Artist {
name: first_artist.name,
bio: first_artist.artist.annotation,
location: first_artist.artist.area.and_then(|area| { Some(area.name) }),
..Default::default()
})
.mbid(Uuid::parse_str(&first_artist.artist.id).unwrap()).build());
}
},
_ => ()
}
Ok(new_artifacts)
}
async fn query(&mut self, args: &Self::Args) -> Result<Vec<Artifact>, Self::Error> {
let mut ret = vec![];
for mbid in &args.mb_ids {
log::debug!("Fetching recording id {}", mbid);
let track = Recording::fetch()
.id(&mbid)
.with_releases().with_artists().with_annotations().execute_async().await;
let track = match track {
Ok(track) => track,
Err(err) => {
log::error!("Failed to grab musicbrainz data: {:?}", err);
continue;
}
};
for release in track.releases.unwrap_or_default() {
log::debug!("Found new release: {:?}", release);
let first_artist = release.artist_credit.unwrap_or_default().first().unwrap().clone();
ret.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Album {
title: release.title.clone(),
artist: first_artist.name.clone(),
about: release.annotation,
..Default::default()
})
.mbid(Uuid::parse_str(&release.id).unwrap()).build());
ret.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Track {
album: Some(release.title),
title: track.title.clone(),
artist: Some(first_artist.artist.name.clone()),
..Default::default()
})
.mbid(Uuid::parse_str(&mbid).unwrap()).build());
ret.push(ArtifactBuilder::new(SourceID::Musicbrainz)
.contents(Artist {
name: first_artist.name,
bio: first_artist.artist.annotation,
location: first_artist.artist.area.and_then(|area| { Some(area.name) }),
..Default::default()
})
.mbid(Uuid::parse_str(&first_artist.artist.id).unwrap()).build());
}
}
Ok(ret)
}
}
impl ToolDescription for MBQuery {
fn description(&self) -> &str {
"Fetches artifacts from Musicbrainz"
}
fn name(&self) -> &str {
"query_musicbrainz"
}
}
#[derive(Debug, Default, Deserialize, Serialize, JsonSchema)]
pub struct MusicbrainzQueryArgs {
pub mb_ids: Vec<String>
}
pub async fn search_artifacts(query: MusicbrainzQueryArgs) -> Result<Vec<Artifact>, musicbrainz_rs::ApiEndpointError> {
let mut ret = vec![];
for mbid in query.mb_ids {
let track = Recording::fetch()
.id(&mbid)
.with_releases().with_artists().with_annotations().execute_async().await?;
for release in track.releases.unwrap_or_default() {
log::debug!("Found new release: {:?}", release);
let first_artist = release.artist_credit.unwrap_or_default().first().unwrap().clone();
ret.push(Artifact::Album(Album {
title: release.title.clone(),
artist: first_artist.name.clone(),
about: release.annotation,
sources: HashSet::from([SourceID::Musicbrainz(release.id.clone())]),
..Default::default()
}));
ret.push(Artifact::Track(Track {
album: Some(release.title),
title: track.title.clone(),
artist: Some(first_artist.artist.name.clone()),
sources: HashSet::from([SourceID::Musicbrainz(release.id.clone())]),
..Default::default()
}));
ret.push(Artifact::Artist(Artist {
name: first_artist.name,
bio: first_artist.artist.annotation,
location: first_artist.artist.area.and_then(|area| { Some(area.name) }),
sources: HashSet::from([SourceID::Musicbrainz(release.id)]),
..Default::default()
}))
}
}
Ok(ret)
}