diff --git a/src/app/app_tests.rs b/src/app/app_tests.rs index 8975ea0..2fb02a0 100644 --- a/src/app/app_tests.rs +++ b/src/app/app_tests.rs @@ -213,7 +213,7 @@ mod tests { ); assert_eq!( sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetOverview.into() + RadarrEvent::GetDiskSpace.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), diff --git a/src/app/radarr/mod.rs b/src/app/radarr/mod.rs index 3ffc43a..1c7f181 100644 --- a/src/app/radarr/mod.rs +++ b/src/app/radarr/mod.rs @@ -179,7 +179,7 @@ impl<'a> App<'a> { .dispatch_network_event(RadarrEvent::GetDownloads.into()) .await; self - .dispatch_network_event(RadarrEvent::GetOverview.into()) + .dispatch_network_event(RadarrEvent::GetDiskSpace.into()) .await; self .dispatch_network_event(RadarrEvent::GetStatus.into()) diff --git a/src/app/radarr/radarr_tests.rs b/src/app/radarr/radarr_tests.rs index 7a2ab5a..e4be8f1 100644 --- a/src/app/radarr/radarr_tests.rs +++ b/src/app/radarr/radarr_tests.rs @@ -511,7 +511,7 @@ mod tests { ); assert_eq!( sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetOverview.into() + RadarrEvent::GetDiskSpace.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), @@ -544,7 +544,7 @@ mod tests { ); assert_eq!( sync_network_rx.recv().await.unwrap(), - RadarrEvent::GetOverview.into() + RadarrEvent::GetDiskSpace.into() ); assert_eq!( sync_network_rx.recv().await.unwrap(), diff --git a/src/cli/sonarr/delete_command_handler.rs b/src/cli/sonarr/delete_command_handler.rs index 038830e..32a9d28 100644 --- a/src/cli/sonarr/delete_command_handler.rs +++ b/src/cli/sonarr/delete_command_handler.rs @@ -42,6 +42,11 @@ pub enum SonarrDeleteCommand { #[arg(long, help = "The ID of the root folder to delete", required = true)] root_folder_id: i64, }, + #[command(about = "Delete the tag with the specified ID")] + Tag { + #[arg(long, help = "The ID of the tag to delete", required = true)] + tag_id: i64, + }, } impl From for Command { @@ -99,6 +104,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, SonarrDeleteCommand> for SonarrDeleteComm .await?; serde_json::to_string_pretty(&resp)? } + SonarrDeleteCommand::Tag { tag_id } => { + let resp = self + .network + .handle_network_event(SonarrEvent::DeleteTag(tag_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } }; Ok(resp) diff --git a/src/cli/sonarr/delete_command_handler_tests.rs b/src/cli/sonarr/delete_command_handler_tests.rs index 9953225..8c66a4e 100644 --- a/src/cli/sonarr/delete_command_handler_tests.rs +++ b/src/cli/sonarr/delete_command_handler_tests.rs @@ -156,6 +156,31 @@ mod tests { assert_eq!(delete_command, expected_args); } } + + #[test] + fn test_delete_tag_requires_arguments() { + let result = Cli::command().try_get_matches_from(["managarr", "sonarr", "delete", "tag"]); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_delete_tag_success() { + let expected_args = SonarrDeleteCommand::Tag { tag_id: 1 }; + + let result = Cli::try_parse_from(["managarr", "sonarr", "delete", "tag", "--tag-id", "1"]); + + assert!(result.is_ok()); + + if let Some(Command::Sonarr(SonarrCommand::Delete(delete_command))) = result.unwrap().command + { + assert_eq!(delete_command, expected_args); + } + } } mod handler { @@ -283,5 +308,31 @@ mod tests { assert!(result.is_ok()); } + + #[tokio::test] + async fn test_handle_delete_tag_command() { + let expected_tag_id = 1; + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + SonarrEvent::DeleteTag(expected_tag_id).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Sonarr(SonarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::default())); + let delete_tag_command = SonarrDeleteCommand::Tag { tag_id: 1 }; + + let result = + SonarrDeleteCommandHandler::with(&app_arc, delete_tag_command, &mut mock_network) + .handle() + .await; + + assert!(result.is_ok()); + } } } diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index 9cf06cf..981f69c 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -10,8 +10,8 @@ use strum_macros::EnumIter; use crate::{models::HorizontallyScrollableText, serde_enum_from}; use super::servarr_models::{ - HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, QueueEvent, Release, - RootFolder, SecurityConfig, Tag, + DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, + QueueEvent, Release, RootFolder, SecurityConfig, Tag, }; use super::{EnumDisplayStyle, Serdeable}; @@ -149,15 +149,6 @@ pub struct DeleteMovieParams { pub add_list_exclusion: bool, } -#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct DiskSpace { - #[serde(deserialize_with = "super::from_i64")] - pub free_space: i64, - #[serde(deserialize_with = "super::from_i64")] - pub total_space: i64, -} - #[derive(Derivative, Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct DownloadRecord { diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index 4f7e6a4..1e2ada9 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -6,14 +6,14 @@ use crate::app::radarr::radarr_context_clues::{ SYSTEM_CONTEXT_CLUES, }; use crate::models::radarr_models::{ - AddMovieSearchResult, BlocklistItem, Collection, CollectionMovie, DiskSpace, DownloadRecord, + AddMovieSearchResult, BlocklistItem, Collection, CollectionMovie, DownloadRecord, IndexerSettings, Movie, Task, }; use crate::models::servarr_data::radarr::modals::{ AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem, MovieDetailsModal, }; -use crate::models::servarr_models::{Indexer, QueueEvent, RootFolder}; +use crate::models::servarr_models::{DiskSpace, Indexer, QueueEvent, RootFolder}; use crate::models::stateful_list::StatefulList; use crate::models::stateful_table::StatefulTable; use crate::models::{ diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index 211582c..c18849b 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -3,7 +3,7 @@ use chrono::{DateTime, Utc}; use strum::EnumIter; use crate::models::{ - servarr_models::{Indexer, QueueEvent, RootFolder}, + servarr_models::{DiskSpace, Indexer, QueueEvent, RootFolder}, sonarr_models::{ BlocklistItem, DownloadRecord, IndexerSettings, Season, Series, SonarrHistoryItem, }, @@ -21,6 +21,7 @@ mod sonarr_data_tests; pub struct SonarrData { pub blocklist: StatefulTable, pub downloads: StatefulTable, + pub disk_space_vec: Vec, pub edit_root_folder: Option, pub history: StatefulTable, pub indexers: StatefulTable, @@ -43,6 +44,7 @@ impl Default for SonarrData { SonarrData { blocklist: StatefulTable::default(), downloads: StatefulTable::default(), + disk_space_vec: Vec::new(), edit_root_folder: None, history: StatefulTable::default(), indexers: StatefulTable::default(), diff --git a/src/models/servarr_data/sonarr/sonarr_data_tests.rs b/src/models/servarr_data/sonarr/sonarr_data_tests.rs index 5c174be..8fc8cc5 100644 --- a/src/models/servarr_data/sonarr/sonarr_data_tests.rs +++ b/src/models/servarr_data/sonarr/sonarr_data_tests.rs @@ -36,6 +36,7 @@ mod tests { assert!(sonarr_data.blocklist.is_empty()); assert!(sonarr_data.downloads.is_empty()); + assert!(sonarr_data.disk_space_vec.is_empty()); assert!(sonarr_data.edit_root_folder.is_none()); assert!(sonarr_data.history.is_empty()); assert!(sonarr_data.indexers.is_empty()); diff --git a/src/models/servarr_models.rs b/src/models/servarr_models.rs index 68f78ef..82cdc9e 100644 --- a/src/models/servarr_models.rs +++ b/src/models/servarr_models.rs @@ -74,6 +74,15 @@ impl Display for CertificateValidation { } } +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct DiskSpace { + #[serde(deserialize_with = "super::from_i64")] + pub free_space: i64, + #[serde(deserialize_with = "super::from_i64")] + pub total_space: i64, +} + #[derive(Default, Deserialize, Serialize, Debug, Clone, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct HostConfig { diff --git a/src/models/sonarr_models.rs b/src/models/sonarr_models.rs index 72d5f17..4a8ea3e 100644 --- a/src/models/sonarr_models.rs +++ b/src/models/sonarr_models.rs @@ -11,8 +11,8 @@ use crate::serde_enum_from; use super::{ servarr_models::{ - HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, QueueEvent, - Release, RootFolder, SecurityConfig, Tag, + DiskSpace, HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, + QueueEvent, Release, RootFolder, SecurityConfig, Tag, }, EnumDisplayStyle, HorizontallyScrollableText, Serdeable, }; @@ -394,6 +394,7 @@ pub struct SonarrHistoryItem { pub enum SonarrSerdeable { Value(Value), DownloadsResponse(DownloadsResponse), + DiskSpaces(Vec), Episode(Episode), Episodes(Vec), HostConfig(HostConfig), @@ -431,6 +432,7 @@ serde_enum_from!( SonarrSerdeable { Value(Value), DownloadsResponse(DownloadsResponse), + DiskSpaces(Vec), Episode(Episode), Episodes(Vec), HostConfig(HostConfig), diff --git a/src/models/sonarr_models_tests.rs b/src/models/sonarr_models_tests.rs index 363ad4f..44f9446 100644 --- a/src/models/sonarr_models_tests.rs +++ b/src/models/sonarr_models_tests.rs @@ -5,8 +5,8 @@ mod tests { use crate::models::{ servarr_models::{ - HostConfig, Indexer, Log, LogResponse, QualityProfile, QueueEvent, Release, RootFolder, - SecurityConfig, Tag, + DiskSpace, HostConfig, Indexer, Log, LogResponse, QualityProfile, QueueEvent, Release, + RootFolder, SecurityConfig, Tag, }, sonarr_models::{ BlocklistItem, BlocklistResponse, DownloadRecord, DownloadsResponse, Episode, @@ -291,6 +291,18 @@ mod tests { ); } + #[test] + fn test_sonarr_serdeable_from_disk_spaces() { + let disk_spaces = vec![DiskSpace { + free_space: 1, + total_space: 1, + }]; + + let sonarr_serdeable: SonarrSerdeable = disk_spaces.clone().into(); + + assert_eq!(sonarr_serdeable, SonarrSerdeable::DiskSpaces(disk_spaces)); + } + #[test] fn test_sonarr_serdeable_from_log_response() { let log_response = LogResponse { diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 717fe32..1f70e65 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -8,7 +8,7 @@ use urlencoding::encode; use crate::models::radarr_models::{ AddMovieBody, AddMovieSearchResult, AddOptions, BlocklistResponse, Collection, CollectionMovie, - CommandBody, Credit, CreditType, DeleteMovieParams, DiskSpace, DownloadRecord, DownloadsResponse, + CommandBody, Credit, CreditType, DeleteMovieParams, DownloadRecord, DownloadsResponse, EditCollectionParams, EditIndexerParams, EditMovieParams, IndexerSettings, IndexerTestResult, Movie, MovieCommandBody, MovieHistoryItem, RadarrSerdeable, ReleaseDownloadBody, SystemStatus, Task, TaskName, Update, @@ -19,8 +19,8 @@ use crate::models::servarr_data::radarr::modals::{ }; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; use crate::models::servarr_models::{ - AddRootFolderBody, HostConfig, Indexer, LogResponse, QualityProfile, QueueEvent, Release, - RootFolder, SecurityConfig, Tag, + AddRootFolderBody, DiskSpace, HostConfig, Indexer, LogResponse, QualityProfile, QueueEvent, + Release, RootFolder, SecurityConfig, Tag, }; use crate::models::stateful_table::StatefulTable; use crate::models::{HorizontallyScrollableText, Route, Scrollable, ScrollableText}; @@ -61,7 +61,7 @@ pub enum RadarrEvent { GetMovieDetails(Option), GetMovieHistory(Option), GetMovies, - GetOverview, + GetDiskSpace, GetQualityProfiles, GetQueuedEvents, GetReleases(Option), @@ -107,7 +107,7 @@ impl NetworkResource for RadarrEvent { RadarrEvent::SearchNewMovie(_) => "/movie/lookup", RadarrEvent::GetMovieCredits(_) => "/credit", RadarrEvent::GetMovieHistory(_) => "/history/movie", - RadarrEvent::GetOverview => "/diskspace", + RadarrEvent::GetDiskSpace => "/diskspace", RadarrEvent::GetQualityProfiles => "/qualityprofile", RadarrEvent::GetReleases(_) | RadarrEvent::DownloadRelease(_) => "/release", RadarrEvent::AddRootFolder(_) @@ -220,7 +220,7 @@ impl<'a, 'b> Network<'a, 'b> { .await .map(RadarrSerdeable::from), RadarrEvent::GetMovies => self.get_movies().await.map(RadarrSerdeable::from), - RadarrEvent::GetOverview => self.get_diskspace().await.map(RadarrSerdeable::from), + RadarrEvent::GetDiskSpace => self.get_radarr_diskspace().await.map(RadarrSerdeable::from), RadarrEvent::GetQualityProfiles => self .get_radarr_quality_profiles() .await @@ -1368,9 +1368,9 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_diskspace(&mut self) -> Result> { + async fn get_radarr_diskspace(&mut self) -> Result> { info!("Fetching Radarr disk space"); - let event = RadarrEvent::GetOverview; + let event = RadarrEvent::GetDiskSpace; let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, None) diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index bfadf03..a36c4dc 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -222,7 +222,7 @@ mod test { #[case(RadarrEvent::SearchNewMovie(None), "/movie/lookup")] #[case(RadarrEvent::GetMovieCredits(None), "/credit")] #[case(RadarrEvent::GetMovieHistory(None), "/history/movie")] - #[case(RadarrEvent::GetOverview, "/diskspace")] + #[case(RadarrEvent::GetDiskSpace, "/diskspace")] #[case(RadarrEvent::GetQualityProfiles, "/qualityprofile")] #[case(RadarrEvent::GetStatus, "/system/status")] #[case(RadarrEvent::GetTasks, "/system/task")] @@ -262,7 +262,7 @@ mod test { } #[tokio::test] - async fn test_handle_get_diskspace_event() { + async fn test_handle_get_radarr_diskspace_event() { let (async_server, app_arc, _server) = mock_servarr_api( RequestMethod::Get, None, @@ -277,7 +277,7 @@ mod test { } ])), None, - RadarrEvent::GetOverview, + RadarrEvent::GetDiskSpace, None, None, ) @@ -295,7 +295,7 @@ mod test { ]; if let RadarrSerdeable::DiskSpaces(disk_space) = network - .handle_radarr_event(RadarrEvent::GetOverview) + .handle_radarr_event(RadarrEvent::GetDiskSpace) .await .unwrap() { diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index 22341b5..680016e 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -10,8 +10,8 @@ use crate::{ sonarr_data::ActiveSonarrBlock, }, servarr_models::{ - AddRootFolderBody, HostConfig, Indexer, LogResponse, QualityProfile, QueueEvent, Release, - RootFolder, SecurityConfig, Tag, + AddRootFolderBody, DiskSpace, HostConfig, Indexer, LogResponse, QualityProfile, QueueEvent, + Release, RootFolder, SecurityConfig, Tag, }, sonarr_models::{ BlocklistResponse, DownloadRecord, DownloadsResponse, Episode, IndexerSettings, Series, @@ -49,6 +49,7 @@ pub enum SonarrEvent { GetEpisodes(Option), GetEpisodeHistory(Option), GetLogs(Option), + GetDiskSpace, GetQualityProfiles, GetQueuedEvents, GetRootFolders, @@ -77,6 +78,7 @@ impl NetworkResource for SonarrEvent { SonarrEvent::GetHostConfig | SonarrEvent::GetSecurityConfig => "/config/host", SonarrEvent::GetIndexers | SonarrEvent::DeleteIndexer(_) => "/indexer", SonarrEvent::GetLogs(_) => "/log", + SonarrEvent::GetDiskSpace => "/diskspace", SonarrEvent::GetQualityProfiles => "/qualityprofile", SonarrEvent::GetQueuedEvents => "/command", SonarrEvent::GetRootFolders @@ -163,6 +165,7 @@ impl<'a, 'b> Network<'a, 'b> { .get_sonarr_logs(events) .await .map(SonarrSerdeable::from), + SonarrEvent::GetDiskSpace => self.get_sonarr_diskspace().await.map(SonarrSerdeable::from), SonarrEvent::GetQualityProfiles => self .get_sonarr_quality_profiles() .await @@ -843,6 +846,21 @@ impl<'a, 'b> Network<'a, 'b> { .await } + async fn get_sonarr_diskspace(&mut self) -> Result> { + info!("Fetching Sonarr disk space"); + let event = SonarrEvent::GetDiskSpace; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), Vec>(request_props, |disk_space_vec, mut app| { + app.data.sonarr_data.disk_space_vec = disk_space_vec; + }) + .await + } + async fn get_sonarr_quality_profiles(&mut self) -> Result> { info!("Fetching Sonarr quality profiles"); let event = SonarrEvent::GetQualityProfiles; diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index 1401935..6737d39 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -17,7 +17,7 @@ mod test { use crate::models::servarr_data::sonarr::modals::{EpisodeDetailsModal, SeasonDetailsModal}; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_models::{ - HostConfig, Indexer, IndexerField, Language, LogResponse, Quality, QualityProfile, + DiskSpace, HostConfig, Indexer, IndexerField, Language, LogResponse, Quality, QualityProfile, QualityWrapper, QueueEvent, Release, RootFolder, SecurityConfig, Tag, }; use crate::models::sonarr_models::SystemStatus; @@ -212,6 +212,7 @@ mod test { #[case(SonarrEvent::DeleteBlocklistItem(None), "/blocklist")] #[case(SonarrEvent::HealthCheck, "/health")] #[case(SonarrEvent::GetBlocklist, "/blocklist?page=1&pageSize=10000")] + #[case(SonarrEvent::GetDiskSpace, "/diskspace")] #[case(SonarrEvent::GetSeriesHistory(None), "/history/series")] #[case(SonarrEvent::GetLogs(Some(500)), "/log")] #[case(SonarrEvent::GetQualityProfiles, "/qualityprofile")] @@ -784,6 +785,53 @@ mod test { } } + #[tokio::test] + async fn test_handle_get_sonarr_diskspace_event() { + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(json!([ + { + "freeSpace": 1111, + "totalSpace": 2222, + }, + { + "freeSpace": 3333, + "totalSpace": 4444 + } + ])), + None, + SonarrEvent::GetDiskSpace, + None, + None, + ) + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + let disk_space_vec = vec![ + DiskSpace { + free_space: 1111, + total_space: 2222, + }, + DiskSpace { + free_space: 3333, + total_space: 4444, + }, + ]; + + if let SonarrSerdeable::DiskSpaces(disk_space) = network + .handle_sonarr_event(SonarrEvent::GetDiskSpace) + .await + .unwrap() + { + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.sonarr_data.disk_space_vec, + disk_space_vec + ); + assert_eq!(disk_space, disk_space_vec); + } + } + #[tokio::test] async fn test_handle_get_sonarr_healthcheck_event() { let (async_server, app_arc, _server) = mock_servarr_api( diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index ddc2422..d19fabc 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -9,9 +9,9 @@ use ratatui::Frame; use crate::app::App; use crate::logos::RADARR_LOGO; -use crate::models::radarr_models::{DiskSpace, DownloadRecord, Movie}; +use crate::models::radarr_models::{DownloadRecord, Movie}; use crate::models::servarr_data::radarr::radarr_data::RadarrData; -use crate::models::servarr_models::RootFolder; +use crate::models::servarr_models::{DiskSpace, RootFolder}; use crate::models::Route; use crate::ui::draw_tabs; use crate::ui::radarr_ui::blocklist::BlocklistUi;