Compare commits
10 Commits
4abf705cb5
...
f31810e48a
| Author | SHA1 | Date | |
|---|---|---|---|
| f31810e48a | |||
| 09bee7473f | |||
| b2814371f0 | |||
| 269057867f | |||
| 450fdd7106 | |||
| c624d1b9e4 | |||
| e94f78dc7b | |||
| b1a6db21f1 | |||
| ca208ff5e4 | |||
| 1a43d1ec7c |
@@ -1,8 +1,12 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::context_clues::{BARE_POPUP_CONTEXT_CLUES, ContextClue, ContextClueProvider};
|
use crate::app::context_clues::{
|
||||||
|
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
||||||
|
};
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{ADD_ARTIST_BLOCKS, ActiveLidarrBlock};
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, EDIT_ARTIST_BLOCKS,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "lidarr_context_clues_tests.rs"]
|
#[path = "lidarr_context_clues_tests.rs"]
|
||||||
@@ -32,6 +36,26 @@ pub static ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES: [ContextClue; 2] = [
|
|||||||
(DEFAULT_KEYBINDINGS.esc, "edit search"),
|
(DEFAULT_KEYBINDINGS.esc, "edit search"),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub static ARTIST_DETAILS_CONTEXT_CLUES: [ContextClue; 8] = [
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.edit, "edit artist"),
|
||||||
|
(DEFAULT_KEYBINDINGS.delete, "delete album"),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
|
"toggle album monitoring",
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc),
|
||||||
|
(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc),
|
||||||
|
(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
|
),
|
||||||
|
(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc),
|
||||||
|
];
|
||||||
|
|
||||||
pub(in crate::app) struct LidarrContextClueProvider;
|
pub(in crate::app) struct LidarrContextClueProvider;
|
||||||
|
|
||||||
impl ContextClueProvider for LidarrContextClueProvider {
|
impl ContextClueProvider for LidarrContextClueProvider {
|
||||||
@@ -41,12 +65,25 @@ impl ContextClueProvider for LidarrContextClueProvider {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match active_lidarr_block {
|
match active_lidarr_block {
|
||||||
|
_ if ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block) => app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artist_info_tabs
|
||||||
|
.get_active_route_contextual_help(),
|
||||||
ActiveLidarrBlock::AddArtistSearchInput | ActiveLidarrBlock::AddArtistEmptySearchResults => {
|
ActiveLidarrBlock::AddArtistSearchInput | ActiveLidarrBlock::AddArtistEmptySearchResults => {
|
||||||
Some(&BARE_POPUP_CONTEXT_CLUES)
|
Some(&BARE_POPUP_CONTEXT_CLUES)
|
||||||
}
|
}
|
||||||
_ if EDIT_ARTIST_BLOCKS.contains(&active_lidarr_block) => {
|
_ if EDIT_ARTIST_BLOCKS.contains(&active_lidarr_block) => {
|
||||||
Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES)
|
Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES)
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectRootFolder
|
||||||
|
| ActiveLidarrBlock::AddArtistTagsInput
|
||||||
|
| ActiveLidarrBlock::AddArtistAlreadyInLibrary => Some(&CONFIRMATION_PROMPT_CONTEXT_CLUES),
|
||||||
_ if ADD_ARTIST_BLOCKS.contains(&active_lidarr_block) => {
|
_ if ADD_ARTIST_BLOCKS.contains(&active_lidarr_block) => {
|
||||||
Some(&ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES)
|
Some(&ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,16 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::context_clues::{
|
use crate::app::context_clues::{
|
||||||
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClueProvider,
|
BARE_POPUP_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES, ContextClue, ContextClueProvider,
|
||||||
};
|
};
|
||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
use crate::app::lidarr::lidarr_context_clues::{
|
use crate::app::lidarr::lidarr_context_clues::{
|
||||||
ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES, LidarrContextClueProvider,
|
ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES, ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||||
|
LidarrContextClueProvider,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ActiveLidarrBlock, EDIT_ARTIST_BLOCKS, LidarrData,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_BLOCKS};
|
|
||||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
@@ -65,6 +68,54 @@ mod tests {
|
|||||||
assert_none!(artists_context_clues_iter.next());
|
assert_none!(artists_context_clues_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_context_clues() {
|
||||||
|
let mut artist_details_context_clues_iter = ARTIST_DETAILS_CONTEXT_CLUES.iter();
|
||||||
|
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh,
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.desc,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.edit, "edit artist")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.delete, "delete album")
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring,
|
||||||
|
"toggle album monitoring",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.search, DEFAULT_KEYBINDINGS.search.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.update, DEFAULT_KEYBINDINGS.update.desc)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search,
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.desc,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
artist_details_context_clues_iter.next(),
|
||||||
|
&(DEFAULT_KEYBINDINGS.esc, DEFAULT_KEYBINDINGS.esc.desc)
|
||||||
|
);
|
||||||
|
assert_none!(artist_details_context_clues_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_artist_search_results_context_clues() {
|
fn test_add_artist_search_results_context_clues() {
|
||||||
let mut add_artist_search_results_context_clues_iter =
|
let mut add_artist_search_results_context_clues_iter =
|
||||||
@@ -92,6 +143,23 @@ mod tests {
|
|||||||
LidarrContextClueProvider::get_context_clues(&mut app);
|
LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(0, ActiveLidarrBlock::ArtistDetails, &ARTIST_DETAILS_CONTEXT_CLUES)]
|
||||||
|
fn test_lidarr_context_clue_provider_artist_info_tabs(
|
||||||
|
#[case] index: usize,
|
||||||
|
#[case] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_context_clues: &[ContextClue],
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data = LidarrData::default();
|
||||||
|
app.data.lidarr_data.artist_info_tabs.set_index(index);
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, expected_context_clues);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lidarr_context_clue_provider_artists_block() {
|
fn test_lidarr_context_clue_provider_artists_block() {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
@@ -169,4 +237,26 @@ mod tests {
|
|||||||
|
|
||||||
assert_some_eq_x!(context_clues, &ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES);
|
assert_some_eq_x!(context_clues, &ADD_ARTIST_SEARCH_RESULTS_CONTEXT_CLUES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_lidarr_context_clue_provider_confirmation_prompt_context_clues_add_artist_blocks(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitor,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitorNewItems,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectQualityProfile,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMetadataProfile,
|
||||||
|
ActiveLidarrBlock::AddArtistSelectRootFolder,
|
||||||
|
ActiveLidarrBlock::AddArtistTagsInput,
|
||||||
|
ActiveLidarrBlock::AddArtistAlreadyInLibrary
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
let context_clues = LidarrContextClueProvider::get_context_clues(&mut app);
|
||||||
|
|
||||||
|
assert_some_eq_x!(context_clues, &CONFIRMATION_PROMPT_CONTEXT_CLUES);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod tests {
|
|||||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
use crate::network::NetworkEvent;
|
use crate::network::NetworkEvent;
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist;
|
||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
use pretty_assertions::{assert_eq, assert_str_eq};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
@@ -32,6 +33,23 @@ mod tests {
|
|||||||
assert_eq!(app.tick_count, 0);
|
assert_eq!(app.tick_count, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_dispatch_by_lidarr_block_artist_details() {
|
||||||
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
||||||
|
app.network_tx = Some(tx);
|
||||||
|
|
||||||
|
app
|
||||||
|
.dispatch_by_lidarr_block(&ActiveLidarrBlock::ArtistDetails)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(app.is_loading);
|
||||||
|
assert_eq!(rx.recv().await.unwrap(), LidarrEvent::GetAlbums(1).into());
|
||||||
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
|
assert_eq!(app.tick_count, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_dispatch_by_lidarr_block_add_artist_search_results() {
|
async fn test_dispatch_by_lidarr_block_add_artist_search_results() {
|
||||||
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
let (tx, mut rx) = mpsc::channel::<NetworkEvent>(500);
|
||||||
@@ -60,4 +78,12 @@ mod tests {
|
|||||||
|
|
||||||
assert_str_eq!(query, "Test Artist");
|
assert_str_eq!(query, "Test Artist");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_extract_artist_id() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
||||||
|
|
||||||
|
assert_eq!(app.extract_artist_id().await, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ impl App<'_> {
|
|||||||
.dispatch_network_event(LidarrEvent::ListArtists.into())
|
.dispatch_network_event(LidarrEvent::ListArtists.into())
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::ArtistDetails => {
|
||||||
|
self
|
||||||
|
.dispatch_network_event(LidarrEvent::GetAlbums(self.extract_artist_id().await).into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
ActiveLidarrBlock::AddArtistSearchResults => {
|
ActiveLidarrBlock::AddArtistSearchResults => {
|
||||||
self
|
self
|
||||||
.dispatch_network_event(
|
.dispatch_network_event(
|
||||||
@@ -53,6 +58,10 @@ impl App<'_> {
|
|||||||
.clone()
|
.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn extract_artist_id(&self) -> i64 {
|
||||||
|
self.data.lidarr_data.artists.current_selection().id
|
||||||
|
}
|
||||||
|
|
||||||
async fn check_for_lidarr_prompt_action(&mut self) {
|
async fn check_for_lidarr_prompt_action(&mut self) {
|
||||||
if self.data.lidarr_data.prompt_confirm {
|
if self.data.lidarr_data.prompt_confirm {
|
||||||
self.data.lidarr_data.prompt_confirm = false;
|
self.data.lidarr_data.prompt_confirm = false;
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ mod tests {
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::app::radarr::ActiveRadarrBlock;
|
use crate::app::radarr::ActiveRadarrBlock;
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
AddMovieBody, AddMovieOptions, Collection, CollectionMovie, Credit, Movie, RadarrRelease,
|
AddMovieBody, AddMovieOptions, Collection, CollectionMovie, Credit, MinimumAvailability, Movie,
|
||||||
|
MovieMonitor, RadarrRelease,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
|
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
|
||||||
use crate::models::servarr_models::Indexer;
|
use crate::models::servarr_models::Indexer;
|
||||||
@@ -88,13 +89,13 @@ mod tests {
|
|||||||
tmdb_id: 1234,
|
tmdb_id: 1234,
|
||||||
title: "Test".to_owned(),
|
title: "Test".to_owned(),
|
||||||
root_folder_path: "/nfs2".to_owned(),
|
root_folder_path: "/nfs2".to_owned(),
|
||||||
minimum_availability: "announced".to_owned(),
|
minimum_availability: MinimumAvailability::Announced,
|
||||||
monitored: true,
|
monitored: true,
|
||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
tags: vec![1, 2],
|
tags: vec![1, 2],
|
||||||
tag_input_string: None,
|
tag_input_string: None,
|
||||||
add_options: AddMovieOptions {
|
add_options: AddMovieOptions {
|
||||||
monitor: "movieOnly".to_owned(),
|
monitor: MovieMonitor::MovieOnly,
|
||||||
search_for_movie: true,
|
search_for_movie: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::{Subcommand, arg};
|
use clap::{ArgAction, Subcommand, arg};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use super::LidarrCommand;
|
use super::LidarrCommand;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
|
models::lidarr_models::{AddArtistBody, AddArtistOptions, MonitorType, NewItemMonitorType},
|
||||||
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,6 +18,63 @@ mod add_command_handler_tests;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
pub enum LidarrAddCommand {
|
pub enum LidarrAddCommand {
|
||||||
|
#[command(about = "Add a new artist to your Lidarr library")]
|
||||||
|
Artist {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The MusicBrainz foreign artist ID of the artist you wish to add to your library",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
foreign_artist_id: String,
|
||||||
|
#[arg(long, help = "The name of the artist", required = true)]
|
||||||
|
artist_name: String,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The root folder path where all artist data and metadata should live",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
root_folder_path: String,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the quality profile to use for this artist",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
quality_profile_id: i64,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the metadata profile to use for this artist",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
metadata_profile_id: i64,
|
||||||
|
#[arg(long, help = "Disable monitoring for this artist")]
|
||||||
|
disable_monitoring: bool,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Tag IDs to tag the artist with",
|
||||||
|
value_parser,
|
||||||
|
action = ArgAction::Append
|
||||||
|
)]
|
||||||
|
tag: Vec<i64>,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "What Lidarr should monitor for this artist",
|
||||||
|
value_enum,
|
||||||
|
default_value_t = MonitorType::default()
|
||||||
|
)]
|
||||||
|
monitor: MonitorType,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "How Lidarr should monitor new items for this artist",
|
||||||
|
value_enum,
|
||||||
|
default_value_t = NewItemMonitorType::default()
|
||||||
|
)]
|
||||||
|
monitor_new_items: NewItemMonitorType,
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "Tell Lidarr to not start a search for missing albums once the artist is added to your library"
|
||||||
|
)]
|
||||||
|
no_search_for_missing_albums: bool,
|
||||||
|
},
|
||||||
#[command(about = "Add new tag")]
|
#[command(about = "Add new tag")]
|
||||||
Tag {
|
Tag {
|
||||||
#[arg(long, help = "The name of the tag to be added", required = true)]
|
#[arg(long, help = "The name of the tag to be added", required = true)]
|
||||||
@@ -51,6 +109,39 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrAddCommand> for LidarrAddCommandHan
|
|||||||
|
|
||||||
async fn handle(self) -> Result<String> {
|
async fn handle(self) -> Result<String> {
|
||||||
let result = match self.command {
|
let result = match self.command {
|
||||||
|
LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id,
|
||||||
|
artist_name,
|
||||||
|
root_folder_path,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
disable_monitoring,
|
||||||
|
tag: tags,
|
||||||
|
monitor,
|
||||||
|
monitor_new_items,
|
||||||
|
no_search_for_missing_albums,
|
||||||
|
} => {
|
||||||
|
let body = AddArtistBody {
|
||||||
|
foreign_artist_id,
|
||||||
|
artist_name,
|
||||||
|
monitored: !disable_monitoring,
|
||||||
|
root_folder_path,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
tags,
|
||||||
|
tag_input_string: None,
|
||||||
|
add_options: AddArtistOptions {
|
||||||
|
monitor,
|
||||||
|
monitor_new_items,
|
||||||
|
search_for_missing_albums: !no_search_for_missing_albums,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::AddArtist(body).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrAddCommand::Tag { name } => {
|
LidarrAddCommand::Tag { name } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ mod tests {
|
|||||||
Command,
|
Command,
|
||||||
lidarr::{LidarrCommand, add_command_handler::LidarrAddCommand},
|
lidarr::{LidarrCommand, add_command_handler::LidarrAddCommand},
|
||||||
},
|
},
|
||||||
|
models::lidarr_models::{MonitorType, NewItemMonitorType},
|
||||||
};
|
};
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
@@ -52,6 +53,321 @@ mod tests {
|
|||||||
};
|
};
|
||||||
assert_eq!(add_command, expected_args);
|
assert_eq!(add_command, expected_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "add", "artist"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_foreign_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--artist-name",
|
||||||
|
"Test",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_artist_name() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_root_folder_path() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_quality_profile_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_requires_metadata_profile_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_success_with_required_args_only() {
|
||||||
|
let expected_args = LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
disable_monitoring: false,
|
||||||
|
tag: vec![],
|
||||||
|
monitor: MonitorType::default(),
|
||||||
|
monitor_new_items: NewItemMonitorType::default(),
|
||||||
|
no_search_for_missing_albums: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type")
|
||||||
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_success_with_all_args() {
|
||||||
|
let expected_args = LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 2,
|
||||||
|
disable_monitoring: true,
|
||||||
|
tag: vec![1, 2],
|
||||||
|
monitor: MonitorType::Future,
|
||||||
|
monitor_new_items: NewItemMonitorType::New,
|
||||||
|
no_search_for_missing_albums: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"2",
|
||||||
|
"--disable-monitoring",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"2",
|
||||||
|
"--monitor",
|
||||||
|
"future",
|
||||||
|
"--monitor-new-items",
|
||||||
|
"new",
|
||||||
|
"--no-search-for-missing-albums",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type")
|
||||||
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_monitor_type_validation() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"2",
|
||||||
|
"--monitor",
|
||||||
|
"test",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_new_item_monitor_type_validation() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"2",
|
||||||
|
"--monitor-new-items",
|
||||||
|
"test",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_tags_is_repeatable() {
|
||||||
|
let expected_args = LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 2,
|
||||||
|
disable_monitoring: false,
|
||||||
|
tag: vec![1, 2],
|
||||||
|
monitor: MonitorType::default(),
|
||||||
|
monitor_new_items: NewItemMonitorType::default(),
|
||||||
|
no_search_for_missing_albums: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"add",
|
||||||
|
"artist",
|
||||||
|
"--foreign-artist-id",
|
||||||
|
"test-id",
|
||||||
|
"--artist-name",
|
||||||
|
"Test Artist",
|
||||||
|
"--root-folder-path",
|
||||||
|
"/music",
|
||||||
|
"--quality-profile-id",
|
||||||
|
"1",
|
||||||
|
"--metadata-profile-id",
|
||||||
|
"2",
|
||||||
|
"--tag",
|
||||||
|
"1",
|
||||||
|
"--tag",
|
||||||
|
"2",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Add(add_command))) = result.unwrap().command else {
|
||||||
|
panic!("Unexpected command type")
|
||||||
|
};
|
||||||
|
assert_eq!(add_command, expected_args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod handler {
|
mod handler {
|
||||||
@@ -64,7 +380,9 @@ mod tests {
|
|||||||
use crate::cli::CliCommandHandler;
|
use crate::cli::CliCommandHandler;
|
||||||
use crate::cli::lidarr::add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
|
use crate::cli::lidarr::add_command_handler::{LidarrAddCommand, LidarrAddCommandHandler};
|
||||||
use crate::models::Serdeable;
|
use crate::models::Serdeable;
|
||||||
use crate::models::lidarr_models::LidarrSerdeable;
|
use crate::models::lidarr_models::{
|
||||||
|
AddArtistBody, AddArtistOptions, LidarrSerdeable, MonitorType, NewItemMonitorType,
|
||||||
|
};
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
@@ -97,5 +415,55 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_add_artist_command() {
|
||||||
|
let expected_body = AddArtistBody {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
monitored: false,
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
tags: vec![1, 2],
|
||||||
|
tag_input_string: None,
|
||||||
|
add_options: AddArtistOptions {
|
||||||
|
monitor: MonitorType::All,
|
||||||
|
monitor_new_items: NewItemMonitorType::All,
|
||||||
|
search_for_missing_albums: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::AddArtist(expected_body).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let add_artist_command = LidarrAddCommand::Artist {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
disable_monitoring: true,
|
||||||
|
tag: vec![1, 2],
|
||||||
|
monitor: MonitorType::All,
|
||||||
|
monitor_new_items: NewItemMonitorType::All,
|
||||||
|
no_search_for_missing_albums: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = LidarrAddCommandHandler::with(&app_arc, add_artist_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use tokio::sync::Mutex;
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{CliCommandHandler, Command},
|
cli::{CliCommandHandler, Command},
|
||||||
models::lidarr_models::DeleteArtistParams,
|
models::lidarr_models::DeleteParams,
|
||||||
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,6 +19,15 @@ mod delete_command_handler_tests;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
pub enum LidarrDeleteCommand {
|
pub enum LidarrDeleteCommand {
|
||||||
|
#[command(about = "Delete an album from your Lidarr library")]
|
||||||
|
Album {
|
||||||
|
#[arg(long, help = "The ID of the album to delete", required = true)]
|
||||||
|
album_id: i64,
|
||||||
|
#[arg(long, help = "Delete the album files from disk as well")]
|
||||||
|
delete_files_from_disk: bool,
|
||||||
|
#[arg(long, help = "Add a list exclusion for this album")]
|
||||||
|
add_list_exclusion: bool,
|
||||||
|
},
|
||||||
#[command(about = "Delete an artist from your Lidarr library")]
|
#[command(about = "Delete an artist from your Lidarr library")]
|
||||||
Artist {
|
Artist {
|
||||||
#[arg(long, help = "The ID of the artist to delete", required = true)]
|
#[arg(long, help = "The ID of the artist to delete", required = true)]
|
||||||
@@ -62,12 +71,28 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrDeleteCommand> for LidarrDeleteComm
|
|||||||
|
|
||||||
async fn handle(self) -> Result<String> {
|
async fn handle(self) -> Result<String> {
|
||||||
let result = match self.command {
|
let result = match self.command {
|
||||||
|
LidarrDeleteCommand::Album {
|
||||||
|
album_id,
|
||||||
|
delete_files_from_disk,
|
||||||
|
add_list_exclusion,
|
||||||
|
} => {
|
||||||
|
let delete_album_params = DeleteParams {
|
||||||
|
id: album_id,
|
||||||
|
delete_files: delete_files_from_disk,
|
||||||
|
add_import_list_exclusion: add_list_exclusion,
|
||||||
|
};
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::DeleteAlbum(delete_album_params).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrDeleteCommand::Artist {
|
LidarrDeleteCommand::Artist {
|
||||||
artist_id,
|
artist_id,
|
||||||
delete_files_from_disk,
|
delete_files_from_disk,
|
||||||
add_list_exclusion,
|
add_list_exclusion,
|
||||||
} => {
|
} => {
|
||||||
let delete_artist_params = DeleteArtistParams {
|
let delete_artist_params = DeleteParams {
|
||||||
id: artist_id,
|
id: artist_id,
|
||||||
delete_files: delete_files_from_disk,
|
delete_files: delete_files_from_disk,
|
||||||
add_import_list_exclusion: add_list_exclusion,
|
add_import_list_exclusion: add_list_exclusion,
|
||||||
|
|||||||
@@ -27,6 +27,65 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_requires_arguments() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "album"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_defaults() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Album {
|
||||||
|
album_id: 1,
|
||||||
|
delete_files_from_disk: false,
|
||||||
|
add_list_exclusion: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
Cli::try_parse_from(["managarr", "lidarr", "delete", "album", "--album-id", "1"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_all_args_defined() {
|
||||||
|
let expected_args = LidarrDeleteCommand::Album {
|
||||||
|
album_id: 1,
|
||||||
|
delete_files_from_disk: true,
|
||||||
|
add_list_exclusion: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"delete",
|
||||||
|
"album",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
"--delete-files-from-disk",
|
||||||
|
"--add-list-exclusion",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Delete(delete_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(delete_command, expected_args);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delete_artist_requires_arguments() {
|
fn test_delete_artist_requires_arguments() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "artist"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "delete", "artist"]);
|
||||||
@@ -128,14 +187,48 @@ mod tests {
|
|||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
Serdeable,
|
Serdeable,
|
||||||
lidarr_models::{DeleteArtistParams, LidarrSerdeable},
|
lidarr_models::{DeleteParams, LidarrSerdeable},
|
||||||
},
|
},
|
||||||
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_album_command() {
|
||||||
|
let expected_delete_album_params = DeleteParams {
|
||||||
|
id: 1,
|
||||||
|
delete_files: true,
|
||||||
|
add_import_list_exclusion: true,
|
||||||
|
};
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::DeleteAlbum(expected_delete_album_params).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let delete_album_command = LidarrDeleteCommand::Album {
|
||||||
|
album_id: 1,
|
||||||
|
delete_files_from_disk: true,
|
||||||
|
add_list_exclusion: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrDeleteCommandHandler::with(&app_arc, delete_album_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_delete_artist_command() {
|
async fn test_handle_delete_artist_command() {
|
||||||
let expected_delete_artist_params = DeleteArtistParams {
|
let expected_delete_artist_params = DeleteParams {
|
||||||
id: 1,
|
id: 1,
|
||||||
delete_files: true,
|
delete_files: true,
|
||||||
add_import_list_exclusion: true,
|
add_import_list_exclusion: true,
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ mod get_command_handler_tests;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
pub enum LidarrGetCommand {
|
pub enum LidarrGetCommand {
|
||||||
|
#[command(about = "Get detailed information for the album with the given ID")]
|
||||||
|
AlbumDetails {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the album whose details you wish to fetch",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
album_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "Get detailed information for the artist with the given ID")]
|
#[command(about = "Get detailed information for the artist with the given ID")]
|
||||||
ArtistDetails {
|
ArtistDetails {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -62,6 +71,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrGetCommand> for LidarrGetCommandHan
|
|||||||
|
|
||||||
async fn handle(self) -> Result<String> {
|
async fn handle(self) -> Result<String> {
|
||||||
let result = match self.command {
|
let result = match self.command {
|
||||||
|
LidarrGetCommand::AlbumDetails { album_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetAlbumDetails(album_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrGetCommand::ArtistDetails { artist_id } => {
|
LidarrGetCommand::ArtistDetails { artist_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -23,6 +23,32 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_details_requires_album_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "get", "album-details"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_details_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"get",
|
||||||
|
"album-details",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_artist_details_requires_artist_id() {
|
fn test_artist_details_requires_artist_id() {
|
||||||
let result =
|
let result =
|
||||||
@@ -91,6 +117,32 @@ mod tests {
|
|||||||
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_album_details_command() {
|
||||||
|
let expected_album_id = 1;
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::GetAlbumDetails(expected_album_id).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let get_album_details_command = LidarrGetCommand::AlbumDetails { album_id: 1 };
|
||||||
|
|
||||||
|
let result =
|
||||||
|
LidarrGetCommandHandler::with(&app_arc, get_album_details_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_get_artist_details_command() {
|
async fn test_handle_get_artist_details_command() {
|
||||||
let expected_artist_id = 1;
|
let expected_artist_id = 1;
|
||||||
|
|||||||
@@ -75,6 +75,31 @@ mod tests {
|
|||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toggle_album_monitoring_requires_album_id() {
|
||||||
|
let result =
|
||||||
|
Cli::command().try_get_matches_from(["managarr", "lidarr", "toggle-album-monitoring"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_toggle_album_monitoring_requirements_satisfied() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"toggle-album-monitoring",
|
||||||
|
"--album-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_search_new_artist_requires_query() {
|
fn test_search_new_artist_requires_query() {
|
||||||
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "search-new-artist"]);
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "search-new-artist"]);
|
||||||
@@ -110,6 +135,7 @@ mod tests {
|
|||||||
use crate::cli::lidarr::add_command_handler::LidarrAddCommand;
|
use crate::cli::lidarr::add_command_handler::LidarrAddCommand;
|
||||||
use crate::cli::lidarr::get_command_handler::LidarrGetCommand;
|
use crate::cli::lidarr::get_command_handler::LidarrGetCommand;
|
||||||
use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand;
|
use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand;
|
||||||
|
use crate::cli::lidarr::trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
cli::{
|
cli::{
|
||||||
@@ -121,7 +147,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
models::{
|
models::{
|
||||||
Serdeable,
|
Serdeable,
|
||||||
lidarr_models::{Artist, DeleteArtistParams, LidarrSerdeable},
|
lidarr_models::{Artist, DeleteParams, LidarrSerdeable},
|
||||||
},
|
},
|
||||||
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
};
|
};
|
||||||
@@ -177,7 +203,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_lidarr_cli_handler_delegates_delete_commands_to_the_delete_command_handler() {
|
async fn test_lidarr_cli_handler_delegates_delete_commands_to_the_delete_command_handler() {
|
||||||
let expected_delete_artist_params = DeleteArtistParams {
|
let expected_delete_artist_params = DeleteParams {
|
||||||
id: 1,
|
id: 1,
|
||||||
delete_files: true,
|
delete_files: true,
|
||||||
add_import_list_exclusion: true,
|
add_import_list_exclusion: true,
|
||||||
@@ -306,5 +332,37 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_lidarr_cli_handler_delegates_trigger_automatic_search_commands_to_the_trigger_automatic_search_command_handler()
|
||||||
|
{
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::TriggerAutomaticArtistSearch(1).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let trigger_automatic_search_command =
|
||||||
|
LidarrCommand::TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand::Artist {
|
||||||
|
artist_id: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = LidarrCliHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
trigger_automatic_search_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Subcommand;
|
use clap::{Subcommand, arg};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -18,6 +18,15 @@ mod list_command_handler_tests;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
pub enum LidarrListCommand {
|
pub enum LidarrListCommand {
|
||||||
|
#[command(about = "List all albums for the artist with the given ID")]
|
||||||
|
Albums {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the artist whose albums you want to list",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
},
|
||||||
#[command(about = "List all artists in your Lidarr library")]
|
#[command(about = "List all artists in your Lidarr library")]
|
||||||
Artists,
|
Artists,
|
||||||
#[command(about = "List all Lidarr metadata profiles")]
|
#[command(about = "List all Lidarr metadata profiles")]
|
||||||
@@ -55,6 +64,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrListCommand> for LidarrListCommandH
|
|||||||
|
|
||||||
async fn handle(self) -> Result<String> {
|
async fn handle(self) -> Result<String> {
|
||||||
let result = match self.command {
|
let result = match self.command {
|
||||||
|
LidarrListCommand::Albums { artist_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::GetAlbums(artist_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrListCommand::Artists => {
|
LidarrListCommand::Artists => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ mod tests {
|
|||||||
|
|
||||||
mod cli {
|
mod cli {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use clap::{Parser, error::ErrorKind};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
@@ -29,6 +31,32 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_albums_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "list", "albums"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_albums_with_artist_id() {
|
||||||
|
let expected_args = LidarrListCommand::Albums { artist_id: 1 };
|
||||||
|
let result =
|
||||||
|
Cli::try_parse_from(["managarr", "lidarr", "list", "albums", "--artist-id", "1"]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::List(album_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(album_command, expected_args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod handler {
|
mod handler {
|
||||||
@@ -77,5 +105,27 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_list_albums_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(LidarrEvent::GetAlbums(1).into()))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let list_command = LidarrListCommand::Albums { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrListCommandHandler::with(&app_arc, list_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ use get_command_handler::{LidarrGetCommand, LidarrGetCommandHandler};
|
|||||||
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
use list_command_handler::{LidarrListCommand, LidarrListCommandHandler};
|
||||||
use refresh_command_handler::{LidarrRefreshCommand, LidarrRefreshCommandHandler};
|
use refresh_command_handler::{LidarrRefreshCommand, LidarrRefreshCommandHandler};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
use trigger_automatic_search_command_handler::{
|
||||||
|
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{app::App, network::NetworkTrait};
|
use crate::{app::App, network::NetworkTrait};
|
||||||
@@ -21,6 +24,7 @@ mod edit_command_handler;
|
|||||||
mod get_command_handler;
|
mod get_command_handler;
|
||||||
mod list_command_handler;
|
mod list_command_handler;
|
||||||
mod refresh_command_handler;
|
mod refresh_command_handler;
|
||||||
|
mod trigger_automatic_search_command_handler;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "lidarr_command_tests.rs"]
|
#[path = "lidarr_command_tests.rs"]
|
||||||
@@ -58,6 +62,11 @@ pub enum LidarrCommand {
|
|||||||
about = "Commands to refresh the data in your Lidarr instance"
|
about = "Commands to refresh the data in your Lidarr instance"
|
||||||
)]
|
)]
|
||||||
Refresh(LidarrRefreshCommand),
|
Refresh(LidarrRefreshCommand),
|
||||||
|
#[command(
|
||||||
|
subcommand,
|
||||||
|
about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance"
|
||||||
|
)]
|
||||||
|
TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand),
|
||||||
#[command(about = "Search for a new artist to add to Lidarr")]
|
#[command(about = "Search for a new artist to add to Lidarr")]
|
||||||
SearchNewArtist {
|
SearchNewArtist {
|
||||||
#[arg(
|
#[arg(
|
||||||
@@ -67,6 +76,17 @@ pub enum LidarrCommand {
|
|||||||
)]
|
)]
|
||||||
query: String,
|
query: String,
|
||||||
},
|
},
|
||||||
|
#[command(
|
||||||
|
about = "Toggle monitoring for the specified album corresponding to the given album ID"
|
||||||
|
)]
|
||||||
|
ToggleAlbumMonitoring {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The Lidarr ID of the album to toggle monitoring on",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
album_id: i64,
|
||||||
|
},
|
||||||
#[command(
|
#[command(
|
||||||
about = "Toggle monitoring for the specified artist corresponding to the given artist ID"
|
about = "Toggle monitoring for the specified artist corresponding to the given artist ID"
|
||||||
)]
|
)]
|
||||||
@@ -137,6 +157,15 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
|||||||
.handle()
|
.handle()
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
LidarrCommand::TriggerAutomaticSearch(trigger_automatic_search_command) => {
|
||||||
|
LidarrTriggerAutomaticSearchCommandHandler::with(
|
||||||
|
self.app,
|
||||||
|
trigger_automatic_search_command,
|
||||||
|
self.network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await?
|
||||||
|
}
|
||||||
LidarrCommand::SearchNewArtist { query } => {
|
LidarrCommand::SearchNewArtist { query } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
@@ -144,6 +173,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, '
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrCommand::ToggleAlbumMonitoring { album_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::ToggleAlbumMonitoring(album_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
LidarrCommand::ToggleArtistMonitoring { artist_id } => {
|
LidarrCommand::ToggleArtistMonitoring { artist_id } => {
|
||||||
let resp = self
|
let resp = self
|
||||||
.network
|
.network
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ mod refresh_command_handler_tests;
|
|||||||
pub enum LidarrRefreshCommand {
|
pub enum LidarrRefreshCommand {
|
||||||
#[command(about = "Refresh all artist data for all artists in your Lidarr library")]
|
#[command(about = "Refresh all artist data for all artists in your Lidarr library")]
|
||||||
AllArtists,
|
AllArtists,
|
||||||
|
#[command(about = "Refresh artist data and scan disk for the artist with the given ID")]
|
||||||
|
Artist {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the artist to refresh information on and to scan the disk for",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<LidarrRefreshCommand> for Command {
|
impl From<LidarrRefreshCommand> for Command {
|
||||||
@@ -57,6 +66,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrRefreshCommand>
|
|||||||
.await?;
|
.await?;
|
||||||
serde_json::to_string_pretty(&resp)?
|
serde_json::to_string_pretty(&resp)?
|
||||||
}
|
}
|
||||||
|
LidarrRefreshCommand::Artist { artist_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::UpdateAndScanArtist(artist_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ mod tests {
|
|||||||
|
|
||||||
mod cli {
|
mod cli {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use clap::{Parser, error::ErrorKind};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_refresh_all_artists_has_no_arg_requirements() {
|
fn test_refresh_all_artists_has_no_arg_requirements() {
|
||||||
@@ -28,6 +30,37 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_artist_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from(["managarr", "lidarr", "refresh", "artist"]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_refresh_artist_with_artist_id() {
|
||||||
|
let expected_args = LidarrRefreshCommand::Artist { artist_id: 1 };
|
||||||
|
let result = Cli::try_parse_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"refresh",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
let Some(Command::Lidarr(LidarrCommand::Refresh(refresh_command))) = result.unwrap().command
|
||||||
|
else {
|
||||||
|
panic!("Unexpected command type");
|
||||||
|
};
|
||||||
|
assert_eq!(refresh_command, expected_args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod handler {
|
mod handler {
|
||||||
@@ -68,5 +101,29 @@ mod tests {
|
|||||||
|
|
||||||
assert_ok!(&result);
|
assert_ok!(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_refresh_artist_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::UpdateAndScanArtist(1).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let refresh_command = LidarrRefreshCommand::Artist { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrRefreshCommandHandler::with(&app_arc, refresh_command, &mut mock_network)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Subcommand;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
cli::{CliCommandHandler, Command},
|
||||||
|
network::{NetworkTrait, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::LidarrCommand;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "trigger_automatic_search_command_handler_tests.rs"]
|
||||||
|
mod trigger_automatic_search_command_handler_tests;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Subcommand)]
|
||||||
|
pub enum LidarrTriggerAutomaticSearchCommand {
|
||||||
|
#[command(about = "Trigger an automatic search for the artist with the specified ID")]
|
||||||
|
Artist {
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
help = "The ID of the artist you want to trigger an automatic search for",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
artist_id: i64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LidarrTriggerAutomaticSearchCommand> for Command {
|
||||||
|
fn from(value: LidarrTriggerAutomaticSearchCommand) -> Self {
|
||||||
|
Command::Lidarr(LidarrCommand::TriggerAutomaticSearch(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct LidarrTriggerAutomaticSearchCommandHandler<'a, 'b> {
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrTriggerAutomaticSearchCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrTriggerAutomaticSearchCommand>
|
||||||
|
for LidarrTriggerAutomaticSearchCommandHandler<'a, 'b>
|
||||||
|
{
|
||||||
|
fn with(
|
||||||
|
_app: &'a Arc<Mutex<App<'b>>>,
|
||||||
|
command: LidarrTriggerAutomaticSearchCommand,
|
||||||
|
network: &'a mut dyn NetworkTrait,
|
||||||
|
) -> Self {
|
||||||
|
LidarrTriggerAutomaticSearchCommandHandler {
|
||||||
|
_app,
|
||||||
|
command,
|
||||||
|
network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle(self) -> Result<String> {
|
||||||
|
let result = match self.command {
|
||||||
|
LidarrTriggerAutomaticSearchCommand::Artist { artist_id } => {
|
||||||
|
let resp = self
|
||||||
|
.network
|
||||||
|
.handle_network_event(LidarrEvent::TriggerAutomaticArtistSearch(artist_id).into())
|
||||||
|
.await?;
|
||||||
|
serde_json::to_string_pretty(&resp)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use crate::Cli;
|
||||||
|
use crate::cli::{
|
||||||
|
Command,
|
||||||
|
lidarr::{
|
||||||
|
LidarrCommand, trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use clap::CommandFactory;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_trigger_automatic_search_command_from() {
|
||||||
|
let command = LidarrTriggerAutomaticSearchCommand::Artist { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = Command::from(command.clone());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
Command::Lidarr(LidarrCommand::TriggerAutomaticSearch(command))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod cli {
|
||||||
|
use super::*;
|
||||||
|
use clap::error::ErrorKind;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trigger_automatic_artist_search_requires_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"trigger-automatic-search",
|
||||||
|
"artist",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_err!(&result);
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().kind(),
|
||||||
|
ErrorKind::MissingRequiredArgument
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_trigger_automatic_artist_search_with_artist_id() {
|
||||||
|
let result = Cli::command().try_get_matches_from([
|
||||||
|
"managarr",
|
||||||
|
"lidarr",
|
||||||
|
"trigger-automatic-search",
|
||||||
|
"artist",
|
||||||
|
"--artist-id",
|
||||||
|
"1",
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod handler {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use mockall::predicate::eq;
|
||||||
|
use serde_json::json;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::cli::lidarr::trigger_automatic_search_command_handler::{
|
||||||
|
LidarrTriggerAutomaticSearchCommand, LidarrTriggerAutomaticSearchCommandHandler,
|
||||||
|
};
|
||||||
|
use crate::{app::App, cli::CliCommandHandler};
|
||||||
|
use crate::{
|
||||||
|
models::{Serdeable, lidarr_models::LidarrSerdeable},
|
||||||
|
network::{MockNetworkTrait, NetworkEvent, lidarr_network::LidarrEvent},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_trigger_automatic_artist_search_command() {
|
||||||
|
let mut mock_network = MockNetworkTrait::new();
|
||||||
|
mock_network
|
||||||
|
.expect_handle_network_event()
|
||||||
|
.with(eq::<NetworkEvent>(
|
||||||
|
LidarrEvent::TriggerAutomaticArtistSearch(1).into(),
|
||||||
|
))
|
||||||
|
.times(1)
|
||||||
|
.returning(|_| {
|
||||||
|
Ok(Serdeable::Lidarr(LidarrSerdeable::Value(
|
||||||
|
json!({"testResponse": "response"}),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::test_default()));
|
||||||
|
let trigger_automatic_search_command =
|
||||||
|
LidarrTriggerAutomaticSearchCommand::Artist { artist_id: 1 };
|
||||||
|
|
||||||
|
let result = LidarrTriggerAutomaticSearchCommandHandler::with(
|
||||||
|
&app_arc,
|
||||||
|
trigger_automatic_search_command,
|
||||||
|
&mut mock_network,
|
||||||
|
)
|
||||||
|
.handle()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_ok!(&result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,12 +122,12 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrAddCommand> for RadarrAddCommandHan
|
|||||||
title: String::new(),
|
title: String::new(),
|
||||||
root_folder_path,
|
root_folder_path,
|
||||||
quality_profile_id,
|
quality_profile_id,
|
||||||
minimum_availability: minimum_availability.to_string(),
|
minimum_availability,
|
||||||
monitored: !disable_monitoring,
|
monitored: !disable_monitoring,
|
||||||
tags,
|
tags,
|
||||||
tag_input_string: None,
|
tag_input_string: None,
|
||||||
add_options: AddMovieOptions {
|
add_options: AddMovieOptions {
|
||||||
monitor: monitor.to_string(),
|
monitor,
|
||||||
search_for_movie: !no_search_for_movie,
|
search_for_movie: !no_search_for_movie,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -384,12 +384,12 @@ mod tests {
|
|||||||
title: String::new(),
|
title: String::new(),
|
||||||
root_folder_path: "/test".to_owned(),
|
root_folder_path: "/test".to_owned(),
|
||||||
quality_profile_id: 1,
|
quality_profile_id: 1,
|
||||||
minimum_availability: "released".to_owned(),
|
minimum_availability: MinimumAvailability::Released,
|
||||||
monitored: false,
|
monitored: false,
|
||||||
tags: vec![1, 2],
|
tags: vec![1, 2],
|
||||||
tag_input_string: None,
|
tag_input_string: None,
|
||||||
add_options: AddMovieOptions {
|
add_options: AddMovieOptions {
|
||||||
monitor: "movieAndCollection".to_owned(),
|
monitor: MovieMonitor::MovieAndCollection,
|
||||||
search_for_movie: false,
|
search_for_movie: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -137,12 +137,12 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrAddCommand> for SonarrAddCommandHan
|
|||||||
root_folder_path,
|
root_folder_path,
|
||||||
quality_profile_id,
|
quality_profile_id,
|
||||||
language_profile_id,
|
language_profile_id,
|
||||||
series_type: series_type.to_string(),
|
series_type,
|
||||||
season_folder: !disable_season_folders,
|
season_folder: !disable_season_folders,
|
||||||
tags,
|
tags,
|
||||||
tag_input_string: None,
|
tag_input_string: None,
|
||||||
add_options: AddSeriesOptions {
|
add_options: AddSeriesOptions {
|
||||||
monitor: monitor.to_string(),
|
monitor,
|
||||||
search_for_cutoff_unmet_episodes: !no_search_for_series,
|
search_for_cutoff_unmet_episodes: !no_search_for_series,
|
||||||
search_for_missing_episodes: !no_search_for_series,
|
search_for_missing_episodes: !no_search_for_series,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -517,13 +517,13 @@ mod tests {
|
|||||||
root_folder_path: "/test".to_owned(),
|
root_folder_path: "/test".to_owned(),
|
||||||
quality_profile_id: 1,
|
quality_profile_id: 1,
|
||||||
language_profile_id: 1,
|
language_profile_id: 1,
|
||||||
series_type: "anime".to_owned(),
|
series_type: SeriesType::Anime,
|
||||||
monitored: false,
|
monitored: false,
|
||||||
tags: vec![1, 2],
|
tags: vec![1, 2],
|
||||||
tag_input_string: None,
|
tag_input_string: None,
|
||||||
season_folder: false,
|
season_folder: false,
|
||||||
add_options: AddSeriesOptions {
|
add_options: AddSeriesOptions {
|
||||||
monitor: "future".to_owned(),
|
monitor: SeriesMonitor::Future,
|
||||||
search_for_cutoff_unmet_episodes: false,
|
search_for_cutoff_unmet_episodes: false,
|
||||||
search_for_missing_episodes: false,
|
search_for_missing_episodes: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
use crate::handlers::KeyEventHandler;
|
|
||||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
use crate::models::Route;
|
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{ADD_ARTIST_BLOCKS, ActiveLidarrBlock};
|
use crate::models::lidarr_models::{AddArtistBody, AddArtistOptions, AddArtistSearchResult};
|
||||||
use crate::{App, Key, handle_text_box_keys, handle_text_box_left_right_keys};
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ActiveLidarrBlock,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_data::lidarr::modals::AddArtistModal;
|
||||||
|
use crate::models::{BlockSelectionState, Route, Scrollable};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::{App, Key, handle_text_box_keys, handle_text_box_left_right_keys, matches_key};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "add_artist_handler_tests.rs"]
|
#[path = "add_artist_handler_tests.rs"]
|
||||||
@@ -15,6 +20,84 @@ pub struct AddArtistHandler<'a, 'b> {
|
|||||||
_context: Option<ActiveLidarrBlock>,
|
_context: Option<ActiveLidarrBlock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AddArtistHandler<'_, '_> {
|
||||||
|
fn build_add_artist_body(&mut self) -> AddArtistBody {
|
||||||
|
let add_artist_modal = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.take()
|
||||||
|
.expect("AddArtistModal is None");
|
||||||
|
let tags = add_artist_modal.tags.text;
|
||||||
|
let AddArtistModal {
|
||||||
|
root_folder_list,
|
||||||
|
monitor_list,
|
||||||
|
monitor_new_items_list,
|
||||||
|
quality_profile_list,
|
||||||
|
metadata_profile_list,
|
||||||
|
..
|
||||||
|
} = add_artist_modal;
|
||||||
|
let (foreign_artist_id, artist_name) = {
|
||||||
|
let AddArtistSearchResult {
|
||||||
|
foreign_artist_id,
|
||||||
|
artist_name,
|
||||||
|
..
|
||||||
|
} = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_searched_artists
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.current_selection();
|
||||||
|
(foreign_artist_id.clone(), artist_name.text.clone())
|
||||||
|
};
|
||||||
|
let quality_profile = quality_profile_list.current_selection();
|
||||||
|
let quality_profile_id = *self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.quality_profile_map
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, value)| *value == quality_profile)
|
||||||
|
.map(|(key, _)| key)
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
let metadata_profile = metadata_profile_list.current_selection();
|
||||||
|
let metadata_profile_id = *self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.metadata_profile_map
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, value)| *value == metadata_profile)
|
||||||
|
.map(|(key, _)| key)
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let path = root_folder_list.current_selection().path.clone();
|
||||||
|
let monitor = *monitor_list.current_selection();
|
||||||
|
let monitor_new_items = *monitor_new_items_list.current_selection();
|
||||||
|
|
||||||
|
AddArtistBody {
|
||||||
|
foreign_artist_id,
|
||||||
|
artist_name,
|
||||||
|
monitored: true,
|
||||||
|
root_folder_path: path,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
tags: Vec::new(),
|
||||||
|
tag_input_string: Some(tags),
|
||||||
|
add_options: AddArtistOptions {
|
||||||
|
monitor,
|
||||||
|
monitor_new_items,
|
||||||
|
search_for_missing_albums: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddArtistHandler<'a, 'b> {
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddArtistHandler<'a, 'b> {
|
||||||
fn handle(&mut self) {
|
fn handle(&mut self) {
|
||||||
let add_artist_table_handling_config =
|
let add_artist_table_handling_config =
|
||||||
@@ -66,40 +149,252 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddArtistHandler<'a,
|
|||||||
!self.app.is_loading
|
!self.app.is_loading
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_scroll_up(&mut self) {}
|
fn handle_scroll_up(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitor => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_list
|
||||||
|
.scroll_up(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitorNewItems => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_new_items_list
|
||||||
|
.scroll_up(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectQualityProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.quality_profile_list
|
||||||
|
.scroll_up(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMetadataProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.metadata_profile_list
|
||||||
|
.scroll_up(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectRootFolder => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.root_folder_list
|
||||||
|
.scroll_up(),
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt => self.app.data.lidarr_data.selected_block.up(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_scroll_down(&mut self) {}
|
fn handle_scroll_down(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitor => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_list
|
||||||
|
.scroll_down(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitorNewItems => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_new_items_list
|
||||||
|
.scroll_down(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectQualityProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.quality_profile_list
|
||||||
|
.scroll_down(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMetadataProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.metadata_profile_list
|
||||||
|
.scroll_down(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectRootFolder => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.root_folder_list
|
||||||
|
.scroll_down(),
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt => self.app.data.lidarr_data.selected_block.down(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_home(&mut self) {
|
fn handle_home(&mut self) {
|
||||||
if self.active_lidarr_block == ActiveLidarrBlock::AddArtistSearchInput {
|
match self.active_lidarr_block {
|
||||||
self
|
ActiveLidarrBlock::AddArtistSelectMonitor => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_list
|
||||||
|
.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitorNewItems => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_new_items_list
|
||||||
|
.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectQualityProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.quality_profile_list
|
||||||
|
.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMetadataProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.metadata_profile_list
|
||||||
|
.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectRootFolder => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.root_folder_list
|
||||||
|
.scroll_to_top(),
|
||||||
|
ActiveLidarrBlock::AddArtistSearchInput => self
|
||||||
.app
|
.app
|
||||||
.data
|
.data
|
||||||
.lidarr_data
|
.lidarr_data
|
||||||
.add_artist_search
|
.add_artist_search
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.scroll_home();
|
.scroll_home(),
|
||||||
|
ActiveLidarrBlock::AddArtistTagsInput => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.tags
|
||||||
|
.scroll_home(),
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_end(&mut self) {
|
fn handle_end(&mut self) {
|
||||||
if self.active_lidarr_block == ActiveLidarrBlock::AddArtistSearchInput {
|
match self.active_lidarr_block {
|
||||||
self
|
ActiveLidarrBlock::AddArtistSelectMonitor => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_list
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitorNewItems => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.monitor_new_items_list
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectQualityProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.quality_profile_list
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMetadataProfile => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.metadata_profile_list
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::AddArtistSelectRootFolder => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.root_folder_list
|
||||||
|
.scroll_to_bottom(),
|
||||||
|
ActiveLidarrBlock::AddArtistSearchInput => self
|
||||||
.app
|
.app
|
||||||
.data
|
.data
|
||||||
.lidarr_data
|
.lidarr_data
|
||||||
.add_artist_search
|
.add_artist_search
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.reset_offset();
|
.reset_offset(),
|
||||||
|
ActiveLidarrBlock::AddArtistTagsInput => self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.tags
|
||||||
|
.reset_offset(),
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_delete(&mut self) {}
|
fn handle_delete(&mut self) {}
|
||||||
|
|
||||||
fn handle_left_right_action(&mut self) {
|
fn handle_left_right_action(&mut self) {
|
||||||
if self.active_lidarr_block == ActiveLidarrBlock::AddArtistSearchInput {
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt => handle_prompt_toggle(self.app, self.key),
|
||||||
|
ActiveLidarrBlock::AddArtistSearchInput => {
|
||||||
handle_text_box_left_right_keys!(
|
handle_text_box_left_right_keys!(
|
||||||
self,
|
self,
|
||||||
self.key,
|
self.key,
|
||||||
@@ -112,28 +407,124 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddArtistHandler<'a,
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistTagsInput => {
|
||||||
|
handle_text_box_left_right_keys!(
|
||||||
|
self,
|
||||||
|
self.key,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.tags
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_submit(&mut self) {
|
fn handle_submit(&mut self) {
|
||||||
match self.active_lidarr_block {
|
match self.active_lidarr_block {
|
||||||
ActiveLidarrBlock::AddArtistSearchInput => {
|
ActiveLidarrBlock::AddArtistSearchInput
|
||||||
let search_text = &self
|
if !self
|
||||||
.app
|
.app
|
||||||
.data
|
.data
|
||||||
.lidarr_data
|
.lidarr_data
|
||||||
.add_artist_search
|
.add_artist_search
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.text;
|
.text
|
||||||
|
.is_empty() =>
|
||||||
if !search_text.is_empty() {
|
{
|
||||||
self
|
self
|
||||||
.app
|
.app
|
||||||
.push_navigation_stack(ActiveLidarrBlock::AddArtistSearchResults.into());
|
.push_navigation_stack(ActiveLidarrBlock::AddArtistSearchResults.into());
|
||||||
self.app.ignore_special_keys_for_textbox_input = false;
|
self.app.ignore_special_keys_for_textbox_input = false;
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistSearchResults
|
||||||
|
if self.app.data.lidarr_data.add_searched_artists.is_some() =>
|
||||||
|
{
|
||||||
|
let foreign_artist_id = self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_searched_artists
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.current_selection()
|
||||||
|
.foreign_artist_id
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
if self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artists
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.any(|artist| artist.foreign_artist_id == foreign_artist_id)
|
||||||
|
{
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::AddArtistAlreadyInLibrary.into());
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::AddArtistPrompt.into());
|
||||||
|
self.app.data.lidarr_data.add_artist_modal = Some((&self.app.data.lidarr_data).into());
|
||||||
|
self.app.data.lidarr_data.selected_block =
|
||||||
|
BlockSelectionState::new(ADD_ARTIST_SELECTION_BLOCKS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt => {
|
||||||
|
match self.app.data.lidarr_data.selected_block.get_active_block() {
|
||||||
|
ActiveLidarrBlock::AddArtistConfirmPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::AddArtist(self.build_add_artist_body()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectRootFolder => self.app.push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.selected_block
|
||||||
|
.get_active_block()
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
ActiveLidarrBlock::AddArtistTagsInput => {
|
||||||
|
self.app.push_navigation_stack(
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.selected_block
|
||||||
|
.get_active_block()
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
self.app.ignore_special_keys_for_textbox_input = true;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectRootFolder => self.app.pop_navigation_stack(),
|
||||||
|
ActiveLidarrBlock::AddArtistTagsInput => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.ignore_special_keys_for_textbox_input = false;
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::AddArtistSearchResults => {}
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,15 +542,32 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddArtistHandler<'a,
|
|||||||
self.app.data.lidarr_data.add_searched_artists = None;
|
self.app.data.lidarr_data.add_searched_artists = None;
|
||||||
self.app.ignore_special_keys_for_textbox_input = true;
|
self.app.ignore_special_keys_for_textbox_input = true;
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.add_artist_modal = None;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistAlreadyInLibrary
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectRootFolder => self.app.pop_navigation_stack(),
|
||||||
|
ActiveLidarrBlock::AddArtistTagsInput => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.ignore_special_keys_for_textbox_input = false;
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_char_key_event(&mut self) {
|
fn handle_char_key_event(&mut self) {
|
||||||
if self.active_lidarr_block == ActiveLidarrBlock::AddArtistSearchInput {
|
let key = self.key;
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddArtistSearchInput => {
|
||||||
handle_text_box_keys!(
|
handle_text_box_keys!(
|
||||||
self,
|
self,
|
||||||
self.key,
|
key,
|
||||||
self
|
self
|
||||||
.app
|
.app
|
||||||
.data
|
.data
|
||||||
@@ -169,6 +577,33 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for AddArtistHandler<'a,
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistTagsInput => {
|
||||||
|
handle_text_box_keys!(
|
||||||
|
self,
|
||||||
|
key,
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.tags
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt => {
|
||||||
|
if self.app.data.lidarr_data.selected_block.get_active_block()
|
||||||
|
== ActiveLidarrBlock::AddArtistConfirmPrompt
|
||||||
|
&& matches_key!(confirm, key)
|
||||||
|
{
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::AddArtist(self.build_add_artist_body()));
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app_mut(&mut self) -> &mut App<'b> {
|
fn app_mut(&mut self) -> &mut App<'b> {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,227 @@
|
|||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::lidarr_handlers::library::delete_album_handler::DeleteAlbumHandler;
|
||||||
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
|
use crate::handlers::{KeyEventHandler, handle_prompt_toggle};
|
||||||
|
use crate::matches_key;
|
||||||
|
use crate::models::lidarr_models::Album;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_SELECTION_BLOCKS,
|
||||||
|
EDIT_ARTIST_SELECTION_BLOCKS,
|
||||||
|
};
|
||||||
|
use crate::models::{BlockSelectionState, Route};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "artist_details_handler_tests.rs"]
|
||||||
|
mod artist_details_handler_tests;
|
||||||
|
|
||||||
|
pub struct ArtistDetailsHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ArtistDetailsHandler<'_, '_> {
|
||||||
|
fn extract_artist_id(&self) -> i64 {
|
||||||
|
self.app.data.lidarr_data.artists.current_selection().id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_album_id(&self) -> i64 {
|
||||||
|
self.app.data.lidarr_data.albums.current_selection().id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for ArtistDetailsHandler<'a, 'b> {
|
||||||
|
fn handle(&mut self) {
|
||||||
|
let albums_table_handling_config =
|
||||||
|
TableHandlingConfig::new(ActiveLidarrBlock::ArtistDetails.into())
|
||||||
|
.searching_block(ActiveLidarrBlock::SearchAlbums.into())
|
||||||
|
.search_error_block(ActiveLidarrBlock::SearchAlbumsError.into())
|
||||||
|
.search_field_fn(|album: &Album| &album.title.text);
|
||||||
|
|
||||||
|
if !handle_table(
|
||||||
|
self,
|
||||||
|
|app| &mut app.data.lidarr_data.albums,
|
||||||
|
albums_table_handling_config,
|
||||||
|
) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
_ if DeleteAlbumHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
DeleteAlbumHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
|
.handle();
|
||||||
|
}
|
||||||
|
_ => self.handle_key_event(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
DeleteAlbumHandler::accepts(active_block) || ARTIST_DETAILS_BLOCKS.contains(&active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ignore_special_keys(&self) -> bool {
|
||||||
|
self.app.ignore_special_keys_for_textbox_input
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: ActiveLidarrBlock,
|
||||||
|
context: Option<ActiveLidarrBlock>,
|
||||||
|
) -> ArtistDetailsHandler<'a, 'b> {
|
||||||
|
ArtistDetailsHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_lidarr_block: active_block,
|
||||||
|
context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self) -> Key {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
!self.app.is_loading
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_up(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_scroll_down(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_home(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_end(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_delete(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::ArtistDetails {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
self.app.data.lidarr_data.selected_block =
|
||||||
|
BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
||||||
|
| ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
||||||
|
handle_prompt_toggle(self.app, self.key);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(
|
||||||
|
LidarrEvent::TriggerAutomaticArtistSearch(self.extract_artist_id()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::UpdateAndScanArtist(self.extract_artist_id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
||||||
|
| ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::ArtistDetails => {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.reset_artist_info_tabs();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
let key = self.key;
|
||||||
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::ArtistDetails => match self.key {
|
||||||
|
_ if matches_key!(refresh, key) => self
|
||||||
|
.app
|
||||||
|
.pop_and_push_navigation_stack(self.active_lidarr_block.into()),
|
||||||
|
_ if matches_key!(auto_search, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::AutomaticallySearchArtistPrompt.into());
|
||||||
|
}
|
||||||
|
_ if matches_key!(update, key) => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::UpdateAndScanArtistPrompt.into());
|
||||||
|
}
|
||||||
|
_ if matches_key!(edit, key) => {
|
||||||
|
self.app.push_navigation_stack(
|
||||||
|
(
|
||||||
|
ActiveLidarrBlock::EditArtistPrompt,
|
||||||
|
Some(self.active_lidarr_block),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
self.app.data.lidarr_data.edit_artist_modal = Some((&self.app.data.lidarr_data).into());
|
||||||
|
self.app.data.lidarr_data.selected_block =
|
||||||
|
BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||||
|
}
|
||||||
|
_ if matches_key!(toggle_monitoring, key) => {
|
||||||
|
if !self.app.data.lidarr_data.albums.is_empty() {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::ToggleAlbumMonitoring(self.extract_album_id()));
|
||||||
|
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.pop_and_push_navigation_stack(self.active_lidarr_block.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action = Some(
|
||||||
|
LidarrEvent::TriggerAutomaticArtistSearch(self.extract_artist_id()),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt => {
|
||||||
|
if matches_key!(confirm, key) {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::UpdateAndScanArtist(self.extract_artist_id()));
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_mut(&mut self) -> &mut App<'b> {
|
||||||
|
self.app
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_route(&self) -> Route {
|
||||||
|
self.app.get_current_route()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,565 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod test_handle_delete {
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_delete_prompt;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::models::lidarr_models::Album;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::DELETE_ALBUM_SELECTION_BLOCKS;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
const DELETE_KEY: Key = DEFAULT_KEYBINDINGS.delete.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_album_delete() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.albums
|
||||||
|
.set_items(vec![Album::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
|
||||||
|
assert_delete_prompt!(
|
||||||
|
ArtistDetailsHandler,
|
||||||
|
app,
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.selected_block.blocks,
|
||||||
|
DELETE_ALBUM_SELECTION_BLOCKS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_left_right_action {
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_left_right_prompt_toggle(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[values(Key::Left, Key::Right)] key: Key,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(key, &mut app, active_lidarr_block, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(key, &mut app, active_lidarr_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_submit {
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::artist;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
|
||||||
|
LidarrEvent::TriggerAutomaticArtistSearch(1)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
||||||
|
LidarrEvent::UpdateAndScanArtist(1)
|
||||||
|
)]
|
||||||
|
fn test_artist_details_prompt_confirm_submit(
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_action: LidarrEvent,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.data.lidarr_data.artists.set_items(vec![artist()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
&expected_action
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_prompt_decline_submit(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
||||||
|
)]
|
||||||
|
prompt_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(SUBMIT_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_esc(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
||||||
|
)]
|
||||||
|
prompt_block: ActiveLidarrBlock,
|
||||||
|
#[values(true, false)] is_ready: bool,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(ESC_KEY, &mut app, prompt_block, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_char_key_event {
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::assert_navigation_pushed;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::library::artist_details_handler::ArtistDetailsHandler;
|
||||||
|
use crate::models::lidarr_models::Artist;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ActiveLidarrBlock, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||||
|
};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::{assert_modal_absent, assert_modal_present, assert_navigation_popped};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_edit_key(
|
||||||
|
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.edit.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(
|
||||||
|
app,
|
||||||
|
(
|
||||||
|
ActiveLidarrBlock::EditArtistPrompt,
|
||||||
|
Some(ActiveLidarrBlock::ArtistDetails)
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
);
|
||||||
|
assert_modal_present!(app.data.lidarr_data.edit_artist_modal);
|
||||||
|
assert!(app.data.lidarr_data.edit_artist_modal.is_some());
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.selected_block.blocks,
|
||||||
|
EDIT_ARTIST_SELECTION_BLOCKS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_edit_key_no_op_when_not_ready(
|
||||||
|
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.edit.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), active_lidarr_block.into());
|
||||||
|
assert_modal_absent!(app.data.lidarr_data.edit_artist_modal);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_toggle_monitoring_key() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.is_routing = false;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::ArtistDetails.into()
|
||||||
|
);
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert!(app.is_routing);
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
Some(LidarrEvent::ToggleAlbumMonitoring(1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_toggle_monitoring_key_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::ArtistDetails.into()
|
||||||
|
);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_toggle_monitoring_key_no_op_when_albums_empty() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.artists.set_items(vec![Artist {
|
||||||
|
id: 1,
|
||||||
|
..Artist::default()
|
||||||
|
}]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.toggle_monitoring.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm_action.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_auto_search_key(
|
||||||
|
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(
|
||||||
|
app,
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_auto_search_key_no_op_when_not_ready(
|
||||||
|
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.auto_search.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), active_lidarr_block.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_update_key(
|
||||||
|
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, ActiveLidarrBlock::UpdateAndScanArtistPrompt.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_update_key_no_op_when_not_ready(
|
||||||
|
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.update.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), active_lidarr_block.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_refresh_key(
|
||||||
|
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.is_routing = false;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_pushed!(app, active_lidarr_block.into());
|
||||||
|
assert!(app.is_routing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_refresh_key_no_op_when_not_ready(
|
||||||
|
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
app.is_routing = false;
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.refresh.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), active_lidarr_block.into());
|
||||||
|
assert!(!app.is_routing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
|
||||||
|
LidarrEvent::TriggerAutomaticArtistSearch(1)
|
||||||
|
)]
|
||||||
|
#[case(
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
||||||
|
LidarrEvent::UpdateAndScanArtist(1)
|
||||||
|
)]
|
||||||
|
fn test_artist_details_prompt_confirm_key(
|
||||||
|
#[case] prompt_block: ActiveLidarrBlock,
|
||||||
|
#[case] expected_action: LidarrEvent,
|
||||||
|
#[values(ActiveLidarrBlock::ArtistDetails)] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|
||||||
|
ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.confirm.key,
|
||||||
|
&mut app,
|
||||||
|
prompt_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert_navigation_popped!(app, active_lidarr_block.into());
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
&expected_action
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_handler_accepts() {
|
||||||
|
let mut artist_details_blocks = ARTIST_DETAILS_BLOCKS.clone().to_vec();
|
||||||
|
artist_details_blocks.extend(DELETE_ALBUM_BLOCKS);
|
||||||
|
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if artist_details_blocks.contains(&active_lidarr_block) {
|
||||||
|
assert!(ArtistDetailsHandler::accepts(active_lidarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!ArtistDetailsHandler::accepts(active_lidarr_block));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_artist_id() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
|
||||||
|
let artist_id = ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.extract_artist_id();
|
||||||
|
|
||||||
|
assert_eq!(artist_id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_album_id() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
|
||||||
|
let album_id = ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.extract_album_id();
|
||||||
|
|
||||||
|
assert_eq!(album_id, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_artist_details_handler_ignore_special_keys(
|
||||||
|
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||||
|
|
||||||
|
let handler = ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
handler.ignore_special_keys(),
|
||||||
|
ignore_special_keys_for_textbox_input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_handler_is_not_ready_when_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let handler = ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_handler_is_ready_when_not_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = ArtistDetailsHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::DeleteParams;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::DELETE_ALBUM_BLOCKS;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::{
|
||||||
|
app::App,
|
||||||
|
event::Key,
|
||||||
|
handlers::{KeyEventHandler, handle_prompt_toggle},
|
||||||
|
matches_key,
|
||||||
|
models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "delete_album_handler_tests.rs"]
|
||||||
|
mod delete_album_handler_tests;
|
||||||
|
|
||||||
|
pub(in crate::handlers::lidarr_handlers) struct DeleteAlbumHandler<'a, 'b> {
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeleteAlbumHandler<'_, '_> {
|
||||||
|
fn build_delete_album_params(&mut self) -> DeleteParams {
|
||||||
|
let id = self.app.data.lidarr_data.albums.current_selection().id;
|
||||||
|
let delete_files = self.app.data.lidarr_data.delete_files;
|
||||||
|
let add_import_list_exclusion = self.app.data.lidarr_data.add_import_list_exclusion;
|
||||||
|
self.app.data.lidarr_data.reset_delete_preferences();
|
||||||
|
|
||||||
|
DeleteParams {
|
||||||
|
id,
|
||||||
|
delete_files,
|
||||||
|
add_import_list_exclusion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for DeleteAlbumHandler<'a, 'b> {
|
||||||
|
fn accepts(active_block: ActiveLidarrBlock) -> bool {
|
||||||
|
DELETE_ALBUM_BLOCKS.contains(&active_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ignore_special_keys(&self) -> bool {
|
||||||
|
self.app.ignore_special_keys_for_textbox_input
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(
|
||||||
|
key: Key,
|
||||||
|
app: &'a mut App<'b>,
|
||||||
|
active_block: ActiveLidarrBlock,
|
||||||
|
_context: Option<ActiveLidarrBlock>,
|
||||||
|
) -> Self {
|
||||||
|
DeleteAlbumHandler {
|
||||||
|
key,
|
||||||
|
app,
|
||||||
|
active_lidarr_block: active_block,
|
||||||
|
_context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self) -> Key {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
!self.app.is_loading
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_up(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt {
|
||||||
|
self.app.data.lidarr_data.selected_block.up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_scroll_down(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt {
|
||||||
|
self.app.data.lidarr_data.selected_block.down();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_home(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_end(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_delete(&mut self) {}
|
||||||
|
|
||||||
|
fn handle_left_right_action(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt {
|
||||||
|
handle_prompt_toggle(self.app, self.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_submit(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt {
|
||||||
|
match self.app.data.lidarr_data.selected_block.get_active_block() {
|
||||||
|
ActiveLidarrBlock::DeleteAlbumConfirmPrompt => {
|
||||||
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DeleteAlbum(self.build_delete_album_params()));
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
} else {
|
||||||
|
self.app.data.lidarr_data.reset_delete_preferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::DeleteAlbumToggleDeleteFile => {
|
||||||
|
self.app.data.lidarr_data.delete_files = !self.app.data.lidarr_data.delete_files;
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion => {
|
||||||
|
self.app.data.lidarr_data.add_import_list_exclusion =
|
||||||
|
!self.app.data.lidarr_data.add_import_list_exclusion;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_esc(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt {
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
self.app.data.lidarr_data.reset_delete_preferences();
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_char_key_event(&mut self) {
|
||||||
|
if self.active_lidarr_block == ActiveLidarrBlock::DeleteAlbumPrompt
|
||||||
|
&& self.app.data.lidarr_data.selected_block.get_active_block()
|
||||||
|
== ActiveLidarrBlock::DeleteAlbumConfirmPrompt
|
||||||
|
&& matches_key!(confirm, self.key)
|
||||||
|
{
|
||||||
|
self.app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
self.app.data.lidarr_data.prompt_confirm_action =
|
||||||
|
Some(LidarrEvent::DeleteAlbum(self.build_delete_album_params()));
|
||||||
|
self.app.should_refresh = true;
|
||||||
|
|
||||||
|
self.app.pop_navigation_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app_mut(&mut self) -> &mut App<'b> {
|
||||||
|
self.app
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_route(&self) -> Route {
|
||||||
|
self.app.get_current_route()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
|
use crate::event::Key;
|
||||||
|
use crate::handlers::KeyEventHandler;
|
||||||
|
use crate::handlers::lidarr_handlers::library::delete_album_handler::DeleteAlbumHandler;
|
||||||
|
use crate::models::lidarr_models::{Album, DeleteParams};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DELETE_ALBUM_BLOCKS};
|
||||||
|
|
||||||
|
mod test_handle_scroll_up_and_down {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use crate::models::BlockSelectionState;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::DELETE_ALBUM_SELECTION_BLOCKS;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delete_album_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS);
|
||||||
|
app.data.lidarr_data.selected_block.down();
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(key, &mut app, ActiveLidarrBlock::DeleteAlbumPrompt, None).handle();
|
||||||
|
|
||||||
|
if key == Key::Up {
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.selected_block.get_active_block(),
|
||||||
|
ActiveLidarrBlock::DeleteAlbumToggleDeleteFile
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.selected_block.get_active_block(),
|
||||||
|
ActiveLidarrBlock::DeleteAlbumConfirmPrompt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delete_album_prompt_scroll_no_op_when_not_ready(
|
||||||
|
#[values(Key::Up, Key::Down)] key: Key,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS);
|
||||||
|
app.data.lidarr_data.selected_block.down();
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(key, &mut app, ActiveLidarrBlock::DeleteAlbumPrompt, None).handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.selected_block.get_active_block(),
|
||||||
|
ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_left_right_action {
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_left_right_prompt_toggle(#[values(Key::Left, Key::Right)] key: Key) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(key, &mut app, ActiveLidarrBlock::DeleteAlbumPrompt, None).handle();
|
||||||
|
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(key, &mut app, ActiveLidarrBlock::DeleteAlbumPrompt, None).handle();
|
||||||
|
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_submit {
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use crate::models::BlockSelectionState;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::DELETE_ALBUM_SELECTION_BLOCKS;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
|
||||||
|
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_prompt_prompt_decline_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.selected_block
|
||||||
|
.set_index(0, DELETE_ALBUM_SELECTION_BLOCKS.len() - 1);
|
||||||
|
app.data.lidarr_data.delete_files = true;
|
||||||
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_confirm_prompt_prompt_confirmation_submit() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.data.lidarr_data.delete_files = true;
|
||||||
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.albums
|
||||||
|
.set_items(vec![Album::default()]);
|
||||||
|
let expected_delete_album_params = DeleteParams {
|
||||||
|
id: 0,
|
||||||
|
delete_files: true,
|
||||||
|
add_import_list_exclusion: true,
|
||||||
|
};
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.selected_block
|
||||||
|
.set_index(0, DELETE_ALBUM_SELECTION_BLOCKS.len() - 1);
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
Some(LidarrEvent::DeleteAlbum(expected_delete_album_params))
|
||||||
|
);
|
||||||
|
assert!(app.should_refresh);
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_confirm_prompt_prompt_confirmation_submit_no_op_when_not_ready() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.data.lidarr_data.delete_files = true;
|
||||||
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt.into()
|
||||||
|
);
|
||||||
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
|
assert!(!app.should_refresh);
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert!(app.data.lidarr_data.delete_files);
|
||||||
|
assert!(app.data.lidarr_data.add_import_list_exclusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_toggle_delete_files_submit() {
|
||||||
|
let current_route = ActiveLidarrBlock::DeleteAlbumPrompt.into();
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), current_route);
|
||||||
|
assert_eq!(app.data.lidarr_data.delete_files, true);
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(
|
||||||
|
SUBMIT_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), current_route);
|
||||||
|
assert_eq!(app.data.lidarr_data.delete_files, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_esc {
|
||||||
|
use super::*;
|
||||||
|
use crate::assert_navigation_popped;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delete_album_prompt_esc(#[values(true, false)] is_ready: bool) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = is_ready;
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
|
app.data.lidarr_data.delete_files = true;
|
||||||
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(
|
||||||
|
ESC_KEY,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test_handle_key_char {
|
||||||
|
use crate::{
|
||||||
|
assert_navigation_popped,
|
||||||
|
models::{
|
||||||
|
BlockSelectionState, servarr_data::lidarr::lidarr_data::DELETE_ALBUM_SELECTION_BLOCKS,
|
||||||
|
},
|
||||||
|
network::lidarr_network::LidarrEvent,
|
||||||
|
};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_confirm_prompt_prompt_confirm() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
app.data.lidarr_data.delete_files = true;
|
||||||
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.albums
|
||||||
|
.set_items(vec![Album::default()]);
|
||||||
|
let expected_delete_album_params = DeleteParams {
|
||||||
|
id: 0,
|
||||||
|
delete_files: true,
|
||||||
|
add_import_list_exclusion: true,
|
||||||
|
};
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS);
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.selected_block
|
||||||
|
.set_index(0, DELETE_ALBUM_SELECTION_BLOCKS.len() - 1);
|
||||||
|
|
||||||
|
DeleteAlbumHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.confirm.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_navigation_popped!(app, ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
assert_eq!(
|
||||||
|
app.data.lidarr_data.prompt_confirm_action,
|
||||||
|
Some(LidarrEvent::DeleteAlbum(expected_delete_album_params))
|
||||||
|
);
|
||||||
|
assert!(app.should_refresh);
|
||||||
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_handler_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if DELETE_ALBUM_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(DeleteAlbumHandler::accepts(active_lidarr_block));
|
||||||
|
} else {
|
||||||
|
assert!(!DeleteAlbumHandler::accepts(active_lidarr_block));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delete_album_handler_ignore_special_keys(
|
||||||
|
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
|
||||||
|
let handler = DeleteAlbumHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::default(),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
handler.ignore_special_keys(),
|
||||||
|
ignore_special_keys_for_textbox_input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_delete_album_params() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.albums
|
||||||
|
.set_items(vec![Album::default()]);
|
||||||
|
app.data.lidarr_data.delete_files = true;
|
||||||
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
|
let expected_delete_album_params = DeleteParams {
|
||||||
|
id: 0,
|
||||||
|
delete_files: true,
|
||||||
|
add_import_list_exclusion: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let delete_album_params = DeleteAlbumHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.build_delete_album_params();
|
||||||
|
|
||||||
|
assert_eq!(delete_album_params, expected_delete_album_params);
|
||||||
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_handler_not_ready_when_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let handler = DeleteAlbumHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!handler.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_handler_ready_when_not_loading() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app.is_loading = false;
|
||||||
|
|
||||||
|
let handler = DeleteAlbumHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(handler.is_ready());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
use crate::models::lidarr_models::DeleteArtistParams;
|
use crate::models::lidarr_models::DeleteParams;
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::App,
|
app::App,
|
||||||
@@ -21,13 +21,13 @@ pub(in crate::handlers::lidarr_handlers) struct DeleteArtistHandler<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DeleteArtistHandler<'_, '_> {
|
impl DeleteArtistHandler<'_, '_> {
|
||||||
fn build_delete_artist_params(&mut self) -> DeleteArtistParams {
|
fn build_delete_artist_params(&mut self) -> DeleteParams {
|
||||||
let id = self.app.data.lidarr_data.artists.current_selection().id;
|
let id = self.app.data.lidarr_data.artists.current_selection().id;
|
||||||
let delete_files = self.app.data.lidarr_data.delete_artist_files;
|
let delete_files = self.app.data.lidarr_data.delete_files;
|
||||||
let add_import_list_exclusion = self.app.data.lidarr_data.add_import_list_exclusion;
|
let add_import_list_exclusion = self.app.data.lidarr_data.add_import_list_exclusion;
|
||||||
self.app.data.lidarr_data.reset_delete_artist_preferences();
|
self.app.data.lidarr_data.reset_delete_preferences();
|
||||||
|
|
||||||
DeleteArtistParams {
|
DeleteParams {
|
||||||
id,
|
id,
|
||||||
delete_files,
|
delete_files,
|
||||||
add_import_list_exclusion,
|
add_import_list_exclusion,
|
||||||
@@ -99,14 +99,13 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for DeleteArtistHandler<
|
|||||||
Some(LidarrEvent::DeleteArtist(self.build_delete_artist_params()));
|
Some(LidarrEvent::DeleteArtist(self.build_delete_artist_params()));
|
||||||
self.app.should_refresh = true;
|
self.app.should_refresh = true;
|
||||||
} else {
|
} else {
|
||||||
self.app.data.lidarr_data.reset_delete_artist_preferences();
|
self.app.data.lidarr_data.reset_delete_preferences();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::DeleteArtistToggleDeleteFile => {
|
ActiveLidarrBlock::DeleteArtistToggleDeleteFile => {
|
||||||
self.app.data.lidarr_data.delete_artist_files =
|
self.app.data.lidarr_data.delete_files = !self.app.data.lidarr_data.delete_files;
|
||||||
!self.app.data.lidarr_data.delete_artist_files;
|
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::DeleteArtistToggleAddListExclusion => {
|
ActiveLidarrBlock::DeleteArtistToggleAddListExclusion => {
|
||||||
self.app.data.lidarr_data.add_import_list_exclusion =
|
self.app.data.lidarr_data.add_import_list_exclusion =
|
||||||
@@ -120,7 +119,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for DeleteArtistHandler<
|
|||||||
fn handle_esc(&mut self) {
|
fn handle_esc(&mut self) {
|
||||||
if self.active_lidarr_block == ActiveLidarrBlock::DeleteArtistPrompt {
|
if self.active_lidarr_block == ActiveLidarrBlock::DeleteArtistPrompt {
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
self.app.data.lidarr_data.reset_delete_artist_preferences();
|
self.app.data.lidarr_data.reset_delete_preferences();
|
||||||
self.app.data.lidarr_data.prompt_confirm = false;
|
self.app.data.lidarr_data.prompt_confirm = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ mod tests {
|
|||||||
use crate::event::Key;
|
use crate::event::Key;
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::library::delete_artist_handler::DeleteArtistHandler;
|
use crate::handlers::lidarr_handlers::library::delete_artist_handler::DeleteArtistHandler;
|
||||||
use crate::models::lidarr_models::{Artist, DeleteArtistParams};
|
use crate::models::lidarr_models::{Artist, DeleteParams};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DELETE_ARTIST_BLOCKS};
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DELETE_ARTIST_BLOCKS};
|
||||||
|
|
||||||
mod test_handle_scroll_up_and_down {
|
mod test_handle_scroll_up_and_down {
|
||||||
@@ -106,7 +106,7 @@ mod tests {
|
|||||||
.lidarr_data
|
.lidarr_data
|
||||||
.selected_block
|
.selected_block
|
||||||
.set_index(0, DELETE_ARTIST_SELECTION_BLOCKS.len() - 1);
|
.set_index(0, DELETE_ARTIST_SELECTION_BLOCKS.len() - 1);
|
||||||
app.data.lidarr_data.delete_artist_files = true;
|
app.data.lidarr_data.delete_files = true;
|
||||||
app.data.lidarr_data.add_import_list_exclusion = true;
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
|
|
||||||
DeleteArtistHandler::new(
|
DeleteArtistHandler::new(
|
||||||
@@ -120,7 +120,7 @@ mod tests {
|
|||||||
assert_navigation_popped!(app, ActiveLidarrBlock::Artists.into());
|
assert_navigation_popped!(app, ActiveLidarrBlock::Artists.into());
|
||||||
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
assert!(!app.data.lidarr_data.prompt_confirm);
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
assert!(!app.data.lidarr_data.delete_artist_files);
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,14 +130,14 @@ mod tests {
|
|||||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into());
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into());
|
||||||
app.data.lidarr_data.prompt_confirm = true;
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.data.lidarr_data.delete_artist_files = true;
|
app.data.lidarr_data.delete_files = true;
|
||||||
app.data.lidarr_data.add_import_list_exclusion = true;
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
app
|
app
|
||||||
.data
|
.data
|
||||||
.lidarr_data
|
.lidarr_data
|
||||||
.artists
|
.artists
|
||||||
.set_items(vec![Artist::default()]);
|
.set_items(vec![Artist::default()]);
|
||||||
let expected_delete_artist_params = DeleteArtistParams {
|
let expected_delete_artist_params = DeleteParams {
|
||||||
id: 0,
|
id: 0,
|
||||||
delete_files: true,
|
delete_files: true,
|
||||||
add_import_list_exclusion: true,
|
add_import_list_exclusion: true,
|
||||||
@@ -165,7 +165,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert!(app.should_refresh);
|
assert!(app.should_refresh);
|
||||||
assert!(app.data.lidarr_data.prompt_confirm);
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
assert!(!app.data.lidarr_data.delete_artist_files);
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ mod tests {
|
|||||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into());
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into());
|
||||||
app.data.lidarr_data.prompt_confirm = true;
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.data.lidarr_data.delete_artist_files = true;
|
app.data.lidarr_data.delete_files = true;
|
||||||
app.data.lidarr_data.add_import_list_exclusion = true;
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
|
|
||||||
DeleteArtistHandler::new(
|
DeleteArtistHandler::new(
|
||||||
@@ -194,7 +194,7 @@ mod tests {
|
|||||||
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
assert_none!(app.data.lidarr_data.prompt_confirm_action);
|
||||||
assert!(!app.should_refresh);
|
assert!(!app.should_refresh);
|
||||||
assert!(app.data.lidarr_data.prompt_confirm);
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
assert!(app.data.lidarr_data.delete_artist_files);
|
assert!(app.data.lidarr_data.delete_files);
|
||||||
assert!(app.data.lidarr_data.add_import_list_exclusion);
|
assert!(app.data.lidarr_data.add_import_list_exclusion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ mod tests {
|
|||||||
.handle();
|
.handle();
|
||||||
|
|
||||||
assert_eq!(app.get_current_route(), current_route);
|
assert_eq!(app.get_current_route(), current_route);
|
||||||
assert_eq!(app.data.lidarr_data.delete_artist_files, true);
|
assert_eq!(app.data.lidarr_data.delete_files, true);
|
||||||
|
|
||||||
DeleteArtistHandler::new(
|
DeleteArtistHandler::new(
|
||||||
SUBMIT_KEY,
|
SUBMIT_KEY,
|
||||||
@@ -226,7 +226,7 @@ mod tests {
|
|||||||
.handle();
|
.handle();
|
||||||
|
|
||||||
assert_eq!(app.get_current_route(), current_route);
|
assert_eq!(app.get_current_route(), current_route);
|
||||||
assert_eq!(app.data.lidarr_data.delete_artist_files, false);
|
assert_eq!(app.data.lidarr_data.delete_files, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ mod tests {
|
|||||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into());
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into());
|
||||||
app.data.lidarr_data.prompt_confirm = true;
|
app.data.lidarr_data.prompt_confirm = true;
|
||||||
app.data.lidarr_data.delete_artist_files = true;
|
app.data.lidarr_data.delete_files = true;
|
||||||
app.data.lidarr_data.add_import_list_exclusion = true;
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
|
|
||||||
DeleteArtistHandler::new(
|
DeleteArtistHandler::new(
|
||||||
@@ -257,7 +257,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_navigation_popped!(app, ActiveLidarrBlock::Artists.into());
|
assert_navigation_popped!(app, ActiveLidarrBlock::Artists.into());
|
||||||
assert!(!app.data.lidarr_data.prompt_confirm);
|
assert!(!app.data.lidarr_data.prompt_confirm);
|
||||||
assert!(!app.data.lidarr_data.delete_artist_files);
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -279,14 +279,14 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into());
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteArtistPrompt.into());
|
||||||
app.data.lidarr_data.delete_artist_files = true;
|
app.data.lidarr_data.delete_files = true;
|
||||||
app.data.lidarr_data.add_import_list_exclusion = true;
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
app
|
app
|
||||||
.data
|
.data
|
||||||
.lidarr_data
|
.lidarr_data
|
||||||
.artists
|
.artists
|
||||||
.set_items(vec![Artist::default()]);
|
.set_items(vec![Artist::default()]);
|
||||||
let expected_delete_artist_params = DeleteArtistParams {
|
let expected_delete_artist_params = DeleteParams {
|
||||||
id: 0,
|
id: 0,
|
||||||
delete_files: true,
|
delete_files: true,
|
||||||
add_import_list_exclusion: true,
|
add_import_list_exclusion: true,
|
||||||
@@ -314,7 +314,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert!(app.should_refresh);
|
assert!(app.should_refresh);
|
||||||
assert!(app.data.lidarr_data.prompt_confirm);
|
assert!(app.data.lidarr_data.prompt_confirm);
|
||||||
assert!(!app.data.lidarr_data.delete_artist_files);
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -357,9 +357,9 @@ mod tests {
|
|||||||
.lidarr_data
|
.lidarr_data
|
||||||
.artists
|
.artists
|
||||||
.set_items(vec![Artist::default()]);
|
.set_items(vec![Artist::default()]);
|
||||||
app.data.lidarr_data.delete_artist_files = true;
|
app.data.lidarr_data.delete_files = true;
|
||||||
app.data.lidarr_data.add_import_list_exclusion = true;
|
app.data.lidarr_data.add_import_list_exclusion = true;
|
||||||
let expected_delete_artist_params = DeleteArtistParams {
|
let expected_delete_artist_params = DeleteParams {
|
||||||
id: 0,
|
id: 0,
|
||||||
delete_files: true,
|
delete_files: true,
|
||||||
add_import_list_exclusion: true,
|
add_import_list_exclusion: true,
|
||||||
@@ -374,7 +374,7 @@ mod tests {
|
|||||||
.build_delete_artist_params();
|
.build_delete_artist_params();
|
||||||
|
|
||||||
assert_eq!(delete_artist_params, expected_delete_artist_params);
|
assert_eq!(delete_artist_params, expected_delete_artist_params);
|
||||||
assert!(!app.data.lidarr_data.delete_artist_files);
|
assert!(!app.data.lidarr_data.delete_files);
|
||||||
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
assert!(!app.data.lidarr_data.add_import_list_exclusion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ mod tests {
|
|||||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||||
use crate::handlers::KeyEventHandler;
|
use crate::handlers::KeyEventHandler;
|
||||||
use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options};
|
use crate::handlers::lidarr_handlers::library::{LibraryHandler, artists_sorting_options};
|
||||||
use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus};
|
use crate::models::lidarr_models::{Album, Artist, ArtistStatistics, ArtistStatus};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ADD_ARTIST_BLOCKS, ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS,
|
ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS,
|
||||||
EDIT_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS,
|
DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS, LIBRARY_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
@@ -26,7 +26,9 @@ mod tests {
|
|||||||
fn test_library_handler_accepts() {
|
fn test_library_handler_accepts() {
|
||||||
let mut library_handler_blocks = Vec::new();
|
let mut library_handler_blocks = Vec::new();
|
||||||
library_handler_blocks.extend(LIBRARY_BLOCKS);
|
library_handler_blocks.extend(LIBRARY_BLOCKS);
|
||||||
|
library_handler_blocks.extend(ARTIST_DETAILS_BLOCKS);
|
||||||
library_handler_blocks.extend(DELETE_ARTIST_BLOCKS);
|
library_handler_blocks.extend(DELETE_ARTIST_BLOCKS);
|
||||||
|
library_handler_blocks.extend(DELETE_ALBUM_BLOCKS);
|
||||||
library_handler_blocks.extend(EDIT_ARTIST_BLOCKS);
|
library_handler_blocks.extend(EDIT_ARTIST_BLOCKS);
|
||||||
library_handler_blocks.extend(ADD_ARTIST_BLOCKS);
|
library_handler_blocks.extend(ADD_ARTIST_BLOCKS);
|
||||||
|
|
||||||
@@ -532,6 +534,31 @@ mod tests {
|
|||||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delegates_delete_album_blocks_to_delete_album_handler() {
|
||||||
|
let mut app = App::test_default();
|
||||||
|
app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.albums
|
||||||
|
.set_items(vec![Album::default()]);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
|
||||||
|
LibraryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
app.get_current_route(),
|
||||||
|
ActiveLidarrBlock::ArtistDetails.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_delegates_delete_artist_blocks_to_delete_artist_handler() {
|
fn test_delegates_delete_artist_blocks_to_delete_artist_handler() {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
@@ -587,6 +614,32 @@ mod tests {
|
|||||||
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_delegates_artist_details_blocks_to_artist_details_handler(
|
||||||
|
#[values(
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
|
||||||
|
ActiveLidarrBlock::SearchAlbums,
|
||||||
|
ActiveLidarrBlock::SearchAlbumsError,
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt
|
||||||
|
)]
|
||||||
|
active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
|
||||||
|
LibraryHandler::new(
|
||||||
|
DEFAULT_KEYBINDINGS.esc.key,
|
||||||
|
&mut app,
|
||||||
|
active_lidarr_block,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.handle();
|
||||||
|
|
||||||
|
assert_eq!(app.get_current_route(), ActiveLidarrBlock::Artists.into());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_key() {
|
fn test_edit_key() {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
|
|||||||
@@ -19,11 +19,14 @@ use super::handle_change_tab_left_right_keys;
|
|||||||
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
|
||||||
|
|
||||||
mod add_artist_handler;
|
mod add_artist_handler;
|
||||||
|
mod artist_details_handler;
|
||||||
|
mod delete_album_handler;
|
||||||
mod delete_artist_handler;
|
mod delete_artist_handler;
|
||||||
mod edit_artist_handler;
|
mod edit_artist_handler;
|
||||||
|
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
pub(in crate::handlers::lidarr_handlers) use add_artist_handler::AddArtistHandler;
|
pub(in crate::handlers::lidarr_handlers) use add_artist_handler::AddArtistHandler;
|
||||||
|
pub(in crate::handlers::lidarr_handlers) use artist_details_handler::ArtistDetailsHandler;
|
||||||
pub(in crate::handlers::lidarr_handlers) use delete_artist_handler::DeleteArtistHandler;
|
pub(in crate::handlers::lidarr_handlers) use delete_artist_handler::DeleteArtistHandler;
|
||||||
pub(in crate::handlers::lidarr_handlers) use edit_artist_handler::EditArtistHandler;
|
pub(in crate::handlers::lidarr_handlers) use edit_artist_handler::EditArtistHandler;
|
||||||
|
|
||||||
@@ -74,6 +77,10 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
|||||||
EditArtistHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
EditArtistHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
.handle();
|
.handle();
|
||||||
}
|
}
|
||||||
|
_ if ArtistDetailsHandler::accepts(self.active_lidarr_block) => {
|
||||||
|
ArtistDetailsHandler::new(self.key, self.app, self.active_lidarr_block, self.context)
|
||||||
|
.handle();
|
||||||
|
}
|
||||||
_ => self.handle_key_event(),
|
_ => self.handle_key_event(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +90,7 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
|||||||
AddArtistHandler::accepts(active_block)
|
AddArtistHandler::accepts(active_block)
|
||||||
|| DeleteArtistHandler::accepts(active_block)
|
|| DeleteArtistHandler::accepts(active_block)
|
||||||
|| EditArtistHandler::accepts(active_block)
|
|| EditArtistHandler::accepts(active_block)
|
||||||
|
|| ArtistDetailsHandler::accepts(active_block)
|
||||||
|| LIBRARY_BLOCKS.contains(&active_block)
|
|| LIBRARY_BLOCKS.contains(&active_block)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,13 +147,21 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveLidarrBlock> for LibraryHandler<'a, '
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn handle_submit(&mut self) {
|
fn handle_submit(&mut self) {
|
||||||
if self.active_lidarr_block == ActiveLidarrBlock::UpdateAllArtistsPrompt {
|
match self.active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::Artists => {
|
||||||
|
self
|
||||||
|
.app
|
||||||
|
.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::UpdateAllArtistsPrompt => {
|
||||||
if self.app.data.lidarr_data.prompt_confirm {
|
if self.app.data.lidarr_data.prompt_confirm {
|
||||||
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::UpdateAllArtists);
|
self.app.data.lidarr_data.prompt_confirm_action = Some(LidarrEvent::UpdateAllArtists);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_esc(&mut self) {
|
fn handle_esc(&mut self) {
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ impl AddMovieHandler<'_, '_> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let path = root_folder_list.current_selection().path.clone();
|
let path = root_folder_list.current_selection().path.clone();
|
||||||
let monitor = monitor_list.current_selection().to_string();
|
let monitor = *monitor_list.current_selection();
|
||||||
let minimum_availability = minimum_availability_list.current_selection().to_string();
|
let minimum_availability = *minimum_availability_list.current_selection();
|
||||||
|
|
||||||
AddMovieBody {
|
AddMovieBody {
|
||||||
tmdb_id,
|
tmdb_id,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ pub(in crate::handlers::radarr_handlers) mod utils {
|
|||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
AddMovieBody, AddMovieOptions, AddMovieSearchResult, Collection, CollectionMovie,
|
AddMovieBody, AddMovieOptions, AddMovieSearchResult, Collection, CollectionMovie,
|
||||||
DownloadRecord, IndexerSettings, MediaInfo, MinimumAvailability, Movie, MovieCollection,
|
DownloadRecord, IndexerSettings, MediaInfo, MinimumAvailability, Movie, MovieCollection,
|
||||||
MovieFile, RadarrRelease, Rating, RatingsList,
|
MovieFile, MovieMonitor, RadarrRelease, Rating, RatingsList,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{
|
use crate::models::servarr_models::{
|
||||||
Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder,
|
Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder,
|
||||||
@@ -470,13 +470,13 @@ pub(in crate::handlers::radarr_handlers) mod utils {
|
|||||||
tmdb_id: 1234,
|
tmdb_id: 1234,
|
||||||
title: "Test".to_owned(),
|
title: "Test".to_owned(),
|
||||||
root_folder_path: "/nfs2".to_owned(),
|
root_folder_path: "/nfs2".to_owned(),
|
||||||
minimum_availability: "announced".to_owned(),
|
minimum_availability: MinimumAvailability::Announced,
|
||||||
monitored: true,
|
monitored: true,
|
||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
tag_input_string: Some("usenet, testing".into()),
|
tag_input_string: Some("usenet, testing".into()),
|
||||||
add_options: AddMovieOptions {
|
add_options: AddMovieOptions {
|
||||||
monitor: "movieOnly".to_owned(),
|
monitor: MovieMonitor::MovieOnly,
|
||||||
search_for_movie: true,
|
search_for_movie: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ impl AddSeriesHandler<'_, '_> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let path = root_folder_list.current_selection().path.clone();
|
let path = root_folder_list.current_selection().path.clone();
|
||||||
let monitor = monitor_list.current_selection().to_string();
|
let monitor = *monitor_list.current_selection();
|
||||||
let series_type = series_type_list.current_selection().to_string();
|
let series_type = *series_type_list.current_selection();
|
||||||
|
|
||||||
AddSeriesBody {
|
AddSeriesBody {
|
||||||
tvdb_id,
|
tvdb_id,
|
||||||
@@ -426,8 +426,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
|
|||||||
|
|
||||||
fn handle_submit(&mut self) {
|
fn handle_submit(&mut self) {
|
||||||
match self.active_sonarr_block {
|
match self.active_sonarr_block {
|
||||||
_ if self.active_sonarr_block == ActiveSonarrBlock::AddSeriesSearchInput
|
ActiveSonarrBlock::AddSeriesSearchInput
|
||||||
&& !self
|
if !self
|
||||||
.app
|
.app
|
||||||
.data
|
.data
|
||||||
.sonarr_data
|
.sonarr_data
|
||||||
@@ -442,8 +442,8 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for AddSeriesHandler<'a,
|
|||||||
.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into());
|
.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into());
|
||||||
self.app.ignore_special_keys_for_textbox_input = false;
|
self.app.ignore_special_keys_for_textbox_input = false;
|
||||||
}
|
}
|
||||||
_ if self.active_sonarr_block == ActiveSonarrBlock::AddSeriesSearchResults
|
ActiveSonarrBlock::AddSeriesSearchResults
|
||||||
&& self.app.data.sonarr_data.add_searched_series.is_some() =>
|
if self.app.data.sonarr_data.add_searched_series.is_some() =>
|
||||||
{
|
{
|
||||||
let tvdb_id = self
|
let tvdb_id = self
|
||||||
.app
|
.app
|
||||||
|
|||||||
@@ -1030,8 +1030,6 @@ mod tests {
|
|||||||
app.is_loading = true;
|
app.is_loading = true;
|
||||||
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
||||||
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into());
|
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into());
|
||||||
let mut add_searched_series = StatefulTable::default();
|
|
||||||
add_searched_series.set_items(vec![AddSeriesSearchResult::default()]);
|
|
||||||
|
|
||||||
AddSeriesHandler::new(
|
AddSeriesHandler::new(
|
||||||
SUBMIT_KEY,
|
SUBMIT_KEY,
|
||||||
@@ -1053,6 +1051,7 @@ mod tests {
|
|||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
||||||
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into());
|
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesSearchResults.into());
|
||||||
|
|
||||||
AddSeriesHandler::new(
|
AddSeriesHandler::new(
|
||||||
SUBMIT_KEY,
|
SUBMIT_KEY,
|
||||||
&mut app,
|
&mut app,
|
||||||
@@ -1092,7 +1091,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_series_prompt_prompt_decline_submit() {
|
fn test_add_series_confirm_prompt_prompt_decline_submit() {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
app.push_navigation_stack(ActiveSonarrBlock::Series.into());
|
||||||
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into());
|
app.push_navigation_stack(ActiveSonarrBlock::AddSeriesPrompt.into());
|
||||||
@@ -1169,12 +1168,12 @@ mod tests {
|
|||||||
root_folder_path: "/nfs2".to_owned(),
|
root_folder_path: "/nfs2".to_owned(),
|
||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
language_profile_id: 2222,
|
language_profile_id: 2222,
|
||||||
series_type: "standard".to_owned(),
|
series_type: SeriesType::Standard,
|
||||||
season_folder: true,
|
season_folder: true,
|
||||||
tags: Vec::default(),
|
tags: Vec::default(),
|
||||||
tag_input_string: Some("usenet, testing".to_owned()),
|
tag_input_string: Some("usenet, testing".to_owned()),
|
||||||
add_options: AddSeriesOptions {
|
add_options: AddSeriesOptions {
|
||||||
monitor: "all".to_owned(),
|
monitor: SeriesMonitor::All,
|
||||||
search_for_cutoff_unmet_episodes: true,
|
search_for_cutoff_unmet_episodes: true,
|
||||||
search_for_missing_episodes: true,
|
search_for_missing_episodes: true,
|
||||||
},
|
},
|
||||||
@@ -1195,9 +1194,9 @@ mod tests {
|
|||||||
.handle();
|
.handle();
|
||||||
|
|
||||||
assert_navigation_popped!(app, ActiveSonarrBlock::Series.into());
|
assert_navigation_popped!(app, ActiveSonarrBlock::Series.into());
|
||||||
assert_eq!(
|
assert_some_eq_x!(
|
||||||
app.data.sonarr_data.prompt_confirm_action,
|
&app.data.sonarr_data.prompt_confirm_action,
|
||||||
Some(SonarrEvent::AddSeries(expected_add_series_body))
|
&SonarrEvent::AddSeries(expected_add_series_body.clone())
|
||||||
);
|
);
|
||||||
assert_modal_absent!(app.data.sonarr_data.add_series_modal);
|
assert_modal_absent!(app.data.sonarr_data.add_series_modal);
|
||||||
}
|
}
|
||||||
@@ -1647,12 +1646,12 @@ mod tests {
|
|||||||
root_folder_path: "/nfs2".to_owned(),
|
root_folder_path: "/nfs2".to_owned(),
|
||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
language_profile_id: 2222,
|
language_profile_id: 2222,
|
||||||
series_type: "standard".to_owned(),
|
series_type: SeriesType::Standard,
|
||||||
season_folder: true,
|
season_folder: true,
|
||||||
tags: Vec::default(),
|
tags: Vec::default(),
|
||||||
tag_input_string: Some("usenet, testing".to_owned()),
|
tag_input_string: Some("usenet, testing".to_owned()),
|
||||||
add_options: AddSeriesOptions {
|
add_options: AddSeriesOptions {
|
||||||
monitor: "all".to_owned(),
|
monitor: SeriesMonitor::All,
|
||||||
search_for_cutoff_unmet_episodes: true,
|
search_for_cutoff_unmet_episodes: true,
|
||||||
search_for_missing_episodes: true,
|
search_for_missing_episodes: true,
|
||||||
},
|
},
|
||||||
@@ -1777,12 +1776,12 @@ mod tests {
|
|||||||
root_folder_path: "/nfs2".to_owned(),
|
root_folder_path: "/nfs2".to_owned(),
|
||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
language_profile_id: 2222,
|
language_profile_id: 2222,
|
||||||
series_type: "standard".to_owned(),
|
series_type: SeriesType::Standard,
|
||||||
season_folder: true,
|
season_folder: true,
|
||||||
tags: Vec::default(),
|
tags: Vec::default(),
|
||||||
tag_input_string: Some("usenet, testing".to_owned()),
|
tag_input_string: Some("usenet, testing".to_owned()),
|
||||||
add_options: AddSeriesOptions {
|
add_options: AddSeriesOptions {
|
||||||
monitor: "all".to_owned(),
|
monitor: SeriesMonitor::All,
|
||||||
search_for_cutoff_unmet_episodes: true,
|
search_for_cutoff_unmet_episodes: true,
|
||||||
search_for_missing_episodes: true,
|
search_for_missing_episodes: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -327,10 +327,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveSonarrBlock> for SeriesDetailsHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveSonarrBlock::UpdateAndScanSeriesPrompt => {
|
ActiveSonarrBlock::UpdateAndScanSeriesPrompt => {
|
||||||
if self.app.data.sonarr_data.prompt_confirm {
|
self.app.data.sonarr_data.prompt_confirm = true;
|
||||||
self.app.data.sonarr_data.prompt_confirm_action =
|
self.app.data.sonarr_data.prompt_confirm_action =
|
||||||
Some(SonarrEvent::UpdateAndScanSeries(self.extract_series_id()));
|
Some(SonarrEvent::UpdateAndScanSeries(self.extract_series_id()));
|
||||||
}
|
|
||||||
|
|
||||||
self.app.pop_navigation_stack();
|
self.app.pop_navigation_stack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -402,7 +402,7 @@ mod tests {
|
|||||||
ActiveSonarrBlock::SeriesDetails.into()
|
ActiveSonarrBlock::SeriesDetails.into()
|
||||||
);
|
);
|
||||||
assert!(!app.data.sonarr_data.prompt_confirm);
|
assert!(!app.data.sonarr_data.prompt_confirm);
|
||||||
assert_modal_absent!(app.data.sonarr_data.prompt_confirm_action);
|
assert_none!(app.data.sonarr_data.prompt_confirm_action);
|
||||||
assert!(!app.is_routing);
|
assert!(!app.is_routing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,7 +555,6 @@ mod tests {
|
|||||||
active_sonarr_block: ActiveSonarrBlock,
|
active_sonarr_block: ActiveSonarrBlock,
|
||||||
) {
|
) {
|
||||||
let mut app = App::test_default();
|
let mut app = App::test_default();
|
||||||
app.data.sonarr_data.prompt_confirm = true;
|
|
||||||
app.data.sonarr_data.series.set_items(vec![series()]);
|
app.data.sonarr_data.series.set_items(vec![series()]);
|
||||||
app.push_navigation_stack(active_sonarr_block.into());
|
app.push_navigation_stack(active_sonarr_block.into());
|
||||||
app.push_navigation_stack(prompt_block.into());
|
app.push_navigation_stack(prompt_block.into());
|
||||||
|
|||||||
+106
-1
@@ -134,6 +134,40 @@ pub enum NewItemMonitorType {
|
|||||||
New,
|
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)]
|
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct DownloadRecord {
|
pub struct DownloadRecord {
|
||||||
@@ -211,14 +245,45 @@ pub struct AddArtistSearchResult {
|
|||||||
pub ratings: Option<Ratings>,
|
pub ratings: Option<Ratings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct LidarrCommandBody {
|
||||||
|
pub name: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub artist_id: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Default, Clone, Eq, PartialEq)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub struct DeleteArtistParams {
|
pub struct DeleteParams {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub delete_files: bool,
|
pub delete_files: bool,
|
||||||
pub add_import_list_exclusion: bool,
|
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)]
|
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct EditArtistParams {
|
pub struct EditArtistParams {
|
||||||
@@ -234,6 +299,44 @@ pub struct EditArtistParams {
|
|||||||
pub clear_tags: bool,
|
pub clear_tags: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Album {
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub id: i64,
|
||||||
|
pub title: HorizontallyScrollableText,
|
||||||
|
pub foreign_album_id: String,
|
||||||
|
pub monitored: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub any_release_ok: bool,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub profile_id: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub duration: i64,
|
||||||
|
pub album_type: Option<String>,
|
||||||
|
pub genres: Vec<String>,
|
||||||
|
pub ratings: Option<Ratings>,
|
||||||
|
pub release_date: Option<DateTime<Utc>>,
|
||||||
|
pub statistics: Option<AlbumStatistics>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct AlbumStatistics {
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub track_file_count: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub track_count: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub total_track_count: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_i64")]
|
||||||
|
pub size_on_disk: i64,
|
||||||
|
#[serde(deserialize_with = "super::from_f64")]
|
||||||
|
pub percent_of_tracks: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for AlbumStatistics {}
|
||||||
|
|
||||||
impl From<LidarrSerdeable> for Serdeable {
|
impl From<LidarrSerdeable> for Serdeable {
|
||||||
fn from(value: LidarrSerdeable) -> Serdeable {
|
fn from(value: LidarrSerdeable) -> Serdeable {
|
||||||
Serdeable::Lidarr(value)
|
Serdeable::Lidarr(value)
|
||||||
@@ -243,6 +346,8 @@ impl From<LidarrSerdeable> for Serdeable {
|
|||||||
serde_enum_from!(
|
serde_enum_from!(
|
||||||
LidarrSerdeable {
|
LidarrSerdeable {
|
||||||
AddArtistSearchResults(Vec<AddArtistSearchResult>),
|
AddArtistSearchResults(Vec<AddArtistSearchResult>),
|
||||||
|
Albums(Vec<Album>),
|
||||||
|
Album(Album),
|
||||||
Artist(Artist),
|
Artist(Artist),
|
||||||
Artists(Vec<Artist>),
|
Artists(Vec<Artist>),
|
||||||
DiskSpaces(Vec<DiskSpace>),
|
DiskSpaces(Vec<DiskSpace>),
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ mod tests {
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
AddArtistSearchResult, DownloadRecord, DownloadStatus, DownloadsResponse, Member,
|
AddArtistSearchResult, Album, DownloadRecord, DownloadStatus, DownloadsResponse, Member,
|
||||||
MetadataProfile, NewItemMonitorType, SystemStatus,
|
MetadataProfile, MonitorType, NewItemMonitorType, SystemStatus,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{
|
use crate::models::servarr_models::{
|
||||||
DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag,
|
DiskSpace, HostConfig, QualityProfile, RootFolder, SecurityConfig, Tag,
|
||||||
@@ -35,6 +35,30 @@ mod tests {
|
|||||||
assert_str_eq!(NewItemMonitorType::New.to_display_str(), "New Albums");
|
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]
|
#[test]
|
||||||
fn test_lidarr_serdeable_from() {
|
fn test_lidarr_serdeable_from() {
|
||||||
let lidarr_serdeable = LidarrSerdeable::Value(json!({}));
|
let lidarr_serdeable = LidarrSerdeable::Value(json!({}));
|
||||||
@@ -377,6 +401,45 @@ mod tests {
|
|||||||
assert_eq!(lidarr_serdeable, LidarrSerdeable::Tags(tags));
|
assert_eq!(lidarr_serdeable, LidarrSerdeable::Tags(tags));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_serdeable_from_add_artist_search_results() {
|
||||||
|
let search_results = vec![AddArtistSearchResult {
|
||||||
|
foreign_artist_id: "test-id".to_owned(),
|
||||||
|
..AddArtistSearchResult::default()
|
||||||
|
}];
|
||||||
|
|
||||||
|
let lidarr_serdeable: LidarrSerdeable = search_results.clone().into();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
lidarr_serdeable,
|
||||||
|
LidarrSerdeable::AddArtistSearchResults(search_results)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_serdeable_from_albums() {
|
||||||
|
let albums = vec![Album {
|
||||||
|
id: 1,
|
||||||
|
..Album::default()
|
||||||
|
}];
|
||||||
|
|
||||||
|
let lidarr_serdeable: LidarrSerdeable = albums.clone().into();
|
||||||
|
|
||||||
|
assert_eq!(lidarr_serdeable, LidarrSerdeable::Albums(albums));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lidarr_serdeable_from_album() {
|
||||||
|
let album = Album {
|
||||||
|
id: 1,
|
||||||
|
..Album::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let lidarr_serdeable: LidarrSerdeable = album.clone().into();
|
||||||
|
|
||||||
|
assert_eq!(lidarr_serdeable, LidarrSerdeable::Album(album));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_artist_status_display() {
|
fn test_artist_status_display() {
|
||||||
assert_str_eq!(ArtistStatus::Continuing.to_string(), "continuing");
|
assert_str_eq!(ArtistStatus::Continuing.to_string(), "continuing");
|
||||||
@@ -477,19 +540,4 @@ mod tests {
|
|||||||
assert!(search_result.genres.is_empty());
|
assert!(search_result.genres.is_empty());
|
||||||
assert_none!(&search_result.ratings);
|
assert_none!(&search_result.ratings);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_lidarr_serdeable_from_add_artist_search_results() {
|
|
||||||
let search_results = vec![AddArtistSearchResult {
|
|
||||||
foreign_artist_id: "test-id".to_owned(),
|
|
||||||
..AddArtistSearchResult::default()
|
|
||||||
}];
|
|
||||||
|
|
||||||
let lidarr_serdeable: LidarrSerdeable = search_results.clone().into();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
lidarr_serdeable,
|
|
||||||
LidarrSerdeable::AddArtistSearchResults(search_results)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ pub struct AddMovieBody {
|
|||||||
pub title: String,
|
pub title: String,
|
||||||
pub root_folder_path: String,
|
pub root_folder_path: String,
|
||||||
pub quality_profile_id: i64,
|
pub quality_profile_id: i64,
|
||||||
pub minimum_availability: String,
|
pub minimum_availability: MinimumAvailability,
|
||||||
pub monitored: bool,
|
pub monitored: bool,
|
||||||
pub tags: Vec<i64>,
|
pub tags: Vec<i64>,
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
@@ -55,7 +55,7 @@ pub struct AddMovieSearchResult {
|
|||||||
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
|
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AddMovieOptions {
|
pub struct AddMovieOptions {
|
||||||
pub monitor: String,
|
pub monitor: MovieMonitor,
|
||||||
pub search_for_movie: bool,
|
pub search_for_movie: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,8 +268,20 @@ pub enum MinimumAvailability {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Default, PartialEq, Eq, Clone, Copy, Debug, EnumIter, ValueEnum, Display, EnumDisplayStyle,
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
Default,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Clone,
|
||||||
|
Copy,
|
||||||
|
Debug,
|
||||||
|
EnumIter,
|
||||||
|
ValueEnum,
|
||||||
|
Display,
|
||||||
|
EnumDisplayStyle,
|
||||||
)]
|
)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
#[strum(serialize_all = "camelCase")]
|
#[strum(serialize_all = "camelCase")]
|
||||||
pub enum MovieMonitor {
|
pub enum MovieMonitor {
|
||||||
#[default]
|
#[default]
|
||||||
|
|||||||
@@ -1,25 +1,28 @@
|
|||||||
use serde_json::Number;
|
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::app::lidarr::lidarr_context_clues::{
|
||||||
|
ARTIST_DETAILS_CONTEXT_CLUES, ARTISTS_CONTEXT_CLUES,
|
||||||
|
};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
BlockSelectionState, HorizontallyScrollableText, Route, TabRoute, TabState,
|
BlockSelectionState, HorizontallyScrollableText, Route, TabRoute, TabState,
|
||||||
lidarr_models::{AddArtistSearchResult, Artist, DownloadRecord},
|
lidarr_models::{AddArtistSearchResult, Album, Artist, DownloadRecord},
|
||||||
servarr_models::{DiskSpace, RootFolder},
|
servarr_models::{DiskSpace, RootFolder},
|
||||||
stateful_table::StatefulTable,
|
stateful_table::StatefulTable,
|
||||||
};
|
};
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use itertools::Itertools;
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use {
|
use {
|
||||||
crate::models::lidarr_models::NewItemMonitorType,
|
crate::models::lidarr_models::{MonitorType, NewItemMonitorType},
|
||||||
crate::models::stateful_table::SortOption,
|
crate::models::stateful_table::SortOption,
|
||||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map,
|
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::quality_profile_map,
|
||||||
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
crate::network::lidarr_network::lidarr_network_test_utils::test_utils::{
|
||||||
add_artist_search_result, download_record, metadata_profile, metadata_profile_map,
|
add_artist_search_result, album, artist, download_record, metadata_profile,
|
||||||
quality_profile, root_folder, tags_map,
|
metadata_profile_map, quality_profile, root_folder, tags_map,
|
||||||
},
|
},
|
||||||
crate::network::servarr_test_utils::diskspace,
|
crate::network::servarr_test_utils::diskspace,
|
||||||
strum::{Display, EnumString, IntoEnumIterator},
|
strum::{Display, EnumString, IntoEnumIterator},
|
||||||
@@ -30,11 +33,14 @@ use {
|
|||||||
mod lidarr_data_tests;
|
mod lidarr_data_tests;
|
||||||
|
|
||||||
pub struct LidarrData<'a> {
|
pub struct LidarrData<'a> {
|
||||||
|
pub add_artist_modal: Option<AddArtistModal>,
|
||||||
pub add_artist_search: Option<HorizontallyScrollableText>,
|
pub add_artist_search: Option<HorizontallyScrollableText>,
|
||||||
pub add_import_list_exclusion: bool,
|
pub add_import_list_exclusion: bool,
|
||||||
pub add_searched_artists: Option<StatefulTable<AddArtistSearchResult>>,
|
pub add_searched_artists: Option<StatefulTable<AddArtistSearchResult>>,
|
||||||
|
pub albums: StatefulTable<Album>,
|
||||||
|
pub artist_info_tabs: TabState,
|
||||||
pub artists: StatefulTable<Artist>,
|
pub artists: StatefulTable<Artist>,
|
||||||
pub delete_artist_files: bool,
|
pub delete_files: bool,
|
||||||
pub disk_space_vec: Vec<DiskSpace>,
|
pub disk_space_vec: Vec<DiskSpace>,
|
||||||
pub downloads: StatefulTable<DownloadRecord>,
|
pub downloads: StatefulTable<DownloadRecord>,
|
||||||
pub edit_artist_modal: Option<EditArtistModal>,
|
pub edit_artist_modal: Option<EditArtistModal>,
|
||||||
@@ -51,11 +57,16 @@ pub struct LidarrData<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl LidarrData<'_> {
|
impl LidarrData<'_> {
|
||||||
pub fn reset_delete_artist_preferences(&mut self) {
|
pub fn reset_delete_preferences(&mut self) {
|
||||||
self.delete_artist_files = false;
|
self.delete_files = false;
|
||||||
self.add_import_list_exclusion = false;
|
self.add_import_list_exclusion = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_artist_info_tabs(&mut self) {
|
||||||
|
self.albums = StatefulTable::default();
|
||||||
|
self.artist_info_tabs.index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn tag_ids_to_display(&self, tag_ids: &[Number]) -> String {
|
pub fn tag_ids_to_display(&self, tag_ids: &[Number]) -> String {
|
||||||
tag_ids
|
tag_ids
|
||||||
.iter()
|
.iter()
|
||||||
@@ -68,28 +79,36 @@ impl LidarrData<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sorted_quality_profile_names(&self) -> Vec<String> {
|
pub fn sorted_quality_profile_names(&self) -> Vec<String> {
|
||||||
let mut quality_profile_names: Vec<String> =
|
self
|
||||||
self.quality_profile_map.right_values().cloned().collect();
|
.quality_profile_map
|
||||||
quality_profile_names.sort();
|
.iter()
|
||||||
quality_profile_names
|
.sorted_by_key(|(id, _)| *id)
|
||||||
|
.map(|(_, name)| name)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sorted_metadata_profile_names(&self) -> Vec<String> {
|
pub fn sorted_metadata_profile_names(&self) -> Vec<String> {
|
||||||
let mut metadata_profile_names: Vec<String> =
|
self
|
||||||
self.metadata_profile_map.right_values().cloned().collect();
|
.metadata_profile_map
|
||||||
metadata_profile_names.sort();
|
.iter()
|
||||||
metadata_profile_names
|
.sorted_by_key(|(id, _)| *id)
|
||||||
|
.map(|(_, name)| name)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Default for LidarrData<'a> {
|
impl<'a> Default for LidarrData<'a> {
|
||||||
fn default() -> LidarrData<'a> {
|
fn default() -> LidarrData<'a> {
|
||||||
LidarrData {
|
LidarrData {
|
||||||
|
add_artist_modal: None,
|
||||||
add_artist_search: None,
|
add_artist_search: None,
|
||||||
add_import_list_exclusion: false,
|
add_import_list_exclusion: false,
|
||||||
add_searched_artists: None,
|
add_searched_artists: None,
|
||||||
|
albums: StatefulTable::default(),
|
||||||
artists: StatefulTable::default(),
|
artists: StatefulTable::default(),
|
||||||
delete_artist_files: false,
|
delete_files: false,
|
||||||
disk_space_vec: Vec::new(),
|
disk_space_vec: Vec::new(),
|
||||||
downloads: StatefulTable::default(),
|
downloads: StatefulTable::default(),
|
||||||
edit_artist_modal: None,
|
edit_artist_modal: None,
|
||||||
@@ -108,6 +127,12 @@ impl<'a> Default for LidarrData<'a> {
|
|||||||
contextual_help: Some(&ARTISTS_CONTEXT_CLUES),
|
contextual_help: Some(&ARTISTS_CONTEXT_CLUES),
|
||||||
config: None,
|
config: None,
|
||||||
}]),
|
}]),
|
||||||
|
artist_info_tabs: TabState::new(vec![TabRoute {
|
||||||
|
title: "Albums".to_string(),
|
||||||
|
route: ActiveLidarrBlock::ArtistDetails.into(),
|
||||||
|
contextual_help: Some(&ARTIST_DETAILS_CONTEXT_CLUES),
|
||||||
|
config: None,
|
||||||
|
}]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,6 +140,25 @@ impl<'a> Default for LidarrData<'a> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl LidarrData<'_> {
|
impl LidarrData<'_> {
|
||||||
pub fn test_default_fully_populated() -> Self {
|
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 {
|
let mut edit_artist_modal = EditArtistModal {
|
||||||
monitored: Some(true),
|
monitored: Some(true),
|
||||||
path: "/nfs/music".into(),
|
path: "/nfs/music".into(),
|
||||||
@@ -132,15 +176,18 @@ impl LidarrData<'_> {
|
|||||||
.set_items(vec![metadata_profile().name]);
|
.set_items(vec![metadata_profile().name]);
|
||||||
|
|
||||||
let mut lidarr_data = LidarrData {
|
let mut lidarr_data = LidarrData {
|
||||||
delete_artist_files: true,
|
delete_files: true,
|
||||||
disk_space_vec: vec![diskspace()],
|
disk_space_vec: vec![diskspace()],
|
||||||
quality_profile_map: quality_profile_map(),
|
quality_profile_map: quality_profile_map(),
|
||||||
metadata_profile_map: metadata_profile_map(),
|
metadata_profile_map: metadata_profile_map(),
|
||||||
edit_artist_modal: Some(edit_artist_modal),
|
edit_artist_modal: Some(edit_artist_modal),
|
||||||
|
add_artist_modal: Some(add_artist_modal),
|
||||||
tags_map: tags_map(),
|
tags_map: tags_map(),
|
||||||
..LidarrData::default()
|
..LidarrData::default()
|
||||||
};
|
};
|
||||||
lidarr_data.artists.set_items(vec![Artist::default()]);
|
lidarr_data.albums.set_items(vec![album()]);
|
||||||
|
lidarr_data.albums.search = Some("album search".into());
|
||||||
|
lidarr_data.artists.set_items(vec![artist()]);
|
||||||
lidarr_data.artists.sorting(vec![SortOption {
|
lidarr_data.artists.sorting(vec![SortOption {
|
||||||
name: "Name",
|
name: "Name",
|
||||||
cmp_fn: Some(|a: &Artist, b: &Artist| a.artist_name.text.cmp(&b.artist_name.text)),
|
cmp_fn: Some(|a: &Artist, b: &Artist| a.artist_name.text.cmp(&b.artist_name.text)),
|
||||||
@@ -164,10 +211,25 @@ impl LidarrData<'_> {
|
|||||||
pub enum ActiveLidarrBlock {
|
pub enum ActiveLidarrBlock {
|
||||||
#[default]
|
#[default]
|
||||||
Artists,
|
Artists,
|
||||||
|
ArtistDetails,
|
||||||
ArtistsSortPrompt,
|
ArtistsSortPrompt,
|
||||||
|
AddArtistAlreadyInLibrary,
|
||||||
|
AddArtistConfirmPrompt,
|
||||||
AddArtistEmptySearchResults,
|
AddArtistEmptySearchResults,
|
||||||
|
AddArtistPrompt,
|
||||||
AddArtistSearchInput,
|
AddArtistSearchInput,
|
||||||
AddArtistSearchResults,
|
AddArtistSearchResults,
|
||||||
|
AddArtistSelectMetadataProfile,
|
||||||
|
AddArtistSelectMonitor,
|
||||||
|
AddArtistSelectMonitorNewItems,
|
||||||
|
AddArtistSelectQualityProfile,
|
||||||
|
AddArtistSelectRootFolder,
|
||||||
|
AddArtistTagsInput,
|
||||||
|
AutomaticallySearchArtistPrompt,
|
||||||
|
DeleteAlbumPrompt,
|
||||||
|
DeleteAlbumConfirmPrompt,
|
||||||
|
DeleteAlbumToggleDeleteFile,
|
||||||
|
DeleteAlbumToggleAddListExclusion,
|
||||||
DeleteArtistPrompt,
|
DeleteArtistPrompt,
|
||||||
DeleteArtistConfirmPrompt,
|
DeleteArtistConfirmPrompt,
|
||||||
DeleteArtistToggleDeleteFile,
|
DeleteArtistToggleDeleteFile,
|
||||||
@@ -182,9 +244,12 @@ pub enum ActiveLidarrBlock {
|
|||||||
EditArtistToggleMonitored,
|
EditArtistToggleMonitored,
|
||||||
FilterArtists,
|
FilterArtists,
|
||||||
FilterArtistsError,
|
FilterArtistsError,
|
||||||
|
SearchAlbums,
|
||||||
|
SearchAlbumsError,
|
||||||
SearchArtists,
|
SearchArtists,
|
||||||
SearchArtistsError,
|
SearchArtistsError,
|
||||||
UpdateAllArtistsPrompt,
|
UpdateAllArtistsPrompt,
|
||||||
|
UpdateAndScanArtistPrompt,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static LIBRARY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
pub static LIBRARY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
||||||
@@ -197,10 +262,37 @@ pub static LIBRARY_BLOCKS: [ActiveLidarrBlock; 7] = [
|
|||||||
ActiveLidarrBlock::UpdateAllArtistsPrompt,
|
ActiveLidarrBlock::UpdateAllArtistsPrompt,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub static ADD_ARTIST_BLOCKS: [ActiveLidarrBlock; 3] = [
|
pub static ARTIST_DETAILS_BLOCKS: [ActiveLidarrBlock; 5] = [
|
||||||
|
ActiveLidarrBlock::ArtistDetails,
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt,
|
||||||
|
ActiveLidarrBlock::SearchAlbums,
|
||||||
|
ActiveLidarrBlock::SearchAlbumsError,
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static ADD_ARTIST_BLOCKS: [ActiveLidarrBlock; 12] = [
|
||||||
|
ActiveLidarrBlock::AddArtistAlreadyInLibrary,
|
||||||
|
ActiveLidarrBlock::AddArtistConfirmPrompt,
|
||||||
ActiveLidarrBlock::AddArtistEmptySearchResults,
|
ActiveLidarrBlock::AddArtistEmptySearchResults,
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt,
|
||||||
ActiveLidarrBlock::AddArtistSearchInput,
|
ActiveLidarrBlock::AddArtistSearchInput,
|
||||||
ActiveLidarrBlock::AddArtistSearchResults,
|
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] = [
|
pub static DELETE_ARTIST_BLOCKS: [ActiveLidarrBlock; 4] = [
|
||||||
@@ -216,6 +308,19 @@ pub const DELETE_ARTIST_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
|
|||||||
&[ActiveLidarrBlock::DeleteArtistConfirmPrompt],
|
&[ActiveLidarrBlock::DeleteArtistConfirmPrompt],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
pub static DELETE_ALBUM_BLOCKS: [ActiveLidarrBlock; 4] = [
|
||||||
|
ActiveLidarrBlock::DeleteAlbumPrompt,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumConfirmPrompt,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumToggleDeleteFile,
|
||||||
|
ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const DELETE_ALBUM_SELECTION_BLOCKS: &[&[ActiveLidarrBlock]] = &[
|
||||||
|
&[ActiveLidarrBlock::DeleteAlbumToggleDeleteFile],
|
||||||
|
&[ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion],
|
||||||
|
&[ActiveLidarrBlock::DeleteAlbumConfirmPrompt],
|
||||||
|
];
|
||||||
|
|
||||||
pub static EDIT_ARTIST_BLOCKS: [ActiveLidarrBlock; 8] = [
|
pub static EDIT_ARTIST_BLOCKS: [ActiveLidarrBlock; 8] = [
|
||||||
ActiveLidarrBlock::EditArtistPrompt,
|
ActiveLidarrBlock::EditArtistPrompt,
|
||||||
ActiveLidarrBlock::EditArtistConfirmPrompt,
|
ActiveLidarrBlock::EditArtistConfirmPrompt,
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::lidarr::lidarr_context_clues::ARTISTS_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::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ADD_ARTIST_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_BLOCKS,
|
ADD_ARTIST_BLOCKS, ADD_ARTIST_SELECTION_BLOCKS, ARTIST_DETAILS_BLOCKS, DELETE_ALBUM_BLOCKS,
|
||||||
EDIT_ARTIST_SELECTION_BLOCKS,
|
DELETE_ALBUM_SELECTION_BLOCKS, DELETE_ARTIST_BLOCKS, DELETE_ARTIST_SELECTION_BLOCKS,
|
||||||
|
EDIT_ARTIST_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
BlockSelectionState, Route,
|
BlockSelectionState, Route,
|
||||||
@@ -31,19 +35,31 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_reset_delete_artist_preferences() {
|
fn test_reset_delete_preferences() {
|
||||||
let mut lidarr_data = LidarrData {
|
let mut lidarr_data = LidarrData {
|
||||||
delete_artist_files: true,
|
delete_files: true,
|
||||||
add_import_list_exclusion: true,
|
add_import_list_exclusion: true,
|
||||||
..LidarrData::default()
|
..LidarrData::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
lidarr_data.reset_delete_artist_preferences();
|
lidarr_data.reset_delete_preferences();
|
||||||
|
|
||||||
assert!(!lidarr_data.delete_artist_files);
|
assert!(!lidarr_data.delete_files);
|
||||||
assert!(!lidarr_data.add_import_list_exclusion);
|
assert!(!lidarr_data.add_import_list_exclusion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reset_artist_info_tabs() {
|
||||||
|
let mut lidarr_data = LidarrData::default();
|
||||||
|
lidarr_data.albums.set_items(vec![Album::default()]);
|
||||||
|
lidarr_data.artist_info_tabs.index = 1;
|
||||||
|
|
||||||
|
lidarr_data.reset_artist_info_tabs();
|
||||||
|
|
||||||
|
assert_is_empty!(lidarr_data.albums);
|
||||||
|
assert_eq!(lidarr_data.artist_info_tabs.index, 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tag_ids_to_display() {
|
fn test_tag_ids_to_display() {
|
||||||
let mut tags_map = BiMap::new();
|
let mut tags_map = BiMap::new();
|
||||||
@@ -64,17 +80,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_sorted_quality_profile_names() {
|
fn test_sorted_quality_profile_names() {
|
||||||
let mut quality_profile_map = BiMap::new();
|
let mut quality_profile_map = BiMap::new();
|
||||||
quality_profile_map.insert(3, "test 3".to_owned());
|
quality_profile_map.insert(3, "test 1".to_owned());
|
||||||
quality_profile_map.insert(2, "test 2".to_owned());
|
quality_profile_map.insert(2, "test 2".to_owned());
|
||||||
quality_profile_map.insert(1, "test 1".to_owned());
|
quality_profile_map.insert(1, "test 3".to_owned());
|
||||||
let lidarr_data = LidarrData {
|
let lidarr_data = LidarrData {
|
||||||
quality_profile_map,
|
quality_profile_map,
|
||||||
..LidarrData::default()
|
..LidarrData::default()
|
||||||
};
|
};
|
||||||
let expected_quality_profile_vec = vec![
|
let expected_quality_profile_vec = vec![
|
||||||
"test 1".to_owned(),
|
|
||||||
"test 2".to_owned(),
|
|
||||||
"test 3".to_owned(),
|
"test 3".to_owned(),
|
||||||
|
"test 2".to_owned(),
|
||||||
|
"test 1".to_owned(),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_iter_eq!(
|
assert_iter_eq!(
|
||||||
@@ -86,17 +102,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_sorted_metadata_profile_names() {
|
fn test_sorted_metadata_profile_names() {
|
||||||
let mut metadata_profile_map = BiMap::new();
|
let mut metadata_profile_map = BiMap::new();
|
||||||
metadata_profile_map.insert(3, "test 3".to_owned());
|
metadata_profile_map.insert(3, "test 1".to_owned());
|
||||||
metadata_profile_map.insert(2, "test 2".to_owned());
|
metadata_profile_map.insert(2, "test 2".to_owned());
|
||||||
metadata_profile_map.insert(1, "test 1".to_owned());
|
metadata_profile_map.insert(1, "test 3".to_owned());
|
||||||
let lidarr_data = LidarrData {
|
let lidarr_data = LidarrData {
|
||||||
metadata_profile_map,
|
metadata_profile_map,
|
||||||
..LidarrData::default()
|
..LidarrData::default()
|
||||||
};
|
};
|
||||||
let expected_metadata_profile_vec = vec![
|
let expected_metadata_profile_vec = vec![
|
||||||
"test 1".to_owned(),
|
|
||||||
"test 2".to_owned(),
|
|
||||||
"test 3".to_owned(),
|
"test 3".to_owned(),
|
||||||
|
"test 2".to_owned(),
|
||||||
|
"test 1".to_owned(),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_iter_eq!(
|
assert_iter_eq!(
|
||||||
@@ -112,8 +128,9 @@ mod tests {
|
|||||||
assert_none!(lidarr_data.add_artist_search);
|
assert_none!(lidarr_data.add_artist_search);
|
||||||
assert!(!lidarr_data.add_import_list_exclusion);
|
assert!(!lidarr_data.add_import_list_exclusion);
|
||||||
assert_none!(lidarr_data.add_searched_artists);
|
assert_none!(lidarr_data.add_searched_artists);
|
||||||
|
assert_is_empty!(lidarr_data.albums);
|
||||||
assert_is_empty!(lidarr_data.artists);
|
assert_is_empty!(lidarr_data.artists);
|
||||||
assert!(!lidarr_data.delete_artist_files);
|
assert!(!lidarr_data.delete_files);
|
||||||
assert_is_empty!(lidarr_data.disk_space_vec);
|
assert_is_empty!(lidarr_data.disk_space_vec);
|
||||||
assert_is_empty!(lidarr_data.downloads);
|
assert_is_empty!(lidarr_data.downloads);
|
||||||
assert_none!(lidarr_data.edit_artist_modal);
|
assert_none!(lidarr_data.edit_artist_modal);
|
||||||
@@ -139,6 +156,18 @@ mod tests {
|
|||||||
&ARTISTS_CONTEXT_CLUES
|
&ARTISTS_CONTEXT_CLUES
|
||||||
);
|
);
|
||||||
assert_none!(lidarr_data.main_tabs.tabs[0].config);
|
assert_none!(lidarr_data.main_tabs.tabs[0].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!(
|
||||||
|
lidarr_data.artist_info_tabs.tabs[0].route,
|
||||||
|
ActiveLidarrBlock::ArtistDetails.into()
|
||||||
|
);
|
||||||
|
assert_some_eq_x!(
|
||||||
|
&lidarr_data.artist_info_tabs.tabs[0].contextual_help,
|
||||||
|
&ARTIST_DETAILS_CONTEXT_CLUES
|
||||||
|
);
|
||||||
|
assert_none!(lidarr_data.artist_info_tabs.tabs[0].config);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -153,12 +182,66 @@ mod tests {
|
|||||||
assert!(LIBRARY_BLOCKS.contains(&ActiveLidarrBlock::UpdateAllArtistsPrompt));
|
assert!(LIBRARY_BLOCKS.contains(&ActiveLidarrBlock::UpdateAllArtistsPrompt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_blocks_contains_expected_blocks() {
|
||||||
|
assert_eq!(ARTIST_DETAILS_BLOCKS.len(), 5);
|
||||||
|
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::ArtistDetails));
|
||||||
|
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::AutomaticallySearchArtistPrompt));
|
||||||
|
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::SearchAlbums));
|
||||||
|
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::SearchAlbumsError));
|
||||||
|
assert!(ARTIST_DETAILS_BLOCKS.contains(&ActiveLidarrBlock::UpdateAndScanArtistPrompt));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_add_artist_blocks_contents() {
|
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::AddArtistEmptySearchResults));
|
||||||
|
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistPrompt));
|
||||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSearchInput));
|
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSearchInput));
|
||||||
assert!(ADD_ARTIST_BLOCKS.contains(&ActiveLidarrBlock::AddArtistSearchResults));
|
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]
|
#[test]
|
||||||
@@ -189,6 +272,34 @@ mod tests {
|
|||||||
assert_none!(delete_artist_block_iter.next());
|
assert_none!(delete_artist_block_iter.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_blocks_contents() {
|
||||||
|
assert_eq!(DELETE_ALBUM_BLOCKS.len(), 4);
|
||||||
|
assert!(DELETE_ALBUM_BLOCKS.contains(&ActiveLidarrBlock::DeleteAlbumPrompt));
|
||||||
|
assert!(DELETE_ALBUM_BLOCKS.contains(&ActiveLidarrBlock::DeleteAlbumConfirmPrompt));
|
||||||
|
assert!(DELETE_ALBUM_BLOCKS.contains(&ActiveLidarrBlock::DeleteAlbumToggleDeleteFile));
|
||||||
|
assert!(DELETE_ALBUM_BLOCKS.contains(&ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_selection_blocks_ordering() {
|
||||||
|
let mut delete_album_block_iter = DELETE_ALBUM_SELECTION_BLOCKS.iter();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
delete_album_block_iter.next().unwrap(),
|
||||||
|
&[ActiveLidarrBlock::DeleteAlbumToggleDeleteFile]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
delete_album_block_iter.next().unwrap(),
|
||||||
|
&[ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
delete_album_block_iter.next().unwrap(),
|
||||||
|
&[ActiveLidarrBlock::DeleteAlbumConfirmPrompt]
|
||||||
|
);
|
||||||
|
assert_none!(delete_album_block_iter.next());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_edit_artist_blocks() {
|
fn test_edit_artist_blocks() {
|
||||||
assert_eq!(EDIT_ARTIST_BLOCKS.len(), 8);
|
assert_eq!(EDIT_ARTIST_BLOCKS.len(), 8);
|
||||||
|
|||||||
@@ -2,13 +2,50 @@ use strum::IntoEnumIterator;
|
|||||||
|
|
||||||
use super::lidarr_data::LidarrData;
|
use super::lidarr_data::LidarrData;
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
HorizontallyScrollableText, lidarr_models::NewItemMonitorType, stateful_list::StatefulList,
|
HorizontallyScrollableText,
|
||||||
|
lidarr_models::{MonitorType, NewItemMonitorType},
|
||||||
|
servarr_models::RootFolder,
|
||||||
|
stateful_list::StatefulList,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "modals_tests.rs"]
|
#[path = "modals_tests.rs"]
|
||||||
mod modals_tests;
|
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)]
|
#[derive(Default)]
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[cfg_attr(test, derive(Debug))]
|
||||||
pub struct EditArtistModal {
|
pub struct EditArtistModal {
|
||||||
|
|||||||
@@ -3,9 +3,66 @@ mod tests {
|
|||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
use pretty_assertions::{assert_eq, assert_str_eq};
|
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::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]
|
#[test]
|
||||||
fn test_edit_artist_modal_from_lidarr_data() {
|
fn test_edit_artist_modal_from_lidarr_data() {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use crate::models::{
|
|||||||
use crate::network::radarr_network::RadarrEvent;
|
use crate::network::radarr_network::RadarrEvent;
|
||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use itertools::Itertools;
|
||||||
use serde_json::Number;
|
use serde_json::Number;
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -112,9 +113,13 @@ impl RadarrData<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sorted_quality_profile_names(&self) -> Vec<String> {
|
pub fn sorted_quality_profile_names(&self) -> Vec<String> {
|
||||||
let mut names: Vec<String> = self.quality_profile_map.right_values().cloned().collect();
|
self
|
||||||
names.sort();
|
.quality_profile_map
|
||||||
names
|
.iter()
|
||||||
|
.sorted_by_key(|(id, _)| *id)
|
||||||
|
.map(|(_, name)| name)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,17 +82,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_sorted_quality_profile_names() {
|
fn test_sorted_quality_profile_names() {
|
||||||
let mut quality_profile_map = BiMap::new();
|
let mut quality_profile_map = BiMap::new();
|
||||||
quality_profile_map.insert(3, "test 3".to_owned());
|
quality_profile_map.insert(3, "test 1".to_owned());
|
||||||
quality_profile_map.insert(2, "test 2".to_owned());
|
quality_profile_map.insert(2, "test 2".to_owned());
|
||||||
quality_profile_map.insert(1, "test 1".to_owned());
|
quality_profile_map.insert(1, "test 3".to_owned());
|
||||||
let radarr_data = RadarrData {
|
let radarr_data = RadarrData {
|
||||||
quality_profile_map,
|
quality_profile_map,
|
||||||
..RadarrData::default()
|
..RadarrData::default()
|
||||||
};
|
};
|
||||||
let expected_quality_profile_vec = vec![
|
let expected_quality_profile_vec = vec![
|
||||||
"test 1".to_owned(),
|
|
||||||
"test 2".to_owned(),
|
|
||||||
"test 3".to_owned(),
|
"test 3".to_owned(),
|
||||||
|
"test 2".to_owned(),
|
||||||
|
"test 1".to_owned(),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_iter_eq!(
|
assert_iter_eq!(
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use itertools::Itertools;
|
||||||
use serde_json::Number;
|
use serde_json::Number;
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -119,15 +120,23 @@ impl SonarrData<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn sorted_quality_profile_names(&self) -> Vec<String> {
|
pub fn sorted_quality_profile_names(&self) -> Vec<String> {
|
||||||
let mut names: Vec<String> = self.quality_profile_map.right_values().cloned().collect();
|
self
|
||||||
names.sort();
|
.quality_profile_map
|
||||||
names
|
.iter()
|
||||||
|
.sorted_by_key(|(id, _)| *id)
|
||||||
|
.map(|(_, name)| name)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sorted_language_profile_names(&self) -> Vec<String> {
|
pub fn sorted_language_profile_names(&self) -> Vec<String> {
|
||||||
let mut names: Vec<String> = self.language_profiles_map.right_values().cloned().collect();
|
self
|
||||||
names.sort();
|
.language_profiles_map
|
||||||
names
|
.iter()
|
||||||
|
.sorted_by_key(|(id, _)| *id)
|
||||||
|
.map(|(_, name)| name)
|
||||||
|
.cloned()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,17 +98,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_sorted_quality_profile_names() {
|
fn test_sorted_quality_profile_names() {
|
||||||
let mut quality_profile_map = BiMap::new();
|
let mut quality_profile_map = BiMap::new();
|
||||||
quality_profile_map.insert(3, "test 3".to_owned());
|
quality_profile_map.insert(3, "test 1".to_owned());
|
||||||
quality_profile_map.insert(2, "test 2".to_owned());
|
quality_profile_map.insert(2, "test 2".to_owned());
|
||||||
quality_profile_map.insert(1, "test 1".to_owned());
|
quality_profile_map.insert(1, "test 3".to_owned());
|
||||||
let sonarr_data = SonarrData {
|
let sonarr_data = SonarrData {
|
||||||
quality_profile_map,
|
quality_profile_map,
|
||||||
..SonarrData::default()
|
..SonarrData::default()
|
||||||
};
|
};
|
||||||
let expected_quality_profile_vec = vec![
|
let expected_quality_profile_vec = vec![
|
||||||
"test 1".to_owned(),
|
|
||||||
"test 2".to_owned(),
|
|
||||||
"test 3".to_owned(),
|
"test 3".to_owned(),
|
||||||
|
"test 2".to_owned(),
|
||||||
|
"test 1".to_owned(),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_iter_eq!(
|
assert_iter_eq!(
|
||||||
@@ -120,17 +120,17 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_sorted_language_profile_names() {
|
fn test_sorted_language_profile_names() {
|
||||||
let mut language_profiles_map = BiMap::new();
|
let mut language_profiles_map = BiMap::new();
|
||||||
language_profiles_map.insert(3, "test 3".to_owned());
|
language_profiles_map.insert(3, "test 1".to_owned());
|
||||||
language_profiles_map.insert(2, "test 2".to_owned());
|
language_profiles_map.insert(2, "test 2".to_owned());
|
||||||
language_profiles_map.insert(1, "test 1".to_owned());
|
language_profiles_map.insert(1, "test 3".to_owned());
|
||||||
let sonarr_data = SonarrData {
|
let sonarr_data = SonarrData {
|
||||||
language_profiles_map,
|
language_profiles_map,
|
||||||
..SonarrData::default()
|
..SonarrData::default()
|
||||||
};
|
};
|
||||||
let expected_language_profiles_vec = vec![
|
let expected_language_profiles_vec = vec![
|
||||||
"test 1".to_owned(),
|
|
||||||
"test 2".to_owned(),
|
|
||||||
"test 3".to_owned(),
|
"test 3".to_owned(),
|
||||||
|
"test 2".to_owned(),
|
||||||
|
"test 1".to_owned(),
|
||||||
];
|
];
|
||||||
|
|
||||||
assert_iter_eq!(
|
assert_iter_eq!(
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ pub struct AddSeriesBody {
|
|||||||
pub root_folder_path: String,
|
pub root_folder_path: String,
|
||||||
pub quality_profile_id: i64,
|
pub quality_profile_id: i64,
|
||||||
pub language_profile_id: i64,
|
pub language_profile_id: i64,
|
||||||
pub series_type: String,
|
pub series_type: SeriesType,
|
||||||
pub season_folder: bool,
|
pub season_folder: bool,
|
||||||
pub tags: Vec<i64>,
|
pub tags: Vec<i64>,
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
@@ -68,7 +68,7 @@ pub struct AddSeriesSearchResultStatistics {
|
|||||||
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
|
#[derive(Default, Clone, Serialize, Debug, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AddSeriesOptions {
|
pub struct AddSeriesOptions {
|
||||||
pub monitor: String,
|
pub monitor: SeriesMonitor,
|
||||||
pub search_for_cutoff_unmet_episodes: bool,
|
pub search_for_cutoff_unmet_episodes: bool,
|
||||||
pub search_for_missing_episodes: bool,
|
pub search_for_missing_episodes: bool,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::models::lidarr_models::{Album, DeleteParams, LidarrSerdeable};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::lidarr_network::lidarr_network_test_utils::test_utils::ALBUM_JSON;
|
||||||
|
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||||
|
use mockito::Matcher;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_albums_event() {
|
||||||
|
let albums_json = json!([{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Test Album",
|
||||||
|
"foreignAlbumId": "test-foreign-album-id",
|
||||||
|
"monitored": true,
|
||||||
|
"anyReleaseOk": true,
|
||||||
|
"profileId": 1,
|
||||||
|
"duration": 180,
|
||||||
|
"albumType": "Album",
|
||||||
|
"genres": ["Classical"],
|
||||||
|
"ratings": {"votes": 15, "value": 8.4},
|
||||||
|
"releaseDate": "2023-01-01T00:00:00Z",
|
||||||
|
"statistics": {
|
||||||
|
"trackFileCount": 10,
|
||||||
|
"trackCount": 10,
|
||||||
|
"totalTrackCount": 10,
|
||||||
|
"sizeOnDisk": 1024,
|
||||||
|
"percentOfTracks": 99.9
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
let response: Vec<Album> = serde_json::from_value(albums_json.clone()).unwrap();
|
||||||
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
|
.returns(albums_json)
|
||||||
|
.query("artistId=1")
|
||||||
|
.build_for(LidarrEvent::GetAlbums(1))
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let result = network.handle_lidarr_event(LidarrEvent::GetAlbums(1)).await;
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
|
||||||
|
let LidarrSerdeable::Albums(albums) = result.unwrap() else {
|
||||||
|
panic!("Expected Albums");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(albums, response);
|
||||||
|
assert!(!app.lock().await.data.lidarr_data.albums.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_delete_album_event() {
|
||||||
|
let delete_album_params = DeleteParams {
|
||||||
|
id: 1,
|
||||||
|
delete_files: true,
|
||||||
|
add_import_list_exclusion: true,
|
||||||
|
};
|
||||||
|
let (async_server, app, _server) = MockServarrApi::delete()
|
||||||
|
.path("/1")
|
||||||
|
.query("deleteFiles=true&addImportListExclusion=true")
|
||||||
|
.build_for(LidarrEvent::DeleteAlbum(delete_album_params.clone()))
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::DeleteAlbum(delete_album_params))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
|
async_server.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_toggle_album_monitoring_event() {
|
||||||
|
let mut expected_body: Value = serde_json::from_str(ALBUM_JSON).unwrap();
|
||||||
|
*expected_body.get_mut("monitored").unwrap() = json!(false);
|
||||||
|
let (get_mock, app, mut server) = MockServarrApi::get()
|
||||||
|
.returns(serde_json::from_str(ALBUM_JSON).unwrap())
|
||||||
|
.path("/1")
|
||||||
|
.build_for(LidarrEvent::GetAlbums(1))
|
||||||
|
.await;
|
||||||
|
let put_mock = server
|
||||||
|
.mock("PUT", "/api/v1/album/1")
|
||||||
|
.match_body(Matcher::Json(expected_body))
|
||||||
|
.match_header("X-Api-Key", "test1234")
|
||||||
|
.with_status(202)
|
||||||
|
.create_async()
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert_ok!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::ToggleAlbumMonitoring(1))
|
||||||
|
.await
|
||||||
|
);
|
||||||
|
|
||||||
|
get_mock.assert_async().await;
|
||||||
|
put_mock.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_get_album_details_event() {
|
||||||
|
let expected_album: Album = serde_json::from_str(ALBUM_JSON).unwrap();
|
||||||
|
let (mock, app, _server) = MockServarrApi::get()
|
||||||
|
.returns(serde_json::from_str(ALBUM_JSON).unwrap())
|
||||||
|
.path("/1")
|
||||||
|
.build_for(LidarrEvent::GetAlbumDetails(1))
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
let result = network
|
||||||
|
.handle_lidarr_event(LidarrEvent::GetAlbumDetails(1))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
|
||||||
|
let LidarrSerdeable::Album(album) = result.unwrap() else {
|
||||||
|
panic!("Expected Album");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(album, expected_album);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
use crate::models::lidarr_models::{Album, DeleteParams};
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::{Network, RequestMethod};
|
||||||
|
use anyhow::Result;
|
||||||
|
use log::{debug, info, warn};
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_albums_network_tests.rs"]
|
||||||
|
mod lidarr_albums_network_tests;
|
||||||
|
|
||||||
|
impl Network<'_, '_> {
|
||||||
|
pub(in crate::network::lidarr_network) async fn get_albums(
|
||||||
|
&mut self,
|
||||||
|
artist_id: i64,
|
||||||
|
) -> Result<Vec<Album>> {
|
||||||
|
info!("Fetching albums for Lidarr artist with ID: {artist_id}");
|
||||||
|
let event = LidarrEvent::GetAlbums(artist_id);
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Get,
|
||||||
|
None::<()>,
|
||||||
|
None,
|
||||||
|
Some(format!("artistId={artist_id}")),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Vec<Album>>(request_props, |mut albums_vec, mut app| {
|
||||||
|
albums_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
app.data.lidarr_data.albums.set_items(albums_vec);
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn get_album_details(
|
||||||
|
&mut self,
|
||||||
|
album_id: i64,
|
||||||
|
) -> Result<Album> {
|
||||||
|
info!("Fetching details for Lidarr album with ID: {album_id}");
|
||||||
|
let event = LidarrEvent::GetAlbumDetails(album_id);
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Get,
|
||||||
|
None::<()>,
|
||||||
|
Some(format!("/{album_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Album>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn delete_album(
|
||||||
|
&mut self,
|
||||||
|
delete_album_params: DeleteParams,
|
||||||
|
) -> Result<()> {
|
||||||
|
let event = LidarrEvent::DeleteAlbum(DeleteParams::default());
|
||||||
|
let DeleteParams {
|
||||||
|
id,
|
||||||
|
delete_files,
|
||||||
|
add_import_list_exclusion,
|
||||||
|
} = delete_album_params;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Deleting Lidarr album with ID: {id} with deleteFiles={delete_files} and addImportListExclusion={add_import_list_exclusion}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Delete,
|
||||||
|
None::<()>,
|
||||||
|
Some(format!("/{id}")),
|
||||||
|
Some(format!(
|
||||||
|
"deleteFiles={delete_files}&addImportListExclusion={add_import_list_exclusion}"
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), ()>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn toggle_album_monitoring(
|
||||||
|
&mut self,
|
||||||
|
album_id: i64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let event = LidarrEvent::ToggleAlbumMonitoring(album_id);
|
||||||
|
info!("Toggling album monitoring for album with ID: {album_id}");
|
||||||
|
info!("Fetching album details for album with ID: {album_id}");
|
||||||
|
|
||||||
|
let detail_event = LidarrEvent::GetAlbums(0);
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
detail_event,
|
||||||
|
RequestMethod::Get,
|
||||||
|
None::<()>,
|
||||||
|
Some(format!("/{album_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Value>(request_props, |detailed_album_body, _| {
|
||||||
|
response = detailed_album_body.to_string()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Constructing toggle album monitoring body");
|
||||||
|
|
||||||
|
match serde_json::from_str::<Value>(&response) {
|
||||||
|
Ok(mut detailed_album_body) => {
|
||||||
|
let monitored = detailed_album_body
|
||||||
|
.get("monitored")
|
||||||
|
.unwrap()
|
||||||
|
.as_bool()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
*detailed_album_body.get_mut("monitored").unwrap() = json!(!monitored);
|
||||||
|
|
||||||
|
debug!("Toggle album monitoring body: {detailed_album_body:?}");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Put,
|
||||||
|
Some(detailed_album_body),
|
||||||
|
Some(format!("/{album_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
warn!("Request for detailed album body was interrupted");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+151
-5
@@ -1,8 +1,8 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
AddArtistSearchResult, Artist, DeleteArtistParams, EditArtistParams, LidarrSerdeable,
|
AddArtistBody, AddArtistOptions, AddArtistSearchResult, Artist, DeleteParams, EditArtistParams,
|
||||||
NewItemMonitorType,
|
LidarrSerdeable, MonitorType, NewItemMonitorType,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
use crate::network::NetworkResource;
|
use crate::network::NetworkResource;
|
||||||
@@ -54,7 +54,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_delete_artist_event() {
|
async fn test_handle_delete_artist_event() {
|
||||||
let delete_artist_params = DeleteArtistParams {
|
let delete_artist_params = DeleteParams {
|
||||||
id: 1,
|
id: 1,
|
||||||
delete_files: true,
|
delete_files: true,
|
||||||
add_import_list_exclusion: true,
|
add_import_list_exclusion: true,
|
||||||
@@ -134,11 +134,10 @@ mod tests {
|
|||||||
app.lock().await.server_tabs.set_index(2);
|
app.lock().await.server_tabs.set_index(2);
|
||||||
let mut network = test_network(&app);
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
assert!(
|
assert_ok!(
|
||||||
network
|
network
|
||||||
.handle_lidarr_event(LidarrEvent::ToggleArtistMonitoring(1))
|
.handle_lidarr_event(LidarrEvent::ToggleArtistMonitoring(1))
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
get_mock.assert_async().await;
|
get_mock.assert_async().await;
|
||||||
@@ -167,6 +166,52 @@ mod tests {
|
|||||||
mock.assert_async().await;
|
mock.assert_async().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_update_and_scan_artist_event() {
|
||||||
|
let (mock, app, _server) = MockServarrApi::post()
|
||||||
|
.with_request_body(json!({
|
||||||
|
"name": "RefreshArtist",
|
||||||
|
"artistId": 1
|
||||||
|
}))
|
||||||
|
.returns(json!({}))
|
||||||
|
.build_for(LidarrEvent::UpdateAndScanArtist(1))
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::UpdateAndScanArtist(1))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_trigger_automatic_artist_search_event() {
|
||||||
|
let (mock, app, _server) = MockServarrApi::post()
|
||||||
|
.with_request_body(json!({
|
||||||
|
"name": "ArtistSearch",
|
||||||
|
"artistId": 1
|
||||||
|
}))
|
||||||
|
.returns(json!({}))
|
||||||
|
.build_for(LidarrEvent::TriggerAutomaticArtistSearch(1))
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::TriggerAutomaticArtistSearch(1))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_edit_artist_event() {
|
async fn test_handle_edit_artist_event() {
|
||||||
let mut expected_body: Value = serde_json::from_str(ARTIST_JSON).unwrap();
|
let mut expected_body: Value = serde_json::from_str(ARTIST_JSON).unwrap();
|
||||||
@@ -439,4 +484,105 @@ mod tests {
|
|||||||
assert_some!(&app.data.lidarr_data.add_searched_artists);
|
assert_some!(&app.data.lidarr_data.add_searched_artists);
|
||||||
assert_is_empty!(app.data.lidarr_data.add_searched_artists.as_ref().unwrap());
|
assert_is_empty!(app.data.lidarr_data.add_searched_artists.as_ref().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_add_artist_event() {
|
||||||
|
let add_artist_body = AddArtistBody {
|
||||||
|
foreign_artist_id: "test-foreign-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
monitored: true,
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
tags: Vec::default(),
|
||||||
|
tag_input_string: Some("usenet, testing".to_owned()),
|
||||||
|
add_options: AddArtistOptions {
|
||||||
|
monitor: MonitorType::All,
|
||||||
|
monitor_new_items: NewItemMonitorType::All,
|
||||||
|
search_for_missing_albums: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let expected_body = json!({
|
||||||
|
"foreignArtistId": "test-foreign-id",
|
||||||
|
"artistName": "Test Artist",
|
||||||
|
"monitored": true,
|
||||||
|
"rootFolderPath": "/music",
|
||||||
|
"qualityProfileId": 1,
|
||||||
|
"metadataProfileId": 1,
|
||||||
|
"tags": [1, 2],
|
||||||
|
"addOptions": {
|
||||||
|
"monitor": "all",
|
||||||
|
"monitorNewItems": "all",
|
||||||
|
"searchForMissingAlbums": true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let (mock, app, _server) = MockServarrApi::post()
|
||||||
|
.with_request_body(expected_body)
|
||||||
|
.returns(json!({"id": 1}))
|
||||||
|
.build_for(LidarrEvent::AddArtist(AddArtistBody::default()))
|
||||||
|
.await;
|
||||||
|
app.lock().await.data.lidarr_data.tags_map =
|
||||||
|
BiMap::from_iter([(1, "usenet".to_owned()), (2, "testing".to_owned())]);
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::AddArtist(add_artist_body))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_handle_add_artist_event_does_not_overwrite_tags_vec_when_tag_input_string_is_none()
|
||||||
|
{
|
||||||
|
let add_artist_body = AddArtistBody {
|
||||||
|
foreign_artist_id: "test-foreign-id".to_owned(),
|
||||||
|
artist_name: "Test Artist".to_owned(),
|
||||||
|
monitored: true,
|
||||||
|
root_folder_path: "/music".to_owned(),
|
||||||
|
quality_profile_id: 1,
|
||||||
|
metadata_profile_id: 1,
|
||||||
|
tags: vec![1, 2],
|
||||||
|
tag_input_string: None,
|
||||||
|
add_options: AddArtistOptions {
|
||||||
|
monitor: MonitorType::All,
|
||||||
|
monitor_new_items: NewItemMonitorType::All,
|
||||||
|
search_for_missing_albums: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let expected_body = json!({
|
||||||
|
"foreignArtistId": "test-foreign-id",
|
||||||
|
"artistName": "Test Artist",
|
||||||
|
"monitored": true,
|
||||||
|
"rootFolderPath": "/music",
|
||||||
|
"qualityProfileId": 1,
|
||||||
|
"metadataProfileId": 1,
|
||||||
|
"tags": [1, 2],
|
||||||
|
"addOptions": {
|
||||||
|
"monitor": "all",
|
||||||
|
"monitorNewItems": "all",
|
||||||
|
"searchForMissingAlbums": true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let (mock, app, _server) = MockServarrApi::post()
|
||||||
|
.with_request_body(expected_body)
|
||||||
|
.returns(json!({"id": 1}))
|
||||||
|
.build_for(LidarrEvent::AddArtist(add_artist_body.clone()))
|
||||||
|
.await;
|
||||||
|
app.lock().await.server_tabs.set_index(2);
|
||||||
|
let mut network = test_network(&app);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
network
|
||||||
|
.handle_lidarr_event(LidarrEvent::AddArtist(add_artist_body))
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
|
||||||
|
mock.assert_async().await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use log::{debug, info, warn};
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::{
|
||||||
|
AddArtistBody, AddArtistSearchResult, Artist, DeleteParams, EditArtistParams, LidarrCommandBody,
|
||||||
|
};
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
||||||
|
use crate::models::stateful_table::StatefulTable;
|
||||||
|
use crate::network::lidarr_network::LidarrEvent;
|
||||||
|
use crate::network::{Network, RequestMethod};
|
||||||
|
use urlencoding::encode;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "lidarr_artists_network_tests.rs"]
|
||||||
|
mod lidarr_artists_network_tests;
|
||||||
|
|
||||||
|
impl Network<'_, '_> {
|
||||||
|
pub(in crate::network::lidarr_network) async fn delete_artist(
|
||||||
|
&mut self,
|
||||||
|
delete_artist_params: DeleteParams,
|
||||||
|
) -> Result<()> {
|
||||||
|
let event = LidarrEvent::DeleteArtist(DeleteParams::default());
|
||||||
|
let DeleteParams {
|
||||||
|
id,
|
||||||
|
delete_files,
|
||||||
|
add_import_list_exclusion,
|
||||||
|
} = delete_artist_params;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Deleting Lidarr artist with ID: {id} with deleteFiles={delete_files} and addImportListExclusion={add_import_list_exclusion}"
|
||||||
|
);
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Delete,
|
||||||
|
None::<()>,
|
||||||
|
Some(format!("/{id}")),
|
||||||
|
Some(format!(
|
||||||
|
"deleteFiles={delete_files}&addImportListExclusion={add_import_list_exclusion}"
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), ()>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn list_artists(&mut self) -> Result<Vec<Artist>> {
|
||||||
|
info!("Fetching Lidarr artists");
|
||||||
|
let event = LidarrEvent::ListArtists;
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Vec<Artist>>(request_props, |mut artists_vec, mut app| {
|
||||||
|
if !matches!(
|
||||||
|
app.get_current_route(),
|
||||||
|
Route::Lidarr(ActiveLidarrBlock::ArtistsSortPrompt, _)
|
||||||
|
) {
|
||||||
|
artists_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
app.data.lidarr_data.artists.set_items(artists_vec);
|
||||||
|
app.data.lidarr_data.artists.apply_sorting_toggle(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn get_artist_details(
|
||||||
|
&mut self,
|
||||||
|
artist_id: i64,
|
||||||
|
) -> Result<Artist> {
|
||||||
|
info!("Fetching details for Lidarr artist with ID: {artist_id}");
|
||||||
|
let event = LidarrEvent::GetArtistDetails(artist_id);
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Get,
|
||||||
|
None::<()>,
|
||||||
|
Some(format!("/{artist_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Artist>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn toggle_artist_monitoring(
|
||||||
|
&mut self,
|
||||||
|
artist_id: i64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let event = LidarrEvent::ToggleArtistMonitoring(artist_id);
|
||||||
|
|
||||||
|
let detail_event = LidarrEvent::GetArtistDetails(artist_id);
|
||||||
|
info!("Toggling artist monitoring for artist with ID: {artist_id}");
|
||||||
|
info!("Fetching artist details for artist with ID: {artist_id}");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
detail_event,
|
||||||
|
RequestMethod::Get,
|
||||||
|
None::<()>,
|
||||||
|
Some(format!("/{artist_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Value>(request_props, |detailed_artist_body, _| {
|
||||||
|
response = detailed_artist_body.to_string()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Constructing toggle artist monitoring body");
|
||||||
|
|
||||||
|
match serde_json::from_str::<Value>(&response) {
|
||||||
|
Ok(mut detailed_artist_body) => {
|
||||||
|
let monitored = detailed_artist_body
|
||||||
|
.get("monitored")
|
||||||
|
.unwrap()
|
||||||
|
.as_bool()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
*detailed_artist_body.get_mut("monitored").unwrap() = json!(!monitored);
|
||||||
|
|
||||||
|
debug!("Toggle artist monitoring body: {detailed_artist_body:?}");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Put,
|
||||||
|
Some(detailed_artist_body),
|
||||||
|
Some(format!("/{artist_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
warn!("Request for detailed artist body was interrupted");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn update_all_artists(&mut self) -> Result<Value> {
|
||||||
|
info!("Updating all artists");
|
||||||
|
let event = LidarrEvent::UpdateAllArtists;
|
||||||
|
let body = LidarrCommandBody {
|
||||||
|
name: "RefreshArtist".to_owned(),
|
||||||
|
..LidarrCommandBody::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(event, RequestMethod::Post, Some(body), None, None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<LidarrCommandBody, Value>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn update_and_scan_artist(
|
||||||
|
&mut self,
|
||||||
|
artist_id: i64,
|
||||||
|
) -> Result<Value> {
|
||||||
|
let event = LidarrEvent::UpdateAndScanArtist(artist_id);
|
||||||
|
info!("Updating and scanning artist with ID: {artist_id}");
|
||||||
|
let body = LidarrCommandBody {
|
||||||
|
name: "RefreshArtist".to_owned(),
|
||||||
|
artist_id: Some(artist_id),
|
||||||
|
..LidarrCommandBody::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(event, RequestMethod::Post, Some(body), None, None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<LidarrCommandBody, Value>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn trigger_automatic_artist_search(
|
||||||
|
&mut self,
|
||||||
|
artist_id: i64,
|
||||||
|
) -> Result<Value> {
|
||||||
|
let event = LidarrEvent::TriggerAutomaticArtistSearch(artist_id);
|
||||||
|
info!("Searching indexers for artist with ID: {artist_id}");
|
||||||
|
let body = LidarrCommandBody {
|
||||||
|
name: "ArtistSearch".to_owned(),
|
||||||
|
artist_id: Some(artist_id),
|
||||||
|
..LidarrCommandBody::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(event, RequestMethod::Post, Some(body), None, None)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<LidarrCommandBody, Value>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn search_artist(
|
||||||
|
&mut self,
|
||||||
|
query: String,
|
||||||
|
) -> Result<Vec<AddArtistSearchResult>> {
|
||||||
|
info!("Searching for artist: {query}");
|
||||||
|
let event = LidarrEvent::SearchNewArtist(String::new());
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Get,
|
||||||
|
None::<()>,
|
||||||
|
None,
|
||||||
|
Some(format!("term={}", encode(&query))),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.handle_request::<(), Vec<AddArtistSearchResult>>(request_props, |artist_vec, mut app| {
|
||||||
|
if artist_vec.is_empty() {
|
||||||
|
app.pop_and_push_navigation_stack(ActiveLidarrBlock::AddArtistEmptySearchResults.into());
|
||||||
|
} else if let Some(add_searched_artists) =
|
||||||
|
app.data.lidarr_data.add_searched_artists.as_mut()
|
||||||
|
{
|
||||||
|
add_searched_artists.set_items(artist_vec);
|
||||||
|
} else {
|
||||||
|
let mut add_searched_artists = StatefulTable::default();
|
||||||
|
add_searched_artists.set_items(artist_vec);
|
||||||
|
app.data.lidarr_data.add_searched_artists = Some(add_searched_artists);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
self.app.lock().await.data.lidarr_data.add_searched_artists = Some(StatefulTable::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn add_artist(
|
||||||
|
&mut self,
|
||||||
|
mut add_artist_body: AddArtistBody,
|
||||||
|
) -> Result<Value> {
|
||||||
|
info!("Adding Lidarr artist: {}", add_artist_body.artist_name);
|
||||||
|
if let Some(tag_input_str) = add_artist_body.tag_input_string.as_ref() {
|
||||||
|
let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await;
|
||||||
|
add_artist_body.tags = tag_ids_vec;
|
||||||
|
}
|
||||||
|
let event = LidarrEvent::AddArtist(AddArtistBody::default());
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Post,
|
||||||
|
Some(add_artist_body),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<AddArtistBody, Value>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::network::lidarr_network) async fn edit_artist(
|
||||||
|
&mut self,
|
||||||
|
mut edit_artist_params: EditArtistParams,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Editing Lidarr artist");
|
||||||
|
if let Some(tag_input_str) = edit_artist_params.tag_input_string.as_ref() {
|
||||||
|
let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await;
|
||||||
|
edit_artist_params.tags = Some(tag_ids_vec);
|
||||||
|
}
|
||||||
|
let artist_id = edit_artist_params.artist_id;
|
||||||
|
let detail_event = LidarrEvent::GetArtistDetails(artist_id);
|
||||||
|
let event = LidarrEvent::EditArtist(EditArtistParams::default());
|
||||||
|
info!("Fetching artist details for artist with ID: {artist_id}");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
detail_event,
|
||||||
|
RequestMethod::Get,
|
||||||
|
None::<()>,
|
||||||
|
Some(format!("/{artist_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut response = String::new();
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<(), Value>(request_props, |detailed_artist_body, _| {
|
||||||
|
response = detailed_artist_body.to_string()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
info!("Constructing edit artist body");
|
||||||
|
|
||||||
|
let mut detailed_artist_body: Value = serde_json::from_str(&response)?;
|
||||||
|
let (
|
||||||
|
monitored,
|
||||||
|
monitor_new_items,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
root_folder_path,
|
||||||
|
tags,
|
||||||
|
) = {
|
||||||
|
let monitored = edit_artist_params.monitored.unwrap_or(
|
||||||
|
detailed_artist_body["monitored"]
|
||||||
|
.as_bool()
|
||||||
|
.expect("Unable to deserialize 'monitored'"),
|
||||||
|
);
|
||||||
|
let monitor_new_items = edit_artist_params.monitor_new_items.unwrap_or_else(|| {
|
||||||
|
serde_json::from_value(detailed_artist_body["monitorNewItems"].clone())
|
||||||
|
.expect("Unable to deserialize 'monitorNewItems'")
|
||||||
|
});
|
||||||
|
let quality_profile_id = edit_artist_params.quality_profile_id.unwrap_or_else(|| {
|
||||||
|
detailed_artist_body["qualityProfileId"]
|
||||||
|
.as_i64()
|
||||||
|
.expect("Unable to deserialize 'qualityProfileId'")
|
||||||
|
});
|
||||||
|
let metadata_profile_id = edit_artist_params.metadata_profile_id.unwrap_or_else(|| {
|
||||||
|
detailed_artist_body["metadataProfileId"]
|
||||||
|
.as_i64()
|
||||||
|
.expect("Unable to deserialize 'metadataProfileId'")
|
||||||
|
});
|
||||||
|
let root_folder_path = edit_artist_params.root_folder_path.unwrap_or_else(|| {
|
||||||
|
detailed_artist_body["path"]
|
||||||
|
.as_str()
|
||||||
|
.expect("Unable to deserialize 'path'")
|
||||||
|
.to_owned()
|
||||||
|
});
|
||||||
|
let tags = if edit_artist_params.clear_tags {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
edit_artist_params.tags.unwrap_or(
|
||||||
|
detailed_artist_body["tags"]
|
||||||
|
.as_array()
|
||||||
|
.expect("Unable to deserialize 'tags'")
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.as_i64().expect("Unable to deserialize tag ID"))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
monitored,
|
||||||
|
monitor_new_items,
|
||||||
|
quality_profile_id,
|
||||||
|
metadata_profile_id,
|
||||||
|
root_folder_path,
|
||||||
|
tags,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
*detailed_artist_body.get_mut("monitored").unwrap() = json!(monitored);
|
||||||
|
*detailed_artist_body.get_mut("monitorNewItems").unwrap() = json!(monitor_new_items);
|
||||||
|
*detailed_artist_body.get_mut("qualityProfileId").unwrap() = json!(quality_profile_id);
|
||||||
|
*detailed_artist_body.get_mut("metadataProfileId").unwrap() = json!(metadata_profile_id);
|
||||||
|
*detailed_artist_body.get_mut("path").unwrap() = json!(root_folder_path);
|
||||||
|
*detailed_artist_body.get_mut("tags").unwrap() = json!(tags);
|
||||||
|
|
||||||
|
debug!("Edit artist body: {detailed_artist_body:?}");
|
||||||
|
|
||||||
|
let request_props = self
|
||||||
|
.request_props_from(
|
||||||
|
event,
|
||||||
|
RequestMethod::Put,
|
||||||
|
Some(detailed_artist_body),
|
||||||
|
Some(format!("/{artist_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self
|
||||||
|
.handle_request::<Value, ()>(request_props, |_, _| ())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,330 +1,2 @@
|
|||||||
use anyhow::Result;
|
mod albums;
|
||||||
use log::{debug, info, warn};
|
mod artists;
|
||||||
use serde_json::{Value, json};
|
|
||||||
|
|
||||||
use crate::models::Route;
|
|
||||||
use crate::models::lidarr_models::{
|
|
||||||
AddArtistSearchResult, Artist, DeleteArtistParams, EditArtistParams,
|
|
||||||
};
|
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::ActiveLidarrBlock;
|
|
||||||
use crate::models::servarr_models::CommandBody;
|
|
||||||
use crate::models::stateful_table::StatefulTable;
|
|
||||||
use crate::network::lidarr_network::LidarrEvent;
|
|
||||||
use crate::network::{Network, RequestMethod};
|
|
||||||
use urlencoding::encode;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "lidarr_library_network_tests.rs"]
|
|
||||||
mod lidarr_library_network_tests;
|
|
||||||
|
|
||||||
impl Network<'_, '_> {
|
|
||||||
pub(in crate::network::lidarr_network) async fn delete_artist(
|
|
||||||
&mut self,
|
|
||||||
delete_artist_params: DeleteArtistParams,
|
|
||||||
) -> Result<()> {
|
|
||||||
let event = LidarrEvent::DeleteArtist(DeleteArtistParams::default());
|
|
||||||
let DeleteArtistParams {
|
|
||||||
id,
|
|
||||||
delete_files,
|
|
||||||
add_import_list_exclusion,
|
|
||||||
} = delete_artist_params;
|
|
||||||
|
|
||||||
info!(
|
|
||||||
"Deleting Lidarr artist with ID: {id} with deleteFiles={delete_files} and addImportListExclusion={add_import_list_exclusion}"
|
|
||||||
);
|
|
||||||
|
|
||||||
let request_props = self
|
|
||||||
.request_props_from(
|
|
||||||
event,
|
|
||||||
RequestMethod::Delete,
|
|
||||||
None::<()>,
|
|
||||||
Some(format!("/{id}")),
|
|
||||||
Some(format!(
|
|
||||||
"deleteFiles={delete_files}&addImportListExclusion={add_import_list_exclusion}"
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
self
|
|
||||||
.handle_request::<(), ()>(request_props, |_, _| ())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::network::lidarr_network) async fn list_artists(&mut self) -> Result<Vec<Artist>> {
|
|
||||||
info!("Fetching Lidarr artists");
|
|
||||||
let event = LidarrEvent::ListArtists;
|
|
||||||
|
|
||||||
let request_props = self
|
|
||||||
.request_props_from(event, RequestMethod::Get, None::<()>, None, None)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
self
|
|
||||||
.handle_request::<(), Vec<Artist>>(request_props, |mut artists_vec, mut app| {
|
|
||||||
if !matches!(
|
|
||||||
app.get_current_route(),
|
|
||||||
Route::Lidarr(ActiveLidarrBlock::ArtistsSortPrompt, _)
|
|
||||||
) {
|
|
||||||
artists_vec.sort_by(|a, b| a.id.cmp(&b.id));
|
|
||||||
app.data.lidarr_data.artists.set_items(artists_vec);
|
|
||||||
app.data.lidarr_data.artists.apply_sorting_toggle(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::network::lidarr_network) async fn get_artist_details(
|
|
||||||
&mut self,
|
|
||||||
artist_id: i64,
|
|
||||||
) -> Result<Artist> {
|
|
||||||
info!("Fetching details for Lidarr artist with ID: {artist_id}");
|
|
||||||
let event = LidarrEvent::GetArtistDetails(artist_id);
|
|
||||||
|
|
||||||
let request_props = self
|
|
||||||
.request_props_from(
|
|
||||||
event,
|
|
||||||
RequestMethod::Get,
|
|
||||||
None::<()>,
|
|
||||||
Some(format!("/{artist_id}")),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
self
|
|
||||||
.handle_request::<(), Artist>(request_props, |_, _| ())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::network::lidarr_network) async fn toggle_artist_monitoring(
|
|
||||||
&mut self,
|
|
||||||
artist_id: i64,
|
|
||||||
) -> Result<()> {
|
|
||||||
let event = LidarrEvent::ToggleArtistMonitoring(artist_id);
|
|
||||||
|
|
||||||
let detail_event = LidarrEvent::GetArtistDetails(artist_id);
|
|
||||||
info!("Toggling artist monitoring for artist with ID: {artist_id}");
|
|
||||||
info!("Fetching artist details for artist with ID: {artist_id}");
|
|
||||||
|
|
||||||
let request_props = self
|
|
||||||
.request_props_from(
|
|
||||||
detail_event,
|
|
||||||
RequestMethod::Get,
|
|
||||||
None::<()>,
|
|
||||||
Some(format!("/{artist_id}")),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut response = String::new();
|
|
||||||
|
|
||||||
self
|
|
||||||
.handle_request::<(), Value>(request_props, |detailed_artist_body, _| {
|
|
||||||
response = detailed_artist_body.to_string()
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!("Constructing toggle artist monitoring body");
|
|
||||||
|
|
||||||
match serde_json::from_str::<Value>(&response) {
|
|
||||||
Ok(mut detailed_artist_body) => {
|
|
||||||
let monitored = detailed_artist_body
|
|
||||||
.get("monitored")
|
|
||||||
.unwrap()
|
|
||||||
.as_bool()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
*detailed_artist_body.get_mut("monitored").unwrap() = json!(!monitored);
|
|
||||||
|
|
||||||
debug!("Toggle artist monitoring body: {detailed_artist_body:?}");
|
|
||||||
|
|
||||||
let request_props = self
|
|
||||||
.request_props_from(
|
|
||||||
event,
|
|
||||||
RequestMethod::Put,
|
|
||||||
Some(detailed_artist_body),
|
|
||||||
Some(format!("/{artist_id}")),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
self
|
|
||||||
.handle_request::<Value, ()>(request_props, |_, _| ())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
warn!("Request for detailed artist body was interrupted");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::network::lidarr_network) async fn update_all_artists(&mut self) -> Result<Value> {
|
|
||||||
info!("Updating all artists");
|
|
||||||
let event = LidarrEvent::UpdateAllArtists;
|
|
||||||
let body = CommandBody {
|
|
||||||
name: "RefreshArtist".to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let request_props = self
|
|
||||||
.request_props_from(event, RequestMethod::Post, Some(body), None, None)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
self
|
|
||||||
.handle_request::<CommandBody, Value>(request_props, |_, _| ())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::network::lidarr_network) async fn search_artist(
|
|
||||||
&mut self,
|
|
||||||
query: String,
|
|
||||||
) -> Result<Vec<AddArtistSearchResult>> {
|
|
||||||
info!("Searching for artist: {query}");
|
|
||||||
let event = LidarrEvent::SearchNewArtist(String::new());
|
|
||||||
|
|
||||||
let request_props = self
|
|
||||||
.request_props_from(
|
|
||||||
event,
|
|
||||||
RequestMethod::Get,
|
|
||||||
None::<()>,
|
|
||||||
None,
|
|
||||||
Some(format!("term={}", encode(&query))),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let result = self
|
|
||||||
.handle_request::<(), Vec<AddArtistSearchResult>>(request_props, |artist_vec, mut app| {
|
|
||||||
if artist_vec.is_empty() {
|
|
||||||
app.pop_and_push_navigation_stack(ActiveLidarrBlock::AddArtistEmptySearchResults.into());
|
|
||||||
} else if let Some(add_searched_artists) =
|
|
||||||
app.data.lidarr_data.add_searched_artists.as_mut()
|
|
||||||
{
|
|
||||||
add_searched_artists.set_items(artist_vec);
|
|
||||||
} else {
|
|
||||||
let mut add_searched_artists = StatefulTable::default();
|
|
||||||
add_searched_artists.set_items(artist_vec);
|
|
||||||
app.data.lidarr_data.add_searched_artists = Some(add_searched_artists);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if result.is_err() {
|
|
||||||
self.app.lock().await.data.lidarr_data.add_searched_artists = Some(StatefulTable::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(in crate::network::lidarr_network) async fn edit_artist(
|
|
||||||
&mut self,
|
|
||||||
mut edit_artist_params: EditArtistParams,
|
|
||||||
) -> Result<()> {
|
|
||||||
info!("Editing Lidarr artist");
|
|
||||||
if let Some(tag_input_str) = edit_artist_params.tag_input_string.as_ref() {
|
|
||||||
let tag_ids_vec = self.extract_and_add_lidarr_tag_ids_vec(tag_input_str).await;
|
|
||||||
edit_artist_params.tags = Some(tag_ids_vec);
|
|
||||||
}
|
|
||||||
let artist_id = edit_artist_params.artist_id;
|
|
||||||
let detail_event = LidarrEvent::GetArtistDetails(artist_id);
|
|
||||||
let event = LidarrEvent::EditArtist(EditArtistParams::default());
|
|
||||||
info!("Fetching artist details for artist with ID: {artist_id}");
|
|
||||||
|
|
||||||
let request_props = self
|
|
||||||
.request_props_from(
|
|
||||||
detail_event,
|
|
||||||
RequestMethod::Get,
|
|
||||||
None::<()>,
|
|
||||||
Some(format!("/{artist_id}")),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let mut response = String::new();
|
|
||||||
|
|
||||||
self
|
|
||||||
.handle_request::<(), Value>(request_props, |detailed_artist_body, _| {
|
|
||||||
response = detailed_artist_body.to_string()
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
info!("Constructing edit artist body");
|
|
||||||
|
|
||||||
let mut detailed_artist_body: Value = serde_json::from_str(&response)?;
|
|
||||||
let (
|
|
||||||
monitored,
|
|
||||||
monitor_new_items,
|
|
||||||
quality_profile_id,
|
|
||||||
metadata_profile_id,
|
|
||||||
root_folder_path,
|
|
||||||
tags,
|
|
||||||
) = {
|
|
||||||
let monitored = edit_artist_params.monitored.unwrap_or(
|
|
||||||
detailed_artist_body["monitored"]
|
|
||||||
.as_bool()
|
|
||||||
.expect("Unable to deserialize 'monitored'"),
|
|
||||||
);
|
|
||||||
let monitor_new_items = edit_artist_params.monitor_new_items.unwrap_or_else(|| {
|
|
||||||
serde_json::from_value(detailed_artist_body["monitorNewItems"].clone())
|
|
||||||
.expect("Unable to deserialize 'monitorNewItems'")
|
|
||||||
});
|
|
||||||
let quality_profile_id = edit_artist_params.quality_profile_id.unwrap_or_else(|| {
|
|
||||||
detailed_artist_body["qualityProfileId"]
|
|
||||||
.as_i64()
|
|
||||||
.expect("Unable to deserialize 'qualityProfileId'")
|
|
||||||
});
|
|
||||||
let metadata_profile_id = edit_artist_params.metadata_profile_id.unwrap_or_else(|| {
|
|
||||||
detailed_artist_body["metadataProfileId"]
|
|
||||||
.as_i64()
|
|
||||||
.expect("Unable to deserialize 'metadataProfileId'")
|
|
||||||
});
|
|
||||||
let root_folder_path = edit_artist_params.root_folder_path.unwrap_or_else(|| {
|
|
||||||
detailed_artist_body["path"]
|
|
||||||
.as_str()
|
|
||||||
.expect("Unable to deserialize 'path'")
|
|
||||||
.to_owned()
|
|
||||||
});
|
|
||||||
let tags = if edit_artist_params.clear_tags {
|
|
||||||
vec![]
|
|
||||||
} else {
|
|
||||||
edit_artist_params.tags.unwrap_or(
|
|
||||||
detailed_artist_body["tags"]
|
|
||||||
.as_array()
|
|
||||||
.expect("Unable to deserialize 'tags'")
|
|
||||||
.iter()
|
|
||||||
.map(|item| item.as_i64().expect("Unable to deserialize tag ID"))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
monitored,
|
|
||||||
monitor_new_items,
|
|
||||||
quality_profile_id,
|
|
||||||
metadata_profile_id,
|
|
||||||
root_folder_path,
|
|
||||||
tags,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
*detailed_artist_body.get_mut("monitored").unwrap() = json!(monitored);
|
|
||||||
*detailed_artist_body.get_mut("monitorNewItems").unwrap() = json!(monitor_new_items);
|
|
||||||
*detailed_artist_body.get_mut("qualityProfileId").unwrap() = json!(quality_profile_id);
|
|
||||||
*detailed_artist_body.get_mut("metadataProfileId").unwrap() = json!(metadata_profile_id);
|
|
||||||
*detailed_artist_body.get_mut("path").unwrap() = json!(root_folder_path);
|
|
||||||
*detailed_artist_body.get_mut("tags").unwrap() = json!(tags);
|
|
||||||
|
|
||||||
debug!("Edit artist body: {detailed_artist_body:?}");
|
|
||||||
|
|
||||||
let request_props = self
|
|
||||||
.request_props_from(
|
|
||||||
event,
|
|
||||||
RequestMethod::Put,
|
|
||||||
Some(detailed_artist_body),
|
|
||||||
Some(format!("/{artist_id}")),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
self
|
|
||||||
.handle_request::<Value, ()>(request_props, |_, _| ())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
pub mod test_utils {
|
pub mod test_utils {
|
||||||
use crate::models::HorizontallyScrollableText;
|
use crate::models::HorizontallyScrollableText;
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
AddArtistSearchResult, Artist, ArtistStatistics, ArtistStatus, DownloadRecord, DownloadStatus,
|
AddArtistSearchResult, Album, AlbumStatistics, Artist, ArtistStatistics, ArtistStatus,
|
||||||
DownloadsResponse, EditArtistParams, Member, MetadataProfile, NewItemMonitorType, Ratings,
|
DownloadRecord, DownloadStatus, DownloadsResponse, EditArtistParams, Member, MetadataProfile,
|
||||||
SystemStatus,
|
NewItemMonitorType, Ratings, SystemStatus,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{QualityProfile, RootFolder, Tag};
|
use crate::models::servarr_models::{QualityProfile, RootFolder, Tag};
|
||||||
use bimap::BiMap;
|
use bimap::BiMap;
|
||||||
@@ -51,6 +51,27 @@ pub mod test_utils {
|
|||||||
}
|
}
|
||||||
}"#;
|
}"#;
|
||||||
|
|
||||||
|
pub const ALBUM_JSON: &str = r#"{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Test Album",
|
||||||
|
"foreignAlbumId": "test-foreign-album-id",
|
||||||
|
"monitored": true,
|
||||||
|
"anyReleaseOk": true,
|
||||||
|
"profileId": 1,
|
||||||
|
"duration": 180,
|
||||||
|
"albumType": "Album",
|
||||||
|
"genres": ["Classical"],
|
||||||
|
"ratings": {"votes": 15, "value": 8.4},
|
||||||
|
"releaseDate": "2023-01-01T00:00:00Z",
|
||||||
|
"statistics": {
|
||||||
|
"trackFileCount": 10,
|
||||||
|
"trackCount": 10,
|
||||||
|
"totalTrackCount": 10,
|
||||||
|
"sizeOnDisk": 1024,
|
||||||
|
"percentOfTracks": 99.9
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
pub fn member() -> Member {
|
pub fn member() -> Member {
|
||||||
Member {
|
Member {
|
||||||
name: Some("alex".to_owned()),
|
name: Some("alex".to_owned()),
|
||||||
@@ -199,4 +220,33 @@ pub mod test_utils {
|
|||||||
ratings: Some(ratings()),
|
ratings: Some(ratings()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn album_statistics() -> AlbumStatistics {
|
||||||
|
AlbumStatistics {
|
||||||
|
track_file_count: 10,
|
||||||
|
track_count: 10,
|
||||||
|
total_track_count: 10,
|
||||||
|
size_on_disk: 1024,
|
||||||
|
percent_of_tracks: 99.9,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn album() -> Album {
|
||||||
|
Album {
|
||||||
|
id: 1,
|
||||||
|
title: "Test Album".into(),
|
||||||
|
foreign_album_id: "test-foreign-album-id".to_string(),
|
||||||
|
monitored: true,
|
||||||
|
any_release_ok: true,
|
||||||
|
profile_id: 1,
|
||||||
|
duration: 180,
|
||||||
|
album_type: Some("Album".to_owned()),
|
||||||
|
genres: vec!["Classical".to_owned()],
|
||||||
|
ratings: Some(ratings()),
|
||||||
|
release_date: Some(DateTime::from(
|
||||||
|
DateTime::parse_from_rfc3339("2023-01-01T00:00:00Z").unwrap(),
|
||||||
|
)),
|
||||||
|
statistics: Some(album_statistics()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::models::lidarr_models::{LidarrSerdeable, MetadataProfile};
|
use crate::models::lidarr_models::{
|
||||||
|
AddArtistBody, DeleteParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
|
||||||
|
};
|
||||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||||
use crate::models::servarr_models::{QualityProfile, Tag};
|
use crate::models::servarr_models::{QualityProfile, Tag};
|
||||||
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
|
||||||
@@ -18,7 +20,10 @@ mod tests {
|
|||||||
#[values(
|
#[values(
|
||||||
LidarrEvent::GetArtistDetails(0),
|
LidarrEvent::GetArtistDetails(0),
|
||||||
LidarrEvent::ListArtists,
|
LidarrEvent::ListArtists,
|
||||||
LidarrEvent::ToggleArtistMonitoring(0)
|
LidarrEvent::AddArtist(AddArtistBody::default()),
|
||||||
|
LidarrEvent::ToggleArtistMonitoring(0),
|
||||||
|
LidarrEvent::DeleteArtist(DeleteParams::default()),
|
||||||
|
LidarrEvent::EditArtist(EditArtistParams::default())
|
||||||
)]
|
)]
|
||||||
event: LidarrEvent,
|
event: LidarrEvent,
|
||||||
) {
|
) {
|
||||||
@@ -45,10 +50,30 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
fn test_resource_command(#[values(LidarrEvent::UpdateAllArtists)] event: LidarrEvent) {
|
fn test_resource_command(
|
||||||
|
#[values(
|
||||||
|
LidarrEvent::UpdateAllArtists,
|
||||||
|
LidarrEvent::TriggerAutomaticArtistSearch(0),
|
||||||
|
LidarrEvent::UpdateAndScanArtist(0)
|
||||||
|
)]
|
||||||
|
event: LidarrEvent,
|
||||||
|
) {
|
||||||
assert_str_eq!(event.resource(), "/command");
|
assert_str_eq!(event.resource(), "/command");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
fn test_resource_album(
|
||||||
|
#[values(
|
||||||
|
LidarrEvent::GetAlbums(0),
|
||||||
|
LidarrEvent::ToggleAlbumMonitoring(0),
|
||||||
|
LidarrEvent::GetAlbumDetails(0),
|
||||||
|
LidarrEvent::DeleteAlbum(DeleteParams::default())
|
||||||
|
)]
|
||||||
|
event: LidarrEvent,
|
||||||
|
) {
|
||||||
|
assert_str_eq!(event.resource(), "/album");
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case(LidarrEvent::GetDiskSpace, "/diskspace")]
|
#[case(LidarrEvent::GetDiskSpace, "/diskspace")]
|
||||||
#[case(LidarrEvent::GetDownloads(500), "/queue")]
|
#[case(LidarrEvent::GetDownloads(500), "/queue")]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use log::info;
|
|||||||
|
|
||||||
use super::{NetworkEvent, NetworkResource};
|
use super::{NetworkEvent, NetworkResource};
|
||||||
use crate::models::lidarr_models::{
|
use crate::models::lidarr_models::{
|
||||||
DeleteArtistParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
|
AddArtistBody, DeleteParams, EditArtistParams, LidarrSerdeable, MetadataProfile,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_models::{QualityProfile, Tag};
|
use crate::models::servarr_models::{QualityProfile, Tag};
|
||||||
use crate::network::{Network, RequestMethod};
|
use crate::network::{Network, RequestMethod};
|
||||||
@@ -23,10 +23,14 @@ pub mod lidarr_network_test_utils;
|
|||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||||
pub enum LidarrEvent {
|
pub enum LidarrEvent {
|
||||||
|
AddArtist(AddArtistBody),
|
||||||
AddTag(String),
|
AddTag(String),
|
||||||
DeleteArtist(DeleteArtistParams),
|
DeleteAlbum(DeleteParams),
|
||||||
|
DeleteArtist(DeleteParams),
|
||||||
DeleteTag(i64),
|
DeleteTag(i64),
|
||||||
EditArtist(EditArtistParams),
|
EditArtist(EditArtistParams),
|
||||||
|
GetAlbums(i64),
|
||||||
|
GetAlbumDetails(i64),
|
||||||
GetArtistDetails(i64),
|
GetArtistDetails(i64),
|
||||||
GetDiskSpace,
|
GetDiskSpace,
|
||||||
GetDownloads(u64),
|
GetDownloads(u64),
|
||||||
@@ -40,8 +44,11 @@ pub enum LidarrEvent {
|
|||||||
HealthCheck,
|
HealthCheck,
|
||||||
ListArtists,
|
ListArtists,
|
||||||
SearchNewArtist(String),
|
SearchNewArtist(String),
|
||||||
|
ToggleAlbumMonitoring(i64),
|
||||||
ToggleArtistMonitoring(i64),
|
ToggleArtistMonitoring(i64),
|
||||||
|
TriggerAutomaticArtistSearch(i64),
|
||||||
UpdateAllArtists,
|
UpdateAllArtists,
|
||||||
|
UpdateAndScanArtist(i64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NetworkResource for LidarrEvent {
|
impl NetworkResource for LidarrEvent {
|
||||||
@@ -52,11 +59,18 @@ impl NetworkResource for LidarrEvent {
|
|||||||
| LidarrEvent::EditArtist(_)
|
| LidarrEvent::EditArtist(_)
|
||||||
| LidarrEvent::GetArtistDetails(_)
|
| LidarrEvent::GetArtistDetails(_)
|
||||||
| LidarrEvent::ListArtists
|
| LidarrEvent::ListArtists
|
||||||
|
| LidarrEvent::AddArtist(_)
|
||||||
| LidarrEvent::ToggleArtistMonitoring(_) => "/artist",
|
| LidarrEvent::ToggleArtistMonitoring(_) => "/artist",
|
||||||
|
LidarrEvent::GetAlbums(_)
|
||||||
|
| LidarrEvent::ToggleAlbumMonitoring(_)
|
||||||
|
| LidarrEvent::GetAlbumDetails(_)
|
||||||
|
| LidarrEvent::DeleteAlbum(_) => "/album",
|
||||||
LidarrEvent::GetDiskSpace => "/diskspace",
|
LidarrEvent::GetDiskSpace => "/diskspace",
|
||||||
LidarrEvent::GetDownloads(_) => "/queue",
|
LidarrEvent::GetDownloads(_) => "/queue",
|
||||||
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host",
|
||||||
LidarrEvent::UpdateAllArtists => "/command",
|
LidarrEvent::TriggerAutomaticArtistSearch(_)
|
||||||
|
| LidarrEvent::UpdateAllArtists
|
||||||
|
| LidarrEvent::UpdateAndScanArtist(_) => "/command",
|
||||||
LidarrEvent::GetMetadataProfiles => "/metadataprofile",
|
LidarrEvent::GetMetadataProfiles => "/metadataprofile",
|
||||||
LidarrEvent::GetQualityProfiles => "/qualityprofile",
|
LidarrEvent::GetQualityProfiles => "/qualityprofile",
|
||||||
LidarrEvent::GetRootFolders => "/rootfolder",
|
LidarrEvent::GetRootFolders => "/rootfolder",
|
||||||
@@ -80,6 +94,9 @@ impl Network<'_, '_> {
|
|||||||
) -> Result<LidarrSerdeable> {
|
) -> Result<LidarrSerdeable> {
|
||||||
match lidarr_event {
|
match lidarr_event {
|
||||||
LidarrEvent::AddTag(tag) => self.add_lidarr_tag(tag).await.map(LidarrSerdeable::from),
|
LidarrEvent::AddTag(tag) => self.add_lidarr_tag(tag).await.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::DeleteAlbum(params) => {
|
||||||
|
self.delete_album(params).await.map(LidarrSerdeable::from)
|
||||||
|
}
|
||||||
LidarrEvent::DeleteArtist(params) => {
|
LidarrEvent::DeleteArtist(params) => {
|
||||||
self.delete_artist(params).await.map(LidarrSerdeable::from)
|
self.delete_artist(params).await.map(LidarrSerdeable::from)
|
||||||
}
|
}
|
||||||
@@ -87,10 +104,17 @@ impl Network<'_, '_> {
|
|||||||
.delete_lidarr_tag(tag_id)
|
.delete_lidarr_tag(tag_id)
|
||||||
.await
|
.await
|
||||||
.map(LidarrSerdeable::from),
|
.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::GetAlbums(artist_id) => {
|
||||||
|
self.get_albums(artist_id).await.map(LidarrSerdeable::from)
|
||||||
|
}
|
||||||
LidarrEvent::GetArtistDetails(artist_id) => self
|
LidarrEvent::GetArtistDetails(artist_id) => self
|
||||||
.get_artist_details(artist_id)
|
.get_artist_details(artist_id)
|
||||||
.await
|
.await
|
||||||
.map(LidarrSerdeable::from),
|
.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::GetAlbumDetails(album_id) => self
|
||||||
|
.get_album_details(album_id)
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::GetDiskSpace => self.get_lidarr_diskspace().await.map(LidarrSerdeable::from),
|
LidarrEvent::GetDiskSpace => self.get_lidarr_diskspace().await.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::GetDownloads(count) => self
|
LidarrEvent::GetDownloads(count) => self
|
||||||
.get_lidarr_downloads(count)
|
.get_lidarr_downloads(count)
|
||||||
@@ -126,12 +150,25 @@ impl Network<'_, '_> {
|
|||||||
LidarrEvent::SearchNewArtist(query) => {
|
LidarrEvent::SearchNewArtist(query) => {
|
||||||
self.search_artist(query).await.map(LidarrSerdeable::from)
|
self.search_artist(query).await.map(LidarrSerdeable::from)
|
||||||
}
|
}
|
||||||
|
LidarrEvent::ToggleAlbumMonitoring(album_id) => self
|
||||||
|
.toggle_album_monitoring(album_id)
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::ToggleArtistMonitoring(artist_id) => self
|
LidarrEvent::ToggleArtistMonitoring(artist_id) => self
|
||||||
.toggle_artist_monitoring(artist_id)
|
.toggle_artist_monitoring(artist_id)
|
||||||
.await
|
.await
|
||||||
.map(LidarrSerdeable::from),
|
.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::TriggerAutomaticArtistSearch(artist_id) => self
|
||||||
|
.trigger_automatic_artist_search(artist_id)
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::UpdateAllArtists => self.update_all_artists().await.map(LidarrSerdeable::from),
|
LidarrEvent::UpdateAllArtists => self.update_all_artists().await.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::UpdateAndScanArtist(artist_id) => self
|
||||||
|
.update_and_scan_artist(artist_id)
|
||||||
|
.await
|
||||||
|
.map(LidarrSerdeable::from),
|
||||||
LidarrEvent::EditArtist(params) => self.edit_artist(params).await.map(LidarrSerdeable::from),
|
LidarrEvent::EditArtist(params) => self.edit_artist(params).await.map(LidarrSerdeable::from),
|
||||||
|
LidarrEvent::AddArtist(body) => self.add_artist(body).await.map(LidarrSerdeable::from),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::models::radarr_models::{
|
use crate::models::radarr_models::{
|
||||||
AddMovieBody, AddMovieOptions, Credit, DeleteMovieParams, DownloadRecord, EditMovieParams,
|
AddMovieBody, AddMovieOptions, Credit, DeleteMovieParams, DownloadRecord, EditMovieParams,
|
||||||
MinimumAvailability, Movie, MovieHistoryItem, RadarrReleaseDownloadBody,
|
MinimumAvailability, Movie, MovieHistoryItem, MovieMonitor, RadarrReleaseDownloadBody,
|
||||||
};
|
};
|
||||||
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
|
use crate::models::servarr_data::radarr::modals::MovieDetailsModal;
|
||||||
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
|
||||||
@@ -50,13 +50,13 @@ mod tests {
|
|||||||
tmdb_id: 1234,
|
tmdb_id: 1234,
|
||||||
title: "Test".to_owned(),
|
title: "Test".to_owned(),
|
||||||
root_folder_path: "/nfs2".to_owned(),
|
root_folder_path: "/nfs2".to_owned(),
|
||||||
minimum_availability: "announced".to_owned(),
|
minimum_availability: MinimumAvailability::Announced,
|
||||||
monitored: true,
|
monitored: true,
|
||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
tags: vec![1, 2],
|
tags: vec![1, 2],
|
||||||
tag_input_string: Some("usenet, testing".into()),
|
tag_input_string: Some("usenet, testing".into()),
|
||||||
add_options: AddMovieOptions {
|
add_options: AddMovieOptions {
|
||||||
monitor: "movieOnly".to_owned(),
|
monitor: MovieMonitor::MovieOnly,
|
||||||
search_for_movie: true,
|
search_for_movie: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -99,13 +99,13 @@ mod tests {
|
|||||||
tmdb_id: 1234,
|
tmdb_id: 1234,
|
||||||
title: "Test".to_owned(),
|
title: "Test".to_owned(),
|
||||||
root_folder_path: "/nfs2".to_owned(),
|
root_folder_path: "/nfs2".to_owned(),
|
||||||
minimum_availability: "announced".to_owned(),
|
minimum_availability: MinimumAvailability::Announced,
|
||||||
monitored: true,
|
monitored: true,
|
||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
tags: vec![1, 2],
|
tags: vec![1, 2],
|
||||||
tag_input_string: None,
|
tag_input_string: None,
|
||||||
add_options: AddMovieOptions {
|
add_options: AddMovieOptions {
|
||||||
monitor: "movieOnly".to_owned(),
|
monitor: MovieMonitor::MovieOnly,
|
||||||
search_for_movie: true,
|
search_for_movie: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock;
|
||||||
use crate::models::sonarr_models::{
|
use crate::models::sonarr_models::{
|
||||||
AddSeriesBody, AddSeriesOptions, DeleteSeriesParams, EditSeriesParams, Series, SeriesType,
|
AddSeriesBody, AddSeriesOptions, DeleteSeriesParams, EditSeriesParams, Series, SeriesMonitor,
|
||||||
SonarrHistoryItem, SonarrSerdeable,
|
SeriesType, SonarrHistoryItem, SonarrSerdeable,
|
||||||
};
|
};
|
||||||
use crate::models::stateful_table::{SortOption, StatefulTable};
|
use crate::models::stateful_table::{SortOption, StatefulTable};
|
||||||
use crate::network::NetworkResource;
|
use crate::network::NetworkResource;
|
||||||
@@ -27,12 +27,12 @@ mod tests {
|
|||||||
root_folder_path: "/nfs2".to_owned(),
|
root_folder_path: "/nfs2".to_owned(),
|
||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
language_profile_id: 2222,
|
language_profile_id: 2222,
|
||||||
series_type: "standard".to_owned(),
|
series_type: SeriesType::Standard,
|
||||||
season_folder: true,
|
season_folder: true,
|
||||||
tags: Vec::new(),
|
tags: Vec::new(),
|
||||||
tag_input_string: Some("usenet, testing".to_owned()),
|
tag_input_string: Some("usenet, testing".to_owned()),
|
||||||
add_options: AddSeriesOptions {
|
add_options: AddSeriesOptions {
|
||||||
monitor: "all".to_owned(),
|
monitor: SeriesMonitor::All,
|
||||||
search_for_cutoff_unmet_episodes: true,
|
search_for_cutoff_unmet_episodes: true,
|
||||||
search_for_missing_episodes: true,
|
search_for_missing_episodes: true,
|
||||||
},
|
},
|
||||||
@@ -82,12 +82,12 @@ mod tests {
|
|||||||
root_folder_path: "/nfs2".to_owned(),
|
root_folder_path: "/nfs2".to_owned(),
|
||||||
quality_profile_id: 2222,
|
quality_profile_id: 2222,
|
||||||
language_profile_id: 2222,
|
language_profile_id: 2222,
|
||||||
series_type: "standard".to_owned(),
|
series_type: SeriesType::Standard,
|
||||||
season_folder: true,
|
season_folder: true,
|
||||||
tags: vec![1, 2],
|
tags: vec![1, 2],
|
||||||
tag_input_string: None,
|
tag_input_string: None,
|
||||||
add_options: AddSeriesOptions {
|
add_options: AddSeriesOptions {
|
||||||
monitor: "all".to_owned(),
|
monitor: SeriesMonitor::All,
|
||||||
search_for_cutoff_unmet_episodes: true,
|
search_for_cutoff_unmet_episodes: true,
|
||||||
search_for_missing_episodes: true,
|
search_for_missing_episodes: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,18 +2,22 @@ use std::sync::atomic::Ordering;
|
|||||||
|
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
use ratatui::layout::{Constraint, Layout, Rect};
|
use ratatui::layout::{Constraint, Layout, Rect};
|
||||||
use ratatui::widgets::{Cell, Row};
|
use ratatui::widgets::{Cell, ListItem, Row};
|
||||||
|
|
||||||
use crate::App;
|
use crate::App;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
use crate::models::lidarr_models::AddArtistSearchResult;
|
use crate::models::lidarr_models::AddArtistSearchResult;
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{ADD_ARTIST_BLOCKS, ActiveLidarrBlock};
|
use crate::models::servarr_data::lidarr::lidarr_data::{ADD_ARTIST_BLOCKS, ActiveLidarrBlock};
|
||||||
|
use crate::models::servarr_data::lidarr::modals::AddArtistModal;
|
||||||
|
use crate::render_selectable_input_box;
|
||||||
use crate::ui::styles::ManagarrStyle;
|
use crate::ui::styles::ManagarrStyle;
|
||||||
use crate::ui::utils::{get_width_from_percentage, layout_block, title_block_centered};
|
use crate::ui::utils::{get_width_from_percentage, layout_block, title_block_centered};
|
||||||
|
use crate::ui::widgets::button::Button;
|
||||||
use crate::ui::widgets::input_box::InputBox;
|
use crate::ui::widgets::input_box::InputBox;
|
||||||
use crate::ui::widgets::managarr_table::ManagarrTable;
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
use crate::ui::widgets::message::Message;
|
use crate::ui::widgets::message::Message;
|
||||||
use crate::ui::widgets::popup::{Popup, Size};
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
|
use crate::ui::widgets::selectable_list::SelectableList;
|
||||||
use crate::ui::{DrawUi, draw_popup};
|
use crate::ui::{DrawUi, draw_popup};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -31,7 +35,28 @@ impl DrawUi for AddArtistUi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||||
draw_popup(f, app, draw_add_artist_search, Size::Large);
|
draw_popup(f, app, draw_add_artist_search, Size::Large);
|
||||||
|
|
||||||
|
match active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectRootFolder
|
||||||
|
| ActiveLidarrBlock::AddArtistTagsInput => {
|
||||||
|
draw_popup(f, app, draw_confirmation_popup, Size::Long);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistAlreadyInLibrary => {
|
||||||
|
f.render_widget(
|
||||||
|
Popup::new(Message::new("This artist is already in your library")).size(Size::Message),
|
||||||
|
f.area(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +144,15 @@ fn draw_add_artist_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
f.render_widget(layout_block().default_color(), results_area);
|
f.render_widget(layout_block().default_color(), results_area);
|
||||||
f.render_widget(error_message_popup, f.area());
|
f.render_widget(error_message_popup, f.area());
|
||||||
}
|
}
|
||||||
ActiveLidarrBlock::AddArtistSearchResults => {
|
ActiveLidarrBlock::AddArtistSearchResults
|
||||||
|
| ActiveLidarrBlock::AddArtistPrompt
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitor
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMonitorNewItems
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectQualityProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectMetadataProfile
|
||||||
|
| ActiveLidarrBlock::AddArtistSelectRootFolder
|
||||||
|
| ActiveLidarrBlock::AddArtistAlreadyInLibrary
|
||||||
|
| ActiveLidarrBlock::AddArtistTagsInput => {
|
||||||
let search_results_table = ManagarrTable::new(
|
let search_results_table = ManagarrTable::new(
|
||||||
app.data.lidarr_data.add_searched_artists.as_mut(),
|
app.data.lidarr_data.add_searched_artists.as_mut(),
|
||||||
search_results_row_mapping,
|
search_results_row_mapping,
|
||||||
@@ -149,3 +182,236 @@ fn draw_add_artist_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
search_box_area,
|
search_box_area,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn draw_confirmation_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||||
|
match active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitor => {
|
||||||
|
draw_confirmation_prompt(f, app, area);
|
||||||
|
draw_add_artist_select_monitor_popup(f, app);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMonitorNewItems => {
|
||||||
|
draw_confirmation_prompt(f, app, area);
|
||||||
|
draw_add_artist_select_monitor_new_items_popup(f, app);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistSelectQualityProfile => {
|
||||||
|
draw_confirmation_prompt(f, app, area);
|
||||||
|
draw_add_artist_select_quality_profile_popup(f, app);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistSelectMetadataProfile => {
|
||||||
|
draw_confirmation_prompt(f, app, area);
|
||||||
|
draw_add_artist_select_metadata_profile_popup(f, app);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistSelectRootFolder => {
|
||||||
|
draw_confirmation_prompt(f, app, area);
|
||||||
|
draw_add_artist_select_root_folder_popup(f, app);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::AddArtistPrompt | ActiveLidarrBlock::AddArtistTagsInput => {
|
||||||
|
draw_confirmation_prompt(f, app, area)
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
let searched_artist = app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_searched_artists
|
||||||
|
.as_ref()
|
||||||
|
.expect("add_searched_artists must be populated")
|
||||||
|
.current_selection();
|
||||||
|
let artist_name = &searched_artist.artist_name.text;
|
||||||
|
let artist_disambiguation = searched_artist.disambiguation.clone().unwrap_or_default();
|
||||||
|
|
||||||
|
let title = if artist_disambiguation.is_empty() {
|
||||||
|
format!("Add - {artist_name}")
|
||||||
|
} else {
|
||||||
|
format!("Add - {artist_name} ({artist_disambiguation})")
|
||||||
|
};
|
||||||
|
let yes_no_value = app.data.lidarr_data.prompt_confirm;
|
||||||
|
let selected_block = app.data.lidarr_data.selected_block.get_active_block();
|
||||||
|
let highlight_yes_no = selected_block == ActiveLidarrBlock::AddArtistConfirmPrompt;
|
||||||
|
let AddArtistModal {
|
||||||
|
monitor_list,
|
||||||
|
monitor_new_items_list,
|
||||||
|
quality_profile_list,
|
||||||
|
metadata_profile_list,
|
||||||
|
root_folder_list,
|
||||||
|
tags,
|
||||||
|
..
|
||||||
|
} = app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_ref()
|
||||||
|
.expect("add_artist_modal must exist in this context");
|
||||||
|
|
||||||
|
let selected_monitor = monitor_list.current_selection();
|
||||||
|
let selected_monitor_new_items = monitor_new_items_list.current_selection();
|
||||||
|
let selected_quality_profile = quality_profile_list.current_selection();
|
||||||
|
let selected_metadata_profile = metadata_profile_list.current_selection();
|
||||||
|
let selected_root_folder = root_folder_list.current_selection();
|
||||||
|
|
||||||
|
f.render_widget(title_block_centered(&title), area);
|
||||||
|
|
||||||
|
let [
|
||||||
|
_,
|
||||||
|
root_folder_area,
|
||||||
|
monitor_area,
|
||||||
|
monitor_new_items_area,
|
||||||
|
quality_profile_area,
|
||||||
|
metadata_profile_area,
|
||||||
|
tags_area,
|
||||||
|
_,
|
||||||
|
buttons_area,
|
||||||
|
] = Layout::vertical([
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
])
|
||||||
|
.margin(1)
|
||||||
|
.areas(area);
|
||||||
|
|
||||||
|
let [add_area, cancel_area] =
|
||||||
|
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
|
.areas(buttons_area);
|
||||||
|
|
||||||
|
let root_folder_drop_down_button = Button::default()
|
||||||
|
.title(&selected_root_folder.path)
|
||||||
|
.label("Root Folder")
|
||||||
|
.icon("▼")
|
||||||
|
.selected(selected_block == ActiveLidarrBlock::AddArtistSelectRootFolder);
|
||||||
|
let monitor_drop_down_button = Button::default()
|
||||||
|
.title(selected_monitor.to_display_str())
|
||||||
|
.label("Monitor")
|
||||||
|
.icon("▼")
|
||||||
|
.selected(selected_block == ActiveLidarrBlock::AddArtistSelectMonitor);
|
||||||
|
let monitor_new_items_drop_down_button = Button::default()
|
||||||
|
.title(selected_monitor_new_items.to_display_str())
|
||||||
|
.label("Monitor New Items")
|
||||||
|
.icon("▼")
|
||||||
|
.selected(selected_block == ActiveLidarrBlock::AddArtistSelectMonitorNewItems);
|
||||||
|
let quality_profile_drop_down_button = Button::default()
|
||||||
|
.title(selected_quality_profile)
|
||||||
|
.label("Quality Profile")
|
||||||
|
.icon("▼")
|
||||||
|
.selected(selected_block == ActiveLidarrBlock::AddArtistSelectQualityProfile);
|
||||||
|
let metadata_profile_drop_down_button = Button::default()
|
||||||
|
.title(selected_metadata_profile)
|
||||||
|
.label("Metadata Profile")
|
||||||
|
.icon("▼")
|
||||||
|
.selected(selected_block == ActiveLidarrBlock::AddArtistSelectMetadataProfile);
|
||||||
|
|
||||||
|
f.render_widget(root_folder_drop_down_button, root_folder_area);
|
||||||
|
f.render_widget(monitor_drop_down_button, monitor_area);
|
||||||
|
f.render_widget(monitor_new_items_drop_down_button, monitor_new_items_area);
|
||||||
|
f.render_widget(quality_profile_drop_down_button, quality_profile_area);
|
||||||
|
f.render_widget(metadata_profile_drop_down_button, metadata_profile_area);
|
||||||
|
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||||
|
let tags_input_box = InputBox::new(&tags.text)
|
||||||
|
.offset(tags.offset.load(Ordering::SeqCst))
|
||||||
|
.label("Tags")
|
||||||
|
.highlighted(selected_block == ActiveLidarrBlock::AddArtistTagsInput)
|
||||||
|
.selected(active_lidarr_block == ActiveLidarrBlock::AddArtistTagsInput);
|
||||||
|
render_selectable_input_box!(tags_input_box, f, tags_area);
|
||||||
|
}
|
||||||
|
|
||||||
|
let add_button = Button::default()
|
||||||
|
.title("Add")
|
||||||
|
.selected(yes_no_value && highlight_yes_no);
|
||||||
|
let cancel_button = Button::default()
|
||||||
|
.title("Cancel")
|
||||||
|
.selected(!yes_no_value && highlight_yes_no);
|
||||||
|
|
||||||
|
f.render_widget(add_button, add_area);
|
||||||
|
f.render_widget(cancel_button, cancel_area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_add_artist_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||||
|
let monitor_list = SelectableList::new(
|
||||||
|
&mut app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("add_artist_modal must exist in this context")
|
||||||
|
.monitor_list,
|
||||||
|
|monitor| ListItem::new(monitor.to_display_str().to_owned()),
|
||||||
|
);
|
||||||
|
let popup = Popup::new(monitor_list).size(Size::Dropdown);
|
||||||
|
|
||||||
|
f.render_widget(popup, f.area());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_add_artist_select_monitor_new_items_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||||
|
let monitor_new_items_list = SelectableList::new(
|
||||||
|
&mut app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("add_artist_modal must exist in this context")
|
||||||
|
.monitor_new_items_list,
|
||||||
|
|monitor_new_items| ListItem::new(monitor_new_items.to_display_str().to_owned()),
|
||||||
|
);
|
||||||
|
let popup = Popup::new(monitor_new_items_list).size(Size::Dropdown);
|
||||||
|
|
||||||
|
f.render_widget(popup, f.area());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_add_artist_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||||
|
let quality_profile_list = SelectableList::new(
|
||||||
|
&mut app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("add_artist_modal must exist in this context")
|
||||||
|
.quality_profile_list,
|
||||||
|
|quality_profile| ListItem::new(quality_profile.clone()),
|
||||||
|
);
|
||||||
|
let popup = Popup::new(quality_profile_list).size(Size::Dropdown);
|
||||||
|
|
||||||
|
f.render_widget(popup, f.area());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_add_artist_select_metadata_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||||
|
let metadata_profile_list = SelectableList::new(
|
||||||
|
&mut app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("add_artist_modal must exist in this context")
|
||||||
|
.metadata_profile_list,
|
||||||
|
|metadata_profile| ListItem::new(metadata_profile.clone()),
|
||||||
|
);
|
||||||
|
let popup = Popup::new(metadata_profile_list).size(Size::Dropdown);
|
||||||
|
|
||||||
|
f.render_widget(popup, f.area());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_add_artist_select_root_folder_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
||||||
|
let root_folder_list = SelectableList::new(
|
||||||
|
&mut app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.add_artist_modal
|
||||||
|
.as_mut()
|
||||||
|
.expect("add_artist_modal must exist in this context")
|
||||||
|
.root_folder_list,
|
||||||
|
|root_folder| ListItem::new(root_folder.path.to_owned()),
|
||||||
|
);
|
||||||
|
let popup = Popup::new(root_folder_list).size(Size::Dropdown);
|
||||||
|
|
||||||
|
f.render_widget(popup, f.area());
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ mod tests {
|
|||||||
mod snapshot_tests {
|
mod snapshot_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::models::HorizontallyScrollableText;
|
use crate::models::servarr_data::lidarr::lidarr_data::ADD_ARTIST_SELECTION_BLOCKS;
|
||||||
|
use crate::models::{BlockSelectionState, HorizontallyScrollableText};
|
||||||
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
|
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
@@ -57,5 +58,67 @@ mod tests {
|
|||||||
|
|
||||||
insta::assert_snapshot!(format!("add_artist_ui_{active_lidarr_block}"), output);
|
insta::assert_snapshot!(format!("add_artist_ui_{active_lidarr_block}"), output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveLidarrBlock::AddArtistPrompt)]
|
||||||
|
#[case(ActiveLidarrBlock::AddArtistConfirmPrompt)]
|
||||||
|
#[case(ActiveLidarrBlock::AddArtistSelectMonitor)]
|
||||||
|
#[case(ActiveLidarrBlock::AddArtistSelectMonitorNewItems)]
|
||||||
|
#[case(ActiveLidarrBlock::AddArtistSelectQualityProfile)]
|
||||||
|
#[case(ActiveLidarrBlock::AddArtistSelectMetadataProfile)]
|
||||||
|
#[case(ActiveLidarrBlock::AddArtistSelectRootFolder)]
|
||||||
|
#[case(ActiveLidarrBlock::AddArtistTagsInput)]
|
||||||
|
fn test_add_artist_modal_ui_renders(#[case] active_lidarr_block: ActiveLidarrBlock) {
|
||||||
|
use crate::models::lidarr_models::{MonitorType, NewItemMonitorType};
|
||||||
|
use crate::models::servarr_data::lidarr::modals::AddArtistModal;
|
||||||
|
use crate::models::servarr_models::RootFolder;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(ADD_ARTIST_SELECTION_BLOCKS);
|
||||||
|
|
||||||
|
let mut add_artist_modal = AddArtistModal {
|
||||||
|
tags: "test".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
|
||||||
|
.quality_profile_list
|
||||||
|
.set_items(vec!["Any".to_owned()]);
|
||||||
|
add_artist_modal
|
||||||
|
.metadata_profile_list
|
||||||
|
.set_items(vec!["Standard".to_owned()]);
|
||||||
|
add_artist_modal
|
||||||
|
.root_folder_list
|
||||||
|
.set_items(vec![RootFolder {
|
||||||
|
path: "/nfs/music".to_owned(),
|
||||||
|
..RootFolder::default()
|
||||||
|
}]);
|
||||||
|
app.data.lidarr_data.add_artist_modal = Some(add_artist_modal);
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
AddArtistUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(format!("add_artist_modal_{active_lidarr_block}"), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_artist_already_in_library_ui_renders() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::AddArtistAlreadyInLibrary.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
AddArtistUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,329 @@
|
|||||||
|
use chrono::Utc;
|
||||||
|
use deunicode::deunicode;
|
||||||
|
use ratatui::Frame;
|
||||||
|
use ratatui::layout::{Constraint, Layout, Rect};
|
||||||
|
use ratatui::style::Stylize;
|
||||||
|
use ratatui::text::Line;
|
||||||
|
use ratatui::widgets::{Cell, Paragraph, Row, Wrap};
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::lidarr_models::Album;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock};
|
||||||
|
use crate::ui::lidarr_ui::library::delete_album_ui::DeleteAlbumUi;
|
||||||
|
use crate::ui::styles::ManagarrStyle;
|
||||||
|
use crate::ui::utils::{
|
||||||
|
borderless_block, get_width_from_percentage, layout_block_top_border, title_block,
|
||||||
|
};
|
||||||
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
||||||
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
|
use crate::ui::{DrawUi, draw_popup, draw_tabs};
|
||||||
|
use crate::utils::convert_to_gb;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "artist_details_ui_tests.rs"]
|
||||||
|
mod artist_details_ui_tests;
|
||||||
|
|
||||||
|
pub(super) struct ArtistDetailsUi;
|
||||||
|
|
||||||
|
impl DrawUi for ArtistDetailsUi {
|
||||||
|
fn accepts(route: Route) -> bool {
|
||||||
|
let Route::Lidarr(active_lidarr_block, _) = route else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
DeleteAlbumUi::accepts(route) || ARTIST_DETAILS_BLOCKS.contains(&active_lidarr_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
let route = app.get_current_route();
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = route {
|
||||||
|
let draw_artist_details_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| {
|
||||||
|
f.render_widget(
|
||||||
|
title_block(
|
||||||
|
&app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artists
|
||||||
|
.current_selection()
|
||||||
|
.artist_name
|
||||||
|
.text,
|
||||||
|
),
|
||||||
|
popup_area,
|
||||||
|
);
|
||||||
|
let [description_area, detail_area] =
|
||||||
|
Layout::vertical([Constraint::Percentage(37), Constraint::Fill(0)])
|
||||||
|
.margin(1)
|
||||||
|
.areas(popup_area);
|
||||||
|
draw_artist_description(f, app, description_area);
|
||||||
|
let content_area = draw_tabs(
|
||||||
|
f,
|
||||||
|
detail_area,
|
||||||
|
"Artist Details",
|
||||||
|
&app.data.lidarr_data.artist_info_tabs,
|
||||||
|
);
|
||||||
|
draw_artist_details(f, app, content_area);
|
||||||
|
|
||||||
|
match active_lidarr_block {
|
||||||
|
_ if DeleteAlbumUi::accepts(route) => DeleteAlbumUi::draw(f, app, area),
|
||||||
|
ActiveLidarrBlock::AutomaticallySearchArtistPrompt => {
|
||||||
|
let prompt = format!(
|
||||||
|
"Do you want to trigger an automatic search of your indexers for all monitored album(s) for the artist: {}?",
|
||||||
|
app.data.lidarr_data.artists.current_selection().artist_name
|
||||||
|
);
|
||||||
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
.title("Automatic Artist Search")
|
||||||
|
.prompt(&prompt)
|
||||||
|
.yes_no_value(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||||
|
f.area(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ActiveLidarrBlock::UpdateAndScanArtistPrompt => {
|
||||||
|
let prompt = format!(
|
||||||
|
"Do you want to trigger an update and disk scan for the artist: {}?",
|
||||||
|
app.data.lidarr_data.artists.current_selection().artist_name
|
||||||
|
);
|
||||||
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
.title("Update and Scan")
|
||||||
|
.prompt(&prompt)
|
||||||
|
.yes_no_value(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||||
|
f.area(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
draw_popup(f, app, draw_artist_details_popup, Size::XXLarge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_artist_description(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
let current_selection = app.data.lidarr_data.artists.current_selection();
|
||||||
|
let monitored = if current_selection.monitored {
|
||||||
|
"Yes"
|
||||||
|
} else {
|
||||||
|
"No"
|
||||||
|
};
|
||||||
|
let quality_profile = app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.quality_profile_map
|
||||||
|
.get_by_left(¤t_selection.quality_profile_id)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let metadata_profile = app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.metadata_profile_map
|
||||||
|
.get_by_left(¤t_selection.metadata_profile_id)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let overview = Regex::new(r"[\r\n\t]")
|
||||||
|
.unwrap()
|
||||||
|
.replace_all(
|
||||||
|
&deunicode(
|
||||||
|
current_selection
|
||||||
|
.overview
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&String::new()),
|
||||||
|
),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let mut artist_description = vec![
|
||||||
|
Line::from(vec![
|
||||||
|
"Artist: ".primary().bold(),
|
||||||
|
current_selection.artist_name.text.clone().primary().bold(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Overview: ".primary().bold(),
|
||||||
|
overview.default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Disambiguation: ".primary().bold(),
|
||||||
|
current_selection
|
||||||
|
.disambiguation
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Type: ".primary().bold(),
|
||||||
|
current_selection
|
||||||
|
.artist_type
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Status: ".primary().bold(),
|
||||||
|
current_selection.status.to_display_str().default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Genres: ".primary().bold(),
|
||||||
|
current_selection.genres.join(", ").default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Rating: ".primary().bold(),
|
||||||
|
current_selection
|
||||||
|
.ratings
|
||||||
|
.as_ref()
|
||||||
|
.map_or_else(
|
||||||
|
|| "N/A".to_owned(),
|
||||||
|
|r| format!("{}%", (r.value * 10.0) as i32),
|
||||||
|
)
|
||||||
|
.default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Path: ".primary().bold(),
|
||||||
|
current_selection.path.clone().default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Quality Profile: ".primary().bold(),
|
||||||
|
quality_profile.default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Metadata Profile: ".primary().bold(),
|
||||||
|
metadata_profile.default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Monitored: ".primary().bold(),
|
||||||
|
monitored.default_color(),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(stats) = current_selection.statistics.as_ref() {
|
||||||
|
let size = convert_to_gb(stats.size_on_disk);
|
||||||
|
artist_description.extend(vec![
|
||||||
|
Line::from(vec![
|
||||||
|
"Albums: ".primary().bold(),
|
||||||
|
stats.album_count.to_string().default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Tracks: ".primary().bold(),
|
||||||
|
format!("{}/{}", stats.track_file_count, stats.total_track_count).default_color(),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
"Size on Disk: ".primary().bold(),
|
||||||
|
format!("{size:.2} GB").default_color(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let description_paragraph = Paragraph::new(artist_description)
|
||||||
|
.block(borderless_block())
|
||||||
|
.wrap(Wrap { trim: true });
|
||||||
|
f.render_widget(description_paragraph, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_artist_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) =
|
||||||
|
app.data.lidarr_data.artist_info_tabs.get_active_route()
|
||||||
|
{
|
||||||
|
match active_lidarr_block {
|
||||||
|
ActiveLidarrBlock::ArtistDetails => draw_albums_table(f, app, area),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_albums_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
if let Route::Lidarr(active_lidarr_block, _) = app.get_current_route() {
|
||||||
|
let current_selection = if app.data.lidarr_data.albums.is_empty() {
|
||||||
|
Album::default()
|
||||||
|
} else {
|
||||||
|
app.data.lidarr_data.albums.current_selection().clone()
|
||||||
|
};
|
||||||
|
let content = Some(&mut app.data.lidarr_data.albums);
|
||||||
|
let album_row_mapping = |album: &Album| {
|
||||||
|
album.title.scroll_left_or_reset(
|
||||||
|
get_width_from_percentage(area, 33),
|
||||||
|
*album == current_selection,
|
||||||
|
app.ui_scroll_tick_count == 0,
|
||||||
|
);
|
||||||
|
let monitored = if album.monitored { "🏷" } else { "" };
|
||||||
|
let album_type = album.album_type.clone().unwrap_or_default();
|
||||||
|
let release_date = album
|
||||||
|
.release_date
|
||||||
|
.map_or_else(|| "N/A".to_owned(), |d| d.format("%Y-%m-%d").to_string());
|
||||||
|
let track_count = album.statistics.as_ref().map_or_else(
|
||||||
|
|| "0/0".to_owned(),
|
||||||
|
|s| format!("{}/{}", s.track_file_count, s.total_track_count),
|
||||||
|
);
|
||||||
|
let size = album
|
||||||
|
.statistics
|
||||||
|
.as_ref()
|
||||||
|
.map_or(0f64, |s| convert_to_gb(s.size_on_disk));
|
||||||
|
let duration_mins = album.duration / 60000;
|
||||||
|
|
||||||
|
let row = Row::new(vec![
|
||||||
|
Cell::from(monitored.to_owned()),
|
||||||
|
Cell::from(album.title.to_string()),
|
||||||
|
Cell::from(album_type),
|
||||||
|
Cell::from(track_count),
|
||||||
|
Cell::from(format!("{duration_mins} min")),
|
||||||
|
Cell::from(release_date),
|
||||||
|
Cell::from(format!("{size:.2} GB")),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if !album.monitored {
|
||||||
|
row.unmonitored()
|
||||||
|
} else if let Some(stats) = album.statistics.as_ref() {
|
||||||
|
if stats.track_file_count == stats.total_track_count && stats.total_track_count > 0 {
|
||||||
|
row.downloaded()
|
||||||
|
} else if let Some(release_date) = album.release_date.as_ref() {
|
||||||
|
if release_date > &Utc::now() {
|
||||||
|
row.unreleased()
|
||||||
|
} else {
|
||||||
|
row.missing()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row.missing()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
row.indeterminate()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_searching = active_lidarr_block == ActiveLidarrBlock::SearchAlbums;
|
||||||
|
let album_table = ManagarrTable::new(content, album_row_mapping)
|
||||||
|
.block(layout_block_top_border())
|
||||||
|
.loading(app.is_loading)
|
||||||
|
.searching(is_searching)
|
||||||
|
.search_produced_empty_results(active_lidarr_block == ActiveLidarrBlock::SearchAlbumsError)
|
||||||
|
.headers([
|
||||||
|
"Monitored",
|
||||||
|
"Title",
|
||||||
|
"Type",
|
||||||
|
"Tracks",
|
||||||
|
"Duration",
|
||||||
|
"Release Date",
|
||||||
|
"Size",
|
||||||
|
])
|
||||||
|
.constraints([
|
||||||
|
Constraint::Percentage(7),
|
||||||
|
Constraint::Percentage(35),
|
||||||
|
Constraint::Percentage(10),
|
||||||
|
Constraint::Percentage(10),
|
||||||
|
Constraint::Percentage(10),
|
||||||
|
Constraint::Percentage(13),
|
||||||
|
Constraint::Percentage(15),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if is_searching {
|
||||||
|
album_table.show_cursor(f, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
f.render_widget(album_table, area);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS,
|
||||||
|
};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::lidarr_ui::library::artist_details_ui::ArtistDetailsUi;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_ui_accepts() {
|
||||||
|
let mut blocks = ARTIST_DETAILS_BLOCKS.clone().to_vec();
|
||||||
|
blocks.extend(DELETE_ALBUM_BLOCKS);
|
||||||
|
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if blocks.contains(&active_lidarr_block) {
|
||||||
|
assert!(ArtistDetailsUi::accepts(active_lidarr_block.into()));
|
||||||
|
} else {
|
||||||
|
assert!(!ArtistDetailsUi::accepts(active_lidarr_block.into()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mod snapshot_tests {
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::models::BlockSelectionState;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ActiveLidarrBlock, DELETE_ALBUM_SELECTION_BLOCKS,
|
||||||
|
};
|
||||||
|
use crate::models::stateful_table::StatefulTable;
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::lidarr_ui::library::artist_details_ui::ArtistDetailsUi;
|
||||||
|
use crate::ui::ui_test_utils::test_utils::{TerminalSize, render_to_string_with_app};
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveLidarrBlock::ArtistDetails, 0)]
|
||||||
|
#[case(ActiveLidarrBlock::SearchAlbums, 0)]
|
||||||
|
#[case(ActiveLidarrBlock::SearchAlbumsError, 0)]
|
||||||
|
#[case(ActiveLidarrBlock::UpdateAndScanArtistPrompt, 0)]
|
||||||
|
#[case(ActiveLidarrBlock::AutomaticallySearchArtistPrompt, 0)]
|
||||||
|
fn test_artist_details_ui_renders(
|
||||||
|
#[case] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[case] index: usize,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
app.data.lidarr_data.artist_info_tabs.set_index(index);
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
ArtistDetailsUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
format!("artist_details_{active_lidarr_block}_{index}"),
|
||||||
|
output
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveLidarrBlock::ArtistDetails, 0)]
|
||||||
|
fn test_artist_details_ui_renders_artist_details_loading(
|
||||||
|
#[case] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[case] index: usize,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
app.data.lidarr_data.artist_info_tabs.set_index(index);
|
||||||
|
app.is_loading = true;
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
ArtistDetailsUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
format!("loading_artist_details_{active_lidarr_block}"),
|
||||||
|
output
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case(ActiveLidarrBlock::ArtistDetails, 0)]
|
||||||
|
fn test_artist_details_ui_renders_artist_details_empty(
|
||||||
|
#[case] active_lidarr_block: ActiveLidarrBlock,
|
||||||
|
#[case] index: usize,
|
||||||
|
) {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.data.lidarr_data.albums = StatefulTable::default();
|
||||||
|
app.push_navigation_stack(active_lidarr_block.into());
|
||||||
|
app.data.lidarr_data.artist_info_tabs.set_index(index);
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
ArtistDetailsUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(
|
||||||
|
format!("empty_artist_details_{active_lidarr_block}"),
|
||||||
|
output
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_ui_renders_delete_album_prompt_over_artist_details() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS);
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
ArtistDetailsUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_ui_renders_update_and_scan_prompt_over_artist_details() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::UpdateAndScanArtistPrompt.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
ArtistDetailsUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_artist_details_ui_renders_automatic_search_prompt_over_artist_details() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::AutomaticallySearchArtistPrompt.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
ArtistDetailsUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
use ratatui::Frame;
|
||||||
|
use ratatui::layout::Rect;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::models::Route;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, DELETE_ALBUM_BLOCKS};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::widgets::checkbox::Checkbox;
|
||||||
|
use crate::ui::widgets::confirmation_prompt::ConfirmationPrompt;
|
||||||
|
use crate::ui::widgets::popup::{Popup, Size};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[path = "delete_album_ui_tests.rs"]
|
||||||
|
mod delete_album_ui_tests;
|
||||||
|
|
||||||
|
pub(in crate::ui::lidarr_ui) struct DeleteAlbumUi;
|
||||||
|
|
||||||
|
impl DrawUi for DeleteAlbumUi {
|
||||||
|
fn accepts(route: Route) -> bool {
|
||||||
|
let Route::Lidarr(active_lidarr_block, _) = route else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
DELETE_ALBUM_BLOCKS.contains(&active_lidarr_block)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
|
||||||
|
if matches!(
|
||||||
|
app.get_current_route(),
|
||||||
|
Route::Lidarr(ActiveLidarrBlock::DeleteAlbumPrompt, _)
|
||||||
|
) {
|
||||||
|
let selected_block = app.data.lidarr_data.selected_block.get_active_block();
|
||||||
|
let prompt = format!(
|
||||||
|
"Do you really want to delete the album: \n{}?",
|
||||||
|
app.data.lidarr_data.albums.current_selection().title.text
|
||||||
|
);
|
||||||
|
let checkboxes = vec![
|
||||||
|
Checkbox::new("Delete Album Files")
|
||||||
|
.checked(app.data.lidarr_data.delete_files)
|
||||||
|
.highlighted(selected_block == ActiveLidarrBlock::DeleteAlbumToggleDeleteFile),
|
||||||
|
Checkbox::new("Add List Exclusion")
|
||||||
|
.checked(app.data.lidarr_data.add_import_list_exclusion)
|
||||||
|
.highlighted(selected_block == ActiveLidarrBlock::DeleteAlbumToggleAddListExclusion),
|
||||||
|
];
|
||||||
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
.title("Delete Album")
|
||||||
|
.prompt(&prompt)
|
||||||
|
.checkboxes(checkboxes)
|
||||||
|
.yes_no_highlighted(selected_block == ActiveLidarrBlock::DeleteAlbumConfirmPrompt)
|
||||||
|
.yes_no_value(app.data.lidarr_data.prompt_confirm);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
Popup::new(confirmation_prompt).size(Size::MediumPrompt),
|
||||||
|
f.area(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::models::BlockSelectionState;
|
||||||
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ActiveLidarrBlock, DELETE_ALBUM_BLOCKS, DELETE_ALBUM_SELECTION_BLOCKS,
|
||||||
|
};
|
||||||
|
use crate::ui::DrawUi;
|
||||||
|
use crate::ui::lidarr_ui::library::delete_album_ui::DeleteAlbumUi;
|
||||||
|
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_ui_accepts() {
|
||||||
|
ActiveLidarrBlock::iter().for_each(|active_lidarr_block| {
|
||||||
|
if DELETE_ALBUM_BLOCKS.contains(&active_lidarr_block) {
|
||||||
|
assert!(DeleteAlbumUi::accepts(active_lidarr_block.into()));
|
||||||
|
} else {
|
||||||
|
assert!(!DeleteAlbumUi::accepts(active_lidarr_block.into()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mod snapshot_tests {
|
||||||
|
use crate::ui::ui_test_utils::test_utils::TerminalSize;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_delete_album_ui_renders_delete_album() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::DeleteAlbumPrompt.into());
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(DELETE_ALBUM_SELECTION_BLOCKS);
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
DeleteAlbumUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,7 +41,7 @@ impl DrawUi for DeleteArtistUi {
|
|||||||
);
|
);
|
||||||
let checkboxes = vec![
|
let checkboxes = vec![
|
||||||
Checkbox::new("Delete Artist Files")
|
Checkbox::new("Delete Artist Files")
|
||||||
.checked(app.data.lidarr_data.delete_artist_files)
|
.checked(app.data.lidarr_data.delete_files)
|
||||||
.highlighted(selected_block == ActiveLidarrBlock::DeleteArtistToggleDeleteFile),
|
.highlighted(selected_block == ActiveLidarrBlock::DeleteArtistToggleDeleteFile),
|
||||||
Checkbox::new("Add List Exclusion")
|
Checkbox::new("Add List Exclusion")
|
||||||
.checked(app.data.lidarr_data.add_import_list_exclusion)
|
.checked(app.data.lidarr_data.add_import_list_exclusion)
|
||||||
|
|||||||
@@ -7,10 +7,13 @@ use ratatui::widgets::ListItem;
|
|||||||
|
|
||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{ActiveLidarrBlock, EDIT_ARTIST_BLOCKS};
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
|
ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, EDIT_ARTIST_BLOCKS,
|
||||||
|
};
|
||||||
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
use crate::models::servarr_data::lidarr::modals::EditArtistModal;
|
||||||
use crate::render_selectable_input_box;
|
use crate::render_selectable_input_box;
|
||||||
|
|
||||||
|
use crate::ui::lidarr_ui::library::artist_details_ui::ArtistDetailsUi;
|
||||||
use crate::ui::utils::title_block_centered;
|
use crate::ui::utils::title_block_centered;
|
||||||
use crate::ui::widgets::button::Button;
|
use crate::ui::widgets::button::Button;
|
||||||
use crate::ui::widgets::checkbox::Checkbox;
|
use crate::ui::widgets::checkbox::Checkbox;
|
||||||
@@ -34,7 +37,13 @@ impl DrawUi for EditArtistUi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, _area: Rect) {
|
||||||
if let Route::Lidarr(active_lidarr_block, _context_option) = app.get_current_route() {
|
if let Route::Lidarr(active_lidarr_block, context_option) = app.get_current_route() {
|
||||||
|
if let Some(context) = context_option
|
||||||
|
&& ARTIST_DETAILS_BLOCKS.contains(&context)
|
||||||
|
{
|
||||||
|
draw_popup(f, app, ArtistDetailsUi::draw, Size::Large);
|
||||||
|
}
|
||||||
|
|
||||||
let draw_edit_artist_prompt = |f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| {
|
let draw_edit_artist_prompt = |f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| {
|
||||||
draw_edit_artist_confirmation_prompt(f, app, prompt_area);
|
draw_edit_artist_confirmation_prompt(f, app, prompt_area);
|
||||||
|
|
||||||
@@ -66,7 +75,19 @@ fn draw_edit_artist_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, ar
|
|||||||
.artist_name
|
.artist_name
|
||||||
.text
|
.text
|
||||||
.clone();
|
.clone();
|
||||||
let title = format!("Edit - {artist_name}");
|
let artist_disambiguation = app
|
||||||
|
.data
|
||||||
|
.lidarr_data
|
||||||
|
.artists
|
||||||
|
.current_selection()
|
||||||
|
.disambiguation
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let title = if artist_disambiguation.is_empty() {
|
||||||
|
format!("Edit - {artist_name}")
|
||||||
|
} else {
|
||||||
|
format!("Edit - {artist_name} ({artist_disambiguation})")
|
||||||
|
};
|
||||||
f.render_widget(title_block_centered(&title), area);
|
f.render_widget(title_block_centered(&title), area);
|
||||||
|
|
||||||
let yes_no_value = app.data.lidarr_data.prompt_confirm;
|
let yes_no_value = app.data.lidarr_data.prompt_confirm;
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ mod tests {
|
|||||||
|
|
||||||
use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus};
|
use crate::models::lidarr_models::{Artist, ArtistStatistics, ArtistStatus};
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ADD_ARTIST_BLOCKS, ActiveLidarrBlock, DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, LIBRARY_BLOCKS,
|
ADD_ARTIST_BLOCKS, ARTIST_DETAILS_BLOCKS, ActiveLidarrBlock, DELETE_ALBUM_BLOCKS,
|
||||||
|
DELETE_ARTIST_BLOCKS, EDIT_ARTIST_BLOCKS, LIBRARY_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::ui::DrawUi;
|
use crate::ui::DrawUi;
|
||||||
use crate::ui::lidarr_ui::library::{LibraryUi, decorate_artist_row_with_style};
|
use crate::ui::lidarr_ui::library::{LibraryUi, decorate_artist_row_with_style};
|
||||||
@@ -17,8 +18,10 @@ mod tests {
|
|||||||
let mut library_ui_blocks = Vec::new();
|
let mut library_ui_blocks = Vec::new();
|
||||||
library_ui_blocks.extend(LIBRARY_BLOCKS);
|
library_ui_blocks.extend(LIBRARY_BLOCKS);
|
||||||
library_ui_blocks.extend(DELETE_ARTIST_BLOCKS);
|
library_ui_blocks.extend(DELETE_ARTIST_BLOCKS);
|
||||||
|
library_ui_blocks.extend(DELETE_ALBUM_BLOCKS);
|
||||||
library_ui_blocks.extend(EDIT_ARTIST_BLOCKS);
|
library_ui_blocks.extend(EDIT_ARTIST_BLOCKS);
|
||||||
library_ui_blocks.extend(ADD_ARTIST_BLOCKS);
|
library_ui_blocks.extend(ADD_ARTIST_BLOCKS);
|
||||||
|
library_ui_blocks.extend(ARTIST_DETAILS_BLOCKS);
|
||||||
|
|
||||||
for active_lidarr_block in ActiveLidarrBlock::iter() {
|
for active_lidarr_block in ActiveLidarrBlock::iter() {
|
||||||
if library_ui_blocks.contains(&active_lidarr_block) {
|
if library_ui_blocks.contains(&active_lidarr_block) {
|
||||||
@@ -182,7 +185,7 @@ mod tests {
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::models::BlockSelectionState;
|
use crate::models::BlockSelectionState;
|
||||||
use crate::models::servarr_data::lidarr::lidarr_data::{
|
use crate::models::servarr_data::lidarr::lidarr_data::{
|
||||||
ActiveLidarrBlock, DELETE_ARTIST_SELECTION_BLOCKS,
|
ActiveLidarrBlock, DELETE_ARTIST_SELECTION_BLOCKS, EDIT_ARTIST_SELECTION_BLOCKS,
|
||||||
};
|
};
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
@@ -263,5 +266,49 @@ mod tests {
|
|||||||
|
|
||||||
insta::assert_snapshot!(output);
|
insta::assert_snapshot!(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_library_ui_renders_artist_details_over_library() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
LibraryUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_library_ui_renders_edit_artist_over_artist_details() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::EditArtistPrompt.into());
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
LibraryUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_library_ui_renders_dropdown_over_edit_artist_over_artist_details() {
|
||||||
|
let mut app = App::test_default_fully_populated();
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::Artists.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::ArtistDetails.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::EditArtistPrompt.into());
|
||||||
|
app.push_navigation_stack(ActiveLidarrBlock::EditArtistSelectMetadataProfile.into());
|
||||||
|
app.data.lidarr_data.selected_block = BlockSelectionState::new(EDIT_ARTIST_SELECTION_BLOCKS);
|
||||||
|
|
||||||
|
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
|
||||||
|
LibraryUi::draw(f, app, f.area());
|
||||||
|
});
|
||||||
|
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use add_artist_ui::AddArtistUi;
|
use add_artist_ui::AddArtistUi;
|
||||||
|
use artist_details_ui::ArtistDetailsUi;
|
||||||
use delete_artist_ui::DeleteArtistUi;
|
use delete_artist_ui::DeleteArtistUi;
|
||||||
use edit_artist_ui::EditArtistUi;
|
use edit_artist_ui::EditArtistUi;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
@@ -28,9 +29,11 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod add_artist_ui;
|
mod add_artist_ui;
|
||||||
|
mod artist_details_ui;
|
||||||
mod delete_artist_ui;
|
mod delete_artist_ui;
|
||||||
mod edit_artist_ui;
|
mod edit_artist_ui;
|
||||||
|
|
||||||
|
mod delete_album_ui;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "library_ui_tests.rs"]
|
#[path = "library_ui_tests.rs"]
|
||||||
mod library_ui_tests;
|
mod library_ui_tests;
|
||||||
@@ -43,6 +46,7 @@ impl DrawUi for LibraryUi {
|
|||||||
return AddArtistUi::accepts(route)
|
return AddArtistUi::accepts(route)
|
||||||
|| DeleteArtistUi::accepts(route)
|
|| DeleteArtistUi::accepts(route)
|
||||||
|| EditArtistUi::accepts(route)
|
|| EditArtistUi::accepts(route)
|
||||||
|
|| ArtistDetailsUi::accepts(route)
|
||||||
|| LIBRARY_BLOCKS.contains(&active_lidarr_block);
|
|| LIBRARY_BLOCKS.contains(&active_lidarr_block);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +60,7 @@ impl DrawUi for LibraryUi {
|
|||||||
match route {
|
match route {
|
||||||
_ if AddArtistUi::accepts(route) => AddArtistUi::draw(f, app, area),
|
_ if AddArtistUi::accepts(route) => AddArtistUi::draw(f, app, area),
|
||||||
_ if DeleteArtistUi::accepts(route) => DeleteArtistUi::draw(f, app, area),
|
_ if DeleteArtistUi::accepts(route) => DeleteArtistUi::draw(f, app, area),
|
||||||
|
_ if ArtistDetailsUi::accepts(route) => ArtistDetailsUi::draw(f, app, area),
|
||||||
_ if EditArtistUi::accepts(route) => EditArtistUi::draw(f, app, area),
|
_ if EditArtistUi::accepts(route) => EditArtistUi::draw(f, app, area),
|
||||||
Route::Lidarr(ActiveLidarrBlock::UpdateAllArtistsPrompt, _) => {
|
Route::Lidarr(ActiveLidarrBlock::UpdateAllArtistsPrompt, _) => {
|
||||||
let confirmation_prompt = ConfirmationPrompt::new()
|
let confirmation_prompt = ConfirmationPrompt::new()
|
||||||
|
|||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
|
||||||
expression: output
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭───────────────────────────────────────────────────── Add Artist ─────────────────────────────────────────────────────╮
|
|
||||||
│Test Artist │
|
|
||||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
||||||
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
||||||
-47
@@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
|
||||||
expression: output
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭───────────────────────────────────────────────────── Add Artist ─────────────────────────────────────────────────────╮
|
|
||||||
│Test Artist │
|
|
||||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
||||||
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
|
||||||
│ ✔ Name Type Status Rating Genres │
|
|
||||||
│=> Test Artist Person Continuing 8.4 soundtrack │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
│ │
|
|
||||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
||||||
+3
-3
@@ -13,8 +13,8 @@ expression: output
|
|||||||
│Test Artist │
|
│Test Artist │
|
||||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ │
|
│ ✔ Name Type Status Rating Genres │
|
||||||
│ │
|
│=> ✔ Test Artist Person Continuing 8.4 soundtrack │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
@@ -26,7 +26,7 @@ expression: output
|
|||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ ╭─────────────── Error ───────────────╮ │
|
│ ╭─────────────── Error ───────────────╮ │
|
||||||
│ │ No artists found matching your query! │ │
|
│ │This artist is already in your library │ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ ╰───────────────────────────────────────╯ │
|
│ ╰───────────────────────────────────────╯ │
|
||||||
│ │
|
│ │
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭───────────────────────────────────────────────────── Add Artist ─────────────────────────────────────────────────────╮
|
||||||
|
│Test Artist │
|
||||||
|
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭──────────────────────────────── Add - Test Artist (American pianist) ─────────────────────────────────╮
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│Test A│ │ │
|
||||||
|
╰──────│ │───────╯
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│ ✔ │ │ │
|
||||||
|
│=> ✔ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Root Folder: │/nfs/music ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Monitor: │All Albums ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Monitor New Items: │All Albums ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Quality Profile: │Any ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Metadata Profile: │Standard ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Tags: │test │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │╭───────────────────────────────────────────────────╮╭──────────────────────────────────────────────────╮│ │
|
||||||
|
│ ││ Add ││ Cancel ││ │
|
||||||
|
╰──────│╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯│───────╯
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭──────────────────────────────── Add - Test Artist (American pianist) ─────────────────────────────────╮
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│Test A│ │ │
|
||||||
|
╰──────│ │───────╯
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│ ✔ │ │ │
|
||||||
|
│=> ✔ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Root Folder: │/nfs/music ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭───────────────────────────────╮──────────────────────────────╮ │ │
|
||||||
|
│ │ │Standard │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Monito│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Qual│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Metad│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ ╰───────────────────────────────╯──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │╭───────────────────────────────────────────────────╮╭──────────────────────────────────────────────────╮│ │
|
||||||
|
│ ││ Add ││ Cancel ││ │
|
||||||
|
╰──────│╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯│───────╯
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭──────────────────────────────── Add - Test Artist (American pianist) ─────────────────────────────────╮
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│Test A│ │ │
|
||||||
|
╰──────│ │───────╯
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│ ✔ │ │ │
|
||||||
|
│=> ✔ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Root Folder: │/nfs/music ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭───────────────────────────────╮──────────────────────────────╮ │ │
|
||||||
|
│ │ │All Albums │ ▼ │ │ │
|
||||||
|
│ │ │Future Albums │──────────────────────────────╯ │ │
|
||||||
|
│ │ │Missing Albums │──────────────────────────────╮ │ │
|
||||||
|
│ │ Monito│Existing Albums │ ▼ │ │ │
|
||||||
|
│ │ │First Album │──────────────────────────────╯ │ │
|
||||||
|
│ │ │Latest Album │──────────────────────────────╮ │ │
|
||||||
|
│ │ Qual│None │ ▼ │ │ │
|
||||||
|
│ │ │Unknown │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Metad│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ ╰───────────────────────────────╯──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │╭───────────────────────────────────────────────────╮╭──────────────────────────────────────────────────╮│ │
|
||||||
|
│ ││ Add ││ Cancel ││ │
|
||||||
|
╰──────│╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯│───────╯
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭──────────────────────────────── Add - Test Artist (American pianist) ─────────────────────────────────╮
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│Test A│ │ │
|
||||||
|
╰──────│ │───────╯
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│ ✔ │ │ │
|
||||||
|
│=> ✔ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Root Folder: │/nfs/music ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭───────────────────────────────╮──────────────────────────────╮ │ │
|
||||||
|
│ │ │All Albums │ ▼ │ │ │
|
||||||
|
│ │ │No New Albums │──────────────────────────────╯ │ │
|
||||||
|
│ │ │New Albums │──────────────────────────────╮ │ │
|
||||||
|
│ │ Monito│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Qual│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Metad│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ ╰───────────────────────────────╯──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │╭───────────────────────────────────────────────────╮╭──────────────────────────────────────────────────╮│ │
|
||||||
|
│ ││ Add ││ Cancel ││ │
|
||||||
|
╰──────│╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯│───────╯
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭──────────────────────────────── Add - Test Artist (American pianist) ─────────────────────────────────╮
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│Test A│ │ │
|
||||||
|
╰──────│ │───────╯
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│ ✔ │ │ │
|
||||||
|
│=> ✔ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Root Folder: │/nfs/music ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭───────────────────────────────╮──────────────────────────────╮ │ │
|
||||||
|
│ │ │Any │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Monito│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Qual│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Metad│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ ╰───────────────────────────────╯──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │╭───────────────────────────────────────────────────╮╭──────────────────────────────────────────────────╮│ │
|
||||||
|
│ ││ Add ││ Cancel ││ │
|
||||||
|
╰──────│╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯│───────╯
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭──────────────────────────────── Add - Test Artist (American pianist) ─────────────────────────────────╮
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│Test A│ │ │
|
||||||
|
╰──────│ │───────╯
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│ ✔ │ │ │
|
||||||
|
│=> ✔ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Root Folder: │/nfs/music ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭───────────────────────────────╮──────────────────────────────╮ │ │
|
||||||
|
│ │ │/nfs/music │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Monito│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Qual│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ Metad│ │ ▼ │ │ │
|
||||||
|
│ │ │ │──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │──────────────────────────────╮ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ ╰───────────────────────────────╯──────────────────────────────╯ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │╭───────────────────────────────────────────────────╮╭──────────────────────────────────────────────────╮│ │
|
||||||
|
│ ││ Add ││ Cancel ││ │
|
||||||
|
╰──────│╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯│───────╯
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/add_artist_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭──────────────────────────────── Add - Test Artist (American pianist) ─────────────────────────────────╮
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│Test A│ │ │
|
||||||
|
╰──────│ │───────╯
|
||||||
|
╭──────│ │───────╮
|
||||||
|
│ ✔ │ │ │
|
||||||
|
│=> ✔ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Root Folder: │/nfs/music ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Monitor: │All Albums ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Monitor New Items: │All Albums ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Quality Profile: │Any ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Metadata Profile: │Standard ▼ │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ ╭─────────────────────────────────────────────────╮ │ │
|
||||||
|
│ │ Tags: │test │ │ │
|
||||||
|
│ │ ╰─────────────────────────────────────────────────╯ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │╭───────────────────────────────────────────────────╮╭──────────────────────────────────────────────────╮│ │
|
||||||
|
│ ││ Add ││ Cancel ││ │
|
||||||
|
╰──────│╰───────────────────────────────────────────────────╯╰──────────────────────────────────────────────────╯│───────╯
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+1
-1
@@ -14,7 +14,7 @@ expression: output
|
|||||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ ✔ Name Type Status Rating Genres │
|
│ ✔ Name Type Status Rating Genres │
|
||||||
│=> Test Artist Person Continuing 8.4 soundtrack │
|
│=> ✔ Test Artist Person Continuing 8.4 soundtrack │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 │
|
||||||
|
│Size on Disk: 0.00 GB │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
|
||||||
|
││ Albums ││
|
||||||
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
|
││ Monitored Title Type Tracks Duration Release Date Size ││
|
||||||
|
││=> 🏷 Test Album Album 10/10 0 min 2023-01-01 0.00 GB ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 ╭──────────────── Automatic Artist Search ────────────────╮ │
|
||||||
|
│Size on Disk: 0.00 GB │Do you want to trigger an automatic search of your indexers│ │
|
||||||
|
│ │ for all monitored album(s) for the artist: Alex? │ │
|
||||||
|
│ │ │ │
|
||||||
|
│╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│
|
||||||
|
││ Albums │ │ ││
|
||||||
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
|
││ Monitored Title │ │ Release Date Size ││
|
||||||
|
││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │╭────────────────────────────╮╭───────────────────────────╮│ ││
|
||||||
|
││ ││ Yes ││ No ││ ││
|
||||||
|
││ │╰────────────────────────────╯╰───────────────────────────╯│ ││
|
||||||
|
││ ╰───────────────────────────────────────────────────────────╯ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 │
|
||||||
|
│Size on Disk: 0.00 GB │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
|
||||||
|
││ Albums ││
|
||||||
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
|
││ Monitored Title Type Tracks Duration Release Date Size ││
|
||||||
|
││=> 🏷 Test Album Album 10/10 0 min 2023-01-01 0.00 GB ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ╭──────────── Error ─────────────╮ ││
|
||||||
|
││ │ No items found matching search │ ││
|
||||||
|
││ ╰──────────────────────────────────╯ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 │
|
||||||
|
│Size on Disk: 0.00 GB │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
|
||||||
|
││ Albums ││
|
||||||
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
|
││ Monitored Title Type Tracks Duration Release Date Size ││
|
||||||
|
││=> 🏷 Test Album Album 10/10 0 min 2023-01-01 0.00 GB ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ╭────────────── Search ───────────────╮ ││
|
||||||
|
││ │album search │ ││
|
||||||
|
││ ╰───────────────────────────────────────╯ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 ╭──────────────────── Update and Scan ────────────────────╮ │
|
||||||
|
│Size on Disk: 0.00 GB │ Do you want to trigger an update and disk scan for the │ │
|
||||||
|
│ │ artist: Alex? │ │
|
||||||
|
│ │ │ │
|
||||||
|
│╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│
|
||||||
|
││ Albums │ │ ││
|
||||||
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
|
││ Monitored Title │ │ Release Date Size ││
|
||||||
|
││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │╭────────────────────────────╮╭───────────────────────────╮│ ││
|
||||||
|
││ ││ Yes ││ No ││ ││
|
||||||
|
││ │╰────────────────────────────╯╰───────────────────────────╯│ ││
|
||||||
|
││ ╰───────────────────────────────────────────────────────────╯ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 ╭──────────────── Automatic Artist Search ────────────────╮ │
|
||||||
|
│Size on Disk: 0.00 GB │Do you want to trigger an automatic search of your indexers│ │
|
||||||
|
│ │ for all monitored album(s) for the artist: Alex? │ │
|
||||||
|
│ │ │ │
|
||||||
|
│╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│
|
||||||
|
││ Albums │ │ ││
|
||||||
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
|
││ Monitored Title │ │ Release Date Size ││
|
||||||
|
││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │╭────────────────────────────╮╭───────────────────────────╮│ ││
|
||||||
|
││ ││ Yes ││ No ││ ││
|
||||||
|
││ │╰────────────────────────────╯╰───────────────────────────╯│ ││
|
||||||
|
││ ╰───────────────────────────────────────────────────────────╯ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 ╭───────────────────── Delete Album ──────────────────────╮ │
|
||||||
|
│Size on Disk: 0.00 GB │ Do you really want to delete the album: │ │
|
||||||
|
│ │ Test Album? │ │
|
||||||
|
│ │ │ │
|
||||||
|
│╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│
|
||||||
|
││ Albums │ ╭───╮ │ ││
|
||||||
|
││─────────────────────────────────────────│ Delete Album Files: │ ✔ │ │───────────────────────────────────────────││
|
||||||
|
││ Monitored Title │ ╰───╯ │ Release Date Size ││
|
||||||
|
││=> 🏷 Test Album │ ╭───╮ │ 2023-01-01 0.00 GB ││
|
||||||
|
││ │ Add List Exclusion: │ │ │ ││
|
||||||
|
││ │ ╰───╯ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │╭────────────────────────────╮╭───────────────────────────╮│ ││
|
||||||
|
││ ││ Yes ││ No ││ ││
|
||||||
|
││ │╰────────────────────────────╯╰───────────────────────────╯│ ││
|
||||||
|
││ ╰───────────────────────────────────────────────────────────╯ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 ╭──────────────────── Update and Scan ────────────────────╮ │
|
||||||
|
│Size on Disk: 0.00 GB │ Do you want to trigger an update and disk scan for the │ │
|
||||||
|
│ │ artist: Alex? │ │
|
||||||
|
│ │ │ │
|
||||||
|
│╭ Artist Details ───────────────────────│ │───────────────────────────────────────────╮│
|
||||||
|
││ Albums │ │ ││
|
||||||
|
││─────────────────────────────────────────│ │───────────────────────────────────────────││
|
||||||
|
││ Monitored Title │ │ Release Date Size ││
|
||||||
|
││=> 🏷 Test Album │ │ 2023-01-01 0.00 GB ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │ │ ││
|
||||||
|
││ │╭────────────────────────────╮╭───────────────────────────╮│ ││
|
||||||
|
││ ││ Yes ││ No ││ ││
|
||||||
|
││ │╰────────────────────────────╯╰───────────────────────────╯│ ││
|
||||||
|
││ ╰───────────────────────────────────────────────────────────╯ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 │
|
||||||
|
│Size on Disk: 0.00 GB │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
|
||||||
|
││ Albums ││
|
||||||
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/artist_details_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭ Alex ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
|
│Artist: Alex │
|
||||||
|
│Overview: some interesting description of the artist │
|
||||||
|
│Disambiguation: American pianist │
|
||||||
|
│Type: Person │
|
||||||
|
│Status: Continuing │
|
||||||
|
│Genres: soundtrack │
|
||||||
|
│Rating: 84% │
|
||||||
|
│Path: /nfs/music/test-artist │
|
||||||
|
│Quality Profile: Lossless │
|
||||||
|
│Metadata Profile: Standard │
|
||||||
|
│Monitored: Yes │
|
||||||
|
│Albums: 1 │
|
||||||
|
│Tracks: 15/15 │
|
||||||
|
│Size on Disk: 0.00 GB │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭ Artist Details ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮│
|
||||||
|
││ Albums ││
|
||||||
|
││─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ Loading ... ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
││ ││
|
||||||
|
│╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
source: src/ui/lidarr_ui/library/delete_album_ui_tests.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
╭───────────────────── Delete Album ──────────────────────╮
|
||||||
|
│ Do you really want to delete the album: │
|
||||||
|
│ Test Album? │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ ╭───╮ │
|
||||||
|
│ Delete Album Files: │ ✔ │ │
|
||||||
|
│ ╰───╯ │
|
||||||
|
│ ╭───╮ │
|
||||||
|
│ Add List Exclusion: │ │ │
|
||||||
|
│ ╰───╯ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│╭────────────────────────────╮╭───────────────────────────╮│
|
||||||
|
││ Yes ││ No ││
|
||||||
|
│╰────────────────────────────╯╰───────────────────────────╯│
|
||||||
|
╰───────────────────────────────────────────────────────────╯
|
||||||
+1
-1
@@ -20,7 +20,7 @@ expression: output
|
|||||||
|
|
||||||
╭───────────────────── Delete Artist ─────────────────────╮
|
╭───────────────────── Delete Artist ─────────────────────╮
|
||||||
│ Do you really want to delete the artist: │
|
│ Do you really want to delete the artist: │
|
||||||
│ ? │
|
│ Alex? │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ ╭───╮ │
|
│ ╭───╮ │
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ expression: output
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮
|
╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ expression: output
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮
|
╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ expression: output
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮
|
╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ expression: output
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮
|
╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ expression: output
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
╭─────────────────────────────────────────────── Edit - ───────────────────────────────────────────────╮
|
╭─────────────────────────────────── Edit - Alex (American pianist) ────────────────────────────────────╮
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user