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