Added network support for updating all indexer settings, editing specific indexer settings, deleting an indexer; Also added keybindings for all of the above that change the current route. Added full support for deleting an indexer; still need to add an indexer_handler to handle the add, edit, and settings functionalities

This commit is contained in:
2023-08-08 10:50:07 -06:00
parent 52f22312f3
commit 2b9ddd0d1e
11 changed files with 419 additions and 58 deletions
+5
View File
@@ -16,6 +16,7 @@ generate_keybindings! {
right,
backspace,
search,
settings,
filter,
sort,
edit,
@@ -66,6 +67,10 @@ pub const DEFAULT_KEYBINDINGS: KeyBindings = KeyBindings {
key: Key::Char('s'),
desc: "Search",
},
settings: KeyBinding {
key: Key::Char('s'),
desc: "Settings",
},
filter: KeyBinding {
key: Key::Char('f'),
desc: "Filter",
+22 -4
View File
@@ -5,8 +5,8 @@ use strum::IntoEnumIterator;
use crate::app::{App, Route};
use crate::models::radarr_models::{
AddMovieSearchResult, Collection, CollectionMovie, Credit, DiskSpace, DownloadRecord, Indexer,
MinimumAvailability, Monitor, Movie, MovieHistoryItem, QueueEvent, Release, ReleaseField,
RootFolder, Task,
IndexerSettings, MinimumAvailability, Monitor, Movie, MovieHistoryItem, QueueEvent, Release,
ReleaseField, RootFolder, Task,
};
use crate::models::{
BlockSelectionState, HorizontallyScrollableText, ScrollableText, StatefulList, StatefulTable,
@@ -37,6 +37,7 @@ pub struct RadarrData<'a> {
pub selected_block: BlockSelectionState<'a, ActiveRadarrBlock>,
pub downloads: StatefulTable<DownloadRecord>,
pub indexers: StatefulTable<Indexer>,
pub indexer_settings: Option<IndexerSettings>,
pub quality_profile_map: BiMap<u64, String>,
pub tags_map: BiMap<u64, String>,
pub movie_details: ScrollableText,
@@ -263,6 +264,7 @@ impl<'a> Default for RadarrData<'a> {
filtered_movies: StatefulTable::default(),
downloads: StatefulTable::default(),
indexers: StatefulTable::default(),
indexer_settings: None,
quality_profile_map: BiMap::default(),
tags_map: BiMap::default(),
file_details: String::default(),
@@ -324,7 +326,7 @@ impl<'a> Default for RadarrData<'a> {
title: "Indexers",
route: ActiveRadarrBlock::Indexers.into(),
help: "",
contextual_help: Some("<r> refresh"),
contextual_help: Some("<enter> edit | <s> settings | <del> delete | <r> refresh"),
},
TabRoute {
title: "System",
@@ -377,6 +379,7 @@ impl<'a> Default for RadarrData<'a> {
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
pub enum ActiveRadarrBlock {
AddIndexer,
AddMovieAlreadyInLibrary,
AddMovieSearchInput,
AddMovieSearchResults,
@@ -394,11 +397,12 @@ pub enum ActiveRadarrBlock {
CollectionDetails,
Cast,
Crew,
DeleteDownloadPrompt,
DeleteIndexerPrompt,
DeleteMoviePrompt,
DeleteMovieConfirmPrompt,
DeleteMovieToggleDeleteFile,
DeleteMovieToggleAddListExclusion,
DeleteDownloadPrompt,
DeleteRootFolderPrompt,
Downloads,
EditCollectionPrompt,
@@ -408,6 +412,7 @@ pub enum ActiveRadarrBlock {
EditCollectionSelectQualityProfile,
EditCollectionToggleSearchOnAdd,
EditCollectionToggleMonitored,
EditIndexer,
EditMoviePrompt,
EditMovieConfirmPrompt,
EditMoviePathInput,
@@ -419,6 +424,7 @@ pub enum ActiveRadarrBlock {
FilterCollections,
FilterMovies,
Indexers,
IndexerSettings,
ManualSearch,
ManualSearchSortPrompt,
ManualSearchConfirmPrompt,
@@ -531,6 +537,13 @@ pub static DELETE_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 3] = [
ActiveRadarrBlock::DeleteMovieToggleAddListExclusion,
ActiveRadarrBlock::DeleteMovieConfirmPrompt,
];
pub static INDEXER_BLOCKS: [ActiveRadarrBlock; 5] = [
ActiveRadarrBlock::Indexers,
ActiveRadarrBlock::IndexerSettings,
ActiveRadarrBlock::AddIndexer,
ActiveRadarrBlock::EditIndexer,
ActiveRadarrBlock::DeleteIndexerPrompt,
];
pub static SYSTEM_DETAILS_BLOCKS: [ActiveRadarrBlock; 5] = [
ActiveRadarrBlock::SystemLogs,
ActiveRadarrBlock::SystemQueuedEvents,
@@ -587,6 +600,11 @@ impl<'a> App<'a> {
.dispatch_network_event(RadarrEvent::GetIndexers.into())
.await;
}
ActiveRadarrBlock::IndexerSettings => {
self
.dispatch_network_event(RadarrEvent::GetIndexerSettings.into())
.await;
}
ActiveRadarrBlock::System => {
self
.dispatch_network_event(RadarrEvent::GetTasks.into())
+19 -1
View File
@@ -274,6 +274,7 @@ mod tests {
assert!(radarr_data.filtered_movies.items.is_empty());
assert!(radarr_data.downloads.items.is_empty());
assert!(radarr_data.indexers.items.is_empty());
assert!(radarr_data.indexer_settings.is_none());
assert!(radarr_data.quality_profile_map.is_empty());
assert!(radarr_data.tags_map.is_empty());
assert!(radarr_data.file_details.is_empty());
@@ -357,7 +358,7 @@ mod tests {
assert!(radarr_data.main_tabs.tabs[4].help.is_empty());
assert_eq!(
radarr_data.main_tabs.tabs[4].contextual_help,
Some("<r> refresh")
Some("<enter> edit | <s> settings | <del> delete | <r> refresh")
);
assert_str_eq!(radarr_data.main_tabs.tabs[5].title, "System");
@@ -713,6 +714,23 @@ mod tests {
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_indexer_settings_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_radarr_block(&ActiveRadarrBlock::IndexerSettings)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetIndexerSettings.into()
);
assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_system_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
+27
View File
@@ -251,6 +251,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
ActiveRadarrBlock::RootFolders => self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteRootFolderPrompt.into()),
ActiveRadarrBlock::Indexers => self
.app
.push_navigation_stack(ActiveRadarrBlock::DeleteIndexerPrompt.into()),
_ => (),
}
}
@@ -278,6 +281,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
_ => (),
},
ActiveRadarrBlock::DeleteDownloadPrompt
| ActiveRadarrBlock::DeleteIndexerPrompt
| ActiveRadarrBlock::DeleteRootFolderPrompt
| ActiveRadarrBlock::UpdateAllMoviesPrompt
| ActiveRadarrBlock::UpdateAllCollectionsPrompt
@@ -404,6 +408,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::DeleteIndexerPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteIndexer);
}
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::UpdateAllMoviesPrompt => {
if self.app.data.radarr_data.prompt_confirm {
self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::UpdateAllMovies);
@@ -431,6 +442,11 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
self.app.should_ignore_quit_key = false;
self.app.pop_navigation_stack();
}
ActiveRadarrBlock::Indexers => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::EditIndexer.into());
}
_ => (),
}
}
@@ -454,6 +470,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
self.app.should_ignore_quit_key = false;
}
ActiveRadarrBlock::DeleteDownloadPrompt
| ActiveRadarrBlock::DeleteIndexerPrompt
| ActiveRadarrBlock::DeleteRootFolderPrompt
| ActiveRadarrBlock::UpdateAllMoviesPrompt
| ActiveRadarrBlock::UpdateAllCollectionsPrompt
@@ -527,9 +544,19 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
_ => (),
},
ActiveRadarrBlock::Indexers => match self.key {
_ if *key == DEFAULT_KEYBINDINGS.add.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::AddIndexer.into());
}
_ if *key == DEFAULT_KEYBINDINGS.refresh.key => {
self.app.should_refresh = true;
}
_ if *key == DEFAULT_KEYBINDINGS.settings.key => {
self
.app
.push_navigation_stack(ActiveRadarrBlock::IndexerSettings.into());
}
_ => (),
},
ActiveRadarrBlock::Collections => match self.key {
@@ -135,4 +135,21 @@ mod utils {
assert_eq!(app.data.radarr_data.edit_search_on_add, Some(true));
};
}
#[macro_export]
macro_rules! assert_delete_prompt {
($block:expr, $expected_block:expr) => {
let mut app = App::default();
RadarrHandler::with(&DELETE_KEY, &mut app, &$block, &None).handle();
assert_eq!(app.get_current_route(), &$expected_block.into());
};
($app:expr, $block:expr, $expected_block:expr) => {
RadarrHandler::with(&DELETE_KEY, &mut $app, &$block, &None).handle();
assert_eq!($app.get_current_route(), &$expected_block.into());
};
}
}
@@ -209,6 +209,8 @@ mod tests {
mod test_handle_delete {
use pretty_assertions::assert_eq;
use crate::assert_delete_prompt;
use super::*;
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
@@ -217,11 +219,10 @@ mod tests {
fn test_movies_delete() {
let mut app = App::default();
RadarrHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Movies, &None).handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::DeleteMoviePrompt.into()
assert_delete_prompt!(
app,
ActiveRadarrBlock::Movies,
ActiveRadarrBlock::DeleteMoviePrompt
);
assert_eq!(
app.data.radarr_data.selected_block.get_active_block(),
@@ -229,34 +230,18 @@ mod tests {
);
}
#[test]
fn test_downloads_delete() {
let mut app = App::default();
RadarrHandler::with(&DELETE_KEY, &mut app, &ActiveRadarrBlock::Downloads, &None).handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::DeleteDownloadPrompt.into()
);
}
#[test]
fn test_root_folder_delete() {
let mut app = App::default();
RadarrHandler::with(
&DELETE_KEY,
&mut app,
&ActiveRadarrBlock::RootFolders,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::DeleteRootFolderPrompt.into()
);
#[rstest]
#[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)]
#[case(
ActiveRadarrBlock::RootFolders,
ActiveRadarrBlock::DeleteRootFolderPrompt
)]
#[case(ActiveRadarrBlock::Indexers, ActiveRadarrBlock::DeleteIndexerPrompt)]
fn test_delete_prompt(
#[case] active_radarr_block: ActiveRadarrBlock,
#[case] expected_radarr_block: ActiveRadarrBlock,
) {
assert_delete_prompt!(active_radarr_block, expected_radarr_block);
}
}
@@ -332,6 +317,7 @@ mod tests {
fn test_left_right_prompt_toggle(
#[values(
ActiveRadarrBlock::DeleteDownloadPrompt,
ActiveRadarrBlock::DeleteIndexerPrompt,
ActiveRadarrBlock::DeleteRootFolderPrompt,
ActiveRadarrBlock::UpdateAllMoviesPrompt,
ActiveRadarrBlock::UpdateAllCollectionsPrompt,
@@ -387,6 +373,18 @@ mod tests {
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[test]
fn test_indexer_submit_aka_edit() {
let mut app = App::default();
RadarrHandler::with(&SUBMIT_KEY, &mut app, &ActiveRadarrBlock::Indexers, &None).handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::EditIndexer.into()
);
}
#[rstest]
#[case(ActiveRadarrBlock::Movies, ActiveRadarrBlock::MovieDetails)]
#[case(ActiveRadarrBlock::Collections, ActiveRadarrBlock::CollectionDetails)]
@@ -629,6 +627,11 @@ mod tests {
ActiveRadarrBlock::DeleteDownloadPrompt,
RadarrEvent::DeleteDownload
)]
#[case(
ActiveRadarrBlock::Indexers,
ActiveRadarrBlock::DeleteIndexerPrompt,
RadarrEvent::DeleteIndexer
)]
#[case(
ActiveRadarrBlock::RootFolders,
ActiveRadarrBlock::DeleteRootFolderPrompt,
@@ -751,6 +754,7 @@ mod tests {
ActiveRadarrBlock::DeleteRootFolderPrompt
)]
#[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::DeleteDownloadPrompt)]
#[case(ActiveRadarrBlock::Indexers, ActiveRadarrBlock::DeleteIndexerPrompt)]
#[case(ActiveRadarrBlock::Downloads, ActiveRadarrBlock::UpdateDownloadsPrompt)]
#[case(
ActiveRadarrBlock::Collections,
@@ -899,6 +903,24 @@ mod tests {
assert!(app.should_ignore_quit_key);
}
#[test]
fn test_indexer_add() {
let mut app = App::default();
RadarrHandler::with(
&DEFAULT_KEYBINDINGS.add.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::AddIndexer.into()
);
}
#[test]
fn test_root_folder_add() {
let mut app = App::default();
@@ -1054,6 +1076,24 @@ mod tests {
);
}
#[test]
fn test_indexer_settings_key() {
let mut app = App::default();
RadarrHandler::with(
&DEFAULT_KEYBINDINGS.settings.key,
&mut app,
&ActiveRadarrBlock::Indexers,
&None,
)
.handle();
assert_eq!(
app.get_current_route(),
&ActiveRadarrBlock::IndexerSettings.into()
);
}
#[test]
fn test_add_root_folder_prompt_backspace_key() {
let mut app = App::default();
+21 -1
View File
@@ -182,7 +182,6 @@ pub struct IndexerField {
pub value: Option<Value>,
#[serde(rename(deserialize = "type"))]
pub field_type: Option<String>,
pub advanced: bool,
pub select_options: Option<Vec<IndexerSelectOption>>,
}
@@ -197,6 +196,27 @@ pub struct IndexerSelectOption {
pub order: Number,
}
#[derive(Derivative, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")]
pub struct IndexerSettings {
pub allow_hardcoded_subs: bool,
#[derivative(Default(value = "Number::from(0)"))]
pub availability_delay: Number,
#[derivative(Default(value = "Number::from(0)"))]
pub id: Number,
#[derivative(Default(value = "Number::from(0)"))]
pub maximum_size: Number,
#[derivative(Default(value = "Number::from(0)"))]
pub minimum_age: Number,
pub prefer_indexer_flags: bool,
#[derivative(Default(value = "Number::from(0)"))]
pub retention: Number,
#[derivative(Default(value = "Number::from(0)"))]
pub rss_sync_interval: Number,
pub whitelisted_hardcoded_subs: String,
}
#[derive(Deserialize, Default, Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct Language {
pub name: String,
+91 -4
View File
@@ -11,8 +11,8 @@ use crate::app::RadarrConfig;
use crate::models::radarr_models::{
AddMovieBody, AddMovieSearchResult, AddOptions, AddRootFolderBody, Collection, CollectionMovie,
CommandBody, Credit, CreditType, DiskSpace, DownloadRecord, DownloadsResponse, Indexer,
LogResponse, Movie, MovieCommandBody, MovieHistoryItem, QualityProfile, QueueEvent, Release,
ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task, Update,
IndexerSettings, LogResponse, Movie, MovieCommandBody, MovieHistoryItem, QualityProfile,
QueueEvent, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task, Update,
};
use crate::models::{HorizontallyScrollableText, Route, Scrollable, ScrollableText};
use crate::network::{Network, NetworkEvent, RequestMethod, RequestProps};
@@ -27,6 +27,7 @@ pub enum RadarrEvent {
AddMovie,
AddRootFolder,
DeleteDownload,
DeleteIndexer,
DeleteMovie,
DeleteRootFolder,
DownloadRelease,
@@ -35,6 +36,7 @@ pub enum RadarrEvent {
GetCollections,
GetDownloads,
GetIndexers,
GetIndexerSettings,
GetLogs,
GetMovieCredits,
GetMovieDetails,
@@ -57,6 +59,7 @@ pub enum RadarrEvent {
UpdateAndScan,
UpdateCollections,
UpdateDownloads,
UpdateIndexerSettings,
}
impl RadarrEvent {
@@ -64,7 +67,8 @@ impl RadarrEvent {
match self {
RadarrEvent::GetCollections | RadarrEvent::EditCollection => "/collection",
RadarrEvent::GetDownloads | RadarrEvent::DeleteDownload => "/queue",
RadarrEvent::GetIndexers => "/indexer",
RadarrEvent::GetIndexers | RadarrEvent::DeleteIndexer => "/indexer",
RadarrEvent::GetIndexerSettings | RadarrEvent::UpdateIndexerSettings => "/config/indexer",
RadarrEvent::GetLogs => "/log",
RadarrEvent::AddMovie
| RadarrEvent::EditMovie
@@ -107,8 +111,9 @@ impl<'a, 'b> Network<'a, 'b> {
match radarr_event {
RadarrEvent::AddMovie => self.add_movie().await,
RadarrEvent::AddRootFolder => self.add_root_folder().await,
RadarrEvent::DeleteMovie => self.delete_movie().await,
RadarrEvent::DeleteDownload => self.delete_download().await,
RadarrEvent::DeleteIndexer => self.delete_indexer().await,
RadarrEvent::DeleteMovie => self.delete_movie().await,
RadarrEvent::DeleteRootFolder => self.delete_root_folder().await,
RadarrEvent::DownloadRelease => self.download_release().await,
RadarrEvent::EditCollection => self.edit_collection().await,
@@ -116,6 +121,7 @@ impl<'a, 'b> Network<'a, 'b> {
RadarrEvent::GetCollections => self.get_collections().await,
RadarrEvent::GetDownloads => self.get_downloads().await,
RadarrEvent::GetIndexers => self.get_indexers().await,
RadarrEvent::GetIndexerSettings => self.get_indexer_settings().await,
RadarrEvent::GetLogs => self.get_logs().await,
RadarrEvent::GetMovieCredits => self.get_credits().await,
RadarrEvent::GetMovieDetails => self.get_movie_details().await,
@@ -138,6 +144,7 @@ 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,
}
}
@@ -293,6 +300,37 @@ impl<'a, 'b> Network<'a, 'b> {
.await;
}
async fn delete_indexer(&self) {
let indexer_id = self
.app
.lock()
.await
.data
.radarr_data
.indexers
.current_selection()
.id
.as_u64()
.unwrap();
info!(
"Deleting Radarr indexer for indexer with id: {}",
indexer_id
);
let request_props = self
.radarr_request_props_from(
format!("{}/{}", RadarrEvent::DeleteIndexer.resource(), indexer_id).as_str(),
RequestMethod::Delete,
None::<()>,
)
.await;
self
.handle_request::<(), ()>(request_props, |_, _| ())
.await;
}
async fn delete_movie(&self) {
let movie_id = self.extract_movie_id().await;
let delete_files = self.app.lock().await.data.radarr_data.delete_movie_files;
@@ -644,6 +682,24 @@ impl<'a, 'b> Network<'a, 'b> {
.await
}
async fn get_indexer_settings(&self) {
info!("Fetching Radarr indexer settings");
let request_props = self
.radarr_request_props_from(
RadarrEvent::GetIndexerSettings.resource(),
RequestMethod::Get,
None::<()>,
)
.await;
self
.handle_request::<(), IndexerSettings>(request_props, |indexer_settings, mut app| {
app.data.radarr_data.indexer_settings = Some(indexer_settings);
})
.await;
}
async fn get_healthcheck(&self) {
info!("Performing Radarr health check");
@@ -1302,6 +1358,37 @@ impl<'a, 'b> Network<'a, 'b> {
.await;
}
async fn update_indexer_settings(&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,
+101 -6
View File
@@ -531,6 +531,44 @@ 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,
RadarrEvent::UpdateIndexerSettings.resource(),
)
.await;
app_arc.lock().await.data.radarr_data.indexer_settings = Some(indexer_settings());
let network = Network::new(reqwest::Client::new(), &app_arc);
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(
@@ -873,13 +911,11 @@ mod test {
"label": "Value Is String",
"value": "hello",
"type": "textbox",
"advanced": false
},
{
"order": 1,
"name": "emptyValueWithSelectOptions",
"label": "Empty Value With Select Options",
"advanced": true,
"type": "select",
"selectOptions": [
{
@@ -895,7 +931,6 @@ mod test {
"label": "Value is an array",
"value": [1, 2],
"type": "select",
"advanced": false,
},
],
"implementationName": "Torznab",
@@ -922,6 +957,39 @@ mod test {
);
}
#[tokio::test]
async fn test_handle_get_indexer_settings_event() {
let indexer_settings_response_json = json!({
"minimumAge": 0,
"maximumSize": 0,
"retention": 0,
"rssSyncInterval": 60,
"preferIndexerFlags": false,
"availabilityDelay": 0,
"allowHardcodedSubs": true,
"whitelistedHardcodedSubs": "",
"id": 1
});
let (async_server, app_arc, _server) = mock_radarr_api(
RequestMethod::Get,
None,
Some(indexer_settings_response_json),
RadarrEvent::GetIndexerSettings.resource(),
)
.await;
let network = Network::new(reqwest::Client::new(), &app_arc);
network
.handle_radarr_event(RadarrEvent::GetIndexerSettings)
.await;
async_server.assert_async().await;
assert_eq!(
app_arc.lock().await.data.radarr_data.indexer_settings,
Some(indexer_settings())
);
}
#[tokio::test]
async fn test_handle_get_queued_events_event() {
let queued_events_json = json!([{
@@ -1362,6 +1430,27 @@ mod test {
async_server.assert_async().await;
}
#[tokio::test]
async fn test_handle_delete_indexer_event() {
let resource = format!("{}/1", RadarrEvent::DeleteIndexer.resource());
let (async_server, app_arc, _server) =
mock_radarr_api(RequestMethod::Delete, None, None, &resource).await;
app_arc
.lock()
.await
.data
.radarr_data
.indexers
.set_items(vec![indexer()]);
let network = Network::new(reqwest::Client::new(), &app_arc);
network
.handle_radarr_event(RadarrEvent::DeleteIndexer)
.await;
async_server.assert_async().await;
}
#[tokio::test]
async fn test_handle_delete_root_folder_event() {
let resource = format!("{}/1", RadarrEvent::DeleteRootFolder.resource());
@@ -2216,7 +2305,6 @@ mod test {
name: Some("valueIsString".to_owned()),
label: Some("Value Is String".to_owned()),
value: Some(json!("hello")),
advanced: false,
field_type: Some("textbox".to_owned()),
select_options: None,
},
@@ -2225,7 +2313,6 @@ mod test {
name: Some("emptyValueWithSelectOptions".to_owned()),
label: Some("Empty Value With Select Options".to_owned()),
value: None,
advanced: true,
field_type: Some("select".to_owned()),
select_options: Some(vec![IndexerSelectOption {
value: Number::from(-2),
@@ -2238,11 +2325,19 @@ mod test {
name: Some("valueIsAnArray".to_owned()),
label: Some("Value is an array".to_owned()),
value: Some(json!([1, 2])),
advanced: false,
field_type: Some("select".to_owned()),
select_options: None,
},
]),
}
}
fn indexer_settings() -> IndexerSettings {
IndexerSettings {
rss_sync_interval: Number::from(60),
allow_hardcoded_subs: true,
id: Number::from(1),
..IndexerSettings::default()
}
}
}
+38 -6
View File
@@ -9,17 +9,24 @@ use crate::app::App;
use crate::models::radarr_models::Indexer;
use crate::models::Route;
use crate::ui::utils::{layout_block_top_border, style_failure, style_primary, style_success};
use crate::ui::{draw_table, DrawUi, TableProps};
use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps};
pub(super) struct IndexersUi {}
impl DrawUi for IndexersUi {
fn draw<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, content_rect: Rect) {
if matches!(
*app.get_current_route(),
Route::Radarr(ActiveRadarrBlock::Indexers, _)
) {
draw_indexers(f, app, content_rect);
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block {
ActiveRadarrBlock::Indexers => draw_indexers(f, app, content_rect),
ActiveRadarrBlock::DeleteIndexerPrompt => draw_prompt_popup_over(
f,
app,
content_rect,
draw_indexers,
draw_delete_indexer_prompt,
),
_ => (),
}
}
}
}
@@ -94,3 +101,28 @@ fn draw_indexers<B: Backend>(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect
true,
)
}
fn draw_delete_indexer_prompt<B: Backend>(
f: &mut Frame<'_, B>,
app: &mut App<'_>,
prompt_area: Rect,
) {
draw_prompt_box(
f,
prompt_area,
"Delete Indexer",
format!(
"Do you really want to delete this indexer: {}?",
app
.data
.radarr_data
.indexers
.current_selection()
.name
.clone()
.unwrap_or_default()
)
.as_str(),
app.data.radarr_data.prompt_confirm,
);
}
+5 -3
View File
@@ -11,8 +11,8 @@ use tui::Frame;
use crate::app::radarr::{
ActiveRadarrBlock, RadarrData, ADD_MOVIE_BLOCKS, COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS,
EDIT_COLLECTION_BLOCKS, EDIT_MOVIE_BLOCKS, FILTER_BLOCKS, MOVIE_DETAILS_BLOCKS, SEARCH_BLOCKS,
SYSTEM_DETAILS_BLOCKS,
EDIT_COLLECTION_BLOCKS, EDIT_MOVIE_BLOCKS, FILTER_BLOCKS, INDEXER_BLOCKS, MOVIE_DETAILS_BLOCKS,
SEARCH_BLOCKS, SYSTEM_DETAILS_BLOCKS,
};
use crate::app::App;
use crate::logos::RADARR_LOGO;
@@ -74,7 +74,9 @@ impl DrawUi for RadarrUi {
ActiveRadarrBlock::Downloads
| ActiveRadarrBlock::DeleteDownloadPrompt
| ActiveRadarrBlock::UpdateDownloadsPrompt => DownloadsUi::draw(f, app, content_rect),
ActiveRadarrBlock::Indexers => IndexersUi::draw(f, app, content_rect),
_ if INDEXER_BLOCKS.contains(&active_radarr_block) => {
IndexersUi::draw(f, app, content_rect)
}
ActiveRadarrBlock::RootFolders
| ActiveRadarrBlock::AddRootFolderPrompt
| ActiveRadarrBlock::DeleteRootFolderPrompt => RootFoldersUi::draw(f, app, content_rect),