refactor write email command

This commit is contained in:
Clément DOUIN 2021-01-06 16:05:08 +01:00
parent 80837044b2
commit c7b326a695
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
3 changed files with 147 additions and 123 deletions

View file

@ -3,7 +3,8 @@ use rfc2047_decoder;
use crate::table::{self, DisplayCell, DisplayRow, DisplayTable};
pub struct Uid(u32);
#[derive(Debug)]
pub struct Uid(pub u32);
impl Uid {
pub fn from_fetch(fetch: &imap::types::Fetch) -> Self {
@ -21,6 +22,7 @@ impl DisplayCell for Uid {
}
}
#[derive(Debug)]
pub struct Flags<'a>(Vec<imap::types::Flag<'a>>);
impl Flags<'_> {
@ -67,6 +69,7 @@ impl DisplayCell for Flags<'_> {
}
}
#[derive(Debug)]
pub struct Sender(String);
impl Sender {
@ -97,6 +100,7 @@ impl DisplayCell for Sender {
}
}
#[derive(Debug)]
pub struct Subject(String);
impl Subject {
@ -124,6 +128,7 @@ impl DisplayCell for Subject {
}
}
#[derive(Debug)]
pub struct Date(String);
impl Date {
@ -148,6 +153,7 @@ impl DisplayCell for Date {
}
}
#[derive(Debug)]
pub struct Email<'a> {
pub uid: Uid,
pub flags: Flags<'a>,

View file

@ -7,6 +7,60 @@ use crate::config;
use crate::email::Email;
use crate::mailbox::Mailbox;
// Error wrapper
#[derive(Debug)]
pub enum Error {
CreateTlsConnectorError(native_tls::Error),
CreateImapSession(imap::Error),
ReadEmailNotFoundError(String),
ReadEmailEmptyPartError(String, String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::CreateTlsConnectorError(err) => err.fmt(f),
Error::CreateImapSession(err) => err.fmt(f),
Error::ReadEmailNotFoundError(uid) => {
write!(f, "No email found for UID {}", uid)
}
Error::ReadEmailEmptyPartError(uid, mime) => {
write!(f, "No {} content found for UID {}", mime, uid)
}
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
Error::CreateTlsConnectorError(ref err) => Some(err),
Error::CreateImapSession(ref err) => Some(err),
Error::ReadEmailNotFoundError(_) => None,
Error::ReadEmailEmptyPartError(_, _) => None,
}
}
}
impl From<native_tls::Error> for Error {
fn from(err: native_tls::Error) -> Error {
Error::CreateTlsConnectorError(err)
}
}
impl From<imap::Error> for Error {
fn from(err: imap::Error) -> Error {
Error::CreateImapSession(err)
}
}
// Result wrapper
type Result<T> = result::Result<T, Error>;
// Imap connector
#[derive(Debug)]
pub struct ImapConnector {
pub config: config::ServerInfo,
@ -14,6 +68,26 @@ pub struct ImapConnector {
}
impl ImapConnector {
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| Self::extract_subparts_by_mime(mime, p, parts));
}
}
}
pub fn new(config: config::ServerInfo) -> Result<Self> {
let tls = TlsConnector::new()?;
let client = imap::connect(config.get_addr(), &config.host, &tls)?;
@ -57,92 +131,26 @@ impl ImapConnector {
Ok(emails)
}
}
// Error wrapper
pub fn read_email(&mut self, mbox: &str, uid: &str, mime: &str) -> Result<String> {
self.sess.select(mbox)?;
#[derive(Debug)]
pub enum Error {
CreateTlsConnectorError(native_tls::Error),
CreateImapSession(imap::Error),
}
match self.sess.uid_fetch(uid, "BODY[]")?.first() {
None => return Err(Error::ReadEmailNotFoundError(uid.to_string())),
Some(email_raw) => {
let email = mailparse::parse_mail(email_raw.body().unwrap_or(&[])).unwrap();
let mut parts = vec![];
Self::extract_subparts_by_mime(mime, &email, &mut parts);
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::CreateTlsConnectorError(err) => err.fmt(f),
Error::CreateImapSession(err) => err.fmt(f),
if parts.len() == 0 {
Err(Error::ReadEmailEmptyPartError(
uid.to_string(),
mime.to_string(),
))
} else {
Ok(parts.join("\r\n"))
}
}
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
Error::CreateTlsConnectorError(ref err) => Some(err),
Error::CreateImapSession(ref err) => Some(err),
}
}
}
impl From<native_tls::Error> for Error {
fn from(err: native_tls::Error) -> Error {
Error::CreateTlsConnectorError(err)
}
}
impl From<imap::Error> for Error {
fn from(err: imap::Error) -> Error {
Error::CreateImapSession(err)
}
}
// Result wrapper
type Result<T> = result::Result<T, Error>;
// 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,
// uid: &str,
// mime: &str,
// ) -> imap::Result<()> {
// imap_sess.select(mbox)?;
// match imap_sess.uid_fetch(uid, "BODY[]")?.first() {
// None => println!("No email found in mailbox {} with UID {}", mbox, uid),
// Some(email_raw) => {
// let email = mailparse::parse_mail(email_raw.body().unwrap_or(&[])).unwrap();
// let mut parts = vec![];
// extract_subparts_by_mime(mime, &email, &mut parts);
// if parts.len() == 0 {
// println!("No {} content found for email {}!", mime, uid);
// } else {
// println!("{}", parts.join("\r\n"));
// }
// }
// }
// Ok(())
// }

View file

@ -6,14 +6,25 @@ mod smtp;
mod table;
use clap::{App, Arg, SubCommand};
use std::env::temp_dir;
use std::fs::{remove_file, File};
use std::io::{Read, Write};
use std::process::{exit, Command};
use crate::config::Config;
use crate::imap::ImapConnector;
use crate::table::DisplayTable;
// fn new_email_tpl() -> String {
// ["To: ", "Subject: ", ""].join("\r\n")
// }
fn main() {
if let Err(err) = dispatch() {
eprintln!("Error: {}", err);
exit(1);
}
}
fn new_email_tpl() -> String {
["To: ", "Subject: ", ""].join("\r\n")
}
// fn forward_email_tpl() -> String {
// ["To: ", "Subject: ", ""].join("\r\n")
@ -35,12 +46,6 @@ fn uid_arg() -> Arg<'static, 'static> {
.required(true)
}
fn main() {
if let Err(err) = dispatch() {
panic!(err);
}
}
fn dispatch() -> Result<(), imap::Error> {
let matches = App::new("Himalaya")
.version("0.1.0")
@ -140,63 +145,68 @@ fn dispatch() -> Result<(), imap::Error> {
}
}
// if let Some(matches) = matches.subcommand_matches("read") {
// let mbox = matches.value_of("mailbox").unwrap();
// let mime = matches.value_of("mime-type").unwrap();
// let uid = matches.value_of("uid").unwrap();
if let Some(matches) = matches.subcommand_matches("read") {
let config = Config::new_from_file();
let mbox = matches.value_of("mailbox").unwrap();
let uid = matches.value_of("uid").unwrap();
let mime = matches.value_of("mime-type").unwrap();
let email = ImapConnector::new(config.imap)?.read_email(&mbox, &uid, &mime)?;
// imap::read_email(&mut imap_sess, mbox, uid, mime).unwrap();
// }
println!("{}", email);
}
// if let Some(_) = matches.subcommand_matches("write") {
// let mut draft_path = env::temp_dir();
// draft_path.push("himalaya-draft.mail");
if let Some(_) = matches.subcommand_matches("write") {
let config = Config::new_from_file();
// fs::File::create(&draft_path)
// .expect("Could not create draft file")
// .write(new_email_tpl().as_bytes())
// .expect("Could not write into draft file");
let mut draft_path = temp_dir();
draft_path.push("himalaya-draft.mail");
// process::Command::new(env!("EDITOR"))
// .arg(&draft_path)
// .status()
// .expect("Could not start $EDITOR");
File::create(&draft_path)
.expect("Could not create draft file")
.write(new_email_tpl().as_bytes())
.expect("Could not write into draft file");
// let mut draft = String::new();
// fs::File::open(&draft_path)
// .expect("Could not open draft file")
// .read_to_string(&mut draft)
// .expect("Could not read draft file");
Command::new(env!("EDITOR"))
.arg(&draft_path)
.status()
.expect("Could not start $EDITOR");
// fs::remove_file(&draft_path).expect("Could not remove draft file");
let mut draft = String::new();
File::open(&draft_path)
.expect("Could not open draft file")
.read_to_string(&mut draft)
.expect("Could not read draft file");
// smtp::send(&config, &draft.as_bytes());
// }
remove_file(&draft_path).expect("Could not remove draft file");
smtp::send(&config, &draft.as_bytes());
}
// if let Some(_) = matches.subcommand_matches("forward") {
// let config = Config::new_from_file();
// let mbox = matches.value_of("mailbox").unwrap();
// let uid = matches.value_of("uid").unwrap();
// let mut draft_path = env::temp_dir();
// let mut draft_path = temp_dir();
// draft_path.push("himalaya-draft.mail");
// fs::File::create(&draft_path)
// File::create(&draft_path)
// .expect("Could not create draft file")
// .write(forward_email_tpl().as_bytes())
// .expect("Could not write into draft file");
// process::Command::new(env!("EDITOR"))
// Command::new(env!("EDITOR"))
// .arg(&draft_path)
// .status()
// .expect("Could not start $EDITOR");
// let mut draft = String::new();
// fs::File::open(&draft_path)
// File::open(&draft_path)
// .expect("Could not open draft file")
// .read_to_string(&mut draft)
// .expect("Could not read draft file");
// fs::remove_file(&draft_path).expect("Could not remove draft file");
// remove_file(&draft_path).expect("Could not remove draft file");
// smtp::send(&config, &draft.as_bytes());
// }