Make fetch also fetch artist MBID if it is missing #201
@ -104,7 +104,7 @@ fn main() {
|
|||||||
|
|
||||||
match opt.entity {
|
match opt.entity {
|
||||||
OptEntity::Artist(opt_artist) => {
|
OptEntity::Artist(opt_artist) => {
|
||||||
let query = SearchArtistRequest::new().no_field(&opt_artist.string);
|
let query = SearchArtistRequest::new().string(&opt_artist.string);
|
||||||
|
|
||||||
println!("Query: {query}");
|
println!("Query: {query}");
|
||||||
|
|
||||||
|
10
src/external/musicbrainz/api/search/artist.rs
vendored
10
src/external/musicbrainz/api/search/artist.rs
vendored
@ -10,20 +10,20 @@ use crate::{
|
|||||||
use super::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin};
|
use super::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin};
|
||||||
|
|
||||||
pub enum SearchArtist<'a> {
|
pub enum SearchArtist<'a> {
|
||||||
NoField(&'a str),
|
String(&'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Display for SearchArtist<'a> {
|
impl<'a> fmt::Display for SearchArtist<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::NoField(s) => write!(f, "\"{s}\""),
|
Self::String(s) => write!(f, "\"{s}\""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SearchArtistRequest<'a> = Query<SearchArtist<'a>>;
|
pub type SearchArtistRequest<'a> = Query<SearchArtist<'a>>;
|
||||||
|
|
||||||
impl_term!(no_field, SearchArtist<'a>, NoField, &'a str);
|
impl_term!(string, SearchArtist<'a>, String, &'a str);
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct SearchArtistResponse {
|
pub struct SearchArtistResponse {
|
||||||
@ -116,7 +116,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn search_no_field() {
|
fn search_string() {
|
||||||
let mut http = MockIMusicBrainzHttp::new();
|
let mut http = MockIMusicBrainzHttp::new();
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"https://musicbrainz.org/ws/2\
|
"https://musicbrainz.org/ws/2\
|
||||||
@ -137,7 +137,7 @@ mod tests {
|
|||||||
|
|
||||||
let name = "an artist";
|
let name = "an artist";
|
||||||
|
|
||||||
let query = SearchArtistRequest::new().no_field(name);
|
let query = SearchArtistRequest::new().string(name);
|
||||||
|
|
||||||
let matches = client.search_artist(query).unwrap();
|
let matches = client.search_artist(query).unwrap();
|
||||||
assert_eq!(matches, response);
|
assert_eq!(matches, response);
|
||||||
|
40
src/external/musicbrainz/api/search/query.rs
vendored
40
src/external/musicbrainz/api/search/query.rs
vendored
@ -225,52 +225,52 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub enum TestEntity<'a> {
|
pub enum TestEntity<'a> {
|
||||||
NoField(&'a str),
|
String(&'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> fmt::Display for TestEntity<'a> {
|
impl<'a> fmt::Display for TestEntity<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::NoField(s) => write!(f, "\"{s}\""),
|
Self::String(s) => write!(f, "\"{s}\""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestEntityRequest<'a> = Query<TestEntity<'a>>;
|
type TestEntityRequest<'a> = Query<TestEntity<'a>>;
|
||||||
|
|
||||||
impl_term!(no_field, TestEntity<'a>, NoField, &'a str);
|
impl_term!(string, TestEntity<'a>, String, &'a str);
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lucene_logical() {
|
fn lucene_logical() {
|
||||||
let query = TestEntityRequest::new()
|
let query = TestEntityRequest::new()
|
||||||
.no_field("jakarta apache")
|
.string("jakarta apache")
|
||||||
.or()
|
.or()
|
||||||
.no_field("jakarta");
|
.string("jakarta");
|
||||||
assert_eq!(format!("{query}"), "\"jakarta apache\" OR \"jakarta\"");
|
assert_eq!(format!("{query}"), "\"jakarta apache\" OR \"jakarta\"");
|
||||||
|
|
||||||
let query = TestEntityRequest::new()
|
let query = TestEntityRequest::new()
|
||||||
.no_field("jakarta apache")
|
.string("jakarta apache")
|
||||||
.and()
|
.and()
|
||||||
.no_field("jakarta");
|
.string("jakarta");
|
||||||
assert_eq!(format!("{query}"), "\"jakarta apache\" AND \"jakarta\"");
|
assert_eq!(format!("{query}"), "\"jakarta apache\" AND \"jakarta\"");
|
||||||
|
|
||||||
let query = TestEntityRequest::new()
|
let query = TestEntityRequest::new()
|
||||||
.require()
|
.require()
|
||||||
.no_field("jakarta")
|
.string("jakarta")
|
||||||
.or()
|
.or()
|
||||||
.no_field("lucene");
|
.string("lucene");
|
||||||
assert_eq!(format!("{query}"), "+\"jakarta\" OR \"lucene\"");
|
assert_eq!(format!("{query}"), "+\"jakarta\" OR \"lucene\"");
|
||||||
|
|
||||||
let query = TestEntityRequest::new()
|
let query = TestEntityRequest::new()
|
||||||
.no_field("lucene")
|
.string("lucene")
|
||||||
.require()
|
.require()
|
||||||
.no_field("jakarta");
|
.string("jakarta");
|
||||||
assert_eq!(format!("{query}"), "\"lucene\" +\"jakarta\"");
|
assert_eq!(format!("{query}"), "\"lucene\" +\"jakarta\"");
|
||||||
|
|
||||||
let query = TestEntityRequest::new()
|
let query = TestEntityRequest::new()
|
||||||
.no_field("jakarta apache")
|
.string("jakarta apache")
|
||||||
.not()
|
.not()
|
||||||
.no_field("Apache Lucene");
|
.string("Apache Lucene");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{query}"),
|
format!("{query}"),
|
||||||
"\"jakarta apache\" NOT \"Apache Lucene\""
|
"\"jakarta apache\" NOT \"Apache Lucene\""
|
||||||
@ -278,18 +278,18 @@ mod tests {
|
|||||||
|
|
||||||
let query = TestEntityRequest::new()
|
let query = TestEntityRequest::new()
|
||||||
.prohibit()
|
.prohibit()
|
||||||
.no_field("Apache Lucene")
|
.string("Apache Lucene")
|
||||||
.or()
|
.or()
|
||||||
.no_field("jakarta apache");
|
.string("jakarta apache");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{query}"),
|
format!("{query}"),
|
||||||
"-\"Apache Lucene\" OR \"jakarta apache\""
|
"-\"Apache Lucene\" OR \"jakarta apache\""
|
||||||
);
|
);
|
||||||
|
|
||||||
let query = TestEntityRequest::new()
|
let query = TestEntityRequest::new()
|
||||||
.no_field("jakarta apache")
|
.string("jakarta apache")
|
||||||
.prohibit()
|
.prohibit()
|
||||||
.no_field("Apache Lucene");
|
.string("Apache Lucene");
|
||||||
assert_eq!(format!("{query}"), "\"jakarta apache\" -\"Apache Lucene\"");
|
assert_eq!(format!("{query}"), "\"jakarta apache\" -\"Apache Lucene\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,12 +298,12 @@ mod tests {
|
|||||||
let query = TestEntityRequest::new()
|
let query = TestEntityRequest::new()
|
||||||
.expression(
|
.expression(
|
||||||
TestEntityRequest::new()
|
TestEntityRequest::new()
|
||||||
.no_field("jakarta")
|
.string("jakarta")
|
||||||
.or()
|
.or()
|
||||||
.no_field("apache"),
|
.string("apache"),
|
||||||
)
|
)
|
||||||
.and()
|
.and()
|
||||||
.no_field("website");
|
.string("website");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{query}"),
|
format!("{query}"),
|
||||||
"(\"jakarta\" OR \"apache\") AND \"website\""
|
"(\"jakarta\" OR \"apache\") AND \"website\""
|
||||||
|
@ -15,7 +15,7 @@ use crate::{
|
|||||||
use super::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin};
|
use super::query::{impl_term, EmptyQuery, EmptyQueryJoin, Query, QueryJoin};
|
||||||
|
|
||||||
pub enum SearchReleaseGroup<'a> {
|
pub enum SearchReleaseGroup<'a> {
|
||||||
NoField(&'a str),
|
String(&'a str),
|
||||||
Arid(&'a Mbid),
|
Arid(&'a Mbid),
|
||||||
FirstReleaseDate(&'a AlbumDate),
|
FirstReleaseDate(&'a AlbumDate),
|
||||||
ReleaseGroup(&'a str),
|
ReleaseGroup(&'a str),
|
||||||
@ -25,7 +25,7 @@ pub enum SearchReleaseGroup<'a> {
|
|||||||
impl<'a> fmt::Display for SearchReleaseGroup<'a> {
|
impl<'a> fmt::Display for SearchReleaseGroup<'a> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::NoField(s) => write!(f, "\"{s}\""),
|
Self::String(s) => write!(f, "\"{s}\""),
|
||||||
Self::Arid(arid) => write!(f, "arid:{}", arid.uuid().as_hyphenated()),
|
Self::Arid(arid) => write!(f, "arid:{}", arid.uuid().as_hyphenated()),
|
||||||
Self::FirstReleaseDate(date) => write!(
|
Self::FirstReleaseDate(date) => write!(
|
||||||
f,
|
f,
|
||||||
@ -40,7 +40,7 @@ impl<'a> fmt::Display for SearchReleaseGroup<'a> {
|
|||||||
|
|
||||||
pub type SearchReleaseGroupRequest<'a> = Query<SearchReleaseGroup<'a>>;
|
pub type SearchReleaseGroupRequest<'a> = Query<SearchReleaseGroup<'a>>;
|
||||||
|
|
||||||
impl_term!(no_field, SearchReleaseGroup<'a>, NoField, &'a str);
|
impl_term!(string, SearchReleaseGroup<'a>, String, &'a str);
|
||||||
impl_term!(arid, SearchReleaseGroup<'a>, Arid, &'a Mbid);
|
impl_term!(arid, SearchReleaseGroup<'a>, Arid, &'a Mbid);
|
||||||
impl_term!(
|
impl_term!(
|
||||||
first_release_date,
|
first_release_date,
|
||||||
@ -150,7 +150,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn search_no_field() {
|
fn search_string() {
|
||||||
let mut http = MockIMusicBrainzHttp::new();
|
let mut http = MockIMusicBrainzHttp::new();
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"https://musicbrainz.org/ws/2\
|
"https://musicbrainz.org/ws/2\
|
||||||
@ -171,7 +171,7 @@ mod tests {
|
|||||||
|
|
||||||
let title = "an album";
|
let title = "an album";
|
||||||
|
|
||||||
let query = SearchReleaseGroupRequest::new().no_field(title);
|
let query = SearchReleaseGroupRequest::new().string(title);
|
||||||
|
|
||||||
let matches = client.search_release_group(query).unwrap();
|
let matches = client.search_release_group(query).unwrap();
|
||||||
assert_eq!(matches, response);
|
assert_eq!(matches, response);
|
||||||
|
@ -146,6 +146,7 @@ mod tests {
|
|||||||
let album_match_1_1 = Match {
|
let album_match_1_1 = Match {
|
||||||
score: 100,
|
score: 100,
|
||||||
item: album_1_1,
|
item: album_1_1,
|
||||||
|
disambiguation: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut album_1_2 = album_1.clone();
|
let mut album_1_2 = album_1.clone();
|
||||||
@ -154,6 +155,7 @@ mod tests {
|
|||||||
let album_match_1_2 = Match {
|
let album_match_1_2 = Match {
|
||||||
score: 100,
|
score: 100,
|
||||||
item: album_1_2,
|
item: album_1_2,
|
||||||
|
disambiguation: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let matches_info_1 = AppMatchesInfo {
|
let matches_info_1 = AppMatchesInfo {
|
||||||
@ -172,6 +174,7 @@ mod tests {
|
|||||||
let album_match_2_1 = Match {
|
let album_match_2_1 = Match {
|
||||||
score: 100,
|
score: 100,
|
||||||
item: album_2_1,
|
item: album_2_1,
|
||||||
|
disambiguation: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let matches_info_2 = AppMatchesInfo {
|
let matches_info_2 = AppMatchesInfo {
|
||||||
|
36
src/tui/lib/external/musicbrainz/mod.rs
vendored
36
src/tui/lib/external/musicbrainz/mod.rs
vendored
@ -3,11 +3,15 @@
|
|||||||
use musichoard::{
|
use musichoard::{
|
||||||
collection::{
|
collection::{
|
||||||
album::{Album, AlbumDate},
|
album::{Album, AlbumDate},
|
||||||
|
artist::Artist,
|
||||||
musicbrainz::Mbid,
|
musicbrainz::Mbid,
|
||||||
},
|
},
|
||||||
external::musicbrainz::{
|
external::musicbrainz::{
|
||||||
api::{
|
api::{
|
||||||
search::{SearchReleaseGroupRequest, SearchReleaseGroupResponseReleaseGroup},
|
search::{
|
||||||
|
SearchArtistRequest, SearchArtistResponseArtist, SearchReleaseGroupRequest,
|
||||||
|
SearchReleaseGroupResponseReleaseGroup,
|
||||||
|
},
|
||||||
MusicBrainzClient,
|
MusicBrainzClient,
|
||||||
},
|
},
|
||||||
IMusicBrainzHttp,
|
IMusicBrainzHttp,
|
||||||
@ -28,6 +32,21 @@ impl<Http> MusicBrainz<Http> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
||||||
|
fn search_artist(
|
||||||
|
&mut self,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Vec<Match<musichoard::collection::artist::Artist>>, Error> {
|
||||||
|
let query = SearchArtistRequest::new().string(name);
|
||||||
|
|
||||||
|
let mb_response = self.client.search_artist(query)?;
|
||||||
|
|
||||||
|
Ok(mb_response
|
||||||
|
.artists
|
||||||
|
.into_iter()
|
||||||
|
.map(from_search_artist_response_artist)
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
fn search_release_group(
|
fn search_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
arid: &Mbid,
|
arid: &Mbid,
|
||||||
@ -54,6 +73,21 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match<Artist> {
|
||||||
|
let mut artist = Artist::new(entity.name);
|
||||||
|
if let Some(sort) = entity.sort {
|
||||||
|
artist.set_sort_key(sort);
|
||||||
|
}
|
||||||
|
artist.set_musicbrainz_ref(entity.id.into());
|
||||||
|
|
||||||
|
let mut artist_match = Match::new(entity.score, artist);
|
||||||
|
if let Some(disambiguation) = entity.disambiguation {
|
||||||
|
artist_match.set_disambiguation(disambiguation);
|
||||||
|
}
|
||||||
|
|
||||||
|
artist_match
|
||||||
|
}
|
||||||
|
|
||||||
fn from_search_release_group_response_release_group(
|
fn from_search_release_group_response_release_group(
|
||||||
entity: SearchReleaseGroupResponseReleaseGroup,
|
entity: SearchReleaseGroupResponseReleaseGroup,
|
||||||
) -> Match<Album> {
|
) -> Match<Album> {
|
||||||
|
@ -3,11 +3,12 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
|
|
||||||
use musichoard::collection::{album::Album, musicbrainz::Mbid};
|
use musichoard::collection::{album::Album, artist::Artist, musicbrainz::Mbid};
|
||||||
|
|
||||||
/// Trait for interacting with the MusicBrainz API.
|
/// Trait for interacting with the MusicBrainz API.
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait IMusicBrainz {
|
pub trait IMusicBrainz {
|
||||||
|
fn search_artist(&mut self, name: &str) -> Result<Vec<Match<Artist>>, Error>;
|
||||||
fn search_release_group(
|
fn search_release_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
arid: &Mbid,
|
arid: &Mbid,
|
||||||
@ -19,11 +20,16 @@ pub trait IMusicBrainz {
|
|||||||
pub struct Match<T> {
|
pub struct Match<T> {
|
||||||
pub score: u8,
|
pub score: u8,
|
||||||
pub item: T,
|
pub item: T,
|
||||||
|
pub disambiguation: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Match<T> {
|
impl<T> Match<T> {
|
||||||
pub fn new(score: u8, item: T) -> Self {
|
pub fn new(score: u8, item: T) -> Self {
|
||||||
Match { score, item }
|
Match { score, item, disambiguation: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_disambiguation<S: Into<String>>(&mut self, disambiguation: S) {
|
||||||
|
self.disambiguation = Some(disambiguation.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,6 +230,7 @@ mod tests {
|
|||||||
let album_match = Match {
|
let album_match = Match {
|
||||||
score: 80,
|
score: 80,
|
||||||
item: album.clone(),
|
item: album.clone(),
|
||||||
|
disambiguation: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut app = AppPublic {
|
let mut app = AppPublic {
|
||||||
|
Loading…
Reference in New Issue
Block a user