diff --git a/src/models/radarr_models.rs b/src/models/radarr_models.rs index bb838b6..9cf06cf 100644 --- a/src/models/radarr_models.rs +++ b/src/models/radarr_models.rs @@ -11,7 +11,7 @@ use crate::{models::HorizontallyScrollableText, serde_enum_from}; use super::servarr_models::{ HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, QueueEvent, Release, - RootFolder, SecurityConfig, + RootFolder, SecurityConfig, Tag, }; use super::{EnumDisplayStyle, Serdeable}; @@ -442,13 +442,6 @@ pub struct SystemStatus { pub start_time: DateTime, } -#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] -pub struct Tag { - #[serde(deserialize_with = "super::from_i64")] - pub id: i64, - pub label: String, -} - #[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct Task { diff --git a/src/models/servarr_data/sonarr/sonarr_data.rs b/src/models/servarr_data/sonarr/sonarr_data.rs index 2c1b820..211582c 100644 --- a/src/models/servarr_data/sonarr/sonarr_data.rs +++ b/src/models/servarr_data/sonarr/sonarr_data.rs @@ -34,6 +34,7 @@ pub struct SonarrData { pub series: StatefulTable, pub series_history: Option>, pub start_time: DateTime, + pub tags_map: BiMap, pub version: String, } @@ -55,6 +56,7 @@ impl Default for SonarrData { series: StatefulTable::default(), series_history: None, start_time: DateTime::default(), + tags_map: BiMap::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 9be5338..5c174be 100644 --- a/src/models/servarr_data/sonarr/sonarr_data_tests.rs +++ b/src/models/servarr_data/sonarr/sonarr_data_tests.rs @@ -49,6 +49,7 @@ mod tests { assert!(sonarr_data.series.is_empty()); assert!(sonarr_data.series_history.is_none()); assert_eq!(sonarr_data.start_time, >::default()); + assert!(sonarr_data.tags_map.is_empty()); assert!(sonarr_data.version.is_empty()); } } diff --git a/src/models/servarr_models.rs b/src/models/servarr_models.rs index b7d92a0..68f78ef 100644 --- a/src/models/servarr_models.rs +++ b/src/models/servarr_models.rs @@ -228,6 +228,13 @@ pub struct SecurityConfig { pub certificate_validation: CertificateValidation, } +#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] +pub struct Tag { + #[serde(deserialize_with = "super::from_i64")] + pub id: i64, + pub label: String, +} + #[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] pub struct UnmappedFolder { pub name: String, diff --git a/src/models/sonarr_models.rs b/src/models/sonarr_models.rs index edba686..72d5f17 100644 --- a/src/models/sonarr_models.rs +++ b/src/models/sonarr_models.rs @@ -12,7 +12,7 @@ use crate::serde_enum_from; use super::{ servarr_models::{ HostConfig, Indexer, Language, LogResponse, QualityProfile, QualityWrapper, QueueEvent, - Release, RootFolder, SecurityConfig, + Release, RootFolder, SecurityConfig, Tag, }, EnumDisplayStyle, HorizontallyScrollableText, Serdeable, }; @@ -409,6 +409,8 @@ pub enum SonarrSerdeable { SonarrHistoryItems(Vec), SonarrHistoryWrapper(SonarrHistoryWrapper), SystemStatus(SystemStatus), + Tag(Tag), + Tags(Vec), BlocklistResponse(BlocklistResponse), LogResponse(LogResponse), } @@ -444,6 +446,8 @@ serde_enum_from!( SonarrHistoryItems(Vec), SonarrHistoryWrapper(SonarrHistoryWrapper), SystemStatus(SystemStatus), + Tag(Tag), + Tags(Vec), BlocklistResponse(BlocklistResponse), LogResponse(LogResponse), } diff --git a/src/models/sonarr_models_tests.rs b/src/models/sonarr_models_tests.rs index 8bee1a1..363ad4f 100644 --- a/src/models/sonarr_models_tests.rs +++ b/src/models/sonarr_models_tests.rs @@ -6,7 +6,7 @@ mod tests { use crate::models::{ servarr_models::{ HostConfig, Indexer, Log, LogResponse, QualityProfile, QueueEvent, Release, RootFolder, - SecurityConfig, + SecurityConfig, Tag, }, sonarr_models::{ BlocklistItem, BlocklistResponse, DownloadRecord, DownloadsResponse, Episode, @@ -370,4 +370,28 @@ mod tests { SonarrSerdeable::SecurityConfig(security_config) ); } + + #[test] + fn test_sonarr_serdeable_from_tag() { + let tag = Tag { + id: 1, + ..Tag::default() + }; + + let sonarr_serdeable: SonarrSerdeable = tag.clone().into(); + + assert_eq!(sonarr_serdeable, SonarrSerdeable::Tag(tag)); + } + + #[test] + fn test_sonarr_serdeable_from_tags() { + let tags = vec![Tag { + id: 1, + ..Tag::default() + }]; + + let sonarr_serdeable: SonarrSerdeable = tags.clone().into(); + + assert_eq!(sonarr_serdeable, SonarrSerdeable::Tags(tags)); + } } diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 40a50b0..cef03a1 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -11,7 +11,7 @@ use crate::models::radarr_models::{ CommandBody, Credit, CreditType, DeleteMovieParams, DiskSpace, DownloadRecord, DownloadsResponse, EditCollectionParams, EditIndexerParams, EditMovieParams, IndexerSettings, IndexerTestResult, Movie, MovieCommandBody, MovieHistoryItem, RadarrSerdeable, ReleaseDownloadBody, SystemStatus, - Tag, Task, TaskName, Update, + Task, TaskName, Update, }; use crate::models::servarr_data::radarr::modals::{ AddMovieModal, EditCollectionModal, EditIndexerModal, EditMovieModal, IndexerTestResultModalItem, @@ -20,7 +20,7 @@ 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, + RootFolder, SecurityConfig, Tag, }; use crate::models::stateful_table::StatefulTable; use crate::models::{HorizontallyScrollableText, Route, Scrollable, ScrollableText}; @@ -148,7 +148,7 @@ impl<'a, 'b> Network<'a, 'b> { .add_radarr_root_folder(path) .await .map(RadarrSerdeable::from), - RadarrEvent::AddTag(tag) => self.add_tag(tag).await.map(RadarrSerdeable::from), + RadarrEvent::AddTag(tag) => self.add_radarr_tag(tag).await.map(RadarrSerdeable::from), RadarrEvent::ClearBlocklist => self .clear_radarr_blocklist() .await @@ -405,7 +405,7 @@ impl<'a, 'b> Network<'a, 'b> { .await } - async fn add_tag(&mut self, tag: String) -> Result { + async fn add_radarr_tag(&mut self, tag: String) -> Result { info!("Adding a new Radarr tag"); let event = RadarrEvent::AddTag(String::new()); @@ -2234,7 +2234,7 @@ impl<'a, 'b> Network<'a, 'b> { for tag in missing_tags_vec { self - .add_tag(tag.trim().to_owned()) + .add_radarr_tag(tag.trim().to_owned()) .await .expect("Unable to add tag"); } diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index 709fcd8..ddfa1a3 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -164,6 +164,18 @@ mod test { assert_str_eq!(event.resource(), "/rootfolder"); } + #[rstest] + fn test_resource_tag( + #[values( + RadarrEvent::AddTag(String::new()), + RadarrEvent::GetTags, + RadarrEvent::DeleteTag(0) + )] + event: RadarrEvent, + ) { + assert_str_eq!(event.resource(), "/tag"); + } + #[rstest] fn test_resource_release( #[values(RadarrEvent::GetReleases(None), RadarrEvent::DownloadRelease(None))] @@ -213,7 +225,6 @@ mod test { #[case(RadarrEvent::GetOverview, "/diskspace")] #[case(RadarrEvent::GetQualityProfiles, "/qualityprofile")] #[case(RadarrEvent::GetStatus, "/system/status")] - #[case(RadarrEvent::GetTags, "/tag")] #[case(RadarrEvent::GetTasks, "/system/task")] #[case(RadarrEvent::GetUpdates, "/update")] #[case(RadarrEvent::TestIndexer(None), "/indexer/test")] @@ -2814,7 +2825,7 @@ mod test { } #[tokio::test] - async fn test_handle_add_tag() { + async fn test_handle_add_radarr_tag() { let tag_json = json!({ "id": 3, "label": "testing" }); let response: Tag = serde_json::from_value(tag_json.clone()).unwrap(); let (async_server, app_arc, _server) = mock_servarr_api( @@ -2822,7 +2833,7 @@ mod test { Some(json!({ "label": "testing" })), Some(tag_json), None, - RadarrEvent::GetTags, + RadarrEvent::AddTag(String::new()), None, None, ) diff --git a/src/network/sonarr_network.rs b/src/network/sonarr_network.rs index 0f7bb6b..31bebdc 100644 --- a/src/network/sonarr_network.rs +++ b/src/network/sonarr_network.rs @@ -11,7 +11,7 @@ use crate::{ }, servarr_models::{ AddRootFolderBody, HostConfig, Indexer, LogResponse, QualityProfile, QueueEvent, Release, - RootFolder, SecurityConfig, + RootFolder, SecurityConfig, Tag, }, sonarr_models::{ BlocklistResponse, DownloadRecord, DownloadsResponse, Episode, IndexerSettings, Series, @@ -32,6 +32,7 @@ mod sonarr_network_tests; #[derive(Debug, Eq, PartialEq, Clone)] pub enum SonarrEvent { AddRootFolder(Option), + AddTag(String), ClearBlocklist, DeleteBlocklistItem(Option), DeleteDownload(Option), @@ -63,6 +64,7 @@ pub enum SonarrEvent { impl NetworkResource for SonarrEvent { fn resource(&self) -> &'static str { match &self { + SonarrEvent::AddTag(_) => "/tag", SonarrEvent::ClearBlocklist => "/blocklist/bulk", SonarrEvent::DeleteBlocklistItem(_) => "/blocklist", SonarrEvent::GetAllIndexerSettings => "/config/indexer", @@ -103,6 +105,7 @@ impl<'a, 'b> Network<'a, 'b> { .add_sonarr_root_folder(path) .await .map(SonarrSerdeable::from), + SonarrEvent::AddTag(tag) => self.add_sonarr_tag(tag).await.map(SonarrSerdeable::from), SonarrEvent::ClearBlocklist => self .clear_sonarr_blocklist() .await @@ -227,6 +230,27 @@ impl<'a, 'b> Network<'a, 'b> { .await } + async fn add_sonarr_tag(&mut self, tag: String) -> Result { + info!("Adding a new Sonarr tag"); + let event = SonarrEvent::AddTag(String::new()); + + let request_props = self + .request_props_from( + event, + RequestMethod::Post, + Some(json!({ "label": tag })), + None, + None, + ) + .await; + + self + .handle_request::(request_props, |tag, mut app| { + app.data.sonarr_data.tags_map.insert(tag.id, tag.label); + }) + .await + } + async fn clear_sonarr_blocklist(&mut self) -> Result<()> { info!("Clearing Sonarr blocklist"); let event = SonarrEvent::ClearBlocklist; diff --git a/src/network/sonarr_network_tests.rs b/src/network/sonarr_network_tests.rs index f1460f3..dc432f0 100644 --- a/src/network/sonarr_network_tests.rs +++ b/src/network/sonarr_network_tests.rs @@ -18,7 +18,7 @@ mod test { use crate::models::servarr_data::sonarr::sonarr_data::ActiveSonarrBlock; use crate::models::servarr_models::{ HostConfig, Indexer, IndexerField, Language, LogResponse, Quality, QualityProfile, - QualityWrapper, QueueEvent, Release, RootFolder, SecurityConfig, + QualityWrapper, QueueEvent, Release, RootFolder, SecurityConfig, Tag, }; use crate::models::sonarr_models::SystemStatus; use crate::models::sonarr_models::{ @@ -138,6 +138,11 @@ mod test { assert_str_eq!(event.resource(), "/series"); } + #[rstest] + fn test_resource_tag(#[values(SonarrEvent::AddTag(String::new()))] event: SonarrEvent) { + assert_str_eq!(event.resource(), "/tag"); + } + #[rstest] fn test_resource_host_config( #[values(SonarrEvent::GetHostConfig, SonarrEvent::GetSecurityConfig)] event: SonarrEvent, @@ -281,6 +286,42 @@ mod test { .is_none()); } + #[tokio::test] + async fn test_handle_add_sonarr_tag() { + let tag_json = json!({ "id": 3, "label": "testing" }); + let response: Tag = serde_json::from_value(tag_json.clone()).unwrap(); + let (async_server, app_arc, _server) = mock_servarr_api( + RequestMethod::Post, + Some(json!({ "label": "testing" })), + Some(tag_json), + None, + SonarrEvent::AddTag(String::new()), + None, + None, + ) + .await; + app_arc.lock().await.data.sonarr_data.tags_map = + BiMap::from_iter([(1, "usenet".to_owned()), (2, "test".to_owned())]); + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + if let SonarrSerdeable::Tag(tag) = network + .handle_sonarr_event(SonarrEvent::AddTag("testing".to_owned())) + .await + .unwrap() + { + async_server.assert_async().await; + assert_eq!( + app_arc.lock().await.data.sonarr_data.tags_map, + BiMap::from_iter([ + (1, "usenet".to_owned()), + (2, "test".to_owned()), + (3, "testing".to_owned()) + ]) + ); + assert_eq!(tag, response); + } + } + #[tokio::test] async fn test_handle_clear_radarr_blocklist_event() { let blocklist_items = vec![