Daemonize the musicbrainz thread #217
@ -42,7 +42,7 @@ impl From<mpsc::TryRecvError> for EventError {
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Key(KeyEvent),
|
Key(KeyEvent),
|
||||||
FetchResultReady,
|
FetchComplete,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventChannel {
|
pub struct EventChannel {
|
||||||
@ -50,6 +50,14 @@ pub struct EventChannel {
|
|||||||
receiver: mpsc::Receiver<Event>,
|
receiver: mpsc::Receiver<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait IKeyEventSender {
|
||||||
|
fn send_key(&self, key_event: KeyEvent) -> Result<(), EventError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IFetchCompleteEventSender {
|
||||||
|
fn send_fetch_complete(&self) -> Result<(), EventError>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EventSender {
|
pub struct EventSender {
|
||||||
sender: mpsc::Sender<Event>,
|
sender: mpsc::Sender<Event>,
|
||||||
@ -78,9 +86,15 @@ impl EventChannel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventSender {
|
impl IKeyEventSender for EventSender {
|
||||||
pub fn send(&self, event: Event) -> Result<(), EventError> {
|
fn send_key(&self, key_event: KeyEvent) -> Result<(), EventError> {
|
||||||
Ok(self.sender.send(event)?)
|
Ok(self.sender.send(Event::Key(key_event))?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IFetchCompleteEventSender for EventSender {
|
||||||
|
fn send_fetch_complete(&self) -> Result<(), EventError> {
|
||||||
|
Ok(self.sender.send(Event::FetchComplete)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,13 +122,13 @@ mod tests {
|
|||||||
let channel = EventChannel::new();
|
let channel = EventChannel::new();
|
||||||
let sender = channel.sender();
|
let sender = channel.sender();
|
||||||
let receiver = channel.receiver();
|
let receiver = channel.receiver();
|
||||||
let event = Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::empty()));
|
let key_event = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
|
||||||
|
|
||||||
let result = sender.send(event);
|
let result = sender.send_key(key_event);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
drop(receiver);
|
drop(receiver);
|
||||||
let result = sender.send(event);
|
let result = sender.send_key(key_event);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,9 +137,9 @@ mod tests {
|
|||||||
let channel = EventChannel::new();
|
let channel = EventChannel::new();
|
||||||
let sender = channel.sender();
|
let sender = channel.sender();
|
||||||
let receiver = channel.receiver();
|
let receiver = channel.receiver();
|
||||||
let event = Event::Key(KeyEvent::new(KeyCode::Up, KeyModifiers::empty()));
|
let key_event = KeyEvent::new(KeyCode::Up, KeyModifiers::empty());
|
||||||
|
|
||||||
sender.send(event).unwrap();
|
sender.send_key(key_event).unwrap();
|
||||||
let result = receiver.recv();
|
let result = receiver.recv();
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
@ -139,12 +153,11 @@ mod tests {
|
|||||||
let channel = EventChannel::new();
|
let channel = EventChannel::new();
|
||||||
let sender = channel.sender();
|
let sender = channel.sender();
|
||||||
let receiver = channel.receiver();
|
let receiver = channel.receiver();
|
||||||
let event = Event::FetchResultReady;
|
|
||||||
|
|
||||||
let result = receiver.try_recv();
|
let result = receiver.try_recv();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
|
||||||
sender.send(event).unwrap();
|
sender.send_fetch_complete().unwrap();
|
||||||
let result = receiver.try_recv();
|
let result = receiver.try_recv();
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ trait IEventHandlerPrivate<APP: IApp> {
|
|||||||
fn handle_error_key_event(app: <APP as IApp>::ErrorState, key_event: KeyEvent) -> APP;
|
fn handle_error_key_event(app: <APP as IApp>::ErrorState, key_event: KeyEvent) -> APP;
|
||||||
fn handle_critical_key_event(app: <APP as IApp>::CriticalState, key_event: KeyEvent) -> APP;
|
fn handle_critical_key_event(app: <APP as IApp>::CriticalState, key_event: KeyEvent) -> APP;
|
||||||
|
|
||||||
fn handle_fetch_result_ready_event(app: APP) -> APP;
|
fn handle_fetch_complete_event(app: APP) -> APP;
|
||||||
|
|
||||||
fn handle_input_key_event<Input: IAppInput<APP = APP>>(app: Input, key_event: KeyEvent) -> APP;
|
fn handle_input_key_event<Input: IAppInput<APP = APP>>(app: Input, key_event: KeyEvent) -> APP;
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ impl<APP: IApp> IEventHandler<APP> for EventHandler {
|
|||||||
fn handle_next_event(&self, app: APP) -> Result<APP, EventError> {
|
fn handle_next_event(&self, app: APP) -> Result<APP, EventError> {
|
||||||
Ok(match self.events.recv()? {
|
Ok(match self.events.recv()? {
|
||||||
Event::Key(key_event) => Self::handle_key_event(app, key_event),
|
Event::Key(key_event) => Self::handle_key_event(app, key_event),
|
||||||
Event::FetchResultReady => Self::handle_fetch_result_ready_event(app),
|
Event::FetchComplete => Self::handle_fetch_complete_event(app),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_fetch_result_ready_event(app: APP) -> APP {
|
fn handle_fetch_complete_event(app: APP) -> APP {
|
||||||
match app.state() {
|
match app.state() {
|
||||||
AppState::Browse(state) => state.no_op(),
|
AppState::Browse(state) => state.no_op(),
|
||||||
AppState::Info(state) => state.no_op(),
|
AppState::Info(state) => state.no_op(),
|
||||||
|
18
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
18
src/tui/lib/external/musicbrainz/daemon/mod.rs
vendored
@ -4,7 +4,7 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, musicbrainz::
|
|||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::MatchStateInfo,
|
app::MatchStateInfo,
|
||||||
event::{Event, EventSender},
|
event::IFetchCompleteEventSender,
|
||||||
lib::interface::musicbrainz::api::{Error as ApiError, IMusicBrainz},
|
lib::interface::musicbrainz::api::{Error as ApiError, IMusicBrainz},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ pub struct MusicBrainzDaemon {
|
|||||||
musicbrainz: Box<dyn IMusicBrainz>,
|
musicbrainz: Box<dyn IMusicBrainz>,
|
||||||
job_receiver: mpsc::Receiver<Job>,
|
job_receiver: mpsc::Receiver<Job>,
|
||||||
job_queue: JobQueue,
|
job_queue: JobQueue,
|
||||||
event_sender: EventSender,
|
event_sender: Box<dyn IFetchCompleteEventSender>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum JobError {
|
enum JobError {
|
||||||
@ -115,16 +115,16 @@ impl JobChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MusicBrainzDaemon {
|
impl MusicBrainzDaemon {
|
||||||
pub fn run<MB: IMusicBrainz + 'static>(
|
pub fn run<MB: IMusicBrainz + 'static, ES: IFetchCompleteEventSender + 'static>(
|
||||||
musicbrainz: MB,
|
musicbrainz: MB,
|
||||||
job_receiver: mpsc::Receiver<Job>,
|
job_receiver: mpsc::Receiver<Job>,
|
||||||
event_sender: EventSender,
|
event_sender: ES,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let daemon = MusicBrainzDaemon {
|
let daemon = MusicBrainzDaemon {
|
||||||
musicbrainz: Box::new(musicbrainz),
|
musicbrainz: Box::new(musicbrainz),
|
||||||
job_receiver,
|
job_receiver,
|
||||||
job_queue: JobQueue::new(),
|
job_queue: JobQueue::new(),
|
||||||
event_sender,
|
event_sender: Box::new(event_sender),
|
||||||
};
|
};
|
||||||
daemon.main()
|
daemon.main()
|
||||||
}
|
}
|
||||||
@ -195,7 +195,7 @@ impl JobInstance {
|
|||||||
fn execute_next(
|
fn execute_next(
|
||||||
&mut self,
|
&mut self,
|
||||||
musicbrainz: &mut Box<dyn IMusicBrainz>,
|
musicbrainz: &mut Box<dyn IMusicBrainz>,
|
||||||
event_sender: &mut EventSender,
|
event_sender: &mut Box<dyn IFetchCompleteEventSender>,
|
||||||
) -> Result<JobInstanceStatus, JobInstanceError> {
|
) -> Result<JobInstanceStatus, JobInstanceError> {
|
||||||
// self.call_queue can be empty if the caller submits an empty job.
|
// self.call_queue can be empty if the caller submits an empty job.
|
||||||
if let Some(params) = self.call_queue.pop_front() {
|
if let Some(params) = self.call_queue.pop_front() {
|
||||||
@ -212,7 +212,7 @@ impl JobInstance {
|
|||||||
fn execute(
|
fn execute(
|
||||||
&mut self,
|
&mut self,
|
||||||
musicbrainz: &mut Box<dyn IMusicBrainz>,
|
musicbrainz: &mut Box<dyn IMusicBrainz>,
|
||||||
event_sender: &mut EventSender,
|
event_sender: &mut Box<dyn IFetchCompleteEventSender>,
|
||||||
api_params: ApiParams,
|
api_params: ApiParams,
|
||||||
) -> Result<(), JobInstanceError> {
|
) -> Result<(), JobInstanceError> {
|
||||||
match api_params {
|
match api_params {
|
||||||
@ -233,7 +233,7 @@ impl JobInstance {
|
|||||||
|
|
||||||
fn return_result(
|
fn return_result(
|
||||||
&mut self,
|
&mut self,
|
||||||
event_sender: &mut EventSender,
|
event_sender: &mut Box<dyn IFetchCompleteEventSender>,
|
||||||
result: Result<MatchStateInfo, ApiError>,
|
result: Result<MatchStateInfo, ApiError>,
|
||||||
) -> Result<(), JobInstanceError> {
|
) -> Result<(), JobInstanceError> {
|
||||||
self.result_sender
|
self.result_sender
|
||||||
@ -243,7 +243,7 @@ impl JobInstance {
|
|||||||
// If this send fails the event listener is dead. Don't panic as this function runs in a
|
// 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.
|
// detached thread so this might be happening during normal shut down.
|
||||||
event_sender
|
event_sender
|
||||||
.send(Event::FetchResultReady)
|
.send_fetch_complete()
|
||||||
.map_err(|_| JobInstanceError::EventChannelDisconnected)?;
|
.map_err(|_| JobInstanceError::EventChannelDisconnected)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -4,7 +4,7 @@ use std::thread;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
|
|
||||||
use crate::tui::event::{Event, EventError, EventSender};
|
use crate::tui::event::{EventError, IKeyEventSender};
|
||||||
|
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait IEventListener {
|
pub trait IEventListener {
|
||||||
@ -12,13 +12,15 @@ pub trait IEventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventListener {
|
pub struct EventListener {
|
||||||
events: EventSender,
|
event_sender: Box<dyn IKeyEventSender + Send>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// GRCOV_EXCL_START
|
// GRCOV_EXCL_START
|
||||||
impl EventListener {
|
impl EventListener {
|
||||||
pub fn new(events: EventSender) -> Self {
|
pub fn new<ES: IKeyEventSender + Send + 'static>(event_sender: ES) -> Self {
|
||||||
EventListener { events }
|
EventListener {
|
||||||
|
event_sender: Box::new(event_sender),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ impl IEventListener for EventListener {
|
|||||||
match event::read() {
|
match event::read() {
|
||||||
Ok(event) => {
|
Ok(event) => {
|
||||||
if let Err(err) = match event {
|
if let Err(err) = match event {
|
||||||
CrosstermEvent::Key(e) => self.events.send(Event::Key(e)),
|
CrosstermEvent::Key(e) => self.event_sender.send_key(e),
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
} {
|
} {
|
||||||
return err;
|
return err;
|
||||||
|
Loading…
Reference in New Issue
Block a user