// // imag - the personal information management suite for the commandline // Copyright (C) 2015-2020 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; extern crate resiter; extern crate handlebars; extern crate libimagrt; extern crate libimagmail; extern crate libimagerror; extern crate libimagstore; extern crate libimagutil; extern crate libimagentryref; extern crate libimaginteraction; use std::io::Write; use failure::Fallible as Result; use failure::err_msg; use clap::App; use resiter::AndThen; use resiter::IterInnerOkOrElse; use resiter::Filter; use resiter::Map; use handlebars::Handlebars; use libimagmail::store::MailStore; use libimagmail::store::Sorting; use libimagmail::mail::Mail; use libimagmail::store::MailStoreWithConnection; use libimagmail::mailtree::Mailtree; use libimagmail::notmuch::connection::NotmuchConnection; use libimagrt::runtime::Runtime; use libimagrt::application::ImagApplication; use libimagstore::iter::get::StoreIdGetIteratorExtension; use libimagstore::store::FileLockEntry; mod ui; mod util; /// Marker enum for implementing ImagApplication on /// /// This is used by binaries crates to execute business logic /// or to build a CLI completion. pub enum ImagMail {} impl ImagApplication for ImagMail { fn run(rt: Runtime) -> Result<()> { match rt.cli().subcommand_name().ok_or_else(|| err_msg("No subcommand called"))? { "import" => import(&rt), "list" => list(&rt), "print-id" => print_id(&rt), other => { debug!("Unknown command"); if rt.handle_unknown_subcommand("imag-mail", other, rt.cli())?.success() { Ok(()) } else { Err(err_msg("Failed to handle unknown subcommand")) } }, } } fn build_cli<'a>(app: App<'a, 'a>) -> App<'a, 'a> { ui::build_ui(app) } fn name() -> &'static str { env!("CARGO_PKG_NAME") } fn description() -> &'static str { "Mail collection tool" } fn version() -> &'static str { env!("CARGO_PKG_VERSION") } } fn import(rt: &Runtime) -> Result<()> { let scmd = rt.cli().subcommand_matches("import").unwrap(); let query = scmd.value_of("query").unwrap(); let store = rt.store(); let notmuch_path = crate::util::get_notmuch_database_path(rt)?; let notmuch_connection = NotmuchConnection::open(notmuch_path)?; debug!("Importing mail with query = '{}'", query); let r = store .with_connection(¬much_connection) .import_with_query(query)? .into_iter() .map(|fle| rt.report_touched(fle.get_location())) .collect::>>() .map(|_| ()); r } fn list(rt: &Runtime) -> Result<()> { debug!("Listing mail"); let scmd = rt.cli().subcommand_matches("list").unwrap(); let store = rt.store(); let list_format = util::get_mail_print_format("mail.list_format", rt, &scmd)?; debug!("List-format: {:?}", list_format); let notmuch_path = crate::util::get_notmuch_database_path(rt)?; debug!("notmuch path: {:?}", notmuch_path); let notmuch_connection = NotmuchConnection::open(notmuch_path)?; let tree = scmd.is_present("tree"); debug!("tree: {}", tree); let find_root = scmd.is_present("find-root"); debug!("find_root: {}", find_root); let mut out = rt.stdout(); let mut i = 0; /// Helper function for processing a tree /// /// This function implements the normal-style printing functionality (without "--tree"). fn process_nontree<'a, I>(iter: I, i: &mut usize, conn: &NotmuchConnection, list_format: &Handlebars, rt: &Runtime, out: &mut dyn Write) -> Result<()> where I: Iterator>> { debug!("Processing non-tree"); iter.and_then_ok(|fle| { trace!("Loading: {}", fle.get_location()); let loaded = fle.load(&conn)? .ok_or_else(|| format_err!("Mail not found: {}", fle.get_location()))?; trace!("Loaded: {}", fle.get_location()); let parsed = loaded.parsed()?; trace!("Parsed: {}", fle.get_location()); let r = crate::util::list_mail(&parsed, *i, 0, list_format, out); // indentation hard coded to zero trace!("Listed: {}", fle.get_location()); *i += 1; // poor mans enumerate() r.map(|_| fle) }) .and_then_ok(|fle| rt.report_touched(fle.get_location())) .collect::>>() .map(|_| ()) }; /// Helper function for processing a tree /// /// This function implements the tree-style printing functionality ("--tree"). fn process_tree<'a, I>(iter: I, mailstore: &'a MailStoreWithConnection<'a>, i: &mut usize, conn: &NotmuchConnection, list_format: &Handlebars, rt: &Runtime, out: &mut dyn Write, find_root: bool) -> Result<()> where I: Iterator>> { debug!("Processing tree"); let iter = if find_root { let iter = iter.and_then_ok(|fle| { trace!("Loading: {}", fle.get_location()); let id = fle .load(conn)? .ok_or_else(|| format_err!("Cannot load mail: {}", fle.get_location()))? .parsed()? .root_parent(&mailstore)? .ok_or_else(|| format_err!("Failed to find root parent: {}", fle.get_location())) .map(|p| p.get_id().clone()); drop(fle); id }); Box::new(iter) as Box>> } else { let iter = iter.and_then_ok(|fle| fle.get_cached_id()); Box::new(iter) as Box>> }; trace!("Printing mailtrees now!"); // we have to collect here, so that all FLEs are drop()ed iter.collect::>>()? .into_iter() .map(|id: String| mailstore.get_mailtree(&id)) .and_then_ok(|mt| print_traverse(&mailstore, mt, i, conn, list_format, rt, out)) .collect::>>() .map(|_| ()) } if let Some(query) = scmd.value_of("query") { // Use notmuch to find mails debug!("Importing mail with query = '{}'", query); let mailstore = store.with_connection(¬much_connection); let iter = mailstore .build_query(query) .sorted(Sorting::OldestFirst) .execute()? .into_iter() .map(Ok); if tree { process_tree(iter, &mailstore, &mut i, ¬much_connection, &list_format, rt, &mut out, find_root) } else { process_nontree(iter, &mut i, ¬much_connection, &list_format, rt, &mut out) } } else { // use StoreIds to find mails let iter = rt.ids::()? .ok_or_else(|| err_msg("No ids supplied"))? .into_iter() .map(Ok) .into_get_iter(rt.store()) .map_inner_ok_or_else(|| err_msg("Did not find one entry")) .and_then_ok(|m| m.is_mail().map(|b| (b, m))) .filter_ok(|tpl| tpl.0) .map_ok(|tpl| tpl.1); if tree { let mailstore = store.with_connection(¬much_connection); process_tree(iter, &mailstore, &mut i, ¬much_connection, &list_format, rt, &mut out, find_root) } else { process_nontree(iter, &mut i, ¬much_connection, &list_format, rt, &mut out) } } } fn print_id(rt: &Runtime) -> Result<()> { rt.ids::()? .ok_or_else(|| err_msg("No ids supplied"))? .into_iter() .map(Ok) .into_get_iter(rt.store()) .map_inner_ok_or_else(|| err_msg("Did not find one entry")) .and_then_ok(|fle| { let id = fle.get_cached_id()?; writeln!(rt.stdout(), "{}", id)?; Ok(fle) }) .and_then_ok(|fle| rt.report_touched(fle.get_location())) .collect::>>() .map(|_| ()) } fn print_traverse<'a>(store: &'a MailStoreWithConnection<'a>, tree: Mailtree, i: &mut usize, conn: &NotmuchConnection, list_format: &Handlebars, rt: &Runtime, out: &mut dyn Write) -> Result<()> { trace!("Print-Traversing starting with: {:?}", tree.root()); for (indent, mid) in tree.traverse() { let fle = store.get_mail_by_id(&mid)?.ok_or_else(|| format_err!("Cannot find mail with id = {}", mid))?; trace!("Printing: {}", fle.get_location()); let loaded = fle.load(&conn)? .ok_or_else(|| format_err!("Mail not found: {}", fle.get_location()))?; trace!("Loaded: {}", fle.get_location()); let parsed = loaded.parsed()?; trace!("Parsed: {}", fle.get_location()); crate::util::list_mail(&parsed, *i, indent, list_format, out)?; trace!("Listed: {}", fle.get_location()); rt.report_touched(fle.get_location())?; *i += 1; // poor mans enumerate() } Ok(()) }