diff options
Diffstat (limited to 'lib/domain/libimagmail/src/mail.rs')
-rw-r--r-- | lib/domain/libimagmail/src/mail.rs | 357 |
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. } |