summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--bin/core/imag-ref/Cargo.toml5
-rw-r--r--bin/core/imag-ref/src/main.rs19
-rw-r--r--bin/core/imag/build.rs2
-rw-r--r--bin/domain/imag-mail/Cargo.toml45
l---------bin/domain/imag-mail/README.md1
-rw-r--r--bin/domain/imag-mail/src/main.rs253
-rw-r--r--bin/domain/imag-mail/src/ui.rs94
-rw-r--r--doc/src/05100-lib-mails.md14
-rw-r--r--imagrc.toml5
-rw-r--r--lib/domain/libimagmail/Cargo.toml36
l---------lib/domain/libimagmail/README.md1
-rw-r--r--lib/domain/libimagmail/src/config.rs136
-rw-r--r--lib/domain/libimagmail/src/fetch.rs117
-rw-r--r--lib/domain/libimagmail/src/hasher.rs40
-rw-r--r--lib/domain/libimagmail/src/lib.rs62
-rw-r--r--lib/domain/libimagmail/src/mail.rs184
-rw-r--r--lib/domain/libimagmail/src/mid.rs33
-rw-r--r--lib/domain/libimagmail/src/send.rs111
-rw-r--r--lib/domain/libimagmail/src/store.rs151
-rw-r--r--lib/domain/libimagmail/src/util.rs64
-rw-r--r--lib/entry/libimagentryref/Cargo.toml7
-rw-r--r--lib/entry/libimagentryref/src/lib.rs2
-rw-r--r--lib/entry/libimagentryref/src/util.rs37
24 files changed, 1398 insertions, 23 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 4276aff..20f7d34 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -22,6 +22,7 @@ members = [
"bin/domain/imag-diary",
"bin/domain/imag-habit",
"bin/domain/imag-log",
+ "bin/domain/imag-mail",
"bin/domain/imag-notes",
"bin/domain/imag-timetrack",
"bin/domain/imag-todo",
@@ -34,6 +35,7 @@ members = [
"lib/domain/libimagdiary",
"lib/domain/libimaghabit",
"lib/domain/libimaglog",
+ "lib/domain/libimagmail",
"lib/domain/libimagnotes",
"lib/domain/libimagtimetrack",
"lib/domain/libimagtodo",
diff --git a/bin/core/imag-ref/Cargo.toml b/bin/core/imag-ref/Cargo.toml
index bf3cb6c..0c4b598 100644
--- a/bin/core/imag-ref/Cargo.toml
+++ b/bin/core/imag-ref/Cargo.toml
@@ -37,8 +37,3 @@ version = "^2.29"
default-features = false
features = ["color", "suggestions", "wrap_help"]
-[dependencies.toml-query]
-version = "0.8"
-default-features = false
-features = ["typed"]
-
diff --git a/bin/core/imag-ref/src/main.rs b/bin/core/imag-ref/src/main.rs
index 63edf36..a9500be 100644
--- a/bin/core/imag-ref/src/main.rs
+++ b/bin/core/imag-ref/src/main.rs
@@ -35,9 +35,7 @@
)]
#[macro_use] extern crate log;
-#[macro_use] extern crate failure;
extern crate clap;
-extern crate toml_query;
extern crate libimagstore;
#[macro_use] extern crate libimagrt;
@@ -52,8 +50,6 @@ use ui::build_ui;
use std::process::exit;
use std::io::Write;
-use failure::Fallible as Result;
-
use libimagerror::trace::MapErrTrace;
use libimagerror::exit::ExitUnwrap;
use libimagrt::setup::generate_runtime_setup;
@@ -62,7 +58,7 @@ use libimagentryref::reference::Ref;
use libimagentryref::reference::MutRef;
use libimagentryref::reference::RefFassade;
use libimagentryref::hasher::default::DefaultHasher;
-use libimagentryref::reference::Config as RefConfig;
+use libimagentryref::util::get_ref_config;
fn main() {
let version = make_imag_version!();
@@ -92,7 +88,7 @@ fn main() {
fn deref(rt: &Runtime) {
let cmd = rt.cli().subcommand_matches("deref").unwrap();
let ids = rt.ids::<::ui::PathProvider>().map_err_trace_exit_unwrap();
- let cfg = get_ref_config(&rt).map_err_trace_exit_unwrap();
+ let cfg = get_ref_config(&rt, "imag-ref").map_err_trace_exit_unwrap();
let out = rt.stdout();
let mut outlock = out.lock();
@@ -163,14 +159,3 @@ fn create(rt: &Runtime) {
unimplemented!()
}
-fn get_ref_config(rt: &Runtime) -> Result<RefConfig> {
- use toml_query::read::TomlValueReadExt;
-
- let setting_name = "ref.basepathes";
-
- rt.config()
- .ok_or_else(|| format_err!("No configuration, cannot find collection name for ref collection"))?
- .read_deserialized::<RefConfig>(setting_name)?
- .ok_or_else(|| format_err!("Setting missing: {}", setting_name))
-}
-
diff --git a/bin/core/imag/build.rs b/bin/core/imag/build.rs
index 33dfc97..7d91385 100644
--- a/bin/core/imag/build.rs
+++ b/bin/core/imag/build.rs
@@ -99,6 +99,7 @@ gen_mods_buildui!(
("../../../bin/domain/imag-diary/src/ui.rs" , imagdiary) ,
("../../../bin/domain/imag-habit/src/ui.rs" , imaghabit) ,
("../../../bin/domain/imag-log/src/ui.rs" , imaglog) ,
+ ("../../../bin/domain/imag-mail/src/ui.rs" , imagmail) ,
("../../../bin/domain/imag-notes/src/ui.rs" , imagnotes) ,
("../../../bin/domain/imag-timetrack/src/ui.rs" , imagtimetrack) ,
("../../../bin/domain/imag-todo/src/ui.rs" , imagtodo) ,
@@ -128,6 +129,7 @@ fn main() {
.subcommand(build_subcommand!("init" , imaginit , version))
.subcommand(build_subcommand!("link" , imaglink , version))
.subcommand(build_subcommand!("log" , imaglog , version))
+ .subcommand(build_subcommand!("mail" , imagmail , version))
.subcommand(build_subcommand!("mv" , imagmv , version))
.subcommand(build_subcommand!("notes" , imagnotes , version))
.subcommand(build_subcommand!("ref" , imagref , version))
diff --git a/bin/domain/imag-mail/Cargo.toml b/bin/domain/imag-mail/Cargo.toml
new file mode 100644
index 0000000..5faa653
--- /dev/null
+++ b/bin/domain/imag-mail/Cargo.toml
@@ -0,0 +1,45 @@
+[package]
+name = "imag-mail"
+version = "0.10.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Part of the imag core distribution: imag-mail command"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../../../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://imag-pim.org/doc/"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+build = "../../../build.rs"
+
+[badges]
+travis-ci = { repository = "matthiasbeyer/imag" }
+is-it-maintained-issue-resolution = { repository = "matthiasbeyer/imag" }
+is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" }
+maintenance = { status = "actively-developed" }
+
+[dependencies]
+log = "0.4.0"
+failure = "0.1"
+indoc = "0.3"
+
+libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" }
+libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
+libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
+libimagmail = { version = "0.10.0", path = "../../../lib/domain/libimagmail" }
+libimagutil = { version = "0.10.0", path = "../../../lib/etc/libimagutil" }
+libimagentryref = { version = "0.10.0", path = "../../../lib/entry/libimagentryref" }
+
+[dependencies.clap]
+version = "^2.29"
+default-features = false
+features = ["color", "suggestions", "wrap_help"]
+
+[dependencies.toml-query]
+version = "0.8"
+default-features = false
+features = ["typed"]
+
diff --git a/bin/domain/imag-mail/README.md b/bin/domain/imag-mail/README.md
new file mode 120000
index 0000000..764e9f3
--- /dev/null
+++ b/bin/domain/imag-mail/README.md
@@ -0,0 +1 @@
+../../../doc/src/04020-module-mails.md \ No newline at end of file
diff --git a/bin/domain/imag-mail/src/main.rs b/bin/domain/imag-mail/src/main.rs
new file mode 100644
index 0000000..8f7418d
--- /dev/null
+++ b/bin/domain/imag-mail/src/main.rs
@@ -0,0 +1,253 @@
+//
+// 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
+//
+
+#![forbid(unsafe_code)]
+
+#![deny(
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_must_use,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+extern crate clap;
+#[macro_use] extern crate log;
+#[macro_use] extern crate failure;
+extern crate toml_query;
+#[macro_use] extern crate indoc;
+
+#[macro_use] extern crate libimagrt;
+extern crate libimagmail;
+extern crate libimagerror;
+extern crate libimagstore;
+extern crate libimagutil;
+extern crate libimagentryref;
+
+use std::io::Write;
+use std::path::PathBuf;
+
+use failure::Fallible as Result;
+use toml_query::read::TomlValueReadTypeExt;
+
+use libimagerror::trace::{MapErrTrace, trace_error};
+use libimagerror::iter::TraceIterator;
+use libimagerror::exit::ExitUnwrap;
+use libimagerror::io::ToExitCode;
+use libimagmail::mail::Mail;
+use libimagmail::store::MailStore;
+use libimagmail::util;
+use libimagentryref::reference::{Ref, RefFassade};
+use libimagentryref::util::get_ref_config;
+use libimagrt::runtime::Runtime;
+use libimagrt::setup::generate_runtime_setup;
+use libimagutil::info_result::*;
+use libimagstore::store::FileLockEntry;
+use libimagstore::storeid::StoreIdIterator;
+use libimagstore::iter::get::StoreIdGetIteratorExtension;
+
+mod ui;
+
+use ui::build_ui;
+
+fn main() {
+ let version = make_imag_version!();
+ let rt = generate_runtime_setup("imag-mail",
+ &version,
+ "Mail collection tool",
+ build_ui);
+
+ rt.cli()
+ .subcommand_name()
+ .map(|name| {
+ debug!("Call {}", name);
+ match name {
+ "import-mail" => import_mail(&rt),
+ "list" => list(&rt),
+ "mail-store" => mail_store(&rt),
+ other => {
+ debug!("Unknown command");
+ let _ = rt.handle_unknown_subcommand("imag-mail", other, rt.cli())
+ .map_err_trace_exit_unwrap()
+ .code()
+ .map(::std::process::exit);
+ }
+ }
+ });
+}
+
+fn import_mail(rt: &Runtime) {
+ let collection_name = get_ref_collection_name(rt).map_err_trace_exit_unwrap();
+ let refconfig = get_ref_config(rt, "imag-mail").map_err_trace_exit_unwrap();
+ let scmd = rt.cli().subcommand_matches("import-mail").unwrap();
+ let store = rt.store();
+
+ debug!(r#"Importing mail with
+ collection_name = {}
+ refconfig = {:?}
+ "#, collection_name, refconfig);
+
+ scmd.values_of("path")
+ .unwrap() // enforced by clap
+ .map(PathBuf::from)
+ .map(|path| {
+ if scmd.is_present("ignore_existing_ids") {
+ store.retrieve_mail_from_path(path, &collection_name, &refconfig)
+ } else {
+ store.create_mail_from_path(path, &collection_name, &refconfig)
+ }
+ .map_info_str("Ok")
+ .map_err_trace_exit_unwrap()
+ })
+ .for_each(|entry| rt.report_touched(entry.get_location()).unwrap_or_exit());
+}
+
+fn list(rt: &Runtime) {
+ let refconfig = get_ref_config(rt, "imag-mail").map_err_trace_exit_unwrap();
+ let scmd = rt.cli().subcommand_matches("list").unwrap(); // safe via clap
+ let print_content = scmd.is_present("list-read");
+
+ if print_content {
+ /// TODO: Check whether workaround with "{}" is still necessary when updating "indoc"
+ warn!("{}", indoc!(r#"You requested to print the content of the mail as well.
+ We use the 'mailparse' crate underneath, but its implementation is nonoptimal.
+ Thus, the content might be printed as empty (no text in the email)
+ This is not reliable and might be wrong."#));
+
+ // TODO: Fix above.
+ }
+
+ // TODO: Implement lister type in libimagmail for this
+ //
+ // Optimization: Pass refconfig here instead of call get_ref_config() in lister function. This
+ // way we do not call get_ref_config() multiple times.
+ fn list_mail<'a>(rt: &Runtime,
+ refconfig: &::libimagentryref::reference::Config,
+ m: &FileLockEntry<'a>,
+ print_content: bool) {
+
+ let id = match m.get_message_id(&refconfig) {
+ Ok(Some(f)) => f,
+ Ok(None) => "<no id>".to_owned(),
+ Err(e) => {
+ trace_error(&e);
+ "<error>".to_owned()
+ },
+ };
+
+ let from = match m.get_from(&refconfig) {
+ Ok(Some(f)) => f,
+ Ok(None) => "<no from>".to_owned(),
+ Err(e) => {
+ trace_error(&e);
+ "<error>".to_owned()
+ },
+ };
+
+ let to = match m.get_to(&refconfig) {
+ Ok(Some(f)) => f,
+ Ok(None) => "<no to>".to_owned(),
+ Err(e) => {
+ trace_error(&e);
+ "<error>".to_owned()
+ },
+ };
+
+ let subject = match m.get_subject(&refconfig) {
+ Ok(Some(f)) => f,
+ Ok(None) => "<no subject>".to_owned(),
+ Err(e) => {
+ trace_error(&e);
+ "<error>".to_owned()
+ },
+ };
+
+ if print_content {
+ use libimagmail::hasher::MailHasher;
+
+ let content = m.as_ref_with_hasher::<MailHasher>()
+ .get_path(&refconfig)
+ .and_then(util::get_mail_text_content)
+ .map_err_trace_exit_unwrap();
+
+ writeln!(rt.stdout(),
+ "Mail: {id}\nFrom: {from}\nTo: {to}\n{subj}\n---\n{content}\n---\n",
+ from = from,
+ id = id,
+ subj = subject,
+ to = to,
+ content = content
+ ).to_exit_code().unwrap_or_exit();
+ } else {
+ writeln!(rt.stdout(),
+ "Mail: {id}\nFrom: {from}\nTo: {to}\n{subj}\n",
+ from = from,
+ id = id,
+ subj = subject,
+ to = to
+ ).to_exit_code().unwrap_or_exit();
+ }
+
+ let _ = rt.report_touched(m.get_location()).unwrap_or_exit();
+ }
+
+ if rt.ids_from_stdin() {
+ let iter = rt.ids::<::ui::PathProvider>()
+ .map_err_trace_exit_unwrap()
+ .into_iter()
+ .map(Ok);
+
+ StoreIdIterator::new(Box::new(iter))
+ } else {
+ rt.store()
+ .all_mails()
+ .map_err_trace_exit_unwrap()
+ .into_storeid_iter()
+ }
+ .map(|id| { debug!("Found: {:?}", id); id })
+ .into_get_iter(rt.store())
+ .trace_unwrap_exit()
+ .filter_map(|e| e)
+ .for_each(|m| list_mail(&rt, &refconfig, &m, print_content));
+}
+
+fn mail_store(rt: &Runtime) {
+ let _ = rt.cli().subcommand_matches("mail-store").unwrap();
+ error!("This feature is currently not implemented.");
+ unimplemented!()
+}
+
+fn get_ref_collection_name(rt: &Runtime) -> Result<String> {
+ let setting_name = "mail.ref_collection_name";
+
+ debug!("Getting configuration: {}", setting_name);
+
+ rt.config()
+ .ok_or_else(|| format_err!("No configuration, cannot find collection name for mail collection"))?
+ .read_string(setting_name)?
+ .ok_or_else(|| format_err!("Setting missing: {}", setting_name))
+}
+
diff --git a/bin/domain/imag-mail/src/ui.rs b/bin/domain/imag-mail/src/ui.rs
new file mode 100644
index 0000000..54a89b6
--- /dev/null
+++ b/bin/domain/imag-mail/src/ui.rs
@@ -0,0 +1,94 @@
+//
+// 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;
+
+use libimagstore::storeid::StoreId;
+use libimagrt::runtime::IdPathProvider;
+use libimagstore::storeid::IntoStoreId;
+use libimagerror::trace::MapErrTrace;
+
+use clap::{Arg, ArgMatches, App, SubCommand};
+
+pub fn build_ui<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
+ app
+ .subcommand(SubCommand::with_name("import-mail")
+ .about("Import a mail (create a reference to it) (Maildir)")
+ .version("0.1")
+ .arg(Arg::with_name("ignore-existing-ids")
+ .long("ignore-existing")
+ .short("I")
+ .takes_value(false)
+ .required(false)
+ .help("Ignore errors that might occur when store entries exist already"))
+
+ .arg(Arg::with_name("path")
+ .index(1)
+ .takes_value(true)
+ .multiple(true)
+ .required(true)
+ .help("Path to the mail file(s) to import")
+ .value_name("PATH"))
+ )
+
+ .subcommand(SubCommand::with_name("list")
+ .about("List all stored references to mails")
+ .version("0.1")
+
+ .arg(Arg::with_name("list-read")
+ .long("read")
+ .short("r")
+ .takes_value(false)
+ .required(false)
+ .multiple(false)
+ .help("Print the textual content of the mail itself as well"))
+
+ .arg(Arg::with_name("list-id")
+ .index(1)
+ .takes_value(true)
+ .required(false)
+ .multiple(true)
+ .help("The ids of the mails to list information for"))
+
+ )
+
+ .subcommand(SubCommand::with_name("mail-store")
+ .about("Operations on (subsets of) all mails")
+ .version("0.1")
+ .subcommand(SubCommand::with_name("update-refs")
+ .about("Create references based on Message-IDs for all loaded mails")
+ .version("0.1"))
+ // TODO: We really should be able to filter here.
+ )
+}
+
+pub struct PathProvider;
+impl IdPathProvider for PathProvider {
+ fn get_ids(matches: &ArgMatches) -> Vec<StoreId> {
+ if matches.is_present("list-id") {
+ matches.values_of("list-id")
+ .unwrap()
+ .map(|s| PathBuf::from(s).into_storeid().map_err_trace_exit_unwrap())
+ .collect()
+ } else {
+ vec![]
+ }
+ }
+}
+
diff --git a/doc/src/05100-lib-mails.md b/doc/src/05100-lib-mails.md
new file mode 100644
index 0000000..fde0879
--- /dev/null
+++ b/doc/src/05100-lib-mails.md
@@ -0,0 +1,14 @@
+## libimagmails
+
+The mail library implements everything that is needed for beeing used to
+implement a mail reader (MUA).
+
+It therefor providea reading mailboxes, getting related content or mails, saving
+attachements to external locations, crafting new mails and responses,...
+
+It also offers, natively, ways to search for mails (which are represented as
+imag entries) via tags, categories or even other metadata.
+
+For more information on the domain of the `imag-mail` command, look at the
+documentation of the @sec:modules:mails module.
+
diff --git a/imagrc.toml b/imagrc.toml
index b1c5478..f1d3be9 100644
--- a/imagrc.toml
+++ b/imagrc.toml
@@ -355,4 +355,9 @@ execute_in_store = false
# The base pathes define the search pathes for libimagentryref
[ref.basepathes]
music = "/home/user/music"
+mail = "/home/user/mail"
+
+[mail]
+# The name of the mail reference collection
+ref_collection_name = "mail"
diff --git a/lib/domain/libimagmail/Cargo.toml b/lib/domain/libimagmail/Cargo.toml
new file mode 100644
index 0000000..09825f1
--- /dev/null
+++ b/lib/domain/libimagmail/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "libimagmail"
+version = "0.10.0"
+authors = ["Matthias Beyer <mail@beyermatthias.de>"]
+
+description = "Library for the imag core distribution"
+
+keywords = ["imag", "PIM", "personal", "information", "management"]
+readme = "../../../README.md"
+license = "LGPL-2.1"
+
+documentation = "https://imag-pim.org/doc/"
+repository = "https://github.com/matthiasbeyer/imag"
+homepage = "http://imag-pim.org"
+
+[badges]
+travis-ci = { repository = "matthiasbeyer/imag" }
+is-it-maintained-issue-resolution = { repository = "matthiasbeyer/imag" }
+is-it-maintained-open-issues = { repository = "matthiasbeyer/imag" }
+maintenance = { status = "actively-developed" }
+
+[dependencies]
+log = "0.4.0"
+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/" }
+
diff --git a/lib/domain/libimagmail/README.md b/lib/domain/libimagmail/README.md
new file mode 120000
index 0000000..9aeb65d
--- /dev/null
+++ b/lib/domain/libimagmail/README.md
@@ -0,0 +1 @@
+../../../doc/src/05100-lib-mails.md \ No newline at end of file
diff --git a/lib/domain/libimagmail/src/config.rs b/lib/domain/libimagmail/src/config.rs
new file mode 100644
index 0000000..6258e6e
--- /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 0000000..de4f4b0
--- /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/hasher.rs b/lib/domain/libimagmail/src/hasher.rs
new file mode 100644
index 0000000..270b177
--- /dev/null
+++ b/lib/domain/libimagmail/src/hasher.rs
@@ -0,0 +1,40 @@
+//
+// 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::Fallible as Result;
+
+use libimagentryref::hasher::Hasher;
+
+pub struct MailHasher;
+
+impl Hasher for MailHasher {
+ const NAME: &'static str = "MailHasher";
+
+ /// 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)
+ }
+}
diff --git a/lib/domain/libimagmail/src/lib.rs b/lib/domain/libimagmail/src/lib.rs
new file mode 100644
index 0000000..5bbeeb3
--- /dev/null
+++ b/lib/domain/libimagmail/src/lib.rs
@@ -0,0 +1,62 @@
+//
+// 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
+//
+
+#![forbid(unsafe_code)]
+
+#![recursion_limit="256"]
+
+#![deny(
+ dead_code,
+ non_camel_case_types,
+ non_snake_case,
+ path_statements,
+ trivial_numeric_casts,
+ unstable_features,
+ unused_allocation,
+ unused_import_braces,
+ unused_imports,
+ unused_must_use,
+ unused_mut,
+ unused_qualifications,
+ while_true,
+)]
+
+#[macro_use] extern crate log;
+extern crate mailparse;
+extern crate toml;
+extern crate toml_query;
+extern crate filters;
+#[macro_use] extern crate failure;
+extern crate serde;
+#[macro_use] extern crate serde_derive;
+
+extern crate libimagerror;
+#[macro_use] extern crate libimagstore;
+extern crate libimagentryref;
+#[macro_use] extern crate libimagentryutil;
+
+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
new file mode 100644
index 0000000..b642944
--- /dev/null
+++ b/lib/domain/libimagmail/src/mail.rs
@@ -0,0 +1,184 @@
+//
+// 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 failure::Fallible as Result;
+use failure::ResultExt;
+use failure::Error;
+
+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>()
+ }
+
+ /// 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
+ }
+ })
+ .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>> {
+ 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")
+ }
+
+ // 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 0000000..ec73202
--- /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 0000000..9c4c540
--- /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 0000000..6c672d9
--- /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()))
+ }
+
+ 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"))
+ }
+}
+
diff --git a/lib/domain/libimagmail/src/util.rs b/lib/domain/libimagmail/src/util.rs
new file mode 100644
index 0000000..8b51a9f
--- /dev/null
+++ b/lib/domain/libimagmail/src/util.rs
@@ -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)
+}
+
diff --git a/lib/entry/libimagentryref/Cargo.toml b/lib/entry/libimagentryref/Cargo.toml
index 60692ac..ccec9af 100644
--- a/lib/entry/libimagentryref/Cargo.toml
+++ b/lib/entry/libimagentryref/Cargo.toml
@@ -25,14 +25,19 @@ log = "0.4.0"
failure = "0.1"
sha-1 = "0.8"
toml = "0.4"
-toml-query = "0.8"
serde = "1"
serde_derive = "1"
libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" }
libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" }
+libimagrt = { version = "0.10.0", path = "../../../lib/core/libimagrt" }
libimagentryutil = { version = "0.10.0", path = "../../../lib/entry/libimagentryutil" }
+[dependencies.toml-query]
+version = "0.8"
+default-features = false
+features = ["typed"]
+
[dev-dependencies]
env_logger = "0.5"
diff --git a/lib/entry/libimagentryref/src/lib.rs b/lib/entry/libimagentryref/src/lib.rs
index 4d8e02e..95c40b2 100644
--- a/lib/entry/libimagentryref/src/lib.rs
+++ b/lib/entry/libimagentryref/src/lib.rs
@@ -45,6 +45,7 @@ extern crate toml_query;
extern crate sha1;
extern crate libimagstore;
+extern crate libimagrt;
extern crate libimagerror;
#[macro_use] extern crate libimagentryutil;
#[macro_use] extern crate failure;
@@ -54,4 +55,5 @@ extern crate env_logger;
pub mod hasher;
pub mod reference;
+pub mod util;
diff --git a/lib/entry/libimagentryref/src/util.rs b/lib/entry/libimagentryref/src/util.rs
new file mode 100644
index 0000000..1daf2e7
--- /dev/null
+++ b/lib/entry/libimagentryref/src/util.rs
@@ -0,0 +1,37 @@
+//
+// 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 failure::Fallible as Result;
+
+use libimagrt::runtime::Runtime;
+
+use reference::Config as RefConfig;
+
+pub fn get_ref_config(rt: &Runtime, app_name: &'static str) -> Result<RefConfig> {
+ use toml_query::read::TomlValueReadExt;
+
+ let setting_name = "ref.basepathes";
+
+ rt.config()
+ .ok_or_else(|| format_err!("No configuration, cannot find collection name for {}", app_name))?
+ .read_deserialized::<RefConfig>(setting_name)?
+ .ok_or_else(|| format_err!("Setting missing: {}", setting_name))
+}
+
+