autopulse_service/settings/targets/
radarr.rs1use crate::settings::rewrite::Rewrite;
2use crate::settings::targets::TargetProcess;
3use autopulse_database::models::ScanEvent;
4use autopulse_utils::get_url;
5use reqwest::header;
6use serde::{Deserialize, Serialize};
7use std::{collections::HashMap, path::Path};
8use tracing::error;
9
10use super::{Request, RequestBuilderPerform};
11
12#[derive(Serialize, Deserialize, Clone)]
13pub struct Radarr {
14 pub url: String,
16 pub token: String,
18 pub rewrite: Option<Rewrite>,
20 #[serde(default)]
22 pub request: Request,
23}
24
25#[derive(Deserialize, Debug)]
26struct RadarrMovie {
27 id: i64,
28 path: String,
29}
30
31#[derive(Serialize)]
32#[serde(rename_all = "camelCase")]
33struct RefreshMovie {
34 movie_ids: Vec<i64>,
35}
36
37#[derive(Serialize)]
38#[serde(tag = "name")]
39#[serde(rename_all = "PascalCase")]
40enum Command {
41 RefreshMovie(RefreshMovie),
42}
43
44impl Radarr {
45 fn get_client(&self) -> anyhow::Result<reqwest::Client> {
46 let mut headers = header::HeaderMap::new();
47
48 headers.insert("X-Api-Key", self.token.parse().unwrap());
49 headers.insert("Accept", "application/json".parse().unwrap());
50
51 self.request
52 .client_builder(headers)
53 .build()
54 .map_err(Into::into)
55 }
56
57 async fn get_movies(&self, evs: &[&ScanEvent]) -> anyhow::Result<Vec<i64>> {
58 let client = self.get_client().unwrap();
59
60 let url = get_url(&self.url)?.join("api/v3/movie")?;
61 let mut to_be_refreshed: HashMap<i64, Vec<String>> = HashMap::new();
62
63 let res = client.get(url).perform().await?;
64
65 let movies = res.json::<Vec<RadarrMovie>>().await?;
66
67 for ev in evs {
68 let ev_path = ev.get_path(&self.rewrite);
69 let ev_path = Path::new(&ev_path);
70
71 for movie in &movies {
72 let movie_path = Path::new(&movie.path);
73 if ev_path.starts_with(movie_path) {
74 to_be_refreshed
75 .entry(movie.id)
76 .or_default()
77 .push(ev.id.clone());
78 break;
79 }
80 }
81 }
82
83 Ok(to_be_refreshed.into_keys().collect())
86 }
87
88 async fn refresh_movies(&self, movie_ids: Vec<i64>) -> anyhow::Result<()> {
89 let client = self.get_client().unwrap();
90 let url = get_url(&self.url)?.join("api/v3/command")?;
91 let payload = Command::RefreshMovie(RefreshMovie { movie_ids });
92
93 client.post(url).json(&payload).perform().await.map(|_| ())
94 }
95}
96
97impl TargetProcess for Radarr {
98 async fn process(&self, evs: &[&ScanEvent]) -> anyhow::Result<Vec<String>> {
99 let mut succeeded = Vec::new();
100
101 let movies = self.get_movies(evs).await?;
102
103 match self.refresh_movies(movies).await {
104 Ok(()) => {
105 succeeded.extend(evs.iter().map(|ev| ev.id.clone()));
106 }
107 Err(e) => {
108 error!("failed to refresh series: {}", e);
109 }
110 }
111
112 Ok(succeeded)
113 }
114}