diff options
-rw-r--r-- | lib/domain/libimagmail/Cargo.toml | 14 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/config.rs | 136 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/fetch.rs | 117 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/hasher.rs (renamed from lib/domain/libimagmail/src/iter.rs) | 41 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/lib.rs | 19 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/mail.rs | 253 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/mid.rs | 33 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/send.rs | 111 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/store.rs | 151 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/util.rs | 64 |
10 files changed, 768 insertions, 171 deletions
diff --git a/lib/domain/libimagmail/Cargo.toml b/lib/domain/libimagmail/Cargo.toml index 294040f3..09825f1e 100644 --- a/lib/domain/libimagmail/Cargo.toml +++ b/lib/domain/libimagmail/Cargo.toml @@ -21,10 +21,16 @@ maintenance = { status = "actively-developed" } [dependencies] log = "0.4.0" -email = "0.0.20" +toml = "0.4" +toml-query = "0.8" +mailparse = "0.6.5" filters = "0.3" failure = "0.1" +serde = "1" +serde_derive = "1" + +libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } +libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } +libimagentryref = { version = "0.10.0", path = "../../../lib/entry/libimagentryref" } +libimagentryutil = { version = "0.10.0", path = "../../../lib/entry/libimagentryutil/" } -libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } -libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } -libimagentryref = { version = "0.10.0", path = "../../../lib/entry/libimagentryref" } diff --git a/lib/domain/libimagmail/src/config.rs b/lib/domain/libimagmail/src/config.rs new file mode 100644 index 00000000..6258e6e3 --- /dev/null +++ b/lib/domain/libimagmail/src/config.rs @@ -0,0 +1,136 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::path::PathBuf; + +/// A struct representing a full mail configuration, required for working with this library +/// +/// For convenience reasons, this implements Serialize and Deserialize, so it can be fetched from a +/// configuration file for example +/// +/// # TODO +/// +/// Figure out how to use handlebars with variables on this. Right now the support for that is not +/// implemented yet. +/// +#[derive(Serialize, Deserialize, Debug)] +pub struct MailConfig { + default_account : String, + accounts : Vec<MailAccountConfig>, + fetchcommand : MailCommand, + postfetchcommand : Option<MailCommand>, + sendcommand : MailCommand, + postsendcommand : Option<MailCommand>, +} + +impl MailConfig { + pub fn default_account(&self) -> &String { + &self.default_account + } + + pub fn accounts(&self) -> &Vec<MailAccountConfig> { + &self.accounts + } + + pub fn account(&self, name: &str) -> Option<&MailAccountConfig> { + self.accounts() + .iter() + .filter(|a| a.name == name) + .next() + } + + pub fn fetchcommand(&self) -> &MailCommand { + &self.fetchcommand + } + + pub fn postfetchcommand(&self) -> Option<&MailCommand> { + self.postfetchcommand.as_ref() + } + + pub fn sendcommand(&self) -> &MailCommand { + &self.sendcommand + } + + pub fn postsendcommand(&self) -> Option<&MailCommand> { + self.postsendcommand.as_ref() + } + + pub fn fetchcommand_for_account(&self, account_name: &str) -> &MailCommand { + self.accounts() + .iter() + .filter(|a| a.name == account_name) + .next() + .and_then(|a| a.fetchcommand.as_ref()) + .unwrap_or_else(|| self.fetchcommand()) + } + + pub fn postfetchcommand_for_account(&self, account_name: &str) -> Option<&MailCommand> { + self.accounts() + .iter() + .filter(|a| a.name == account_name) + .next() + .and_then(|a| a.postfetchcommand.as_ref()) + .or_else(|| self.postfetchcommand()) + } + + pub fn sendcommand_for_account(&self, account_name: &str) -> &MailCommand { + self.accounts() + .iter() + .filter(|a| a.name == account_name) + .next() + .and_then(|a| a.sendcommand.as_ref()) + .unwrap_or_else(|| self.sendcommand()) + } + + pub fn postsendcommand_for_account(&self, account_name: &str) -> Option<&MailCommand> { + self.accounts() + .iter() + .filter(|a| a.name == account_name) + .next() + .and_then(|a| a.postsendcommand.as_ref()) + .or_else(|| self.postsendcommand()) + } + +} + +/// A configuration for a single mail accounts +/// +/// If one of the keys `fetchcommand`, `postfetchcommand`, `sendcommand` or `postsendcommand` is +/// not available, the implementation of the `MailConfig` will automatically use the global +/// configuration if applicable. +#[derive(Serialize, Deserialize, Debug)] +pub struct MailAccountConfig { + pub name : String, + pub outgoingbox : PathBuf, + pub draftbox : PathBuf, + pub sentbox : PathBuf, + pub maildirroot : PathBuf, + pub fetchcommand : Option<MailCommand>, + pub postfetchcommand : Option<MailCommand>, + pub sendcommand : Option<MailCommand>, + pub postsendcommand : Option<MailCommand>, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct MailCommand { + command: String, + env: Vec<String>, + args: Vec<String>, +} + diff --git a/lib/domain/libimagmail/src/fetch.rs b/lib/domain/libimagmail/src/fetch.rs new file mode 100644 index 00000000..de4f4b0e --- /dev/null +++ b/lib/domain/libimagmail/src/fetch.rs @@ -0,0 +1,117 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use config::MailConfig; + +pub struct MailFetcher<'a> { + config: &'a MailConfig, + account_name_to_fetch: Option<String>, + boxes: Vec<String>, + + rescan_maildirs: bool, +} + +impl MailFetcher { + pub fn new(config: &MailConfig) -> Self { + MailFetcher { + config, + account_name_to_fetch: None, + rescan_maildirs: false + } + } + + pub fn fetch_account(mut self, name: String) -> Self { + self.account_name_to_fetch = Some(name); + self + } + + pub fn fetch_box(mut self, name: String) -> Self { + self.boxes.push(name); + self + } + + pub fn fetch_boxes<I>(mut self, names: I) -> Self + where I: IntoIterator<Item = String> + { + self.boxes.append(names.into_iter().collect()) + self + } + + pub fn rescan_maildirs(mut self, b: bool) -> Self { + self.rescan_maildirs = b; + self + } + + pub fn run(&self, store: &Store) -> Result<()> { + let fetchcommand = match self.account_name_to_fetch { + Some(name) => self.config.fetchcommand_for_account(name), + None => self.confnig.fetchcommand(), + }; + + let postfetchcommand = match self.account_name_to_fetch { + Some(name) => self.config.postfetchcommand_for_account(name), + None => self.confnig.postfetchcommand(), + }; + + let account = config + .account(self.account_name_to_fetch) + .ok_or_else(|| format_err!("Account '{}' does not exist", self.account_name_to_fetch))?; + + if fetchcommand.contains(" ") { + // error on whitespace in command + } + + if postfetchcommand.contains(" ") { + // error on whitespace in command + } + + // fetchcommand + + let mut output = Command::new(fetchcommand) + // TODO: Add argument support + // TODO: Add support for passing config variables + // TODO: Add support for passing environment + .args(self.boxes) + .wait_with_output() + .context("Mail fetching")?; + + write!(rt.stdout(), "{}", output.stdout)?; + write!(rt.stderr(), "{}", output.stderr)?; + + // postfetchcommand + + let output = Command::new(postfetchcommand) + // TODO: Add argument support + // TODO: Add support for passing config variables + .wait_with_output() + .context("Post 'Mail fetching' command")?; + + write!(rt.stdout(), "{}", output.stdout)?; + write!(rt.stderr(), "{}", output.stderr)?; + + if self.rescan_maildirs { + // scan + // account.maildirroot + // recursively for new mail and store them in imag + } + } + +} + + diff --git a/lib/domain/libimagmail/src/iter.rs b/lib/domain/libimagmail/src/hasher.rs index e4d375cd..270b1777 100644 --- a/lib/domain/libimagmail/src/iter.rs +++ b/lib/domain/libimagmail/src/hasher.rs @@ -17,39 +17,24 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // -//! Module for the MailIter -//! -//! MailIter is a iterator which takes an Iterator that yields `Ref` and yields itself -//! `Result<Mail>`, where `Err(_)` is returned if the Ref is not a Mail or parsing of the -//! referenced mail file failed. -//! +use std::path::Path; -use mail::Mail; use failure::Fallible as Result; -use libimagstore::store::FileLockEntry; +use libimagentryref::hasher::Hasher; -use std::marker::PhantomData; +pub struct MailHasher; -pub struct MailIter<'a, I: Iterator<Item = FileLockEntry<'a>>> { - _marker: PhantomData<I>, - i: I, -} - -impl<'a, I: Iterator<Item = FileLockEntry<'a>>> MailIter<'a, I> { +impl Hasher for MailHasher { + const NAME: &'static str = "MailHasher"; - pub fn new(i: I) -> MailIter<'a, I> { - MailIter { _marker: PhantomData, i: i } + /// hash the file at path `path` + /// + /// TODO: This is the expensive implementation. We use the message Id as hash, which is + /// convenient and _should_ be safe + /// + /// TODO: Confirm that this approach is right + fn hash<P: AsRef<Path>>(path: P) -> Result<String> { + ::util::get_message_id_for_mailfile(path) } - } - -impl<'a, I: Iterator<Item = FileLockEntry<'a>>> Iterator for MailIter<'a, I> { - type Item = Result<Mail<'a>>; - - fn next(&mut self) -> Option<Self::Item> { - self.i.next().map(Mail::from_fle) - } - -} - diff --git a/lib/domain/libimagmail/src/lib.rs b/lib/domain/libimagmail/src/lib.rs index 3ba7397f..5bbeeb33 100644 --- a/lib/domain/libimagmail/src/lib.rs +++ b/lib/domain/libimagmail/src/lib.rs @@ -38,14 +38,25 @@ )] #[macro_use] extern crate log; -extern crate email; +extern crate mailparse; +extern crate toml; +extern crate toml_query; extern crate filters; -extern crate failure; +#[macro_use] extern crate failure; +extern crate serde; +#[macro_use] extern crate serde_derive; extern crate libimagerror; -extern crate libimagstore; +#[macro_use] extern crate libimagstore; extern crate libimagentryref; +#[macro_use] extern crate libimagentryutil; -pub mod iter; +module_entry_path_mod!("mail"); + +pub mod config; +pub mod hasher; pub mod mail; +pub mod mid; +pub mod store; +pub mod util; diff --git a/lib/domain/libimagmail/src/mail.rs b/lib/domain/libimagmail/src/mail.rs index ab59a694..b6429446 100644 --- a/lib/domain/libimagmail/src/mail.rs +++ b/lib/domain/libimagmail/src/mail.rs @@ -17,185 +17,168 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // -use std::path::Path; -use std::fs::File; -use std::io::Read; -use std::fs::OpenOptions; - -use libimagstore::store::Store; -use libimagstore::storeid::StoreId; -use libimagstore::store::FileLockEntry; -use libimagentryref::reference::Ref; -use libimagentryref::refstore::RefStore; -use libimagentryref::refstore::UniqueRefPathGenerator; -use libimagerror::errors::ErrorMsg as EM; - -use email::MimeMessage; -use email::results::ParsingResult as EmailParsingResult; - use failure::Fallible as Result; use failure::ResultExt; use failure::Error; -use failure::err_msg; -struct UniqueMailRefGenerator; -impl UniqueRefPathGenerator for UniqueMailRefGenerator { - /// The collection the `StoreId` should be created for - fn collection() -> &'static str { - "mail" +use libimagstore::store::Entry; +use libimagentryutil::isa::Is; +use libimagentryutil::isa::IsKindHeaderPathProvider; +use libimagentryref::reference::Config as RefConfig; +use libimagentryref::reference::{Ref, RefFassade}; + +provide_kindflag_path!(pub IsMail, "mail.is_mail"); + +pub trait Mail : RefFassade { + fn is_mail(&self) -> Result<bool>; + fn get_field(&self, refconfig: &RefConfig, field: &str) -> Result<Option<String>>; + fn get_from(&self, refconfig: &RefConfig) -> Result<Option<String>>; + fn get_to(&self, refconfig: &RefConfig) -> Result<Option<String>>; + fn get_subject(&self, refconfig: &RefConfig) -> Result<Option<String>>; + fn get_message_id(&self, refconfig: &RefConfig) -> Result<Option<String>>; + fn get_in_reply_to(&self, refconfig: &RefConfig) -> Result<Option<String>>; +} + +impl Mail for Entry { + + fn is_mail(&self) -> Result<bool> { + self.is::<IsMail>() } - /// A function which should generate a unique string for a Path - fn unique_hash<A: AsRef<Path>>(path: A) -> Result<String> { - use filters::filter::Filter; - use email::Header; - - let mut s = String::new(); - let _ = OpenOptions::new() - .read(true) - .write(false) - .create(false) - .open(path)? - .read_to_string(&mut s)?; - - MimeMessage::parse(&s) - .context(err_msg("Error creating ref")) - .map_err(Error::from) - .and_then(|mail| { - let has_key = |hdr: &Header, exp: &str| hdr.name == exp; - - let subject_filter = |hdr: &Header| has_key(hdr, "Subject"); - let from_filter = |hdr: &Header| has_key(hdr, "From"); - let to_filter = |hdr: &Header| has_key(hdr, "To"); - - let filter = subject_filter.or(from_filter).or(to_filter); - - let mut v : Vec<String> = vec![]; - for hdr in mail.headers.iter().filter(|item| filter.filter(item)) { - let s = hdr - .get_value() - .context(err_msg("Ref creation error"))?; - - v.push(s); + /// Get a value of a single field of the mail file + fn get_field(&self, refconfig: &RefConfig, field: &str) -> Result<Option<String>> { + use std::fs::read_to_string; + use hasher::MailHasher; + + debug!("Getting field in mail: {:?}", field); + let mail_file_location = self.as_ref_with_hasher::<MailHasher>().get_path(refconfig)?; + + match ::mailparse::parse_mail(read_to_string(mail_file_location.as_path())?.as_bytes()) + .context(format_err!("Cannot parse Email {}", mail_file_location.display()))? + .headers + .into_iter() + .filter_map(|hdr| match hdr.get_key() { + Err(e) => Some(Err(e).map_err(Error::from)), + Ok(k) => if k == field { + Some(Ok(hdr)) + } else { + None } - let s : String = v.join(""); - Ok(s) }) + .next() + { + None => Ok(None), + Some(Err(e)) => Err(e), + Some(Ok(hdr)) => Ok(Some(hdr.get_value()?)) + } } - /// Postprocess the generated `StoreId` object - fn postprocess_storeid(sid: StoreId) -> Result<StoreId> { - Ok(sid) + /// Get a value of the `From` field of the mail file + /// + /// # Note + /// + /// Use `Mail::mail_header()` if you need to read more than one field. + fn get_from(&self, refconfig: &RefConfig) -> Result<Option<String>> { + self.get_field(refconfig, "From") } -} - -struct Buffer(String); -impl Buffer { - pub fn parsed(&self) -> EmailParsingResult<MimeMessage> { - MimeMessage::parse(&self.0) + /// Get a value of the `To` field of the mail file + /// + /// # Note + /// + /// Use `Mail::mail_header()` if you need to read more than one field. + fn get_to(&self, refconfig: &RefConfig) -> Result<Option<String>> { + self.get_field(refconfig, "To") } -} -impl From<String> for Buffer { - fn from(data: String) -> Buffer { - Buffer(data) + /// Get a value of the `Subject` field of the mail file + /// + /// # Note + /// + /// Use `Mail::mail_header()` if you need to read more than one field. + fn get_subject(&self, refconfig: &RefConfig) -> Result<Option<String>> { + self.get_field(refconfig, "Subject") } -} -pub struct Mail<'a>(FileLockEntry<'a>, Buffer); - -impl<'a> Mail<'a> { - - /// Imports a mail from the Path passed - pub fn import_from_path<P: AsRef<Path>>(store: &Store, p: P) -> Result<Mail> { - debug!("Importing Mail from path"); - store.retrieve_ref::<UniqueMailRefGenerator, P>(p) - .and_then(|reference| { - debug!("Build reference file: {:?}", reference); - reference.get_path() - .context(err_msg("Ref handling error")) - .map_err(Error::from) - .and_then(|path| File::open(path).context(EM::IO).map_err(Error::from)) - .and_then(|mut file| { - let mut s = String::new(); - file.read_to_string(&mut s) - .map(|_| s) - .context(EM::IO) - .map_err(Error::from) - }) - .map(Buffer::from) - .map(|buffer| Mail(reference, buffer)) - }) + /// Get a value of the `Message-ID` field of the mail file + /// + /// # Note + /// + /// Use `Mail::mail_header()` if you need to read more than one field. + fn get_message_id(&self, refconfig: &RefConfig) -> Result<Option<String>> { + self.get_field(refconfig, "Message-ID") + .map(|o| o.map(::util::strip_message_delimiters)) } - /// Opens a mail by the passed hash - pub fn open<S: AsRef<str>>(store: &Store, hash: S) -> Result<Option<Mail>> { - debug!("Opening Mail by Hash"); - store.get_ref::<UniqueMailRefGenerator, S>(hash) - .context(err_msg("Fetch by hash error")) - .context(err_msg("Fetch error")) - .map_err(Error::from) - .and_then(|o| match o { - Some(r) => Mail::from_fle(r).map(Some), - None => Ok(None), - }) + /// Get a value of the `In-Reply-To` field of the mail file + /// + /// # Note + /// + /// Use `Mail::mail_header()` if you need to read more than one field. + fn get_in_reply_to(&self, refconfig: &RefConfig) -> Result<Option<String>> { + self.get_field(refconfig, "In-Reply-To") } - /// Implement me as TryFrom as soon as it is stable - pub fn from_fle(fle: FileLockEntry<'a>) -> Result<Mail<'a>> { - fle.get_path() - .context(err_msg("Ref handling error")) - .map_err(Error::from) - .and_then(|path| File::open(path).context(EM::IO).map_err(Error::from)) - .and_then(|mut file| { - let mut s = String::new(); - file.read_to_string(&mut s) - .map(|_| s) - .context(EM::IO) - .map_err(Error::from) - }) - .map(Buffer::from) - .map(|buffer| Mail(fle, buffer)) +} + +#[derive(Debug)] +pub struct MailHeader<'a>(Vec<::mailparse::MailHeader<'a>>); + +impl<'a> From<Vec<::mailparse::MailHeader<'a>>> for MailHeader<'a> { + fn from(mh: Vec<::mailparse::MailHeader<'a>>) -> Self { + MailHeader(mh) } +} +impl<'a> MailHeader<'a> { + /// Get a value of a single field of the mail file pub fn get_field(&self, field: &str) -> Result<Option<String>> { - debug!("Getting field in mail: {:?}", field); - self.1 - .parsed() - .context(err_msg("Mail parsing error")) - .map_err(Error::from) - .map(|parsed| { - parsed.headers - .iter() - .filter(|hdr| hdr.name == field) - .nth(0) - .and_then(|field| field.get_value().ok()) + match self.0 + .iter() + .filter_map(|hdr| match hdr.get_key() { + Err(e) => Some(Err(e).map_err(Error::from)), + Ok(key) => if key == field { + Some(Ok(hdr)) + } else { + None + } }) + .next() + { + None => Ok(None), + Some(Err(e)) => Err(e), + Some(Ok(hdr)) => Ok(Some(hdr.get_value()?)) + } } + /// Get a value of the `From` field of the mail file pub fn get_from(&self) -> Result<Option<String>> { self.get_field("From") } + /// Get a value of the `To` field of the mail file pub fn get_to(&self) -> Result<Option<String>> { self.get_field("To") } + /// Get a value of the `Subject` field of the mail file pub fn get_subject(&self) -> Result<Option<String>> { self.get_field("Subject") } + /// Get a value of the `Message-ID` field of the mail file pub fn get_message_id(&self) -> Result<Option<String>> { self.get_field("Message-ID") } + /// Get a value of the `In-Reply-To` field of the mail file pub fn get_in_reply_to(&self) -> Result<Option<String>> { self.get_field("In-Reply-To") } - pub fn fle(&self) -> &FileLockEntry<'a> { - &self.0 - } - + // TODO: Offer functionality to load and parse mail _once_ from disk, and then use helper object + // to offer access to header fields and content. + // + // With the existing functionality, one has to open-parse-close the file all the time, which is + // _NOT_ optimal. } diff --git a/lib/domain/libimagmail/src/mid.rs b/lib/domain/libimagmail/src/mid.rs new file mode 100644 index 00000000..ec732027 --- /dev/null +++ b/lib/domain/libimagmail/src/mid.rs @@ -0,0 +1,33 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +/// Helper type for handling message IDs +/// +/// Message IDs are used to identfy emails uniquely, so we should at least have a type for +/// representing them and make handling a bit easier. +/// +#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct MessageId(String); + +impl Into<String> for MessageId { + fn into(self) -> String { + self.0 + } +} + diff --git a/lib/domain/libimagmail/src/send.rs b/lib/domain/libimagmail/src/send.rs new file mode 100644 index 00000000..9c4c5407 --- /dev/null +++ b/lib/domain/libimagmail/src/send.rs @@ -0,0 +1,111 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use config::MailConfig; + +pub struct MailSender<'a> { + config: &'a MailConfig, + account_name_to_send_with: Option<String>, + + rescan_maildirs: bool, +} + +impl MailSender { + pub fn new(config: &MailConfig) -> Self { + MailSender { + config, + account_name_to_send_with: None, + rescan_maildirs: false + } + } + + pub fn send_account(mut self, name: String) -> Self { + self.account_name_to_send_with = Some(name); + self + } + + pub fn rescan_maildirs(mut self, b: bool) -> Self { + self.rescan_maildirs = b; + self + } + + pub fn run(&self, store: &Store) -> Result<()> { + let sendcommand = match self.account_name_to_send_with { + Some(name) => self.config.sendcommand_for_account(name), + None => self.confnig.sendcommand(), + }; + + let postsendcommand = match self.account_name_to_send_with { + Some(name) => self.config.postsendcommand_for_account(name), + None => self.confnig.sendcommand(), + }; + + let account = config + .account(self.account_name_to_send_with) + .ok_or_else(|| format_err!("Account '{}' does not exist", self.account_name_to_send_with))?; + + if sendcommand.contains(" ") { + // error on whitespace in command + } + + if postsendcommand.contains(" ") { + // error on whitespace in command + } + + // sendcommand + // + let outgoingbox = account + .outgoingbox + .to_str() + .ok_or_else(|| format_err!("Cannot use '{:?}' as outgoingbox", account.outgoingbox))?; + + let mut output = Command::new(sendcommand) + // TODO: Add argument support + // TODO: Add support for passing config variables + // TODO: Add support for passing environment + .arg(outgoingbox) + .wait_with_output() + .context("Mail sending")?; + + write!(rt.stdout(), "{}", output.stdout)?; + write!(rt.stderr(), "{}", output.stderr)?; + + // TODO: Move all files in outgoingbox to account.sentbox + + // postfetchcommand + + let output = Command::new(postsendcommand) + // TODO: Add argument support + // TODO: Add support for passing config variables + .wait_with_output() + .context("Post 'Mail sending' command")?; + + write!(rt.stdout(), "{}", output.stdout)?; + write!(rt.stderr(), "{}", output.stderr)?; + + if self.rescan_maildirs { + // scan + // account.maildirroot + // recursively for new mail and store them in imag + } + } + +} + + diff --git a/lib/domain/libimagmail/src/store.rs b/lib/domain/libimagmail/src/store.rs new file mode 100644 index 00000000..6c672d9e --- /dev/null +++ b/lib/domain/libimagmail/src/store.rs @@ -0,0 +1,151 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::path::Path; +use std::path::PathBuf; +use std::fmt::Debug; + +use failure::Fallible as Result; +use toml::Value; +use toml_query::insert::TomlValueInsertExt; + +use libimagstore::store::FileLockEntry; +use libimagstore::store::Store; +use libimagstore::storeid::IntoStoreId; +use libimagstore::storeid::StoreId; +use libimagstore::iter::Entries; +use libimagentryref::hasher::default::DefaultHasher; +use libimagentryref::reference::Config; +use libimagentryref::reference::RefFassade; +use libimagentryref::reference::Ref; +use libimagentryref::reference::MutRef; + +use module_path::ModuleEntryPath; +use mid::MessageId; +use mail::Mail; +use hasher::MailHasher; +use util::get_message_id_for_mailfile; + +pub trait MailStore<'a> { + fn create_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config) + -> Result<FileLockEntry<'a>> + where P: AsRef<Path> + Debug, + CollName: AsRef<str> + Debug; + + fn get_mail_from_path<P>(&'a self, p: P) + -> Result<Option<FileLockEntry<'a>>> + where P: AsRef<Path> + Debug; + + fn retrieve_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config) + -> Result<FileLockEntry<'a>> + where P: AsRef<Path> + Debug, + CollName: AsRef<str> + Debug; + + fn get_mail(&'a self, mid: MessageId) -> Result<Option<FileLockEntry<'a>>>; + fn all_mails(&'a self) -> Result<Entries<'a>>; +} + +impl<'a> MailStore<'a> for Store { + + fn create_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config) + -> Result<FileLockEntry<'a>> + where P: AsRef<Path> + Debug, + CollName: AsRef<str> + Debug + { + let message_id = get_message_id_for_mailfile(p.as_ref())?; + let new_sid = ModuleEntryPath::new(message_id.clone()).into_storeid()?; + + let mut entry = self.create(new_sid)?; + let _ = entry + .as_ref_with_hasher_mut::<MailHasher>() + .make_ref(p, collection_name, config, false)?; + + let _ = entry + .get_header_mut() + .insert("mail.message-id", Value::String(message_id))?; + + Ok(entry) + } + + /// Same as MailStore::retrieve_mail_from_path() but uses Store::get() instead of + /// Store::retrieve() + fn get_mail_from_path<P>(&'a self, p: P) + -> Result<Option<FileLockEntry<'a>>> + where P: AsRef<Path> + Debug + { + let message_id = get_message_id_for_mailfile(p.as_ref())?; + let new_sid = ModuleEntryPath::new(message_id.clone()).into_storeid()?; + + match self.get(new_sid)? { + Some(mut entry) => { + if !entry.is_ref()? { + return Err(format_err!("{} is not a ref", entry.get_location())) + } + + if p.as_ref().ends_with(entry.as_ref_with_hasher::<MailHasher>().get_relative_path()?) { + return Err(format_err!("{} is not a ref to {:?}", + entry.get_location(), + p.as_ref().display())) + } |