mirror of
https://github.com/pimalaya/himalaya.git
synced 2026-06-15 20:07:57 +08:00
clean unused config, add id command
This commit is contained in:
+17
-24
@@ -8,41 +8,34 @@ use dirs::download_dir;
|
||||
#[derive(Debug)]
|
||||
pub struct Account<B> {
|
||||
pub backend: B,
|
||||
|
||||
pub email: String,
|
||||
pub display_name: Option<String>,
|
||||
pub signature: String,
|
||||
pub downloads_dir: PathBuf,
|
||||
|
||||
pub table_preset: &'static str,
|
||||
pub table_preset: String,
|
||||
}
|
||||
|
||||
impl<B> Account<B> {
|
||||
pub fn new(config: Config, account_config: AccountConfig, backend: B) -> Result<Self> {
|
||||
Ok(Self {
|
||||
backend,
|
||||
email: account_config.email,
|
||||
display_name: account_config.display_name.or(config.display_name),
|
||||
signature: match account_config.signature.or(config.signature) {
|
||||
None => String::new(),
|
||||
Some(ref signature) => {
|
||||
account_config
|
||||
.signature_delim
|
||||
.or(config.signature_delim)
|
||||
.unwrap_or(String::from("-- \n"))
|
||||
+ signature
|
||||
}
|
||||
},
|
||||
downloads_dir: match account_config
|
||||
|
||||
downloads_dir: account_config
|
||||
.downloads_dir
|
||||
.as_ref()
|
||||
.and_then(|dir| dir.to_str())
|
||||
{
|
||||
Some(dir) => PathBuf::from(shellexpand::full(dir)?.to_string()),
|
||||
None => download_dir().unwrap_or_else(temp_dir),
|
||||
},
|
||||
.and_then(|dir| shellexpand::full(dir).ok())
|
||||
.map(|dir| PathBuf::from(dir.to_string()))
|
||||
.or(config
|
||||
.downloads_dir
|
||||
.as_ref()
|
||||
.and_then(|dir| dir.to_str())
|
||||
.and_then(|dir| shellexpand::full(dir).ok())
|
||||
.map(|dir| PathBuf::from(dir.to_string())))
|
||||
.or(download_dir())
|
||||
.unwrap_or_else(temp_dir),
|
||||
|
||||
table_preset: presets::UTF8_FULL_CONDENSED,
|
||||
table_preset: config
|
||||
.table_preset
|
||||
.or(account_config.table_preset)
|
||||
.unwrap_or(presets::UTF8_FULL_CONDENSED.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+4
-34
@@ -12,11 +12,8 @@ use url::Url;
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
pub display_name: Option<String>,
|
||||
pub signature: Option<String>,
|
||||
pub signature_delim: Option<String>,
|
||||
pub downloads_dir: Option<PathBuf>,
|
||||
|
||||
pub table_preset: Option<String>,
|
||||
pub accounts: HashMap<String, AccountConfig>,
|
||||
}
|
||||
|
||||
@@ -48,12 +45,8 @@ pub struct AccountConfig {
|
||||
#[serde(default)]
|
||||
pub default: bool,
|
||||
|
||||
#[serde(deserialize_with = "shell_expanded_string")]
|
||||
pub email: String,
|
||||
pub display_name: Option<String>,
|
||||
pub signature: Option<String>,
|
||||
pub signature_delim: Option<String>,
|
||||
pub downloads_dir: Option<PathBuf>,
|
||||
pub table_preset: Option<String>,
|
||||
|
||||
pub imap: Option<ImapConfig>,
|
||||
pub smtp: Option<SmtpConfig>,
|
||||
@@ -64,36 +57,12 @@ pub struct AccountConfig {
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct ImapConfig {
|
||||
pub url: Url,
|
||||
|
||||
#[serde(default)]
|
||||
pub tls: TlsConfig,
|
||||
#[serde(default)]
|
||||
pub starttls: bool,
|
||||
#[serde(default)]
|
||||
pub sasl: SaslConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub ext: ImapExtensionsConfig,
|
||||
}
|
||||
|
||||
/// IMAP extensions configuration.
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct ImapExtensionsConfig {
|
||||
#[serde(default)]
|
||||
id: ImapIdExtensionConfig,
|
||||
}
|
||||
|
||||
/// IMAP ID configuration.
|
||||
///
|
||||
/// https://www.rfc-editor.org/rfc/rfc2971.html
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct ImapIdExtensionConfig {
|
||||
/// Automatically sends the ID command straight after
|
||||
/// authentication.
|
||||
#[serde(default)]
|
||||
send_after_auth: bool,
|
||||
}
|
||||
|
||||
/// SMTP configuration.
|
||||
@@ -101,7 +70,6 @@ pub struct ImapIdExtensionConfig {
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct SmtpConfig {
|
||||
pub url: Url,
|
||||
|
||||
#[serde(default)]
|
||||
pub tls: TlsConfig,
|
||||
#[serde(default)]
|
||||
@@ -171,6 +139,7 @@ pub enum SaslMechanismConfig {
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct SaslLoginConfig {
|
||||
#[serde(deserialize_with = "shell_expanded_string")]
|
||||
pub username: String,
|
||||
pub password: SecretConfig,
|
||||
}
|
||||
@@ -180,6 +149,7 @@ pub struct SaslLoginConfig {
|
||||
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
|
||||
pub struct SaslPlainConfig {
|
||||
pub authzid: Option<String>,
|
||||
#[serde(deserialize_with = "shell_expanded_string")]
|
||||
pub authcid: String,
|
||||
pub passwd: SecretConfig,
|
||||
}
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@ use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::imap::{
|
||||
account::ImapAccount, envelope::command::EnvelopeCommand, flag::command::FlagCommand,
|
||||
mailbox::command::MailboxCommand, message::command::MessageCommand,
|
||||
id::IdCommand, mailbox::command::MailboxCommand, message::command::MessageCommand,
|
||||
};
|
||||
|
||||
/// IMAP CLI (requires `imap` cargo feature).
|
||||
@@ -25,6 +25,7 @@ pub enum ImapCommand {
|
||||
#[command(subcommand)]
|
||||
#[command(aliases = ["msgs", "msg"])]
|
||||
Messages(MessageCommand),
|
||||
Id(IdCommand),
|
||||
}
|
||||
|
||||
impl ImapCommand {
|
||||
@@ -32,6 +33,7 @@ impl ImapCommand {
|
||||
match self {
|
||||
Self::Envelopes(cmd) => cmd.exec(printer, account),
|
||||
Self::Flags(cmd) => cmd.exec(printer, account),
|
||||
Self::Id(cmd) => cmd.exec(printer, account),
|
||||
Self::Mailboxes(cmd) => cmd.exec(printer, account),
|
||||
Self::Messages(cmd) => cmd.exec(printer, account),
|
||||
}
|
||||
|
||||
@@ -2,15 +2,12 @@ use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::{
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
envelope::{
|
||||
get::GetEnvelopeCommand, list::ListEnvelopesCommand, search::SearchEnvelopesCommand,
|
||||
sort::SortEnvelopesCommand, thread::ThreadEnvelopesCommand,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// Manage message envelopes.
|
||||
|
||||
@@ -2,15 +2,12 @@ use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::{
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
flag::{
|
||||
add::AddFlagsCommand, list::ListFlagsCommand, remove::RemoveFlagsCommand,
|
||||
set::SetFlagsCommand,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// Manage message flags.
|
||||
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use io_imap::{
|
||||
coroutines::id::*,
|
||||
types::{
|
||||
core::{IString, NString},
|
||||
IntoStatic,
|
||||
},
|
||||
};
|
||||
use io_stream::runtimes::std::handle;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::imap::{account::ImapAccount, stream};
|
||||
|
||||
/// Get information about the IMAP server.
|
||||
///
|
||||
/// This command allows you to exchange parameters with the IMAP
|
||||
/// server accordingly to the [RFC 2971]. Some providers like mail.qq
|
||||
/// enforce sending ID command before selecting a mailbox.
|
||||
///
|
||||
/// [RFC 2971]: https://www.rfc-editor.org/rfc/rfc2971.html
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct IdCommand {
|
||||
#[arg(short, long, num_args = 1..)]
|
||||
#[arg(value_name = "KEY:VAL", value_parser = parameter_parser)]
|
||||
parameter: Option<Vec<(IString<'static>, NString<'static>)>>,
|
||||
}
|
||||
|
||||
impl IdCommand {
|
||||
pub fn exec(self, printer: &mut impl Printer, account: ImapAccount) -> Result<()> {
|
||||
let (context, mut stream) = stream::connect(account.backend)?;
|
||||
|
||||
let mut params = HashMap::new();
|
||||
|
||||
params.extend([
|
||||
(
|
||||
IString::try_from("name").unwrap(),
|
||||
NString::try_from(env!("CARGO_PKG_NAME")).unwrap(),
|
||||
),
|
||||
(
|
||||
IString::try_from("version").unwrap(),
|
||||
NString::try_from(env!("CARGO_PKG_VERSION")).unwrap(),
|
||||
),
|
||||
(
|
||||
IString::try_from("vendor").unwrap(),
|
||||
NString::try_from("Pimalaya").unwrap(),
|
||||
),
|
||||
(
|
||||
IString::try_from("support-url").unwrap(),
|
||||
NString::try_from("https://github.com/pimalaya/himalaya").unwrap(),
|
||||
),
|
||||
]);
|
||||
|
||||
if let Some(more) = self.parameter {
|
||||
params.extend(more);
|
||||
}
|
||||
|
||||
let mut arg = None;
|
||||
let mut coroutine = ImapId::new(context, Some(params.into_iter().collect()));
|
||||
|
||||
let params = loop {
|
||||
match coroutine.resume(arg.take()) {
|
||||
ImapIdResult::Io { io } => arg = Some(handle(&mut stream, io)?),
|
||||
ImapIdResult::Ok { server_id, .. } => break server_id,
|
||||
ImapIdResult::Err { err, .. } => bail!(err),
|
||||
}
|
||||
};
|
||||
|
||||
let table = ServerIdTable {
|
||||
preset: account.table_preset,
|
||||
server_id: params
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(key, val)| {
|
||||
(
|
||||
String::from_utf8(key.into_inner().into_owned()).unwrap(),
|
||||
match val.into_option() {
|
||||
Some(val) => Some(String::from_utf8(val.into_owned()).unwrap()),
|
||||
None => None,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
printer.out(table)
|
||||
}
|
||||
}
|
||||
|
||||
fn parameter_parser(param: &str) -> Result<(IString<'static>, NString<'static>), String> {
|
||||
let Some((key, val)) = param.split_once(':') else {
|
||||
return Err(format!("Invalid parameter `{param}`: missing `:`"));
|
||||
};
|
||||
|
||||
let Ok(ikey) = IString::try_from(key.trim()) else {
|
||||
return Err(format!("Invalid parameter key `{key}`"));
|
||||
};
|
||||
|
||||
let nval = if val.trim().is_empty() {
|
||||
NString::NIL
|
||||
} else {
|
||||
let Ok(nval) = NString::try_from(val.trim()) else {
|
||||
return Err(format!("Invalid parameter value `{val}` for `{key}`"));
|
||||
};
|
||||
|
||||
nval
|
||||
};
|
||||
|
||||
Ok((ikey.into_static(), nval.into_static()))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct ServerIdTable {
|
||||
#[serde(skip)]
|
||||
pub preset: String,
|
||||
pub server_id: HashMap<String, Option<String>>,
|
||||
}
|
||||
|
||||
impl fmt::Display for ServerIdTable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut table = Table::new();
|
||||
|
||||
table
|
||||
.load_preset(&self.preset)
|
||||
.set_header(Row::from([Cell::new("PARAMETER"), Cell::new("VALUE")]));
|
||||
|
||||
for (key, val) in &self.server_id {
|
||||
table.add_row(Row::from([
|
||||
Cell::new(key),
|
||||
match val {
|
||||
Some(val) => Cell::new(val),
|
||||
None => Cell::new(""),
|
||||
},
|
||||
]));
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
writeln!(f, "{table}")
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,14 @@ use anyhow::Result;
|
||||
use clap::Subcommand;
|
||||
use pimalaya_toolbox::terminal::printer::Printer;
|
||||
|
||||
use crate::{
|
||||
config::ImapConfig,
|
||||
imap::{
|
||||
use crate::imap::{
|
||||
account::ImapAccount,
|
||||
mailbox::{
|
||||
close::CloseMailboxCommand, create::CreateMailboxCommand, delete::DeleteMailboxCommand,
|
||||
expunge::ExpungeMailboxCommand, list::ListMailboxesCommand, purge::PurgeMailboxCommand,
|
||||
rename::RenameMailboxCommand, select::SelectMailboxCommand,
|
||||
status::StatusMailboxCommand, subscribe::SubscribeMailboxCommand,
|
||||
unselect::UnselectMailboxCommand, unsubscribe::UnsubscribeMailboxCommand,
|
||||
},
|
||||
rename::RenameMailboxCommand, select::SelectMailboxCommand, status::StatusMailboxCommand,
|
||||
subscribe::SubscribeMailboxCommand, unselect::UnselectMailboxCommand,
|
||||
unsubscribe::UnsubscribeMailboxCommand,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{Cell, ContentArrangement, Row, Table};
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use io_imap::{
|
||||
coroutines::{list::*, lsub::*},
|
||||
types::{core::QuotedChar, flag::FlagNameAttribute, mailbox::Mailbox},
|
||||
@@ -13,7 +13,7 @@ use serde::{Serialize, Serializer};
|
||||
|
||||
use crate::imap::{account::ImapAccount, stream};
|
||||
|
||||
/// List mailboxes.
|
||||
/// List, search and filter mailboxes.
|
||||
///
|
||||
/// This command allows you to list mailboxes from your IMAP account.
|
||||
/// By default, only subscribed mailboxes are listed. Use --all to
|
||||
@@ -65,8 +65,8 @@ impl ListMailboxesCommand {
|
||||
};
|
||||
|
||||
let table = MailboxesTable {
|
||||
rows: mailboxes.into_iter().map(From::from).collect(),
|
||||
preset: account.table_preset,
|
||||
rows: mailboxes.into_iter().map(From::from).collect(),
|
||||
};
|
||||
|
||||
printer.out(table)
|
||||
@@ -75,8 +75,8 @@ impl ListMailboxesCommand {
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MailboxesTable {
|
||||
pub preset: String,
|
||||
pub rows: Vec<MailboxRow>,
|
||||
pub preset: &'static str,
|
||||
}
|
||||
|
||||
impl fmt::Display for MailboxesTable {
|
||||
@@ -84,8 +84,7 @@ impl fmt::Display for MailboxesTable {
|
||||
let mut table = Table::new();
|
||||
|
||||
table
|
||||
.load_preset(self.preset)
|
||||
.set_content_arrangement(ContentArrangement::DynamicFullWidth)
|
||||
.load_preset(&self.preset)
|
||||
.set_header(Row::from([
|
||||
Cell::new("NAME"),
|
||||
Cell::new("DELIMITER"),
|
||||
@@ -93,11 +92,11 @@ impl fmt::Display for MailboxesTable {
|
||||
]))
|
||||
.add_rows(self.rows.iter().map(|mbox| {
|
||||
let mut row = Row::new();
|
||||
row.max_height(1);
|
||||
|
||||
row.add_cell(Cell::new(&mbox.name));
|
||||
row.add_cell(Cell::new(&mbox.delimiter));
|
||||
row.add_cell(Cell::new(&mbox.attributes.join(", ")));
|
||||
row.max_height(1)
|
||||
.add_cell(Cell::new(&mbox.name))
|
||||
.add_cell(Cell::new(&mbox.delimiter))
|
||||
.add_cell(Cell::new(&mbox.attributes.join(", ")));
|
||||
|
||||
row
|
||||
}));
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::fmt;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use clap::Parser;
|
||||
use comfy_table::{presets, Cell, ContentArrangement, Row, Table};
|
||||
use comfy_table::{Cell, Row, Table};
|
||||
use io_imap::{
|
||||
coroutines::status::*,
|
||||
types::status::{StatusDataItem, StatusDataItemName},
|
||||
@@ -47,10 +47,12 @@ impl StatusMailboxCommand {
|
||||
}
|
||||
};
|
||||
|
||||
let table = MailboxStatusTable::from(items);
|
||||
let table = MailboxStatusTable {
|
||||
preset: account.table_preset,
|
||||
status: items.into(),
|
||||
};
|
||||
|
||||
printer.out(table)?;
|
||||
Ok(())
|
||||
printer.out(table)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,15 +99,8 @@ impl From<Vec<StatusDataItem>> for MailboxStatus {
|
||||
}
|
||||
|
||||
pub struct MailboxStatusTable {
|
||||
status: MailboxStatus,
|
||||
}
|
||||
|
||||
impl From<Vec<StatusDataItem>> for MailboxStatusTable {
|
||||
fn from(items: Vec<StatusDataItem>) -> Self {
|
||||
Self {
|
||||
status: MailboxStatus::from(items),
|
||||
}
|
||||
}
|
||||
pub preset: String,
|
||||
pub status: MailboxStatus,
|
||||
}
|
||||
|
||||
impl fmt::Display for MailboxStatusTable {
|
||||
@@ -113,8 +108,7 @@ impl fmt::Display for MailboxStatusTable {
|
||||
let mut table = Table::new();
|
||||
|
||||
table
|
||||
.load_preset(presets::ASCII_MARKDOWN)
|
||||
.set_content_arrangement(ContentArrangement::DynamicFullWidth)
|
||||
.load_preset(&self.preset)
|
||||
.set_header(Row::from([Cell::new("ATTRIBUTE"), Cell::new("VALUE")]));
|
||||
|
||||
if let Some(n) = self.status.messages {
|
||||
|
||||
@@ -126,7 +126,7 @@ impl ExportMessageCommand {
|
||||
|
||||
// Generate filename from subject or message-id
|
||||
let filename = generate_eml_filename(&message, self.id);
|
||||
let dir = self.directory.unwrap_or_else(|| PathBuf::from("."));
|
||||
let dir = self.directory.unwrap_or(account.downloads_dir);
|
||||
|
||||
if !dir.exists() {
|
||||
fs::create_dir_all(&dir)?;
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod account;
|
||||
pub mod command;
|
||||
pub mod envelope;
|
||||
pub mod flag;
|
||||
pub mod id;
|
||||
pub mod mailbox;
|
||||
pub mod message;
|
||||
pub mod stream;
|
||||
|
||||
Reference in New Issue
Block a user