Added the ability to edit existing indexers with basic options, added a tags column to the indexers table, and fixed a bug in the counter fields that displayed the cursor next to the integer instead of on it to make understanding the counter easier. Also upgraded to confy v0.60.0 and rust version to 1.75
This commit is contained in:
Generated
+16
-9
@@ -246,13 +246,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "confy"
|
||||
version = "0.5.1"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e37668cb35145dcfaa1931a5f37fde375eeae8068b4c0d2f289da28a270b2d2c"
|
||||
checksum = "15d296c475c6ed4093824c28e222420831d27577aaaf0a1163a3b7fc35b248a5"
|
||||
dependencies = [
|
||||
"directories",
|
||||
"serde",
|
||||
"serde_yaml 0.8.26",
|
||||
"serde_yaml 0.9.29",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -331,22 +331,23 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "4.0.1"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
|
||||
checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.3.7"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -860,7 +861,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "managarr"
|
||||
version = "0.0.30"
|
||||
version = "0.0.31"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"backtrace",
|
||||
@@ -1047,6 +1048,12 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.10.1"
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "managarr"
|
||||
version = "0.0.30"
|
||||
version = "0.0.31"
|
||||
authors = ["Alex Clarke <alex.j.tusa@gmail.com>"]
|
||||
description = "A TUI to manage your Servarrs"
|
||||
keywords = ["managarr", "tui-rs", "dashboard", "servarr", "tui"]
|
||||
@@ -9,14 +9,14 @@ repository = "https://github.com/Dark-Alex-17/managarr"
|
||||
homepage = "https://github.com/Dark-Alex-17/managarr"
|
||||
readme = "README.md"
|
||||
edition = "2021"
|
||||
rust-version = "1.72.0"
|
||||
rust-version = "1.75.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
backtrace = "0.3.67"
|
||||
bimap = "0.6.3"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
confy = { version = "0.5.1", default_features = false, features = ["yaml_conf"] }
|
||||
confy = { version = "0.6.0", default_features = false, features = ["yaml_conf"] }
|
||||
crossterm = "0.27.0"
|
||||
derivative = "2.2.0"
|
||||
human-panic = "1.1.3"
|
||||
|
||||
@@ -5,8 +5,8 @@ Managarr is a TUI to help you manage your HTPC (Home Theater PC). Built with lov
|
||||
|
||||
## NOTE: Managarr is not yet stable (Pre-Alpha)
|
||||
I'm regularly making changes to get Managarr to an alpha release. As such, I'm regularly refactoring the code to be cleaner
|
||||
and more easily extensible. This makes contributing difficult and until I get Managarr across the alpha-release finish line,
|
||||
contributions will likely be difficult. Thus, stability is not guaranteed (yet!).
|
||||
and more easily extensible. Until I get Managarr across the alpha-release finish line, this regular refactoring will make
|
||||
contributions difficult. Thus, stability is not guaranteed (yet!).
|
||||
|
||||
This means that while all tests will pass, there may be certain menus or keymappings that are no-ops, or produce empty
|
||||
screens, or things of this sort.
|
||||
@@ -40,16 +40,15 @@ pleasant as possible!
|
||||
- [x] View details of a specific movie including description, history, downloaded file info, or the credits
|
||||
- [x] View details of any collection and the movies in them
|
||||
- [x] Search your library or collections
|
||||
- [x] Add or delete movies and downloads
|
||||
- [x] Add or delete movies, downloads, and indexers
|
||||
- [x] Trigger automatic searches for movies
|
||||
- [x] Trigger refresh and disk scan for movies, downloads, and collections
|
||||
- [x] Manually search for movies
|
||||
- [x] Edit your movies and collections
|
||||
- [x] Edit your movies, collections, and indexers
|
||||
- [x] Manage your tags
|
||||
- [x] Manage your root folders
|
||||
- [ ] Manage your quality profiles
|
||||
- [ ] Manage your quality definitions
|
||||
- [ ] Manage your indexers
|
||||
- [x] View and browse logs, tasks, events queues, and updates
|
||||
- [x] Manually trigger scheduled tasks
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ pub static ROOT_FOLDERS_CONTEXT_CLUES: [ContextClue; 3] = [
|
||||
|
||||
pub static INDEXERS_CONTEXT_CLUES: [ContextClue; 6] = [
|
||||
(DEFAULT_KEYBINDINGS.add, DEFAULT_KEYBINDINGS.add.desc),
|
||||
(DEFAULT_KEYBINDINGS.edit, DEFAULT_KEYBINDINGS.edit.desc),
|
||||
(DEFAULT_KEYBINDINGS.submit, "edit indexer"),
|
||||
(
|
||||
DEFAULT_KEYBINDINGS.settings,
|
||||
DEFAULT_KEYBINDINGS.settings.desc,
|
||||
|
||||
@@ -151,8 +151,8 @@ mod tests {
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.edit);
|
||||
assert_str_eq!(*description, DEFAULT_KEYBINDINGS.edit.desc);
|
||||
assert_eq!(*key_binding, DEFAULT_KEYBINDINGS.submit);
|
||||
assert_str_eq!(*description, "edit indexer");
|
||||
|
||||
let (key_binding, description) = indexers_context_clues_iter.next().unwrap();
|
||||
|
||||
|
||||
+1
-4
@@ -1,7 +1,4 @@
|
||||
pub use self::{
|
||||
input_event::{Events, InputEvent},
|
||||
key::Key,
|
||||
};
|
||||
pub use self::key::Key;
|
||||
|
||||
pub mod input_event;
|
||||
mod key;
|
||||
|
||||
@@ -0,0 +1,431 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::{handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::{handle_text_box_keys, handle_text_box_left_right_keys};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_handler_tests.rs"]
|
||||
mod edit_indexer_handler_tests;
|
||||
|
||||
pub(super) struct EditIndexerHandler<'a, 'b> {
|
||||
key: &'a Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_radarr_block: &'a ActiveRadarrBlock,
|
||||
_context: &'a Option<ActiveRadarrBlock>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditIndexerHandler<'a, 'b> {
|
||||
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
|
||||
EDIT_INDEXER_BLOCKS.contains(active_block)
|
||||
}
|
||||
|
||||
fn with(
|
||||
key: &'a Key,
|
||||
app: &'a mut App<'b>,
|
||||
active_block: &'a ActiveRadarrBlock,
|
||||
_context: &'a Option<ActiveRadarrBlock>,
|
||||
) -> EditIndexerHandler<'a, 'b> {
|
||||
EditIndexerHandler {
|
||||
key,
|
||||
app,
|
||||
active_radarr_block: active_block,
|
||||
_context,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key(&self) -> &Key {
|
||||
self.key
|
||||
}
|
||||
|
||||
fn handle_scroll_up(&mut self) {
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::EditIndexerPrompt {
|
||||
self.app.data.radarr_data.selected_block.previous();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_scroll_down(&mut self) {
|
||||
if self.active_radarr_block == &ActiveRadarrBlock::EditIndexerPrompt {
|
||||
self.app.data.radarr_data.selected_block.next();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_home(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::EditIndexerNameInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.name
|
||||
.scroll_home();
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerUrlInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.url
|
||||
.scroll_home();
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.api_key
|
||||
.scroll_home();
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.seed_ratio
|
||||
.scroll_home();
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.tags
|
||||
.scroll_home();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_end(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::EditIndexerNameInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.name
|
||||
.reset_offset();
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerUrlInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.url
|
||||
.reset_offset();
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.api_key
|
||||
.reset_offset();
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.seed_ratio
|
||||
.reset_offset();
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.tags
|
||||
.reset_offset();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_delete(&mut self) {}
|
||||
|
||||
fn handle_left_right_action(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::EditIndexerPrompt => {
|
||||
if self.app.data.radarr_data.selected_block.get_active_block()
|
||||
== &ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||
{
|
||||
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::EditIndexerNameInput => {
|
||||
handle_text_box_left_right_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.name
|
||||
);
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerUrlInput => {
|
||||
handle_text_box_left_right_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.url
|
||||
);
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput => {
|
||||
handle_text_box_left_right_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.api_key
|
||||
);
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput => {
|
||||
handle_text_box_left_right_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.seed_ratio
|
||||
);
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
handle_text_box_left_right_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.tags
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_submit(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::EditIndexerPrompt => {
|
||||
let selected_block = *self.app.data.radarr_data.selected_block.get_active_block();
|
||||
match selected_block {
|
||||
ActiveRadarrBlock::EditIndexerConfirmPrompt => {
|
||||
let radarr_data = &mut self.app.data.radarr_data;
|
||||
if radarr_data.prompt_confirm {
|
||||
radarr_data.prompt_confirm_action = Some(RadarrEvent::EditIndexer);
|
||||
self.app.should_refresh = true;
|
||||
} else {
|
||||
radarr_data.edit_indexer_modal = None;
|
||||
}
|
||||
|
||||
self.app.pop_navigation_stack();
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerNameInput
|
||||
| ActiveRadarrBlock::EditIndexerUrlInput
|
||||
| ActiveRadarrBlock::EditIndexerApiKeyInput
|
||||
| ActiveRadarrBlock::EditIndexerSeedRatioInput
|
||||
| ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
self.app.push_navigation_stack(selected_block.into());
|
||||
self.app.should_ignore_quit_key = true;
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableRss => {
|
||||
let indexer = self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap();
|
||||
indexer.enable_rss = Some(!indexer.enable_rss.unwrap_or_default());
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch => {
|
||||
let indexer = self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap();
|
||||
indexer.enable_automatic_search =
|
||||
Some(!indexer.enable_automatic_search.unwrap_or_default());
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch => {
|
||||
let indexer = self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap();
|
||||
indexer.enable_interactive_search =
|
||||
Some(!indexer.enable_interactive_search.unwrap_or_default());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerNameInput
|
||||
| ActiveRadarrBlock::EditIndexerUrlInput
|
||||
| ActiveRadarrBlock::EditIndexerApiKeyInput
|
||||
| ActiveRadarrBlock::EditIndexerSeedRatioInput
|
||||
| ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_esc(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::EditIndexerPrompt => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.data.radarr_data.prompt_confirm = false;
|
||||
self.app.data.radarr_data.edit_indexer_modal = None;
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerNameInput
|
||||
| ActiveRadarrBlock::EditIndexerUrlInput
|
||||
| ActiveRadarrBlock::EditIndexerApiKeyInput
|
||||
| ActiveRadarrBlock::EditIndexerSeedRatioInput
|
||||
| ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
self.app.pop_navigation_stack();
|
||||
self.app.should_ignore_quit_key = false;
|
||||
}
|
||||
_ => self.app.pop_navigation_stack(),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_char_key_event(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
ActiveRadarrBlock::EditIndexerNameInput => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.name
|
||||
);
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerUrlInput => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.url
|
||||
);
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.api_key
|
||||
);
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.seed_ratio
|
||||
);
|
||||
}
|
||||
ActiveRadarrBlock::EditIndexerTagsInput => {
|
||||
handle_text_box_keys!(
|
||||
self,
|
||||
self.key,
|
||||
self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.tags
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
|
||||
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()
|
||||
self.app.data.radarr_data.selected_block.previous();
|
||||
}
|
||||
ActiveRadarrBlock::IndexerSettingsMinimumAgeInput => {
|
||||
indexer_settings.minimum_age += 1;
|
||||
@@ -166,7 +166,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexerSettingsHandl
|
||||
ActiveRadarrBlock::IndexerSettingsConfirmPrompt => {
|
||||
let radarr_data = &mut self.app.data.radarr_data;
|
||||
if radarr_data.prompt_confirm {
|
||||
radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateIndexerSettings);
|
||||
radarr_data.prompt_confirm_action = Some(RadarrEvent::EditAllIndexerSettings);
|
||||
self.app.should_refresh = true;
|
||||
} else {
|
||||
radarr_data.indexer_settings = None;
|
||||
|
||||
@@ -456,7 +456,7 @@ mod tests {
|
||||
assert_eq!(app.get_current_route(), &ActiveRadarrBlock::Indexers.into());
|
||||
assert_eq!(
|
||||
app.data.radarr_data.prompt_confirm_action,
|
||||
Some(RadarrEvent::UpdateIndexerSettings)
|
||||
Some(RadarrEvent::EditAllIndexerSettings)
|
||||
);
|
||||
assert!(app.data.radarr_data.indexer_settings.is_some());
|
||||
assert!(app.should_refresh);
|
||||
|
||||
@@ -7,10 +7,10 @@ mod tests {
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::indexers::{IndexersHandler, TestAllIndexersHandler};
|
||||
use crate::handlers::radarr_handlers::indexers::IndexersHandler;
|
||||
use crate::handlers::KeyEventHandler;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
|
||||
ActiveRadarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
|
||||
};
|
||||
use crate::test_handler_delegation;
|
||||
|
||||
@@ -147,7 +147,14 @@ mod tests {
|
||||
}
|
||||
|
||||
mod test_handle_submit {
|
||||
use crate::models::radarr_models::{Indexer, IndexerField};
|
||||
use crate::models::servarr_data::radarr::modals::EditIndexerModal;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
RadarrData, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
|
||||
};
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::{Number, Value};
|
||||
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
@@ -155,16 +162,85 @@ mod tests {
|
||||
|
||||
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||
|
||||
#[test]
|
||||
fn test_indexer_submit_aka_edit() {
|
||||
#[rstest]
|
||||
fn test_edit_indexer_submit(#[values(true, false)] torrent_protocol: bool) {
|
||||
let mut app = App::default();
|
||||
let protocol = if torrent_protocol {
|
||||
"torrent".to_owned()
|
||||
} else {
|
||||
"usenet".to_owned()
|
||||
};
|
||||
let mut expected_edit_indexer_modal = EditIndexerModal {
|
||||
name: "Test".into(),
|
||||
enable_rss: Some(true),
|
||||
enable_automatic_search: Some(true),
|
||||
enable_interactive_search: Some(true),
|
||||
url: "https://test.com".into(),
|
||||
api_key: "1234".into(),
|
||||
tags: "usenet, test".into(),
|
||||
..EditIndexerModal::default()
|
||||
};
|
||||
let mut radarr_data = RadarrData {
|
||||
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
|
||||
..RadarrData::default()
|
||||
};
|
||||
let mut fields = vec![
|
||||
IndexerField {
|
||||
name: Some("baseUrl".to_owned()),
|
||||
value: Some(Value::String("https://test.com".to_owned())),
|
||||
},
|
||||
IndexerField {
|
||||
name: Some("apiKey".to_owned()),
|
||||
value: Some(Value::String("1234".to_owned())),
|
||||
},
|
||||
];
|
||||
|
||||
if torrent_protocol {
|
||||
fields.push(IndexerField {
|
||||
name: Some("seedCriteria.seedRatio".to_owned()),
|
||||
value: Some(Value::from(1.2f64)),
|
||||
});
|
||||
expected_edit_indexer_modal.seed_ratio = "1.2".into();
|
||||
}
|
||||
|
||||
let indexer = Indexer {
|
||||
name: Some("Test".to_owned()),
|
||||
enable_rss: true,
|
||||
enable_automatic_search: true,
|
||||
enable_interactive_search: true,
|
||||
protocol,
|
||||
tags: vec![Number::from(1), Number::from(2)],
|
||||
fields: Some(fields),
|
||||
..Indexer::default()
|
||||
};
|
||||
radarr_data.indexers.set_items(vec![indexer]);
|
||||
app.data.radarr_data = radarr_data;
|
||||
|
||||
IndexersHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle();
|
||||
|
||||
assert_eq!(
|
||||
app.get_current_route(),
|
||||
&ActiveRadarrBlock::EditIndexer.into()
|
||||
&ActiveRadarrBlock::EditIndexerPrompt.into()
|
||||
);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.edit_indexer_modal,
|
||||
Some((&app.data.radarr_data).into())
|
||||
);
|
||||
assert_eq!(
|
||||
app.data.radarr_data.edit_indexer_modal,
|
||||
Some(expected_edit_indexer_modal)
|
||||
);
|
||||
if torrent_protocol {
|
||||
assert_eq!(
|
||||
app.data.radarr_data.selected_block.blocks,
|
||||
&EDIT_INDEXER_TORRENT_SELECTION_BLOCKS
|
||||
);
|
||||
} else {
|
||||
assert_eq!(
|
||||
app.data.radarr_data.selected_block.blocks,
|
||||
&EDIT_INDEXER_NZB_SELECTION_BLOCKS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -322,6 +398,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_delegates_edit_indexer_blocks_to_edit_indexer_handler(
|
||||
#[values(
|
||||
ActiveRadarrBlock::EditIndexerPrompt,
|
||||
ActiveRadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||
ActiveRadarrBlock::EditIndexerNameInput,
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveRadarrBlock::EditIndexerUrlInput,
|
||||
ActiveRadarrBlock::EditIndexerTagsInput
|
||||
)]
|
||||
active_radarr_block: ActiveRadarrBlock,
|
||||
) {
|
||||
test_handler_delegation!(
|
||||
IndexersHandler,
|
||||
ActiveRadarrBlock::Indexers,
|
||||
active_radarr_block
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_delegates_indexer_settings_blocks_to_indexer_settings_handler(
|
||||
#[values(
|
||||
@@ -348,7 +447,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_delegates_test_all_indexers_block_to_test_all_indexers_handler() {
|
||||
test_handler_delegation!(
|
||||
TestAllIndexersHandler,
|
||||
IndexersHandler,
|
||||
ActiveRadarrBlock::Indexers,
|
||||
ActiveRadarrBlock::TestAllIndexers
|
||||
);
|
||||
@@ -359,6 +458,8 @@ mod tests {
|
||||
let mut indexers_blocks = Vec::new();
|
||||
indexers_blocks.extend(INDEXERS_BLOCKS);
|
||||
indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS);
|
||||
indexers_blocks.extend(EDIT_INDEXER_BLOCKS);
|
||||
indexers_blocks.push(ActiveRadarrBlock::TestAllIndexers);
|
||||
|
||||
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||
if indexers_blocks.contains(&active_radarr_block) {
|
||||
|
||||
@@ -2,15 +2,18 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::App;
|
||||
use crate::event::Key;
|
||||
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
|
||||
use crate::handlers::radarr_handlers::indexers::edit_indexer_handler::EditIndexerHandler;
|
||||
use crate::handlers::radarr_handlers::indexers::edit_indexer_settings_handler::IndexerSettingsHandler;
|
||||
use crate::handlers::radarr_handlers::indexers::test_all_indexers_handler::TestAllIndexersHandler;
|
||||
use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler};
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS,
|
||||
ActiveRadarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS,
|
||||
INDEXERS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::models::{BlockSelectionState, Scrollable};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
|
||||
mod edit_indexer_handler;
|
||||
mod edit_indexer_settings_handler;
|
||||
mod test_all_indexers_handler;
|
||||
|
||||
@@ -28,6 +31,10 @@ pub(super) struct IndexersHandler<'a, 'b> {
|
||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a, 'b> {
|
||||
fn handle(&mut self) {
|
||||
match self.active_radarr_block {
|
||||
_ if EditIndexerHandler::accepts(self.active_radarr_block) => {
|
||||
EditIndexerHandler::with(self.key, self.app, self.active_radarr_block, self.context)
|
||||
.handle()
|
||||
}
|
||||
_ if IndexerSettingsHandler::accepts(self.active_radarr_block) => {
|
||||
IndexerSettingsHandler::with(self.key, self.app, self.active_radarr_block, self.context)
|
||||
.handle()
|
||||
@@ -41,7 +48,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|
||||
}
|
||||
|
||||
fn accepts(active_block: &'a ActiveRadarrBlock) -> bool {
|
||||
IndexerSettingsHandler::accepts(active_block) || INDEXERS_BLOCKS.contains(active_block)
|
||||
EditIndexerHandler::accepts(active_block)
|
||||
|| IndexerSettingsHandler::accepts(active_block)
|
||||
|| TestAllIndexersHandler::accepts(active_block)
|
||||
|| INDEXERS_BLOCKS.contains(active_block)
|
||||
}
|
||||
|
||||
fn with(
|
||||
@@ -115,7 +125,22 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for IndexersHandler<'a,
|
||||
ActiveRadarrBlock::Indexers => {
|
||||
self
|
||||
.app
|
||||
.push_navigation_stack(ActiveRadarrBlock::EditIndexer.into());
|
||||
.push_navigation_stack(ActiveRadarrBlock::EditIndexerPrompt.into());
|
||||
self.app.data.radarr_data.edit_indexer_modal = Some((&self.app.data.radarr_data).into());
|
||||
let protocol = &self
|
||||
.app
|
||||
.data
|
||||
.radarr_data
|
||||
.indexers
|
||||
.current_selection()
|
||||
.protocol;
|
||||
if protocol == "torrent" {
|
||||
self.app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(&EDIT_INDEXER_TORRENT_SELECTION_BLOCKS);
|
||||
} else {
|
||||
self.app.data.radarr_data.selected_block =
|
||||
BlockSelectionState::new(&EDIT_INDEXER_NZB_SELECTION_BLOCKS);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -164,30 +164,13 @@ pub struct Indexer {
|
||||
pub priority: i64,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub download_client_id: i64,
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub tags: Vec<Number>,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IndexerField {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub order: i64,
|
||||
pub name: Option<String>,
|
||||
pub label: Option<String>,
|
||||
pub value: Option<Value>,
|
||||
#[serde(rename(deserialize = "type"))]
|
||||
pub field_type: Option<String>,
|
||||
pub select_options: Option<Vec<IndexerSelectOption>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IndexerSelectOption {
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub value: i64,
|
||||
pub name: Option<String>,
|
||||
#[serde(deserialize_with = "super::from_i64")]
|
||||
pub order: i64,
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::models::radarr_models::{
|
||||
Collection, Credit, MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release, ReleaseField,
|
||||
RootFolder,
|
||||
Collection, Credit, Indexer, MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release,
|
||||
ReleaseField, RootFolder,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::radarr_data::RadarrData;
|
||||
use crate::models::{HorizontallyScrollableText, ScrollableText, StatefulList, StatefulTable};
|
||||
@@ -24,6 +24,96 @@ pub struct MovieDetailsModal {
|
||||
pub sort_ascending: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub struct EditIndexerModal {
|
||||
pub name: HorizontallyScrollableText,
|
||||
pub enable_rss: Option<bool>,
|
||||
pub enable_automatic_search: Option<bool>,
|
||||
pub enable_interactive_search: Option<bool>,
|
||||
pub url: HorizontallyScrollableText,
|
||||
pub api_key: HorizontallyScrollableText,
|
||||
pub seed_ratio: HorizontallyScrollableText,
|
||||
pub tags: HorizontallyScrollableText,
|
||||
}
|
||||
|
||||
impl From<&RadarrData<'_>> for EditIndexerModal {
|
||||
fn from(radarr_data: &RadarrData<'_>) -> EditIndexerModal {
|
||||
let mut edit_indexer_modal = EditIndexerModal::default();
|
||||
let Indexer {
|
||||
name,
|
||||
enable_rss,
|
||||
enable_automatic_search,
|
||||
enable_interactive_search,
|
||||
tags,
|
||||
fields,
|
||||
..
|
||||
} = radarr_data.indexers.current_selection();
|
||||
let seed_ratio_field_option = fields
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|field| field.name.as_ref().unwrap() == "seedCriteria.seedRatio");
|
||||
let seed_ratio_value_option = if let Some(seed_ratio_field) = seed_ratio_field_option {
|
||||
seed_ratio_field.value.clone()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
edit_indexer_modal.name = name.clone().unwrap().into();
|
||||
edit_indexer_modal.enable_rss = Some(*enable_rss);
|
||||
edit_indexer_modal.enable_automatic_search = Some(*enable_automatic_search);
|
||||
edit_indexer_modal.enable_interactive_search = Some(*enable_interactive_search);
|
||||
edit_indexer_modal.url = fields
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|field| field.name.as_ref().unwrap() == "baseUrl")
|
||||
.unwrap()
|
||||
.value
|
||||
.clone()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.into();
|
||||
edit_indexer_modal.api_key = fields
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|field| field.name.as_ref().unwrap() == "apiKey")
|
||||
.unwrap()
|
||||
.value
|
||||
.clone()
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
if seed_ratio_value_option.is_some() {
|
||||
edit_indexer_modal.seed_ratio = seed_ratio_value_option
|
||||
.unwrap()
|
||||
.as_f64()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.into();
|
||||
}
|
||||
|
||||
edit_indexer_modal.tags = tags
|
||||
.iter()
|
||||
.map(|tag_id| {
|
||||
radarr_data
|
||||
.tags_map
|
||||
.get_by_left(&tag_id.as_i64().unwrap())
|
||||
.unwrap()
|
||||
.clone()
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
.into();
|
||||
|
||||
edit_indexer_modal
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EditMovieModal {
|
||||
pub minimum_availability_list: StatefulList<MinimumAvailability>,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::models::radarr_models::{Collection, MinimumAvailability, Monitor, Movie, RootFolder};
|
||||
use crate::models::radarr_models::{
|
||||
Collection, Indexer, IndexerField, MinimumAvailability, Monitor, Movie, RootFolder,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::modals::{
|
||||
AddMovieModal, EditCollectionModal, EditMovieModal,
|
||||
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal,
|
||||
};
|
||||
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;
|
||||
@@ -10,9 +12,103 @@ mod test {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
use rstest::rstest;
|
||||
use serde_json::Number;
|
||||
use serde_json::{Number, Value};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_indexer_modal_from_radarr_data(#[values(true, false)] seed_ratio_present: bool) {
|
||||
let mut radarr_data = RadarrData {
|
||||
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
|
||||
..RadarrData::default()
|
||||
};
|
||||
let mut fields = vec![
|
||||
IndexerField {
|
||||
name: Some("baseUrl".to_owned()),
|
||||
value: Some(Value::String("https://test.com".to_owned())),
|
||||
},
|
||||
IndexerField {
|
||||
name: Some("apiKey".to_owned()),
|
||||
value: Some(Value::String("1234".to_owned())),
|
||||
},
|
||||
];
|
||||
|
||||
if seed_ratio_present {
|
||||
fields.push(IndexerField {
|
||||
name: Some("seedCriteria.seedRatio".to_owned()),
|
||||
value: Some(Value::from(1.2f64)),
|
||||
});
|
||||
}
|
||||
|
||||
let indexer = Indexer {
|
||||
name: Some("Test".to_owned()),
|
||||
enable_rss: true,
|
||||
enable_automatic_search: true,
|
||||
enable_interactive_search: true,
|
||||
tags: vec![Number::from(1), Number::from(2)],
|
||||
fields: Some(fields),
|
||||
..Indexer::default()
|
||||
};
|
||||
radarr_data.indexers.set_items(vec![indexer]);
|
||||
|
||||
let edit_indexer_modal = EditIndexerModal::from(&radarr_data);
|
||||
|
||||
assert_str_eq!(edit_indexer_modal.name.text, "Test");
|
||||
assert_eq!(edit_indexer_modal.enable_rss, Some(true));
|
||||
assert_eq!(edit_indexer_modal.enable_automatic_search, Some(true));
|
||||
assert_eq!(edit_indexer_modal.enable_interactive_search, Some(true));
|
||||
assert_str_eq!(edit_indexer_modal.url.text, "https://test.com");
|
||||
assert_str_eq!(edit_indexer_modal.api_key.text, "1234");
|
||||
|
||||
if seed_ratio_present {
|
||||
assert_str_eq!(edit_indexer_modal.seed_ratio.text, "1.2");
|
||||
} else {
|
||||
assert!(edit_indexer_modal.seed_ratio.text.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_modal_from_radarr_data_seed_ratio_value_is_none() {
|
||||
let mut radarr_data = RadarrData {
|
||||
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
|
||||
..RadarrData::default()
|
||||
};
|
||||
let fields = vec![
|
||||
IndexerField {
|
||||
name: Some("baseUrl".to_owned()),
|
||||
value: Some(Value::String("https://test.com".to_owned())),
|
||||
},
|
||||
IndexerField {
|
||||
name: Some("apiKey".to_owned()),
|
||||
value: Some(Value::String("1234".to_owned())),
|
||||
},
|
||||
IndexerField {
|
||||
name: Some("seedCriteria.seedRatio".to_owned()),
|
||||
value: None,
|
||||
},
|
||||
];
|
||||
|
||||
let indexer = Indexer {
|
||||
name: Some("Test".to_owned()),
|
||||
enable_rss: true,
|
||||
enable_automatic_search: true,
|
||||
enable_interactive_search: true,
|
||||
tags: vec![Number::from(1), Number::from(2)],
|
||||
fields: Some(fields),
|
||||
..Indexer::default()
|
||||
};
|
||||
radarr_data.indexers.set_items(vec![indexer]);
|
||||
|
||||
let edit_indexer_modal = EditIndexerModal::from(&radarr_data);
|
||||
|
||||
assert_str_eq!(edit_indexer_modal.name.text, "Test");
|
||||
assert_eq!(edit_indexer_modal.enable_rss, Some(true));
|
||||
assert_eq!(edit_indexer_modal.enable_automatic_search, Some(true));
|
||||
assert_eq!(edit_indexer_modal.enable_interactive_search, Some(true));
|
||||
assert_str_eq!(edit_indexer_modal.url.text, "https://test.com");
|
||||
assert_str_eq!(edit_indexer_modal.api_key.text, "1234");
|
||||
assert!(edit_indexer_modal.seed_ratio.text.is_empty());
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_edit_movie_modal_from_radarr_data(#[values(true, false)] test_filtered_movies: bool) {
|
||||
let mut radarr_data = RadarrData {
|
||||
|
||||
@@ -10,7 +10,8 @@ use crate::models::radarr_models::{
|
||||
IndexerSettings, Movie, QueueEvent, RootFolder, Task,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::modals::{
|
||||
AddMovieModal, EditCollectionModal, EditMovieModal, IndexerTestResultModalItem, MovieDetailsModal,
|
||||
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
|
||||
MovieDetailsModal,
|
||||
};
|
||||
use crate::models::{
|
||||
BlockSelectionState, HorizontallyScrollableText, Route, ScrollableText, StatefulList,
|
||||
@@ -55,6 +56,7 @@ pub struct RadarrData<'a> {
|
||||
pub add_searched_movies: Option<StatefulTable<AddMovieSearchResult>>,
|
||||
pub edit_movie_modal: Option<EditMovieModal>,
|
||||
pub edit_collection_modal: Option<EditCollectionModal>,
|
||||
pub edit_indexer_modal: Option<EditIndexerModal>,
|
||||
pub edit_root_folder: Option<HorizontallyScrollableText>,
|
||||
pub filtered_collections: Option<StatefulTable<Collection>>,
|
||||
pub filtered_movies: Option<StatefulTable<Movie>>,
|
||||
@@ -123,6 +125,7 @@ impl<'a> Default for RadarrData<'a> {
|
||||
add_searched_movies: None,
|
||||
edit_movie_modal: None,
|
||||
edit_collection_modal: None,
|
||||
edit_indexer_modal: None,
|
||||
edit_root_folder: None,
|
||||
filtered_collections: None,
|
||||
filtered_movies: None,
|
||||
@@ -252,7 +255,16 @@ pub enum ActiveRadarrBlock {
|
||||
EditCollectionSelectQualityProfile,
|
||||
EditCollectionToggleSearchOnAdd,
|
||||
EditCollectionToggleMonitored,
|
||||
EditIndexer,
|
||||
EditIndexerPrompt,
|
||||
EditIndexerConfirmPrompt,
|
||||
EditIndexerApiKeyInput,
|
||||
EditIndexerNameInput,
|
||||
EditIndexerSeedRatioInput,
|
||||
EditIndexerToggleEnableRss,
|
||||
EditIndexerToggleEnableAutomaticSearch,
|
||||
EditIndexerToggleEnableInteractiveSearch,
|
||||
EditIndexerUrlInput,
|
||||
EditIndexerTagsInput,
|
||||
EditMoviePrompt,
|
||||
EditMovieConfirmPrompt,
|
||||
EditMoviePathInput,
|
||||
@@ -318,12 +330,10 @@ pub static COLLECTIONS_BLOCKS: [ActiveRadarrBlock; 6] = [
|
||||
ActiveRadarrBlock::FilterCollectionsError,
|
||||
ActiveRadarrBlock::UpdateAllCollectionsPrompt,
|
||||
];
|
||||
pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 5] = [
|
||||
pub static INDEXERS_BLOCKS: [ActiveRadarrBlock; 3] = [
|
||||
ActiveRadarrBlock::AddIndexer,
|
||||
ActiveRadarrBlock::EditIndexer,
|
||||
ActiveRadarrBlock::DeleteIndexerPrompt,
|
||||
ActiveRadarrBlock::Indexers,
|
||||
ActiveRadarrBlock::TestAllIndexers,
|
||||
];
|
||||
pub static ROOT_FOLDERS_BLOCKS: [ActiveRadarrBlock; 3] = [
|
||||
ActiveRadarrBlock::RootFolders,
|
||||
@@ -416,6 +426,42 @@ pub static DELETE_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 3] = [
|
||||
ActiveRadarrBlock::DeleteMovieToggleAddListExclusion,
|
||||
ActiveRadarrBlock::DeleteMovieConfirmPrompt,
|
||||
];
|
||||
pub static EDIT_INDEXER_BLOCKS: [ActiveRadarrBlock; 10] = [
|
||||
ActiveRadarrBlock::EditIndexerPrompt,
|
||||
ActiveRadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||
ActiveRadarrBlock::EditIndexerNameInput,
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveRadarrBlock::EditIndexerUrlInput,
|
||||
ActiveRadarrBlock::EditIndexerTagsInput,
|
||||
];
|
||||
pub static EDIT_INDEXER_TORRENT_SELECTION_BLOCKS: [ActiveRadarrBlock; 10] = [
|
||||
ActiveRadarrBlock::EditIndexerNameInput,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveRadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveRadarrBlock::EditIndexerUrlInput,
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||
ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
||||
ActiveRadarrBlock::EditIndexerTagsInput,
|
||||
ActiveRadarrBlock::EditIndexerConfirmPrompt,
|
||||
];
|
||||
pub static EDIT_INDEXER_NZB_SELECTION_BLOCKS: [ActiveRadarrBlock; 10] = [
|
||||
ActiveRadarrBlock::EditIndexerNameInput,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableRss,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
ActiveRadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveRadarrBlock::EditIndexerUrlInput,
|
||||
ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||
ActiveRadarrBlock::EditIndexerTagsInput,
|
||||
ActiveRadarrBlock::EditIndexerConfirmPrompt,
|
||||
ActiveRadarrBlock::EditIndexerConfirmPrompt,
|
||||
];
|
||||
pub static INDEXER_SETTINGS_BLOCKS: [ActiveRadarrBlock; 10] = [
|
||||
ActiveRadarrBlock::IndexerSettingsPrompt,
|
||||
ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
|
||||
|
||||
@@ -98,6 +98,7 @@ mod tests {
|
||||
assert!(radarr_data.edit_movie_modal.is_none());
|
||||
assert!(radarr_data.edit_collection_modal.is_none());
|
||||
assert!(radarr_data.edit_root_folder.is_none());
|
||||
assert!(radarr_data.edit_indexer_modal.is_none());
|
||||
assert!(radarr_data.filtered_collections.is_none());
|
||||
assert!(radarr_data.filtered_movies.is_none());
|
||||
assert!(radarr_data.indexer_settings.is_none());
|
||||
@@ -270,9 +271,10 @@ mod tests {
|
||||
ActiveRadarrBlock, ADD_MOVIE_BLOCKS, ADD_MOVIE_SELECTION_BLOCKS, COLLECTIONS_BLOCKS,
|
||||
COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS, DELETE_MOVIE_SELECTION_BLOCKS,
|
||||
DOWNLOADS_BLOCKS, EDIT_COLLECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS,
|
||||
EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
|
||||
INDEXER_SETTINGS_SELECTION_BLOCKS, LIBRARY_BLOCKS, MOVIE_DETAILS_BLOCKS, ROOT_FOLDERS_BLOCKS,
|
||||
SYSTEM_DETAILS_BLOCKS,
|
||||
EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
|
||||
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS,
|
||||
INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, LIBRARY_BLOCKS,
|
||||
MOVIE_DETAILS_BLOCKS, ROOT_FOLDERS_BLOCKS, SYSTEM_DETAILS_BLOCKS,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -299,12 +301,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_indexers_blocks_contents() {
|
||||
assert_eq!(INDEXERS_BLOCKS.len(), 5);
|
||||
assert_eq!(INDEXERS_BLOCKS.len(), 3);
|
||||
assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::AddIndexer));
|
||||
assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::EditIndexer));
|
||||
assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::DeleteIndexerPrompt));
|
||||
assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::Indexers));
|
||||
assert!(INDEXERS_BLOCKS.contains(&ActiveRadarrBlock::TestAllIndexers));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -398,6 +398,25 @@ mod tests {
|
||||
assert!(DELETE_MOVIE_BLOCKS.contains(&ActiveRadarrBlock::DeleteMovieToggleAddListExclusion));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_blocks_contents() {
|
||||
assert_eq!(EDIT_INDEXER_BLOCKS.len(), 10);
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerPrompt));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerConfirmPrompt));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerApiKeyInput));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerNameInput));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerSeedRatioInput));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerToggleEnableRss));
|
||||
assert!(
|
||||
EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch)
|
||||
);
|
||||
assert!(
|
||||
EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch)
|
||||
);
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerUrlInput));
|
||||
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveRadarrBlock::EditIndexerTagsInput));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexer_settings_blocks_contents() {
|
||||
assert_eq!(INDEXER_SETTINGS_BLOCKS.len(), 10);
|
||||
@@ -542,6 +561,101 @@ mod tests {
|
||||
assert_eq!(delete_movie_block_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_torrent_selection_blocks_ordering() {
|
||||
let mut edit_indexer_torrent_selection_block_iter =
|
||||
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerNameInput
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerToggleEnableRss
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerUrlInput
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerApiKeyInput
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerSeedRatioInput
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerTagsInput
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_torrent_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||
);
|
||||
assert_eq!(edit_indexer_torrent_selection_block_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_nzb_selection_blocks_ordering() {
|
||||
let mut edit_indexer_nzb_selection_block_iter = EDIT_INDEXER_NZB_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerNameInput
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerToggleEnableRss
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerUrlInput
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerApiKeyInput
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerTagsInput
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||
);
|
||||
assert_eq!(
|
||||
edit_indexer_nzb_selection_block_iter.next().unwrap(),
|
||||
&ActiveRadarrBlock::EditIndexerConfirmPrompt
|
||||
);
|
||||
assert_eq!(edit_indexer_nzb_selection_block_iter.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indexer_settings_selection_blocks_ordering() {
|
||||
let mut indexer_settings_block_iter = INDEXER_SETTINGS_SELECTION_BLOCKS.iter();
|
||||
|
||||
+166
-38
@@ -16,7 +16,8 @@ use crate::models::radarr_models::{
|
||||
Update,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::modals::{
|
||||
AddMovieModal, EditCollectionModal, EditMovieModal, IndexerTestResultModalItem, MovieDetailsModal,
|
||||
AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem,
|
||||
MovieDetailsModal,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::{HorizontallyScrollableText, Route, Scrollable, ScrollableText, StatefulTable};
|
||||
@@ -36,7 +37,9 @@ pub enum RadarrEvent {
|
||||
DeleteMovie,
|
||||
DeleteRootFolder,
|
||||
DownloadRelease,
|
||||
EditAllIndexerSettings,
|
||||
EditCollection,
|
||||
EditIndexer,
|
||||
EditMovie,
|
||||
GetCollections,
|
||||
GetDownloads,
|
||||
@@ -65,7 +68,6 @@ pub enum RadarrEvent {
|
||||
UpdateAndScan,
|
||||
UpdateCollections,
|
||||
UpdateDownloads,
|
||||
UpdateIndexerSettings,
|
||||
}
|
||||
|
||||
impl RadarrEvent {
|
||||
@@ -73,8 +75,10 @@ impl RadarrEvent {
|
||||
match self {
|
||||
RadarrEvent::GetCollections | RadarrEvent::EditCollection => "/collection",
|
||||
RadarrEvent::GetDownloads | RadarrEvent::DeleteDownload => "/queue",
|
||||
RadarrEvent::GetIndexers | RadarrEvent::DeleteIndexer => "/indexer",
|
||||
RadarrEvent::GetIndexerSettings | RadarrEvent::UpdateIndexerSettings => "/config/indexer",
|
||||
RadarrEvent::GetIndexers | RadarrEvent::EditIndexer | RadarrEvent::DeleteIndexer => {
|
||||
"/indexer"
|
||||
}
|
||||
RadarrEvent::GetIndexerSettings | RadarrEvent::EditAllIndexerSettings => "/config/indexer",
|
||||
RadarrEvent::GetLogs => "/log",
|
||||
RadarrEvent::AddMovie
|
||||
| RadarrEvent::EditMovie
|
||||
@@ -123,7 +127,9 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
RadarrEvent::DeleteMovie => self.delete_movie().await,
|
||||
RadarrEvent::DeleteRootFolder => self.delete_root_folder().await,
|
||||
RadarrEvent::DownloadRelease => self.download_release().await,
|
||||
RadarrEvent::EditAllIndexerSettings => self.edit_all_indexer_settings().await,
|
||||
RadarrEvent::EditCollection => self.edit_collection().await,
|
||||
RadarrEvent::EditIndexer => self.edit_indexer().await,
|
||||
RadarrEvent::EditMovie => self.edit_movie().await,
|
||||
RadarrEvent::GetCollections => self.get_collections().await,
|
||||
RadarrEvent::GetDownloads => self.get_downloads().await,
|
||||
@@ -152,7 +158,6 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
RadarrEvent::UpdateAndScan => self.update_and_scan().await,
|
||||
RadarrEvent::UpdateCollections => self.update_collections().await,
|
||||
RadarrEvent::UpdateDownloads => self.update_downloads().await,
|
||||
RadarrEvent::UpdateIndexerSettings => self.update_indexer_settings().await,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,6 +471,37 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn edit_all_indexer_settings(&mut self) {
|
||||
info!("Updating Radarr indexer settings");
|
||||
|
||||
let body = self
|
||||
.app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.indexer_settings
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
debug!("Indexer settings body: {body:?}");
|
||||
|
||||
let request_props = self
|
||||
.radarr_request_props_from(
|
||||
RadarrEvent::EditAllIndexerSettings.resource(),
|
||||
RequestMethod::Put,
|
||||
Some(body),
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<IndexerSettings, Value>(request_props, |_, _| {})
|
||||
.await;
|
||||
|
||||
self.app.lock().await.data.radarr_data.indexer_settings = None;
|
||||
}
|
||||
|
||||
async fn edit_collection(&mut self) {
|
||||
info!("Editing Radarr collection");
|
||||
|
||||
@@ -545,6 +581,129 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn edit_indexer(&mut self) {
|
||||
let id = self
|
||||
.app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.indexers
|
||||
.current_selection()
|
||||
.id;
|
||||
info!("Updating Radarr indexer with ID: {id}");
|
||||
|
||||
info!("Fetching indexer details for indexer with ID: {id}");
|
||||
|
||||
let request_props = self
|
||||
.radarr_request_props_from(
|
||||
format!("{}/{id}", RadarrEvent::GetIndexers.resource()).as_str(),
|
||||
RequestMethod::Get,
|
||||
None::<()>,
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut response = String::new();
|
||||
|
||||
self
|
||||
.handle_request::<(), Value>(request_props, |detailed_indexer_body, _| {
|
||||
response = detailed_indexer_body.to_string()
|
||||
})
|
||||
.await;
|
||||
|
||||
info!("Constructing edit indexer body");
|
||||
|
||||
let body = {
|
||||
let tags = self
|
||||
.app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.edit_indexer_modal
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.tags
|
||||
.text
|
||||
.clone();
|
||||
let tag_ids_vec = self.extract_and_add_tag_ids_vec(tags).await;
|
||||
let mut app = self.app.lock().await;
|
||||
let mut detailed_indexer_body: Value = serde_json::from_str(&response).unwrap();
|
||||
|
||||
let EditIndexerModal {
|
||||
name,
|
||||
enable_rss,
|
||||
enable_automatic_search,
|
||||
enable_interactive_search,
|
||||
url,
|
||||
api_key,
|
||||
seed_ratio,
|
||||
..
|
||||
} = app.data.radarr_data.edit_indexer_modal.as_ref().unwrap();
|
||||
|
||||
*detailed_indexer_body.get_mut("name").unwrap() = json!(name.text.clone());
|
||||
*detailed_indexer_body.get_mut("enableRss").unwrap() = json!(enable_rss.unwrap_or_default());
|
||||
*detailed_indexer_body
|
||||
.get_mut("enableAutomaticSearch")
|
||||
.unwrap() = json!(enable_automatic_search.unwrap_or_default());
|
||||
*detailed_indexer_body
|
||||
.get_mut("enableInteractiveSearch")
|
||||
.unwrap() = json!(enable_interactive_search.unwrap_or_default());
|
||||
*detailed_indexer_body
|
||||
.get_mut("fields")
|
||||
.unwrap()
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.iter_mut()
|
||||
.find(|field| field["name"] == "baseUrl")
|
||||
.unwrap()
|
||||
.get_mut("value")
|
||||
.unwrap() = json!(url.text.clone());
|
||||
*detailed_indexer_body
|
||||
.get_mut("fields")
|
||||
.unwrap()
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.iter_mut()
|
||||
.find(|field| field["name"] == "apiKey")
|
||||
.unwrap()
|
||||
.get_mut("value")
|
||||
.unwrap() = json!(api_key.text.clone());
|
||||
*detailed_indexer_body.get_mut("tags").unwrap() = json!(tag_ids_vec);
|
||||
let seed_ratio_field_option = detailed_indexer_body
|
||||
.get_mut("fields")
|
||||
.unwrap()
|
||||
.as_array_mut()
|
||||
.unwrap()
|
||||
.iter_mut()
|
||||
.find(|field| field["name"] == "seedCriteria.seedRatio");
|
||||
if let Some(seed_ratio_field) = seed_ratio_field_option {
|
||||
seed_ratio_field
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.insert("value".to_string(), json!(seed_ratio.text.clone()));
|
||||
}
|
||||
|
||||
app.data.radarr_data.edit_indexer_modal = None;
|
||||
|
||||
detailed_indexer_body
|
||||
};
|
||||
|
||||
debug!("Edit indexer body: {body:?}");
|
||||
|
||||
let request_props = self
|
||||
.radarr_request_props_from(
|
||||
format!("{}/{id}", RadarrEvent::EditIndexer.resource()).as_str(),
|
||||
RequestMethod::Put,
|
||||
Some(body),
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn edit_movie(&mut self) {
|
||||
info!("Editing Radarr movie");
|
||||
|
||||
@@ -663,13 +822,13 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.handle_request::<(), Vec<Credit>>(request_props, |credit_vec, mut app| {
|
||||
let cast_vec: Vec<Credit> = credit_vec
|
||||
.iter()
|
||||
.filter(|&credit| credit.credit_type == CreditType::Cast)
|
||||
.cloned()
|
||||
.filter(|credit| credit.credit_type == CreditType::Cast)
|
||||
.collect();
|
||||
let crew_vec: Vec<Credit> = credit_vec
|
||||
.iter()
|
||||
.filter(|&credit| credit.credit_type == CreditType::Crew)
|
||||
.cloned()
|
||||
.filter(|credit| credit.credit_type == CreditType::Crew)
|
||||
.collect();
|
||||
|
||||
if app.data.radarr_data.movie_details_modal.is_none() {
|
||||
@@ -1500,37 +1659,6 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn update_indexer_settings(&mut self) {
|
||||
info!("Updating Radarr indexer settings");
|
||||
|
||||
let body = self
|
||||
.app
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.indexer_settings
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
debug!("Indexer settings body: {body:?}");
|
||||
|
||||
let request_props = self
|
||||
.radarr_request_props_from(
|
||||
RadarrEvent::UpdateIndexerSettings.resource(),
|
||||
RequestMethod::Put,
|
||||
Some(body),
|
||||
)
|
||||
.await;
|
||||
|
||||
self
|
||||
.handle_request::<IndexerSettings, Value>(request_props, |_, _| {})
|
||||
.await;
|
||||
|
||||
self.app.lock().await.data.radarr_data.indexer_settings = None;
|
||||
}
|
||||
|
||||
async fn radarr_request_props_from<T: Serialize + Debug>(
|
||||
&self,
|
||||
resource: &str,
|
||||
|
||||
+409
-140
@@ -13,8 +13,8 @@ mod test {
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::models::radarr_models::{
|
||||
CollectionMovie, IndexerField, IndexerSelectOption, Language, MediaInfo, MinimumAvailability,
|
||||
Monitor, MovieFile, Quality, QualityWrapper, Rating, RatingsList,
|
||||
CollectionMovie, IndexerField, Language, MediaInfo, MinimumAvailability, Monitor, MovieFile,
|
||||
Quality, QualityWrapper, Rating, RatingsList,
|
||||
};
|
||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||
use crate::models::{HorizontallyScrollableText, StatefulTable};
|
||||
@@ -136,7 +136,7 @@ mod test {
|
||||
|
||||
#[rstest]
|
||||
fn test_resource_indexer_settings(
|
||||
#[values(RadarrEvent::GetIndexerSettings, RadarrEvent::UpdateIndexerSettings)]
|
||||
#[values(RadarrEvent::GetIndexerSettings, RadarrEvent::EditAllIndexerSettings)]
|
||||
event: RadarrEvent,
|
||||
) {
|
||||
assert_str_eq!(event.resource(), "/config/indexer");
|
||||
@@ -782,45 +782,6 @@ mod test {
|
||||
async_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_update_indexer_settings_event() {
|
||||
let indexer_settings_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::Put,
|
||||
Some(indexer_settings_json),
|
||||
None,
|
||||
None,
|
||||
RadarrEvent::UpdateIndexerSettings.resource(),
|
||||
)
|
||||
.await;
|
||||
|
||||
app_arc.lock().await.data.radarr_data.indexer_settings = Some(indexer_settings());
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
.handle_radarr_event(RadarrEvent::UpdateIndexerSettings)
|
||||
.await;
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert!(app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.indexer_settings
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_update_collections_event() {
|
||||
let (async_server, app_arc, _server) = mock_radarr_api(
|
||||
@@ -1211,37 +1172,22 @@ mod test {
|
||||
"name": "Test Indexer",
|
||||
"fields": [
|
||||
{
|
||||
"order": 0,
|
||||
"name": "valueIsString",
|
||||
"label": "Value Is String",
|
||||
"value": "hello",
|
||||
"type": "textbox",
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"order": 1,
|
||||
"name": "emptyValueWithSelectOptions",
|
||||
"label": "Empty Value With Select Options",
|
||||
"type": "select",
|
||||
"selectOptions": [
|
||||
{
|
||||
"value": -2,
|
||||
"name": "Original",
|
||||
"order": 0,
|
||||
}
|
||||
]
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"order": 2,
|
||||
"name": "valueIsAnArray",
|
||||
"label": "Value is an array",
|
||||
"value": [1, 2],
|
||||
"type": "select",
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.2",
|
||||
},
|
||||
],
|
||||
"implementationName": "Torznab",
|
||||
"implementation": "Torznab",
|
||||
"configContract": "TorznabSettings",
|
||||
"tags": ["test_tag"],
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
}]);
|
||||
let (async_server, app_arc, _server) = mock_radarr_api(
|
||||
@@ -2083,66 +2029,42 @@ mod test {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_movie_event() {
|
||||
let mut expected_body: Value = serde_json::from_str(MOVIE_JSON).unwrap();
|
||||
*expected_body.get_mut("monitored").unwrap() = json!(false);
|
||||
*expected_body.get_mut("minimumAvailability").unwrap() = json!("announced");
|
||||
*expected_body.get_mut("qualityProfileId").unwrap() = json!(1111);
|
||||
*expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path");
|
||||
*expected_body.get_mut("tags").unwrap() = json!([1, 2]);
|
||||
|
||||
let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource());
|
||||
let (async_details_server, app_arc, mut server) = mock_radarr_api(
|
||||
RequestMethod::Get,
|
||||
async fn test_handle_edit_all_indexer_settings_event() {
|
||||
let indexer_settings_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::Put,
|
||||
Some(indexer_settings_json),
|
||||
None,
|
||||
Some(serde_json::from_str(MOVIE_JSON).unwrap()),
|
||||
None,
|
||||
&resource,
|
||||
RadarrEvent::EditAllIndexerSettings.resource(),
|
||||
)
|
||||
.await;
|
||||
let async_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!("/api/v3{}/1", RadarrEvent::EditMovie.resource()).as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_body))
|
||||
.create_async()
|
||||
.await;
|
||||
{
|
||||
let mut app = app_arc.lock().await;
|
||||
app.data.radarr_data.tags_map =
|
||||
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
|
||||
let mut edit_movie = EditMovieModal {
|
||||
tags: "usenet, testing".to_owned().into(),
|
||||
path: "/nfs/Test Path".to_owned().into(),
|
||||
monitored: Some(false),
|
||||
..EditMovieModal::default()
|
||||
};
|
||||
edit_movie
|
||||
.quality_profile_list
|
||||
.set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]);
|
||||
edit_movie
|
||||
.minimum_availability_list
|
||||
.set_items(Vec::from_iter(MinimumAvailability::iter()));
|
||||
app.data.radarr_data.edit_movie_modal = Some(edit_movie);
|
||||
app.data.radarr_data.movies.set_items(vec![Movie {
|
||||
monitored: false,
|
||||
..movie()
|
||||
}]);
|
||||
app.data.radarr_data.quality_profile_map =
|
||||
BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]);
|
||||
}
|
||||
|
||||
app_arc.lock().await.data.radarr_data.indexer_settings = Some(indexer_settings());
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network.handle_radarr_event(RadarrEvent::EditMovie).await;
|
||||
network
|
||||
.handle_radarr_event(RadarrEvent::EditAllIndexerSettings)
|
||||
.await;
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
async_edit_server.assert_async().await;
|
||||
|
||||
let app = app_arc.lock().await;
|
||||
assert!(app.data.radarr_data.edit_movie_modal.is_none());
|
||||
async_server.assert_async().await;
|
||||
assert!(app_arc
|
||||
.lock()
|
||||
.await
|
||||
.data
|
||||
.radarr_data
|
||||
.indexer_settings
|
||||
.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -2240,6 +2162,369 @@ mod test {
|
||||
assert!(app.data.radarr_data.edit_collection_modal.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_indexer_event() {
|
||||
let indexer_details_json = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.2",
|
||||
},
|
||||
],
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
});
|
||||
let expected_indexer_edit_body_json = json!({
|
||||
"enableRss": false,
|
||||
"enableAutomaticSearch": false,
|
||||
"enableInteractiveSearch": false,
|
||||
"name": "Test Update",
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://localhost:9696/1/",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "test1234",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.3",
|
||||
},
|
||||
],
|
||||
"tags": [1, 2],
|
||||
"id": 1
|
||||
});
|
||||
|
||||
let resource = format!("{}/1", RadarrEvent::GetIndexers.resource());
|
||||
let (async_details_server, app_arc, mut server) = mock_radarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
Some(indexer_details_json),
|
||||
None,
|
||||
&resource,
|
||||
)
|
||||
.await;
|
||||
let async_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!("/api/v3{}/1", RadarrEvent::EditIndexer.resource()).as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_indexer_edit_body_json))
|
||||
.create_async()
|
||||
.await;
|
||||
{
|
||||
let mut app = app_arc.lock().await;
|
||||
app.data.radarr_data.tags_map =
|
||||
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
|
||||
let edit_indexer_modal = EditIndexerModal {
|
||||
name: "Test Update".into(),
|
||||
enable_rss: Some(false),
|
||||
enable_automatic_search: Some(false),
|
||||
enable_interactive_search: Some(false),
|
||||
url: "https://localhost:9696/1/".into(),
|
||||
api_key: "test1234".into(),
|
||||
seed_ratio: "1.3".into(),
|
||||
tags: "usenet, testing".into(),
|
||||
};
|
||||
app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal);
|
||||
app.data.radarr_data.indexers.set_items(vec![indexer()]);
|
||||
}
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network.handle_radarr_event(RadarrEvent::EditIndexer).await;
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
async_edit_server.assert_async().await;
|
||||
|
||||
let app = app_arc.lock().await;
|
||||
assert!(app.data.radarr_data.edit_indexer_modal.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_indexer_event_does_not_add_seed_ratio_when_seed_ratio_field_is_none_in_details(
|
||||
) {
|
||||
let indexer_details_json = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
],
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
});
|
||||
let expected_indexer_edit_body_json = json!({
|
||||
"enableRss": false,
|
||||
"enableAutomaticSearch": false,
|
||||
"enableInteractiveSearch": false,
|
||||
"name": "Test Update",
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://localhost:9696/1/",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "test1234",
|
||||
},
|
||||
],
|
||||
"tags": [1, 2],
|
||||
"id": 1
|
||||
});
|
||||
|
||||
let resource = format!("{}/1", RadarrEvent::GetIndexers.resource());
|
||||
let (async_details_server, app_arc, mut server) = mock_radarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
Some(indexer_details_json),
|
||||
None,
|
||||
&resource,
|
||||
)
|
||||
.await;
|
||||
let async_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!("/api/v3{}/1", RadarrEvent::EditIndexer.resource()).as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_indexer_edit_body_json))
|
||||
.create_async()
|
||||
.await;
|
||||
{
|
||||
let mut app = app_arc.lock().await;
|
||||
app.data.radarr_data.tags_map =
|
||||
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
|
||||
let edit_indexer_modal = EditIndexerModal {
|
||||
name: "Test Update".into(),
|
||||
enable_rss: Some(false),
|
||||
enable_automatic_search: Some(false),
|
||||
enable_interactive_search: Some(false),
|
||||
url: "https://localhost:9696/1/".into(),
|
||||
api_key: "test1234".into(),
|
||||
seed_ratio: "1.3".into(),
|
||||
tags: "usenet, testing".into(),
|
||||
};
|
||||
app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal);
|
||||
let mut indexer = indexer();
|
||||
indexer.fields = Some(
|
||||
indexer
|
||||
.fields
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter(|field| field.name != Some("seedCriteria.seedRatio".to_string()))
|
||||
.collect(),
|
||||
);
|
||||
app.data.radarr_data.indexers.set_items(vec![indexer]);
|
||||
}
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network.handle_radarr_event(RadarrEvent::EditIndexer).await;
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
async_edit_server.assert_async().await;
|
||||
|
||||
let app = app_arc.lock().await;
|
||||
assert!(app.data.radarr_data.edit_indexer_modal.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_indexer_event_populates_the_seed_ratio_value_when_seed_ratio_field_is_present_in_details(
|
||||
) {
|
||||
let indexer_details_json = json!({
|
||||
"enableRss": true,
|
||||
"enableAutomaticSearch": true,
|
||||
"enableInteractiveSearch": true,
|
||||
"name": "Test Indexer",
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://test.com",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
},
|
||||
],
|
||||
"tags": [1],
|
||||
"id": 1
|
||||
});
|
||||
let expected_indexer_edit_body_json = json!({
|
||||
"enableRss": false,
|
||||
"enableAutomaticSearch": false,
|
||||
"enableInteractiveSearch": false,
|
||||
"name": "Test Update",
|
||||
"fields": [
|
||||
{
|
||||
"name": "baseUrl",
|
||||
"value": "https://localhost:9696/1/",
|
||||
},
|
||||
{
|
||||
"name": "apiKey",
|
||||
"value": "test1234",
|
||||
},
|
||||
{
|
||||
"name": "seedCriteria.seedRatio",
|
||||
"value": "1.3",
|
||||
},
|
||||
],
|
||||
"tags": [1, 2],
|
||||
"id": 1
|
||||
});
|
||||
|
||||
let resource = format!("{}/1", RadarrEvent::GetIndexers.resource());
|
||||
let (async_details_server, app_arc, mut server) = mock_radarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
Some(indexer_details_json),
|
||||
None,
|
||||
&resource,
|
||||
)
|
||||
.await;
|
||||
let async_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!("/api/v3{}/1", RadarrEvent::EditIndexer.resource()).as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_indexer_edit_body_json))
|
||||
.create_async()
|
||||
.await;
|
||||
{
|
||||
let mut app = app_arc.lock().await;
|
||||
app.data.radarr_data.tags_map =
|
||||
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
|
||||
let edit_indexer_modal = EditIndexerModal {
|
||||
name: "Test Update".into(),
|
||||
enable_rss: Some(false),
|
||||
enable_automatic_search: Some(false),
|
||||
enable_interactive_search: Some(false),
|
||||
url: "https://localhost:9696/1/".into(),
|
||||
api_key: "test1234".into(),
|
||||
seed_ratio: "1.3".into(),
|
||||
tags: "usenet, testing".into(),
|
||||
};
|
||||
app.data.radarr_data.edit_indexer_modal = Some(edit_indexer_modal);
|
||||
let mut indexer = indexer();
|
||||
indexer.fields = Some(
|
||||
indexer
|
||||
.fields
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|mut field| {
|
||||
if field.name == Some("seedCriteria.seedRatio".to_string()) {
|
||||
field.value = None;
|
||||
field
|
||||
} else {
|
||||
field
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
app.data.radarr_data.indexers.set_items(vec![indexer]);
|
||||
}
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network.handle_radarr_event(RadarrEvent::EditIndexer).await;
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
async_edit_server.assert_async().await;
|
||||
|
||||
let app = app_arc.lock().await;
|
||||
assert!(app.data.radarr_data.edit_indexer_modal.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_edit_movie_event() {
|
||||
let mut expected_body: Value = serde_json::from_str(MOVIE_JSON).unwrap();
|
||||
*expected_body.get_mut("monitored").unwrap() = json!(false);
|
||||
*expected_body.get_mut("minimumAvailability").unwrap() = json!("announced");
|
||||
*expected_body.get_mut("qualityProfileId").unwrap() = json!(1111);
|
||||
*expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path");
|
||||
*expected_body.get_mut("tags").unwrap() = json!([1, 2]);
|
||||
|
||||
let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource());
|
||||
let (async_details_server, app_arc, mut server) = mock_radarr_api(
|
||||
RequestMethod::Get,
|
||||
None,
|
||||
Some(serde_json::from_str(MOVIE_JSON).unwrap()),
|
||||
None,
|
||||
&resource,
|
||||
)
|
||||
.await;
|
||||
let async_edit_server = server
|
||||
.mock(
|
||||
"PUT",
|
||||
format!("/api/v3{}/1", RadarrEvent::EditMovie.resource()).as_str(),
|
||||
)
|
||||
.with_status(202)
|
||||
.match_header("X-Api-Key", "test1234")
|
||||
.match_body(Matcher::Json(expected_body))
|
||||
.create_async()
|
||||
.await;
|
||||
{
|
||||
let mut app = app_arc.lock().await;
|
||||
app.data.radarr_data.tags_map =
|
||||
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
|
||||
let mut edit_movie = EditMovieModal {
|
||||
tags: "usenet, testing".to_owned().into(),
|
||||
path: "/nfs/Test Path".to_owned().into(),
|
||||
monitored: Some(false),
|
||||
..EditMovieModal::default()
|
||||
};
|
||||
edit_movie
|
||||
.quality_profile_list
|
||||
.set_items(vec!["Any".to_owned(), "HD - 1080p".to_owned()]);
|
||||
edit_movie
|
||||
.minimum_availability_list
|
||||
.set_items(Vec::from_iter(MinimumAvailability::iter()));
|
||||
app.data.radarr_data.edit_movie_modal = Some(edit_movie);
|
||||
app.data.radarr_data.movies.set_items(vec![Movie {
|
||||
monitored: false,
|
||||
..movie()
|
||||
}]);
|
||||
app.data.radarr_data.quality_profile_map =
|
||||
BiMap::from_iter([(1111, "Any".to_owned()), (2222, "HD - 1080p".to_owned())]);
|
||||
}
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network.handle_radarr_event(RadarrEvent::EditMovie).await;
|
||||
|
||||
async_details_server.assert_async().await;
|
||||
async_edit_server.assert_async().await;
|
||||
|
||||
let app = app_arc.lock().await;
|
||||
assert!(app.data.radarr_data.edit_movie_modal.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_download_release_event() {
|
||||
let (async_server, app_arc, _server) = mock_radarr_api(
|
||||
@@ -2786,36 +3071,20 @@ mod test {
|
||||
implementation_name: Some("Torznab".to_owned()),
|
||||
implementation: Some("Torznab".to_owned()),
|
||||
config_contract: Some("TorznabSettings".to_owned()),
|
||||
tags: Some(vec!["test_tag".to_owned()]),
|
||||
tags: vec![Number::from(1)],
|
||||
id: 1,
|
||||
fields: Some(vec![
|
||||
IndexerField {
|
||||
order: 0,
|
||||
name: Some("valueIsString".to_owned()),
|
||||
label: Some("Value Is String".to_owned()),
|
||||
value: Some(json!("hello")),
|
||||
field_type: Some("textbox".to_owned()),
|
||||
select_options: None,
|
||||
name: Some("baseUrl".to_owned()),
|
||||
value: Some(json!("https://test.com")),
|
||||
},
|
||||
IndexerField {
|
||||
order: 1,
|
||||
name: Some("emptyValueWithSelectOptions".to_owned()),
|
||||
label: Some("Empty Value With Select Options".to_owned()),
|
||||
value: None,
|
||||
field_type: Some("select".to_owned()),
|
||||
select_options: Some(vec![IndexerSelectOption {
|
||||
value: -2,
|
||||
name: Some("Original".to_owned()),
|
||||
order: 0,
|
||||
}]),
|
||||
name: Some("apiKey".to_owned()),
|
||||
value: Some(json!("")),
|
||||
},
|
||||
IndexerField {
|
||||
order: 2,
|
||||
name: Some("valueIsAnArray".to_owned()),
|
||||
label: Some("Value is an array".to_owned()),
|
||||
value: Some(json!([1, 2])),
|
||||
field_type: Some("select".to_owned()),
|
||||
select_options: None,
|
||||
name: Some("seedCriteria.seedRatio".to_owned()),
|
||||
value: Some(json!("1.2")),
|
||||
},
|
||||
]),
|
||||
}
|
||||
|
||||
+59
-28
@@ -676,15 +676,26 @@ fn draw_help_and_get_content_rect(f: &mut Frame<'_>, area: Rect, help: Option<St
|
||||
}
|
||||
}
|
||||
|
||||
pub fn draw_text_box(
|
||||
f: &mut Frame<'_>,
|
||||
text_box_area: Rect,
|
||||
block_title: Option<&str>,
|
||||
block_content: &str,
|
||||
offset: usize,
|
||||
should_show_cursor: bool,
|
||||
is_selected: bool,
|
||||
) {
|
||||
pub struct TextBoxProps<'a> {
|
||||
pub text_box_area: Rect,
|
||||
pub block_title: Option<&'a str>,
|
||||
pub block_content: &'a str,
|
||||
pub offset: usize,
|
||||
pub should_show_cursor: bool,
|
||||
pub is_selected: bool,
|
||||
pub cursor_after_string: bool,
|
||||
}
|
||||
|
||||
pub fn draw_text_box(f: &mut Frame<'_>, text_box_props: TextBoxProps<'_>) {
|
||||
let TextBoxProps {
|
||||
text_box_area,
|
||||
block_title,
|
||||
block_content,
|
||||
offset,
|
||||
should_show_cursor,
|
||||
is_selected,
|
||||
cursor_after_string,
|
||||
} = text_box_props;
|
||||
let (block, style) = if let Some(title) = block_title {
|
||||
(title_block_centered(title), style_default())
|
||||
} else {
|
||||
@@ -703,19 +714,33 @@ pub fn draw_text_box(
|
||||
f.render_widget(paragraph, text_box_area);
|
||||
|
||||
if should_show_cursor {
|
||||
show_cursor(f, text_box_area, offset, block_content);
|
||||
show_cursor(f, text_box_area, offset, block_content, cursor_after_string);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LabeledTextBoxProps<'a> {
|
||||
pub area: Rect,
|
||||
pub label: &'a str,
|
||||
pub text: &'a str,
|
||||
pub offset: usize,
|
||||
pub is_selected: bool,
|
||||
pub should_show_cursor: bool,
|
||||
pub cursor_after_string: bool,
|
||||
}
|
||||
|
||||
pub fn draw_text_box_with_label(
|
||||
f: &mut Frame<'_>,
|
||||
area: Rect,
|
||||
label: &str,
|
||||
text: &str,
|
||||
offset: usize,
|
||||
is_selected: bool,
|
||||
should_show_cursor: bool,
|
||||
labeled_text_box_props: LabeledTextBoxProps<'_>,
|
||||
) {
|
||||
let LabeledTextBoxProps {
|
||||
area,
|
||||
label,
|
||||
text,
|
||||
offset,
|
||||
is_selected,
|
||||
should_show_cursor,
|
||||
cursor_after_string,
|
||||
} = labeled_text_box_props;
|
||||
let horizontal_chunks = horizontal_chunks(
|
||||
vec![
|
||||
Constraint::Percentage(48),
|
||||
@@ -734,12 +759,15 @@ pub fn draw_text_box_with_label(
|
||||
|
||||
draw_text_box(
|
||||
f,
|
||||
horizontal_chunks[1],
|
||||
None,
|
||||
text,
|
||||
offset,
|
||||
should_show_cursor,
|
||||
is_selected,
|
||||
TextBoxProps {
|
||||
text_box_area: horizontal_chunks[1],
|
||||
block_title: None,
|
||||
block_content: text,
|
||||
offset,
|
||||
should_show_cursor,
|
||||
is_selected,
|
||||
cursor_after_string,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -761,12 +789,15 @@ pub fn draw_input_box_popup(
|
||||
|
||||
draw_text_box(
|
||||
f,
|
||||
chunks[0],
|
||||
Some(box_title),
|
||||
&box_content.text,
|
||||
*box_content.offset.borrow(),
|
||||
true,
|
||||
false,
|
||||
TextBoxProps {
|
||||
text_box_area: chunks[0],
|
||||
block_title: Some(box_title),
|
||||
block_content: &box_content.text,
|
||||
offset: *box_content.offset.borrow(),
|
||||
should_show_cursor: true,
|
||||
is_selected: false,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
|
||||
let help = Paragraph::new("<esc> cancel")
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::ui::utils::{
|
||||
use crate::ui::{
|
||||
draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup,
|
||||
draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup,
|
||||
draw_selectable_list, draw_text_box_with_label, DrawUi,
|
||||
draw_selectable_list, draw_text_box_with_label, DrawUi, LabeledTextBoxProps,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -191,12 +191,16 @@ fn draw_edit_collection_confirmation_prompt(
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
chunks[4],
|
||||
"Root Folder",
|
||||
&path.text,
|
||||
*path.offset.borrow(),
|
||||
selected_block == &ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
||||
active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
||||
LabeledTextBoxProps {
|
||||
area: chunks[4],
|
||||
label: "Root Folder",
|
||||
text: &path.text,
|
||||
offset: *path.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
||||
should_show_cursor: active_radarr_block
|
||||
== ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
use crate::app::App;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::models::Route;
|
||||
use crate::ui::radarr_ui::indexers::draw_indexers;
|
||||
use crate::ui::utils::{
|
||||
horizontal_chunks, horizontal_chunks_with_margin, title_block_centered, vertical_chunks,
|
||||
vertical_chunks_with_margin,
|
||||
};
|
||||
use crate::ui::{
|
||||
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading,
|
||||
DrawUi, LabeledTextBoxProps,
|
||||
};
|
||||
use ratatui::layout::{Constraint, Rect};
|
||||
use ratatui::Frame;
|
||||
use std::iter;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "edit_indexer_ui_tests.rs"]
|
||||
mod edit_indexer_ui_tests;
|
||||
|
||||
pub(super) struct EditIndexerUi;
|
||||
|
||||
impl DrawUi for EditIndexerUi {
|
||||
fn accepts(route: Route) -> bool {
|
||||
if let Route::Radarr(active_radarr_block, _) = route {
|
||||
return EDIT_INDEXER_BLOCKS.contains(&active_radarr_block);
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) {
|
||||
draw_popup_over(
|
||||
f,
|
||||
app,
|
||||
content_rect,
|
||||
draw_indexers,
|
||||
draw_edit_indexer_prompt,
|
||||
70,
|
||||
45,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) {
|
||||
let block = title_block_centered("Edit Indexer");
|
||||
let yes_no_value = app.data.radarr_data.prompt_confirm;
|
||||
let selected_block = app.data.radarr_data.selected_block.get_active_block();
|
||||
let highlight_yes_no = selected_block == &ActiveRadarrBlock::EditIndexerConfirmPrompt;
|
||||
let edit_indexer_modal_option = &app.data.radarr_data.edit_indexer_modal;
|
||||
let protocol = &app.data.radarr_data.indexers.current_selection().protocol;
|
||||
|
||||
if edit_indexer_modal_option.is_some() {
|
||||
let edit_indexer_modal = edit_indexer_modal_option.as_ref().unwrap();
|
||||
f.render_widget(block, prompt_area);
|
||||
|
||||
let chunks = vertical_chunks_with_margin(
|
||||
vec![Constraint::Min(0), Constraint::Length(3)],
|
||||
prompt_area,
|
||||
1,
|
||||
);
|
||||
|
||||
let split_chunks = horizontal_chunks_with_margin(
|
||||
vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)],
|
||||
chunks[0],
|
||||
1,
|
||||
);
|
||||
|
||||
let left_chunks = vertical_chunks(
|
||||
vec![
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Min(0),
|
||||
],
|
||||
split_chunks[0],
|
||||
);
|
||||
let right_chunks = vertical_chunks(
|
||||
vec![
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Length(3),
|
||||
Constraint::Min(0),
|
||||
],
|
||||
split_chunks[1],
|
||||
);
|
||||
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
LabeledTextBoxProps {
|
||||
area: left_chunks[0],
|
||||
label: "Name",
|
||||
text: &edit_indexer_modal.name.text,
|
||||
offset: *edit_indexer_modal.name.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerNameInput,
|
||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerNameInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
LabeledTextBoxProps {
|
||||
area: right_chunks[0],
|
||||
label: "URL",
|
||||
text: &edit_indexer_modal.url.text,
|
||||
offset: *edit_indexer_modal.url.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerUrlInput,
|
||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerUrlInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
LabeledTextBoxProps {
|
||||
area: right_chunks[1],
|
||||
label: "API Key",
|
||||
text: &edit_indexer_modal.api_key.text,
|
||||
offset: *edit_indexer_modal.api_key.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerApiKeyInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
if protocol == "torrent" {
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
LabeledTextBoxProps {
|
||||
area: right_chunks[2],
|
||||
label: "Seed Ratio",
|
||||
text: &edit_indexer_modal.seed_ratio.text,
|
||||
offset: *edit_indexer_modal.seed_ratio.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
LabeledTextBoxProps {
|
||||
area: right_chunks[3],
|
||||
label: "Tags",
|
||||
text: &edit_indexer_modal.tags.text,
|
||||
offset: *edit_indexer_modal.tags.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerTagsInput,
|
||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerTagsInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
LabeledTextBoxProps {
|
||||
area: right_chunks[2],
|
||||
label: "Tags",
|
||||
text: &edit_indexer_modal.tags.text,
|
||||
offset: *edit_indexer_modal.tags.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerTagsInput,
|
||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerTagsInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
draw_checkbox_with_label(
|
||||
f,
|
||||
left_chunks[1],
|
||||
"Enable RSS",
|
||||
edit_indexer_modal.enable_rss.unwrap_or_default(),
|
||||
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableRss,
|
||||
);
|
||||
draw_checkbox_with_label(
|
||||
f,
|
||||
left_chunks[2],
|
||||
"Enable Automatic Search",
|
||||
edit_indexer_modal
|
||||
.enable_automatic_search
|
||||
.unwrap_or_default(),
|
||||
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
||||
);
|
||||
draw_checkbox_with_label(
|
||||
f,
|
||||
left_chunks[3],
|
||||
"Enable Interactive Search",
|
||||
edit_indexer_modal
|
||||
.enable_interactive_search
|
||||
.unwrap_or_default(),
|
||||
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||
);
|
||||
|
||||
let button_chunks = horizontal_chunks(
|
||||
iter::repeat(Constraint::Ratio(1, 4)).take(4).collect(),
|
||||
chunks[1],
|
||||
);
|
||||
|
||||
draw_button(
|
||||
f,
|
||||
button_chunks[1],
|
||||
"Save",
|
||||
yes_no_value && highlight_yes_no,
|
||||
);
|
||||
draw_button(
|
||||
f,
|
||||
button_chunks[2],
|
||||
"Cancel",
|
||||
!yes_no_value && highlight_yes_no,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
loading(f, block, prompt_area, app.is_loading);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
|
||||
use crate::ui::radarr_ui::indexers::edit_indexer_ui::EditIndexerUi;
|
||||
use crate::ui::DrawUi;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[test]
|
||||
fn test_edit_indexer_ui_accepts() {
|
||||
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||
if EDIT_INDEXER_BLOCKS.contains(&active_radarr_block) {
|
||||
assert!(EditIndexerUi::accepts(active_radarr_block.into()));
|
||||
} else {
|
||||
assert!(!EditIndexerUi::accepts(active_radarr_block.into()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ use crate::ui::utils::{
|
||||
vertical_chunks_with_margin,
|
||||
};
|
||||
use crate::ui::{
|
||||
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading, DrawUi,
|
||||
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading,
|
||||
DrawUi, LabeledTextBoxProps,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -91,57 +92,82 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
left_chunks[0],
|
||||
"Minimum Age (minutes) ▴▾",
|
||||
&indexer_settings.minimum_age.to_string(),
|
||||
0,
|
||||
selected_block == &ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
|
||||
active_radarr_block == ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
|
||||
LabeledTextBoxProps {
|
||||
area: left_chunks[0],
|
||||
label: "Minimum Age (minutes) ▴▾",
|
||||
text: &indexer_settings.minimum_age.to_string(),
|
||||
offset: 0,
|
||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
|
||||
should_show_cursor: active_radarr_block
|
||||
== ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
|
||||
cursor_after_string: false,
|
||||
},
|
||||
);
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
left_chunks[1],
|
||||
"Retention (days) ▴▾",
|
||||
&indexer_settings.retention.to_string(),
|
||||
0,
|
||||
selected_block == &ActiveRadarrBlock::IndexerSettingsRetentionInput,
|
||||
active_radarr_block == ActiveRadarrBlock::IndexerSettingsRetentionInput,
|
||||
LabeledTextBoxProps {
|
||||
area: left_chunks[1],
|
||||
label: "Retention (days) ▴▾",
|
||||
text: &indexer_settings.retention.to_string(),
|
||||
offset: 0,
|
||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsRetentionInput,
|
||||
should_show_cursor: active_radarr_block
|
||||
== ActiveRadarrBlock::IndexerSettingsRetentionInput,
|
||||
cursor_after_string: false,
|
||||
},
|
||||
);
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
left_chunks[2],
|
||||
"Maximum Size (MB) ▴▾",
|
||||
&indexer_settings.maximum_size.to_string(),
|
||||
0,
|
||||
selected_block == &ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
|
||||
active_radarr_block == ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
|
||||
LabeledTextBoxProps {
|
||||
area: left_chunks[2],
|
||||
label: "Maximum Size (MB) ▴▾",
|
||||
text: &indexer_settings.maximum_size.to_string(),
|
||||
offset: 0,
|
||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
|
||||
should_show_cursor: active_radarr_block
|
||||
== ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
|
||||
cursor_after_string: false,
|
||||
},
|
||||
);
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
right_chunks[0],
|
||||
"Availability Delay (days) ▴▾",
|
||||
&indexer_settings.availability_delay.to_string(),
|
||||
0,
|
||||
selected_block == &ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
|
||||
active_radarr_block == ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
|
||||
LabeledTextBoxProps {
|
||||
area: right_chunks[0],
|
||||
label: "Availability Delay (days) ▴▾",
|
||||
text: &indexer_settings.availability_delay.to_string(),
|
||||
offset: 0,
|
||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
|
||||
should_show_cursor: active_radarr_block
|
||||
== ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
|
||||
cursor_after_string: false,
|
||||
},
|
||||
);
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
right_chunks[1],
|
||||
"RSS Sync Interval (minutes) ▴▾",
|
||||
&indexer_settings.rss_sync_interval.to_string(),
|
||||
0,
|
||||
selected_block == &ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
|
||||
active_radarr_block == ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
|
||||
LabeledTextBoxProps {
|
||||
area: right_chunks[1],
|
||||
label: "RSS Sync Interval (minutes) ▴▾",
|
||||
text: &indexer_settings.rss_sync_interval.to_string(),
|
||||
offset: 0,
|
||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
|
||||
should_show_cursor: active_radarr_block
|
||||
== ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
|
||||
cursor_after_string: false,
|
||||
},
|
||||
);
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
right_chunks[2],
|
||||
"Whitelisted Subtitle Tags",
|
||||
&indexer_settings.whitelisted_hardcoded_subs.text,
|
||||
*indexer_settings.whitelisted_hardcoded_subs.offset.borrow(),
|
||||
selected_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
|
||||
active_radarr_block == ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
|
||||
LabeledTextBoxProps {
|
||||
area: right_chunks[2],
|
||||
label: "Whitelisted Subtitle Tags",
|
||||
text: &indexer_settings.whitelisted_hardcoded_subs.text,
|
||||
offset: *indexer_settings.whitelisted_hardcoded_subs.offset.borrow(),
|
||||
is_selected: selected_block
|
||||
== &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
|
||||
should_show_cursor: active_radarr_block
|
||||
== ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ mod tests {
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use crate::models::servarr_data::radarr::radarr_data::{
|
||||
ActiveRadarrBlock, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
|
||||
ActiveRadarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS,
|
||||
};
|
||||
use crate::ui::radarr_ui::indexers::IndexersUi;
|
||||
use crate::ui::DrawUi;
|
||||
@@ -13,6 +13,8 @@ mod tests {
|
||||
let mut indexers_blocks = Vec::new();
|
||||
indexers_blocks.extend(INDEXERS_BLOCKS);
|
||||
indexers_blocks.extend(INDEXER_SETTINGS_BLOCKS);
|
||||
indexers_blocks.extend(EDIT_INDEXER_BLOCKS);
|
||||
indexers_blocks.push(ActiveRadarrBlock::TestAllIndexers);
|
||||
|
||||
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
|
||||
if indexers_blocks.contains(&active_radarr_block) {
|
||||
|
||||
@@ -7,24 +7,29 @@ use crate::app::App;
|
||||
use crate::models::radarr_models::Indexer;
|
||||
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, INDEXERS_BLOCKS};
|
||||
use crate::models::Route;
|
||||
use crate::ui::radarr_ui::indexers::edit_indexer_ui::EditIndexerUi;
|
||||
use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi;
|
||||
use crate::ui::radarr_ui::indexers::test_all_indexers_ui::TestAllIndexersUi;
|
||||
use crate::ui::utils::{layout_block_top_border, style_failure, style_primary, style_success};
|
||||
use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps};
|
||||
|
||||
mod edit_indexer_ui;
|
||||
mod indexer_settings_ui;
|
||||
mod test_all_indexers_ui;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "indexers_ui_tests.rs"]
|
||||
mod indexers_ui_tests;
|
||||
mod test_all_indexers_ui;
|
||||
|
||||
pub(super) struct IndexersUi;
|
||||
|
||||
impl DrawUi for IndexersUi {
|
||||
fn accepts(route: Route) -> bool {
|
||||
if let Route::Radarr(active_radarr_block, _) = route {
|
||||
return IndexerSettingsUi::accepts(route) || INDEXERS_BLOCKS.contains(&active_radarr_block);
|
||||
return EditIndexerUi::accepts(route)
|
||||
|| IndexerSettingsUi::accepts(route)
|
||||
|| TestAllIndexersUi::accepts(route)
|
||||
|| INDEXERS_BLOCKS.contains(&active_radarr_block);
|
||||
}
|
||||
|
||||
false
|
||||
@@ -45,6 +50,7 @@ impl DrawUi for IndexersUi {
|
||||
};
|
||||
|
||||
match route {
|
||||
_ if EditIndexerUi::accepts(route) => EditIndexerUi::draw(f, app, content_rect),
|
||||
_ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, content_rect),
|
||||
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, content_rect),
|
||||
Route::Radarr(active_radarr_block, _) if INDEXERS_BLOCKS.contains(&active_radarr_block) => {
|
||||
@@ -69,13 +75,15 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
"Automatic Search",
|
||||
"Interactive Search",
|
||||
"Priority",
|
||||
"Tags",
|
||||
],
|
||||
constraints: vec![
|
||||
Constraint::Ratio(1, 5),
|
||||
Constraint::Ratio(1, 5),
|
||||
Constraint::Ratio(1, 5),
|
||||
Constraint::Ratio(1, 5),
|
||||
Constraint::Ratio(1, 5),
|
||||
Constraint::Percentage(25),
|
||||
Constraint::Percentage(13),
|
||||
Constraint::Percentage(13),
|
||||
Constraint::Percentage(13),
|
||||
Constraint::Percentage(13),
|
||||
Constraint::Percentage(23),
|
||||
],
|
||||
help: app
|
||||
.data
|
||||
@@ -90,6 +98,7 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
enable_automatic_search,
|
||||
enable_interactive_search,
|
||||
priority,
|
||||
tags,
|
||||
..
|
||||
} = indexer;
|
||||
let bool_to_text = |flag: bool| {
|
||||
@@ -112,6 +121,19 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
bool_to_text(*enable_interactive_search);
|
||||
let mut interactive_search = Text::from(interactive_search_text);
|
||||
interactive_search.patch_style(interactive_search_style);
|
||||
let tags: String = tags
|
||||
.iter()
|
||||
.map(|tag_id| {
|
||||
app
|
||||
.data
|
||||
.radarr_data
|
||||
.tags_map
|
||||
.get_by_left(&tag_id.as_i64().unwrap())
|
||||
.unwrap()
|
||||
.clone()
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ");
|
||||
|
||||
Row::new(vec![
|
||||
Cell::from(name.clone().unwrap_or_default()),
|
||||
@@ -119,6 +141,7 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
Cell::from(automatic_search),
|
||||
Cell::from(interactive_search),
|
||||
Cell::from(priority.to_string()),
|
||||
Cell::from(tags),
|
||||
])
|
||||
.style(style_primary())
|
||||
},
|
||||
|
||||
@@ -19,7 +19,8 @@ use crate::ui::utils::{
|
||||
use crate::ui::{
|
||||
draw_button, draw_drop_down_menu_button, draw_drop_down_popup, draw_error_popup,
|
||||
draw_error_popup_over, draw_large_popup_over, draw_medium_popup_over, draw_selectable_list,
|
||||
draw_table, draw_text_box, draw_text_box_with_label, DrawUi, TableProps,
|
||||
draw_table, draw_text_box, draw_text_box_with_label, DrawUi, LabeledTextBoxProps, TableProps,
|
||||
TextBoxProps,
|
||||
};
|
||||
use crate::utils::convert_runtime;
|
||||
use crate::App;
|
||||
@@ -135,12 +136,15 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
ActiveRadarrBlock::AddMovieSearchInput => {
|
||||
draw_text_box(
|
||||
f,
|
||||
chunks[0],
|
||||
Some("Add Movie"),
|
||||
block_content,
|
||||
offset,
|
||||
true,
|
||||
false,
|
||||
TextBoxProps {
|
||||
text_box_area: chunks[0],
|
||||
block_title: Some("Add Movie"),
|
||||
block_content,
|
||||
offset,
|
||||
should_show_cursor: true,
|
||||
is_selected: false,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
f.render_widget(layout_block(), chunks[1]);
|
||||
|
||||
@@ -267,12 +271,15 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||
|
||||
draw_text_box(
|
||||
f,
|
||||
chunks[0],
|
||||
Some("Add Movie"),
|
||||
block_content,
|
||||
offset,
|
||||
false,
|
||||
false,
|
||||
TextBoxProps {
|
||||
text_box_area: chunks[0],
|
||||
block_title: Some("Add Movie"),
|
||||
block_content,
|
||||
offset,
|
||||
should_show_cursor: false,
|
||||
is_selected: false,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -441,12 +448,15 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
chunks[5],
|
||||
"Tags",
|
||||
&tags.text,
|
||||
*tags.offset.borrow(),
|
||||
selected_block == &ActiveRadarrBlock::AddMovieTagsInput,
|
||||
active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput,
|
||||
LabeledTextBoxProps {
|
||||
area: chunks[5],
|
||||
label: "Tags",
|
||||
text: &tags.text,
|
||||
offset: *tags.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::AddMovieTagsInput,
|
||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use crate::ui::utils::{
|
||||
use crate::ui::{
|
||||
draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup,
|
||||
draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup,
|
||||
draw_selectable_list, draw_text_box_with_label, DrawUi,
|
||||
draw_selectable_list, draw_text_box_with_label, DrawUi, LabeledTextBoxProps,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -179,21 +179,27 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, pro
|
||||
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
chunks[4],
|
||||
"Path",
|
||||
&path.text,
|
||||
*path.offset.borrow(),
|
||||
selected_block == &ActiveRadarrBlock::EditMoviePathInput,
|
||||
active_radarr_block == ActiveRadarrBlock::EditMoviePathInput,
|
||||
LabeledTextBoxProps {
|
||||
area: chunks[4],
|
||||
label: "Path",
|
||||
text: &path.text,
|
||||
offset: *path.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::EditMoviePathInput,
|
||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditMoviePathInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
draw_text_box_with_label(
|
||||
f,
|
||||
chunks[5],
|
||||
"Tags",
|
||||
&tags.text,
|
||||
*tags.offset.borrow(),
|
||||
selected_block == &ActiveRadarrBlock::EditMovieTagsInput,
|
||||
active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput,
|
||||
LabeledTextBoxProps {
|
||||
area: chunks[5],
|
||||
label: "Tags",
|
||||
text: &tags.text,
|
||||
offset: *tags.offset.borrow(),
|
||||
is_selected: selected_block == &ActiveRadarrBlock::EditMovieTagsInput,
|
||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput,
|
||||
cursor_after_string: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+12
-2
@@ -267,8 +267,18 @@ pub fn line_gauge_with_label(title: &str, ratio: f64) -> LineGauge<'_> {
|
||||
.label(Line::from(format!("{title}: {:.0}%", ratio * 100.0)))
|
||||
}
|
||||
|
||||
pub fn show_cursor(f: &mut Frame<'_>, area: Rect, offset: usize, string: &str) {
|
||||
f.set_cursor(area.x + (string.len() - offset) as u16 + 1, area.y + 1);
|
||||
pub fn show_cursor(
|
||||
f: &mut Frame<'_>,
|
||||
area: Rect,
|
||||
offset: usize,
|
||||
string: &str,
|
||||
cursor_after_string: bool,
|
||||
) {
|
||||
if cursor_after_string {
|
||||
f.set_cursor(area.x + (string.len() - offset) as u16 + 1, area.y + 1);
|
||||
} else {
|
||||
f.set_cursor(area.x + 1u16, area.y + 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_width_from_percentage(area: Rect, percentage: u16) -> usize {
|
||||
|
||||
Reference in New Issue
Block a user