Added the full Radarr CLI so users can programmatically access all the same management features as in the TUI
This commit is contained in:
+41
-16
@@ -1,7 +1,8 @@
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error, warn};
|
||||
use regex::Regex;
|
||||
use reqwest::{Client, RequestBuilder};
|
||||
@@ -13,7 +14,10 @@ use tokio::sync::{Mutex, MutexGuard};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::app::App;
|
||||
use crate::models::Serdeable;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
pub mod radarr_network;
|
||||
mod utils;
|
||||
@@ -22,17 +26,41 @@ mod utils;
|
||||
#[path = "network_tests.rs"]
|
||||
mod network_tests;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum NetworkEvent {
|
||||
Radarr(RadarrEvent),
|
||||
}
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
#[async_trait]
|
||||
pub trait NetworkTrait {
|
||||
async fn handle_network_event(&mut self, network_event: NetworkEvent) -> Result<Serdeable>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Network<'a, 'b> {
|
||||
client: Client,
|
||||
cancellation_token: CancellationToken,
|
||||
pub app: &'a Arc<Mutex<App<'b>>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a, 'b> NetworkTrait for Network<'a, 'b> {
|
||||
async fn handle_network_event(&mut self, network_event: NetworkEvent) -> Result<Serdeable> {
|
||||
let resp = match network_event {
|
||||
NetworkEvent::Radarr(radarr_event) => self
|
||||
.handle_radarr_event(radarr_event)
|
||||
.await
|
||||
.map(Serdeable::from),
|
||||
};
|
||||
|
||||
let mut app = self.app.lock().await;
|
||||
app.is_loading = false;
|
||||
|
||||
resp
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> Network<'a, 'b> {
|
||||
pub fn new(app: &'a Arc<Mutex<App<'b>>>, cancellation_token: CancellationToken) -> Self {
|
||||
Network {
|
||||
@@ -42,22 +70,14 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_network_event(&mut self, network_event: NetworkEvent) {
|
||||
match network_event {
|
||||
NetworkEvent::Radarr(radarr_event) => self.handle_radarr_event(radarr_event).await,
|
||||
}
|
||||
|
||||
let mut app = self.app.lock().await;
|
||||
app.is_loading = false;
|
||||
}
|
||||
|
||||
pub async fn handle_request<B, R>(
|
||||
async fn handle_request<B, R>(
|
||||
&mut self,
|
||||
request_props: RequestProps<B>,
|
||||
mut app_update_fn: impl FnMut(R, MutexGuard<'_, App<'_>>),
|
||||
) where
|
||||
) -> Result<R>
|
||||
where
|
||||
B: Serialize + Default + Debug,
|
||||
R: DeserializeOwned,
|
||||
R: DeserializeOwned + Default + Clone,
|
||||
{
|
||||
let ignore_status_code = request_props.ignore_status_code;
|
||||
let method = request_props.method;
|
||||
@@ -68,6 +88,7 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
let mut app = self.app.lock().await;
|
||||
self.cancellation_token = app.reset_cancellation_token();
|
||||
app.is_loading = false;
|
||||
Ok(R::default())
|
||||
}
|
||||
resp = self.call_api(request_props).await.send() => {
|
||||
match resp {
|
||||
@@ -78,7 +99,8 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
match utils::parse_response::<R>(response).await {
|
||||
Ok(value) => {
|
||||
let app = self.app.lock().await;
|
||||
app_update_fn(value, app);
|
||||
app_update_fn(value.clone(), app);
|
||||
Ok(value)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to parse response! {e:?}");
|
||||
@@ -87,10 +109,11 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.lock()
|
||||
.await
|
||||
.handle_error(anyhow!("Failed to parse response! {e:?}"));
|
||||
Err(anyhow!("Failed to parse response! {e:?}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
RequestMethod::Delete | RequestMethod::Put => (),
|
||||
RequestMethod::Delete | RequestMethod::Put => Ok(R::default()),
|
||||
}
|
||||
} else {
|
||||
let status = response.status();
|
||||
@@ -102,6 +125,7 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
|
||||
error!("Request failed. Received {status} response code with body: {response_body}");
|
||||
self.app.lock().await.handle_error(anyhow!("Request failed. Received {status} response code with body: {error_body}"));
|
||||
Err(anyhow!("Request failed. Received {status} response code with body: {error_body}"))
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -111,6 +135,7 @@ impl<'a, 'b> Network<'a, 'b> {
|
||||
.lock()
|
||||
.await
|
||||
.handle_error(anyhow!("Failed to send request. {e} "));
|
||||
Err(anyhow!("Failed to send request. {e} "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ mod tests {
|
||||
use crate::app::{App, AppConfig, RadarrConfig};
|
||||
use crate::models::HorizontallyScrollableText;
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
use crate::network::{Network, NetworkEvent, RequestMethod, RequestProps};
|
||||
use crate::network::{Network, NetworkEvent, NetworkTrait, RequestMethod, RequestProps};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_handle_network_event_radarr_event() {
|
||||
@@ -22,6 +22,7 @@ mod tests {
|
||||
let radarr_server = server
|
||||
.mock("GET", "/api/v3/health")
|
||||
.with_status(200)
|
||||
.with_body("{}")
|
||||
.create_async()
|
||||
.await;
|
||||
let host = server.host_with_port().split(':').collect::<Vec<&str>>()[0].to_owned();
|
||||
@@ -41,7 +42,7 @@ mod tests {
|
||||
let app_arc = Arc::new(Mutex::new(app));
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
let _ = network
|
||||
.handle_network_event(RadarrEvent::HealthCheck.into())
|
||||
.await;
|
||||
|
||||
@@ -65,7 +66,7 @@ mod tests {
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
let _ = network
|
||||
.handle_request::<Test, ()>(
|
||||
RequestProps {
|
||||
uri: format!("{}/test", server.url()),
|
||||
@@ -91,7 +92,7 @@ mod tests {
|
||||
let (async_server, app_arc, server) = mock_api(request_method, 200, true).await;
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
let resp = network
|
||||
.handle_request::<(), Test>(
|
||||
RequestProps {
|
||||
uri: format!("{}/test", server.url()),
|
||||
@@ -106,6 +107,13 @@ mod tests {
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert_str_eq!(app_arc.lock().await.error.text, "Test");
|
||||
assert!(resp.is_ok());
|
||||
assert_eq!(
|
||||
resp.unwrap(),
|
||||
Test {
|
||||
value: "Test".to_owned()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -115,9 +123,9 @@ mod tests {
|
||||
) {
|
||||
let (async_server, app_arc, server) = mock_api(request_method, 400, true).await;
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
let mut test_result = String::default();
|
||||
let mut test_result = String::new();
|
||||
|
||||
network
|
||||
let resp = network
|
||||
.handle_request::<(), Test>(
|
||||
RequestProps {
|
||||
uri: format!("{}/test", server.url()),
|
||||
@@ -132,6 +140,13 @@ mod tests {
|
||||
|
||||
async_server.assert_async().await;
|
||||
assert!(app_arc.lock().await.error.text.is_empty());
|
||||
assert!(resp.is_ok());
|
||||
assert_eq!(
|
||||
resp.unwrap(),
|
||||
Test {
|
||||
value: "Test".to_owned()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -148,7 +163,7 @@ mod tests {
|
||||
let mut network = Network::new(&app_arc, cancellation_token);
|
||||
network.cancellation_token.cancel();
|
||||
|
||||
network
|
||||
let resp = network
|
||||
.handle_request::<(), Test>(
|
||||
RequestProps {
|
||||
uri: format!("{}/test", server.url()),
|
||||
@@ -164,6 +179,8 @@ mod tests {
|
||||
assert!(!async_server.matched_async().await);
|
||||
assert!(app_arc.lock().await.error.text.is_empty());
|
||||
assert!(!network.cancellation_token.is_cancelled());
|
||||
assert!(resp.is_ok());
|
||||
assert_eq!(resp.unwrap(), Test::default());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -179,7 +196,7 @@ mod tests {
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
let resp = network
|
||||
.handle_request::<(), Test>(
|
||||
RequestProps {
|
||||
uri: format!("{}/test", server.url()),
|
||||
@@ -199,6 +216,11 @@ mod tests {
|
||||
.error
|
||||
.text
|
||||
.starts_with("Failed to parse response!"));
|
||||
assert!(resp.is_err());
|
||||
assert!(resp
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.starts_with("Failed to parse response!"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -206,10 +228,10 @@ mod tests {
|
||||
let app_arc = Arc::new(Mutex::new(App::default()));
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
let resp = network
|
||||
.handle_request::<(), Test>(
|
||||
RequestProps {
|
||||
uri: String::default(),
|
||||
uri: String::new(),
|
||||
method: RequestMethod::Get,
|
||||
body: None,
|
||||
api_token: "test1234".to_owned(),
|
||||
@@ -225,6 +247,11 @@ mod tests {
|
||||
.error
|
||||
.text
|
||||
.starts_with("Failed to send request."));
|
||||
assert!(resp.is_err());
|
||||
assert!(resp
|
||||
.unwrap_err()
|
||||
.to_string()
|
||||
.starts_with("Failed to send request."));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -241,7 +268,7 @@ mod tests {
|
||||
let (async_server, app_arc, server) = mock_api(request_method, 404, true).await;
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
let resp = network
|
||||
.handle_request::<(), Test>(
|
||||
RequestProps {
|
||||
uri: format!("{}/test", server.url()),
|
||||
@@ -259,6 +286,11 @@ mod tests {
|
||||
app_arc.lock().await.error.text,
|
||||
r#"Request failed. Received 404 Not Found response code with body: { "value": "Test" }"#
|
||||
);
|
||||
assert!(resp.is_err());
|
||||
assert_str_eq!(
|
||||
resp.unwrap_err().to_string(),
|
||||
r#"Request failed. Received 404 Not Found response code with body: { "value": "Test" }"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -266,7 +298,7 @@ mod tests {
|
||||
let (async_server, app_arc, server) = mock_api(RequestMethod::Post, 404, false).await;
|
||||
let mut network = Network::new(&app_arc, CancellationToken::new());
|
||||
|
||||
network
|
||||
let resp = network
|
||||
.handle_request::<(), Test>(
|
||||
RequestProps {
|
||||
uri: format!("{}/test", server.url()),
|
||||
@@ -284,6 +316,11 @@ mod tests {
|
||||
app_arc.lock().await.error.text,
|
||||
r#"Request failed. Received 404 Not Found response code with body: "#
|
||||
);
|
||||
assert!(resp.is_err());
|
||||
assert_str_eq!(
|
||||
resp.unwrap_err().to_string(),
|
||||
r#"Request failed. Received 404 Not Found response code with body: "#
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
@@ -335,7 +372,7 @@ mod tests {
|
||||
async_server.assert_async().await;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
|
||||
struct Test {
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
+912
-516
File diff suppressed because it is too large
Load Diff
+1804
-452
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user