Add option for manual input during fetch #219

Merged
wojtek merged 9 commits from 188---add-option-for-manual-input-during-fetch into main 2024-09-23 22:40:25 +02:00
16 changed files with 549 additions and 127 deletions
Showing only changes of commit 87fc692278 - Show all commits

View File

@ -34,9 +34,9 @@ fn main() {
let mut request = LookupArtistRequest::new(&mbid); let mut request = LookupArtistRequest::new(&mbid);
request.include_release_groups(); request.include_release_groups();
let albums = client let response = client
.lookup_artist(request) .lookup_artist(request)
.expect("failed to make API call"); .expect("failed to make API call");
println!("{albums:#?}"); println!("{response:#?}");
} }

View File

@ -205,11 +205,11 @@ impl AlbumMeta {
} }
pub fn set_musicbrainz_ref(&mut self, mbref: MbAlbumRef) { pub fn set_musicbrainz_ref(&mut self, mbref: MbAlbumRef) {
_ = self.musicbrainz.insert(mbref); self.musicbrainz.replace(mbref);
} }
pub fn clear_musicbrainz_ref(&mut self) { pub fn clear_musicbrainz_ref(&mut self) {
_ = self.musicbrainz.take(); self.musicbrainz.take();
} }
} }
@ -371,7 +371,7 @@ mod tests {
album album
.meta .meta
.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap()); .set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap());
_ = expected.insert(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap()); expected.replace(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(album.meta.musicbrainz, expected); assert_eq!(album.meta.musicbrainz, expected);
album album
@ -382,12 +382,12 @@ mod tests {
album album
.meta .meta
.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap()); .set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap());
_ = expected.insert(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap()); expected.replace(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap());
assert_eq!(album.meta.musicbrainz, expected); assert_eq!(album.meta.musicbrainz, expected);
// Clearing URLs. // Clearing URLs.
album.meta.clear_musicbrainz_ref(); album.meta.clear_musicbrainz_ref();
_ = expected.take(); expected.take();
assert_eq!(album.meta.musicbrainz, expected); assert_eq!(album.meta.musicbrainz, expected);
} }
} }

View File

@ -89,15 +89,15 @@ impl ArtistMeta {
} }
pub fn clear_sort_key(&mut self) { pub fn clear_sort_key(&mut self) {
_ = self.sort.take(); self.sort.take();
} }
pub fn set_musicbrainz_ref(&mut self, mbref: MbArtistRef) { pub fn set_musicbrainz_ref(&mut self, mbref: MbArtistRef) {
_ = self.musicbrainz.insert(mbref); self.musicbrainz.replace(mbref);
} }
pub fn clear_musicbrainz_ref(&mut self) { pub fn clear_musicbrainz_ref(&mut self) {
_ = self.musicbrainz.take(); self.musicbrainz.take();
} }
// In the functions below, it would be better to use `contains` instead of `iter().any`, but for // In the functions below, it would be better to use `contains` instead of `iter().any`, but for
@ -262,7 +262,7 @@ mod tests {
artist artist
.meta .meta
.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap()); .set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
_ = expected.insert(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap()); expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(artist.meta.musicbrainz, expected); assert_eq!(artist.meta.musicbrainz, expected);
artist artist
@ -273,12 +273,12 @@ mod tests {
artist artist
.meta .meta
.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap()); .set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap());
_ = expected.insert(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap()); expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap());
assert_eq!(artist.meta.musicbrainz, expected); assert_eq!(artist.meta.musicbrainz, expected);
// Clearing URLs. // Clearing URLs.
artist.meta.clear_musicbrainz_ref(); artist.meta.clear_musicbrainz_ref();
_ = expected.take(); expected.take();
assert_eq!(artist.meta.musicbrainz, expected); assert_eq!(artist.meta.musicbrainz, expected);
} }

View File

@ -454,7 +454,7 @@ mod tests {
assert!(music_hoard assert!(music_hoard
.set_artist_musicbrainz(&artist_id, MUSICBRAINZ) .set_artist_musicbrainz(&artist_id, MUSICBRAINZ)
.is_ok()); .is_ok());
_ = expected.insert(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap()); expected.replace(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected); assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);
// Clearing URLs on an artist that does not exist is an error. // Clearing URLs on an artist that does not exist is an error.
@ -463,7 +463,7 @@ mod tests {
// Clearing URLs. // Clearing URLs.
assert!(music_hoard.clear_artist_musicbrainz(&artist_id).is_ok()); assert!(music_hoard.clear_artist_musicbrainz(&artist_id).is_ok());
_ = expected.take(); expected.take();
assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected); assert_eq!(music_hoard.collection[0].meta.musicbrainz, expected);
} }

View File

@ -3,7 +3,8 @@ use url::form_urlencoded;
use crate::{ use crate::{
collection::{ collection::{
album::{AlbumDate, AlbumPrimaryType, AlbumSecondaryType}, album::{AlbumDate, AlbumId, AlbumPrimaryType, AlbumSecondaryType},
artist::ArtistId,
musicbrainz::Mbid, musicbrainz::Mbid,
}, },
external::musicbrainz::{ external::musicbrainz::{
@ -36,6 +37,19 @@ impl<Http: IMusicBrainzHttp> MusicBrainzClient<Http> {
let response: DeserializeLookupArtistResponse = self.http.get(&url)?; let response: DeserializeLookupArtistResponse = self.http.get(&url)?;
Ok(response.into()) Ok(response.into())
} }
pub fn lookup_release_group(
&mut self,
request: LookupReleaseGroupRequest,
) -> Result<LookupReleaseGroupResponse, Error> {
let url = format!(
"{MB_BASE_URL}/release-group/{mbid}",
mbid = request.mbid.uuid().as_hyphenated()
);
let response: DeserializeLookupReleaseGroupResponse = self.http.get(&url)?;
Ok(response.into())
}
} }
pub struct LookupArtistRequest<'a> { pub struct LookupArtistRequest<'a> {
@ -59,19 +73,37 @@ impl<'a> LookupArtistRequest<'a> {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct LookupArtistResponse { pub struct LookupArtistResponse {
pub id: Mbid,
pub name: ArtistId,
pub sort: Option<ArtistId>,
pub disambiguation: Option<String>,
pub release_groups: Vec<LookupArtistResponseReleaseGroup>, pub release_groups: Vec<LookupArtistResponseReleaseGroup>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))] #[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeLookupArtistResponse { struct DeserializeLookupArtistResponse {
release_groups: Vec<DeserializeLookupArtistResponseReleaseGroup>, id: SerdeMbid,
name: String,
sort_name: String,
disambiguation: Option<String>,
release_groups: Option<Vec<DeserializeLookupArtistResponseReleaseGroup>>,
} }
impl From<DeserializeLookupArtistResponse> for LookupArtistResponse { impl From<DeserializeLookupArtistResponse> for LookupArtistResponse {
fn from(value: DeserializeLookupArtistResponse) -> Self { fn from(value: DeserializeLookupArtistResponse) -> Self {
let sort: Option<ArtistId> = Some(value.sort_name)
.filter(|s| s != &value.name)
.map(Into::into);
LookupArtistResponse { LookupArtistResponse {
release_groups: value.release_groups.into_iter().map(Into::into).collect(), id: value.id.into(),
name: value.name.into(),
sort,
disambiguation: value.disambiguation,
release_groups: value
.release_groups
.map(|rgs| rgs.into_iter().map(Into::into).collect())
.unwrap_or_default(),
} }
} }
} }
@ -107,6 +139,49 @@ impl From<DeserializeLookupArtistResponseReleaseGroup> for LookupArtistResponseR
} }
} }
pub struct LookupReleaseGroupRequest<'a> {
mbid: &'a Mbid,
}
impl<'a> LookupReleaseGroupRequest<'a> {
pub fn new(mbid: &'a Mbid) -> Self {
LookupReleaseGroupRequest { mbid }
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct LookupReleaseGroupResponse {
pub id: Mbid,
pub title: AlbumId,
pub first_release_date: AlbumDate,
pub primary_type: AlbumPrimaryType,
pub secondary_types: Option<Vec<AlbumSecondaryType>>,
}
#[derive(Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct DeserializeLookupReleaseGroupResponse {
id: SerdeMbid,
title: String,
first_release_date: SerdeAlbumDate,
primary_type: SerdeAlbumPrimaryType,
secondary_types: Option<Vec<SerdeAlbumSecondaryType>>,
}
impl From<DeserializeLookupReleaseGroupResponse> for LookupReleaseGroupResponse {
fn from(value: DeserializeLookupReleaseGroupResponse) -> Self {
LookupReleaseGroupResponse {
id: value.id.into(),
title: value.title.into(),
first_release_date: value.first_release_date.into(),
primary_type: value.primary_type.into(),
secondary_types: value
.secondary_types
.map(|v| v.into_iter().map(Into::into).collect()),
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use mockall::predicate; use mockall::predicate;
@ -117,12 +192,14 @@ mod tests {
#[test] #[test]
fn lookup_artist() { fn lookup_artist() {
let mbid = "00000000-0000-0000-0000-000000000000";
let mut http = MockIMusicBrainzHttp::new(); let mut http = MockIMusicBrainzHttp::new();
let url = format!( let url = format!("https://musicbrainz.org/ws/2/artist/{mbid}?inc=release-groups",);
"https://musicbrainz.org/ws/2/artist/{mbid}?inc=release-groups",
mbid = "00000000-0000-0000-0000-000000000000",
);
let de_id = SerdeMbid(mbid.try_into().unwrap());
let de_name = String::from("the artist");
let de_sort_name = String::from("artist, the");
let de_disambiguation = Some(String::from("disambig"));
let de_release_group = DeserializeLookupArtistResponseReleaseGroup { let de_release_group = DeserializeLookupArtistResponseReleaseGroup {
id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()), id: SerdeMbid("11111111-1111-1111-1111-111111111111".try_into().unwrap()),
title: String::from("an album"), title: String::from("an album"),
@ -131,7 +208,11 @@ mod tests {
secondary_types: vec![SerdeAlbumSecondaryType(AlbumSecondaryType::Compilation)], secondary_types: vec![SerdeAlbumSecondaryType(AlbumSecondaryType::Compilation)],
}; };
let de_response = DeserializeLookupArtistResponse { let de_response = DeserializeLookupArtistResponse {
release_groups: vec![de_release_group.clone()], id: de_id.clone(),
name: de_name.clone(),
sort_name: de_sort_name.clone(),
disambiguation: de_disambiguation.clone(),
release_groups: Some(vec![de_release_group.clone()]),
}; };
let release_group = LookupArtistResponseReleaseGroup { let release_group = LookupArtistResponseReleaseGroup {
@ -146,6 +227,10 @@ mod tests {
.collect(), .collect(),
}; };
let response = LookupArtistResponse { let response = LookupArtistResponse {
id: de_id.0,
name: de_name.into(),
sort: Some(de_sort_name.into()),
disambiguation: de_disambiguation,
release_groups: vec![release_group], release_groups: vec![release_group],
}; };

View File

@ -3,7 +3,11 @@ use std::{
sync::mpsc::{self, TryRecvError}, sync::mpsc::{self, TryRecvError},
}; };
use musichoard::collection::{artist::Artist, musicbrainz::IMusicBrainzRef}; use musichoard::collection::{
album::AlbumMeta,
artist::{Artist, ArtistMeta},
musicbrainz::{IMusicBrainzRef, Mbid},
};
use crate::tui::{ use crate::tui::{
app::{ app::{
@ -17,12 +21,29 @@ use crate::tui::{
pub struct FetchState { pub struct FetchState {
fetch_rx: FetchReceiver, fetch_rx: FetchReceiver,
lookup_rx: Option<FetchReceiver>,
} }
pub type FetchReceiver = mpsc::Receiver<MbApiResult>; pub type FetchReceiver = mpsc::Receiver<MbApiResult>;
impl FetchState { impl FetchState {
pub fn new(fetch_rx: FetchReceiver) -> Self { pub fn new(fetch_rx: FetchReceiver) -> Self {
FetchState { fetch_rx } FetchState {
fetch_rx,
lookup_rx: None,
}
}
fn try_recv(&mut self) -> Result<MbApiResult, TryRecvError> {
if let Some(lookup_rx) = &self.lookup_rx {
let result = lookup_rx.try_recv();
match result {
Ok(_) | Err(TryRecvError::Empty) => return result,
_ => {
self.lookup_rx.take();
}
}
}
self.fetch_rx.try_recv()
} }
} }
@ -53,8 +74,46 @@ impl AppMachine<FetchState> {
Self::app_fetch(inner, fetch, false) Self::app_fetch(inner, fetch, false)
} }
fn app_fetch(inner: AppInner, fetch: FetchState, first: bool) -> App { pub fn app_lookup_artist(
match fetch.fetch_rx.try_recv() { inner: AppInner,
fetch: FetchState,
artist: &ArtistMeta,
mbid: Mbid,
) -> App {
let f = Self::submit_lookup_artist_job;
Self::app_lookup(f, inner, fetch, artist, mbid)
}
pub fn app_lookup_album(
inner: AppInner,
fetch: FetchState,
album: &AlbumMeta,
mbid: Mbid,
) -> App {
let f = Self::submit_lookup_release_group_job;
Self::app_lookup(f, inner, fetch, album, mbid)
}
fn app_lookup<F, Meta>(
submit: F,
inner: AppInner,
mut fetch: FetchState,
meta: Meta,
mbid: Mbid,
) -> App
where
F: FnOnce(&dyn IMbJobSender, ResultSender, Meta, Mbid) -> Result<(), DaemonError>,
{
let (lookup_tx, lookup_rx) = mpsc::channel::<MbApiResult>();
if let Err(err) = submit(&*inner.musicbrainz, lookup_tx, meta, mbid) {
return AppMachine::error_state(inner, err.to_string()).into();
}
fetch.lookup_rx.replace(lookup_rx);
Self::app_fetch_next(inner, fetch)
}
fn app_fetch(inner: AppInner, mut fetch: FetchState, first: bool) -> App {
match fetch.try_recv() {
Ok(fetch_result) => match fetch_result { Ok(fetch_result) => match fetch_result {
Ok(next_match) => { Ok(next_match) => {
let current = Some(next_match); let current = Some(next_match);
@ -95,6 +154,26 @@ impl AppMachine<FetchState> {
}; };
musicbrainz.submit_background_job(result_sender, requests) musicbrainz.submit_background_job(result_sender, requests)
} }
fn submit_lookup_artist_job(
musicbrainz: &dyn IMbJobSender,
result_sender: ResultSender,
artist: &ArtistMeta,
mbid: Mbid,
) -> Result<(), DaemonError> {
let requests = VecDeque::from([MbParams::lookup_artist(artist.clone(), mbid)]);
musicbrainz.submit_foreground_job(result_sender, requests)
}
fn submit_lookup_release_group_job(
musicbrainz: &dyn IMbJobSender,
result_sender: ResultSender,
album: &AlbumMeta,
mbid: Mbid,
) -> Result<(), DaemonError> {
let requests = VecDeque::from([MbParams::lookup_release_group(album.clone(), mbid)]);
musicbrainz.submit_foreground_job(result_sender, requests)
}
} }
impl From<AppMachine<FetchState>> for App { impl From<AppMachine<FetchState>> for App {
@ -133,7 +212,7 @@ mod tests {
use crate::tui::{ use crate::tui::{
app::{ app::{
machine::tests::{inner, music_hoard}, machine::tests::{inner, music_hoard},
Delta, IApp, IAppAccess, IAppInteractBrowse, MatchOption, MatchStateInfo, Delta, IApp, IAppAccess, IAppInteractBrowse, MatchStateInfo, MissOption, SearchOption,
}, },
lib::interface::musicbrainz::{self, api::Match, daemon::MockIMbJobSender}, lib::interface::musicbrainz::{self, api::Match, daemon::MockIMbJobSender},
testmod::COLLECTION, testmod::COLLECTION,
@ -237,7 +316,8 @@ mod tests {
let artist = COLLECTION[3].meta.clone(); let artist = COLLECTION[3].meta.clone();
let artist_match = Match::new(80, COLLECTION[2].meta.clone()); let artist_match = Match::new(80, COLLECTION[2].meta.clone());
let artist_match_info = MatchStateInfo::artist(artist.clone(), vec![artist_match.clone()]); let artist_match_info =
MatchStateInfo::artist_search(artist.clone(), vec![artist_match.clone()]);
let fetch_result = Ok(artist_match_info); let fetch_result = Ok(artist_match_info);
tx.send(fetch_result).unwrap(); tx.send(fetch_result).unwrap();
@ -250,10 +330,10 @@ mod tests {
let match_state = public.state.unwrap_match(); let match_state = public.state.unwrap_match();
let match_options = vec![ let match_options = vec![
artist_match.into(), artist_match.into(),
MatchOption::CannotHaveMbid, SearchOption::None(MissOption::CannotHaveMbid),
MatchOption::ManualInputMbid, SearchOption::None(MissOption::ManualInputMbid),
]; ];
let expected = MatchStateInfo::artist(artist, match_options); let expected = MatchStateInfo::artist_search(artist, match_options);
assert_eq!(match_state.info, Some(expected).as_ref()); assert_eq!(match_state.info, Some(expected).as_ref());
} }
@ -309,7 +389,8 @@ mod tests {
assert!(matches!(app, AppState::Fetch(_))); assert!(matches!(app, AppState::Fetch(_)));
let artist = COLLECTION[3].meta.clone(); let artist = COLLECTION[3].meta.clone();
let fetch_result = Ok(MatchStateInfo::artist::<Match<ArtistMeta>>(artist, vec![])); let match_info = MatchStateInfo::artist_search::<Match<ArtistMeta>>(artist, vec![]);
let fetch_result = Ok(match_info);
tx.send(fetch_result).unwrap(); tx.send(fetch_result).unwrap();
let app = app.unwrap_fetch().fetch_result_ready(); let app = app.unwrap_fetch().fetch_result_ready();

View File

@ -11,6 +11,12 @@ impl<'app> From<&'app Input> for InputPublic<'app> {
} }
} }
impl Input {
pub fn value(&self) -> &str {
self.0.value()
}
}
impl From<App> for AppMode<App, AppInputMode> { impl From<App> for AppMode<App, AppInputMode> {
fn from(mut app: App) -> Self { fn from(mut app: App) -> Self {
if let Some(input) = app.input_mut().take() { if let Some(input) = app.input_mut().take() {
@ -43,9 +49,9 @@ impl IAppInput for AppInputMode {
self.app self.app
} }
fn confirm(mut self) -> Self::APP { fn confirm(self) -> Self::APP {
if let AppState::Match(state) = &mut self.app { if let AppState::Match(state) = self.app {
state.submit_input(self.input); return state.submit_input(self.input);
} }
self.app self.app
} }

View File

@ -1,26 +1,62 @@
use std::cmp; use std::cmp;
use musichoard::collection::musicbrainz::Mbid;
use crate::tui::app::{ use crate::tui::app::{
machine::{fetch_state::FetchState, input::Input, App, AppInner, AppMachine}, machine::{fetch_state::FetchState, input::Input, App, AppInner, AppMachine},
AlbumMatches, AppPublicState, AppState, ArtistMatches, IAppInteractMatch, MatchOption, AlbumMatches, AppPublicState, AppState, ArtistMatches, IAppInteractMatch, ListOption,
MatchStateInfo, MatchStatePublic, WidgetState, LookupOption, MatchStateInfo, MatchStatePublic, MissOption, SearchOption, WidgetState,
}; };
impl<T: PartialEq> ListOption<T> {
fn len(&self) -> usize {
match self {
ListOption::Lookup(list) => list.len(),
ListOption::Search(list) => list.len(),
}
}
fn push_cannot_have_mbid(&mut self) {
match self {
ListOption::Lookup(list) => list.push(LookupOption::None(MissOption::CannotHaveMbid)),
ListOption::Search(list) => list.push(SearchOption::None(MissOption::CannotHaveMbid)),
}
}
fn push_manual_input_mbid(&mut self) {
match self {
ListOption::Lookup(list) => list.push(LookupOption::None(MissOption::ManualInputMbid)),
ListOption::Search(list) => list.push(SearchOption::None(MissOption::ManualInputMbid)),
}
}
fn is_manual_input_mbid(&self, index: usize) -> bool {
match self {
ListOption::Lookup(list) => {
list.get(index) == Some(&LookupOption::None(MissOption::ManualInputMbid))
}
ListOption::Search(list) => {
list.get(index) == Some(&SearchOption::None(MissOption::ManualInputMbid))
}
}
}
}
impl ArtistMatches { impl ArtistMatches {
fn len(&self) -> usize { fn len(&self) -> usize {
self.list.len() self.list.len()
} }
fn push_cannot_have_mbid(&mut self) { fn push_cannot_have_mbid(&mut self) {
self.list.push(MatchOption::CannotHaveMbid) self.list.push_cannot_have_mbid();
} }
fn push_manual_input_mbid(&mut self) { fn push_manual_input_mbid(&mut self) {
self.list.push(MatchOption::ManualInputMbid) self.list.push_manual_input_mbid();
} }
fn is_manual_input_mbid(&self, index: usize) -> bool { fn is_manual_input_mbid(&self, index: usize) -> bool {
self.list.get(index) == Some(&MatchOption::ManualInputMbid) self.list.is_manual_input_mbid(index)
} }
} }
@ -30,15 +66,15 @@ impl AlbumMatches {
} }
fn push_cannot_have_mbid(&mut self) { fn push_cannot_have_mbid(&mut self) {
self.list.push(MatchOption::CannotHaveMbid) self.list.push_cannot_have_mbid();
} }
fn push_manual_input_mbid(&mut self) { fn push_manual_input_mbid(&mut self) {
self.list.push(MatchOption::ManualInputMbid) self.list.push_manual_input_mbid();
} }
fn is_manual_input_mbid(&self, index: usize) -> bool { fn is_manual_input_mbid(&self, index: usize) -> bool {
self.list.get(index) == Some(&MatchOption::ManualInputMbid) self.list.is_manual_input_mbid(index)
} }
} }
@ -99,7 +135,22 @@ impl AppMachine<MatchState> {
AppMachine::new(inner, state) AppMachine::new(inner, state)
} }
pub fn submit_input(&mut self, _input: Input) {} pub fn submit_input(self, input: Input) -> App {
let mbid: Mbid = match input.value().try_into() {
Ok(mbid) => mbid,
Err(err) => return AppMachine::error_state(self.inner, err.to_string()).into(),
};
match self.state.current.as_ref().unwrap() {
MatchStateInfo::Artist(artist_matches) => {
let matching = &artist_matches.matching;
AppMachine::app_lookup_artist(self.inner, self.state.fetch, matching, mbid)
}
MatchStateInfo::Album(album_matches) => {
let matching = &album_matches.matching;
AppMachine::app_lookup_album(self.inner, self.state.fetch, matching, mbid)
}
}
}
} }
impl From<AppMachine<MatchState>> for App { impl From<AppMachine<MatchState>> for App {
@ -205,7 +256,7 @@ mod tests {
artist_match_2.disambiguation = Some(String::from("some disambiguation")); artist_match_2.disambiguation = Some(String::from("some disambiguation"));
let list = vec![artist_match_1.clone(), artist_match_2.clone()]; let list = vec![artist_match_1.clone(), artist_match_2.clone()];
MatchStateInfo::artist(artist, list) MatchStateInfo::artist_search(artist, list)
} }
fn album_match() -> MatchStateInfo { fn album_match() -> MatchStateInfo {
@ -225,7 +276,7 @@ mod tests {
let album_match_2 = Match::new(100, album_2); let album_match_2 = Match::new(100, album_2);
let list = vec![album_match_1.clone(), album_match_2.clone()]; let list = vec![album_match_1.clone(), album_match_2.clone()];
MatchStateInfo::album(album, list) MatchStateInfo::album_search(album, list)
} }
fn fetch_state() -> FetchState { fn fetch_state() -> FetchState {
@ -233,8 +284,8 @@ mod tests {
FetchState::new(rx) FetchState::new(rx)
} }
fn match_state(matches_info: Option<MatchStateInfo>) -> MatchState { fn match_state(match_state_info: Option<MatchStateInfo>) -> MatchState {
MatchState::new(matches_info, fetch_state()) MatchState::new(match_state_info, fetch_state())
} }
#[test] #[test]

View File

@ -32,6 +32,8 @@ macro_rules! IAppState {
} }
use IAppState; use IAppState;
use super::lib::interface::musicbrainz::api::Lookup;
pub trait IApp { pub trait IApp {
type BrowseState: IAppBase<APP = Self> + IAppInteractBrowse<APP = Self>; type BrowseState: IAppBase<APP = Self> + IAppInteractBrowse<APP = Self>;
type InfoState: IAppBase<APP = Self> + IAppInteractInfo<APP = Self>; type InfoState: IAppBase<APP = Self> + IAppInteractInfo<APP = Self>;
@ -175,28 +177,51 @@ pub struct AppPublicInner<'app> {
pub type InputPublic<'app> = &'app tui_input::Input; pub type InputPublic<'app> = &'app tui_input::Input;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum MatchOption<T> { pub enum MissOption {
Match(Match<T>),
CannotHaveMbid, CannotHaveMbid,
ManualInputMbid, ManualInputMbid,
} }
impl<T> From<Match<T>> for MatchOption<T> { #[derive(Clone, Debug, PartialEq, Eq)]
pub enum SearchOption<T> {
Match(Match<T>),
None(MissOption),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LookupOption<T> {
Match(Lookup<T>),
None(MissOption),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ListOption<T> {
Search(Vec<SearchOption<T>>),
Lookup(Vec<LookupOption<T>>),
}
impl<T> From<Match<T>> for SearchOption<T> {
fn from(value: Match<T>) -> Self { fn from(value: Match<T>) -> Self {
MatchOption::Match(value) SearchOption::Match(value)
}
}
impl<T> From<Lookup<T>> for LookupOption<T> {
fn from(value: Lookup<T>) -> Self {
LookupOption::Match(value)
} }
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct ArtistMatches { pub struct ArtistMatches {
pub matching: ArtistMeta, pub matching: ArtistMeta,
pub list: Vec<MatchOption<ArtistMeta>>, pub list: ListOption<ArtistMeta>,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlbumMatches { pub struct AlbumMatches {
pub matching: AlbumMeta, pub matching: AlbumMeta,
pub list: Vec<MatchOption<AlbumMeta>>, pub list: ListOption<AlbumMeta>,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -206,13 +231,29 @@ pub enum MatchStateInfo {
} }
impl MatchStateInfo { impl MatchStateInfo {
pub fn artist<M: Into<MatchOption<ArtistMeta>>>(matching: ArtistMeta, list: Vec<M>) -> Self { pub fn artist_search<M: Into<SearchOption<ArtistMeta>>>(
let list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect(); matching: ArtistMeta,
list: Vec<M>,
) -> Self {
let list = ListOption::Search(list.into_iter().map(Into::into).collect());
MatchStateInfo::Artist(ArtistMatches { matching, list }) MatchStateInfo::Artist(ArtistMatches { matching, list })
} }
pub fn album<M: Into<MatchOption<AlbumMeta>>>(matching: AlbumMeta, list: Vec<M>) -> Self { pub fn album_search<M: Into<SearchOption<AlbumMeta>>>(
let list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect(); matching: AlbumMeta,
list: Vec<M>,
) -> Self {
let list = ListOption::Search(list.into_iter().map(Into::into).collect());
MatchStateInfo::Album(AlbumMatches { matching, list })
}
pub fn artist_lookup<M: Into<LookupOption<ArtistMeta>>>(matching: ArtistMeta, item: M) -> Self {
let list = ListOption::Lookup(vec![item.into()]);
MatchStateInfo::Artist(ArtistMatches { matching, list })
}
pub fn album_lookup<M: Into<LookupOption<AlbumMeta>>>(matching: AlbumMeta, item: M) -> Self {
let list = ListOption::Lookup(vec![item.into()]);
MatchStateInfo::Album(AlbumMatches { matching, list }) MatchStateInfo::Album(AlbumMatches { matching, list })
} }
} }

View File

@ -10,6 +10,10 @@ use musichoard::{
}, },
external::musicbrainz::{ external::musicbrainz::{
api::{ api::{
lookup::{
LookupArtistRequest, LookupArtistResponse, LookupReleaseGroupRequest,
LookupReleaseGroupResponse,
},
search::{ search::{
SearchArtistRequest, SearchArtistResponseArtist, SearchReleaseGroupRequest, SearchArtistRequest, SearchArtistResponseArtist, SearchReleaseGroupRequest,
SearchReleaseGroupResponseReleaseGroup, SearchReleaseGroupResponseReleaseGroup,
@ -20,7 +24,7 @@ use musichoard::{
}, },
}; };
use crate::tui::lib::interface::musicbrainz::api::{Error, IMusicBrainz, Match}; use crate::tui::lib::interface::musicbrainz::api::{Error, IMusicBrainz, Lookup, Match};
// GRCOV_EXCL_START // GRCOV_EXCL_START
pub struct MusicBrainz<Http> { pub struct MusicBrainz<Http> {
@ -34,6 +38,22 @@ impl<Http> MusicBrainz<Http> {
} }
impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> { impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
fn lookup_artist(&mut self, mbid: &Mbid) -> Result<Lookup<ArtistMeta>, Error> {
let request = LookupArtistRequest::new(mbid);
let mb_response = self.client.lookup_artist(request)?;
Ok(from_lookup_artist_response(mb_response))
}
fn lookup_release_group(&mut self, mbid: &Mbid) -> Result<Lookup<AlbumMeta>, Error> {
let request = LookupReleaseGroupRequest::new(mbid);
let mb_response = self.client.lookup_release_group(request)?;
Ok(from_lookup_release_group_response(mb_response))
}
fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Match<ArtistMeta>>, Error> { fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Match<ArtistMeta>>, Error> {
let query = SearchArtistRequest::new().string(&artist.id.name); let query = SearchArtistRequest::new().string(&artist.id.name);
@ -72,6 +92,32 @@ impl<Http: IMusicBrainzHttp> IMusicBrainz for MusicBrainz<Http> {
} }
} }
fn from_lookup_artist_response(entity: LookupArtistResponse) -> Lookup<ArtistMeta> {
Lookup {
item: ArtistMeta {
id: entity.name,
sort: entity.sort.map(Into::into),
musicbrainz: Some(entity.id.into()),
properties: HashMap::new(),
},
disambiguation: entity.disambiguation,
}
}
fn from_lookup_release_group_response(entity: LookupReleaseGroupResponse) -> Lookup<AlbumMeta> {
Lookup {
item: AlbumMeta {
id: entity.title,
date: entity.first_release_date,
seq: AlbumSeq::default(),
musicbrainz: Some(entity.id.into()),
primary_type: Some(entity.primary_type),
secondary_types: entity.secondary_types.unwrap_or_default(),
},
disambiguation: None,
}
}
fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match<ArtistMeta> { fn from_search_artist_response_artist(entity: SearchArtistResponseArtist) -> Match<ArtistMeta> {
Match { Match {
score: entity.score, score: entity.score,

View File

@ -5,7 +5,7 @@ use crate::tui::{
event::IFetchCompleteEventSender, event::IFetchCompleteEventSender,
lib::interface::musicbrainz::{ lib::interface::musicbrainz::{
api::{Error as ApiError, IMusicBrainz}, api::{Error as ApiError, IMusicBrainz},
daemon::{Error, IMbJobSender, MbParams, ResultSender, SearchParams}, daemon::{Error, IMbJobSender, LookupParams, MbParams, ResultSender, SearchParams},
}, },
}; };
@ -105,6 +105,14 @@ impl JobChannel {
} }
impl IMbJobSender for JobSender { impl IMbJobSender for JobSender {
fn submit_foreground_job(
&self,
result_sender: ResultSender,
requests: VecDeque<MbParams>,
) -> Result<(), Error> {
self.send_foreground_job(result_sender, requests)
}
fn submit_background_job( fn submit_background_job(
&self, &self,
result_sender: ResultSender, result_sender: ResultSender,
@ -115,6 +123,14 @@ impl IMbJobSender for JobSender {
} }
impl JobSender { impl JobSender {
fn send_foreground_job(
&self,
result_sender: ResultSender,
requests: VecDeque<MbParams>,
) -> Result<(), Error> {
self.send_job(JobPriority::Foreground, result_sender, requests)
}
fn send_background_job( fn send_background_job(
&self, &self,
result_sender: ResultSender, result_sender: ResultSender,
@ -238,20 +254,25 @@ impl JobInstance {
event_sender: &mut dyn IFetchCompleteEventSender, event_sender: &mut dyn IFetchCompleteEventSender,
api_params: MbParams, api_params: MbParams,
) -> Result<(), JobInstanceError> { ) -> Result<(), JobInstanceError> {
match api_params { let result = match api_params {
MbParams::Search(search) => match search { MbParams::Lookup(lookup) => match lookup {
SearchParams::Artist(params) => { LookupParams::Artist(params) => musicbrainz
let result = musicbrainz.search_artist(&params.artist); .lookup_artist(&params.mbid)
let result = result.map(|list| MatchStateInfo::artist(params.artist, list)); .map(|rv| MatchStateInfo::artist_lookup(params.artist, rv)),
self.return_result(event_sender, result) LookupParams::ReleaseGroup(params) => musicbrainz
} .lookup_release_group(&params.mbid)
SearchParams::ReleaseGroup(params) => { .map(|rv| MatchStateInfo::album_lookup(params.album, rv)),
let result = musicbrainz.search_release_group(&params.arid, &params.album);
let result = result.map(|list| MatchStateInfo::album(params.album, list));
self.return_result(event_sender, result)
}
}, },
} MbParams::Search(search) => match search {
SearchParams::Artist(params) => musicbrainz
.search_artist(&params.artist)
.map(|rv| MatchStateInfo::artist_search(params.artist, rv)),
SearchParams::ReleaseGroup(params) => musicbrainz
.search_release_group(&params.arid, &params.album)
.map(|rv| MatchStateInfo::album_search(params.album, rv)),
},
};
self.return_result(event_sender, result)
} }
fn return_result( fn return_result(
@ -531,7 +552,7 @@ mod tests {
assert_eq!(result, Ok(())); assert_eq!(result, Ok(()));
let result = result_receiver.try_recv().unwrap(); let result = result_receiver.try_recv().unwrap();
assert_eq!(result, Ok(MatchStateInfo::artist(artist, matches))); assert_eq!(result, Ok(MatchStateInfo::artist_search(artist, matches)));
} }
fn search_release_group_expectation( fn search_release_group_expectation(
@ -582,10 +603,10 @@ mod tests {
assert_eq!(result, Ok(())); assert_eq!(result, Ok(()));
let result = result_receiver.try_recv().unwrap(); let result = result_receiver.try_recv().unwrap();
assert_eq!(result, Ok(MatchStateInfo::album(album_1, matches_1))); assert_eq!(result, Ok(MatchStateInfo::album_search(album_1, matches_1)));
let result = result_receiver.try_recv().unwrap(); let result = result_receiver.try_recv().unwrap();
assert_eq!(result, Ok(MatchStateInfo::album(album_4, matches_4))); assert_eq!(result, Ok(MatchStateInfo::album_search(album_4, matches_4)));
} }
#[test] #[test]

View File

@ -8,6 +8,8 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::
/// 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 lookup_artist(&mut self, mbid: &Mbid) -> Result<Lookup<ArtistMeta>, Error>;
fn lookup_release_group(&mut self, mbid: &Mbid) -> Result<Lookup<AlbumMeta>, Error>;
fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Match<ArtistMeta>>, Error>; fn search_artist(&mut self, artist: &ArtistMeta) -> Result<Vec<Match<ArtistMeta>>, Error>;
fn search_release_group( fn search_release_group(
&mut self, &mut self,
@ -23,4 +25,10 @@ pub struct Match<T> {
pub disambiguation: Option<String>, pub disambiguation: Option<String>,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Lookup<T> {
pub item: T,
pub disambiguation: Option<String>,
}
pub type Error = musichoard::external::musicbrainz::api::Error; pub type Error = musichoard::external::musicbrainz::api::Error;

View File

@ -27,6 +27,12 @@ pub type ResultSender = mpsc::Sender<MbApiResult>;
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
pub trait IMbJobSender { pub trait IMbJobSender {
fn submit_foreground_job(
&self,
result_sender: ResultSender,
requests: VecDeque<MbParams>,
) -> Result<(), Error>;
fn submit_background_job( fn submit_background_job(
&self, &self,
result_sender: ResultSender, result_sender: ResultSender,
@ -36,9 +42,28 @@ pub trait IMbJobSender {
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum MbParams { pub enum MbParams {
Lookup(LookupParams),
Search(SearchParams), Search(SearchParams),
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum LookupParams {
Artist(LookupArtistParams),
ReleaseGroup(LookupReleaseGroupParams),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LookupArtistParams {
pub artist: ArtistMeta,
pub mbid: Mbid,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LookupReleaseGroupParams {
pub album: AlbumMeta,
pub mbid: Mbid,
}
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum SearchParams { pub enum SearchParams {
Artist(SearchArtistParams), Artist(SearchArtistParams),
@ -57,6 +82,17 @@ pub struct SearchReleaseGroupParams {
} }
impl MbParams { impl MbParams {
pub fn lookup_artist(artist: ArtistMeta, mbid: Mbid) -> Self {
MbParams::Lookup(LookupParams::Artist(LookupArtistParams { artist, mbid }))
}
pub fn lookup_release_group(album: AlbumMeta, mbid: Mbid) -> Self {
MbParams::Lookup(LookupParams::ReleaseGroup(LookupReleaseGroupParams {
album,
mbid,
}))
}
pub fn search_artist(artist: ArtistMeta) -> Self { pub fn search_artist(artist: ArtistMeta) -> Self {
MbParams::Search(SearchParams::Artist(SearchArtistParams { artist })) MbParams::Search(SearchParams::Artist(SearchArtistParams { artist }))
} }

View File

@ -4,7 +4,7 @@ use musichoard::collection::{
track::{TrackFormat, TrackQuality}, track::{TrackFormat, TrackQuality},
}; };
use crate::tui::app::{MatchOption, MatchStateInfo}; use crate::tui::app::{LookupOption, MatchStateInfo, MissOption, SearchOption};
pub struct UiDisplay; pub struct UiDisplay;
@ -124,37 +124,69 @@ impl UiDisplay {
} }
} }
pub fn display_artist_match(match_option: &MatchOption<ArtistMeta>) -> String { pub fn display_search_option_artist(match_option: &SearchOption<ArtistMeta>) -> String {
match match_option { match match_option {
MatchOption::Match(match_artist) => format!( SearchOption::Match(match_artist) => format!(
"{}{} ({}%)", "{} ({}%)",
&match_artist.item.id.name, Self::display_option_artist(&match_artist.item, &match_artist.disambiguation),
&match_artist
.disambiguation
.as_ref()
.map(|d| format!(" ({d})"))
.unwrap_or_default(),
match_artist.score, match_artist.score,
), ),
MatchOption::CannotHaveMbid => Self::display_cannot_have_mbid().to_string(), SearchOption::None(miss) => Self::display_miss_option(miss).to_string(),
MatchOption::ManualInputMbid => Self::display_manual_input_mbid().to_string(),
} }
} }
pub fn display_album_match(match_option: &MatchOption<AlbumMeta>) -> String { pub fn display_lookup_option_artist(lookup_option: &LookupOption<ArtistMeta>) -> String {
match lookup_option {
LookupOption::Match(match_artist) => {
Self::display_option_artist(&match_artist.item, &match_artist.disambiguation)
}
LookupOption::None(miss) => Self::display_miss_option(miss).to_string(),
}
}
fn display_option_artist(artist: &ArtistMeta, disambiguation: &Option<String>) -> String {
format!(
"{}{}",
artist.id.name,
disambiguation
.as_ref()
.filter(|s| !s.is_empty())
.map(|d| format!(" ({d})"))
.unwrap_or_default(),
)
}
pub fn display_search_option_album(match_option: &SearchOption<AlbumMeta>) -> String {
match match_option { match match_option {
MatchOption::Match(match_album) => format!( SearchOption::Match(match_album) => format!(
"{:010} | {} [{}] ({}%)", "{} ({}%)",
UiDisplay::display_album_date(&match_album.item.date), Self::display_option_album(&match_album.item),
&match_album.item.id.title,
UiDisplay::display_type(
&match_album.item.primary_type,
&match_album.item.secondary_types
),
match_album.score, match_album.score,
), ),
MatchOption::CannotHaveMbid => Self::display_cannot_have_mbid().to_string(), SearchOption::None(miss) => Self::display_miss_option(miss).to_string(),
MatchOption::ManualInputMbid => Self::display_manual_input_mbid().to_string(), }
}
pub fn display_lookup_option_album(lookup_option: &LookupOption<AlbumMeta>) -> String {
match lookup_option {
LookupOption::Match(match_album) => Self::display_option_album(&match_album.item),
LookupOption::None(miss) => Self::display_miss_option(miss).to_string(),
}
}
fn display_option_album(album: &AlbumMeta) -> String {
format!(
"{:010} | {} [{}]",
UiDisplay::display_album_date(&album.date),
album.id.title,
UiDisplay::display_type(&album.primary_type, &album.secondary_types),
)
}
fn display_miss_option(miss_option: &MissOption) -> &'static str {
match miss_option {
MissOption::CannotHaveMbid => Self::display_cannot_have_mbid(),
MissOption::ManualInputMbid => Self::display_manual_input_mbid(),
} }
} }

View File

@ -2,7 +2,7 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta};
use ratatui::widgets::{List, ListItem}; use ratatui::widgets::{List, ListItem};
use crate::tui::{ use crate::tui::{
app::{MatchOption, MatchStateInfo, WidgetState}, app::{ListOption, MatchStateInfo, WidgetState},
ui::display::UiDisplay, ui::display::UiDisplay,
}; };
@ -13,7 +13,7 @@ pub struct MatchOverlay<'a, 'b> {
} }
impl<'a, 'b> MatchOverlay<'a, 'b> { impl<'a, 'b> MatchOverlay<'a, 'b> {
pub fn new(info: Option<&MatchStateInfo>, state: &'b mut WidgetState) -> Self { pub fn new(info: Option<&'a MatchStateInfo>, state: &'b mut WidgetState) -> Self {
match info { match info {
Some(info) => match info { Some(info) => match info {
MatchStateInfo::Artist(m) => Self::artists(&m.matching, &m.list, state), MatchStateInfo::Artist(m) => Self::artists(&m.matching, &m.list, state),
@ -33,18 +33,19 @@ impl<'a, 'b> MatchOverlay<'a, 'b> {
fn artists( fn artists(
matching: &ArtistMeta, matching: &ArtistMeta,
matches: &[MatchOption<ArtistMeta>], matches: &'a ListOption<ArtistMeta>,
state: &'b mut WidgetState, state: &'b mut WidgetState,
) -> Self { ) -> Self {
let matching = UiDisplay::display_artist_matching(matching); let matching = UiDisplay::display_artist_matching(matching);
let list = List::new( let list = match matches {
matches ListOption::Search(matches) => {
.iter() Self::display_list(UiDisplay::display_search_option_artist, matches)
.map(UiDisplay::display_artist_match) }
.map(ListItem::new) ListOption::Lookup(matches) => {
.collect::<Vec<ListItem>>(), Self::display_list(UiDisplay::display_lookup_option_artist, matches)
); }
};
MatchOverlay { MatchOverlay {
matching, matching,
@ -55,18 +56,19 @@ impl<'a, 'b> MatchOverlay<'a, 'b> {
fn albums( fn albums(
matching: &AlbumMeta, matching: &AlbumMeta,
matches: &[MatchOption<AlbumMeta>], matches: &'a ListOption<AlbumMeta>,
state: &'b mut WidgetState, state: &'b mut WidgetState,
) -> Self { ) -> Self {
let matching = UiDisplay::display_album_matching(matching); let matching = UiDisplay::display_album_matching(matching);
let list = List::new( let list = match matches {
matches ListOption::Search(matches) => {
.iter() Self::display_list(UiDisplay::display_search_option_album, matches)
.map(UiDisplay::display_album_match) }
.map(ListItem::new) ListOption::Lookup(matches) => {
.collect::<Vec<ListItem>>(), Self::display_list(UiDisplay::display_lookup_option_album, matches)
); }
};
MatchOverlay { MatchOverlay {
matching, matching,
@ -74,4 +76,17 @@ impl<'a, 'b> MatchOverlay<'a, 'b> {
state, state,
} }
} }
fn display_list<F, T>(display: F, options: &[T]) -> List
where
F: FnMut(&T) -> String,
{
List::new(
options
.iter()
.map(display)
.map(ListItem::new)
.collect::<Vec<ListItem>>(),
)
}
} }

View File

@ -206,7 +206,7 @@ mod tests {
}; };
use crate::tui::{ use crate::tui::{
app::{AppPublic, AppPublicInner, Delta, MatchOption, MatchStatePublic}, app::{AppPublic, AppPublicInner, Delta, MatchStatePublic, MissOption, SearchOption},
lib::interface::musicbrainz::api::Match, lib::interface::musicbrainz::api::Match,
testmod::COLLECTION, testmod::COLLECTION,
tests::terminal, tests::terminal,
@ -251,17 +251,17 @@ mod tests {
} }
fn artist_matches(matching: ArtistMeta, list: Vec<Match<ArtistMeta>>) -> MatchStateInfo { fn artist_matches(matching: ArtistMeta, list: Vec<Match<ArtistMeta>>) -> MatchStateInfo {
let mut list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect(); let mut list: Vec<SearchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
list.push(MatchOption::CannotHaveMbid); list.push(SearchOption::None(MissOption::CannotHaveMbid));
list.push(MatchOption::ManualInputMbid); list.push(SearchOption::None(MissOption::ManualInputMbid));
MatchStateInfo::artist(matching, list) MatchStateInfo::artist_search(matching, list)
} }
fn album_matches(matching: AlbumMeta, list: Vec<Match<AlbumMeta>>) -> MatchStateInfo { fn album_matches(matching: AlbumMeta, list: Vec<Match<AlbumMeta>>) -> MatchStateInfo {
let mut list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect(); let mut list: Vec<SearchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
list.push(MatchOption::CannotHaveMbid); list.push(SearchOption::None(MissOption::CannotHaveMbid));
list.push(MatchOption::ManualInputMbid); list.push(SearchOption::None(MissOption::ManualInputMbid));
MatchStateInfo::album(matching, list) MatchStateInfo::album_search(matching, list)
} }
fn draw_test_suite(collection: &Collection, selection: &mut Selection) { fn draw_test_suite(collection: &Collection, selection: &mut Selection) {