// // 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 anyhow; extern crate toml_query; extern crate resiter; extern crate handlebars; extern crate itertools; 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 std::io::BufRead; use failure::Fallible as Result; use failure::err_msg; use failure::Error; 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::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(anyhow!("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 notmuch_path = crate::util::get_notmuch_database_path(rt)?; debug!("notmuch path: {:?}", notmuch_path); let list_format = util::get_mail_print_format("mail.list_format", rt, &scmd)?; debug!("List-format: {:?}", list_format); let notmuch_connection = NotmuchConnection::open(notmuch_path)?; 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<'a, I>(iter: I, i: &mut usize, conn: &NotmuchConnection, list_format: &Handlebars, rt: &Runtime, out: &mut dyn Write) -> Result<()> where I: Iterator>> { 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(|_| ()) }; 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); process(iter, &mut i, ¬much_connection, &list_format, rt, &mut out) } else { if scmd.is_present("query-from-stdin") { let stdin = ::std::io::stdin(); let mailstore = store.with_connection(¬much_connection); stdin.lock() .lines() .map(|r| r.map_err(Error::from).and_then(|query| { debug!("Querying: {}", query); let it = mailstore.query(&query)?; debug!("Found: {:?}", it); let it = it.into_iter().map(Ok); process(it, &mut i, ¬much_connection, &list_format, rt, &mut out) })) .collect::>() } 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); process(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(|_| ()) }