newsletter-to-web/src/message_reader.rs

166 lines
4.2 KiB
Rust

use std::{
collections::HashMap,
error::Error,
fs::{read_dir, DirEntry},
net::TcpStream,
path::PathBuf,
vec::IntoIter,
};
use imap::Session;
use rustls_connector::RustlsConnector;
use crate::Message;
pub(crate) trait EmailReader {
fn read_rfc822_messages(&mut self) -> Box<IntoIter<Message>>;
}
pub(crate) struct DataDirectoryMessageReader {
path: PathBuf,
}
impl DataDirectoryMessageReader {
pub fn new(path: PathBuf) -> Self {
DataDirectoryMessageReader { path }
}
}
impl EmailReader for DataDirectoryMessageReader {
fn read_rfc822_messages(&mut self) -> Box<IntoIter<Message>> {
println!("Reading files in {}", &self.path.display());
let reader = match read_dir(&self.path) {
Ok(r) => r,
Err(e) => {
dbg!(e);
return Box::new(Vec::new().into_iter());
}
};
let items = reader
.filter(|i| i.is_ok())
.map(|i| i.unwrap() as DirEntry)
.filter(|d| match d.path().extension() {
Some(ext) => ext == "eml",
None => false,
})
.filter_map(|i| {
let uid = i
.path()
.file_stem()
.unwrap()
.to_owned()
.into_string()
.expect("Could not convert filename to string.")
.split('_')
.collect::<Vec<&str>>()[0]
.trim_start_matches('0')
.to_string();
if let Ok(data) = std::fs::read(i.path()) {
Some((uid, data))
} else {
None
}
})
.map(|i| Message::new(i.0, i.1));
let iter = items.collect::<Vec<Message>>().into_iter();
Box::new(iter)
}
}
pub struct ImapReader {
host: String,
port: u16,
username: String,
password: String,
}
impl ImapReader {
pub fn new(host: String, port: u16, username: String, password: String) -> Self {
ImapReader {
host,
port,
username,
password,
}
}
fn connect(&self) -> Result<HashMap<String, Vec<u8>>, Box<dyn Error>> {
let mut session = self.open_session()?;
session.examine("INBOX")?;
let items = match session.uid_search("ALL") {
Ok(i) => i,
Err(e) => return Err(Box::new(e)),
};
let mut msgs = HashMap::<String, Vec<u8>>::with_capacity(items.len());
//println!("# of messages: {}", &items.len());
for item in items {
let msg = session.uid_fetch(&item.to_string(), "(BODY.PEEK[] UID)")?;
let message = if let Some(m) = msg.iter().next() {
m
} else {
continue;
};
let body = message.body().expect("Message did not have a body.");
msgs.insert(item.to_string(), body.to_owned());
}
session.logout().expect("Could not log out");
Ok(msgs)
}
fn open_session(
&self,
) -> Result<
Session<
rustls_connector::rustls::StreamOwned<
rustls_connector::rustls::ClientConnection,
TcpStream,
>,
>,
Box<dyn Error + 'static>,
> {
let stream = TcpStream::connect((self.host.as_ref(), self.port))?;
let tls = RustlsConnector::new_with_webpki_roots_certs();
let tls_stream = tls.connect(&self.host, stream)?;
let client = imap::Client::new(tls_stream);
Ok(client
.login(&self.username, &self.password)
.map_err(|e| e.0)?)
}
}
impl EmailReader for ImapReader {
fn read_rfc822_messages(&mut self) -> Box<IntoIter<Message>> {
let msgs = match self.connect() {
Ok(m) => m,
Err(e) => {
dbg!(e);
return Box::new(Vec::new().into_iter());
}
};
let items = msgs
.iter()
.map(|i| Message::new(i.0.to_owned(), i.1.to_owned()));
let iter = items.collect::<Vec<Message>>().into_iter();
Box::new(iter)
}
}