1use std::{
2 collections::BTreeMap,
3 env,
4 fs::{self, read_to_string},
5 path::PathBuf,
6};
7
8use serde::{Deserialize, Serialize};
9
10mod eol_rule;
11
12use eol_rule::EolRule;
13
14use crate::{EolAction, Error, MessageAge, Result, Retention};
15
16#[derive(Debug, Serialize, Deserialize)]
18pub struct Config {
19 credentials: Option<String>,
20 rules: BTreeMap<String, EolRule>,
21}
22
23impl Default for Config {
24 fn default() -> Self {
25 let rules = BTreeMap::new();
26
27 let mut cfg = Self {
28 credentials: Some("credential.json".to_string()),
29 rules,
30 };
31
32 cfg.add_rule(Retention::new(MessageAge::Years(1), true), None, false)
33 .add_rule(Retention::new(MessageAge::Weeks(1), true), None, false)
34 .add_rule(Retention::new(MessageAge::Months(1), true), None, false)
35 .add_rule(Retention::new(MessageAge::Years(5), true), None, false);
36
37 cfg
38 }
39}
40
41impl Config {
42 pub fn new() -> Self {
44 Config::default()
45 }
46
47 pub fn set_credentials(&mut self, file_name: &str) -> &mut Self {
49 self.credentials = Some(file_name.to_string());
50 self
51 }
52
53 pub fn get_rule(&self, id: usize) -> Option<EolRule> {
55 self.rules.get(&id.to_string()).cloned()
56 }
57
58 pub fn add_rule(
60 &mut self,
61 retention: Retention,
62 label: Option<&String>,
63 delete: bool,
64 ) -> &mut Self {
65 let mut current_labels = Vec::new();
66 for rule in self.rules.values() {
67 let mut ls = rule.labels().clone();
68 current_labels.append(&mut ls);
69 }
70
71 if label.is_some() && current_labels.contains(label.unwrap()) {
72 log::warn!("a rule already applies to label {}", label.unwrap());
73 return self;
74 }
75
76 let id = if let Some((_, max)) = self.rules.iter().max_by_key(|(_, r)| r.id()) {
77 max.id() + 1
78 } else {
79 1
80 };
81
82 let mut rule = EolRule::new(id);
83 rule.set_retention(retention);
84 if let Some(l) = label {
85 rule.add_label(l);
86 }
87 if delete {
88 rule.set_action(&EolAction::Delete);
89 }
90 log::info!("added rule: {rule}");
91 self.rules.insert(rule.id().to_string(), rule);
92 self
93 }
94
95 pub fn labels(&self) -> Vec<String> {
97 let mut labels = Vec::new();
98 for rule in self.rules.values() {
99 labels.append(&mut rule.labels().clone());
100 }
101 labels
102 }
103
104 fn find_label(&self, label: &str) -> usize {
106 let rules_by_label = self.get_rules_by_label();
107 if let Some(rule) = rules_by_label.get(label) {
108 rule.id()
109 } else {
110 0
111 }
112 }
113
114 pub fn remove_rule_by_id(&mut self, id: usize) -> crate::Result<()> {
116 self.rules.remove(&id.to_string());
117 println!("Rule `{id}` has been removed.");
118 Ok(())
119 }
120
121 pub fn remove_rule_by_label(&mut self, label: &str) -> crate::Result<()> {
123 let labels = self.labels();
124
125 if !labels.contains(&label.to_string()) {
126 return Err(Error::LabelNotFoundInRules(label.to_string()));
127 }
128
129 let rule_id = self.find_label(label);
130 if rule_id == 0 {
131 return Err(Error::NoRuleFoundForLabel(label.to_string()));
132 }
133
134 self.rules.remove(&rule_id.to_string());
135
136 log::info!("Rule containing the label `{label}` has been removed.");
137 Ok(())
138 }
139
140 fn get_rules_by_label(&self) -> BTreeMap<String, EolRule> {
141 let mut rbl = BTreeMap::new();
142
143 for rule in self.rules.values() {
144 for label in rule.labels() {
145 rbl.insert(label, rule.clone());
146 }
147 }
148
149 rbl
150 }
151
152 pub fn add_label_to_rule(&mut self, id: usize, label: &str) -> Result<()> {
154 let Some(rule) = self.rules.get_mut(id.to_string().as_str()) else {
155 return Err(Error::RuleNotFound(id));
156 };
157 rule.add_label(label);
158 self.save()?;
159 println!("Label `{label}` added to rule `#{id}`");
160
161 Ok(())
162 }
163
164 pub fn remove_label_from_rule(&mut self, id: usize, label: &str) -> Result<()> {
166 let Some(rule) = self.rules.get_mut(id.to_string().as_str()) else {
167 return Err(Error::RuleNotFound(id));
168 };
169 rule.remove_label(label);
170 self.save()?;
171 println!("Label `{label}` removed from rule `#{id}`");
172
173 Ok(())
174 }
175
176 pub fn set_action_on_rule(&mut self, id: usize, action: &EolAction) -> Result<()> {
178 let Some(rule) = self.rules.get_mut(id.to_string().as_str()) else {
179 return Err(Error::RuleNotFound(id));
180 };
181 rule.set_action(action);
182 self.save()?;
183 println!("Action set to `{action}` on rule `#{id}`");
184
185 Ok(())
186 }
187
188 pub fn save(&self) -> Result<()> {
190 let home_dir = env::home_dir().unwrap();
191 let path = PathBuf::new()
192 .join(home_dir)
193 .join(".cull-gmail/cull-gmail.toml");
194
195 let res = toml::to_string(self);
196 log::trace!("toml conversion result: {res:#?}");
197
198 if let Ok(output) = res {
199 fs::write(&path, output)?;
200 log::trace!("Config saved to {}", path.display());
201 }
202
203 Ok(())
204 }
205
206 pub fn load() -> Result<Config> {
208 let home_dir = env::home_dir().unwrap();
209 let path = PathBuf::new()
210 .join(home_dir)
211 .join(".cull-gmail/cull-gmail.toml");
212 log::trace!("Loading config from {}", path.display());
213
214 let input = read_to_string(path)?;
215 let config = toml::from_str::<Config>(&input)?;
216 Ok(config)
217 }
218
219 pub fn credential_file(&self) -> &str {
221 if let Some(file) = &self.credentials {
222 file
223 } else {
224 ""
225 }
226 }
227
228 pub fn list_rules(&self) -> Result<()> {
230 for rule in self.rules.values() {
231 println!("{rule}");
232 }
233 Ok(())
234 }
235}