From ffa26594a44d232cf93808bb52ddb8ebcbfb22a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Tue, 3 Mar 2026 20:16:05 +0100 Subject: [PATCH] repair mailbox table, add unix stream support --- Cargo.lock | 284 ++++++++++++++++++++++++++++++- Cargo.toml | 9 +- src/imap/mailbox/command/list.rs | 258 ++++++++++++++++++---------- src/imap/stream.rs | 57 ++++--- 4 files changed, 492 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7689a045..5db1f3e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,7 +83,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "367fd0ad87307588d087544707bc5fbf4805ded96c7db922b70d368fa1cb5702" dependencies = [ "concolor", - "unicode-width", + "unicode-width 0.1.14", "yansi", ] @@ -132,6 +132,9 @@ name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] [[package]] name = "bounded-static" @@ -280,6 +283,17 @@ dependencies = [ "memchr", ] +[[package]] +name = "comfy-table" +version = "7.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" +dependencies = [ + "crossterm 0.29.0", + "unicode-segmentation", + "unicode-width 0.2.2", +] + [[package]] name = "concolor" version = "0.0.11" @@ -313,6 +327,46 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.11.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "serde", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.11.0", + "crossterm_winapi", + "document-features", + "parking_lot", + "rustix", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "dirs" version = "6.0.0" @@ -345,6 +399,15 @@ dependencies = [ "syn", ] +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dunce" version = "1.0.5" @@ -521,6 +584,8 @@ dependencies = [ "anyhow", "ariadne", "clap", + "comfy-table", + "crossterm 0.27.0", "io-imap", "io-process", "io-stream", @@ -533,6 +598,7 @@ dependencies = [ "serde", "serde_json", "shellexpand", + "uds_windows", "url", "uuid", ] @@ -864,6 +930,21 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -876,12 +957,33 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "native-tls" version = "0.2.18" @@ -980,6 +1082,29 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1116,6 +1241,15 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -1288,6 +1422,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "secrecy" version = "0.10.3" @@ -1403,6 +1543,37 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "smallvec" version = "1.15.1" @@ -1563,18 +1734,41 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -1751,6 +1945,22 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -1760,6 +1970,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-link" version = "0.2.1" @@ -1775,6 +1991,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -1817,6 +2042,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -1856,6 +2096,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -1874,6 +2120,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -1892,6 +2144,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1922,6 +2180,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -1940,6 +2204,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -1958,6 +2228,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -1976,6 +2252,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 828b4e00..35be5a0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,10 +39,8 @@ pimalaya-toolbox = { version = "0.0.4", default-features = false, features = ["b anyhow = "1" ariadne = { version = "0.2", features = ["auto-color"] } clap = { version = "4.4", features = ["derive", "env", "wrap_help"] } -# email-lib = { version = "0.27", default-features = false, features = ["tokio-rustls", "derive", "thread"] } -# mml-lib = { version = "1", default-features = false, features = ["compiler", "interpreter", "derive"] } -# once_cell = "1.16" -# open = "5.3" +comfy-table = "7" +crossterm = { version = "0.27", features = ["serde"] } io-imap = { version = "0.0.1", default-features = false, optional = true } io-process = { version = "0.0.2", default-features = false } io-stream = { version = "0.0.2", default-features = false, features = ["std"] } @@ -58,6 +56,9 @@ shellexpand = "3.1" url = { version = "2.2", features = ["serde"] } uuid = { version = "1.19", features = ["v4"] } +[target.'cfg(windows)'.dependencies] +uds_windows = "1" + [patch.crates-io] pimalaya-toolbox.path = "../toolbox" io-imap.path = "../imap" diff --git a/src/imap/mailbox/command/list.rs b/src/imap/mailbox/command/list.rs index b71b219f..c6b97fa9 100644 --- a/src/imap/mailbox/command/list.rs +++ b/src/imap/mailbox/command/list.rs @@ -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, -// pub name_color: Option, -// pub desc_color: Option, -// } +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct ListMailboxesTableConfig { + pub preset: Option, + pub name_color: Option, + pub desc_color: Option, +} -// 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>, -// width: Option, -// config: ListMailboxesTableConfig, -// } +#[derive(Clone, Debug, Serialize)] +pub struct Mailbox { + pub name: String, + pub delimiter: String, + pub attributes: Vec, +} -// impl MailboxesTable { -// pub fn with_some_width(mut self, width: Option) -> Self { -// self.width = width; -// self -// } +#[derive(Clone, Debug, Default, Serialize)] +pub struct Mailboxes(Vec); -// pub fn with_some_preset(mut self, preset: Option) -> Self { -// self.config.preset = preset; -// self -// } +impl> From for Mailboxes { + fn from(mboxes: T) -> Self { + Self(mboxes.into_iter().collect()) + } +} -// pub fn with_some_name_color(mut self, color: Option) -> Self { -// self.config.name_color = color; -// self -// } +impl Deref for Mailboxes { + type Target = Vec; -// pub fn with_some_desc_color(mut self, color: Option) -> Self { -// self.config.desc_color = color; -// self -// } -// } + fn deref(&self) -> &Self::Target { + &self.0 + } +} -// impl From for MailboxesTable { -// fn from(mailboxes: Mailboxes) -> Self { -// Self { -// mailboxes, -// width: None, -// config: Default::default(), -// } -// } -// } +pub struct MailboxesTable { + mailboxes: Mailboxes, + width: Option, + 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) -> 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) -> 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) -> 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) -> Self { + self.config.desc_color = color; + self + } +} -// impl Serialize for MailboxesTable { -// fn serialize(&self, serializer: S) -> Result { -// self.mailboxes.serialize(serializer) -// } -// } +impl + From< + Vec<( + io_imap::types::mailbox::Mailbox<'static>, + Option, + Vec>, + )>, + > for MailboxesTable +{ + fn from( + mailboxes: Vec<( + io_imap::types::mailbox::Mailbox<'static>, + Option, + Vec>, + )>, + ) -> 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(&self, serializer: S) -> Result { + 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), + } +} diff --git a/src/imap/stream.rs b/src/imap/stream.rs index 8f7922ec..c20a01e0 100644 --- a/src/imap/stream.rs +++ b/src/imap/stream.rs @@ -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), #[cfg(feature = "native-tls")] NativeTls(native_tls::TlsStream), } -impl Stream { - pub fn set_read_timeout(&self, dur: Option) -> 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 { 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 { 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");