feat: Full CLI and TUI support for adding an artist to Lidarr
This commit is contained in:
@@ -134,6 +134,40 @@ pub enum NewItemMonitorType {
|
||||
New,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
EnumIter,
|
||||
clap::ValueEnum,
|
||||
Display,
|
||||
EnumDisplayStyle,
|
||||
)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[strum(serialize_all = "camelCase")]
|
||||
pub enum MonitorType {
|
||||
#[default]
|
||||
#[display_style(name = "All Albums")]
|
||||
All,
|
||||
#[display_style(name = "Future Albums")]
|
||||
Future,
|
||||
#[display_style(name = "Missing Albums")]
|
||||
Missing,
|
||||
#[display_style(name = "Existing Albums")]
|
||||
Existing,
|
||||
#[display_style(name = "First Album")]
|
||||
First,
|
||||
#[display_style(name = "Latest Album")]
|
||||
Latest,
|
||||
None,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DownloadRecord {
|
||||
@@ -219,6 +253,29 @@ pub struct DeleteArtistParams {
|
||||
pub add_import_list_exclusion: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AddArtistBody {
|
||||
pub foreign_artist_id: String,
|
||||
pub artist_name: String,
|
||||
pub monitored: bool,
|
||||
pub root_folder_path: String,
|
||||
pub quality_profile_id: i64,
|
||||
pub metadata_profile_id: i64,
|
||||
pub tags: Vec<i64>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub tag_input_string: Option<String>,
|
||||
pub add_options: AddArtistOptions,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AddArtistOptions {
|
||||
pub monitor: MonitorType,
|
||||
pub monitor_new_items: NewItemMonitorType,
|
||||
pub search_for_missing_albums: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EditArtistParams {
|
||||
|
||||
@@ -6,7 +6,7 @@ mod tests {
|
||||
|
||||
use crate::models::lidarr_models::{
|
||||
AddArtistSearchResult, DownloadRecord, DownloadStatus, DownloadsResponse, Member,
|
||||
MetadataProfile, NewItemMonitorType, SystemStatus,
|
||||
MetadataProfile, MonitorType, NewItemMonitorType, SystemStatus,
|
||||
};
|
||||
use crate::models::servarr_models::{
|
||||
DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag,
|
||||
@@ -35,6 +35,30 @@ mod tests {
|
||||
assert_str_eq!(NewItemMonitorType::New.to_display_str(), "New Albums");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_monitor_type_display() {
|
||||
assert_str_eq!(MonitorType::All.to_string(), "all");
|
||||
assert_str_eq!(MonitorType::Future.to_string(), "future");
|
||||
assert_str_eq!(MonitorType::Missing.to_string(), "missing");
|
||||
assert_str_eq!(MonitorType::Existing.to_string(), "existing");
|
||||
assert_str_eq!(MonitorType::First.to_string(), "first");
|
||||
assert_str_eq!(MonitorType::Latest.to_string(), "latest");
|
||||
assert_str_eq!(MonitorType::None.to_string(), "none");
|
||||
assert_str_eq!(MonitorType::Unknown.to_string(), "unknown");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_monitor_type_to_display_str() {
|
||||
assert_str_eq!(MonitorType::All.to_display_str(), "All Albums");
|
||||
assert_str_eq!(MonitorType::Future.to_display_str(), "Future Albums");
|
||||
assert_str_eq!(MonitorType::Missing.to_display_str(), "Missing Albums");
|
||||
assert_str_eq!(MonitorType::Existing.to_display_str(), "Existing Albums");
|
||||
assert_str_eq!(MonitorType::First.to_display_str(), "First Album");
|
||||
assert_str_eq!(MonitorType::Latest.to_display_str(), "Latest Album");
|
||||
assert_str_eq!(MonitorType::None.to_display_str(), "None");
|
||||
assert_str_eq!(MonitorType::Unknown.to_display_str(), "Unknown");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lidarr_serdeable_from() {
|
||||
let lidarr_serdeable = LidarrSerdeable::Value(json!({}));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use serde_json::Number;
|
||||
|
||||
use super::modals::EditArtistModal;
|
||||
use super::modals::{AddArtistModal, EditArtistModal};
|
||||
use crate::app::lidarr::lidarr_context_clues::ARTISTS_CONTEXT_CLUES;
|
||||
use crate::models::{
|
||||
BlockSelectionState, HorizontallyScrollableText, Route, TabRoute, TabState,
|
||||
@@ -31,6 +31,7 @@ use {
|
||||
mod lidarr_data_tests;
|
||||
|
||||
pub struct LidarrData<'a> {
|
||||
pub add_artist_modal: Option<AddArtistModal>,
|
||||
pub add_artist_search: Option<HorizontallyScrollableText>,
|
||||
pub add_import_list_exclusion: bool,
|
||||
pub add_searched_artists: Option<StatefulTable<AddArtistSearchResult>>,
|
||||
@@ -92,6 +93,7 @@ impl LidarrData<'_> {
|
||||
impl<'a> Default for LidarrData<'a> {
|
||||
fn default() -> LidarrData<'a> {
|
||||
LidarrData {
|
||||
add_artist_modal: None,
|
||||
add_artist_search: None,
|
||||
add_import_list_exclusion: false,
|
||||
add_searched_artists: None,
|
||||
@@ -122,6 +124,25 @@ impl<'a> Default for LidarrData<'a> {
|
||||
#[cfg(test)]
|
||||
impl LidarrData<'_> {
|
||||
pub fn test_default_fully_populated() -> Self {
|
||||
let mut add_artist_modal = AddArtistModal {
|
||||
tags: "usenet, testing".into(),
|
||||
..AddArtistModal::default()
|
||||
};
|
||||
add_artist_modal
|
||||
.monitor_list
|
||||
.set_items(Vec::from_iter(MonitorType::iter()));
|
||||
add_artist_modal
|
||||
.monitor_new_items_list
|
||||
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
|
||||
add_artist_modal
|
||||
.metadata_profile_list
|
||||
.set_items(vec![metadata_profile().name]);
|
||||
add_artist_modal
|
||||
.quality_profile_list
|
||||
.set_items(vec![quality_profile().name]);
|
||||
add_artist_modal
|
||||
.root_folder_list
|
||||
.set_items(vec![root_folder()]);
|
||||
let mut edit_artist_modal = EditArtistModal {
|
||||
monitored: Some(true),
|
||||
path: "/nfs/music".into(),
|
||||
@@ -144,6 +165,7 @@ impl LidarrData<'_> {
|
||||
quality_profile_map: quality_profile_map(),
|
||||
metadata_profile_map: metadata_profile_map(),
|
||||
edit_artist_modal: Some(edit_artist_modal),
|
||||
add_artist_modal: Some(add_artist_modal),
|
||||
tags_map: tags_map(),
|
||||
..LidarrData::default()
|
||||
};
|
||||
@@ -172,9 +194,18 @@ pub enum ActiveLidarrBlock {
|
||||
#[default]
|
||||
Artists,
|
||||
ArtistsSortPrompt,
|
||||
AddArtistAlreadyInLibrary,
|
||||
AddArtistConfirmPrompt,
|
||||
AddArtistEmptySearchResults,
|
||||
AddArtistPrompt,
|
||||
AddArtistSearchInput,
|
||||
AddArtistSearchResults,
|
||||
AddArtistSelectMetadataProfile,
|
||||
AddArtistSelectMonitor,
|
||||
AddArtistSelectMonitorNewItems,
|
||||
AddArtistSelectQualityProfile,
|
||||
AddArtistSelectRootFolder,
|
||||
AddArtistTagsInput,
|
||||
DeleteArtistPrompt,
|
||||
DeleteArtistConfirmPrompt,
|
||||
DeleteArtistToggleDeleteFile,
|
||||
@@ -204,10 +235,29 @@ pub static LIBRARY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
||||
ActiveLidarrBlock::UpdateAllArtistsPrompt,
|
||||
];
|
||||
|
||||
pub static ADD_ARTIST_BLOCKS: [ActiveLidarrBlock; 3] = [
|
||||
pub static ADD_ARTIST_BLOCKS: [ActiveLidarrBlock; 12] = [
|
||||
ActiveLidarrBlock::AddArtistAlreadyInLibrary,
|
||||
ActiveLidarrBlock::AddArtistConfirmPrompt,
|
||||
ActiveLidarrBlock::AddArtistEmptySearchResults,
|
||||
ActiveLidarrBlock::AddArtistPrompt,
|
||||
ActiveLidarrBlock::AddArtistSearchInput,
|
||||
ActiveLidarrBlock::AddArtistSearchResults,
|
||||
ActiveLidarrBlock::AddArtistSelectMetadataProfile,
|
||||
ActiveLidarrBlock::AddArtistSelectMonitor,
|
||||
ActiveLidarrBlock::AddArtistSelectMonitorNewItems,
|
||||
ActiveLidarrBlock::AddArtistSelectQualityProfile,
|
||||
ActiveLidarrBlock::AddArtistSelectRootFolder,
|
||||
ActiveLidarrBlock::AddArtistTagsInput,
|
||||
];
|
||||
|
||||
pub const ADD_ARTIST_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
|
||||
&[ActiveLidarrBlock::AddArtistSelectRootFolder],
|
||||
&[ActiveLidarrBlock::AddArtistSelectMonitor],
|
||||
&[ActiveLidarrBlock::AddArtistSelectMonitorNewItems],
|
||||
&[ActiveLidarrBlock::AddArtistSelectQualityProfile],
|
||||
&[ActiveLidarrBlock::AddArtistSelectMetadataProfile],
|
||||
&[ActiveLidarrBlock::AddArtistTagsInput],
|
||||
&[ActiveLidarrBlock::AddArtistConfirmPrompt],
|
||||
];
|
||||
|
||||
pub static DELETE_ARTIST_BLOCKS: [ActiveLidarrBlock; 4] = [
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
mod tests {
|
||||
use crate::app::lidarr::lidarr_context_clues::ARTISTS_CONTEXT_CLUES;
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||
ADD_ARTIST_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_BLOCKS,
|
||||
EDIT_ARTIST_SELECTION_BLOCKS,
|
||||
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS,
|
||||
DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||
};
|
||||
use crate::models::{
|
||||
BlockSelectionState, Route,
|
||||
@@ -155,10 +155,54 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_add_artist_blocks_contents() {
|
||||
assert_eq!(ADD_ARTIST_BLOCKS.len(), 3);
|
||||
assert_eq!(ADD_ARTIST_BLOCKS.len(), 12);
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistAlreadyInLibrary));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistConfirmPrompt));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistEmptySearchResults));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistPrompt));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSearchInput));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSearchResults));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSelectMetadataProfile));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSelectMonitor));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSelectMonitorNewItems));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSelectQualityProfile));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSelectRootFolder));
|
||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistTagsInput));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_artist_selection_blocks_ordering() {
|
||||
let mut add_artist_block_iter = ADD_ARTIST_SELECTION_BLOCKS.iter();
|
||||
|
||||
assert_eq!(
|
||||
add_artist_block_iter.next().unwrap(),
|
||||
&[ActiveLidarrBlock::AddArtistSelectRootFolder]
|
||||
);
|
||||
assert_eq!(
|
||||
add_artist_block_iter.next().unwrap(),
|
||||
&[ActiveLidarrBlock::AddArtistSelectMonitor]
|
||||
);
|
||||
assert_eq!(
|
||||
add_artist_block_iter.next().unwrap(),
|
||||
&[ActiveLidarrBlock::AddArtistSelectMonitorNewItems]
|
||||
);
|
||||
assert_eq!(
|
||||
add_artist_block_iter.next().unwrap(),
|
||||
&[ActiveLidarrBlock::AddArtistSelectQualityProfile]
|
||||
);
|
||||
assert_eq!(
|
||||
add_artist_block_iter.next().unwrap(),
|
||||
&[ActiveLidarrBlock::AddArtistSelectMetadataProfile]
|
||||
);
|
||||
assert_eq!(
|
||||
add_artist_block_iter.next().unwrap(),
|
||||
&[ActiveLidarrBlock::AddArtistTagsInput]
|
||||
);
|
||||
assert_eq!(
|
||||
add_artist_block_iter.next().unwrap(),
|
||||
&[ActiveLidarrBlock::AddArtistConfirmPrompt]
|
||||
);
|
||||
assert_none!(add_artist_block_iter.next());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2,13 +2,50 @@ use strum::IntoEnumIterator;
|
||||
|
||||
use super::lidarr_data::LidarrData;
|
||||
use crate::models::{
|
||||
HorizontallyScrollableText, lidarr_models::NewItemMonitorType, stateful_list::StatefulList,
|
||||
HorizontallyScrollableText,
|
||||
lidarr_models::{MonitorType, NewItemMonitorType},
|
||||
servarr_models::RootFolder,
|
||||
stateful_list::StatefulList,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "modals_tests.rs"]
|
||||
mod modals_tests;
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct AddArtistModal {
|
||||
pub root_folder_list: StatefulList<RootFolder>,
|
||||
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 AddArtistModal {
|
||||
fn from(lidarr_data: &LidarrData<'_>) -> AddArtistModal {
|
||||
let mut add_artist_modal = AddArtistModal::default();
|
||||
add_artist_modal
|
||||
.monitor_list
|
||||
.set_items(Vec::from_iter(MonitorType::iter()));
|
||||
add_artist_modal
|
||||
.monitor_new_items_list
|
||||
.set_items(Vec::from_iter(NewItemMonitorType::iter()));
|
||||
add_artist_modal
|
||||
.quality_profile_list
|
||||
.set_items(lidarr_data.sorted_quality_profile_names());
|
||||
add_artist_modal
|
||||
.metadata_profile_list
|
||||
.set_items(lidarr_data.sorted_metadata_profile_names());
|
||||
add_artist_modal
|
||||
.root_folder_list
|
||||
.set_items(lidarr_data.root_folders.items.to_vec());
|
||||
|
||||
add_artist_modal
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[cfg_attr(test, derive(Debug))]
|
||||
pub struct EditArtistModal {
|
||||
|
||||
@@ -3,9 +3,66 @@ mod tests {
|
||||
use bimap::BiMap;
|
||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||
|
||||
use crate::models::lidarr_models::{Artist, NewItemMonitorType};
|
||||
use crate::models::lidarr_models::{Artist, MonitorType, NewItemMonitorType};
|
||||
use crate::models::servarr_data::lidarr::lidarr_data::LidarrData;
|
||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||
use crate::models::servarr_data::lidarr::modals::{AddArtistModal, EditArtistModal};
|
||||
use crate::models::servarr_models::RootFolder;
|
||||
|
||||
#[test]
|
||||
fn test_add_artist_modal_from_lidarr_data() {
|
||||
let mut lidarr_data = LidarrData {
|
||||
quality_profile_map: BiMap::from_iter([
|
||||
(2i64, "Lossless".to_owned()),
|
||||
(1i64, "Standard".to_owned()),
|
||||
]),
|
||||
metadata_profile_map: BiMap::from_iter([
|
||||
(2i64, "None".to_owned()),
|
||||
(1i64, "Standard".to_owned()),
|
||||
]),
|
||||
..LidarrData::default()
|
||||
};
|
||||
let root_folder_1 = RootFolder {
|
||||
id: 1,
|
||||
path: "/nfs".to_owned(),
|
||||
accessible: true,
|
||||
free_space: 219902325555200,
|
||||
unmapped_folders: None,
|
||||
};
|
||||
lidarr_data.root_folders.set_items(vec![
|
||||
root_folder_1.clone(),
|
||||
RootFolder {
|
||||
id: 2,
|
||||
path: "/nfs2".to_owned(),
|
||||
accessible: true,
|
||||
free_space: 21990232555520,
|
||||
unmapped_folders: None,
|
||||
},
|
||||
]);
|
||||
|
||||
let add_artist_modal = AddArtistModal::from(&lidarr_data);
|
||||
|
||||
assert_eq!(
|
||||
*add_artist_modal.monitor_list.current_selection(),
|
||||
MonitorType::default()
|
||||
);
|
||||
assert_eq!(
|
||||
*add_artist_modal.monitor_new_items_list.current_selection(),
|
||||
NewItemMonitorType::default()
|
||||
);
|
||||
assert_str_eq!(
|
||||
add_artist_modal.quality_profile_list.current_selection(),
|
||||
"Standard"
|
||||
);
|
||||
assert_str_eq!(
|
||||
add_artist_modal.metadata_profile_list.current_selection(),
|
||||
"Standard"
|
||||
);
|
||||
assert_eq!(
|
||||
add_artist_modal.root_folder_list.current_selection(),
|
||||
&root_folder_1
|
||||
);
|
||||
assert_is_empty!(add_artist_modal.tags.text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edit_artist_modal_from_lidarr_data() {
|
||||
|
||||
Reference in New Issue
Block a user