melib: add search method in mail backends
parent
3d7b9ff7cb
commit
e9a935dbf7
|
@ -19,6 +19,7 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use smallvec::SmallVec;
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! tag_hash {
|
macro_rules! tag_hash {
|
||||||
($tag:ident) => {{
|
($tag:ident) => {{
|
||||||
|
@ -328,6 +329,14 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
Err(MeliError::new("Unimplemented."))
|
Err(MeliError::new("Unimplemented."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search(
|
||||||
|
&self,
|
||||||
|
_query: crate::search::Query,
|
||||||
|
_mailbox_hash: Option<MailboxHash>,
|
||||||
|
) -> Result<SmallVec<[EnvelopeHash; 512]>> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A `BackendOp` manages common operations for the various mail backends. They only live for the
|
/// A `BackendOp` manages common operations for the various mail backends. They only live for the
|
||||||
|
|
|
@ -704,6 +704,136 @@ impl MailBackend for ImapType {
|
||||||
|
|
||||||
Err(MeliError::new("Unimplemented."))
|
Err(MeliError::new("Unimplemented."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn search(
|
||||||
|
&self,
|
||||||
|
query: crate::search::Query,
|
||||||
|
mailbox_hash: Option<MailboxHash>,
|
||||||
|
) -> Result<SmallVec<[EnvelopeHash; 512]>> {
|
||||||
|
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 {
|
impl ImapType {
|
||||||
|
@ -887,41 +1017,6 @@ impl ImapType {
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn search(
|
|
||||||
&self,
|
|
||||||
query: String,
|
|
||||||
mailbox_hash: MailboxHash,
|
|
||||||
) -> Result<SmallVec<[EnvelopeHash; 512]>> {
|
|
||||||
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<()> {
|
pub fn validate_config(s: &AccountSettings) -> Result<()> {
|
||||||
get_conf_val!(s["server_hostname"])?;
|
get_conf_val!(s["server_hostname"])?;
|
||||||
get_conf_val!(s["server_username"])?;
|
get_conf_val!(s["server_username"])?;
|
||||||
|
|
|
@ -137,6 +137,7 @@ extern crate encoding;
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
extern crate uuid;
|
extern crate uuid;
|
||||||
|
pub use smallvec;
|
||||||
|
|
||||||
pub use crate::backends::{Backends, RefreshEvent, RefreshEventConsumer, SpecialUsageMailbox};
|
pub use crate::backends::{Backends, RefreshEvent, RefreshEventConsumer, SpecialUsageMailbox};
|
||||||
pub use crate::collection::*;
|
pub use crate::collection::*;
|
||||||
|
|
|
@ -21,14 +21,7 @@
|
||||||
|
|
||||||
use crate::parsec::*;
|
use crate::parsec::*;
|
||||||
use crate::UnixTimestamp;
|
use crate::UnixTimestamp;
|
||||||
use crate::{
|
|
||||||
backends::{MailBackend, MailboxHash},
|
|
||||||
email::EnvelopeHash,
|
|
||||||
thread::{SortField, SortOrder},
|
|
||||||
Result,
|
|
||||||
};
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::sync::{Arc, RwLock};
|
|
||||||
|
|
||||||
pub use query_parser::query;
|
pub use query_parser::query;
|
||||||
use Query::*;
|
use Query::*;
|
||||||
|
@ -229,9 +222,9 @@ pub mod query_parser {
|
||||||
///
|
///
|
||||||
/// # Invocation
|
/// # Invocation
|
||||||
/// ```
|
/// ```
|
||||||
/// use ui::cache::query;
|
/// use melib::search::query;
|
||||||
/// use ui::cache::Query;
|
/// use melib::search::Query;
|
||||||
/// use crate::parsec::Parser;
|
/// use melib::parsec::Parser;
|
||||||
///
|
///
|
||||||
/// let input = "test";
|
/// let input = "test";
|
||||||
/// let query = query().parse(input);
|
/// 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<RwLock<Box<dyn MailBackend>>>,
|
|
||||||
) -> Result<smallvec::SmallVec<[EnvelopeHash; 512]>> {
|
|
||||||
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::<crate::backends::ImapType>() {
|
|
||||||
imap_backend.search(query_to_imap(&query), mailbox_hash)
|
|
||||||
} else {
|
|
||||||
panic!("Could not downcast ImapType backend. BUG");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn escape_double_quote(w: &str) -> Cow<str> {
|
pub fn escape_double_quote(w: &str) -> Cow<str> {
|
||||||
if w.contains('"') {
|
if w.contains('"') {
|
||||||
|
|
|
@ -1133,7 +1133,13 @@ impl Account {
|
||||||
mailbox_hash: MailboxHash,
|
mailbox_hash: MailboxHash,
|
||||||
) -> Result<SmallVec<[EnvelopeHash; 512]>> {
|
) -> Result<SmallVec<[EnvelopeHash; 512]>> {
|
||||||
if self.settings.account().format() == "imap" {
|
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")]
|
#[cfg(feature = "notmuch")]
|
||||||
|
|
Loading…
Reference in New Issue