Browse Source

melib/MailBackend: add copy_messages,set_flags,delete_messages methods

master
Manos Pitsidianakis 1 year ago
parent
commit
00acba7717
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 71
      melib/src/backends.rs
  2. 196
      melib/src/backends/imap.rs
  3. 92
      melib/src/backends/imap/operations.rs
  4. 8
      melib/src/backends/jmap/operations.rs
  5. 50
      melib/src/backends/maildir.rs
  6. 70
      melib/src/backends/maildir/backend.rs
  7. 8
      melib/src/backends/mbox.rs
  8. 300
      melib/src/backends/notmuch.rs
  9. 32
      melib/src/email.rs
  10. 361
      src/components/mail/listing.rs
  11. 21
      src/components/mail/listing/plain.rs
  12. 20
      src/components/mail/view.rs
  13. 13
      src/conf/accounts.rs
  14. 8
      src/plugins/backend.rs

71
melib/src/backends.rs

@ -335,6 +335,31 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
mailbox_hash: MailboxHash,
flags: Option<Flag>,
) -> ResultFuture<()>;
fn copy_messages(
&mut self,
_env_hashes: EnvelopeHashBatch,
_source_mailbox_hash: MailboxHash,
_destination_mailbox_hash: MailboxHash,
_move_: bool,
_destination_flags: Option<Flag>,
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn set_flags(
&mut self,
_env_hashes: EnvelopeHashBatch,
_mailbox_hash: MailboxHash,
_flags: SmallVec<[(std::result::Result<Flag, String>, bool); 8]>,
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn delete_messages(
&self,
_env_hashes: EnvelopeHashBatch,
_mailbox_hash: MailboxHash,
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn delete(&self, _env_hash: EnvelopeHash, _mailbox_hash: MailboxHash) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
@ -431,12 +456,7 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
/// ```
pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
fn as_bytes(&mut self) -> ResultFuture<Vec<u8>>;
fn copy_to(&self, _mailbox_hash: MailboxHash) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn fetch_flags(&self) -> ResultFuture<Flag>;
fn set_flag(&mut self, flag: Flag, value: bool) -> ResultFuture<()>;
fn set_tag(&mut self, tag: String, value: bool) -> ResultFuture<()>;
}
/// Wrapper for BackendOps that are to be set read-only.
@ -461,12 +481,6 @@ impl BackendOp for ReadOnlyOp {
fn fetch_flags(&self) -> ResultFuture<Flag> {
self.op.fetch_flags()
}
fn set_flag(&mut self, _flag: Flag, _value: bool) -> ResultFuture<()> {
Err(MeliError::new("read-only set."))
}
fn set_tag(&mut self, _tag: String, _value: bool) -> ResultFuture<()> {
Err(MeliError::new("read-only set."))
}
}
#[derive(Debug, Copy, Hash, Eq, Clone, Serialize, Deserialize, PartialEq)]
@ -651,3 +665,38 @@ impl std::fmt::Display for MailboxPermissions {
write!(fmt, "{:#?}", self)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnvelopeHashBatch {
pub first: EnvelopeHash,
pub rest: SmallVec<[EnvelopeHash; 64]>,
}
impl From<EnvelopeHash> for EnvelopeHashBatch {
fn from(value: EnvelopeHash) -> Self {
EnvelopeHashBatch {
first: value,
rest: SmallVec::new(),
}
}
}
impl std::convert::TryFrom<&[EnvelopeHash]> for EnvelopeHashBatch {
type Error = ();
fn try_from(value: &[EnvelopeHash]) -> std::result::Result<Self, Self::Error> {
if value.is_empty() {
return Err(());
}
Ok(EnvelopeHashBatch {
first: value[0],
rest: value[1..].iter().cloned().collect(),
})
}
}
impl EnvelopeHashBatch {
fn iter(&self) -> impl std::iter::Iterator<Item = EnvelopeHash> + '_ {
std::iter::once(self.first).chain(self.rest.iter().cloned())
}
}

196
melib/src/backends/imap.rs

@ -446,6 +446,202 @@ impl MailBackend for ImapType {
}))
}
fn copy_messages(
&mut self,
env_hashes: EnvelopeHashBatch,
source_mailbox_hash: MailboxHash,
destination_mailbox_hash: MailboxHash,
move_: bool,
destination_flags: Option<Flag>,
) -> ResultFuture<()> {
let uid_store = self.uid_store.clone();
let connection = self.connection.clone();
Ok(Box::pin(async move {
let dest_path = {
let mailboxes = uid_store.mailboxes.lock().await;
let mailbox = mailboxes
.get(&source_mailbox_hash)
.ok_or_else(|| MeliError::new("Source mailbox not found"))?;
if move_ && !mailbox.permissions.lock().unwrap().delete_messages {
return Err(MeliError::new(format!(
"You are not allowed to delete messages from mailbox {}",
mailbox.path()
)));
}
let mailbox = mailboxes
.get(&destination_mailbox_hash)
.ok_or_else(|| MeliError::new("Destination mailbox not found"))?;
if !mailbox.permissions.lock().unwrap().create_messages {
return Err(MeliError::new(format!(
"You are not allowed to delete messages from mailbox {}",
mailbox.path()
)));
}
mailbox.imap_path().to_string()
};
let mut response = String::with_capacity(8 * 1024);
let mut conn = connection.lock().await;
conn.select_mailbox(source_mailbox_hash, &mut response, false)
.await?;
let command = {
let hash_index_lck = uid_store.hash_index.lock().unwrap();
let mut cmd = format!("UID COPY {}", hash_index_lck[&env_hashes.first].0);
for env_hash in &env_hashes.rest {
cmd = format!("{},{}", cmd, hash_index_lck[env_hash].0);
}
format!("{} \"{}\"", cmd, dest_path)
};
conn.send_command(command.as_bytes()).await?;
conn.read_response(&mut response, RequiredResponses::empty())
.await?;
if let Some(_flags) = destination_flags {
//FIXME
}
if move_ {
let command = {
let hash_index_lck = uid_store.hash_index.lock().unwrap();
let mut cmd = format!("UID STORE {}", hash_index_lck[&env_hashes.first].0);
for env_hash in env_hashes.rest {
cmd = format!("{},{}", cmd, hash_index_lck[&env_hash].0);
}
format!("{} +FLAGS.SILENT (\\Deleted)", cmd)
};
conn.send_command(command.as_bytes()).await?;
conn.read_response(&mut response, RequiredResponses::empty())
.await?;
}
Ok(())
}))
}
fn set_flags(
&mut self,
env_hashes: EnvelopeHashBatch,
mailbox_hash: MailboxHash,
flags: SmallVec<[(std::result::Result<Flag, String>, bool); 8]>,
) -> ResultFuture<()> {
let connection = self.connection.clone();
let uid_store = self.uid_store.clone();
Ok(Box::pin(async move {
let mut response = String::with_capacity(8 * 1024);
let mut conn = connection.lock().await;
conn.select_mailbox(mailbox_hash, &mut response, false)
.await?;
if flags.iter().any(|(_, b)| *b) {
/* Set flags/tags to true */
let command = {
let hash_index_lck = uid_store.hash_index.lock().unwrap();
let mut cmd = format!("UID STORE {}", hash_index_lck[&env_hashes.first].0);
for env_hash in &env_hashes.rest {
cmd = format!("{},{}", cmd, hash_index_lck[env_hash].0);
}
cmd = format!("{} +FLAGS.SILENT (", cmd);
for (f, v) in flags.iter() {
if !*v {
continue;
}
match f {
Ok(flag) if *flag == Flag::REPLIED => {
cmd.push_str("\\Answered ");
}
Ok(flag) if *flag == Flag::FLAGGED => {
cmd.push_str("\\Flagged ");
}
Ok(flag) if *flag == Flag::TRASHED => {
cmd.push_str("\\Deleted ");
}
Ok(flag) if *flag == Flag::SEEN => {
cmd.push_str("\\Seen ");
}
Ok(flag) if *flag == Flag::DRAFT => {
cmd.push_str("\\Draft ");
}
Ok(_) => {
crate::log(
format!(
"Application error: more than one flag bit set in set_flags: {:?}", flags
),
crate::ERROR,
);
return Err(MeliError::new(format!(
"Application error: more than one flag bit set in set_flags: {:?}", flags
)));
}
Err(tag) => {
cmd.push_str(tag);
cmd.push(' ');
}
}
}
// pop last space
cmd.pop();
cmd.push(')');
cmd
};
conn.send_command(command.as_bytes()).await?;
conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)
.await?;
}
if flags.iter().any(|(_, b)| !*b) {
/* Set flags/tags to false */
let command = {
let hash_index_lck = uid_store.hash_index.lock().unwrap();
let mut cmd = format!("UID STORE {}", hash_index_lck[&env_hashes.first].0);
for env_hash in &env_hashes.rest {
cmd = format!("{},{}", cmd, hash_index_lck[env_hash].0);
}
cmd = format!("{} -FLAGS.SILENT (", cmd);
for (f, v) in flags.iter() {
if *v {
continue;
}
match f {
Ok(flag) if *flag == Flag::REPLIED => {
cmd.push_str("\\Answered ");
}
Ok(flag) if *flag == Flag::FLAGGED => {
cmd.push_str("\\Flagged ");
}
Ok(flag) if *flag == Flag::TRASHED => {
cmd.push_str("\\Deleted ");
}
Ok(flag) if *flag == Flag::SEEN => {
cmd.push_str("\\Seen ");
}
Ok(flag) if *flag == Flag::DRAFT => {
cmd.push_str("\\Draft ");
}
Ok(_) => {
crate::log(
format!(
"Application error: more than one flag bit set in set_flags: {:?}", flags
),
crate::ERROR,
);
return Err(MeliError::new(format!(
"Application error: more than one flag bit set in set_flags: {:?}", flags
)));
}
Err(tag) => {
cmd.push_str(tag);
cmd.push(' ');
}
}
}
// pop last space
cmd.pop();
cmd.push(')');
cmd
};
conn.send_command(command.as_bytes()).await?;
conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)
.await?;
}
Ok(())
}))
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}

92
melib/src/backends/imap/operations.rs

@ -23,7 +23,7 @@ use super::*;
use crate::backends::*;
use crate::email::*;
use crate::error::{MeliError, Result};
use crate::error::MeliError;
use std::sync::Arc;
/// `BackendOp` implementor for Imap
@ -159,94 +159,4 @@ impl BackendOp for ImapOp {
}
}))
}
fn set_flag(
&mut self,
flag: Flag,
value: bool,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
let flags = self.fetch_flags()?;
let mut response = String::with_capacity(8 * 1024);
let connection = self.connection.clone();
let mailbox_hash = self.mailbox_hash;
let uid = self.uid;
let uid_store = self.uid_store.clone();
Ok(Box::pin(async move {
let mut flags = flags.await?;
flags.set(flag, value);
let mut conn = connection.lock().await;
conn.select_mailbox(mailbox_hash, &mut response, false)
.await?;
debug!(&response);
conn.send_command(
format!(
"UID STORE {} FLAGS.SILENT ({})",
uid,
flags_to_imap_list!(flags)
)
.as_bytes(),
)
.await?;
conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)
.await?;
debug!(&response);
match protocol_parser::uid_fetch_flags_response(response.as_bytes())
.map(|(_, v)| v)
.map_err(MeliError::from)
{
Ok(v) => {
if v.len() == 1 {
debug!("responses len is {}", v.len());
let (_uid, (_flags, _)) = v[0];
assert_eq!(_uid, uid);
}
}
Err(e) => Err(e)?,
}
let mut bytes_cache = uid_store.byte_cache.lock()?;
let cache = bytes_cache.entry(uid).or_default();
cache.flags = Some(flags);
Ok(())
}))
}
fn set_tag(
&mut self,
tag: String,
value: bool,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
let mut response = String::with_capacity(8 * 1024);
let connection = self.connection.clone();
let mailbox_hash = self.mailbox_hash;
let uid = self.uid;
let uid_store = self.uid_store.clone();
Ok(Box::pin(async move {
let mut conn = connection.lock().await;
conn.select_mailbox(mailbox_hash, &mut response, false)
.await?;
conn.send_command(
format!(
"UID STORE {} {}FLAGS.SILENT ({})",
uid,
if value { "+" } else { "-" },
&tag
)
.as_bytes(),
)
.await?;
conn.read_response(&mut response, RequiredResponses::STORE_REQUIRED)
.await?;
protocol_parser::uid_fetch_flags_response(response.as_bytes())
.map(|(_, v)| v)
.map_err(MeliError::from)?;
let hash = tag_hash!(tag);
if value {
uid_store.tag_index.write().unwrap().insert(hash, tag);
} else {
uid_store.tag_index.write().unwrap().remove(&hash);
}
Ok(())
}))
}
}

8
melib/src/backends/jmap/operations.rs

@ -91,12 +91,4 @@ impl BackendOp for JmapOp {
fn fetch_flags(&self) -> ResultFuture<Flag> {
Ok(Box::pin(async { Ok(Flag::default()) }))
}
fn set_flag(&mut self, _f: Flag, _value: bool) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented"))
}
fn set_tag(&mut self, _tag: String, _value: bool) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented"))
}
}

50
melib/src/backends/maildir.rs

@ -104,56 +104,6 @@ impl<'a> BackendOp for MaildirOp {
let ret = Ok(path.flags());
Ok(Box::pin(async move { ret }))
}
fn set_flag(&mut self, f: Flag, value: bool) -> ResultFuture<()> {
let mut flags = futures::executor::block_on(self.fetch_flags()?)?;
let old_hash = self.hash;
let mailbox_hash = self.mailbox_hash;
let hash_index = self.hash_index.clone();
let path = self.path();
Ok(Box::pin(async move {
let path = path;
let path = path.to_str().unwrap(); // Assume UTF-8 validity
let idx: usize = path
.rfind(":2,")
.ok_or_else(|| MeliError::new(format!("Invalid email filename: {:?}", path)))?
+ 3;
let mut new_name: String = path[..idx].to_string();
flags.set(f, value);
if !(flags & Flag::DRAFT).is_empty() {
new_name.push('D');
}
if !(flags & Flag::FLAGGED).is_empty() {
new_name.push('F');
}
if !(flags & Flag::PASSED).is_empty() {
new_name.push('P');
}
if !(flags & Flag::REPLIED).is_empty() {
new_name.push('R');
}
if !(flags & Flag::SEEN).is_empty() {
new_name.push('S');
}
if !(flags & Flag::TRASHED).is_empty() {
new_name.push('T');
}
let new_name: PathBuf = new_name.into();
let mut map = hash_index.lock().unwrap();
let map = map.entry(mailbox_hash).or_default();
map.entry(old_hash).or_default().modified = Some(PathMod::Path(new_name.clone()));
debug!("renaming {:?} to {:?}", path, new_name);
fs::rename(&path, &new_name)?;
debug!("success in rename");
Ok(())
}))
}
fn set_tag(&mut self, _tag: String, _value: bool) -> ResultFuture<()> {
Err(MeliError::new("Maildir doesn't support tags."))
}
}
#[derive(Debug, Default, Clone)]

70
melib/src/backends/maildir/backend.rs

@ -697,6 +697,76 @@ impl MailBackend for MaildirType {
}))
}
fn set_flags(
&mut self,
env_hashes: EnvelopeHashBatch,
mailbox_hash: MailboxHash,
flags: SmallVec<[(std::result::Result<Flag, String>, bool); 8]>,
) -> ResultFuture<()> {
let hash_index = self.hash_indexes.clone();
if flags.iter().any(|(f, _)| f.is_err()) {
return Err(MeliError::new("Maildir doesn't support tags."));
}
Ok(Box::pin(async move {
let mut hash_indexes_lck = hash_index.lock().unwrap();
let hash_index = hash_indexes_lck.entry(mailbox_hash).or_default();
for env_hash in env_hashes.iter() {
let _path = {
if !hash_index.contains_key(&env_hash) {
continue;
}
if let Some(modif) = &hash_index[&env_hash].modified {
match modif {
PathMod::Path(ref path) => path.clone(),
PathMod::Hash(hash) => hash_index[&hash].to_path_buf(),
}
} else {
hash_index[&env_hash].to_path_buf()
}
};
let mut env_flags = _path.flags();
let path = _path.to_str().unwrap(); // Assume UTF-8 validity
let idx: usize = path
.rfind(":2,")
.ok_or_else(|| MeliError::new(format!("Invalid email filename: {:?}", path)))?
+ 3;
let mut new_name: String = path[..idx].to_string();
for (f, value) in flags.iter() {
env_flags.set(*f.as_ref().unwrap(), *value);
}
if !(env_flags & Flag::DRAFT).is_empty() {
new_name.push('D');
}
if !(env_flags & Flag::FLAGGED).is_empty() {
new_name.push('F');
}
if !(env_flags & Flag::PASSED).is_empty() {
new_name.push('P');
}
if !(env_flags & Flag::REPLIED).is_empty() {
new_name.push('R');
}
if !(env_flags & Flag::SEEN).is_empty() {
new_name.push('S');
}
if !(env_flags & Flag::TRASHED).is_empty() {
new_name.push('T');
}
let new_name: PathBuf = new_name.into();
hash_index.entry(env_hash).or_default().modified =
Some(PathMod::Path(new_name.clone()));
debug!("renaming {:?} to {:?}", path, new_name);
fs::rename(&path, &new_name)?;
debug!("success in rename");
}
Ok(())
}))
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}

8
melib/src/backends/mbox.rs

@ -248,14 +248,6 @@ impl BackendOp for MboxOp {
}
Ok(Box::pin(async move { Ok(flags) }))
}
fn set_flag(&mut self, _flag: Flag, _value: bool) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn set_tag(&mut self, _tag: String, _value: bool) -> ResultFuture<()> {
Err(MeliError::new("mbox doesn't support tags."))
}
}
#[derive(Debug, Clone, Copy)]

300
melib/src/backends/notmuch.rs

@ -75,14 +75,14 @@ impl Error for NotmuchError {
macro_rules! try_call {
($lib:expr, $call:expr) => {{
let status = unsafe { $call };
let status = $call;
if status == _notmuch_status_NOTMUCH_STATUS_SUCCESS {
Ok(())
} else {
let c_str = unsafe { call!($lib, notmuch_status_to_string)(status) };
Err(NotmuchError(unsafe {
CStr::from_ptr(c_str).to_string_lossy().into_owned()
}))
let c_str = call!($lib, notmuch_status_to_string)(status);
Err(NotmuchError(
CStr::from_ptr(c_str).to_string_lossy().into_owned(),
))
}
}};
}
@ -551,14 +551,16 @@ impl MailBackend for NotmuchDb {
let database_lck = database.inner.read().unwrap();
index.write().unwrap().retain(|&env_hash, msg_id| {
let mut message: *mut notmuch_message_t = std::ptr::null_mut();
if let Err(err) = try_call!(
lib,
call!(lib, notmuch_database_find_message)(
*database_lck,
msg_id.as_ptr(),
&mut message as *mut _,
if let Err(err) = unsafe {
try_call!(
lib,
call!(lib, notmuch_database_find_message)(
*database_lck,
msg_id.as_ptr(),
&mut message as *mut _,
)
)
) {
} {
debug!(err);
false
} else {
@ -638,6 +640,135 @@ impl MailBackend for NotmuchDb {
Ok(Box::pin(async { Ok(()) }))
}
fn set_flags(
&mut self,
env_hashes: EnvelopeHashBatch,
_mailbox_hash: MailboxHash,
flags: SmallVec<[(std::result::Result<Flag, String>, bool); 8]>,
) -> ResultFuture<()> {
let database = Self::new_connection(self.path.as_path(), self.lib.clone(), true)?;
let tag_index = self.tag_index.clone();
let mut index_lck = self.index.write().unwrap();
for env_hash in env_hashes.iter() {
let mut message: *mut notmuch_message_t = std::ptr::null_mut();
unsafe {
call!(self.lib, notmuch_database_find_message)(
*database.inner.read().unwrap(),
index_lck[&env_hash].as_ptr(),
&mut message as *mut _,
)
};
if message.is_null() {
return Err(MeliError::new(format!(
"Error, message with path {:?} not found in notmuch database.",
index_lck[&env_hash]
)));
}
let tags = TagIterator::new(self.lib.clone(), message).collect::<Vec<&CStr>>();
//flags.set(f, value);
macro_rules! cstr {
($l:literal) => {
&CStr::from_bytes_with_nul_unchecked($l)
};
}
macro_rules! add_tag {
($l:literal) => {{
add_tag!(unsafe { cstr!($l) })
}};
($l:expr) => {{
let l = $l;
if tags.contains(l) {
continue;
}
if let Err(err) = unsafe {
try_call!(
self.lib,
call!(self.lib, notmuch_message_add_tag)(message, l.as_ptr())
)
} {
return Err(
MeliError::new("Could not set tag.").set_source(Some(Arc::new(err)))
);
}
}};
}
macro_rules! remove_tag {
($l:literal) => {{
remove_tag!(unsafe { cstr!($l) })
}};
($l:expr) => {{
let l = $l;
if !tags.contains(l) {
continue;
}
if let Err(err) = unsafe {
try_call!(
self.lib,
call!(self.lib, notmuch_message_remove_tag)(message, l.as_ptr())
)
} {
return Err(
MeliError::new("Could not set tag.").set_source(Some(Arc::new(err)))
);
}
}};
}
for (f, v) in flags.iter() {
let value = *v;
match f {
Ok(Flag::DRAFT) if value => add_tag!(b"draft\0"),
Ok(Flag::DRAFT) => remove_tag!(b"draft\0"),
Ok(Flag::FLAGGED) if value => add_tag!(b"flagged\0"),
Ok(Flag::FLAGGED) => remove_tag!(b"flagged\0"),
Ok(Flag::PASSED) if value => add_tag!(b"passed\0"),
Ok(Flag::PASSED) => remove_tag!(b"passed\0"),
Ok(Flag::REPLIED) if value => add_tag!(b"replied\0"),
Ok(Flag::REPLIED) => remove_tag!(b"replied\0"),
Ok(Flag::SEEN) if value => remove_tag!(b"unread\0"),
Ok(Flag::SEEN) => add_tag!(b"unread\0"),
Ok(Flag::TRASHED) if value => add_tag!(b"trashed\0"),
Ok(Flag::TRASHED) => remove_tag!(b"trashed\0"),
Ok(_) => debug!("flags is {:?} value = {}", f, value),
Err(tag) if value => {
let c_tag = CString::new(tag.as_str()).unwrap();
add_tag!(&c_tag.as_ref());
}
Err(tag) => {
let c_tag = CString::new(tag.as_str()).unwrap();
add_tag!(&c_tag.as_ref());
}
}
}
/* Update message filesystem path. */
if let Err(err) = unsafe {
try_call!(
self.lib,
call!(self.lib, notmuch_message_tags_to_maildir_flags)(message)
)
} {
return Err(MeliError::new("Could not set flags.").set_source(Some(Arc::new(err))));
}
let msg_id = unsafe { call!(self.lib, notmuch_message_get_message_id)(message) };
let c_str = unsafe { CStr::from_ptr(msg_id) };
if let Some(p) = index_lck.get_mut(&env_hash) {
*p = c_str.into();
}
}
for (f, v) in flags.iter() {
if let (Err(tag), true) = (f, v) {
let hash = tag_hash!(tag);
tag_index.write().unwrap().insert(hash, tag.to_string());
}
}
Ok(Box::pin(async { Ok(()) }))
}
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
@ -691,139 +822,6 @@ impl BackendOp for NotmuchOp {
let (flags, _tags) = TagIterator::new(self.lib.clone(), message).collect_flags_and_tags();
Ok(Box::pin(async move { Ok(flags) }))
}
fn set_flag(&mut self, f: Flag, value: bool) -> ResultFuture<()> {
let mut flags = futures::executor::block_on(self.fetch_flags()?)?;
flags.set(f, value);
let mut message: *mut notmuch_message_t = std::ptr::null_mut();
let mut index_lck = self.index.write().unwrap();
unsafe {
call!(self.lib, notmuch_database_find_message)(
*self.database.inner.read().unwrap(),
index_lck[&self.hash].as_ptr(),
&mut message as *mut _,
)
};
if message.is_null() {
return Err(MeliError::new(format!(
"Error, message with path {:?} not found in notmuch database.",
index_lck[&self.hash]
)));
}
// TODO: freeze/thaw message.
let tags = TagIterator::new(self.lib.clone(), message).collect::<Vec<&CStr>>();
debug!(&tags);
macro_rules! cstr {
($l:literal) => {
CStr::from_bytes_with_nul_unchecked($l)
};
}
macro_rules! add_tag {
($l:literal) => {{
if tags.contains(unsafe { &cstr!($l) }) {
return Ok(Box::pin(async { Ok(()) }));
}
if let Err(err) = try_call!(
self.lib,
call!(self.lib, notmuch_message_add_tag)(message, cstr!($l).as_ptr())
) {
return Err(
MeliError::new("Could not set tag.").set_source(Some(Arc::new(err)))
);
}
}};
}
macro_rules! remove_tag {
($l:literal) => {{
if !tags.contains(unsafe { &cstr!($l) }) {
return Ok(Box::pin(async { Ok(()) }));
}
if let Err(err) = try_call!(
self.lib,
call!(self.lib, notmuch_message_remove_tag)(message, cstr!($l).as_ptr())
) {
return Err(
MeliError::new("Could not set tag.").set_source(Some(Arc::new(err)))
);
}
}};
}
match f {
Flag::DRAFT if value => add_tag!(b"draft\0"),
Flag::DRAFT => remove_tag!(b"draft\0"),
Flag::FLAGGED if value => add_tag!(b"flagged\0"),
Flag::FLAGGED => remove_tag!(b"flagged\0"),
Flag::PASSED if value => add_tag!(b"passed\0"),
Flag::PASSED => remove_tag!(b"passed\0"),
Flag::REPLIED if value => add_tag!(b"replied\0"),
Flag::REPLIED => remove_tag!(b"replied\0"),
Flag::SEEN if value => remove_tag!(b"unread\0"),
Flag::SEEN => add_tag!(b"unread\0"),
Flag::TRASHED if value => add_tag!(b"trashed\0"),
Flag::TRASHED => remove_tag!(b"trashed\0"),
_ => debug!("flags is {:?} value = {}", f, value),
}
/* Update message filesystem path. */
if let Err(err) = try_call!(
self.lib,
call!(self.lib, notmuch_message_tags_to_maildir_flags)(message)
) {
return Err(MeliError::new("Could not set tag.").set_source(Some(Arc::new(err))));
}
let msg_id = unsafe { call!(self.lib, notmuch_message_get_message_id)(message) };
let c_str = unsafe { CStr::from_ptr(msg_id) };
if let Some(p) = index_lck.get_mut(&self.hash) {
*p = c_str.into();
}
Ok(Box::pin(async { Ok(()) }))
}
fn set_tag(&mut self, tag: String, value: bool) -> ResultFuture<()> {
let mut message: *mut notmuch_message_t = std::ptr::null_mut();
let index_lck = self.index.read().unwrap();
unsafe {
call!(self.lib, notmuch_database_find_message)(
*self.database.inner.read().unwrap(),
index_lck[&self.hash].as_ptr(),
&mut message as *mut _,
)
};
if message.is_null() {
return Err(MeliError::new(format!(
"Error, message with path {:?} not found in notmuch database.",
index_lck[&self.hash]
)));
}
let c_tag = CString::new(tag.as_str()).unwrap();
if value {
if let Err(err) = try_call!(
self.lib,
call!(self.lib, notmuch_message_add_tag)(message, c_tag.as_ptr(),)
) {
return Err(MeliError::new("Could not set tag.").set_source(Some(Arc::new(err))));
}
debug!("added tag {}", &tag);
} else {
if let Err(err) = try_call!(
self.lib,
call!(self.lib, notmuch_message_remove_tag)(message, c_tag.as_ptr(),)
) {
return Err(MeliError::new("Could not set tag.").set_source(Some(Arc::new(err))));
}
debug!("removed tag {}", &tag);
}
let hash = tag_hash!(tag);
if value {
self.tag_index.write().unwrap().insert(hash, tag);
}
Ok(Box::pin(async { Ok(()) }))
}
}
pub struct MessageIterator {
@ -976,11 +974,13 @@ impl<'s> Query<'s> {
fn count(&self) -> Result<u32> {
let mut count = 0_u32;
try_call!(
self.lib,
call!(self.lib, notmuch_query_count_messages)(self.ptr, &mut count as *mut _)
)
.map_err(|err| err.0)?;
unsafe {
try_call!(
self.lib,
call!(self.lib, notmuch_query_count_messages)(self.ptr, &mut count as *mut _)
)
.map_err(|err| err.0)?;
}
Ok(count)
}

32
melib/src/email.rs

@ -49,10 +49,8 @@ use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::future::Future;
use std::hash::Hasher;
use std::option::Option;
use std::pin::Pin;
use std::str;
use std::string::String;
@ -595,14 +593,8 @@ impl Envelope {
pub fn set_datetime(&mut self, new_val: UnixTimestamp) {
self.timestamp = new_val;
}
pub fn set_flag(
&mut self,
f: Flag,
value: bool,
mut operation: Box<dyn BackendOp>,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
pub fn set_flag(&mut self, f: Flag, value: bool) {
self.flags.set(f, value);
operation.set_flag(f, value)
}
pub fn set_flags(&mut self, f: Flag) {
self.flags = f;
@ -610,25 +602,11 @@ impl Envelope {
pub fn flags(&self) -> Flag {
self.flags
}
pub fn set_seen(
&mut self,
operation: Box<dyn BackendOp>,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
if !self.flags.contains(Flag::SEEN) {
self.set_flag(Flag::SEEN, true, operation)
} else {
Ok(Box::pin(async { Ok(()) }))
}
pub fn set_seen(&mut self) {
self.set_flag(Flag::SEEN, true)
}
pub fn set_unseen(
&mut self,
operation: Box<dyn BackendOp>,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
if self.flags.contains(Flag::SEEN) {
self.set_flag(Flag::SEEN, false, operation)
} else {
Ok(Box::pin(async { Ok(()) }))
}
pub fn set_unseen(&mut self) {
self.set_flag(Flag::SEEN, false)
}
pub fn is_seen(&self) -> bool {
self.flags.contains(Flag::SEEN)

361
src/components/mail/listing.rs

@ -22,8 +22,10 @@
use super::*;
use crate::conf::accounts::JobRequest;
use crate::types::segment_tree::SegmentTree;
use melib::backends::EnvelopeHashBatch;
use smallvec::SmallVec;
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::ops::{Deref, DerefMut};
mod conversations;
@ -141,248 +143,201 @@ pub trait MailListingTrait: ListingTrait {
fn perform_action(
&mut self,
context: &mut Context,
thread_hash: ThreadHash,
thread_hashes: SmallVec<[ThreadHash; 8]>,
a: &ListingAction,
) {
let account_pos = self.coordinates().0;
let account = &mut context.accounts[account_pos];
let mut envs_to_set: SmallVec<[EnvelopeHash; 8]> = SmallVec::new();
let mailbox_hash = self.coordinates().1;
for (_, h) in account
.collection
.get_threads(mailbox_hash)
.thread_group_iter(thread_hash)
{
envs_to_set.push(
account.collection.get_threads(mailbox_hash).thread_nodes()[&h]
.message()
.unwrap(),
);
let threads_lck = account.collection.get_threads(mailbox_hash);
for thread_hash in thread_hashes {
for (_, h) in threads_lck.thread_group_iter(thread_hash) {
envs_to_set.push(threads_lck.thread_nodes()[&h].message().unwrap());
}
self.row_updates().push(thread_hash);
}
}
for env_hash in envs_to_set {
let account = &mut context.accounts[self.coordinates().0];
let mut op =
match account.operation(env_hash) {
Ok(op) => op,
if envs_to_set.is_empty() {
return;
}
let env_hashes = EnvelopeHashBatch::try_from(envs_to_set.as_slice()).unwrap();
match a {
ListingAction::SetSeen => {
let job = account.backend.write().unwrap().set_flags(
env_hashes.clone(),
mailbox_hash,
smallvec::smallvec![(Ok(Flag::SEEN), true)],
);
match job {
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(err.to_string()),
));
continue;
}
};
let mut envelope: EnvelopeRefMut = account.collection.get_env_mut(env_hash);
match a {
ListingAction::SetSeen => match envelope.set_seen(op) {
Err(e) => {
Ok(fut) => {
let (channel, handle, job_id) = account.job_executor.spawn_specialized(fut);
account
.insert_job(job_id, JobRequest::SetFlags(env_hashes, handle, channel));
}
}
}
ListingAction::SetUnseen => {
let job = account.backend.write().unwrap().set_flags(
env_hashes.clone(),
mailbox_hash,
smallvec::smallvec![(Ok(Flag::SEEN), false)],
);
match job {
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(e.to_string()),
StatusEvent::DisplayMessage(err.to_string()),
));
}
Ok(fut) => {
let (rcvr, handle, job_id) = account.job_executor.spawn_specialized(fut);
let (channel, handle, job_id) = account.job_executor.spawn_specialized(fut);
account
.active_jobs
.insert(job_id, JobRequest::SetFlags(env_hash, handle, rcvr));
.insert_job(job_id, JobRequest::SetFlags(env_hashes, handle, channel));
}
},
ListingAction::SetUnseen => match envelope.set_unseen(op) {
Err(e) => {
}
}
ListingAction::Tag(Remove(ref tag_str)) => {
let job = account.backend.write().unwrap().set_flags(
env_hashes.clone(),
mailbox_hash,
smallvec::smallvec![(Err(tag_str.to_string()), false)],
);
match job {
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(e.to_string()),
StatusEvent::DisplayMessage(err.to_string()),
));
}
Ok(fut) => {
let (rcvr, handle, job_id) = account.job_executor.spawn_specialized(fut);
let (channel, handle, job_id) = account.job_executor.spawn_specialized(fut);
account
.active_jobs
.insert(job_id, JobRequest::SetFlags(env_hash, handle, rcvr));
}
},
ListingAction::Delete => {
drop(envelope);
match account.delete(env_hash, mailbox_hash) {
Err(err) => {
context.replies.push_back(UIEvent::Notification(
Some("Could not delete.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
}
Ok(fut) => {
let (rcvr, handle, job_id) =
account.job_executor.spawn_specialized(fut);
account
.active_jobs
.insert(job_id, JobRequest::DeleteMessage(env_hash, handle, rcvr));
}
.insert_job(job_id, JobRequest::SetFlags(env_hashes, handle, channel));
}
continue;
}
ListingAction::CopyTo(ref mailbox_path) => {
drop(envelope);
match account
.mailbox_by_path(mailbox_path)
.and_then(|mailbox_hash| op.copy_to(mailbox_hash))
{
Err(err) => {
context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
}
Ok(fut) => {
let (rcvr, handle, job_id) =
account.job_executor.spawn_specialized(fut);
account.active_jobs.insert(
job_id,
JobRequest::SaveMessage(mailbox_hash, handle, rcvr),
);
}
}
ListingAction::Tag(Add(ref tag_str)) => {
let job = account.backend.write().unwrap().set_flags(
env_hashes.clone(),
mailbox_hash,
smallvec::smallvec![(Err(tag_str.to_string()), true)],
);
match job {
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(err.to_string()),
));
}
continue;
}
ListingAction::CopyToOtherAccount(ref account_name, ref mailbox_path) => {
drop(envelope);
if let Err(err) = op.as_bytes().and_then(|bytes_fut| {
let account_pos = context
.accounts
.iter()
.position(|el| el.name() == account_name)
.ok_or_else(|| {
MeliError::new(format!(
"`{}` is not a valid account name",
account_name
))
})?;
let account = &mut context.accounts[account_pos];
let mailbox_hash = account.mailbox_by_path(mailbox_path)?;
let (rcvr, handle, job_id) =
account.job_executor.spawn_specialized(bytes_fut);
Ok(fut) => {
let (channel, handle, job_id) = account.job_executor.spawn_specialized(fut);
account
.active_jobs
.insert(job_id, JobRequest::CopyTo(mailbox_hash, handle, rcvr));
Ok(())
}) {
context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
.insert_job(job_id, JobRequest::SetFlags(env_hashes, handle, channel));
}
continue;
}
ListingAction::MoveTo(ref mailbox_path) => {
drop(envelope);
match account
.mailbox_by_path(mailbox_path)
.and_then(|mailbox_hash| op.copy_to(mailbox_hash))
{
Err(err) => {
context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
}
Ok(fut) => {
let (rcvr, handle, job_id) =
account.job_executor.spawn_specialized(fut);
account.active_jobs.insert(
job_id,
JobRequest::SaveMessage(mailbox_hash, handle, rcvr),
);
}
}
ListingAction::Delete => {
let job = account
.backend
.write()
.unwrap()
.delete_messages(env_hashes.clone(), mailbox_hash);
match job {
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(err.to_string()),
));
}
Ok(fut) => {
let (channel, handle, job_id) = account.job_executor.spawn_specialized(fut);
account.insert_job(
job_id,
JobRequest::DeleteMessages(env_hashes, handle, channel),
);
}
continue;
}
ListingAction::MoveToOtherAccount(ref account_name, ref mailbox_path) => {
drop(envelope);
if let Err(err) = op.as_bytes().and_then(|bytes_fut| {
let account_pos = context
.accounts
.iter()
.position(|el| el.name() == account_name)
.ok_or_else(|| {
MeliError::new(format!(
"`{}` is not a valid account name",
account_name
))
})?;
let account = &mut context.accounts[account_pos];
let mailbox_hash = account.mailbox_by_path(mailbox_path)?;
let (rcvr, handle, job_id) =
account.job_executor.spawn_specialized(bytes_fut);
account
.active_jobs
.insert(job_id, JobRequest::CopyTo(mailbox_hash, handle, rcvr));
Ok(())
}
ListingAction::CopyTo(ref mailbox_path) => {
match account
.mailbox_by_path(mailbox_path)
.and_then(|destination_mailbox_hash| {
account.backend.write().unwrap().copy_messages(
env_hashes,
mailbox_hash,
destination_mailbox_hash,
/* move? */ false,
/* flags */ None,
)
}) {
context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
Err(err) => {
context.replies.push_back(UIEvent::StatusEvent(
StatusEvent::DisplayMessage(err.to_string()),
));
return;
}
/*
account
.delete(env_hash, mailbox_hash)
.chain_err_summary(|| {
"Envelope was copied but removal from original account failed"
})
*/
continue;
}
ListingAction::Tag(Remove(ref tag_str)) => {
match op.set_tag(tag_str.to_string(), false) {
Err(err) => {