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
12pub use 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 pub fn get_rules_by_label(&self) -> BTreeMap<String, EolRule> {
142 let mut rbl = BTreeMap::new();
143
144 for rule in self.rules.values() {
145 for label in rule.labels() {
146 rbl.insert(label, rule.clone());
147 }
148 }
149
150 rbl
151 }
152
153 pub fn add_label_to_rule(&mut self, id: usize, label: &str) -> Result<()> {
155 let Some(rule) = self.rules.get_mut(id.to_string().as_str()) else {
156 return Err(Error::RuleNotFound(id));
157 };
158 rule.add_label(label);
159 self.save()?;
160 println!("Label `{label}` added to rule `#{id}`");
161
162 Ok(())
163 }
164
165 pub fn remove_label_from_rule(&mut self, id: usize, label: &str) -> Result<()> {
167 let Some(rule) = self.rules.get_mut(id.to_string().as_str()) else {
168 return Err(Error::RuleNotFound(id));
169 };
170 rule.remove_label(label);
171 self.save()?;
172 println!("Label `{label}` removed from rule `#{id}`");
173
174 Ok(())
175 }
176
177 pub fn set_action_on_rule(&mut self, id: usize, action: &EolAction) -> Result<()> {
179 let Some(rule) = self.rules.get_mut(id.to_string().as_str()) else {
180 return Err(Error::RuleNotFound(id));
181 };
182 rule.set_action(action);
183 self.save()?;
184 println!("Action set to `{action}` on rule `#{id}`");
185
186 Ok(())
187 }
188
189 pub fn save(&self) -> Result<()> {
191 let home_dir = env::home_dir().unwrap();
192 let path = PathBuf::new()
193 .join(home_dir)
194 .join(".cull-gmail/cull-gmail.toml");
195
196 let res = toml::to_string(self);
197 log::trace!("toml conversion result: {res:#?}");
198
199 if let Ok(output) = res {
200 fs::write(&path, output)?;
201 log::trace!("Config saved to {}", path.display());
202 }
203
204 Ok(())
205 }
206
207 pub fn load() -> Result<Config> {
209 let home_dir = env::home_dir().unwrap();
210 let path = PathBuf::new()
211 .join(home_dir)
212 .join(".cull-gmail/cull-gmail.toml");
213 log::trace!("Loading config from {}", path.display());
214
215 let input = read_to_string(path)?;
216 let config = toml::from_str::<Config>(&input)?;
217 Ok(config)
218 }
219
220 pub fn credential_file(&self) -> &str {
222 if let Some(file) = &self.credentials {
223 file
224 } else {
225 ""
226 }
227 }
228
229 pub fn list_rules(&self) -> Result<()> {
231 for rule in self.rules.values() {
232 println!("{rule}");
233 }
234 Ok(())
235 }
236}