mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-15 20:07:57 +08:00
refactor: improve maildir, bring m2dir support
This commit is contained in:
Generated
+30
-20
@@ -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",
|
||||
]
|
||||
|
||||
+4
-1
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
+9
-1
@@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -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)?;
|
||||
|
||||
+11
-1
@@ -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.<name>]` 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<MaildirConfig>,
|
||||
#[allow(unused)]
|
||||
pub m2dir: Option<M2dirConfig>,
|
||||
#[allow(unused)]
|
||||
pub smtp: Option<SmtpConfig>,
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// This file is part of Himalaya, a CLI to manage emails.
|
||||
//
|
||||
// Copyright (C) 2022-2026 soywod <pimalaya.org@posteo.net>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// This file is part of Himalaya, a CLI to manage emails.
|
||||
//
|
||||
// Copyright (C) 2022-2026 soywod <pimalaya.org@posteo.net>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// This file is part of Himalaya, a CLI to manage emails.
|
||||
//
|
||||
// Copyright (C) 2022-2026 soywod <pimalaya.org@posteo.net>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<M2dirClient> {
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// This file is part of Himalaya, a CLI to manage emails.
|
||||
//
|
||||
// Copyright (C) 2022-2026 soywod <pimalaya.org@posteo.net>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// This file is part of Himalaya, a CLI to manage emails.
|
||||
//
|
||||
// Copyright (C) 2022-2026 soywod <pimalaya.org@posteo.net>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// This file is part of Himalaya, a CLI to manage emails.
|
||||
//
|
||||
// Copyright (C) 2022-2026 soywod <pimalaya.org@posteo.net>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<M2dirRow>,
|
||||
}
|
||||
|
||||
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<M2dir> 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 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// This file is part of Himalaya, a CLI to manage emails.
|
||||
//
|
||||
// Copyright (C) 2022-2026 soywod <pimalaya.org@posteo.net>
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod arg;
|
||||
pub mod cli;
|
||||
pub mod client;
|
||||
pub mod create;
|
||||
pub mod delete;
|
||||
pub mod list;
|
||||
+14
-3
@@ -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<Maildir> {
|
||||
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 {
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ impl From<Maildir> 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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;
|
||||
|
||||
+13
-1
@@ -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");
|
||||
}
|
||||
|
||||
@@ -229,17 +229,17 @@ impl fmt::Display for Envelopes {
|
||||
/// (v1.2.0 defaults: `*`, `R`, `!`).
|
||||
pub(super) fn format_flags(flags: &BTreeSet<Flag>, 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 {
|
||||
' '
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)?),
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user