diff --git a/.cargo-husky/hooks/pre-commit b/.cargo-husky/hooks/pre-commit old mode 100644 new mode 100755 diff --git a/.cargo-husky/hooks/pre-push b/.cargo-husky/hooks/pre-push old mode 100644 new mode 100755 diff --git a/Cargo.toml b/Cargo.toml index 9fb8d5d..bdccade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "managarr" -version = "0.0.20" +version = "0.0.22" authors = ["Alex Clarke "] description = "A TUI for managing *arr servers" keywords = ["managarr", "tui-rs", "dashboard", "servarr"] @@ -9,7 +9,7 @@ repository = "https://github.com/Dark-Alex-17/managarr" homepage = "https://github.com/Dark-Alex-17/managarr" readme = "README.md" edition = "2021" -rust-version = "1.65.0" +rust-version = "1.69.0" [dependencies] anyhow = "1.0.68" @@ -32,7 +32,7 @@ strum = {version = "0.25.0", features = ["derive"] } strum_macros = "0.25.0" tokio = { version = "1.29.0", features = ["full"] } tokio-util = "0.7.8" -tui = { version = "0.21.0", package = "ratatui", features = ["all-widgets"] } +tui = { version = "0.22.0", package = "ratatui", features = ["all-widgets"] } urlencoding = "2.1.2" [dev-dependencies] diff --git a/src/app/app_tests.rs b/src/app/app_tests.rs index 115ffad..6bd9862 100644 --- a/src/app/app_tests.rs +++ b/src/app/app_tests.rs @@ -19,7 +19,6 @@ mod tests { assert!(app.network_tx.is_none()); assert!(!app.cancellation_token.is_cancelled()); assert_eq!(app.error, HorizontallyScrollableText::default()); - assert!(app.response.is_empty()); assert_eq!(app.server_tabs.index, 0); assert_eq!( app.server_tabs.tabs, diff --git a/src/app/mod.rs b/src/app/mod.rs index d38821d..b2bfa45 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -26,7 +26,6 @@ pub struct App<'a> { cancellation_token: CancellationToken, pub server_tabs: TabState, pub error: HorizontallyScrollableText, - pub response: String, pub title: &'static str, pub tick_until_poll: u64, pub ticks_until_scroll: u64, @@ -135,7 +134,6 @@ impl<'a> Default for App<'a> { network_tx: None, cancellation_token: CancellationToken::new(), error: HorizontallyScrollableText::default(), - response: String::default(), server_tabs: TabState::new(vec![ TabRoute { title: "Radarr", diff --git a/src/handlers/handler_test_utils.rs b/src/handlers/handler_test_utils.rs index ed09f47..65171ed 100644 --- a/src/handlers/handler_test_utils.rs +++ b/src/handlers/handler_test_utils.rs @@ -345,38 +345,6 @@ mod test_utils { }; } - #[macro_export] - macro_rules! test_text_box_home_end_keys { - ($handler:ident, $block:expr, $field:ident) => { - let mut app = App::default(); - app.data.radarr_data.$field = "Test".to_owned().into(); - - $handler::with(&DEFAULT_KEYBINDINGS.home.key, &mut app, &$block, &None).handle(); - - assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 4); - - $handler::with(&DEFAULT_KEYBINDINGS.end.key, &mut app, &$block, &None).handle(); - - assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 0); - }; - } - - #[macro_export] - macro_rules! test_text_box_left_right_keys { - ($handler:ident, $block:expr, $field:ident) => { - let mut app = App::default(); - app.data.radarr_data.$field = "Test".to_owned().into(); - - $handler::with(&DEFAULT_KEYBINDINGS.left.key, &mut app, &$block, &None).handle(); - - assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 1); - - $handler::with(&DEFAULT_KEYBINDINGS.right.key, &mut app, &$block, &None).handle(); - - assert_eq!(*app.data.radarr_data.$field.offset.borrow(), 0); - }; - } - #[macro_export] macro_rules! test_handler_delegation { ($handler:ident, $base:expr, $active_block:expr) => { diff --git a/src/handlers/radarr_handlers/collections/collections_handler_tests.rs b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs index b21df8a..2e475b6 100644 --- a/src/handlers/radarr_handlers/collections/collections_handler_tests.rs +++ b/src/handlers/radarr_handlers/collections/collections_handler_tests.rs @@ -49,9 +49,7 @@ mod tests { mod test_handle_home_end { use pretty_assertions::assert_eq; - use crate::{ - extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys, - }; + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use super::*; @@ -79,19 +77,93 @@ mod tests { #[test] fn test_collection_search_box_home_end_keys() { - test_text_box_home_end_keys!( - CollectionsHandler, - ActiveRadarrBlock::SearchCollection, - search + let mut app = App::default(); + app.data.radarr_data.search = Some("Test".into()); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.home.key, + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 4 + ); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.end.key, + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 0 ); } #[test] fn test_collection_filter_box_home_end_keys() { - test_text_box_home_end_keys!( - CollectionsHandler, - ActiveRadarrBlock::FilterCollections, - filter + let mut app = App::default(); + app.data.radarr_data.filter = Some("Test".into()); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.home.key, + &mut app, + &ActiveRadarrBlock::FilterCollections, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .offset + .borrow(), + 4 + ); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.end.key, + &mut app, + &ActiveRadarrBlock::FilterCollections, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .offset + .borrow(), + 0 ); } } @@ -100,8 +172,6 @@ mod tests { use pretty_assertions::assert_eq; use rstest::rstest; - use crate::test_text_box_left_right_keys; - use super::*; #[test] @@ -179,19 +249,93 @@ mod tests { #[test] fn test_collection_search_box_left_right_keys() { - test_text_box_left_right_keys!( - CollectionsHandler, - ActiveRadarrBlock::SearchCollection, - search + let mut app = App::default(); + app.data.radarr_data.search = Some("Test".into()); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 1 + ); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::SearchCollection, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 0 ); } #[test] fn test_collection_filter_box_left_right_keys() { - test_text_box_left_right_keys!( - CollectionsHandler, - ActiveRadarrBlock::FilterCollections, - filter + let mut app = App::default(); + app.data.radarr_data.filter = Some("Test".into()); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::FilterCollections, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .offset + .borrow(), + 1 + ); + + CollectionsHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::FilterCollections, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .offset + .borrow(), + 0 ); } } @@ -234,7 +378,7 @@ mod tests { Collection, HorizontallyScrollableText )); - app.data.radarr_data.search = "Test 2".to_owned().into(); + app.data.radarr_data.search = Some("Test 2".into()); CollectionsHandler::with( &SUBMIT_KEY, @@ -267,7 +411,7 @@ mod tests { Collection, HorizontallyScrollableText )); - app.data.radarr_data.search = "Test 2".to_owned().into(); + app.data.radarr_data.search = Some("Test 2".into()); CollectionsHandler::with( &SUBMIT_KEY, @@ -300,7 +444,7 @@ mod tests { Collection, HorizontallyScrollableText )); - app.data.radarr_data.filter = "Test".to_owned().into(); + app.data.radarr_data.filter = Some("Test".into()); CollectionsHandler::with( &SUBMIT_KEY, @@ -507,6 +651,7 @@ mod tests { ); assert!(app.data.radarr_data.is_searching); assert!(app.should_ignore_quit_key); + assert!(app.data.radarr_data.search.is_some()); } #[test] @@ -527,6 +672,7 @@ mod tests { ); assert!(app.data.radarr_data.is_filtering); assert!(app.should_ignore_quit_key); + assert!(app.data.radarr_data.filter.is_some()); } #[test] @@ -564,7 +710,7 @@ mod tests { #[test] fn test_search_collections_box_backspace_key() { let mut app = App::default(); - app.data.radarr_data.search = "Test".to_owned().into(); + app.data.radarr_data.search = Some("Test".into()); CollectionsHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -574,13 +720,13 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.text, "Tes"); + assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "Tes"); } #[test] fn test_filter_collections_box_backspace_key() { let mut app = App::default(); - app.data.radarr_data.filter = "Test".to_owned().into(); + app.data.radarr_data.filter = Some("Test".into()); CollectionsHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -590,12 +736,13 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.filter.text, "Tes"); + assert_str_eq!(app.data.radarr_data.filter.as_ref().unwrap().text, "Tes"); } #[test] fn test_search_collections_box_char_key() { let mut app = App::default(); + app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); CollectionsHandler::with( &Key::Char('h'), @@ -605,12 +752,13 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.text, "h"); + assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "h"); } #[test] fn test_filter_collections_box_char_key() { let mut app = App::default(); + app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); CollectionsHandler::with( &Key::Char('h'), @@ -620,7 +768,7 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.filter.text, "h"); + assert_str_eq!(app.data.radarr_data.filter.as_ref().unwrap().text, "h"); } } diff --git a/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs b/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs index fed1ec8..692f85d 100644 --- a/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs +++ b/src/handlers/radarr_handlers/collections/edit_collection_handler_tests.rs @@ -445,7 +445,10 @@ mod tests { fn test_edit_collection_root_folder_path_input_submit() { let mut app = App::default(); app.should_ignore_quit_key = true; - app.data.radarr_data.edit_path = "Test Path".to_owned().into(); + app.data.radarr_data.edit_collection_modal = Some(EditCollectionModal { + path: "Test Path".into(), + ..EditCollectionModal::default() + }); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionRootFolderPathInput.into()); @@ -458,7 +461,15 @@ mod tests { .handle(); assert!(!app.should_ignore_quit_key); - assert!(!app.data.radarr_data.edit_path.text.is_empty()); + assert!(!app + .data + .radarr_data + .edit_collection_modal + .as_ref() + .unwrap() + .path + .text + .is_empty()); assert_eq!( app.get_current_route(), &ActiveRadarrBlock::EditCollectionPrompt.into() diff --git a/src/handlers/radarr_handlers/collections/mod.rs b/src/handlers/radarr_handlers/collections/mod.rs index 5350626..54ce297 100644 --- a/src/handlers/radarr_handlers/collections/mod.rs +++ b/src/handlers/radarr_handlers/collections/mod.rs @@ -10,7 +10,7 @@ use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, COLLECTIONS_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, }; -use crate::models::{BlockSelectionState, Scrollable}; +use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable}; use crate::network::radarr_network::RadarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; @@ -122,8 +122,22 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' self.app.data.radarr_data.collections.scroll_to_top() } } - ActiveRadarrBlock::SearchCollection => self.app.data.radarr_data.search.scroll_home(), - ActiveRadarrBlock::FilterCollections => self.app.data.radarr_data.filter.scroll_home(), + ActiveRadarrBlock::SearchCollection => self + .app + .data + .radarr_data + .search + .as_mut() + .unwrap() + .scroll_home(), + ActiveRadarrBlock::FilterCollections => self + .app + .data + .radarr_data + .filter + .as_mut() + .unwrap() + .scroll_home(), _ => (), } } @@ -149,8 +163,22 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' self.app.data.radarr_data.collections.scroll_to_bottom() } } - ActiveRadarrBlock::SearchCollection => self.app.data.radarr_data.search.reset_offset(), - ActiveRadarrBlock::FilterCollections => self.app.data.radarr_data.filter.reset_offset(), + ActiveRadarrBlock::SearchCollection => self + .app + .data + .radarr_data + .search + .as_mut() + .unwrap() + .reset_offset(), + ActiveRadarrBlock::FilterCollections => self + .app + .data + .radarr_data + .filter + .as_mut() + .unwrap() + .reset_offset(), _ => (), } } @@ -162,10 +190,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' ActiveRadarrBlock::Collections => handle_change_tab_left_right_keys(self.app, self.key), ActiveRadarrBlock::UpdateAllCollectionsPrompt => handle_prompt_toggle(self.app, self.key), ActiveRadarrBlock::SearchCollection => { - handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search) + handle_text_box_left_right_keys!( + self, + self.key, + self.app.data.radarr_data.search.as_mut().unwrap() + ) } ActiveRadarrBlock::FilterCollections => { - handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.filter) + handle_text_box_left_right_keys!( + self, + self.key, + self.app.data.radarr_data.filter.as_mut().unwrap() + ) } _ => (), } @@ -269,6 +305,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' self .app .push_navigation_stack(ActiveRadarrBlock::SearchCollection.into()); + self.app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.is_searching = true; self.app.should_ignore_quit_key = true; } @@ -276,6 +313,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' self .app .push_navigation_stack(ActiveRadarrBlock::FilterCollections.into()); + self.app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.is_filtering = true; self.app.should_ignore_quit_key = true; } @@ -287,7 +325,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' ) .into(), ); - self.app.data.radarr_data.edit_collection_modal = Some((&self.app.data.radarr_data).into()); + self.app.data.radarr_data.edit_collection_modal = + Some((&self.app.data.radarr_data).into()); self.app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); } @@ -302,10 +341,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionsHandler<' _ => (), }, ActiveRadarrBlock::SearchCollection => { - handle_text_box_keys!(self, key, self.app.data.radarr_data.search) + handle_text_box_keys!( + self, + key, + self.app.data.radarr_data.search.as_mut().unwrap() + ) } ActiveRadarrBlock::FilterCollections => { - handle_text_box_keys!(self, key, self.app.data.radarr_data.filter) + handle_text_box_keys!( + self, + key, + self.app.data.radarr_data.filter.as_mut().unwrap() + ) } _ => (), } diff --git a/src/handlers/radarr_handlers/library/add_movie_handler.rs b/src/handlers/radarr_handlers/library/add_movie_handler.rs index 3ab212f..9f00e66 100644 --- a/src/handlers/radarr_handlers/library/add_movie_handler.rs +++ b/src/handlers/radarr_handlers/library/add_movie_handler.rs @@ -177,7 +177,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, .unwrap() .root_folder_list .scroll_to_top(), - ActiveRadarrBlock::AddMovieSearchInput => self.app.data.radarr_data.search.scroll_home(), + ActiveRadarrBlock::AddMovieSearchInput => self + .app + .data + .radarr_data + .search + .as_mut() + .unwrap() + .scroll_home(), ActiveRadarrBlock::AddMovieTagsInput => self .app .data @@ -235,7 +242,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, .unwrap() .root_folder_list .scroll_to_bottom(), - ActiveRadarrBlock::AddMovieSearchInput => self.app.data.radarr_data.search.reset_offset(), + ActiveRadarrBlock::AddMovieSearchInput => self + .app + .data + .radarr_data + .search + .as_mut() + .unwrap() + .reset_offset(), ActiveRadarrBlock::AddMovieTagsInput => self .app .data @@ -255,7 +269,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, match self.active_radarr_block { ActiveRadarrBlock::AddMoviePrompt => handle_prompt_toggle(self.app, self.key), ActiveRadarrBlock::AddMovieSearchInput => { - handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search) + handle_text_box_left_right_keys!( + self, + self.key, + self.app.data.radarr_data.search.as_mut().unwrap() + ) } ActiveRadarrBlock::AddMovieTagsInput => { handle_text_box_left_right_keys!( @@ -278,7 +296,15 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, fn handle_submit(&mut self) { match self.active_radarr_block { _ if *self.active_radarr_block == ActiveRadarrBlock::AddMovieSearchInput - && !self.app.data.radarr_data.search.text.is_empty() => + && !self + .app + .data + .radarr_data + .search + .as_mut() + .unwrap() + .text + .is_empty() => { self .app @@ -402,7 +428,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, let key = self.key; match self.active_radarr_block { ActiveRadarrBlock::AddMovieSearchInput => { - handle_text_box_keys!(self, key, self.app.data.radarr_data.search) + handle_text_box_keys!( + self, + key, + self.app.data.radarr_data.search.as_mut().unwrap() + ) } ActiveRadarrBlock::AddMovieTagsInput => { handle_text_box_keys!( diff --git a/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs b/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs index 4cdd1b6..958204d 100644 --- a/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/add_movie_handler_tests.rs @@ -303,9 +303,7 @@ mod tests { use strum::IntoEnumIterator; use crate::models::servarr_data::radarr::modals::AddMovieModal; - use crate::{ - extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys, - }; + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use super::*; @@ -546,10 +544,47 @@ mod tests { #[test] fn test_add_movie_search_input_home_end_keys() { - test_text_box_home_end_keys!( - AddMovieHandler, - ActiveRadarrBlock::AddMovieSearchInput, - search + let mut app = App::default(); + app.data.radarr_data.search = Some("Test".into()); + + AddMovieHandler::with( + &DEFAULT_KEYBINDINGS.home.key, + &mut app, + &ActiveRadarrBlock::AddMovieSearchInput, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 4 + ); + + AddMovieHandler::with( + &DEFAULT_KEYBINDINGS.end.key, + &mut app, + &ActiveRadarrBlock::AddMovieSearchInput, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 0 ); } @@ -609,8 +644,6 @@ mod tests { use crate::models::servarr_data::radarr::modals::AddMovieModal; use rstest::rstest; - use crate::test_text_box_left_right_keys; - use super::*; #[rstest] @@ -628,10 +661,47 @@ mod tests { #[test] fn test_add_movie_search_input_left_right_keys() { - test_text_box_left_right_keys!( - AddMovieHandler, - ActiveRadarrBlock::AddMovieSearchInput, - search + let mut app = App::default(); + app.data.radarr_data.search = Some("Test".into()); + + AddMovieHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::AddMovieSearchInput, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 1 + ); + + AddMovieHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::AddMovieSearchInput, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 0 ); } @@ -706,7 +776,7 @@ mod tests { fn test_add_movie_search_input_submit() { let mut app = App::default(); app.should_ignore_quit_key = true; - app.data.radarr_data.search = "test".into(); + app.data.radarr_data.search = Some("test".into()); AddMovieHandler::with( &SUBMIT_KEY, @@ -726,6 +796,7 @@ mod tests { #[test] fn test_add_movie_search_input_submit_noop_on_empty_search() { let mut app = App::default(); + app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); app.push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); app.should_ignore_quit_key = true; @@ -1191,7 +1262,7 @@ mod tests { #[test] fn test_add_movie_search_input_backspace() { let mut app = App::default(); - app.data.radarr_data.search = "Test".to_owned().into(); + app.data.radarr_data.search = Some("Test".into()); AddMovieHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -1201,7 +1272,7 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.text, "Tes"); + assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "Tes"); } #[test] @@ -1236,6 +1307,7 @@ mod tests { #[test] fn test_add_movie_search_input_char_key() { let mut app = App::default(); + app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); AddMovieHandler::with( &Key::Char('h'), @@ -1245,7 +1317,7 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.text, "h"); + assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "h"); } #[test] diff --git a/src/handlers/radarr_handlers/library/library_handler_tests.rs b/src/handlers/radarr_handlers/library/library_handler_tests.rs index 924f877..76f82c3 100644 --- a/src/handlers/radarr_handlers/library/library_handler_tests.rs +++ b/src/handlers/radarr_handlers/library/library_handler_tests.rs @@ -48,9 +48,7 @@ mod tests { mod test_handle_home_end { use pretty_assertions::assert_eq; - use crate::{ - extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys, - }; + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use super::*; @@ -78,12 +76,94 @@ mod tests { #[test] fn test_movie_search_box_home_end_keys() { - test_text_box_home_end_keys!(LibraryHandler, ActiveRadarrBlock::SearchMovie, search); + let mut app = App::default(); + app.data.radarr_data.search = Some("Test".into()); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.home.key, + &mut app, + &ActiveRadarrBlock::SearchMovie, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 4 + ); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.end.key, + &mut app, + &ActiveRadarrBlock::SearchMovie, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 0 + ); } #[test] fn test_movie_filter_box_home_end_keys() { - test_text_box_home_end_keys!(LibraryHandler, ActiveRadarrBlock::FilterMovies, filter); + let mut app = App::default(); + app.data.radarr_data.filter = Some("Test".into()); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.home.key, + &mut app, + &ActiveRadarrBlock::FilterMovies, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .offset + .borrow(), + 4 + ); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.end.key, + &mut app, + &ActiveRadarrBlock::FilterMovies, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .offset + .borrow(), + 0 + ); } } @@ -118,8 +198,6 @@ mod tests { use pretty_assertions::assert_eq; use rstest::rstest; - use crate::test_text_box_left_right_keys; - use super::*; #[test] @@ -194,12 +272,94 @@ mod tests { #[test] fn test_movie_search_box_left_right_keys() { - test_text_box_left_right_keys!(LibraryHandler, ActiveRadarrBlock::SearchMovie, search); + let mut app = App::default(); + app.data.radarr_data.search = Some("Test".into()); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::SearchMovie, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 1 + ); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::SearchMovie, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + 0 + ); } #[test] fn test_movie_filter_box_left_right_keys() { - test_text_box_left_right_keys!(LibraryHandler, ActiveRadarrBlock::FilterMovies, filter); + let mut app = App::default(); + app.data.radarr_data.filter = Some("Test".into()); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::FilterMovies, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .offset + .borrow(), + 1 + ); + + LibraryHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::FilterMovies, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .offset + .borrow(), + 0 + ); } } @@ -236,7 +396,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.search = "Test 2".to_owned().into(); + app.data.radarr_data.search = Some("Test 2".into()); LibraryHandler::with( &SUBMIT_KEY, @@ -263,7 +423,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.search = "Test 2".to_owned().into(); + app.data.radarr_data.search = Some("Test 2".into()); LibraryHandler::with( &SUBMIT_KEY, @@ -296,7 +456,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.filter = "Test".to_owned().into(); + app.data.radarr_data.filter = Some("Test".into()); LibraryHandler::with( &SUBMIT_KEY, @@ -473,6 +633,7 @@ mod tests { ); assert!(app.data.radarr_data.is_searching); assert!(app.should_ignore_quit_key); + assert!(app.data.radarr_data.search.is_some()); } #[test] @@ -493,10 +654,11 @@ mod tests { ); assert!(app.data.radarr_data.is_filtering); assert!(app.should_ignore_quit_key); + assert!(app.data.radarr_data.filter.is_some()); } #[test] - fn test_movie_add() { + fn test_movie_add_key() { let mut app = App::default(); LibraryHandler::with( @@ -512,6 +674,7 @@ mod tests { &ActiveRadarrBlock::AddMovieSearchInput.into() ); assert!(app.should_ignore_quit_key); + assert!(app.data.radarr_data.search.is_some()); } #[test] @@ -549,7 +712,7 @@ mod tests { #[test] fn test_search_movies_box_backspace_key() { let mut app = App::default(); - app.data.radarr_data.search = "Test".to_owned().into(); + app.data.radarr_data.search = Some("Test".into()); LibraryHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -559,13 +722,13 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.text, "Tes"); + assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "Tes"); } #[test] fn test_filter_movies_box_backspace_key() { let mut app = App::default(); - app.data.radarr_data.filter = "Test".to_owned().into(); + app.data.radarr_data.filter = Some("Test".into()); LibraryHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -575,12 +738,13 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.filter.text, "Tes"); + assert_str_eq!(app.data.radarr_data.filter.as_ref().unwrap().text, "Tes"); } #[test] fn test_search_movies_box_char_key() { let mut app = App::default(); + app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); LibraryHandler::with( &Key::Char('h'), @@ -590,12 +754,13 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.search.text, "h"); + assert_str_eq!(app.data.radarr_data.search.as_ref().unwrap().text, "h"); } #[test] fn test_filter_movies_box_char_key() { let mut app = App::default(); + app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); LibraryHandler::with( &Key::Char('h'), @@ -605,7 +770,7 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.filter.text, "h"); + assert_str_eq!(app.data.radarr_data.filter.as_ref().unwrap().text, "h"); } } diff --git a/src/handlers/radarr_handlers/library/mod.rs b/src/handlers/radarr_handlers/library/mod.rs index 7da2949..185ca33 100644 --- a/src/handlers/radarr_handlers/library/mod.rs +++ b/src/handlers/radarr_handlers/library/mod.rs @@ -12,7 +12,7 @@ use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, DELETE_MOVIE_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, LIBRARY_BLOCKS, }; -use crate::models::{BlockSelectionState, Scrollable}; +use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable}; use crate::network::radarr_network::RadarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; @@ -109,10 +109,24 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' } } ActiveRadarrBlock::SearchMovie => { - self.app.data.radarr_data.search.scroll_home(); + self + .app + .data + .radarr_data + .search + .as_mut() + .unwrap() + .scroll_home(); } ActiveRadarrBlock::FilterMovies => { - self.app.data.radarr_data.filter.scroll_home(); + self + .app + .data + .radarr_data + .filter + .as_mut() + .unwrap() + .scroll_home(); } _ => (), } @@ -127,8 +141,22 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' self.app.data.radarr_data.movies.scroll_to_bottom() } } - ActiveRadarrBlock::SearchMovie => self.app.data.radarr_data.search.reset_offset(), - ActiveRadarrBlock::FilterMovies => self.app.data.radarr_data.filter.reset_offset(), + ActiveRadarrBlock::SearchMovie => self + .app + .data + .radarr_data + .search + .as_mut() + .unwrap() + .reset_offset(), + ActiveRadarrBlock::FilterMovies => self + .app + .data + .radarr_data + .filter + .as_mut() + .unwrap() + .reset_offset(), _ => (), } } @@ -148,10 +176,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' ActiveRadarrBlock::Movies => handle_change_tab_left_right_keys(self.app, self.key), ActiveRadarrBlock::UpdateAllMoviesPrompt => handle_prompt_toggle(self.app, self.key), ActiveRadarrBlock::SearchMovie => { - handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.search) + handle_text_box_left_right_keys!( + self, + self.key, + self.app.data.radarr_data.search.as_mut().unwrap() + ) } ActiveRadarrBlock::FilterMovies => { - handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.filter) + handle_text_box_left_right_keys!( + self, + self.key, + self.app.data.radarr_data.filter.as_mut().unwrap() + ) } _ => (), } @@ -248,6 +284,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' self .app .push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); + self.app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.is_searching = true; self.app.should_ignore_quit_key = true; } @@ -255,6 +292,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' self .app .push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + self.app.data.radarr_data.filter = Some(HorizontallyScrollableText::default()); self.app.data.radarr_data.is_filtering = true; self.app.should_ignore_quit_key = true; } @@ -274,6 +312,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' self .app .push_navigation_stack(ActiveRadarrBlock::AddMovieSearchInput.into()); + self.app.data.radarr_data.search = Some(HorizontallyScrollableText::default()); self.app.should_ignore_quit_key = true; } _ if *key == DEFAULT_KEYBINDINGS.update.key => { @@ -287,10 +326,18 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for LibraryHandler<'a, ' _ => (), }, ActiveRadarrBlock::SearchMovie => { - handle_text_box_keys!(self, key, self.app.data.radarr_data.search) + handle_text_box_keys!( + self, + key, + self.app.data.radarr_data.search.as_mut().unwrap() + ) } ActiveRadarrBlock::FilterMovies => { - handle_text_box_keys!(self, key, self.app.data.radarr_data.filter) + handle_text_box_keys!( + self, + key, + self.app.data.radarr_data.filter.as_mut().unwrap() + ) } _ => (), } diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index 3c8a26c..4cf1f90 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -104,10 +104,25 @@ pub fn search_table(app: &mut App<'_>, rows: &[T], field_selection_fn: F) where F: Fn(&T) -> &str, { - let search_string = app.data.radarr_data.search.drain().to_lowercase(); - let search_index = rows.iter().position(|item| { - strip_non_search_characters(field_selection_fn(item)).contains(&search_string) - }); + let search_index = if app.data.radarr_data.search.is_some() { + let search_string = app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .text + .clone() + .to_lowercase(); + + app.data.radarr_data.search = None; + + rows.iter().position(|item| { + strip_non_search_characters(field_selection_fn(item)).contains(&search_string) + }) + } else { + None + }; app.data.radarr_data.is_searching = false; app.should_ignore_quit_key = false; @@ -124,17 +139,42 @@ where F: Fn(&T) -> &str, T: Clone, { - let filter = strip_non_search_characters(&app.data.radarr_data.filter.drain()); - let filter_matches: Vec = rows - .iter() - .filter(|&item| strip_non_search_characters(field_selection_fn(item)).contains(&filter)) - .cloned() - .collect(); + let empty_filter = app.data.radarr_data.filter.is_some() + && app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .text + .is_empty(); + let filter_matches = if app.data.radarr_data.filter.is_some() + && !app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .text + .is_empty() + { + let filter = + strip_non_search_characters(&app.data.radarr_data.filter.as_ref().unwrap().text.clone()); + rows + .iter() + .filter(|&item| strip_non_search_characters(field_selection_fn(item)).contains(&filter)) + .cloned() + .collect() + } else { + Vec::new() + }; + + app.data.radarr_data.filter = None; app.data.radarr_data.is_filtering = false; app.should_ignore_quit_key = false; - if !filter_matches.is_empty() { + if !filter_matches.is_empty() || empty_filter { app.pop_navigation_stack(); } diff --git a/src/handlers/radarr_handlers/radarr_handler_tests.rs b/src/handlers/radarr_handlers/radarr_handler_tests.rs index c7339be..2a13194 100644 --- a/src/handlers/radarr_handlers/radarr_handler_tests.rs +++ b/src/handlers/radarr_handlers/radarr_handler_tests.rs @@ -26,7 +26,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.search = "Test 2".to_owned().into(); + app.data.radarr_data.search = Some("Test 2".into()); app.data.radarr_data.is_searching = true; app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); @@ -39,7 +39,7 @@ mod tests { assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert!(!app.data.radarr_data.is_searching); assert!(!app.should_ignore_quit_key); - assert!(app.data.radarr_data.search.text.is_empty()); + assert!(app.data.radarr_data.search.is_none()); } #[test] @@ -53,7 +53,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.search = "Test 5".to_owned().into(); + app.data.radarr_data.search = Some("Test 5".into()); app.data.radarr_data.is_searching = true; app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::SearchMovie.into()); @@ -69,7 +69,7 @@ mod tests { ); assert!(!app.data.radarr_data.is_searching); assert!(!app.should_ignore_quit_key); - assert!(app.data.radarr_data.search.text.is_empty()); + assert!(app.data.radarr_data.search.is_none()); } #[test] @@ -83,8 +83,8 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.filter = "Test 2".to_owned().into(); - app.data.radarr_data.is_searching = true; + app.data.radarr_data.filter = Some("Test 2".into()); + app.data.radarr_data.is_filtering = true; app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); @@ -97,7 +97,7 @@ mod tests { assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); assert!(!app.data.radarr_data.is_filtering); assert!(!app.should_ignore_quit_key); - assert!(app.data.radarr_data.filter.text.is_empty()); + assert!(app.data.radarr_data.filter.is_none()); } #[test] @@ -111,7 +111,7 @@ mod tests { Movie, HorizontallyScrollableText )); - app.data.radarr_data.filter = "Test 5".to_owned().into(); + app.data.radarr_data.filter = Some("Test 5".into()); app.data.radarr_data.is_filtering = true; app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); @@ -125,9 +125,65 @@ mod tests { app.get_current_route(), &ActiveRadarrBlock::FilterMovies.into() ); - assert!(!app.data.radarr_data.is_searching); + assert!(!app.data.radarr_data.is_filtering); assert!(!app.should_ignore_quit_key); - assert!(app.data.radarr_data.filter.text.is_empty()); + assert!(app.data.radarr_data.filter.is_none()); + } + + #[test] + fn test_filter_table_reset_and_pop_navigation_on_empty_filter() { + let mut app = App::default(); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.filter = Some("".into()); + app.data.radarr_data.is_filtering = true; + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + + let movies = &app.data.radarr_data.movies.items.clone(); + + let filter_matches = filter_table(&mut app, movies, |movie| &movie.title.text); + + assert!(filter_matches.is_empty()); + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Movies.into()); + assert!(!app.data.radarr_data.is_filtering); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.filter.is_none()); + } + + #[test] + fn test_filter_table_noop_on_none_filter() { + let mut app = App::default(); + app + .data + .radarr_data + .movies + .set_items(extended_stateful_iterable_vec!( + Movie, + HorizontallyScrollableText + )); + app.data.radarr_data.is_filtering = true; + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::FilterMovies.into()); + + let movies = &app.data.radarr_data.movies.items.clone(); + + let filter_matches = filter_table(&mut app, movies, |movie| &movie.title.text); + + assert!(filter_matches.is_empty()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::FilterMovies.into() + ); + assert!(!app.data.radarr_data.is_filtering); + assert!(!app.should_ignore_quit_key); + assert!(app.data.radarr_data.filter.is_none()); } #[rstest] diff --git a/src/handlers/radarr_handlers/root_folders/mod.rs b/src/handlers/radarr_handlers/root_folders/mod.rs index cfa21c9..2227711 100644 --- a/src/handlers/radarr_handlers/root_folders/mod.rs +++ b/src/handlers/radarr_handlers/root_folders/mod.rs @@ -57,7 +57,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' fn handle_home(&mut self) { match self.active_radarr_block { ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_top(), - ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.scroll_home(), + ActiveRadarrBlock::AddRootFolderPrompt => self + .app + .data + .radarr_data + .edit_root_folder + .as_mut() + .unwrap() + .scroll_home(), _ => (), } } @@ -65,7 +72,14 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' fn handle_end(&mut self) { match self.active_radarr_block { ActiveRadarrBlock::RootFolders => self.app.data.radarr_data.root_folders.scroll_to_bottom(), - ActiveRadarrBlock::AddRootFolderPrompt => self.app.data.radarr_data.edit_path.reset_offset(), + ActiveRadarrBlock::AddRootFolderPrompt => self + .app + .data + .radarr_data + .edit_root_folder + .as_mut() + .unwrap() + .reset_offset(), _ => (), } } @@ -83,7 +97,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' ActiveRadarrBlock::RootFolders => handle_change_tab_left_right_keys(self.app, self.key), ActiveRadarrBlock::DeleteRootFolderPrompt => handle_prompt_toggle(self.app, self.key), ActiveRadarrBlock::AddRootFolderPrompt => { - handle_text_box_left_right_keys!(self, self.key, self.app.data.radarr_data.edit_path) + handle_text_box_left_right_keys!( + self, + self.key, + self.app.data.radarr_data.edit_root_folder.as_mut().unwrap() + ) } _ => (), } @@ -98,7 +116,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' self.app.pop_navigation_stack(); } - ActiveRadarrBlock::AddRootFolderPrompt => { + _ if *self.active_radarr_block == ActiveRadarrBlock::AddRootFolderPrompt + && !self + .app + .data + .radarr_data + .edit_root_folder + .as_ref() + .unwrap() + .text + .is_empty() => + { self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddRootFolder); self.app.data.radarr_data.prompt_confirm = true; self.app.should_ignore_quit_key = false; @@ -112,7 +140,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' match self.active_radarr_block { ActiveRadarrBlock::AddRootFolderPrompt => { self.app.pop_navigation_stack(); - self.app.data.radarr_data.edit_path = HorizontallyScrollableText::default(); + self.app.data.radarr_data.edit_root_folder = None; self.app.data.radarr_data.prompt_confirm = false; self.app.should_ignore_quit_key = false; } @@ -135,12 +163,17 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RootFoldersHandler<' self .app .push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); + self.app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); self.app.should_ignore_quit_key = true; } _ => (), }, ActiveRadarrBlock::AddRootFolderPrompt => { - handle_text_box_keys!(self, key, self.app.data.radarr_data.edit_path) + handle_text_box_keys!( + self, + key, + self.app.data.radarr_data.edit_root_folder.as_mut().unwrap() + ) } _ => (), } diff --git a/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs b/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs index 5c35f3e..60631f6 100644 --- a/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs +++ b/src/handlers/radarr_handlers/root_folders/root_folders_handler_tests.rs @@ -34,9 +34,7 @@ mod tests { use pretty_assertions::assert_eq; use crate::models::radarr_models::RootFolder; - use crate::{ - extended_stateful_iterable_vec, test_iterable_home_and_end, test_text_box_home_end_keys, - }; + use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use super::*; @@ -52,10 +50,47 @@ mod tests { #[test] fn test_add_root_folder_prompt_home_end_keys() { - test_text_box_home_end_keys!( - RootFoldersHandler, - ActiveRadarrBlock::AddRootFolderPrompt, - edit_path + let mut app = App::default(); + app.data.radarr_data.edit_root_folder = Some("Test".into()); + + RootFoldersHandler::with( + &DEFAULT_KEYBINDINGS.home.key, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .edit_root_folder + .as_ref() + .unwrap() + .offset + .borrow(), + 4 + ); + + RootFoldersHandler::with( + &DEFAULT_KEYBINDINGS.end.key, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .edit_root_folder + .as_ref() + .unwrap() + .offset + .borrow(), + 0 ); } } @@ -83,8 +118,6 @@ mod tests { use pretty_assertions::assert_eq; use rstest::rstest; - use crate::test_text_box_left_right_keys; - use super::*; #[test] @@ -159,10 +192,47 @@ mod tests { #[test] fn test_add_root_folder_prompt_left_right_keys() { - test_text_box_left_right_keys!( - RootFoldersHandler, - ActiveRadarrBlock::AddRootFolderPrompt, - edit_path + let mut app = App::default(); + app.data.radarr_data.edit_root_folder = Some("Test".into()); + + RootFoldersHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .edit_root_folder + .as_ref() + .unwrap() + .offset + .borrow(), + 1 + ); + + RootFoldersHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .edit_root_folder + .as_ref() + .unwrap() + .offset + .borrow(), + 0 ); } } @@ -179,6 +249,7 @@ mod tests { #[test] fn test_add_root_folder_prompt_confirm_submit() { let mut app = App::default(); + app.data.radarr_data.edit_root_folder = Some("Test".into()); app.data.radarr_data.prompt_confirm = true; app.should_ignore_quit_key = true; app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); @@ -204,6 +275,32 @@ mod tests { ); } + #[test] + fn test_add_root_folder_prompt_confirm_submit_noop_on_empty_folder() { + let mut app = App::default(); + app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); + app.data.radarr_data.prompt_confirm = false; + app.should_ignore_quit_key = true; + app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); + app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); + + RootFoldersHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::AddRootFolderPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + assert!(app.should_ignore_quit_key); + assert!(app.data.radarr_data.prompt_confirm_action.is_none()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::AddRootFolderPrompt.into() + ); + } + #[test] fn test_delete_root_folder_prompt_confirm_submit() { let mut app = App::default(); @@ -287,7 +384,7 @@ mod tests { let mut app = App::default(); app.push_navigation_stack(ActiveRadarrBlock::RootFolders.into()); app.push_navigation_stack(ActiveRadarrBlock::AddRootFolderPrompt.into()); - app.data.radarr_data.edit_path = HorizontallyScrollableText::from("/nfs/test"); + app.data.radarr_data.edit_root_folder = Some("/nfs/test".into()); app.should_ignore_quit_key = true; RootFoldersHandler::with( @@ -303,7 +400,7 @@ mod tests { &ActiveRadarrBlock::RootFolders.into() ); - assert!(app.data.radarr_data.edit_path.text.is_empty()); + assert!(app.data.radarr_data.edit_root_folder.is_none()); assert!(!app.data.radarr_data.prompt_confirm); assert!(!app.should_ignore_quit_key); } @@ -349,6 +446,7 @@ mod tests { &ActiveRadarrBlock::AddRootFolderPrompt.into() ); assert!(app.should_ignore_quit_key); + assert!(app.data.radarr_data.edit_root_folder.is_some()); } #[test] @@ -359,7 +457,7 @@ mod tests { #[test] fn test_add_root_folder_prompt_backspace_key() { let mut app = App::default(); - app.data.radarr_data.edit_path = "/nfs/test".to_owned().into(); + app.data.radarr_data.edit_root_folder = Some("/nfs/test".into()); RootFoldersHandler::with( &DEFAULT_KEYBINDINGS.backspace.key, @@ -369,12 +467,16 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.edit_path.text, "/nfs/tes"); + assert_str_eq!( + app.data.radarr_data.edit_root_folder.as_ref().unwrap().text, + "/nfs/tes" + ); } #[test] fn test_add_root_folder_prompt_char_key() { let mut app = App::default(); + app.data.radarr_data.edit_root_folder = Some(HorizontallyScrollableText::default()); RootFoldersHandler::with( &Key::Char('h'), @@ -384,7 +486,10 @@ mod tests { ) .handle(); - assert_str_eq!(app.data.radarr_data.edit_path.text, "h"); + assert_str_eq!( + app.data.radarr_data.edit_root_folder.as_ref().unwrap().text, + "h" + ); } } diff --git a/src/models/mod.rs b/src/models/mod.rs index f35768d..a9d7fd4 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -264,11 +264,6 @@ impl HorizontallyScrollableText { } } - pub fn drain(&mut self) -> String { - self.reset_offset(); - self.text.drain(..).collect() - } - pub fn pop(&mut self) { if *self.offset.borrow() < self.text.len() { self diff --git a/src/models/model_tests.rs b/src/models/model_tests.rs index 4584ada..9845661 100644 --- a/src/models/model_tests.rs +++ b/src/models/model_tests.rs @@ -440,16 +440,6 @@ mod tests { assert_eq!(*horizontally_scrollable_test.offset.borrow(), 0); } - #[test] - fn test_horizontally_scrollable_text_drain() { - let test_text = "Test string"; - let mut horizontally_scrollable_text = HorizontallyScrollableText::from(test_text); - - assert_str_eq!(horizontally_scrollable_text.drain(), test_text); - assert!(horizontally_scrollable_text.text.is_empty()); - assert_eq!(*horizontally_scrollable_text.offset.borrow(), 0); - } - #[test] fn test_horizontally_scrollable_text_pop() { let test_text = "Test string"; diff --git a/src/models/servarr_data/radarr/modals_tests.rs b/src/models/servarr_data/radarr/modals_tests.rs index bc4f27d..4369d87 100644 --- a/src/models/servarr_data/radarr/modals_tests.rs +++ b/src/models/servarr_data/radarr/modals_tests.rs @@ -6,7 +6,7 @@ mod test { }; use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data; use crate::models::servarr_data::radarr::radarr_data::RadarrData; - use crate::models::{HorizontallyScrollableText, StatefulTable}; + use crate::models::StatefulTable; use bimap::BiMap; use pretty_assertions::{assert_eq, assert_str_eq}; use rstest::rstest; @@ -16,9 +16,6 @@ mod test { #[rstest] fn test_edit_movie_modal_from_radarr_data(#[values(true, false)] test_filtered_movies: bool) { let mut radarr_data = RadarrData { - edit_path: HorizontallyScrollableText::default(), - edit_tags: HorizontallyScrollableText::default(), - edit_monitored: None, quality_profile_map: BiMap::from_iter([ (2222, "HD - 1080p".to_owned()), (1111, "Any".to_owned()), diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index 9a9d7bf..6901563 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -7,8 +7,7 @@ use crate::app::radarr::radarr_context_clues::{ }; use crate::models::radarr_models::{ AddMovieSearchResult, Collection, CollectionMovie, Credit, DiskSpace, DownloadRecord, Indexer, - IndexerSettings, MinimumAvailability, Monitor, Movie, MovieHistoryItem, QueueEvent, Release, - ReleaseField, RootFolder, Task, + IndexerSettings, Movie, MovieHistoryItem, QueueEvent, Release, ReleaseField, RootFolder, Task, }; use crate::models::servarr_data::radarr::modals::{ AddMovieModal, EditCollectionModal, EditMovieModal, @@ -38,10 +37,6 @@ pub struct RadarrData<'a> { pub movies: StatefulTable, pub filtered_movies: StatefulTable, pub add_searched_movies: StatefulTable, - pub monitor_list: StatefulList, - pub minimum_availability_list: StatefulList, - pub quality_profile_list: StatefulList, - pub root_folder_list: StatefulList, pub selected_block: BlockSelectionState<'a, ActiveRadarrBlock>, pub downloads: StatefulTable, pub indexers: StatefulTable, @@ -68,15 +63,12 @@ pub struct RadarrData<'a> { pub prompt_confirm_action: Option, pub main_tabs: TabState, pub movie_info_tabs: TabState, - pub search: HorizontallyScrollableText, - pub filter: HorizontallyScrollableText, + pub search: Option, + pub filter: Option, pub add_movie_modal: Option, pub edit_movie_modal: Option, pub edit_collection_modal: Option, - pub edit_path: HorizontallyScrollableText, - pub edit_tags: HorizontallyScrollableText, - pub edit_monitored: Option, - pub edit_search_on_add: Option, + pub edit_root_folder: Option, pub sort_ascending: Option, pub prompt_confirm: bool, pub delete_movie_files: bool, @@ -93,8 +85,8 @@ impl<'a> RadarrData<'a> { pub fn reset_search(&mut self) { self.is_searching = false; - self.search = HorizontallyScrollableText::default(); - self.filter = HorizontallyScrollableText::default(); + self.search = None; + self.filter = None; self.filtered_movies = StatefulTable::default(); self.filtered_collections = StatefulTable::default(); self.add_searched_movies = StatefulTable::default(); @@ -102,7 +94,7 @@ impl<'a> RadarrData<'a> { pub fn reset_filter(&mut self) { self.is_filtering = false; - self.filter = HorizontallyScrollableText::default(); + self.filter = None; self.filtered_movies = StatefulTable::default(); self.filtered_collections = StatefulTable::default(); } @@ -131,10 +123,6 @@ impl<'a> Default for RadarrData<'a> { start_time: DateTime::default(), movies: StatefulTable::default(), add_searched_movies: StatefulTable::default(), - monitor_list: StatefulList::default(), - minimum_availability_list: StatefulList::default(), - quality_profile_list: StatefulList::default(), - root_folder_list: StatefulList::default(), selected_block: BlockSelectionState::default(), filtered_movies: StatefulTable::default(), downloads: StatefulTable::default(), @@ -160,15 +148,12 @@ impl<'a> Default for RadarrData<'a> { queued_events: StatefulTable::default(), updates: ScrollableText::default(), prompt_confirm_action: None, - search: HorizontallyScrollableText::default(), - filter: HorizontallyScrollableText::default(), + search: None, + filter: None, add_movie_modal: None, edit_movie_modal: None, edit_collection_modal: None, - edit_path: HorizontallyScrollableText::default(), - edit_tags: HorizontallyScrollableText::default(), - edit_monitored: None, - edit_search_on_add: None, + edit_root_folder: None, sort_ascending: None, is_searching: false, is_filtering: false, @@ -497,6 +482,7 @@ impl From<(ActiveRadarrBlock, Option)> for Route { } } +#[allow(dead_code)] // Returning to this work tomorrow pub struct EditIndexerSettings { pub allow_hardcoded_subs: bool, pub availability_delay: HorizontallyScrollableText, diff --git a/src/models/servarr_data/radarr/radarr_data_tests.rs b/src/models/servarr_data/radarr/radarr_data_tests.rs index 35d3eab..dd3fe3d 100644 --- a/src/models/servarr_data/radarr/radarr_data_tests.rs +++ b/src/models/servarr_data/radarr/radarr_data_tests.rs @@ -81,10 +81,6 @@ mod tests { assert_eq!(radarr_data.start_time, >::default()); assert!(radarr_data.movies.items.is_empty()); assert!(radarr_data.add_searched_movies.items.is_empty()); - assert!(radarr_data.monitor_list.items.is_empty()); - assert!(radarr_data.minimum_availability_list.items.is_empty()); - assert!(radarr_data.quality_profile_list.items.is_empty()); - assert!(radarr_data.root_folder_list.items.is_empty()); assert_eq!(radarr_data.selected_block, BlockSelectionState::default()); assert!(radarr_data.filtered_movies.items.is_empty()); assert!(radarr_data.downloads.items.is_empty()); @@ -110,15 +106,12 @@ mod tests { assert!(radarr_data.queued_events.items.is_empty()); assert!(radarr_data.updates.get_text().is_empty()); assert!(radarr_data.prompt_confirm_action.is_none()); - assert!(radarr_data.search.text.is_empty()); - assert!(radarr_data.filter.text.is_empty()); + assert!(radarr_data.search.is_none()); + assert!(radarr_data.filter.is_none()); assert!(radarr_data.add_movie_modal.is_none()); assert!(radarr_data.edit_movie_modal.is_none()); assert!(radarr_data.edit_collection_modal.is_none()); - assert!(radarr_data.edit_path.text.is_empty()); - assert!(radarr_data.edit_tags.text.is_empty()); - assert!(radarr_data.edit_monitored.is_none()); - assert!(radarr_data.edit_search_on_add.is_none()); + assert!(radarr_data.edit_root_folder.is_none()); assert!(radarr_data.sort_ascending.is_none()); assert!(!radarr_data.is_searching); assert!(!radarr_data.is_filtering); diff --git a/src/models/servarr_data/radarr/radarr_test_utils.rs b/src/models/servarr_data/radarr/radarr_test_utils.rs index 19024bc..c6d3b4e 100644 --- a/src/models/servarr_data/radarr/radarr_test_utils.rs +++ b/src/models/servarr_data/radarr/radarr_test_utils.rs @@ -1,8 +1,8 @@ #[cfg(test)] pub mod utils { use crate::models::radarr_models::{ - AddMovieSearchResult, Collection, CollectionMovie, Credit, MinimumAvailability, Monitor, Movie, - MovieHistoryItem, Release, ReleaseField, RootFolder, + AddMovieSearchResult, Collection, CollectionMovie, Credit, Movie, MovieHistoryItem, Release, + ReleaseField, }; use crate::models::servarr_data::radarr::radarr_data::RadarrData; use crate::models::{HorizontallyScrollableText, ScrollableText}; @@ -13,12 +13,9 @@ pub mod utils { is_filtering: true, delete_movie_files: true, add_list_exclusion: true, - search: "test search".to_owned().into(), - filter: "test filter".to_owned().into(), - edit_path: "test path".to_owned().into(), - edit_tags: "usenet, test".to_owned().into(), - edit_monitored: Some(true), - edit_search_on_add: Some(true), + search: Some("test search".into()), + filter: Some("test filter".into()), + edit_root_folder: Some("test path".into()), file_details: "test file details".to_owned(), audio_details: "test audio details".to_owned(), video_details: "test video details".to_owned(), @@ -34,16 +31,6 @@ pub mod utils { .movie_releases .set_items(vec![Release::default()]); radarr_data.movie_info_tabs.index = 1; - radarr_data.monitor_list.set_items(vec![Monitor::default()]); - radarr_data - .minimum_availability_list - .set_items(vec![MinimumAvailability::default()]); - radarr_data - .quality_profile_list - .set_items(vec![String::default()]); - radarr_data - .root_folder_list - .set_items(vec![RootFolder::default()]); radarr_data .movie_releases_sort .set_items(vec![ReleaseField::default()]); @@ -71,8 +58,8 @@ pub mod utils { macro_rules! assert_search_reset { ($radarr_data:expr) => { assert!(!$radarr_data.is_searching); - assert!($radarr_data.search.text.is_empty()); - assert!($radarr_data.filter.text.is_empty()); + assert!($radarr_data.search.is_none()); + assert!($radarr_data.filter.is_none()); assert!($radarr_data.filtered_movies.items.is_empty()); assert!($radarr_data.filtered_collections.items.is_empty()); assert!($radarr_data.add_searched_movies.items.is_empty()); @@ -83,7 +70,7 @@ pub mod utils { macro_rules! assert_filter_reset { ($radarr_data:expr) => { assert!(!$radarr_data.is_filtering); - assert!($radarr_data.filter.text.is_empty()); + assert!($radarr_data.filter.is_none()); assert!($radarr_data.filtered_movies.items.is_empty()); assert!($radarr_data.filtered_collections.items.is_empty()); }; diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 08a6041..36fd519 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -1,7 +1,8 @@ +use anyhow::anyhow; use std::fmt::Debug; use indoc::formatdoc; -use log::{debug, info}; +use log::{debug, info, warn}; use serde::Serialize; use serde_json::{json, Number, Value}; use urlencoding::encode; @@ -184,7 +185,7 @@ impl<'a, 'b> Network<'a, 'b> { .collection_movies .current_selection() .clone(); - (tmdb_id, title.text.clone()) + (tmdb_id, title.text) } else { let AddMovieSearchResult { tmdb_id, title, .. } = app .data @@ -192,7 +193,7 @@ impl<'a, 'b> Network<'a, 'b> { .add_searched_movies .current_selection() .clone(); - (tmdb_id, title.text.clone()) + (tmdb_id, title.text) } } else { let AddMovieSearchResult { tmdb_id, title, .. } = app @@ -201,7 +202,7 @@ impl<'a, 'b> Network<'a, 'b> { .add_searched_movies .current_selection() .clone(); - (tmdb_id, title.text.clone()) + (tmdb_id, title.text) }; let quality_profile = quality_profile_list.current_selection(); let quality_profile_id = *app @@ -252,8 +253,20 @@ impl<'a, 'b> Network<'a, 'b> { async fn add_root_folder(&mut self) { info!("Adding new root folder to Radarr"); - let body = AddRootFolderBody { - path: self.app.lock().await.data.radarr_data.edit_path.drain(), + let body = { + let mut app = self.app.lock().await; + let path = app + .data + .radarr_data + .edit_root_folder + .as_ref() + .unwrap() + .text + .clone(); + + app.data.radarr_data.edit_root_folder = None; + + AddRootFolderBody { path } }; debug!("Add root folder body: {:?}", body); @@ -356,13 +369,13 @@ impl<'a, 'b> Network<'a, 'b> { } async fn delete_movie(&mut self) { - let movie_id = self.extract_movie_id().await; + let (movie_id, tmdb_id) = self.extract_movie_id().await; let delete_files = self.app.lock().await.data.radarr_data.delete_movie_files; let add_import_exclusion = self.app.lock().await.data.radarr_data.add_list_exclusion; info!( - "Deleting Radarr movie with id: {} with deleteFiles={} and addImportExclusion={}", - movie_id, delete_files, add_import_exclusion + "Deleting Radarr movie with tmdb_id {} and Radarr id: {} with deleteFiles={} and addImportExclusion={}", + tmdb_id, movie_id, delete_files, add_import_exclusion ); let request_props = self @@ -477,9 +490,11 @@ impl<'a, 'b> Network<'a, 'b> { ) .await; + let mut response = String::new(); + self - .handle_request::<(), Value>(request_props, |detailed_collection_body, mut app| { - app.response = detailed_collection_body.to_string() + .handle_request::<(), Value>(request_props, |detailed_collection_body, _| { + response = detailed_collection_body.to_string() }) .await; @@ -487,7 +502,6 @@ impl<'a, 'b> Network<'a, 'b> { let body = { let mut app = self.app.lock().await; - let response = app.response.drain(..).collect::(); let mut detailed_collection_body: Value = serde_json::from_str(&response).unwrap(); let EditCollectionModal { path, @@ -550,8 +564,9 @@ impl<'a, 'b> Network<'a, 'b> { async fn edit_movie(&mut self) { info!("Editing Radarr movie"); - info!("Fetching movie details"); - let movie_id = self.extract_movie_id().await; + let (movie_id, tmdb_id) = self.extract_movie_id().await; + info!("Fetching movie details for movie with TMDB ID: {tmdb_id}"); + let request_props = self .radarr_request_props_from( format!("{}/{}", RadarrEvent::GetMovieDetails.resource(), movie_id).as_str(), @@ -560,9 +575,11 @@ impl<'a, 'b> Network<'a, 'b> { ) .await; + let mut response = String::new(); + self - .handle_request::<(), Value>(request_props, |detailed_movie_body, mut app| { - app.response = detailed_movie_body.to_string() + .handle_request::<(), Value>(request_props, |detailed_movie_body, _| { + response = detailed_movie_body.to_string() }) .await; @@ -583,7 +600,6 @@ impl<'a, 'b> Network<'a, 'b> { .clone(); let tag_ids_vec = self.extract_and_add_tag_ids_vec(tags).await; let mut app = self.app.lock().await; - let response = app.response.drain(..).collect::(); let mut detailed_movie_body: Value = serde_json::from_str(&response).unwrap(); let EditMovieModal { @@ -819,7 +835,9 @@ impl<'a, 'b> Network<'a, 'b> { async fn get_movie_details(&mut self) { info!("Fetching Radarr movie details"); - let movie_id = self.extract_movie_id().await; + let (movie_id, tmdb_id) = self.extract_movie_id().await; + info!("Fetching movie details for movie with TMDB ID: {tmdb_id}"); + let request_props = self .radarr_request_props_from( format!("{}/{}", RadarrEvent::GetMovieDetails.resource(), movie_id).as_str(), @@ -1057,8 +1075,11 @@ impl<'a, 'b> Network<'a, 'b> { } async fn get_releases(&mut self) { - let movie_id = self.extract_movie_id().await; - info!("Fetching releases for movie with id: {}", movie_id); + let (movie_id, tmdb_id) = self.extract_movie_id().await; + info!( + "Fetching releases for movie with TMDB id {} and with Radarr id: {}", + tmdb_id, movie_id + ); let request_props = self .radarr_request_props_from( @@ -1249,34 +1270,56 @@ impl<'a, 'b> Network<'a, 'b> { async fn search_movie(&mut self) { info!("Searching for specific Radarr movie"); + let search = self + .app + .lock() + .await + .data + .radarr_data + .search + .clone() + .ok_or(anyhow!("Encountered a race condition")); - let search_string = self.app.lock().await.data.radarr_data.search.text.clone(); - let request_props = self - .radarr_request_props_from( - format!( - "{}?term={}", - RadarrEvent::SearchNewMovie.resource(), - encode(&search_string) - ) - .as_str(), - RequestMethod::Get, - None::<()>, - ) - .await; + match search { + Ok(search_string) => { + let request_props = self + .radarr_request_props_from( + format!( + "{}?term={}", + RadarrEvent::SearchNewMovie.resource(), + encode(&search_string.text) + ) + .as_str(), + RequestMethod::Get, + None::<()>, + ) + .await; - self - .handle_request::<(), Vec>(request_props, |movie_vec, mut app| { - if movie_vec.is_empty() { - app.pop_and_push_navigation_stack(ActiveRadarrBlock::AddMovieEmptySearchResults.into()); - } else { - app - .data - .radarr_data - .add_searched_movies - .set_items(movie_vec); - } - }) - .await; + self + .handle_request::<(), Vec>(request_props, |movie_vec, mut app| { + if movie_vec.is_empty() { + app.pop_and_push_navigation_stack( + ActiveRadarrBlock::AddMovieEmptySearchResults.into(), + ); + } else { + app + .data + .radarr_data + .add_searched_movies + .set_items(movie_vec); + } + }) + .await; + } + Err(e) => { + warn!( + "Encountered a race condition: {}\n \ + This is most likely caused by the user trying to navigate between modals rapidly. \ + Ignoring search request.", + e + ); + } + } } async fn start_task(&mut self) { @@ -1309,8 +1352,11 @@ impl<'a, 'b> Network<'a, 'b> { } async fn trigger_automatic_search(&mut self) { - let movie_id = self.extract_movie_id().await; - info!("Searching indexers for movie with id: {}", movie_id); + let (movie_id, tmdb_id) = self.extract_movie_id().await; + info!( + "Searching indexers for movie with TMDB id {} and with Radarr id: {}", + tmdb_id, movie_id + ); let body = MovieCommandBody { name: "MoviesSearch".to_owned(), movie_ids: vec![movie_id], @@ -1350,8 +1396,11 @@ impl<'a, 'b> Network<'a, 'b> { } async fn update_and_scan(&mut self) { - let movie_id = self.extract_movie_id().await; - info!("Updating and scanning movie with id: {}", movie_id); + let (movie_id, tmdb_id) = self.extract_movie_id().await; + info!( + "Updating and scanning movie with TMDB id {} and with Radarr id: {}", + tmdb_id, movie_id + ); let body = MovieCommandBody { name: "RefreshMovie".to_owned(), movie_ids: vec![movie_id], @@ -1493,40 +1542,46 @@ impl<'a, 'b> Network<'a, 'b> { .collect() } - async fn extract_movie_id(&mut self) -> u64 { - if !self - .app - .lock() - .await - .data - .radarr_data - .filtered_movies - .items - .is_empty() - { - self - .app - .lock() - .await - .data - .radarr_data - .filtered_movies - .current_selection() - .id - .as_u64() - .unwrap() + async fn extract_movie_id(&mut self) -> (u64, u64) { + let app = self.app.lock().await; + if !app.data.radarr_data.filtered_movies.items.is_empty() { + ( + app + .data + .radarr_data + .filtered_movies + .current_selection() + .id + .as_u64() + .unwrap(), + app + .data + .radarr_data + .filtered_movies + .current_selection() + .tmdb_id + .as_u64() + .unwrap(), + ) } else { - self - .app - .lock() - .await - .data - .radarr_data - .movies - .current_selection() - .id - .as_u64() - .unwrap() + ( + app + .data + .radarr_data + .movies + .current_selection() + .id + .as_u64() + .unwrap(), + app + .data + .radarr_data + .movies + .current_selection() + .tmdb_id + .as_u64() + .unwrap(), + ) } } @@ -1568,7 +1623,7 @@ impl<'a, 'b> Network<'a, 'b> { } async fn append_movie_id_param(&mut self, resource: &str) -> String { - let movie_id = self.extract_movie_id().await; + let (movie_id, _) = self.extract_movie_id().await; format!("{}?movieId={}", resource, movie_id) } } diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index 3251cb9..d7da0cd 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -357,7 +357,7 @@ mod test { &resource, ) .await; - app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into(); + app_arc.lock().await.data.radarr_data.search = Some("test term".into()); let mut network = Network::new(&app_arc, CancellationToken::new()); network @@ -413,7 +413,7 @@ mod test { ); let (async_server, app_arc, _server) = mock_radarr_api(RequestMethod::Get, None, Some(json!([])), &resource).await; - app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into(); + app_arc.lock().await.data.radarr_data.search = Some("test term".into()); let mut network = Network::new(&app_arc, CancellationToken::new()); network @@ -435,6 +435,56 @@ mod test { ); } + #[tokio::test] + async fn test_handle_search_new_movie_event_no_panic_on_race_condition() { + let resource = format!( + "{}?term=test%20term", + RadarrEvent::SearchNewMovie.resource() + ); + let mut server = Server::new_async().await; + let mut async_server = server + .mock( + &RequestMethod::Get.to_string().to_uppercase(), + format!("/api/v3{}", resource).as_str(), + ) + .match_header("X-Api-Key", "test1234"); + async_server = async_server.expect_at_most(0).create_async().await; + + let host = server.host_with_port().split(':').collect::>()[0].to_owned(); + let port = Some( + server.host_with_port().split(':').collect::>()[1] + .parse() + .unwrap(), + ); + let mut app = App::default(); + let radarr_config = RadarrConfig { + host, + port, + api_token: "test1234".to_owned(), + }; + app.config.radarr = radarr_config; + let app_arc = Arc::new(Mutex::new(app)); + let mut network = Network::new(&app_arc, CancellationToken::new()); + + network + .handle_radarr_event(RadarrEvent::SearchNewMovie) + .await; + + async_server.assert_async().await; + assert!(app_arc + .lock() + .await + .data + .radarr_data + .add_searched_movies + .items + .is_empty()); + assert_eq!( + app_arc.lock().await.get_current_route(), + &ActiveRadarrBlock::Movies.into() + ); + } + #[tokio::test] async fn test_handle_trigger_automatic_search_event() { let (async_server, app_arc, _server) = mock_radarr_api( @@ -1574,7 +1624,7 @@ mod test { ) .await; - app_arc.lock().await.data.radarr_data.edit_path = HorizontallyScrollableText::from("/nfs/test"); + app_arc.lock().await.data.radarr_data.edit_root_folder = Some("/nfs/test".into()); let mut network = Network::new(&app_arc, CancellationToken::new()); network @@ -1587,9 +1637,8 @@ mod test { .await .data .radarr_data - .edit_path - .text - .is_empty()); + .edit_root_folder + .is_none()); } #[tokio::test] @@ -1651,7 +1700,6 @@ mod test { async_edit_server.assert_async().await; let app = app_arc.lock().await; - assert!(app.response.is_empty()); assert!(app.data.radarr_data.edit_movie_modal.is_none()); assert!(app.data.radarr_data.movie_details.items.is_empty()); } @@ -1747,7 +1795,6 @@ mod test { async_edit_server.assert_async().await; let app = app_arc.lock().await; - assert!(app.response.is_empty()); assert!(app.data.radarr_data.edit_collection_modal.is_none()); assert!(app.data.radarr_data.movie_details.items.is_empty()); } @@ -1846,11 +1893,12 @@ mod test { .movies .set_items(vec![Movie { id: Number::from(1), + tmdb_id: Number::from(2), ..Movie::default() }]); let mut network = Network::new(&app_arc, CancellationToken::new()); - assert_eq!(network.extract_movie_id().await, 1); + assert_eq!(network.extract_movie_id().await, (1, 2)); } #[tokio::test] @@ -1864,11 +1912,12 @@ mod test { .filtered_movies .set_items(vec![Movie { id: Number::from(1), + tmdb_id: Number::from(2), ..Movie::default() }]); let mut network = Network::new(&app_arc, CancellationToken::new()); - assert_eq!(network.extract_movie_id().await, 1); + assert_eq!(network.extract_movie_id().await, (1, 2)); } #[tokio::test] diff --git a/src/ui/radarr_ui/library/add_movie_ui.rs b/src/ui/radarr_ui/library/add_movie_ui.rs index e4b083d..a85c8a6 100644 --- a/src/ui/radarr_ui/library/add_movie_ui.rs +++ b/src/ui/radarr_ui/library/add_movie_ui.rs @@ -124,8 +124,15 @@ fn draw_add_movie_search(f: &mut Frame<'_, B>, app: &mut App<'_>, ar area, 1, ); - let block_content = &app.data.radarr_data.search.text; - let offset = *app.data.radarr_data.search.offset.borrow(); + let block_content = &app.data.radarr_data.search.as_ref().unwrap().text; + let offset = *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(); if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index f3974d2..59c3bd3 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -254,8 +254,15 @@ fn draw_search_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Re Route::Radarr(active_radarr_block, _) => match active_radarr_block { _ if SEARCH_BLOCKS.contains(active_radarr_block) => ( "Search", - *app.data.radarr_data.search.offset.borrow(), - &app.data.radarr_data.search.text, + *app + .data + .radarr_data + .search + .as_ref() + .unwrap() + .offset + .borrow(), + &app.data.radarr_data.search.as_ref().unwrap().text, ), _ => ("", 0, &default_content), }, @@ -295,8 +302,15 @@ fn draw_filter_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Re Route::Radarr(active_radarr_block, _) => match active_radarr_block { _ if FILTER_BLOCKS.contains(active_radarr_block) => ( "Filter", - *app.data.radarr_data.filter.offset.borrow(), - &app.data.radarr_data.filter.text, + *app + .data + .radarr_data + .filter + .as_ref() + .unwrap() + .offset + .borrow(), + &app.data.radarr_data.filter.as_ref().unwrap().text, ), _ => ("", 0, &default_content), }, diff --git a/src/ui/radarr_ui/root_folders/mod.rs b/src/ui/radarr_ui/root_folders/mod.rs index afe2d52..d4aebe7 100644 --- a/src/ui/radarr_ui/root_folders/mod.rs +++ b/src/ui/radarr_ui/root_folders/mod.rs @@ -119,8 +119,15 @@ fn draw_add_root_folder_prompt_box( 1, ); let block_title = "Add Root Folder"; - let offset = *app.data.radarr_data.edit_path.offset.borrow(); - let block_content = &app.data.radarr_data.edit_path.text; + let offset = *app + .data + .radarr_data + .edit_root_folder + .as_ref() + .unwrap() + .offset + .borrow(); + let block_content = &app.data.radarr_data.edit_root_folder.as_ref().unwrap().text; let input = Paragraph::new(block_content.as_str()) .style(style_default())