repair mailbox table, add unix stream support

This commit is contained in:
Clément DOUIN
2026-03-03 20:16:05 +01:00
parent 297f5773aa
commit ffa26594a4
4 changed files with 492 additions and 116 deletions
+170 -88
View File
@@ -1,8 +1,13 @@
use std::{fmt, ops::Deref};
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_stream::runtimes::std::handle;
use pimalaya_toolbox::terminal::printer::Printer;
use serde::{Serialize, Serializer};
use crate::{config::ImapConfig, imap::stream};
@@ -23,7 +28,7 @@ pub struct ListMailboxesCommand {
}
impl ListMailboxesCommand {
pub fn execute(self, _printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
pub fn execute(self, printer: &mut impl Printer, config: ImapConfig) -> Result<()> {
let (context, mut stream) = stream::connect(config)?;
let mut arg = None;
@@ -37,109 +42,186 @@ impl ListMailboxesCommand {
}
};
println!("mailboxes: {mailboxes:#?}");
let table = MailboxesTable::from(mailboxes);
// TODO: list mailboxs
// let mailboxs = Mailboxs::from(backend.list_mailboxs().await?);
// let table = MailboxsTable::from(mailboxs)
// .with_some_width(self.table_max_width)
// .with_some_preset(toml_account_config.mailbox_list_table_preset())
// .with_some_name_color(toml_account_config.mailbox_list_table_name_color())
// .with_some_desc_color(toml_account_config.mailbox_list_table_desc_color());
// printer.out(table)?;
printer.out(table)?;
Ok(())
}
}
// #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
// #[serde(rename_all = "kebab-case")]
// pub struct ListMailboxesTableConfig {
// pub preset: Option<String>,
// pub name_color: Option<Color>,
// pub desc_color: Option<Color>,
// }
#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct ListMailboxesTableConfig {
pub preset: Option<String>,
pub name_color: Option<Color>,
pub desc_color: Option<Color>,
}
// impl ListMailboxesTableConfig {
// pub fn preset(&self) -> &str {
// self.preset.as_deref().unwrap_or(presets::ASCII_MARKDOWN)
// }
impl ListMailboxesTableConfig {
pub fn preset(&self) -> &str {
self.preset.as_deref().unwrap_or(presets::ASCII_MARKDOWN)
}
// pub fn name_color(&self) -> comfy_table::Color {
// map_color(self.name_color.unwrap_or(Color::Blue))
// }
pub fn name_color(&self) -> comfy_table::Color {
map_color(self.name_color.unwrap_or(Color::Blue))
}
// pub fn desc_color(&self) -> comfy_table::Color {
// map_color(self.desc_color.unwrap_or(Color::Green))
// }
// }
pub fn desc_color(&self) -> comfy_table::Color {
map_color(self.desc_color.unwrap_or(Color::Green))
}
}
// pub struct MailboxesTable {
// mailboxes: Vec<Mailbox<'static>>,
// width: Option<u16>,
// config: ListMailboxesTableConfig,
// }
#[derive(Clone, Debug, Serialize)]
pub struct Mailbox {
pub name: String,
pub delimiter: String,
pub attributes: Vec<String>,
}
// impl MailboxesTable {
// pub fn with_some_width(mut self, width: Option<u16>) -> Self {
// self.width = width;
// self
// }
#[derive(Clone, Debug, Default, Serialize)]
pub struct Mailboxes(Vec<Mailbox>);
// pub fn with_some_preset(mut self, preset: Option<String>) -> Self {
// self.config.preset = preset;
// self
// }
impl<T: IntoIterator<Item = Mailbox>> From<T> for Mailboxes {
fn from(mboxes: T) -> Self {
Self(mboxes.into_iter().collect())
}
}
// pub fn with_some_name_color(mut self, color: Option<Color>) -> Self {
// self.config.name_color = color;
// self
// }
impl Deref for Mailboxes {
type Target = Vec<Mailbox>;
// pub fn with_some_desc_color(mut self, color: Option<Color>) -> Self {
// self.config.desc_color = color;
// self
// }
// }
fn deref(&self) -> &Self::Target {
&self.0
}
}
// impl From<Mailboxes> for MailboxesTable {
// fn from(mailboxes: Mailboxes) -> Self {
// Self {
// mailboxes,
// width: None,
// config: Default::default(),
// }
// }
// }
pub struct MailboxesTable {
mailboxes: Mailboxes,
width: Option<u16>,
config: ListMailboxesTableConfig,
}
// impl fmt::Display for MailboxesTable {
// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// let mut table = Table::new();
impl MailboxesTable {
pub fn with_some_width(mut self, width: Option<u16>) -> Self {
self.width = width;
self
}
// table
// .load_preset(self.config.preset())
// .set_content_arrangement(ContentArrangement::DynamicFullWidth)
// .set_header(Row::from([Cell::new("NAME"), Cell::new("DESC")]))
// .add_rows(
// self.mailboxes
// .iter()
// .map(|mailbox| mailbox.to_row(&self.config)),
// );
pub fn with_some_preset(mut self, preset: Option<String>) -> Self {
self.config.preset = preset;
self
}
// if let Some(width) = self.width {
// table.set_width(width);
// }
pub fn with_some_name_color(mut self, color: Option<Color>) -> Self {
self.config.name_color = color;
self
}
// writeln!(f)?;
// write!(f, "{table}")?;
// writeln!(f)?;
// Ok(())
// }
// }
pub fn with_some_desc_color(mut self, color: Option<Color>) -> Self {
self.config.desc_color = color;
self
}
}
// impl Serialize for MailboxesTable {
// fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
// self.mailboxes.serialize(serializer)
// }
// }
impl
From<
Vec<(
io_imap::types::mailbox::Mailbox<'static>,
Option<io_imap::types::core::QuotedChar>,
Vec<io_imap::types::flag::FlagNameAttribute<'static>>,
)>,
> for MailboxesTable
{
fn from(
mailboxes: Vec<(
io_imap::types::mailbox::Mailbox<'static>,
Option<io_imap::types::core::QuotedChar>,
Vec<io_imap::types::flag::FlagNameAttribute<'static>>,
)>,
) -> Self {
Self {
mailboxes: mailboxes
.into_iter()
.map(|(mbox, delim, attrs)| Mailbox {
name: match mbox {
io_imap::types::mailbox::Mailbox::Inbox => "Inbox".into(),
io_imap::types::mailbox::Mailbox::Other(mbox) => {
String::from_utf8_lossy(mbox.inner().as_ref()).to_string()
}
},
delimiter: match delim {
Some(delim) => delim.inner().to_string(),
None => String::new(),
},
attributes: attrs.into_iter().map(|attr| attr.to_string()).collect(),
})
.into(),
width: None,
config: Default::default(),
}
}
}
impl fmt::Display for MailboxesTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut table = Table::new();
table
.load_preset(self.config.preset())
.set_content_arrangement(ContentArrangement::DynamicFullWidth)
.set_header(Row::from([
Cell::new("NAME"),
Cell::new("DELIMITER"),
Cell::new("ATTRIBUTES"),
]))
.add_rows(self.mailboxes.iter().map(|mbox| {
let mut row = Row::new();
row.max_height(1);
row.add_cell(Cell::new(&mbox.name).fg(self.config.name_color()));
row.add_cell(Cell::new(&mbox.delimiter).fg(self.config.desc_color()));
row.add_cell(Cell::new(&mbox.attributes.join(", ")).fg(self.config.desc_color()));
row
}));
if let Some(width) = self.width {
table.set_width(width);
}
writeln!(f)?;
write!(f, "{table}")?;
writeln!(f)?;
Ok(())
}
}
impl Serialize for MailboxesTable {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.mailboxes.serialize(serializer)
}
}
fn map_color(color: Color) -> comfy_table::Color {
match color {
Color::Reset => comfy_table::Color::Reset,
Color::Black => comfy_table::Color::Black,
Color::DarkGrey => comfy_table::Color::DarkGrey,
Color::Red => comfy_table::Color::Red,
Color::DarkRed => comfy_table::Color::DarkRed,
Color::Green => comfy_table::Color::Green,
Color::DarkGreen => comfy_table::Color::DarkGreen,
Color::Yellow => comfy_table::Color::Yellow,
Color::DarkYellow => comfy_table::Color::DarkYellow,
Color::Blue => comfy_table::Color::Blue,
Color::DarkBlue => comfy_table::Color::DarkBlue,
Color::Magenta => comfy_table::Color::Magenta,
Color::DarkMagenta => comfy_table::Color::DarkMagenta,
Color::Cyan => comfy_table::Color::Cyan,
Color::DarkCyan => comfy_table::Color::DarkCyan,
Color::White => comfy_table::Color::White,
Color::Grey => comfy_table::Color::Grey,
Color::Rgb { r, g, b } => comfy_table::Color::Rgb { r, g, b },
Color::AnsiValue(n) => comfy_table::Color::AnsiValue(n),
}
}
+34 -23
View File
@@ -1,9 +1,10 @@
#[cfg(unix)]
use std::os::unix::net::UnixStream;
use std::{
fs,
io::{self, Read, Write},
net::TcpStream,
sync::Arc,
time::Duration,
};
use anyhow::{bail, Result};
@@ -28,33 +29,25 @@ use rustls::{
};
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
use rustls_platform_verifier::{ConfigVerifierExt, Verifier};
#[cfg(windows)]
use uds_windows::UnixStream;
use crate::config::{ImapConfig, RustlsCryptoConfig, SaslMechanismConfig, TlsProviderConfig};
pub enum Stream {
Plain(TcpStream),
Tcp(TcpStream),
Unix(UnixStream),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Rustls(StreamOwned<ClientConnection, TcpStream>),
#[cfg(feature = "native-tls")]
NativeTls(native_tls::TlsStream<TcpStream>),
}
impl Stream {
pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
match self {
Self::Plain(s) => s.set_read_timeout(dur),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.get_ref().set_read_timeout(dur),
#[cfg(feature = "native-tls")]
Self::NativeTls(s) => s.get_ref().set_read_timeout(dur),
}
}
}
impl Read for Stream {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self {
Self::Plain(s) => s.read(buf),
Self::Tcp(s) => s.read(buf),
Self::Unix(s) => s.read(buf),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.read(buf),
#[cfg(feature = "native-tls")]
@@ -66,7 +59,8 @@ impl Read for Stream {
impl Write for Stream {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self {
Self::Plain(s) => s.write(buf),
Self::Tcp(s) => s.write(buf),
Self::Unix(s) => s.write(buf),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.write(buf),
#[cfg(feature = "native-tls")]
@@ -76,7 +70,8 @@ impl Write for Stream {
fn flush(&mut self) -> io::Result<()> {
match self {
Self::Plain(s) => s.flush(),
Self::Tcp(s) => s.flush(),
Self::Unix(s) => s.flush(),
#[cfg(any(feature = "rustls-aws", feature = "rustls-ring"))]
Self::Rustls(s) => s.flush(),
#[cfg(feature = "native-tls")]
@@ -109,11 +104,11 @@ pub fn connect(mut config: ImapConfig) -> Result<(ImapContext, Stream)> {
}
}
(context, Stream::Plain(stream))
(context, Stream::Tcp(stream))
}
scheme if scheme.eq_ignore_ascii_case("imaps") => {
let port = config.url.port().unwrap_or(993);
let mut tcp = TcpStream::connect((host, port))?;
let mut stream = TcpStream::connect((host, port))?;
if config.starttls {
let mut coroutine = ImapStartTls::new(context);
@@ -121,7 +116,7 @@ pub fn connect(mut config: ImapConfig) -> Result<(ImapContext, Stream)> {
loop {
match coroutine.resume(arg.take()) {
ImapStartTlsResult::Io(io) => arg = Some(handle(&mut tcp, io)?),
ImapStartTlsResult::Io(io) => arg = Some(handle(&mut stream, io)?),
ImapStartTlsResult::Ok { context: c } => break context = c,
ImapStartTlsResult::Err { err, .. } => Err(err)?,
}
@@ -226,7 +221,7 @@ pub fn connect(mut config: ImapConfig) -> Result<(ImapContext, Stream)> {
let server_name = host.to_string().try_into()?;
let conn = ClientConnection::new(Arc::new(config), server_name)?;
Stream::Rustls(StreamOwned::new(conn, tcp))
Stream::Rustls(StreamOwned::new(conn, stream))
}
#[cfg(feature = "native-tls")]
TlsProviderConfig::NativeTls => {
@@ -240,7 +235,7 @@ pub fn connect(mut config: ImapConfig) -> Result<(ImapContext, Stream)> {
}
let connector = builder.build()?;
Stream::NativeTls(connector.connect(host, tcp)?)
Stream::NativeTls(connector.connect(host, stream)?)
}
#[allow(unreachable_patterns)]
_ => unreachable!(),
@@ -277,7 +272,23 @@ pub fn connect(mut config: ImapConfig) -> Result<(ImapContext, Stream)> {
(context, stream)
}
scheme if scheme.eq_ignore_ascii_case("unix") => {
todo!()
let sock_path = config.url.path();
let mut stream = UnixStream::connect(&sock_path)?;
let mut coroutine = GetImapGreetingWithCapability::new(context);
let mut arg = None;
loop {
match coroutine.resume(arg.take()) {
GetImapGreetingWithCapabilityResult::Io(io) => {
arg = Some(handle(&mut stream, io)?)
}
GetImapGreetingWithCapabilityResult::Ok { context: c } => break context = c,
GetImapGreetingWithCapabilityResult::Err { err, .. } => Err(err)?,
}
}
(context, Stream::Unix(stream))
}
scheme => {
bail!("Unknown scheme {scheme}, expected imap, imaps or unix");