diff --git a/melib/src/backends.rs b/melib/src/backends.rs index 12ee16a99..94c4a9475 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -299,8 +299,8 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync { fn mailboxes(&self) -> Result>; fn operation(&self, hash: EnvelopeHash) -> Box; - fn save(&self, bytes: &[u8], mailbox: &str, flags: Option) -> Result<()>; - fn delete(&self, _env_hash: EnvelopeHash) -> Result<()> { + fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option) -> Result<()>; + fn delete(&self, _env_hash: EnvelopeHash, _mailbox_hash: MailboxHash) -> Result<()> { Err(MeliError::new("Unimplemented.")) } fn tags(&self) -> Option>>> { diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 69dd1c38f..e3d75f6c2 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -574,29 +574,22 @@ impl MailBackend for ImapType { )) } - fn save(&self, bytes: &[u8], mailbox: &str, flags: Option) -> Result<()> { + fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option) -> Result<()> { let path = { let mailboxes = self.uid_store.mailboxes.read().unwrap(); - let f_result = mailboxes - .values() - .find(|v| v.path == mailbox || v.name == mailbox); - if f_result - .map(|f| !f.permissions.lock().unwrap().create_messages) - .unwrap_or(false) - { + let mailbox = mailboxes.get(&mailbox_hash).ok_or(MeliError::new(format!( + "Mailbox with hash {} not found.", + mailbox_hash + )))?; + if !mailbox.permissions.lock().unwrap().create_messages { return Err(MeliError::new(format!( "You are not allowed to create messages in mailbox {}", - mailbox + mailbox.path() ))); } - f_result - .map(|v| v.imap_path().to_string()) - .ok_or(MeliError::new(format!( - "Mailbox with name {} not found.", - mailbox - )))? + mailbox.imap_path().to_string() }; let mut response = String::with_capacity(8 * 1024); let mut conn = try_lock(&self.connection, Some(std::time::Duration::new(5, 0)))?; diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index f321243cf..aabed98f2 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -262,8 +262,8 @@ impl MailBackend for JmapType { )) } - fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option) -> Result<()> { - Ok(()) + fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option) -> Result<()> { + Err(MeliError::new("Unimplemented.")) } fn as_any(&self) -> &dyn::std::any::Any { diff --git a/melib/src/backends/maildir/backend.rs b/melib/src/backends/maildir/backend.rs index 23a975e98..b63fa4974 100644 --- a/melib/src/backends/maildir/backend.rs +++ b/melib/src/backends/maildir/backend.rs @@ -656,17 +656,8 @@ impl MailBackend for MaildirType { )) } - fn save(&self, bytes: &[u8], mailbox: &str, flags: Option) -> Result<()> { - for f in self.mailboxes.values() { - 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 save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option) -> Result<()> { + MaildirType::save_to_mailbox(self.mailboxes[&mailbox_hash].fs_path.clone(), bytes, flags) } fn as_any(&self) -> &dyn::std::any::Any { diff --git a/melib/src/backends/mbox.rs b/melib/src/backends/mbox.rs index 312373ff0..f90a1bb7c 100644 --- a/melib/src/backends/mbox.rs +++ b/melib/src/backends/mbox.rs @@ -612,7 +612,7 @@ impl MailBackend for MboxType { Box::new(MboxOp::new(hash, self.path.as_path(), offset, length)) } - fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option) -> Result<()> { + fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option) -> Result<()> { Err(MeliError::new("Unimplemented.")) } diff --git a/melib/src/backends/notmuch.rs b/melib/src/backends/notmuch.rs index 7326583b2..d9cd3f61b 100644 --- a/melib/src/backends/notmuch.rs +++ b/melib/src/backends/notmuch.rs @@ -612,7 +612,7 @@ impl MailBackend for NotmuchDb { }) } - fn save(&self, bytes: &[u8], _mailbox: &str, flags: Option) -> Result<()> { + fn save(&self, bytes: &[u8], _mailbox_hash: MailboxHash, flags: Option) -> Result<()> { let path = self .save_messages_to .as_ref() diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs index 4a3e19fad..10e92fda8 100644 --- a/src/components/mail/listing.rs +++ b/src/components/mail/listing.rs @@ -143,7 +143,8 @@ pub trait MailListingTrait: ListingTrait { thread_hash: ThreadHash, 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 mailbox_hash = self.coordinates().1; 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 { + let account = &mut context.accounts[self.coordinates().0]; let mut op = account.operation(env_hash); let mut envelope: EnvelopeRefMut = account.collection.get_env_mut(env_hash); match a { @@ -173,7 +175,7 @@ pub trait MailListingTrait: ListingTrait { } ListingAction::Delete => { 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( Some("Could not delete.".to_string()), err.to_string(), @@ -185,9 +187,13 @@ pub trait MailListingTrait: ListingTrait { } ListingAction::CopyTo(ref mailbox_path) => { drop(envelope); - if let Err(err) = op - .as_bytes() - .and_then(|bytes| account.save(bytes, mailbox_path, None)) + 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()), @@ -198,6 +204,89 @@ pub trait MailListingTrait: ListingTrait { } 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)) => { if let Err(err) = op.set_tag(&mut envelope, tag_str.to_string(), false) { context.replies.push_back(UIEvent::Notification( diff --git a/src/conf/accounts.rs b/src/conf/accounts.rs index 1af32db07..6da91cc14 100644 --- a/src/conf/accounts.rs +++ b/src/conf/accounts.rs @@ -26,8 +26,8 @@ use super::{AccountConf, FileMailboxConf}; use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; use melib::backends::{ - BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp, RefreshEvent, - RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox, + AccountHash, BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp, + RefreshEvent, RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox, }; use melib::email::*; use melib::error::{MeliError, Result}; @@ -113,6 +113,7 @@ impl MailboxEntry { pub struct Account { pub index: usize, name: String, + hash: AccountHash, pub is_online: bool, pub(crate) mailbox_entries: HashMap, pub(crate) mailboxes_order: Vec, @@ -187,6 +188,7 @@ pub struct MailboxNode { impl Account { pub fn new( index: usize, + hash: AccountHash, name: String, mut settings: AccountConf, map: &Backends, @@ -232,6 +234,7 @@ impl Account { Ok(Account { index, + hash, name, is_online: false, mailbox_entries: Default::default(), @@ -931,24 +934,27 @@ impl Account { Ok(()) } - pub fn save(&self, bytes: &[u8], mailbox: &str, flags: Option) -> Result<()> { + pub fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option) -> Result<()> { if self.settings.account.read_only() { return Err(MeliError::new(format!( "Account {} is read-only.", 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() { return Err(MeliError::new(format!( "Account {} is read-only.", 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 { @@ -1044,15 +1050,7 @@ impl Account { if self.mailbox_entries.len() == 1 { return Err(MeliError::new("Cannot delete only mailbox.")); } - let mailbox_hash = if let Some((mailbox_hash, _)) = self - .mailbox_entries - .iter() - .find(|(_, f)| f.ref_mailbox.path() == path) - { - *mailbox_hash - } else { - return Err(MeliError::new("Mailbox with that path not found.")); - }; + let mailbox_hash = self.mailbox_by_path(&path)?; let mut mailboxes = self.backend.write().unwrap().delete_mailbox(mailbox_hash)?; self.sender .send(ThreadEvent::UIEvent(UIEvent::MailboxDelete(( @@ -1070,14 +1068,9 @@ impl Account { self.sent_mailbox = None; } 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 let Some(parent_hash) = self - .mailbox_entries - .remove(&mailbox_hash) - .unwrap() - .ref_mailbox - .parent() - { + if let Some(parent_hash) = deleted_mailbox.ref_mailbox.parent() { self.mailbox_entries .entry(parent_hash) .and_modify(|parent| { @@ -1094,18 +1087,13 @@ impl Account { // 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) => { - let mailbox_hash = if let Some((mailbox_hash, _)) = self - .mailbox_entries - .iter() - .find(|(_, f)| f.ref_mailbox.path() == path) - { - *mailbox_hash - } else { - return Err(MeliError::new("Mailbox with that path not found.")); - }; + let mailbox_hash = self.mailbox_by_path(&path)?; self.backend .write() .unwrap() @@ -1118,15 +1106,7 @@ impl Account { Ok(format!("'`{}` has been subscribed.", &path)) } MailboxOperation::Unsubscribe(path) => { - let mailbox_hash = if let Some((mailbox_hash, _)) = self - .mailbox_entries - .iter() - .find(|(_, f)| f.ref_mailbox.path() == path) - { - *mailbox_hash - } else { - return Err(MeliError::new("Mailbox with that path not found.")); - }; + let mailbox_hash = self.mailbox_by_path(&path)?; self.backend .write() .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 { let ret = self .mailbox_entries .iter() .find(|(_, f)| f.conf.mailbox_conf().usage == Some(special_use)); if let Some(ret) = ret.as_ref() { - Some(ret.1.name()) + Some(ret.1.ref_mailbox.hash()) } else { None } @@ -1237,6 +1217,18 @@ impl Account { Ok(ret) } } + + pub fn mailbox_by_path(&self, path: &str) -> Result { + 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 { diff --git a/src/execute.rs b/src/execute.rs index 32b928e83..ee84fd99b 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -236,22 +236,67 @@ define_commands!([ 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")))]))], parser: - (fn envelope_action<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> { - alt(( + (fn seen_flag<'a>(input: &'a [u8]) -> IResult<&'a [u8], Action> { preceded( tag("set"), alt(( - map(tag("seen"), |_| Listing(SetSeen)) - , map(tag("unseen"), |_| Listing(SetUnseen)) + map(tag("seen"), |_| Listing(SetSeen)), + map(tag("unseen"), |_| Listing(SetUnseen)) )) - ) , map(preceded(tag("delete"), eof), |_| Listing(Delete)) - , |input: &'a [u8]| -> IResult<&'a [u8], Action> { + )(input) + } + ) + }, + { 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, _) = is_a(" ")(input)?; - let (input,path) = quoted_argument(input)?; - Ok( (input, { Listing(CopyTo(path.to_string())) })) } + let (input, path) = quoted_argument(input)?; + 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) - }) + } + ) }, { tags: ["close"], desc: "close non-sticky tabs", @@ -595,7 +640,9 @@ fn conversations(input: &[u8]) -> IResult<&[u8], Action> { fn listing_action(input: &[u8]) -> IResult<&[u8], Action> { alt(( toggle, - envelope_action, + seen_flag, + delete_message, + copymove, search, toggle_thread_snooze, open_in_new_tab, diff --git a/src/execute/actions.rs b/src/execute/actions.rs index 53e7c5d0c..ea8078387 100644 --- a/src/execute/actions.rs +++ b/src/execute/actions.rs @@ -47,6 +47,9 @@ pub enum ListingAction { SetSeen, SetUnseen, CopyTo(MailboxPath), + CopyToOtherAccount(AccountName, MailboxPath), + MoveTo(MailboxPath), + MoveToOtherAccount(AccountName, MailboxPath), Delete, OpenInNewTab, Tag(TagAction), diff --git a/src/plugins/backend.rs b/src/plugins/backend.rs index b55fab765..d3bd27161 100644 --- a/src/plugins/backend.rs +++ b/src/plugins/backend.rs @@ -213,7 +213,7 @@ impl MailBackend for PluginBackend { }) } - fn save(&self, _bytes: &[u8], _mailbox: &str, _flags: Option) -> Result<()> { + fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option) -> Result<()> { Err(MeliError::new("Unimplemented.")) } fn create_mailbox( diff --git a/src/state.rs b/src/state.rs index 6eae7bd03..aa2bd279f 100644 --- a/src/state.rs +++ b/src/state.rs @@ -255,6 +255,7 @@ impl State { account_hashes.insert(account_hash, index); Account::new( index, + account_hash, n.to_string(), a_s.clone(), &backends,