improve list msgs perf, remove flags column

This commit is contained in:
Clément DOUIN 2021-01-18 11:57:53 +01:00
parent d392d71129
commit 5e948b5cc1
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
8 changed files with 82 additions and 76 deletions

12
Cargo.lock generated
View file

@ -235,6 +235,7 @@ dependencies = [
"lettre", "lettre",
"mailparse", "mailparse",
"native-tls", "native-tls",
"rfc2047-decoder",
"serde", "serde",
"serde_json", "serde_json",
"terminal_size", "terminal_size",
@ -707,6 +708,17 @@ dependencies = [
"winapi", "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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"

View file

@ -11,6 +11,7 @@ imap = "2.4.0"
lettre = "0.10.0-alpha.4" lettre = "0.10.0-alpha.4"
mailparse = "0.13.1" mailparse = "0.13.1"
native-tls = "0.2" native-tls = "0.2"
rfc2047-decoder = "0.1.2"
serde = { version = "1.0.118", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
serde_json = "1.0.61" serde_json = "1.0.61"
terminal_size = "0.1.15" terminal_size = "0.1.15"

View file

@ -167,7 +167,7 @@ impl Config {
Ok(toml::from_slice(&content)?) 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 { match name {
Some(name) => self Some(name) => self
.accounts .accounts

View file

@ -88,8 +88,8 @@ impl<'a> ImapConnector<'a> {
Ok(Self { account, sess }) Ok(Self { account, sess })
} }
pub fn close(&mut self) { pub fn logout(&mut self) {
match self.sess.close() { match self.sess.logout() {
_ => (), _ => (),
} }
} }
@ -113,7 +113,7 @@ impl<'a> ImapConnector<'a> {
let msgs = self let msgs = self
.sess .sess
.fetch(range, "(UID BODY.PEEK[])")? .fetch(range, "(UID ENVELOPE INTERNALDATE)")?
.iter() .iter()
.rev() .rev()
.map(Msg::from) .map(Msg::from)
@ -143,9 +143,8 @@ impl<'a> ImapConnector<'a> {
let msgs = self let msgs = self
.sess .sess
.fetch(range, "(UID BODY.PEEK[])")? .fetch(range, "(UID ENVELOPE INTERNALDATE)")?
.iter() .iter()
.rev()
.map(Msg::from) .map(Msg::from)
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View file

@ -221,19 +221,20 @@ fn run() -> Result<()> {
if let Some(_) = matches.subcommand_matches("mailboxes") { if let Some(_) = matches.subcommand_matches("mailboxes") {
let config = Config::new_from_file()?; 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 mut imap_conn = ImapConnector::new(&account)?;
let mboxes = imap_conn.list_mboxes()?; let mboxes = imap_conn.list_mboxes()?;
print(&output_type, mboxes)?; print(&output_type, mboxes)?;
imap_conn.close(); imap_conn.logout();
} }
if let Some(matches) = matches.subcommand_matches("list") { if let Some(matches) = matches.subcommand_matches("list") {
let config = Config::new_from_file()?; 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 mut imap_conn = ImapConnector::new(&account)?;
let mbox = matches.value_of("mailbox").unwrap(); let mbox = matches.value_of("mailbox").unwrap();
let page_size: u32 = matches let page_size: u32 = matches
.value_of("size") .value_of("size")
@ -249,12 +250,12 @@ fn run() -> Result<()> {
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?; let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
print(&output_type, msgs)?; print(&output_type, msgs)?;
imap_conn.close(); imap_conn.logout();
} }
if let Some(matches) = matches.subcommand_matches("search") { if let Some(matches) = matches.subcommand_matches("search") {
let config = Config::new_from_file()?; 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 mut imap_conn = ImapConnector::new(&account)?;
let mbox = matches.value_of("mailbox").unwrap(); let mbox = matches.value_of("mailbox").unwrap();
let page_size: usize = matches let page_size: usize = matches
@ -295,12 +296,12 @@ fn run() -> Result<()> {
let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?; let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?;
print(&output_type, msgs)?; print(&output_type, msgs)?;
imap_conn.close(); imap_conn.logout();
} }
if let Some(matches) = matches.subcommand_matches("read") { if let Some(matches) = matches.subcommand_matches("read") {
let config = Config::new_from_file()?; 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 mut imap_conn = ImapConnector::new(&account)?;
let mbox = matches.value_of("mailbox").unwrap(); let mbox = matches.value_of("mailbox").unwrap();
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -310,12 +311,12 @@ fn run() -> Result<()> {
let text_bodies = msg.text_bodies(&mime)?; let text_bodies = msg.text_bodies(&mime)?;
println!("{}", text_bodies); println!("{}", text_bodies);
imap_conn.close(); imap_conn.logout();
} }
if let Some(matches) = matches.subcommand_matches("attachments") { if let Some(matches) = matches.subcommand_matches("attachments") {
let config = Config::new_from_file()?; 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 mut imap_conn = ImapConnector::new(&account)?;
let mbox = matches.value_of("mailbox").unwrap(); let mbox = matches.value_of("mailbox").unwrap();
let uid = matches.value_of("uid").unwrap(); let uid = matches.value_of("uid").unwrap();
@ -334,12 +335,12 @@ fn run() -> Result<()> {
println!("Done!"); println!("Done!");
} }
imap_conn.close(); imap_conn.logout();
} }
if let Some(_) = matches.subcommand_matches("write") { if let Some(_) = matches.subcommand_matches("write") {
let config = Config::new_from_file()?; 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 mut imap_conn = ImapConnector::new(&account)?;
let tpl = Msg::build_new_tpl(&config, &account)?; let tpl = Msg::build_new_tpl(&config, &account)?;
let content = input::open_editor_with_tpl(&tpl.as_bytes())?; 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()?)?; imap_conn.append_msg("Sent", &msg.to_vec()?)?;
println!("Done!"); println!("Done!");
imap_conn.close(); imap_conn.logout();
} }
if let Some(matches) = matches.subcommand_matches("reply") { if let Some(matches) = matches.subcommand_matches("reply") {
let config = Config::new_from_file()?; 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 mut imap_conn = ImapConnector::new(&account)?;
let mbox = matches.value_of("mailbox").unwrap(); let mbox = matches.value_of("mailbox").unwrap();
@ -380,12 +381,12 @@ fn run() -> Result<()> {
imap_conn.append_msg("Sent", &msg.to_vec()?)?; imap_conn.append_msg("Sent", &msg.to_vec()?)?;
println!("Done!"); println!("Done!");
imap_conn.close(); imap_conn.logout();
} }
if let Some(matches) = matches.subcommand_matches("forward") { if let Some(matches) = matches.subcommand_matches("forward") {
let config = Config::new_from_file()?; 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 mut imap_conn = ImapConnector::new(&account)?;
let mbox = matches.value_of("mailbox").unwrap(); let mbox = matches.value_of("mailbox").unwrap();
@ -403,7 +404,7 @@ fn run() -> Result<()> {
imap_conn.append_msg("Sent", &msg.to_vec()?)?; imap_conn.append_msg("Sent", &msg.to_vec()?)?;
println!("Done!"); println!("Done!");
imap_conn.close(); imap_conn.logout();
} }
Ok(()) Ok(())

View file

@ -1,5 +1,6 @@
use lettre; use lettre;
use mailparse::{self, MailHeaderMap}; use mailparse::{self, MailHeaderMap};
use rfc2047_decoder;
use serde::Serialize; use serde::Serialize;
use std::{fmt, result}; use std::{fmt, result};
@ -45,38 +46,54 @@ type Result<T> = result::Result<T, Error>;
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct Msg { pub struct Msg {
pub uid: u32, pub uid: u32,
pub flags: Vec<String>, pub subject: String,
pub sender: String,
pub date: String,
#[serde(skip_serializing)] #[serde(skip_serializing)]
raw: Vec<u8>, raw: Vec<u8>,
} }
impl From<String> for Msg { impl From<Vec<u8>> for Msg {
fn from(item: String) -> Self { fn from(raw: Vec<u8>) -> Self {
Self { Self {
uid: 0, uid: 0,
flags: vec![], subject: String::from(""),
raw: item.as_bytes().to_vec(), sender: String::from(""),
date: String::from(""),
raw,
} }
} }
} }
impl From<Vec<u8>> for Msg { impl From<String> for Msg {
fn from(item: Vec<u8>) -> Self { fn from(raw: String) -> Self {
Self { Self::from(raw.as_bytes().to_vec())
uid: 0,
flags: vec![],
raw: item,
}
} }
} }
impl From<&imap::types::Fetch> for Msg { impl From<&imap::types::Fetch> for Msg {
fn from(fetch: &imap::types::Fetch) -> Self { fn from(fetch: &imap::types::Fetch) -> Self {
Self { match fetch.envelope() {
uid: fetch.uid.unwrap_or_default(), None => Self::from(fetch.body().unwrap_or_default().to_vec()),
flags: vec![], Some(envelope) => Self {
raw: fetch.body().unwrap_or_default().to_vec(), 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 { impl DisplayRow for Msg {
fn to_row(&self) -> Vec<table::Cell> { fn to_row(&self) -> Vec<table::Cell> {
match self.parse() { use crate::table::*;
Err(_) => vec![],
Ok(parsed) => {
let headers = parsed.get_headers();
let uid = &self.uid.to_string(); vec![
let flags = match self.extract_attachments().map(|vec| vec.is_empty()) { Cell::new(&[RED], &self.uid.to_string()),
Ok(false) => "", Cell::new(&[BLUE], &self.sender),
_ => " ", FlexCell::new(&[GREEN], &self.subject),
}; Cell::new(&[YELLOW], &self.date),
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),
]
}
}
}
} }
} }
@ -420,7 +415,6 @@ impl<'a> DisplayTable<'a, Msg> for Msgs {
vec![ vec![
Cell::new(&[BOLD, UNDERLINE, WHITE], "UID"), Cell::new(&[BOLD, UNDERLINE, WHITE], "UID"),
Cell::new(&[BOLD, UNDERLINE, WHITE], "FLAGS"),
Cell::new(&[BOLD, UNDERLINE, WHITE], "SENDER"), Cell::new(&[BOLD, UNDERLINE, WHITE], "SENDER"),
FlexCell::new(&[BOLD, UNDERLINE, WHITE], "SUBJECT"), FlexCell::new(&[BOLD, UNDERLINE, WHITE], "SUBJECT"),
Cell::new(&[BOLD, UNDERLINE, WHITE], "DATE"), Cell::new(&[BOLD, UNDERLINE, WHITE], "DATE"),

View file

@ -42,6 +42,9 @@ type Result<T> = result::Result<T, Error>;
pub fn send(account: &Account, msg: &lettre::Message) -> Result<()> { pub fn send(account: &Account, msg: &lettre::Message) -> Result<()> {
use lettre::Transport; use lettre::Transport;
// TODO
// lettre::transport::smtp::SmtpTransport::starttls_relay
lettre::transport::smtp::SmtpTransport::relay(&account.smtp_host)? lettre::transport::smtp::SmtpTransport::relay(&account.smtp_host)?
.credentials(account.smtp_creds()?) .credentials(account.smtp_creds()?)
.build() .build()

View file

@ -60,15 +60,11 @@ impl Cell {
let style_end = "\x1b[0m"; let style_end = "\x1b[0m";
if col_size > 0 && self.printable_value_len() > col_size { if col_size > 0 && self.printable_value_len() > col_size {
let col_size = self let value: String = self.value.chars().collect::<Vec<_>>()[0..=col_size - 2]
.value .into_iter()
.char_indices() .collect();
.map(|(i, _)| i)
.nth(col_size)
.unwrap()
- 2;
String::from(style_begin + &self.value[0..=col_size] + "" + style_end) String::from(style_begin + &value + "" + style_end)
} else { } else {
let padding = if col_size == 0 { let padding = if col_size == 0 {
"".to_string() "".to_string()