summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2020-02-28 11:46:05 +0100
committerMatthias Beyer <mail@beyermatthias.de>2020-06-01 13:56:45 +0200
commit23f25cf563db94d83bed4076b8ddcf6acb3ded7c (patch)
tree3f51a40b365fa33254bcd2cfceda437e1ab02773
parent35a8bfdaa60e1bc449648171d7f18b253f206a26 (diff)
libimagmail: Rewrite backend to use notmuch
This commit rewrites libimagmail to use notmuch as backend for mail operations. Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r--doc/src/05100-lib-mails.md16
-rw-r--r--lib/domain/libimagmail/Cargo.toml7
-rw-r--r--lib/domain/libimagmail/src/config.rs131
-rw-r--r--lib/domain/libimagmail/src/fetch.rs117
-rw-r--r--lib/domain/libimagmail/src/iter.rs83
-rw-r--r--lib/domain/libimagmail/src/lib.rs17
-rw-r--r--lib/domain/libimagmail/src/mail.rs525
-rw-r--r--lib/domain/libimagmail/src/mailflags.rs93
-rw-r--r--lib/domain/libimagmail/src/mailtree.rs245
-rw-r--r--lib/domain/libimagmail/src/notmuch/connection.rs109
-rw-r--r--lib/domain/libimagmail/src/notmuch/mod.rs (renamed from lib/domain/libimagmail/src/mid.rs)42
-rw-r--r--lib/domain/libimagmail/src/notmuch/thread.rs (renamed from lib/domain/libimagmail/src/hasher.rs)40
-rw-r--r--lib/domain/libimagmail/src/send.rs111
-rw-r--r--lib/domain/libimagmail/src/store.rs306
-rw-r--r--lib/domain/libimagmail/src/util.rs77
15 files changed, 808 insertions, 1111 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 ad6c2a90..22bd5eba 100644
--- a/lib/domain/libimagmail/Cargo.toml
+++ b/lib/domain/libimagmail/Cargo.toml
@@ -27,8 +27,11 @@ mailparse = "0.8.0"
filters = "0.3.0"
anyhow = "1"
resiter = "0.4.0"
-serde = "1.0.94"
-serde_derive = "1.0.94"
+notmuch = "0.6"
+chrono = "0.4"
+indextree = "4"
+itertools = "0.8"
+mda = { git = "https://github.com/matthiasbeyer/mda-rs", branch = "my-master" } # "0.1"
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 72f5d32c..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(|| anyhow!("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 f1bb11a0..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 anyhow::Result;
-use anyhow::Context;
-use anyhow::Error;
-
-
-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(anyhow!("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 65ea418c..f30c5e7c 100644
--- a/lib/domain/libimagmail/src/lib.rs
+++ b/lib/domain/libimagmail/src/lib.rs
@@ -38,14 +38,16 @@
)]
#[macro_use] extern crate log;
-extern crate mailparse;
extern crate toml;
extern crate toml_query;
extern crate filters;
#[macro_use] extern crate anyhow;
extern crate resiter;
-extern crate serde;
-#[macro_use] extern crate serde_derive;
+extern crate chrono;
+extern crate notmuch as notmuch_rs;
+extern crate mda;
+extern crate indextree;
+extern crate itertools;
extern crate libimagerror;
#[macro_use] extern crate libimagstore;
@@ -55,12 +57,9 @@ 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 mailtree;
pub mod store;
-pub mod util;
+mod util;
diff --git a/lib/domain/libimagmail/src/mail.rs b/lib/domain/libimagmail/src/mail.rs
index d26346dd..1f001aab 100644
--- a/lib/domain/libimagmail/src/mail.rs
+++ b/lib/domain/libimagmail/src/mail.rs
@@ -17,371 +17,300 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
-use std::str::FromStr;
-
-use anyhow::Result;
-use anyhow::Context;
-use anyhow::Error;
-use toml_query::read::TomlValueReadExt;
-use resiter::Filter;
+use std::path::PathBuf;
+use std::fs::OpenOptions;
+use std::io::Read;
+use std::fmt::{Debug, Result as FmtResult, Formatter};
+use std::ops::Deref;
+
+use failure::Fallible as Result;
+use failure::Error;
+use toml_query::read::TomlValueReadTypeExt;
+use chrono::NaiveDateTime;
+use mda::Email;
use libimagstore::store::Entry;
-use libimagentryutil::isa::Is;
+use libimagstore::store::FileLockEntry;
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 crate::mid::MessageId;
-use crate::mailflags::MailFlag;
-use crate::hasher::MailHasher;
-use crate::iter::MailIterator;
-use crate::iter::IntoMailIterator;
+use libimagentryutil::isa::Is;
+
+use crate::notmuch::connection::NotmuchConnection;
+use crate::notmuch::thread::Thread;
+use crate::store::MailStoreWithConnection;
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>;
+ fn get_cached_id(&self) -> Result<String>;
+
+ fn load(&self, connection: &NotmuchConnection) -> Result<Option<LoadedMail>>;
}
impl Mail for Entry {
-
fn is_mail(&self) -> Result<bool> {
- self.is::<IsMail>()
+ self.is::<IsMail>().map_err(Error::from)
}
- /// 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;
+ fn get_cached_id(&self) -> Result<String> {
+ self.get_header()
+ .read_string("mail.id")?
+ .ok_or_else(|| format_err!("Cached ID missing for: {}", self.get_location()))
+ }
- debug!("Getting field in mail: {:?}", field);
- let mail_file_location = self.as_ref_with_hasher::<MailHasher>().get_path(refconfig)?;
+ fn load(&self, connection: &NotmuchConnection) -> Result<Option<LoadedMail>> {
+ LoadedMail::load(self, connection)
+ }
+}
- match ::mailparse::parse_mail(read_to_string(mail_file_location.as_path())?.as_bytes())
- .context(anyhow!("Cannot parse Email {}", mail_file_location.display()))?
- .headers
- .into_iter()
- .filter_map(|hdr| {
- match hdr.get_key()
- .context(anyhow!("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)),
+#[derive(Debug)]
+pub struct LoadedMail {
+ id: String,
+ thread_id: String,
+ files: i32,
+ filename: PathBuf,
+ filenames: Vec<PathBuf>,
+ date: NaiveDateTime,
+ tags: Vec<String>,
+}
+
+impl LoadedMail {
+
+ pub fn load(entry: &Entry, conn: &NotmuchConnection) -> Result<Option<Self>> {
+ let id = entry
+ .get_header()
+ .read_string("mail.id")?
+ .ok_or_else(|| format_err!("Missing header: 'mail.id' in {}", entry.get_location()))?;
+
+ trace!("Loading {} with id: {}", entry.get_location(), id);
+ conn.execute(|db| {
+ if let Some(msg) = db.find_message(&id)? {
+ let id = msg.id().into_owned();
+ let thread_id = msg.thread_id().into_owned();
+ let files = msg.count_files();
+ let filenames = msg.filenames().collect();
+ let filename = msg.filename();
+ let tags = msg.tags().collect();
+ let date = NaiveDateTime::from_timestamp_opt(msg.date(), 0)
+ .ok_or_else(|| format_err!("Invalid timestamp: {}", msg.date()))?;
+
+ let lm = LoadedMail {
+ id,
+ thread_id,
+ files,
+ filename,
+ filenames,
+ date,
+ tags,
+ };
+
+ trace!("Loaded {:?}", lm);
+ Ok(Some(lm))
+ } else {
+ trace!("Loaded None");
+ Ok(None)
}
})
- .next()
- {
- None => Ok(None),
- Some(Err(e)) => Err(e),
- Some(Ok(hdr)) => Ok(Some(hdr.get_value()?))
- }
+
}
- /// Get a value of the `From` field of the mail file
- ///
- /// # Note
- ///
- /// Use `Mail::mail_header()` if you need to read more than one field.
- fn get_from(&self, refconfig: &RefConfig) -> Result<Option<String>> {
- self.get_field(refconfig, "From")
- }
-
- /// Get a value of the `To` field of the mail file
- ///
- /// # Note
- ///
- /// Use `Mail::mail_header()` if you need to read more than one field.
- fn get_to(&self, refconfig: &RefConfig) -> Result<Option<String>> {
- self.get_field(refconfig, "To")
- }
-
- /// Get a value of the `Subject` field of the mail file
- ///
- /// # Note
- ///
- /// Use `Mail::mail_header()` if you need to read more than one field.
- fn get_subject(&self, refconfig: &RefConfig) -> Result<Option<String>> {
- self.get_field(refconfig, "Subject")
- }
-
- /// Get a value of the `Message-ID` field of the mail file
- ///
- /// # Note
- ///
- /// Use `Mail::mail_header()` if you need to read more than one field.
- fn get_message_id(&self, refconfig: &RefConfig) -> Result<Option<MessageId>> {
- if let Some(s) = self.get_header().read("mail.message-id")? {
- let s = s.as_str()
- .ok_or_else(|| anyhow!("'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))
- }
+ pub fn load_entry<'a>(&self, store: &'a MailStoreWithConnection<'a>) -> Result<Option<FileLockEntry<'a>>> {
+ store.get_mail_by_id(self.get_id())
}
- /// 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))
+ pub fn get_id(&self) -> &String {
+ &self.id
}
- /// 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)?;
+ pub fn get_thread_id(&self) -> &String {
+ &self.thread_id
+ }
- if !path.exists() {
- return Err(anyhow!("Path {} does not exist", path.display()))
- }
+ pub fn get_files(&self) -> &i32 {
+ &self.files
+ }
- {
- // Now parse mail flags
- path.to_str()
- .ok_or_else(|| anyhow!("Path is not UTF-8: {}", path.display()))?
- .split("2,")
- .map(String::from)
- .collect::<Vec<String>>()
- .split_last()
- .ok_or_else(|| anyhow!("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<_>>>()
- }
+ pub fn get_filename(&self) -> &PathBuf {
+ &self.filename
}
- /// 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))
+ pub fn get_filenames(&self) -> &Vec<PathBuf> {
+ &self.filenames
}
- /// 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))
+ pub fn get_date(&self) -> &NaiveDateTime {
+ &self.date
}
- /// 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))
+ pub fn get_tags(&self) -> &Vec<String> {
+ &self.tags
}
- /// 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))
+
+ // More convenience functionality
+
+ pub fn threads(&self, conn: &NotmuchConnection) -> Result<Vec<Thread>> {
+ let id = self.get_id();
+ conn.execute(|db| {
+ db.create_query(&format!("thread:{}", id))?
+ .search_threads()
+ .map_err(Error::from)
+ .map(|it| it.map(Thread::from).collect())
+ })
}
- /// 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))
+ pub fn replies(&self, conn: &NotmuchConnection) -> Result<Vec<String>> {
+ let id = self.get_id();
+ conn.execute(|db| {
+ db.find_message(id)?
+ .ok_or_else(|| format_err!("Cannot find message with id {}", id))?
+ .replies()
+ .map(|msg| Ok(msg.id().into_owned()))
+ .collect()
+ })
}
- /// 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))
+ // Parsing the actual mail file for further processing
+ pub fn parsed(self) -> Result<ParsedMail> {
+ let mut buffer = Vec::with_capacity(4096);
+ OpenOptions::new()
+ .read(true)
+ .open(self.get_filename())?
+ .read_to_end(&mut buffer)?;
+
+ Ok(ParsedMail {
+ loaded: self,
+ parsed: Email::from_vec(buffer).map_err(|e| format_err!("Parser error: {}", e.description()))?,
+ })
}
+}
- /// 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);
+pub struct ParsedMail {
+ loaded: LoadedMail,
+ parsed: Email,
+}
- Ok(StoreIdIterator::new(Box::new(iter)))
- }
+impl Deref for ParsedMail {
+ type Target = LoadedMail;
- /// 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()
- })
+ fn deref(&self) -> &Self::Target {
+ &self.loaded
}
+}
- /// 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()
- }
+impl Debug for ParsedMail {
+ fn fmt(&self, f: &mut Formatter) -> FmtResult {
+ write!(f, "ParsedMail {{ {:?}, ... }}", self.loaded)
+ }
+}
- // 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);
+impl ParsedMail {
- // Get the FileLockEntry for the StoreId, or fail if it cannot be found
- let next_entry = store.get(n.clone())?.ok_or_else(|| anyhow!("Cannot find {}", n))?;
+ pub fn get_keys(&self) -> Result<Vec<String>> {
+ Ok(self.parsed.header_keys().into_iter().map(ToOwned::to_owned).collect())
+ }
- // if the FileLockEntry is a Mail
- if next_entry.is_mail()? {
-