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}