From a012945df2401a966ee2730d0c915610147917cb Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Mon, 18 Nov 2024 20:49:07 -0700 Subject: [PATCH] feat(network): Added network support for fetching host and security configs from Sonarr --- .../indexers/indexers_handler_tests.rs | 6 +- src/handlers/radarr_handlers/indexers/mod.rs | 3 +- src/models/mod.rs | 1 + src/models/radarr_models.rs | 115 +--------------- src/models/radarr_models_tests.rs | 35 +---- src/models/servarr_data/radarr/modals.rs | 4 +- .../servarr_data/radarr/modals_tests.rs | 7 +- src/models/servarr_data/radarr/radarr_data.rs | 3 +- src/models/servarr_data/sonarr/sonarr_data.rs | 3 +- src/models/servarr_models.rs | 125 ++++++++++++++++++ src/models/servarr_models_tests.rs | 34 +++++ src/models/sonarr_models.rs | 38 ++---- src/network/radarr_network.rs | 23 ++-- src/network/radarr_network_tests.rs | 10 +- src/network/sonarr_network.rs | 42 +++++- src/network/sonarr_network_tests.rs | 82 +++++++++++- src/ui/radarr_ui/indexers/mod.rs | 2 +- 17 files changed, 328 insertions(+), 205 deletions(-) create mode 100644 src/models/servarr_models.rs create mode 100644 src/models/servarr_models_tests.rs diff --git a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs index 1d3d59e..50d646c 100644 --- a/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs +++ b/src/handlers/radarr_handlers/indexers/indexers_handler_tests.rs @@ -9,16 +9,15 @@ mod tests { use crate::event::Key; use crate::handlers::radarr_handlers::indexers::IndexersHandler; use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::Indexer; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, EDIT_INDEXER_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_BLOCKS, }; + use crate::models::servarr_models::Indexer; use crate::test_handler_delegation; mod test_handle_scroll_up_and_down { use rstest::rstest; - use crate::models::radarr_models::Indexer; use crate::{simple_stateful_iterable_vec, test_iterable_scroll}; use super::*; @@ -65,7 +64,6 @@ mod tests { } mod test_handle_home_end { - use crate::models::radarr_models::Indexer; use crate::{extended_stateful_iterable_vec, test_iterable_home_and_end}; use super::*; @@ -239,11 +237,11 @@ 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 crate::models::servarr_models::{Indexer, IndexerField}; use bimap::BiMap; use pretty_assertions::assert_eq; use serde_json::{Number, Value}; diff --git a/src/handlers/radarr_handlers/indexers/mod.rs b/src/handlers/radarr_handlers/indexers/mod.rs index f8d00fc..84d4832 100644 --- a/src/handlers/radarr_handlers/indexers/mod.rs +++ b/src/handlers/radarr_handlers/indexers/mod.rs @@ -10,7 +10,8 @@ use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, EDIT_INDEXER_NZB_SELECTION_BLOCKS, EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, INDEXERS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, }; -use crate::models::{BlockSelectionState, Scrollable}; +use crate::models::BlockSelectionState; +use crate::models::Scrollable; use crate::network::radarr_network::RadarrEvent; mod edit_indexer_handler; diff --git a/src/models/mod.rs b/src/models/mod.rs index 14abfbb..5541424 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -10,6 +10,7 @@ use servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use sonarr_models::SonarrSerdeable; pub mod radarr_models; pub mod servarr_data; +pub mod servarr_models; pub mod sonarr_models; pub mod stateful_list; pub mod stateful_table; diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index 225d338..5934cfc 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -9,6 +9,7 @@ use strum_macros::EnumIter; use crate::{models::HorizontallyScrollableText, serde_enum_from}; +use super::servarr_models::{HostConfig, Indexer, SecurityConfig}; use super::Serdeable; #[cfg(test)] @@ -57,44 +58,6 @@ pub struct AddRootFolderBody { pub path: String, } -#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, ValueEnum)] -#[serde(rename_all = "camelCase")] -pub enum AuthenticationMethod { - #[default] - Basic, - Forms, - None, -} - -impl Display for AuthenticationMethod { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let authentication_method = match self { - AuthenticationMethod::Basic => "basic", - AuthenticationMethod::Forms => "forms", - AuthenticationMethod::None => "none", - }; - write!(f, "{authentication_method}") - } -} - -#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, ValueEnum)] -#[serde(rename_all = "camelCase")] -pub enum AuthenticationRequired { - Enabled, - #[default] - DisabledForLocalAddresses, -} - -impl Display for AuthenticationRequired { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let authentication_required = match self { - AuthenticationRequired::Enabled => "enabled", - AuthenticationRequired::DisabledForLocalAddresses => "disabledForLocalAddresses", - }; - write!(f, "{authentication_required}") - } -} - #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct BlocklistResponse { pub records: Vec, @@ -123,26 +86,6 @@ pub struct BlocklistItemMovie { pub title: HorizontallyScrollableText, } -#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, ValueEnum)] -#[serde(rename_all = "camelCase")] -pub enum CertificateValidation { - #[default] - Enabled, - DisabledForLocalAddresses, - Disabled, -} - -impl Display for CertificateValidation { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let certificate_validation = match self { - CertificateValidation::Enabled => "enabled", - CertificateValidation::DisabledForLocalAddresses => "disabledForLocalAddresses", - CertificateValidation::Disabled => "disabled", - }; - write!(f, "{certificate_validation}") - } -} - #[derive(Serialize, Deserialize, Derivative, Default, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Collection { @@ -281,51 +224,6 @@ pub struct EditMovieParams { pub clear_tags: bool, } -#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct HostConfig { - pub bind_address: HorizontallyScrollableText, - #[serde(deserialize_with = "super::from_i64")] - pub port: i64, - pub url_base: Option, - pub instance_name: Option, - pub application_url: Option, - pub enable_ssl: bool, - #[serde(deserialize_with = "super::from_i64")] - pub ssl_port: i64, - pub ssl_cert_path: Option, - pub ssl_cert_password: Option, -} - -#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Indexer { - #[serde(deserialize_with = "super::from_i64")] - pub id: i64, - pub name: Option, - pub implementation: Option, - pub implementation_name: Option, - pub config_contract: Option, - pub supports_rss: bool, - pub supports_search: bool, - pub fields: Option>, - pub enable_rss: bool, - pub enable_automatic_search: bool, - pub enable_interactive_search: bool, - pub protocol: String, - #[serde(deserialize_with = "super::from_i64")] - pub priority: i64, - #[serde(deserialize_with = "super::from_i64")] - pub download_client_id: i64, - pub tags: Vec, -} - -#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] -pub struct IndexerField { - pub name: Option, - pub value: Option, -} - #[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IndexerSettings { @@ -634,17 +532,6 @@ pub struct RootFolder { pub unmapped_folders: Option>, } -#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct SecurityConfig { - pub authentication_method: AuthenticationMethod, - pub authentication_required: AuthenticationRequired, - pub username: String, - pub password: Option, - pub api_key: String, - pub certificate_validation: CertificateValidation, -} - #[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SystemStatus { diff --git a/src/models/radarr_models_tests.rs b/src/models/radarr_models_tests.rs index 4553f22..e562eb1 100644 --- a/src/models/radarr_models_tests.rs +++ b/src/models/radarr_models_tests.rs @@ -5,41 +5,14 @@ mod tests { use crate::models::{ radarr_models::{ - AddMovieSearchResult, AuthenticationMethod, AuthenticationRequired, BlocklistItem, - BlocklistResponse, CertificateValidation, Collection, Credit, DiskSpace, DownloadRecord, - DownloadsResponse, Indexer, IndexerSettings, IndexerTestResult, Log, LogResponse, - MinimumAvailability, Monitor, Movie, MovieHistoryItem, QualityProfile, QueueEvent, - RadarrSerdeable, Release, RootFolder, SystemStatus, Tag, Task, TaskName, Update, + AddMovieSearchResult, BlocklistItem, BlocklistResponse, Collection, Credit, DiskSpace, + DownloadRecord, DownloadsResponse, Indexer, IndexerSettings, IndexerTestResult, Log, + LogResponse, MinimumAvailability, Monitor, Movie, MovieHistoryItem, QualityProfile, + QueueEvent, RadarrSerdeable, Release, RootFolder, SystemStatus, Tag, Task, TaskName, Update, }, Serdeable, }; - #[test] - fn test_authentication_method_display() { - assert_str_eq!(AuthenticationMethod::Basic.to_string(), "basic"); - assert_str_eq!(AuthenticationMethod::Forms.to_string(), "forms"); - assert_str_eq!(AuthenticationMethod::None.to_string(), "none"); - } - - #[test] - fn test_authentication_required_display() { - assert_str_eq!(AuthenticationRequired::Enabled.to_string(), "enabled"); - assert_str_eq!( - AuthenticationRequired::DisabledForLocalAddresses.to_string(), - "disabledForLocalAddresses" - ); - } - - #[test] - fn test_certificate_validation_display() { - assert_str_eq!(CertificateValidation::Enabled.to_string(), "enabled"); - assert_str_eq!( - CertificateValidation::DisabledForLocalAddresses.to_string(), - "disabledForLocalAddresses" - ); - assert_str_eq!(CertificateValidation::Disabled.to_string(), "disabled"); - } - #[test] fn test_task_name_display() { assert_str_eq!( diff --git a/src/models/servarr_data/radarr/modals.rs b/src/models/servarr_data/radarr/modals.rs index 074991c..93ef321 100644 --- a/src/models/servarr_data/radarr/modals.rs +++ b/src/models/servarr_data/radarr/modals.rs @@ -1,10 +1,10 @@ use strum::IntoEnumIterator; use crate::models::radarr_models::{ - Collection, Credit, Indexer, MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release, - RootFolder, + Collection, Credit, MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release, RootFolder, }; use crate::models::servarr_data::radarr::radarr_data::RadarrData; +use crate::models::servarr_models::Indexer; use crate::models::stateful_list::StatefulList; use crate::models::stateful_table::StatefulTable; use crate::models::{HorizontallyScrollableText, ScrollableText}; diff --git a/src/models/servarr_data/radarr/modals_tests.rs b/src/models/servarr_data/radarr/modals_tests.rs index 63479aa..5da0298 100644 --- a/src/models/servarr_data/radarr/modals_tests.rs +++ b/src/models/servarr_data/radarr/modals_tests.rs @@ -1,13 +1,12 @@ #[cfg(test)] mod test { - use crate::models::radarr_models::{ - Collection, Indexer, IndexerField, MinimumAvailability, Monitor, Movie, RootFolder, - }; + use crate::models::radarr_models::{Collection, MinimumAvailability, Monitor, Movie, RootFolder}; use crate::models::servarr_data::radarr::modals::{ 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; + use crate::models::servarr_models::{Indexer, IndexerField}; use crate::models::stateful_table::StatefulTable; use bimap::BiMap; use pretty_assertions::{assert_eq, assert_str_eq}; @@ -17,6 +16,8 @@ mod test { #[rstest] fn test_edit_indexer_modal_from_radarr_data(#[values(true, false)] seed_ratio_present: bool) { + use crate::models::servarr_models::{Indexer, IndexerField}; + let mut radarr_data = RadarrData { tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]), ..RadarrData::default() diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index e422670..e6ca06f 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -7,12 +7,13 @@ use crate::app::radarr::radarr_context_clues::{ }; use crate::models::radarr_models::{ AddMovieSearchResult, BlocklistItem, Collection, CollectionMovie, DiskSpace, DownloadRecord, - Indexer, IndexerSettings, Movie, QueueEvent, RootFolder, Task, + IndexerSettings, Movie, QueueEvent, RootFolder, Task, }; use crate::models::servarr_data::radarr::modals::{ AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem, MovieDetailsModal, }; +use crate::models::servarr_models::Indexer; use crate::models::stateful_list::StatefulList; use crate::models::stateful_table::StatefulTable; use crate::models::{ diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index 39f3b13..dd53909 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -3,7 +3,8 @@ use chrono::{DateTime, Utc}; use strum::EnumIter; use crate::models::{ - sonarr_models::{BlocklistItem, DownloadRecord, Episode, Indexer, Series}, + servarr_models::Indexer, + sonarr_models::{BlocklistItem, DownloadRecord, Episode, Series}, stateful_list::StatefulList, stateful_table::StatefulTable, stateful_tree::StatefulTree, diff --git a/src/models/servarr_models.rs b/src/models/servarr_models.rs new file mode 100644 index 0000000..8dca55b --- /dev/null +++ b/src/models/servarr_models.rs @@ -0,0 +1,125 @@ +use std::fmt::{Display, Formatter, Result}; + +use clap::ValueEnum; +use serde::{Deserialize, Serialize}; +use serde_json::{Number, Value}; + +use super::HorizontallyScrollableText; + +#[cfg(test)] +#[path = "servarr_models_tests.rs"] +mod servarr_models_tests; + +#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, ValueEnum)] +#[serde(rename_all = "camelCase")] +pub enum AuthenticationMethod { + #[default] + Basic, + Forms, + None, +} + +impl Display for AuthenticationMethod { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let authentication_method = match self { + AuthenticationMethod::Basic => "basic", + AuthenticationMethod::Forms => "forms", + AuthenticationMethod::None => "none", + }; + write!(f, "{authentication_method}") + } +} + +#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, ValueEnum)] +#[serde(rename_all = "camelCase")] +pub enum AuthenticationRequired { + Enabled, + #[default] + DisabledForLocalAddresses, +} + +impl Display for AuthenticationRequired { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let authentication_required = match self { + AuthenticationRequired::Enabled => "enabled", + AuthenticationRequired::DisabledForLocalAddresses => "disabledForLocalAddresses", + }; + write!(f, "{authentication_required}") + } +} + +#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Debug, ValueEnum)] +#[serde(rename_all = "camelCase")] +pub enum CertificateValidation { + #[default] + Enabled, + DisabledForLocalAddresses, + Disabled, +} + +impl Display for CertificateValidation { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let certificate_validation = match self { + CertificateValidation::Enabled => "enabled", + CertificateValidation::DisabledForLocalAddresses => "disabledForLocalAddresses", + CertificateValidation::Disabled => "disabled", + }; + write!(f, "{certificate_validation}") + } +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct HostConfig { + pub bind_address: HorizontallyScrollableText, + #[serde(deserialize_with = "super::from_i64")] + pub port: i64, + pub url_base: Option, + pub instance_name: Option, + pub application_url: Option, + pub enable_ssl: bool, + #[serde(deserialize_with = "super::from_i64")] + pub ssl_port: i64, + pub ssl_cert_path: Option, + pub ssl_cert_password: Option, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Indexer { + #[serde(deserialize_with = "super::from_i64")] + pub id: i64, + pub name: Option, + pub implementation: Option, + pub implementation_name: Option, + pub config_contract: Option, + pub supports_rss: bool, + pub supports_search: bool, + pub fields: Option>, + pub enable_rss: bool, + pub enable_automatic_search: bool, + pub enable_interactive_search: bool, + pub protocol: String, + #[serde(deserialize_with = "super::from_i64")] + pub priority: i64, + #[serde(deserialize_with = "super::from_i64")] + pub download_client_id: i64, + pub tags: Vec, +} + +#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] +pub struct IndexerField { + pub name: Option, + pub value: Option, +} + +#[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct SecurityConfig { + pub authentication_method: AuthenticationMethod, + pub authentication_required: AuthenticationRequired, + pub username: String, + pub password: Option, + pub api_key: String, + pub certificate_validation: CertificateValidation, +} diff --git a/src/models/servarr_models_tests.rs b/src/models/servarr_models_tests.rs new file mode 100644 index 0000000..8b1468d --- /dev/null +++ b/src/models/servarr_models_tests.rs @@ -0,0 +1,34 @@ +#[cfg(test)] +mod tests { + use pretty_assertions::assert_str_eq; + + use crate::models::servarr_models::{ + AuthenticationMethod, AuthenticationRequired, CertificateValidation, + }; + + #[test] + fn test_authentication_method_display() { + assert_str_eq!(AuthenticationMethod::Basic.to_string(), "basic"); + assert_str_eq!(AuthenticationMethod::Forms.to_string(), "forms"); + assert_str_eq!(AuthenticationMethod::None.to_string(), "none"); + } + + #[test] + fn test_authentication_required_display() { + assert_str_eq!(AuthenticationRequired::Enabled.to_string(), "enabled"); + assert_str_eq!( + AuthenticationRequired::DisabledForLocalAddresses.to_string(), + "disabledForLocalAddresses" + ); + } + + #[test] + fn test_certificate_validation_display() { + assert_str_eq!(CertificateValidation::Enabled.to_string(), "enabled"); + assert_str_eq!( + CertificateValidation::DisabledForLocalAddresses.to_string(), + "disabledForLocalAddresses" + ); + assert_str_eq!(CertificateValidation::Disabled.to_string(), "disabled"); + } +} diff --git a/src/models/sonarr_models.rs b/src/models/sonarr_models.rs index 6b5f6d9..03f6937 100644 --- a/src/models/sonarr_models.rs +++ b/src/models/sonarr_models.rs @@ -9,7 +9,10 @@ use strum::EnumIter; use crate::serde_enum_from; -use super::{HorizontallyScrollableText, Serdeable}; +use super::{ + servarr_models::{HostConfig, Indexer, SecurityConfig}, + HorizontallyScrollableText, Serdeable, +}; #[cfg(test)] #[path = "sonarr_models_tests.rs"] @@ -103,35 +106,6 @@ pub struct EpisodeFile { pub media_info: Option, } -#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Indexer { - #[serde(deserialize_with = "super::from_i64")] - pub id: i64, - pub name: Option, - pub implementation: Option, - pub implementation_name: Option, - pub config_contract: Option, - pub supports_rss: bool, - pub supports_search: bool, - pub fields: Option>, - pub enable_rss: bool, - pub enable_automatic_search: bool, - pub enable_interactive_search: bool, - pub protocol: String, - #[serde(deserialize_with = "super::from_i64")] - pub priority: i64, - #[serde(deserialize_with = "super::from_i64")] - pub download_client_id: i64, - pub tags: Vec, -} - -#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] -pub struct IndexerField { - pub name: Option, - pub value: Option, -} - #[derive(Serialize, Deserialize, Default, Debug, Hash, Clone, PartialEq, Eq, Ord, PartialOrd)] pub struct Language { pub name: String, @@ -359,8 +333,10 @@ pub enum SonarrSerdeable { DownloadsResponse(DownloadsResponse), Episode(Episode), Episodes(Vec), + HostConfig(HostConfig), Indexers(Vec), QualityProfiles(Vec), + SecurityConfig(SecurityConfig), SeriesVec(Vec), SystemStatus(SystemStatus), BlocklistResponse(BlocklistResponse), @@ -385,8 +361,10 @@ serde_enum_from!( DownloadsResponse(DownloadsResponse), Episode(Episode), Episodes(Vec), + HostConfig(HostConfig), Indexers(Vec), QualityProfiles(Vec), + SecurityConfig(SecurityConfig), SeriesVec(Vec), SystemStatus(SystemStatus), BlocklistResponse(BlocklistResponse), diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 8924dd5..99490ad 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -9,16 +9,17 @@ use urlencoding::encode; use crate::models::radarr_models::{ AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, BlocklistResponse, Collection, CollectionMovie, CommandBody, Credit, CreditType, DeleteMovieParams, DiskSpace, DownloadRecord, - DownloadsResponse, EditCollectionParams, EditIndexerParams, EditMovieParams, HostConfig, Indexer, - IndexerSettings, IndexerTestResult, LogResponse, Movie, MovieCommandBody, MovieHistoryItem, - QualityProfile, QueueEvent, RadarrSerdeable, Release, ReleaseDownloadBody, RootFolder, - SecurityConfig, SystemStatus, Tag, Task, TaskName, Update, + DownloadsResponse, EditCollectionParams, EditIndexerParams, EditMovieParams, IndexerSettings, + IndexerTestResult, LogResponse, Movie, MovieCommandBody, MovieHistoryItem, QualityProfile, + QueueEvent, RadarrSerdeable, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task, + TaskName, Update, }; use crate::models::servarr_data::radarr::modals::{ AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem, MovieDetailsModal, }; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; +use crate::models::servarr_models::{HostConfig, Indexer, SecurityConfig}; use crate::models::stateful_table::StatefulTable; use crate::models::{HorizontallyScrollableText, Route, Scrollable, ScrollableText}; use crate::network::{Network, NetworkEvent, RequestMethod}; @@ -192,7 +193,10 @@ impl<'a, 'b> Network<'a, 'b> { RadarrEvent::GetBlocklist => self.get_radarr_blocklist().await.map(RadarrSerdeable::from), RadarrEvent::GetCollections => self.get_collections().await.map(RadarrSerdeable::from), RadarrEvent::GetDownloads => self.get_radarr_downloads().await.map(RadarrSerdeable::from), - RadarrEvent::GetHostConfig => self.get_host_config().await.map(RadarrSerdeable::from), + RadarrEvent::GetHostConfig => self + .get_radarr_host_config() + .await + .map(RadarrSerdeable::from), RadarrEvent::GetIndexers => self.get_radarr_indexers().await.map(RadarrSerdeable::from), RadarrEvent::GetLogs(events) => self .get_radarr_logs(events) @@ -220,7 +224,10 @@ impl<'a, 'b> Network<'a, 'b> { self.get_releases(movie_id).await.map(RadarrSerdeable::from) } RadarrEvent::GetRootFolders => self.get_root_folders().await.map(RadarrSerdeable::from), - RadarrEvent::GetSecurityConfig => self.get_security_config().await.map(RadarrSerdeable::from), + RadarrEvent::GetSecurityConfig => self + .get_radarr_security_config() + .await + .map(RadarrSerdeable::from), RadarrEvent::GetStatus => self.get_radarr_status().await.map(RadarrSerdeable::from), RadarrEvent::GetTags => self.get_tags().await.map(RadarrSerdeable::from), RadarrEvent::GetTasks => self.get_tasks().await.map(RadarrSerdeable::from), @@ -1382,7 +1389,7 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_host_config(&mut self) -> Result { + async fn get_radarr_host_config(&mut self) -> Result { info!("Fetching Radarr host config"); let event = RadarrEvent::GetHostConfig; @@ -1788,7 +1795,7 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_security_config(&mut self) -> Result { + async fn get_radarr_security_config(&mut self) -> Result { info!("Fetching Radarr security config"); let event = RadarrEvent::GetSecurityConfig; diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index bd98025..d4e5344 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -15,11 +15,11 @@ mod test { use crate::app::ServarrConfig; use crate::models::radarr_models::{ - BlocklistItem, BlocklistItemMovie, CollectionMovie, IndexerField, Language, MediaInfo, - MinimumAvailability, Monitor, MovieCollection, MovieFile, Quality, QualityWrapper, Rating, - RatingsList, + BlocklistItem, BlocklistItemMovie, CollectionMovie, Language, MediaInfo, MinimumAvailability, + Monitor, MovieCollection, MovieFile, Quality, QualityWrapper, Rating, RatingsList, }; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; + use crate::models::servarr_models::{HostConfig, IndexerField}; use crate::models::stateful_table::SortOption; use crate::models::HorizontallyScrollableText; use crate::network::network_tests::test_utils::mock_servarr_api; @@ -2225,7 +2225,7 @@ mod test { } #[tokio::test] - async fn test_handle_get_host_config_event() { + async fn test_handle_get_radarr_host_config_event() { let host_config_response = json!({ "bindAddress": "*", "port": 7878, @@ -2905,7 +2905,7 @@ mod test { } #[tokio::test] - async fn test_handle_get_security_config_event() { + async fn test_handle_get_radarr_security_config_event() { let security_config_response = json!({ "authenticationMethod": "forms", "authenticationRequired": "disabledForLocalAddresses", diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index 2db80ff..25aedef 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -9,9 +9,10 @@ use serde_json::{json, Value}; use crate::{ models::{ servarr_data::sonarr::{modals::EpisodeDetailsModal, sonarr_data::ActiveSonarrBlock}, + servarr_models::{HostConfig, Indexer, SecurityConfig}, sonarr_models::{ - BlocklistResponse, DownloadRecord, DownloadsResponse, Episode, Indexer, LogResponse, - QualityProfile, Series, SonarrSerdeable, SystemStatus, + BlocklistResponse, DownloadRecord, DownloadsResponse, Episode, LogResponse, QualityProfile, + Series, SonarrSerdeable, SystemStatus, }, HorizontallyScrollableText, Route, Scrollable, ScrollableText, }, @@ -30,11 +31,13 @@ pub enum SonarrEvent { DeleteBlocklistItem(Option), GetBlocklist, GetDownloads, + GetHostConfig, GetIndexers, GetEpisodeDetails(Option), GetEpisodes(Option), GetLogs(Option), GetQualityProfiles, + GetSecurityConfig, GetStatus, HealthCheck, ListSeries, @@ -48,6 +51,7 @@ impl NetworkResource for SonarrEvent { SonarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000", SonarrEvent::GetDownloads => "/queue", SonarrEvent::GetEpisodes(_) | SonarrEvent::GetEpisodeDetails(_) => "/episode", + SonarrEvent::GetHostConfig | SonarrEvent::GetSecurityConfig => "/config/host", SonarrEvent::GetIndexers => "/indexer", SonarrEvent::GetLogs(_) => "/log", SonarrEvent::GetQualityProfiles => "/qualityprofile", @@ -89,6 +93,10 @@ impl<'a, 'b> Network<'a, 'b> { .await .map(SonarrSerdeable::from), SonarrEvent::GetIndexers => self.get_sonarr_indexers().await.map(SonarrSerdeable::from), + SonarrEvent::GetHostConfig => self + .get_sonarr_host_config() + .await + .map(SonarrSerdeable::from), SonarrEvent::GetQualityProfiles => self .get_sonarr_quality_profiles() .await @@ -97,6 +105,10 @@ impl<'a, 'b> Network<'a, 'b> { .get_sonarr_logs(events) .await .map(SonarrSerdeable::from), + SonarrEvent::GetSecurityConfig => self + .get_sonarr_security_config() + .await + .map(SonarrSerdeable::from), SonarrEvent::GetStatus => self.get_sonarr_status().await.map(SonarrSerdeable::from), SonarrEvent::HealthCheck => self .get_sonarr_healthcheck() @@ -393,6 +405,19 @@ impl<'a, 'b> Network<'a, 'b> { .await } + async fn get_sonarr_host_config(&mut self) -> Result { + info!("Fetching Sonarr host config"); + let event = SonarrEvent::GetHostConfig; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), HostConfig>(request_props, |_, _| ()) + .await + } + async fn get_sonarr_indexers(&mut self) -> Result> { info!("Fetching Sonarr indexers"); let event = SonarrEvent::GetIndexers; @@ -473,6 +498,19 @@ impl<'a, 'b> Network<'a, 'b> { .await } + async fn get_sonarr_security_config(&mut self) -> Result { + info!("Fetching Sonarr security config"); + let event = SonarrEvent::GetSecurityConfig; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), SecurityConfig>(request_props, |_, _| ()) + .await + } + async fn list_series(&mut self) -> Result> { info!("Fetching Sonarr library"); let event = SonarrEvent::ListSeries; diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index 8d3b31e..9a0e819 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -22,9 +22,10 @@ mod test { use crate::app::App; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; + use crate::models::servarr_models::{HostConfig, Indexer, IndexerField, SecurityConfig}; use crate::models::sonarr_models::{ - BlocklistItem, DownloadRecord, DownloadsResponse, Episode, EpisodeFile, Indexer, IndexerField, - Language, LogResponse, MediaInfo, QualityProfile, + BlocklistItem, DownloadRecord, DownloadsResponse, Episode, EpisodeFile, Language, LogResponse, + MediaInfo, QualityProfile, }; use crate::models::sonarr_models::{BlocklistResponse, Quality}; use crate::models::sonarr_models::{QualityWrapper, SystemStatus}; @@ -137,6 +138,13 @@ mod test { assert_str_eq!(event.resource(), "/series"); } + #[rstest] + fn test_resource_host_config( + #[values(SonarrEvent::GetHostConfig, SonarrEvent::GetSecurityConfig)] event: SonarrEvent, + ) { + assert_str_eq!(event.resource(), "/config/host"); + } + #[rstest] fn test_resource_indexer(#[values(SonarrEvent::GetIndexers)] event: SonarrEvent) { assert_str_eq!(event.resource(), "/indexer"); @@ -651,6 +659,42 @@ mod test { } } + #[tokio::test] + async fn test_handle_get_sonarr_host_config_event() { + let host_config_response = json!({ + "bindAddress": "*", + "port": 7878, + "urlBase": "some.test.site/sonarr", + "instanceName": "Sonarr", + "applicationUrl": "https://some.test.site:7878/sonarr", + "enableSsl": true, + "sslPort": 9898, + "sslCertPath": "/app/sonarr.pfx", + "sslCertPassword": "test" + }); + let response: HostConfig = serde_json::from_value(host_config_response.clone()).unwrap(); + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(host_config_response), + None, + SonarrEvent::GetHostConfig, + None, + None, + ) + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + if let SonarrSerdeable::HostConfig(host_config) = network + .handle_sonarr_event(SonarrEvent::GetHostConfig) + .await + .unwrap() + { + async_server.assert_async().await; + assert_eq!(host_config, response); + } + } + #[tokio::test] async fn test_handle_get_sonarr_indexers_event() { let indexers_response_json = json!([{ @@ -1226,6 +1270,40 @@ mod test { assert!(app_arc.lock().await.data.sonarr_data.series.sort_asc); } + #[tokio::test] + async fn test_handle_get_sonarr_security_config_event() { + let security_config_response = json!({ + "authenticationMethod": "forms", + "authenticationRequired": "disabledForLocalAddresses", + "username": "test", + "password": "some password", + "apiKey": "someApiKey12345", + "certificateValidation": "disabledForLocalAddresses", + }); + let response: SecurityConfig = + serde_json::from_value(security_config_response.clone()).unwrap(); + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(security_config_response), + None, + SonarrEvent::GetSecurityConfig, + None, + None, + ) + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + if let SonarrSerdeable::SecurityConfig(security_config) = network + .handle_sonarr_event(SonarrEvent::GetSecurityConfig) + .await + .unwrap() + { + async_server.assert_async().await; + assert_eq!(security_config, response); + } + } + #[tokio::test] async fn test_handle_get_status_event() { let (async_server, app_arc, _server) = mock_servarr_api( diff --git a/src/ui/radarr_ui/indexers/mod.rs b/src/ui/radarr_ui/indexers/mod.rs index 93a22a8..d79c84f 100644 --- a/src/ui/radarr_ui/indexers/mod.rs +++ b/src/ui/radarr_ui/indexers/mod.rs @@ -5,8 +5,8 @@ use ratatui::widgets::{Cell, Row}; use ratatui::Frame; use crate::app::App; -use crate::models::radarr_models::Indexer; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, INDEXERS_BLOCKS}; +use crate::models::servarr_models::Indexer; use crate::models::Route; use crate::ui::radarr_ui::indexers::edit_indexer_ui::EditIndexerUi; use crate::ui::radarr_ui::indexers::indexer_settings_ui::IndexerSettingsUi;