diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2020-01-03 17:55:01 +0100 |
---|---|---|
committer | Matthias Beyer <mail@beyermatthias.de> | 2020-01-03 17:55:01 +0100 |
commit | 18ca974e29fa475b87d3de0359ab821bafe1998d (patch) | |
tree | 9fa866a739386226cbcef74c7be3961fcb0d8c59 /lib | |
parent | 96992b86b3cb198bb132555e79827d253a45bdbc (diff) | |
parent | 61d3d2b769a1ec20d9eb6141c70ca40de028fe4b (diff) |
Merge branch 'libimagmail/neighbors' into master
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/domain/libimagmail/Cargo.toml | 2 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/iter.rs | 83 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/lib.rs | 3 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/mail.rs | 117 | ||||
-rw-r--r-- | lib/domain/libimagmail/src/store.rs | 20 |
5 files changed, 224 insertions, 1 deletions
diff --git a/lib/domain/libimagmail/Cargo.toml b/lib/domain/libimagmail/Cargo.toml index 32255a51..e0b1a891 100644 --- a/lib/domain/libimagmail/Cargo.toml +++ b/lib/domain/libimagmail/Cargo.toml @@ -26,11 +26,13 @@ toml-query = "0.9.2" 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" libimagstore = { version = "0.10.0", path = "../../../lib/core/libimagstore" } libimagerror = { version = "0.10.0", path = "../../../lib/core/libimagerror" } libimagentryref = { version = "0.10.0", path = "../../../lib/entry/libimagentryref" } +libimagentrylink = { version = "0.10.0", path = "../../../lib/entry/libimagentrylink/" } libimagentryutil = { version = "0.10.0", path = "../../../lib/entry/libimagentryutil/" } diff --git a/lib/domain/libimagmail/src/iter.rs b/lib/domain/libimagmail/src/iter.rs new file mode 100644 index 00000000..bba6b690 --- /dev/null +++ b/lib/domain/libimagmail/src/iter.rs @@ -0,0 +1,83 @@ +// +// 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 2334a74f..913acfc9 100644 --- a/lib/domain/libimagmail/src/lib.rs +++ b/lib/domain/libimagmail/src/lib.rs @@ -43,18 +43,21 @@ extern crate toml; 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 libimagerror; #[macro_use] extern crate libimagstore; extern crate libimagentryref; +extern crate libimagentrylink; #[macro_use] extern crate libimagentryutil; module_entry_path_mod!("mail"); pub mod config; pub mod hasher; +pub mod iter; pub mod mail; pub mod mailflags; pub mod mid; diff --git a/lib/domain/libimagmail/src/mail.rs b/lib/domain/libimagmail/src/mail.rs index db75b5d3..f24f5506 100644 --- a/lib/domain/libimagmail/src/mail.rs +++ b/lib/domain/libimagmail/src/mail.rs @@ -23,20 +23,28 @@ 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 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 { +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>>; @@ -52,6 +60,11 @@ pub trait Mail : RefFassade { 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>>; + } impl Mail for Entry { @@ -200,6 +213,108 @@ impl Mail for Entry { 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) + } } diff --git a/lib/domain/libimagmail/src/store.rs b/lib/domain/libimagmail/src/store.rs index 637dac54..88f49df1 100644 --- a/lib/domain/libimagmail/src/store.rs +++ b/lib/domain/libimagmail/src/store.rs @@ -59,6 +59,8 @@ pub trait MailStore<'a> { fn get_mail(&'a self, mid: MessageId) -> Result<Option<FileLockEntry<'a>>>; fn all_mails(&'a self) -> Result<Entries<'a>>; + + fn thread_root_of(&'a self, mid: MessageId, refconfig: &Config) -> Result<Option<FileLockEntry<'a>>>; } impl<'a> MailStore<'a> for Store { @@ -149,5 +151,23 @@ impl<'a> MailStore<'a> for Store { fn all_mails(&'a self) -> Result<Entries<'a>> { self.entries()?.in_collection("mail") } + + /// Compute the thread root of the thread the Mail belongs to + /// + /// This function recursively traverses the thread using `Store::get_mail()` and + /// `Mail::get_in_reply_to()` to find the root of the thread (The mail that is not a reply to + /// any other mail). + /// + fn thread_root_of(&'a self, mid: MessageId, refconfig: &Config) -> Result<Option<FileLockEntry<'a>>> { + if let Some(entry) = self.get_mail(mid)? { + if let Some(parent_mid) = entry.get_in_reply_to(refconfig)? { + self.thread_root_of(parent_mid, refconfig) + } else { + Ok(Some(entry)) + } + } else { + Ok(None) + } + } } |