diff --git a/src/cli/lidarr/lidarr_command_tests.rs b/src/cli/lidarr/lidarr_command_tests.rs index b0cbe46..575d383 100644 --- a/src/cli/lidarr/lidarr_command_tests.rs +++ b/src/cli/lidarr/lidarr_command_tests.rs @@ -60,6 +60,55 @@ mod tests { assert_err!(&result); } + #[test] + fn test_download_release_requires_guid() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "lidarr", + "download-release", + "--indexer-id", + "1", + ]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_download_release_requires_indexer_id() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "lidarr", + "download-release", + "--guid", + "1", + ]); + + assert_err!(&result); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_download_release_requirements_satisfied() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "lidarr", + "download-release", + "--guid", + "1", + "--indexer-id", + "1", + ]); + + assert_ok!(&result); + } + #[test] fn test_toggle_artist_monitoring_requires_artist_id() { let result = @@ -235,7 +284,7 @@ mod tests { use crate::cli::lidarr::manual_search_command_handler::LidarrManualSearchCommand; use crate::cli::lidarr::refresh_command_handler::LidarrRefreshCommand; use crate::cli::lidarr::trigger_automatic_search_command_handler::LidarrTriggerAutomaticSearchCommand; - use crate::models::lidarr_models::LidarrTaskName; + use crate::models::lidarr_models::{LidarrReleaseDownloadBody, LidarrTaskName}; use crate::models::servarr_models::IndexerSettings; use crate::{ app::App, @@ -428,9 +477,9 @@ mod tests { ))) }); let app_arc = Arc::new(Mutex::new(App::test_default())); - let refresh_series_command = LidarrCommand::Refresh(LidarrRefreshCommand::AllArtists); + let refresh_artist_command = LidarrCommand::Refresh(LidarrRefreshCommand::AllArtists); - let result = LidarrCliHandler::with(&app_arc, refresh_series_command, &mut mock_network) + let result = LidarrCliHandler::with(&app_arc, refresh_artist_command, &mut mock_network) .handle() .await; @@ -497,6 +546,37 @@ mod tests { assert_ok!(&result); } + #[tokio::test] + async fn test_download_release_command() { + let expected_release_download_body = LidarrReleaseDownloadBody { + guid: "guid".to_owned(), + indexer_id: 1, + }; + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + LidarrEvent::DownloadRelease(expected_release_download_body).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Lidarr(LidarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let download_release_command = LidarrCommand::DownloadRelease { + guid: "guid".to_owned(), + indexer_id: 1, + }; + + let result = LidarrCliHandler::with(&app_arc, download_release_command, &mut mock_network) + .handle() + .await; + + assert_ok!(&result); + } + #[tokio::test] async fn test_toggle_artist_monitoring_command() { let mut mock_network = MockNetworkTrait::new(); diff --git a/src/cli/lidarr/manual_search_command_handler.rs b/src/cli/lidarr/manual_search_command_handler.rs index 16e8dad..72cae84 100644 --- a/src/cli/lidarr/manual_search_command_handler.rs +++ b/src/cli/lidarr/manual_search_command_handler.rs @@ -1,10 +1,13 @@ use crate::app::App; use crate::cli::lidarr::LidarrCommand; use crate::cli::{CliCommandHandler, Command}; +use crate::models::Serdeable; +use crate::models::lidarr_models::{LidarrRelease, LidarrSerdeable}; use crate::network::NetworkTrait; use crate::network::lidarr_network::LidarrEvent; use anyhow::Result; use clap::Subcommand; +use serde_json::json; use std::sync::Arc; use tokio::sync::Mutex; @@ -15,7 +18,7 @@ mod manual_search_command_handler_tests; #[derive(Debug, Clone, PartialEq, Eq, Subcommand)] pub enum LidarrManualSearchCommand { #[command( - about = "Trigger a manual search of discography releases for the given artist corresponding to the artist with the given ID.\nNote that when downloading a discography release, ensure that the release includes 'discography: true', otherwise you'll run into issues" + about = "Trigger a manual search of discography releases for the given artist corresponding to the artist with the given ID." )] Discography { #[arg( @@ -58,11 +61,21 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrManualSearchCommand> let result = match self.command { LidarrManualSearchCommand::Discography { artist_id } => { println!("Searching for artist discography releases. This may take a minute..."); - let resp = self + match self .network .handle_network_event(LidarrEvent::GetDiscographyReleases(artist_id).into()) - .await?; - serde_json::to_string_pretty(&resp)? + .await + { + Ok(Serdeable::Lidarr(LidarrSerdeable::Releases(releases_vec))) => { + let discography_vec: Vec = releases_vec + .into_iter() + .filter(|release| release.discography) + .collect(); + serde_json::to_string_pretty(&discography_vec)? + } + Err(e) => return Err(e), + _ => serde_json::to_string_pretty(&json!({"message": "Failed to parse response"}))?, + } } }; diff --git a/src/cli/lidarr/mod.rs b/src/cli/lidarr/mod.rs index f7bf569..594f636 100644 --- a/src/cli/lidarr/mod.rs +++ b/src/cli/lidarr/mod.rs @@ -18,7 +18,7 @@ use super::{CliCommandHandler, Command}; use crate::cli::lidarr::manual_search_command_handler::{ LidarrManualSearchCommand, LidarrManualSearchCommandHandler, }; -use crate::models::lidarr_models::LidarrTaskName; +use crate::models::lidarr_models::{LidarrReleaseDownloadBody, LidarrTaskName}; use crate::network::lidarr_network::LidarrEvent; use crate::{app::App, network::NetworkTrait}; @@ -27,13 +27,13 @@ mod delete_command_handler; mod edit_command_handler; mod get_command_handler; mod list_command_handler; +mod manual_search_command_handler; mod refresh_command_handler; mod trigger_automatic_search_command_handler; #[cfg(test)] #[path = "lidarr_command_tests.rs"] mod lidarr_command_tests; -mod manual_search_command_handler; #[derive(Debug, Clone, PartialEq, Eq, Subcommand)] pub enum LidarrCommand { @@ -74,6 +74,17 @@ pub enum LidarrCommand { about = "Commands to trigger automatic searches for releases of different resources in your Lidarr instance" )] TriggerAutomaticSearch(LidarrTriggerAutomaticSearchCommand), + #[command(about = "Manually download the given release")] + DownloadRelease { + #[arg(long, help = "The GUID of the release to download", required = true)] + guid: String, + #[arg( + long, + help = "The indexer ID to download the release from", + required = true + )] + indexer_id: i64, + }, #[command(about = "Mark the Lidarr history item with the given ID as 'failed'")] MarkHistoryItemAsFailed { #[arg( @@ -206,6 +217,14 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, LidarrCommand> for LidarrCliHandler<'a, ' .handle() .await? } + LidarrCommand::DownloadRelease { guid, indexer_id } => { + let params = LidarrReleaseDownloadBody { guid, indexer_id }; + let resp = self + .network + .handle_network_event(LidarrEvent::DownloadRelease(params).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } LidarrCommand::MarkHistoryItemAsFailed { history_item_id } => { let _ = self .network diff --git a/src/models/lidarr_models.rs b/src/models/lidarr_models.rs index 1893266..a85d4ce 100644 --- a/src/models/lidarr_models.rs +++ b/src/models/lidarr_models.rs @@ -488,6 +488,13 @@ pub struct LidarrRelease { pub quality: QualityWrapper, } +#[derive(Default, Serialize, Debug, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct LidarrReleaseDownloadBody { + pub guid: String, + pub indexer_id: i64, +} + impl From for Serdeable { fn from(value: LidarrSerdeable) -> Serdeable { Serdeable::Lidarr(value) diff --git a/src/network/lidarr_network/library/lidarr_library_network_tests.rs b/src/network/lidarr_network/library/lidarr_library_network_tests.rs new file mode 100644 index 0000000..133d37b --- /dev/null +++ b/src/network/lidarr_network/library/lidarr_library_network_tests.rs @@ -0,0 +1,34 @@ +#[cfg(test)] +mod tests { + use crate::models::lidarr_models::LidarrReleaseDownloadBody; + use crate::network::lidarr_network::LidarrEvent; + use crate::network::network_tests::test_utils::{MockServarrApi, test_network}; + use serde_json::json; + + #[tokio::test] + async fn test_handle_download_lidarr_release_event_uses_provided_params() { + let params = LidarrReleaseDownloadBody { + guid: "1234".to_owned(), + indexer_id: 2, + }; + + let (mock, app, _server) = MockServarrApi::post() + .with_request_body(json!({ + "guid": "1234", + "indexerId": 2, + })) + .returns(json!({})) + .build_for(LidarrEvent::DownloadRelease(params.clone())) + .await; + + app.lock().await.server_tabs.set_index(2); + let mut network = test_network(&app); + + let result = network + .handle_lidarr_event(LidarrEvent::DownloadRelease(params)) + .await; + + mock.assert_async().await; + assert_ok!(result); + } +} diff --git a/src/network/lidarr_network/library/mod.rs b/src/network/lidarr_network/library/mod.rs index 3b6eaff..6adafa1 100644 --- a/src/network/lidarr_network/library/mod.rs +++ b/src/network/lidarr_network/library/mod.rs @@ -1,2 +1,37 @@ +use crate::models::lidarr_models::LidarrReleaseDownloadBody; +use crate::network::lidarr_network::LidarrEvent; +use crate::network::{Network, RequestMethod}; +use anyhow::Result; +use log::info; +use serde_json::Value; + mod albums; mod artists; + +#[cfg(test)] +#[path = "lidarr_library_network_tests.rs"] +mod lidarr_library_network_tests; + +impl Network<'_, '_> { + pub(in crate::network::lidarr_network) async fn download_lidarr_release( + &mut self, + lidarr_release_download_body: LidarrReleaseDownloadBody, + ) -> Result { + let event = LidarrEvent::DownloadRelease(LidarrReleaseDownloadBody::default()); + info!("Downloading Lidarr release with params: {lidarr_release_download_body:?}"); + + let request_props = self + .request_props_from( + event, + RequestMethod::Post, + Some(lidarr_release_download_body), + None, + None, + ) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } +} diff --git a/src/network/lidarr_network/lidarr_network_tests.rs b/src/network/lidarr_network/lidarr_network_tests.rs index 3f24cb8..80dcea6 100644 --- a/src/network/lidarr_network/lidarr_network_tests.rs +++ b/src/network/lidarr_network/lidarr_network_tests.rs @@ -125,7 +125,13 @@ mod tests { } #[rstest] - fn test_resource_release(#[values(LidarrEvent::GetDiscographyReleases(0))] event: LidarrEvent) { + fn test_resource_release( + #[values( + LidarrEvent::GetDiscographyReleases(0), + LidarrEvent::DownloadRelease(Default::default()) + )] + event: LidarrEvent, + ) { assert_str_eq!(event.resource(), "/release"); } diff --git a/src/network/lidarr_network/mod.rs b/src/network/lidarr_network/mod.rs index 6c85bf9..ab28cde 100644 --- a/src/network/lidarr_network/mod.rs +++ b/src/network/lidarr_network/mod.rs @@ -3,8 +3,8 @@ use log::info; use super::{NetworkEvent, NetworkResource}; use crate::models::lidarr_models::{ - AddArtistBody, AddLidarrRootFolderBody, DeleteParams, EditArtistParams, LidarrSerdeable, - LidarrTaskName, MetadataProfile, + AddArtistBody, AddLidarrRootFolderBody, DeleteParams, EditArtistParams, + LidarrReleaseDownloadBody, LidarrSerdeable, LidarrTaskName, MetadataProfile, }; use crate::models::servarr_models::{EditIndexerParams, IndexerSettings, QualityProfile, Tag}; use crate::network::{Network, RequestMethod}; @@ -35,6 +35,7 @@ pub enum LidarrEvent { DeleteIndexer(i64), DeleteRootFolder(i64), DeleteTag(i64), + DownloadRelease(LidarrReleaseDownloadBody), EditArtist(EditArtistParams), EditAllIndexerSettings(IndexerSettings), EditIndexer(EditIndexerParams), @@ -97,7 +98,7 @@ impl NetworkResource for LidarrEvent { LidarrEvent::GetDownloads(_) | LidarrEvent::DeleteDownload(_) => "/queue", LidarrEvent::GetHistory(_) => "/history", LidarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed", - LidarrEvent::GetDiscographyReleases(_) => "/release", + LidarrEvent::GetDiscographyReleases(_) | LidarrEvent::DownloadRelease(_) => "/release", LidarrEvent::GetHostConfig | LidarrEvent::GetSecurityConfig => "/config/host", LidarrEvent::GetIndexers | LidarrEvent::DeleteIndexer(_) | LidarrEvent::EditIndexer(_) => { "/indexer" @@ -171,6 +172,10 @@ impl Network<'_, '_> { .delete_lidarr_tag(tag_id) .await .map(LidarrSerdeable::from), + LidarrEvent::DownloadRelease(lidarr_release_download_body) => self + .download_lidarr_release(lidarr_release_download_body) + .await + .map(LidarrSerdeable::from), LidarrEvent::GetAlbums(artist_id) => { self.get_albums(artist_id).await.map(LidarrSerdeable::from) }