use anyhow::Result; use log::info; use serde_json::{Value, json}; use super::{Network, NetworkEvent, NetworkResource}; use crate::{ models::{ servarr_models::{AddRootFolderBody, EditIndexerParams, Language, QualityProfile, Tag}, sonarr_models::{ AddSeriesBody, DeleteSeriesParams, EditSeriesParams, IndexerSettings, SonarrReleaseDownloadBody, SonarrSerdeable, SonarrTaskName, }, }, network::RequestMethod, }; #[cfg(test)] #[path = "sonarr_network_tests.rs"] mod sonarr_network_tests; #[cfg(test)] #[path = "sonarr_network_test_utils.rs"] pub mod sonarr_network_test_utils; mod blocklist; mod downloads; mod history; mod indexers; mod library; mod root_folders; mod system; #[derive(Debug, Eq, PartialEq, Clone)] pub enum SonarrEvent { AddRootFolder(AddRootFolderBody), AddSeries(AddSeriesBody), AddTag(String), ClearBlocklist, DeleteBlocklistItem(i64), DeleteDownload(i64), DeleteEpisodeFile(i64), DeleteIndexer(i64), DeleteRootFolder(i64), DeleteSeries(DeleteSeriesParams), DeleteTag(i64), DownloadRelease(SonarrReleaseDownloadBody), EditAllIndexerSettings(IndexerSettings), EditIndexer(EditIndexerParams), EditSeries(EditSeriesParams), GetAllIndexerSettings, GetBlocklist, GetDownloads(u64), GetHistory(u64), GetHostConfig, GetIndexers, GetEpisodeDetails(i64), GetEpisodes(i64), GetEpisodeFiles(i64), GetEpisodeHistory(i64), GetLanguageProfiles, GetLogs(u64), GetDiskSpace, GetQualityProfiles, GetQueuedEvents, GetRootFolders, GetEpisodeReleases(i64), GetSeasonHistory((i64, i64)), GetSeasonReleases((i64, i64)), GetSecurityConfig, GetSeriesDetails(i64), GetSeriesHistory(i64), GetStatus, GetUpdates, GetTags, GetTasks, HealthCheck, ListSeries, MarkHistoryItemAsFailed(i64), SearchNewSeries(String), StartTask(SonarrTaskName), TestIndexer(i64), TestAllIndexers, ToggleSeasonMonitoring((i64, i64)), ToggleSeriesMonitoring(i64), ToggleEpisodeMonitoring(i64), TriggerAutomaticEpisodeSearch(i64), TriggerAutomaticSeasonSearch((i64, i64)), TriggerAutomaticSeriesSearch(i64), UpdateAllSeries, UpdateAndScanSeries(i64), UpdateDownloads, } impl NetworkResource for SonarrEvent { fn resource(&self) -> &'static str { match &self { SonarrEvent::AddTag(_) | SonarrEvent::DeleteTag(_) | SonarrEvent::GetTags => "/tag", SonarrEvent::ClearBlocklist => "/blocklist/bulk", SonarrEvent::DownloadRelease(_) => "/release", SonarrEvent::DeleteBlocklistItem(_) => "/blocklist", SonarrEvent::GetAllIndexerSettings | SonarrEvent::EditAllIndexerSettings(_) => { "/config/indexer" } SonarrEvent::GetEpisodeFiles(_) | SonarrEvent::DeleteEpisodeFile(_) => "/episodefile", SonarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000", SonarrEvent::GetDownloads(_) | SonarrEvent::DeleteDownload(_) => "/queue", SonarrEvent::GetEpisodes(_) | SonarrEvent::GetEpisodeDetails(_) => "/episode", SonarrEvent::GetHistory(_) | SonarrEvent::GetEpisodeHistory(_) => "/history", SonarrEvent::GetHostConfig | SonarrEvent::GetSecurityConfig => "/config/host", SonarrEvent::GetIndexers | SonarrEvent::DeleteIndexer(_) | SonarrEvent::EditIndexer(_) => { "/indexer" } SonarrEvent::GetLanguageProfiles => "/language", SonarrEvent::GetLogs(_) => "/log", SonarrEvent::GetDiskSpace => "/diskspace", SonarrEvent::GetQualityProfiles => "/qualityprofile", SonarrEvent::GetQueuedEvents | SonarrEvent::StartTask(_) | SonarrEvent::TriggerAutomaticSeriesSearch(_) | SonarrEvent::TriggerAutomaticSeasonSearch(_) | SonarrEvent::TriggerAutomaticEpisodeSearch(_) | SonarrEvent::UpdateAllSeries | SonarrEvent::UpdateAndScanSeries(_) | SonarrEvent::UpdateDownloads => "/command", SonarrEvent::GetRootFolders | SonarrEvent::DeleteRootFolder(_) | SonarrEvent::AddRootFolder(_) => "/rootfolder", SonarrEvent::GetSeasonReleases(_) | SonarrEvent::GetEpisodeReleases(_) => "/release", SonarrEvent::GetSeriesHistory(_) | SonarrEvent::GetSeasonHistory(_) => "/history/series", SonarrEvent::GetStatus => "/system/status", SonarrEvent::GetTasks => "/system/task", SonarrEvent::GetUpdates => "/update", SonarrEvent::HealthCheck => "/health", SonarrEvent::AddSeries(_) | SonarrEvent::ListSeries | SonarrEvent::GetSeriesDetails(_) | SonarrEvent::DeleteSeries(_) | SonarrEvent::EditSeries(_) | SonarrEvent::ToggleSeasonMonitoring(_) | SonarrEvent::ToggleSeriesMonitoring(_) => "/series", SonarrEvent::SearchNewSeries(_) => "/series/lookup", SonarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed", SonarrEvent::TestIndexer(_) => "/indexer/test", SonarrEvent::TestAllIndexers => "/indexer/testall", SonarrEvent::ToggleEpisodeMonitoring(_) => "/episode/monitor", } } } impl From for NetworkEvent { fn from(sonarr_event: SonarrEvent) -> Self { NetworkEvent::Sonarr(sonarr_event) } } impl Network<'_, '_> { pub async fn handle_sonarr_event( &mut self, sonarr_event: SonarrEvent, ) -> Result { match sonarr_event { SonarrEvent::AddRootFolder(path) => self .add_sonarr_root_folder(path) .await .map(SonarrSerdeable::from), SonarrEvent::AddSeries(body) => self .add_sonarr_series(body) .await .map(SonarrSerdeable::from), SonarrEvent::AddTag(tag) => self.add_sonarr_tag(tag).await.map(SonarrSerdeable::from), SonarrEvent::ClearBlocklist => self .clear_sonarr_blocklist() .await .map(SonarrSerdeable::from), SonarrEvent::GetAllIndexerSettings => self .get_all_sonarr_indexer_settings() .await .map(SonarrSerdeable::from), SonarrEvent::DeleteBlocklistItem(blocklist_item_id) => self .delete_sonarr_blocklist_item(blocklist_item_id) .await .map(SonarrSerdeable::from), SonarrEvent::DeleteDownload(download_id) => self .delete_sonarr_download(download_id) .await .map(SonarrSerdeable::from), SonarrEvent::DeleteEpisodeFile(episode_file_id) => self .delete_sonarr_episode_file(episode_file_id) .await .map(SonarrSerdeable::from), SonarrEvent::DeleteIndexer(indexer_id) => self .delete_sonarr_indexer(indexer_id) .await .map(SonarrSerdeable::from), SonarrEvent::DeleteRootFolder(root_folder_id) => self .delete_sonarr_root_folder(root_folder_id) .await .map(SonarrSerdeable::from), SonarrEvent::DeleteSeries(params) => { self.delete_series(params).await.map(SonarrSerdeable::from) } SonarrEvent::DeleteTag(tag_id) => self .delete_sonarr_tag(tag_id) .await .map(SonarrSerdeable::from), SonarrEvent::DownloadRelease(sonarr_release_download_body) => self .download_sonarr_release(sonarr_release_download_body) .await .map(SonarrSerdeable::from), SonarrEvent::EditAllIndexerSettings(params) => self .edit_all_sonarr_indexer_settings(params) .await .map(SonarrSerdeable::from), SonarrEvent::EditIndexer(params) => self .edit_sonarr_indexer(params) .await .map(SonarrSerdeable::from), SonarrEvent::EditSeries(params) => self .edit_sonarr_series(params) .await .map(SonarrSerdeable::from), SonarrEvent::GetBlocklist => self.get_sonarr_blocklist().await.map(SonarrSerdeable::from), SonarrEvent::GetDownloads(count) => self .get_sonarr_downloads(count) .await .map(SonarrSerdeable::from), SonarrEvent::GetEpisodes(series_id) => self .get_episodes(series_id) .await .map(SonarrSerdeable::from), SonarrEvent::GetEpisodeFiles(series_id) => self .get_episode_files(series_id) .await .map(SonarrSerdeable::from), SonarrEvent::GetEpisodeDetails(episode_id) => self .get_episode_details(episode_id) .await .map(SonarrSerdeable::from), SonarrEvent::GetEpisodeHistory(episode_id) => self .get_sonarr_episode_history(episode_id) .await .map(SonarrSerdeable::from), SonarrEvent::GetHistory(events) => self .get_sonarr_history(events) .await .map(SonarrSerdeable::from), SonarrEvent::GetHostConfig => self .get_sonarr_host_config() .await .map(SonarrSerdeable::from), SonarrEvent::GetIndexers => self.get_sonarr_indexers().await.map(SonarrSerdeable::from), SonarrEvent::GetLanguageProfiles => self .get_sonarr_language_profiles() .await .map(SonarrSerdeable::from), SonarrEvent::GetLogs(events) => self .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 .map(SonarrSerdeable::from), SonarrEvent::GetQueuedEvents => self .get_queued_sonarr_events() .await .map(SonarrSerdeable::from), SonarrEvent::GetRootFolders => self .get_sonarr_root_folders() .await .map(SonarrSerdeable::from), SonarrEvent::GetEpisodeReleases(params) => self .get_episode_releases(params) .await .map(SonarrSerdeable::from), SonarrEvent::GetSeasonHistory(params) => self .get_sonarr_season_history(params) .await .map(SonarrSerdeable::from), SonarrEvent::GetSeasonReleases(params) => self .get_season_releases(params) .await .map(SonarrSerdeable::from), SonarrEvent::GetSecurityConfig => self .get_sonarr_security_config() .await .map(SonarrSerdeable::from), SonarrEvent::GetSeriesDetails(series_id) => self .get_series_details(series_id) .await .map(SonarrSerdeable::from), SonarrEvent::GetSeriesHistory(series_id) => self .get_sonarr_series_history(series_id) .await .map(SonarrSerdeable::from), SonarrEvent::GetStatus => self.get_sonarr_status().await.map(SonarrSerdeable::from), SonarrEvent::GetTags => self.get_sonarr_tags().await.map(SonarrSerdeable::from), SonarrEvent::GetTasks => self.get_sonarr_tasks().await.map(SonarrSerdeable::from), SonarrEvent::GetUpdates => self.get_sonarr_updates().await.map(SonarrSerdeable::from), SonarrEvent::HealthCheck => self .get_sonarr_healthcheck() .await .map(SonarrSerdeable::from), SonarrEvent::ListSeries => self.list_series().await.map(SonarrSerdeable::from), SonarrEvent::MarkHistoryItemAsFailed(history_item_id) => self .mark_sonarr_history_item_as_failed(history_item_id) .await .map(SonarrSerdeable::from), SonarrEvent::SearchNewSeries(query) => self .search_sonarr_series(query) .await .map(SonarrSerdeable::from), SonarrEvent::StartTask(task_name) => self .start_sonarr_task(task_name) .await .map(SonarrSerdeable::from), SonarrEvent::TestIndexer(indexer_id) => self .test_sonarr_indexer(indexer_id) .await .map(SonarrSerdeable::from), SonarrEvent::TestAllIndexers => self .test_all_sonarr_indexers() .await .map(SonarrSerdeable::from), SonarrEvent::ToggleEpisodeMonitoring(episode_id) => self .toggle_sonarr_episode_monitoring(episode_id) .await .map(SonarrSerdeable::from), SonarrEvent::ToggleSeasonMonitoring(params) => self .toggle_sonarr_season_monitoring(params) .await .map(SonarrSerdeable::from), SonarrEvent::ToggleSeriesMonitoring(series_id) => self .toggle_sonarr_series_monitoring(series_id) .await .map(SonarrSerdeable::from), SonarrEvent::TriggerAutomaticSeasonSearch(params) => self .trigger_automatic_season_search(params) .await .map(SonarrSerdeable::from), SonarrEvent::TriggerAutomaticSeriesSearch(series_id) => self .trigger_automatic_series_search(series_id) .await .map(SonarrSerdeable::from), SonarrEvent::TriggerAutomaticEpisodeSearch(episode_id) => self .trigger_automatic_episode_search(episode_id) .await .map(SonarrSerdeable::from), SonarrEvent::UpdateAllSeries => self.update_all_series().await.map(SonarrSerdeable::from), SonarrEvent::UpdateAndScanSeries(series_id) => self .update_and_scan_series(series_id) .await .map(SonarrSerdeable::from), SonarrEvent::UpdateDownloads => self .update_sonarr_downloads() .await .map(SonarrSerdeable::from), } } 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 delete_sonarr_tag(&mut self, id: i64) -> Result<()> { info!("Deleting Sonarr tag with id: {id}"); let event = SonarrEvent::DeleteTag(id); let request_props = self .request_props_from( event, RequestMethod::Delete, None::<()>, Some(format!("/{id}")), None, ) .await; self .handle_request::<(), ()>(request_props, |_, _| ()) .await } async fn get_sonarr_healthcheck(&mut self) -> Result<()> { info!("Performing Sonarr health check"); let event = SonarrEvent::HealthCheck; let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, None) .await; self .handle_request::<(), ()>(request_props, |_, _| ()) .await } async fn get_sonarr_language_profiles(&mut self) -> Result> { info!("Fetching Sonarr language profiles"); let event = SonarrEvent::GetLanguageProfiles; let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, None) .await; self .handle_request::<(), Vec>(request_props, |language_profiles_vec, mut app| { app.data.sonarr_data.language_profiles_map = language_profiles_vec .into_iter() .map(|language| (language.id, language.name)) .collect(); }) .await } async fn get_sonarr_quality_profiles(&mut self) -> Result> { info!("Fetching Sonarr quality profiles"); let event = SonarrEvent::GetQualityProfiles; let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, None) .await; self .handle_request::<(), Vec>(request_props, |quality_profiles, mut app| { app.data.sonarr_data.quality_profile_map = quality_profiles .into_iter() .map(|profile| (profile.id, profile.name)) .collect(); }) .await } async fn get_sonarr_tags(&mut self) -> Result> { info!("Fetching Sonarr tags"); let event = SonarrEvent::GetTags; let request_props = self .request_props_from(event, RequestMethod::Get, None::<()>, None, None) .await; self .handle_request::<(), Vec>(request_props, |tags_vec, mut app| { app.data.sonarr_data.tags_map = tags_vec .into_iter() .map(|tag| (tag.id, tag.label)) .collect(); }) .await } pub(in crate::network::sonarr_network) async fn extract_and_add_sonarr_tag_ids_vec( &mut self, edit_tags: &str, ) -> Vec { let missing_tags_vec = { let tags_map = &self.app.lock().await.data.sonarr_data.tags_map; edit_tags .split(',') .filter(|&tag| { !tag.is_empty() && tags_map.get_by_right(tag.to_lowercase().trim()).is_none() }) .collect::>() }; for tag in missing_tags_vec { self .add_sonarr_tag(tag.trim().to_owned()) .await .expect("Unable to add tag"); } let app = self.app.lock().await; edit_tags .split(',') .filter(|tag| !tag.is_empty()) .map(|tag| { *app .data .sonarr_data .tags_map .get_by_right(tag.to_lowercase().trim()) .unwrap() }) .collect() } }