melib/MailBackend: add copy_messages,set_flags,delete_messages methods
parent
a049a83fe3
commit
00acba7717
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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!(
|
||||
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;
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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_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_seen(&mut self) {
|
||||
self.set_flag(Flag::SEEN, true)
|
||||
}
|
||||
pub fn set_unseen(&mut self) {
|
||||
self.set_flag(Flag::SEEN, false)
|
||||
}
|
||||
pub fn is_seen(&self) -> bool {
|
||||
self.flags.contains(Flag::SEEN)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
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,
|
||||
self.row_updates().push(thread_hash);
|
||||
}
|
||||
}
|
||||
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));
|
||||
.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::SetUnseen => {
|
||||
let job = account.backend.write().unwrap().set_flags(
|
||||
env_hashes.clone(),
|
||||
mailbox_hash,
|
||||
smallvec::smallvec![(Ok(Flag::SEEN), false)],
|
||||
);
|
||||
}
|
||||
}
|
||||
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))
|
||||
{
|
||||
match job {
|
||||
Err(err) => {
|
||||
context.replies.push_back(UIEvent::Notification(
|
||||
Some("Could not copy.".to_string()),
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(err.to_string()),
|
||||
));
|
||||
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);
|
||||
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));
|
||||
}
|
||||
}
|
||||
/*
|
||||
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) {
|
||||
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::Notification(
|
||||
Some("Could not set tag.".to_string()),
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(err.to_string()),
|
||||
));
|
||||
return;
|
||||
}
|
||||
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::Tag(Add(ref tag_str)) => {
|
||||
match op.set_tag(tag_str.to_string(), true) {
|
||||
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::Notification(
|
||||
Some("Could not set tag.".to_string()),
|
||||
err.to_string(),
|
||||
Some(NotificationType::ERROR),
|
||||
context.replies.push_back(UIEvent::StatusEvent(
|
||||
StatusEvent::DisplayMessage(err.to_string()),
|
||||
));
|
||||
return;
|
||||
}
|
||||
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::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.row_updates().push(thread_hash);
|
||||
self.set_dirty(true);
|
||||
drop(envelope);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 {
|
|||
)),
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Result<()>>),
|
||||
Refresh(MailboxHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||
SetFlags(EnvelopeHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||
SetFlags(EnvelopeHashBatch, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||
SaveMessage(MailboxHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||
SendMessage,
|
||||
SendMessageBackground(JoinHandle, JobChannel<()>),
|
||||
CopyTo(MailboxHash, JoinHandle, oneshot::Receiver<Result<Vec<u8>>>),
|
||||
DeleteMessage(EnvelopeHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||
DeleteMessages(EnvelopeHashBatch, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||
CreateMailbox(
|
||||
JoinHandle,
|
||||
oneshot::Receiver<Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)>>,
|
||||
|
@ -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
|
||||
|
|
|
@ -331,12 +331,4 @@ impl BackendOp for PluginOp {
|
|||
fn fetch_flags(&self) -> ResultFuture<Flag> {
|
||||
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."))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue