summaryrefslogtreecommitdiffstats
path: root/lib/domain/libimagmail/src/mail.rs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/domain/libimagmail/src/mail.rs')
-rw-r--r--lib/domain/libimagmail/src/mail.rs357
1 files changed, 4 insertions, 353 deletions
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.
}