Handle idle time between fetch results #212
@ -1,26 +1,9 @@
|
||||
use std::{
|
||||
sync::{mpsc, Arc, Mutex},
|
||||
thread, time,
|
||||
};
|
||||
|
||||
use musichoard::collection::{
|
||||
album::AlbumMeta,
|
||||
artist::{Artist, ArtistMeta},
|
||||
musicbrainz::{IMusicBrainzRef, Mbid},
|
||||
};
|
||||
|
||||
use crate::tui::{
|
||||
app::{
|
||||
use crate::tui::app::{
|
||||
machine::{App, AppInner, AppMachine},
|
||||
selection::{Delta, ListSelection},
|
||||
AppMatchesInfo, AppPublic, AppState, IAppInteractBrowse,
|
||||
},
|
||||
event::{Event, EventSender},
|
||||
lib::interface::musicbrainz::{self, IMusicBrainz},
|
||||
AppPublic, AppState, IAppInteractBrowse,
|
||||
};
|
||||
|
||||
use super::fetch::{FetchResult, FetchSender};
|
||||
|
||||
pub struct AppBrowse;
|
||||
|
||||
impl AppMachine<AppBrowse> {
|
||||
@ -30,80 +13,6 @@ impl AppMachine<AppBrowse> {
|
||||
state: AppBrowse,
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_fetch_thread(
|
||||
inner: &AppInner,
|
||||
artist: &Artist,
|
||||
tx: FetchSender,
|
||||
) -> thread::JoinHandle<()> {
|
||||
match artist.meta.musicbrainz {
|
||||
Some(ref arid) => {
|
||||
let musicbrainz = Arc::clone(&inner.musicbrainz);
|
||||
let events = inner.events.clone();
|
||||
let arid = arid.mbid().clone();
|
||||
let albums = artist.albums.iter().map(|a| &a.meta).cloned().collect();
|
||||
thread::spawn(|| Self::fetch_albums(musicbrainz, tx, events, arid, albums))
|
||||
}
|
||||
None => {
|
||||
let musicbrainz = Arc::clone(&inner.musicbrainz);
|
||||
let events = inner.events.clone();
|
||||
let artist = artist.meta.clone();
|
||||
thread::spawn(|| Self::fetch_artist(musicbrainz, tx, events, artist))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_artist(
|
||||
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
||||
fetch_tx: FetchSender,
|
||||
events: EventSender,
|
||||
artist: ArtistMeta,
|
||||
) {
|
||||
let result = musicbrainz.lock().unwrap().search_artist(&artist);
|
||||
let result = result.map(|list| AppMatchesInfo::artist(artist, list));
|
||||
Self::send_fetch_result(&fetch_tx, &events, result).ok();
|
||||
}
|
||||
|
||||
fn fetch_albums(
|
||||
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
||||
fetch_tx: FetchSender,
|
||||
events: EventSender,
|
||||
arid: Mbid,
|
||||
albums: Vec<AlbumMeta>,
|
||||
) {
|
||||
let mut musicbrainz = musicbrainz.lock().unwrap();
|
||||
let mut album_iter = albums.into_iter().peekable();
|
||||
while let Some(album) = album_iter.next() {
|
||||
if album.musicbrainz.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let result = musicbrainz.search_release_group(&arid, &album);
|
||||
let result = result.map(|list| AppMatchesInfo::album(album, list));
|
||||
if Self::send_fetch_result(&fetch_tx, &events, result).is_err() {
|
||||
return;
|
||||
};
|
||||
|
||||
if album_iter.peek().is_some() {
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_fetch_result(
|
||||
fetch_tx: &FetchSender,
|
||||
events: &EventSender,
|
||||
result: Result<AppMatchesInfo, musicbrainz::Error>,
|
||||
) -> Result<(), ()> {
|
||||
// If receiver disconnects just drop the rest.
|
||||
fetch_tx.send(result).map_err(|_| ())?;
|
||||
|
||||
// If this send fails the event listener is dead. Don't panic as this function runs in a
|
||||
// detached thread so this might be happening during normal shut down.
|
||||
events.send(Event::FetchResultReady).map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AppMachine<AppBrowse>> for App {
|
||||
@ -170,35 +79,19 @@ impl IAppInteractBrowse for AppMachine<AppBrowse> {
|
||||
}
|
||||
|
||||
fn fetch_musicbrainz(self) -> Self::APP {
|
||||
let coll = self.inner.music_hoard.get_collection();
|
||||
let artist = match self.inner.selection.state_artist(coll) {
|
||||
Some(artist_state) => &coll[artist_state.index],
|
||||
None => {
|
||||
return AppMachine::error(self.inner, "cannot fetch: no artist selected").into()
|
||||
}
|
||||
};
|
||||
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel::<FetchResult>();
|
||||
Self::spawn_fetch_thread(&self.inner, artist, fetch_tx);
|
||||
|
||||
AppMachine::app_fetch_new(self.inner, fetch_rx)
|
||||
AppMachine::app_fetch_new(self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mockall::{predicate, Sequence};
|
||||
use mpsc::TryRecvError;
|
||||
|
||||
use crate::tui::{
|
||||
app::{
|
||||
machine::tests::{inner, inner_with_mb, music_hoard},
|
||||
AppAlbumMatches, AppArtistMatches, Category, IAppAccess, IAppInteract,
|
||||
Category, IAppAccess, IAppInteract,
|
||||
},
|
||||
event::EventReceiver,
|
||||
lib::interface::musicbrainz::{Match, MockIMusicBrainz},
|
||||
lib::interface::musicbrainz::MockIMusicBrainz,
|
||||
testmod::COLLECTION,
|
||||
EventChannel,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
@ -291,195 +184,10 @@ mod tests {
|
||||
|
||||
let public = app.get();
|
||||
|
||||
// Because of threaded behaviour, this unit test cannot expect one or the other.
|
||||
// Because of fetch's threaded behaviour, this unit test cannot expect one or the other.
|
||||
assert!(
|
||||
matches!(public.state, AppState::Matches(_))
|
||||
|| matches!(public.state, AppState::Fetch(_))
|
||||
);
|
||||
}
|
||||
|
||||
fn event_channel() -> (EventSender, EventReceiver) {
|
||||
let event_channel = EventChannel::new();
|
||||
let events_tx = event_channel.sender();
|
||||
let events_rx = event_channel.receiver();
|
||||
(events_tx, events_rx)
|
||||
}
|
||||
|
||||
fn album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
|
||||
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
let album_match_1_1 = Match::new(100, album_1.clone());
|
||||
let album_match_1_2 = Match::new(50, album_4.clone());
|
||||
let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()];
|
||||
|
||||
(album_1, matches_1)
|
||||
}
|
||||
|
||||
fn album_expectations_4() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
|
||||
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
let album_match_4_1 = Match::new(100, album_4.clone());
|
||||
let album_match_4_2 = Match::new(30, album_1.clone());
|
||||
let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()];
|
||||
|
||||
(album_4, matches_4)
|
||||
}
|
||||
|
||||
fn search_release_group_expectation(
|
||||
api: &mut MockIMusicBrainz,
|
||||
seq: &mut Sequence,
|
||||
arid: &Mbid,
|
||||
album: &AlbumMeta,
|
||||
matches: &[Match<AlbumMeta>],
|
||||
) {
|
||||
let result = Ok(matches.to_owned());
|
||||
api.expect_search_release_group()
|
||||
.with(predicate::eq(arid.clone()), predicate::eq(album.clone()))
|
||||
.times(1)
|
||||
.in_sequence(seq)
|
||||
.return_once(|_, _| result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_albums() {
|
||||
let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
||||
|
||||
let (album_1, matches_1) = album_expectations_1();
|
||||
let (album_4, matches_4) = album_expectations_4();
|
||||
|
||||
// Other albums have an MBID and so they will be skipped.
|
||||
let mut seq = Sequence::new();
|
||||
|
||||
search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1);
|
||||
search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_4, &matches_4);
|
||||
|
||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
let (events_tx, events_rx) = event_channel();
|
||||
let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
// Use the second artist for this test.
|
||||
let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx);
|
||||
handle.join().unwrap();
|
||||
|
||||
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||
let result = fetch_rx.try_recv().unwrap();
|
||||
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
|
||||
matching: album_1.clone(),
|
||||
list: matches_1.iter().cloned().map(Into::into).collect(),
|
||||
}));
|
||||
assert_eq!(result, expected);
|
||||
|
||||
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||
let result = fetch_rx.try_recv().unwrap();
|
||||
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
|
||||
matching: album_4.clone(),
|
||||
list: matches_4.iter().cloned().map(Into::into).collect(),
|
||||
}));
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
fn artist_expectations() -> (ArtistMeta, Vec<Match<ArtistMeta>>) {
|
||||
let artist = COLLECTION[3].meta.clone();
|
||||
|
||||
let artist_match_1 = Match::new(100, artist.clone());
|
||||
let artist_match_2 = Match::new(50, artist.clone());
|
||||
let matches = vec![artist_match_1.clone(), artist_match_2.clone()];
|
||||
|
||||
(artist, matches)
|
||||
}
|
||||
|
||||
fn search_artist_expectation(
|
||||
api: &mut MockIMusicBrainz,
|
||||
seq: &mut Sequence,
|
||||
artist: &ArtistMeta,
|
||||
matches: &[Match<ArtistMeta>],
|
||||
) {
|
||||
let result = Ok(matches.to_owned());
|
||||
api.expect_search_artist()
|
||||
.with(predicate::eq(artist.clone()))
|
||||
.times(1)
|
||||
.in_sequence(seq)
|
||||
.return_once(|_| result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_artist() {
|
||||
let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
let (artist, matches) = artist_expectations();
|
||||
let mut seq = Sequence::new();
|
||||
search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches);
|
||||
|
||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
let (events_tx, events_rx) = event_channel();
|
||||
let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
// Use the fourth artist for this test as they have no MBID.
|
||||
let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx);
|
||||
handle.join().unwrap();
|
||||
|
||||
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||
let result = fetch_rx.try_recv().unwrap();
|
||||
let expected = Ok(AppMatchesInfo::Artist(AppArtistMatches {
|
||||
matching: artist.clone(),
|
||||
list: matches.iter().cloned().map(Into::into).collect(),
|
||||
}));
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_artist_fetch_disconnect() {
|
||||
let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
let (artist, matches) = artist_expectations();
|
||||
let mut seq = Sequence::new();
|
||||
search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches);
|
||||
|
||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
let (events_tx, events_rx) = event_channel();
|
||||
let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
let (fetch_tx, _) = mpsc::channel();
|
||||
// Use the fourth artist for this test as they have no MBID.
|
||||
let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx);
|
||||
handle.join().unwrap();
|
||||
|
||||
assert!(events_rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_albums_event_disconnect() {
|
||||
let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
||||
|
||||
let (album_1, matches_1) = album_expectations_1();
|
||||
|
||||
let mut seq = Sequence::new();
|
||||
search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1);
|
||||
|
||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
let (events_tx, _) = event_channel();
|
||||
let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
// Use the second artist for this test.
|
||||
let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx);
|
||||
handle.join().unwrap();
|
||||
|
||||
let result = fetch_rx.try_recv().unwrap();
|
||||
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
|
||||
matching: album_1.clone(),
|
||||
list: matches_1.iter().cloned().map(Into::into).collect(),
|
||||
}));
|
||||
assert_eq!(result, expected);
|
||||
|
||||
assert_eq!(fetch_rx.try_recv().unwrap_err(), TryRecvError::Disconnected);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,24 @@
|
||||
use std::sync::mpsc::{self, TryRecvError};
|
||||
use std::{
|
||||
sync::{
|
||||
mpsc::{self, TryRecvError},
|
||||
Arc, Mutex,
|
||||
},
|
||||
thread, time,
|
||||
};
|
||||
|
||||
use musichoard::collection::{
|
||||
album::AlbumMeta,
|
||||
artist::{Artist, ArtistMeta},
|
||||
musicbrainz::{IMusicBrainzRef, Mbid},
|
||||
};
|
||||
|
||||
use crate::tui::{
|
||||
app::{
|
||||
machine::{App, AppInner, AppMachine},
|
||||
AppMatchesInfo, AppPublic, AppState, IAppEventFetch, IAppInteractFetch,
|
||||
},
|
||||
lib::interface::musicbrainz::Error as MbError,
|
||||
event::{Event, EventSender},
|
||||
lib::interface::musicbrainz::{self, Error as MbError, IMusicBrainz},
|
||||
};
|
||||
|
||||
use super::matches::AppMatches;
|
||||
@ -30,7 +43,16 @@ impl AppMachine<AppFetch> {
|
||||
AppMachine { inner, state }
|
||||
}
|
||||
|
||||
pub fn app_fetch_new(inner: AppInner, fetch_rx: FetchReceiver) -> App {
|
||||
pub fn app_fetch_new(inner: AppInner) -> App {
|
||||
let coll = inner.music_hoard.get_collection();
|
||||
let artist = match inner.selection.state_artist(coll) {
|
||||
Some(artist_state) => &coll[artist_state.index],
|
||||
None => return AppMachine::error(inner, "cannot fetch: no artist selected").into(),
|
||||
};
|
||||
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel::<FetchResult>();
|
||||
Self::spawn_fetch_thread(&inner, artist, fetch_tx);
|
||||
|
||||
let fetch = AppFetch::new(fetch_rx);
|
||||
AppMachine::app_fetch(inner, fetch, true)
|
||||
}
|
||||
@ -62,6 +84,80 @@ impl AppMachine<AppFetch> {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_fetch_thread(
|
||||
inner: &AppInner,
|
||||
artist: &Artist,
|
||||
tx: FetchSender,
|
||||
) -> thread::JoinHandle<()> {
|
||||
match artist.meta.musicbrainz {
|
||||
Some(ref arid) => {
|
||||
let musicbrainz = Arc::clone(&inner.musicbrainz);
|
||||
let events = inner.events.clone();
|
||||
let arid = arid.mbid().clone();
|
||||
let albums = artist.albums.iter().map(|a| &a.meta).cloned().collect();
|
||||
thread::spawn(|| Self::fetch_albums(musicbrainz, tx, events, arid, albums))
|
||||
}
|
||||
None => {
|
||||
let musicbrainz = Arc::clone(&inner.musicbrainz);
|
||||
let events = inner.events.clone();
|
||||
let artist = artist.meta.clone();
|
||||
thread::spawn(|| Self::fetch_artist(musicbrainz, tx, events, artist))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_artist(
|
||||
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
||||
fetch_tx: FetchSender,
|
||||
events: EventSender,
|
||||
artist: ArtistMeta,
|
||||
) {
|
||||
let result = musicbrainz.lock().unwrap().search_artist(&artist);
|
||||
let result = result.map(|list| AppMatchesInfo::artist(artist, list));
|
||||
Self::send_fetch_result(&fetch_tx, &events, result).ok();
|
||||
}
|
||||
|
||||
fn fetch_albums(
|
||||
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
||||
fetch_tx: FetchSender,
|
||||
events: EventSender,
|
||||
arid: Mbid,
|
||||
albums: Vec<AlbumMeta>,
|
||||
) {
|
||||
let mut musicbrainz = musicbrainz.lock().unwrap();
|
||||
let mut album_iter = albums.into_iter().peekable();
|
||||
while let Some(album) = album_iter.next() {
|
||||
if album.musicbrainz.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let result = musicbrainz.search_release_group(&arid, &album);
|
||||
let result = result.map(|list| AppMatchesInfo::album(album, list));
|
||||
if Self::send_fetch_result(&fetch_tx, &events, result).is_err() {
|
||||
return;
|
||||
};
|
||||
|
||||
if album_iter.peek().is_some() {
|
||||
thread::sleep(time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_fetch_result(
|
||||
fetch_tx: &FetchSender,
|
||||
events: &EventSender,
|
||||
result: Result<AppMatchesInfo, musicbrainz::Error>,
|
||||
) -> Result<(), ()> {
|
||||
// If receiver disconnects just drop the rest.
|
||||
fetch_tx.send(result).map_err(|_| ())?;
|
||||
|
||||
// If this send fails the event listener is dead. Don't panic as this function runs in a
|
||||
// detached thread so this might be happening during normal shut down.
|
||||
events.send(Event::FetchResultReady).map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AppMachine<AppFetch>> for App {
|
||||
@ -97,16 +193,207 @@ impl IAppEventFetch for AppMachine<AppFetch> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mockall::{predicate, Sequence};
|
||||
use musichoard::collection::artist::ArtistMeta;
|
||||
|
||||
use crate::tui::{
|
||||
app::machine::tests::{inner, music_hoard},
|
||||
lib::interface::musicbrainz::{self, Match},
|
||||
app::{
|
||||
machine::tests::{inner, music_hoard},
|
||||
AppAlbumMatches, AppArtistMatches,
|
||||
},
|
||||
event::EventReceiver,
|
||||
lib::interface::musicbrainz::{self, Match, MockIMusicBrainz},
|
||||
testmod::COLLECTION,
|
||||
EventChannel,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn event_channel() -> (EventSender, EventReceiver) {
|
||||
let event_channel = EventChannel::new();
|
||||
let events_tx = event_channel.sender();
|
||||
let events_rx = event_channel.receiver();
|
||||
(events_tx, events_rx)
|
||||
}
|
||||
|
||||
fn album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
|
||||
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
let album_match_1_1 = Match::new(100, album_1.clone());
|
||||
let album_match_1_2 = Match::new(50, album_4.clone());
|
||||
let matches_1 = vec![album_match_1_1.clone(), album_match_1_2.clone()];
|
||||
|
||||
(album_1, matches_1)
|
||||
}
|
||||
|
||||
fn album_expectations_4() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
|
||||
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||
|
||||
let album_match_4_1 = Match::new(100, album_4.clone());
|
||||
let album_match_4_2 = Match::new(30, album_1.clone());
|
||||
let matches_4 = vec![album_match_4_1.clone(), album_match_4_2.clone()];
|
||||
|
||||
(album_4, matches_4)
|
||||
}
|
||||
|
||||
fn search_release_group_expectation(
|
||||
api: &mut MockIMusicBrainz,
|
||||
seq: &mut Sequence,
|
||||
arid: &Mbid,
|
||||
album: &AlbumMeta,
|
||||
matches: &[Match<AlbumMeta>],
|
||||
) {
|
||||
let result = Ok(matches.to_owned());
|
||||
api.expect_search_release_group()
|
||||
.with(predicate::eq(arid.clone()), predicate::eq(album.clone()))
|
||||
.times(1)
|
||||
.in_sequence(seq)
|
||||
.return_once(|_, _| result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_albums() {
|
||||
let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
||||
|
||||
let (album_1, matches_1) = album_expectations_1();
|
||||
let (album_4, matches_4) = album_expectations_4();
|
||||
|
||||
// Other albums have an MBID and so they will be skipped.
|
||||
let mut seq = Sequence::new();
|
||||
|
||||
search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1);
|
||||
search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_4, &matches_4);
|
||||
|
||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
let (events_tx, events_rx) = event_channel();
|
||||
let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
// Use the second artist for this test.
|
||||
let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx);
|
||||
handle.join().unwrap();
|
||||
|
||||
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||
let result = fetch_rx.try_recv().unwrap();
|
||||
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
|
||||
matching: album_1.clone(),
|
||||
list: matches_1.iter().cloned().map(Into::into).collect(),
|
||||
}));
|
||||
assert_eq!(result, expected);
|
||||
|
||||
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||
let result = fetch_rx.try_recv().unwrap();
|
||||
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
|
||||
matching: album_4.clone(),
|
||||
list: matches_4.iter().cloned().map(Into::into).collect(),
|
||||
}));
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
fn artist_expectations() -> (ArtistMeta, Vec<Match<ArtistMeta>>) {
|
||||
let artist = COLLECTION[3].meta.clone();
|
||||
|
||||
let artist_match_1 = Match::new(100, artist.clone());
|
||||
let artist_match_2 = Match::new(50, artist.clone());
|
||||
let matches = vec![artist_match_1.clone(), artist_match_2.clone()];
|
||||
|
||||
(artist, matches)
|
||||
}
|
||||
|
||||
fn search_artist_expectation(
|
||||
api: &mut MockIMusicBrainz,
|
||||
seq: &mut Sequence,
|
||||
artist: &ArtistMeta,
|
||||
matches: &[Match<ArtistMeta>],
|
||||
) {
|
||||
let result = Ok(matches.to_owned());
|
||||
api.expect_search_artist()
|
||||
.with(predicate::eq(artist.clone()))
|
||||
.times(1)
|
||||
.in_sequence(seq)
|
||||
.return_once(|_| result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_artist() {
|
||||
let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
let (artist, matches) = artist_expectations();
|
||||
let mut seq = Sequence::new();
|
||||
search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches);
|
||||
|
||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
let (events_tx, events_rx) = event_channel();
|
||||
let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
// Use the fourth artist for this test as they have no MBID.
|
||||
let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx);
|
||||
handle.join().unwrap();
|
||||
|
||||
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
|
||||
let result = fetch_rx.try_recv().unwrap();
|
||||
let expected = Ok(AppMatchesInfo::Artist(AppArtistMatches {
|
||||
matching: artist.clone(),
|
||||
list: matches.iter().cloned().map(Into::into).collect(),
|
||||
}));
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_artist_fetch_disconnect() {
|
||||
let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
let (artist, matches) = artist_expectations();
|
||||
let mut seq = Sequence::new();
|
||||
search_artist_expectation(&mut mb_api, &mut seq, &artist, &matches);
|
||||
|
||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
let (events_tx, events_rx) = event_channel();
|
||||
let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
let (fetch_tx, _) = mpsc::channel();
|
||||
// Use the fourth artist for this test as they have no MBID.
|
||||
let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[3], fetch_tx);
|
||||
handle.join().unwrap();
|
||||
|
||||
assert!(events_rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fetch_albums_event_disconnect() {
|
||||
let mut mb_api = MockIMusicBrainz::new();
|
||||
|
||||
let arid: Mbid = "11111111-1111-1111-1111-111111111111".try_into().unwrap();
|
||||
|
||||
let (album_1, matches_1) = album_expectations_1();
|
||||
|
||||
let mut seq = Sequence::new();
|
||||
search_release_group_expectation(&mut mb_api, &mut seq, &arid, &album_1, &matches_1);
|
||||
|
||||
let music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
let (events_tx, _) = event_channel();
|
||||
let inner = AppInner::new(music_hoard, mb_api, events_tx);
|
||||
|
||||
let (fetch_tx, fetch_rx) = mpsc::channel();
|
||||
// Use the second artist for this test.
|
||||
let handle = AppMachine::spawn_fetch_thread(&inner, &COLLECTION[1], fetch_tx);
|
||||
handle.join().unwrap();
|
||||
|
||||
let result = fetch_rx.try_recv().unwrap();
|
||||
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
|
||||
matching: album_1.clone(),
|
||||
list: matches_1.iter().cloned().map(Into::into).collect(),
|
||||
}));
|
||||
assert_eq!(result, expected);
|
||||
|
||||
assert_eq!(fetch_rx.try_recv().unwrap_err(), TryRecvError::Disconnected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recv_ok_fetch_ok() {
|
||||
let (tx, rx) = mpsc::channel::<FetchResult>();
|
||||
@ -115,7 +402,9 @@ mod tests {
|
||||
let fetch_result = Ok(AppMatchesInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
|
||||
tx.send(fetch_result).unwrap();
|
||||
|
||||
let app = AppMachine::app_fetch_new(inner(music_hoard(COLLECTION.clone())), rx);
|
||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||
let fetch = AppFetch::new(rx);
|
||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||
assert!(matches!(app, AppState::Matches(_)));
|
||||
}
|
||||
|
||||
@ -126,7 +415,9 @@ mod tests {
|
||||
let fetch_result = Err(musicbrainz::Error::RateLimit);
|
||||
tx.send(fetch_result).unwrap();
|
||||
|
||||
let app = AppMachine::app_fetch_new(inner(music_hoard(COLLECTION.clone())), rx);
|
||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||
let fetch = AppFetch::new(rx);
|
||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||
assert!(matches!(app, AppState::Error(_)));
|
||||
}
|
||||
|
||||
@ -134,7 +425,9 @@ mod tests {
|
||||
fn recv_err_empty() {
|
||||
let (_tx, rx) = mpsc::channel::<FetchResult>();
|
||||
|
||||
let app = AppMachine::app_fetch_new(inner(music_hoard(COLLECTION.clone())), rx);
|
||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||
let fetch = AppFetch::new(rx);
|
||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||
assert!(matches!(app, AppState::Fetch(_)));
|
||||
}
|
||||
|
||||
@ -142,7 +435,9 @@ mod tests {
|
||||
fn recv_err_disconnected_first() {
|
||||
let (_, rx) = mpsc::channel::<FetchResult>();
|
||||
|
||||
let app = AppMachine::app_fetch_new(inner(music_hoard(COLLECTION.clone())), rx);
|
||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||
let fetch = AppFetch::new(rx);
|
||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||
assert!(matches!(app, AppState::Matches(_)));
|
||||
}
|
||||
|
||||
@ -159,7 +454,9 @@ mod tests {
|
||||
fn empty_first_then_ready() {
|
||||
let (tx, rx) = mpsc::channel::<FetchResult>();
|
||||
|
||||
let app = AppMachine::app_fetch_new(inner(music_hoard(COLLECTION.clone())), rx);
|
||||
let inner = inner(music_hoard(COLLECTION.clone()));
|
||||
let fetch = AppFetch::new(rx);
|
||||
let app = AppMachine::app_fetch(inner, fetch, true);
|
||||
assert!(matches!(app, AppState::Fetch(_)));
|
||||
|
||||
let artist = COLLECTION[3].meta.clone();
|
||||
|
Loading…
Reference in New Issue
Block a user