feat: Full CLI and TUI support for the Lidarr Indexers tab

This commit is contained in:
2026-01-14 13:30:51 -07:00
parent 8abcf44866
commit c74d5936d2
91 changed files with 9481 additions and 166 deletions
+12 -8
View File
@@ -1,3 +1,12 @@
use super::{
HorizontallyScrollableText, Serdeable,
servarr_models::{
DiskSpace, HostConfig, Indexer, IndexerTestResult, QualityProfile, QualityWrapper, RootFolder,
SecurityConfig, Tag,
},
};
use crate::models::servarr_models::IndexerSettings;
use crate::serde_enum_from;
use chrono::{DateTime, Utc};
use derivative::Derivative;
use enum_display_style_derive::EnumDisplayStyle;
@@ -5,14 +14,6 @@ use serde::{Deserialize, Serialize};
use serde_json::{Number, Value};
use strum::{Display, EnumIter};
use super::{
HorizontallyScrollableText, Serdeable,
servarr_models::{
DiskSpace, HostConfig, QualityProfile, QualityWrapper, RootFolder, SecurityConfig, Tag,
},
};
use crate::serde_enum_from;
#[cfg(test)]
#[path = "lidarr_models_tests.rs"]
mod lidarr_models_tests;
@@ -442,6 +443,9 @@ serde_enum_from!(
DownloadsResponse(DownloadsResponse),
HistoryWrapper(LidarrHistoryWrapper),
HostConfig(HostConfig),
IndexerSettings(IndexerSettings),
Indexers(Vec<Indexer>),
IndexerTestResults(Vec<IndexerTestResult>),
MetadataProfiles(Vec<MetadataProfile>),
QualityProfiles(Vec<QualityProfile>),
RootFolders(Vec<RootFolder>),
+44 -1
View File
@@ -10,7 +10,8 @@ mod tests {
MonitorType, NewItemMonitorType, SystemStatus,
};
use crate::models::servarr_models::{
DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag,
DiskSpace, HostConfig, Indexer, IndexerSettings, IndexerTestResult, QualityProfile, RootFolder,
SecurityConfig, Tag,
};
use crate::models::{
Serdeable,
@@ -320,6 +321,48 @@ mod tests {
);
}
#[test]
fn test_lidarr_serdeable_from_indexers() {
let indexers = vec![Indexer {
id: 1,
..Indexer::default()
}];
let lidarr_serdeable: LidarrSerdeable = indexers.clone().into();
assert_eq!(lidarr_serdeable, LidarrSerdeable::Indexers(indexers));
}
#[test]
fn test_lidarr_serdeable_from_indexer_settings() {
let indexer_settings = IndexerSettings {
id: 1,
..IndexerSettings::default()
};
let lidarr_serdeable: LidarrSerdeable = indexer_settings.clone().into();
assert_eq!(
lidarr_serdeable,
LidarrSerdeable::IndexerSettings(indexer_settings)
);
}
#[test]
fn test_lidarr_serdeable_from_indexer_test_results() {
let indexer_test_results = vec![IndexerTestResult {
id: 1,
..IndexerTestResult::default()
}];
let lidarr_serdeable: LidarrSerdeable = indexer_test_results.clone().into();
assert_eq!(
lidarr_serdeable,
LidarrSerdeable::IndexerTestResults(indexer_test_results)
);
}
#[test]
fn test_lidarr_serdeable_from_metadata_profiles() {
let metadata_profiles = vec![MetadataProfile {
+153 -3
View File
@@ -2,15 +2,19 @@ use serde_json::Number;
use super::modals::{AddArtistModal, AddRootFolderModal, EditArtistModal};
use crate::app::context_clues::{
DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
ROOT_FOLDERS_CONTEXT_CLUES,
};
use crate::app::lidarr::lidarr_context_clues::{
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
};
use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_models::IndexerSettings;
use crate::models::{
BlockSelectionState, HorizontallyScrollableText, Route, TabRoute, TabState,
lidarr_models::{AddArtistSearchResult, Album, Artist, DownloadRecord, LidarrHistoryItem},
servarr_models::{DiskSpace, RootFolder},
servarr_data::modals::IndexerTestResultModalItem,
servarr_models::{DiskSpace, Indexer, RootFolder},
stateful_table::StatefulTable,
};
use crate::network::lidarr_network::LidarrEvent;
@@ -22,12 +26,14 @@ use strum::EnumIter;
use {
crate::models::lidarr_models::{MonitorType, NewItemMonitorType},
crate::models::stateful_table::SortOption,
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::indexer_settings,
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map,
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
add_artist_search_result, album, artist, download_record, lidarr_history_item,
add_artist_search_result, album, artist, download_record, indexer, lidarr_history_item,
metadata_profile, metadata_profile_map, quality_profile, root_folder, tags_map,
},
crate::network::servarr_test_utils::diskspace,
crate::network::servarr_test_utils::indexer_test_result,
strum::{Display, EnumString, IntoEnumIterator},
};
@@ -48,7 +54,12 @@ pub struct LidarrData<'a> {
pub disk_space_vec: Vec<DiskSpace>,
pub downloads: StatefulTable<DownloadRecord>,
pub edit_artist_modal: Option<EditArtistModal>,
pub edit_indexer_modal: Option<EditIndexerModal>,
pub history: StatefulTable<LidarrHistoryItem>,
pub indexers: StatefulTable<Indexer>,
pub indexer_settings: Option<IndexerSettings>,
pub indexer_test_all_results: Option<StatefulTable<IndexerTestResultModalItem>>,
pub indexer_test_errors: Option<String>,
pub main_tabs: TabState,
pub metadata_profile_map: BiMap<i64, String>,
pub prompt_confirm: bool,
@@ -118,7 +129,12 @@ impl<'a> Default for LidarrData<'a> {
disk_space_vec: Vec::new(),
downloads: StatefulTable::default(),
edit_artist_modal: None,
edit_indexer_modal: None,
history: StatefulTable::default(),
indexers: StatefulTable::default(),
indexer_settings: None,
indexer_test_all_results: None,
indexer_test_errors: None,
metadata_profile_map: BiMap::new(),
prompt_confirm: false,
prompt_confirm_action: None,
@@ -153,6 +169,12 @@ impl<'a> Default for LidarrData<'a> {
contextual_help: Some(&ROOT_FOLDERS_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "Indexers".to_string(),
route: ActiveLidarrBlock::Indexers.into(),
contextual_help: Some(&INDEXERS_CONTEXT_CLUES),
config: None,
},
]),
artist_info_tabs: TabState::new(vec![TabRoute {
title: "Albums".to_string(),
@@ -221,14 +243,33 @@ impl LidarrData<'_> {
.metadata_profile_list
.set_items(vec![metadata_profile().name]);
let edit_indexer_modal = EditIndexerModal {
name: "DrunkenSlug".into(),
enable_rss: Some(true),
enable_automatic_search: Some(true),
enable_interactive_search: Some(true),
url: "http://127.0.0.1:9696/1/".into(),
api_key: "someApiKey".into(),
seed_ratio: "ratio".into(),
tags: "25".into(),
priority: 1,
};
let mut indexer_test_all_results = StatefulTable::default();
indexer_test_all_results.set_items(vec![indexer_test_result()]);
let mut lidarr_data = LidarrData {
delete_files: true,
disk_space_vec: vec![diskspace()],
quality_profile_map: quality_profile_map(),
metadata_profile_map: metadata_profile_map(),
edit_artist_modal: Some(edit_artist_modal),
edit_indexer_modal: Some(edit_indexer_modal),
add_root_folder_modal: Some(add_root_folder_modal),
add_artist_modal: Some(add_artist_modal),
indexer_settings: Some(indexer_settings()),
indexer_test_all_results: Some(indexer_test_all_results),
indexer_test_errors: Some("error".to_string()),
tags_map: tags_map(),
..LidarrData::default()
};
@@ -250,6 +291,7 @@ impl LidarrData<'_> {
lidarr_data.history.search = Some("test search".into());
lidarr_data.history.filter = Some("test filter".into());
lidarr_data.root_folders.set_items(vec![root_folder()]);
lidarr_data.indexers.set_items(vec![indexer()]);
lidarr_data.version = "1.0.0".to_owned();
lidarr_data.add_artist_search = Some("Test Artist".into());
let mut add_searched_artists = StatefulTable::default();
@@ -288,6 +330,7 @@ pub enum ActiveLidarrBlock {
AddRootFolderSelectQualityProfile,
AddRootFolderSelectMetadataProfile,
AddRootFolderTagsInput,
AllIndexerSettingsPrompt,
AutomaticallySearchArtistPrompt,
DeleteAlbumPrompt,
DeleteAlbumConfirmPrompt,
@@ -308,6 +351,18 @@ pub enum ActiveLidarrBlock {
EditArtistSelectQualityProfile,
EditArtistTagsInput,
EditArtistToggleMonitored,
EditIndexerPrompt,
EditIndexerConfirmPrompt,
EditIndexerApiKeyInput,
EditIndexerNameInput,
EditIndexerSeedRatioInput,
EditIndexerToggleEnableRss,
EditIndexerToggleEnableAutomaticSearch,
EditIndexerToggleEnableInteractiveSearch,
EditIndexerUrlInput,
EditIndexerPriorityInput,
EditIndexerTagsInput,
DeleteIndexerPrompt,
FilterArtists,
FilterArtistsError,
FilterHistory,
@@ -315,6 +370,14 @@ pub enum ActiveLidarrBlock {
History,
HistoryItemDetails,
HistorySortPrompt,
Indexers,
IndexerSettingsConfirmPrompt,
IndexerSettingsMaximumSizeInput,
IndexerSettingsMinimumAgeInput,
IndexerSettingsRetentionInput,
IndexerSettingsRssSyncIntervalInput,
TestAllIndexers,
TestIndexer,
RootFolders,
SearchAlbums,
SearchAlbumsError,
@@ -461,6 +524,93 @@ pub const ADD_ROOT_FOLDER_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
&[ActiveLidarrBlock::AddRootFolderConfirmPrompt],
];
pub static EDIT_INDEXER_BLOCKS: [ActiveLidarrBlock; 11] = [
ActiveLidarrBlock::EditIndexerPrompt,
ActiveLidarrBlock::EditIndexerConfirmPrompt,
ActiveLidarrBlock::EditIndexerApiKeyInput,
ActiveLidarrBlock::EditIndexerNameInput,
ActiveLidarrBlock::EditIndexerSeedRatioInput,
ActiveLidarrBlock::EditIndexerToggleEnableRss,
ActiveLidarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveLidarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveLidarrBlock::EditIndexerPriorityInput,
ActiveLidarrBlock::EditIndexerUrlInput,
ActiveLidarrBlock::EditIndexerTagsInput,
];
pub const EDIT_INDEXER_TORRENT_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
&[
ActiveLidarrBlock::EditIndexerNameInput,
ActiveLidarrBlock::EditIndexerUrlInput,
],
&[
ActiveLidarrBlock::EditIndexerToggleEnableRss,
ActiveLidarrBlock::EditIndexerApiKeyInput,
],
&[
ActiveLidarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveLidarrBlock::EditIndexerSeedRatioInput,
],
&[
ActiveLidarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveLidarrBlock::EditIndexerTagsInput,
],
&[
ActiveLidarrBlock::EditIndexerPriorityInput,
ActiveLidarrBlock::EditIndexerConfirmPrompt,
],
&[
ActiveLidarrBlock::EditIndexerConfirmPrompt,
ActiveLidarrBlock::EditIndexerConfirmPrompt,
],
];
pub const EDIT_INDEXER_NZB_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
&[
ActiveLidarrBlock::EditIndexerNameInput,
ActiveLidarrBlock::EditIndexerUrlInput,
],
&[
ActiveLidarrBlock::EditIndexerToggleEnableRss,
ActiveLidarrBlock::EditIndexerApiKeyInput,
],
&[
ActiveLidarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveLidarrBlock::EditIndexerTagsInput,
],
&[
ActiveLidarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveLidarrBlock::EditIndexerPriorityInput,
],
&[
ActiveLidarrBlock::EditIndexerConfirmPrompt,
ActiveLidarrBlock::EditIndexerConfirmPrompt,
],
];
pub static INDEXER_SETTINGS_BLOCKS: [ActiveLidarrBlock; 6] = [
ActiveLidarrBlock::AllIndexerSettingsPrompt,
ActiveLidarrBlock::IndexerSettingsConfirmPrompt,
ActiveLidarrBlock::IndexerSettingsMaximumSizeInput,
ActiveLidarrBlock::IndexerSettingsMinimumAgeInput,
ActiveLidarrBlock::IndexerSettingsRetentionInput,
ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput,
];
pub const INDEXER_SETTINGS_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
&[ActiveLidarrBlock::IndexerSettingsMinimumAgeInput],
&[ActiveLidarrBlock::IndexerSettingsRetentionInput],
&[ActiveLidarrBlock::IndexerSettingsMaximumSizeInput],
&[ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput],
&[ActiveLidarrBlock::IndexerSettingsConfirmPrompt],
];
pub static INDEXERS_BLOCKS: [ActiveLidarrBlock; 3] = [
ActiveLidarrBlock::Indexers,
ActiveLidarrBlock::DeleteIndexerPrompt,
ActiveLidarrBlock::TestIndexer,
];
impl From<ActiveLidarrBlock> for Route {
fn from(active_lidarr_block: ActiveLidarrBlock) -> Route {
Route::Lidarr(active_lidarr_block, None)
@@ -1,17 +1,20 @@
#[cfg(test)]
mod tests {
use crate::app::context_clues::{
DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
ROOT_FOLDERS_CONTEXT_CLUES,
};
use crate::app::lidarr::lidarr_context_clues::{
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
};
use crate::models::lidarr_models::Album;
use crate::models::servarr_data::lidarr::lidarr_data::{
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ALBUM_BLOCKS,
DELETE_ALBUM_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS,
DOWNLOADS_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, HISTORY_BLOCKS,
ROOT_FOLDERS_BLOCKS,
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ADD_ROOT_FOLDER_BLOCKS, ARTIST_DETAILS_BLOCKS,
DELETE_ALBUM_BLOCKS, DELETE_ALBUM_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS,
DELETE_ARTIST_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_ARTIST_BLOCKS,
EDIT_ARTIST_SELECTION_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, HISTORY_BLOCKS, INDEXER_SETTINGS_BLOCKS,
INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS, ROOT_FOLDERS_BLOCKS,
};
use crate::models::{
BlockSelectionState, Route,
@@ -150,7 +153,7 @@ mod tests {
assert_is_empty!(lidarr_data.tags_map);
assert_is_empty!(lidarr_data.version);
assert_eq!(lidarr_data.main_tabs.tabs.len(), 4);
assert_eq!(lidarr_data.main_tabs.tabs.len(), 5);
assert_str_eq!(lidarr_data.main_tabs.tabs[0].title, "Library");
assert_eq!(
@@ -196,6 +199,17 @@ mod tests {
);
assert_none!(lidarr_data.main_tabs.tabs[3].config);
assert_str_eq!(lidarr_data.main_tabs.tabs[4].title, "Indexers");
assert_eq!(
lidarr_data.main_tabs.tabs[4].route,
ActiveLidarrBlock::Indexers.into()
);
assert_some_eq_x!(
&lidarr_data.main_tabs.tabs[4].contextual_help,
&INDEXERS_CONTEXT_CLUES
);
assert_none!(lidarr_data.main_tabs.tabs[4].config);
assert_eq!(lidarr_data.artist_info_tabs.tabs.len(), 1);
assert_str_eq!(lidarr_data.artist_info_tabs.tabs[0].title, "Albums");
assert_eq!(
@@ -414,9 +428,168 @@ mod tests {
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveLidarrBlock::DeleteRootFolderPrompt));
}
#[test]
fn test_edit_indexer_blocks_contents() {
assert_eq!(EDIT_INDEXER_BLOCKS.len(), 11);
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerPrompt));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerConfirmPrompt));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerApiKeyInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerNameInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerSeedRatioInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerToggleEnableRss));
assert!(
EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerToggleEnableAutomaticSearch)
);
assert!(
EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerToggleEnableInteractiveSearch)
);
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerUrlInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerTagsInput));
assert!(EDIT_INDEXER_BLOCKS.contains(&ActiveLidarrBlock::EditIndexerPriorityInput));
}
#[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(),
&[
ActiveLidarrBlock::EditIndexerNameInput,
ActiveLidarrBlock::EditIndexerUrlInput,
]
);
assert_eq!(
edit_indexer_nzb_selection_block_iter.next().unwrap(),
&[
ActiveLidarrBlock::EditIndexerToggleEnableRss,
ActiveLidarrBlock::EditIndexerApiKeyInput,
]
);
assert_eq!(
edit_indexer_nzb_selection_block_iter.next().unwrap(),
&[
ActiveLidarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveLidarrBlock::EditIndexerTagsInput,
]
);
assert_eq!(
edit_indexer_nzb_selection_block_iter.next().unwrap(),
&[
ActiveLidarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveLidarrBlock::EditIndexerPriorityInput,
]
);
assert_eq!(
edit_indexer_nzb_selection_block_iter.next().unwrap(),
&[
ActiveLidarrBlock::EditIndexerConfirmPrompt,
ActiveLidarrBlock::EditIndexerConfirmPrompt,
]
);
assert_eq!(edit_indexer_nzb_selection_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(),
&[
ActiveLidarrBlock::EditIndexerNameInput,
ActiveLidarrBlock::EditIndexerUrlInput,
]
);
assert_eq!(
edit_indexer_torrent_selection_block_iter.next().unwrap(),
&[
ActiveLidarrBlock::EditIndexerToggleEnableRss,
ActiveLidarrBlock::EditIndexerApiKeyInput,
]
);
assert_eq!(
edit_indexer_torrent_selection_block_iter.next().unwrap(),
&[
ActiveLidarrBlock::EditIndexerToggleEnableAutomaticSearch,
ActiveLidarrBlock::EditIndexerSeedRatioInput,
]
);
assert_eq!(
edit_indexer_torrent_selection_block_iter.next().unwrap(),
&[
ActiveLidarrBlock::EditIndexerToggleEnableInteractiveSearch,
ActiveLidarrBlock::EditIndexerTagsInput,
]
);
assert_eq!(
edit_indexer_torrent_selection_block_iter.next().unwrap(),
&[
ActiveLidarrBlock::EditIndexerPriorityInput,
ActiveLidarrBlock::EditIndexerConfirmPrompt,
]
);
assert_eq!(
edit_indexer_torrent_selection_block_iter.next().unwrap(),
&[
ActiveLidarrBlock::EditIndexerConfirmPrompt,
ActiveLidarrBlock::EditIndexerConfirmPrompt,
]
);
assert_eq!(edit_indexer_torrent_selection_block_iter.next(), None);
}
#[test]
fn test_indexer_settings_blocks_contents() {
assert_eq!(INDEXER_SETTINGS_BLOCKS.len(), 6);
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveLidarrBlock::AllIndexerSettingsPrompt));
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveLidarrBlock::IndexerSettingsConfirmPrompt));
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveLidarrBlock::IndexerSettingsMaximumSizeInput));
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveLidarrBlock::IndexerSettingsMinimumAgeInput));
assert!(INDEXER_SETTINGS_BLOCKS.contains(&ActiveLidarrBlock::IndexerSettingsRetentionInput));
assert!(
INDEXER_SETTINGS_BLOCKS.contains(&ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput)
);
}
#[test]
fn test_indexer_settings_selection_blocks_ordering() {
let mut indexer_settings_block_iter = INDEXER_SETTINGS_SELECTION_BLOCKS.iter();
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveLidarrBlock::IndexerSettingsMinimumAgeInput,]
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveLidarrBlock::IndexerSettingsRetentionInput,]
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveLidarrBlock::IndexerSettingsMaximumSizeInput,]
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveLidarrBlock::IndexerSettingsRssSyncIntervalInput,]
);
assert_eq!(
indexer_settings_block_iter.next().unwrap(),
&[ActiveLidarrBlock::IndexerSettingsConfirmPrompt,]
);
assert_eq!(indexer_settings_block_iter.next(), None);
}
#[test]
fn test_indexers_blocks_contents() {
assert_eq!(INDEXERS_BLOCKS.len(), 3);
assert!(INDEXERS_BLOCKS.contains(&ActiveLidarrBlock::Indexers));
assert!(INDEXERS_BLOCKS.contains(&ActiveLidarrBlock::DeleteIndexerPrompt));
assert!(INDEXERS_BLOCKS.contains(&ActiveLidarrBlock::TestIndexer));
}
#[test]
fn test_add_root_folder_blocks_contents() {
use crate::models::servarr_data::lidarr::lidarr_data::ADD_ROOT_FOLDER_BLOCKS;
assert_eq!(ADD_ROOT_FOLDER_BLOCKS.len(), 9);
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderPrompt));
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderConfirmPrompt));
+72
View File
@@ -1,6 +1,8 @@
use strum::IntoEnumIterator;
use super::lidarr_data::LidarrData;
use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_models::Indexer;
use crate::models::{
HorizontallyScrollableText,
lidarr_models::{MonitorType, NewItemMonitorType},
@@ -114,6 +116,76 @@ impl From<&LidarrData<'_>> for EditArtistModal {
}
}
impl From<&LidarrData<'_>> for EditIndexerModal {
fn from(lidarr_data: &LidarrData<'_>) -> EditIndexerModal {
let mut edit_indexer_modal = EditIndexerModal::default();
let Indexer {
name,
enable_rss,
enable_automatic_search,
enable_interactive_search,
tags,
fields,
priority,
..
} = lidarr_data.indexers.current_selection();
let seed_ratio_field_option = fields
.as_ref()
.expect("indexer fields must exist")
.iter()
.find(|field| {
field.name.as_ref().expect("indexer field name must exist") == "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().expect("indexer name must exist").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.priority = *priority;
edit_indexer_modal.url = fields
.as_ref()
.expect("indexer fields must exist")
.iter()
.find(|field| field.name.as_ref().expect("indexer field name must exist") == "baseUrl")
.expect("baseUrl field must exist")
.value
.clone()
.expect("baseUrl field value must exist")
.as_str()
.expect("baseUrl field value must be a string")
.into();
edit_indexer_modal.api_key = fields
.as_ref()
.expect("indexer fields must exist")
.iter()
.find(|field| field.name.as_ref().expect("indexer field name must exist") == "apiKey")
.expect("apiKey field must exist")
.value
.clone()
.expect("apiKey field value must exist")
.as_str()
.expect("apiKey field value must be a string")
.into();
if let Some(seed_ratio_value) = seed_ratio_value_option {
edit_indexer_modal.seed_ratio = seed_ratio_value
.as_f64()
.expect("Seed ratio value must be a valid f64")
.to_string()
.into();
}
edit_indexer_modal.tags = lidarr_data.tag_ids_to_display(tags).into();
edit_indexer_modal
}
}
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct AddRootFolderModal {
+104 -4
View File
@@ -1,12 +1,14 @@
#[cfg(test)]
mod tests {
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use crate::models::lidarr_models::{Artist, MonitorType, NewItemMonitorType};
use crate::models::servarr_data::lidarr::lidarr_data::LidarrData;
use crate::models::servarr_data::lidarr::modals::{AddArtistModal, EditArtistModal};
use crate::models::servarr_models::RootFolder;
use crate::models::servarr_data::modals::EditIndexerModal;
use crate::models::servarr_models::{Indexer, IndexerField, RootFolder};
use bimap::BiMap;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use serde_json::{Number, Value};
#[test]
fn test_add_artist_modal_from_lidarr_data() {
@@ -108,4 +110,102 @@ mod tests {
assert_str_eq!(edit_artist_modal.path.text, "/nfs/music/test_artist");
assert_str_eq!(edit_artist_modal.tags.text, "usenet");
}
#[rstest]
fn test_edit_indexer_modal_from_lidarr_data(#[values(true, false)] seed_ratio_present: bool) {
let mut lidarr_data = LidarrData {
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
..LidarrData::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),
priority: 1,
..Indexer::default()
};
lidarr_data.indexers.set_items(vec![indexer]);
let edit_indexer_modal = EditIndexerModal::from(&lidarr_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_eq!(edit_indexer_modal.priority, 1);
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_lidarr_data_seed_ratio_value_is_none() {
let mut lidarr_data = LidarrData {
tags_map: BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]),
..LidarrData::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),
priority: 1,
..Indexer::default()
};
lidarr_data.indexers.set_items(vec![indexer]);
let edit_indexer_modal = EditIndexerModal::from(&lidarr_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_eq!(edit_indexer_modal.priority, 1);
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());
}
}
+21 -1
View File
@@ -1,6 +1,10 @@
use crate::models::HorizontallyScrollableText;
#[derive(Default, Debug, PartialEq, Eq)]
#[cfg(test)]
#[path = "modals_tests.rs"]
mod modals_tests;
#[derive(Debug, PartialEq, Eq)]
pub struct EditIndexerModal {
pub name: HorizontallyScrollableText,
pub enable_rss: Option<bool>,
@@ -13,6 +17,22 @@ pub struct EditIndexerModal {
pub priority: i64,
}
impl Default for EditIndexerModal {
fn default() -> Self {
Self {
name: Default::default(),
enable_rss: None,
enable_automatic_search: None,
enable_interactive_search: None,
url: Default::default(),
api_key: Default::default(),
seed_ratio: Default::default(),
tags: Default::default(),
priority: 1,
}
}
}
#[derive(Default, Clone, Eq, PartialEq, Debug)]
pub struct IndexerTestResultModalItem {
pub name: String,
+20
View File
@@ -0,0 +1,20 @@
#[cfg(test)]
mod tests {
use crate::models::servarr_data::modals::EditIndexerModal;
use pretty_assertions::assert_eq;
#[test]
fn test_edit_indexer_modal_default() {
let edit_indexer_modal = EditIndexerModal::default();
assert_is_empty!(edit_indexer_modal.name.text);
assert_none!(&edit_indexer_modal.enable_rss);
assert_none!(&edit_indexer_modal.enable_automatic_search);
assert_none!(&edit_indexer_modal.enable_interactive_search);
assert_is_empty!(edit_indexer_modal.url.text);
assert_is_empty!(edit_indexer_modal.api_key.text);
assert_is_empty!(edit_indexer_modal.seed_ratio.text);
assert_is_empty!(edit_indexer_modal.tags.text);
assert_eq!(edit_indexer_modal.priority, 1);
}
}
@@ -12,10 +12,10 @@ use crate::{
models::{
BlockSelectionState, HorizontallyScrollableText, Route, ScrollableText, TabRoute, TabState,
servarr_data::modals::{EditIndexerModal, IndexerTestResultModalItem},
servarr_models::{DiskSpace, Indexer, QueueEvent, RootFolder},
servarr_models::{DiskSpace, Indexer, IndexerSettings, QueueEvent, RootFolder},
sonarr_models::{
AddSeriesSearchResult, BlocklistItem, DownloadRecord, IndexerSettings, Season, Series,
SonarrHistoryItem, SonarrTask,
AddSeriesSearchResult, BlocklistItem, DownloadRecord, Season, Series, SonarrHistoryItem,
SonarrTask,
},
stateful_list::StatefulList,
stateful_table::StatefulTable,
@@ -33,11 +33,12 @@ use {
crate::models::sonarr_models::{SeriesMonitor, SeriesType},
crate::models::stateful_table::SortOption,
crate::network::servarr_test_utils::diskspace,
crate::network::servarr_test_utils::indexer_settings,
crate::network::servarr_test_utils::indexer_test_result,
crate::network::servarr_test_utils::queued_event,
crate::network::sonarr_network::sonarr_network_test_utils::test_utils::{
add_series_search_result, blocklist_item, download_record, history_item, indexer,
indexer_settings, log_line, root_folder,
add_series_search_result, blocklist_item, download_record, history_item, indexer, log_line,
root_folder,
},
crate::network::sonarr_network::sonarr_network_test_utils::test_utils::{
episode, episode_file, language_profiles_map, quality_profile_map, season, series, tags_map,
+38 -1
View File
@@ -89,6 +89,21 @@ pub struct DiskSpace {
pub total_space: i64,
}
#[derive(Default, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IndexerSettings {
#[serde(deserialize_with = "super::from_i64")]
pub id: i64,
#[serde(deserialize_with = "super::from_i64")]
pub minimum_age: i64,
#[serde(deserialize_with = "super::from_i64")]
pub retention: i64,
#[serde(deserialize_with = "super::from_i64")]
pub maximum_size: i64,
#[serde(deserialize_with = "super::from_i64")]
pub rss_sync_interval: i64,
}
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct EditIndexerParams {
@@ -130,7 +145,7 @@ pub struct HostConfig {
pub ssl_cert_password: Option<String>,
}
#[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Indexer {
#[serde(deserialize_with = "super::from_i64")]
@@ -153,6 +168,28 @@ pub struct Indexer {
pub tags: Vec<Number>,
}
impl Default for Indexer {
fn default() -> Self {
Self {
id: 0,
name: None,
implementation: None,
implementation_name: None,
config_contract: None,
supports_rss: false,
supports_search: false,
fields: None,
enable_rss: false,
enable_automatic_search: false,
enable_interactive_search: false,
protocol: "".to_string(),
priority: 1,
download_client_id: 0,
tags: vec![],
}
}
}
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct IndexerTestResult {
+22 -1
View File
@@ -3,9 +3,30 @@ mod tests {
use pretty_assertions::{assert_eq, assert_str_eq};
use crate::models::servarr_models::{
AuthenticationMethod, AuthenticationRequired, CertificateValidation, QualityProfile,
AuthenticationMethod, AuthenticationRequired, CertificateValidation, Indexer, QualityProfile,
};
#[test]
fn test_indexer_default() {
let indexer = Indexer::default();
assert_eq!(indexer.id, 0);
assert_none!(indexer.name);
assert_none!(indexer.implementation);
assert_none!(indexer.implementation_name);
assert_none!(indexer.config_contract);
assert!(!indexer.supports_rss);
assert!(!indexer.supports_search);
assert_none!(indexer.fields);
assert!(!indexer.enable_rss);
assert!(!indexer.enable_automatic_search);
assert!(!indexer.enable_interactive_search);
assert_is_empty!(indexer.protocol);
assert_eq!(indexer.priority, 1);
assert_eq!(indexer.download_client_id, 0);
assert_is_empty!(indexer.tags);
}
#[test]
fn test_authentication_method_display() {
assert_str_eq!(AuthenticationMethod::Basic.to_string(), "basic");
+4 -16
View File
@@ -1,6 +1,9 @@
use std::fmt::{Display, Formatter};
use crate::{models::servarr_models::IndexerTestResult, serde_enum_from};
use crate::{
models::servarr_models::{IndexerSettings, IndexerTestResult},
serde_enum_from,
};
use chrono::{DateTime, Utc};
use clap::ValueEnum;
use derivative::Derivative;
@@ -221,21 +224,6 @@ pub struct EpisodeFile {
pub media_info: Option<MediaInfo>,
}
#[derive(Default, Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IndexerSettings {
#[serde(deserialize_with = "super::from_i64")]
pub id: i64,
#[serde(deserialize_with = "super::from_i64")]
pub minimum_age: i64,
#[serde(deserialize_with = "super::from_i64")]
pub retention: i64,
#[serde(deserialize_with = "super::from_i64")]
pub maximum_size: i64,
#[serde(deserialize_with = "super::from_i64")]
pub rss_sync_interval: i64,
}
#[derive(Serialize, Deserialize, Derivative, Debug, Clone, PartialEq, Eq)]
#[derivative(Default)]
#[serde(rename_all = "camelCase")]
+5 -5
View File
@@ -6,14 +6,14 @@ mod tests {
use crate::models::{
Serdeable,
servarr_models::{
DiskSpace, HostConfig, Indexer, IndexerTestResult, Language, Log, LogResponse,
QualityProfile, QueueEvent, RootFolder, SecurityConfig, Tag, Update,
DiskSpace, HostConfig, Indexer, IndexerSettings, IndexerTestResult, Language, Log,
LogResponse, QualityProfile, QueueEvent, RootFolder, SecurityConfig, Tag, Update,
},
sonarr_models::{
AddSeriesSearchResult, BlocklistItem, BlocklistResponse, DownloadRecord, DownloadStatus,
DownloadsResponse, Episode, EpisodeFile, IndexerSettings, Series, SeriesMonitor,
SeriesStatus, SeriesType, SonarrHistoryEventType, SonarrHistoryItem, SonarrRelease,
SonarrSerdeable, SonarrTask, SonarrTaskName, SystemStatus,
DownloadsResponse, Episode, EpisodeFile, Series, SeriesMonitor, SeriesStatus, SeriesType,
SonarrHistoryEventType, SonarrHistoryItem, SonarrRelease, SonarrSerdeable, SonarrTask,
SonarrTaskName, SystemStatus,
},
};