use std::{borrow::Cow, ffi::CString, ptr::NonNull, sync::Arc};
use crate::{
email::HeaderName,
error::{Error, ErrorKind, Result},
notmuch::{
ffi::{
notmuch_messages_t, notmuch_query_count_messages, notmuch_query_create,
notmuch_query_destroy, notmuch_query_search_messages, notmuch_query_t,
},
DbConnection, MessageIterator, NotmuchLibrary,
},
};
pub struct Query<'s> {
pub lib: Arc<NotmuchLibrary>,
pub ptr: NonNull<notmuch_query_t>,
pub query_str: &'s str,
}
impl<'s> Query<'s> {
pub fn new(database: &DbConnection, query_str: &'s str) -> Result<Self> {
let lib: Arc<NotmuchLibrary> = database.lib.clone();
let query_cstr = CString::new(query_str)?;
let query: *mut notmuch_query_t = unsafe {
call!(lib, notmuch_query_create)(
database.inner.lock().unwrap().as_mut(),
query_cstr.as_ptr(),
)
};
Ok(Query {
lib,
ptr: NonNull::new(query)
.ok_or_else(|| Error::new("Could not create query. Out of memory?"))?,
query_str,
})
}
pub fn count(&self) -> Result<u32> {
let mut count = 0_u32;
unsafe {
try_call!(
self.lib,
call!(self.lib, notmuch_query_count_messages)(
self.ptr.as_ptr(),
std::ptr::addr_of_mut!(count)
)
)
.map_err(|err| err.0)?;
}
Ok(count)
}
pub fn search(&'s self) -> Result<MessageIterator<'s>> {
let mut messages: *mut notmuch_messages_t = std::ptr::null_mut();
let status = unsafe {
call!(self.lib, notmuch_query_search_messages)(
self.ptr.as_ptr(),
std::ptr::addr_of_mut!(messages),
)
};
if status != 0 {
return Err(Error::new(format!(
"Search for {} returned {status}",
self.query_str
)));
}
let messages = Some(NonNull::new(messages).ok_or_else(|| {
Error::new(format!(
"Search for {} failed because of an internal libnotmuch error.",
self.query_str
))
.set_details(
"notmuch_query_search_messages returned status == 0 but the passed `messages` \
pointer argument is NULL.",
)
.set_kind(ErrorKind::LinkedLibrary(match self.lib.dlpath {
Cow::Borrowed(v) => v,
Cow::Owned(_) => "user configured path",
}))
})?);
Ok(MessageIterator {
messages,
lib: self.lib.clone(),
is_from_thread: false,
_ph: std::marker::PhantomData,
})
}
}
impl Drop for Query<'_> {
fn drop(&mut self) {
unsafe {
call!(self.lib, notmuch_query_destroy)(self.ptr.as_ptr());
}
}
}
pub trait MelibQueryToNotmuchQuery {
fn query_to_string(&self, ret: &mut String) -> Result<()>;
}
impl MelibQueryToNotmuchQuery for crate::search::Query {
fn query_to_string(&self, ret: &mut String) -> Result<()> {
use crate::search::Query::*;
match self {
Before(timestamp) => {
ret.push_str("date:..@");
ret.push_str(×tamp.to_string());
}
After(timestamp) => {
ret.push_str("date:@");
ret.push_str(×tamp.to_string());
ret.push_str("..");
}
Between(a, b) => {
ret.push_str("date:@");
ret.push_str(&a.to_string());
ret.push_str("..@");
ret.push_str(&b.to_string());
}
On(timestamp) => {
ret.push_str("date:@");
ret.push_str(×tamp.to_string());
}
From(s) => {
ret.push_str("from:\"");
for c in s.chars() {
if c == '"' {
ret.push_str("\\\"");
} else {
ret.push(c);
}
}
ret.push('"');
}
To(s) | Cc(s) | Bcc(s) => {
ret.push_str("to:\"");
for c in s.chars() {
if c == '"' {
ret.push_str("\\\"");
} else {
ret.push(c);
}
}
ret.push('"');
}
AllAddresses(s) => {
return <Self as MelibQueryToNotmuchQuery>::query_to_string(
&Or(
Box::new(From(s.to_string())),
Box::new(Or(
Box::new(Cc(s.to_string())),
Box::new(Bcc(s.to_string())),
)),
),
ret,
);
}
Header(t, v) if t == HeaderName::MESSAGE_ID => {
ret.push_str("id:\"");
for c in v.chars() {
if c == '"' {
ret.push_str("\\\"");
} else {
ret.push(c);
}
}
ret.push('"');
}
InReplyTo(s) => {
return <Self as MelibQueryToNotmuchQuery>::query_to_string(
&Header(HeaderName::IN_REPLY_TO, s.to_string()),
ret,
)
}
References(s) => {
return <Self as MelibQueryToNotmuchQuery>::query_to_string(
&Header(HeaderName::REFERENCES, s.to_string()),
ret,
)
}
Header(t, v) => {
for c in t.as_str().chars() {
if c == '-' {
continue;
}
ret.push(c);
}
ret.push_str(":\"");
for c in v.chars() {
if c == '"' {
ret.push_str("\\\"");
} else {
ret.push(c);
}
}
ret.push('"');
}
Body(s) => {
ret.push_str("body:\"");
for c in s.chars() {
if c == '"' {
ret.push_str("\\\"");
} else {
ret.push(c);
}
}
ret.push('"');
}
Subject(s) => {
ret.push_str("subject:\"");
for c in s.chars() {
if c == '"' {
ret.push_str("\\\"");
} else {
ret.push(c);
}
}
ret.push('"');
}
AllText(s) => {
ret.push('"');
for c in s.chars() {
if c == '"' {
ret.push_str("\\\"");
} else {
ret.push(c);
}
}
ret.push('"');
}
Flags(v) => {
for f in v {
ret.push_str("tag:\"");
for c in f.chars() {
if c == '"' {
ret.push_str("\\\"");
} else {
ret.push(c);
}
}
ret.push_str("\" ");
}
if !v.is_empty() {
ret.pop();
}
}
HasAttachment => {
ret.push_str("tag:attachment");
}
And(q1, q2) => {
ret.push('(');
q1.query_to_string(ret)?;
ret.push_str(") AND (");
q2.query_to_string(ret)?;
ret.push(')');
}
Or(q1, q2) => {
ret.push('(');
q1.query_to_string(ret)?;
ret.push_str(") OR (");
q2.query_to_string(ret)?;
ret.push(')');
}
Not(q) => {
ret.push_str("(NOT (");
q.query_to_string(ret)?;
ret.push_str("))");
}
Answered | AnsweredBy { .. } | Larger { .. } | Smaller { .. } => {
return Err(
Error::new(format!("{self:?} query is not implemented for notmuch"))
.set_kind(ErrorKind::NotImplemented),
);
}
}
Ok(())
}
}