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>
This commit is contained in:
commit
44896327e5
24 changed files with 1398 additions and 23 deletions
|
@ -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",
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
45
bin/domain/imag-mail/Cargo.toml
Normal file
45
bin/domain/imag-mail/Cargo.toml
Normal file
|
@ -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"]
|
||||
|
1
bin/domain/imag-mail/README.md
Symbolic link
1
bin/domain/imag-mail/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../doc/src/04020-module-mails.md
|
253
bin/domain/imag-mail/src/main.rs
Normal file
253
bin/domain/imag-mail/src/main.rs
Normal file
|
@ -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))
|
||||
}
|
||||
|
94
bin/domain/imag-mail/src/ui.rs
Normal file
94
bin/domain/imag-mail/src/ui.rs
Normal file
|
@ -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![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
14
doc/src/05100-lib-mails.md
Normal file
14
doc/src/05100-lib-mails.md
Normal file
|
@ -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.
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
36
lib/domain/libimagmail/Cargo.toml
Normal file
36
lib/domain/libimagmail/Cargo.toml
Normal file
|
@ -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/" }
|
||||
|
1
lib/domain/libimagmail/README.md
Symbolic link
1
lib/domain/libimagmail/README.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../doc/src/05100-lib-mails.md
|
136
lib/domain/libimagmail/src/config.rs
Normal file
136
lib/domain/libimagmail/src/config.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
//
|
||||
// imag - the personal information management suite for the commandline
|
||||
// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; version
|
||||
// 2.1 of the License.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// A struct representing a full mail configuration, required for working with this library
|
||||
///
|
||||
/// For convenience reasons, this implements Serialize and Deserialize, so it can be fetched from a
|
||||
/// configuration file for example
|
||||
///
|
||||
/// # TODO
|
||||
///
|
||||
/// Figure out how to use handlebars with variables on this. Right now the support for that is not
|
||||
/// implemented yet.
|
||||
///
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct MailConfig {
|
||||
default_account : String,
|
||||
accounts : Vec<MailAccountConfig>,
|
||||
fetchcommand : MailCommand,
|
||||
postfetchcommand : Option<MailCommand>,
|
||||
sendcommand : MailCommand,
|
||||
postsendcommand : Option<MailCommand>,
|
||||
}
|
||||
|
||||
impl MailConfig {
|
||||
pub fn default_account(&self) -> &String {
|
||||
&self.default_account
|
||||
}
|
||||
|
||||
pub fn accounts(&self) -> &Vec<MailAccountConfig> {
|
||||
&self.accounts
|
||||
}
|
||||
|
||||
pub fn account(&self, name: &str) -> Option<&MailAccountConfig> {
|
||||
self.accounts()
|
||||
.iter()
|
||||
.filter(|a| a.name == name)
|
||||
.next()
|
||||
}
|
||||
|
||||
pub fn fetchcommand(&self) -> &MailCommand {
|
||||
&self.fetchcommand
|
||||
}
|
||||
|
||||
pub fn postfetchcommand(&self) -> Option<&MailCommand> {
|
||||
self.postfetchcommand.as_ref()
|
||||
}
|
||||
|
||||
pub fn sendcommand(&self) -> &MailCommand {
|
||||
&self.sendcommand
|
||||
}
|
||||
|
||||
pub fn postsendcommand(&self) -> Option<&MailCommand> {
|
||||
self.postsendcommand.as_ref()
|
||||
}
|
||||
|
||||
pub fn fetchcommand_for_account(&self, account_name: &str) -> &MailCommand {
|
||||
self.accounts()
|
||||
.iter()
|
||||
.filter(|a| a.name == account_name)
|
||||
.next()
|
||||
.and_then(|a| a.fetchcommand.as_ref())
|
||||
.unwrap_or_else(|| self.fetchcommand())
|
||||
}
|
||||
|
||||
pub fn postfetchcommand_for_account(&self, account_name: &str) -> Option<&MailCommand> {
|
||||
self.accounts()
|
||||
.iter()
|
||||
.filter(|a| a.name == account_name)
|
||||
.next()
|
||||
.and_then(|a| a.postfetchcommand.as_ref())
|
||||
.or_else(|| self.postfetchcommand())
|
||||
}
|
||||
|
||||
pub fn sendcommand_for_account(&self, account_name: &str) -> &MailCommand {
|
||||
self.accounts()
|
||||
.iter()
|
||||
.filter(|a| a.name == account_name)
|
||||
.next()
|
||||
.and_then(|a| a.sendcommand.as_ref())
|
||||
.unwrap_or_else(|| self.sendcommand())
|
||||
}
|
||||
|
||||
pub fn postsendcommand_for_account(&self, account_name: &str) -> Option<&MailCommand> {
|
||||
self.accounts()
|
||||
.iter()
|
||||
.filter(|a| a.name == account_name)
|
||||
.next()
|
||||
.and_then(|a| a.postsendcommand.as_ref())
|
||||
.or_else(|| self.postsendcommand())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// A configuration for a single mail accounts
|
||||
///
|
||||
/// If one of the keys `fetchcommand`, `postfetchcommand`, `sendcommand` or `postsendcommand` is
|
||||
/// not available, the implementation of the `MailConfig` will automatically use the global
|
||||
/// configuration if applicable.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct MailAccountConfig {
|
||||
pub name : String,
|
||||
pub outgoingbox : PathBuf,
|
||||
pub draftbox : PathBuf,
|
||||
pub sentbox : PathBuf,
|
||||
pub maildirroot : PathBuf,
|
||||
pub fetchcommand : Option<MailCommand>,
|
||||
pub postfetchcommand : Option<MailCommand>,
|
||||
pub sendcommand : Option<MailCommand>,
|
||||
pub postsendcommand : Option<MailCommand>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct MailCommand {
|
||||
command: String,
|
||||
env: Vec<String>,
|
||||
args: Vec<String>,
|
||||
}
|
||||
|
117
lib/domain/libimagmail/src/fetch.rs
Normal file
117
lib/domain/libimagmail/src/fetch.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
//
|
||||
// imag - the personal information management suite for the commandline
|
||||
// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; version
|
||||
// 2.1 of the License.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
|
||||
use config::MailConfig;
|
||||
|
||||
pub struct MailFetcher<'a> {
|
||||
config: &'a MailConfig,
|
||||
account_name_to_fetch: Option<String>,
|
||||
boxes: Vec<String>,
|
||||
|
||||
rescan_maildirs: bool,
|
||||
}
|
||||
|
||||
impl MailFetcher {
|
||||
pub fn new(config: &MailConfig) -> Self {
|
||||
MailFetcher {
|
||||
config,
|
||||
account_name_to_fetch: None,
|
||||
rescan_maildirs: false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch_account(mut self, name: String) -> Self {
|
||||
self.account_name_to_fetch = Some(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fetch_box(mut self, name: String) -> Self {
|
||||
self.boxes.push(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn fetch_boxes<I>(mut self, names: I) -> Self
|
||||
where I: IntoIterator<Item = String>
|
||||
{
|
||||
self.boxes.append(names.into_iter().collect())
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rescan_maildirs(mut self, b: bool) -> Self {
|
||||
self.rescan_maildirs = b;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(&self, store: &Store) -> Result<()> {
|
||||
let fetchcommand = match self.account_name_to_fetch {
|
||||
Some(name) => self.config.fetchcommand_for_account(name),
|
||||
None => self.confnig.fetchcommand(),
|
||||
};
|
||||
|
||||
let postfetchcommand = match self.account_name_to_fetch {
|
||||
Some(name) => self.config.postfetchcommand_for_account(name),
|
||||
None => self.confnig.postfetchcommand(),
|
||||
};
|
||||
|
||||
let account = config
|
||||
.account(self.account_name_to_fetch)
|
||||
.ok_or_else(|| format_err!("Account '{}' does not exist", self.account_name_to_fetch))?;
|
||||
|
||||
if fetchcommand.contains(" ") {
|
||||
// error on whitespace in command
|
||||
}
|
||||
|
||||
if postfetchcommand.contains(" ") {
|
||||
// error on whitespace in command
|
||||
}
|
||||
|
||||
// fetchcommand
|
||||
|
||||
let mut output = Command::new(fetchcommand)
|
||||
// TODO: Add argument support
|
||||
// TODO: Add support for passing config variables
|
||||
// TODO: Add support for passing environment
|
||||
.args(self.boxes)
|
||||
.wait_with_output()
|
||||
.context("Mail fetching")?;
|
||||
|
||||
write!(rt.stdout(), "{}", output.stdout)?;
|
||||
write!(rt.stderr(), "{}", output.stderr)?;
|
||||
|
||||
// postfetchcommand
|
||||
|
||||
let output = Command::new(postfetchcommand)
|
||||
// TODO: Add argument support
|
||||
// TODO: Add support for passing config variables
|
||||
.wait_with_output()
|
||||
.context("Post 'Mail fetching' command")?;
|
||||
|
||||
write!(rt.stdout(), "{}", output.stdout)?;
|
||||
write!(rt.stderr(), "{}", output.stderr)?;
|
||||
|
||||
if self.rescan_maildirs {
|
||||
// scan
|
||||
// account.maildirroot
|
||||
// recursively for new mail and store them in imag
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
40
lib/domain/libimagmail/src/hasher.rs
Normal file
40
lib/domain/libimagmail/src/hasher.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
62
lib/domain/libimagmail/src/lib.rs
Normal file
62
lib/domain/libimagmail/src/lib.rs
Normal file
|
@ -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;
|
||||
|
184
lib/domain/libimagmail/src/mail.rs
Normal file
184
lib/domain/libimagmail/src/mail.rs
Normal file
|
@ -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.
|
||||
}
|
33
lib/domain/libimagmail/src/mid.rs
Normal file
33
lib/domain/libimagmail/src/mid.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// imag - the personal information management suite for the commandline
|
||||
// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; version
|
||||
// 2.1 of the License.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
|
||||
/// Helper type for handling message IDs
|
||||
///
|
||||
/// Message IDs are used to identfy emails uniquely, so we should at least have a type for
|
||||
/// representing them and make handling a bit easier.
|
||||
///
|
||||
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct MessageId(String);
|
||||
|
||||
impl Into<String> for MessageId {
|
||||
fn into(self) -> String {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
111
lib/domain/libimagmail/src/send.rs
Normal file
111
lib/domain/libimagmail/src/send.rs
Normal file
|
@ -0,0 +1,111 @@
|
|||
//
|
||||
// imag - the personal information management suite for the commandline
|
||||
// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; version
|
||||
// 2.1 of the License.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
|
||||
use config::MailConfig;
|
||||
|
||||
pub struct MailSender<'a> {
|
||||
config: &'a MailConfig,
|
||||
account_name_to_send_with: Option<String>,
|
||||
|
||||
rescan_maildirs: bool,
|
||||
}
|
||||
|
||||
impl MailSender {
|
||||
pub fn new(config: &MailConfig) -> Self {
|
||||
MailSender {
|
||||
config,
|
||||
account_name_to_send_with: None,
|
||||
rescan_maildirs: false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_account(mut self, name: String) -> Self {
|
||||
self.account_name_to_send_with = Some(name);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rescan_maildirs(mut self, b: bool) -> Self {
|
||||
self.rescan_maildirs = b;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn run(&self, store: &Store) -> Result<()> {
|
||||
let sendcommand = match self.account_name_to_send_with {
|
||||
Some(name) => self.config.sendcommand_for_account(name),
|
||||
None => self.confnig.sendcommand(),
|
||||
};
|
||||
|
||||
let postsendcommand = match self.account_name_to_send_with {
|
||||
Some(name) => self.config.postsendcommand_for_account(name),
|
||||
None => self.confnig.sendcommand(),
|
||||
};
|
||||
|
||||
let account = config
|
||||
.account(self.account_name_to_send_with)
|
||||
.ok_or_else(|| format_err!("Account '{}' does not exist", self.account_name_to_send_with))?;
|
||||
|
||||
if sendcommand.contains(" ") {
|
||||
// error on whitespace in command
|
||||
}
|
||||
|
||||
if postsendcommand.contains(" ") {
|
||||
// error on whitespace in command
|
||||
}
|
||||
|
||||
// sendcommand
|
||||
//
|
||||
let outgoingbox = account
|
||||
.outgoingbox
|
||||
.to_str()
|
||||
.ok_or_else(|| format_err!("Cannot use '{:?}' as outgoingbox", account.outgoingbox))?;
|
||||
|
||||
let mut output = Command::new(sendcommand)
|
||||
// TODO: Add argument support
|
||||
// TODO: Add support for passing config variables
|
||||
// TODO: Add support for passing environment
|
||||
.arg(outgoingbox)
|
||||
.wait_with_output()
|
||||
.context("Mail sending")?;
|
||||
|
||||
write!(rt.stdout(), "{}", output.stdout)?;
|
||||
write!(rt.stderr(), "{}", output.stderr)?;
|
||||
|
||||
// TODO: Move all files in outgoingbox to account.sentbox
|
||||
|
||||
// postfetchcommand
|
||||
|
||||
let output = Command::new(postsendcommand)
|
||||
// TODO: Add argument support
|
||||
// TODO: Add support for passing config variables
|
||||
.wait_with_output()
|
||||
.context("Post 'Mail sending' command")?;
|
||||
|
||||
write!(rt.stdout(), "{}", output.stdout)?;
|
||||
write!(rt.stderr(), "{}", output.stderr)?;
|
||||
|
||||
if self.rescan_maildirs {
|
||||
// scan
|
||||
// account.maildirroot
|
||||
// recursively for new mail and store them in imag
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
151
lib/domain/libimagmail/src/store.rs
Normal file
151
lib/domain/libimagmail/src/store.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
//
|
||||
// imag - the personal information management suite for the commandline
|
||||
// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; version
|
||||
// 2.1 of the License.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use failure::Fallible as Result;
|
||||
use toml::Value;
|
||||
use toml_query::insert::TomlValueInsertExt;
|
||||
|
||||
use libimagstore::store::FileLockEntry;
|
||||
use libimagstore::store::Store;
|
||||
use libimagstore::storeid::IntoStoreId;
|
||||
use libimagstore::storeid::StoreId;
|
||||
use libimagstore::iter::Entries;
|
||||
use libimagentryref::hasher::default::DefaultHasher;
|
||||
use libimagentryref::reference::Config;
|
||||
use libimagentryref::reference::RefFassade;
|
||||
use libimagentryref::reference::Ref;
|
||||
use libimagentryref::reference::MutRef;
|
||||
|
||||
use module_path::ModuleEntryPath;
|
||||
use mid::MessageId;
|
||||
use mail::Mail;
|
||||
use hasher::MailHasher;
|
||||
use util::get_message_id_for_mailfile;
|
||||
|
||||
pub trait MailStore<'a> {
|
||||
fn create_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config)
|
||||
-> Result<FileLockEntry<'a>>
|
||||
where P: AsRef<Path> + Debug,
|
||||
CollName: AsRef<str> + Debug;
|
||||
|
||||
fn get_mail_from_path<P>(&'a self, p: P)
|
||||
-> Result<Option<FileLockEntry<'a>>>
|
||||
where P: AsRef<Path> + Debug;
|
||||
|
||||
fn retrieve_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config)
|
||||
-> Result<FileLockEntry<'a>>
|
||||
where P: AsRef<Path> + Debug,
|
||||
CollName: AsRef<str> + Debug;
|
||||
|
||||
fn get_mail(&'a self, mid: MessageId) -> Result<Option<FileLockEntry<'a>>>;
|
||||
fn all_mails(&'a self) -> Result<Entries<'a>>;
|
||||
}
|
||||
|
||||
impl<'a> MailStore<'a> for Store {
|
||||
|
||||
fn create_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config)
|
||||
-> Result<FileLockEntry<'a>>
|
||||
where P: AsRef<Path> + Debug,
|
||||
CollName: AsRef<str> + Debug
|
||||
{
|
||||
let message_id = get_message_id_for_mailfile(p.as_ref())?;
|
||||
let new_sid = ModuleEntryPath::new(message_id.clone()).into_storeid()?;
|
||||
|
||||
let mut entry = self.create(new_sid)?;
|
||||
let _ = entry
|
||||
.as_ref_with_hasher_mut::<MailHasher>()
|
||||
.make_ref(p, collection_name, config, false)?;
|
||||
|
||||
let _ = entry
|
||||
.get_header_mut()
|
||||
.insert("mail.message-id", Value::String(message_id))?;
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
/// Same as MailStore::retrieve_mail_from_path() but uses Store::get() instead of
|
||||
/// Store::retrieve()
|
||||
fn get_mail_from_path<P>(&'a self, p: P)
|
||||
-> Result<Option<FileLockEntry<'a>>>
|
||||
where P: AsRef<Path> + Debug
|
||||
{
|
||||
let message_id = get_message_id_for_mailfile(p.as_ref())?;
|
||||
let new_sid = ModuleEntryPath::new(message_id.clone()).into_storeid()?;
|
||||
|
||||
match self.get(new_sid)? {
|
||||
Some(mut entry) => {
|
||||
if !entry.is_ref()? {
|
||||
return Err(format_err!("{} is not a ref", entry.get_location()))
|
||||
}
|
||||
|
||||
if p.as_ref().ends_with(entry.as_ref_with_hasher::<MailHasher>().get_relative_path()?) {
|
||||
return Err(format_err!("{} is not a ref to {:?}",
|
||||
entry.get_location(),
|
||||
p.as_ref().display()))
|
||||
}
|
||||
|
||||
let _ = entry.get_header_mut().insert("mail.message-id", Value::String(message_id))?;
|
||||
Ok(Some(entry))
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn retrieve_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config)
|
||||
-> Result<FileLockEntry<'a>>
|
||||
where P: AsRef<Path> + Debug,
|
||||
CollName: AsRef<str> + Debug
|
||||
{
|
||||
let message_id = get_message_id_for_mailfile(&p)?;
|
||||
let new_sid = ModuleEntryPath::new(message_id.clone()).into_storeid()?;
|
||||
let mut entry = self.retrieve(new_sid)?;
|
||||
|
||||
let _ = entry
|
||||
.get_header_mut()
|
||||
.insert("mail.message-id", Value::String(message_id))?;
|
||||
|
||||
let _ = entry
|
||||
.as_ref_with_hasher_mut::<DefaultHasher>()
|
||||
.make_ref(p, collection_name, config, false)?;
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
fn get_mail(&'a self, mid: MessageId) -> Result<Option<FileLockEntry<'a>>> {
|
||||
let mid_s : String = mid.into();
|
||||
self.get(StoreId::new(PathBuf::from(mid_s))?)
|
||||
.and_then(|oe| match oe {
|
||||
Some(e) => if e.is_mail()? {
|
||||
Ok(Some(e))
|
||||
} else {
|
||||
Err(format_err!("{} is not a mail entry", e.get_location()))
|
||||
},
|
||||
None => Ok(None)
|
||||
})
|
||||
}
|
||||
|
||||
fn all_mails(&'a self) -> Result<Entries<'a>> {
|
||||
self.entries().map(|ent| ent.in_collection("mail"))
|
||||
}
|
||||
}
|
||||
|
64
lib/domain/libimagmail/src/util.rs
Normal file
64
lib/domain/libimagmail/src/util.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// imag - the personal information management suite for the commandline
|
||||
// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; version
|
||||
// 2.1 of the License.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
//
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use failure::Error;
|
||||
use failure::Fallible as Result;
|
||||
use failure::ResultExt;
|
||||
|
||||
pub(crate) fn get_message_id_for_mailfile<P: AsRef<Path>>(p: P) -> Result<String> {
|
||||
::mailparse::parse_mail(::std::fs::read_to_string(p.as_ref())?.as_bytes())
|
||||
.context(format_err!("Cannot parse Email {}", p.as_ref().display()))?
|
||||
.headers
|
||||
.into_iter()
|
||||
.filter_map(|hdr| match hdr.get_key() {
|
||||
Err(e) => Some(Err(e).map_err(Error::from)),
|
||||
Ok(k) => if k.to_lowercase() == "message-id" {
|
||||
Some(Ok(hdr))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
.ok_or_else(|| format_err!("Message Id not found in {}", p.as_ref().display()))?
|
||||
.and_then(|hdr| hdr.get_value().map_err(Error::from))
|
||||
.map(strip_message_delimiters)
|
||||
}
|
||||
|
||||
/// Strips message delimiters ('<' and '>') from a Message-ID field.
|
||||
pub(crate) fn strip_message_delimiters<ID: AsRef<str>>(id: ID) -> String {
|
||||
let len = id.as_ref().len();
|
||||
// We have to strip the '<' and '>' if there are any, because they do not belong to the
|
||||
// Message-Id at all
|
||||
id.as_ref()
|
||||
.chars()
|
||||
.enumerate()
|
||||
.filter(|(idx, chr)| !(*idx == 0 && *chr == '<' || *idx == len - 1 && *chr == '>'))
|
||||
.map(|tpl| tpl.1)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_mail_text_content<P: AsRef<Path>>(p: P) -> Result<String> {
|
||||
::mailparse::parse_mail(::std::fs::read_to_string(p.as_ref())?.as_bytes())
|
||||
.context(format_err!("Cannot parse Email {}", p.as_ref().display()))?
|
||||
.get_body()
|
||||
.map_err(Error::from)
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
37
lib/entry/libimagentryref/src/util.rs
Normal file
37
lib/entry/libimagentryref/src/util.rs
Normal file
|
@ -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))
|
||||
}
|
||||
|
||||
|
Loading…
Reference in a new issue