Skip to main content

autopulse_service/settings/triggers/
sonarr.rs

1use crate::settings::path_filter::PathFilter;
2use crate::settings::rewrite::Rewrite;
3use crate::settings::timer::{EventTimers, Timer};
4use crate::settings::triggers::{TriggerConfig, TriggerRequest};
5use autopulse_utils::join_path;
6use serde::{Deserialize, Serialize};
7
8#[derive(Serialize, Deserialize, Clone)]
9pub struct Sonarr {
10    /// Rewrite path
11    pub rewrite: Option<Rewrite>,
12    /// Timer settings
13    pub timer: Option<Timer>,
14    /// Targets to ignore
15    #[serde(default)]
16    pub excludes: Vec<String>,
17    /// Path filter matched against the rewritten file path.
18    #[serde(default)]
19    pub filter: PathFilter,
20    /// Event-specific timers
21    pub event_timers: Option<EventTimers>,
22}
23
24impl TriggerConfig for Sonarr {
25    fn rewrite(&self) -> Option<&Rewrite> {
26        self.rewrite.as_ref()
27    }
28
29    fn timer(&self) -> Option<&Timer> {
30        self.timer.as_ref()
31    }
32
33    fn excludes(&self) -> &Vec<String> {
34        &self.excludes
35    }
36
37    fn filter(&self) -> &PathFilter {
38        &self.filter
39    }
40
41    fn event_timers(&self) -> Option<&EventTimers> {
42        self.event_timers.as_ref()
43    }
44}
45
46#[derive(Deserialize, Clone)]
47#[serde(rename_all = "camelCase")]
48#[doc(hidden)]
49pub struct EpisodeFile {
50    pub relative_path: String,
51}
52
53#[derive(Deserialize, Clone)]
54#[serde(rename_all = "camelCase")]
55#[doc(hidden)]
56pub struct Series {
57    pub path: String,
58}
59
60#[derive(Deserialize, Clone)]
61#[serde(rename_all = "camelCase")]
62#[doc(hidden)]
63pub struct RenamedEpisodeFile {
64    pub previous_path: String,
65    pub relative_path: String,
66}
67#[derive(Deserialize, Clone)]
68#[serde(tag = "eventType")]
69#[doc(hidden)]
70pub enum SonarrRequest {
71    #[serde(rename = "Download")]
72    #[serde(rename_all = "camelCase")]
73    Download {
74        /// Single file import (WebhookImportPayload)
75        episode_file: Option<EpisodeFile>,
76        /// Batch import (WebhookImportCompletePayload) - Sonarr sends `episodeFiles` (plural)
77        #[serde(default)]
78        episode_files: Vec<EpisodeFile>,
79        #[serde(default)]
80        deleted_files: Vec<EpisodeFile>,
81        series: Series,
82    },
83    #[serde(rename = "Rename")]
84    #[serde(rename_all = "camelCase")]
85    Rename {
86        series: Series,
87        renamed_episode_files: Vec<RenamedEpisodeFile>,
88    },
89    #[serde(rename = "SeriesDelete")]
90    #[serde(rename_all = "camelCase")]
91    SeriesDelete { series: Series },
92    #[serde(rename = "EpisodeFileDelete")]
93    #[serde(rename_all = "camelCase")]
94    EpisodeFileDelete {
95        episode_file: EpisodeFile,
96        series: Series,
97    },
98    #[serde(rename = "Test")]
99    Test,
100    #[serde(other)]
101    Other,
102}
103
104impl TriggerRequest for SonarrRequest {
105    fn from_json(json: serde_json::Value) -> anyhow::Result<Self> {
106        serde_json::from_value(json).map_err(|e| anyhow::anyhow!(e))
107    }
108
109    fn paths(&self) -> Vec<(String, bool)> {
110        match self {
111            Self::EpisodeFileDelete {
112                episode_file,
113                series,
114            } => {
115                vec![(join_path(&series.path, &episode_file.relative_path), false)]
116            }
117            Self::Rename {
118                series,
119                renamed_episode_files,
120            } => {
121                let mut paths = vec![];
122
123                for file in renamed_episode_files {
124                    paths.push((file.previous_path.clone(), false));
125                    paths.push((join_path(&series.path, &file.relative_path), true));
126                }
127
128                paths
129            }
130            Self::SeriesDelete { series } => vec![(series.path.clone(), false)],
131            Self::Download {
132                episode_file,
133                episode_files,
134                series,
135                deleted_files,
136            } => {
137                let mut paths: Vec<(String, bool)> = vec![];
138
139                if let Some(ef) = episode_file {
140                    paths.push((join_path(&series.path, &ef.relative_path), true));
141                }
142
143                for ef in episode_files {
144                    paths.push((join_path(&series.path, &ef.relative_path), true));
145                }
146
147                for file in deleted_files {
148                    paths.push((join_path(&series.path, &file.relative_path), false));
149                }
150
151                paths
152            }
153            Self::Test | Self::Other => vec![],
154        }
155    }
156}