refactor list mailbox command

This commit is contained in:
Clément DOUIN 2021-01-06 14:42:19 +01:00
parent 18042b02b8
commit 80837044b2
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
4 changed files with 358 additions and 240 deletions

191
src/email.rs Normal file
View file

@ -0,0 +1,191 @@
use imap;
use rfc2047_decoder;
use crate::table::{self, DisplayCell, DisplayRow, DisplayTable};
pub struct Uid(u32);
impl Uid {
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
Self(fetch.uid.unwrap())
}
}
impl DisplayCell for Uid {
fn styles(&self) -> &[table::Style] {
&[table::RED]
}
fn value(&self) -> String {
self.0.to_string()
}
}
pub struct Flags<'a>(Vec<imap::types::Flag<'a>>);
impl Flags<'_> {
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
let flags = fetch.flags().iter().fold(vec![], |mut flags, flag| {
use imap::types::Flag::*;
match flag {
Seen => flags.push(Seen),
Answered => flags.push(Answered),
Draft => flags.push(Draft),
Flagged => flags.push(Flagged),
_ => (),
};
flags
});
Self(flags)
}
}
impl DisplayCell for Flags<'_> {
fn styles(&self) -> &[table::Style] {
&[table::WHITE]
}
fn value(&self) -> String {
use imap::types::Flag::*;
let Flags(flags) = self;
let mut flags_str = String::new();
flags_str.push_str(if !flags.contains(&Seen) { &"N" } else { &" " });
flags_str.push_str(if flags.contains(&Answered) {
&"R"
} else {
&" "
});
flags_str.push_str(if flags.contains(&Draft) { &"D" } else { &" " });
flags_str.push_str(if flags.contains(&Flagged) { &"F" } else { &" " });
flags_str
}
}
pub struct Sender(String);
impl Sender {
fn try_from_fetch(fetch: &imap::types::Fetch) -> Option<String> {
let addr = fetch.envelope()?.from.as_ref()?.first()?;
addr.name
.and_then(|bytes| rfc2047_decoder::decode(bytes).ok())
.or_else(|| {
let mbox = String::from_utf8(addr.mailbox?.to_vec()).ok()?;
let host = String::from_utf8(addr.host?.to_vec()).ok()?;
Some(format!("{}@{}", mbox, host))
})
}
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
Self(Self::try_from_fetch(fetch).unwrap_or(String::new()))
}
}
impl DisplayCell for Sender {
fn styles(&self) -> &[table::Style] {
&[table::BLUE]
}
fn value(&self) -> String {
self.0.to_owned()
}
}
pub struct Subject(String);
impl Subject {
fn try_from_fetch(fetch: &imap::types::Fetch) -> Option<String> {
fetch
.envelope()?
.subject
.and_then(|bytes| rfc2047_decoder::decode(bytes).ok())
.and_then(|subject| Some(subject.replace("\r", "")))
.and_then(|subject| Some(subject.replace("\n", "")))
}
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
Self(Self::try_from_fetch(fetch).unwrap_or(String::new()))
}
}
impl DisplayCell for Subject {
fn styles(&self) -> &[table::Style] {
&[table::GREEN]
}
fn value(&self) -> String {
self.0.to_owned()
}
}
pub struct Date(String);
impl Date {
fn try_from_fetch(fetch: &imap::types::Fetch) -> Option<String> {
fetch
.internal_date()
.and_then(|date| Some(date.to_rfc3339()))
}
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
Self(Self::try_from_fetch(fetch).unwrap_or(String::new()))
}
}
impl DisplayCell for Date {
fn styles(&self) -> &[table::Style] {
&[table::YELLOW]
}
fn value(&self) -> String {
self.0.to_owned()
}
}
pub struct Email<'a> {
pub uid: Uid,
pub flags: Flags<'a>,
pub from: Sender,
pub subject: Subject,
pub date: Date,
}
impl Email<'_> {
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
Self {
uid: Uid::from_fetch(fetch),
from: Sender::from_fetch(fetch),
subject: Subject::from_fetch(fetch),
date: Date::from_fetch(fetch),
flags: Flags::from_fetch(fetch),
}
}
}
impl<'a> DisplayRow for Email<'a> {
fn to_row(&self) -> Vec<table::Cell> {
vec![
self.uid.to_cell(),
self.flags.to_cell(),
self.from.to_cell(),
self.subject.to_cell(),
self.date.to_cell(),
]
}
}
impl<'a> DisplayTable<'a, Email<'a>> for Vec<Email<'a>> {
fn cols() -> &'a [&'a str] {
&["uid", "flags", "from", "subject", "date"]
}
fn rows(&self) -> &Vec<Email<'a>> {
self
}
}

View file

@ -1,150 +1,11 @@
use imap;
use mailparse::{self, MailHeaderMap};
use native_tls::{self, TlsConnector, TlsStream};
use rfc2047_decoder;
use std::{error, fmt, net::TcpStream, result};
use crate::config;
use crate::table;
// Email
pub struct Uid(u32);
impl table::DisplayCell for Uid {
fn styles(&self) -> &[table::Style] {
&[table::RED]
}
fn value(&self) -> String {
self.0.to_string()
}
}
pub struct Flags<'a>(Vec<imap::types::Flag<'a>>);
impl table::DisplayCell for Flags<'_> {
fn styles(&self) -> &[table::Style] {
&[table::WHITE]
}
fn value(&self) -> String {
use imap::types::Flag::*;
let Flags(flags) = self;
let mut flags_str = String::new();
flags_str.push_str(if !flags.contains(&Seen) { &"N" } else { &" " });
flags_str.push_str(if flags.contains(&Answered) {
&"R"
} else {
&" "
});
flags_str.push_str(if flags.contains(&Draft) { &"D" } else { &" " });
flags_str.push_str(if flags.contains(&Flagged) { &"F" } else { &" " });
flags_str
}
}
pub struct Sender(String);
impl table::DisplayCell for Sender {
fn styles(&self) -> &[table::Style] {
&[table::BLUE]
}
fn value(&self) -> String {
self.0.to_owned()
}
}
pub struct Subject(String);
impl table::DisplayCell for Subject {
fn styles(&self) -> &[table::Style] {
&[table::GREEN]
}
fn value(&self) -> String {
self.0.to_owned()
}
}
pub struct Date(String);
impl table::DisplayCell for Date {
fn styles(&self) -> &[table::Style] {
&[table::YELLOW]
}
fn value(&self) -> String {
self.0.to_owned()
}
}
pub struct Email<'a> {
uid: Uid,
flags: Flags<'a>,
from: Sender,
subject: Subject,
date: Date,
}
impl Email<'_> {
fn first_sender_from_fetch(fetch: &imap::types::Fetch) -> Option<String> {
let addr = fetch.envelope()?.from.as_ref()?.first()?;
addr.name
.and_then(|bytes| rfc2047_decoder::decode(bytes).ok())
.or_else(|| {
let mbox = String::from_utf8(addr.mailbox?.to_vec()).ok()?;
let host = String::from_utf8(addr.host?.to_vec()).ok()?;
Some(format!("{}@{}", mbox, host))
})
}
fn subject_from_fetch(fetch: &imap::types::Fetch) -> Option<String> {
fetch
.envelope()?
.subject
.and_then(|bytes| rfc2047_decoder::decode(bytes).ok())
.and_then(|subject| Some(subject.replace("\r", "")))
.and_then(|subject| Some(subject.replace("\n", "")))
}
fn date_from_fetch(fetch: &imap::types::Fetch) -> Option<String> {
fetch
.internal_date()
.and_then(|date| Some(date.to_rfc3339()))
}
}
impl<'a> table::DisplayRow for Email<'a> {
fn to_row(&self) -> Vec<table::Cell> {
use table::DisplayCell;
vec![
self.uid.to_cell(),
self.flags.to_cell(),
self.from.to_cell(),
self.subject.to_cell(),
self.date.to_cell(),
]
}
}
impl<'a> table::DisplayTable<'a, Email<'a>> for Vec<Email<'a>> {
fn cols() -> &'a [&'a str] {
&["uid", "flags", "from", "subject", "date"]
}
fn rows(&self) -> &Vec<Email<'a>> {
self
}
}
// IMAP Connector
use crate::email::Email;
use crate::mailbox::Mailbox;
#[derive(Debug)]
pub struct ImapConnector {
@ -163,6 +24,17 @@ impl ImapConnector {
Ok(Self { config, sess })
}
pub fn list_mailboxes(&mut self) -> Result<Vec<Mailbox<'_>>> {
let mboxes = self
.sess
.list(Some(""), Some("*"))?
.iter()
.map(Mailbox::from_name)
.collect::<Vec<_>>();
Ok(mboxes)
}
pub fn read_emails(&mut self, mbox: &str, query: &str) -> Result<Vec<Email<'_>>> {
self.sess.select(mbox)?;
@ -180,29 +52,7 @@ impl ImapConnector {
"(UID ENVELOPE INTERNALDATE)",
)?
.iter()
.map(|fetch| {
let flags = fetch.flags().iter().fold(vec![], |mut flags, flag| {
use imap::types::Flag::*;
match flag {
Seen => flags.push(Seen),
Answered => flags.push(Answered),
Draft => flags.push(Draft),
Flagged => flags.push(Flagged),
_ => (),
};
flags
});
Email {
uid: Uid(fetch.uid.unwrap()),
from: Sender(Email::first_sender_from_fetch(fetch).unwrap_or(String::new())),
subject: Subject(Email::subject_from_fetch(fetch).unwrap_or(String::new())),
date: Date(Email::date_from_fetch(fetch).unwrap_or(String::new())),
flags: Flags(flags),
}
})
.map(Email::from_fetch)
.collect::<Vec<_>>();
Ok(emails)
@ -251,75 +101,26 @@ impl From<imap::Error> for Error {
type Result<T> = result::Result<T, Error>;
// pub fn list_mailboxes(imap_sess: &mut ImapSession) -> imap::Result<()> {
// let mboxes = imap_sess.list(Some(""), Some("*"))?;
// let table_head = vec![
// table::Cell::new(
// vec![table::BOLD, table::UNDERLINE, table::WHITE],
// String::from("DELIM"),
// ),
// table::Cell::new(
// vec![table::BOLD, table::UNDERLINE, table::WHITE],
// String::from("NAME"),
// ),
// table::Cell::new(
// vec![table::BOLD, table::UNDERLINE, table::WHITE],
// String::from("ATTRIBUTES"),
// ),
// ];
// let mut table_rows = mboxes
// .iter()
// .map(|mbox| {
// vec![
// table::Cell::new(
// vec![table::BLUE],
// mbox.delimiter().unwrap_or("").to_string(),
// ),
// table::Cell::new(vec![table::GREEN], mbox.name().to_string()),
// table::Cell::new(
// vec![table::YELLOW],
// mbox.attributes()
// .iter()
// .map(|a| format!("{:?}", a))
// .collect::<Vec<_>>()
// .join(", "),
// ),
// ]
// })
// .collect::<Vec<_>>();
// if table_rows.len() == 0 {
// println!("No email found!");
// } else {
// table_rows.insert(0, table_head);
// println!("{}", table::render(table_rows));
// fn extract_subparts_by_mime(mime: &str, part: &mailparse::ParsedMail, parts: &mut Vec<String>) {
// match part.subparts.len() {
// 0 => {
// if part
// .get_headers()
// .get_first_value("content-type")
// .and_then(|v| if v.starts_with(mime) { Some(()) } else { None })
// .is_some()
// {
// parts.push(part.get_body().unwrap_or(String::new()))
// }
// }
// _ => {
// part.subparts
// .iter()
// .for_each(|p| extract_subparts_by_mime(mime, p, parts));
// }
// }
// Ok(())
// }
fn extract_subparts_by_mime(mime: &str, part: &mailparse::ParsedMail, parts: &mut Vec<String>) {
match part.subparts.len() {
0 => {
if part
.get_headers()
.get_first_value("content-type")
.and_then(|v| if v.starts_with(mime) { Some(()) } else { None })
.is_some()
{
parts.push(part.get_body().unwrap_or(String::new()))
}
}
_ => {
part.subparts
.iter()
.for_each(|p| extract_subparts_by_mime(mime, p, parts));
}
}
}
// pub fn read_email(
// imap_sess: &mut ImapSession,
// mbox: &str,

120
src/mailbox.rs Normal file
View file

@ -0,0 +1,120 @@
use imap;
use crate::table::{self, DisplayCell, DisplayRow, DisplayTable};
pub struct Delim(String);
impl Delim {
pub fn from_name(name: &imap::types::Name) -> Self {
Self(name.delimiter().unwrap_or("/").to_string())
}
}
impl DisplayCell for Delim {
fn styles(&self) -> &[table::Style] {
&[table::BLUE]
}
fn value(&self) -> String {
self.0.to_owned()
}
}
pub struct Name(String);
impl Name {
pub fn from_name(name: &imap::types::Name) -> Self {
Self(name.name().to_string())
}
}
impl DisplayCell for Name {
fn styles(&self) -> &[table::Style] {
&[table::GREEN]
}
fn value(&self) -> String {
self.0.to_owned()
}
}
pub struct Attributes<'a>(Vec<imap::types::NameAttribute<'a>>);
impl Attributes<'_> {
pub fn from_name(name: &imap::types::Name) -> Self {
let attrs = name.attributes().iter().fold(vec![], |mut attrs, attr| {
use imap::types::NameAttribute::*;
match attr {
NoInferiors => attrs.push(NoInferiors),
NoSelect => attrs.push(NoSelect),
Marked => attrs.push(Marked),
Unmarked => attrs.push(Unmarked),
_ => (),
};
attrs
});
Self(attrs)
}
}
impl DisplayCell for Attributes<'_> {
fn styles(&self) -> &[table::Style] {
&[table::YELLOW]
}
fn value(&self) -> String {
use imap::types::NameAttribute::*;
self.0
.iter()
.map(|attr| match attr {
NoInferiors => vec!["no inferiors"],
NoSelect => vec!["no select"],
Marked => vec!["marked"],
Unmarked => vec!["unmarked"],
_ => vec![],
})
.collect::<Vec<_>>()
.concat()
.join(", ")
}
}
pub struct Mailbox<'a> {
pub delim: Delim,
pub name: Name,
pub attributes: Attributes<'a>,
}
impl Mailbox<'_> {
pub fn from_name(name: &imap::types::Name) -> Self {
Self {
delim: Delim::from_name(name),
name: Name::from_name(name),
attributes: Attributes::from_name(name),
}
}
}
impl<'a> DisplayRow for Mailbox<'a> {
fn to_row(&self) -> Vec<table::Cell> {
vec![
self.delim.to_cell(),
self.name.to_cell(),
self.attributes.to_cell(),
]
}
}
impl<'a> DisplayTable<'a, Mailbox<'a>> for Vec<Mailbox<'a>> {
fn cols() -> &'a [&'a str] {
&["delim", "name", "attributes"]
}
fn rows(&self) -> &Vec<Mailbox<'a>> {
self
}
}

View file

@ -1,5 +1,7 @@
mod config;
mod email;
mod imap;
mod mailbox;
mod smtp;
mod table;
@ -9,13 +11,13 @@ use crate::config::Config;
use crate::imap::ImapConnector;
use crate::table::DisplayTable;
fn new_email_tpl() -> String {
["To: ", "Subject: ", ""].join("\r\n")
}
// fn new_email_tpl() -> String {
// ["To: ", "Subject: ", ""].join("\r\n")
// }
fn forward_email_tpl() -> String {
["To: ", "Subject: ", ""].join("\r\n")
}
// fn forward_email_tpl() -> String {
// ["To: ", "Subject: ", ""].join("\r\n")
// }
fn mailbox_arg() -> Arg<'static, 'static> {
Arg::with_name("mailbox")
@ -93,10 +95,14 @@ fn dispatch() -> Result<(), imap::Error> {
)
.get_matches();
// if let Some(_) = matches.subcommand_matches("list") {
// let config = Config::new_from_file();
// ImapConnector::new(&config.imap).list_mailboxes().unwrap();
// }
if let Some(_) = matches.subcommand_matches("list") {
let config = Config::new_from_file();
let mboxes = ImapConnector::new(config.imap)?
.list_mailboxes()?
.to_table();
println!("{}", mboxes);
}
if let Some(matches) = matches.subcommand_matches("search") {
let config = Config::new_from_file();