Daemonize the musicbrainz thread #217
@ -53,11 +53,11 @@ pub struct EventChannel {
|
|||||||
receiver: mpsc::Receiver<Event>,
|
receiver: mpsc::Receiver<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, automock)]
|
|
||||||
pub trait IKeyEventSender {
|
pub trait IKeyEventSender {
|
||||||
fn send_key(&self, key_event: KeyEvent) -> Result<(), EventError>;
|
fn send_key(&self, key_event: KeyEvent) -> Result<(), EventError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(test, automock)]
|
||||||
pub trait IFetchCompleteEventSender {
|
pub trait IFetchCompleteEventSender {
|
||||||
fn send_fetch_complete(&self) -> Result<(), EventError>;
|
fn send_fetch_complete(&self) -> Result<(), EventError>;
|
||||||
}
|
}
|
||||||
|
586
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
586
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
@ -21,6 +21,7 @@ struct JobQueue {
|
|||||||
background_queue: VecDeque<JobInstance>,
|
background_queue: VecDeque<JobInstance>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Job {
|
struct Job {
|
||||||
priority: JobPriority,
|
priority: JobPriority,
|
||||||
instance: JobInstance,
|
instance: JobInstance,
|
||||||
@ -32,21 +33,26 @@ impl Job {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
enum JobPriority {
|
enum JobPriority {
|
||||||
|
#[cfg(test)]
|
||||||
Foreground,
|
Foreground,
|
||||||
Background,
|
Background,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct JobInstance {
|
struct JobInstance {
|
||||||
result_sender: ResultSender,
|
result_sender: ResultSender,
|
||||||
requests: VecDeque<MbParams>,
|
requests: VecDeque<MbParams>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
enum JobInstanceStatus {
|
enum JobInstanceStatus {
|
||||||
Continue,
|
Continue,
|
||||||
Complete,
|
Complete,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
enum JobInstanceError {
|
enum JobInstanceError {
|
||||||
ReturnChannelDisconnected,
|
ReturnChannelDisconnected,
|
||||||
EventChannelDisconnected,
|
EventChannelDisconnected,
|
||||||
@ -61,6 +67,7 @@ impl JobInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
enum JobError {
|
enum JobError {
|
||||||
JobQueueEmpty,
|
JobQueueEmpty,
|
||||||
EventChannelDisconnected,
|
EventChannelDisconnected,
|
||||||
@ -130,6 +137,7 @@ impl JobSender {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MusicBrainzDaemon {
|
impl MusicBrainzDaemon {
|
||||||
|
// GRCOV_EXCL_START
|
||||||
pub fn run<MB: IMusicBrainz + 'static, ES: IFetchCompleteEventSender + 'static>(
|
pub fn run<MB: IMusicBrainz + 'static, ES: IFetchCompleteEventSender + 'static>(
|
||||||
musicbrainz: MB,
|
musicbrainz: MB,
|
||||||
job_receiver: JobReceiver,
|
job_receiver: JobReceiver,
|
||||||
@ -163,6 +171,7 @@ impl MusicBrainzDaemon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// GRCOV_EXCL_STOP
|
||||||
|
|
||||||
fn enqueue_all_pending_jobs(&mut self) -> Result<(), Error> {
|
fn enqueue_all_pending_jobs(&mut self) -> Result<(), Error> {
|
||||||
loop {
|
loop {
|
||||||
@ -178,7 +187,7 @@ impl MusicBrainzDaemon {
|
|||||||
|
|
||||||
fn execute_next_job(&mut self) -> Result<(), JobError> {
|
fn execute_next_job(&mut self) -> Result<(), JobError> {
|
||||||
if let Some(instance) = self.job_queue.front_mut() {
|
if let Some(instance) = self.job_queue.front_mut() {
|
||||||
let result = instance.execute_next(&mut self.musicbrainz, &mut self.event_sender);
|
let result = instance.execute_next(&mut *self.musicbrainz, &mut *self.event_sender);
|
||||||
match result {
|
match result {
|
||||||
Ok(JobInstanceStatus::Continue) => {}
|
Ok(JobInstanceStatus::Continue) => {}
|
||||||
Ok(JobInstanceStatus::Complete)
|
Ok(JobInstanceStatus::Complete)
|
||||||
@ -209,8 +218,8 @@ impl MusicBrainzDaemon {
|
|||||||
impl JobInstance {
|
impl JobInstance {
|
||||||
fn execute_next(
|
fn execute_next(
|
||||||
&mut self,
|
&mut self,
|
||||||
musicbrainz: &mut Box<dyn IMusicBrainz>,
|
musicbrainz: &mut dyn IMusicBrainz,
|
||||||
event_sender: &mut Box<dyn IFetchCompleteEventSender>,
|
event_sender: &mut dyn IFetchCompleteEventSender,
|
||||||
) -> Result<JobInstanceStatus, JobInstanceError> {
|
) -> Result<JobInstanceStatus, JobInstanceError> {
|
||||||
// self.requests can be empty if the caller submits an empty job.
|
// self.requests can be empty if the caller submits an empty job.
|
||||||
if let Some(params) = self.requests.pop_front() {
|
if let Some(params) = self.requests.pop_front() {
|
||||||
@ -226,8 +235,8 @@ impl JobInstance {
|
|||||||
|
|
||||||
fn execute(
|
fn execute(
|
||||||
&mut self,
|
&mut self,
|
||||||
musicbrainz: &mut Box<dyn IMusicBrainz>,
|
musicbrainz: &mut dyn IMusicBrainz,
|
||||||
event_sender: &mut Box<dyn IFetchCompleteEventSender>,
|
event_sender: &mut dyn IFetchCompleteEventSender,
|
||||||
api_params: MbParams,
|
api_params: MbParams,
|
||||||
) -> Result<(), JobInstanceError> {
|
) -> Result<(), JobInstanceError> {
|
||||||
match api_params {
|
match api_params {
|
||||||
@ -248,7 +257,7 @@ impl JobInstance {
|
|||||||
|
|
||||||
fn return_result(
|
fn return_result(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_sender: &mut Box<dyn IFetchCompleteEventSender>,
|
event_sender: &mut dyn IFetchCompleteEventSender,
|
||||||
result: Result<MatchStateInfo, ApiError>,
|
result: Result<MatchStateInfo, ApiError>,
|
||||||
) -> Result<(), JobInstanceError> {
|
) -> Result<(), JobInstanceError> {
|
||||||
self.result_sender
|
self.result_sender
|
||||||
@ -291,6 +300,7 @@ impl JobQueue {
|
|||||||
|
|
||||||
fn push_back(&mut self, job: Job) {
|
fn push_back(&mut self, job: Job) {
|
||||||
match job.priority {
|
match job.priority {
|
||||||
|
#[cfg(test)]
|
||||||
JobPriority::Foreground => self.foreground_queue.push_back(job.instance),
|
JobPriority::Foreground => self.foreground_queue.push_back(job.instance),
|
||||||
JobPriority::Background => self.background_queue.push_back(job.instance),
|
JobPriority::Background => self.background_queue.push_back(job.instance),
|
||||||
}
|
}
|
||||||
@ -299,190 +309,388 @@ impl JobQueue {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
// fn event_channel() -> (EventSender, EventReceiver) {
|
use mockall::{predicate, Sequence};
|
||||||
// let event_channel = EventChannel::new();
|
use musichoard::collection::{
|
||||||
// let events_tx = event_channel.sender();
|
album::AlbumMeta,
|
||||||
// let events_rx = event_channel.receiver();
|
artist::ArtistMeta,
|
||||||
// (events_tx, events_rx)
|
musicbrainz::{IMusicBrainzRef, Mbid},
|
||||||
// }
|
};
|
||||||
|
|
||||||
|
use crate::tui::{
|
||||||
|
event::{Event, EventError, MockIFetchCompleteEventSender},
|
||||||
|
lib::interface::musicbrainz::api::{Match, MockIMusicBrainz},
|
||||||
|
testmod::COLLECTION,
|
||||||
|
};
|
||||||
|
|
||||||
// fn album_expectations_1() -> (AlbumMeta, Vec<Match<AlbumMeta>>) {
|
use super::*;
|
||||||
// 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(MatchStateInfo::Album(AlbumMatches {
|
|
||||||
// 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(MatchStateInfo::Album(AlbumMatches {
|
|
||||||
// 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(MatchStateInfo::Artist(ArtistMatches {
|
|
||||||
// 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(MatchStateInfo::Album(AlbumMatches {
|
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
fn musicbrainz() -> MockIMusicBrainz {
|
||||||
|
MockIMusicBrainz::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn job_channel() -> (JobSender, JobReceiver) {
|
||||||
|
let channel = JobChannel::new();
|
||||||
|
let sender = channel.sender();
|
||||||
|
let receiver = channel.receiver();
|
||||||
|
(sender, receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn event_sender() -> MockIFetchCompleteEventSender {
|
||||||
|
MockIFetchCompleteEventSender::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_complete_expectation(event_sender: &mut MockIFetchCompleteEventSender, times: usize) {
|
||||||
|
event_sender
|
||||||
|
.expect_send_fetch_complete()
|
||||||
|
.times(times)
|
||||||
|
.returning(|| Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn daemon(job_receiver: JobReceiver) -> MusicBrainzDaemon {
|
||||||
|
MusicBrainzDaemon {
|
||||||
|
musicbrainz: Box::new(musicbrainz()),
|
||||||
|
job_receiver: job_receiver.receiver,
|
||||||
|
job_queue: JobQueue::new(),
|
||||||
|
event_sender: Box::new(event_sender()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn daemon_with(
|
||||||
|
musicbrainz: MockIMusicBrainz,
|
||||||
|
job_receiver: JobReceiver,
|
||||||
|
event_sender: MockIFetchCompleteEventSender,
|
||||||
|
) -> MusicBrainzDaemon {
|
||||||
|
MusicBrainzDaemon {
|
||||||
|
musicbrainz: Box::new(musicbrainz),
|
||||||
|
job_receiver: job_receiver.receiver,
|
||||||
|
job_queue: JobQueue::new(),
|
||||||
|
event_sender: Box::new(event_sender),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_artist_requests() -> VecDeque<MbParams> {
|
||||||
|
let artist = COLLECTION[3].meta.clone();
|
||||||
|
VecDeque::from([MbParams::search_artist(artist)])
|
||||||
|
}
|
||||||
|
|
||||||
|
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_albums_requests() -> VecDeque<MbParams> {
|
||||||
|
let mbref = COLLECTION[1].meta.musicbrainz.as_ref();
|
||||||
|
let arid = mbref.unwrap().mbid().clone();
|
||||||
|
|
||||||
|
let album_1 = COLLECTION[1].albums[0].meta.clone();
|
||||||
|
let album_4 = COLLECTION[1].albums[3].meta.clone();
|
||||||
|
|
||||||
|
VecDeque::from([
|
||||||
|
MbParams::search_release_group(arid.clone(), album_1),
|
||||||
|
MbParams::search_release_group(arid.clone(), album_4),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn album_arid_expectation() -> Mbid {
|
||||||
|
let mbref = COLLECTION[1].meta.musicbrainz.as_ref();
|
||||||
|
mbref.unwrap().mbid().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enqueue_job_channel_disconnected() {
|
||||||
|
let (_, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon(job_receiver);
|
||||||
|
let result = daemon.enqueue_all_pending_jobs();
|
||||||
|
assert_eq!(result, Err(Error::JobChannelDisconnected));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wait_for_job_channel_disconnected() {
|
||||||
|
let (_, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon(job_receiver);
|
||||||
|
let result = daemon.wait_for_jobs();
|
||||||
|
assert_eq!(result, Err(Error::JobChannelDisconnected));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enqueue_job_queue_empty() {
|
||||||
|
let (_job_sender, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon(job_receiver);
|
||||||
|
let result = daemon.enqueue_all_pending_jobs();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enqueue_job() {
|
||||||
|
let (job_sender, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon(job_receiver);
|
||||||
|
|
||||||
|
let requests = search_artist_requests();
|
||||||
|
let (result_sender, _) = mpsc::channel();
|
||||||
|
let result = job_sender.submit_background_job(result_sender, requests.clone());
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.enqueue_all_pending_jobs();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(daemon.job_queue.pop_front().unwrap().requests, requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wait_for_jobs_job() {
|
||||||
|
let (job_sender, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon(job_receiver);
|
||||||
|
|
||||||
|
let requests = search_artist_requests();
|
||||||
|
let (result_sender, _) = mpsc::channel();
|
||||||
|
let result = job_sender.submit_background_job(result_sender, requests.clone());
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.wait_for_jobs();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(daemon.job_queue.pop_front().unwrap().requests, requests);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_empty() {
|
||||||
|
let (_job_sender, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon(job_receiver);
|
||||||
|
|
||||||
|
let (result_sender, _) = mpsc::channel();
|
||||||
|
let mut instance = JobInstance::new(result_sender, VecDeque::new());
|
||||||
|
let result = instance.execute_next(&mut musicbrainz(), &mut event_sender());
|
||||||
|
assert_eq!(result, Ok(JobInstanceStatus::Complete));
|
||||||
|
|
||||||
|
let result = daemon.enqueue_all_pending_jobs();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.execute_next_job();
|
||||||
|
assert_eq!(result, Err(JobError::JobQueueEmpty));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_artist_expectation(
|
||||||
|
musicbrainz: &mut MockIMusicBrainz,
|
||||||
|
artist: &ArtistMeta,
|
||||||
|
matches: &[Match<ArtistMeta>],
|
||||||
|
) {
|
||||||
|
let result = Ok(matches.to_owned());
|
||||||
|
musicbrainz
|
||||||
|
.expect_search_artist()
|
||||||
|
.with(predicate::eq(artist.clone()))
|
||||||
|
.times(1)
|
||||||
|
.return_once(|_| result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_search_artist() {
|
||||||
|
let mut musicbrainz = musicbrainz();
|
||||||
|
let (artist, matches) = artist_expectations();
|
||||||
|
search_artist_expectation(&mut musicbrainz, &artist, &matches);
|
||||||
|
|
||||||
|
let mut event_sender = event_sender();
|
||||||
|
fetch_complete_expectation(&mut event_sender, 1);
|
||||||
|
|
||||||
|
let (job_sender, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
|
||||||
|
|
||||||
|
let requests = search_artist_requests();
|
||||||
|
let (result_sender, result_receiver) = mpsc::channel();
|
||||||
|
let result = job_sender.submit_background_job(result_sender, requests);
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.enqueue_all_pending_jobs();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.execute_next_job();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = result_receiver.try_recv().unwrap();
|
||||||
|
assert_eq!(result, Ok(MatchStateInfo::artist(artist, matches)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_release_group_expectation(
|
||||||
|
musicbrainz: &mut MockIMusicBrainz,
|
||||||
|
seq: &mut Sequence,
|
||||||
|
arid: &Mbid,
|
||||||
|
album: &AlbumMeta,
|
||||||
|
matches: &[Match<AlbumMeta>],
|
||||||
|
) {
|
||||||
|
let result = Ok(matches.to_owned());
|
||||||
|
musicbrainz
|
||||||
|
.expect_search_release_group()
|
||||||
|
.with(predicate::eq(arid.clone()), predicate::eq(album.clone()))
|
||||||
|
.times(1)
|
||||||
|
.in_sequence(seq)
|
||||||
|
.return_once(|_, _| result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_search_release_groups() {
|
||||||
|
let mut musicbrainz = musicbrainz();
|
||||||
|
let arid = album_arid_expectation();
|
||||||
|
let (album_1, matches_1) = album_expectations_1();
|
||||||
|
let (album_4, matches_4) = album_expectations_4();
|
||||||
|
|
||||||
|
let mut seq = Sequence::new();
|
||||||
|
search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_1, &matches_1);
|
||||||
|
search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_4, &matches_4);
|
||||||
|
|
||||||
|
let mut event_sender = event_sender();
|
||||||
|
fetch_complete_expectation(&mut event_sender, 2);
|
||||||
|
|
||||||
|
let (job_sender, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
|
||||||
|
|
||||||
|
let requests = search_albums_requests();
|
||||||
|
let (result_sender, result_receiver) = mpsc::channel();
|
||||||
|
let result = job_sender.submit_background_job(result_sender, requests);
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.enqueue_all_pending_jobs();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.execute_next_job();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.execute_next_job();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = result_receiver.try_recv().unwrap();
|
||||||
|
assert_eq!(result, Ok(MatchStateInfo::album(album_1, matches_1)));
|
||||||
|
|
||||||
|
let result = result_receiver.try_recv().unwrap();
|
||||||
|
assert_eq!(result, Ok(MatchStateInfo::album(album_4, matches_4)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_search_release_groups_result_disconnect() {
|
||||||
|
let mut musicbrainz = musicbrainz();
|
||||||
|
let arid = album_arid_expectation();
|
||||||
|
let (album_1, matches_1) = album_expectations_1();
|
||||||
|
|
||||||
|
let mut seq = Sequence::new();
|
||||||
|
search_release_group_expectation(&mut musicbrainz, &mut seq, &arid, &album_1, &matches_1);
|
||||||
|
|
||||||
|
let mut event_sender = event_sender();
|
||||||
|
fetch_complete_expectation(&mut event_sender, 0);
|
||||||
|
|
||||||
|
let (job_sender, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
|
||||||
|
|
||||||
|
let requests = search_albums_requests();
|
||||||
|
let (result_sender, _) = mpsc::channel();
|
||||||
|
let result = job_sender.submit_background_job(result_sender, requests);
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.enqueue_all_pending_jobs();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.execute_next_job();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
// Two albums were submitted, but as one job. If the first one fails due to the
|
||||||
|
// result_channel disconnecting, all remaining requests in that job are dropped.
|
||||||
|
assert!(daemon.job_queue.pop_front().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn execute_search_artist_event_disconnect() {
|
||||||
|
let mut musicbrainz = musicbrainz();
|
||||||
|
let (artist, matches) = artist_expectations();
|
||||||
|
search_artist_expectation(&mut musicbrainz, &artist, &matches);
|
||||||
|
|
||||||
|
let mut event_sender = event_sender();
|
||||||
|
event_sender
|
||||||
|
.expect_send_fetch_complete()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Err(EventError::Send(Event::FetchComplete)));
|
||||||
|
|
||||||
|
let (job_sender, job_receiver) = job_channel();
|
||||||
|
let mut daemon = daemon_with(musicbrainz, job_receiver, event_sender);
|
||||||
|
|
||||||
|
let requests = search_artist_requests();
|
||||||
|
let (result_sender, _result_receiver) = mpsc::channel();
|
||||||
|
let result = job_sender.submit_background_job(result_sender, requests);
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.enqueue_all_pending_jobs();
|
||||||
|
assert_eq!(result, Ok(()));
|
||||||
|
|
||||||
|
let result = daemon.execute_next_job();
|
||||||
|
assert_eq!(result, Err(JobError::EventChannelDisconnected));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn job_queue() {
|
||||||
|
let mut queue = JobQueue::new();
|
||||||
|
assert!(queue.is_empty());
|
||||||
|
assert_eq!(queue.foreground_queue.len(), 0);
|
||||||
|
assert_eq!(queue.background_queue.len(), 0);
|
||||||
|
|
||||||
|
let (result_sender, _) = mpsc::channel();
|
||||||
|
let bg = Job::new(
|
||||||
|
JobPriority::Background,
|
||||||
|
JobInstance::new(result_sender, VecDeque::new()),
|
||||||
|
);
|
||||||
|
queue.push_back(bg);
|
||||||
|
|
||||||
|
assert!(!queue.is_empty());
|
||||||
|
assert_eq!(queue.foreground_queue.len(), 0);
|
||||||
|
assert_eq!(queue.background_queue.len(), 1);
|
||||||
|
|
||||||
|
let (result_sender, _) = mpsc::channel();
|
||||||
|
let fg = Job::new(
|
||||||
|
JobPriority::Foreground,
|
||||||
|
JobInstance::new(result_sender, VecDeque::new()),
|
||||||
|
);
|
||||||
|
queue.push_back(fg);
|
||||||
|
|
||||||
|
assert!(!queue.is_empty());
|
||||||
|
assert_eq!(queue.foreground_queue.len(), 1);
|
||||||
|
assert_eq!(queue.background_queue.len(), 1);
|
||||||
|
|
||||||
|
let instance = queue.pop_front();
|
||||||
|
assert!(instance.is_some());
|
||||||
|
assert!(!queue.is_empty());
|
||||||
|
assert_eq!(queue.foreground_queue.len(), 0);
|
||||||
|
assert_eq!(queue.background_queue.len(), 1);
|
||||||
|
|
||||||
|
let instance = queue.pop_front();
|
||||||
|
assert!(instance.is_some());
|
||||||
|
assert!(queue.is_empty());
|
||||||
|
assert_eq!(queue.foreground_queue.len(), 0);
|
||||||
|
assert_eq!(queue.background_queue.len(), 0);
|
||||||
|
|
||||||
|
let instance = queue.pop_front();
|
||||||
|
assert!(instance.is_none());
|
||||||
|
assert!(queue.is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ use crate::tui::{app::MatchStateInfo, lib::interface::musicbrainz::api::Error as
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
EventChannelDisconnected,
|
EventChannelDisconnected,
|
||||||
JobChannelDisconnected,
|
JobChannelDisconnected,
|
||||||
@ -33,23 +34,23 @@ pub trait IMbJobSender {
|
|||||||
) -> Result<(), Error>;
|
) -> Result<(), Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum MbParams {
|
pub enum MbParams {
|
||||||
Search(SearchParams),
|
Search(SearchParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub enum SearchParams {
|
pub enum SearchParams {
|
||||||
Artist(SearchArtistParams),
|
Artist(SearchArtistParams),
|
||||||
ReleaseGroup(SearchReleaseGroupParams),
|
ReleaseGroup(SearchReleaseGroupParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SearchArtistParams {
|
pub struct SearchArtistParams {
|
||||||
pub artist: ArtistMeta,
|
pub artist: ArtistMeta,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct SearchReleaseGroupParams {
|
pub struct SearchReleaseGroupParams {
|
||||||
pub arid: Mbid,
|
pub arid: Mbid,
|
||||||
pub album: AlbumMeta,
|
pub album: AlbumMeta,
|
||||||
@ -67,3 +68,14 @@ impl MbParams {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn errors() {
|
||||||
|
assert!(!format!("{}", Error::EventChannelDisconnected).is_empty());
|
||||||
|
assert!(!format!("{}", Error::JobChannelDisconnected).is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user