use std::fmt;
use std::fs;
use std::io;
use chrono::prelude::*;
use super::super::storage;
use super::defs::*;
use crate::{account::model::*, support::error::Error};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SpooledMessageId(storage::MessageId);
impl fmt::Display for SpooledMessageId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&(self.0).0, f)
}
}
impl std::str::FromStr for SpooledMessageId {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.parse::<i64>().map(|n| Self(storage::MessageId(n)))
}
}
impl SpooledMessageId {
#[cfg(test)]
pub const DUMMY: Self = Self(storage::MessageId(0));
}
pub struct SpooledMessage {
#[allow(dead_code)]
pub id: SpooledMessageId,
pub transfer: storage::SmtpTransfer,
#[allow(dead_code)]
pub expires: DateTime<Utc>,
pub mail_from: String,
pub destinations: Vec<String>,
pub size: u32,
pub data: Box<dyn io::BufRead>,
}
impl Account {
pub fn spool_message(
&mut self,
message: BufferedMessage,
transfer: storage::SmtpTransfer,
mail_from: String,
destinations: Vec<String>,
) -> Result<SpooledMessageId, Error> {
let user_config = self.load_config()?;
let canonical_path = fs::File::open(&message.0)
.and_then(storage::MessageStore::canonical_path)?;
let message_id =
self.metadb
.intern_messages_as_orphans(&mut std::iter::once(
canonical_path
.to_str()
.expect("canonical paths are always UTF-8"),
))?[0];
self.message_store.insert(&message.0, &canonical_path)?;
if let Some(ref save_mailbox) = user_config.smtp_out.save {
if let Ok(mailbox_id) = self.metadb.find_mailbox(save_mailbox) {
let seen_flag_id = self.metadb.intern_flag(&Flag::Seen)?;
self.metadb.append_mailbox_messages(
mailbox_id,
&mut std::iter::once((
message_id,
Some(&std::iter::once(seen_flag_id.0).collect()),
)),
)?;
}
}
self.metadb.insert_message_spool(&storage::MessageSpool {
message_id,
transfer,
expires: storage::UnixTimestamp(
Utc::now() + chrono::Duration::days(30),
),
mail_from,
destinations,
})?;
Ok(SpooledMessageId(message_id))
}
pub fn open_spooled_message(
&mut self,
id: SpooledMessageId,
) -> Result<SpooledMessage, Error> {
let spooled = self
.metadb
.fetch_message_spool(id.0)?
.ok_or(Error::NxMessage)?;
let (metadata, reader) = self.open_message(id.0)?;
Ok(SpooledMessage {
id,
transfer: spooled.transfer,
expires: spooled.expires.0,
mail_from: spooled.mail_from,
destinations: spooled.destinations,
size: metadata.size,
data: reader,
})
}
pub fn delete_spooled_message_destinations(
&mut self,
id: SpooledMessageId,
destinations: &mut dyn Iterator<Item = &str>,
) -> Result<(), Error> {
self.metadb
.delete_message_spool_destinations(id.0, destinations)
}
pub fn fetch_all_foreign_smtp_tls_stati(
&mut self,
) -> Result<Vec<ForeignSmtpTlsStatus>, Error> {
self.metadb.fetch_all_foreign_smtp_tls_stati()
}
pub fn fetch_foreign_smtp_tls_status(
&mut self,
domain: &str,
) -> Result<Option<ForeignSmtpTlsStatus>, Error> {
self.metadb.fetch_foreign_smtp_tls_status(domain)
}
pub fn put_foreign_smtp_tls_status(
&mut self,
status: &ForeignSmtpTlsStatus,
) -> Result<(), Error> {
self.metadb.put_foreign_smtp_tls_status(status)
}
pub fn delete_foreign_smtp_tls_status(
&mut self,
domain: &str,
) -> Result<(), Error> {
self.metadb.delete_foreign_smtp_tls_status(domain)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn spool_message_default() {
let mut fixture = TestFixture::new();
let buffered_message = fixture
.buffer_message(Utc::now().into(), b"foo bar".as_slice())
.unwrap();
let spool_id = fixture
.spool_message(
buffered_message,
storage::SmtpTransfer::EightBit,
"foo@example.com".to_owned(),
vec!["bar@example.net".to_owned()],
)
.unwrap();
let mut spooled = fixture.open_spooled_message(spool_id).unwrap();
assert_eq!("foo@example.com", spooled.mail_from);
assert_eq!(vec!["bar@example.net".to_owned()], spooled.destinations);
assert_eq!(7, spooled.size);
let mut data = Vec::<u8>::new();
spooled.data.read_to_end(&mut data).unwrap();
assert_eq!(b"foo bar".to_vec(), data);
}
#[test]
fn spool_message_into_sent() {
let mut fixture = TestFixture::new();
fixture
.update_config(SetUserConfigRequest {
smtp_out_save: Some(Some("Sent".to_owned())),
..Default::default()
})
.unwrap();
let buffered_message = fixture
.buffer_message(Utc::now().into(), b"foo bar".as_slice())
.unwrap();
fixture
.spool_message(
buffered_message,
storage::SmtpTransfer::Binary,
"foo@example.com".to_owned(),
vec!["bar@example.net".to_owned()],
)
.unwrap();
let (sent, _) = fixture.select("Sent", false, None).unwrap();
assert_eq!(1, sent.select_response().unwrap().exists);
assert!(sent.test_flag_o(&Flag::Seen, Uid::u(1)));
}
}