melib/notmuch: split queries and mailbox into submodules
Signed-off-by: Manos Pitsidianakis <manos@pitsidianak.is>pull/327/head
parent
7412c23870
commit
1b0bdd0a9a
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* meli - notmuch backend
|
||||
*
|
||||
* Copyright 2019 - 2023 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use crate::{
|
||||
backends::{BackendMailbox, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox},
|
||||
error::Result,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NotmuchMailbox {
|
||||
pub(super) hash: MailboxHash,
|
||||
pub(super) children: Vec<MailboxHash>,
|
||||
pub(super) parent: Option<MailboxHash>,
|
||||
pub(super) name: String,
|
||||
pub(super) path: String,
|
||||
pub(super) query_str: String,
|
||||
pub(super) usage: Arc<RwLock<SpecialUsageMailbox>>,
|
||||
pub(super) total: Arc<Mutex<usize>>,
|
||||
pub(super) unseen: Arc<Mutex<usize>>,
|
||||
}
|
||||
|
||||
impl NotmuchMailbox {
|
||||
/// Get the actual notmuch query used to build this mailbox.
|
||||
pub fn query_value(&self) -> &str {
|
||||
&self.query_str
|
||||
}
|
||||
}
|
||||
|
||||
impl BackendMailbox for NotmuchMailbox {
|
||||
fn hash(&self) -> MailboxHash {
|
||||
self.hash
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn path(&self) -> &str {
|
||||
&self.path
|
||||
}
|
||||
|
||||
fn clone(&self) -> Mailbox {
|
||||
Box::new(Clone::clone(self))
|
||||
}
|
||||
|
||||
fn children(&self) -> &[MailboxHash] {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<MailboxHash> {
|
||||
self.parent
|
||||
}
|
||||
|
||||
fn special_usage(&self) -> SpecialUsageMailbox {
|
||||
*self.usage.read().unwrap()
|
||||
}
|
||||
|
||||
fn permissions(&self) -> MailboxPermissions {
|
||||
MailboxPermissions::default()
|
||||
}
|
||||
|
||||
fn is_subscribed(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_is_subscribed(&mut self, _new_val: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
|
||||
*self.usage.write()? = new_val;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn count(&self) -> Result<(usize, usize)> {
|
||||
Ok((*self.unseen.lock()?, *self.total.lock()?))
|
||||
}
|
||||
}
|
|
@ -20,19 +20,28 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::thread::{ThreadHash, ThreadNode, ThreadNodeHash};
|
||||
use crate::{
|
||||
notmuch::ffi::{
|
||||
notmuch_database_find_message, notmuch_message_add_tag, notmuch_message_destroy,
|
||||
notmuch_message_get_date, notmuch_message_get_filename, notmuch_message_get_header,
|
||||
notmuch_message_get_message_id, notmuch_message_get_replies, notmuch_message_remove_tag,
|
||||
notmuch_message_tags_to_maildir_flags, notmuch_messages_get, notmuch_messages_move_to_next,
|
||||
notmuch_messages_valid,
|
||||
},
|
||||
thread::{ThreadHash, ThreadNode, ThreadNodeHash},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Message<'m> {
|
||||
pub lib: Arc<libloading::Library>,
|
||||
pub message: *mut notmuch_message_t,
|
||||
pub message: *mut ffi::notmuch_message_t,
|
||||
pub is_from_thread: bool,
|
||||
pub _ph: std::marker::PhantomData<&'m notmuch_message_t>,
|
||||
pub _ph: std::marker::PhantomData<&'m ffi::notmuch_message_t>,
|
||||
}
|
||||
|
||||
impl<'m> Message<'m> {
|
||||
pub fn find_message(db: &'m DbConnection, msg_id: &CStr) -> Result<Message<'m>> {
|
||||
let mut message: *mut notmuch_message_t = std::ptr::null_mut();
|
||||
let mut message: *mut ffi::notmuch_message_t = std::ptr::null_mut();
|
||||
let lib = db.lib.clone();
|
||||
unsafe {
|
||||
call!(lib, notmuch_database_find_message)(
|
||||
|
@ -251,7 +260,7 @@ impl Drop for Message<'_> {
|
|||
|
||||
pub struct MessageIterator<'query> {
|
||||
pub lib: Arc<libloading::Library>,
|
||||
pub messages: *mut notmuch_messages_t,
|
||||
pub messages: *mut ffi::notmuch_messages_t,
|
||||
pub is_from_thread: bool,
|
||||
pub _ph: std::marker::PhantomData<*const Query<'query>>,
|
||||
}
|
||||
|
|
|
@ -44,6 +44,12 @@ use crate::{
|
|||
|
||||
macro_rules! call {
|
||||
($lib:expr, $func:ty) => {{
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(
|
||||
!stringify!($func).starts_with("ffi::"),
|
||||
"{} must be a valid FFI symbol.",
|
||||
stringify!($func)
|
||||
);
|
||||
let func: libloading::Symbol<$func> = $lib.get(stringify!($func).as_bytes()).unwrap();
|
||||
func
|
||||
}};
|
||||
|
@ -52,19 +58,28 @@ macro_rules! call {
|
|||
macro_rules! try_call {
|
||||
($lib:expr, $call:expr) => {{
|
||||
let status = $call;
|
||||
if status == _notmuch_status_NOTMUCH_STATUS_SUCCESS {
|
||||
if status == $crate::notmuch::ffi::_notmuch_status_NOTMUCH_STATUS_SUCCESS {
|
||||
Ok(())
|
||||
} else {
|
||||
let c_str = call!($lib, notmuch_status_to_string)(status);
|
||||
Err(NotmuchError(
|
||||
CStr::from_ptr(c_str).to_string_lossy().into_owned(),
|
||||
Err($crate::notmuch::NotmuchError(
|
||||
std::ffi::CStr::from_ptr(c_str)
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
))
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub mod bindings;
|
||||
use bindings::*;
|
||||
pub mod query;
|
||||
use query::{MelibQueryToNotmuchQuery, Query};
|
||||
pub mod mailbox;
|
||||
use mailbox::NotmuchMailbox;
|
||||
pub mod ffi;
|
||||
use ffi::{
|
||||
notmuch_database_close, notmuch_database_destroy, notmuch_database_get_revision,
|
||||
notmuch_database_open, notmuch_status_to_string,
|
||||
};
|
||||
mod message;
|
||||
pub use message::*;
|
||||
mod tags;
|
||||
|
@ -74,21 +89,20 @@ pub use thread::*;
|
|||
|
||||
#[derive(Debug)]
|
||||
#[repr(transparent)]
|
||||
struct DbPointer(NonNull<notmuch_database_t>);
|
||||
struct DbPointer(NonNull<ffi::notmuch_database_t>);
|
||||
|
||||
unsafe impl Send for DbPointer {}
|
||||
unsafe impl Sync for DbPointer {}
|
||||
|
||||
impl DbPointer {
|
||||
#[inline]
|
||||
pub(self) fn as_mut(&mut self) -> *mut notmuch_database_t {
|
||||
pub(self) fn as_mut(&mut self) -> *mut ffi::notmuch_database_t {
|
||||
unsafe { self.0.as_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DbConnection {
|
||||
#[allow(dead_code)]
|
||||
pub lib: Arc<libloading::Library>,
|
||||
inner: Arc<Mutex<DbPointer>>,
|
||||
pub revision_uuid: Arc<RwLock<u64>>,
|
||||
|
@ -219,14 +233,14 @@ impl Drop for DbConnection {
|
|||
self.lib,
|
||||
call!(self.lib, notmuch_database_close)(inner.as_mut())
|
||||
) {
|
||||
debug!(err);
|
||||
log::error!("Could not call C notmuch_database_close: {}", err);
|
||||
return;
|
||||
}
|
||||
if let Err(err) = try_call!(
|
||||
self.lib,
|
||||
call!(self.lib, notmuch_database_destroy)(inner.as_mut())
|
||||
) {
|
||||
debug!(err);
|
||||
log::error!("Could not call C notmuch_database_destroy: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -248,77 +262,6 @@ pub struct NotmuchDb {
|
|||
save_messages_to: Option<PathBuf>,
|
||||
}
|
||||
|
||||
unsafe impl Send for NotmuchDb {}
|
||||
unsafe impl Sync for NotmuchDb {}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct NotmuchMailbox {
|
||||
hash: MailboxHash,
|
||||
children: Vec<MailboxHash>,
|
||||
parent: Option<MailboxHash>,
|
||||
name: String,
|
||||
path: String,
|
||||
query_str: String,
|
||||
usage: Arc<RwLock<SpecialUsageMailbox>>,
|
||||
|
||||
total: Arc<Mutex<usize>>,
|
||||
unseen: Arc<Mutex<usize>>,
|
||||
}
|
||||
|
||||
impl BackendMailbox for NotmuchMailbox {
|
||||
fn hash(&self) -> MailboxHash {
|
||||
self.hash
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_str()
|
||||
}
|
||||
|
||||
fn path(&self) -> &str {
|
||||
self.path.as_str()
|
||||
}
|
||||
|
||||
fn clone(&self) -> Mailbox {
|
||||
Box::new(std::clone::Clone::clone(self))
|
||||
}
|
||||
|
||||
fn children(&self) -> &[MailboxHash] {
|
||||
&self.children
|
||||
}
|
||||
|
||||
fn parent(&self) -> Option<MailboxHash> {
|
||||
self.parent
|
||||
}
|
||||
|
||||
fn special_usage(&self) -> SpecialUsageMailbox {
|
||||
*self.usage.read().unwrap()
|
||||
}
|
||||
|
||||
fn permissions(&self) -> MailboxPermissions {
|
||||
MailboxPermissions::default()
|
||||
}
|
||||
|
||||
fn is_subscribed(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn set_is_subscribed(&mut self, _new_val: bool) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_special_usage(&mut self, new_val: SpecialUsageMailbox) -> Result<()> {
|
||||
*self.usage.write()? = new_val;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn count(&self) -> Result<(usize, usize)> {
|
||||
Ok((*self.unseen.lock()?, *self.total.lock()?))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for NotmuchMailbox {}
|
||||
unsafe impl Sync for NotmuchMailbox {}
|
||||
|
||||
impl NotmuchDb {
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(
|
||||
|
@ -546,16 +489,16 @@ impl NotmuchDb {
|
|||
lib: Arc<libloading::Library>,
|
||||
write: bool,
|
||||
) -> Result<DbConnection> {
|
||||
let path_c = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
|
||||
let path_c = CString::new(path.to_str().unwrap()).unwrap();
|
||||
let path_ptr = path_c.as_ptr();
|
||||
let mut database: *mut notmuch_database_t = std::ptr::null_mut();
|
||||
let mut database: *mut ffi::notmuch_database_t = std::ptr::null_mut();
|
||||
let status = unsafe {
|
||||
call!(lib, notmuch_database_open)(
|
||||
path_ptr,
|
||||
if write {
|
||||
notmuch_database_mode_t_NOTMUCH_DATABASE_MODE_READ_WRITE
|
||||
ffi::notmuch_database_mode_t_NOTMUCH_DATABASE_MODE_READ_WRITE
|
||||
} else {
|
||||
notmuch_database_mode_t_NOTMUCH_DATABASE_MODE_READ_ONLY
|
||||
ffi::notmuch_database_mode_t_NOTMUCH_DATABASE_MODE_READ_ONLY
|
||||
},
|
||||
std::ptr::addr_of_mut!(database),
|
||||
)
|
||||
|
@ -708,7 +651,7 @@ impl MailBackend for NotmuchDb {
|
|||
iter: v.into_iter(),
|
||||
};
|
||||
Ok(Box::pin(async_stream::try_stream! {
|
||||
while let Some(res) = state.fetch().await.map_err(|err| { debug!("fetch err {:?}", &err); err})? {
|
||||
while let Some(res) = state.fetch().await.map_err(|err| { log::debug!("fetch err {:?}", &err); err})? {
|
||||
yield res;
|
||||
}
|
||||
}))
|
||||
|
@ -864,17 +807,15 @@ impl MailBackend for NotmuchDb {
|
|||
Ok(Box::pin(async move {
|
||||
let mut index_lck = index.write().unwrap();
|
||||
for env_hash in env_hashes.iter() {
|
||||
debug!(&env_hash);
|
||||
let message = match Message::find_message(&database, &index_lck[&env_hash]) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
debug!("not found {}", err);
|
||||
log::debug!("not found {}", err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let tags = debug!(message.tags().collect::<Vec<&CStr>>());
|
||||
//flags.set(f, value);
|
||||
let tags = message.tags().collect::<Vec<&CStr>>();
|
||||
|
||||
macro_rules! cstr {
|
||||
($l:literal) => {
|
||||
|
@ -928,7 +869,7 @@ impl MailBackend for NotmuchDb {
|
|||
let c_tag = CString::new(tag.as_str()).unwrap();
|
||||
remove_tag!(&c_tag.as_ref());
|
||||
}
|
||||
_ => debug!("flag_op is {:?}", op),
|
||||
_ => log::debug!("flag_op is {:?}", op),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1085,209 +1026,3 @@ impl BackendOp for NotmuchOp {
|
|||
Ok(Box::pin(async move { ret }))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Query<'s> {
|
||||
#[allow(dead_code)]
|
||||
lib: Arc<libloading::Library>,
|
||||
ptr: *mut notmuch_query_t,
|
||||
query_str: &'s str,
|
||||
}
|
||||
|
||||
impl<'s> Query<'s> {
|
||||
fn new(database: &DbConnection, query_str: &'s str) -> Result<Self> {
|
||||
let lib: Arc<libloading::Library> = database.lib.clone();
|
||||
let query_cstr = std::ffi::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(),
|
||||
)
|
||||
};
|
||||
if query.is_null() {
|
||||
return Err(Error::new("Could not create query. Out of memory?"));
|
||||
}
|
||||
Ok(Query {
|
||||
lib,
|
||||
ptr: query,
|
||||
query_str,
|
||||
})
|
||||
}
|
||||
|
||||
fn count(&self) -> Result<u32> {
|
||||
let mut count = 0_u32;
|
||||
unsafe {
|
||||
try_call!(
|
||||
self.lib,
|
||||
call!(self.lib, notmuch_query_count_messages)(
|
||||
self.ptr,
|
||||
std::ptr::addr_of_mut!(count)
|
||||
)
|
||||
)
|
||||
.map_err(|err| err.0)?;
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
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,
|
||||
std::ptr::addr_of_mut!(messages),
|
||||
)
|
||||
};
|
||||
if status != 0 {
|
||||
return Err(Error::new(format!(
|
||||
"Search for {} returned {}",
|
||||
self.query_str, status,
|
||||
)));
|
||||
}
|
||||
assert!(!messages.is_null());
|
||||
Ok(MessageIterator {
|
||||
messages,
|
||||
lib: self.lib.clone(),
|
||||
_ph: std::marker::PhantomData,
|
||||
is_from_thread: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Query<'_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
call!(self.lib, notmuch_query_destroy)(self.ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait MelibQueryToNotmuchQuery {
|
||||
fn query_to_string(&self, ret: &mut String);
|
||||
}
|
||||
|
||||
impl MelibQueryToNotmuchQuery for crate::search::Query {
|
||||
fn query_to_string(&self, ret: &mut String) {
|
||||
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('"');
|
||||
}
|
||||
InReplyTo(_s) | References(_s) | AllAddresses(_s) => {}
|
||||
/* * * * */
|
||||
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 => todo!(),
|
||||
AnsweredBy { .. } => todo!(),
|
||||
Larger { .. } => todo!(),
|
||||
Smaller { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* meli - notmuch backend
|
||||
*
|
||||
* Copyright 2019 - 2023 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{ffi::CString, ptr::NonNull, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
notmuch::{
|
||||
ffi::{
|
||||
self, notmuch_query_count_messages, notmuch_query_create, notmuch_query_destroy,
|
||||
notmuch_query_search_messages, notmuch_status_to_string,
|
||||
},
|
||||
DbConnection, MessageIterator,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Query<'s> {
|
||||
pub lib: Arc<libloading::Library>,
|
||||
pub ptr: NonNull<ffi::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<libloading::Library> = database.lib.clone();
|
||||
let query_cstr = CString::new(query_str)?;
|
||||
let query: *mut ffi::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 ffi::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 {}",
|
||||
self.query_str, status,
|
||||
)));
|
||||
}
|
||||
assert!(!messages.is_null());
|
||||
Ok(MessageIterator {
|
||||
messages,
|
||||
lib: self.lib.clone(),
|
||||
_ph: std::marker::PhantomData,
|
||||
is_from_thread: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
impl MelibQueryToNotmuchQuery for crate::search::Query {
|
||||
fn query_to_string(&self, ret: &mut String) {
|
||||
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('"');
|
||||
}
|
||||
InReplyTo(_s) | References(_s) | AllAddresses(_s) => {}
|
||||
/* * * * */
|
||||
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 => todo!(),
|
||||
AnsweredBy { .. } => todo!(),
|
||||
Larger { .. } => todo!(),
|
||||
Smaller { .. } => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,9 +20,13 @@
|
|||
*/
|
||||
|
||||
use super::*;
|
||||
use crate::notmuch::ffi::{
|
||||
notmuch_message_get_filename, notmuch_message_get_tags, notmuch_tags_destroy, notmuch_tags_get,
|
||||
notmuch_tags_move_to_next, notmuch_tags_valid,
|
||||
};
|
||||
|
||||
pub struct TagIterator<'m> {
|
||||
pub tags: *mut notmuch_tags_t,
|
||||
pub tags: *mut ffi::notmuch_tags_t,
|
||||
pub message: &'m Message<'m>,
|
||||
}
|
||||
|
||||
|
|
|
@ -19,11 +19,18 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use super::*;
|
||||
use crate::thread::ThreadHash;
|
||||
use crate::{
|
||||
notmuch::ffi::{
|
||||
notmuch_thread_destroy, notmuch_thread_get_messages, notmuch_thread_get_newest_date,
|
||||
notmuch_thread_get_thread_id, notmuch_thread_get_total_messages, notmuch_threads_get,
|
||||
notmuch_threads_move_to_next, notmuch_threads_valid,
|
||||
},
|
||||
thread::ThreadHash,
|
||||
};
|
||||
|
||||
pub struct Thread<'query> {
|
||||
pub lib: Arc<libloading::Library>,
|
||||
pub ptr: *mut notmuch_thread_t,
|
||||
pub ptr: *mut ffi::notmuch_thread_t,
|
||||
pub _ph: std::marker::PhantomData<*const Query<'query>>,
|
||||
}
|
||||
|
||||
|
@ -65,7 +72,7 @@ impl Drop for Thread<'_> {
|
|||
|
||||
pub struct ThreadsIterator<'query> {
|
||||
pub lib: Arc<libloading::Library>,
|
||||
pub threads: *mut notmuch_threads_t,
|
||||
pub threads: *mut ffi::notmuch_threads_t,
|
||||
pub _ph: std::marker::PhantomData<*const Query<'query>>,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue