diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2020-01-05 13:49:25 +0100 |
---|---|---|
committer | Matthias Beyer <mail@beyermatthias.de> | 2020-01-05 16:11:13 +0100 |
commit | 24e21a5b9c2ee3bd2b3ac72644e16fb1e937967c (patch) | |
tree | d33ea6de22d8f96838da86dc96c8acc33cb865ed | |
parent | 93d3bda837122bfc7a05ec20690bac2b49fbaf15 (diff) |
Start rewriting backend to use notmuch
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r-- | doc/src/05100-lib-mails.md | 16 | ||||
-rw-r--r-- | lib/domain/libimagmail/Cargo.toml | 3 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/config.rs | 131 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/fetch.rs | 117 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/iter.rs | 83 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/lib.rs | 11 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/mail.rs | 357 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/mailflags.rs | 93 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/mid.rs | 59 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/notmuch/connection.rs | 77 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/notmuch/mod.rs (renamed from lib/domain/libimagmail/src/hasher.rs) | 25 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/send.rs | 111 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/store.rs | 211 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/util.rs | 96 |
14 files changed, 176 insertions, 1214 deletions
diff --git a/doc/src/05100-lib-mails.md b/doc/src/05100-lib-mails.md index 9a42a47a..b108f364 100644 --- a/doc/src/05100-lib-mails.md +++ b/doc/src/05100-lib-mails.md @@ -1,14 +1,10 @@ -## libimagmails +## libimagmail -The mail library implements everything that is needed for being used to -implement a mail reader (MUA). +This library implements an integration from notmuch for imag. -It therefor provides reading mailboxes, getting related content or mails, saving -attachments to external locations, crafting new mails and responses,... +The library does not (yet) provide functionality to alter the database of +notmuch and maybe never will. -It also offers, natively, ways to search for mails (which are represented as -imag entries). - -For more information on the domain of the `imag-mail` command, look at the -documentation of the @sec:modules:mails module. +It creates one imag entry for one notmuch message id. It only stores the message +id in the entry, all other data can be fetched from notmuch at runtime. diff --git a/lib/domain/libimagmail/Cargo.toml b/lib/domain/libimagmail/Cargo.toml index e0b1a891..daedf129 100644 --- a/lib/domain/libimagmail/Cargo.toml +++ b/lib/domain/libimagmail/Cargo.toml @@ -27,8 +27,7 @@ mailparse = "0.8.0" filters = "0.3.0" failure = "0.1.5" resiter = "0.4.0" -serde = "1.0.94" -serde_derive = "1.0.94" +notmuch = "0.6" libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } diff --git a/lib/domain/libimagmail/src/config.rs b/lib/domain/libimagmail/src/config.rs deleted file mode 100644 index 4c59cb87..00000000 --- a/lib/domain/libimagmail/src/config.rs +++ /dev/null @@ -1,131 +0,0 @@ -// -// imag - the personal information management suite for the commandline -// Copyright (C) 2015-2020 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() - .find(|a| a.name == name) - } - - 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() - .find(|a| a.name == account_name) - .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() - .find(|a| a.name == account_name) - .and_then(|a| a.postfetchcommand.as_ref()) - .or_else(|| self.postfetchcommand()) - } - - pub fn sendcommand_for_account(&self, account_name: &str) -> &MailCommand { - self.accounts() - .iter() - .find(|a| a.name == account_name) - .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() - .find(|a| a.name == account_name) - .and_then(|a| a.postsendcommand.as_ref()) - .or_else(|| self.postsendcommand()) - } - -} - -/// A configuration for a single mail accounts -/// -/// If one of the keys `fetchcommand`, `postfetchcommand`, `sendcommand` or `postsendcommand` is -/// not available, the implementation of the `MailConfig` will automatically use the global -/// configuration if applicable. -#[derive(Serialize, Deserialize, Debug)] -pub struct MailAccountConfig { - pub name : String, - pub outgoingbox : PathBuf, - pub draftbox : PathBuf, - pub sentbox : PathBuf, - pub maildirroot : PathBuf, - pub fetchcommand : Option<MailCommand>, - pub postfetchcommand : Option<MailCommand>, - pub sendcommand : Option<MailCommand>, - pub postsendcommand : Option<MailCommand>, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct MailCommand { - command: String, - env: Vec<String>, - args: Vec<String>, -} - diff --git a/lib/domain/libimagmail/src/fetch.rs b/lib/domain/libimagmail/src/fetch.rs deleted file mode 100644 index fa7106d5..00000000 --- a/lib/domain/libimagmail/src/fetch.rs +++ /dev/null @@ -1,117 +0,0 @@ -// -// imag - the personal information management suite for the commandline -// Copyright (C) 2015-2020 Matthias Beyer <mail@beyermatthias.de> and contributors -// -// This library is free software; you can redistribute it and/or -// modify it under the terms of the GNU Lesser General Public -// License as published by the Free Software Foundation; version -// 2.1 of the License. -// -// This library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public -// License along with this library; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -// - -use config::MailConfig; - -pub struct MailFetcher<'a> { - config: &'a MailConfig, - account_name_to_fetch: Option<String>, - boxes: Vec<String>, - - rescan_maildirs: bool, -} - -impl MailFetcher { - pub fn new(config: &MailConfig) -> Self { - MailFetcher { - config, - account_name_to_fetch: None, - rescan_maildirs: false - } - } - - pub fn fetch_account(mut self, name: String) -> Self { - self.account_name_to_fetch = Some(name); - self - } - - pub fn fetch_box(mut self, name: String) -> Self { - self.boxes.push(name); - self - } - - pub fn fetch_boxes<I>(mut self, names: I) -> Self - where I: IntoIterator<Item = String> - { - self.boxes.append(names.into_iter().collect()) - self - } - - pub fn rescan_maildirs(mut self, b: bool) -> Self { - self.rescan_maildirs = b; - self - } - - pub fn run(&self, store: &Store) -> Result<()> { - let fetchcommand = match self.account_name_to_fetch { - Some(name) => self.config.fetchcommand_for_account(name), - None => self.confnig.fetchcommand(), - }; - - let postfetchcommand = match self.account_name_to_fetch { - Some(name) => self.config.postfetchcommand_for_account(name), - None => self.confnig.postfetchcommand(), - }; - - let account = config - .account(self.account_name_to_fetch) - .ok_or_else(|| format_err!("Account '{}' does not exist", self.account_name_to_fetch))?; - - if fetchcommand.contains(" ") { - // error on whitespace in command - } - - if postfetchcommand.contains(" ") { - // error on whitespace in command - } - - // fetchcommand - - let mut output = Command::new(fetchcommand) - // TODO: Add argument support - // TODO: Add support for passing config variables - // TODO: Add support for passing environment - .args(self.boxes) - .wait_with_output() - .context("Mail fetching")?; - - write!(rt.stdout(), "{}", output.stdout)?; - write!(rt.stderr(), "{}", output.stderr)?; - - // postfetchcommand - - let output = Command::new(postfetchcommand) - // TODO: Add argument support - // TODO: Add support for passing config variables - .wait_with_output() - .context("Post 'Mail fetching' command")?; - - write!(rt.stdout(), "{}", output.stdout)?; - write!(rt.stderr(), "{}", output.stderr)?; - - if self.rescan_maildirs { - // scan - // account.maildirroot - // recursively for new mail and store them in imag - } - } - -} - - diff --git a/lib/domain/libimagmail/src/iter.rs b/lib/domain/libimagmail/src/iter.rs deleted file mode 100644 index bba6b690..00000000 --- a/lib/domain/libimagmail/src/iter.rs +++ /dev/null @@ -1,83 +0,0 @@ -// -// imag - the personal information management suite for the commandline -// Copyright (C) 2015-2020 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 failure::err_msg; - -use libimagstore::iter::get::StoreGetIterator; -use libimagstore::store::FileLockEntry; - -use crate::mail::Mail; - -pub struct MailIterator<'a> { - inner: StoreGetIterator<'a>, - ignore_ungetable: bool, -} - -impl<'a> MailIterator<'a> { - pub fn new(sgi: StoreGetIterator<'a>) -> Self { - MailIterator { inner: sgi, ignore_ungetable: true } - } - - pub fn ignore_ungetable(mut self, b: bool) -> Self { - self.ignore_ungetable = b; - self - } -} - -pub trait IntoMailIterator<'a> { - fn into_mail_iterator(self) -> MailIterator<'a>; -} - -impl<'a> IntoMailIterator<'a> for StoreGetIterator<'a> { - fn into_mail_iterator(self) -> MailIterator<'a> { - MailIterator::new(self) - } -} - -impl<'a> Iterator for MailIterator<'a> { - type Item = Result<FileLockEntry<'a>>; - - fn next(&mut self) -> Option<Self::Item> { - while let Some(n) = self.inner.next() { - match n { - Ok(Some(fle)) => { - match fle.is_mail().context("Checking whether entry is a Mail").map_err(Error::from) { - Err(e) => return Some(Err(e)), - Ok(true) => return Some(Ok(fle)), - Ok(false) => continue, - } - }, - - Ok(None) => if self.ignore_ungetable { - continue - } else { - return Some(Err(err_msg("Failed to get one entry"))) - }, - - Err(e) => return Some(Err(e)), - } - } - - None - } -} - diff --git a/lib/domain/libimagmail/src/lib.rs b/lib/domain/libimagmail/src/lib.rs index 913acfc9..010ec10c 100644 --- a/lib/domain/libimagmail/src/lib.rs +++ b/lib/domain/libimagmail/src/lib.rs @@ -44,8 +44,8 @@ extern crate toml_query; extern crate filters; #[macro_use] extern crate failure; extern crate resiter; -extern crate serde; -#[macro_use] extern crate serde_derive; +extern crate chrono; +extern crate notmuch as notmuch_rs; extern crate libimagerror; #[macro_use] extern crate libimagstore; @@ -55,12 +55,7 @@ extern crate libimagentrylink; module_entry_path_mod!("mail"); -pub mod config; -pub mod hasher; -pub mod iter; +pub mod notmuch; pub mod mail; -pub mod mailflags; -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 index f24f5506..dd2b93db 100644 --- a/lib/domain/libimagmail/src/mail.rs +++ b/lib/domain/libimagmail/src/mail.rs @@ -17,371 +17,22 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // -use std::str::FromStr; - use failure::Fallible as Result; -use failure::ResultExt; use failure::Error; -use toml_query::read::TomlValueReadExt; -use resiter::Filter; use libimagstore::store::Entry; -use libimagentryutil::isa::Is; use libimagentryutil::isa::IsKindHeaderPathProvider; -use libimagentryref::reference::Config as RefConfig; -use libimagentryref::reference::{Ref, RefFassade}; -use libimagentrylink::linkable::Linkable; -use libimagstore::store::Store; -use libimagstore::storeid::StoreId; -use libimagstore::storeid::StoreIdIterator; -use libimagstore::iter::get::StoreIdGetIteratorExtension; +use libimagentryutil::isa::Is; -use crate::mid::MessageId; -use crate::mailflags::MailFlag; -use crate::hasher::MailHasher; -use crate::iter::MailIterator; -use crate::iter::IntoMailIterator; provide_kindflag_path!(pub IsMail, "mail.is_mail"); -pub trait Mail : RefFassade + Linkable { - 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<MessageId>>; - fn get_in_reply_to(&self, refconfig: &RefConfig) -> Result<Option<MessageId>>; - - fn flags(&self, refconfig: &RefConfig) -> Result<Vec<MailFlag>>; - fn is_passed(&self, refconfig: &RefConfig) -> Result<bool>; - fn is_replied(&self, refconfig: &RefConfig) -> Result<bool>; - fn is_seen(&self, refconfig: &RefConfig) -> Result<bool>; - fn is_trashed(&self, refconfig: &RefConfig) -> Result<bool>; - fn is_draft(&self, refconfig: &RefConfig) -> Result<bool>; - fn is_flagged(&self, refconfig: &RefConfig) -> Result<bool>; - - fn neighbors(&self) -> Result<StoreIdIterator>; - fn get_neighbors<'a>(&self, store: &'a Store) -> Result<MailIterator<'a>>; - fn get_thread<'a>(&self, store: &'a Store) -> Result<MailIterator<'a>>; - +pub trait Mail { + fn is_mail(&self) -> Result<bool>; } 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; - - 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() - .context(format_err!("Cannot fetch key '{}' from Email {}", field, mail_file_location.display())) - .map_err(Error::from) - { - Ok(k) => if k == field { - Some(Ok(hdr)) - } else { - None - }, - Err(e) => Some(Err(e)), - } - }) - .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<MessageId>> { - if let Some(s) = self.get_header().read("mail.message-id")? { - let s = s.as_str() - .ok_or_else(|| format_err!("'mail.message-id' is not a String in {}", self.get_location()))?; - Ok(Some(MessageId::from(String::from(s)))) - } else { - self.get_field(refconfig, "Message-ID") - .map(|o| o.map(crate::util::strip_message_delimiters).map(MessageId::from)) - } - } - - /// 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<MessageId>> { - self.get_field(refconfig, "In-Reply-To") - .map(|o| o.map(crate::util::strip_message_delimiters).map(MessageId::from)) - } - - /// Get the flags of the message - fn flags(&self, refconfig: &RefConfig) -> Result<Vec<MailFlag>> { - let path = self.as_ref_with_hasher::<MailHasher>().get_path(refconfig)?; - - if !path.exists() { - return Err(format_err!("Path {} does not exist", path.display())) - } - - { - // Now parse mail flags - path.to_str() - .ok_or_else(|| format_err!("Path is not UTF-8: {}", path.display()))? - .split("2,") - .map(String::from) - .collect::<Vec<String>>() - .split_last() - .ok_or_else(|| format_err!("Splitting path into prefix and flags failed: {}", path.display()))? - .0 - .chars() - .map(|c| c.to_string()) - .map(|c| MailFlag::from_str(&c)) - .collect::<Result<Vec<_>>>() - } - } - - /// Check whether the mail is passed - fn is_passed(&self, refconfig: &RefConfig) -> Result<bool> { - self.flags(refconfig).map(|fs| fs.into_iter().any(|f| MailFlag::Passed == f)) - } - - /// Check whether the mail is replied - fn is_replied(&self, refconfig: &RefConfig) -> Result<bool> { - self.flags(refconfig).map(|fs| fs.into_iter().any(|f| MailFlag::Replied == f)) - } - - /// Check whether the mail is seen - fn is_seen(&self, refconfig: &RefConfig) -> Result<bool> { - self.flags(refconfig).map(|fs| fs.into_iter().any(|f| MailFlag::Seen == f)) - } - - /// Check whether the mail is trashed - fn is_trashed(&self, refconfig: &RefConfig) -> Result<bool> { - self.flags(refconfig).map(|fs| fs.into_iter().any(|f| MailFlag::Trashed == f)) - } - - /// Check whether the mail is draft - fn is_draft(&self, refconfig: &RefConfig) -> Result<bool> { - self.flags(refconfig).map(|fs| fs.into_iter().any(|f| MailFlag::Draft == f)) - } - - /// Check whether the mail is flagged - fn is_flagged(&self, refconfig: &RefConfig) -> Result<bool> { - self.flags(refconfig).map(|fs| fs.into_iter().any(|f| MailFlag::Flagged == f)) - } - - /// Get all direct neighbors for the Mail - /// - /// # Note - /// - /// This fetches only the neighbors which are linked. So it basically only checks the entries - /// which this entry is linked to and filters them for Mail::is_mail() - /// - /// # Warning - /// - /// Might yield store entries which are not a Mail in the Mail::is_mail() sence but are simply - /// stored in /mail in the store. - /// - /// To be sure, you should filter this iterator after getting the FileLockEntries from Store. - /// Or use `Mail::get_neighbors(&store)`. - /// - fn neighbors(&self) -> Result<StoreIdIterator> { - let iter = self - .links()? - .map(|link| link.into()) - .filter(|id: &StoreId| id.is_in_collection(&["mail"])) - .map(Ok); - - Ok(StoreIdIterator::new(Box::new(iter))) - } - - /// Get alldirect neighbors for the Mail (as FileLockEntry) - /// - /// # See also - /// - /// Documentation of `Mail::neighbors()`. - fn get_neighbors<'a>(&self, store: &'a Store) -> Result<MailIterator<'a>> { - self.links() - .map(|iter| { - iter.map(|link| link.into()) - .map(Ok) - .into_get_iter(store) - .into_mail_iterator() - }) - } - - /// Get the full thread starting from this Mail - /// - /// This function recursively traverses the linked mails, assumes them all to be in the same - /// thread and returns an iterator over all Mails it finds in this way. - /// - /// # Warning - /// - /// If a Mail is linked to this mail (even transitively!) but is _not_ in the same thread, it - /// is considered to be in the same thread. - /// - /// This function works recursively. Keep that in mind for large threads. Because it needs to - /// collect() internally, it might take a lot of memory for large threads. - /// - /// # Return value - /// - /// This function returns an Iterator over StoreIds in the same thread as this mail itself. - /// It does not yield any qualification about the distance between a mail in this thread and - /// this very mail. - /// - fn get_thread<'a>(&self, store: &'a Store) -> Result<MailIterator<'a>> { - trace!("Getting thread, starting point at: {}", self.get_location()); - let mut thread = vec![self.get_location().clone()]; - - fn traverse<'a>(entry: &'a Entry, thread: &mut Vec<StoreId>, store: &Store) -> Result<()> { - // Helper function to get neighbors of a Mail, but filtered - fn get_filtered_neighbors<'a>(entry: &'a Entry, skiplist: &[StoreId]) -> Result<Vec<StoreId>> { - trace!("Getting filtered neighbors of {}", entry.get_location()); - entry.neighbors()?.filter_ok(|id| !skiplist.contains(id)).collect() - } - - // Get the neighbors, filtered by StoreIds which are already in the thread - // Then iterate over them - for n in get_filtered_neighbors(entry, thread)? { - trace!("Fetching {}", n); - - // Get the FileLockEntry for the StoreId, or fail if it cannot be found - let next_entry = store.get(n.clone())?.ok_or_else(|| format_err!("Cannot find {}", n))?; - - // if the FileLockEntry is a Mail - if next_entry.is_mail()? { - trace!("{} is a Mail", n); - thread.push(n); // it belongs to the thread - - // And then traverse further starting from the current Mail - traverse(&next_entry, thread, store)?; - } - } - - Ok(()) - } - - trace!("Starting traversing..."); - traverse(self, &mut thread, store)?; - trace!("Finished traversing."); - trace!("Found {} entries in thread", thread.len()); - - let iter = StoreIdIterator::new(Box::new(thread.into_iter().map(Ok))) - .into_get_iter(store) - .into_mail_iterator(); - - Ok(iter) - } - -} - -#[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() - .context(format_err!("Cannot get field {}", field)) - .map_err(Error::from) - { - Ok(key) => if key == field { - Some(Ok(hdr)) - } else { - None - }, - Err(e) => Some(Err(e)) - } - }) - .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") + self.is::<IsMail>().map_err(Error::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<MessageId>> { - self.get_field("In-Reply-To") - .map(|o| o.map(crate::util::strip_message_delimiters).map(MessageId::from)) - } - - // 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/mailflags.rs b/lib/domain/libimagmail/src/mailflags.rs deleted file mode 100644 index 5fd52761..00000000 --- a/lib/domain/libimagmail/src/mailflags.rs +++ /dev/null @@ -1,93 +0,0 @@ -// -// imag - the personal information management suite for the commandline -// Copyright (C) 2015-2020 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::fmt::{Display, Result as FmtResult, Formatter}; -use std::str::FromStr; - -use failure::Fallible as Result; -use failure::Error; - -/// Message flags -/// -/// As defined by https://cr.yp.to/proto/maildir.html with strong typing -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum MailFlag { - /// Flag "P" (passed): the user has resent/forwarded/bounced this message to someone else. - Passed, - - /// Flag "R" (replied): the user has replied to this message. - Replied, - - /// Flag "S" (seen): the user has viewed this message, though perhaps he didn't read all the way through it. - Seen, - - /// Flag "T" (trashed): the user has moved this message to the trash; the trash will be emptied by a later user action. - Trashed, - - /// Flag "D" (draft): the user considers this message a draft; toggled at user discretion. - Draft, - - /// Flag "F" (flagged): user-defined flag; toggled at user discretion. - Flagged, -} - -impl MailFlag { - pub fn as_char(self) -> char { - match self { - MailFlag::Passed => 'P', - MailFlag::Replied => 'R', - MailFlag::Seen => 'S', - MailFlag::Trashed => 'T', - MailFlag::Draft => 'D', - MailFlag::Flagged =& |