mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-17 21:29:24 +08:00
rename imap folder into mailbox
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::{account::Account, imap::mailbox::command::MailboxCommand};
|
||||
|
||||
/// IMAP CLI (requires `imap` cargo feature).
|
||||
///
|
||||
/// This command gives you access to the IMAP CLI API, and allows
|
||||
/// you to manage IMAP mailboxes: list mailboxes, read messages,
|
||||
/// add flags etc.
|
||||
#[derive(Debug, Subcommand)]
|
||||
#[command(rename_all = "lowercase")]
|
||||
pub enum ImapCommand {
|
||||
#[command(subcommand)]
|
||||
#[command(aliases = ["mboxes", "mbox"])]
|
||||
Mailboxes(MailboxCommand),
|
||||
}
|
||||
|
||||
impl ImapCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: Account) -> Result<()> {
|
||||
match self {
|
||||
Self::Mailboxes(cmd) => cmd.execute(printer, account),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod name;
|
||||
@@ -0,0 +1,60 @@
|
||||
use clap::Parser;
|
||||
use email::folder::INBOX;
|
||||
|
||||
/// The optional folder 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 name: String,
|
||||
}
|
||||
|
||||
impl Default for FolderNameOptionalFlag {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: INBOX.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The optional folder 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 name: String,
|
||||
}
|
||||
|
||||
impl Default for FolderNameOptionalArg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: INBOX.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The required folder name argument parser.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct FolderNameArg {
|
||||
/// The name of the folder.
|
||||
#[arg(name = "folder_name", value_name = "FOLDER")]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// The optional source folder 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 name: String,
|
||||
}
|
||||
|
||||
/// The target folder name argument parser.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct TargetFolderNameArg {
|
||||
/// The name of the target folder.
|
||||
#[arg(name = "target_folder_name", value_name = "TARGET")]
|
||||
pub name: String,
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
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"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use std::{process, sync::Arc};
|
||||
|
||||
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 crate::{
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
|
||||
};
|
||||
|
||||
/// Delete the given folder.
|
||||
///
|
||||
/// All emails from the given folder are definitely deleted. The
|
||||
/// folder is also deleted after execution of the command.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct FolderDeleteCommand {
|
||||
#[command(flatten)]
|
||||
pub folder: FolderNameArg,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
|
||||
#[arg(long, short)]
|
||||
pub yes: bool,
|
||||
}
|
||||
|
||||
impl FolderDeleteCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing delete folder command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
||||
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.");
|
||||
|
||||
if !prompt::bool(confirm, false)? {
|
||||
process::exit(0);
|
||||
};
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
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 crate::{
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
|
||||
};
|
||||
|
||||
/// Expunge the given folder.
|
||||
///
|
||||
/// The concept of expunging is similar to the IMAP one: it definitely
|
||||
/// deletes emails from the given folder that contain the "deleted"
|
||||
/// flag.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct FolderExpungeCommand {
|
||||
#[command(flatten)]
|
||||
pub folder: FolderNameArg,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
}
|
||||
|
||||
impl FolderExpungeCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing expunge 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_expunge_folder(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.expunge_folder(folder).await?;
|
||||
|
||||
printer.out(format!("Folder {folder} successfully expunged!\n"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use io_imap::{
|
||||
coroutines::{
|
||||
authenticate::*, authenticate_anonymous::*, authenticate_plain::*, list::*, login::*,
|
||||
},
|
||||
types::response::Capability,
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use log::warn;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::{account::Account, sasl::SaslMechanism, stream};
|
||||
|
||||
/// List all mailboxes.
|
||||
///
|
||||
/// This command allows you to list all exsting mailboxes from your
|
||||
/// IMAP account.
|
||||
#[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>,
|
||||
}
|
||||
|
||||
impl ListMailboxesCommand {
|
||||
pub fn execute(self, printer: &mut impl Printer, account: Account) -> Result<()> {
|
||||
let imap = account.imap.unwrap();
|
||||
|
||||
let (mut context, mut stream) = if imap.tls.disable {
|
||||
let port = imap.port.unwrap_or(143);
|
||||
stream::tcp(imap.host, port)?
|
||||
} else {
|
||||
let port = imap.port.unwrap_or(if imap.starttls { 143 } else { 993 });
|
||||
stream::rustls(imap.host, port, imap.starttls, imap.tls.cert)?
|
||||
};
|
||||
|
||||
let ir = context.capability.contains(&Capability::SaslIr);
|
||||
|
||||
let mut candidates = vec![];
|
||||
|
||||
for mechanism in imap.sasl.mechanisms {
|
||||
match mechanism {
|
||||
SaslMechanism::Login => {
|
||||
let Some(ref auth) = imap.sasl.login else {
|
||||
warn!("missing SASL LOGIN configuration, skipping it");
|
||||
continue;
|
||||
};
|
||||
|
||||
let params = ImapLoginParams::new(&auth.username, auth.password.get()?)?;
|
||||
candidates.push(ImapAuthenticateCandidate::Login(params));
|
||||
}
|
||||
SaslMechanism::Plain => {
|
||||
let Some(ref auth) = imap.sasl.plain else {
|
||||
warn!("missing SASL PLAIN configuration, skipping it");
|
||||
continue;
|
||||
};
|
||||
|
||||
let params = ImapAuthenticatePlainParams::new(
|
||||
auth.authzid.as_ref(),
|
||||
&auth.authcid,
|
||||
auth.passwd.get()?,
|
||||
ir,
|
||||
);
|
||||
|
||||
candidates.push(ImapAuthenticateCandidate::Plain(params))
|
||||
}
|
||||
SaslMechanism::Anonymous => {
|
||||
let msg = imap
|
||||
.sasl
|
||||
.anonymous
|
||||
.as_ref()
|
||||
.and_then(|auth| auth.message.as_ref());
|
||||
|
||||
let params = ImapAuthenticateAnonymousParams::new(msg, ir);
|
||||
|
||||
candidates.push(ImapAuthenticateCandidate::Anonymous(params))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapAuthenticate::new(context, candidates);
|
||||
|
||||
loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapAuthenticateResult::Io(io) => arg = Some(handle(&mut stream, io)?),
|
||||
ImapAuthenticateResult::Ok { context: c } => break context = c,
|
||||
ImapAuthenticateResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
}
|
||||
|
||||
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(ok) => break ok.mailboxes,
|
||||
ImapListResult::Err(err) => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
println!("mailboxes: {mailboxes:#?}");
|
||||
|
||||
// TODO: list folders
|
||||
|
||||
// let folders = Folders::from(backend.list_folders().await?);
|
||||
// let table = FoldersTable::from(folders)
|
||||
// .with_some_width(self.table_max_width)
|
||||
// .with_some_preset(toml_account_config.folder_list_table_preset())
|
||||
// .with_some_name_color(toml_account_config.folder_list_table_name_color())
|
||||
// .with_some_desc_color(toml_account_config.folder_list_table_desc_color());
|
||||
|
||||
// printer.out(table)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// mod add;
|
||||
// mod delete;
|
||||
// mod expunge;
|
||||
pub mod list;
|
||||
// mod purge;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::{account::Account, imap::mailbox::command::list::ListMailboxesCommand};
|
||||
|
||||
/// Create, list and purge mailboxes.
|
||||
///
|
||||
/// A mailbox is a message container. This subcommand allows you to
|
||||
/// manage them.
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MailboxCommand {
|
||||
// #[command(visible_alias = "create", alias = "new")]
|
||||
// Add(FolderAddCommand),
|
||||
List(ListMailboxesCommand),
|
||||
// #[command()]
|
||||
// Expunge(FolderExpungeCommand),
|
||||
|
||||
// #[command()]
|
||||
// Purge(FolderPurgeCommand),
|
||||
|
||||
// #[command(alias = "remove", alias = "rm")]
|
||||
// Delete(FolderDeleteCommand),
|
||||
}
|
||||
|
||||
impl MailboxCommand {
|
||||
#[allow(unused)]
|
||||
pub fn execute(self, printer: &mut impl Printer, account: Account) -> Result<()> {
|
||||
match self {
|
||||
// Self::Add(cmd) => cmd.execute(printer, config).await,
|
||||
Self::List(cmd) => cmd.execute(printer, account),
|
||||
// Self::Expunge(cmd) => cmd.execute(printer, config).await,
|
||||
// Self::Purge(cmd) => cmd.execute(printer, config).await,
|
||||
// Self::Delete(cmd) => cmd.execute(printer, config).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use std::{process, sync::Arc};
|
||||
|
||||
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 tracing::info;
|
||||
|
||||
use crate::{
|
||||
account::arg::name::AccountNameFlag, config::TomlConfig, folder::arg::name::FolderNameArg,
|
||||
};
|
||||
|
||||
/// Purge the given folder.
|
||||
///
|
||||
/// All emails from the given folder are definitely deleted. The
|
||||
/// purged folder will remain empty after execution of the command.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct FolderPurgeCommand {
|
||||
#[command(flatten)]
|
||||
pub folder: FolderNameArg,
|
||||
|
||||
#[command(flatten)]
|
||||
pub account: AccountNameFlag,
|
||||
|
||||
#[arg(long, short)]
|
||||
pub yes: bool,
|
||||
}
|
||||
|
||||
impl FolderPurgeCommand {
|
||||
pub async fn execute(self, printer: &mut impl Printer, config: &TomlConfig) -> Result<()> {
|
||||
info!("executing purge folder command");
|
||||
|
||||
let folder = &self.folder.name;
|
||||
|
||||
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.");
|
||||
|
||||
if !prompt::bool(confirm, false)? {
|
||||
process::exit(0);
|
||||
};
|
||||
};
|
||||
|
||||
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_purge_folder(BackendFeatureSource::Context)
|
||||
},
|
||||
)
|
||||
.without_sending_backend()
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
backend.purge_folder(folder).await?;
|
||||
|
||||
printer.out(format!("Folder {folder} successfully purged!\n"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// pub mod arg;
|
||||
pub mod command;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod command;
|
||||
pub mod mailbox;
|
||||
Reference in New Issue
Block a user