mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-08 18:45:13 +00:00
refactor + clean table system [WIP]
This commit is contained in:
parent
401b51a760
commit
18042b02b8
|
@ -14,21 +14,9 @@ pub struct ServerInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerInfo {
|
impl ServerInfo {
|
||||||
pub fn get_host(&self) -> &str {
|
|
||||||
&self.host
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_addr(&self) -> (&str, u16) {
|
pub fn get_addr(&self) -> (&str, u16) {
|
||||||
(&self.host, self.port)
|
(&self.host, self.port)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_login(&self) -> &str {
|
|
||||||
&self.login
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_password(&self) -> &str {
|
|
||||||
&self.password
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -40,6 +28,13 @@ pub struct Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
pub fn new_from_file() -> Self {
|
||||||
|
match read_file_content() {
|
||||||
|
Err(err) => panic!(err),
|
||||||
|
Ok(content) => toml::from_str(&content).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn email_full(&self) -> String {
|
pub fn email_full(&self) -> String {
|
||||||
format!("{} <{}>", self.name, self.email)
|
format!("{} <{}>", self.name, self.email)
|
||||||
}
|
}
|
||||||
|
@ -91,10 +86,3 @@ pub fn read_file_content() -> Result<String, io::Error> {
|
||||||
file.read_to_string(&mut content)?;
|
file.read_to_string(&mut content)?;
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_file() -> Config {
|
|
||||||
match read_file_content() {
|
|
||||||
Err(err) => panic!(err),
|
|
||||||
Ok(content) => toml::from_str(&content).unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
482
src/imap.rs
482
src/imap.rs
|
@ -1,207 +1,305 @@
|
||||||
use imap;
|
use imap;
|
||||||
use mailparse::{self, MailHeaderMap};
|
use mailparse::{self, MailHeaderMap};
|
||||||
use native_tls::{TlsConnector, TlsStream};
|
use native_tls::{self, TlsConnector, TlsStream};
|
||||||
use rfc2047_decoder;
|
use rfc2047_decoder;
|
||||||
use std::net::TcpStream;
|
use std::{error, fmt, net::TcpStream, result};
|
||||||
|
|
||||||
use crate::config::{Config, ServerInfo};
|
use crate::config;
|
||||||
use crate::table;
|
use crate::table;
|
||||||
|
|
||||||
type ImapClient = imap::Client<TlsStream<TcpStream>>;
|
// Email
|
||||||
type ImapSession = imap::Session<TlsStream<TcpStream>>;
|
|
||||||
|
|
||||||
pub fn create_tls_connector() -> TlsConnector {
|
pub struct Uid(u32);
|
||||||
match native_tls::TlsConnector::new() {
|
|
||||||
Ok(connector) => connector,
|
impl table::DisplayCell for Uid {
|
||||||
Err(err) => {
|
fn styles(&self) -> &[table::Style] {
|
||||||
println!("The TLS connector could not be created.");
|
&[table::RED]
|
||||||
panic!(err);
|
}
|
||||||
}
|
|
||||||
|
fn value(&self) -> String {
|
||||||
|
self.0.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_imap_client(server: &ServerInfo, tls: &TlsConnector) -> ImapClient {
|
pub struct Flags<'a>(Vec<imap::types::Flag<'a>>);
|
||||||
match imap::connect(server.get_addr(), server.get_host(), &tls) {
|
|
||||||
Ok(client) => client,
|
impl table::DisplayCell for Flags<'_> {
|
||||||
Err(err) => {
|
fn styles(&self) -> &[table::Style] {
|
||||||
println!("The IMAP socket could not be opened.");
|
&[table::WHITE]
|
||||||
panic!(err);
|
}
|
||||||
}
|
|
||||||
|
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 fn create_imap_sess(client: ImapClient, server: &ServerInfo) -> ImapSession {
|
pub struct Sender(String);
|
||||||
match client.login(server.get_login(), server.get_password()) {
|
|
||||||
Ok(sess) => sess,
|
impl table::DisplayCell for Sender {
|
||||||
Err((err, _)) => {
|
fn styles(&self) -> &[table::Style] {
|
||||||
println!("The IMAP connection could not be established.");
|
&[table::BLUE]
|
||||||
panic!(err);
|
}
|
||||||
}
|
|
||||||
|
fn value(&self) -> String {
|
||||||
|
self.0.to_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn login(config: &Config) -> ImapSession {
|
pub struct Subject(String);
|
||||||
let tls = create_tls_connector();
|
|
||||||
let client = create_imap_client(&config.imap, &tls);
|
|
||||||
let imap_sess = create_imap_sess(client, &config.imap);
|
|
||||||
imap_sess
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subject_from_fetch(fetch: &imap::types::Fetch) -> String {
|
impl table::DisplayCell for Subject {
|
||||||
let envelope = fetch.envelope().expect("envelope is missing");
|
fn styles(&self) -> &[table::Style] {
|
||||||
|
&[table::GREEN]
|
||||||
|
}
|
||||||
|
|
||||||
match &envelope.subject {
|
fn value(&self) -> String {
|
||||||
None => String::new(),
|
self.0.to_owned()
|
||||||
Some(bytes) => match rfc2047_decoder::decode(bytes) {
|
|
||||||
Err(_) => String::new(),
|
|
||||||
Ok(subject) => subject,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn first_addr_from_fetch(fetch: &imap::types::Fetch) -> String {
|
pub struct Date(String);
|
||||||
let envelope = fetch.envelope().expect("envelope is missing");
|
|
||||||
|
|
||||||
match &envelope.from {
|
impl table::DisplayCell for Date {
|
||||||
None => String::new(),
|
fn styles(&self) -> &[table::Style] {
|
||||||
Some(addresses) => match addresses.first() {
|
&[table::YELLOW]
|
||||||
None => String::new(),
|
}
|
||||||
Some(address) => {
|
|
||||||
let mbox = String::from_utf8(address.mailbox.expect("invalid addr mbox").to_vec())
|
|
||||||
.expect("invalid addr mbox");
|
|
||||||
let host = String::from_utf8(address.host.expect("invalid addr host").to_vec())
|
|
||||||
.expect("invalid addr host");
|
|
||||||
let email = format!("{}@{}", mbox, host);
|
|
||||||
|
|
||||||
match address.name {
|
fn value(&self) -> String {
|
||||||
None => email,
|
self.0.to_owned()
|
||||||
Some(name) => match rfc2047_decoder::decode(name) {
|
}
|
||||||
Err(_) => email,
|
}
|
||||||
Ok(name) => name,
|
|
||||||
},
|
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
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ImapConnector {
|
||||||
|
pub config: config::ServerInfo,
|
||||||
|
pub sess: imap::Session<TlsStream<TcpStream>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImapConnector {
|
||||||
|
pub fn new(config: config::ServerInfo) -> Result<Self> {
|
||||||
|
let tls = TlsConnector::new()?;
|
||||||
|
let client = imap::connect(config.get_addr(), &config.host, &tls)?;
|
||||||
|
let sess = client
|
||||||
|
.login(&config.login, &config.password)
|
||||||
|
.map_err(|res| res.0)?;
|
||||||
|
|
||||||
|
Ok(Self { config, sess })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_emails(&mut self, mbox: &str, query: &str) -> Result<Vec<Email<'_>>> {
|
||||||
|
self.sess.select(mbox)?;
|
||||||
|
|
||||||
|
let uids = self
|
||||||
|
.sess
|
||||||
|
.uid_search(query)?
|
||||||
|
.iter()
|
||||||
|
.map(|n| n.to_string())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let emails = self
|
||||||
|
.sess
|
||||||
|
.uid_fetch(
|
||||||
|
uids[..20.min(uids.len())].join(","),
|
||||||
|
"(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),
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
},
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(emails)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn date_from_fetch(fetch: &imap::types::Fetch) -> String {
|
// Error wrapper
|
||||||
let envelope = fetch.envelope().expect("envelope is missing");
|
|
||||||
|
|
||||||
match &envelope.date {
|
#[derive(Debug)]
|
||||||
None => String::new(),
|
pub enum Error {
|
||||||
Some(date) => match String::from_utf8(date.to_vec()) {
|
CreateTlsConnectorError(native_tls::Error),
|
||||||
Err(_) => String::new(),
|
CreateImapSession(imap::Error),
|
||||||
Ok(date) => date,
|
}
|
||||||
},
|
|
||||||
|
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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_emails(imap_sess: &mut ImapSession, mbox: &str, query: &str) -> imap::Result<()> {
|
impl error::Error for Error {
|
||||||
imap_sess.select(mbox)?;
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||||
|
match *self {
|
||||||
let uids = imap_sess
|
Error::CreateTlsConnectorError(ref err) => Some(err),
|
||||||
.uid_search(query)?
|
Error::CreateImapSession(ref err) => Some(err),
|
||||||
.iter()
|
}
|
||||||
.map(|n| n.to_string())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let table_head = vec![
|
|
||||||
table::Cell::new(
|
|
||||||
vec![table::BOLD, table::UNDERLINE, table::WHITE],
|
|
||||||
String::from("ID"),
|
|
||||||
),
|
|
||||||
table::Cell::new(
|
|
||||||
vec![table::BOLD, table::UNDERLINE, table::WHITE],
|
|
||||||
String::from("FLAGS"),
|
|
||||||
),
|
|
||||||
table::Cell::new(
|
|
||||||
vec![table::BOLD, table::UNDERLINE, table::WHITE],
|
|
||||||
String::from("FROM"),
|
|
||||||
),
|
|
||||||
table::Cell::new(
|
|
||||||
vec![table::BOLD, table::UNDERLINE, table::WHITE],
|
|
||||||
String::from("SUBJECT"),
|
|
||||||
),
|
|
||||||
table::Cell::new(
|
|
||||||
vec![table::BOLD, table::UNDERLINE, table::WHITE],
|
|
||||||
String::from("DATE"),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut table_rows = imap_sess
|
|
||||||
.uid_fetch(
|
|
||||||
uids[..20.min(uids.len())].join(","),
|
|
||||||
"(INTERNALDATE ENVELOPE UID)",
|
|
||||||
)?
|
|
||||||
.iter()
|
|
||||||
.map(|fetch| {
|
|
||||||
vec![
|
|
||||||
table::Cell::new(vec![table::RED], fetch.uid.unwrap_or(0).to_string()),
|
|
||||||
table::Cell::new(vec![table::WHITE], String::from("!@")),
|
|
||||||
table::Cell::new(vec![table::BLUE], first_addr_from_fetch(fetch)),
|
|
||||||
table::Cell::new(vec![table::GREEN], subject_from_fetch(fetch)),
|
|
||||||
table::Cell::new(vec![table::YELLOW], date_from_fetch(fetch)),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
table_rows.insert(0, table_head);
|
|
||||||
|
|
||||||
println!("{}", table::render(table_rows));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>;
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
fn extract_subparts_by_mime(mime: &str, part: &mailparse::ParsedMail, parts: &mut Vec<String>) {
|
fn extract_subparts_by_mime(mime: &str, part: &mailparse::ParsedMail, parts: &mut Vec<String>) {
|
||||||
match part.subparts.len() {
|
match part.subparts.len() {
|
||||||
0 => {
|
0 => {
|
||||||
|
@ -222,28 +320,28 @@ fn extract_subparts_by_mime(mime: &str, part: &mailparse::ParsedMail, parts: &mu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_email(
|
// pub fn read_email(
|
||||||
imap_sess: &mut ImapSession,
|
// imap_sess: &mut ImapSession,
|
||||||
mbox: &str,
|
// mbox: &str,
|
||||||
uid: &str,
|
// uid: &str,
|
||||||
mime: &str,
|
// mime: &str,
|
||||||
) -> imap::Result<()> {
|
// ) -> imap::Result<()> {
|
||||||
imap_sess.select(mbox)?;
|
// imap_sess.select(mbox)?;
|
||||||
|
|
||||||
match imap_sess.uid_fetch(uid, "BODY[]")?.first() {
|
// match imap_sess.uid_fetch(uid, "BODY[]")?.first() {
|
||||||
None => println!("No email found in mailbox {} with UID {}", mbox, uid),
|
// None => println!("No email found in mailbox {} with UID {}", mbox, uid),
|
||||||
Some(email_raw) => {
|
// Some(email_raw) => {
|
||||||
let email = mailparse::parse_mail(email_raw.body().unwrap_or(&[])).unwrap();
|
// let email = mailparse::parse_mail(email_raw.body().unwrap_or(&[])).unwrap();
|
||||||
let mut parts = vec![];
|
// let mut parts = vec![];
|
||||||
extract_subparts_by_mime(mime, &email, &mut parts);
|
// extract_subparts_by_mime(mime, &email, &mut parts);
|
||||||
|
|
||||||
if parts.len() == 0 {
|
// if parts.len() == 0 {
|
||||||
println!("No {} content found for email {}!", mime, uid);
|
// println!("No {} content found for email {}!", mime, uid);
|
||||||
} else {
|
// } else {
|
||||||
println!("{}", parts.join("\r\n"));
|
// println!("{}", parts.join("\r\n"));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
// }
|
||||||
|
|
114
src/main.rs
114
src/main.rs
|
@ -4,10 +4,16 @@ mod smtp;
|
||||||
mod table;
|
mod table;
|
||||||
|
|
||||||
use clap::{App, Arg, SubCommand};
|
use clap::{App, Arg, SubCommand};
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::{env, fs, process};
|
|
||||||
|
|
||||||
fn nem_email_tpl() -> String {
|
use crate::config::Config;
|
||||||
|
use crate::imap::ImapConnector;
|
||||||
|
use crate::table::DisplayTable;
|
||||||
|
|
||||||
|
fn new_email_tpl() -> String {
|
||||||
|
["To: ", "Subject: ", ""].join("\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn forward_email_tpl() -> String {
|
||||||
["To: ", "Subject: ", ""].join("\r\n")
|
["To: ", "Subject: ", ""].join("\r\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,9 +34,12 @@ fn uid_arg() -> Arg<'static, 'static> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let config = config::read_file();
|
if let Err(err) = dispatch() {
|
||||||
let mut imap_sess = imap::login(&config);
|
panic!(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch() -> Result<(), imap::Error> {
|
||||||
let matches = App::new("Himalaya")
|
let matches = App::new("Himalaya")
|
||||||
.version("0.1.0")
|
.version("0.1.0")
|
||||||
.about("📫 Minimalist CLI email client")
|
.about("📫 Minimalist CLI email client")
|
||||||
|
@ -84,11 +93,13 @@ fn main() {
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("list") {
|
// if let Some(_) = matches.subcommand_matches("list") {
|
||||||
imap::list_mailboxes(&mut imap_sess).unwrap();
|
// let config = Config::new_from_file();
|
||||||
}
|
// ImapConnector::new(&config.imap).list_mailboxes().unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("search") {
|
if let Some(matches) = matches.subcommand_matches("search") {
|
||||||
|
let config = Config::new_from_file();
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
|
||||||
if let Some(matches) = matches.values_of("query") {
|
if let Some(matches) = matches.values_of("query") {
|
||||||
|
@ -115,41 +126,74 @@ fn main() {
|
||||||
.1
|
.1
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
imap::read_emails(&mut imap_sess, &mbox, &query).unwrap();
|
let emails = ImapConnector::new(config.imap)?
|
||||||
|
.read_emails(&mbox, &query)?
|
||||||
|
.to_table();
|
||||||
|
|
||||||
|
println!("{}", emails);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("read") {
|
// if let Some(matches) = matches.subcommand_matches("read") {
|
||||||
let mbox = matches.value_of("mailbox").unwrap();
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
let mime = matches.value_of("mime-type").unwrap();
|
// let mime = matches.value_of("mime-type").unwrap();
|
||||||
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
|
||||||
if let Some(uid) = matches.value_of("uid") {
|
// imap::read_email(&mut imap_sess, mbox, uid, mime).unwrap();
|
||||||
imap::read_email(&mut imap_sess, mbox, uid, mime).unwrap();
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(_) = matches.subcommand_matches("write") {
|
// if let Some(_) = matches.subcommand_matches("write") {
|
||||||
let mut draft_path = env::temp_dir();
|
// let mut draft_path = env::temp_dir();
|
||||||
draft_path.push("himalaya-draft.mail");
|
// draft_path.push("himalaya-draft.mail");
|
||||||
|
|
||||||
fs::File::create(&draft_path)
|
// fs::File::create(&draft_path)
|
||||||
.expect("Could not create draft file")
|
// .expect("Could not create draft file")
|
||||||
.write(nem_email_tpl().as_bytes())
|
// .write(new_email_tpl().as_bytes())
|
||||||
.expect("Could not write into draft file");
|
// .expect("Could not write into draft file");
|
||||||
|
|
||||||
process::Command::new(env!("EDITOR"))
|
// process::Command::new(env!("EDITOR"))
|
||||||
.arg(&draft_path)
|
// .arg(&draft_path)
|
||||||
.status()
|
// .status()
|
||||||
.expect("Could not start $EDITOR");
|
// .expect("Could not start $EDITOR");
|
||||||
|
|
||||||
let mut draft = String::new();
|
// let mut draft = String::new();
|
||||||
fs::File::open(&draft_path)
|
// fs::File::open(&draft_path)
|
||||||
.expect("Could not open draft file")
|
// .expect("Could not open draft file")
|
||||||
.read_to_string(&mut draft)
|
// .read_to_string(&mut draft)
|
||||||
.expect("Could not read draft file");
|
// .expect("Could not read draft file");
|
||||||
|
|
||||||
fs::remove_file(&draft_path).expect("Could not remove draft file");
|
// fs::remove_file(&draft_path).expect("Could not remove draft file");
|
||||||
|
|
||||||
smtp::send(&config, &draft.as_bytes());
|
// smtp::send(&config, &draft.as_bytes());
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
// if let Some(_) = matches.subcommand_matches("forward") {
|
||||||
|
// let mbox = matches.value_of("mailbox").unwrap();
|
||||||
|
// let uid = matches.value_of("uid").unwrap();
|
||||||
|
|
||||||
|
// let mut draft_path = env::temp_dir();
|
||||||
|
// draft_path.push("himalaya-draft.mail");
|
||||||
|
|
||||||
|
// fs::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"))
|
||||||
|
// .arg(&draft_path)
|
||||||
|
// .status()
|
||||||
|
// .expect("Could not start $EDITOR");
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
// fs::remove_file(&draft_path).expect("Could not remove draft file");
|
||||||
|
|
||||||
|
// smtp::send(&config, &draft.as_bytes());
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
97
src/table.rs
97
src/table.rs
|
@ -31,19 +31,16 @@ impl fmt::Display for Style {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Cell {
|
pub struct Cell {
|
||||||
styles: Vec<Style>,
|
pub styles: Vec<Style>,
|
||||||
value: String,
|
pub value: String,
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Cell {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Cell::new(self.styles.clone(), self.value.clone())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cell {
|
impl Cell {
|
||||||
pub fn new(styles: Vec<Style>, value: String) -> Cell {
|
pub fn new<'a>(styles: &'a [Style], value: &'a str) -> Cell {
|
||||||
Cell { styles, value }
|
Cell {
|
||||||
|
styles: styles.to_vec(),
|
||||||
|
value: value.to_string(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn printable_value_len(&self) -> usize {
|
pub fn printable_value_len(&self) -> usize {
|
||||||
|
@ -68,50 +65,60 @@ impl Cell {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Matrix<T> = Vec<Vec<T>>;
|
pub trait DisplayCell {
|
||||||
|
fn styles(&self) -> &[Style];
|
||||||
|
fn value(&self) -> String;
|
||||||
|
|
||||||
pub fn transpose<T: Clone>(m: Matrix<T>) -> Matrix<T> {
|
fn to_cell(&self) -> Cell {
|
||||||
let mut tm: Matrix<T> = vec![];
|
Cell::new(self.styles(), &self.value())
|
||||||
let col_size = m.iter().next().unwrap_or(&vec![]).len();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for idx in 0..col_size {
|
pub trait DisplayRow {
|
||||||
let col = m
|
fn to_row(&self) -> Vec<Cell>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait DisplayTable<'a, T: DisplayRow> {
|
||||||
|
fn cols() -> &'a [&'a str];
|
||||||
|
fn rows(&self) -> &Vec<T>;
|
||||||
|
|
||||||
|
fn to_table(&self) -> String {
|
||||||
|
let mut col_sizes = vec![];
|
||||||
|
|
||||||
|
let head = Self::cols()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|row| row.get(idx).unwrap().clone())
|
.map(|col| {
|
||||||
|
let cell = Cell::new(&[BOLD, UNDERLINE, WHITE], &col.to_uppercase());
|
||||||
|
col_sizes.push(cell.printable_value_len());
|
||||||
|
cell
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
tm.push(col)
|
let mut body = self
|
||||||
}
|
.rows()
|
||||||
|
|
||||||
tm
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_cols(cells: Matrix<Cell>) -> Matrix<String> {
|
|
||||||
fn render_tcols(tcells: &Vec<Cell>) -> Vec<String> {
|
|
||||||
let col_size = tcells
|
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cell| cell.printable_value_len())
|
.map(|item| {
|
||||||
.max()
|
let row = item.to_row();
|
||||||
.unwrap();
|
row.iter().enumerate().for_each(|(i, cell)| {
|
||||||
tcells.iter().map(|tcell| tcell.render(col_size)).collect()
|
col_sizes[i] = col_sizes[i].max(cell.printable_value_len())
|
||||||
};
|
});
|
||||||
|
row
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let tcells: Matrix<String> = transpose(cells).iter().map(render_tcols).collect();
|
body.insert(0, head);
|
||||||
transpose(tcells)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_rows(m: Matrix<String>) -> Vec<String> {
|
body.iter().fold(String::new(), |output, row| {
|
||||||
m.iter()
|
let row_str = row
|
||||||
.map(|row| String::from(row.join(&sep()) + "\n"))
|
.iter()
|
||||||
.collect()
|
.enumerate()
|
||||||
}
|
.map(|(i, cell)| cell.render(col_sizes[i]))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(&Cell::new(&[ext(8)], "|").render(0));
|
||||||
|
|
||||||
pub fn render(m: Matrix<Cell>) -> String {
|
output + &row_str + "\n"
|
||||||
render_rows(render_cols(m)).concat()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sep() -> String {
|
|
||||||
Cell::new(vec![ext(8)], "|".to_string()).render(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
Loading…
Reference in a new issue