feat: Full support for adding a root folder in Lidarr from both the CLI and TUI

This commit is contained in:
2026-01-14 09:06:27 -07:00
parent d2217509f2
commit 8abcf44866
31 changed files with 3495 additions and 461 deletions
+14
View File
@@ -301,6 +301,20 @@ pub struct EditArtistParams {
pub clear_tags: bool,
}
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AddLidarrRootFolderBody {
pub name: String,
pub path: String,
pub default_quality_profile_id: i64,
pub default_metadata_profile_id: i64,
pub default_monitor_option: MonitorType,
pub default_new_item_monitor_option: NewItemMonitorType,
pub default_tags: Vec<i64>,
#[serde(skip_serializing, skip_deserializing)]
pub tag_input_string: Option<String>,
}
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Album {
+55 -6
View File
@@ -1,6 +1,6 @@
use serde_json::Number;
use super::modals::{AddArtistModal, EditArtistModal};
use super::modals::{AddArtistModal, AddRootFolderModal, EditArtistModal};
use crate::app::context_clues::{
DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES,
};
@@ -39,6 +39,7 @@ pub struct LidarrData<'a> {
pub add_artist_modal: Option<AddArtistModal>,
pub add_artist_search: Option<HorizontallyScrollableText>,
pub add_import_list_exclusion: bool,
pub add_root_folder_modal: Option<AddRootFolderModal>,
pub add_searched_artists: Option<StatefulTable<AddArtistSearchResult>>,
pub albums: StatefulTable<Album>,
pub artist_info_tabs: TabState,
@@ -47,7 +48,6 @@ pub struct LidarrData<'a> {
pub disk_space_vec: Vec<DiskSpace>,
pub downloads: StatefulTable<DownloadRecord>,
pub edit_artist_modal: Option<EditArtistModal>,
pub edit_root_folder: Option<HorizontallyScrollableText>,
pub history: StatefulTable<LidarrHistoryItem>,
pub main_tabs: TabState,
pub metadata_profile_map: BiMap<i64, String>,
@@ -110,6 +110,7 @@ impl<'a> Default for LidarrData<'a> {
add_artist_modal: None,
add_artist_search: None,
add_import_list_exclusion: false,
add_root_folder_modal: None,
add_searched_artists: None,
albums: StatefulTable::default(),
artists: StatefulTable::default(),
@@ -117,7 +118,6 @@ impl<'a> Default for LidarrData<'a> {
disk_space_vec: Vec::new(),
downloads: StatefulTable::default(),
edit_artist_modal: None,
edit_root_folder: None,
history: StatefulTable::default(),
metadata_profile_map: BiMap::new(),
prompt_confirm: false,
@@ -202,13 +202,32 @@ impl LidarrData<'_> {
.metadata_profile_list
.set_items(vec![metadata_profile().name]);
let mut add_root_folder_modal = AddRootFolderModal {
name: "Test Root Folder".into(),
path: "/nfs/music".into(),
tags: "test".into(),
..AddRootFolderModal::default()
};
add_root_folder_modal
.monitor_list
.set_items(Vec::from_iter(MonitorType::iter()));
add_root_folder_modal
.monitor_new_items_list
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
add_root_folder_modal
.quality_profile_list
.set_items(vec![quality_profile().name]);
add_root_folder_modal
.metadata_profile_list
.set_items(vec![metadata_profile().name]);
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_root_folder: Some("/nfs".into()),
add_root_folder_modal: Some(add_root_folder_modal),
add_artist_modal: Some(add_artist_modal),
tags_map: tags_map(),
..LidarrData::default()
@@ -261,6 +280,14 @@ pub enum ActiveLidarrBlock {
AddArtistSelectRootFolder,
AddArtistTagsInput,
AddRootFolderPrompt,
AddRootFolderConfirmPrompt,
AddRootFolderNameInput,
AddRootFolderPathInput,
AddRootFolderSelectMonitor,
AddRootFolderSelectMonitorNewItems,
AddRootFolderSelectQualityProfile,
AddRootFolderSelectMetadataProfile,
AddRootFolderTagsInput,
AutomaticallySearchArtistPrompt,
DeleteAlbumPrompt,
DeleteAlbumConfirmPrompt,
@@ -406,12 +433,34 @@ pub const EDIT_ARTIST_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
&[ActiveLidarrBlock::EditArtistConfirmPrompt],
];
pub const ROOT_FOLDERS_BLOCKS: [ActiveLidarrBlock; 3] = [
pub const ROOT_FOLDERS_BLOCKS: [ActiveLidarrBlock; 2] = [
ActiveLidarrBlock::RootFolders,
ActiveLidarrBlock::AddRootFolderPrompt,
ActiveLidarrBlock::DeleteRootFolderPrompt,
];
pub static ADD_ROOT_FOLDER_BLOCKS: [ActiveLidarrBlock; 9] = [
ActiveLidarrBlock::AddRootFolderPrompt,
ActiveLidarrBlock::AddRootFolderConfirmPrompt,
ActiveLidarrBlock::AddRootFolderNameInput,
ActiveLidarrBlock::AddRootFolderPathInput,
ActiveLidarrBlock::AddRootFolderSelectMonitor,
ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems,
ActiveLidarrBlock::AddRootFolderSelectQualityProfile,
ActiveLidarrBlock::AddRootFolderSelectMetadataProfile,
ActiveLidarrBlock::AddRootFolderTagsInput,
];
pub const ADD_ROOT_FOLDER_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
&[ActiveLidarrBlock::AddRootFolderNameInput],
&[ActiveLidarrBlock::AddRootFolderPathInput],
&[ActiveLidarrBlock::AddRootFolderSelectMonitor],
&[ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems],
&[ActiveLidarrBlock::AddRootFolderSelectQualityProfile],
&[ActiveLidarrBlock::AddRootFolderSelectMetadataProfile],
&[ActiveLidarrBlock::AddRootFolderTagsInput],
&[ActiveLidarrBlock::AddRootFolderConfirmPrompt],
];
impl From<ActiveLidarrBlock> for Route {
fn from(active_lidarr_block: ActiveLidarrBlock) -> Route {
Route::Lidarr(active_lidarr_block, None)
@@ -138,7 +138,7 @@ mod tests {
assert_is_empty!(lidarr_data.disk_space_vec);
assert_is_empty!(lidarr_data.downloads);
assert_none!(lidarr_data.edit_artist_modal);
assert_none!(lidarr_data.edit_root_folder);
assert_none!(lidarr_data.add_root_folder_modal);
assert_is_empty!(lidarr_data.history);
assert_is_empty!(lidarr_data.metadata_profile_map);
assert!(!lidarr_data.prompt_confirm);
@@ -409,9 +409,27 @@ mod tests {
#[test]
fn test_root_folders_blocks_contents() {
assert_eq!(ROOT_FOLDERS_BLOCKS.len(), 3);
assert_eq!(ROOT_FOLDERS_BLOCKS.len(), 2);
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveLidarrBlock::RootFolders));
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderPrompt));
assert!(ROOT_FOLDERS_BLOCKS.contains(&ActiveLidarrBlock::DeleteRootFolderPrompt));
}
#[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));
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderNameInput));
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderPathInput));
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderSelectMonitor));
assert!(
ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderSelectMonitorNewItems)
);
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderSelectQualityProfile));
assert!(
ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderSelectMetadataProfile)
);
assert!(ADD_ROOT_FOLDER_BLOCKS.contains(&ActiveLidarrBlock::AddRootFolderTagsInput));
}
}
+32
View File
@@ -113,3 +113,35 @@ impl From<&LidarrData<'_>> for EditArtistModal {
edit_artist_modal
}
}
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct AddRootFolderModal {
pub name: HorizontallyScrollableText,
pub path: HorizontallyScrollableText,
pub monitor_list: StatefulList<MonitorType>,
pub monitor_new_items_list: StatefulList<NewItemMonitorType>,
pub quality_profile_list: StatefulList<String>,
pub metadata_profile_list: StatefulList<String>,
pub tags: HorizontallyScrollableText,
}
impl From<&LidarrData<'_>> for AddRootFolderModal {
fn from(lidarr_data: &LidarrData<'_>) -> AddRootFolderModal {
let mut add_root_folder_modal = AddRootFolderModal::default();
add_root_folder_modal
.monitor_list
.set_items(Vec::from_iter(MonitorType::iter()));
add_root_folder_modal
.monitor_new_items_list
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
add_root_folder_modal
.quality_profile_list
.set_items(lidarr_data.sorted_quality_profile_names());
add_root_folder_modal
.metadata_profile_list
.set_items(lidarr_data.sorted_metadata_profile_names());
add_root_folder_modal
}
}