From fcce1f353669706872932c390c84a249358e16e8 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 4 Oct 2025 15:09:08 -0700 Subject: [PATCH 1/2] notmuch: update the count whenever the database is updated Specifically, this re-runs the count command whenever the databased lock is released. This event is triggered whenever the database is closed after it was opened for writing, but NOT when it was opened for reading. --- NEWS.md | 6 ++++++ cspell.yaml | 1 + src/blocks/notmuch.rs | 24 ++++++++++++++++++++---- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index d768cbdc56..f726c3d7cd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# i3status-rust 0.35.0 [unreleased] + +### Breaking Changes + +- The `interval` option has been removed from the `notmuch` block in favor of watching the notmuch database for changes. + # i3status-rust 0.34.0 ### New Blocks and Features diff --git a/cspell.yaml b/cspell.yaml index 80a7b3bb26..69dfbc719b 100644 --- a/cspell.yaml +++ b/cspell.yaml @@ -13,6 +13,7 @@ ignoreRegExpList: # Ignore unicode characters - /(\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8})/g words: + - xapian - aarch - alacritty - alphanum diff --git a/src/blocks/notmuch.rs b/src/blocks/notmuch.rs index f7ce2ddc20..cb4f793e54 100644 --- a/src/blocks/notmuch.rs +++ b/src/blocks/notmuch.rs @@ -43,14 +43,16 @@ //! # Icons Used //! - `mail` +use std::path::Path; + +use inotify::{Inotify, WatchMask}; + use super::prelude::*; #[derive(Deserialize, Debug, SmartDefault)] #[serde(deny_unknown_fields, default)] pub struct Config { pub format: FormatConfig, - #[default(10.into())] - pub interval: Seconds, #[default("~/.mail".into())] pub maildir: ShellString, pub query: String, @@ -68,7 +70,21 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { let format = config.format.with_default(" $icon $count ")?; let db = config.maildir.expand()?; - let mut timer = config.interval.timer(); + let notify = Inotify::init().error("Failed to start inotify")?; + + for lockpath in ["xapian/flintlock", ".notmuch/xapian/flintlock"] { + let fname = Path::new(&*db).join(lockpath); + if fname.exists() { + notify + .watches() + .add(fname, WatchMask::CLOSE_WRITE) + .error("failed to add inotify watch")?; + } + } + + let mut updates = notify + .into_event_stream([0; 1024]) + .error("Failed to create event stream")?; loop { // TODO: spawn_blocking? @@ -96,7 +112,7 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { api.set_widget(widget)?; tokio::select! { - _ = timer.tick() => (), + _ = updates.next_debounced() => (), _ = api.wait_for_update_request() => (), } } From 3707630cee20ddec91fd4559ef69310942352439 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Sat, 4 Oct 2025 15:31:15 -0700 Subject: [PATCH 2/2] notmuch: auto-locate the notmuch database 1. The notmuch database may not be the same as the maildir. 2. Notmuch has some pretty thorough logic for automatically locating the database (documented in notmuch(3)). This change: 1. Renames the `maildir` option to `database` to accurately reflect its purpose. 2. Makes the `database` option optional. 3. Adds a `profile` option so the user can specify the notmuch profile they want to use without hard-coding the path to the database. --- NEWS.md | 5 ++++ src/blocks/notmuch.rs | 65 ++++++++++++++++++++++++++++++++----------- 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/NEWS.md b/NEWS.md index f726c3d7cd..e4a61522dd 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,13 @@ # i3status-rust 0.35.0 [unreleased] +### Bug Fixes and Improvements + +- The `notmuch` block can now automatically locate the notmuch database (the same way the `notmuch` command does) without being explicitly configured. Furthermore, the block has gained a `profile` option that can be used to specify a notmuch profile other than "default". + ### Breaking Changes - The `interval` option has been removed from the `notmuch` block in favor of watching the notmuch database for changes. +- The `maildir` option in the `notmuch` block has been renamed to `database` to better reflect its purpose, and has been made optional. # i3status-rust 0.34.0 diff --git a/src/blocks/notmuch.rs b/src/blocks/notmuch.rs index cb4f793e54..1c3077979b 100644 --- a/src/blocks/notmuch.rs +++ b/src/blocks/notmuch.rs @@ -43,8 +43,6 @@ //! # Icons Used //! - `mail` -use std::path::Path; - use inotify::{Inotify, WatchMask}; use super::prelude::*; @@ -53,8 +51,15 @@ use super::prelude::*; #[serde(deny_unknown_fields, default)] pub struct Config { pub format: FormatConfig, - #[default("~/.mail".into())] - pub maildir: ShellString, + /// Path to the notmuch database. + /// + /// Defaults to the database used by the notmuch CLI tool. + pub database: Option, + /// Database profile. Cannot be specified at the same time as `database`. + /// + /// Defaults to the profile used by the notmuch CLI tool. + pub profile: Option, + /// The notmuch query to count. pub query: String, #[default(u32::MAX)] pub threshold_warning: u32, @@ -69,17 +74,27 @@ pub struct Config { pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { let format = config.format.with_default(" $icon $count ")?; - let db = config.maildir.expand()?; + if config.database.is_some() && config.profile.is_some() { + return Err(Error::new( + "cannot specify both a notmuch database and a notmuch profile", + )); + } + + let profile = config.profile.as_deref(); + + let db_path = config.database.as_ref().map(|p| p.expand()).transpose()?; + let db_path: Option<&str> = db_path.as_deref(); let notify = Inotify::init().error("Failed to start inotify")?; - for lockpath in ["xapian/flintlock", ".notmuch/xapian/flintlock"] { - let fname = Path::new(&*db).join(lockpath); - if fname.exists() { - notify - .watches() - .add(fname, WatchMask::CLOSE_WRITE) - .error("failed to add inotify watch")?; - } + { + let lock_path = open_database(db_path, profile) + .error("failed to open the notmuch database")? + .path() + .join("xapian/flintlock"); + notify + .watches() + .add(lock_path, WatchMask::CLOSE_WRITE) + .error("failed to watch the notmuch database lock")?; } let mut updates = notify @@ -88,7 +103,7 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { loop { // TODO: spawn_blocking? - let count = run_query(&db, &config.query).error("Failed to get count")?; + let count = run_query(db_path, profile, &config.query).error("Failed to get count")?; let mut widget = Widget::new().with_format(format.clone()); @@ -118,12 +133,28 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> { } } -fn run_query(db_path: &str, query_string: &str) -> std::result::Result { +fn open_database( + db_path: Option<&str>, + profile: Option<&str>, +) -> std::result::Result { + notmuch::Database::open_with_config( + db_path, + notmuch::DatabaseMode::ReadOnly, + None::<&str>, + profile, + ) +} + +fn run_query( + db_path: Option<&str>, + profile: Option<&str>, + query_string: &str, +) -> std::result::Result { let db = notmuch::Database::open_with_config( - Some(db_path), + db_path, notmuch::DatabaseMode::ReadOnly, None::<&str>, - None, + profile, )?; let query = db.create_query(query_string)?; query.count_messages()