Rewrite libimagmail
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
This commit is contained in:
parent
198c59f717
commit
a819aeb6ed
10 changed files with 778 additions and 181 deletions
|
@ -21,10 +21,16 @@ maintenance = { status = "actively-developed" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4.0"
|
log = "0.4.0"
|
||||||
email = "0.0.20"
|
toml = "0.4"
|
||||||
|
toml-query = "0.8"
|
||||||
|
mailparse = "0.6.5"
|
||||||
filters = "0.3"
|
filters = "0.3"
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
|
serde = "1"
|
||||||
|
serde_derive = "1"
|
||||||
|
|
||||||
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
|
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
|
||||||
libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
|
libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
|
||||||
libimagentryref = { version = "0.10.0", path = "../../../lib/entry/libimagentryref" }
|
libimagentryref = { version = "0.10.0", path = "../../../lib/entry/libimagentryref" }
|
||||||
|
libimagentryutil = { version = "0.10.0", path = "../../../lib/entry/libimagentryutil/" }
|
||||||
|
|
||||||
|
|
136
lib/domain/libimagmail/src/config.rs
Normal file
136
lib/domain/libimagmail/src/config.rs
Normal file
|
@ -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>,
|
||||||
|
}
|
||||||
|
|
117
lib/domain/libimagmail/src/fetch.rs
Normal file
117
lib/domain/libimagmail/src/fetch.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,39 +17,24 @@
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
//
|
//
|
||||||
|
|
||||||
//! Module for the MailIter
|
use std::path::Path;
|
||||||
//!
|
|
||||||
//! 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 mail::Mail;
|
|
||||||
use failure::Fallible as Result;
|
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>>> {
|
impl Hasher for MailHasher {
|
||||||
_marker: PhantomData<I>,
|
const NAME: &'static str = "MailHasher";
|
||||||
i: I,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, I: Iterator<Item = FileLockEntry<'a>>> MailIter<'a, I> {
|
/// hash the file at path `path`
|
||||||
|
///
|
||||||
pub fn new(i: I) -> MailIter<'a, I> {
|
/// TODO: This is the expensive implementation. We use the message Id as hash, which is
|
||||||
MailIter { _marker: PhantomData, i: i }
|
/// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -38,14 +38,25 @@
|
||||||
)]
|
)]
|
||||||
|
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
extern crate email;
|
extern crate mailparse;
|
||||||
|
extern crate toml;
|
||||||
|
extern crate toml_query;
|
||||||
extern crate filters;
|
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 libimagerror;
|
||||||
extern crate libimagstore;
|
#[macro_use] extern crate libimagstore;
|
||||||
extern crate libimagentryref;
|
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 mail;
|
||||||
|
pub mod mid;
|
||||||
|
pub mod store;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
|
|
|
@ -17,185 +17,168 @@
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
// 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::Fallible as Result;
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::err_msg;
|
|
||||||
|
|
||||||
struct UniqueMailRefGenerator;
|
use libimagstore::store::Entry;
|
||||||
impl UniqueRefPathGenerator for UniqueMailRefGenerator {
|
use libimagentryutil::isa::Is;
|
||||||
/// The collection the `StoreId` should be created for
|
use libimagentryutil::isa::IsKindHeaderPathProvider;
|
||||||
fn collection() -> &'static str {
|
use libimagentryref::reference::Config as RefConfig;
|
||||||
"mail"
|
use libimagentryref::reference::{Ref, RefFassade};
|
||||||
}
|
|
||||||
|
|
||||||
/// A function which should generate a unique string for a Path
|
provide_kindflag_path!(pub IsMail, "mail.is_mail");
|
||||||
fn unique_hash<A: AsRef<Path>>(path: A) -> Result<String> {
|
|
||||||
use filters::filter::Filter;
|
|
||||||
use email::Header;
|
|
||||||
|
|
||||||
let mut s = String::new();
|
pub trait Mail : RefFassade {
|
||||||
let _ = OpenOptions::new()
|
fn is_mail(&self) -> Result<bool>;
|
||||||
.read(true)
|
fn get_field(&self, refconfig: &RefConfig, field: &str) -> Result<Option<String>>;
|
||||||
.write(false)
|
fn get_from(&self, refconfig: &RefConfig) -> Result<Option<String>>;
|
||||||
.create(false)
|
fn get_to(&self, refconfig: &RefConfig) -> Result<Option<String>>;
|
||||||
.open(path)?
|
fn get_subject(&self, refconfig: &RefConfig) -> Result<Option<String>>;
|
||||||
.read_to_string(&mut s)?;
|
fn get_message_id(&self, refconfig: &RefConfig) -> Result<Option<String>>;
|
||||||
|
fn get_in_reply_to(&self, refconfig: &RefConfig) -> Result<Option<String>>;
|
||||||
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);
|
|
||||||
}
|
|
||||||
let s : String = v.join("");
|
|
||||||
Ok(s)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Postprocess the generated `StoreId` object
|
|
||||||
fn postprocess_storeid(sid: StoreId) -> Result<StoreId> {
|
|
||||||
Ok(sid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Buffer(String);
|
impl Mail for Entry {
|
||||||
|
|
||||||
impl Buffer {
|
fn is_mail(&self) -> Result<bool> {
|
||||||
pub fn parsed(&self) -> EmailParsingResult<MimeMessage> {
|
self.is::<IsMail>()
|
||||||
MimeMessage::parse(&self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for Buffer {
|
|
||||||
fn from(data: String) -> Buffer {
|
|
||||||
Buffer(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens a mail by the passed hash
|
/// Get a value of a single field of the mail file
|
||||||
pub fn open<S: AsRef<str>>(store: &Store, hash: S) -> Result<Option<Mail>> {
|
fn get_field(&self, refconfig: &RefConfig, field: &str) -> Result<Option<String>> {
|
||||||
debug!("Opening Mail by Hash");
|
use std::fs::read_to_string;
|
||||||
store.get_ref::<UniqueMailRefGenerator, S>(hash)
|
use hasher::MailHasher;
|
||||||
.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),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_field(&self, field: &str) -> Result<Option<String>> {
|
|
||||||
debug!("Getting field in mail: {:?}", field);
|
debug!("Getting field in mail: {:?}", field);
|
||||||
self.1
|
let mail_file_location = self.as_ref_with_hasher::<MailHasher>().get_path(refconfig)?;
|
||||||
.parsed()
|
|
||||||
.context(err_msg("Mail parsing error"))
|
match ::mailparse::parse_mail(read_to_string(mail_file_location.as_path())?.as_bytes())
|
||||||
.map_err(Error::from)
|
.context(format_err!("Cannot parse Email {}", mail_file_location.display()))?
|
||||||
.map(|parsed| {
|
.headers
|
||||||
parsed.headers
|
.into_iter()
|
||||||
.iter()
|
.filter_map(|hdr| match hdr.get_key() {
|
||||||
.filter(|hdr| hdr.name == field)
|
Err(e) => Some(Err(e).map_err(Error::from)),
|
||||||
.nth(0)
|
Ok(k) => if k == field {
|
||||||
.and_then(|field| field.get_value().ok())
|
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
|
||||||
|
///
|
||||||
|
/// # 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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>> {
|
||||||
|
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>> {
|
pub fn get_from(&self) -> Result<Option<String>> {
|
||||||
self.get_field("From")
|
self.get_field("From")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a value of the `To` field of the mail file
|
||||||
pub fn get_to(&self) -> Result<Option<String>> {
|
pub fn get_to(&self) -> Result<Option<String>> {
|
||||||
self.get_field("To")
|
self.get_field("To")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a value of the `Subject` field of the mail file
|
||||||
pub fn get_subject(&self) -> Result<Option<String>> {
|
pub fn get_subject(&self) -> Result<Option<String>> {
|
||||||
self.get_field("Subject")
|
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>> {
|
pub fn get_message_id(&self) -> Result<Option<String>> {
|
||||||
self.get_field("Message-ID")
|
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>> {
|
pub fn get_in_reply_to(&self) -> Result<Option<String>> {
|
||||||
self.get_field("In-Reply-To")
|
self.get_field("In-Reply-To")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fle(&self) -> &FileLockEntry<'a> {
|
// TODO: Offer functionality to load and parse mail _once_ from disk, and then use helper object
|
||||||
&self.0
|
// 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.
|
||||||
}
|
}
|
||||||
|
|
33
lib/domain/libimagmail/src/mid.rs
Normal file
33
lib/domain/libimagmail/src/mid.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
111
lib/domain/libimagmail/src/send.rs
Normal file
111
lib/domain/libimagmail/src/send.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
151
lib/domain/libimagmail/src/store.rs
Normal file
151
lib/domain/libimagmail/src/store.rs
Normal file
|
@ -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()))
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = entry.get_header_mut().insert("mail.message-id", Value::String(message_id))?;
|
||||||
|
Ok(Some(entry))
|
||||||
|
},
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
let message_id = get_message_id_for_mailfile(&p)?;
|
||||||
|
let new_sid = ModuleEntryPath::new(message_id.clone()).into_storeid()?;
|
||||||
|
let mut entry = self.retrieve(new_sid)?;
|
||||||
|
|
||||||
|
let _ = entry
|
||||||
|
.get_header_mut()
|
||||||
|
.insert("mail.message-id", Value::String(message_id))?;
|
||||||
|
|
||||||
|
let _ = entry
|
||||||
|
.as_ref_with_hasher_mut::<DefaultHasher>()
|
||||||
|
.make_ref(p, collection_name, config, false)?;
|
||||||
|
|
||||||
|
Ok(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mail(&'a self, mid: MessageId) -> Result<Option<FileLockEntry<'a>>> {
|
||||||
|
let mid_s : String = mid.into();
|
||||||
|
self.get(StoreId::new(PathBuf::from(mid_s))?)
|
||||||
|
.and_then(|oe| match oe {
|
||||||
|
Some(e) => if e.is_mail()? {
|
||||||
|
Ok(Some(e))
|
||||||
|
} else {
|
||||||
|
Err(format_err!("{} is not a mail entry", e.get_location()))
|
||||||
|
},
|
||||||
|
None => Ok(None)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_mails(&'a self) -> Result<Entries<'a>> {
|
||||||
|
self.entries().map(|ent| ent.in_collection("mail"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
64
lib/domain/libimagmail/src/util.rs
Normal file
64
lib/domain/libimagmail/src/util.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
//
|
||||||
|
// 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 failure::Error;
|
||||||
|
use failure::Fallible as Result;
|
||||||
|
use failure::ResultExt;
|
||||||
|
|
||||||
|
pub(crate) fn get_message_id_for_mailfile<P: AsRef<Path>>(p: P) -> Result<String> {
|
||||||
|
::mailparse::parse_mail(::std::fs::read_to_string(p.as_ref())?.as_bytes())
|
||||||
|
.context(format_err!("Cannot parse Email {}", p.as_ref().display()))?
|
||||||
|
.headers
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|hdr| match hdr.get_key() {
|
||||||
|
Err(e) => Some(Err(e).map_err(Error::from)),
|
||||||
|
Ok(k) => if k.to_lowercase() == "message-id" {
|
||||||
|
Some(Ok(hdr))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| format_err!("Message Id not found in {}", p.as_ref().display()))?
|
||||||
|
.and_then(|hdr| hdr.get_value().map_err(Error::from))
|
||||||
|
.map(strip_message_delimiters)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Strips message delimiters ('<' and '>') from a Message-ID field.
|
||||||
|
pub(crate) fn strip_message_delimiters<ID: AsRef<str>>(id: ID) -> String {
|
||||||
|
let len = id.as_ref().len();
|
||||||
|
// We have to strip the '<' and '>' if there are any, because they do not belong to the
|
||||||
|
// Message-Id at all
|
||||||
|
id.as_ref()
|
||||||
|
.chars()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(idx, chr)| !(*idx == 0 && *chr == '<' || *idx == len - 1 && *chr == '>'))
|
||||||
|
.map(|tpl| tpl.1)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mail_text_content<P: AsRef<Path>>(p: P) -> Result<String> {
|
||||||
|
::mailparse::parse_mail(::std::fs::read_to_string(p.as_ref())?.as_bytes())
|
||||||
|
.context(format_err!("Cannot parse Email {}", p.as_ref().display()))?
|
||||||
|
.get_body()
|
||||||
|
.map_err(Error::from)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue