Implement cannot-have-mbid option
All checks were successful
Cargo CI / Build and Test (pull_request) Successful in 2m3s
Cargo CI / Lint (pull_request) Successful in 1m9s

This commit is contained in:
Wojciech Kozlowski 2024-08-31 16:25:29 +02:00
parent 006a981347
commit 70db1bd48d
6 changed files with 114 additions and 63 deletions

View File

@ -140,7 +140,7 @@ mod tests {
app::{ app::{
machine::tests::{inner, inner_with_mb, music_hoard}, machine::tests::{inner, inner_with_mb, music_hoard},
AppAlbumMatches, AppArtistMatches, AppMatchesInfo, Category, IAppAccess, IAppInteract, AppAlbumMatches, AppArtistMatches, AppMatchesInfo, Category, IAppAccess, IAppInteract,
IAppInteractMatches, IAppInteractMatches, MatchOption,
}, },
lib::interface::musicbrainz::{self, Match, MockIMusicBrainz}, lib::interface::musicbrainz::{self, Match, MockIMusicBrainz},
testmod::COLLECTION, testmod::COLLECTION,
@ -263,6 +263,9 @@ mod tests {
let public_matches = public.state.unwrap_matches(); let public_matches = public.state.unwrap_matches();
let mut matches_1: Vec<MatchOption<Album>> =
matches_1.into_iter().map(Into::into).collect();
matches_1.push(MatchOption::CannotHaveMbid);
let expected = Some(AppMatchesInfo::Album(AppAlbumMatches { let expected = Some(AppMatchesInfo::Album(AppAlbumMatches {
matching: album_1.clone(), matching: album_1.clone(),
list: matches_1.clone(), list: matches_1.clone(),
@ -276,6 +279,9 @@ mod tests {
let public_matches = public.state.unwrap_matches(); let public_matches = public.state.unwrap_matches();
let mut matches_4: Vec<MatchOption<Album>> =
matches_4.into_iter().map(Into::into).collect();
matches_4.push(MatchOption::CannotHaveMbid);
let expected = Some(AppMatchesInfo::Album(AppAlbumMatches { let expected = Some(AppMatchesInfo::Album(AppAlbumMatches {
matching: album_4.clone(), matching: album_4.clone(),
list: matches_4.clone(), list: matches_4.clone(),
@ -324,6 +330,8 @@ mod tests {
let public_matches = public.state.unwrap_matches(); let public_matches = public.state.unwrap_matches();
let mut matches: Vec<MatchOption<Artist>> = matches.into_iter().map(Into::into).collect();
matches.push(MatchOption::CannotHaveMbid);
let expected = Some(AppMatchesInfo::Artist(AppArtistMatches { let expected = Some(AppMatchesInfo::Artist(AppArtistMatches {
matching: artist.clone(), matching: artist.clone(),
list: matches.clone(), list: matches.clone(),

View File

@ -3,43 +3,43 @@ use std::{cmp, sync::mpsc};
use crate::tui::app::{ use crate::tui::app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
AppAlbumMatches, AppArtistMatches, AppMatchesInfo, AppPublic, AppPublicMatches, AppState, AppAlbumMatches, AppArtistMatches, AppMatchesInfo, AppPublic, AppPublicMatches, AppState,
IAppInteractMatches, WidgetState, IAppInteractMatches, MatchOption, WidgetState,
}; };
impl AppArtistMatches { impl AppArtistMatches {
fn is_empty(&self) -> bool {
self.list.is_empty()
}
fn len(&self) -> usize { fn len(&self) -> usize {
self.list.len() self.list.len()
} }
fn push_cannot_have_mbid(&mut self) {
self.list.push(MatchOption::CannotHaveMbid)
}
} }
impl AppAlbumMatches { impl AppAlbumMatches {
fn is_empty(&self) -> bool {
self.list.is_empty()
}
fn len(&self) -> usize { fn len(&self) -> usize {
self.list.len() self.list.len()
} }
fn push_cannot_have_mbid(&mut self) {
self.list.push(MatchOption::CannotHaveMbid)
}
} }
impl AppMatchesInfo { impl AppMatchesInfo {
fn is_empty(&self) -> bool {
match self {
Self::Artist(a) => a.is_empty(),
Self::Album(a) => a.is_empty(),
}
}
fn len(&self) -> usize { fn len(&self) -> usize {
match self { match self {
Self::Artist(a) => a.len(), Self::Artist(a) => a.len(),
Self::Album(a) => a.len(), Self::Album(a) => a.len(),
} }
} }
fn push_cannot_have_mbid(&mut self) {
match self {
Self::Artist(a) => a.push_cannot_have_mbid(),
Self::Album(a) => a.push_cannot_have_mbid(),
}
}
} }
pub struct AppMatches { pub struct AppMatches {
@ -107,13 +107,7 @@ impl IAppInteractMatches for AppMachine<AppMatches> {
fn select(mut self) -> Self::APP { fn select(mut self) -> Self::APP {
self.state.next_matches_info(); self.state.next_matches_info();
match self.state.current { match self.state.current {
Some(ref matches_info) => { Some(_) => self.into(),
self.state.state = WidgetState::default();
if !matches_info.is_empty() {
self.state.state.list.select(Some(0));
}
self.into()
}
None => AppMachine::browse(self.inner).into(), None => AppMachine::browse(self.inner).into(),
} }
} }
@ -135,11 +129,10 @@ impl IAppInteractMatchesPrivate for AppMatches {
fn next_matches_info(&mut self) { fn next_matches_info(&mut self) {
// FIXME: try_recv might not be appropriate for asynchronous version. // FIXME: try_recv might not be appropriate for asynchronous version.
(self.current, self.state) = match self.matches_rx.try_recv() { (self.current, self.state) = match self.matches_rx.try_recv() {
Ok(next_match) => { Ok(mut next_match) => {
next_match.push_cannot_have_mbid();
let mut state = WidgetState::default(); let mut state = WidgetState::default();
if !next_match.is_empty() { state.list.select(Some(0));
state.list.select(Some(0));
}
(Some(next_match), state) (Some(next_match), state)
} }
Err(_) => (None, WidgetState::default()), Err(_) => (None, WidgetState::default()),
@ -232,6 +225,12 @@ mod tests {
rx rx
} }
fn push_cannot_have_mbid(matches_info_vec: &mut [AppMatchesInfo]) {
for matches_info in matches_info_vec.iter_mut() {
matches_info.push_cannot_have_mbid();
}
}
#[test] #[test]
fn create_empty() { fn create_empty() {
let matches = AppMachine::matches(inner(music_hoard(vec![])), receiver(vec![])); let matches = AppMachine::matches(inner(music_hoard(vec![])), receiver(vec![]));
@ -251,11 +250,12 @@ mod tests {
#[test] #[test]
fn create_nonempty() { fn create_nonempty() {
let matches_info_vec = album_matches_info_vec(); let mut matches_info_vec = album_matches_info_vec();
let matches = AppMachine::matches( let matches = AppMachine::matches(
inner(music_hoard(vec![])), inner(music_hoard(vec![])),
receiver(matches_info_vec.clone()), receiver(matches_info_vec.clone()),
); );
push_cannot_have_mbid(&mut matches_info_vec);
let mut widget_state = WidgetState::default(); let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0)); widget_state.list.select(Some(0));
@ -271,11 +271,12 @@ mod tests {
assert_eq!(public_matches.state, &widget_state); assert_eq!(public_matches.state, &widget_state);
} }
fn matches_flow(matches_info_vec: Vec<AppMatchesInfo>) { fn matches_flow(mut matches_info_vec: Vec<AppMatchesInfo>) {
let matches = AppMachine::matches( let matches = AppMachine::matches(
inner(music_hoard(vec![])), inner(music_hoard(vec![])),
receiver(matches_info_vec.clone()), receiver(matches_info_vec.clone()),
); );
push_cannot_have_mbid(&mut matches_info_vec);
let mut widget_state = WidgetState::default(); let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0)); widget_state.list.select(Some(0));
@ -293,10 +294,16 @@ mod tests {
assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0]));
assert_eq!(matches.state.state.list.selected(), Some(1)); assert_eq!(matches.state.state.list.selected(), Some(1));
// Next is CannotHaveMBID
let matches = matches.next_match().unwrap_matches(); let matches = matches.next_match().unwrap_matches();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0])); assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0]));
assert_eq!(matches.state.state.list.selected(), Some(1)); assert_eq!(matches.state.state.list.selected(), Some(2));
let matches = matches.next_match().unwrap_matches();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info_vec[0]));
assert_eq!(matches.state.state.list.selected(), Some(2));
let matches = matches.select().unwrap_matches(); let matches = matches.select().unwrap_matches();
@ -319,11 +326,12 @@ mod tests {
#[test] #[test]
fn matches_abort() { fn matches_abort() {
let matches_info_vec = album_matches_info_vec(); let mut matches_info_vec = album_matches_info_vec();
let matches = AppMachine::matches( let matches = AppMachine::matches(
inner(music_hoard(vec![])), inner(music_hoard(vec![])),
receiver(matches_info_vec.clone()), receiver(matches_info_vec.clone()),
); );
push_cannot_have_mbid(&mut matches_info_vec);
let mut widget_state = WidgetState::default(); let mut widget_state = WidgetState::default();
widget_state.list.select(Some(0)); widget_state.list.select(Some(0));

View File

@ -129,16 +129,28 @@ pub struct AppPublicInner<'app> {
pub selection: &'app mut Selection, pub selection: &'app mut Selection,
} }
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MatchOption<T> {
Match(Match<T>),
CannotHaveMbid,
}
impl<T> From<Match<T>> for MatchOption<T> {
fn from(value: Match<T>) -> Self {
MatchOption::Match(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct AppArtistMatches { pub struct AppArtistMatches {
pub matching: Artist, pub matching: Artist,
pub list: Vec<Match<Artist>>, pub list: Vec<MatchOption<Artist>>,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct AppAlbumMatches { pub struct AppAlbumMatches {
pub matching: Album, pub matching: Album,
pub list: Vec<Match<Album>>, pub list: Vec<MatchOption<Album>>,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
@ -148,11 +160,13 @@ pub enum AppMatchesInfo {
} }
impl AppMatchesInfo { impl AppMatchesInfo {
pub fn artist(matching: Artist, list: Vec<Match<Artist>>) -> Self { pub fn artist<M: Into<MatchOption<Artist>>>(matching: Artist, list: Vec<M>) -> Self {
let list: Vec<MatchOption<Artist>> = list.into_iter().map(Into::into).collect();
AppMatchesInfo::Artist(AppArtistMatches { matching, list }) AppMatchesInfo::Artist(AppArtistMatches { matching, list })
} }
pub fn album(matching: Album, list: Vec<Match<Album>>) -> Self { pub fn album<M: Into<MatchOption<Album>>>(matching: Album, list: Vec<M>) -> Self {
let list: Vec<MatchOption<Album>> = list.into_iter().map(Into::into).collect();
AppMatchesInfo::Album(AppAlbumMatches { matching, list }) AppMatchesInfo::Album(AppAlbumMatches { matching, list })
} }
} }

View File

@ -4,7 +4,7 @@ use musichoard::collection::{
track::{TrackFormat, TrackQuality}, track::{TrackFormat, TrackQuality},
}; };
use crate::tui::{app::AppMatchesInfo, lib::interface::musicbrainz::Match}; use crate::tui::app::{AppMatchesInfo, MatchOption};
pub struct UiDisplay; pub struct UiDisplay;
@ -124,30 +124,40 @@ impl UiDisplay {
} }
} }
pub fn display_artist_match(match_artist: &Match<Artist>) -> String { pub fn display_artist_match(match_option: &MatchOption<Artist>) -> String {
format!( match match_option {
"{}{} ({}%)", MatchOption::Match(match_artist) => format!(
&match_artist.item.id.name, "{}{} ({}%)",
&match_artist &match_artist.item.id.name,
.disambiguation &match_artist
.as_ref() .disambiguation
.map(|d| format!(" ({d})")) .as_ref()
.unwrap_or_default(), .map(|d| format!(" ({d})"))
match_artist.score, .unwrap_or_default(),
) match_artist.score,
),
MatchOption::CannotHaveMbid => Self::display_cannot_have_mbid().to_string(),
}
} }
pub fn display_album_match(match_album: &Match<Album>) -> String { pub fn display_album_match(match_option: &MatchOption<Album>) -> String {
format!( match match_option {
"{:010} | {} [{}] ({}%)", MatchOption::Match(match_album) => format!(
UiDisplay::display_album_date(&match_album.item.date), "{:010} | {} [{}] ({}%)",
&match_album.item.id.title, UiDisplay::display_album_date(&match_album.item.date),
UiDisplay::display_type( &match_album.item.id.title,
&match_album.item.primary_type, UiDisplay::display_type(
&match_album.item.secondary_types &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(),
) }
}
fn display_cannot_have_mbid() -> &'static str {
"-- Cannot have a MusicBrainz Identifier --"
} }
} }

View File

@ -2,8 +2,7 @@ use musichoard::collection::{album::Album, artist::Artist};
use ratatui::widgets::{List, ListItem}; use ratatui::widgets::{List, ListItem};
use crate::tui::{ use crate::tui::{
app::{AppMatchesInfo, WidgetState}, app::{AppMatchesInfo, MatchOption, WidgetState},
lib::interface::musicbrainz::Match,
ui::display::UiDisplay, ui::display::UiDisplay,
}; };
@ -32,7 +31,11 @@ impl<'a, 'b> MatchesState<'a, 'b> {
} }
} }
fn artists(matching: &Artist, matches: &[Match<Artist>], state: &'b mut WidgetState) -> Self { fn artists(
matching: &Artist,
matches: &[MatchOption<Artist>],
state: &'b mut WidgetState,
) -> Self {
let matching = UiDisplay::display_artist_matching(matching); let matching = UiDisplay::display_artist_matching(matching);
let list = List::new( let list = List::new(
@ -50,7 +53,11 @@ impl<'a, 'b> MatchesState<'a, 'b> {
} }
} }
fn albums(matching: &Album, matches: &[Match<Album>], state: &'b mut WidgetState) -> Self { fn albums(
matching: &Album,
matches: &[MatchOption<Album>],
state: &'b mut WidgetState,
) -> Self {
let matching = UiDisplay::display_album_matching(matching); let matching = UiDisplay::display_album_matching(matching);
let list = List::new( let list = List::new(

View File

@ -182,7 +182,7 @@ mod tests {
}; };
use crate::tui::{ use crate::tui::{
app::{AppPublic, AppPublicInner, AppPublicMatches, Delta}, app::{AppPublic, AppPublicInner, AppPublicMatches, Delta, MatchOption},
lib::interface::musicbrainz::Match, lib::interface::musicbrainz::Match,
testmod::COLLECTION, testmod::COLLECTION,
tests::terminal, tests::terminal,
@ -225,10 +225,14 @@ mod tests {
} }
fn artist_matches(matching: Artist, list: Vec<Match<Artist>>) -> AppMatchesInfo { fn artist_matches(matching: Artist, list: Vec<Match<Artist>>) -> AppMatchesInfo {
let mut list: Vec<MatchOption<Artist>> = list.into_iter().map(Into::into).collect();
list.push(MatchOption::CannotHaveMbid);
AppMatchesInfo::artist(matching, list) AppMatchesInfo::artist(matching, list)
} }
fn album_matches(matching: Album, list: Vec<Match<Album>>) -> AppMatchesInfo { fn album_matches(matching: Album, list: Vec<Match<Album>>) -> AppMatchesInfo {
let mut list: Vec<MatchOption<Album>> = list.into_iter().map(Into::into).collect();
list.push(MatchOption::CannotHaveMbid);
AppMatchesInfo::album(matching, list) AppMatchesInfo::album(matching, list)
} }