mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-15 20:07:57 +08:00
add msg copy move delete save commands
This commit is contained in:
+5
-1
@@ -6,7 +6,7 @@ use crate::{
|
||||
config::ImapConfig,
|
||||
imap::{
|
||||
envelope::command::EnvelopeCommand, flag::command::FlagCommand,
|
||||
mailbox::command::MailboxCommand,
|
||||
mailbox::command::MailboxCommand, message::command::MessageCommand,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -26,6 +26,9 @@ pub enum ImapCommand {
|
||||
#[command(subcommand)]
|
||||
#[command(aliases = ["mboxes", "mbox"])]
|
||||
Mailboxes(MailboxCommand),
|
||||
#[command(subcommand)]
|
||||
#[command(aliases = ["message", "msg"])]
|
||||
Messages(MessageCommand),
|
||||
}
|
||||
|
||||
impl ImapCommand {
|
||||
@@ -34,6 +37,7 @@ impl ImapCommand {
|
||||
Self::Envelopes(cmd) => cmd.execute(printer, config),
|
||||
Self::Flags(cmd) => cmd.execute(printer, config),
|
||||
Self::Mailboxes(cmd) => cmd.execute(printer, config),
|
||||
Self::Messages(cmd) => cmd.execute(printer, config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
coroutines::{copy::*, select::*},
|
||||
types::mailbox::Mailbox,
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream};
|
||||
|
||||
/// Copy messages to another mailbox.
|
||||
///
|
||||
/// This command copies messages identified by the given sequence set
|
||||
/// from the source mailbox to the destination mailbox.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct CopyMessageCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameOptionalFlag,
|
||||
|
||||
/// The sequence set of messages (e.g., "1", "1,2,3", "1:*").
|
||||
#[arg(name = "sequence_set", value_name = "SEQUENCE")]
|
||||
pub sequence_set: String,
|
||||
|
||||
/// The destination mailbox.
|
||||
#[arg(name = "destination", value_name = "DESTINATION")]
|
||||
pub destination: String,
|
||||
|
||||
/// Use sequence numbers instead of UIDs.
|
||||
#[arg(long)]
|
||||
pub seq: bool,
|
||||
}
|
||||
|
||||
impl CopyMessageCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let mailbox = self.mailbox.name.try_into()?;
|
||||
|
||||
// SELECT mailbox
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapSelect::new(context, mailbox);
|
||||
|
||||
let context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapSelectResult::Ok { context, .. } => break context,
|
||||
ImapSelectResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
// Parse sequence set and destination
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
let destination: Mailbox<'static> = self.destination.try_into()?;
|
||||
|
||||
// COPY
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapCopy::new(context, sequence_set, destination, !self.seq);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapCopyResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapCopyResult::Ok { .. } => break,
|
||||
ImapCopyResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Message(s) successfully copied"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
coroutines::{expunge::*, select::*, store::*},
|
||||
types::flag::{Flag, StoreType},
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream};
|
||||
|
||||
/// Delete messages from a mailbox.
|
||||
///
|
||||
/// This command marks messages as deleted and expunges them from the
|
||||
/// mailbox. The messages are permanently removed.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct DeleteMessageCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameOptionalFlag,
|
||||
|
||||
/// The sequence set of messages (e.g., "1", "1,2,3", "1:*").
|
||||
#[arg(name = "sequence_set", value_name = "SEQUENCE")]
|
||||
pub sequence_set: String,
|
||||
|
||||
/// Use sequence numbers instead of UIDs.
|
||||
#[arg(long)]
|
||||
pub seq: bool,
|
||||
}
|
||||
|
||||
impl DeleteMessageCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let mailbox = self.mailbox.name.try_into()?;
|
||||
|
||||
// SELECT mailbox
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapSelect::new(context, mailbox);
|
||||
|
||||
let context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapSelectResult::Ok { context, .. } => break context,
|
||||
ImapSelectResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
// Parse sequence set
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
|
||||
// STORE +FLAGS \Deleted
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapStoreSilent::new(
|
||||
context,
|
||||
sequence_set,
|
||||
StoreType::Add,
|
||||
vec![Flag::Deleted],
|
||||
!self.seq,
|
||||
);
|
||||
|
||||
let context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapStoreSilentResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapStoreSilentResult::Ok { context, .. } => break context,
|
||||
ImapStoreSilentResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
// EXPUNGE
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapExpunge::new(context);
|
||||
|
||||
let expunged = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapExpungeResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapExpungeResult::Ok { expunged, .. } => break expunged,
|
||||
ImapExpungeResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
printer.out(Message::new(format!(
|
||||
"{} message(s) successfully deleted",
|
||||
expunged.len()
|
||||
)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
pub mod copy;
|
||||
pub mod delete;
|
||||
pub mod r#move;
|
||||
pub mod save;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::message::command::{
|
||||
copy::CopyMessageCommand, delete::DeleteMessageCommand, r#move::MoveMessageCommand,
|
||||
save::SaveMessageCommand,
|
||||
},
|
||||
};
|
||||
|
||||
/// Manage messages.
|
||||
///
|
||||
/// A message is a complete email including headers and body. This
|
||||
/// subcommand allows you to save, copy, move, and delete messages.
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MessageCommand {
|
||||
Save(SaveMessageCommand),
|
||||
Copy(CopyMessageCommand),
|
||||
Move(MoveMessageCommand),
|
||||
Delete(DeleteMessageCommand),
|
||||
}
|
||||
|
||||
impl MessageCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
match self {
|
||||
Self::Save(cmd) => cmd.execute(printer, config),
|
||||
Self::Copy(cmd) => cmd.execute(printer, config),
|
||||
Self::Move(cmd) => cmd.execute(printer, config),
|
||||
Self::Delete(cmd) => cmd.execute(printer, config),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
coroutines::{r#move::*, select::*},
|
||||
types::mailbox::Mailbox,
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameOptionalFlag, imap::stream};
|
||||
|
||||
/// Move messages to another mailbox.
|
||||
///
|
||||
/// This command moves messages identified by the given sequence set
|
||||
/// from the source mailbox to the destination mailbox. Requires the
|
||||
/// MOVE IMAP extension.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct MoveMessageCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameOptionalFlag,
|
||||
|
||||
/// The sequence set of messages (e.g., "1", "1,2,3", "1:*").
|
||||
#[arg(name = "sequence_set", value_name = "SEQUENCE")]
|
||||
pub sequence_set: String,
|
||||
|
||||
/// The destination mailbox.
|
||||
#[arg(name = "destination", value_name = "DESTINATION")]
|
||||
pub destination: String,
|
||||
|
||||
/// Use sequence numbers instead of UIDs.
|
||||
#[arg(long)]
|
||||
pub seq: bool,
|
||||
}
|
||||
|
||||
impl MoveMessageCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let mailbox = self.mailbox.name.try_into()?;
|
||||
|
||||
// SELECT mailbox
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapSelect::new(context, mailbox);
|
||||
|
||||
let context = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSelectResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapSelectResult::Ok { context, .. } => break context,
|
||||
ImapSelectResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
// Parse sequence set and destination
|
||||
let sequence_set = self.sequence_set.as_str().try_into()?;
|
||||
let destination: Mailbox<'static> = self.destination.try_into()?;
|
||||
|
||||
// MOVE
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapMove::new(context, sequence_set, destination, !self.seq);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapMoveResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapMoveResult::Ok { .. } => break,
|
||||
ImapMoveResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Message(s) successfully moved"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
use std::io::{self, Read};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
coroutines::append::*,
|
||||
types::{core::Literal, extensions::binary::LiteralOrLiteral8, mailbox::Mailbox},
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::stream};
|
||||
|
||||
/// Save a message to a mailbox.
|
||||
///
|
||||
/// This command appends a message to the specified mailbox. The
|
||||
/// message is read from stdin in RFC 5322 format (raw email).
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct SaveMessageCommand {
|
||||
/// The mailbox to save the message to.
|
||||
#[arg(name = "mailbox", value_name = "MAILBOX")]
|
||||
pub mailbox: String,
|
||||
}
|
||||
|
||||
impl SaveMessageCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
// Read message from stdin
|
||||
let mut message = Vec::new();
|
||||
io::stdin().read_to_end(&mut message)?;
|
||||
|
||||
if message.is_empty() {
|
||||
bail!("No message provided on stdin");
|
||||
}
|
||||
|
||||
let mailbox: Mailbox<'static> = self.mailbox.try_into()?;
|
||||
let literal = Literal::try_from(message)?;
|
||||
let message = LiteralOrLiteral8::Literal(literal);
|
||||
|
||||
// APPEND
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapAppend::new(context, mailbox, vec![], None, message);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapAppendResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapAppendResult::Ok { .. } => break,
|
||||
ImapAppendResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Message successfully saved"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod command;
|
||||
@@ -2,4 +2,5 @@ pub mod command;
|
||||
pub mod envelope;
|
||||
pub mod flag;
|
||||
pub mod mailbox;
|
||||
pub mod message;
|
||||
pub mod stream;
|
||||
|
||||
Reference in New Issue
Block a user