diff --git a/src/cli/radarr/mod.rs b/src/cli/radarr/mod.rs index 2641691..11b4d9e 100644 --- a/src/cli/radarr/mod.rs +++ b/src/cli/radarr/mod.rs @@ -118,6 +118,17 @@ pub enum RadarrCommand { }, #[command(about = "Test all Radarr indexers")] TestAllIndexers, + #[command( + about = "Toggle monitoring for the specified movie corresponding to the given movie ID" + )] + ToggleMovieMonitoring { + #[arg( + long, + help = "The Radarr ID of the movie to toggle monitoring on", + required = true + )] + movie_id: i64, + }, #[command(about = "Trigger an automatic search for the movie with the specified ID")] TriggerAutomaticSearch { #[arg( @@ -250,6 +261,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, ' .await?; serde_json::to_string_pretty(&resp)? } + RadarrCommand::ToggleMovieMonitoring { movie_id } => { + let resp = self + .network + .handle_network_event(RadarrEvent::ToggleMovieMonitoring(movie_id).into()) + .await?; + serde_json::to_string_pretty(&resp)? + } RadarrCommand::TriggerAutomaticSearch { movie_id } => { let resp = self .network diff --git a/src/cli/radarr/radarr_command_tests.rs b/src/cli/radarr/radarr_command_tests.rs index ace8826..90a7efe 100644 --- a/src/cli/radarr/radarr_command_tests.rs +++ b/src/cli/radarr/radarr_command_tests.rs @@ -215,6 +215,31 @@ mod tests { assert!(result.is_ok()); } + #[test] + fn test_toggle_movie_monitoring_requires_movie_id() { + let result = + Cli::command().try_get_matches_from(["managarr", "radarr", "toggle-movie-monitoring"]); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().kind(), + ErrorKind::MissingRequiredArgument + ); + } + + #[test] + fn test_toggle_movie_monitoring_requirements_satisfied() { + let result = Cli::command().try_get_matches_from([ + "managarr", + "radarr", + "toggle-movie-monitoring", + "--movie-id", + "1", + ]); + + assert!(result.is_ok()); + } + #[test] fn test_trigger_automatic_search_requires_movie_id() { let result = @@ -461,6 +486,32 @@ mod tests { assert!(result.is_ok()); } + #[tokio::test] + async fn test_toggle_movie_monitoring_command() { + let expected_movie_id = 1; + let mut mock_network = MockNetworkTrait::new(); + mock_network + .expect_handle_network_event() + .with(eq::( + RadarrEvent::ToggleMovieMonitoring(expected_movie_id).into(), + )) + .times(1) + .returning(|_| { + Ok(Serdeable::Radarr(RadarrSerdeable::Value( + json!({"testResponse": "response"}), + ))) + }); + let app_arc = Arc::new(Mutex::new(App::test_default())); + let toggle_movie_monitoring_command = RadarrCommand::ToggleMovieMonitoring { movie_id: 1 }; + + let result = + RadarrCliHandler::with(&app_arc, toggle_movie_monitoring_command, &mut mock_network) + .handle() + .await; + + assert!(result.is_ok()); + } + #[tokio::test] async fn test_trigger_automatic_search_command() { let expected_movie_id = 1; diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 759cf37..e4c40f9 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -2,7 +2,7 @@ use anyhow::Result; use std::fmt::Debug; use indoc::formatdoc; -use log::{debug, info}; +use log::{debug, info, warn}; use serde_json::{json, Value}; use urlencoding::encode; @@ -73,6 +73,7 @@ pub enum RadarrEvent { StartTask(RadarrTaskName), TestIndexer(i64), TestAllIndexers, + ToggleMovieMonitoring(i64), TriggerAutomaticSearch(i64), UpdateAllMovies, UpdateAndScan(i64), @@ -100,7 +101,8 @@ impl NetworkResource for RadarrEvent { | RadarrEvent::EditMovie(_) | RadarrEvent::GetMovies | RadarrEvent::GetMovieDetails(_) - | RadarrEvent::DeleteMovie(_) => "/movie", + | RadarrEvent::DeleteMovie(_) + | RadarrEvent::ToggleMovieMonitoring(_) => "/movie", RadarrEvent::SearchNewMovie(_) => "/movie/lookup", RadarrEvent::GetMovieCredits(_) => "/credit", RadarrEvent::GetMovieHistory(_) => "/history/movie", @@ -265,6 +267,10 @@ impl Network<'_, '_> { .test_all_radarr_indexers() .await .map(RadarrSerdeable::from), + RadarrEvent::ToggleMovieMonitoring(movie_id) => self + .toggle_movie_monitoring(movie_id) + .await + .map(RadarrSerdeable::from), RadarrEvent::TriggerAutomaticSearch(movie_id) => self .trigger_automatic_movie_search(movie_id) .await @@ -1748,6 +1754,66 @@ impl Network<'_, '_> { .await } + async fn toggle_movie_monitoring(&mut self, movie_id: i64) -> Result<()> { + let event = RadarrEvent::ToggleMovieMonitoring(movie_id); + + let detail_event = RadarrEvent::GetMovieDetails(movie_id); + info!("Toggling movie monitoring for movie with ID: {movie_id}"); + info!("Fetching movie details for movie with ID: {movie_id}"); + + let request_props = self + .request_props_from( + detail_event, + RequestMethod::Get, + None::<()>, + Some(format!("/{movie_id}")), + None, + ) + .await; + + let mut response = String::new(); + + self + .handle_request::<(), Value>(request_props, |detailed_movie_body, _| { + response = detailed_movie_body.to_string() + }) + .await?; + + info!("Constructing toggle movie monitoring body"); + + match serde_json::from_str::(&response) { + Ok(mut detailed_movie_body) => { + let monitored = detailed_movie_body + .get("monitored") + .unwrap() + .as_bool() + .unwrap(); + + *detailed_movie_body.get_mut("monitored").unwrap() = json!(!monitored); + + debug!("Toggle movie monitoring body: {detailed_movie_body:?}"); + + let request_props = self + .request_props_from( + event, + RequestMethod::Put, + Some(detailed_movie_body), + Some(format!("/{movie_id}")), + None, + ) + .await; + + self + .handle_request::(request_props, |_, _| ()) + .await + } + Err(_) => { + warn!("Request for detailed movie body was interrupted"); + Ok(()) + } + } + } + async fn trigger_automatic_movie_search(&mut self, movie_id: i64) -> Result { let event = RadarrEvent::TriggerAutomaticSearch(movie_id); info!("Searching indexers for movie with ID: {movie_id}"); diff --git a/src/network/radarr_network_tests.rs b/src/network/radarr_network_tests.rs index 16b6235..6b9722f 100644 --- a/src/network/radarr_network_tests.rs +++ b/src/network/radarr_network_tests.rs @@ -120,7 +120,8 @@ mod test { RadarrEvent::EditMovie(EditMovieParams::default()), RadarrEvent::GetMovies, RadarrEvent::GetMovieDetails(0), - RadarrEvent::DeleteMovie(DeleteMovieParams::default()) + RadarrEvent::DeleteMovie(DeleteMovieParams::default()), + RadarrEvent::ToggleMovieMonitoring(0) )] event: RadarrEvent, ) { @@ -953,6 +954,50 @@ mod test { } } + #[tokio::test] + async fn test_handle_toggle_movies_monitoring_event() { + let mut expected_body: Value = serde_json::from_str(MOVIE_JSON).unwrap(); + *expected_body.get_mut("monitored").unwrap() = json!(false); + + let (async_details_server, app_arc, mut server) = mock_servarr_api( + RequestMethod::Get, + None, + Some(serde_json::from_str(MOVIE_JSON).unwrap()), + None, + RadarrEvent::GetMovieDetails(1), + Some("/1"), + None, + ) + .await; + let async_toggle_server = server + .mock( + "PUT", + format!( + "/api/v3{}/1", + RadarrEvent::ToggleMovieMonitoring(1).resource() + ) + .as_str(), + ) + .with_status(202) + .match_header("X-Api-Key", "test1234") + .match_body(Matcher::Json(expected_body)) + .create_async() + .await; + { + let mut app = app_arc.lock().await; + app.data.radarr_data.movies.set_items(vec![movie()]); + } + let mut network = Network::new(&app_arc, CancellationToken::new(), Client::new()); + + assert!(network + .handle_radarr_event(RadarrEvent::ToggleMovieMonitoring(1)) + .await + .is_ok()); + + async_details_server.assert_async().await; + async_toggle_server.assert_async().await; + } + #[tokio::test] async fn test_handle_trigger_automatic_movie_search_event() { let (async_server, app_arc, _server) = mock_servarr_api(