From 00acba771759fbee2ad96d45acd2d52141ae1f03 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 24 Jul 2020 20:17:06 +0300 Subject: [PATCH] melib/MailBackend: add copy_messages,set_flags,delete_messages methods --- melib/src/backends.rs | 71 ++++- melib/src/backends/imap.rs | 196 +++++++++++++ melib/src/backends/imap/operations.rs | 92 +----- melib/src/backends/jmap/operations.rs | 8 - melib/src/backends/maildir.rs | 50 ---- melib/src/backends/maildir/backend.rs | 70 +++++ melib/src/backends/mbox.rs | 8 - melib/src/backends/notmuch.rs | 300 +++++++++---------- melib/src/email.rs | 32 +- src/components/mail/listing.rs | 405 ++++++++++++-------------- src/components/mail/listing/plain.rs | 21 +- src/components/mail/view.rs | 20 +- src/conf/accounts.rs | 13 +- src/plugins/backend.rs | 8 - 14 files changed, 691 insertions(+), 603 deletions(-) diff --git a/melib/src/backends.rs b/melib/src/backends.rs index 554b38f0..bf5568ac 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -335,6 +335,31 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync { mailbox_hash: MailboxHash, flags: Option, ) -> ResultFuture<()>; + fn copy_messages( + &mut self, + _env_hashes: EnvelopeHashBatch, + _source_mailbox_hash: MailboxHash, + _destination_mailbox_hash: MailboxHash, + _move_: bool, + _destination_flags: Option, + ) -> ResultFuture<()> { + Err(MeliError::new("Unimplemented.")) + } + fn set_flags( + &mut self, + _env_hashes: EnvelopeHashBatch, + _mailbox_hash: MailboxHash, + _flags: SmallVec<[(std::result::Result, 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>; - fn copy_to(&self, _mailbox_hash: MailboxHash) -> ResultFuture<()> { - Err(MeliError::new("Unimplemented.")) - } fn fetch_flags(&self) -> ResultFuture; - 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 { 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 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 { + 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 + '_ { + std::iter::once(self.first).chain(self.rest.iter().cloned()) + } +} diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 33711af7..344055f5 100644 --- a/melib/src/backends/imap.rs +++ b/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, + ) -> 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, 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 } diff --git a/melib/src/backends/imap/operations.rs b/melib/src/backends/imap/operations.rs index d30f2165..9157049b 100644 --- a/melib/src/backends/imap/operations.rs +++ b/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> + 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> + 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(()) - })) - } } diff --git a/melib/src/backends/jmap/operations.rs b/melib/src/backends/jmap/operations.rs index 1cfd018f..4b093ba4 100644 --- a/melib/src/backends/jmap/operations.rs +++ b/melib/src/backends/jmap/operations.rs @@ -91,12 +91,4 @@ impl BackendOp for JmapOp { fn fetch_flags(&self) -> ResultFuture { 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")) - } } diff --git a/melib/src/backends/maildir.rs b/melib/src/backends/maildir.rs index 45e87e46..70494ff5 100644 --- a/melib/src/backends/maildir.rs +++ b/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)] diff --git a/melib/src/backends/maildir/backend.rs b/melib/src/backends/maildir/backend.rs index 99973a7a..11258b42 100644 --- a/melib/src/backends/maildir/backend.rs +++ b/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, 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 } diff --git a/melib/src/backends/mbox.rs b/melib/src/backends/mbox.rs index cc043e1f..449d5d0a 100644 --- a/melib/src/backends/mbox.rs +++ b/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)] diff --git a/melib/src/backends/notmuch.rs b/melib/src/backends/notmuch.rs index 3ad4d24f..343d1354 100644 --- a/melib/src/backends/notmuch.rs +++ b/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, 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::>(); + //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::>(); - 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 { 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) } diff --git a/melib/src/email.rs b/melib/src/email.rs index c69c61a8..8bf366b4 100644 --- a/melib/src/email.rs +++ b/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, - ) -> 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, - ) -> 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, - ) -> 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) diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs index 8cb55502..475a34f7 100644 --- a/src/components/mail/listing.rs +++ b/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) => { - context.replies.push_back(UIEvent::StatusEvent( - StatusEvent::DisplayMessage(e.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::SetUnseen => match envelope.set_unseen(op) { - Err(e) => { - context.replies.push_back(UIEvent::StatusEvent( - StatusEvent::DisplayMessage(e.to_string()), - )); - } - Ok(fut) => { - let (rcvr, 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)); - } - } - 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), - ); - } - } - 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); - 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; - } - 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), - ); - } - } - 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(()) - }) { - context.replies.push_back(UIEvent::Notification( - Some("Could not copy.".to_string()), - err.to_string(), - Some(NotificationType::ERROR), - )); - 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) => { - context.replies.push_back(UIEvent::Notification( - Some("Could not set tag.".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::SetFlags(env_hash, handle, rcvr)); - } + .insert_job(job_id, JobRequest::SetFlags(env_hashes, handle, channel)); } } - ListingAction::Tag(Add(ref tag_str)) => { - match op.set_tag(tag_str.to_string(), true) { - Err(err) => { - context.replies.push_back(UIEvent::Notification( - Some("Could not set tag.".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::SetFlags(env_hash, handle, rcvr)); - } - } - } - _ => unreachable!(), } - self.row_updates().push(thread_hash); - self.set_dirty(true); - drop(envelope); + 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(err.to_string()), + )); + } + 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::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(err.to_string()), + )); + } + 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::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()), + )); + } + 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::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), + ); + } + } + } + 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, + ) + }) { + 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::Generic { + name: "message copying".into(), + handle, + channel, + }, + ); + } + } + } + ListingAction::CopyToOtherAccount(ref _account_name, ref _mailbox_path) => { + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage( + "Unimplemented.".into(), + ))); + } + ListingAction::MoveTo(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? */ true, + /* flags */ None, + ) + }) { + 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::Generic { + name: "message moving".into(), + handle, + channel, + }, + ); + } + } + } + ListingAction::MoveToOtherAccount(ref _account_name, ref _mailbox_path) => { + context + .replies + .push_back(UIEvent::StatusEvent(StatusEvent::DisplayMessage( + "Unimplemented.".into(), + ))); + } + _ => unreachable!(), } + self.set_dirty(true); } fn row_updates(&mut self) -> &mut SmallVec<[ThreadHash; 8]>; @@ -787,9 +742,7 @@ impl Component for Listing { | Action::Listing(a @ ListingAction::Delete) | Action::Listing(a @ ListingAction::Tag(_)) => { let focused = self.component.get_focused_items(context); - for i in focused { - self.component.perform_action(context, i, a); - } + self.component.perform_action(context, focused, a); self.component.set_dirty(true); return true; } diff --git a/src/components/mail/listing/plain.rs b/src/components/mail/listing/plain.rs index 402b3e27..33a1c097 100644 --- a/src/components/mail/listing/plain.rs +++ b/src/components/mail/listing/plain.rs @@ -1011,19 +1011,25 @@ impl PlainListing { fn perform_action(&mut self, context: &mut Context, env_hash: EnvelopeHash, a: &ListingAction) { let account = &mut context.accounts[self.cursor_pos.0]; - let hash = account.collection.get_env(env_hash).hash(); - match account.operation(hash).and_then(|op| { - let mut envelope: EnvelopeRefMut = account.collection.get_env_mut(env_hash); + match { match a { - ListingAction::SetSeen => envelope.set_seen(op), - ListingAction::SetUnseen => envelope.set_unseen(op), + ListingAction::SetSeen => account.backend.write().unwrap().set_flags( + env_hash.into(), + self.cursor_pos.1, + smallvec::smallvec![(Ok(Flag::SEEN), true)], + ), + ListingAction::SetUnseen => account.backend.write().unwrap().set_flags( + env_hash.into(), + self.cursor_pos.1, + smallvec::smallvec![(Ok(Flag::SEEN), false)], + ), ListingAction::Delete => { /* do nothing */ Err(MeliError::new("Delete is unimplemented")) } _ => unreachable!(), } - }) { + } { Err(e) => { context .replies @@ -1295,8 +1301,7 @@ impl Component for PlainListing { .job_executor .spawn_specialized(job); context.accounts[self.cursor_pos.0] - .active_jobs - .insert(job_id, crate::conf::accounts::JobRequest::Search(handle)); + .insert_job(job_id, crate::conf::accounts::JobRequest::Search(handle)); self.search_job = Some((filter_term.to_string(), chan, job_id)); } Err(err) => { diff --git a/src/components/mail/view.rs b/src/components/mail/view.rs index 930c4c5f..a0d26cbf 100644 --- a/src/components/mail/view.rs +++ b/src/components/mail/view.rs @@ -200,9 +200,7 @@ impl MailView { } else { self.state = MailViewState::LoadingBody { job_id, chan }; self.active_jobs.insert(job_id); - account - .active_jobs - .insert(job_id, JobRequest::AsBytes(handle)); + account.insert_job(job_id, JobRequest::AsBytes(handle)); context .replies .push_back(UIEvent::StatusEvent(StatusEvent::NewJob(job_id))); @@ -216,15 +214,17 @@ impl MailView { } } if !account.collection.get_env(self.coordinates.2).is_seen() { - match account - .operation(self.coordinates.2) - .and_then(|mut op| op.set_flag(Flag::SEEN, true)) - { + let job = account.backend.write().unwrap().set_flags( + self.coordinates.2.into(), + self.coordinates.1, + smallvec::smallvec![(Ok(Flag::SEEN), true)], + ); + match job { Ok(fut) => { let (rcvr, handle, job_id) = account.job_executor.spawn_specialized(fut); - account.active_jobs.insert( + account.insert_job( job_id, - JobRequest::SetFlags(self.coordinates.2, handle, rcvr), + JobRequest::SetFlags(self.coordinates.2.into(), handle, rcvr), ); } Err(e) => { @@ -235,7 +235,7 @@ impl MailView { )), )); } - } + }; } } } diff --git a/src/conf/accounts.rs b/src/conf/accounts.rs index ae3a687f..e638f976 100644 --- a/src/conf/accounts.rs +++ b/src/conf/accounts.rs @@ -27,8 +27,9 @@ use super::{AccountConf, FileMailboxConf}; use crate::jobs::{JobChannel, JobExecutor, JobId, JoinHandle}; use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; use melib::backends::{ - AccountHash, BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp, - RefreshEvent, RefreshEventConsumer, RefreshEventKind, ResultFuture, SpecialUsageMailbox, + AccountHash, BackendOp, Backends, EnvelopeHashBatch, MailBackend, Mailbox, MailboxHash, + NotifyFn, ReadOnlyOp, RefreshEvent, RefreshEventConsumer, RefreshEventKind, ResultFuture, + SpecialUsageMailbox, }; use melib::email::*; use melib::error::{MeliError, Result}; @@ -178,12 +179,12 @@ pub enum JobRequest { }, IsOnline(JoinHandle, oneshot::Receiver>), Refresh(MailboxHash, JoinHandle, oneshot::Receiver>), - SetFlags(EnvelopeHash, JoinHandle, oneshot::Receiver>), + SetFlags(EnvelopeHashBatch, JoinHandle, oneshot::Receiver>), SaveMessage(MailboxHash, JoinHandle, oneshot::Receiver>), SendMessage, SendMessageBackground(JoinHandle, JobChannel<()>), CopyTo(MailboxHash, JoinHandle, oneshot::Receiver>>), - DeleteMessage(EnvelopeHash, JoinHandle, oneshot::Receiver>), + DeleteMessages(EnvelopeHashBatch, JoinHandle, oneshot::Receiver>), CreateMailbox( JoinHandle, oneshot::Receiver)>>, @@ -211,7 +212,7 @@ impl core::fmt::Debug for JobRequest { JobRequest::SetFlags(_, _, _) => write!(f, "JobRequest::SetFlags"), JobRequest::SaveMessage(_, _, _) => write!(f, "JobRequest::SaveMessage"), JobRequest::CopyTo(_, _, _) => write!(f, "JobRequest::CopyTo"), - JobRequest::DeleteMessage(_, _, _) => write!(f, "JobRequest::DeleteMessage"), + JobRequest::DeleteMessages(_, _, _) => write!(f, "JobRequest::DeleteMessages"), JobRequest::CreateMailbox(_, _) => write!(f, "JobRequest::CreateMailbox"), JobRequest::DeleteMailbox(_, _) => write!(f, "JobRequest::DeleteMailbox"), //JobRequest::RenameMailbox, @@ -1860,7 +1861,7 @@ impl Account { .expect("Could not send event on main channel"); } } - JobRequest::DeleteMessage(_, _, mut chan) => { + JobRequest::DeleteMessages(_, _, mut chan) => { let r = chan.try_recv().unwrap(); if let Some(Err(err)) = r { self.sender diff --git a/src/plugins/backend.rs b/src/plugins/backend.rs index 15833a5e..b3038af4 100644 --- a/src/plugins/backend.rs +++ b/src/plugins/backend.rs @@ -331,12 +331,4 @@ impl BackendOp for PluginOp { fn fetch_flags(&self) -> ResultFuture { Err(MeliError::new("Unimplemented.")) } - - 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.")) - } }