Put event sender behind interface

This commit is contained in:
Wojciech Kozlowski 2024-09-21 14:40:44 +02:00
parent 993de9a38b
commit 7d99219f44
4 changed files with 43 additions and 28 deletions

View File

@ -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());

View File

@ -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(),

View File

@ -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(())

View File

@ -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;