diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index df1ec0b4..d8eb0086 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -127,6 +127,7 @@ pub struct UIDStore { uidvalidity: Arc>>, hash_index: Arc>>, uid_index: Arc>>, + msn_index: Arc>>>, byte_cache: Arc>>, tag_index: Arc>>, @@ -145,6 +146,7 @@ impl Default for UIDStore { uidvalidity: Default::default(), hash_index: Default::default(), uid_index: Default::default(), + msn_index: Default::default(), byte_cache: Default::default(), mailboxes: Arc::new(RwLock::new(Default::default())), tag_index: Arc::new(RwLock::new(Default::default())), @@ -414,6 +416,7 @@ impl MailBackend for ImapType { debug!("responses len is {}", v.len()); for UidFetchResponse { uid, + message_sequence_number, flags, envelope, .. @@ -438,6 +441,13 @@ impl MailBackend for ImapType { env.labels_mut().push(hash); } } + uid_store + .msn_index + .lock() + .unwrap() + .entry(mailbox_hash) + .or_default() + .insert(message_sequence_number - 1, uid); uid_store .hash_index .lock() diff --git a/melib/src/backends/imap/connection.rs b/melib/src/backends/imap/connection.rs index ef866854..38fc6276 100644 --- a/melib/src/backends/imap/connection.rs +++ b/melib/src/backends/imap/connection.rs @@ -595,6 +595,30 @@ impl ImapConnection { self.uid_store.refresh_events.lock().unwrap().push(ev); } } + + pub fn create_uid_msn_cache(&mut self, mailbox_hash: MailboxHash, low: usize) -> Result<()> { + debug_assert!(low > 0); + let mut response = String::new(); + if self + .current_mailbox + .map(|h| h != mailbox_hash) + .unwrap_or(true) + { + self.examine_mailbox(mailbox_hash, &mut response)?; + } + self.send_command(format!("UID SEARCH {}:*", low).as_bytes())?; + self.read_response(&mut response, RequiredResponses::SEARCH)?; + debug!("uid search response {:?}", &response); + let mut msn_index_lck = self.uid_store.msn_index.lock().unwrap(); + let msn_index = msn_index_lck.entry(mailbox_hash).or_default(); + let _ = msn_index.drain(low - 1..); + msn_index.extend( + debug!(protocol_parser::search_results(response.as_bytes()))? + .1 + .into_iter(), + ); + Ok(()) + } } pub struct ImapBlockingConnection { diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index e053ba4a..ec673c81 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -421,6 +421,7 @@ pub fn my_flags(input: &[u8]) -> IResult<&[u8], Flag> { #[derive(Debug)] pub struct UidFetchResponse<'a> { pub uid: UID, + pub message_sequence_number: usize, pub flags: Option<(Flag, Vec)>, pub body: Option<&'a [u8]>, pub envelope: Option, @@ -471,7 +472,18 @@ pub fn uid_fetch_response(input: &str) -> ImapParseResult> }; } - while (input.as_bytes()[i] as char).is_numeric() { + let mut ret = UidFetchResponse { + uid: 0, + message_sequence_number: 0, + flags: None, + body: None, + envelope: None, + }; + + while input.as_bytes()[i].is_ascii_digit() { + let b: u8 = input.as_bytes()[i] - 0x30; + ret.message_sequence_number *= 10; + ret.message_sequence_number += b as usize; i += 1; bounds!(); } @@ -479,13 +491,6 @@ pub fn uid_fetch_response(input: &str) -> ImapParseResult> eat_whitespace!(); should_start_with!(input[i..], "FETCH ("); i += "FETCH (".len(); - - let mut ret = UidFetchResponse { - uid: 0, - flags: None, - body: None, - envelope: None, - }; let mut has_attachments = false; while i < input.len() { eat_whitespace!(break); diff --git a/melib/src/backends/imap/untagged.rs b/melib/src/backends/imap/untagged.rs index 52646936..ae74697a 100644 --- a/melib/src/backends/imap/untagged.rs +++ b/melib/src/backends/imap/untagged.rs @@ -74,7 +74,32 @@ impl ImapConnection { (std::time::Instant::now(), Err(reason.into())); } UntaggedResponse::Expunge(n) => { - debug!("expunge {}", n); + let deleted_uid = self + .uid_store + .msn_index + .lock() + .unwrap() + .entry(mailbox_hash) + .or_default() + .remove(n); + debug!("expunge {}, UID = {}", n, deleted_uid); + let deleted_hash: crate::email::EnvelopeHash = self + .uid_store + .uid_index + .lock() + .unwrap() + .remove(&(mailbox_hash, deleted_uid)) + .unwrap(); + self.uid_store + .hash_index + .lock() + .unwrap() + .remove(&deleted_hash); + self.add_refresh_event(RefreshEvent { + account_hash: self.uid_store.account_hash, + mailbox_hash, + kind: Remove(deleted_hash), + }); } UntaggedResponse::Exists(n) => { /* UID FETCH ALL UID, cross-ref, then FETCH difference headers diff --git a/melib/src/backends/imap/watch.rs b/melib/src/backends/imap/watch.rs index b342d4c7..038344d5 100644 --- a/melib/src/backends/imap/watch.rs +++ b/melib/src/backends/imap/watch.rs @@ -400,11 +400,37 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { } } Ok(Some(Expunge(n))) => { + // The EXPUNGE response reports that the specified message sequence + // number has been permanently removed from the mailbox. The message + // sequence number for each successive message in the mailbox is + // immediately decremented by 1, and this decrement is reflected in + // message sequence numbers in subsequent responses (including other + // untagged EXPUNGE responses). + let mut conn = super::try_lock(&main_conn, Some(std::time::Duration::new(10, 0)))?; work_context .set_status .send((thread_id, format!("got `{} EXPUNGED` notification", n))) .unwrap(); - debug!("expunge {}", n); + let deleted_uid = uid_store + .msn_index + .lock() + .unwrap() + .entry(mailbox_hash) + .or_default() + .remove(n); + debug!("expunge {}, UID = {}", n, deleted_uid); + let deleted_hash: EnvelopeHash = uid_store + .uid_index + .lock() + .unwrap() + .remove(&(mailbox_hash, deleted_uid)) + .unwrap(); + uid_store.hash_index.lock().unwrap().remove(&deleted_hash); + conn.add_refresh_event(RefreshEvent { + account_hash: uid_store.account_hash, + mailbox_hash, + kind: Remove(deleted_hash), + }); } Ok(Some(Exists(n))) => { let mut conn = super::try_lock(&main_conn, Some(std::time::Duration::new(10, 0)))?;