diff --git a/Cargo.lock b/Cargo.lock index 6e8a718..ab17b8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1246,7 +1246,7 @@ dependencies = [ [[package]] name = "email-lib" version = "0.17.1" -source = "git+https://git.sr.ht/~soywod/pimalaya#8fb9b5ecc417a34a824a6decc3c0cda01af98ffe" +source = "git+https://git.sr.ht/~soywod/pimalaya#29d6c73d444e78d667d3d4d70d3ec2ffc032be6d" dependencies = [ "advisory-lock", "anyhow", diff --git a/config.sample.toml b/config.sample.toml index e2ab591..c0bce7f 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -41,16 +41,19 @@ envelope.list.datetime-local-tz = true # Override the backend used for listing envelopes. envelope.list.backend = "imap" +# Send notification on receiving new envelopes +envelope.watch.received.notify.summary = "📬 New message from {sender}" + +# Available placeholders: id, subject, sender, sender.name, +# sender.address, recipient, recipient.name, recipient.address. +envelope.watch.received.notify.body = "{subject}" + +# Shell commands can also be executed when envelopes change +# envelope.watch.any.cmd = "mbsync -a" + # Override the backend used for sending messages. message.send.backend = "smtp" -# Send notification when receiving new messages -message.watch.received.notify.summary = "📬 New message from {sender}" -message.watch.received.notify.body = "{subject}" - -# Shell commands can also be executed -# message.watch.received.cmd = "mbsync -a" - # IMAP config imap.host = "localhost" imap.port = 3143 diff --git a/src/account/config.rs b/src/account/config.rs index 294b7e1..33a665a 100644 --- a/src/account/config.rs +++ b/src/account/config.rs @@ -192,9 +192,9 @@ impl TomlAccountConfig { } pub fn get_watch_message_kind(&self) -> Option<&BackendKind> { - self.message + self.envelope .as_ref() - .and_then(|msg| msg.watch.as_ref()) + .and_then(|envelope| envelope.watch.as_ref()) .and_then(|watch| watch.backend.as_ref()) .or_else(|| self.backend.as_ref()) } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 9e80afd..85ca851 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -11,10 +11,10 @@ use email::imap::{ImapSessionBuilder, ImapSessionSync}; use email::smtp::{SmtpClientBuilder, SmtpClientSync}; use email::{ account::config::AccountConfig, - email::watch::{imap::WatchImapEmails, maildir::WatchMaildirEmails}, envelope::{ get::{imap::GetEnvelopeImap, maildir::GetEnvelopeMaildir}, list::{imap::ListEnvelopesImap, maildir::ListEnvelopesMaildir}, + watch::{imap::WatchImapEnvelopes, maildir::WatchMaildirEnvelopes}, Id, SingleId, }, flag::{ @@ -358,26 +358,27 @@ impl BackendBuilder { match toml_account_config.backend { Some(BackendKind::Maildir) => { - backend_builder = backend_builder.with_watch_emails(|ctx| { - ctx.maildir.as_ref().and_then(WatchMaildirEmails::new) + backend_builder = backend_builder.with_watch_envelopes(|ctx| { + ctx.maildir.as_ref().and_then(WatchMaildirEnvelopes::new) }); } Some(BackendKind::MaildirForSync) => { - backend_builder = backend_builder.with_watch_emails(|ctx| { + backend_builder = backend_builder.with_watch_envelopes(|ctx| { ctx.maildir_for_sync .as_ref() - .and_then(WatchMaildirEmails::new) + .and_then(WatchMaildirEnvelopes::new) }); } #[cfg(feature = "imap")] Some(BackendKind::Imap) => { - backend_builder = backend_builder - .with_watch_emails(|ctx| ctx.imap.as_ref().and_then(WatchImapEmails::new)); + backend_builder = backend_builder.with_watch_envelopes(|ctx| { + ctx.imap.as_ref().and_then(WatchImapEnvelopes::new) + }); } #[cfg(feature = "notmuch")] Some(BackendKind::Notmuch) => { - backend_builder = backend_builder.with_watch_emails(|ctx| { - ctx.notmuch.as_ref().and_then(WatchNotmuchEmails::new) + backend_builder = backend_builder.with_watch_envelopes(|ctx| { + ctx.notmuch.as_ref().and_then(WatchNotmuchEnvelopes::new) }); } _ => (), diff --git a/src/config/mod.rs b/src/config/mod.rs index 619a531..8663e75 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -206,12 +206,12 @@ impl TomlConfig { }), envelope: config.envelope.map(|c| EnvelopeConfig { list: c.list.map(|c| c.remote), + watch: c.watch.map(|c| c.remote), }), message: config.message.map(|c| MessageConfig { read: c.read.map(|c| c.remote), write: c.write.map(|c| c.remote), send: c.send.map(|c| c.remote), - watch: c.watch.map(|c| c.remote), }), sync: config.sync, #[cfg(feature = "pgp")] diff --git a/src/email/envelope/command/list.rs b/src/email/envelope/command/list.rs index a1a0721..fc47a49 100644 --- a/src/email/envelope/command/list.rs +++ b/src/email/envelope/command/list.rs @@ -17,7 +17,7 @@ use crate::{ /// This command allows you to list all envelopes included in the /// given folder. #[derive(Debug, Parser)] -pub struct EnvelopeListCommand { +pub struct ListEnvelopesCommand { #[command(flatten)] pub folder: FolderNameOptionalArg, @@ -44,7 +44,7 @@ pub struct EnvelopeListCommand { pub account: AccountNameFlag, } -impl EnvelopeListCommand { +impl ListEnvelopesCommand { pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { info!("executing envelope list command"); diff --git a/src/email/envelope/command/mod.rs b/src/email/envelope/command/mod.rs index 1ebf707..a97a2d2 100644 --- a/src/email/envelope/command/mod.rs +++ b/src/email/envelope/command/mod.rs @@ -1,11 +1,12 @@ pub mod list; +pub mod watch; use anyhow::Result; use clap::Subcommand; use crate::{config::TomlConfig, printer::Printer}; -use self::list::EnvelopeListCommand; +use self::{list::ListEnvelopesCommand, watch::WatchEnvelopesCommand}; /// Manage envelopes. /// @@ -16,13 +17,17 @@ use self::list::EnvelopeListCommand; #[derive(Debug, Subcommand)] pub enum EnvelopeSubcommand { #[command(alias = "lst")] - List(EnvelopeListCommand), + List(ListEnvelopesCommand), + + #[command()] + Watch(WatchEnvelopesCommand), } impl EnvelopeSubcommand { pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { match self { Self::List(cmd) => cmd.execute(printer, config).await, + Self::Watch(cmd) => cmd.execute(printer, config).await, } } } diff --git a/src/folder/command/watch.rs b/src/email/envelope/command/watch.rs similarity index 60% rename from src/folder/command/watch.rs rename to src/email/envelope/command/watch.rs index dbeaf9b..a21d3a8 100644 --- a/src/folder/command/watch.rs +++ b/src/email/envelope/command/watch.rs @@ -4,17 +4,17 @@ use log::info; use crate::{ account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag, - config::TomlConfig, folder::arg::name::FolderNameArg, printer::Printer, + config::TomlConfig, folder::arg::name::FolderNameOptionalArg, printer::Printer, }; -/// Watch a folder for changes. +/// Watch envelopes for changes. /// -/// This command allows you to watch a new folder using the given -/// name. +/// This command allows you to watch a folder and execute hooks when +/// changes occur on envelopes. #[derive(Debug, Parser)] -pub struct FolderWatchCommand { +pub struct WatchEnvelopesCommand { #[command(flatten)] - pub folder: FolderNameArg, + pub folder: FolderNameOptionalArg, #[command(flatten)] pub cache: CacheDisableFlag, @@ -23,9 +23,9 @@ pub struct FolderWatchCommand { pub account: AccountNameFlag, } -impl FolderWatchCommand { +impl WatchEnvelopesCommand { pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> { - info!("executing folder watch command"); + info!("executing envelopes watch command"); let folder = &self.folder.name; @@ -35,8 +35,10 @@ impl FolderWatchCommand { .into_account_configs(some_account_name, self.cache.disable)?; let backend = Backend::new(toml_account_config, account_config.clone(), false).await?; - printer.print_log(format!("Start watching folder {folder} for changes…"))?; + printer.print_log(format!( + "Start watching folder {folder} for envelopes changes…" + ))?; - backend.watch_emails(&folder).await + backend.watch_envelopes(&folder).await } } diff --git a/src/email/envelope/config.rs b/src/email/envelope/config.rs index 20855f8..c90d840 100644 --- a/src/email/envelope/config.rs +++ b/src/email/envelope/config.rs @@ -5,8 +5,9 @@ use crate::backend::BackendKind; #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct EnvelopeConfig { - pub list: Option, - pub get: Option, + pub list: Option, + pub watch: Option, + pub get: Option, } impl EnvelopeConfig { @@ -21,19 +22,23 @@ impl EnvelopeConfig { kinds.extend(get.get_used_backends()); } + if let Some(watch) = &self.watch { + kinds.extend(watch.get_used_backends()); + } + kinds } } #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct EnvelopeListConfig { +pub struct ListEnvelopesConfig { pub backend: Option, #[serde(flatten)] pub remote: email::envelope::list::config::EnvelopeListConfig, } -impl EnvelopeListConfig { +impl ListEnvelopesConfig { pub fn get_used_backends(&self) -> HashSet<&BackendKind> { let mut kinds = HashSet::default(); @@ -46,11 +51,31 @@ impl EnvelopeListConfig { } #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct EnvelopeGetConfig { +pub struct WatchEnvelopesConfig { pub backend: Option, + + #[serde(flatten)] + pub remote: email::envelope::watch::config::WatchEnvelopeConfig, } -impl EnvelopeGetConfig { +impl WatchEnvelopesConfig { + pub fn get_used_backends(&self) -> HashSet<&BackendKind> { + let mut kinds = HashSet::default(); + + if let Some(kind) = &self.backend { + kinds.insert(kind); + } + + kinds + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] +pub struct GetEnvelopeConfig { + pub backend: Option, +} + +impl GetEnvelopeConfig { pub fn get_used_backends(&self) -> HashSet<&BackendKind> { let mut kinds = HashSet::default(); diff --git a/src/email/message/config.rs b/src/email/message/config.rs index f8a1338..4d95ef4 100644 --- a/src/email/message/config.rs +++ b/src/email/message/config.rs @@ -12,8 +12,6 @@ pub struct MessageConfig { pub copy: Option, #[serde(rename = "move")] pub move_: Option, - - pub watch: Option, } impl MessageConfig { @@ -44,10 +42,6 @@ impl MessageConfig { kinds.extend(move_.get_used_backends()); } - if let Some(watch) = &self.watch { - kinds.extend(watch.get_used_backends()); - } - kinds } } @@ -162,23 +156,3 @@ impl MessageMoveConfig { kinds } } - -#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct WatchMessageConfig { - pub backend: Option, - - #[serde(flatten)] - pub remote: email::message::watch::config::WatchMessageConfig, -} - -impl WatchMessageConfig { - pub fn get_used_backends(&self) -> HashSet<&BackendKind> { - let mut kinds = HashSet::default(); - - if let Some(kind) = &self.backend { - kinds.insert(kind); - } - - kinds - } -} diff --git a/src/folder/command/mod.rs b/src/folder/command/mod.rs index ede0d31..5de0450 100644 --- a/src/folder/command/mod.rs +++ b/src/folder/command/mod.rs @@ -3,7 +3,6 @@ mod delete; mod expunge; mod list; mod purge; -mod watch; use anyhow::Result; use clap::Subcommand; @@ -12,7 +11,7 @@ use crate::{config::TomlConfig, printer::Printer}; use self::{ create::FolderCreateCommand, delete::FolderDeleteCommand, expunge::FolderExpungeCommand, - list::FolderListCommand, purge::FolderPurgeCommand, watch::FolderWatchCommand, + list::FolderListCommand, purge::FolderPurgeCommand, }; /// Manage folders. @@ -27,9 +26,6 @@ pub enum FolderSubcommand { #[command(alias = "lst")] List(FolderListCommand), - #[command()] - Watch(FolderWatchCommand), - #[command()] Expunge(FolderExpungeCommand), @@ -45,7 +41,6 @@ impl FolderSubcommand { match self { Self::Create(cmd) => cmd.execute(printer, config).await, Self::List(cmd) => cmd.execute(printer, config).await, - Self::Watch(cmd) => cmd.execute(printer, config).await, Self::Expunge(cmd) => cmd.execute(printer, config).await, Self::Purge(cmd) => cmd.execute(printer, config).await, Self::Delete(cmd) => cmd.execute(printer, config).await,