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:
@@ -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
@@ -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
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user