Provide a keyboard shortcut to sync all existing albums with MusicBrainz #167

Merged
wojtek merged 13 commits from 166---provide-a-keyboard-shortcut-to-sync-all-existing-albums-with-musicbrainz into main 2024-08-27 17:55:53 +02:00
4 changed files with 46 additions and 15 deletions
Showing only changes of commit 0fe8504b8c - Show all commits

View File

@ -8,7 +8,7 @@ use musichoard::{
use crate::tui::{ use crate::tui::{
app::{ app::{
machine::{App, AppInner, AppMachine}, machine::{matches::AppMatchInfo, App, AppInner, AppMachine},
selection::{Delta, ListSelection}, selection::{Delta, ListSelection},
AppPublic, AppState, IAppInteractBrowse, AppPublic, AppState, IAppInteractBrowse,
}, },
@ -119,13 +119,23 @@ impl<MH: IMusicHoard> IAppInteractBrowse for AppMachine<MH, AppBrowse> {
} }
let mut artist_album_matches = vec![]; let mut artist_album_matches = vec![];
for album in artist.albums.iter() { let mut album_iter = artist.albums.iter().peekable();
while let Some(album) = album_iter.next() {
if album.musicbrainz.is_some() {
continue;
}
match api.search_release_group(arid, album) { match api.search_release_group(arid, album) {
Ok(matches) => artist_album_matches.push(matches), Ok(matches) => artist_album_matches.push(AppMatchInfo {
matching: album.clone(),
matches,
}),
Err(err) => return AppMachine::error(self.inner, err.to_string()).into(), Err(err) => return AppMachine::error(self.inner, err.to_string()).into(),
} }
thread::sleep(time::Duration::from_secs(1)); if album_iter.peek().is_some() {
thread::sleep(time::Duration::from_secs(1));
}
} }
AppMachine::matches(self.inner, artist_album_matches).into() AppMachine::matches(self.inner, artist_album_matches).into()

View File

@ -10,18 +10,23 @@ use crate::tui::{
lib::IMusicHoard, lib::IMusicHoard,
}; };
pub struct AppMatchInfo {
pub matching: Album,
pub matches: Vec<Match<Album>>,
}
pub struct AppMatches { pub struct AppMatches {
matches: Vec<Vec<Match<Album>>>, matches: Vec<AppMatchInfo>,
index: usize, index: usize,
state: WidgetState, state: WidgetState,
} }
impl<MH: IMusicHoard> AppMachine<MH, AppMatches> { impl<MH: IMusicHoard> AppMachine<MH, AppMatches> {
pub fn matches(inner: AppInner<MH>, matches: Vec<Vec<Match<Album>>>) -> Self { pub fn matches(inner: AppInner<MH>, matches: Vec<AppMatchInfo>) -> Self {
assert!(!matches.is_empty()); assert!(!matches.is_empty());
let mut state = WidgetState::default(); let mut state = WidgetState::default();
if !matches[0].is_empty() { if !matches[0].matches.is_empty() {
state.list.select(Some(0)); state.list.select(Some(0));
} }
@ -47,7 +52,8 @@ impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppMatches>> for AppPublic
AppPublic { AppPublic {
inner: (&mut machine.inner).into(), inner: (&mut machine.inner).into(),
state: AppState::Matches(AppPublicMatches { state: AppState::Matches(AppPublicMatches {
matches: &machine.state.matches[machine.state.index], matching: &machine.state.matches[machine.state.index].matching,
matches: &machine.state.matches[machine.state.index].matches,
state: &mut machine.state.state, state: &mut machine.state.state,
}), }),
} }
@ -69,7 +75,10 @@ impl<MH: IMusicHoard> IAppInteractMatches for AppMachine<MH, AppMatches> {
fn next_match(mut self) -> Self::APP { fn next_match(mut self) -> Self::APP {
if let Some(index) = self.state.state.list.selected() { if let Some(index) = self.state.state.list.selected() {
let result = index.saturating_add(1); let result = index.saturating_add(1);
let to = cmp::min(result, self.state.matches[self.state.index].len() - 1); let to = cmp::min(
result,
self.state.matches[self.state.index].matches.len() - 1,
);
self.state.state.list.select(Some(to)); self.state.state.list.select(Some(to));
} }
@ -80,7 +89,7 @@ impl<MH: IMusicHoard> IAppInteractMatches for AppMachine<MH, AppMatches> {
self.state.index = self.state.index.saturating_add(1); self.state.index = self.state.index.saturating_add(1);
if self.state.index < self.state.matches.len() { if self.state.index < self.state.matches.len() {
self.state.state = WidgetState::default(); self.state.state = WidgetState::default();
if !self.state.matches[self.state.index].is_empty() { if !self.state.matches[self.state.index].matches.is_empty() {
self.state.state.list.select(Some(0)); self.state.state.list.select(Some(0));
} }
self.into() self.into()

View File

@ -131,6 +131,7 @@ pub struct AppPublicInner<'app> {
} }
pub struct AppPublicMatches<'app> { pub struct AppPublicMatches<'app> {
pub matching: &'app Album,
pub matches: &'app [Match<Album>], pub matches: &'app [Match<Album>],
pub state: &'app mut WidgetState, pub state: &'app mut WidgetState,
} }

View File

@ -529,12 +529,16 @@ impl Minibuffer<'_> {
], ],
columns, columns,
}, },
AppState::Matches(_) => Minibuffer { AppState::Matches(public) => Minibuffer {
paragraphs: vec![ paragraphs: vec![
Paragraph::new("enter: apply highlighted"), Paragraph::new(format!(
"Matching: {} | {}",
AlbumState::display_album_date(&public.matching.date),
&public.matching.id.title
)),
Paragraph::new("q: abort"), Paragraph::new("q: abort"),
], ],
columns, columns: 2,
}, },
AppState::Error(_) => Minibuffer { AppState::Error(_) => Minibuffer {
paragraphs: vec![Paragraph::new( paragraphs: vec![Paragraph::new(
@ -816,6 +820,7 @@ impl Ui {
} }
fn render_matches_overlay( fn render_matches_overlay(
matching: &Album,
matches: &[Match<Album>], matches: &[Match<Album>],
state: &mut WidgetState, state: &mut WidgetState,
frame: &mut Frame, frame: &mut Frame,
@ -837,7 +842,12 @@ impl Ui {
.collect::<Vec<ListItem>>(), .collect::<Vec<ListItem>>(),
); );
Self::render_overlay_list_widget("Matches", list, state, true, area, frame) let matching_string = format!(
"Matching: {} | {}",
AlbumState::display_album_date(&matching.date),
&matching.id.title
);
Self::render_overlay_list_widget(&matching_string, list, state, true, area, frame)
} }
fn render_reload_overlay(frame: &mut Frame) { fn render_reload_overlay(frame: &mut Frame) {
@ -876,7 +886,7 @@ impl IUi for Ui {
match state { match state {
AppState::Info(_) => Self::render_info_overlay(collection, selection, frame), AppState::Info(_) => Self::render_info_overlay(collection, selection, frame),
AppState::Matches(public) => { AppState::Matches(public) => {
Self::render_matches_overlay(public.matches, public.state, frame) Self::render_matches_overlay(public.matching, public.matches, public.state, frame)
} }
AppState::Reload(_) => Self::render_reload_overlay(frame), AppState::Reload(_) => Self::render_reload_overlay(frame),
AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame), AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame),
@ -912,6 +922,7 @@ mod tests {
AppState::Reload(()) => AppState::Reload(()), AppState::Reload(()) => AppState::Reload(()),
AppState::Search(s) => AppState::Search(s), AppState::Search(s) => AppState::Search(s),
AppState::Matches(ref mut m) => AppState::Matches(AppPublicMatches { AppState::Matches(ref mut m) => AppState::Matches(AppPublicMatches {
matching: m.matching,
matches: &m.matches, matches: &m.matches,
state: &mut m.state, state: &mut m.state,
}), }),