diff --git a/Cargo.toml b/Cargo.toml index 4276aff5..20f7d34f 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 bf3cb6c0..0c4b5987 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 63edf367..a9500beb 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 { - 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::(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 33dfc978..7d91385c 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 00000000..5faa653d --- /dev/null +++ b/bin/domain/imag-mail/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "imag-mail" +version = "0.10.0" +authors = ["Matthias Beyer "] + +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 00000000..764e9f33 --- /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 00000000..8f7418df --- /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 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) => "".to_owned(), + Err(e) => { + trace_error(&e); + "".to_owned() + }, + }; + + let from = match m.get_from(&refconfig) { + Ok(Some(f)) => f, + Ok(None) => "".to_owned(), + Err(e) => { + trace_error(&e); + "".to_owned() + }, + }; + + let to = match m.get_to(&refconfig) { + Ok(Some(f)) => f, + Ok(None) => "".to_owned(), + Err(e) => { + trace_error(&e); + "".to_owned() + }, + }; + + let subject = match m.get_subject(&refconfig) { + Ok(Some(f)) => f, + Ok(None) => "".to_owned(), + Err(e) => { + trace_error(&e); + "".to_owned() + }, + }; + + if print_content { + use libimagmail::hasher::MailHasher; + + let content = m.as_ref_with_hasher::() + .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 { + 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 00000000..54a89b63 --- /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 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 { + 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 00000000..fde0879f --- /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 b1c5478d..f1d3be91 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 00000000..09825f1e --- /dev/null +++ b/lib/domain/libimagmail/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "libimagmail" +version = "0.10.0" +authors = ["Matthias Beyer "] + +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 00000000..9aeb65d2 --- /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 00000000..6258e6e3 --- /dev/null +++ b/lib/domain/libimagmail/src/config.rs @@ -0,0 +1,136 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer 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, + fetchcommand : MailCommand, + postfetchcommand : Option, + sendcommand : MailCommand, + postsendcommand : Option, +} + +impl MailConfig { + pub fn default_account(&self) -> &String { + &self.default_account + } + + pub fn accounts(&self) -> &Vec { + &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, + pub postfetchcommand : Option, + pub sendcommand : Option, + pub postsendcommand : Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct MailCommand { + command: String, + env: Vec, + args: Vec, +} + diff --git a/lib/domain/libimagmail/src/fetch.rs b/lib/domain/libimagmail/src/fetch.rs new file mode 100644 index 00000000..de4f4b0e --- /dev/null +++ b/lib/domain/libimagmail/src/fetch.rs @@ -0,0 +1,117 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer 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, + boxes: Vec, + + 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(mut self, names: I) -> Self + where I: IntoIterator + { + 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 00000000..270b1777 --- /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 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>(path: P) -> Result { + ::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 00000000..5bbeeb33 --- /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 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 00000000..b6429446 --- /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 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; + fn get_field(&self, refconfig: &RefConfig, field: &str) -> Result>; + fn get_from(&self, refconfig: &RefConfig) -> Result>; + fn get_to(&self, refconfig: &RefConfig) -> Result>; + fn get_subject(&self, refconfig: &RefConfig) -> Result>; + fn get_message_id(&self, refconfig: &RefConfig) -> Result>; + fn get_in_reply_to(&self, refconfig: &RefConfig) -> Result>; +} + +impl Mail for Entry { + + fn is_mail(&self) -> Result { + self.is::() + } + + /// Get a value of a single field of the mail file + fn get_field(&self, refconfig: &RefConfig, field: &str) -> Result> { + use std::fs::read_to_string; + use hasher::MailHasher; + + debug!("Getting field in mail: {:?}", field); + let mail_file_location = self.as_ref_with_hasher::().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> { + 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> { + 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> { + 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> { + 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> { + self.get_field(refconfig, "In-Reply-To") + } + +} + +#[derive(Debug)] +pub struct MailHeader<'a>(Vec<::mailparse::MailHeader<'a>>); + +impl<'a> From>> 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> { + 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> { + self.get_field("From") + } + + /// Get a value of the `To` field of the mail file + pub fn get_to(&self) -> Result> { + self.get_field("To") + } + + /// Get a value of the `Subject` field of the mail file + pub fn get_subject(&self) -> Result> { + self.get_field("Subject") + } + + /// Get a value of the `Message-ID` field of the mail file + pub fn get_message_id(&self) -> Result> { + 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> { + 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 00000000..ec732027 --- /dev/null +++ b/lib/domain/libimagmail/src/mid.rs @@ -0,0 +1,33 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer 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 for MessageId { + fn into(self) -> String { + self.0 + } +} + diff --git a/lib/domain/libimagmail/src/send.rs b/lib/domain/libimagmail/src/send.rs new file mode 100644 index 00000000..9c4c5407 --- /dev/null +++ b/lib/domain/libimagmail/src/send.rs @@ -0,0 +1,111 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer 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, + + rescan_maildirs: bool, +} + +impl MailSender { + pub fn new(config: &MailConfig) -> Self { + MailSender { + config, + account_name_to_send_with: None, + rescan_maildirs: false + } + } + + pub fn send_account(mut self, name: String) -> Self { + self.account_name_to_send_with = Some(name); + self + } + + pub fn rescan_maildirs(mut self, b: bool) -> Self { + self.rescan_maildirs = b; + self + } + + pub fn run(&self, store: &Store) -> Result<()> { + let sendcommand = match self.account_name_to_send_with { + Some(name) => self.config.sendcommand_for_account(name), + None => self.confnig.sendcommand(), + }; + + let postsendcommand = match self.account_name_to_send_with { + Some(name) => self.config.postsendcommand_for_account(name), + None => self.confnig.sendcommand(), + }; + + let account = config + .account(self.account_name_to_send_with) + .ok_or_else(|| format_err!("Account '{}' does not exist", self.account_name_to_send_with))?; + + if sendcommand.contains(" ") { + // error on whitespace in command + } + + if postsendcommand.contains(" ") { + // error on whitespace in command + } + + // sendcommand + // + let outgoingbox = account + .outgoingbox + .to_str() + .ok_or_else(|| format_err!("Cannot use '{:?}' as outgoingbox", account.outgoingbox))?; + + let mut output = Command::new(sendcommand) + // TODO: Add argument support + // TODO: Add support for passing config variables + // TODO: Add support for passing environment + .arg(outgoingbox) + .wait_with_output() + .context("Mail sending")?; + + write!(rt.stdout(), "{}", output.stdout)?; + write!(rt.stderr(), "{}", output.stderr)?; + + // TODO: Move all files in outgoingbox to account.sentbox + + // postfetchcommand + + let output = Command::new(postsendcommand) + // TODO: Add argument support + // TODO: Add support for passing config variables + .wait_with_output() + .context("Post 'Mail sending' command")?; + + write!(rt.stdout(), "{}", output.stdout)?; + write!(rt.stderr(), "{}", output.stderr)?; + + if self.rescan_maildirs { + // scan + // account.maildirroot + // recursively for new mail and store them in imag + } + } + +} + + diff --git a/lib/domain/libimagmail/src/store.rs b/lib/domain/libimagmail/src/store.rs new file mode 100644 index 00000000..6c672d9e --- /dev/null +++ b/lib/domain/libimagmail/src/store.rs @@ -0,0 +1,151 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer 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(&'a self, p: P, collection_name: CollName, config: &Config) + -> Result> + where P: AsRef + Debug, + CollName: AsRef + Debug; + + fn get_mail_from_path

(&'a self, p: P) + -> Result>> + where P: AsRef + Debug; + + fn retrieve_mail_from_path(&'a self, p: P, collection_name: CollName, config: &Config) + -> Result> + where P: AsRef + Debug, + CollName: AsRef + Debug; + + fn get_mail(&'a self, mid: MessageId) -> Result>>; + fn all_mails(&'a self) -> Result>; +} + +impl<'a> MailStore<'a> for Store { + + fn create_mail_from_path(&'a self, p: P, collection_name: CollName, config: &Config) + -> Result> + where P: AsRef + Debug, + CollName: AsRef + 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::() + .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

(&'a self, p: P) + -> Result>> + where P: AsRef + 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::().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(&'a self, p: P, collection_name: CollName, config: &Config) + -> Result> + where P: AsRef + Debug, + CollName: AsRef + 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::() + .make_ref(p, collection_name, config, false)?; + + Ok(entry) + } + + fn get_mail(&'a self, mid: MessageId) -> Result>> { + 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> { + 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 00000000..8b51a9f8 --- /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 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: P) -> Result { + ::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: 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: P) -> Result { + ::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 60692acd..ccec9afa 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 4d8e02e8..95c40b20 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 00000000..1daf2e75 --- /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 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 { + 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::(setting_name)? + .ok_or_else(|| format_err!("Setting missing: {}", setting_name)) +} + +