mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-15 11:27:53 +08:00
fix(imap): add sort fallback
Introduce a new config option imap.sort.fallback: true for a slower SEARCH + SORT combination, false for the SORT. If omitted, use fallback when the SORT capability is not returned by the server. Refs: #698
This commit is contained in:
Generated
+26
-29
@@ -1046,8 +1046,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "io-email"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14d8602a9427bbeec3c2b37ad6c84789c2abc74d5a5ef21ef6b596f6bb3270b0"
|
||||
source = "git+https://github.com/pimalaya/io-email#6eb3fd75e254586639a6f198a46973498e63015f"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chumsky",
|
||||
@@ -1089,8 +1088,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "io-imap"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3abdd895505c9e96145abec34f7a3ef106e70bff71b04b0f116362c379d27e7"
|
||||
source = "git+https://github.com/pimalaya/io-imap#e6b4fb10633875b2f7d313f459dbd561458ed244"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -1278,13 +1276,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.99"
|
||||
version = "0.3.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||
checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -1393,9 +1390,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.1"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
@@ -1578,9 +1575,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "300.6.0+3.6.2"
|
||||
version = "300.6.1+3.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8e8cbfd3a4a8c8f089147fd7aaa33cf8c7450c4d09f8f80698a0cf093abeff4"
|
||||
checksum = "46eb8fb9fb3b61ce1c0f8a026c4c1a0714d3a9e138e7fbde78753ce2babc3846"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
@@ -1882,9 +1879,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "rfc2047-decoder"
|
||||
@@ -2241,9 +2238,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
version = "1.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
@@ -2481,9 +2478,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.23.2"
|
||||
version = "1.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7"
|
||||
checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7"
|
||||
dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"js-sys",
|
||||
@@ -2538,9 +2535,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.122"
|
||||
version = "0.2.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||
checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -2551,9 +2548,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.122"
|
||||
version = "0.2.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||
checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -2561,9 +2558,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.122"
|
||||
version = "0.2.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||
checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
@@ -2574,9 +2571,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.122"
|
||||
version = "0.2.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||
checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2942,18 +2939,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.50"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
|
||||
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.50"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
|
||||
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -77,4 +77,6 @@ uds_windows = "1"
|
||||
|
||||
[patch.crates-io]
|
||||
domain = { git = "https://github.com/nlnetlabs/domain", rev = "b30087125c13e71010d9783a1bcdd9eb9fa3f336" }
|
||||
io-email.git = "https://github.com/pimalaya/io-email"
|
||||
io-imap.git = "https://github.com/pimalaya/io-imap"
|
||||
pimconf.git = "https://github.com/pimalaya/pimconf"
|
||||
|
||||
@@ -217,6 +217,12 @@ imap.sasl.plain.password.raw = "***"
|
||||
# a warning. `false` always sends NIL. An empty map sends `ID NIL`.
|
||||
#imap.id.fields = { name = true, version = true, vendor = true, support-url = true }
|
||||
|
||||
# RFC 5256 SORT fallback. When the server lacks the SORT capability, himalaya
|
||||
# sorts client-side via SEARCH + FETCH. Leave unset to follow the capability;
|
||||
# set `true` to always sort client-side, or `false` to always issue a server SORT.
|
||||
# https://www.rfc-editor.org/rfc/rfc5256.html
|
||||
#imap.sort.fallback = false
|
||||
|
||||
# --------------------------------------------------------------------------------
|
||||
# JMAP config
|
||||
# https://www.iana.org/go/rfc8620
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
[sources]
|
||||
allow-git = [
|
||||
"https://github.com/nlnetlabs/domain",
|
||||
# "https://github.com/pimalaya/cli",
|
||||
# "https://github.com/pimalaya/config",
|
||||
# "https://github.com/pimalaya/io-email",
|
||||
# "https://github.com/pimalaya/io-http",
|
||||
# "https://github.com/pimalaya/io-imap",
|
||||
# "https://github.com/pimalaya/io-jmap",
|
||||
# "https://github.com/pimalaya/io-m2dir",
|
||||
# "https://github.com/pimalaya/io-maildir",
|
||||
# "https://github.com/pimalaya/io-smtp",
|
||||
"https://github.com/pimalaya/io-email",
|
||||
"https://github.com/pimalaya/io-imap",
|
||||
"https://github.com/pimalaya/pimconf",
|
||||
# "https://github.com/pimalaya/stream",
|
||||
]
|
||||
unknown-git = "deny"
|
||||
unknown-registry = "deny"
|
||||
|
||||
+24
-10
@@ -314,24 +314,38 @@ pub struct ImapConfig {
|
||||
#[serde(default)]
|
||||
pub starttls: bool,
|
||||
|
||||
/// ALPN protocol identifiers offered during the TLS handshake.
|
||||
/// Defaults to `["imap"]` (RFC 7595, IANA registry). Set to `[]`
|
||||
/// to skip ALPN negotiation entirely. Only relevant for the
|
||||
/// rustls provider; `native-tls` ignores ALPN.
|
||||
/// ALPN protocol identifiers offered during the TLS handshake. Defaults to
|
||||
/// `["imap"]` (RFC 7595, IANA registry). Set to `[]` to skip ALPN
|
||||
/// negotiation entirely. Only relevant for the rustls provider;
|
||||
/// `native-tls` ignores ALPN.
|
||||
#[serde(default = "io_imap::client::default_alpn")]
|
||||
pub alpn: Vec<String>,
|
||||
|
||||
/// Optional SASL credentials. When omitted, the connection skips
|
||||
/// authentication entirely (no `AUTHENTICATE` command is sent);
|
||||
/// to advertise the ANONYMOUS mechanism explicitly, set
|
||||
/// `sasl.anonymous = {}`.
|
||||
/// authentication entirely (no `AUTHENTICATE` command is sent); to
|
||||
/// advertise the ANONYMOUS mechanism explicitly, set `sasl.anonymous = {}`.
|
||||
pub sasl: Option<SaslConfig>,
|
||||
|
||||
/// RFC 2971 `ID` extension quirks. Some providers (notably
|
||||
/// mail.qq.com, fastmail) require an `ID` exchange straight after
|
||||
/// authentication; set `id.auto = true` to opt in.
|
||||
/// RFC 2971 `ID` extension quirks. Some providers (notably mail.qq.com,
|
||||
/// fastmail) require an `ID` exchange straight after authentication; set
|
||||
/// `id.auto = true` to opt in.
|
||||
#[serde(default)]
|
||||
pub id: ImapIdConfig,
|
||||
|
||||
/// RFC 5256 `SORT` extension config.
|
||||
#[serde(default)]
|
||||
pub sort: ImapSortConfig,
|
||||
}
|
||||
|
||||
/// Per-account `imap.sort.*` options.
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct ImapSortConfig {
|
||||
/// Forces the SORT fallback on or off. `Some(true)` always sorts
|
||||
/// client-side via SEARCH + FETCH; `Some(false)` always issues a server
|
||||
/// `SORT`. Left unset, the fallback is enabled only when the server lacks
|
||||
/// the SORT capability.
|
||||
pub fallback: Option<bool>,
|
||||
}
|
||||
|
||||
/// Per-account `imap.id.*` quirks.
|
||||
|
||||
+22
-7
@@ -11,7 +11,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use io_imap::client::ImapClientStd as Inner;
|
||||
use io_imap::{client::ImapClientStd as Inner, has_imap_capability, types::response::Capability};
|
||||
use pimalaya_config::toml::TomlConfig;
|
||||
use pimalaya_stream::sasl::Sasl;
|
||||
use url::Url;
|
||||
@@ -23,14 +23,16 @@ use crate::{
|
||||
|
||||
pub struct ImapClient {
|
||||
inner: Inner,
|
||||
capabilities: Vec<Capability<'static>>,
|
||||
sort_fallback: Option<bool>,
|
||||
}
|
||||
|
||||
impl ImapClient {
|
||||
/// Opens the IMAP connection (TCP/TLS/STARTTLS, greeting, SASL).
|
||||
/// The capability list reported by the connect handshake is
|
||||
/// discarded; IMAP-specific subcommands that need it should call
|
||||
/// [`Inner::capability`] explicitly.
|
||||
/// Opens the IMAP connection (TCP/TLS/STARTTLS, greeting, SASL),
|
||||
/// caching the capability list reported by the handshake and the
|
||||
/// `imap.sort.fallback` config override for later policy checks.
|
||||
pub fn new(config: ImapConfig) -> Result<Self> {
|
||||
let sort_fallback = config.sort.fallback;
|
||||
let tls = config.tls.into_tls(config.alpn);
|
||||
let auto_id = resolve_auto_id_params(&config.id)?;
|
||||
let server = parse_imap_server(&config.server)?;
|
||||
@@ -42,8 +44,21 @@ impl ImapClient {
|
||||
Some(cfg.try_into_sasl(host, port))
|
||||
})
|
||||
.transpose()?;
|
||||
let (inner, _capability) = Inner::connect(&server, &tls, config.starttls, sasl, auto_id)?;
|
||||
Ok(Self { inner })
|
||||
let (inner, capabilities) = Inner::connect(&server, &tls, config.starttls, sasl, auto_id)?;
|
||||
Ok(Self {
|
||||
inner,
|
||||
capabilities,
|
||||
sort_fallback,
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolves the SORT fallback policy: the `imap.sort.fallback`
|
||||
/// config override when set, otherwise on only when the server
|
||||
/// lacks the SORT capability. When `true`, sort client-side via
|
||||
/// SEARCH + FETCH instead of issuing a server `SORT`.
|
||||
pub fn sort_fallback(&self) -> bool {
|
||||
self.sort_fallback
|
||||
.unwrap_or_else(|| !has_imap_capability!(self.capabilities, Sort(_)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ use std::fmt;
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use io_imap::types::{
|
||||
core::Vec1,
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::ImapMessageFetchOptions, select::ImapMailboxSelectOptions},
|
||||
types::{
|
||||
core::Vec1,
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
},
|
||||
};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
@@ -47,14 +50,21 @@ impl ImapEnvelopeGetCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let item_names =
|
||||
MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::Envelope]);
|
||||
|
||||
let sequence_set = self.id.parse()?;
|
||||
let mut data = client.fetch(sequence_set, item_names, !self.seq)?;
|
||||
let mut data = client.fetch(
|
||||
sequence_set,
|
||||
item_names,
|
||||
ImapMessageFetchOptions {
|
||||
uid: !self.seq,
|
||||
modifiers: Vec::new(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let Some((_, items)) = data.pop_first() else {
|
||||
bail!("No envelope returned for ID {}", self.id);
|
||||
|
||||
@@ -3,12 +3,15 @@ use std::{collections::BTreeMap, fmt, num::NonZeroU32};
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
|
||||
use io_imap::types::{
|
||||
core::Vec1,
|
||||
envelope::Address,
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
sequence::{SeqOrUid, Sequence, SequenceSet},
|
||||
status::{StatusDataItem, StatusDataItemName},
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::ImapMessageFetchOptions, select::ImapMailboxSelectOptions},
|
||||
types::{
|
||||
core::Vec1,
|
||||
envelope::Address,
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
sequence::{SeqOrUid, Sequence, SequenceSet},
|
||||
status::{StatusDataItem, StatusDataItemName},
|
||||
},
|
||||
};
|
||||
use log::debug;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
@@ -66,7 +69,9 @@ impl ImapEnvelopeListCommand {
|
||||
_ => None,
|
||||
})
|
||||
} else {
|
||||
client.select(mailbox)?.exists
|
||||
client
|
||||
.select(mailbox, ImapMailboxSelectOptions::default())?
|
||||
.exists
|
||||
};
|
||||
|
||||
let mut has_sequence = false;
|
||||
@@ -86,7 +91,14 @@ impl ImapEnvelopeListCommand {
|
||||
MessageDataItemName::Envelope,
|
||||
]);
|
||||
|
||||
let data = client.fetch(sequence_set, item_names, !self.sequence && has_sequence)?;
|
||||
let data = client.fetch(
|
||||
sequence_set,
|
||||
item_names,
|
||||
ImapMessageFetchOptions {
|
||||
uid: !self.sequence && has_sequence,
|
||||
modifiers: Vec::new(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let table = EnvelopesTable {
|
||||
preset: account.table_preset().to_string(),
|
||||
|
||||
@@ -3,10 +3,13 @@ use std::fmt;
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Color, ContentArrangement, Row, Table};
|
||||
use io_imap::types::{
|
||||
core::{AString, Vec1},
|
||||
datetime::NaiveDate,
|
||||
search::SearchKey,
|
||||
use io_imap::{
|
||||
rfc3501::{search::ImapMessageSearchOptions, select::ImapMailboxSelectOptions},
|
||||
types::{
|
||||
core::{AString, Vec1},
|
||||
datetime::NaiveDate,
|
||||
search::SearchKey,
|
||||
},
|
||||
};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
@@ -68,11 +71,11 @@ impl ImapEnvelopeSearchCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let criteria = parse_query(&self.query)?;
|
||||
let ids = client.search(criteria, !self.seq)?;
|
||||
let ids = client.search(criteria, ImapMessageSearchOptions { uid: !self.seq })?;
|
||||
|
||||
let table = SearchTable {
|
||||
preset: account.table_preset().to_string(),
|
||||
|
||||
@@ -3,9 +3,13 @@ use std::fmt;
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Color, ContentArrangement, Row, Table, presets};
|
||||
use io_imap::types::{
|
||||
core::Vec1,
|
||||
extensions::sort::{SortCriterion, SortKey},
|
||||
use io_imap::{
|
||||
rfc3501::select::ImapMailboxSelectOptions,
|
||||
rfc5256::sort::ImapMessageSortOptions,
|
||||
types::{
|
||||
core::Vec1,
|
||||
extensions::sort::{SortCriterion, SortKey},
|
||||
},
|
||||
};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
@@ -60,7 +64,7 @@ impl ImapEnvelopeSortCommand {
|
||||
) -> Result<()> {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
|
||||
let sort_key = parse_sort_key(&self.sort)?;
|
||||
let sort_criteria = Vec1::unvalidated(vec![SortCriterion {
|
||||
@@ -69,7 +73,15 @@ impl ImapEnvelopeSortCommand {
|
||||
}]);
|
||||
let search_criteria = parse_query(&self.query)?;
|
||||
|
||||
let ids = client.sort(sort_criteria, search_criteria, !self.seq)?;
|
||||
let fallback = client.sort_fallback();
|
||||
let ids = client.sort(
|
||||
sort_criteria,
|
||||
search_criteria,
|
||||
ImapMessageSortOptions {
|
||||
uid: !self.seq,
|
||||
fallback,
|
||||
},
|
||||
)?;
|
||||
|
||||
let id_color = account.envelopes_list_table_id_color();
|
||||
let table = SortResultsTable::new(ids, !self.seq, id_color);
|
||||
|
||||
@@ -2,10 +2,14 @@ use std::{collections::HashMap, fmt, num::NonZeroU32};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use io_imap::types::{
|
||||
extensions::thread::{Thread, ThreadingAlgorithm},
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
sequence::SequenceSet,
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::ImapMessageFetchOptions, select::ImapMailboxSelectOptions},
|
||||
rfc5256::thread::ImapMessageThreadOptions,
|
||||
types::{
|
||||
extensions::thread::{Thread, ThreadingAlgorithm},
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
sequence::SequenceSet,
|
||||
},
|
||||
};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::{Serialize, Serializer, ser::SerializeStruct};
|
||||
@@ -49,13 +53,17 @@ impl ImapEnvelopeThreadCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let algorithm = parse_algorithm(&self.algorithm)?;
|
||||
let search_criteria = parse_query(&self.query)?;
|
||||
|
||||
let threads = client.thread(algorithm, search_criteria, !self.seq)?;
|
||||
let threads = client.thread(
|
||||
algorithm,
|
||||
search_criteria,
|
||||
ImapMessageThreadOptions { uid: !self.seq },
|
||||
)?;
|
||||
|
||||
let all_ids = collect_thread_ids(&threads);
|
||||
let subjects = if !all_ids.is_empty() {
|
||||
@@ -127,7 +135,14 @@ fn fetch_subjects(
|
||||
MessageDataItemName::Uid,
|
||||
]);
|
||||
|
||||
let data = client.fetch(sequence_set, item_names, uid)?;
|
||||
let data = client.fetch(
|
||||
sequence_set,
|
||||
item_names,
|
||||
ImapMessageFetchOptions {
|
||||
uid,
|
||||
modifiers: Vec::new(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut subjects: HashMap<u32, String> = HashMap::new();
|
||||
|
||||
|
||||
+13
-5
@@ -1,8 +1,11 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::types::{
|
||||
IntoStatic,
|
||||
flag::{Flag, StoreType},
|
||||
use io_imap::{
|
||||
rfc3501::{select::ImapMailboxSelectOptions, store::ImapMessageStoreOptions},
|
||||
types::{
|
||||
IntoStatic,
|
||||
flag::{Flag, StoreType},
|
||||
},
|
||||
};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
@@ -39,7 +42,7 @@ impl ImapFlagAddCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
@@ -49,7 +52,12 @@ impl ImapFlagAddCommand {
|
||||
.map(|f| Flag::try_from(f.as_str()).map(|flag| flag.into_static()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
client.store(sequence_set, StoreType::Add, flags, !self.seq)?;
|
||||
client.store(
|
||||
sequence_set,
|
||||
StoreType::Add,
|
||||
flags,
|
||||
ImapMessageStoreOptions { uid: !self.seq },
|
||||
)?;
|
||||
|
||||
printer.out(Message::new("Flag(s) successfully added"))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ use std::{collections::BTreeMap, fmt};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, ContentArrangement, Row, Table};
|
||||
use io_imap::types::flag::{Flag, FlagPerm};
|
||||
use io_imap::{
|
||||
rfc3501::select::ImapMailboxSelectOptions,
|
||||
types::flag::{Flag, FlagPerm},
|
||||
};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
@@ -30,7 +33,7 @@ impl ImapFlagListCommand {
|
||||
) -> Result<()> {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let data = client.select(mailbox)?;
|
||||
let data = client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
let flags = data.flags.unwrap_or_default();
|
||||
let permanent_flags = data.permanent_flags.unwrap_or_default();
|
||||
|
||||
|
||||
+13
-5
@@ -1,8 +1,11 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::types::{
|
||||
IntoStatic,
|
||||
flag::{Flag, StoreType},
|
||||
use io_imap::{
|
||||
rfc3501::{select::ImapMailboxSelectOptions, store::ImapMessageStoreOptions},
|
||||
types::{
|
||||
IntoStatic,
|
||||
flag::{Flag, StoreType},
|
||||
},
|
||||
};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
@@ -39,7 +42,7 @@ impl ImapFlagRemoveCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
@@ -49,7 +52,12 @@ impl ImapFlagRemoveCommand {
|
||||
.map(|f| Flag::try_from(f.as_str()).map(|flag| flag.into_static()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
client.store(sequence_set, StoreType::Remove, flags, !self.seq)?;
|
||||
client.store(
|
||||
sequence_set,
|
||||
StoreType::Remove,
|
||||
flags,
|
||||
ImapMessageStoreOptions { uid: !self.seq },
|
||||
)?;
|
||||
|
||||
printer.out(Message::new("Flag(s) successfully removed"))
|
||||
}
|
||||
|
||||
+13
-5
@@ -1,8 +1,11 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::types::{
|
||||
IntoStatic,
|
||||
flag::{Flag, StoreType},
|
||||
use io_imap::{
|
||||
rfc3501::{select::ImapMailboxSelectOptions, store::ImapMessageStoreOptions},
|
||||
types::{
|
||||
IntoStatic,
|
||||
flag::{Flag, StoreType},
|
||||
},
|
||||
};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
@@ -39,7 +42,7 @@ impl ImapFlagSetCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
@@ -49,7 +52,12 @@ impl ImapFlagSetCommand {
|
||||
.map(|f| Flag::try_from(f.as_str()).map(|flag| flag.into_static()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
client.store(sequence_set, StoreType::Replace, flags, !self.seq)?;
|
||||
client.store(
|
||||
sequence_set,
|
||||
StoreType::Replace,
|
||||
flags,
|
||||
ImapMessageStoreOptions { uid: !self.seq },
|
||||
)?;
|
||||
|
||||
printer.out(Message::new("Flag(s) successfully replaced"))
|
||||
}
|
||||
|
||||
+9
-4
@@ -3,9 +3,12 @@ use std::{collections::HashMap, fmt};
|
||||
use anyhow::{Result, anyhow};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use io_imap::types::{
|
||||
IntoStatic,
|
||||
core::{IString, NString},
|
||||
use io_imap::{
|
||||
rfc2971::id::ImapServerIdOptions,
|
||||
types::{
|
||||
IntoStatic,
|
||||
core::{IString, NString},
|
||||
},
|
||||
};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
@@ -44,7 +47,9 @@ impl ImapIdCommand {
|
||||
params.extend(more);
|
||||
}
|
||||
|
||||
let params = client.id(Some(params.into_iter().collect()))?;
|
||||
let params = client.id(ImapServerIdOptions {
|
||||
parameters: Some(params.into_iter().collect()),
|
||||
})?;
|
||||
|
||||
let table = ServerIdTable {
|
||||
preset: account.table_preset().to_string(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::select::ImapMailboxSelectOptions;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
@@ -24,7 +25,7 @@ impl ImapMailboxExpungeCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
client.expunge()?;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::types::flag::{Flag, StoreType};
|
||||
use io_imap::{
|
||||
rfc3501::{select::ImapMailboxSelectOptions, store::ImapMessageStoreOptions},
|
||||
types::flag::{Flag, StoreType},
|
||||
};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
@@ -26,14 +29,14 @@ impl ImapMailboxPurgeCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
client.store(
|
||||
"1:*".try_into()?,
|
||||
StoreType::Add,
|
||||
vec![Flag::Deleted],
|
||||
false,
|
||||
ImapMessageStoreOptions { uid: false },
|
||||
)?;
|
||||
client.expunge()?;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::select::ImapMailboxSelectOptions;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{client::ImapClient, mailbox::arg::MailboxNameArg};
|
||||
@@ -21,7 +22,7 @@ pub struct ImapMailboxSelectCommand {
|
||||
impl ImapMailboxSelectCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
printer.out(Message::new("Mailbox successfully selected"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::types::mailbox::Mailbox;
|
||||
use io_imap::{
|
||||
rfc3501::{copy::ImapMessageCopyOptions, select::ImapMailboxSelectOptions},
|
||||
types::mailbox::Mailbox,
|
||||
};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
@@ -35,13 +38,17 @@ impl ImapMessageCopyCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
let destination: Mailbox = self.mailbox_dest_name.inner.try_into()?;
|
||||
|
||||
client.copy(sequence_set, destination, !self.seq)?;
|
||||
client.copy(
|
||||
sequence_set,
|
||||
destination,
|
||||
ImapMessageCopyOptions { uid: !self.seq },
|
||||
)?;
|
||||
|
||||
printer.out(Message::new("Message(s) successfully copied"))
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ use std::{
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use io_imap::types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName};
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::ImapMessageFetchOptions, select::ImapMailboxSelectOptions},
|
||||
types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
};
|
||||
use mail_parser::{MessageParser, MimeHeaders};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
@@ -65,7 +68,7 @@ impl ImapMessageExportCommand {
|
||||
) -> Result<()> {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
|
||||
if self.id == 0 {
|
||||
bail!("Export message error: ID must be non-zero");
|
||||
@@ -79,7 +82,14 @@ impl ImapMessageExportCommand {
|
||||
}]);
|
||||
|
||||
let sequence_set = self.id.to_string().parse()?;
|
||||
let mut data = client.fetch(sequence_set, item_names, !self.seq)?;
|
||||
let mut data = client.fetch(
|
||||
sequence_set,
|
||||
item_names,
|
||||
ImapMessageFetchOptions {
|
||||
uid: !self.seq,
|
||||
modifiers: Vec::new(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let Some((_, items)) = data.pop_first() else {
|
||||
bail!(
|
||||
|
||||
+13
-3
@@ -3,7 +3,10 @@ use std::fmt;
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, ContentArrangement, Row, Table, presets};
|
||||
use io_imap::types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName};
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::ImapMessageFetchOptions, select::ImapMailboxSelectOptions},
|
||||
types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
};
|
||||
use mail_parser::{Addr, Address, ContentType, Message, MessageParser, MimeHeaders};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
@@ -36,7 +39,7 @@ impl ImapMessageGetCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let item_names =
|
||||
@@ -47,7 +50,14 @@ impl ImapMessageGetCommand {
|
||||
}]);
|
||||
|
||||
let sequence_set = self.id.parse()?;
|
||||
let mut data = client.fetch(sequence_set, item_names, !self.seq)?;
|
||||
let mut data = client.fetch(
|
||||
sequence_set,
|
||||
item_names,
|
||||
ImapMessageFetchOptions {
|
||||
uid: !self.seq,
|
||||
modifiers: Vec::new(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let Some((_, items)) = data.pop_first() else {
|
||||
bail!("Get message `{}` error: no message data returned", self.id);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::types::mailbox::Mailbox;
|
||||
use io_imap::{
|
||||
rfc3501::select::ImapMailboxSelectOptions, rfc6851::r#move::ImapMessageMoveOptions,
|
||||
types::mailbox::Mailbox,
|
||||
};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
@@ -36,13 +39,17 @@ impl ImapMessageMoveCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
let destination: Mailbox<'static> = self.mailbox_dest_name.inner.try_into()?;
|
||||
|
||||
client.r#move(sequence_set, destination, !self.seq)?;
|
||||
client.r#move(
|
||||
sequence_set,
|
||||
destination,
|
||||
ImapMessageMoveOptions { uid: !self.seq },
|
||||
)?;
|
||||
|
||||
printer.out(Message::new("Message(s) successfully moved"))
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ use std::fmt;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use clap::Parser;
|
||||
use io_imap::types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName};
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::ImapMessageFetchOptions, select::ImapMailboxSelectOptions},
|
||||
types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
};
|
||||
use mail_parser::{Message, MessageParser};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
@@ -41,7 +44,7 @@ impl ImapMessageReadCommand {
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
client.select(mailbox)?;
|
||||
client.select(mailbox, ImapMailboxSelectOptions::default())?;
|
||||
}
|
||||
|
||||
let item_names =
|
||||
@@ -52,7 +55,14 @@ impl ImapMessageReadCommand {
|
||||
}]);
|
||||
|
||||
let sequence_set = self.id.parse()?;
|
||||
let mut data = client.fetch(sequence_set, item_names, !self.seq)?;
|
||||
let mut data = client.fetch(
|
||||
sequence_set,
|
||||
item_names,
|
||||
ImapMessageFetchOptions {
|
||||
uid: !self.seq,
|
||||
modifiers: Vec::new(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let Some((_, items)) = data.pop_first() else {
|
||||
bail!("Read message `{}` error: no message data returned", self.id);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::types::{
|
||||
IntoStatic, core::Literal, extensions::binary::LiteralOrLiteral8, flag::Flag, mailbox::Mailbox,
|
||||
use io_imap::{
|
||||
rfc3501::append::ImapMessageAppendOptions,
|
||||
types::{IntoStatic, flag::Flag, mailbox::Mailbox},
|
||||
};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
@@ -32,17 +33,23 @@ impl ImapMessageSaveCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, client: &mut ImapClient) -> Result<()> {
|
||||
let mailbox: Mailbox<'static> = self.mailbox.inner.try_into()?;
|
||||
let message = self.message.parse()?;
|
||||
let message = Literal::try_from(message)?;
|
||||
let message = LiteralOrLiteral8::Literal(message);
|
||||
|
||||
let flags: Vec<_> = self
|
||||
let flags: Vec<Flag<'static>> = self
|
||||
.flag
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.map(|f| Flag::try_from(f).map(IntoStatic::into_static))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
client.append(mailbox, flags, None, message)?;
|
||||
client.append(
|
||||
mailbox,
|
||||
message.as_bytes(),
|
||||
ImapMessageAppendOptions {
|
||||
flags,
|
||||
date: None,
|
||||
non_sync: false,
|
||||
},
|
||||
)?;
|
||||
|
||||
printer.out(Message::new("Message successfully saved"))
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ pub fn imap_to_config(w: WizardImapConfig) -> Result<ImapConfig> {
|
||||
alpn: io_imap::client::default_alpn(),
|
||||
sasl,
|
||||
id: Default::default(),
|
||||
sort: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user