autopulse_service/settings/targets/mod.rs
1/// Audiobookshelf - Audiobookshelf target
2///
3/// This target is used to send a file to the Audiobookshelf watcher
4///
5/// # Example
6///
7/// ```yml
8/// targets:
9/// audiobookshelf:
10/// type: audiobookshelf
11/// url: http://localhost:13378
12/// token: "<API_KEY>"
13/// ```
14///
15/// See [`Audiobookshelf`] for all options
16pub mod audiobookshelf;
17/// Autopulse - Autopulse target
18///
19/// This target is used to process a file in another instance of Autopulse
20///
21/// # Example
22///
23/// ```yml
24/// targets:
25/// autopulse:
26/// type: autopulse
27/// url: http://localhost:2875
28/// auth:
29/// username: "admin"
30/// password: "password"
31/// ```
32/// or
33/// ```yml
34/// targets:
35/// autopulse:
36/// type: autopulse
37/// url: http://localhost:2875
38/// auth:
39/// username: "admin"
40/// password: "password"
41/// trigger: "other"
42/// ```
43///
44/// See [`Autopulse`] for all options
45pub mod autopulse;
46/// Command - Command target
47///
48/// This target is used to run a command to process a file
49///
50/// # Example
51///
52/// ```yml
53/// targets:
54/// list:
55/// type: command
56/// raw: "echo $FILE_PATH >> list.log"
57/// ```
58///
59/// or
60///
61/// ```yml
62/// targets:
63/// list:
64/// type: command
65/// path: "/path/to/script.sh"
66/// ```
67///
68/// See [`Command`] for all options
69pub mod command;
70/// Emby - Emby/Jellyfin target
71///
72/// This target is used to refresh/scan a file in Emby/Jellyfin
73///
74/// # Example
75///
76/// ```yml
77/// targets:
78/// my_jellyfin:
79/// type: jellyfin
80/// url: http://localhost:8096
81/// token: "<API_KEY>"
82/// # refresh_metadata: false # To disable metadata refresh
83/// ```
84/// or
85/// ```yml
86/// targets:
87/// my_emby:
88/// type: emby
89/// url: http://localhost:8096
90/// token: "<API_KEY>"
91/// # refresh_metadata: false # To disable metadata refresh
92/// # metadata_refresh_mode: "validation_only" # To change metadata refresh mode
93/// ```
94///
95/// See [`Emby`] for all options
96#[doc(alias("jellyfin"))]
97pub mod emby;
98/// `FileFlows` - `FileFlows` target
99///
100/// This target is used to process a file in `FileFlows`
101///
102/// # Example
103///
104/// ```yml
105/// targets:
106/// fileflows:
107/// type: fileflows
108/// url: http://localhost:5000
109/// ```
110///
111/// See [`FileFlows`] for all options
112pub mod fileflows;
113/// Plex - Plex target
114///
115/// This target is used to scan a file in Plex
116///
117/// # Example
118///
119/// ```yml
120/// targets:
121/// my_plex:
122/// type: plex
123/// url: http://localhost:32400
124/// token: "<PLEX_TOKEN>"
125/// ```
126/// or
127/// ```yml
128/// targets:
129/// my_plex:
130/// type: plex
131/// url: http://localhost:32400
132/// token: "<PLEX_TOKEN>"
133/// refresh: true
134/// analyze: true
135/// ```
136///
137/// See [`Plex`] for all options
138pub mod plex;
139/// Radarr - Radarr target
140///
141/// This target is used to refresh/rescan a movie in Radarr
142///
143/// # Example
144///
145/// ```yml
146/// targets:
147/// radarr:
148/// type: radarr
149/// url: http://localhost:7878
150/// token: "<API_KEY>"
151/// ```
152///
153/// See [`Radarr`] for all options
154pub mod radarr;
155/// Sonarr - Sonarr target
156///
157/// This target is used to refresh/rescan a series in Sonarr
158///
159/// # Example
160///
161/// ```yml
162/// targets:
163/// sonarr:
164/// type: sonarr
165/// url: http://localhost:8989
166/// token: "<API_KEY>"
167/// ```
168///
169/// See [`Sonarr`] for all options
170pub mod sonarr;
171/// Tdarr - Tdarr target
172///
173/// This target is used to process a file in Tdarr
174///
175/// # Example
176///
177/// ```yml
178/// targets:
179/// tdarr:
180/// type: tdarr
181/// url: http://localhost:8265
182/// db_id: "<LIBRARY_ID>"
183/// ```
184///
185/// See [`Tdarr`] for all options
186pub mod tdarr;
187
188use audiobookshelf::Audiobookshelf;
189use autopulse_database::models::ScanEvent;
190use reqwest::{RequestBuilder, Response};
191use serde::{Deserialize, Serialize};
192use {
193 autopulse::Autopulse, command::Command, emby::Emby, fileflows::FileFlows, plex::Plex,
194 radarr::Radarr, sonarr::Sonarr, tdarr::Tdarr,
195};
196
197#[derive(Serialize, Deserialize)]
198#[serde(rename_all = "lowercase")]
199pub enum TargetType {
200 Plex,
201 Jellyfin,
202 Emby,
203 Tdarr,
204 Sonarr,
205 Radarr,
206 Command,
207 FileFlows,
208 Autopulse,
209}
210
211#[derive(Serialize, Deserialize, Clone)]
212#[serde(tag = "type", rename_all = "lowercase")]
213pub enum Target {
214 Plex(Plex),
215 Jellyfin(Emby),
216 Emby(Emby),
217 Tdarr(Tdarr),
218 Sonarr(Sonarr),
219 Radarr(Radarr),
220 Command(Command),
221 FileFlows(FileFlows),
222 Autopulse(Autopulse),
223 Audiobookshelf(Audiobookshelf),
224}
225
226pub trait TargetProcess {
227 fn process(
228 &self,
229 evs: &[&ScanEvent],
230 ) -> impl std::future::Future<Output = anyhow::Result<Vec<String>>> + Send;
231}
232
233impl TargetProcess for Target {
234 async fn process(&self, evs: &[&ScanEvent]) -> anyhow::Result<Vec<String>> {
235 match self {
236 Self::Plex(t) => t.process(evs).await,
237 Self::Jellyfin(t) | Self::Emby(t) => t.process(evs).await,
238 Self::Command(t) => t.process(evs).await,
239 Self::Tdarr(t) => t.process(evs).await,
240 Self::Sonarr(t) => t.process(evs).await,
241 Self::Radarr(t) => t.process(evs).await,
242 Self::FileFlows(t) => t.process(evs).await,
243 Self::Autopulse(t) => t.process(evs).await,
244 Self::Audiobookshelf(t) => t.process(evs).await,
245 }
246 }
247}
248
249pub trait RequestBuilderPerform {
250 fn perform(self) -> impl std::future::Future<Output = anyhow::Result<Response>> + Send;
251}
252
253impl RequestBuilderPerform for RequestBuilder {
254 async fn perform(self) -> anyhow::Result<Response> {
255 let copy = self
256 .try_clone()
257 .ok_or_else(|| anyhow::anyhow!("failed to clone request"))?;
258 let built = copy
259 .build()
260 .map_err(|e| anyhow::anyhow!("failed to build request: {}", e))?;
261 let response = self.send().await;
262
263 match response {
264 Ok(response) => {
265 if !response.status().is_success() {
266 return Err(anyhow::anyhow!(
267 // failed to PUT /path/to/file: 404 - Not Found
268 "unable to {} {}: {} - {}",
269 built.method(),
270 built.url(),
271 response.status(),
272 response
273 .text()
274 .await
275 .unwrap_or_else(|_| "unknown error".to_string()),
276 ));
277 }
278
279 Ok(response)
280 }
281
282 Err(e) => {
283 let status = e.status();
284 if let Some(status) = status {
285 return Err(anyhow::anyhow!(
286 "failed to {} {}: {} - {}",
287 built.method(),
288 built.url(),
289 status,
290 e
291 ));
292 }
293
294 Err(anyhow::anyhow!(
295 "failed to {} {}: {}",
296 built.method(),
297 built.url(),
298 e,
299 ))
300 }
301 }
302 }
303}