re-implement old mailbox commands

This commit is contained in:
Clément DOUIN
2026-03-03 21:37:57 +01:00
parent ffa26594a4
commit 715fdb002a
8 changed files with 202 additions and 260 deletions
+26 -25
View File
@@ -1,16 +1,17 @@
use clap::Parser;
use email::folder::INBOX;
/// The optional folder name flag parser.
const INBOX: &str = "INBOX";
/// The optional mailbox name flag parser.
#[derive(Debug, Parser)]
pub struct FolderNameOptionalFlag {
/// The name of the folder.
#[arg(long = "folder", short = 'f')]
#[arg(name = "folder_name", value_name = "NAME", default_value = INBOX)]
pub struct MailboxNameOptionalFlag {
/// The name of the mailbox.
#[arg(long = "mailbox", short = 'm')]
#[arg(name = "mailbox_name", value_name = "NAME", default_value = INBOX)]
pub name: String,
}
impl Default for FolderNameOptionalFlag {
impl Default for MailboxNameOptionalFlag {
fn default() -> Self {
Self {
name: INBOX.to_owned(),
@@ -18,15 +19,15 @@ impl Default for FolderNameOptionalFlag {
}
}
/// The optional folder name argument parser.
/// The optional mailbox name argument parser.
#[derive(Debug, Parser)]
pub struct FolderNameOptionalArg {
/// The name of the folder.
#[arg(name = "folder_name", value_name = "FOLDER", default_value = INBOX)]
pub struct MailboxNameOptionalArg {
/// The name of the mailbox.
#[arg(name = "mailbox_name", value_name = "MAILBOX", default_value = INBOX)]
pub name: String,
}
impl Default for FolderNameOptionalArg {
impl Default for MailboxNameOptionalArg {
fn default() -> Self {
Self {
name: INBOX.to_owned(),
@@ -34,27 +35,27 @@ impl Default for FolderNameOptionalArg {
}
}
/// The required folder name argument parser.
/// The required mailbox name argument parser.
#[derive(Debug, Parser)]
pub struct FolderNameArg {
/// The name of the folder.
#[arg(name = "folder_name", value_name = "FOLDER")]
pub struct MailboxNameArg {
/// The name of the mailbox.
#[arg(name = "mailbox_name", value_name = "MAILBOX")]
pub name: String,
}
/// The optional source folder name flag parser.
/// The optional source mailbox name flag parser.
#[derive(Debug, Parser)]
pub struct SourceFolderNameOptionalFlag {
/// The name of the source folder.
#[arg(long = "folder", short = 'f')]
#[arg(name = "source_folder_name", value_name = "SOURCE", default_value = INBOX)]
pub struct SourceMailboxNameOptionalFlag {
/// The name of the source mailbox.
#[arg(long = "mailbox", short = 'm')]
#[arg(name = "source_mailbox_name", value_name = "SOURCE", default_value = INBOX)]
pub name: String,
}
/// The target folder name argument parser.
/// The target mailbox name argument parser.
#[derive(Debug, Parser)]
pub struct TargetFolderNameArg {
/// The name of the target folder.
#[arg(name = "target_folder_name", value_name = "TARGET")]
pub struct TargetMailboxNameArg {
/// The name of the target mailbox.
#[arg(name = "target_mailbox_name", value_name = "TARGET")]
pub name: String,
}
-61
View File
@@ -1,61 +0,0 @@
use std::sync::Arc;
use clap::Parser;
use color_eyre::Result;
use email::{
config::Config,
{backend::feature::BackendFeatureSource, folder::add::AddFolder},
};
use pimalaya_tui::{
himalaya::backend::BackendBuilder,
terminal::{cli::printer::Printer, config::TomlConfig as _},
};
use tracing::info;
use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
};
/// Create the given folder.
///
/// This command allows you to create a new folder using the given
/// name.
#[derive(Debug, Parser)]
pub struct FolderAddCommand {
#[command(flatten)]
pub folder: FolderNameArg,
#[command(flatten)]
pub account: AccountNameFlag,
}
impl FolderAddCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing create folder command");
let folder = &self.folder.name;
let (toml_account_config, account_config) = config
.clone()
.into_account_configs(self.account.name.as_deref(), |c: &Config, name| {
c.account(name).ok()
})?;
let backend = BackendBuilder::new(
Arc::new(toml_account_config),
Arc::new(account_config),
|builder| {
builder
.without_features()
.with_add_folder(BackendFeatureSource::Context)
},
)
.without_sending_backend()
.build()
.await?;
backend.add_folder(folder).await?;
printer.out(format!("Folder {folder} successfully created!\n"))
}
}
+38
View File
@@ -0,0 +1,38 @@
use anyhow::{bail, Result};
use clap::Parser;
use io_imap::coroutines::create::*;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream};
/// Create the given mailbox.
///
/// This command allows you to create a new mailbox using the given
/// name.
#[derive(Debug, Parser)]
pub struct CreateMailboxCommand {
#[command(flatten)]
pub mailbox: MailboxNameArg,
}
impl CreateMailboxCommand {
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 = ImapCreate::new(context, mailbox);
loop {
match coroutine.resume(arg.take()) {
ImapCreateResult::Io(io) => arg = Some(handle(&mut stream, io)?),
ImapCreateResult::Ok { .. } => break,
ImapCreateResult::Err { err, .. } => bail!(err),
}
}
printer.out(Message::new("Mailbox successfully created"))
}
}
+23 -58
View File
@@ -1,73 +1,38 @@
use std::{process, sync::Arc};
use anyhow::{bail, Result};
use clap::Parser;
use color_eyre::Result;
use email::{
config::Config,
{backend::feature::BackendFeatureSource, folder::delete::DeleteFolder},
};
use pimalaya_tui::{
himalaya::backend::BackendBuilder,
terminal::{cli::printer::Printer, config::TomlConfig as _, prompt},
};
use tracing::info;
use io_imap::coroutines::delete::*;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
};
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream};
/// Delete the given folder.
/// Delete the given mailbox.
///
/// All emails from the given folder are definitely deleted. The
/// folder is also deleted after execution of the command.
/// All emails from the given mailbox are definitely deleted. The
/// mailbox is also deleted after execution of the command.
#[derive(Debug, Parser)]
pub struct FolderDeleteCommand {
pub struct DeleteMailboxCommand {
#[command(flatten)]
pub folder: FolderNameArg,
#[command(flatten)]
pub account: AccountNameFlag,
#[arg(long, short)]
pub yes: bool,
pub mailbox: MailboxNameArg,
}
impl FolderDeleteCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing delete folder command");
impl DeleteMailboxCommand {
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
let (context, mut stream) = stream::connect(config)?;
let folder = &self.folder.name;
let mailbox = self.mailbox.name.try_into()?;
if !self.yes {
let confirm = format!("Do you really want to delete the folder {folder}");
let confirm = format!("{confirm}? All emails will be definitely deleted.");
let mut arg = None;
let mut coroutine = ImapDelete::new(context, mailbox);
if !prompt::bool(confirm, false)? {
process::exit(0);
};
loop {
match coroutine.resume(arg.take()) {
ImapDeleteResult::Io(io) => arg = Some(handle(&mut stream, io)?),
ImapDeleteResult::Ok { .. } => break,
ImapDeleteResult::Err { err, .. } => bail!(err),
}
}
let (toml_account_config, account_config) = config
.clone()
.into_account_configs(self.account.name.as_deref(), |c: &Config, name| {
c.account(name).ok()
})?;
let backend = BackendBuilder::new(
Arc::new(toml_account_config),
Arc::new(account_config),
|builder| {
builder
.without_features()
.with_delete_folder(BackendFeatureSource::Context)
},
)
.without_sending_backend()
.build()
.await?;
backend.delete_folder(folder).await?;
printer.out(format!("Folder {folder} successfully deleted!\n"))
printer.out(Message::new("Mailbox successfully deleted"))
}
}
+36 -44
View File
@@ -1,60 +1,52 @@
use std::sync::Arc;
use anyhow::{bail, Result};
use clap::Parser;
use color_eyre::Result;
use email::{
backend::feature::BackendFeatureSource, config::Config, folder::expunge::ExpungeFolder,
};
use pimalaya_tui::{
himalaya::backend::BackendBuilder,
terminal::{cli::printer::Printer, config::TomlConfig as _},
};
use tracing::info;
use io_imap::coroutines::{expunge::*, select::*};
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
};
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream};
/// Expunge the given folder.
/// Expunge the given mailbox.
///
/// The concept of expunging is similar to the IMAP one: it definitely
/// deletes emails from the given folder that contain the "deleted"
/// deletes emails from the given mailbox that contain the "deleted"
/// flag.
#[derive(Debug, Parser)]
pub struct FolderExpungeCommand {
pub struct ExpungeMailboxCommand {
#[command(flatten)]
pub folder: FolderNameArg,
#[command(flatten)]
pub account: AccountNameFlag,
pub mailbox: MailboxNameArg,
}
impl FolderExpungeCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing expunge folder command");
impl ExpungeMailboxCommand {
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
let (context, mut stream) = stream::connect(config)?;
let folder = &self.folder.name;
let (toml_account_config, account_config) = config
.clone()
.into_account_configs(self.account.name.as_deref(), |c: &Config, name| {
c.account(name).ok()
})?;
let mailbox = self.mailbox.name.try_into()?;
let backend = BackendBuilder::new(
Arc::new(toml_account_config),
Arc::new(account_config),
|builder| {
builder
.without_features()
.with_expunge_folder(BackendFeatureSource::Context)
},
)
.without_sending_backend()
.build()
.await?;
// First, select the mailbox
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
backend.expunge_folder(folder).await?;
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),
}
};
printer.out(format!("Folder {folder} successfully expunged!\n"))
// Then, expunge it
let mut arg = None;
let mut coroutine = ImapExpunge::new(context);
loop {
match coroutine.resume(arg.take()) {
ImapExpungeResult::Io(io) => arg = Some(handle(&mut stream, io)?),
ImapExpungeResult::Ok { .. } => break,
ImapExpungeResult::Err { err, .. } => bail!(err),
}
}
printer.out(Message::new("Mailbox successfully expunged"))
}
}
+21 -20
View File
@@ -1,14 +1,20 @@
// mod add;
// mod delete;
// mod expunge;
pub mod create;
pub mod delete;
pub mod expunge;
pub mod list;
// mod purge;
pub mod purge;
use anyhow::Result;
use clap::Subcommand;
use pimalaya_toolbox::terminal::printer::Printer;
use crate::{config::ImapConfig, imap::mailbox::command::list::ListMailboxesCommand};
use crate::{
config::ImapConfig,
imap::mailbox::command::{
create::CreateMailboxCommand, delete::DeleteMailboxCommand,
expunge::ExpungeMailboxCommand, list::ListMailboxesCommand, purge::PurgeMailboxCommand,
},
};
/// Create, list and purge mailboxes.
///
@@ -16,28 +22,23 @@ use crate::{config::ImapConfig, imap::mailbox::command::list::ListMailboxesComma
/// manage them.
#[derive(Debug, Subcommand)]
pub enum MailboxCommand {
// #[command(visible_alias = "create", alias = "new")]
// Add(FolderAddCommand),
#[command(alias = "add", alias = "new")]
Create(CreateMailboxCommand),
#[command(alias = "remove", alias = "rm")]
Delete(DeleteMailboxCommand),
Expunge(ExpungeMailboxCommand),
List(ListMailboxesCommand),
// #[command()]
// Expunge(FolderExpungeCommand),
// #[command()]
// Purge(FolderPurgeCommand),
// #[command(alias = "remove", alias = "rm")]
// Delete(FolderDeleteCommand),
Purge(PurgeMailboxCommand),
}
impl MailboxCommand {
#[allow(unused)]
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
match self {
// Self::Add(cmd) => cmd.execute(printer, config).await,
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::Expunge(cmd) => cmd.execute(printer, config).await,
// Self::Purge(cmd) => cmd.execute(printer, config).await,
// Self::Delete(cmd) => cmd.execute(printer, config).await,
Self::Purge(cmd) => cmd.execute(printer, config),
}
}
}
+57 -51
View File
@@ -1,70 +1,76 @@
use std::{process, sync::Arc};
use anyhow::{bail, Result};
use clap::Parser;
use color_eyre::Result;
use email::{backend::feature::BackendFeatureSource, config::Config, folder::purge::PurgeFolder};
use pimalaya_tui::{
himalaya::backend::BackendBuilder,
terminal::{cli::printer::Printer, config::TomlConfig as _, prompt},
use io_imap::{
coroutines::{expunge::*, select::*, store::*},
types::{
flag::{Flag, StoreType},
sequence::SequenceSet,
},
};
use tracing::info;
use io_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::{Message, Printer};
use crate::{
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
};
use crate::{config::ImapConfig, imap::mailbox::arg::name::MailboxNameArg, imap::stream};
/// Purge the given folder.
/// Purge the given mailbox.
///
/// All emails from the given folder are definitely deleted. The
/// purged folder will remain empty after execution of the command.
/// All emails from the given mailbox are definitely deleted. The
/// purged mailbox will remain empty after execution of the command.
#[derive(Debug, Parser)]
pub struct FolderPurgeCommand {
pub struct PurgeMailboxCommand {
#[command(flatten)]
pub folder: FolderNameArg,
#[command(flatten)]
pub account: AccountNameFlag,
#[arg(long, short)]
pub yes: bool,
pub mailbox: MailboxNameArg,
}
impl FolderPurgeCommand {
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
info!("executing purge folder command");
impl PurgeMailboxCommand {
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
let (context, mut stream) = stream::connect(config)?;
let folder = &self.folder.name;
let mailbox = self.mailbox.name.try_into()?;
if !self.yes {
let confirm = format!("Do you really want to purge the folder {folder}");
let confirm = format!("{confirm}? All emails will be definitely deleted.");
// First, select the mailbox
let mut arg = None;
let mut coroutine = ImapSelect::new(context, mailbox);
if !prompt::bool(confirm, false)? {
process::exit(0);
};
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),
}
};
let (toml_account_config, account_config) = config
.clone()
.into_account_configs(self.account.name.as_deref(), |c: &Config, name| {
c.account(name).ok()
})?;
// Then, mark all messages as deleted (1:*)
let mut arg = None;
let sequence_set: SequenceSet = "1:*".try_into()?;
let mut coroutine = ImapStoreSilent::new(
context,
sequence_set,
StoreType::Add,
vec![Flag::Deleted],
false,
);
let backend = BackendBuilder::new(
Arc::new(toml_account_config),
Arc::new(account_config),
|builder| {
builder
.without_features()
.with_purge_folder(BackendFeatureSource::Context)
},
)
.without_sending_backend()
.build()
.await?;
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),
}
};
backend.purge_folder(folder).await?;
// Finally, expunge the mailbox
let mut arg = None;
let mut coroutine = ImapExpunge::new(context);
printer.out(format!("Folder {folder} successfully purged!\n"))
loop {
match coroutine.resume(arg.take()) {
ImapExpungeResult::Io(io) => arg = Some(handle(&mut stream, io)?),
ImapExpungeResult::Ok { .. } => break,
ImapExpungeResult::Err { err, .. } => bail!(err),
}
}
printer.out(Message::new("Mailbox successfully purged"))
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
// pub mod arg;
pub mod arg;
pub mod command;