summaryrefslogtreecommitdiffstats
path: root/lib/domain/libimagmail/src/store.rs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/domain/libimagmail/src/store.rs')
-rw-r--r--lib/domain/libimagmail/src/store.rs306
1 files changed, 181 insertions, 125 deletions
diff --git a/lib/domain/libimagmail/src/store.rs b/lib/domain/libimagmail/src/store.rs
index 72d1e95d..7a01dd1d 100644
--- a/lib/domain/libimagmail/src/store.rs
+++ b/lib/domain/libimagmail/src/store.rs
@@ -17,156 +17,212 @@
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
-use std::path::Path;
-use std::path::PathBuf;
-use std::fmt::Debug;
+use std::borrow::Cow;
+use std::ops::Deref;
-use anyhow::Result;
-use toml::Value;
+use failure::Fallible as Result;
+use failure::ResultExt;
+use failure::Error;
use toml_query::insert::TomlValueInsertExt;
+use toml::Value;
+use notmuch_rs::Sort as NotmuchSorting;
+use notmuch_rs::Query;
-use libimagstore::store::FileLockEntry;
use libimagstore::store::Store;
-use libimagstore::iter::Entries;
-use libimagentryref::hasher::default::DefaultHasher;
-use libimagentryref::reference::Config;
-use libimagentryref::reference::RefFassade;
-use libimagentryref::reference::Ref;
-use libimagentryref::reference::MutRef;
+use libimagstore::store::FileLockEntry;
use libimagentryutil::isa::Is;
-use crate::mid::MessageId;
+use crate::notmuch::connection::NotmuchConnection;
use crate::mail::Mail;
use crate::mail::IsMail;
-use crate::hasher::MailHasher;
-use crate::util::get_message_id_for_mailfile;
+use crate::mailtree::Mailtree;
pub trait MailStore<'a> {
- fn create_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config)
- -> Result<FileLockEntry<'a>>
- where P: AsRef<Path> + Debug,
- CollName: AsRef<str> + Debug;
+ fn with_connection(&'a self, c: &'a NotmuchConnection) -> MailStoreWithConnection<'a>;
+}
- fn get_mail_from_path<P>(&'a self, p: P)
- -> Result<Option<FileLockEntry<'a>>>
- where P: AsRef<Path> + Debug;
+impl<'a> MailStore<'a> for Store {
+ fn with_connection(&'a self, c: &'a NotmuchConnection) -> MailStoreWithConnection<'a> {
+ debug!("Accessing Store with notmuch connection: {:?}", c);
+ MailStoreWithConnection {
+ store: self,
+ connection: c,
+ }
+ }
+}
- fn retrieve_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config, force_making_ref: bool)
- -> Result<FileLockEntry<'a>>
- where P: AsRef<Path> + Debug,
- CollName: AsRef<str> + Debug;
+#[derive(Debug)]
+pub struct MailStoreWithConnection<'a> {
+ store: &'a Store,
+ connection: &'a NotmuchConnection,
+}
- fn get_mail(&'a self, mid: MessageId) -> Result<Option<FileLockEntry<'a>>>;
- fn all_mails(&'a self) -> Result<Entries<'a>>;
+impl<'a> Deref for MailStoreWithConnection<'a> {
+ type Target = Store;
- fn thread_root_of(&'a self, mid: MessageId, refconfig: &Config) -> Result<Option<FileLockEntry<'a>>>;
+ fn deref(&self) -> &Self::Target {
+ self.store
+ }
}
-impl<'a> MailStore<'a> for Store {
+impl<'a> MailStoreWithConnection<'a> {
+ pub(crate) fn connection(&self) -> &NotmuchConnection {
+ &self.connection
+ }
- fn create_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config)
- -> Result<FileLockEntry<'a>>
- where P: AsRef<Path> + Debug,
- CollName: AsRef<str> + Debug
- {
- let message_id = get_message_id_for_mailfile(p.as_ref())?;
- let new_sid = crate::module_path::new_id(message_id.as_ref())?;
-
- let mut entry = self.create(new_sid)?;
- entry
- .as_ref_with_hasher_mut::<MailHasher>()
- .make_ref(p, collection_name, config, false)?;
-
- let _ = entry
- .get_header_mut()
- .insert("mail.message-id", Value::String(message_id.into()))?;
-
- entry.set_isflag::<IsMail>()?;
- Ok(entry)
- }
-
- /// Same as MailStore::retrieve_mail_from_path() but uses Store::get() instead of
- /// Store::retrieve()
- fn get_mail_from_path<P>(&'a self, p: P)
- -> Result<Option<FileLockEntry<'a>>>
- where P: AsRef<Path> + Debug
- {
- let message_id = get_message_id_for_mailfile(p.as_ref())?;
- let new_sid = crate::module_path::new_id(message_id.as_ref())?;
-
- match self.get(new_sid)? {
- Some(mut entry) => {
- if !entry.is_ref()? {
- return Err(anyhow!("{} is not a ref", entry.get_location()))
- }
-
- if p.as_ref().ends_with(entry.as_ref_with_hasher::<MailHasher>().get_relative_path()?) {
- return Err(anyhow!("{} is not a ref to {:?}",
- entry.get_location(),
- p.as_ref().display()))
- }
-
- let _ = entry.get_header_mut().insert("mail.message-id", Value::String(message_id.into()))?;
- Ok(Some(entry))
- },
- None => Ok(None),
- }
+ pub fn import_with_query(&self, q: &str) -> Result<Vec<FileLockEntry<'a>>> {
+ let mut new_entries = vec![];
+
+ self.connection
+ .execute(|db| {
+ let query = db.create_query(q)?;
+ query
+ .search_messages()?
+ .map(|message| {
+ self.create_entry_for_id(message.id())
+ .map(|entry| new_entries.push(entry))
+ })
+ .collect::<Result<Vec<_>>>()?;
+
+ Ok(())
+ })?;
+
+ Ok(new_entries)
+ }
+
+ pub fn query(&'a self, q: &str) -> Result<Vec<FileLockEntry<'a>>> {
+ let r = self.build_query(q).execute()?;
+ Ok(r)
+ }
+
+ pub fn query_atomic(&'a self, q: &str) -> Result<Vec<FileLockEntry<'a>>> {
+ let r = self.build_query(q).atomic(true).execute()?;
+ Ok(r)
}
- fn retrieve_mail_from_path<P, CollName>(&'a self, p: P, collection_name: CollName, config: &Config, force_making_ref: bool)
- -> Result<FileLockEntry<'a>>
- where P: AsRef<Path> + Debug,
- CollName: AsRef<str> + Debug
- {
- let message_id = get_message_id_for_mailfile(&p)?;
- let new_sid = crate::module_path::new_id(message_id.as_ref())?;
- let mut entry = self.retrieve(new_sid)?;
-
- let _ = entry
- .get_header_mut()
- .insert("mail.message-id", Value::String(message_id.into()))?;
-
- entry
- .as_ref_with_hasher_mut::<DefaultHasher>()
- .make_ref(p, collection_name, config, force_making_ref)?;
-
- entry.set_isflag::<IsMail>()?;
- Ok(entry)
- }
-
- fn get_mail(&'a self, mid: MessageId) -> Result<Option<FileLockEntry<'a>>> {
- let mid_s : String = mid.into();
- let sid = crate::module_path::new_id(PathBuf::from(mid_s))?;
- self.get(sid)
- .and_then(|oe| match oe {
- Some(e) => if e.is_mail()? {
- Ok(Some(e))
- } else {
- Err(anyhow!("{} is not a mail entry", e.get_location()))
- },
- None => Ok(None)
+ fn execute_query<'d>(&self, q: Query<'d>) -> Result<Vec<FileLockEntry<'a>>> {
+ q.search_messages()?
+ .map(|msg| {
+ self.get_entry_for_id(msg.id())?
+ .ok_or_else(|| format_err!("Message with id '{}' not found", msg.id()))
})
+ .collect::<Result<Vec<_>>>()
+ }
+
+ pub fn build_query<'q>(&'a self, q: &'q str) -> QueryBuilder<'q, 'a> {
+ QueryBuilder::new(self, q)
+ }
+
+ pub fn get_mail_by_id(&self, id: &str) -> Result<Option<FileLockEntry>> {
+ self.get_entry_for_id(Cow::from(id))
+ }
+
+ pub fn get_mailtree(&self, root_id: &str) -> Result<Mailtree> {
+ trace!("Getting mail by id: {}", root_id);
+ let root = self.get_mail_by_id(root_id)?
+ .ok_or_else(|| format_err!("Cannot find root message: {}", root_id))?;
+
+ trace!("Getting mail from connection: {:?}", root);
+ let loaded_root = root.load(self.connection())?
+ .ok_or_else(|| format_err!("Cannot load root message: {}", root_id))?;
+
+ drop(root);
+ trace!("Filling mailtree from: {:?}, {:?}", self, loaded_root);
+ Mailtree::fill_from(self, loaded_root)
+ }
+
+ fn create_entry_for_id(&self, id: Cow<str>) -> Result<FileLockEntry<'a>> {
+ let sid = crate::module_path::new_id(id.as_ref())?;
+ self.store
+ .retrieve(sid)
+ .and_then(|mut entry| {
+ entry.get_header_mut().insert("mail.id", Value::String(String::from(id)))?;
+ entry.set_isflag::<IsMail>()?;
+
+ Ok(entry)
+ })
+ }
+
+ fn get_entry_for_id(&self, id: Cow<str>) -> Result<Option<FileLockEntry<'a>>> {
+ let sid = crate::module_path::new_id(id.as_ref())?;
+ self.store
+ .get(sid)
+ .with_context(|_| format!("Getting entry for Id: {}", id.as_ref()))
+ .map_err(Error::from)
+ }
+
+}
+
+#[derive(Debug)]
+pub struct QueryBuilder<'q, 's> {
+ store: &'s MailStoreWithConnection<'s>,
+ query: &'q str,
+ sorting: Sorting,
+ atomic: bool,
+}
+
+#[derive(Debug)]
+pub enum Sorting {
+ OldestFirst,
+ NewestFirst,
+ MessageId,
+ Unsorted,
+}
+
+impl Default for Sorting {
+ fn default() -> Self {
+ Sorting::Unsorted
+ }
+}
+
+impl Into<NotmuchSorting> for Sorting {
+ fn into(self) -> NotmuchSorting {
+ match self {
+ Sorting::OldestFirst => NotmuchSorting::OldestFirst,
+ Sorting::NewestFirst => NotmuchSorting::NewestFirst,
+ Sorting::MessageId => NotmuchSorting::MessageID,
+ Sorting::Unsorted => NotmuchSorting::Unsorted,
+ }
+ }
+}
+
+impl<'q, 's> QueryBuilder<'q, 's> {
+ fn new(store: &'s MailStoreWithConnection, query: &'q str) -> Self {
+ QueryBuilder {
+ store,
+ query,
+ sorting: Sorting::default(),
+ atomic: false,
+ }
+ }
+
+ pub fn sorted(mut self, sorting: Sorting) -> Self {
+ self.sorting = sorting;
+ self
}
- fn all_mails(&'a self) -> Result<Entries<'a>> {
- self.entries()?.in_collection("mail")
+ pub fn atomic(mut self, atomic: bool) -> Self {
+ self.atomic = atomic;
+ self
}
- /// 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))
- }
+ pub fn execute(self) -> Result<Vec<FileLockEntry<'s>>> {
+ if self.atomic {
+ self.store
+ .connection
+ .execute_atomic(|db| {
+ let query = db.create_query(self.query)?;
+ query.set_sort(self.sorting.into());
+ self.store.execute_query(query)
+ })
} else {
- Ok(None)
+ self.store
+ .connection
+ .execute(|db| {
+ let query = db.create_query(self.query)?;
+ query.set_sort(self.sorting.into());
+ self.store.execute_query(query)
+ })
}
}
}