Skip to main content

i3status_rs/blocks/
notmuch.rs

1//! Count of notmuch messages
2//!
3//! This block queries a notmuch database and displays the count of messages.
4//!
5//! The simplest configuration will return the total count of messages in the notmuch database stored at $HOME/.mail
6//!
7//! Note that you need to enable `notmuch` feature to use this block:
8//! ```sh
9//! cargo build --release --features notmuch
10//! ```
11//!
12//! # Configuration
13//!
14//! Key | Values | Default
15//! ----|--------|--------
16//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $count "`
17//! `maildir` | Path to the directory containing the notmuch database. Supports path expansions e.g. `~`. | `~/.mail`
18//! `query` | Query to run on the database. | `""`
19//! `threshold_critical` | Mail count that triggers `critical` state. | `99999`
20//! `threshold_warning` | Mail count that triggers `warning` state. | `99999`
21//! `threshold_good` | Mail count that triggers `good` state. | `99999`
22//! `threshold_info` | Mail count that triggers `info` state. | `99999`
23//! `interval` | Update interval in seconds. | `10`
24//!
25//! Placeholder | Value                                      | Type   | Unit
26//! ------------|--------------------------------------------|--------|-----
27//! `icon`      | A static icon                              | Icon   | -
28//! `count`     | Number of messages for the query           | Number | -
29//!
30//! # Example
31//!
32//! ```toml
33//! [[block]]
34//! block = "notmuch"
35//! query = "tag:alert and not tag:trash"
36//! threshold_warning = 1
37//! threshold_critical = 10
38//! [[block.click]]
39//! button = "left"
40//! update = true
41//! ```
42//!
43//! # Icons Used
44//! - `mail` (`$icon`)
45
46use 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        // TODO: spawn_blocking?
82        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}