From 6b64b5ecc4852195deb1229440301465e67577f1 Mon Sep 17 00:00:00 2001 From: Alex Clarke Date: Tue, 19 Nov 2024 12:01:07 -0700 Subject: [PATCH] feat(network): Added support for fetching Sonarr queued events --- .../system/system_details_handler_tests.rs | 3 +- .../system/system_handler_tests.rs | 3 +- src/models/radarr_models.rs | 15 +---- src/models/radarr_models_tests.rs | 4 +- src/models/servarr_data/radarr/radarr_data.rs | 4 +- src/models/servarr_data/sonarr/sonarr_data.rs | 32 +++++----- .../servarr_data/sonarr/sonarr_data_tests.rs | 15 ++--- src/models/servarr_models.rs | 14 +++++ src/models/sonarr_models.rs | 4 +- src/models/sonarr_models_tests.rs | 14 ++++- src/network/radarr_network.rs | 13 ++-- src/network/radarr_network_tests.rs | 2 +- src/network/sonarr_network.rs | 27 ++++++++- src/network/sonarr_network_tests.rs | 60 ++++++++++++++++++- src/ui/radarr_ui/system/mod.rs | 3 +- 15 files changed, 160 insertions(+), 53 deletions(-) diff --git a/src/handlers/radarr_handlers/system/system_details_handler_tests.rs b/src/handlers/radarr_handlers/system/system_details_handler_tests.rs index c8947a8..cde9d2f 100644 --- a/src/handlers/radarr_handlers/system/system_details_handler_tests.rs +++ b/src/handlers/radarr_handlers/system/system_details_handler_tests.rs @@ -8,10 +8,11 @@ mod tests { use crate::event::Key; use crate::handlers::radarr_handlers::system::system_details_handler::SystemDetailsHandler; use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::{QueueEvent, Task}; + use crate::models::radarr_models::Task; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS, }; + use crate::models::servarr_models::QueueEvent; use crate::models::{HorizontallyScrollableText, ScrollableText}; mod test_handle_scroll_up_and_down { diff --git a/src/handlers/radarr_handlers/system/system_handler_tests.rs b/src/handlers/radarr_handlers/system/system_handler_tests.rs index 5f2234d..b1c864a 100644 --- a/src/handlers/radarr_handlers/system/system_handler_tests.rs +++ b/src/handlers/radarr_handlers/system/system_handler_tests.rs @@ -9,10 +9,11 @@ mod tests { use crate::event::Key; use crate::handlers::radarr_handlers::system::SystemHandler; use crate::handlers::KeyEventHandler; - use crate::models::radarr_models::{QueueEvent, Task}; + use crate::models::radarr_models::Task; use crate::models::servarr_data::radarr::radarr_data::{ ActiveRadarrBlock, SYSTEM_DETAILS_BLOCKS, }; + use crate::models::servarr_models::QueueEvent; use crate::test_handler_delegation; mod test_handle_left_right_action { diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index 5934cfc..83aef7c 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -9,7 +9,7 @@ use strum_macros::EnumIter; use crate::{models::HorizontallyScrollableText, serde_enum_from}; -use super::servarr_models::{HostConfig, Indexer, SecurityConfig}; +use super::servarr_models::{HostConfig, Indexer, QueueEvent, SecurityConfig}; use super::Serdeable; #[cfg(test)] @@ -462,19 +462,6 @@ pub struct QualityWrapper { pub quality: Quality, } -#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct QueueEvent { - pub trigger: String, - pub name: String, - pub command_name: String, - pub status: String, - pub queued: DateTime, - pub started: Option>, - pub ended: Option>, - pub duration: Option, -} - #[derive(Derivative, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[derivative(Default)] pub struct Rating { diff --git a/src/models/radarr_models_tests.rs b/src/models/radarr_models_tests.rs index 90ce5e2..da0c5df 100644 --- a/src/models/radarr_models_tests.rs +++ b/src/models/radarr_models_tests.rs @@ -8,9 +8,9 @@ mod tests { AddMovieSearchResult, BlocklistItem, BlocklistResponse, Collection, Credit, DiskSpace, DownloadRecord, DownloadsResponse, Indexer, IndexerSettings, IndexerTestResult, Log, LogResponse, MinimumAvailability, Monitor, Movie, MovieHistoryItem, QualityProfile, - QueueEvent, RadarrSerdeable, Release, RootFolder, SystemStatus, Tag, Task, TaskName, Update, + RadarrSerdeable, Release, RootFolder, SystemStatus, Tag, Task, TaskName, Update, }, - servarr_models::{HostConfig, SecurityConfig}, + servarr_models::{HostConfig, QueueEvent, SecurityConfig}, Serdeable, }; diff --git a/src/models/servarr_data/radarr/radarr_data.rs b/src/models/servarr_data/radarr/radarr_data.rs index e6ca06f..e3d4a7c 100644 --- a/src/models/servarr_data/radarr/radarr_data.rs +++ b/src/models/servarr_data/radarr/radarr_data.rs @@ -7,13 +7,13 @@ use crate::app::radarr::radarr_context_clues::{ }; use crate::models::radarr_models::{ AddMovieSearchResult, BlocklistItem, Collection, CollectionMovie, DiskSpace, DownloadRecord, - IndexerSettings, Movie, QueueEvent, RootFolder, Task, + IndexerSettings, Movie, RootFolder, Task, }; use crate::models::servarr_data::radarr::modals::{ AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem, MovieDetailsModal, }; -use crate::models::servarr_models::Indexer; +use crate::models::servarr_models::{Indexer, QueueEvent}; 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 b2eeee9..427ea0f 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, + servarr_models::{Indexer, QueueEvent}, sonarr_models::{BlocklistItem, DownloadRecord, Episode, IndexerSettings, Series}, stateful_list::StatefulList, stateful_table::StatefulTable, @@ -18,35 +18,37 @@ use super::modals::EpisodeDetailsModal; mod sonarr_data_tests; pub struct SonarrData { - pub version: String, - pub start_time: DateTime, - pub series: StatefulTable, pub blocklist: StatefulTable, - pub logs: StatefulList, - pub episodes_tree: StatefulTree, - pub episodes_table: StatefulTable, pub downloads: StatefulTable, pub episode_details_modal: Option, - pub quality_profile_map: BiMap, + pub episodes_table: StatefulTable, + pub episodes_tree: StatefulTree, pub indexers: StatefulTable, pub indexer_settings: Option, + pub logs: StatefulList, + pub quality_profile_map: BiMap, + pub queued_events: StatefulTable, + pub series: StatefulTable, + pub start_time: DateTime, + pub version: String, } impl Default for SonarrData { fn default() -> SonarrData { SonarrData { - version: String::new(), - start_time: DateTime::default(), - series: StatefulTable::default(), blocklist: StatefulTable::default(), - logs: StatefulList::default(), - episodes_tree: StatefulTree::default(), - episodes_table: StatefulTable::default(), downloads: StatefulTable::default(), episode_details_modal: None, - quality_profile_map: BiMap::new(), + episodes_table: StatefulTable::default(), + episodes_tree: StatefulTree::default(), indexers: StatefulTable::default(), indexer_settings: None, + logs: StatefulList::default(), + quality_profile_map: BiMap::new(), + queued_events: StatefulTable::default(), + series: StatefulTable::default(), + start_time: DateTime::default(), + version: String::new(), } } } diff --git a/src/models/servarr_data/sonarr/sonarr_data_tests.rs b/src/models/servarr_data/sonarr/sonarr_data_tests.rs index 66ba96c..49efe6c 100644 --- a/src/models/servarr_data/sonarr/sonarr_data_tests.rs +++ b/src/models/servarr_data/sonarr/sonarr_data_tests.rs @@ -34,18 +34,19 @@ mod tests { fn test_sonarr_data_defaults() { let sonarr_data = SonarrData::default(); - assert!(sonarr_data.version.is_empty()); - assert_eq!(sonarr_data.start_time, >::default()); - assert!(sonarr_data.series.is_empty()); assert!(sonarr_data.blocklist.is_empty()); - assert!(sonarr_data.logs.is_empty()); - assert!(sonarr_data.episodes_tree.is_empty()); - assert!(sonarr_data.episodes_table.is_empty()); assert!(sonarr_data.downloads.is_empty()); assert!(sonarr_data.episode_details_modal.is_none()); - assert!(sonarr_data.quality_profile_map.is_empty()); + assert!(sonarr_data.episodes_table.is_empty()); + assert!(sonarr_data.episodes_tree.is_empty()); assert!(sonarr_data.indexers.is_empty()); assert!(sonarr_data.indexer_settings.is_none()); + assert!(sonarr_data.logs.is_empty()); + assert!(sonarr_data.quality_profile_map.is_empty()); + assert!(sonarr_data.queued_events.is_empty()); + assert!(sonarr_data.series.is_empty()); + assert_eq!(sonarr_data.start_time, >::default()); + assert!(sonarr_data.version.is_empty()); } } } diff --git a/src/models/servarr_models.rs b/src/models/servarr_models.rs index 0944b74..8c04e9e 100644 --- a/src/models/servarr_models.rs +++ b/src/models/servarr_models.rs @@ -1,5 +1,6 @@ use std::fmt::{Display, Formatter, Result}; +use chrono::{DateTime, Utc}; use clap::ValueEnum; use serde::{Deserialize, Serialize}; use serde_json::{Number, Value}; @@ -113,6 +114,19 @@ pub struct IndexerField { pub value: Option, } +#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct QueueEvent { + pub trigger: String, + pub name: String, + pub command_name: String, + pub status: String, + pub queued: DateTime, + pub started: Option>, + pub ended: Option>, + pub duration: Option, +} + #[derive(Default, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct SecurityConfig { diff --git a/src/models/sonarr_models.rs b/src/models/sonarr_models.rs index 7e3505d..a712e7f 100644 --- a/src/models/sonarr_models.rs +++ b/src/models/sonarr_models.rs @@ -10,7 +10,7 @@ use strum::EnumIter; use crate::serde_enum_from; use super::{ - servarr_models::{HostConfig, Indexer, SecurityConfig}, + servarr_models::{HostConfig, Indexer, QueueEvent, SecurityConfig}, HorizontallyScrollableText, Serdeable, }; @@ -352,6 +352,7 @@ pub enum SonarrSerdeable { IndexerSettings(IndexerSettings), Indexers(Vec), QualityProfiles(Vec), + QueueEvents(Vec), SecurityConfig(SecurityConfig), SeriesVec(Vec), SystemStatus(SystemStatus), @@ -381,6 +382,7 @@ serde_enum_from!( IndexerSettings(IndexerSettings), Indexers(Vec), QualityProfiles(Vec), + QueueEvents(Vec), SecurityConfig(SecurityConfig), SeriesVec(Vec), SystemStatus(SystemStatus), diff --git a/src/models/sonarr_models_tests.rs b/src/models/sonarr_models_tests.rs index d561b7f..4ff109a 100644 --- a/src/models/sonarr_models_tests.rs +++ b/src/models/sonarr_models_tests.rs @@ -4,7 +4,7 @@ mod tests { use serde_json::json; use crate::models::{ - servarr_models::{HostConfig, Indexer, SecurityConfig}, + servarr_models::{HostConfig, Indexer, QueueEvent, SecurityConfig}, sonarr_models::{ BlocklistItem, BlocklistResponse, DownloadRecord, DownloadsResponse, Episode, IndexerSettings, Log, LogResponse, QualityProfile, Series, SeriesStatus, SeriesType, @@ -231,6 +231,18 @@ mod tests { ); } + #[test] + fn test_sonarr_serdeable_from_queue_events() { + let queue_events = vec![QueueEvent { + trigger: "test".to_owned(), + ..QueueEvent::default() + }]; + + let sonarr_serdeable: SonarrSerdeable = queue_events.clone().into(); + + assert_eq!(sonarr_serdeable, SonarrSerdeable::QueueEvents(queue_events)); + } + #[test] fn test_sonarr_serdeable_from_security_config() { let security_config = SecurityConfig { diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 9a48bb8..304895e 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -11,15 +11,15 @@ use crate::models::radarr_models::{ CollectionMovie, CommandBody, Credit, CreditType, DeleteMovieParams, DiskSpace, DownloadRecord, DownloadsResponse, EditCollectionParams, EditIndexerParams, EditMovieParams, IndexerSettings, IndexerTestResult, LogResponse, Movie, MovieCommandBody, MovieHistoryItem, QualityProfile, - QueueEvent, RadarrSerdeable, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task, - TaskName, Update, + RadarrSerdeable, Release, ReleaseDownloadBody, RootFolder, SystemStatus, Tag, Task, TaskName, + Update, }; use crate::models::servarr_data::radarr::modals::{ AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem, MovieDetailsModal, }; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; -use crate::models::servarr_models::{HostConfig, Indexer, SecurityConfig}; +use crate::models::servarr_models::{HostConfig, Indexer, QueueEvent, SecurityConfig}; use crate::models::stateful_table::StatefulTable; use crate::models::{HorizontallyScrollableText, Route, Scrollable, ScrollableText}; use crate::network::{Network, NetworkEvent, RequestMethod}; @@ -219,7 +219,10 @@ impl<'a, 'b> Network<'a, 'b> { .get_radarr_quality_profiles() .await .map(RadarrSerdeable::from), - RadarrEvent::GetQueuedEvents => self.get_queued_events().await.map(RadarrSerdeable::from), + RadarrEvent::GetQueuedEvents => self + .get_queued_radarr_events() + .await + .map(RadarrSerdeable::from), RadarrEvent::GetReleases(movie_id) => { self.get_releases(movie_id).await.map(RadarrSerdeable::from) } @@ -1728,7 +1731,7 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn get_queued_events(&mut self) -> Result> { + async fn get_queued_radarr_events(&mut self) -> Result> { info!("Fetching Radarr queued events"); let event = RadarrEvent::GetQueuedEvents; diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index d4e5344..c80f719 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -2399,7 +2399,7 @@ mod test { } #[tokio::test] - async fn test_handle_get_queued_events_event() { + async fn test_handle_get_queued_radarr_events_event() { let queued_events_json = json!([{ "name": "RefreshMonitoredDownloads", "commandName": "Refresh Monitored Downloads", diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index 0ba51a4..2b1676d 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -9,7 +9,7 @@ use serde_json::{json, Value}; use crate::{ models::{ servarr_data::sonarr::{modals::EpisodeDetailsModal, sonarr_data::ActiveSonarrBlock}, - servarr_models::{HostConfig, Indexer, SecurityConfig}, + servarr_models::{HostConfig, Indexer, QueueEvent, SecurityConfig}, sonarr_models::{ BlocklistResponse, DownloadRecord, DownloadsResponse, Episode, IndexerSettings, LogResponse, QualityProfile, Series, SonarrSerdeable, SystemStatus, @@ -38,6 +38,7 @@ pub enum SonarrEvent { GetEpisodes(Option), GetLogs(Option), GetQualityProfiles, + GetQueuedEvents, GetSecurityConfig, GetStatus, HealthCheck, @@ -57,6 +58,7 @@ impl NetworkResource for SonarrEvent { SonarrEvent::GetIndexers => "/indexer", SonarrEvent::GetLogs(_) => "/log", SonarrEvent::GetQualityProfiles => "/qualityprofile", + SonarrEvent::GetQueuedEvents => "/command", SonarrEvent::GetStatus => "/system/status", SonarrEvent::HealthCheck => "/health", SonarrEvent::ListSeries => "/series", @@ -107,6 +109,10 @@ impl<'a, 'b> Network<'a, 'b> { .get_sonarr_quality_profiles() .await .map(SonarrSerdeable::from), + SonarrEvent::GetQueuedEvents => self + .get_queued_sonarr_events() + .await + .map(SonarrSerdeable::from), SonarrEvent::GetLogs(events) => self .get_sonarr_logs(events) .await @@ -523,6 +529,25 @@ impl<'a, 'b> Network<'a, 'b> { .await } + async fn get_queued_sonarr_events(&mut self) -> Result> { + info!("Fetching Sonarr queued events"); + let event = SonarrEvent::GetQueuedEvents; + + let request_props = self + .request_props_from(event, RequestMethod::Get, None::<()>, None, None) + .await; + + self + .handle_request::<(), Vec>(request_props, |queued_events_vec, mut app| { + app + .data + .sonarr_data + .queued_events + .set_items(queued_events_vec); + }) + .await + } + async fn get_sonarr_security_config(&mut self) -> Result { info!("Fetching Sonarr security config"); let event = SonarrEvent::GetSecurityConfig; diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index 5fe5d46..b16200d 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -22,7 +22,9 @@ mod test { use crate::app::App; use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; - use crate::models::servarr_models::{HostConfig, Indexer, IndexerField, SecurityConfig}; + use crate::models::servarr_models::{ + HostConfig, Indexer, IndexerField, QueueEvent, SecurityConfig, + }; use crate::models::sonarr_models::{ BlocklistItem, DownloadRecord, DownloadsResponse, Episode, EpisodeFile, Language, LogResponse, MediaInfo, QualityProfile, @@ -145,6 +147,11 @@ mod test { assert_str_eq!(event.resource(), "/config/host"); } + #[rstest] + fn test_resource_command(#[values(SonarrEvent::GetQueuedEvents)] event: SonarrEvent) { + assert_str_eq!(event.resource(), "/command"); + } + #[rstest] fn test_resource_indexer(#[values(SonarrEvent::GetIndexers)] event: SonarrEvent) { assert_str_eq!(event.resource(), "/indexer"); @@ -1130,6 +1137,57 @@ mod test { } } + #[tokio::test] + async fn test_handle_get_queued_sonarr_events_event() { + let queued_events_json = json!([{ + "name": "RefreshMonitoredDownloads", + "commandName": "Refresh Monitored Downloads", + "status": "completed", + "queued": "2023-05-20T21:29:16Z", + "started": "2023-05-20T21:29:16Z", + "ended": "2023-05-20T21:29:16Z", + "duration": "00:00:00.5111547", + "trigger": "scheduled", + }]); + let response: Vec = serde_json::from_value(queued_events_json.clone()).unwrap(); + let timestamp = DateTime::from(DateTime::parse_from_rfc3339("2023-05-20T21:29:16Z").unwrap()); + let expected_event = QueueEvent { + name: "RefreshMonitoredDownloads".to_owned(), + command_name: "Refresh Monitored Downloads".to_owned(), + status: "completed".to_owned(), + queued: timestamp, + started: Some(timestamp), + ended: Some(timestamp), + duration: Some("00:00:00.5111547".to_owned()), + trigger: "scheduled".to_owned(), + }; + + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(queued_events_json), + None, + SonarrEvent::GetQueuedEvents, + None, + None, + ) + .await; + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + if let SonarrSerdeable::QueueEvents(events) = network + .handle_sonarr_event(SonarrEvent::GetQueuedEvents) + .await + .unwrap() + { + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.sonarr_data.queued_events.items, + vec![expected_event] + ); + assert_eq!(events, response); + } + } + #[rstest] #[tokio::test] async fn test_handle_get_series_event(#[values(true, false)] use_custom_sorting: bool) { diff --git a/src/ui/radarr_ui/system/mod.rs b/src/ui/radarr_ui/system/mod.rs index 93c1a04..5616649 100644 --- a/src/ui/radarr_ui/system/mod.rs +++ b/src/ui/radarr_ui/system/mod.rs @@ -12,8 +12,9 @@ use ratatui::{ }; use crate::app::App; -use crate::models::radarr_models::{QueueEvent, Task}; +use crate::models::radarr_models::Task; use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock; +use crate::models::servarr_models::QueueEvent; use crate::ui::radarr_ui::radarr_ui_utils::{convert_to_minutes_hours_days, style_log_list_item}; use crate::ui::radarr_ui::system::system_details_ui::SystemDetailsUi; use crate::ui::styles::ManagarrStyle;