i3status_rs/blocks/
notmuch.rs1use super::prelude::*;
47
48#[derive(Deserialize, Debug, SmartDefault)]
49#[serde(deny_unknown_fields, default)]
50pub struct Config {
51 pub format: FormatConfig,
52 #[default(10.into())]
53 pub interval: Seconds,
54 #[default("~/.mail".into())]
55 pub maildir: ShellString,
56 pub query: String,
57 #[default(u32::MAX)]
58 pub threshold_warning: u32,
59 #[default(u32::MAX)]
60 pub threshold_critical: u32,
61 #[default(u32::MAX)]
62 pub threshold_info: u32,
63 #[default(u32::MAX)]
64 pub threshold_good: u32,
65}
66
67pub(crate) fn prepare(config: &Config) -> Result<Arc<BlockPlan>> {
68 BlockPlan::new(vec![
69 OutputPlan::new("main", config.format.with_default(" $icon $count ")?)
70 .icon("icon", IconChoices::one(icons::MAIL)),
71 ])
72}
73
74pub(crate) async fn run(config: &Config, api: &CommonApi, plan: &Arc<BlockPlan>) -> Result<()> {
75 let output_main = plan.output("main")?;
76
77 let db = config.maildir.expand()?;
78 let mut timer = config.interval.timer();
79
80 loop {
81 let count = run_query(&db, &config.query).error("Failed to get count")?;
83
84 let mut widget = output_main.new_widget();
85
86 widget.set_values(map! {
87 "icon" => Value::icon(icons::MAIL),
88 "count" => Value::number(count)
89 });
90
91 widget.state = if count >= config.threshold_critical {
92 State::Critical
93 } else if count >= config.threshold_warning {
94 State::Warning
95 } else if count >= config.threshold_good {
96 State::Good
97 } else if count >= config.threshold_info {
98 State::Info
99 } else {
100 State::Idle
101 };
102
103 api.set_widget(widget)?;
104
105 tokio::select! {
106 _ = timer.tick() => (),
107 _ = api.wait_for_update_request() => (),
108 }
109 }
110}
111
112fn run_query(db_path: &str, query_string: &str) -> std::result::Result<u32, notmuch::Error> {
113 let db = notmuch::Database::open_with_config(
114 Some(db_path),
115 notmuch::DatabaseMode::ReadOnly,
116 None::<&str>,
117 None,
118 )?;
119 let query = db.create_query(query_string)?;
120 query.count_messages()
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn plan_declares_main_output_with_mail_icon() {
129 let plan = prepare(&Config::default()).unwrap();
130 let declared: Vec<_> = plan.outputs().map(|o| o.id()).collect();
131 assert_eq!(declared, ["main"]);
132 let output = plan.output("main").unwrap();
133 assert_eq!(output.single_icon("icon").unwrap(), "mail");
134 }
135
136 #[test]
137 fn custom_format_is_respected() {
138 let config = Config {
139 format: " $count ".parse().unwrap(),
140 ..Config::default()
141 };
142 let plan = prepare(&config).unwrap();
143 let output = plan.output("main").unwrap();
144 assert!(output.format().contains_key("count"));
145 assert!(!output.format().contains_key("icon"));
146 }
147}