From e9a935dbf7feab39d8e21382c16f2b9ce0016cf9 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sat, 4 Apr 2020 20:09:51 +0300 Subject: [PATCH] melib: add search method in mail backends --- melib/src/backends.rs | 9 ++ melib/src/backends/imap.rs | 165 +++++++++++++++++++++++++++++-------- melib/src/lib.rs | 1 + melib/src/search.rs | 121 +-------------------------- src/conf/accounts.rs | 8 +- 5 files changed, 150 insertions(+), 154 deletions(-) diff --git a/melib/src/backends.rs b/melib/src/backends.rs index cc913b8e..71ac5186 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ +use smallvec::SmallVec; #[macro_export] macro_rules! tag_hash { ($tag:ident) => {{ @@ -328,6 +329,14 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync { ) -> Result<()> { Err(MeliError::new("Unimplemented.")) } + + fn search( + &self, + _query: crate::search::Query, + _mailbox_hash: Option, + ) -> Result> { + Err(MeliError::new("Unimplemented.")) + } } /// A `BackendOp` manages common operations for the various mail backends. They only live for the diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 3b01e3ba..eedb7fa0 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -704,6 +704,136 @@ impl MailBackend for ImapType { Err(MeliError::new("Unimplemented.")) } + + fn search( + &self, + query: crate::search::Query, + mailbox_hash: Option, + ) -> Result> { + if mailbox_hash.is_none() { + return Err(MeliError::new( + "Cannot search without specifying mailbox on IMAP", + )); + } + let mailbox_hash = mailbox_hash.unwrap(); + fn rec(q: &crate::search::Query, s: &mut String) { + use crate::search::{escape_double_quote, Query::*}; + match q { + Subject(t) => { + s.push_str(" SUBJECT \""); + s.extend(escape_double_quote(t).chars()); + s.push_str("\""); + } + From(t) => { + s.push_str(" FROM \""); + s.extend(escape_double_quote(t).chars()); + s.push_str("\""); + } + To(t) => { + s.push_str(" TO \""); + s.extend(escape_double_quote(t).chars()); + s.push_str("\""); + } + Cc(t) => { + s.push_str(" CC \""); + s.extend(escape_double_quote(t).chars()); + s.push_str("\""); + } + Bcc(t) => { + s.push_str(" BCC \""); + s.extend(escape_double_quote(t).chars()); + s.push_str("\""); + } + AllText(t) => { + s.push_str(" TEXT \""); + s.extend(escape_double_quote(t).chars()); + s.push_str("\""); + } + Flags(v) => { + for f in v { + match f.as_str() { + "draft" => { + s.push_str(" DRAFT "); + } + "deleted" => { + s.push_str(" DELETED "); + } + "flagged" => { + s.push_str(" FLAGGED "); + } + "recent" => { + s.push_str(" RECENT "); + } + "seen" | "read" => { + s.push_str(" SEEN "); + } + "unseen" | "unread" => { + s.push_str(" UNSEEN "); + } + "answered" => { + s.push_str(" ANSWERED "); + } + "unanswered" => { + s.push_str(" UNANSWERED "); + } + keyword => { + s.push_str(" KEYWORD "); + s.extend(keyword.chars()); + s.push_str(" "); + } + } + } + } + And(q1, q2) => { + rec(q1, s); + s.push_str(" "); + rec(q2, s); + } + Or(q1, q2) => { + s.push_str(" OR "); + rec(q1, s); + s.push_str(" "); + rec(q2, s); + } + Not(q) => { + s.push_str(" NOT "); + rec(q, s); + } + _ => {} + } + } + let mut query_str = String::new(); + rec(&query, &mut query_str); + + let mailboxes_lck = self.mailboxes.read()?; + let mut response = String::with_capacity(8 * 1024); + let mut conn = try_lock(&self.connection)?; + conn.send_command( + format!("EXAMINE \"{}\"", mailboxes_lck[&mailbox_hash].imap_path()).as_bytes(), + )?; + conn.read_response(&mut response)?; + conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query_str).as_bytes())?; + conn.read_response(&mut response)?; + debug!(&response); + + let mut lines = response.lines(); + for l in lines.by_ref() { + if l.starts_with("* SEARCH") { + use std::iter::FromIterator; + let uid_index = self.uid_store.uid_index.lock()?; + return Ok(SmallVec::from_iter( + l["* SEARCH".len()..] + .trim() + .split_whitespace() + .map(usize::from_str) + .filter_map(std::result::Result::ok) + .filter_map(|uid| uid_index.get(&uid)) + .map(|env_hash_ref| *env_hash_ref), + )); + } + } + Err(MeliError::new(response)) + } } impl ImapType { @@ -887,41 +1017,6 @@ impl ImapType { .unwrap_or_default() } - pub fn search( - &self, - query: String, - mailbox_hash: MailboxHash, - ) -> Result> { - let mailboxes_lck = self.mailboxes.read()?; - let mut response = String::with_capacity(8 * 1024); - let mut conn = try_lock(&self.connection)?; - conn.send_command( - format!("EXAMINE \"{}\"", mailboxes_lck[&mailbox_hash].imap_path()).as_bytes(), - )?; - conn.read_response(&mut response)?; - conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query).as_bytes())?; - conn.read_response(&mut response)?; - debug!(&response); - - let mut lines = response.lines(); - for l in lines.by_ref() { - if l.starts_with("* SEARCH") { - use std::iter::FromIterator; - let uid_index = self.uid_store.uid_index.lock()?; - return Ok(SmallVec::from_iter( - l["* SEARCH".len()..] - .trim() - .split_whitespace() - .map(usize::from_str) - .filter_map(std::result::Result::ok) - .filter_map(|uid| uid_index.get(&uid)) - .map(|env_hash_ref| *env_hash_ref), - )); - } - } - Err(MeliError::new(response)) - } - pub fn validate_config(s: &AccountSettings) -> Result<()> { get_conf_val!(s["server_hostname"])?; get_conf_val!(s["server_username"])?; diff --git a/melib/src/lib.rs b/melib/src/lib.rs index 38000c79..1d3d9076 100644 --- a/melib/src/lib.rs +++ b/melib/src/lib.rs @@ -137,6 +137,7 @@ extern crate encoding; extern crate bitflags; extern crate fnv; extern crate uuid; +pub use smallvec; pub use crate::backends::{Backends, RefreshEvent, RefreshEventConsumer, SpecialUsageMailbox}; pub use crate::collection::*; diff --git a/melib/src/search.rs b/melib/src/search.rs index 12a4f842..c2ca0982 100644 --- a/melib/src/search.rs +++ b/melib/src/search.rs @@ -21,14 +21,7 @@ use crate::parsec::*; use crate::UnixTimestamp; -use crate::{ - backends::{MailBackend, MailboxHash}, - email::EnvelopeHash, - thread::{SortField, SortOrder}, - Result, -}; use std::borrow::Cow; -use std::sync::{Arc, RwLock}; pub use query_parser::query; use Query::*; @@ -229,9 +222,9 @@ pub mod query_parser { /// /// # Invocation /// ``` - /// use ui::cache::query; - /// use ui::cache::Query; - /// use crate::parsec::Parser; + /// use melib::search::query; + /// use melib::search::Query; + /// use melib::parsec::Parser; /// /// let input = "test"; /// let query = query().parse(input); @@ -371,114 +364,6 @@ pub mod query_parser { } } -pub fn query_to_imap(q: &Query) -> String { - fn rec(q: &Query, s: &mut String) { - match q { - Subject(t) => { - s.push_str(" SUBJECT \""); - s.extend(escape_double_quote(t).chars()); - s.push_str("\""); - } - From(t) => { - s.push_str(" FROM \""); - s.extend(escape_double_quote(t).chars()); - s.push_str("\""); - } - To(t) => { - s.push_str(" TO \""); - s.extend(escape_double_quote(t).chars()); - s.push_str("\""); - } - Cc(t) => { - s.push_str(" CC \""); - s.extend(escape_double_quote(t).chars()); - s.push_str("\""); - } - Bcc(t) => { - s.push_str(" BCC \""); - s.extend(escape_double_quote(t).chars()); - s.push_str("\""); - } - AllText(t) => { - s.push_str(" TEXT \""); - s.extend(escape_double_quote(t).chars()); - s.push_str("\""); - } - Flags(v) => { - for f in v { - match f.as_str() { - "draft" => { - s.push_str(" DRAFT "); - } - "deleted" => { - s.push_str(" DELETED "); - } - "flagged" => { - s.push_str(" FLAGGED "); - } - "recent" => { - s.push_str(" RECENT "); - } - "seen" | "read" => { - s.push_str(" SEEN "); - } - "unseen" | "unread" => { - s.push_str(" UNSEEN "); - } - "answered" => { - s.push_str(" ANSWERED "); - } - "unanswered" => { - s.push_str(" UNANSWERED "); - } - keyword => { - s.push_str(" KEYWORD "); - s.extend(keyword.chars()); - s.push_str(" "); - } - } - } - } - And(q1, q2) => { - rec(q1, s); - s.push_str(" "); - rec(q2, s); - } - Or(q1, q2) => { - s.push_str(" OR "); - rec(q1, s); - s.push_str(" "); - rec(q2, s); - } - Not(q) => { - s.push_str(" NOT "); - rec(q, s); - } - _ => {} - } - } - let mut ret = String::new(); - rec(q, &mut ret); - ret -} - -pub fn imap_search( - term: &str, - (_sort_field, _sort_order): (SortField, SortOrder), - mailbox_hash: MailboxHash, - backend: &Arc>>, -) -> Result> { - let query = query().parse(term)?.1; - let backend_lck = backend.read().unwrap(); - - let b = (*backend_lck).as_any(); - if let Some(imap_backend) = b.downcast_ref::() { - imap_backend.search(query_to_imap(&query), mailbox_hash) - } else { - panic!("Could not downcast ImapType backend. BUG"); - } -} - #[inline(always)] pub fn escape_double_quote(w: &str) -> Cow { if w.contains('"') { diff --git a/src/conf/accounts.rs b/src/conf/accounts.rs index cd91d9e3..9475af5d 100644 --- a/src/conf/accounts.rs +++ b/src/conf/accounts.rs @@ -1133,7 +1133,13 @@ impl Account { mailbox_hash: MailboxHash, ) -> Result> { if self.settings.account().format() == "imap" { - return melib::search::imap_search(search_term, sort, mailbox_hash, &self.backend); + use melib::parsec::Parser; + let query = melib::search::query().parse(search_term)?.1; + return self + .backend + .read() + .unwrap() + .search(query, Some(mailbox_hash)); } #[cfg(feature = "notmuch")]