Handle idle time between fetch results #212
11
src/main.rs
11
src/main.rs
@ -79,18 +79,19 @@ fn with<Database: IDatabase + 'static, Library: ILibrary + 'static>(
|
|||||||
let backend = CrosstermBackend::new(io::stdout());
|
let backend = CrosstermBackend::new(io::stdout());
|
||||||
let terminal = Terminal::new(backend).expect("failed to initialise terminal");
|
let terminal = Terminal::new(backend).expect("failed to initialise terminal");
|
||||||
|
|
||||||
let channel = EventChannel::new();
|
|
||||||
let listener = EventListener::new(channel.sender());
|
|
||||||
let handler = EventHandler::new(channel.receiver());
|
|
||||||
|
|
||||||
let http =
|
let http =
|
||||||
MusicBrainzHttp::new(MUSICHOARD_HTTP_USER_AGENT).expect("failed to initialise HTTP client");
|
MusicBrainzHttp::new(MUSICHOARD_HTTP_USER_AGENT).expect("failed to initialise HTTP client");
|
||||||
let client = MusicBrainzClient::new(http);
|
let client = MusicBrainzClient::new(http);
|
||||||
let musicbrainz = MusicBrainz::new(client);
|
let musicbrainz = MusicBrainz::new(client);
|
||||||
|
|
||||||
let app = App::new(music_hoard, musicbrainz);
|
let channel = EventChannel::new();
|
||||||
|
let listener = EventListener::new(channel.sender());
|
||||||
|
|
||||||
|
let app = App::new(music_hoard, musicbrainz, channel.sender());
|
||||||
let ui = Ui;
|
let ui = Ui;
|
||||||
|
|
||||||
|
let handler = EventHandler::new(channel.receiver());
|
||||||
|
|
||||||
// Run the TUI application.
|
// Run the TUI application.
|
||||||
Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui");
|
Tui::run(terminal, app, ui, handler, listener).expect("failed to run tui");
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,10 @@ impl IAppInteractError for AppMachine<AppError> {
|
|||||||
fn dismiss_error(self) -> Self::APP {
|
fn dismiss_error(self) -> Self::APP {
|
||||||
AppMachine::browse(self.inner).into()
|
AppMachine::browse(self.inner).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP {
|
||||||
|
self.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
use std::{
|
use std::{
|
||||||
sync::{mpsc, Arc, Mutex},
|
sync::{
|
||||||
|
mpsc::{self, TryRecvError},
|
||||||
|
Arc, Mutex,
|
||||||
|
},
|
||||||
thread, time,
|
thread, time,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -11,10 +14,8 @@ use musichoard::collection::{
|
|||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{
|
||||||
machine::{App, AppInner, AppMachine},
|
machine::{App, AppInner, AppMachine}, AppMatchesInfo, AppPublic, AppState, IAppEventFetch, IAppInteractFetch
|
||||||
AppMatchesInfo, AppPublic, AppState, IAppInteractFetch,
|
}, event::{Event, EventSender}, lib::interface::musicbrainz::{Error as MbError, IMusicBrainz}
|
||||||
},
|
|
||||||
lib::interface::musicbrainz::{Error as MbError, IMusicBrainz},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::matches::AppMatches;
|
use super::matches::AppMatches;
|
||||||
@ -24,6 +25,10 @@ pub struct AppFetch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AppMachine<AppFetch> {
|
impl AppMachine<AppFetch> {
|
||||||
|
fn fetch(inner: AppInner, state: AppFetch) -> Self {
|
||||||
|
AppMachine { inner, state }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn app_fetch_new(inner: AppInner) -> App {
|
pub fn app_fetch_new(inner: AppInner) -> App {
|
||||||
let coll = inner.music_hoard.get_collection();
|
let coll = inner.music_hoard.get_collection();
|
||||||
let artist = match inner.selection.state_artist(coll) {
|
let artist = match inner.selection.state_artist(coll) {
|
||||||
@ -36,14 +41,16 @@ impl AppMachine<AppFetch> {
|
|||||||
match artist.meta.musicbrainz {
|
match artist.meta.musicbrainz {
|
||||||
Some(ref arid) => {
|
Some(ref arid) => {
|
||||||
let musicbrainz = Arc::clone(&inner.musicbrainz);
|
let musicbrainz = Arc::clone(&inner.musicbrainz);
|
||||||
|
let events = inner.events.clone();
|
||||||
let arid = arid.mbid().clone();
|
let arid = arid.mbid().clone();
|
||||||
let albums = artist.albums.iter().map(|a| &a.meta).cloned().collect();
|
let albums = artist.albums.iter().map(|a| &a.meta).cloned().collect();
|
||||||
thread::spawn(|| Self::fetch_albums(musicbrainz, fetch_tx, arid, albums));
|
thread::spawn(|| Self::fetch_albums(musicbrainz, fetch_tx, events, arid, albums));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let musicbrainz = Arc::clone(&inner.musicbrainz);
|
let musicbrainz = Arc::clone(&inner.musicbrainz);
|
||||||
|
let events = inner.events.clone();
|
||||||
let artist = artist.meta.clone();
|
let artist = artist.meta.clone();
|
||||||
thread::spawn(|| Self::fetch_artist(musicbrainz, fetch_tx, artist));
|
thread::spawn(|| Self::fetch_artist(musicbrainz, fetch_tx, events, artist));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -52,23 +59,27 @@ impl AppMachine<AppFetch> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn app_fetch_next(inner: AppInner, fetch: AppFetch, first: bool) -> App {
|
pub fn app_fetch_next(inner: AppInner, fetch: AppFetch, first: bool) -> App {
|
||||||
match fetch.fetch_rx.recv() {
|
match fetch.fetch_rx.try_recv() {
|
||||||
Ok(fetch_result) => match fetch_result {
|
Ok(fetch_result) => match fetch_result {
|
||||||
Ok(mut next_match) => {
|
Ok(mut next_match) => {
|
||||||
next_match.push_cannot_have_mbid();
|
next_match.push_cannot_have_mbid();
|
||||||
let current = Some(next_match);
|
let current = Some(next_match);
|
||||||
AppMachine::matches(inner, AppMatches::new(current, fetch)).into()
|
AppMachine::matches(inner, AppMatches::new(current, fetch)).into()
|
||||||
}
|
}
|
||||||
Err(err) => AppMachine::error(inner, format!("fetch failed: {err}")).into(),
|
Err(fetch_err) => {
|
||||||
|
AppMachine::error(inner, format!("fetch failed: {fetch_err}")).into()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// only happens when the sender disconnects which means it finished its job
|
Err(try_recv_err) => match try_recv_err {
|
||||||
Err(_) => {
|
TryRecvError::Disconnected => {
|
||||||
if first {
|
if first {
|
||||||
AppMachine::matches(inner, AppMatches::empty(fetch)).into()
|
AppMachine::matches(inner, AppMatches::empty(fetch)).into()
|
||||||
} else {
|
} else {
|
||||||
AppMachine::browse(inner).into()
|
AppMachine::browse(inner).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TryRecvError::Empty => AppMachine::fetch(inner, fetch).into(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,6 +115,14 @@ impl IAppInteractFetch for AppMachine<AppFetch> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IAppEventFetch for AppMachine<AppFetch> {
|
||||||
|
type APP = App;
|
||||||
|
|
||||||
|
fn fetch_result_ready(self) -> Self::APP {
|
||||||
|
Self::app_fetch_next(self.inner, self.state, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type FetchError = MbError;
|
type FetchError = MbError;
|
||||||
type FetchResult = Result<AppMatchesInfo, FetchError>;
|
type FetchResult = Result<AppMatchesInfo, FetchError>;
|
||||||
type FetchSender = mpsc::Sender<FetchResult>;
|
type FetchSender = mpsc::Sender<FetchResult>;
|
||||||
@ -113,11 +132,13 @@ trait IAppInteractFetchPrivate {
|
|||||||
fn fetch_artist(
|
fn fetch_artist(
|
||||||
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
||||||
fetch_tx: FetchSender,
|
fetch_tx: FetchSender,
|
||||||
|
events: EventSender,
|
||||||
artist: ArtistMeta,
|
artist: ArtistMeta,
|
||||||
);
|
);
|
||||||
fn fetch_albums(
|
fn fetch_albums(
|
||||||
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
||||||
fetch_tx: FetchSender,
|
fetch_tx: FetchSender,
|
||||||
|
events: EventSender,
|
||||||
arid: Mbid,
|
arid: Mbid,
|
||||||
albums: Vec<AlbumMeta>,
|
albums: Vec<AlbumMeta>,
|
||||||
);
|
);
|
||||||
@ -127,16 +148,22 @@ impl IAppInteractFetchPrivate for AppMachine<AppFetch> {
|
|||||||
fn fetch_artist(
|
fn fetch_artist(
|
||||||
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
||||||
fetch_tx: FetchSender,
|
fetch_tx: FetchSender,
|
||||||
|
events: EventSender,
|
||||||
artist: ArtistMeta,
|
artist: ArtistMeta,
|
||||||
) {
|
) {
|
||||||
let result = musicbrainz.lock().unwrap().search_artist(&artist);
|
let result = musicbrainz.lock().unwrap().search_artist(&artist);
|
||||||
let result = result.map(|list| AppMatchesInfo::artist(artist, list));
|
let result = result.map(|list| AppMatchesInfo::artist(artist, list));
|
||||||
fetch_tx.send(result).ok();
|
if fetch_tx.send(result).is_ok() {
|
||||||
|
// 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).ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_albums(
|
fn fetch_albums(
|
||||||
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
||||||
fetch_tx: FetchSender,
|
fetch_tx: FetchSender,
|
||||||
|
events: EventSender,
|
||||||
arid: Mbid,
|
arid: Mbid,
|
||||||
albums: Vec<AlbumMeta>,
|
albums: Vec<AlbumMeta>,
|
||||||
) {
|
) {
|
||||||
@ -154,6 +181,12 @@ impl IAppInteractFetchPrivate for AppMachine<AppFetch> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if events.send(Event::FetchResultReady).is_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.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if album_iter.peek().is_some() {
|
if album_iter.peek().is_some() {
|
||||||
thread::sleep(time::Duration::from_secs(1));
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ use std::sync::{Arc, Mutex};
|
|||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{selection::Selection, AppPublic, AppPublicInner, AppState, IAppAccess, IAppInteract},
|
app::{selection::Selection, AppPublic, AppPublicInner, AppState, IAppAccess, IAppInteract},
|
||||||
|
event::EventSender,
|
||||||
lib::{interface::musicbrainz::IMusicBrainz, IMusicHoard},
|
lib::{interface::musicbrainz::IMusicBrainz, IMusicHoard},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,15 +45,17 @@ pub struct AppInner {
|
|||||||
music_hoard: Box<dyn IMusicHoard>,
|
music_hoard: Box<dyn IMusicHoard>,
|
||||||
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
|
||||||
selection: Selection,
|
selection: Selection,
|
||||||
|
events: EventSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new<MH: IMusicHoard + 'static, MB: IMusicBrainz + Send + 'static>(
|
pub fn new<MH: IMusicHoard + 'static, MB: IMusicBrainz + Send + 'static>(
|
||||||
mut music_hoard: MH,
|
mut music_hoard: MH,
|
||||||
musicbrainz: MB,
|
musicbrainz: MB,
|
||||||
|
events: EventSender,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let init_result = Self::init(&mut music_hoard);
|
let init_result = Self::init(&mut music_hoard);
|
||||||
let inner = AppInner::new(music_hoard, musicbrainz);
|
let inner = AppInner::new(music_hoard, musicbrainz, events);
|
||||||
match init_result {
|
match init_result {
|
||||||
Ok(()) => AppMachine::browse(inner).into(),
|
Ok(()) => AppMachine::browse(inner).into(),
|
||||||
Err(err) => AppMachine::critical(inner, err.to_string()).into(),
|
Err(err) => AppMachine::critical(inner, err.to_string()).into(),
|
||||||
@ -137,6 +140,7 @@ impl AppInner {
|
|||||||
pub fn new<MH: IMusicHoard + 'static, MB: IMusicBrainz + Send + 'static>(
|
pub fn new<MH: IMusicHoard + 'static, MB: IMusicBrainz + Send + 'static>(
|
||||||
music_hoard: MH,
|
music_hoard: MH,
|
||||||
musicbrainz: MB,
|
musicbrainz: MB,
|
||||||
|
events: EventSender,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let selection = Selection::new(music_hoard.get_collection());
|
let selection = Selection::new(music_hoard.get_collection());
|
||||||
AppInner {
|
AppInner {
|
||||||
@ -144,6 +148,7 @@ impl AppInner {
|
|||||||
music_hoard: Box::new(music_hoard),
|
music_hoard: Box::new(music_hoard),
|
||||||
musicbrainz: Arc::new(Mutex::new(musicbrainz)),
|
musicbrainz: Arc::new(Mutex::new(musicbrainz)),
|
||||||
selection,
|
selection,
|
||||||
|
events,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,7 +170,7 @@ mod tests {
|
|||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{AppState, IAppInteract, IAppInteractBrowse},
|
app::{AppState, IAppInteract, IAppInteractBrowse},
|
||||||
lib::{interface::musicbrainz::MockIMusicBrainz, MockIMusicHoard},
|
lib::{interface::musicbrainz::MockIMusicBrainz, MockIMusicHoard}, EventChannel,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -250,17 +255,21 @@ mod tests {
|
|||||||
MockIMusicBrainz::new()
|
MockIMusicBrainz::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn events() -> EventSender {
|
||||||
|
EventChannel::new().sender()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner(music_hoard: MockIMusicHoard) -> AppInner {
|
pub fn inner(music_hoard: MockIMusicHoard) -> AppInner {
|
||||||
AppInner::new(music_hoard, mb_api())
|
AppInner::new(music_hoard, mb_api(), events())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inner_with_mb(music_hoard: MockIMusicHoard, mb_api: MockIMusicBrainz) -> AppInner {
|
pub fn inner_with_mb(music_hoard: MockIMusicHoard, mb_api: MockIMusicBrainz) -> AppInner {
|
||||||
AppInner::new(music_hoard, mb_api)
|
AppInner::new(music_hoard, mb_api, events())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_browse() {
|
fn state_browse() {
|
||||||
let mut app = App::new(music_hoard_init(vec![]), mb_api());
|
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
let state = app.state();
|
let state = app.state();
|
||||||
@ -276,7 +285,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_info() {
|
fn state_info() {
|
||||||
let mut app = App::new(music_hoard_init(vec![]), mb_api());
|
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
app = app.unwrap_browse().show_info_overlay();
|
app = app.unwrap_browse().show_info_overlay();
|
||||||
@ -294,7 +303,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_reload() {
|
fn state_reload() {
|
||||||
let mut app = App::new(music_hoard_init(vec![]), mb_api());
|
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
app = app.unwrap_browse().show_reload_menu();
|
app = app.unwrap_browse().show_reload_menu();
|
||||||
@ -312,7 +321,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_search() {
|
fn state_search() {
|
||||||
let mut app = App::new(music_hoard_init(vec![]), mb_api());
|
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
app = app.unwrap_browse().begin_search();
|
app = app.unwrap_browse().begin_search();
|
||||||
@ -349,7 +358,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_error() {
|
fn state_error() {
|
||||||
let mut app = App::new(music_hoard_init(vec![]), mb_api());
|
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
app = AppMachine::error(app.unwrap_browse().inner, "get rekt").into();
|
app = AppMachine::error(app.unwrap_browse().inner, "get rekt").into();
|
||||||
@ -367,7 +376,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn state_critical() {
|
fn state_critical() {
|
||||||
let mut app = App::new(music_hoard_init(vec![]), mb_api());
|
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
app = AppMachine::critical(app.unwrap_browse().inner, "get rekt").into();
|
app = AppMachine::critical(app.unwrap_browse().inner, "get rekt").into();
|
||||||
@ -393,7 +402,7 @@ mod tests {
|
|||||||
.return_once(|| Err(musichoard::Error::LibraryError(String::from("get rekt"))));
|
.return_once(|| Err(musichoard::Error::LibraryError(String::from("get rekt"))));
|
||||||
music_hoard.expect_get_collection().return_const(vec![]);
|
music_hoard.expect_get_collection().return_const(vec![]);
|
||||||
|
|
||||||
let app = App::new(music_hoard, mb_api());
|
let app = App::new(music_hoard, mb_api(), events());
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
app.unwrap_critical();
|
app.unwrap_critical();
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ pub trait IAppInteract {
|
|||||||
type IS: IAppInteractInfo<APP = Self>;
|
type IS: IAppInteractInfo<APP = Self>;
|
||||||
type RS: IAppInteractReload<APP = Self>;
|
type RS: IAppInteractReload<APP = Self>;
|
||||||
type SS: IAppInteractSearch<APP = Self>;
|
type SS: IAppInteractSearch<APP = Self>;
|
||||||
type FS: IAppInteractFetch<APP = Self>;
|
type FS: IAppInteractFetch<APP = Self> + IAppEventFetch<APP = Self>;
|
||||||
type MS: IAppInteractMatches<APP = Self>;
|
type MS: IAppInteractMatches<APP = Self>;
|
||||||
type ES: IAppInteractError<APP = Self>;
|
type ES: IAppInteractError<APP = Self>;
|
||||||
type CS: IAppInteractCritical<APP = Self>;
|
type CS: IAppInteractCritical<APP = Self>;
|
||||||
@ -98,6 +98,12 @@ pub trait IAppInteractFetch {
|
|||||||
fn no_op(self) -> Self::APP;
|
fn no_op(self) -> Self::APP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait IAppEventFetch {
|
||||||
|
type APP: IAppInteract;
|
||||||
|
|
||||||
|
fn fetch_result_ready(self) -> Self::APP;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait IAppInteractMatches {
|
pub trait IAppInteractMatches {
|
||||||
type APP: IAppInteract;
|
type APP: IAppInteract;
|
||||||
|
|
||||||
@ -114,6 +120,8 @@ pub trait IAppInteractError {
|
|||||||
type APP: IAppInteract;
|
type APP: IAppInteract;
|
||||||
|
|
||||||
fn dismiss_error(self) -> Self::APP;
|
fn dismiss_error(self) -> Self::APP;
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IAppInteractCritical {
|
pub trait IAppInteractCritical {
|
||||||
|
@ -36,6 +36,7 @@ impl From<mpsc::RecvError> for EventError {
|
|||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Key(KeyEvent),
|
Key(KeyEvent),
|
||||||
|
FetchResultReady,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventChannel {
|
pub struct EventChannel {
|
||||||
@ -43,6 +44,7 @@ pub struct EventChannel {
|
|||||||
receiver: mpsc::Receiver<Event>,
|
receiver: mpsc::Receiver<Event>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct EventSender {
|
pub struct EventSender {
|
||||||
sender: mpsc::Sender<Event>,
|
sender: mpsc::Sender<Event>,
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ use crate::tui::{
|
|||||||
event::{Event, EventError, EventReceiver},
|
event::{Event, EventError, EventReceiver},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::app::IAppEventFetch;
|
||||||
|
|
||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait IEventHandler<APP: IAppInteract> {
|
pub trait IEventHandler<APP: IAppInteract> {
|
||||||
fn handle_next_event(&self, app: APP) -> Result<APP, EventError>;
|
fn handle_next_event(&self, app: APP) -> Result<APP, EventError>;
|
||||||
@ -27,6 +29,8 @@ trait IEventHandlerPrivate<APP: IAppInteract> {
|
|||||||
fn handle_matches_key_event(app: <APP as IAppInteract>::MS, key_event: KeyEvent) -> APP;
|
fn handle_matches_key_event(app: <APP as IAppInteract>::MS, key_event: KeyEvent) -> APP;
|
||||||
fn handle_error_key_event(app: <APP as IAppInteract>::ES, key_event: KeyEvent) -> APP;
|
fn handle_error_key_event(app: <APP as IAppInteract>::ES, key_event: KeyEvent) -> APP;
|
||||||
fn handle_critical_key_event(app: <APP as IAppInteract>::CS, key_event: KeyEvent) -> APP;
|
fn handle_critical_key_event(app: <APP as IAppInteract>::CS, key_event: KeyEvent) -> APP;
|
||||||
|
|
||||||
|
fn handle_fetch_result_ready_event(app: APP) -> APP;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
@ -41,11 +45,11 @@ impl EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
|
impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
|
||||||
fn handle_next_event(&self, mut app: APP) -> Result<APP, EventError> {
|
fn handle_next_event(&self, app: APP) -> Result<APP, EventError> {
|
||||||
match self.events.recv()? {
|
Ok(match self.events.recv()? {
|
||||||
Event::Key(key_event) => app = 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),
|
||||||
Ok(app)
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +91,19 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_fetch_result_ready_event(app: APP) -> APP {
|
||||||
|
match app.state() {
|
||||||
|
AppState::Browse(browse) => browse.no_op(),
|
||||||
|
AppState::Info(info) => info.no_op(),
|
||||||
|
AppState::Reload(reload) => reload.no_op(),
|
||||||
|
AppState::Search(search) => search.no_op(),
|
||||||
|
AppState::Fetch(fetch) => fetch.fetch_result_ready(),
|
||||||
|
AppState::Matches(matches) => matches.no_op(),
|
||||||
|
AppState::Error(error) => error.no_op(),
|
||||||
|
AppState::Critical(critical) => critical.no_op(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP {
|
fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Exit application on `ESC` or `q`.
|
// Exit application on `ESC` or `q`.
|
||||||
|
@ -174,6 +174,7 @@ mod testmod;
|
|||||||
mod tests {
|
mod tests {
|
||||||
use std::{io, thread};
|
use std::{io, thread};
|
||||||
|
|
||||||
|
use event::EventSender;
|
||||||
use lib::interface::musicbrainz::MockIMusicBrainz;
|
use lib::interface::musicbrainz::MockIMusicBrainz;
|
||||||
use ratatui::{backend::TestBackend, Terminal};
|
use ratatui::{backend::TestBackend, Terminal};
|
||||||
|
|
||||||
@ -202,8 +203,12 @@ mod tests {
|
|||||||
music_hoard
|
music_hoard
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn events() -> EventSender {
|
||||||
|
EventChannel::new().sender()
|
||||||
|
}
|
||||||
|
|
||||||
fn app(collection: Collection) -> App {
|
fn app(collection: Collection) -> App {
|
||||||
App::new(music_hoard(collection), MockIMusicBrainz::new())
|
App::new(music_hoard(collection), MockIMusicBrainz::new(), events())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listener() -> MockIEventListener {
|
fn listener() -> MockIEventListener {
|
||||||
|
Loading…
Reference in New Issue
Block a user