Add copy/move to other account operations

async
Manos Pitsidianakis 2020-06-08 22:08:10 +03:00
parent c07185a3aa
commit f3d5edfe14
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
12 changed files with 207 additions and 91 deletions

View File

@ -299,8 +299,8 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>>; fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>>;
fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp>; fn operation(&self, hash: EnvelopeHash) -> Box<dyn BackendOp>;
fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()>; fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()>;
fn delete(&self, _env_hash: EnvelopeHash) -> Result<()> { fn delete(&self, _env_hash: EnvelopeHash, _mailbox_hash: MailboxHash) -> Result<()> {
Err(MeliError::new("Unimplemented.")) Err(MeliError::new("Unimplemented."))
} }
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> { fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {

View File

@ -574,29 +574,22 @@ impl MailBackend for ImapType {
)) ))
} }
fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()> { fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
let path = { let path = {
let mailboxes = self.uid_store.mailboxes.read().unwrap(); let mailboxes = self.uid_store.mailboxes.read().unwrap();
let f_result = mailboxes let mailbox = mailboxes.get(&mailbox_hash).ok_or(MeliError::new(format!(
.values() "Mailbox with hash {} not found.",
.find(|v| v.path == mailbox || v.name == mailbox); mailbox_hash
if f_result )))?;
.map(|f| !f.permissions.lock().unwrap().create_messages) if !mailbox.permissions.lock().unwrap().create_messages {
.unwrap_or(false)
{
return Err(MeliError::new(format!( return Err(MeliError::new(format!(
"You are not allowed to create messages in mailbox {}", "You are not allowed to create messages in mailbox {}",
mailbox mailbox.path()
))); )));
} }
f_result mailbox.imap_path().to_string()
.map(|v| v.imap_path().to_string())
.ok_or(MeliError::new(format!(
"Mailbox with name {} not found.",
mailbox
)))?
}; };
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
let mut conn = try_lock(&self.connection, Some(std::time::Duration::new(5, 0)))?; let mut conn = try_lock(&self.connection, Some(std::time::Duration::new(5, 0)))?;

View File

@ -262,8 +262,8 @@ impl MailBackend for JmapType {
)) ))
} }
fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option<Flag>) -> Result<()> { fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
Ok(()) Err(MeliError::new("Unimplemented."))
} }
fn as_any(&self) -> &dyn::std::any::Any { fn as_any(&self) -> &dyn::std::any::Any {

View File

@ -656,17 +656,8 @@ impl MailBackend for MaildirType {
)) ))
} }
fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()> { fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
for f in self.mailboxes.values() { MaildirType::save_to_mailbox(self.mailboxes[&mailbox_hash].fs_path.clone(), bytes, flags)
if f.name == mailbox || f.path.to_str().unwrap() == mailbox {
return MaildirType::save_to_mailbox(f.fs_path.clone(), bytes, flags);
}
}
Err(MeliError::new(format!(
"'{}' is not a valid mailbox.",
mailbox
)))
} }
fn as_any(&self) -> &dyn::std::any::Any { fn as_any(&self) -> &dyn::std::any::Any {

View File

@ -612,7 +612,7 @@ impl MailBackend for MboxType {
Box::new(MboxOp::new(hash, self.path.as_path(), offset, length)) Box::new(MboxOp::new(hash, self.path.as_path(), offset, length))
} }
fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option<Flag>) -> Result<()> { fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
Err(MeliError::new("Unimplemented.")) Err(MeliError::new("Unimplemented."))
} }

View File

@ -612,7 +612,7 @@ impl MailBackend for NotmuchDb {
}) })
} }
fn save(&self, bytes: &[u8], _mailbox: &str, flags: Option<Flag>) -> Result<()> { fn save(&self, bytes: &[u8], _mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
let path = self let path = self
.save_messages_to .save_messages_to
.as_ref() .as_ref()

View File

@ -143,7 +143,8 @@ pub trait MailListingTrait: ListingTrait {
thread_hash: ThreadHash, thread_hash: ThreadHash,
a: &ListingAction, a: &ListingAction,
) { ) {
let account = &mut context.accounts[self.coordinates().0]; let account_pos = self.coordinates().0;
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.threads[&mailbox_hash].thread_group_iter(thread_hash) { for (_, h) in account.collection.threads[&mailbox_hash].thread_group_iter(thread_hash) {
@ -154,6 +155,7 @@ pub trait MailListingTrait: ListingTrait {
); );
} }
for env_hash in envs_to_set { for env_hash in envs_to_set {
let account = &mut context.accounts[self.coordinates().0];
let mut op = account.operation(env_hash); let mut op = account.operation(env_hash);
let mut envelope: EnvelopeRefMut = account.collection.get_env_mut(env_hash); let mut envelope: EnvelopeRefMut = account.collection.get_env_mut(env_hash);
match a { match a {
@ -173,7 +175,7 @@ pub trait MailListingTrait: ListingTrait {
} }
ListingAction::Delete => { ListingAction::Delete => {
drop(envelope); drop(envelope);
if let Err(err) = account.delete(env_hash) { if let Err(err) = account.delete(env_hash, mailbox_hash) {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
Some("Could not delete.".to_string()), Some("Could not delete.".to_string()),
err.to_string(), err.to_string(),
@ -185,9 +187,13 @@ pub trait MailListingTrait: ListingTrait {
} }
ListingAction::CopyTo(ref mailbox_path) => { ListingAction::CopyTo(ref mailbox_path) => {
drop(envelope); drop(envelope);
if let Err(err) = op if let Err(err) =
.as_bytes() account
.and_then(|bytes| account.save(bytes, mailbox_path, None)) .mailbox_by_path(mailbox_path)
.and_then(|mailbox_hash| {
op.as_bytes()
.and_then(|bytes| account.save(bytes, mailbox_hash, None))
})
{ {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()), Some("Could not copy.".to_string()),
@ -198,6 +204,89 @@ pub trait MailListingTrait: ListingTrait {
} }
continue; continue;
} }
ListingAction::CopyToOtherAccount(ref account_name, ref mailbox_path) => {
drop(envelope);
if let Err(err) = op.as_bytes().map(|b| b.to_vec()).and_then(|bytes| {
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)?;
account.save(&bytes, mailbox_hash, None)
}) {
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);
if let Err(err) =
account
.mailbox_by_path(mailbox_path)
.and_then(|mailbox_hash| {
op.as_bytes()
.and_then(|bytes| account.save(bytes, mailbox_hash, None))
})
{
context.replies.push_back(UIEvent::Notification(
Some("Could not copy.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
}
continue;
}
ListingAction::MoveToOtherAccount(ref account_name, ref mailbox_path) => {
drop(envelope);
if let Err(err) = op
.as_bytes()
.map(|b| b.to_vec())
.and_then(|bytes| {
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)?;
account.save(&bytes, mailbox_hash, None)
})
.and_then(|()| {
let account = &mut context.accounts[account_pos];
account
.delete(env_hash, mailbox_hash)
.chain_err_summary(|| {
"Envelope was copied but removal from original account failed"
})
})
{
context.replies.push_back(UIEvent::Notification(
Some("Could not move.".to_string()),
err.to_string(),
Some(NotificationType::ERROR),
));
return;
}
continue;
}
ListingAction::Tag(Remove(ref tag_str)) => { ListingAction::Tag(Remove(ref tag_str)) => {
if let Err(err) = op.set_tag(&mut envelope, tag_str.to_string(), false) { if let Err(err) = op.set_tag(&mut envelope, tag_str.to_string(), false) {
context.replies.push_back(UIEvent::Notification( context.replies.push_back(UIEvent::Notification(

View File

@ -26,8 +26,8 @@
use super::{AccountConf, FileMailboxConf}; use super::{AccountConf, FileMailboxConf};
use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
use melib::backends::{ use melib::backends::{
BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp, RefreshEvent, AccountHash, BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp,
RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox, RefreshEvent, RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox,
}; };
use melib::email::*; use melib::email::*;
use melib::error::{MeliError, Result}; use melib::error::{MeliError, Result};
@ -113,6 +113,7 @@ impl MailboxEntry {
pub struct Account { pub struct Account {
pub index: usize, pub index: usize,
name: String, name: String,
hash: AccountHash,
pub is_online: bool, pub is_online: bool,
pub(crate) mailbox_entries: HashMap<MailboxHash, MailboxEntry>, pub(crate) mailbox_entries: HashMap<MailboxHash, MailboxEntry>,
pub(crate) mailboxes_order: Vec<MailboxHash>, pub(crate) mailboxes_order: Vec<MailboxHash>,
@ -187,6 +188,7 @@ pub struct MailboxNode {
impl Account { impl Account {
pub fn new( pub fn new(
index: usize, index: usize,
hash: AccountHash,
name: String, name: String,
mut settings: AccountConf, mut settings: AccountConf,
map: &Backends, map: &Backends,
@ -232,6 +234,7 @@ impl Account {
Ok(Account { Ok(Account {
index, index,
hash,
name, name,
is_online: false, is_online: false,
mailbox_entries: Default::default(), mailbox_entries: Default::default(),
@ -931,24 +934,27 @@ impl Account {
Ok(()) Ok(())
} }
pub fn save(&self, bytes: &[u8], mailbox: &str, flags: Option<Flag>) -> Result<()> { pub fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
if self.settings.account.read_only() { if self.settings.account.read_only() {
return Err(MeliError::new(format!( return Err(MeliError::new(format!(
"Account {} is read-only.", "Account {} is read-only.",
self.name.as_str() self.name.as_str()
))); )));
} }
self.backend.write().unwrap().save(bytes, mailbox, flags) self.backend
.write()
.unwrap()
.save(bytes, mailbox_hash, flags)
} }
pub fn delete(&self, env_hash: EnvelopeHash) -> Result<()> { pub fn delete(&self, env_hash: EnvelopeHash, mailbox_hash: MailboxHash) -> Result<()> {
if self.settings.account.read_only() { if self.settings.account.read_only() {
return Err(MeliError::new(format!( return Err(MeliError::new(format!(
"Account {} is read-only.", "Account {} is read-only.",
self.name.as_str() self.name.as_str()
))); )));
} }
self.backend.write().unwrap().delete(env_hash) self.backend.write().unwrap().delete(env_hash, mailbox_hash)
} }
pub fn contains_key(&self, h: EnvelopeHash) -> bool { pub fn contains_key(&self, h: EnvelopeHash) -> bool {
@ -1044,15 +1050,7 @@ impl Account {
if self.mailbox_entries.len() == 1 { if self.mailbox_entries.len() == 1 {
return Err(MeliError::new("Cannot delete only mailbox.")); return Err(MeliError::new("Cannot delete only mailbox."));
} }
let mailbox_hash = if let Some((mailbox_hash, _)) = self let mailbox_hash = self.mailbox_by_path(&path)?;
.mailbox_entries
.iter()
.find(|(_, f)| f.ref_mailbox.path() == path)
{
*mailbox_hash
} else {
return Err(MeliError::new("Mailbox with that path not found."));
};
let mut mailboxes = self.backend.write().unwrap().delete_mailbox(mailbox_hash)?; let mut mailboxes = self.backend.write().unwrap().delete_mailbox(mailbox_hash)?;
self.sender self.sender
.send(ThreadEvent::UIEvent(UIEvent::MailboxDelete(( .send(ThreadEvent::UIEvent(UIEvent::MailboxDelete((
@ -1070,14 +1068,9 @@ impl Account {
self.sent_mailbox = None; self.sent_mailbox = None;
} }
self.collection.threads.remove(&mailbox_hash); self.collection.threads.remove(&mailbox_hash);
let deleted_mailbox = self.mailbox_entries.remove(&mailbox_hash).unwrap();
/* if deleted mailbox had parent, we need to update its children field */ /* if deleted mailbox had parent, we need to update its children field */
if let Some(parent_hash) = self if let Some(parent_hash) = deleted_mailbox.ref_mailbox.parent() {
.mailbox_entries
.remove(&mailbox_hash)
.unwrap()
.ref_mailbox
.parent()
{
self.mailbox_entries self.mailbox_entries
.entry(parent_hash) .entry(parent_hash)
.and_modify(|parent| { .and_modify(|parent| {
@ -1094,18 +1087,13 @@ impl Account {
// FIXME remove from settings as well // FIXME remove from settings as well
Ok(format!("'`{}` has been deleted.", &path)) Ok(format!(
"'`{}` has been deleted.",
&deleted_mailbox.ref_mailbox.path()
))
} }
MailboxOperation::Subscribe(path) => { MailboxOperation::Subscribe(path) => {
let mailbox_hash = if let Some((mailbox_hash, _)) = self let mailbox_hash = self.mailbox_by_path(&path)?;
.mailbox_entries
.iter()
.find(|(_, f)| f.ref_mailbox.path() == path)
{
*mailbox_hash
} else {
return Err(MeliError::new("Mailbox with that path not found."));
};
self.backend self.backend
.write() .write()
.unwrap() .unwrap()
@ -1118,15 +1106,7 @@ impl Account {
Ok(format!("'`{}` has been subscribed.", &path)) Ok(format!("'`{}` has been subscribed.", &path))
} }
MailboxOperation::Unsubscribe(path) => { MailboxOperation::Unsubscribe(path) => {
let mailbox_hash = if let Some((mailbox_hash, _)) = self let mailbox_hash = self.mailbox_by_path(&path)?;
.mailbox_entries
.iter()
.find(|(_, f)| f.ref_mailbox.path() == path)
{
*mailbox_hash
} else {
return Err(MeliError::new("Mailbox with that path not found."));
};
self.backend self.backend
.write() .write()
.unwrap() .unwrap()
@ -1143,13 +1123,13 @@ impl Account {
} }
} }
pub fn special_use_mailbox(&self, special_use: SpecialUsageMailbox) -> Option<&str> { pub fn special_use_mailbox(&self, special_use: SpecialUsageMailbox) -> Option<MailboxHash> {
let ret = self let ret = self
.mailbox_entries .mailbox_entries
.iter() .iter()
.find(|(_, f)| f.conf.mailbox_conf().usage == Some(special_use)); .find(|(_, f)| f.conf.mailbox_conf().usage == Some(special_use));
if let Some(ret) = ret.as_ref() { if let Some(ret) = ret.as_ref() {
Some(ret.1.name()) Some(ret.1.ref_mailbox.hash())
} else { } else {
None None
} }
@ -1237,6 +1217,18 @@ impl Account {
Ok(ret) Ok(ret)
} }
} }
pub fn mailbox_by_path(&self, path: &str) -> Result<MailboxHash> {
if let Some((mailbox_hash, _)) = self
.mailbox_entries
.iter()
.find(|(_, f)| f.ref_mailbox.path() == path)
{
Ok(*mailbox_hash)
} else {
Err(MeliError::new("Mailbox with that path not found."))
}
}
} }
impl Index<&MailboxHash> for Account { impl Index<&MailboxHash> for Account {

View File

@ -236,22 +236,67 @@ define_commands!([
desc: "set [seen/unseen], toggles message's Seen flag.", desc: "set [seen/unseen], toggles message's Seen flag.",
tokens: &[One(Literal("set")), One(Alternatives(&[to_stream!(One(Literal("seen"))), to_stream!(One(Literal("unseen")))]))], tokens: &[One(Literal("set")), One(Alternatives(&[to_stream!(One(Literal("seen"))), to_stream!(One(Literal("unseen")))]))],
parser: parser:
(fn envelope_action<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> { (fn seen_flag<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> {
alt((
preceded( preceded(
tag("set"), tag("set"),
alt(( alt((
map(tag("seen"), |_| Listing(SetSeen)) map(tag("seen"), |_| Listing(SetSeen)),
, map(tag("unseen"), |_| Listing(SetUnseen)) map(tag("unseen"), |_| Listing(SetUnseen))
)) ))
) , map(preceded(tag("delete"), eof), |_| Listing(Delete)) )(input)
, |input: &'a [u8]| -> IResult<&'a [u8], Action> { }
)
},
{ tags: ["delete"],
desc: "delete message",
tokens: &[One(Literal("delete"))],
parser: (
fn delete_message<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> {
map(preceded(tag("delete"), eof), |_| Listing(Delete))(input)
}
)
},
{ tags: ["copyto", "moveto"],
desc: "copy/move message",
tokens: &[One(Alternatives(&[to_stream!(One(Literal("copyto"))), to_stream!(One(Literal("moveto")))])), ZeroOrOne(AccountName), One(MailboxPath)],
parser: (
fn copymove<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> {
alt((
|input: &'a [u8]| -> IResult<&'a [u8], Action> {
let (input, _) = tag("copyto")(input.trim())?; let (input, _) = tag("copyto")(input.trim())?;
let (input, _) = is_a(" ")(input)?; let (input, _) = is_a(" ")(input)?;
let (input,path) = quoted_argument(input)?; let (input, path) = quoted_argument(input)?;
Ok( (input, { Listing(CopyTo(path.to_string())) })) } let (input, _) = eof(input)?;
Ok( (input, { Listing(CopyTo(path.to_string())) }))
},
|input: &'a [u8]| -> IResult<&'a [u8], Action> {
let (input, _) = tag("copyto")(input.trim())?;
let (input, _) = is_a(" ")(input)?;
let (input, account) = quoted_argument(input)?;
let (input, _) = is_a(" ")(input)?;
let (input, path) = quoted_argument(input)?;
let (input, _) = eof(input)?;
Ok( (input, { Listing(CopyToOtherAccount(account.to_string(), path.to_string())) }))
},
|input: &'a [u8]| -> IResult<&'a [u8], Action> {
let (input, _) = tag("moveto")(input.trim())?;
let (input, _) = is_a(" ")(input)?;
let (input, path) = quoted_argument(input)?;
let (input, _) = eof(input)?;
Ok( (input, { Listing(MoveTo(path.to_string())) }))
},
|input: &'a [u8]| -> IResult<&'a [u8], Action> {
let (input, _) = tag("moveto")(input.trim())?;
let (input, _) = is_a(" ")(input)?;
let (input, account) = quoted_argument(input)?;
let (input, _) = is_a(" ")(input)?;
let (input, path) = quoted_argument(input)?;
let (input, _) = eof(input)?;
Ok( (input, { Listing(MoveToOtherAccount(account.to_string(), path.to_string())) }))
}
))(input) ))(input)
}) }
)
}, },
{ tags: ["close"], { tags: ["close"],
desc: "close non-sticky tabs", desc: "close non-sticky tabs",
@ -595,7 +640,9 @@ fn conversations(input: &[u8]) -> IResult<&[u8], Action> {
fn listing_action(input: &[u8]) -> IResult<&[u8], Action> { fn listing_action(input: &[u8]) -> IResult<&[u8], Action> {
alt(( alt((
toggle, toggle,
envelope_action, seen_flag,
delete_message,
copymove,
search, search,
toggle_thread_snooze, toggle_thread_snooze,
open_in_new_tab, open_in_new_tab,

View File

@ -47,6 +47,9 @@ pub enum ListingAction {
SetSeen, SetSeen,
SetUnseen, SetUnseen,
CopyTo(MailboxPath), CopyTo(MailboxPath),
CopyToOtherAccount(AccountName, MailboxPath),
MoveTo(MailboxPath),
MoveToOtherAccount(AccountName, MailboxPath),
Delete, Delete,
OpenInNewTab, OpenInNewTab,
Tag(TagAction), Tag(TagAction),

View File

@ -213,7 +213,7 @@ impl MailBackend for PluginBackend {
}) })
} }
fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option<Flag>) -> Result<()> { fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
Err(MeliError::new("Unimplemented.")) Err(MeliError::new("Unimplemented."))
} }
fn create_mailbox( fn create_mailbox(

View File

@ -255,6 +255,7 @@ impl State {
account_hashes.insert(account_hash, index); account_hashes.insert(account_hash, index);
Account::new( Account::new(
index, index,
account_hash,
n.to_string(), n.to_string(),
a_s.clone(), a_s.clone(),
&backends, &backends,