Skip to main content

autopulse_service/settings/triggers/
mod.rs

1/// Autoscan - autoscan compatibility trigger
2///
3/// Provides a trigger with compatibility for applications that are built for autoscan
4///
5/// # Example
6///
7/// ```yml
8/// triggers:
9///   my_autoscan:
10///     type: autoscan
11/// ```
12///
13/// or
14///
15/// ```yml
16/// triggers:
17///   my_autoscan:
18///     type: autoscan
19///     rewrite:
20///       from: "/downloads/all"
21///       to: "/all"
22///     timer:
23///       wait: 30
24///     excludes: [ "ignored_target" ]
25/// ```
26///
27/// See [`Autoscan`] for all options
28pub mod autoscan;
29/// Lidarr - Lidarr trigger
30///
31/// This trigger is used to process a file from Lidarr
32///
33/// # Example
34///
35/// ```yml
36/// triggers:
37///   my_lidarr:
38///     type: lidarr
39/// ```
40///
41/// or
42///
43/// ```yml
44/// triggers:
45///   my_lidarr:
46///     type: lidarr
47///     rewrite:
48///       from: "/downloads/music"
49///       to: "/music"
50///     timer:
51///       wait: 30
52///     excludes: [ "ignored_target" ]
53/// ```
54///
55/// See [`Lidarr`] for all options
56pub mod lidarr;
57/// Manual - Manual trigger
58///
59/// This trigger is used to manually process a file. Often used when implementing a custom trigger
60///
61/// Note: A default route of `/triggers/manual` is provided
62///
63/// # Example
64///
65/// ```yml
66/// triggers:
67///   my_manual:
68///     type: manual
69/// ```
70///
71/// or
72///
73/// ```yml
74/// triggers:
75///   my_manual:
76///     type: manual
77///     rewrite:
78///       from: "/downloads/stuff"
79///       to: "/stuff"
80///     timer:
81///       wait: 30
82///     excludes: [ "ignored_target" ]
83/// ```
84///
85/// See [`Manual`] for all options
86/// and
87/// [`ManualQueryParams`](manual::ManualQueryParams) for query parameters
88pub mod manual;
89/// Notify - Notify trigger
90///
91/// Cross-platform monitoring for a directory to process based on file events
92///
93/// # Example
94///
95/// ```yml
96/// triggers:
97///   my_notify:
98///     type: notify
99///     paths:
100///       - "/path/to/monitor"
101/// ```
102///
103/// or
104///
105/// ```yml
106/// triggers:
107///   my_notify:
108///     type: notify
109///     paths:
110///       - "/downloads"
111///     recursive: false
112///     rewrite:
113///       from: "/downloads"
114///       to: "/media"
115///     timer:
116///       wait: 30
117///     excludes: [ "ignored_target" ]
118/// ```
119///
120/// See [`Notify`] for all options
121pub mod notify;
122/// Radarr - Radarr trigger
123///
124/// This trigger is used to process a file from Radarr
125///
126/// # Example
127///
128/// ```yml
129/// triggers:
130///   my_radarr:
131///     type: radarr
132/// ```
133///
134/// or
135///
136/// ```yml
137/// triggers:
138///   my_radarr:
139///     type: radarr
140///     rewrite:
141///       from: "/downloads/movies"
142///       to: "/movies"
143///     timer:
144///       wait: 30
145///     excludes: [ "ignored_target" ]
146/// ```
147///
148/// See [`Radarr`] for all options
149pub mod radarr;
150/// Readarr - Readarr trigger
151///
152/// This trigger is used to process a file from Readarr
153///
154/// # Example
155///
156/// ```yml
157/// triggers:
158///   my_readarr:
159///     type: readarr
160/// ```
161///
162/// or
163///
164/// ```yml
165/// triggers:
166///   my_readarr:
167///     type: readarr
168///     rewrite:
169///       from: "/downloads/books"
170///       to: "/books"
171///     timer:
172///       wait: 30
173///     excludes: [ "ignored_target" ]
174/// ```
175///
176/// See [`Readarr`] for all options
177pub mod readarr;
178/// Sonarr - Sonarr trigger
179///
180/// This trigger is used to process a file from Sonarr
181///
182/// # Example
183///
184/// ```yml
185/// triggers:
186///   my_sonarr:
187///     type: sonarr
188/// ```
189///
190/// or
191///
192/// ```yml
193/// triggers:
194///   my_sonarr:
195///     type: sonarr
196///     rewrite:
197///       from: "/downloads/shows"
198///       to: "/shows"
199///     timer:
200///       wait: 30
201///     excludes: [ "ignored_target" ]
202/// ```
203///
204/// See [`Sonarr`] for all options
205pub mod sonarr;
206/// Sportarr - Sportarr trigger
207///
208/// This trigger is used to process a file from Sportarr
209///
210/// # Example
211///
212/// ```yml
213/// triggers:
214///   my_sportarr:
215///     type: sportarr
216/// ```
217///
218/// or
219///
220/// ```yml
221/// triggers:
222///   my_sportarr:
223///     type: sportarr
224///     rewrite:
225///       from: "/downloads"
226///       to: "/sports"
227///     timer:
228///       wait: 30
229///     excludes: [ "ignored_target" ]
230/// ```
231///
232/// See [`Sportarr`] for all options
233pub mod sportarr;
234
235use crate::settings::path_filter::PathFilter;
236use crate::settings::timer::EventTimers;
237use crate::settings::timer::Timer;
238use crate::settings::{rewrite::Rewrite, triggers::autoscan::Autoscan};
239use serde::{Deserialize, Serialize};
240use {
241    lidarr::{Lidarr, LidarrRequest},
242    manual::Manual,
243    notify::Notify,
244    radarr::{Radarr, RadarrRequest},
245    readarr::{Readarr, ReadarrRequest},
246    sonarr::{Sonarr, SonarrRequest},
247    sportarr::{Sportarr, SportarrRequest},
248};
249
250pub trait TriggerRequest {
251    fn from_json(json: serde_json::Value) -> anyhow::Result<Self>
252    where
253        Self: Sized;
254
255    // where the bool represents whether to check found status
256    fn paths(&self) -> Vec<(String, bool)>;
257}
258
259pub trait TriggerConfig {
260    fn rewrite(&self) -> Option<&Rewrite>;
261    fn timer(&self) -> Option<&Timer>;
262    fn excludes(&self) -> &Vec<String>;
263    fn filter(&self) -> &PathFilter;
264    fn event_timers(&self) -> Option<&EventTimers> {
265        None
266    }
267}
268
269#[derive(Serialize, Deserialize)]
270#[serde(rename_all = "lowercase")]
271pub enum TriggerType {
272    Manual,
273    Autoscan,
274    Radarr,
275    Bazarr,
276    Sonarr,
277    Sportarr,
278    Lidarr,
279    Readarr,
280    Notify,
281}
282
283#[derive(Serialize, Deserialize, Clone)]
284#[serde(tag = "type", rename_all = "lowercase")]
285pub enum Trigger {
286    Manual(Manual),
287    Autoscan(Autoscan),
288    Bazarr(Manual),
289    Radarr(Radarr),
290    Sonarr(Sonarr),
291    Sportarr(Sportarr),
292    Lidarr(Lidarr),
293    Readarr(Readarr),
294    Notify(Notify),
295}
296
297impl Trigger {
298    fn as_config(&self) -> &dyn TriggerConfig {
299        match self {
300            Self::Manual(trigger) | Self::Bazarr(trigger) => trigger,
301            Self::Autoscan(trigger) => trigger,
302            Self::Radarr(trigger) => trigger,
303            Self::Sonarr(trigger) => trigger,
304            Self::Sportarr(trigger) => trigger,
305            Self::Lidarr(trigger) => trigger,
306            Self::Readarr(trigger) => trigger,
307            Self::Notify(trigger) => trigger,
308        }
309    }
310
311    pub fn get_rewrite(&self) -> Option<&Rewrite> {
312        self.as_config().rewrite()
313    }
314
315    pub fn get_timer(&self, event_name: Option<String>) -> Timer {
316        let config = self.as_config();
317        let mut base_timer = config.timer().cloned().unwrap_or_default();
318
319        let event_specific_timer = event_name
320            .as_ref()
321            .and_then(|event| config.event_timers().and_then(|timers| timers.get(event)));
322
323        if let Some(event_timer) = event_specific_timer {
324            base_timer = base_timer.chain(event_timer);
325        }
326
327        base_timer
328    }
329
330    pub fn paths(&self, body: serde_json::Value) -> anyhow::Result<(String, Vec<(String, bool)>)> {
331        let event_name = body["eventType"].as_str().unwrap_or("unknown").to_string();
332
333        let paths = match &self {
334            Self::Sonarr(_) => Ok(SonarrRequest::from_json(body)?.paths()),
335            Self::Sportarr(_) => Ok(SportarrRequest::from_json(body)?.paths()),
336            Self::Radarr(_) => Ok(RadarrRequest::from_json(body)?.paths()),
337            Self::Lidarr(_) => Ok(LidarrRequest::from_json(body)?.paths()),
338            Self::Readarr(_) => Ok(ReadarrRequest::from_json(body)?.paths()),
339            Self::Manual(_) | Self::Notify(_) | Self::Autoscan(_) | Self::Bazarr(_) => {
340                Err(anyhow::anyhow!("Manual trigger does not have paths"))
341            }
342        }?;
343
344        Ok((event_name, paths))
345    }
346
347    pub fn excludes(&self) -> &Vec<String> {
348        self.as_config().excludes()
349    }
350
351    pub fn should_process_path(&self, path: &str) -> bool {
352        self.as_config().filter().allows(path)
353    }
354}