From 68ff784c240891e0634b26e18013ee7b67e2c3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Sun, 24 May 2026 01:37:14 +0200 Subject: [PATCH] refactor: improve maildir, bring m2dir support --- Cargo.lock | 50 ++++++++++-------- Cargo.toml | 5 +- src/account/context.rs | 5 +- src/backend.rs | 10 +++- src/cli.rs | 10 ++++ src/config.rs | 12 ++++- src/jmap/email/export.rs | 7 ++- src/m2dir/arg.rs | 25 +++++++++ src/m2dir/cli.rs | 49 ++++++++++++++++++ src/m2dir/client.rs | 77 +++++++++++++++++++++++++++ src/m2dir/create.rs | 40 +++++++++++++++ src/m2dir/delete.rs | 38 ++++++++++++++ src/m2dir/list.rs | 97 +++++++++++++++++++++++++++++++++++ src/m2dir/mod.rs | 23 +++++++++ src/maildir/client.rs | 17 ++++-- src/maildir/create.rs | 6 ++- src/maildir/delete.rs | 6 ++- src/maildir/envelope/get.rs | 10 ++-- src/maildir/envelope/list.rs | 6 +-- src/maildir/flag/add.rs | 7 +-- src/maildir/flag/remove.rs | 7 +-- src/maildir/flag/set.rs | 7 +-- src/maildir/list.rs | 2 +- src/maildir/message/copy.rs | 12 +---- src/maildir/message/export.rs | 10 ++-- src/maildir/message/get.rs | 10 ++-- src/maildir/message/move.rs | 12 +---- src/maildir/message/read.rs | 10 ++-- src/maildir/message/save.rs | 8 ++- src/maildir/rename.rs | 6 ++- src/main.rs | 2 + src/shared/client.rs | 14 ++++- src/shared/envelopes/list.rs | 6 +-- src/shared/flags/arg.rs | 16 +++--- src/wizard/discover.rs | 2 + src/wizard/edit.rs | 3 ++ 36 files changed, 510 insertions(+), 117 deletions(-) create mode 100644 src/m2dir/arg.rs create mode 100644 src/m2dir/cli.rs create mode 100644 src/m2dir/client.rs create mode 100644 src/m2dir/create.rs create mode 100644 src/m2dir/delete.rs create mode 100644 src/m2dir/list.rs create mode 100644 src/m2dir/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 84204fdb..a6933ebc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,9 +127,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" @@ -207,9 +207,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" @@ -814,6 +814,7 @@ dependencies = [ "io-email", "io-imap", "io-jmap", + "io-m2dir", "io-maildir", "io-smtp", "log", @@ -1061,12 +1062,13 @@ dependencies = [ [[package]] name = "io-email" version = "0.0.1" -source = "git+https://github.com/pimalaya/io-email#4dc69352fc31cae441611ce638f42fbab207e98b" +source = "git+https://github.com/pimalaya/io-email#6c898bbc8faced492e974fff27dd4da0f46f4827" dependencies = [ "chrono", "chumsky", "io-imap", "io-jmap", + "io-m2dir", "io-maildir", "io-smtp", "log", @@ -1083,7 +1085,7 @@ dependencies = [ [[package]] name = "io-http" version = "0.0.3" -source = "git+https://github.com/pimalaya/io-http#812d8a30891631e49c70722555eefecaa0033458" +source = "git+https://github.com/pimalaya/io-http#1e71383b97e823f9440ea2d7c2af378bcd3b5933" dependencies = [ "anyhow", "base64", @@ -1099,7 +1101,7 @@ dependencies = [ [[package]] name = "io-imap" version = "0.0.1" -source = "git+https://github.com/pimalaya/io-imap#f627d6ac1e5b04d293aaff3863eac4c6bfe1a16d" +source = "git+https://github.com/pimalaya/io-imap#0a6a3ef4761b12b29cd5b9178b0cb7889d84ccbc" dependencies = [ "anyhow", "base64", @@ -1114,7 +1116,7 @@ dependencies = [ [[package]] name = "io-jmap" version = "0.0.1" -source = "git+https://github.com/pimalaya/io-jmap#a53855723683e0c08269ebaabfd530efceeadb5d" +source = "git+https://github.com/pimalaya/io-jmap#740ac4f314d4de3b4c6b53eba61722787f01f24c" dependencies = [ "anyhow", "io-http", @@ -1127,15 +1129,23 @@ dependencies = [ "url", ] +[[package]] +name = "io-m2dir" +version = "0.0.1" +source = "git+https://github.com/pimalaya/io-m2dir#69541e3b50bde6bdcc00135d5ec2bb388471ee5c" +dependencies = [ + "log", + "thiserror", +] + [[package]] name = "io-maildir" version = "0.0.1" -source = "git+https://github.com/pimalaya/io-maildir#d6756d8bb19c8f9dcc61e10ec580200e6ed308a7" +source = "git+https://github.com/pimalaya/io-maildir#ac75cf8359ce828e5702f1c912e52bb9cfb66c6d" dependencies = [ "gethostname", "log", "mail-parser", - "memchr", "thiserror", ] @@ -1272,9 +1282,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -2523,9 +2533,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -2536,9 +2546,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2546,9 +2556,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -2559,9 +2569,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] diff --git a/Cargo.toml b/Cargo.toml index cf26d5d1..e387406d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,11 +17,12 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["imap", "smtp", "jmap", "maildir", "rustls-ring"] +default = ["imap", "smtp", "jmap", "maildir", "m2dir", "rustls-ring"] imap = ["dep:io-imap", "dep:mail-parser", "dep:rfc2047-decoder", "io-email/imap", "io-imap/client"] jmap = ["dep:base64", "dep:io-jmap", "dep:mail-parser", "dep:serde_json", "io-email/jmap", "io-jmap/client"] smtp = ["dep:io-smtp", "dep:mail-parser", "io-email/smtp"] maildir = ["dep:convert_case", "dep:io-maildir", "dep:mail-parser", "dep:mime_guess", "io-email/maildir", "io-maildir/client"] +m2dir = ["dep:io-m2dir", "dep:mail-parser", "io-email/m2dir", "io-m2dir/client"] native-tls = ["pimalaya-stream/native-tls", "io-discovery/native-tls", "io-imap?/native-tls", "io-jmap?/native-tls", "io-smtp?/native-tls"] rustls-aws = ["pimalaya-stream/rustls-aws", "io-discovery/rustls-aws", "io-imap?/rustls-aws", "io-jmap?/rustls-aws", "io-smtp?/rustls-aws"] rustls-ring = ["pimalaya-stream/rustls-ring", "io-discovery/rustls-ring", "io-imap?/rustls-ring", "io-jmap?/rustls-ring", "io-smtp?/rustls-ring"] @@ -51,6 +52,7 @@ ariadne = "0.6" io-email = { version = "0.0.1", default-features = false, features = ["serde", "client", "search"] } io-imap = { version = "0.0.1", default-features = false, optional = true } io-jmap = { version = "0.0.1", default-features = false, optional = true } +io-m2dir = { version = "0.0.1", default-features = false, optional = true } io-maildir = { version = "0.0.1", default-features = false, optional = true } io-smtp = { version = "0.0.1", default-features = false, optional = true } log = "0.4" @@ -87,6 +89,7 @@ io-email.git = "https://github.com/pimalaya/io-email" io-http.git = "https://github.com/pimalaya/io-http" io-imap.git = "https://github.com/pimalaya/io-imap" io-jmap.git = "https://github.com/pimalaya/io-jmap" +io-m2dir.git = "https://github.com/pimalaya/io-m2dir" io-maildir.git = "https://github.com/pimalaya/io-maildir" io-smtp.git = "https://github.com/pimalaya/io-smtp" pimalaya-cli.git = "https://github.com/pimalaya/cli" diff --git a/src/account/context.rs b/src/account/context.rs index 3970cf02..04c31fa8 100644 --- a/src/account/context.rs +++ b/src/account/context.rs @@ -470,7 +470,10 @@ mod tests { .map(|(k, v)| ((*k).to_string(), (*v).to_string())) .collect(); let config = Config { - mailbox: MailboxConfig { alias }, + mailbox: MailboxConfig { + alias, + ..MailboxConfig::default() + }, ..Config::default() }; Account::from(config) diff --git a/src/backend.rs b/src/backend.rs index 1989b48a..e5d964c5 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -28,7 +28,7 @@ use clap::Parser; /// (config missing, or the operation has no arm for that backend). /// /// The protocol-specific subcommands (`imap`, `jmap`, `maildir`, -/// `smtp`) ignore this arg entirely. +/// `m2dir`, `smtp`) ignore this arg entirely. #[derive(Clone, Copy, Debug, Default, Parser, PartialEq, Eq)] pub enum Backend { #[default] @@ -36,6 +36,7 @@ pub enum Backend { Imap, Jmap, Maildir, + M2dir, Smtp, } @@ -56,6 +57,11 @@ impl Backend { matches!(self, Self::Auto | Self::Maildir) } + /// Whether the m2dir arm of a shared command is allowed to run. + pub fn allows_m2dir(self) -> bool { + matches!(self, Self::Auto | Self::M2dir) + } + /// Whether the SMTP arm of a shared command is allowed to run. pub fn allows_smtp(self) -> bool { matches!(self, Self::Auto | Self::Smtp) @@ -71,6 +77,7 @@ impl FromStr for Backend { "imap" => Ok(Self::Imap), "jmap" => Ok(Self::Jmap), "maildir" => Ok(Self::Maildir), + "m2dir" => Ok(Self::M2dir), "smtp" => Ok(Self::Smtp), backend => bail!("Invalid backend {backend}"), } @@ -84,6 +91,7 @@ impl fmt::Display for Backend { Self::Imap => write!(f, "imap"), Self::Jmap => write!(f, "jmap"), Self::Maildir => write!(f, "maildir"), + Self::M2dir => write!(f, "m2dir"), Self::Smtp => write!(f, "smtp"), } } diff --git a/src/cli.rs b/src/cli.rs index de380a78..b3eab3c4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -34,6 +34,8 @@ use pimalaya_config::toml::TomlConfig; use crate::imap::{cli::ImapCommand, client::build_imap_client}; #[cfg(feature = "jmap")] use crate::jmap::{cli::JmapCommand, client::build_jmap_client}; +#[cfg(feature = "m2dir")] +use crate::m2dir::{cli::M2dirCommand, client::build_m2dir_client}; #[cfg(feature = "maildir")] use crate::maildir::{cli::MaildirCommand, client::build_maildir_client}; #[cfg(feature = "smtp")] @@ -119,6 +121,9 @@ pub enum HimalayaCommand { #[cfg(feature = "maildir")] #[command(subcommand)] Maildir(MaildirCommand), + #[cfg(feature = "m2dir")] + #[command(subcommand)] + M2dir(M2dirCommand), #[cfg(feature = "smtp")] #[command(subcommand)] Smtp(SmtpCommand), @@ -206,6 +211,11 @@ impl HimalayaCommand { let client = build_maildir_client(config_paths, account_name)?; cmd.execute(printer, client) } + #[cfg(feature = "m2dir")] + Self::M2dir(cmd) => { + let client = build_m2dir_client(config_paths, account_name)?; + cmd.execute(printer, client) + } #[cfg(feature = "smtp")] Self::Smtp(cmd) => { let client = build_smtp_client(config_paths, account_name)?; diff --git a/src/config.rs b/src/config.rs index 9043d4c0..bbae5775 100644 --- a/src/config.rs +++ b/src/config.rs @@ -107,7 +107,7 @@ impl Config { /// `deny_unknown_fields` is omitted so per-account TUI-only fields /// (`email`, `display-name`, `signature`, `signature-delim`) coexist /// in the same `[accounts.]` block when the file is shared. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct AccountConfig { #[serde(default)] @@ -133,6 +133,8 @@ pub struct AccountConfig { #[allow(unused)] pub maildir: Option, #[allow(unused)] + pub m2dir: Option, + #[allow(unused)] pub smtp: Option, } @@ -426,6 +428,14 @@ pub struct MaildirConfig { pub root: PathBuf, } +/// m2dir configuration. +#[allow(unused)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct M2dirConfig { + pub root: PathBuf, +} + /// SMTP configuration. #[allow(unused)] #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/src/jmap/email/export.rs b/src/jmap/email/export.rs index d1d5c4a8..7ae7f67a 100644 --- a/src/jmap/email/export.rs +++ b/src/jmap/email/export.rs @@ -17,7 +17,10 @@ use anyhow::{Result, anyhow}; use clap::Parser; -use io_jmap::{client::JmapClientStd, rfc8621::capabilities::MAIL}; +use io_jmap::{ + client::JmapClientStd, + rfc8621::{capabilities::MAIL, email::EmailProperty}, +}; use pimalaya_cli::printer::{Message, Printer}; use pimalaya_stream::tls::Tls; use url::Url; @@ -36,7 +39,7 @@ pub struct JmapEmailExportCommand { impl JmapEmailExportCommand { pub fn execute(self, printer: &mut impl Printer, mut client: JmapClient) -> Result<()> { - let properties = Some(vec!["id".to_owned(), "blobId".to_owned()]); + let properties = Some(vec![EmailProperty::Id, EmailProperty::BlobId]); let output = client.email_get(vec![self.id.clone()], properties, false, false, 0)?; let session = client.session().expect("session loaded by new_jmap_client"); diff --git a/src/m2dir/arg.rs b/src/m2dir/arg.rs new file mode 100644 index 00000000..0401ea24 --- /dev/null +++ b/src/m2dir/arg.rs @@ -0,0 +1,25 @@ +// This file is part of Himalaya, a CLI to manage emails. +// +// Copyright (C) 2022-2026 soywod +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use clap::Parser; + +#[derive(Debug, Parser)] +pub struct M2dirNameArg { + /// Name of the m2dir folder, relative to the m2store root. + #[arg(name = "m2dir_name", value_name = "NAME")] + pub inner: String, +} diff --git a/src/m2dir/cli.rs b/src/m2dir/cli.rs new file mode 100644 index 00000000..9c8ca115 --- /dev/null +++ b/src/m2dir/cli.rs @@ -0,0 +1,49 @@ +// This file is part of Himalaya, a CLI to manage emails. +// +// Copyright (C) 2022-2026 soywod +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use anyhow::Result; +use clap::Subcommand; +use pimalaya_cli::printer::Printer; + +use crate::m2dir::{ + client::M2dirClient, create::M2dirMailboxCreateCommand, delete::M2dirMailboxDeleteCommand, + list::M2dirMailboxListCommand, +}; + +/// m2dir CLI. +/// +/// Protocol-specific entry point for the m2dir backend. Wider +/// operations (envelopes, flags, messages) go through the shared +/// commands `himalaya envelopes`, `himalaya flags`, `himalaya +/// messages` with `--backend m2dir`. +#[derive(Debug, Subcommand)] +#[command(rename_all = "kebab-case")] +pub enum M2dirCommand { + Create(M2dirMailboxCreateCommand), + Delete(M2dirMailboxDeleteCommand), + List(M2dirMailboxListCommand), +} + +impl M2dirCommand { + pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + match self { + Self::Create(cmd) => cmd.execute(printer, client), + Self::Delete(cmd) => cmd.execute(printer, client), + Self::List(cmd) => cmd.execute(printer, client), + } + } +} diff --git a/src/m2dir/client.rs b/src/m2dir/client.rs new file mode 100644 index 00000000..347028a0 --- /dev/null +++ b/src/m2dir/client.rs @@ -0,0 +1,77 @@ +// This file is part of Himalaya, a CLI to manage emails. +// +// Copyright (C) 2022-2026 soywod +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//! Himalaya wrapper around [`io_m2dir::client::M2dirClient`] bundling +//! the merged [`Account`] alongside the m2dir client. + +use std::{ + ops::{Deref, DerefMut}, + path::PathBuf, +}; + +use anyhow::{Result, anyhow}; +use io_m2dir::client::M2dirClient as Inner; +use pimalaya_config::toml::TomlConfig; + +use crate::{account::context::Account, cli::load_or_wizard, config::M2dirConfig}; + +pub struct M2dirClient { + inner: Inner, + pub account: Account, +} + +impl M2dirClient { + /// Builds an [`M2dirClient`] rooted at the configured m2store + /// path. + pub fn new(config: M2dirConfig, account: Account) -> Self { + let inner = Inner::new(config.root.to_string_lossy().into_owned()); + Self { inner, account } + } +} + +impl Deref for M2dirClient { + type Target = Inner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for M2dirClient { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +/// Loads the configuration, picks the active account, builds the +/// merged [`Account`] then opens the m2dir client. Bails when the +/// account has no `[m2dir]` block. +pub fn build_m2dir_client( + config_paths: &[PathBuf], + account_name: Option<&str>, +) -> Result { + let mut config = load_or_wizard(config_paths)?; + let (name, mut ac) = config + .take_account(account_name)? + .ok_or_else(|| anyhow!("Cannot find account"))?; + let m2dir_config = ac + .m2dir + .take() + .ok_or_else(|| anyhow!("m2dir config is missing for account `{name}`"))?; + let account = Account::from(config).merge(Account::from(ac)); + Ok(M2dirClient::new(m2dir_config, account)) +} diff --git a/src/m2dir/create.rs b/src/m2dir/create.rs new file mode 100644 index 00000000..902243f7 --- /dev/null +++ b/src/m2dir/create.rs @@ -0,0 +1,40 @@ +// This file is part of Himalaya, a CLI to manage emails. +// +// Copyright (C) 2022-2026 soywod +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use anyhow::Result; +use clap::Parser; +use pimalaya_cli::printer::{Message, Printer}; + +use crate::m2dir::{arg::M2dirNameArg, client::M2dirClient}; + +/// Create the given m2dir folder. +/// +/// Initialises the m2store at the client root if needed, then creates +/// the m2dir folder named after `name` (relative to the store root). +#[derive(Debug, Parser)] +pub struct M2dirMailboxCreateCommand { + #[command(flatten)] + pub m2dir_name: M2dirNameArg, +} + +impl M2dirMailboxCreateCommand { + pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + client.init_store()?; + client.create_mailbox(&self.m2dir_name.inner)?; + printer.out(Message::new("m2dir folder successfully created")) + } +} diff --git a/src/m2dir/delete.rs b/src/m2dir/delete.rs new file mode 100644 index 00000000..dfdde34f --- /dev/null +++ b/src/m2dir/delete.rs @@ -0,0 +1,38 @@ +// This file is part of Himalaya, a CLI to manage emails. +// +// Copyright (C) 2022-2026 soywod +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use anyhow::Result; +use clap::Parser; +use pimalaya_cli::printer::{Message, Printer}; + +use crate::m2dir::{arg::M2dirNameArg, client::M2dirClient}; + +/// Delete the given m2dir folder. +#[derive(Debug, Parser)] +pub struct M2dirMailboxDeleteCommand { + #[command(flatten)] + pub m2dir_name: M2dirNameArg, +} + +impl M2dirMailboxDeleteCommand { + pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + let store = client.open_store()?; + let path = store.resolve_folder_path(&self.m2dir_name.inner)?; + client.delete_mailbox(path)?; + printer.out(Message::new("m2dir folder successfully deleted")) + } +} diff --git a/src/m2dir/list.rs b/src/m2dir/list.rs new file mode 100644 index 00000000..d7ad1756 --- /dev/null +++ b/src/m2dir/list.rs @@ -0,0 +1,97 @@ +// This file is part of Himalaya, a CLI to manage emails. +// +// Copyright (C) 2022-2026 soywod +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use std::fmt; + +use anyhow::Result; +use clap::Parser; +use comfy_table::{Cell, Color, Row, Table}; +use io_m2dir::m2dir::M2dir; +use pimalaya_cli::printer::Printer; +use serde::Serialize; + +use crate::m2dir::client::M2dirClient; + +/// List m2dir folders found under the store root. +#[derive(Debug, Parser)] +pub struct M2dirMailboxListCommand; + +impl M2dirMailboxListCommand { + pub fn execute(self, printer: &mut impl Printer, client: M2dirClient) -> Result<()> { + let m2dirs = client.list_mailboxes()?; + + let table = M2dirsTable { + preset: client.account.table_preset().to_string(), + name_color: client.account.mailboxes_list_table_name_color(), + rows: m2dirs.into_iter().map(From::from).collect(), + }; + + printer.out(table) + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct M2dirsTable { + #[serde(skip)] + pub preset: String, + #[serde(skip)] + pub name_color: Color, + #[serde(rename = "m2dirs")] + pub rows: Vec, +} + +impl fmt::Display for M2dirsTable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut table = Table::new(); + + table + .load_preset(&self.preset) + .set_header(Row::from([Cell::new("NAME"), Cell::new("PATH")])) + .add_rows(self.rows.iter().map(|m| { + let mut row = Row::new(); + + row.max_height(1) + .add_cell(Cell::new(&m.name).fg(self.name_color)) + .add_cell(Cell::new(&m.path)); + + row + })); + + writeln!(f)?; + write!(f, "{table}")?; + writeln!(f)?; + Ok(()) + } +} + +#[derive(Clone, Debug, Serialize)] +pub struct M2dirRow { + pub name: String, + pub path: String, +} + +impl From for M2dirRow { + fn from(m2dir: M2dir) -> Self { + let name = m2dir + .path() + .file_name() + .map(str::to_owned) + .unwrap_or_default(); + let path = m2dir.path().as_str().to_owned(); + Self { name, path } + } +} diff --git a/src/m2dir/mod.rs b/src/m2dir/mod.rs new file mode 100644 index 00000000..af3313be --- /dev/null +++ b/src/m2dir/mod.rs @@ -0,0 +1,23 @@ +// This file is part of Himalaya, a CLI to manage emails. +// +// Copyright (C) 2022-2026 soywod +// +// This program is free software: you can redistribute it and/or modify it under +// the terms of the GNU Affero General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) any +// later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +// details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +pub mod arg; +pub mod cli; +pub mod client; +pub mod create; +pub mod delete; +pub mod list; diff --git a/src/maildir/client.rs b/src/maildir/client.rs index 09fec4df..f1101966 100644 --- a/src/maildir/client.rs +++ b/src/maildir/client.rs @@ -24,11 +24,11 @@ use std::{ ops::{Deref, DerefMut}, - path::PathBuf, + path::{Path, PathBuf}, }; use anyhow::{Result, anyhow}; -use io_maildir::client::MaildirClient as Inner; +use io_maildir::{client::MaildirClient as Inner, maildir::Maildir}; use pimalaya_config::toml::TomlConfig; use crate::{account::context::Account, cli::load_or_wizard, config::MaildirConfig}; @@ -47,13 +47,24 @@ impl MaildirClient { /// path. pub fn new(config: MaildirConfig, account: Account) -> Self { let root = config.root.clone(); - let inner = Inner::new(config.root); + let inner = Inner::new(root.to_string_lossy().into_owned()); Self { inner, account, root, } } + + /// Resolves a maildir CLI argument: tries `path` as-is first, then + /// falls back to `self.root.join(path)`. Both attempts go through + /// [`io_maildir::client::MaildirClient::load_maildir`] so the + /// `cur` / `new` / `tmp` markers are validated. + pub fn resolve_maildir(&self, path: &Path) -> Result { + if let Ok(maildir) = self.load_maildir(path.to_string_lossy().into_owned()) { + return Ok(maildir); + } + Ok(self.load_maildir(self.root.join(path).to_string_lossy().into_owned())?) + } } impl Deref for MaildirClient { diff --git a/src/maildir/create.rs b/src/maildir/create.rs index cbd5e422..eb4aad4b 100644 --- a/src/maildir/create.rs +++ b/src/maildir/create.rs @@ -33,7 +33,11 @@ pub struct MaildirMailboxCreateCommand { impl MaildirMailboxCreateCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let path = client.root.join(&self.maildir_name.inner); + let path = client + .root + .join(&self.maildir_name.inner) + .to_string_lossy() + .into_owned(); client.create_maildir(path)?; printer.out(Message::new("Maildir successfully created")) diff --git a/src/maildir/delete.rs b/src/maildir/delete.rs index a6c3d327..3c5f6a6b 100644 --- a/src/maildir/delete.rs +++ b/src/maildir/delete.rs @@ -33,7 +33,11 @@ pub struct MaildirMailboxDeleteCommand { impl MaildirMailboxDeleteCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let path = client.root.join(&self.maildir_path.inner); + let path = client + .root + .join(&self.maildir_path.inner) + .to_string_lossy() + .into_owned(); client.delete_maildir(path)?; printer.out(Message::new("Maildir successfully deleted")) diff --git a/src/maildir/envelope/get.rs b/src/maildir/envelope/get.rs index 08342c1e..cdfc5712 100644 --- a/src/maildir/envelope/get.rs +++ b/src/maildir/envelope/get.rs @@ -20,7 +20,6 @@ use std::fmt; use anyhow::{Result, bail}; use clap::Parser; use comfy_table::{Cell, Row, Table}; -use io_maildir::maildir::Maildir; use mail_parser::Header; use pimalaya_cli::printer::Printer; use serde::Serialize; @@ -45,17 +44,14 @@ pub struct MaildirEnvelopeGetCommand { impl MaildirEnvelopeGetCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let maildir = match Maildir::try_from(self.maildir.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.maildir.inner))?, - }; + let maildir = client.resolve_maildir(&self.maildir.inner)?; let message = client.get(maildir, &self.id.inner)?; - let path = message.path().to_owned(); + let path = message.path().clone(); let Some(parsed) = message.headers() else { - bail!("Invalid MIME message at {}", path.display()); + bail!("Invalid MIME message at {path}"); }; let table = EnvelopeTable { diff --git a/src/maildir/envelope/list.rs b/src/maildir/envelope/list.rs index fcfa3bec..89af66fe 100644 --- a/src/maildir/envelope/list.rs +++ b/src/maildir/envelope/list.rs @@ -20,7 +20,6 @@ use std::fmt; use anyhow::Result; use clap::Parser; use comfy_table::{Cell, Color, ContentArrangement, Row, Table}; -use io_maildir::maildir::Maildir; use pimalaya_cli::printer::Printer; use serde::Serialize; @@ -39,10 +38,7 @@ pub struct MaildirEnvelopeListCommand { impl MaildirEnvelopeListCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let maildir = match Maildir::try_from(self.maildir.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.maildir.inner))?, - }; + let maildir = client.resolve_maildir(&self.maildir.inner)?; let messages = client.list_messages(maildir)?; diff --git a/src/maildir/flag/add.rs b/src/maildir/flag/add.rs index affd431e..665e4b1c 100644 --- a/src/maildir/flag/add.rs +++ b/src/maildir/flag/add.rs @@ -17,7 +17,7 @@ use anyhow::Result; use clap::Parser; -use io_maildir::{flag::Flags, maildir::Maildir}; +use io_maildir::flag::Flags; use pimalaya_cli::printer::{Message, Printer}; use crate::maildir::{ @@ -44,10 +44,7 @@ pub struct MaildirFlagAddCommand { impl MaildirFlagAddCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let maildir = match Maildir::try_from(self.maildir.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.maildir.inner))?, - }; + let maildir = client.resolve_maildir(&self.maildir.inner)?; let flags = Flags::from_iter(self.flags.into_iter().map(Into::into)); diff --git a/src/maildir/flag/remove.rs b/src/maildir/flag/remove.rs index 31f937e7..5f0533de 100644 --- a/src/maildir/flag/remove.rs +++ b/src/maildir/flag/remove.rs @@ -17,7 +17,7 @@ use anyhow::Result; use clap::Parser; -use io_maildir::{flag::Flags, maildir::Maildir}; +use io_maildir::flag::Flags; use pimalaya_cli::printer::{Message, Printer}; use crate::maildir::{ @@ -44,10 +44,7 @@ pub struct MaildirFlagRemoveCommand { impl MaildirFlagRemoveCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let maildir = match Maildir::try_from(self.maildir.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.maildir.inner))?, - }; + let maildir = client.resolve_maildir(&self.maildir.inner)?; let flags = Flags::from_iter(self.flags.into_iter().map(Into::into)); diff --git a/src/maildir/flag/set.rs b/src/maildir/flag/set.rs index 99908776..bc40598a 100644 --- a/src/maildir/flag/set.rs +++ b/src/maildir/flag/set.rs @@ -17,7 +17,7 @@ use anyhow::Result; use clap::Parser; -use io_maildir::{flag::Flags, maildir::Maildir}; +use io_maildir::flag::Flags; use pimalaya_cli::printer::{Message, Printer}; use crate::maildir::{ @@ -44,10 +44,7 @@ pub struct MaildirFlagSetCommand { impl MaildirFlagSetCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let maildir = match Maildir::try_from(self.maildir.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.maildir.inner))?, - }; + let maildir = client.resolve_maildir(&self.maildir.inner)?; let flags = Flags::from_iter(self.flags.into_iter().map(Into::into)); diff --git a/src/maildir/list.rs b/src/maildir/list.rs index f2d50eaf..4c7cc00b 100644 --- a/src/maildir/list.rs +++ b/src/maildir/list.rs @@ -92,7 +92,7 @@ impl From for MaildirRow { fn from(maildir: Maildir) -> Self { Self { name: maildir.name().unwrap_or("Unknown").to_owned(), - path: maildir.as_ref().to_owned(), + path: PathBuf::from(maildir.path().as_str()), } } } diff --git a/src/maildir/message/copy.rs b/src/maildir/message/copy.rs index c0182eb1..7daf5e9d 100644 --- a/src/maildir/message/copy.rs +++ b/src/maildir/message/copy.rs @@ -17,7 +17,6 @@ use anyhow::Result; use clap::Parser; -use io_maildir::maildir::Maildir; use pimalaya_cli::printer::{Message, Printer}; use crate::maildir::{ @@ -45,15 +44,8 @@ pub struct MaildirMessageCopyCommand { impl MaildirMessageCopyCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let source = match Maildir::try_from(self.source.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.source.inner))?, - }; - - let target = match Maildir::try_from(self.target.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.target.inner))?, - }; + let source = client.resolve_maildir(&self.source.inner)?; + let target = client.resolve_maildir(&self.target.inner)?; for id in self.ids.inner { client.copy( diff --git a/src/maildir/message/export.rs b/src/maildir/message/export.rs index ba3d42b7..df237a1c 100644 --- a/src/maildir/message/export.rs +++ b/src/maildir/message/export.rs @@ -20,7 +20,6 @@ use std::{fmt, fs, path::PathBuf}; use anyhow::{Result, bail}; use clap::{Parser, ValueEnum}; use convert_case::ccase; -use io_maildir::maildir::Maildir; use mail_parser::MimeHeaders; use mime_guess::{get_mime_extensions_str, mime::OCTET_STREAM}; use pimalaya_cli::printer::Printer; @@ -59,10 +58,7 @@ pub struct MaildirMessageExportCommand { impl MaildirMessageExportCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let maildir = match Maildir::try_from(self.maildir.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.maildir.inner))?, - }; + let maildir = client.resolve_maildir(&self.maildir.inner)?; let msg = client.get(maildir, &self.id.inner)?; @@ -72,10 +68,10 @@ impl MaildirMessageExportCommand { printer.out(ExportRaw { contents })?; } ExportType::Parts => { - let path = msg.path().to_owned(); + let path = msg.path().clone(); let Some(parsed) = msg.parsed() else { - bail!("Invalid MIME message at {}", path.display()); + bail!("Invalid MIME message at {path}"); }; let dir = match self.directory { diff --git a/src/maildir/message/get.rs b/src/maildir/message/get.rs index ef35278f..9a2976aa 100644 --- a/src/maildir/message/get.rs +++ b/src/maildir/message/get.rs @@ -19,7 +19,6 @@ use std::fmt; use anyhow::{Result, bail}; use clap::Parser; -use io_maildir::maildir::Maildir; use mail_parser::Message; use pimalaya_cli::printer::Printer; use serde::Serialize; @@ -43,17 +42,14 @@ pub struct MaildirMessageGetCommand { impl MaildirMessageGetCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let maildir = match Maildir::try_from(self.maildir.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.maildir.inner))?, - }; + let maildir = client.resolve_maildir(&self.maildir.inner)?; let msg = client.get(maildir, &self.id.inner)?; - let path = msg.path().to_owned(); + let path = msg.path().clone(); let Some(parsed) = msg.headers() else { - bail!("Invalid MIME message at {}", path.display()); + bail!("Invalid MIME message at {path}"); }; printer.out(MessageView(parsed)) diff --git a/src/maildir/message/move.rs b/src/maildir/message/move.rs index dea6ad4f..46167c08 100644 --- a/src/maildir/message/move.rs +++ b/src/maildir/message/move.rs @@ -17,7 +17,6 @@ use anyhow::Result; use clap::Parser; -use io_maildir::maildir::Maildir; use pimalaya_cli::printer::{Message, Printer}; use crate::maildir::{ @@ -45,15 +44,8 @@ pub struct MaildirMessageMoveCommand { impl MaildirMessageMoveCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let source = match Maildir::try_from(self.source.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.source.inner))?, - }; - - let target = match Maildir::try_from(self.target.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.target.inner))?, - }; + let source = client.resolve_maildir(&self.source.inner)?; + let target = client.resolve_maildir(&self.target.inner)?; for id in self.ids.inner { client.r#move( diff --git a/src/maildir/message/read.rs b/src/maildir/message/read.rs index cdb0bc9b..4fb6f5e2 100644 --- a/src/maildir/message/read.rs +++ b/src/maildir/message/read.rs @@ -19,7 +19,6 @@ use std::fmt; use anyhow::{Result, bail}; use clap::Parser; -use io_maildir::maildir::Maildir; use mail_parser::Message; use pimalaya_cli::printer::Printer; use serde::Serialize; @@ -50,17 +49,14 @@ pub struct MaildirMessageReadCommand { impl MaildirMessageReadCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let maildir = match Maildir::try_from(self.maildir.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.maildir.inner))?, - }; + let maildir = client.resolve_maildir(&self.maildir.inner)?; let message = client.get(maildir, &self.id.inner)?; - let path = message.path().to_owned(); + let path = message.path().clone(); let Some(parsed) = message.parsed() else { - bail!("Invalid MIME message at {}", path.display()); + bail!("Invalid MIME message at {path}"); }; if self.html { diff --git a/src/maildir/message/save.rs b/src/maildir/message/save.rs index 36b2dd1c..9bb46c47 100644 --- a/src/maildir/message/save.rs +++ b/src/maildir/message/save.rs @@ -23,7 +23,7 @@ use std::{ use anyhow::Result; use clap::Parser; -use io_maildir::{flag::Flags, maildir::Maildir}; +use io_maildir::flag::Flags; use pimalaya_cli::printer::Printer; use serde::Serialize; @@ -59,10 +59,7 @@ pub struct MaildirMessageSaveCommand { impl MaildirMessageSaveCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let maildir = match Maildir::try_from(self.maildir.inner.clone()) { - Ok(maildir) => maildir, - Err(_) => Maildir::try_from(client.root.join(&self.maildir.inner))?, - }; + let maildir = client.resolve_maildir(&self.maildir.inner)?; let msg = if stdin().is_terminal() || printer.is_json() { self.message @@ -81,6 +78,7 @@ impl MaildirMessageSaveCommand { let flags = Flags::from_iter(self.flags.into_iter().map(Into::into)); let (id, path) = client.store(maildir, self.subdir.into(), flags, msg.into_bytes())?; + let path = PathBuf::from(path.into_string()); printer.out(StoredMessage { id, path }) } diff --git a/src/maildir/rename.rs b/src/maildir/rename.rs index eabcadcd..0805e7be 100644 --- a/src/maildir/rename.rs +++ b/src/maildir/rename.rs @@ -38,7 +38,11 @@ pub struct MaildirMailboxRenameCommand { impl MaildirMailboxRenameCommand { pub fn execute(self, printer: &mut impl Printer, client: MaildirClient) -> Result<()> { - let path = client.root.join(&self.maildir_path.inner); + let path = client + .root + .join(&self.maildir_path.inner) + .to_string_lossy() + .into_owned(); client.rename_maildir(path, self.maildir_name.inner)?; printer.out(Message::new("Maildir successfully renamed")) diff --git a/src/main.rs b/src/main.rs index d1accd5a..986ada60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,8 @@ mod config; mod imap; #[cfg(feature = "jmap")] mod jmap; +#[cfg(feature = "m2dir")] +mod m2dir; #[cfg(feature = "maildir")] mod maildir; mod shared; diff --git a/src/shared/client.rs b/src/shared/client.rs index ee40939c..e6023269 100644 --- a/src/shared/client.rs +++ b/src/shared/client.rs @@ -101,13 +101,25 @@ impl EmailClient { if let Some(maildir_config) = account_config.maildir.take() { use io_maildir::client::MaildirClient; - let client = MaildirClient::new(maildir_config.root); + let client = MaildirClient::new(maildir_config.root.to_string_lossy().into_owned()); inner = inner.with_maildir(client); configured = true; } } + #[cfg(feature = "m2dir")] + if !configured && backend.allows_m2dir() { + if let Some(m2dir_config) = account_config.m2dir.take() { + use io_m2dir::client::M2dirClient; + + let client = M2dirClient::new(m2dir_config.root.to_string_lossy().into_owned()); + + inner = inner.with_m2dir(client); + configured = true; + } + } + if !configured { bail!("no backend matching `{backend}` is configured for this account"); } diff --git a/src/shared/envelopes/list.rs b/src/shared/envelopes/list.rs index cd57aaae..2eee3af9 100644 --- a/src/shared/envelopes/list.rs +++ b/src/shared/envelopes/list.rs @@ -229,17 +229,17 @@ impl fmt::Display for Envelopes { /// (v1.2.0 defaults: `*`, `R`, `!`). pub(super) fn format_flags(flags: &BTreeSet, chars: &FlagChars) -> String { let mut out = String::with_capacity(3); - out.push(if flags.contains(&Flag::Seen) { + out.push(if flags.iter().any(Flag::is_seen) { ' ' } else { chars.unseen }); - out.push(if flags.contains(&Flag::Answered) { + out.push(if flags.iter().any(Flag::is_answered) { chars.replied } else { ' ' }); - out.push(if flags.contains(&Flag::Flagged) { + out.push(if flags.iter().any(Flag::is_flagged) { chars.flagged } else { ' ' diff --git a/src/shared/flags/arg.rs b/src/shared/flags/arg.rs index 6cad2945..5bfe46d0 100644 --- a/src/shared/flags/arg.rs +++ b/src/shared/flags/arg.rs @@ -87,14 +87,16 @@ impl From<&FlagArg> for io_maildir::flag::Flag { impl From<&FlagArg> for io_email::flag::Flag { fn from(flag: &FlagArg) -> Self { - use io_email::flag::Flag; + use io_email::flag::{Flag, IanaFlag}; - match flag { - FlagArg::Seen => Flag::Seen, - FlagArg::Answered => Flag::Answered, - FlagArg::Flagged => Flag::Flagged, - FlagArg::Draft => Flag::Draft, - } + let iana = match flag { + FlagArg::Seen => IanaFlag::Seen, + FlagArg::Answered => IanaFlag::Answered, + FlagArg::Flagged => IanaFlag::Flagged, + FlagArg::Draft => IanaFlag::Draft, + }; + + Flag::from_iana(iana) } } diff --git a/src/wizard/discover.rs b/src/wizard/discover.rs index 4ebbd246..34fdf4d3 100644 --- a/src/wizard/discover.rs +++ b/src/wizard/discover.rs @@ -193,6 +193,7 @@ fn build_account_from_discovery( imap: None, jmap: Some(jmap_to_config(jmap)?), maildir: None, + m2dir: None, smtp: None, }) } else { @@ -209,6 +210,7 @@ fn build_account_from_discovery( imap: Some(imap_to_config(imap)?), jmap: None, maildir: None, + m2dir: None, smtp: Some(smtp_to_config(smtp)?), }) } diff --git a/src/wizard/edit.rs b/src/wizard/edit.rs index b0e7868f..bbd375e0 100644 --- a/src/wizard/edit.rs +++ b/src/wizard/edit.rs @@ -111,6 +111,7 @@ pub fn edit_account(target: &Path, mut config: Config, account_name: &str) -> Re .map(|a| a.attachment.clone()) .unwrap_or_default(); let maildir = existing.as_ref().and_then(|a| a.maildir.clone()); + let m2dir = existing.as_ref().and_then(|a| a.m2dir.clone()); let account = if jmap_defaults.is_some() { let jmap = jmap_wizard::run(account_name, local_part, domain, jmap_defaults.as_ref())?; @@ -124,6 +125,7 @@ pub fn edit_account(target: &Path, mut config: Config, account_name: &str) -> Re imap: None, jmap: Some(jmap_to_config(jmap)?), maildir, + m2dir, smtp: None, } } else { @@ -139,6 +141,7 @@ pub fn edit_account(target: &Path, mut config: Config, account_name: &str) -> Re imap: Some(imap_to_config(imap)?), jmap: None, maildir, + m2dir, smtp: Some(smtp_to_config(smtp)?), } };