From 4829e13c478021bd96690287509008049b8d8732 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 11 Sep 2020 16:58:56 +0300 Subject: [PATCH] melib/maildir: impl copy_messages for Maildir --- melib/src/backends.rs | 26 +-- melib/src/backends/imap.rs | 4 - melib/src/backends/jmap.rs | 1 - melib/src/backends/maildir/backend.rs | 222 ++++++++++++++++--- melib/src/backends/mbox.rs | 19 ++ melib/src/backends/nntp.rs | 1 - melib/src/backends/notmuch.rs | 10 + src/components/mail/listing.rs | 2 - src/components/mail/listing/conversations.rs | 6 + src/conf/accounts.rs | 4 +- 10 files changed, 238 insertions(+), 57 deletions(-) diff --git a/melib/src/backends.rs b/melib/src/backends.rs index bad05eb5..242cf0a1 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -318,24 +318,22 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync { mailbox_hash: MailboxHash, flags: Option, ) -> ResultFuture<()>; + fn copy_messages( &mut self, - _env_hashes: EnvelopeHashBatch, - _source_mailbox_hash: MailboxHash, - _destination_mailbox_hash: MailboxHash, - _move_: bool, - _destination_flags: Option, - ) -> ResultFuture<()> { - Err(MeliError::new("Unimplemented.")) - } + env_hashes: EnvelopeHashBatch, + source_mailbox_hash: MailboxHash, + destination_mailbox_hash: MailboxHash, + move_: bool, + ) -> ResultFuture<()>; + fn set_flags( &mut self, - _env_hashes: EnvelopeHashBatch, - _mailbox_hash: MailboxHash, - _flags: SmallVec<[(std::result::Result, bool); 8]>, - ) -> ResultFuture<()> { - Err(MeliError::new("Unimplemented.")) - } + env_hashes: EnvelopeHashBatch, + mailbox_hash: MailboxHash, + flags: SmallVec<[(std::result::Result, bool); 8]>, + ) -> ResultFuture<()>; + fn delete_messages( &self, _env_hashes: EnvelopeHashBatch, diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 4086eb60..c6cbefcb 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -533,7 +533,6 @@ impl MailBackend for ImapType { source_mailbox_hash: MailboxHash, destination_mailbox_hash: MailboxHash, move_: bool, - destination_flags: Option, ) -> ResultFuture<()> { let uid_store = self.uid_store.clone(); let connection = self.connection.clone(); @@ -607,9 +606,6 @@ impl MailBackend for ImapType { 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 mut cmd = format!("UID STORE {}", uids[0]); diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index ea88c3ac..c3cf9b35 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -380,7 +380,6 @@ impl MailBackend for JmapType { _source_mailbox_hash: MailboxHash, _destination_mailbox_hash: MailboxHash, _move_: bool, - _destination_flags: Option, ) -> ResultFuture<()> { Err(MeliError::new("Unimplemented.")) } diff --git a/melib/src/backends/maildir/backend.rs b/melib/src/backends/maildir/backend.rs index b969acdb..a0e759c5 100644 --- a/melib/src/backends/maildir/backend.rs +++ b/melib/src/backends/maildir/backend.rs @@ -23,7 +23,7 @@ use super::{MaildirMailbox, MaildirOp, MaildirPathTrait}; use crate::backends::{RefreshEventKind::*, *}; use crate::conf::AccountSettings; use crate::email::{Envelope, EnvelopeHash, Flag}; -use crate::error::{MeliError, Result}; +use crate::error::{ErrorKind, MeliError, Result}; use crate::shellexpand::ShellExpandTrait; use futures::prelude::Stream; @@ -565,6 +565,14 @@ impl MailBackend for MaildirType { DebouncedEvent::Rename(src, dest) => { debug!("DebouncedEvent::Rename(src = {:?}, dest = {:?})", src, dest); let mailbox_hash = get_path_hash!(src); + let dest_mailbox = { + let dest_mailbox = get_path_hash!(dest); + if dest_mailbox == mailbox_hash { + None + } else { + Some(dest_mailbox) + } + }; let old_hash: EnvelopeHash = get_file_hash(src.as_path()); let new_hash: EnvelopeHash = get_file_hash(dest.as_path()); @@ -578,39 +586,88 @@ impl MailBackend for MaildirType { if index_lock.contains_key(&old_hash) && !index_lock[&old_hash].removed { debug!("contains_old_key"); - index_lock.entry(old_hash).and_modify(|e| { - debug!(&e.modified); - e.modified = Some(PathMod::Hash(new_hash)); - }); - (sender)( - account_hash, - BackendEvent::Refresh(RefreshEvent { - account_hash, - mailbox_hash: get_path_hash!(dest), - kind: Rename(old_hash, new_hash), - }), - ); - if !was_seen && is_seen { - let mut lck = mailbox_counts[&mailbox_hash].0.lock().unwrap(); - *lck = lck.saturating_sub(1); - } else if was_seen && !is_seen { - *mailbox_counts[&mailbox_hash].0.lock().unwrap() += 1; - } - if old_flags != new_flags { + if let Some(dest_mailbox) = dest_mailbox { + index_lock.entry(old_hash).and_modify(|e| { + e.removed = true; + }); + (sender)( account_hash, BackendEvent::Refresh(RefreshEvent { account_hash, - mailbox_hash: get_path_hash!(dest), - kind: NewFlags(new_hash, (new_flags, vec![])), + mailbox_hash, + kind: Remove(old_hash), }), ); + let file_name = dest + .as_path() + .strip_prefix(&root_path) + .unwrap() + .to_path_buf(); + drop(hash_indexes_lock); + if let Ok(env) = add_path_to_index( + &hash_indexes, + dest_mailbox, + dest.as_path(), + &cache_dir, + file_name, + ) { + mailbox_index + .lock() + .unwrap() + .insert(env.hash(), dest_mailbox); + debug!( + "Create event {} {} {}", + env.hash(), + env.subject(), + dest.display() + ); + if !env.is_seen() { + *mailbox_counts[&dest_mailbox].0.lock().unwrap() += 1; + } + *mailbox_counts[&dest_mailbox].1.lock().unwrap() += 1; + (sender)( + account_hash, + BackendEvent::Refresh(RefreshEvent { + account_hash, + mailbox_hash: dest_mailbox, + kind: Create(Box::new(env)), + }), + ); + } + } else { + index_lock.entry(old_hash).and_modify(|e| { + debug!(&e.modified); + e.modified = Some(PathMod::Hash(new_hash)); + }); + (sender)( + account_hash, + BackendEvent::Refresh(RefreshEvent { + account_hash, + mailbox_hash, + kind: Rename(old_hash, new_hash), + }), + ); + if !was_seen && is_seen { + let mut lck = + mailbox_counts[&mailbox_hash].0.lock().unwrap(); + *lck = lck.saturating_sub(1); + } else if was_seen && !is_seen { + *mailbox_counts[&mailbox_hash].0.lock().unwrap() += 1; + } + if old_flags != new_flags { + (sender)( + account_hash, + BackendEvent::Refresh(RefreshEvent { + account_hash, + mailbox_hash, + kind: NewFlags(new_hash, (new_flags, vec![])), + }), + ); + } + mailbox_index.lock().unwrap().insert(new_hash, mailbox_hash); + index_lock.insert(new_hash, dest.into()); } - mailbox_index - .lock() - .unwrap() - .insert(new_hash, get_path_hash!(dest)); - index_lock.insert(new_hash, dest.into()); continue; } else if !index_lock.contains_key(&new_hash) && index_lock @@ -640,7 +697,7 @@ impl MailBackend for MaildirType { drop(hash_indexes_lock); if let Ok(env) = add_path_to_index( &hash_indexes, - mailbox_hash, + dest_mailbox.unwrap_or(mailbox_hash), dest.as_path(), &cache_dir, file_name, @@ -648,7 +705,7 @@ impl MailBackend for MaildirType { mailbox_index .lock() .unwrap() - .insert(env.hash(), mailbox_hash); + .insert(env.hash(), dest_mailbox.unwrap_or(mailbox_hash)); debug!( "Create event {} {} {}", env.hash(), @@ -656,14 +713,20 @@ impl MailBackend for MaildirType { dest.display() ); if !env.is_seen() { - *mailbox_counts[&mailbox_hash].0.lock().unwrap() += 1; + *mailbox_counts[&dest_mailbox.unwrap_or(mailbox_hash)] + .0 + .lock() + .unwrap() += 1; } - *mailbox_counts[&mailbox_hash].1.lock().unwrap() += 1; + *mailbox_counts[&dest_mailbox.unwrap_or(mailbox_hash)] + .1 + .lock() + .unwrap() += 1; (sender)( account_hash, BackendEvent::Refresh(RefreshEvent { account_hash, - mailbox_hash, + mailbox_hash: dest_mailbox.unwrap_or(mailbox_hash), kind: Create(Box::new(env)), }), ); @@ -671,6 +734,43 @@ impl MailBackend for MaildirType { } else { debug!("not valid email"); } + } else if let Some(dest_mailbox) = dest_mailbox { + drop(hash_indexes_lock); + let file_name = dest + .as_path() + .strip_prefix(&root_path) + .unwrap() + .to_path_buf(); + if let Ok(env) = add_path_to_index( + &hash_indexes, + dest_mailbox, + dest.as_path(), + &cache_dir, + file_name, + ) { + mailbox_index + .lock() + .unwrap() + .insert(env.hash(), dest_mailbox); + debug!( + "Create event {} {} {}", + env.hash(), + env.subject(), + dest.display() + ); + if !env.is_seen() { + *mailbox_counts[&dest_mailbox].0.lock().unwrap() += 1; + } + *mailbox_counts[&dest_mailbox].1.lock().unwrap() += 1; + (sender)( + account_hash, + BackendEvent::Refresh(RefreshEvent { + account_hash, + mailbox_hash: dest_mailbox, + kind: Create(Box::new(env)), + }), + ); + } } else { if was_seen && !is_seen { *mailbox_counts[&mailbox_hash].0.lock().unwrap() += 1; @@ -679,7 +779,7 @@ impl MailBackend for MaildirType { account_hash, BackendEvent::Refresh(RefreshEvent { account_hash, - mailbox_hash: get_path_hash!(dest), + mailbox_hash, kind: Rename(old_hash, new_hash), }), ); @@ -689,7 +789,7 @@ impl MailBackend for MaildirType { account_hash, BackendEvent::Refresh(RefreshEvent { account_hash, - mailbox_hash: get_path_hash!(dest), + mailbox_hash, kind: NewFlags(new_hash, (new_flags, vec![])), }), ); @@ -813,6 +913,60 @@ impl MailBackend for MaildirType { })) } + fn copy_messages( + &mut self, + env_hashes: EnvelopeHashBatch, + source_mailbox_hash: MailboxHash, + destination_mailbox_hash: MailboxHash, + move_: bool, + ) -> ResultFuture<()> { + let hash_index = self.hash_indexes.clone(); + if !self.mailboxes.contains_key(&source_mailbox_hash) { + return Err(MeliError::new("Invalid source mailbox hash").set_kind(ErrorKind::Bug)); + } else if !self.mailboxes.contains_key(&destination_mailbox_hash) { + return Err(MeliError::new("Invalid destination mailbox hash").set_kind(ErrorKind::Bug)); + } + let mut dest_path: PathBuf = self.mailboxes[&destination_mailbox_hash].fs_path().into(); + dest_path.push("cur"); + Ok(Box::pin(async move { + let mut hash_indexes_lck = hash_index.lock().unwrap(); + let hash_index = hash_indexes_lck.entry(source_mailbox_hash).or_default(); + + for env_hash in env_hashes.iter() { + let path_src = { + 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 filename = path_src + .file_name() + .expect(&format!("Could not get filename of {}", path_src.display())); + dest_path.push(filename); + hash_index.entry(env_hash).or_default().modified = + Some(PathMod::Path(dest_path.clone())); + if move_ { + debug!("renaming {:?} to {:?}", path_src, dest_path); + fs::rename(&path_src, &dest_path)?; + debug!("success in rename"); + } else { + debug!("copying {:?} to {:?}", path_src, dest_path); + fs::copy(&path_src, &dest_path)?; + debug!("success in copy"); + } + dest_path.pop(); + } + Ok(()) + })) + } + fn create_mailbox( &mut self, new_path: String, diff --git a/melib/src/backends/mbox.rs b/melib/src/backends/mbox.rs index 075d8d1c..378ba062 100644 --- a/melib/src/backends/mbox.rs +++ b/melib/src/backends/mbox.rs @@ -993,6 +993,25 @@ impl MailBackend for MboxType { ))) } + fn copy_messages( + &mut self, + _env_hashes: EnvelopeHashBatch, + _source_mailbox_hash: MailboxHash, + _destination_mailbox_hash: MailboxHash, + _move_: bool, + ) -> ResultFuture<()> { + Err(MeliError::new("Unimplemented.")) + } + + fn set_flags( + &mut self, + _env_hashes: EnvelopeHashBatch, + _mailbox_hash: MailboxHash, + _flags: SmallVec<[(std::result::Result, bool); 8]>, + ) -> ResultFuture<()> { + Err(MeliError::new("Unimplemented.")) + } + fn save( &self, _bytes: Vec, diff --git a/melib/src/backends/nntp.rs b/melib/src/backends/nntp.rs index eff3fb1d..413c2134 100644 --- a/melib/src/backends/nntp.rs +++ b/melib/src/backends/nntp.rs @@ -287,7 +287,6 @@ impl MailBackend for NntpType { _source_mailbox_hash: MailboxHash, _destination_mailbox_hash: MailboxHash, _move_: bool, - _destination_flags: Option, ) -> ResultFuture<()> { Err(MeliError::new("Unimplemented.")) } diff --git a/melib/src/backends/notmuch.rs b/melib/src/backends/notmuch.rs index 5611ff39..512fcf00 100644 --- a/melib/src/backends/notmuch.rs +++ b/melib/src/backends/notmuch.rs @@ -684,6 +684,16 @@ impl MailBackend for NotmuchDb { Ok(Box::pin(async { Ok(()) })) } + fn copy_messages( + &mut self, + _env_hashes: EnvelopeHashBatch, + _source_mailbox_hash: MailboxHash, + _destination_mailbox_hash: MailboxHash, + _move_: bool, + ) -> ResultFuture<()> { + Err(MeliError::new("Unimplemented.")) + } + fn set_flags( &mut self, env_hashes: EnvelopeHashBatch, diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs index e5dbce68..0fbece3b 100644 --- a/src/components/mail/listing.rs +++ b/src/components/mail/listing.rs @@ -270,7 +270,6 @@ pub trait MailListingTrait: ListingTrait { mailbox_hash, destination_mailbox_hash, /* move? */ false, - /* flags */ None, ) }) { Err(err) => { @@ -309,7 +308,6 @@ pub trait MailListingTrait: ListingTrait { mailbox_hash, destination_mailbox_hash, /* move? */ true, - /* flags */ None, ) }) { Err(err) => { diff --git a/src/components/mail/listing/conversations.rs b/src/components/mail/listing/conversations.rs index a910628d..00e2c31b 100644 --- a/src/components/mail/listing/conversations.rs +++ b/src/components/mail/listing/conversations.rs @@ -1437,6 +1437,12 @@ impl Component for ConversationsListing { ); } } + UIEvent::EnvelopeRemove(ref _env_hash, ref thread_hash) => { + if self.order.contains_key(thread_hash) { + self.refresh_mailbox(context, false); + self.set_dirty(true); + } + } UIEvent::EnvelopeUpdate(ref env_hash) => { let account = &context.accounts[&self.cursor_pos.0]; let threads = account.collection.get_threads(self.cursor_pos.1); diff --git a/src/conf/accounts.rs b/src/conf/accounts.rs index 60799871..ffef7325 100644 --- a/src/conf/accounts.rs +++ b/src/conf/accounts.rs @@ -605,7 +605,9 @@ impl Account { } pub fn reload(&mut self, event: RefreshEvent, mailbox_hash: MailboxHash) -> Option { - if !self.mailbox_entries[&mailbox_hash].status.is_available() { + if !self.mailbox_entries[&mailbox_hash].status.is_available() + && !self.mailbox_entries[&mailbox_hash].status.is_parsing() + { self.event_queue.push_back((mailbox_hash, event)); return None; }