init folder watch command

This commit is contained in:
Clément DOUIN 2023-12-14 12:13:08 +01:00
parent a68d297366
commit 7fccdd822a
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
9 changed files with 329 additions and 7 deletions

215
Cargo.lock generated
View file

@ -188,6 +188,32 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "async-executor"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
dependencies = [
"async-lock 3.2.0",
"async-task",
"concurrent-queue",
"fastrand 2.0.1",
"futures-lite 2.1.0",
"slab",
]
[[package]]
name = "async-fs"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06"
dependencies = [
"async-lock 2.8.0",
"autocfg",
"blocking",
"futures-lite 1.13.0",
]
[[package]] [[package]]
name = "async-io" name = "async-io"
version = "1.13.0" version = "1.13.0"
@ -390,6 +416,12 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "block"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@ -834,9 +866,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.16" version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -1096,6 +1128,16 @@ dependencies = [
"dirs-sys 0.4.1", "dirs-sys 0.4.1",
] ]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]] [[package]]
name = "dirs-sys" name = "dirs-sys"
version = "0.3.7" version = "0.3.7"
@ -1119,6 +1161,17 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.4" version = "1.0.4"
@ -1193,8 +1246,7 @@ dependencies = [
[[package]] [[package]]
name = "email-lib" name = "email-lib"
version = "0.17.1" version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://git.sr.ht/~soywod/pimalaya#8fb9b5ecc417a34a824a6decc3c0cda01af98ffe"
checksum = "743371f76482a94403ce0ab49da129065fc84cbcf9ed126524882c6ed5389efc"
dependencies = [ dependencies = [
"advisory-lock", "advisory-lock",
"anyhow", "anyhow",
@ -1213,6 +1265,8 @@ dependencies = [
"maildirpp", "maildirpp",
"md5", "md5",
"mml-lib", "mml-lib",
"notify",
"notify-rust",
"notmuch", "notmuch",
"oauth-lib", "oauth-lib",
"once_cell", "once_cell",
@ -1546,7 +1600,10 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143" checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143"
dependencies = [ dependencies = [
"fastrand 2.0.1",
"futures-core", "futures-core",
"futures-io",
"parking",
"pin-project-lite", "pin-project-lite",
] ]
@ -2090,7 +2147,7 @@ dependencies = [
"dirs 4.0.0", "dirs 4.0.0",
"gix-path", "gix-path",
"libc", "libc",
"windows", "windows 0.43.0",
] ]
[[package]] [[package]]
@ -2629,6 +2686,26 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "inotify"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags 1.3.2",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "inout" name = "inout"
version = "0.1.3" version = "0.1.3"
@ -2748,6 +2825,26 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "kqueue"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
dependencies = [
"bitflags 1.3.2",
"libc",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -2855,6 +2952,19 @@ dependencies = [
"linked-hash-map", "linked-hash-map",
] ]
[[package]]
name = "mac-notification-sys"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51fca4d74ff9dbaac16a01b924bc3693fa2bba0862c2c633abc73f9a8ea21f64"
dependencies = [
"cc",
"dirs-next",
"objc-foundation",
"objc_id",
"time",
]
[[package]] [[package]]
name = "mail-auth" name = "mail-auth"
version = "0.3.6" version = "0.3.6"
@ -2925,6 +3035,15 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "match_cfg" name = "match_cfg"
version = "0.1.0" version = "0.1.0"
@ -3008,6 +3127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [ dependencies = [
"libc", "libc",
"log",
"wasi", "wasi",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -3063,6 +3183,36 @@ dependencies = [
"minimal-lexical", "minimal-lexical",
] ]
[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.4.1",
"filetime",
"inotify",
"kqueue",
"libc",
"log",
"mio",
"walkdir",
"windows-sys 0.48.0",
]
[[package]]
name = "notify-rust"
version = "4.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "827c5edfa80235ded4ab3fe8e9dc619b4f866ef16fe9b1c6b8a7f8692c0f2226"
dependencies = [
"log",
"mac-notification-sys",
"serde",
"tauri-winrt-notification",
"zbus",
]
[[package]] [[package]]
name = "notmuch" name = "notmuch"
version = "0.8.0" version = "0.8.0"
@ -3238,6 +3388,35 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "objc"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
dependencies = [
"malloc_buf",
]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc_id"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b"
dependencies = [
"objc",
]
[[package]] [[package]]
name = "object" name = "object"
version = "0.32.1" version = "0.32.1"
@ -4545,6 +4724,16 @@ version = "0.12.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
[[package]]
name = "tauri-winrt-notification"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "006851c9ccefa3c38a7646b8cec804bb429def3da10497bfa977179869c3e8e2"
dependencies = [
"quick-xml",
"windows 0.51.1",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.8.1" version = "3.8.1"
@ -5152,6 +5341,16 @@ dependencies = [
"windows_x86_64_msvc 0.42.2", "windows_x86_64_msvc 0.42.2",
] ]
[[package]]
name = "windows"
version = "0.51.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9"
dependencies = [
"windows-core",
"windows-targets 0.48.5",
]
[[package]] [[package]]
name = "windows-core" name = "windows-core"
version = "0.51.1" version = "0.51.1"
@ -5422,9 +5621,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948" checksum = "31de390a2d872e4cd04edd71b425e29853f786dc99317ed72d73d6fcf5ebb948"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-executor",
"async-fs",
"async-io 1.13.0",
"async-lock 2.8.0",
"async-process", "async-process",
"async-recursion", "async-recursion",
"async-task",
"async-trait", "async-trait",
"blocking",
"byteorder", "byteorder",
"derivative", "derivative",
"enumflags2", "enumflags2",

View file

@ -50,7 +50,7 @@ clap_mangen = "0.2"
console = "0.15.2" console = "0.15.2"
dialoguer = "0.10.2" dialoguer = "0.10.2"
dirs = "4.0" dirs = "4.0"
email-lib = { version = "=0.17.1", default-features = false } email-lib = { git = "https://git.sr.ht/~soywod/pimalaya", default-features = false }
email_address = "0.2.4" email_address = "0.2.4"
env_logger = "0.8" env_logger = "0.8"
erased-serde = "0.3" erased-serde = "0.3"

View file

@ -44,6 +44,13 @@ envelope.list.backend = "imap"
# Override the backend used for sending messages. # Override the backend used for sending messages.
message.send.backend = "smtp" 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 config
imap.host = "localhost" imap.host = "localhost"
imap.port = 3143 imap.port = 3143

View file

@ -191,6 +191,14 @@ impl TomlAccountConfig {
.or_else(|| self.backend.as_ref()) .or_else(|| self.backend.as_ref())
} }
pub fn get_watch_message_kind(&self) -> Option<&BackendKind> {
self.message
.as_ref()
.and_then(|msg| msg.watch.as_ref())
.and_then(|watch| watch.backend.as_ref())
.or_else(|| self.backend.as_ref())
}
pub fn get_used_backends(&self) -> HashSet<&BackendKind> { pub fn get_used_backends(&self) -> HashSet<&BackendKind> {
let mut used_backends = HashSet::default(); let mut used_backends = HashSet::default();

View file

@ -11,6 +11,7 @@ use email::imap::{ImapSessionBuilder, ImapSessionSync};
use email::smtp::{SmtpClientBuilder, SmtpClientSync}; use email::smtp::{SmtpClientBuilder, SmtpClientSync};
use email::{ use email::{
account::config::AccountConfig, account::config::AccountConfig,
email::watch::{imap::WatchImapEmails, maildir::WatchMaildirEmails},
envelope::{ envelope::{
get::{imap::GetEnvelopeImap, maildir::GetEnvelopeMaildir}, get::{imap::GetEnvelopeImap, maildir::GetEnvelopeMaildir},
list::{imap::ListEnvelopesImap, maildir::ListEnvelopesMaildir}, list::{imap::ListEnvelopesImap, maildir::ListEnvelopesMaildir},
@ -355,6 +356,33 @@ 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)
});
}
Some(BackendKind::MaildirForSync) => {
backend_builder = backend_builder.with_watch_emails(|ctx| {
ctx.maildir_for_sync
.as_ref()
.and_then(WatchMaildirEmails::new)
});
}
#[cfg(feature = "imap")]
Some(BackendKind::Imap) => {
backend_builder = backend_builder
.with_watch_emails(|ctx| ctx.imap.as_ref().and_then(WatchImapEmails::new));
}
#[cfg(feature = "notmuch")]
Some(BackendKind::Notmuch) => {
backend_builder = backend_builder.with_watch_emails(|ctx| {
ctx.notmuch.as_ref().and_then(WatchNotmuchEmails::new)
});
}
_ => (),
}
match toml_account_config.get_envelope_kind() { match toml_account_config.get_envelope_kind() {
Some(BackendKind::Maildir) => { Some(BackendKind::Maildir) => {
backend_builder = backend_builder.with_get_envelope(|ctx| { backend_builder = backend_builder.with_get_envelope(|ctx| {

View file

@ -211,6 +211,7 @@ impl TomlConfig {
read: c.read.map(|c| c.remote), read: c.read.map(|c| c.remote),
write: c.write.map(|c| c.remote), write: c.write.map(|c| c.remote),
send: c.send.map(|c| c.remote), send: c.send.map(|c| c.remote),
watch: c.watch.map(|c| c.remote),
}), }),
sync: config.sync, sync: config.sync,
#[cfg(feature = "pgp")] #[cfg(feature = "pgp")]

View file

@ -12,6 +12,8 @@ pub struct MessageConfig {
pub copy: Option<MessageCopyConfig>, pub copy: Option<MessageCopyConfig>,
#[serde(rename = "move")] #[serde(rename = "move")]
pub move_: Option<MessageMoveConfig>, pub move_: Option<MessageMoveConfig>,
pub watch: Option<WatchMessageConfig>,
} }
impl MessageConfig { impl MessageConfig {
@ -42,6 +44,10 @@ impl MessageConfig {
kinds.extend(move_.get_used_backends()); kinds.extend(move_.get_used_backends());
} }
if let Some(watch) = &self.watch {
kinds.extend(watch.get_used_backends());
}
kinds kinds
} }
} }
@ -156,3 +162,23 @@ impl MessageMoveConfig {
kinds kinds
} }
} }
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
pub struct WatchMessageConfig {
pub backend: Option<BackendKind>,
#[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
}
}

View file

@ -3,6 +3,7 @@ mod delete;
mod expunge; mod expunge;
mod list; mod list;
mod purge; mod purge;
mod watch;
use anyhow::Result; use anyhow::Result;
use clap::Subcommand; use clap::Subcommand;
@ -11,7 +12,7 @@ use crate::{config::TomlConfig, printer::Printer};
use self::{ use self::{
create::FolderCreateCommand, delete::FolderDeleteCommand, expunge::FolderExpungeCommand, create::FolderCreateCommand, delete::FolderDeleteCommand, expunge::FolderExpungeCommand,
list::FolderListCommand, purge::FolderPurgeCommand, list::FolderListCommand, purge::FolderPurgeCommand, watch::FolderWatchCommand,
}; };
/// Manage folders. /// Manage folders.
@ -26,6 +27,9 @@ pub enum FolderSubcommand {
#[command(alias = "lst")] #[command(alias = "lst")]
List(FolderListCommand), List(FolderListCommand),
#[command()]
Watch(FolderWatchCommand),
#[command()] #[command()]
Expunge(FolderExpungeCommand), Expunge(FolderExpungeCommand),
@ -41,6 +45,7 @@ impl FolderSubcommand {
match self { match self {
Self::Create(cmd) => cmd.execute(printer, config).await, Self::Create(cmd) => cmd.execute(printer, config).await,
Self::List(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::Expunge(cmd) => cmd.execute(printer, config).await,
Self::Purge(cmd) => cmd.execute(printer, config).await, Self::Purge(cmd) => cmd.execute(printer, config).await,
Self::Delete(cmd) => cmd.execute(printer, config).await, Self::Delete(cmd) => cmd.execute(printer, config).await,

View file

@ -0,0 +1,42 @@
use anyhow::Result;
use clap::Parser;
use log::info;
use crate::{
account::arg::name::AccountNameFlag, backend::Backend, cache::arg::disable::CacheDisableFlag,
config::TomlConfig, folder::arg::name::FolderNameArg, printer::Printer,
};
/// Watch a folder for changes.
///
/// This command allows you to watch a new folder using the given
/// name.
#[derive(Debug, Parser)]
pub struct FolderWatchCommand {
#[command(flatten)]
pub folder: FolderNameArg,
#[command(flatten)]
pub cache: CacheDisableFlag,
#[command(flatten)]
pub account: AccountNameFlag,
}
impl FolderWatchCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing folder watch command");
let folder = &self.folder.name;
let some_account_name = self.account.name.as_ref().map(String::as_str);
let (toml_account_config, account_config) = config
.clone()
.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…"))?;
backend.watch_emails(&folder).await
}
}