i3status_rs/blocks/
maildir.rs1use super::prelude::*;
40use maildir::Maildir;
41use std::path::PathBuf;
42
43#[derive(Deserialize, Debug, SmartDefault)]
44#[serde(deny_unknown_fields, default)]
45pub struct Config {
46 pub format: FormatConfig,
47 #[default(5.into())]
48 pub interval: Seconds,
49 pub inboxes: Vec<String>,
50 #[default(1)]
51 pub threshold_warning: usize,
52 #[default(10)]
53 pub threshold_critical: usize,
54 #[default(MailType::New)]
55 pub display_type: MailType,
56}
57
58fn expand_inbox(inbox: &str) -> Result<impl Iterator<Item = PathBuf>> {
59 let expanded = shellexpand::full(inbox).error("Failed to expand inbox")?;
60 let paths = glob::glob(&expanded).error("Glob expansion failed")?;
61 Ok(paths.filter_map(|p| p.ok()))
62}
63
64pub(crate) fn prepare(config: &Config) -> Result<Arc<BlockPlan>> {
65 BlockPlan::new(vec![
66 OutputPlan::new("main", config.format.with_default(" $icon $status ")?)
67 .icon("icon", IconChoices::one(icons::MAIL)),
68 ])
69}
70
71pub(crate) async fn run(config: &Config, api: &CommonApi, plan: &Arc<BlockPlan>) -> Result<()> {
72 let output_main = plan.output("main")?;
73
74 let mut inboxes = Vec::with_capacity(config.inboxes.len());
75 for inbox in &config.inboxes {
76 inboxes.extend(expand_inbox(inbox)?.map(Maildir::from));
77 }
78
79 loop {
80 let mut newmails = 0;
81 for inbox in &inboxes {
82 newmails += match config.display_type {
84 MailType::New => inbox.count_new(),
85 MailType::Cur => inbox.count_cur(),
86 MailType::All => inbox.count_new() + inbox.count_cur(),
87 };
88 }
89
90 let mut widget = output_main.new_widget();
91 widget.state = if newmails >= config.threshold_critical {
92 State::Critical
93 } else if newmails >= config.threshold_warning {
94 State::Warning
95 } else {
96 State::Idle
97 };
98 widget.set_values(map!(
99 "icon" => Value::icon(icons::MAIL),
100 "status" => Value::number(newmails)
101 ));
102 api.set_widget(widget)?;
103
104 select! {
105 _ = sleep(config.interval.0) => (),
106 _ = api.wait_for_update_request() => (),
107 }
108 }
109}
110
111#[derive(Clone, Debug, Deserialize)]
112#[serde(rename_all = "lowercase")]
113pub enum MailType {
114 New,
115 Cur,
116 All,
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn plan_declares_main_output_with_mail_icon() {
125 let plan = prepare(&Config::default()).unwrap();
126 let declared: Vec<_> = plan.outputs().map(|o| o.id()).collect();
127 assert_eq!(declared, ["main"]);
128 let output = plan.output("main").unwrap();
129 assert_eq!(output.single_icon("icon").unwrap(), "mail");
130 }
131
132 #[test]
133 fn custom_format_is_respected() {
134 let config = Config {
135 format: " $status ".parse().unwrap(),
136 ..Config::default()
137 };
138 let plan = prepare(&config).unwrap();
139 let output = plan.output("main").unwrap();
140 assert!(output.format().contains_key("status"));
141 assert!(!output.format().contains_key("icon"));
142 }
143}