mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-17 13:17:55 +08:00
use std clients
This commit is contained in:
+9
-5
@@ -1,17 +1,21 @@
|
||||
use anyhow::Result;
|
||||
use pimalaya_toolbox::stream::imap::ImapSession;
|
||||
use io_imap::client::ImapClient;
|
||||
|
||||
use crate::{account::Account, config::ImapConfig};
|
||||
use crate::{account::Account, config::ImapConfig, imap::session::ImapSession};
|
||||
|
||||
pub type ImapAccount = Account<ImapConfig>;
|
||||
|
||||
impl ImapAccount {
|
||||
pub fn new_imap_session(&self) -> Result<ImapSession> {
|
||||
ImapSession::new(
|
||||
/// Opens the IMAP connection (TCP/TLS/STARTTLS, greeting, SASL),
|
||||
/// then hands the established stream and context off to a fresh
|
||||
/// [`ImapClient`].
|
||||
pub fn new_imap_client(&self) -> Result<ImapClient> {
|
||||
let session = ImapSession::new(
|
||||
self.backend.url.clone(),
|
||||
self.backend.tls.clone().try_into()?,
|
||||
self.backend.starttls,
|
||||
self.backend.sasl.clone().try_into()?,
|
||||
)
|
||||
)?;
|
||||
Ok(ImapClient::from_parts(session.stream, session.context))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount, envelope::command::ImapEnvelopeCommand, flag::command::ImapFlagCommand,
|
||||
id::ImapIdCommand, mailbox::command::ImapMailboxCommand, message::command::ImapMessageCommand,
|
||||
account::ImapAccount, envelope::cli::ImapEnvelopeCommand, flag::cli::ImapFlagCommand,
|
||||
id::ImapIdCommand, mailbox::cli::ImapMailboxCommand, message::cli::ImapMessageCommand,
|
||||
};
|
||||
|
||||
/// IMAP CLI (requires the `imap` cargo feature).
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
+13
-51
@@ -1,20 +1,13 @@
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
num::NonZeroU32,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::*, select::*},
|
||||
types::{
|
||||
core::Vec1,
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
},
|
||||
use io_imap::types::{
|
||||
core::Vec1,
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::imap::{
|
||||
@@ -23,8 +16,6 @@ use crate::imap::{
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Get a single IMAP envelope.
|
||||
///
|
||||
/// This command displays detailed envelope information for a specific
|
||||
@@ -47,54 +38,25 @@ pub struct ImapEnvelopeGetCommand {
|
||||
|
||||
impl ImapEnvelopeGetCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let Some(id) = NonZeroU32::new(self.id) else {
|
||||
if self.id == 0 {
|
||||
bail!("ID must be non-zero");
|
||||
};
|
||||
}
|
||||
|
||||
let item_names =
|
||||
MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::Envelope]);
|
||||
|
||||
let mut coroutine = ImapMessageFetchFirst::new(imap.context, id, item_names, !self.seq);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
let sequence_set = self.id.to_string().parse()?;
|
||||
let mut data = client.fetch(sequence_set, item_names, !self.seq)?;
|
||||
|
||||
let items = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageFetchFirstResult::Ok { items, .. } => break items,
|
||||
ImapMessageFetchFirstResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageFetchFirstResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageFetchFirstResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
let Some((_, items)) = data.pop_first() else {
|
||||
bail!("No envelope returned for ID {}", self.id);
|
||||
};
|
||||
|
||||
let table = EnvelopeTable {
|
||||
|
||||
+16
-87
@@ -1,25 +1,17 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
num::NonZeroU32,
|
||||
};
|
||||
use std::{collections::BTreeMap, fmt, num::NonZeroU32};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, ContentArrangement, Row, Table};
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::*, select::*, status::*},
|
||||
types::{
|
||||
core::Vec1,
|
||||
envelope::Address,
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
sequence::{SeqOrUid, Sequence, SequenceSet},
|
||||
status::{StatusDataItem, StatusDataItemName},
|
||||
},
|
||||
use io_imap::types::{
|
||||
core::Vec1,
|
||||
envelope::Address,
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
sequence::{SeqOrUid, Sequence, SequenceSet},
|
||||
status::{StatusDataItem, StatusDataItemName},
|
||||
};
|
||||
use log::debug;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use rfc2047_decoder::{Decoder, RecoverStrategy};
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -28,8 +20,6 @@ use crate::imap::{
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// List IMAP envelopes from the given mailbox.
|
||||
///
|
||||
/// This command displays envelopes for messages in the specified
|
||||
@@ -61,57 +51,17 @@ pub struct ImapEnvelopeListCommand {
|
||||
|
||||
impl ImapEnvelopeListCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
let exists = if self.mailbox_no_select.inner {
|
||||
let mut coroutine =
|
||||
ImapMailboxStatus::new(imap.context, mailbox, &[StatusDataItemName::Messages]);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxStatusResult::Ok { context, items } => {
|
||||
imap.context = context;
|
||||
break items.into_iter().find_map(|i| match i {
|
||||
StatusDataItem::Messages(exists) => Some(exists),
|
||||
_ => None,
|
||||
});
|
||||
}
|
||||
ImapMailboxStatusResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxStatusResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxStatusResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
let items = client.status(mailbox, &[StatusDataItemName::Messages])?;
|
||||
items.into_iter().find_map(|i| match i {
|
||||
StatusDataItem::Messages(exists) => Some(exists),
|
||||
_ => None,
|
||||
})
|
||||
} else {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, data } => {
|
||||
imap.context = context;
|
||||
break data.exists;
|
||||
}
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.select(mailbox)?.exists
|
||||
};
|
||||
|
||||
let mut has_sequence = false;
|
||||
@@ -131,28 +81,7 @@ impl ImapEnvelopeListCommand {
|
||||
MessageDataItemName::Envelope,
|
||||
]);
|
||||
|
||||
let mut coroutine = ImapMessageFetch::new(
|
||||
imap.context,
|
||||
sequence_set,
|
||||
item_names,
|
||||
!self.sequence && has_sequence,
|
||||
);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let data = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageFetchResult::Ok { data, .. } => break data,
|
||||
ImapMessageFetchResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageFetchResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageFetchResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
let data = client.fetch(sequence_set, item_names, !self.sequence && has_sequence)?;
|
||||
|
||||
let table = EnvelopesTable {
|
||||
preset: account.table_preset,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod command;
|
||||
pub mod cli;
|
||||
pub mod get;
|
||||
pub mod list;
|
||||
pub mod search;
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, ContentArrangement, Row, Table};
|
||||
use io_imap::{
|
||||
rfc3501::{search::*, select::*},
|
||||
types::{
|
||||
core::{AString, Vec1},
|
||||
datetime::NaiveDate,
|
||||
search::SearchKey,
|
||||
},
|
||||
use io_imap::types::{
|
||||
core::{AString, Vec1},
|
||||
datetime::NaiveDate,
|
||||
search::SearchKey,
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::imap::{
|
||||
@@ -22,8 +16,6 @@ use crate::imap::{
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Search IMAP messages by criteria.
|
||||
///
|
||||
/// This command searches for messages matching the given criteria and
|
||||
@@ -67,50 +59,15 @@ pub struct ImapEnvelopeSearchCommand {
|
||||
|
||||
impl ImapEnvelopeSearchCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let criteria = parse_query(&self.query)?;
|
||||
|
||||
let mut coroutine = ImapMessageSearch::new(imap.context, criteria, !self.seq);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let ids = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageSearchResult::Ok { ids, .. } => break ids,
|
||||
ImapMessageSearchResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageSearchResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageSearchResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
let ids = client.search(criteria, !self.seq)?;
|
||||
|
||||
let table = SearchTable {
|
||||
preset: account.table_preset,
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{presets, Cell, ContentArrangement, Row, Table};
|
||||
use io_imap::{
|
||||
rfc3501::select::*,
|
||||
rfc5256::sort::*,
|
||||
types::{
|
||||
core::Vec1,
|
||||
extensions::sort::{SortCriterion, SortKey},
|
||||
},
|
||||
use io_imap::types::{
|
||||
core::Vec1,
|
||||
extensions::sort::{SortCriterion, SortKey},
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount, envelope::search::parse_query, mailbox::arg::MailboxNameOptionalArg,
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Sort messages by criteria.
|
||||
///
|
||||
/// This command searches for messages matching the given query and
|
||||
@@ -61,59 +52,19 @@ pub struct ImapEnvelopeSortCommand {
|
||||
|
||||
impl ImapEnvelopeSortCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
client.select(mailbox)?;
|
||||
|
||||
// SELECT mailbox
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
// Parse sort criteria
|
||||
let sort_key = parse_sort_key(&self.sort)?;
|
||||
let sort_criteria = Vec1::unvalidated(vec![SortCriterion {
|
||||
reverse: self.reverse,
|
||||
key: sort_key,
|
||||
}]);
|
||||
|
||||
// Parse search criteria
|
||||
let search_criteria = parse_query(&self.query)?;
|
||||
|
||||
// SORT
|
||||
let mut coroutine =
|
||||
ImapMailboxSort::new(context, sort_criteria, search_criteria, !self.seq);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let ids = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSortResult::Ok { ids, .. } => break ids,
|
||||
ImapMailboxSortResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSortResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSortResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
let ids = client.sort(sort_criteria, search_criteria, !self.seq)?;
|
||||
|
||||
let table = SortResultsTable::new(ids, !self.seq);
|
||||
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
num::NonZeroU32,
|
||||
};
|
||||
use std::{collections::HashMap, fmt, num::NonZeroU32};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::*, select::*},
|
||||
rfc5256::thread::*,
|
||||
client::ImapClient,
|
||||
types::{
|
||||
extensions::thread::{Thread, ThreadingAlgorithm},
|
||||
fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
sequence::SequenceSet,
|
||||
},
|
||||
};
|
||||
use pimalaya_toolbox::{stream::imap::ImapSession, terminal::printer::Printer};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::{ser::SerializeStruct, Serialize, Serializer};
|
||||
|
||||
use crate::imap::{
|
||||
@@ -25,8 +19,6 @@ use crate::imap::{
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Thread IMAP messages by algorithm.
|
||||
///
|
||||
/// This command groups messages into conversation threads using the
|
||||
@@ -57,64 +49,21 @@ pub struct ImapEnvelopeThreadCommand {
|
||||
|
||||
impl ImapEnvelopeThreadCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let algorithm = parse_algorithm(&self.algorithm)?;
|
||||
let search_criteria = parse_query(&self.query)?;
|
||||
|
||||
let mut coroutine =
|
||||
ImapMessageThread::new(imap.context, algorithm, search_criteria, !self.seq);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
let threads = client.thread(algorithm, search_criteria, !self.seq)?;
|
||||
|
||||
let threads = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageThreadResult::Ok {
|
||||
context, threads, ..
|
||||
} => {
|
||||
imap.context = context;
|
||||
break threads;
|
||||
}
|
||||
ImapMessageThreadResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageThreadResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageThreadResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
// Collect all message IDs from threads to fetch subjects
|
||||
let all_ids = collect_thread_ids(&threads);
|
||||
|
||||
// Fetch subjects for all messages in threads
|
||||
let subjects = if !all_ids.is_empty() {
|
||||
fetch_subjects(imap, &all_ids, !self.seq)?
|
||||
fetch_subjects(&mut client, &all_ids, !self.seq)?
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
@@ -162,7 +111,7 @@ fn collect_thread_ids_recursive(thread: &Thread, ids: &mut Vec<NonZeroU32>) {
|
||||
}
|
||||
|
||||
fn fetch_subjects(
|
||||
mut imap: ImapSession,
|
||||
client: &mut ImapClient,
|
||||
ids: &[NonZeroU32],
|
||||
uid: bool,
|
||||
) -> Result<HashMap<u32, String>> {
|
||||
@@ -170,7 +119,6 @@ fn fetch_subjects(
|
||||
return Ok(HashMap::new());
|
||||
}
|
||||
|
||||
// Build sequence set from IDs
|
||||
let seq_set_str = ids
|
||||
.iter()
|
||||
.map(|id| id.to_string())
|
||||
@@ -183,24 +131,7 @@ fn fetch_subjects(
|
||||
MessageDataItemName::Uid,
|
||||
]);
|
||||
|
||||
let mut coroutine = ImapMessageFetch::new(imap.context, sequence_set, item_names, uid);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let data = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageFetchResult::Ok { data, .. } => break data,
|
||||
ImapMessageFetchResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageFetchResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageFetchResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
let data = client.fetch(sequence_set, item_names, uid)?;
|
||||
|
||||
let mut subjects: HashMap<u32, String> = HashMap::new();
|
||||
|
||||
|
||||
+8
-55
@@ -1,23 +1,16 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
rfc3501::{select::*, store::*},
|
||||
types::{
|
||||
flag::{Flag, StoreType},
|
||||
IntoStatic,
|
||||
},
|
||||
use io_imap::types::{
|
||||
flag::{Flag, StoreType},
|
||||
IntoStatic,
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Add IMAP flag(s) to message(s).
|
||||
///
|
||||
/// This command adds the given flags to messages identified by the
|
||||
@@ -43,29 +36,11 @@ pub struct ImapFlagAddCommand {
|
||||
|
||||
impl ImapFlagAddCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
@@ -75,29 +50,7 @@ impl ImapFlagAddCommand {
|
||||
.map(|f| Flag::try_from(f.as_str()).map(|flag| flag.into_static()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let mut coroutine = ImapMessageStoreSilent::new(
|
||||
imap.context,
|
||||
sequence_set,
|
||||
StoreType::Add,
|
||||
flags,
|
||||
!self.seq,
|
||||
);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageStoreSilentResult::Ok(_) => break,
|
||||
ImapMessageStoreSilentResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageStoreSilentResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageStoreSilentResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.store(sequence_set, StoreType::Add, flags, !self.seq)?;
|
||||
|
||||
printer.out(Message::new("Flag(s) successfully added"))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
+8
-37
@@ -1,23 +1,14 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::{collections::BTreeMap, fmt};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, ContentArrangement, Row, Table};
|
||||
use io_imap::{
|
||||
rfc3501::select::*,
|
||||
types::flag::{Flag, FlagPerm},
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use io_imap::types::flag::{Flag, FlagPerm};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// List available IMAP flags for the given mailbox.
|
||||
///
|
||||
/// This command displays the flags and permanent flags that are
|
||||
@@ -31,32 +22,12 @@ pub struct ImapFlagListCommand {
|
||||
|
||||
impl ImapFlagListCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let (flags, permanent_flags) = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { data, .. } => {
|
||||
break (
|
||||
data.flags.unwrap_or_default(),
|
||||
data.permanent_flags.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
let data = client.select(mailbox)?;
|
||||
let flags = data.flags.unwrap_or_default();
|
||||
let permanent_flags = data.permanent_flags.unwrap_or_default();
|
||||
|
||||
let table = FlagsTable {
|
||||
preset: account.table_preset,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod add;
|
||||
pub mod command;
|
||||
pub mod cli;
|
||||
pub mod list;
|
||||
pub mod remove;
|
||||
pub mod set;
|
||||
|
||||
+8
-55
@@ -1,23 +1,16 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
rfc3501::{select::*, store::*},
|
||||
types::{
|
||||
flag::{Flag, StoreType},
|
||||
IntoStatic,
|
||||
},
|
||||
use io_imap::types::{
|
||||
flag::{Flag, StoreType},
|
||||
IntoStatic,
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Remove IMAP flag(s) from message(s).
|
||||
///
|
||||
/// This command removes the specified flag(s) from message(s)
|
||||
@@ -43,29 +36,11 @@ pub struct ImapFlagRemoveCommand {
|
||||
|
||||
impl ImapFlagRemoveCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
@@ -75,29 +50,7 @@ impl ImapFlagRemoveCommand {
|
||||
.map(|f| Flag::try_from(f.as_str()).map(|flag| flag.into_static()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let mut coroutine = ImapMessageStoreSilent::new(
|
||||
imap.context,
|
||||
sequence_set,
|
||||
StoreType::Remove,
|
||||
flags,
|
||||
!self.seq,
|
||||
);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageStoreSilentResult::Ok(_) => break,
|
||||
ImapMessageStoreSilentResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageStoreSilentResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageStoreSilentResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.store(sequence_set, StoreType::Remove, flags, !self.seq)?;
|
||||
|
||||
printer.out(Message::new("Flag(s) successfully removed"))
|
||||
}
|
||||
|
||||
+8
-55
@@ -1,23 +1,16 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
rfc3501::{select::*, store::*},
|
||||
types::{
|
||||
flag::{Flag, StoreType},
|
||||
IntoStatic,
|
||||
},
|
||||
use io_imap::types::{
|
||||
flag::{Flag, StoreType},
|
||||
IntoStatic,
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Set IMAP flag(s) on message(s), replacing any existing flags.
|
||||
///
|
||||
/// This command replaces all existing flags on messages identified by
|
||||
@@ -43,29 +36,11 @@ pub struct ImapFlagSetCommand {
|
||||
|
||||
impl ImapFlagSetCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
@@ -75,29 +50,7 @@ impl ImapFlagSetCommand {
|
||||
.map(|f| Flag::try_from(f.as_str()).map(|flag| flag.into_static()))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let mut coroutine = ImapMessageStoreSilent::new(
|
||||
imap.context,
|
||||
sequence_set,
|
||||
StoreType::Replace,
|
||||
flags,
|
||||
!self.seq,
|
||||
);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageStoreSilentResult::Ok(_) => break,
|
||||
ImapMessageStoreSilentResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageStoreSilentResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageStoreSilentResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.store(sequence_set, StoreType::Replace, flags, !self.seq)?;
|
||||
|
||||
printer.out(Message::new("Flag(s) successfully replaced"))
|
||||
}
|
||||
|
||||
+8
-34
@@ -1,26 +1,17 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use io_imap::{
|
||||
rfc2971::id::*,
|
||||
types::{
|
||||
core::{IString, NString},
|
||||
IntoStatic,
|
||||
},
|
||||
use io_imap::types::{
|
||||
core::{IString, NString},
|
||||
IntoStatic,
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::imap::account::ImapAccount;
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Get information about the IMAP server.
|
||||
///
|
||||
/// This command allows you to exchange parameters with the IMAP
|
||||
@@ -37,7 +28,7 @@ pub struct ImapIdCommand {
|
||||
|
||||
impl ImapIdCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mut params = HashMap::new();
|
||||
|
||||
params.extend([
|
||||
@@ -63,24 +54,7 @@ impl ImapIdCommand {
|
||||
params.extend(more);
|
||||
}
|
||||
|
||||
let mut coroutine = ImapServerId::new(imap.context, Some(params.into_iter().collect()));
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let params = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapServerIdResult::Ok { server_id, .. } => break server_id,
|
||||
ImapServerIdResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapServerIdResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapServerIdResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
let params = client.id(Some(params.into_iter().collect()))?;
|
||||
|
||||
let table = ServerIdTable {
|
||||
preset: account.table_preset,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
@@ -1,14 +1,9 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::close::*;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::account::ImapAccount;
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Close the current, selected mailbox.
|
||||
///
|
||||
/// This command also expunges the current mailbox and returns to the
|
||||
@@ -23,27 +18,8 @@ pub struct ImapMailboxCloseCommand;
|
||||
|
||||
impl ImapMailboxCloseCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
|
||||
let mut close_coroutine = ImapMailboxClose::new(imap.context);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match close_coroutine.resume(arg.take()) {
|
||||
ImapMailboxCloseResult::Ok(_) => break,
|
||||
ImapMailboxCloseResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxCloseResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxCloseResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
|
||||
let mut client = account.new_imap_client()?;
|
||||
client.close()?;
|
||||
printer.out(Message::new("Mailbox successfully closed"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::create::*;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Create the given mailbox.
|
||||
///
|
||||
/// This command allows you to create a new mailbox using the given
|
||||
@@ -21,29 +16,9 @@ pub struct ImapMailboxCreateCommand {
|
||||
|
||||
impl ImapMailboxCreateCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut coroutine = ImapMailboxCreate::new(imap.context, mailbox);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxCreateResult::Ok(_) => break,
|
||||
ImapMailboxCreateResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxCreateResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxCreateResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
|
||||
client.create(mailbox)?;
|
||||
printer.out(Message::new("Mailbox successfully created"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::delete::*;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Delete the given mailbox.
|
||||
///
|
||||
/// All emails from the given mailbox are definitely deleted. The
|
||||
@@ -21,28 +16,9 @@ pub struct ImapMailboxDeleteCommand {
|
||||
|
||||
impl ImapMailboxDeleteCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut coroutine = ImapMailboxDelete::new(imap.context, mailbox);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxDeleteResult::Ok(_) => break,
|
||||
ImapMailboxDeleteResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxDeleteResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxDeleteResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
|
||||
client.delete(mailbox)?;
|
||||
printer.out(Message::new("Mailbox successfully deleted"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::{expunge::*, select::*};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
mailbox::arg::{MailboxNameArg, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Expunge the given mailbox.
|
||||
///
|
||||
/// All envelopes with the \Deleted flag will be definitely removed
|
||||
@@ -26,48 +21,14 @@ pub struct ImapMailboxExpungeCommand {
|
||||
|
||||
impl ImapMailboxExpungeCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let mut coroutine = ImapMailboxExpunge::new(imap.context);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxExpungeResult::Ok { .. } => break,
|
||||
ImapMailboxExpungeResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxExpungeResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxExpungeResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.expunge()?;
|
||||
|
||||
printer.out(Message::new("Mailbox successfully expunged"))
|
||||
}
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use io_imap::{
|
||||
rfc3501::{list::*, lsub::*},
|
||||
types::{core::QuotedChar, flag::FlagNameAttribute, mailbox::Mailbox},
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use io_imap::types::{core::QuotedChar, flag::FlagNameAttribute, mailbox::Mailbox};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::imap::account::ImapAccount;
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// List, search and filter mailboxes.
|
||||
///
|
||||
/// This command allows you to list mailboxes from your IMAP account.
|
||||
@@ -39,48 +31,14 @@ pub struct ImapMailboxListCommand {
|
||||
|
||||
impl ImapMailboxListCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let reference = self.reference.try_into()?;
|
||||
let pattern = self.pattern.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
let mailboxes = if self.all {
|
||||
let mut coroutine = ImapMailboxList::new(imap.context, reference, pattern);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxListResult::Ok { mailboxes, .. } => break mailboxes,
|
||||
ImapMailboxListResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxListResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxListResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.list(reference, pattern)?
|
||||
} else {
|
||||
let mut coroutine = ImapMailboxLsub::new(imap.context, reference, pattern);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxLsubResult::Ok { mailboxes, .. } => break mailboxes,
|
||||
ImapMailboxLsubResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxLsubResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxLsubResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.lsub(reference, pattern)?
|
||||
};
|
||||
|
||||
let table = MailboxesTable {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub mod arg;
|
||||
pub mod cli;
|
||||
pub mod close;
|
||||
pub mod command;
|
||||
pub mod create;
|
||||
pub mod delete;
|
||||
pub mod expunge;
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
rfc3501::{expunge::*, select::*, store::*},
|
||||
types::flag::{Flag, StoreType},
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use io_imap::types::flag::{Flag, StoreType};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
mailbox::arg::{MailboxNameArg, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Shortcut for marking as deleted all envelopes then expunging the
|
||||
/// given mailbox.
|
||||
///
|
||||
@@ -30,72 +23,20 @@ pub struct ImapMailboxPurgeCommand {
|
||||
|
||||
impl ImapMailboxPurgeCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let mut coroutine = ImapMessageStoreSilent::new(
|
||||
imap.context,
|
||||
client.store(
|
||||
"1:*".try_into()?,
|
||||
StoreType::Add,
|
||||
vec![Flag::Deleted],
|
||||
false,
|
||||
);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageStoreSilentResult::Ok(context) => break context,
|
||||
ImapMessageStoreSilentResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageStoreSilentResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageStoreSilentResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
let mut coroutine = ImapMailboxExpunge::new(imap.context);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxExpungeResult::Ok { .. } => break,
|
||||
ImapMailboxExpungeResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxExpungeResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxExpungeResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
)?;
|
||||
client.expunge()?;
|
||||
|
||||
printer.out(Message::new("Mailbox successfully purged"))
|
||||
}
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::rename::*;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
mailbox::arg::{MailboxNameArg, TargetMailboxNameArg},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Rename the given mailbox.
|
||||
///
|
||||
/// This command renames an existing mailbox to a new name.
|
||||
@@ -25,29 +20,10 @@ pub struct ImapMailboxRenameCommand {
|
||||
|
||||
impl ImapMailboxRenameCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let from = self.mailbox_source_name.inner.try_into()?;
|
||||
let to = self.mailbox_dest_name.inner.try_into()?;
|
||||
|
||||
let mut coroutine = ImapMailboxRename::new(imap.context, from, to);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxRenameResult::Ok(_) => break,
|
||||
ImapMailboxRenameResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxRenameResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxRenameResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
|
||||
client.rename(from, to)?;
|
||||
printer.out(Message::new("Mailbox successfully renamed"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::select::*;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Select the given mailbox.
|
||||
///
|
||||
/// This command permanently removes all messages with the \Deleted
|
||||
@@ -25,28 +20,9 @@ pub struct ImapMailboxSelectCommand {
|
||||
|
||||
impl ImapMailboxSelectCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { .. } => break,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
|
||||
client.select(mailbox)?;
|
||||
printer.out(Message::new("Mailbox successfully selected"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use io_imap::{
|
||||
rfc3501::status::*,
|
||||
types::status::{StatusDataItem, StatusDataItemName},
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use io_imap::types::status::{StatusDataItem, StatusDataItemName};
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Get the status of the given mailbox.
|
||||
///
|
||||
/// This command displays status information about a mailbox,
|
||||
@@ -29,7 +21,7 @@ pub struct ImapMailboxStatusCommand {
|
||||
|
||||
impl ImapMailboxStatusCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
let item_names = vec![
|
||||
StatusDataItemName::Messages,
|
||||
@@ -39,24 +31,7 @@ impl ImapMailboxStatusCommand {
|
||||
StatusDataItemName::UidValidity,
|
||||
];
|
||||
|
||||
let mut coroutine = ImapMailboxStatus::new(imap.context, mailbox, item_names);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let items = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxStatusResult::Ok { items, .. } => break items,
|
||||
ImapMailboxStatusResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxStatusResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxStatusResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
let items = client.status(mailbox, item_names)?;
|
||||
|
||||
let table = MailboxStatusTable {
|
||||
preset: account.table_preset,
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::subscribe::*;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Subscribe to the given mailbox.
|
||||
///
|
||||
/// This command subscribes to a mailbox, making it appear in the
|
||||
@@ -21,28 +16,9 @@ pub struct ImapMailboxSubscribeCommand {
|
||||
|
||||
impl ImapMailboxSubscribeCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut coroutine = ImapMailboxSubscribe::new(imap.context, mailbox);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSubscribeResult::Ok(_) => break,
|
||||
ImapMailboxSubscribeResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSubscribeResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSubscribeResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
|
||||
client.subscribe(mailbox)?;
|
||||
printer.out(Message::new("Mailbox successfully subscribed"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3691::unselect::*;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::account::ImapAccount;
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Unselect a current, selected mailbox.
|
||||
///
|
||||
/// Unlike CLOSE, UNSELECT does not expunge deleted messages.
|
||||
@@ -22,27 +17,8 @@ pub struct ImapMailboxUnselectCommand;
|
||||
|
||||
impl ImapMailboxUnselectCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
|
||||
let mut coroutine = ImapMailboxUnselect::new(imap.context);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxUnselectResult::Ok(_) => break,
|
||||
ImapMailboxUnselectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxUnselectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxUnselectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
|
||||
let mut client = account.new_imap_client()?;
|
||||
client.unselect()?;
|
||||
printer.out(Message::new("Mailbox successfully unselected"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::rfc3501::unsubscribe::*;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Unsubscribe from the given mailbox.
|
||||
///
|
||||
/// This command unsubscribes from a mailbox, removing it from the
|
||||
@@ -21,28 +16,9 @@ pub struct ImapMailboxUnsubscribeCommand {
|
||||
|
||||
impl ImapMailboxUnsubscribeCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut coroutine = ImapMailboxUnsubscribe::new(imap.context, mailbox);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxUnsubscribeResult::Ok(_) => break,
|
||||
ImapMailboxUnsubscribeResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxUnsubscribeResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxUnsubscribeResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
|
||||
client.unsubscribe(mailbox)?;
|
||||
printer.out(Message::new("Mailbox successfully unsubscribed"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
@@ -1,20 +1,13 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
rfc3501::{copy::*, select::*},
|
||||
types::mailbox::Mailbox,
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use io_imap::types::mailbox::Mailbox;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag, TargetMailboxNameArg},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Copy IMAP message(s) to the given mailbox.
|
||||
///
|
||||
/// This command copies message(s) identified by the given sequence
|
||||
@@ -39,52 +32,17 @@ pub struct ImapMessageCopyCommand {
|
||||
|
||||
impl ImapMessageCopyCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
let destination: Mailbox = self.mailbox_dest_name.inner.try_into()?;
|
||||
|
||||
let mut coroutine =
|
||||
ImapMessageCopy::new(imap.context, sequence_set, destination, !self.seq);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageCopyResult::Ok { .. } => break,
|
||||
ImapMessageCopyResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageCopyResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageCopyResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.copy(sequence_set, destination, !self.seq)?;
|
||||
|
||||
printer.out(Message::new("Message(s) successfully copied"))
|
||||
}
|
||||
|
||||
+14
-49
@@ -1,23 +1,17 @@
|
||||
use std::{
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
num::NonZeroU32,
|
||||
io::{self, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::*, select::*},
|
||||
types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
};
|
||||
use io_imap::types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName};
|
||||
use mail_parser::{MessageParser, MimeHeaders};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameOptionalFlag};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Export type for message export.
|
||||
#[derive(Debug, Clone, clap::ValueEnum)]
|
||||
pub enum ExportType {
|
||||
@@ -63,34 +57,14 @@ pub struct ImapMessageExportCommand {
|
||||
|
||||
impl ImapMessageExportCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
client.select(mailbox)?;
|
||||
|
||||
// SELECT mailbox
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
let context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
|
||||
// FETCH with BODY.PEEK[] to avoid marking as read
|
||||
let Some(id) = NonZeroU32::new(self.id) else {
|
||||
if self.id == 0 {
|
||||
bail!("Export message error: ID must be non-zero");
|
||||
};
|
||||
}
|
||||
|
||||
let item_names =
|
||||
MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::BodyExt {
|
||||
@@ -99,25 +73,16 @@ impl ImapMessageExportCommand {
|
||||
peek: true,
|
||||
}]);
|
||||
|
||||
let mut coroutine = ImapMessageFetchFirst::new(context, id, item_names, !self.seq);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
let sequence_set = self.id.to_string().parse()?;
|
||||
let mut data = client.fetch(sequence_set, item_names, !self.seq)?;
|
||||
|
||||
let items = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageFetchFirstResult::Ok { items, .. } => break items,
|
||||
ImapMessageFetchFirstResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageFetchFirstResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageFetchFirstResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
let Some((_, items)) = data.pop_first() else {
|
||||
bail!(
|
||||
"Export message `{}` error: no message data returned",
|
||||
self.id
|
||||
);
|
||||
};
|
||||
|
||||
// Extract raw message bytes
|
||||
let mut raw_message: Option<Vec<u8>> = None;
|
||||
for item in items.into_iter() {
|
||||
if let MessageDataItem::BodyExt { data, .. } = item {
|
||||
|
||||
+11
-49
@@ -1,18 +1,11 @@
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
num::NonZeroU32,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{presets, Cell, ContentArrangement, Row, Table};
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::*, select::*},
|
||||
types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
};
|
||||
use io_imap::types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName};
|
||||
use mail_parser::{Addr, Address, ContentType, Message, MessageParser, MimeHeaders};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::imap::{
|
||||
@@ -20,8 +13,6 @@ use crate::imap::{
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Get a message and display its structure.
|
||||
///
|
||||
/// This command fetches a message and displays its headers along with
|
||||
@@ -42,32 +33,14 @@ pub struct ImapMessageGetCommand {
|
||||
|
||||
impl ImapMessageGetCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
let Some(id) = NonZeroU32::new(self.id) else {
|
||||
if self.id == 0 {
|
||||
bail!("ID must be non-zero");
|
||||
};
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
}
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let item_names =
|
||||
@@ -77,22 +50,11 @@ impl ImapMessageGetCommand {
|
||||
peek: true,
|
||||
}]);
|
||||
|
||||
let mut coroutine = ImapMessageFetchFirst::new(imap.context, id, item_names, !self.seq);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
let sequence_set = self.id.to_string().parse()?;
|
||||
let mut data = client.fetch(sequence_set, item_names, !self.seq)?;
|
||||
|
||||
let items = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageFetchFirstResult::Ok { items, .. } => break items,
|
||||
ImapMessageFetchFirstResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageFetchFirstResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageFetchFirstResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
let Some((_, items)) = data.pop_first() else {
|
||||
bail!("Get message `{}` error: no message data returned", self.id);
|
||||
};
|
||||
|
||||
let mut raw_message: Option<Vec<u8>> = None;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
pub mod command;
|
||||
pub mod cli;
|
||||
pub mod copy;
|
||||
pub mod export;
|
||||
pub mod get;
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::{rfc3501::select::*, rfc6851::r#move::*, types::mailbox::Mailbox};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use io_imap::types::mailbox::Mailbox;
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag, TargetMailboxNameArg},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Move message(s) to the given mailbox.
|
||||
///
|
||||
/// This command moves messages identified by the given sequence set
|
||||
@@ -37,52 +33,17 @@ pub struct ImapMessageMoveCommand {
|
||||
|
||||
impl ImapMessageMoveCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
let destination: Mailbox<'static> = self.mailbox_dest_name.inner.try_into()?;
|
||||
|
||||
let mut coroutine =
|
||||
ImapMessageMove::new(imap.context, sequence_set, destination, !self.seq);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageMoveResult::Ok { .. } => break,
|
||||
ImapMessageMoveResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageMoveResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageMoveResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.r#move(sequence_set, destination, !self.seq)?;
|
||||
|
||||
printer.out(Message::new("Message(s) successfully moved"))
|
||||
}
|
||||
|
||||
+11
-49
@@ -1,17 +1,10 @@
|
||||
use std::{
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
num::NonZeroU32,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
rfc3501::{fetch::*, select::*},
|
||||
types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName},
|
||||
};
|
||||
use io_imap::types::fetch::{MacroOrMessageDataItemNames, MessageDataItem, MessageDataItemName};
|
||||
use mail_parser::{Message, MessageParser};
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use pimalaya_cli::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::imap::{
|
||||
@@ -19,8 +12,6 @@ use crate::imap::{
|
||||
mailbox::arg::{MailboxNameOptionalFlag, MailboxNoSelectFlag},
|
||||
};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Read message content.
|
||||
///
|
||||
/// This command fetches a message and displays its text content.
|
||||
@@ -47,34 +38,16 @@ pub struct ImapMessageReadCommand {
|
||||
|
||||
impl ImapMessageReadCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox = self.mailbox_name.inner.try_into()?;
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
|
||||
if !self.mailbox_no_select.inner {
|
||||
let mut coroutine = ImapMailboxSelect::new(imap.context, mailbox);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
imap.context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMailboxSelectResult::Ok { context, .. } => break context,
|
||||
ImapMailboxSelectResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMailboxSelectResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMailboxSelectResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
};
|
||||
client.select(mailbox)?;
|
||||
}
|
||||
|
||||
let Some(id) = NonZeroU32::new(self.id) else {
|
||||
if self.id == 0 {
|
||||
bail!("ID must be non-zero");
|
||||
};
|
||||
}
|
||||
|
||||
let item_names =
|
||||
MacroOrMessageDataItemNames::MessageDataItemNames(vec![MessageDataItemName::BodyExt {
|
||||
@@ -83,22 +56,11 @@ impl ImapMessageReadCommand {
|
||||
peek: true,
|
||||
}]);
|
||||
|
||||
let mut coroutine = ImapMessageFetchFirst::new(imap.context, id, item_names, !self.seq);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
let sequence_set = self.id.to_string().parse()?;
|
||||
let mut data = client.fetch(sequence_set, item_names, !self.seq)?;
|
||||
|
||||
let items = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageFetchFirstResult::Ok { items, .. } => break items,
|
||||
ImapMessageFetchFirstResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageFetchFirstResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageFetchFirstResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
let Some((_, items)) = data.pop_first() else {
|
||||
bail!("Read message `{}` error: no message data returned", self.id);
|
||||
};
|
||||
|
||||
let mut raw_message: Option<Vec<u8>> = None;
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
use std::io::{stdin, BufRead, IsTerminal, Read, Write};
|
||||
use std::io::{stdin, BufRead, IsTerminal};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
rfc3501::append::*,
|
||||
types::{
|
||||
core::Literal, extensions::binary::LiteralOrLiteral8, flag::Flag, mailbox::Mailbox,
|
||||
IntoStatic,
|
||||
},
|
||||
use io_imap::types::{
|
||||
core::Literal, extensions::binary::LiteralOrLiteral8, flag::Flag, mailbox::Mailbox, IntoStatic,
|
||||
};
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
use pimalaya_cli::printer::{Message, Printer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, mailbox::arg::MailboxNameArg};
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
/// Save a message to a mailbox.
|
||||
///
|
||||
/// This command appends a message to the specified mailbox. The
|
||||
@@ -36,7 +30,7 @@ pub struct ImapMessageSaveCommand {
|
||||
|
||||
impl ImapMessageSaveCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let mut imap = account.new_imap_session()?;
|
||||
let mut client = account.new_imap_client()?;
|
||||
let mailbox: Mailbox<'static> = self.mailbox.inner.try_into()?;
|
||||
let message = if !self.message.is_empty() || stdin().is_terminal() || printer.is_json() {
|
||||
self.message
|
||||
@@ -61,24 +55,7 @@ impl ImapMessageSaveCommand {
|
||||
.map(|f| Flag::try_from(f).map(IntoStatic::into_static))
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let mut coroutine = ImapMessageAppend::new(imap.context, mailbox, flags, None, message);
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMessageAppendResult::Ok { .. } => break,
|
||||
ImapMessageAppendResult::WantsRead => {
|
||||
let n = imap.stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapMessageAppendResult::WantsWrite(bytes) => {
|
||||
imap.stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapMessageAppendResult::Err { err, .. } => bail!("{err}"),
|
||||
}
|
||||
}
|
||||
client.append(mailbox, flags, None, message)?;
|
||||
|
||||
printer.out(Message::new("Message successfully saved"))
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
pub mod account;
|
||||
pub mod command;
|
||||
pub mod cli;
|
||||
pub mod envelope;
|
||||
pub mod flag;
|
||||
pub mod id;
|
||||
pub mod mailbox;
|
||||
pub mod message;
|
||||
pub mod session;
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
//! Transitional IMAP session helper ported from `pimalaya-toolbox`.
|
||||
//!
|
||||
//! Will be replaced by `io_imap::client::ImapClient` once the
|
||||
//! protocol-specific subcommands switch over.
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use io_imap::{
|
||||
context::ImapContext,
|
||||
rfc3501::{
|
||||
capability::{ImapCapabilityGet, ImapCapabilityGetResult},
|
||||
greeting_with_capability::{
|
||||
ImapGreetingWithCapabilityGet, ImapGreetingWithCapabilityGetResult,
|
||||
},
|
||||
login::{ImapSessionLogin, ImapSessionLoginParams, ImapSessionLoginResult},
|
||||
starttls::{ImapStartTls, ImapStartTlsResult},
|
||||
},
|
||||
sasl::authenticate_plain::{
|
||||
ImapSessionAuthenticatePlain, ImapSessionAuthenticatePlainParams,
|
||||
ImapSessionAuthenticatePlainResult,
|
||||
},
|
||||
types::response::Capability,
|
||||
};
|
||||
use log::info;
|
||||
use pimalaya_stream::{
|
||||
sasl::{Sasl, SaslMechanism},
|
||||
std::stream::Stream,
|
||||
tls::{upgrade_tls, Tls},
|
||||
};
|
||||
#[cfg(windows)]
|
||||
use uds_windows::UnixStream;
|
||||
use url::Url;
|
||||
|
||||
const READ_BUFFER_SIZE: usize = 16 * 1024;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImapSession {
|
||||
pub context: ImapContext,
|
||||
pub stream: Stream,
|
||||
}
|
||||
|
||||
fn drive_greeting_with_capability<S: Read + Write>(
|
||||
stream: &mut S,
|
||||
context: ImapContext,
|
||||
) -> Result<ImapContext> {
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut coroutine = ImapGreetingWithCapabilityGet::new(context);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapGreetingWithCapabilityGetResult::Ok(context) => return Ok(context),
|
||||
ImapGreetingWithCapabilityGetResult::WantsRead => {
|
||||
let n = stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapGreetingWithCapabilityGetResult::WantsWrite(bytes) => {
|
||||
stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapGreetingWithCapabilityGetResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drive_capability<S: Read + Write>(stream: &mut S, context: ImapContext) -> Result<ImapContext> {
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut coroutine = ImapCapabilityGet::new(context);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapCapabilityGetResult::Ok(context) => return Ok(context),
|
||||
ImapCapabilityGetResult::WantsRead => {
|
||||
let n = stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapCapabilityGetResult::WantsWrite(bytes) => {
|
||||
stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapCapabilityGetResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drive_starttls<S: Read + Write>(stream: &mut S, context: ImapContext) -> Result<ImapContext> {
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut coroutine = ImapStartTls::new(context);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapStartTlsResult::WantsStartTls { context, .. } => return Ok(context),
|
||||
ImapStartTlsResult::WantsRead => {
|
||||
let n = stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapStartTlsResult::WantsWrite(bytes) => {
|
||||
stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapStartTlsResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ImapSession {
|
||||
pub fn new(url: Url, tls: Tls, starttls: bool, mut sasl: Sasl) -> Result<Self> {
|
||||
info!("connecting to IMAP server using {url}");
|
||||
|
||||
let context = ImapContext::new();
|
||||
let host = url.host_str().unwrap_or("127.0.0.1");
|
||||
|
||||
let (mut context, mut stream) = match url.scheme() {
|
||||
scheme if scheme.eq_ignore_ascii_case("imap") => {
|
||||
let port = url.port().unwrap_or(143);
|
||||
let mut tcp = TcpStream::connect((host, port))?;
|
||||
let context = drive_greeting_with_capability(&mut tcp, context)?;
|
||||
(context, Stream::Tcp(tcp))
|
||||
}
|
||||
scheme if scheme.eq_ignore_ascii_case("imaps") => {
|
||||
let port = url.port().unwrap_or(993);
|
||||
let mut tcp = TcpStream::connect((host, port))?;
|
||||
|
||||
let context = if starttls {
|
||||
drive_starttls(&mut tcp, context)?
|
||||
} else {
|
||||
context
|
||||
};
|
||||
|
||||
let mut stream = upgrade_tls(host, tcp, &tls, &[b"imap"])?;
|
||||
|
||||
let context = if starttls {
|
||||
drive_capability(&mut stream, context)?
|
||||
} else {
|
||||
drive_greeting_with_capability(&mut stream, context)?
|
||||
};
|
||||
|
||||
(context, stream)
|
||||
}
|
||||
scheme if scheme.eq_ignore_ascii_case("unix") => {
|
||||
let sock_path = url.path();
|
||||
let mut unix = UnixStream::connect(sock_path)?;
|
||||
let context = drive_greeting_with_capability(&mut unix, context)?;
|
||||
(context, Stream::Unix(unix))
|
||||
}
|
||||
scheme => {
|
||||
bail!("Unknown scheme {scheme}, expected imap, imaps or unix");
|
||||
}
|
||||
};
|
||||
|
||||
if !context.authenticated {
|
||||
let ir = context.capability.contains(&Capability::SaslIr);
|
||||
|
||||
let mechanism = sasl
|
||||
.mechanism
|
||||
.or(Some(SaslMechanism::Plain).filter(|_| sasl.plain.is_some()))
|
||||
.or(Some(SaslMechanism::Login).filter(|_| sasl.login.is_some()));
|
||||
|
||||
match mechanism {
|
||||
None => bail!("no SASL mechanism configured"),
|
||||
Some(SaslMechanism::Login) => {
|
||||
let Some(auth) = sasl.login.take() else {
|
||||
bail!("missing SASL LOGIN configuration");
|
||||
};
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut coroutine = ImapSessionLogin::new(
|
||||
context,
|
||||
ImapSessionLoginParams::new(auth.username, auth.password)?,
|
||||
);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSessionLoginResult::Ok(c) => break c,
|
||||
ImapSessionLoginResult::WantsRead => {
|
||||
let n = stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapSessionLoginResult::WantsWrite(bytes) => {
|
||||
stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapSessionLoginResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(SaslMechanism::Plain) => {
|
||||
let Some(auth) = sasl.plain.take() else {
|
||||
bail!("missing SASL PLAIN configuration");
|
||||
};
|
||||
|
||||
let mut buf = [0u8; READ_BUFFER_SIZE];
|
||||
let mut coroutine = ImapSessionAuthenticatePlain::new(
|
||||
context,
|
||||
ImapSessionAuthenticatePlainParams::new(
|
||||
auth.authzid,
|
||||
auth.authcid,
|
||||
auth.passwd,
|
||||
ir,
|
||||
),
|
||||
);
|
||||
let mut arg: Option<&[u8]> = None;
|
||||
|
||||
context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSessionAuthenticatePlainResult::Ok(c) => break c,
|
||||
ImapSessionAuthenticatePlainResult::WantsRead => {
|
||||
let n = stream.read(&mut buf)?;
|
||||
arg = Some(&buf[..n]);
|
||||
}
|
||||
ImapSessionAuthenticatePlainResult::WantsWrite(bytes) => {
|
||||
stream.write_all(&bytes)?;
|
||||
arg = None;
|
||||
}
|
||||
ImapSessionAuthenticatePlainResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(SaslMechanism::Anonymous) => {
|
||||
unimplemented!("ANONYMOUS SASL mechanism not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { context, stream })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user