From fa385fe1336327de0efea52119b44e9b034292ee Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Wed, 31 Jan 2024 23:07:22 +0100 Subject: [PATCH] Remove AppError --- src/main.rs | 2 +- src/tui/app.rs | 164 ++++++++++++++++++++++++++++++--------------- src/tui/event.rs | 18 ----- src/tui/handler.rs | 81 ++++++---------------- src/tui/mod.rs | 7 +- 5 files changed, 131 insertions(+), 141 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5419d1a..75423d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,7 +67,7 @@ fn with(builder: MusicHoardBuilder) { let listener = EventListener::new(channel.sender()); let handler = EventHandler::new(channel.receiver()); - let app = App::new(music_hoard).expect("failed to initialise application"); + let app = App::new(music_hoard); let ui = Ui; // Run the TUI application. diff --git a/src/tui/app.rs b/src/tui/app.rs index bd4d0e9..cb4e80d 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -1,28 +1,7 @@ -use std::fmt; - use musichoard::collection::{album::Album, artist::Artist, track::Track, Collection}; use ratatui::widgets::ListState; -use crate::tui::{lib::IMusicHoard, Error}; - -#[derive(Debug)] -pub enum AppError { - Lib(String), -} - -impl fmt::Display for AppError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Self::Lib(ref s) => write!(f, "the musichoard library returned an error: {s}"), - } - } -} - -impl From for AppError { - fn from(err: musichoard::Error) -> AppError { - AppError::Lib(err.to_string()) - } -} +use crate::tui::lib::IMusicHoard; pub enum AppState { Browse(BS), @@ -57,8 +36,9 @@ pub trait IAppInteract { fn is_running(&self) -> bool; fn quit(&mut self); + fn force_quit(&mut self); - fn save(&mut self) -> Result<(), AppError>; + fn save(&mut self); fn state(&mut self) -> AppState<&mut Self::BS, &mut Self::IS, &mut Self::RS, &mut Self::ES>; } @@ -337,17 +317,24 @@ pub struct App { } impl App { - pub fn new(mut music_hoard: MH) -> Result { - // FIXME: if either returns an error start in an error state - music_hoard.load_from_database()?; - music_hoard.rescan_library()?; + pub fn new(mut music_hoard: MH) -> Self { + let state = match Self::init(&mut music_hoard) { + Ok(()) => AppState::Browse(()), + Err(err) => AppState::Error(err.to_string()), + }; let selection = Selection::new(Some(music_hoard.get_collection())); - Ok(App { + App { running: true, music_hoard, selection, - state: AppState::Browse(()), - }) + state, + } + } + + fn init(music_hoard: &mut MH) -> Result<(), musichoard::Error> { + music_hoard.load_from_database()?; + music_hoard.rescan_library()?; + Ok(()) } } @@ -362,11 +349,19 @@ impl IAppInteract for App { } fn quit(&mut self) { + if !self.state.is_error() { + self.running = false; + } + } + + fn force_quit(&mut self) { self.running = false; } - fn save(&mut self) -> Result<(), AppError> { - Ok(self.music_hoard.save_to_database()?) + fn save(&mut self) { + if let Err(err) = self.music_hoard.save_to_database() { + self.state = AppState::Error(err.to_string()); + } } fn state(&mut self) -> AppState<&mut Self::BS, &mut Self::IS, &mut Self::RS, &mut Self::ES> { @@ -642,14 +637,50 @@ mod tests { } #[test] - fn running() { - let mut app = App::new(music_hoard(COLLECTION.to_owned())).unwrap(); + fn running_quit() { + let mut app = App::new(music_hoard(COLLECTION.to_owned())); assert!(app.is_running()); app.quit(); assert!(!app.is_running()); } + #[test] + fn error_quit() { + let mut app = App::new(music_hoard(COLLECTION.to_owned())); + assert!(app.is_running()); + + app.state = AppState::Error(String::from("get rekt")); + + app.quit(); + assert!(app.is_running()); + + app.dismiss_error(); + + app.quit(); + assert!(!app.is_running()); + } + + #[test] + fn running_force_quit() { + let mut app = App::new(music_hoard(COLLECTION.to_owned())); + assert!(app.is_running()); + + app.force_quit(); + assert!(!app.is_running()); + } + + #[test] + fn error_force_quit() { + let mut app = App::new(music_hoard(COLLECTION.to_owned())); + assert!(app.is_running()); + + app.state = AppState::Error(String::from("get rekt")); + + app.force_quit(); + assert!(!app.is_running()); + } + #[test] fn save() { let mut music_hoard = music_hoard(COLLECTION.to_owned()); @@ -659,15 +690,47 @@ mod tests { .times(1) .return_once(|| Ok(())); - let mut app = App::new(music_hoard).unwrap(); + let mut app = App::new(music_hoard); - let result = app.save(); - assert!(result.is_ok()); + app.save(); + assert!(app.state.is_browse()); + } + + #[test] + fn save_error() { + let mut music_hoard = music_hoard(COLLECTION.to_owned()); + + music_hoard + .expect_save_to_database() + .times(1) + .return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt")))); + + let mut app = App::new(music_hoard); + + app.save(); + + assert!(app.state.is_error()); + } + + #[test] + fn init_error() { + let mut music_hoard = MockIMusicHoard::new(); + + music_hoard + .expect_load_from_database() + .times(1) + .return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt")))); + music_hoard.expect_get_collection().return_const(vec![]); + + let app = App::new(music_hoard); + + assert!(app.is_running()); + assert!(app.state.is_error()); } #[test] fn modifiers() { - let mut app = App::new(music_hoard(COLLECTION.to_owned())).unwrap(); + let mut app = App::new(music_hoard(COLLECTION.to_owned())); assert!(app.is_running()); assert_eq!(app.selection.active, Category::Artist); @@ -759,7 +822,7 @@ mod tests { let mut collection = COLLECTION.to_owned(); collection[0].albums[0].tracks = vec![]; - let mut app = App::new(music_hoard(collection)).unwrap(); + let mut app = App::new(music_hoard(collection)); assert!(app.is_running()); assert_eq!(app.selection.active, Category::Artist); @@ -788,7 +851,7 @@ mod tests { let mut collection = COLLECTION.to_owned(); collection[0].albums = vec![]; - let mut app = App::new(music_hoard(collection)).unwrap(); + let mut app = App::new(music_hoard(collection)); assert!(app.is_running()); assert_eq!(app.selection.active, Category::Artist); @@ -827,7 +890,7 @@ mod tests { #[test] fn no_artists() { - let mut app = App::new(music_hoard(vec![])).unwrap(); + let mut app = App::new(music_hoard(vec![])); assert!(app.is_running()); assert_eq!(app.selection.active, Category::Artist); @@ -880,7 +943,7 @@ mod tests { #[test] fn info_overlay() { - let mut app = App::new(music_hoard(COLLECTION.to_owned())).unwrap(); + let mut app = App::new(music_hoard(COLLECTION.to_owned())); assert!(app.state().is_browse()); app.show_info_overlay(); @@ -892,7 +955,7 @@ mod tests { #[test] fn reload_go_back() { - let mut app = App::new(music_hoard(COLLECTION.to_owned())).unwrap(); + let mut app = App::new(music_hoard(COLLECTION.to_owned())); assert!(app.state().is_browse()); app.show_reload_menu(); @@ -911,7 +974,7 @@ mod tests { .times(1) .return_once(|| Ok(())); - let mut app = App::new(music_hoard).unwrap(); + let mut app = App::new(music_hoard); assert!(app.state().is_browse()); app.show_reload_menu(); @@ -930,7 +993,7 @@ mod tests { .times(1) .return_once(|| Ok(())); - let mut app = App::new(music_hoard).unwrap(); + let mut app = App::new(music_hoard); assert!(app.state().is_browse()); app.show_reload_menu(); @@ -949,7 +1012,7 @@ mod tests { .times(1) .return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt")))); - let mut app = App::new(music_hoard).unwrap(); + let mut app = App::new(music_hoard); assert!(app.state().is_browse()); app.show_reload_menu(); @@ -961,13 +1024,4 @@ mod tests { app.dismiss_error(); assert!(app.state().is_browse()); } - - #[test] - fn errors() { - let app_err: AppError = musichoard::Error::DatabaseError(String::from("get rekt")).into(); - - assert!(!app_err.to_string().is_empty()); - - assert!(!format!("{:?}", app_err).is_empty()); - } } diff --git a/src/tui/event.rs b/src/tui/event.rs index 3963487..494357f 100644 --- a/src/tui/event.rs +++ b/src/tui/event.rs @@ -2,14 +2,11 @@ use crossterm::event::{KeyEvent, MouseEvent}; use std::fmt; use std::sync::mpsc; -use crate::tui::app::AppError; - #[derive(Debug)] pub enum EventError { Send(Event), Recv, Io(std::io::Error), - App(String), } impl fmt::Display for EventError { @@ -20,12 +17,6 @@ impl fmt::Display for EventError { Self::Io(ref e) => { write!(f, "an I/O error was triggered during event handling: {e}") } - Self::App(ref s) => { - write!( - f, - "the application returned an error during event handling: {s}" - ) - } } } } @@ -42,12 +33,6 @@ impl From for EventError { } } -impl From for EventError { - fn from(err: AppError) -> EventError { - EventError::App(err.to_string()) - } -} - #[derive(Clone, Copy, Debug)] pub enum Event { Key(KeyEvent), @@ -148,16 +133,13 @@ mod tests { })); let recv_err = EventError::Recv; let io_err = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "interrupted")); - let app_err: EventError = AppError::Lib(String::from("lib error")).into(); assert!(!send_err.to_string().is_empty()); assert!(!recv_err.to_string().is_empty()); assert!(!io_err.to_string().is_empty()); - assert!(!app_err.to_string().is_empty()); assert!(!format!("{:?}", send_err).is_empty()); assert!(!format!("{:?}", recv_err).is_empty()); assert!(!format!("{:?}", io_err).is_empty()); - assert!(!format!("{:?}", app_err).is_empty()); } } diff --git a/src/tui/handler.rs b/src/tui/handler.rs index 1ffacb0..585fe6f 100644 --- a/src/tui/handler.rs +++ b/src/tui/handler.rs @@ -1,4 +1,3 @@ -// FIXME: Can code here be less verbose use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; #[cfg(test)] @@ -18,24 +17,11 @@ pub trait IEventHandler { } trait IEventHandlerPrivate { - fn handle_key_event(app: &mut APP, key_event: KeyEvent) -> Result<(), EventError>; - fn handle_browse_key_event( - app: &mut ::BS, - key_event: KeyEvent, - ) -> Result<(), EventError>; - fn handle_info_key_event( - app: &mut ::IS, - key_event: KeyEvent, - ) -> Result<(), EventError>; - fn handle_reload_key_event( - app: &mut ::RS, - key_event: KeyEvent, - ) -> Result<(), EventError>; - fn handle_error_key_event( - app: &mut ::ES, - key_event: KeyEvent, - ) -> Result<(), EventError>; - fn quit(app: &mut APP) -> Result<(), EventError>; + fn handle_key_event(app: &mut APP, key_event: KeyEvent); + fn handle_browse_key_event(app: &mut ::BS, key_event: KeyEvent); + fn handle_info_key_event(app: &mut ::IS, key_event: KeyEvent); + fn handle_reload_key_event(app: &mut ::RS, key_event: KeyEvent); + fn handle_error_key_event(app: &mut ::ES, key_event: KeyEvent); } pub struct EventHandler { @@ -52,7 +38,7 @@ impl EventHandler { impl IEventHandler for EventHandler { fn handle_next_event(&self, app: &mut APP) -> Result<(), EventError> { 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::Mouse(_) => {} Event::Resize(_, _) => {} }; @@ -61,44 +47,37 @@ impl IEventHandler for EventHandler { } impl IEventHandlerPrivate for EventHandler { - fn handle_key_event(app: &mut APP, key_event: KeyEvent) -> Result<(), EventError> { + fn handle_key_event(app: &mut APP, key_event: KeyEvent) { match key_event.code { // Exit application on `ESC` or `q`. KeyCode::Esc | KeyCode::Char('q') => { - Self::quit(app)?; + app.save(); + app.quit(); } // Exit application on `Ctrl-C`. KeyCode::Char('c') | KeyCode::Char('C') => { if key_event.modifiers == KeyModifiers::CONTROL { - Self::quit(app)?; + app.force_quit(); } } _ => match app.state() { AppState::Browse(browse) => { - >::handle_browse_key_event( - browse, key_event, - )?; + >::handle_browse_key_event(browse, key_event); } AppState::Info(info) => { - >::handle_info_key_event(info, key_event)?; + >::handle_info_key_event(info, key_event); } AppState::Reload(reload) => { - >::handle_reload_key_event( - reload, key_event, - )?; + >::handle_reload_key_event(reload, key_event); } AppState::Error(error) => { - >::handle_error_key_event(error, key_event)?; + >::handle_error_key_event(error, key_event); } }, - }; - Ok(()) + } } - fn handle_browse_key_event( - app: &mut ::BS, - key_event: KeyEvent, - ) -> Result<(), EventError> { + fn handle_browse_key_event(app: &mut ::BS, key_event: KeyEvent) { match key_event.code { // Category change. KeyCode::Left => app.decrement_category(), @@ -113,28 +92,18 @@ impl IEventHandlerPrivate for EventHandler { // Othey keys. _ => {} } - - Ok(()) } - fn handle_info_key_event( - app: &mut ::IS, - key_event: KeyEvent, - ) -> Result<(), EventError> { + fn handle_info_key_event(app: &mut ::IS, key_event: KeyEvent) { match key_event.code { // Toggle overlay. KeyCode::Char('m') | KeyCode::Char('M') => app.hide_info_overlay(), // Othey keys. _ => {} } - - Ok(()) } - fn handle_reload_key_event( - app: &mut ::RS, - key_event: KeyEvent, - ) -> Result<(), EventError> { + fn handle_reload_key_event(app: &mut ::RS, key_event: KeyEvent) { match key_event.code { // Reload keys. KeyCode::Char('l') | KeyCode::Char('L') => app.reload_library(), @@ -144,23 +113,11 @@ impl IEventHandlerPrivate for EventHandler { // Othey keys. _ => {} } - - Ok(()) } - fn handle_error_key_event( - app: &mut ::ES, - _key_event: KeyEvent, - ) -> Result<(), EventError> { + fn handle_error_key_event(app: &mut ::ES, _key_event: KeyEvent) { // Any key dismisses the error. app.dismiss_error(); - Ok(()) - } - - fn quit(app: &mut APP) -> Result<(), EventError> { - app.quit(); - app.save()?; - Ok(()) } } // GRCOV_EXCL_STOP diff --git a/src/tui/mod.rs b/src/tui/mod.rs index bf5b616..e610ef4 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -81,9 +81,6 @@ impl Tui { handler: impl IEventHandler, ) -> Result<(), Error> { while app.is_running() { - // FIXME: rendering logic should be in render.rs - reverse Renderer/Ui relationship so - // that TAPP calls are renderer.render(ui, frame); Then rename ui -> app and render -> ui - // as it used to be originally. self.terminal.draw(|frame| UI::render(&mut app, frame))?; handler.handle_next_event(&mut app)?; } @@ -123,7 +120,7 @@ impl Tui { Ok(err) => return Err(err.into()), // Calling std::panic::resume_unwind(err) as recommended by the Rust docs // will not produce an error message. The panic error message is printed at - // the location of the panic which at the time is hidden by the TAPP. + // the location of the panic which at the time is hidden by the TUI. Err(_) => return Err(Error::ListenerPanic), } } @@ -211,7 +208,7 @@ mod tests { } fn app(collection: Collection) -> App { - App::new(music_hoard(collection)).unwrap() + App::new(music_hoard(collection)) } fn listener() -> MockIEventListener {