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,
|
mailbox_hash: MailboxHash,
|
||||||
flags: Option<Flag>,
|
flags: Option<Flag>,
|
||||||
) -> ResultFuture<()>;
|
) -> 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<()> {
|
fn delete(&self, _env_hash: EnvelopeHash, _mailbox_hash: MailboxHash) -> ResultFuture<()> {
|
||||||
Err(MeliError::new("Unimplemented."))
|
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 {
|
pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
|
||||||
fn as_bytes(&mut self) -> ResultFuture<Vec<u8>>;
|
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 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.
|
/// Wrapper for BackendOps that are to be set read-only.
|
||||||
|
@ -461,12 +481,6 @@ impl BackendOp for ReadOnlyOp {
|
||||||
fn fetch_flags(&self) -> ResultFuture<Flag> {
|
fn fetch_flags(&self) -> ResultFuture<Flag> {
|
||||||
self.op.fetch_flags()
|
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)]
|
#[derive(Debug, Copy, Hash, Eq, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
@ -651,3 +665,38 @@ impl std::fmt::Display for MailboxPermissions {
|
||||||
write!(fmt, "{:#?}", self)
|
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 {
|
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ use super::*;
|
||||||
|
|
||||||
use crate::backends::*;
|
use crate::backends::*;
|
||||||
use crate::email::*;
|
use crate::email::*;
|
||||||
use crate::error::{MeliError, Result};
|
use crate::error::MeliError;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// `BackendOp` implementor for Imap
|
/// `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> {
|
fn fetch_flags(&self) -> ResultFuture<Flag> {
|
||||||
Ok(Box::pin(async { Ok(Flag::default()) }))
|
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());
|
let ret = Ok(path.flags());
|
||||||
Ok(Box::pin(async move { ret }))
|
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)]
|
#[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 {
|
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,14 +248,6 @@ impl BackendOp for MboxOp {
|
||||||
}
|
}
|
||||||
Ok(Box::pin(async move { Ok(flags) }))
|
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)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
|
@ -75,14 +75,14 @@ impl Error for NotmuchError {
|
||||||
|
|
||||||
macro_rules! try_call {
|
macro_rules! try_call {
|
||||||
($lib:expr, $call:expr) => {{
|
($lib:expr, $call:expr) => {{
|
||||||
let status = unsafe { $call };
|
let status = $call;
|
||||||
if status == _notmuch_status_NOTMUCH_STATUS_SUCCESS {
|
if status == _notmuch_status_NOTMUCH_STATUS_SUCCESS {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let c_str = unsafe { call!($lib, notmuch_status_to_string)(status) };
|
let c_str = call!($lib, notmuch_status_to_string)(status);
|
||||||
Err(NotmuchError(unsafe {
|
Err(NotmuchError(
|
||||||
CStr::from_ptr(c_str).to_string_lossy().into_owned()
|
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();
|
let database_lck = database.inner.read().unwrap();
|
||||||
index.write().unwrap().retain(|&env_hash, msg_id| {
|
index.write().unwrap().retain(|&env_hash, msg_id| {
|
||||||
let mut message: *mut notmuch_message_t = std::ptr::null_mut();
|
let mut message: *mut notmuch_message_t = std::ptr::null_mut();
|
||||||
if let Err(err) = try_call!(
|
if let Err(err) = unsafe {
|
||||||
lib,
|
try_call!(
|
||||||
call!(lib, notmuch_database_find_message)(
|
lib,
|
||||||
*database_lck,
|
call!(lib, notmuch_database_find_message)(
|
||||||
msg_id.as_ptr(),
|
*database_lck,
|
||||||
&mut message as *mut _,
|
msg_id.as_ptr(),
|
||||||
|
&mut message as *mut _,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
) {
|
} {
|
||||||
debug!(err);
|
debug!(err);
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
|
@ -638,6 +640,135 @@ impl MailBackend for NotmuchDb {
|
||||||
Ok(Box::pin(async { Ok(()) }))
|
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 {
|
fn as_any(&self) -> &dyn ::std::any::Any {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -691,139 +822,6 @@ impl BackendOp for NotmuchOp {
|
||||||
let (flags, _tags) = TagIterator::new(self.lib.clone(), message).collect_flags_and_tags();
|
let (flags, _tags) = TagIterator::new(self.lib.clone(), message).collect_flags_and_tags();
|
||||||
Ok(Box::pin(async move { Ok(flags) }))
|
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 {
|
pub struct MessageIterator {
|
||||||
|
@ -976,11 +974,13 @@ impl<'s> Query<'s> {
|
||||||
|
|
||||||
fn count(&self) -> Result<u32> {
|
fn count(&self) -> Result<u32> {
|
||||||
let mut count = 0_u32;
|
let mut count = 0_u32;
|
||||||
try_call!(
|
unsafe {
|
||||||
self.lib,
|
try_call!(
|
||||||
call!(self.lib, notmuch_query_count_messages)(self.ptr, &mut count as *mut _)
|
self.lib,
|
||||||
)
|
call!(self.lib, notmuch_query_count_messages)(self.ptr, &mut count as *mut _)
|
||||||
.map_err(|err| err.0)?;
|
)
|
||||||
|
.map_err(|err| err.0)?;
|
||||||
|
}
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,10 +49,8 @@ use std::borrow::Cow;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::future::Future;
|
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
use std::pin::Pin;
|
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
|
|
||||||
|
@ -595,14 +593,8 @@ impl Envelope {
|
||||||
pub fn set_datetime(&mut self, new_val: UnixTimestamp) {
|
pub fn set_datetime(&mut self, new_val: UnixTimestamp) {
|
||||||
self.timestamp = new_val;
|
self.timestamp = new_val;
|
||||||
}
|
}
|
||||||
pub fn set_flag(
|
pub fn set_flag(&mut self, f: Flag, value: bool) {
|
||||||
&mut self,
|
|
||||||
f: Flag,
|
|
||||||
value: bool,
|
|
||||||
mut operation: Box<dyn BackendOp>,
|
|
||||||
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
|
|
||||||
self.flags.set(f, value);
|
self.flags.set(f, value);
|
||||||
operation.set_flag(f, value)
|
|
||||||
}
|
}
|
||||||
pub fn set_flags(&mut self, f: Flag) {
|
pub fn set_flags(&mut self, f: Flag) {
|
||||||
self.flags = f;
|
self.flags = f;
|
||||||
|
@ -610,25 +602,11 @@ impl Envelope {
|
||||||
pub fn flags(&self) -> Flag {
|
pub fn flags(&self) -> Flag {
|
||||||
self.flags
|
self.flags
|
||||||
}
|
}
|
||||||
pub fn set_seen(
|
pub fn set_seen(&mut self) {
|
||||||
&mut self,
|
self.set_flag(Flag::SEEN, true)
|
||||||
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(
|
pub fn set_unseen(&mut self) {
|
||||||
&mut self,
|
self.set_flag(Flag::SEEN, false)
|
||||||
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 is_seen(&self) -> bool {
|
pub fn is_seen(&self) -> bool {
|
||||||
self.flags.contains(Flag::SEEN)
|
self.flags.contains(Flag::SEEN)
|
||||||
|
|
|
@ -22,8 +22,10 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::conf::accounts::JobRequest;
|
use crate::conf::accounts::JobRequest;
|
||||||
use crate::types::segment_tree::SegmentTree;
|
use crate::types::segment_tree::SegmentTree;
|
||||||
|
use melib::backends::EnvelopeHashBatch;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
mod conversations;
|
mod conversations;
|
||||||
|
@ -141,248 +143,201 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
fn perform_action(
|
fn perform_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut Context,
|
context: &mut Context,
|
||||||
thread_hash: ThreadHash,
|
thread_hashes: SmallVec<[ThreadHash; 8]>,
|
||||||
a: &ListingAction,
|
a: &ListingAction,
|
||||||
) {
|
) {
|
||||||
let account_pos = self.coordinates().0;
|
let account_pos = self.coordinates().0;
|
||||||
let account = &mut context.accounts[account_pos];
|
let account = &mut context.accounts[account_pos];
|
||||||
let mut envs_to_set: SmallVec<[EnvelopeHash; 8]> = SmallVec::new();
|
let mut envs_to_set: SmallVec<[EnvelopeHash; 8]> = SmallVec::new();
|
||||||
let mailbox_hash = self.coordinates().1;
|
let mailbox_hash = self.coordinates().1;
|
||||||
for (_, h) in account
|
|
||||||
.collection
|
|
||||||
.get_threads(mailbox_hash)
|
|
||||||
.thread_group_iter(thread_hash)
|
|
||||||
{
|
{
|
||||||
envs_to_set.push(
|
let threads_lck = account.collection.get_threads(mailbox_hash);
|
||||||
account.collection.get_threads(mailbox_hash).thread_nodes()[&h]
|
for thread_hash in thread_hashes {
|
||||||
.message()
|
for (_, h) in threads_lck.thread_group_iter(thread_hash) {
|
||||||
.unwrap(),
|
envs_to_set.push(threads_lck.thread_nodes()[&h].message().unwrap());
|
||||||
);
|
}
|
||||||
|
self.row_updates().push(thread_hash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for env_hash in envs_to_set {
|
if envs_to_set.is_empty() {
|
||||||
let account = &mut context.accounts[self.coordinates().0];
|
return;
|
||||||
let mut op =
|
}
|
||||||
match account.operation(env_hash) {
|
let env_hashes = EnvelopeHashBatch::try_from(envs_to_set.as_slice()).unwrap();
|
||||||
Ok(op) => op,
|
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) => {
|
Err(err) => {
|
||||||
context.replies.push_back(UIEvent::StatusEvent(
|
context.replies.push_back(UIEvent::StatusEvent(
|
||||||
StatusEvent::DisplayMessage(err.to_string()),
|
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) => {
|
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
|
account
|
||||||
.active_jobs
|
.insert_job(job_id, JobRequest::SetFlags(env_hashes, handle, channel));
|
||||||
.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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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);
|
ListingAction::SetUnseen => {
|
||||||
self.set_dirty(true);
|
let job = account.backend.write().unwrap().set_flags(
|
||||||
drop(envelope);
|
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]>;
|
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::Delete)
|
||||||
| Action::Listing(a @ ListingAction::Tag(_)) => {
|
| Action::Listing(a @ ListingAction::Tag(_)) => {
|
||||||
let focused = self.component.get_focused_items(context);
|
let focused = self.component.get_focused_items(context);
|
||||||
for i in focused {
|
self.component.perform_action(context, focused, a);
|
||||||
self.component.perform_action(context, i, a);
|
|
||||||
}
|
|
||||||
self.component.set_dirty(true);
|
self.component.set_dirty(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1011,19 +1011,25 @@ impl PlainListing {
|
||||||
|
|
||||||
fn perform_action(&mut self, context: &mut Context, env_hash: EnvelopeHash, a: &ListingAction) {
|
fn perform_action(&mut self, context: &mut Context, env_hash: EnvelopeHash, a: &ListingAction) {
|
||||||
let account = &mut context.accounts[self.cursor_pos.0];
|
let account = &mut context.accounts[self.cursor_pos.0];
|
||||||
let hash = account.collection.get_env(env_hash).hash();
|
match {
|
||||||
match account.operation(hash).and_then(|op| {
|
|
||||||
let mut envelope: EnvelopeRefMut = account.collection.get_env_mut(env_hash);
|
|
||||||
match a {
|
match a {
|
||||||
ListingAction::SetSeen => envelope.set_seen(op),
|
ListingAction::SetSeen => account.backend.write().unwrap().set_flags(
|
||||||
ListingAction::SetUnseen => envelope.set_unseen(op),
|
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 => {
|
ListingAction::Delete => {
|
||||||
/* do nothing */
|
/* do nothing */
|
||||||
Err(MeliError::new("Delete is unimplemented"))
|
Err(MeliError::new("Delete is unimplemented"))
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}) {
|
} {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
context
|
context
|
||||||
.replies
|
.replies
|
||||||
|
@ -1295,8 +1301,7 @@ impl Component for PlainListing {
|
||||||
.job_executor
|
.job_executor
|
||||||
.spawn_specialized(job);
|
.spawn_specialized(job);
|
||||||
context.accounts[self.cursor_pos.0]
|
context.accounts[self.cursor_pos.0]
|
||||||
.active_jobs
|
.insert_job(job_id, crate::conf::accounts::JobRequest::Search(handle));
|
||||||
.insert(job_id, crate::conf::accounts::JobRequest::Search(handle));
|
|
||||||
self.search_job = Some((filter_term.to_string(), chan, job_id));
|
self.search_job = Some((filter_term.to_string(), chan, job_id));
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|
|
@ -200,9 +200,7 @@ impl MailView {
|
||||||
} else {
|
} else {
|
||||||
self.state = MailViewState::LoadingBody { job_id, chan };
|
self.state = MailViewState::LoadingBody { job_id, chan };
|
||||||
self.active_jobs.insert(job_id);
|
self.active_jobs.insert(job_id);
|
||||||
account
|
account.insert_job(job_id, JobRequest::AsBytes(handle));
|
||||||
.active_jobs
|
|
||||||
.insert(job_id, JobRequest::AsBytes(handle));
|
|
||||||
context
|
context
|
||||||
.replies
|
.replies
|
||||||
.push_back(UIEvent::StatusEvent(StatusEvent::NewJob(job_id)));
|
.push_back(UIEvent::StatusEvent(StatusEvent::NewJob(job_id)));
|
||||||
|
@ -216,15 +214,17 @@ impl MailView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !account.collection.get_env(self.coordinates.2).is_seen() {
|
if !account.collection.get_env(self.coordinates.2).is_seen() {
|
||||||
match account
|
let job = account.backend.write().unwrap().set_flags(
|
||||||
.operation(self.coordinates.2)
|
self.coordinates.2.into(),
|
||||||
.and_then(|mut op| op.set_flag(Flag::SEEN, true))
|
self.coordinates.1,
|
||||||
{
|
smallvec::smallvec![(Ok(Flag::SEEN), true)],
|
||||||
|
);
|
||||||
|
match job {
|
||||||
Ok(fut) => {
|
Ok(fut) => {
|
||||||
let (rcvr, handle, job_id) = account.job_executor.spawn_specialized(fut);
|
let (rcvr, handle, job_id) = account.job_executor.spawn_specialized(fut);
|
||||||
account.active_jobs.insert(
|
account.insert_job(
|
||||||
job_id,
|
job_id,
|
||||||
JobRequest::SetFlags(self.coordinates.2, handle, rcvr),
|
JobRequest::SetFlags(self.coordinates.2.into(), handle, rcvr),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -235,7 +235,7 @@ impl MailView {
|
||||||
)),
|
)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,9 @@ use super::{AccountConf, FileMailboxConf};
|
||||||
use crate::jobs::{JobChannel, JobExecutor, JobId, JoinHandle};
|
use crate::jobs::{JobChannel, JobExecutor, JobId, JoinHandle};
|
||||||
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
|
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
|
||||||
use melib::backends::{
|
use melib::backends::{
|
||||||
AccountHash, BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp,
|
AccountHash, BackendOp, Backends, EnvelopeHashBatch, MailBackend, Mailbox, MailboxHash,
|
||||||
RefreshEvent, RefreshEventConsumer, RefreshEventKind, ResultFuture, SpecialUsageMailbox,
|
NotifyFn, ReadOnlyOp, RefreshEvent, RefreshEventConsumer, RefreshEventKind, ResultFuture,
|
||||||
|
SpecialUsageMailbox,
|
||||||
};
|
};
|
||||||
use melib::email::*;
|
use melib::email::*;
|
||||||
use melib::error::{MeliError, Result};
|
use melib::error::{MeliError, Result};
|
||||||
|
@ -178,12 +179,12 @@ pub enum JobRequest {
|
||||||
},
|
},
|
||||||
IsOnline(JoinHandle, oneshot::Receiver<Result<()>>),
|
IsOnline(JoinHandle, oneshot::Receiver<Result<()>>),
|
||||||
Refresh(MailboxHash, 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<()>>),
|
SaveMessage(MailboxHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||||
SendMessage,
|
SendMessage,
|
||||||
SendMessageBackground(JoinHandle, JobChannel<()>),
|
SendMessageBackground(JoinHandle, JobChannel<()>),
|
||||||
CopyTo(MailboxHash, JoinHandle, oneshot::Receiver<Result<Vec<u8>>>),
|
CopyTo(MailboxHash, JoinHandle, oneshot::Receiver<Result<Vec<u8>>>),
|
||||||
DeleteMessage(EnvelopeHash, JoinHandle, oneshot::Receiver<Result<()>>),
|
DeleteMessages(EnvelopeHashBatch, JoinHandle, oneshot::Receiver<Result<()>>),
|
||||||
CreateMailbox(
|
CreateMailbox(
|
||||||
JoinHandle,
|
JoinHandle,
|
||||||
oneshot::Receiver<Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)>>,
|
oneshot::Receiver<Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)>>,
|
||||||
|
@ -211,7 +212,7 @@ impl core::fmt::Debug for JobRequest {
|
||||||
JobRequest::SetFlags(_, _, _) => write!(f, "JobRequest::SetFlags"),
|
JobRequest::SetFlags(_, _, _) => write!(f, "JobRequest::SetFlags"),
|
||||||
JobRequest::SaveMessage(_, _, _) => write!(f, "JobRequest::SaveMessage"),
|
JobRequest::SaveMessage(_, _, _) => write!(f, "JobRequest::SaveMessage"),
|
||||||
JobRequest::CopyTo(_, _, _) => write!(f, "JobRequest::CopyTo"),
|
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::CreateMailbox(_, _) => write!(f, "JobRequest::CreateMailbox"),
|
||||||
JobRequest::DeleteMailbox(_, _) => write!(f, "JobRequest::DeleteMailbox"),
|
JobRequest::DeleteMailbox(_, _) => write!(f, "JobRequest::DeleteMailbox"),
|
||||||
//JobRequest::RenameMailbox,
|
//JobRequest::RenameMailbox,
|
||||||
|
@ -1860,7 +1861,7 @@ impl Account {
|
||||||
.expect("Could not send event on main channel");
|
.expect("Could not send event on main channel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JobRequest::DeleteMessage(_, _, mut chan) => {
|
JobRequest::DeleteMessages(_, _, mut chan) => {
|
||||||
let r = chan.try_recv().unwrap();
|
let r = chan.try_recv().unwrap();
|
||||||
if let Some(Err(err)) = r {
|
if let Some(Err(err)) = r {
|
||||||
self.sender
|
self.sender
|
||||||
|
|
|
@ -331,12 +331,4 @@ impl BackendOp for PluginOp {
|
||||||
fn fetch_flags(&self) -> ResultFuture<Flag> {
|
fn fetch_flags(&self) -> ResultFuture<Flag> {
|
||||||
Err(MeliError::new("Unimplemented."))
|
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