mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-15 20:07:57 +08:00
add more mailbox useful commands
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::coroutines::close::*;
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::stream};
|
||||
|
||||
/// Close a mailbox.
|
||||
///
|
||||
/// This command first selects the given mailbox, then closes it.
|
||||
/// CLOSE permanently removes all messages with the \Deleted flag
|
||||
/// and returns to the authenticated state.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct CloseMailboxCommand;
|
||||
|
||||
impl CloseMailboxCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let mut arg = None;
|
||||
let mut close_coroutine = ImapClose::new(context);
|
||||
|
||||
loop {
|
||||
match close_coroutine.resume(arg.take()) {
|
||||
ImapCloseResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapCloseResult::Ok { .. } => break,
|
||||
ImapCloseResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Mailbox successfully closed"))
|
||||
}
|
||||
}
|
||||
@@ -4,41 +4,52 @@ use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{presets, Cell, ContentArrangement, Row, Table};
|
||||
use crossterm::style::Color;
|
||||
use io_imap::coroutines::list::*;
|
||||
use io_imap::coroutines::{list::*, lsub::*};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::stream};
|
||||
|
||||
/// List all mailboxes.
|
||||
/// List mailboxes.
|
||||
///
|
||||
/// This command allows you to list all exsting mailboxes from your
|
||||
/// IMAP account.
|
||||
/// This command allows you to list mailboxes from your IMAP account.
|
||||
/// By default, only subscribed mailboxes are listed. Use --all to
|
||||
/// list all mailboxes.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ListMailboxesCommand {
|
||||
// /// The maximum width the table should not exceed.
|
||||
// ///
|
||||
// /// This argument will force the table not to exceed the given
|
||||
// /// width, in pixels. Columns may shrink with ellipsis in order to
|
||||
// /// fit the width.
|
||||
// #[arg(long = "max-width", short = 'w')]
|
||||
// #[arg(name = "table_max_width", value_name = "PIXELS")]
|
||||
// pub table_max_width: Option<u16>,
|
||||
/// List all mailboxes, not just subscribed ones.
|
||||
#[arg(short = 'A', long)]
|
||||
pub all: bool,
|
||||
}
|
||||
|
||||
impl ListMailboxesCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapList::new(context, "".try_into().unwrap(), "*".try_into().unwrap());
|
||||
let mailboxes = if self.all {
|
||||
let mut arg = None;
|
||||
let mut coroutine =
|
||||
ImapList::new(context, "".try_into().unwrap(), "*".try_into().unwrap());
|
||||
|
||||
let mailboxes = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapListResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapListResult::Ok { mailboxes, .. } => break mailboxes,
|
||||
ImapListResult::Err { err, .. } => bail!(err),
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapListResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapListResult::Ok { mailboxes, .. } => break mailboxes,
|
||||
ImapListResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let mut arg = None;
|
||||
let mut coroutine =
|
||||
ImapLsub::new(context, "".try_into().unwrap(), "*".try_into().unwrap());
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapLsubResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapLsubResult::Ok { mailboxes, .. } => break mailboxes,
|
||||
ImapLsubResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
pub mod close;
|
||||
pub mod create;
|
||||
pub mod delete;
|
||||
pub mod expunge;
|
||||
pub mod list;
|
||||
pub mod purge;
|
||||
pub mod rename;
|
||||
pub mod status;
|
||||
pub mod subscribe;
|
||||
pub mod unselect;
|
||||
pub mod unsubscribe;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
@@ -11,8 +17,11 @@ use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::mailbox::command::{
|
||||
create::CreateMailboxCommand, delete::DeleteMailboxCommand,
|
||||
close::CloseMailboxCommand, create::CreateMailboxCommand, delete::DeleteMailboxCommand,
|
||||
expunge::ExpungeMailboxCommand, list::ListMailboxesCommand, purge::PurgeMailboxCommand,
|
||||
rename::RenameMailboxCommand, status::StatusMailboxCommand,
|
||||
subscribe::SubscribeMailboxCommand, unselect::UnselectMailboxCommand,
|
||||
unsubscribe::UnsubscribeMailboxCommand,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,6 +31,7 @@ use crate::{
|
||||
/// manage them.
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MailboxCommand {
|
||||
Close(CloseMailboxCommand),
|
||||
#[command(alias = "add", alias = "new")]
|
||||
Create(CreateMailboxCommand),
|
||||
#[command(alias = "remove", alias = "rm")]
|
||||
@@ -29,16 +39,27 @@ pub enum MailboxCommand {
|
||||
Expunge(ExpungeMailboxCommand),
|
||||
List(ListMailboxesCommand),
|
||||
Purge(PurgeMailboxCommand),
|
||||
Rename(RenameMailboxCommand),
|
||||
Status(StatusMailboxCommand),
|
||||
Subscribe(SubscribeMailboxCommand),
|
||||
Unselect(UnselectMailboxCommand),
|
||||
Unsubscribe(UnsubscribeMailboxCommand),
|
||||
}
|
||||
|
||||
impl MailboxCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
match self {
|
||||
Self::Close(cmd) => cmd.execute(printer, config),
|
||||
Self::Create(cmd) => cmd.execute(printer, config),
|
||||
Self::Delete(cmd) => cmd.execute(printer, config),
|
||||
Self::Expunge(cmd) => cmd.execute(printer, config),
|
||||
Self::List(cmd) => cmd.execute(printer, config),
|
||||
Self::Purge(cmd) => cmd.execute(printer, config),
|
||||
Self::Rename(cmd) => cmd.execute(printer, config),
|
||||
Self::Status(cmd) => cmd.execute(printer, config),
|
||||
Self::Subscribe(cmd) => cmd.execute(printer, config),
|
||||
Self::Unselect(cmd) => cmd.execute(printer, config),
|
||||
Self::Unsubscribe(cmd) => cmd.execute(printer, config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::coroutines::rename::*;
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::mailbox::arg::name::{MailboxNameArg, TargetMailboxNameArg},
|
||||
imap::stream,
|
||||
};
|
||||
|
||||
/// Rename a mailbox.
|
||||
///
|
||||
/// This command renames an existing mailbox to a new name.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct RenameMailboxCommand {
|
||||
#[command(flatten)]
|
||||
pub from: MailboxNameArg,
|
||||
|
||||
#[command(flatten)]
|
||||
pub to: TargetMailboxNameArg,
|
||||
}
|
||||
|
||||
impl RenameMailboxCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(config)?;
|
||||
|
||||
let from = self.from.name.try_into()?;
|
||||
let to = self.to.name.try_into()?;
|
||||
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapRename::new(context, from, to);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapRenameResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapRenameResult::Ok { .. } => break,
|
||||
ImapRenameResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Mailbox successfully renamed"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{presets, Cell, ContentArrangement, Row, Table};
|
||||
use io_imap::coroutines::status::*;
|
||||
use io_imap::types::status::{StatusDataItem, StatusDataItemName};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream};
|
||||
|
||||
/// Get the status of a mailbox.
|
||||
///
|
||||
/// This command displays status information about a mailbox,
|
||||
/// including message counts and UID values.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct StatusMailboxCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameArg,
|
||||
}
|
||||
|
||||
impl StatusMailboxCommand {
|
||||
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()?;
|
||||
let item_names = vec![
|
||||
StatusDataItemName::Messages,
|
||||
StatusDataItemName::Recent,
|
||||
StatusDataItemName::Unseen,
|
||||
StatusDataItemName::UidNext,
|
||||
StatusDataItemName::UidValidity,
|
||||
];
|
||||
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapStatus::new(context, mailbox, item_names);
|
||||
|
||||
let items = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapStatusResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapStatusResult::Ok { items, .. } => break items,
|
||||
ImapStatusResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
let table = MailboxStatusTable::from(items);
|
||||
|
||||
printer.out(table)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct MailboxStatus {
|
||||
pub messages: Option<u32>,
|
||||
pub recent: Option<u32>,
|
||||
pub unseen: Option<u32>,
|
||||
pub uidnext: Option<u32>,
|
||||
pub uidvalidity: Option<u32>,
|
||||
}
|
||||
|
||||
impl From<Vec<StatusDataItem>> for MailboxStatus {
|
||||
fn from(items: Vec<StatusDataItem>) -> Self {
|
||||
let mut status = MailboxStatus {
|
||||
messages: None,
|
||||
recent: None,
|
||||
unseen: None,
|
||||
uidnext: None,
|
||||
uidvalidity: None,
|
||||
};
|
||||
|
||||
for item in items {
|
||||
match item {
|
||||
StatusDataItem::Messages(n) => status.messages = Some(n),
|
||||
StatusDataItem::Recent(n) => status.recent = Some(n),
|
||||
StatusDataItem::Unseen(n) => status.unseen = Some(n),
|
||||
StatusDataItem::UidNext(n) => status.uidnext = Some(n.get()),
|
||||
StatusDataItem::UidValidity(n) => status.uidvalidity = Some(n.get()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MailboxStatusTable {
|
||||
status: MailboxStatus,
|
||||
}
|
||||
|
||||
impl From<Vec<StatusDataItem>> for MailboxStatusTable {
|
||||
fn from(items: Vec<StatusDataItem>) -> Self {
|
||||
Self {
|
||||
status: MailboxStatus::from(items),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MailboxStatusTable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut table = Table::new();
|
||||
|
||||
table
|
||||
.load_preset(presets::ASCII_MARKDOWN)
|
||||
.set_content_arrangement(ContentArrangement::DynamicFullWidth)
|
||||
.set_header(Row::from([Cell::new("ATTRIBUTE"), Cell::new("VALUE")]));
|
||||
|
||||
if let Some(messages) = self.status.messages {
|
||||
table.add_row(Row::from([Cell::new("Messages"), Cell::new(messages)]));
|
||||
}
|
||||
if let Some(recent) = self.status.recent {
|
||||
table.add_row(Row::from([Cell::new("Recent"), Cell::new(recent)]));
|
||||
}
|
||||
if let Some(unseen) = self.status.unseen {
|
||||
table.add_row(Row::from([Cell::new("Unseen"), Cell::new(unseen)]));
|
||||
}
|
||||
if let Some(uidnext) = self.status.uidnext {
|
||||
table.add_row(Row::from([Cell::new("UIDNext"), Cell::new(uidnext)]));
|
||||
}
|
||||
if let Some(uidvalidity) = self.status.uidvalidity {
|
||||
table.add_row(Row::from([
|
||||
Cell::new("UIDValidity"),
|
||||
Cell::new(uidvalidity),
|
||||
]));
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
write!(f, "{table}")?;
|
||||
writeln!(f)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for MailboxStatusTable {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
self.status.serialize(serializer)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::coroutines::subscribe::*;
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream};
|
||||
|
||||
/// Subscribe to the given mailbox.
|
||||
///
|
||||
/// This command subscribes to a mailbox, making it appear in the
|
||||
/// list of subscribed mailboxes.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct SubscribeMailboxCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameArg,
|
||||
}
|
||||
|
||||
impl SubscribeMailboxCommand {
|
||||
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()?;
|
||||
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapSubscribe::new(context, mailbox);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapSubscribeResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapSubscribeResult::Ok { .. } => break,
|
||||
ImapSubscribeResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Mailbox successfully subscribed"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::coroutines::{select::*, unselect::*};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream};
|
||||
|
||||
/// Unselect a mailbox.
|
||||
///
|
||||
/// This command first selects the given mailbox, then unselects it.
|
||||
/// Unlike CLOSE, UNSELECT does not expunge deleted messages.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct UnselectMailboxCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameArg,
|
||||
}
|
||||
|
||||
impl UnselectMailboxCommand {
|
||||
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()?;
|
||||
|
||||
// First SELECT the mailbox
|
||||
let mut arg = None;
|
||||
let mut select_coroutine = ImapSelect::new(context, mailbox);
|
||||
|
||||
let context = loop {
|
||||
match select_coroutine.resume(arg.take()) {
|
||||
ImapSelectResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapSelectResult::Ok { context, .. } => break context,
|
||||
ImapSelectResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
// Then UNSELECT
|
||||
let mut arg = None;
|
||||
let mut unselect_coroutine = ImapUnselect::new(context);
|
||||
|
||||
loop {
|
||||
match unselect_coroutine.resume(arg.take()) {
|
||||
ImapUnselectResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapUnselectResult::Ok { .. } => break,
|
||||
ImapUnselectResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Mailbox successfully unselected"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::coroutines::unsubscribe::*;
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::{Message, Printer};
|
||||
|
||||
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream};
|
||||
|
||||
/// Unsubscribe from the given mailbox.
|
||||
///
|
||||
/// This command unsubscribes from a mailbox, removing it from the
|
||||
/// list of subscribed mailboxes.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct UnsubscribeMailboxCommand {
|
||||
#[command(flatten)]
|
||||
pub mailbox: MailboxNameArg,
|
||||
}
|
||||
|
||||
impl UnsubscribeMailboxCommand {
|
||||
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()?;
|
||||
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapUnsubscribe::new(context, mailbox);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapUnsubscribeResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapUnsubscribeResult::Ok { .. } => break,
|
||||
ImapUnsubscribeResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
printer.out(Message::new("Mailbox successfully unsubscribed"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user