summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2019-02-20 16:23:19 +0100
committerMatthias Beyer <mail@beyermatthias.de>2019-02-20 16:23:19 +0100
commit44896327e567860bc1e924024f4a4a0403556322 (patch)
treeeeda58359f7a28d58bc626aeaf059672180f76bc
parent27c0a30494b7b827b48e62bfc90ec96da35e8e00 (diff)
parentdfc02a56799504e268b02c089d9070ec1d038f9b (diff)
downloadimag-44896327e567860bc1e924024f4a4a0403556322.zip
imag-44896327e567860bc1e924024f4a4a0403556322.tar.gz
Merge branch 'rewrite-mail-code' into master
This merge includes the rewritten mail infrastructure. It was rewritten for the new libimagentryref API, but might not be complete in regards towards maildir handling. Also, it only contains minimal features, not even bulk-import is implemented yet. Also, sending, receiving and other nice-to-have MUA features are not yet implemented. Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-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))
+}
+
+