From 5e948b5cc12aa8923899747046ee7bbb6ee42e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Mon, 18 Jan 2021 11:57:53 +0100 Subject: [PATCH] improve list msgs perf, remove flags column --- Cargo.lock | 12 +++++++ Cargo.toml | 1 + src/config.rs | 2 +- src/imap.rs | 9 +++--- src/main.rs | 33 ++++++++++---------- src/msg.rs | 86 ++++++++++++++++++++++++--------------------------- src/smtp.rs | 3 ++ src/table.rs | 12 +++---- 8 files changed, 82 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a1baa5..a6330f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -235,6 +235,7 @@ dependencies = [ "lettre", "mailparse", "native-tls", + "rfc2047-decoder", "serde", "serde_json", "terminal_size", @@ -707,6 +708,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "rfc2047-decoder" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ecf2ba387f446155e26796aabb727e9ae1427dd13ac9cc21773a3fbda19d77" +dependencies = [ + "base64 0.13.0", + "charset", + "quoted_printable", +] + [[package]] name = "ryu" version = "1.0.5" diff --git a/Cargo.toml b/Cargo.toml index 3529c02..44fd112 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ imap = "2.4.0" lettre = "0.10.0-alpha.4" mailparse = "0.13.1" native-tls = "0.2" +rfc2047-decoder = "0.1.2" serde = { version = "1.0.118", features = ["derive"] } serde_json = "1.0.61" terminal_size = "0.1.15" diff --git a/src/config.rs b/src/config.rs index e733828..1e0858a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -167,7 +167,7 @@ impl Config { Ok(toml::from_slice(&content)?) } - pub fn get_account(&self, name: Option<&str>) -> Result<&Account> { + pub fn find_account_by_name(&self, name: Option<&str>) -> Result<&Account> { match name { Some(name) => self .accounts diff --git a/src/imap.rs b/src/imap.rs index 550245c..e311379 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -88,8 +88,8 @@ impl<'a> ImapConnector<'a> { Ok(Self { account, sess }) } - pub fn close(&mut self) { - match self.sess.close() { + pub fn logout(&mut self) { + match self.sess.logout() { _ => (), } } @@ -113,7 +113,7 @@ impl<'a> ImapConnector<'a> { let msgs = self .sess - .fetch(range, "(UID BODY.PEEK[])")? + .fetch(range, "(UID ENVELOPE INTERNALDATE)")? .iter() .rev() .map(Msg::from) @@ -143,9 +143,8 @@ impl<'a> ImapConnector<'a> { let msgs = self .sess - .fetch(range, "(UID BODY.PEEK[])")? + .fetch(range, "(UID ENVELOPE INTERNALDATE)")? .iter() - .rev() .map(Msg::from) .collect::>(); diff --git a/src/main.rs b/src/main.rs index 8aad980..07925d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -221,19 +221,20 @@ fn run() -> Result<()> { if let Some(_) = matches.subcommand_matches("mailboxes") { let config = Config::new_from_file()?; - let account = config.get_account(account_name)?; + let account = config.find_account_by_name(account_name)?; let mut imap_conn = ImapConnector::new(&account)?; let mboxes = imap_conn.list_mboxes()?; print(&output_type, mboxes)?; - imap_conn.close(); + imap_conn.logout(); } if let Some(matches) = matches.subcommand_matches("list") { let config = Config::new_from_file()?; - let account = config.get_account(account_name)?; + let account = config.find_account_by_name(account_name)?; let mut imap_conn = ImapConnector::new(&account)?; + let mbox = matches.value_of("mailbox").unwrap(); let page_size: u32 = matches .value_of("size") @@ -249,12 +250,12 @@ fn run() -> Result<()> { let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?; print(&output_type, msgs)?; - imap_conn.close(); + imap_conn.logout(); } if let Some(matches) = matches.subcommand_matches("search") { let config = Config::new_from_file()?; - let account = config.get_account(account_name)?; + let account = config.find_account_by_name(account_name)?; let mut imap_conn = ImapConnector::new(&account)?; let mbox = matches.value_of("mailbox").unwrap(); let page_size: usize = matches @@ -295,12 +296,12 @@ fn run() -> Result<()> { let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?; print(&output_type, msgs)?; - imap_conn.close(); + imap_conn.logout(); } if let Some(matches) = matches.subcommand_matches("read") { let config = Config::new_from_file()?; - let account = config.get_account(account_name)?; + let account = config.find_account_by_name(account_name)?; let mut imap_conn = ImapConnector::new(&account)?; let mbox = matches.value_of("mailbox").unwrap(); let uid = matches.value_of("uid").unwrap(); @@ -310,12 +311,12 @@ fn run() -> Result<()> { let text_bodies = msg.text_bodies(&mime)?; println!("{}", text_bodies); - imap_conn.close(); + imap_conn.logout(); } if let Some(matches) = matches.subcommand_matches("attachments") { let config = Config::new_from_file()?; - let account = config.get_account(account_name)?; + let account = config.find_account_by_name(account_name)?; let mut imap_conn = ImapConnector::new(&account)?; let mbox = matches.value_of("mailbox").unwrap(); let uid = matches.value_of("uid").unwrap(); @@ -334,12 +335,12 @@ fn run() -> Result<()> { println!("Done!"); } - imap_conn.close(); + imap_conn.logout(); } if let Some(_) = matches.subcommand_matches("write") { let config = Config::new_from_file()?; - let account = config.get_account(account_name)?; + let account = config.find_account_by_name(account_name)?; let mut imap_conn = ImapConnector::new(&account)?; let tpl = Msg::build_new_tpl(&config, &account)?; let content = input::open_editor_with_tpl(&tpl.as_bytes())?; @@ -352,12 +353,12 @@ fn run() -> Result<()> { imap_conn.append_msg("Sent", &msg.to_vec()?)?; println!("Done!"); - imap_conn.close(); + imap_conn.logout(); } if let Some(matches) = matches.subcommand_matches("reply") { let config = Config::new_from_file()?; - let account = config.get_account(account_name)?; + let account = config.find_account_by_name(account_name)?; let mut imap_conn = ImapConnector::new(&account)?; let mbox = matches.value_of("mailbox").unwrap(); @@ -380,12 +381,12 @@ fn run() -> Result<()> { imap_conn.append_msg("Sent", &msg.to_vec()?)?; println!("Done!"); - imap_conn.close(); + imap_conn.logout(); } if let Some(matches) = matches.subcommand_matches("forward") { let config = Config::new_from_file()?; - let account = config.get_account(account_name)?; + let account = config.find_account_by_name(account_name)?; let mut imap_conn = ImapConnector::new(&account)?; let mbox = matches.value_of("mailbox").unwrap(); @@ -403,7 +404,7 @@ fn run() -> Result<()> { imap_conn.append_msg("Sent", &msg.to_vec()?)?; println!("Done!"); - imap_conn.close(); + imap_conn.logout(); } Ok(()) diff --git a/src/msg.rs b/src/msg.rs index 8d23e6a..31e6917 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -1,5 +1,6 @@ use lettre; use mailparse::{self, MailHeaderMap}; +use rfc2047_decoder; use serde::Serialize; use std::{fmt, result}; @@ -45,38 +46,54 @@ type Result = result::Result; #[derive(Debug, Serialize)] pub struct Msg { pub uid: u32, - pub flags: Vec, + pub subject: String, + pub sender: String, + pub date: String, #[serde(skip_serializing)] raw: Vec, } -impl From for Msg { - fn from(item: String) -> Self { +impl From> for Msg { + fn from(raw: Vec) -> Self { Self { uid: 0, - flags: vec![], - raw: item.as_bytes().to_vec(), + subject: String::from(""), + sender: String::from(""), + date: String::from(""), + raw, } } } -impl From> for Msg { - fn from(item: Vec) -> Self { - Self { - uid: 0, - flags: vec![], - raw: item, - } +impl From for Msg { + fn from(raw: String) -> Self { + Self::from(raw.as_bytes().to_vec()) } } impl From<&imap::types::Fetch> for Msg { fn from(fetch: &imap::types::Fetch) -> Self { - Self { - uid: fetch.uid.unwrap_or_default(), - flags: vec![], - raw: fetch.body().unwrap_or_default().to_vec(), + match fetch.envelope() { + None => Self::from(fetch.body().unwrap_or_default().to_vec()), + Some(envelope) => Self { + uid: fetch.uid.unwrap_or_default(), + subject: envelope + .subject + .and_then(|subj| rfc2047_decoder::decode(subj).ok()) + .unwrap_or_default(), + sender: envelope + .from + .as_ref() + .and_then(|addrs| addrs.first()?.name) + .and_then(|name| rfc2047_decoder::decode(name).ok()) + .unwrap_or_default(), + date: fetch + .internal_date() + .map(|date| date.naive_local().to_string()) + .unwrap_or_default(), + raw: fetch.body().unwrap_or_default().to_vec(), + }, } } } @@ -376,36 +393,14 @@ impl<'a> Msg { impl DisplayRow for Msg { fn to_row(&self) -> Vec { - match self.parse() { - Err(_) => vec![], - Ok(parsed) => { - let headers = parsed.get_headers(); + use crate::table::*; - let uid = &self.uid.to_string(); - let flags = match self.extract_attachments().map(|vec| vec.is_empty()) { - Ok(false) => "", - _ => " ", - }; - let sender = headers - .get_first_value("reply-to") - .or(headers.get_first_value("from")) - .unwrap_or_default(); - let subject = headers.get_first_value("subject").unwrap_or_default(); - let date = headers.get_first_value("date").unwrap_or_default(); - - { - use crate::table::*; - - vec![ - Cell::new(&[RED], &uid), - Cell::new(&[WHITE], &flags), - Cell::new(&[BLUE], &sender), - FlexCell::new(&[GREEN], &subject), - Cell::new(&[YELLOW], &date), - ] - } - } - } + vec![ + Cell::new(&[RED], &self.uid.to_string()), + Cell::new(&[BLUE], &self.sender), + FlexCell::new(&[GREEN], &self.subject), + Cell::new(&[YELLOW], &self.date), + ] } } @@ -420,7 +415,6 @@ impl<'a> DisplayTable<'a, Msg> for Msgs { vec![ Cell::new(&[BOLD, UNDERLINE, WHITE], "UID"), - Cell::new(&[BOLD, UNDERLINE, WHITE], "FLAGS"), Cell::new(&[BOLD, UNDERLINE, WHITE], "SENDER"), FlexCell::new(&[BOLD, UNDERLINE, WHITE], "SUBJECT"), Cell::new(&[BOLD, UNDERLINE, WHITE], "DATE"), diff --git a/src/smtp.rs b/src/smtp.rs index fd4a338..8da58a0 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -42,6 +42,9 @@ type Result = result::Result; pub fn send(account: &Account, msg: &lettre::Message) -> Result<()> { use lettre::Transport; + // TODO + // lettre::transport::smtp::SmtpTransport::starttls_relay + lettre::transport::smtp::SmtpTransport::relay(&account.smtp_host)? .credentials(account.smtp_creds()?) .build() diff --git a/src/table.rs b/src/table.rs index 5fce500..6eb28a0 100644 --- a/src/table.rs +++ b/src/table.rs @@ -60,15 +60,11 @@ impl Cell { let style_end = "\x1b[0m"; if col_size > 0 && self.printable_value_len() > col_size { - let col_size = self - .value - .char_indices() - .map(|(i, _)| i) - .nth(col_size) - .unwrap() - - 2; + let value: String = self.value.chars().collect::>()[0..=col_size - 2] + .into_iter() + .collect(); - String::from(style_begin + &self.value[0..=col_size] + "… " + style_end) + String::from(style_begin + &value + "… " + style_end) } else { let padding = if col_size == 0 { "".to_string()