From 2b15afcb834bd453f8a21ac4d27cfa56cae34a0e Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Fri, 8 Sep 2023 16:06:20 -0600 Subject: [PATCH] Added support for editing all indexer settings (finally)! --- Cargo.toml | 2 +- Makefile | 2 +- .../indexers/edit_indexer_settings_handler.rs | 199 ++++- .../edit_indexer_settings_handler_tests.rs | 754 +++++++++++++++++- src/handlers/radarr_handlers/indexers/mod.rs | 5 +- src/models/mod.rs | 11 +- src/models/model_tests.rs | 12 +- src/models/radarr_models.rs | 3 +- src/models/servarr_data/radarr/radarr_data.rs | 3 +- .../servarr_data/radarr/radarr_data_tests.rs | 4 + src/network/radarr_network.rs | 6 +- src/network/radarr_network_tests.rs | 34 + src/ui/radarr_ui/downloads/mod.rs | 2 +- .../radarr_ui/indexers/indexer_settings_ui.rs | 18 +- 14 files changed, 1018 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d6d294f..64dd0e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "managarr" -version = "0.0.28" +version = "0.0.29" authors = ["Alex Clarke "] description = "A TUI for managing *arr servers" keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"] diff --git a/Makefile b/Makefile index 12d3594..c592b1d 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ run: @CARGO_INCREMENTAL=1 cargo fmt && make lint && cargo run lint: -` @find . | grep '\.\/src\/.*\.rs$$' | xargs touch && CARGO_INCREMENTAL=0 cargo clippy --all-targets --workspace + @find . | grep '\.\/src\/.*\.rs$$' | xargs touch && CARGO_INCREMENTAL=0 cargo clippy --all-targets --workspace lint-fix: @cargo fix diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs index f957877..f6857cf 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler.rs @@ -1,9 +1,12 @@ +use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::App; use crate::event::Key; -use crate::handlers::KeyEventHandler; +use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS, }; +use crate::network::radarr_network::RadarrEvent; +use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; #[cfg(test)] #[path = "./edit_indexer_settings_handler_tests.rs"] @@ -40,26 +43,181 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl } fn handle_scroll_up(&mut self) { - if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsPrompt { - self.app.data.radarr_data.selected_block.previous() + let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap(); + match self.active_radarr_block { + ActiveRadarrBlock::IndexerSettingsPrompt => { + self.app.data.radarr_data.selected_block.previous() + } + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput => { + indexer_settings.minimum_age += 1; + } + ActiveRadarrBlock::IndexerSettingsRetentionInput => { + indexer_settings.retention += 1; + } + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput => { + indexer_settings.maximum_size += 1; + } + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput => { + indexer_settings.availability_delay += 1; + } + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput => { + indexer_settings.rss_sync_interval += 1; + } + _ => (), } } fn handle_scroll_down(&mut self) { - if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsPrompt { - self.app.data.radarr_data.selected_block.next() + let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap(); + match self.active_radarr_block { + ActiveRadarrBlock::IndexerSettingsPrompt => self.app.data.radarr_data.selected_block.next(), + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput => { + if indexer_settings.minimum_age > 0 { + indexer_settings.minimum_age -= 1; + } + } + ActiveRadarrBlock::IndexerSettingsRetentionInput => { + if indexer_settings.retention > 0 { + indexer_settings.retention -= 1; + } + } + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput => { + if indexer_settings.maximum_size > 0 { + indexer_settings.maximum_size -= 1; + } + } + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput => { + indexer_settings.availability_delay -= 1; + } + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput => { + if indexer_settings.rss_sync_interval > 0 { + indexer_settings.rss_sync_interval -= 1; + } + } + _ => (), } } - fn handle_home(&mut self) {} + fn handle_home(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput { + self + .app + .data + .radarr_data + .indexer_settings + .as_mut() + .unwrap() + .whitelisted_hardcoded_subs + .scroll_home(); + } + } - fn handle_end(&mut self) {} + fn handle_end(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput { + self + .app + .data + .radarr_data + .indexer_settings + .as_mut() + .unwrap() + .whitelisted_hardcoded_subs + .reset_offset(); + } + } fn handle_delete(&mut self) {} - fn handle_left_right_action(&mut self) {} + fn handle_left_right_action(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::IndexerSettingsPrompt => { + if self.app.data.radarr_data.selected_block.get_active_block() + == &ActiveRadarrBlock::IndexerSettingsConfirmPrompt + { + handle_prompt_toggle(self.app, self.key); + } else { + let len = self.app.data.radarr_data.selected_block.blocks.len(); + let idx = self.app.data.radarr_data.selected_block.index; + self.app.data.radarr_data.selected_block.index = (idx + 5) % len; + } + } + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => { + handle_text_box_left_right_keys!( + self, + self.key, + self + .app + .data + .radarr_data + .indexer_settings + .as_mut() + .unwrap() + .whitelisted_hardcoded_subs + ) + } + _ => (), + } + } - fn handle_submit(&mut self) {} + fn handle_submit(&mut self) { + match self.active_radarr_block { + ActiveRadarrBlock::IndexerSettingsPrompt => { + match self.app.data.radarr_data.selected_block.get_active_block() { + ActiveRadarrBlock::IndexerSettingsConfirmPrompt => { + let radarr_data = &mut self.app.data.radarr_data; + if radarr_data.prompt_confirm { + radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateIndexerSettings); + self.app.should_refresh = true; + } else { + radarr_data.indexer_settings = None; + } + + self.app.pop_navigation_stack(); + } + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput + | ActiveRadarrBlock::IndexerSettingsRetentionInput + | ActiveRadarrBlock::IndexerSettingsMaximumSizeInput + | ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput + | ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput => { + self.app.push_navigation_stack( + ( + *self.app.data.radarr_data.selected_block.get_active_block(), + None, + ) + .into(), + ) + } + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => { + self.app.push_navigation_stack( + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into(), + ); + self.app.should_ignore_quit_key = true; + } + ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags => { + let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap(); + + indexer_settings.prefer_indexer_flags = !indexer_settings.prefer_indexer_flags; + } + ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs => { + let indexer_settings = self.app.data.radarr_data.indexer_settings.as_mut().unwrap(); + + indexer_settings.allow_hardcoded_subs = !indexer_settings.allow_hardcoded_subs; + } + _ => (), + } + } + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => { + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + } + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput + | ActiveRadarrBlock::IndexerSettingsRetentionInput + | ActiveRadarrBlock::IndexerSettingsMaximumSizeInput + | ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput + | ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput => self.app.pop_navigation_stack(), + _ => (), + } + } fn handle_esc(&mut self) { match self.active_radarr_block { @@ -68,9 +226,28 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl self.app.data.radarr_data.prompt_confirm = false; self.app.data.radarr_data.indexer_settings = None; } - _ => self.app.pop_navigation_stack(), // Need to tweak this still and add unit tests + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput => { + self.app.pop_navigation_stack(); + self.app.should_ignore_quit_key = false; + } + _ => self.app.pop_navigation_stack(), } } - fn handle_char_key_event(&mut self) {} + fn handle_char_key_event(&mut self) { + if self.active_radarr_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput { + handle_text_box_keys!( + self, + self.key, + self + .app + .data + .radarr_data + .indexer_settings + .as_mut() + .unwrap() + .whitelisted_hardcoded_subs + ) + } + } } diff --git a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs index 9e6f9fb..f58d9c7 100644 --- a/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/edit_indexer_settings_handler_tests.rs @@ -15,14 +15,87 @@ mod tests { use pretty_assertions::assert_eq; use rstest::rstest; + use crate::models::radarr_models::IndexerSettings; use crate::models::servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS; use crate::models::BlockSelectionState; use super::*; + macro_rules! test_i64_counter_scroll_value { + ($block:expr, $key:expr, $data_ref:ident, $negatives:literal) => { + let mut app = App::default(); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + + IndexerSettingsHandler::with(&$key, &mut app, &$block, &None).handle(); + + if $key == Key::Up { + assert_eq!( + app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + 1 + ); + } else { + if $negatives { + assert_eq!( + app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + -1 + ); + } else { + assert_eq!( + app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + 0 + ); + + IndexerSettingsHandler::with(&Key::Up, &mut app, &$block, &None).handle(); + + assert_eq!( + app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + 1 + ); + + IndexerSettingsHandler::with(&$key, &mut app, &$block, &None).handle(); + assert_eq!( + app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .$data_ref, + 0 + ); + } + } + }; + } + #[rstest] fn test_edit_indexer_settings_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { let mut app = App::default(); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); app.data.radarr_data.selected_block = BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); app.data.radarr_data.selected_block.next(); @@ -47,18 +120,572 @@ mod tests { ); } } + + #[rstest] + fn test_edit_indexer_settings_minimum_age_scroll(#[values(Key::Up, Key::Down)] key: Key) { + test_i64_counter_scroll_value!( + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, + key, + minimum_age, + false + ); + } + + #[rstest] + fn test_edit_indexer_settings_retention_scroll(#[values(Key::Up, Key::Down)] key: Key) { + test_i64_counter_scroll_value!( + ActiveRadarrBlock::IndexerSettingsRetentionInput, + key, + retention, + false + ); + } + + #[rstest] + fn test_edit_indexer_settings_maximum_size_scroll(#[values(Key::Up, Key::Down)] key: Key) { + test_i64_counter_scroll_value!( + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, + key, + maximum_size, + false + ); + } + + #[rstest] + fn test_edit_indexer_settings_availability_delay_scroll( + #[values(Key::Up, Key::Down)] key: Key, + ) { + test_i64_counter_scroll_value!( + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, + key, + availability_delay, + true + ); + } + + #[rstest] + fn test_edit_indexer_settings_rss_sync_interval_scroll(#[values(Key::Up, Key::Down)] key: Key) { + test_i64_counter_scroll_value!( + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, + key, + rss_sync_interval, + false + ); + } } - mod test_handle_home_end {} + mod test_handle_home_end { + use pretty_assertions::assert_eq; - mod test_handle_left_right_action {} + use crate::models::radarr_models::IndexerSettings; - mod test_handle_submit {} + use super::*; + + #[test] + fn test_edit_indexer_settings_whiteliested_subtitle_tags_input_home_end() { + let mut app = App::default(); + app.data.radarr_data.indexer_settings = Some(IndexerSettings { + whitelisted_hardcoded_subs: "Test".into(), + ..IndexerSettings::default() + }); + + IndexerSettingsHandler::with( + &DEFAULT_KEYBINDINGS.home.key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .whitelisted_hardcoded_subs + .offset + .borrow(), + 4 + ); + + IndexerSettingsHandler::with( + &DEFAULT_KEYBINDINGS.end.key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .whitelisted_hardcoded_subs + .offset + .borrow(), + 0 + ); + } + } + + mod test_handle_left_right_action { + use crate::models::radarr_models::IndexerSettings; + use crate::models::servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + use pretty_assertions::assert_eq; + use rstest::rstest; + + use super::*; + + #[rstest] + fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) { + let mut app = App::default(); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.index = INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1; + + IndexerSettingsHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert!(app.data.radarr_data.prompt_confirm); + + IndexerSettingsHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert!(!app.data.radarr_data.prompt_confirm); + } + + #[rstest] + #[case( + 0, + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput + )] + #[case( + 1, + ActiveRadarrBlock::IndexerSettingsRetentionInput, + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput + )] + #[case( + 2, + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput + )] + #[case( + 3, + ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags, + ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs + )] + fn test_left_right_block_toggle( + #[values(Key::Left, Key::Right)] key: Key, + #[case] starting_index: usize, + #[case] left_block: ActiveRadarrBlock, + #[case] right_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.index = starting_index; + + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &left_block + ); + + IndexerSettingsHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &right_block + ); + + IndexerSettingsHandler::with( + &key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.data.radarr_data.selected_block.get_active_block(), + &left_block + ); + } + + #[test] + fn test_edit_indexer_settings_whitelisted_subtitle_tags_input_left_right_keys() { + let mut app = App::default(); + app.data.radarr_data.indexer_settings = Some(IndexerSettings { + whitelisted_hardcoded_subs: "Test".into(), + ..IndexerSettings::default() + }); + + IndexerSettingsHandler::with( + &DEFAULT_KEYBINDINGS.left.key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .whitelisted_hardcoded_subs + .offset + .borrow(), + 1 + ); + + IndexerSettingsHandler::with( + &DEFAULT_KEYBINDINGS.right.key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, + &None, + ) + .handle(); + + assert_eq!( + *app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .whitelisted_hardcoded_subs + .offset + .borrow(), + 0 + ); + } + } + + mod test_handle_submit { + use pretty_assertions::assert_eq; + use rstest::rstest; + + use crate::{ + models::{ + radarr_models::IndexerSettings, + servarr_data::radarr::radarr_data::INDEXER_SETTINGS_SELECTION_BLOCKS, BlockSelectionState, + }, + network::radarr_network::RadarrEvent, + }; + + use super::*; + + const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key; + + #[test] + fn test_edit_indexer_settings_prompt_prompt_decline_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + + IndexerSettingsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + assert_eq!(app.data.radarr_data.prompt_confirm_action, None); + assert!(!app.should_refresh); + assert_eq!(app.data.radarr_data.indexer_settings, None); + } + + #[test] + fn test_edit_indexer_settings_prompt_prompt_confirmation_submit() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(INDEXER_SETTINGS_SELECTION_BLOCKS.len() - 1); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.radarr_data.prompt_confirm = true; + + IndexerSettingsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + assert_eq!( + app.data.radarr_data.prompt_confirm_action, + Some(RadarrEvent::UpdateIndexerSettings) + ); + assert!(app.data.radarr_data.indexer_settings.is_some()); + assert!(app.should_refresh); + } + + #[rstest] + #[case(ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, 0)] + #[case(ActiveRadarrBlock::IndexerSettingsRetentionInput, 1)] + #[case(ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, 2)] + #[case(ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, 5)] + #[case(ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, 6)] + fn test_edit_indexer_settings_prompt_submit_selected_block( + #[case] selected_block: ActiveRadarrBlock, + #[case] index: usize, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(index); + + IndexerSettingsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &selected_block.into()); + } + + #[test] + fn test_edit_indexer_settings_prompt_submit_whitelisted_subtitle_tags_input() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(7); + + IndexerSettingsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into() + ); + assert!(app.should_ignore_quit_key); + } + + #[test] + fn test_edit_indexer_settings_toggle_prefer_indexer_flags_submit() { + let mut app = App::default(); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(3); + app.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + + IndexerSettingsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::IndexerSettingsPrompt.into() + ); + assert!( + app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .prefer_indexer_flags + ); + + IndexerSettingsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::IndexerSettingsPrompt.into() + ); + assert!( + !app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .prefer_indexer_flags + ); + } + + #[test] + fn test_edit_indexer_settings_toggle_allow_hardcoded_subs_submit() { + let mut app = App::default(); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + app.data.radarr_data.selected_block = + BlockSelectionState::new(&INDEXER_SETTINGS_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(8); + app.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + + IndexerSettingsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::IndexerSettingsPrompt.into() + ); + assert!( + app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .allow_hardcoded_subs + ); + + IndexerSettingsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsPrompt, + &None, + ) + .handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::IndexerSettingsPrompt.into() + ); + assert!( + !app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .allow_hardcoded_subs + ); + } + + #[test] + fn test_edit_indexer_settings_whitelisted_subtitle_tags_input_submit() { + let mut app = App::default(); + app.should_ignore_quit_key = true; + app.data.radarr_data.indexer_settings = Some(IndexerSettings { + whitelisted_hardcoded_subs: "Test tags".into(), + ..IndexerSettings::default() + }); + app.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + app.push_navigation_stack( + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into(), + ); + + IndexerSettingsHandler::with( + &SUBMIT_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, + &None, + ) + .handle(); + + assert!(!app.should_ignore_quit_key); + assert!(!app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .whitelisted_hardcoded_subs + .text + .is_empty()); + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::IndexerSettingsPrompt.into() + ); + } + + #[rstest] + fn test_edit_indexer_settings_selected_block_submit( + #[values( + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, + ActiveRadarrBlock::IndexerSettingsRetentionInput, + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + app.push_navigation_stack(active_radarr_block.into()); + + IndexerSettingsHandler::with(&SUBMIT_KEY, &mut app, &active_radarr_block, &None).handle(); + + assert_eq!( + app.get_current_route(), + &ActiveRadarrBlock::IndexerSettingsPrompt.into() + ); + } + } mod test_handle_esc { use pretty_assertions::assert_eq; + use rstest::rstest; - use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data; + use crate::models::radarr_models::IndexerSettings; use super::*; @@ -68,8 +695,8 @@ mod tests { fn test_edit_indexer_settings_prompt_esc() { let mut app = App::default(); app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); - app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); - app.data.radarr_data = create_test_radarr_data(); + app.push_navigation_stack(ActiveRadarrBlock::IndexerSettingsPrompt.into()); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); IndexerSettingsHandler::with( &ESC_KEY, @@ -83,9 +710,122 @@ mod tests { assert!(!app.data.radarr_data.prompt_confirm); assert_eq!(app.data.radarr_data.indexer_settings, None); } + + #[test] + fn test_edit_indexer_settings_whitelisted_subtitle_tags_input_esc() { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack( + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput.into(), + ); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + app.should_ignore_quit_key = true; + + IndexerSettingsHandler::with( + &ESC_KEY, + &mut app, + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, + &None, + ) + .handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + assert!(!app.should_ignore_quit_key); + assert_eq!( + app.data.radarr_data.indexer_settings, + Some(IndexerSettings::default()) + ); + } + + #[rstest] + fn test_edit_indexer_settings_selected_blocks_esc( + #[values( + ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, + ActiveRadarrBlock::IndexerSettingsRetentionInput, + ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, + ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, + ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, + ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput + )] + active_radarr_block: ActiveRadarrBlock, + ) { + let mut app = App::default(); + app.push_navigation_stack(ActiveRadarrBlock::Indexers.into()); + app.push_navigation_stack(active_radarr_block.into()); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + + IndexerSettingsHandler::with(&ESC_KEY, &mut app, &active_radarr_block, &None).handle(); + + assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into()); + assert_eq!( + app.data.radarr_data.indexer_settings, + Some(IndexerSettings::default()) + ); + } } - mod test_handle_key_char {} + mod test_handle_key_char { + use pretty_assertions::assert_str_eq; + + use crate::models::radarr_models::IndexerSettings; + + use super::*; + + #[test] + fn test_edit_indexer_settings_whitelisted_subtitle_tags_input_backspace() { + let mut app = App::default(); + app.data.radarr_data.indexer_settings = Some(IndexerSettings { + whitelisted_hardcoded_subs: "Test".into(), + ..IndexerSettings::default() + }); + + IndexerSettingsHandler::with( + &DEFAULT_KEYBINDINGS.backspace.key, + &mut app, + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, + &None, + ) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .whitelisted_hardcoded_subs + .text, + "Tes" + ); + } + + #[test] + fn test_edit_indexer_settings_whitelisted_subtitle_tags_input_char_key() { + let mut app = App::default(); + app.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + + IndexerSettingsHandler::with( + &Key::Char('h'), + &mut app, + &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, + &None, + ) + .handle(); + + assert_str_eq!( + app + .data + .radarr_data + .indexer_settings + .as_ref() + .unwrap() + .whitelisted_hardcoded_subs + .text, + "h" + ); + } + } #[test] fn test_indexer_settings_handler_accepts() { diff --git a/src/handlers/radarr_handlers/indexers/mod.rs b/src/handlers/radarr_handlers/indexers/mod.rs index 7cf2caf..f1bbe4a 100644 --- a/src/handlers/radarr_handlers/indexers/mod.rs +++ b/src/handlers/radarr_handlers/indexers/mod.rs @@ -99,8 +99,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, fn handle_submit(&mut self) { match self.active_radarr_block { ActiveRadarrBlock::DeleteIndexerPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer); + let radarr_data = &mut self.app.data.radarr_data; + if radarr_data.prompt_confirm { + radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer); } self.app.pop_navigation_stack(); diff --git a/src/models/mod.rs b/src/models/mod.rs index a195a8b..7cd4e66 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::fmt::{Debug, Display, Formatter}; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; -use serde::{de, Deserialize, Deserializer}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Number; use tui::widgets::{ListState, TableState}; @@ -223,6 +223,15 @@ impl Display for HorizontallyScrollableText { } } +impl Serialize for HorizontallyScrollableText { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.text) + } +} + impl HorizontallyScrollableText { pub fn new(text: String) -> HorizontallyScrollableText { HorizontallyScrollableText { diff --git a/src/models/model_tests.rs b/src/models/model_tests.rs index fc3e6ac..77538a5 100644 --- a/src/models/model_tests.rs +++ b/src/models/model_tests.rs @@ -7,6 +7,7 @@ mod tests { use serde::de::value::F64Deserializer; use serde::de::value::I64Deserializer; use serde::de::IntoDeserializer; + use serde_json::to_string; use crate::models::from_i64; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; @@ -731,12 +732,21 @@ mod tests { fn test_from_i64_error() { let deserializer: F64Deserializer = 1f64.into_deserializer(); - assert_eq!( + assert_str_eq!( from_i64(deserializer).unwrap_err().to_string(), "Unable to convert Number to i64: Number(1.0)" ); } + #[test] + fn test_horizontally_scrollable_serialize() { + let text = HorizontallyScrollableText::from("Test"); + + let serialized = to_string(&text).expect("Serialization failed!"); + + assert_str_eq!(serialized, r#""Test""#); + } + fn create_test_tab_routes() -> Vec { vec![ TabRoute { diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index d0d128e..a64beed 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -207,7 +207,8 @@ pub struct IndexerSettings { pub retention: i64, #[serde(deserialize_with = "super::from_i64")] pub rss_sync_interval: i64, - pub whitelisted_hardcoded_subs: String, + #[serde(default)] + pub whitelisted_hardcoded_subs: HorizontallyScrollableText, } #[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)] diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index b724421..ddf9462 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -424,11 +424,12 @@ pub static INDEXER_SETTINGS_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, ]; -pub static INDEXER_SETTINGS_SELECTION_BLOCKS: [ActiveRadarrBlock; 9] = [ +pub static INDEXER_SETTINGS_SELECTION_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, ActiveRadarrBlock::IndexerSettingsRetentionInput, ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags, + ActiveRadarrBlock::IndexerSettingsConfirmPrompt, ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, diff --git a/src/models/servarr_data/radarr/radarr_data_tests.rs b/src/models/servarr_data/radarr/radarr_data_tests.rs index 0b9ac22..aafd751 100644 --- a/src/models/servarr_data/radarr/radarr_data_tests.rs +++ b/src/models/servarr_data/radarr/radarr_data_tests.rs @@ -560,6 +560,10 @@ mod tests { indexer_settings_block_iter.next().unwrap(), &ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags ); + assert_eq!( + indexer_settings_block_iter.next().unwrap(), + &ActiveRadarrBlock::IndexerSettingsConfirmPrompt + ); assert_eq!( indexer_settings_block_iter.next().unwrap(), &ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index cc14cbd..c1fc776 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -747,7 +747,11 @@ impl<'a, 'b> Network<'a, 'b> { self .handle_request::<(), IndexerSettings>(request_props, |indexer_settings, mut app| { - app.data.radarr_data.indexer_settings = Some(indexer_settings); + if app.data.radarr_data.indexer_settings.is_none() { + app.data.radarr_data.indexer_settings = Some(indexer_settings); + } else { + debug!("Indexer Settings are being modified. Ignoring update..."); + } }) .await; } diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index 7555193..2c331be 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -1058,6 +1058,40 @@ mod test { ); } + #[tokio::test] + async fn test_handle_get_indexer_settings_event_no_op_if_already_present() { + let indexer_settings_response_json = json!({ + "minimumAge": 0, + "maximumSize": 0, + "retention": 0, + "rssSyncInterval": 60, + "preferIndexerFlags": false, + "availabilityDelay": 0, + "allowHardcodedSubs": true, + "whitelistedHardcodedSubs": "", + "id": 1 + }); + let (async_server, app_arc, _server) = mock_radarr_api( + RequestMethod::Get, + None, + Some(indexer_settings_response_json), + RadarrEvent::GetIndexerSettings.resource(), + ) + .await; + app_arc.lock().await.data.radarr_data.indexer_settings = Some(IndexerSettings::default()); + let mut network = Network::new(&app_arc, CancellationToken::new()); + + network + .handle_radarr_event(RadarrEvent::GetIndexerSettings) + .await; + + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.radarr_data.indexer_settings, + Some(IndexerSettings::default()) + ); + } + #[tokio::test] async fn test_handle_get_queued_events_event() { let queued_events_json = json!([{ diff --git a/src/ui/radarr_ui/downloads/mod.rs b/src/ui/radarr_ui/downloads/mod.rs index b656eef..dccc031 100644 --- a/src/ui/radarr_ui/downloads/mod.rs +++ b/src/ui/radarr_ui/downloads/mod.rs @@ -97,7 +97,7 @@ fn draw_downloads(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rec .. } = download_record; - if matches!(output_path, Some(_)) { + if output_path.is_some() { output_path.as_ref().unwrap().scroll_left_or_reset( get_width_from_percentage(area, 18), current_selection == *download_record, diff --git a/src/ui/radarr_ui/indexers/indexer_settings_ui.rs b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs index 334d3c0..a18f2c2 100644 --- a/src/ui/radarr_ui/indexers/indexer_settings_ui.rs +++ b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs @@ -39,8 +39,8 @@ impl DrawUi for IndexerSettingsUi { content_rect, draw_indexers, draw_edit_indexer_settings_prompt, - 60, - 40, + 70, + 45, ); } } @@ -97,7 +97,7 @@ fn draw_edit_indexer_settings_prompt( draw_text_box_with_label( f, left_chunks[0], - "Minimum Age", + "Minimum Age (minutes) ▴▾", &indexer_settings.minimum_age.to_string(), 0, selected_block == &ActiveRadarrBlock::IndexerSettingsMinimumAgeInput, @@ -106,7 +106,7 @@ fn draw_edit_indexer_settings_prompt( draw_text_box_with_label( f, left_chunks[1], - "Retention", + "Retention (days) ▴▾", &indexer_settings.retention.to_string(), 0, selected_block == &ActiveRadarrBlock::IndexerSettingsRetentionInput, @@ -115,7 +115,7 @@ fn draw_edit_indexer_settings_prompt( draw_text_box_with_label( f, left_chunks[2], - "Maximum Size", + "Maximum Size (MB) ▴▾", &indexer_settings.maximum_size.to_string(), 0, selected_block == &ActiveRadarrBlock::IndexerSettingsMaximumSizeInput, @@ -124,7 +124,7 @@ fn draw_edit_indexer_settings_prompt( draw_text_box_with_label( f, right_chunks[0], - "Availability Delay", + "Availability Delay (days) ▴▾", &indexer_settings.availability_delay.to_string(), 0, selected_block == &ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput, @@ -133,7 +133,7 @@ fn draw_edit_indexer_settings_prompt( draw_text_box_with_label( f, right_chunks[1], - "RSS Sync Interval", + "RSS Sync Interval (minutes) ▴▾", &indexer_settings.rss_sync_interval.to_string(), 0, selected_block == &ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput, @@ -143,8 +143,8 @@ fn draw_edit_indexer_settings_prompt( f, right_chunks[2], "Whitelisted Subtitle Tags", - &indexer_settings.whitelisted_hardcoded_subs.to_string(), - 0, + &indexer_settings.whitelisted_hardcoded_subs.text, + *indexer_settings.whitelisted_hardcoded_subs.offset.borrow(), selected_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, active_radarr_block == ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput, );