summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2020-01-03 17:55:01 +0100
committerMatthias Beyer <mail@beyermatthias.de>2020-01-03 17:55:01 +0100
commit18ca974e29fa475b87d3de0359ab821bafe1998d (patch)
tree9fa866a739386226cbcef74c7be3961fcb0d8c59
parent96992b86b3cb198bb132555e79827d253a45bdbc (diff)
parent61d3d2b769a1ec20d9eb6141c70ca40de028fe4b (diff)
Merge branch 'libimagmail/neighbors' into master
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r--lib/domain/libimagmail/Cargo.toml2
-rw-r--r--lib/domain/libimagmail/src/iter.rs83
-rw-r--r--lib/domain/libimagmail/src/lib.rs3
-rw-r--r--lib/domain/libimagmail/src/mail.rs117
-rw-r--r--lib/domain/libimagmail/src/store.rs20
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)
+ }
+ }
}