diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index d8eb0086..12676738 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -46,7 +46,7 @@ use crate::conf::AccountSettings; use crate::email::*; use crate::error::{MeliError, Result, ResultIntoMeliError}; use std::collections::{hash_map::DefaultHasher, BTreeMap}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::hash::Hasher; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; @@ -236,7 +236,7 @@ impl MailBackend for ImapType { let _tx = tx.clone(); if let Err(err) = (move || { let tx = _tx; - let mut our_unseen = 0; + let mut our_unseen: BTreeSet = Default::default(); let mut valid_hash_set: HashSet = HashSet::default(); let cached_hash_set: HashSet = (|| -> Result> { @@ -266,8 +266,8 @@ impl MailBackend for ImapType { if !envelopes.is_empty() { let mut payload = vec![]; for (uid, env) in envelopes { - if !env.flags().contains(Flag::SEEN) { - our_unseen += 1; + if !env.is_seen() { + our_unseen.insert(env.hash()); } uid_store .hash_index @@ -283,7 +283,7 @@ impl MailBackend for ImapType { } debug!("sending cached payload for {}", mailbox_hash); - *unseen.lock().unwrap() = our_unseen; + unseen.lock().unwrap().insert_set(our_unseen.clone()); tx.send(AsyncStatus::Payload(Ok(payload))).unwrap(); } Ok(ret) @@ -334,8 +334,10 @@ impl MailBackend for ImapType { permissions.rename_messages = !examine_response.read_only; permissions.delete_messages = !examine_response.read_only; permissions.delete_messages = !examine_response.read_only; - let mut mailbox_exists = mailbox_exists.lock().unwrap(); - *mailbox_exists = examine_response.exists; + mailbox_exists + .lock() + .unwrap() + .set_not_yet_seen(examine_response.exists); } if examine_response.exists == 0 { if uid_store.cache_headers { @@ -429,8 +431,8 @@ impl MailBackend for ImapType { env.set_hash(h.finish()); valid_hash_set.insert(env.hash()); if let Some((flags, keywords)) = flags { - if !flags.contains(Flag::SEEN) { - our_unseen += 1; + if !flags.intersects(Flag::SEEN) { + our_unseen.insert(env.hash()); } env.set_flags(flags); for f in keywords { @@ -487,8 +489,14 @@ impl MailBackend for ImapType { kind: RefreshEventKind::Remove(env_hash), }); } - *unseen.lock().unwrap() = our_unseen; let progress = envelopes.len(); + unseen + .lock() + .unwrap() + .insert_set(our_unseen.iter().cloned().collect()); + mailbox_exists.lock().unwrap().insert_existing_set( + envelopes.iter().map(|(_, env)| env.hash()).collect::<_>(), + ); tx.send(AsyncStatus::Payload(Ok(envelopes .into_iter() .map(|(_, env)| env) diff --git a/melib/src/backends/imap/mailbox.rs b/melib/src/backends/imap/mailbox.rs index 8fc96c8c..31508445 100644 --- a/melib/src/backends/imap/mailbox.rs +++ b/melib/src/backends/imap/mailbox.rs @@ -21,9 +21,78 @@ use crate::backends::{ BackendMailbox, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox, }; +use crate::email::EnvelopeHash; use crate::error::*; +use std::collections::BTreeSet; use std::sync::{Arc, Mutex, RwLock}; +#[derive(Debug, Default, Clone)] +pub struct LazyCountSet { + not_yet_seen: usize, + set: BTreeSet, +} + +impl LazyCountSet { + pub fn set_not_yet_seen(&mut self, new_val: usize) { + self.not_yet_seen = new_val; + } + + pub fn insert_existing(&mut self, new_val: EnvelopeHash) -> bool { + if self.not_yet_seen == 0 { + false + } else { + self.not_yet_seen -= 1; + self.set.insert(new_val); + true + } + } + + pub fn insert_existing_set(&mut self, set: BTreeSet) -> bool { + debug!("insert_existing_set {:?}", &set); + if self.not_yet_seen < set.len() { + false + } else { + self.not_yet_seen -= set.len(); + self.set.extend(set.into_iter()); + true + } + } + + #[inline(always)] + pub fn len(&self) -> usize { + self.set.len() + self.not_yet_seen + } + + #[inline(always)] + pub fn clear(&mut self) { + self.set.clear(); + self.not_yet_seen = 0; + } + + pub fn insert_new(&mut self, new_val: EnvelopeHash) { + self.set.insert(new_val); + } + + pub fn insert_set(&mut self, set: BTreeSet) { + debug!("insert__set {:?}", &set); + self.set.extend(set.into_iter()); + } + + pub fn remove(&mut self, new_val: EnvelopeHash) -> bool { + self.set.remove(&new_val) + } +} + +#[test] +fn test_lazy_count_set() { + let mut new = LazyCountSet::default(); + new.set_not_yet_seen(10); + for i in 0..10 { + assert!(new.insert_existing(i)); + } + assert!(!new.insert_existing(10)); +} + #[derive(Debug, Default, Clone)] pub struct ImapMailbox { pub(super) hash: MailboxHash, @@ -38,8 +107,8 @@ pub struct ImapMailbox { pub is_subscribed: bool, pub permissions: Arc>, - pub exists: Arc>, - pub unseen: Arc>, + pub exists: Arc>, + pub unseen: Arc>, } impl ImapMailbox { @@ -98,6 +167,6 @@ impl BackendMailbox for ImapMailbox { } fn count(&self) -> Result<(usize, usize)> { - Ok((*self.unseen.lock()?, *self.exists.lock()?)) + Ok((self.unseen.lock()?.len(), self.exists.lock()?.len())) } } diff --git a/melib/src/backends/imap/untagged.rs b/melib/src/backends/imap/untagged.rs index 08cf752c..9acae88e 100644 --- a/melib/src/backends/imap/untagged.rs +++ b/melib/src/backends/imap/untagged.rs @@ -106,13 +106,13 @@ impl ImapConnection { * */ let mut prev_exists = mailbox.exists.lock().unwrap(); debug!("exists {}", n); - if n > *prev_exists { + if n > prev_exists.len() { try_fail!( mailbox_hash, self.send_command( &[ b"FETCH", - format!("{}:{}", *prev_exists + 1, n).as_bytes(), + format!("{}:{}", prev_exists.len() + 1, n).as_bytes(), b"(UID FLAGS RFC822)", ] .join(&b' '), @@ -165,8 +165,9 @@ impl ImapConnection { mailbox.path(), ); if !env.is_seen() { - *mailbox.unseen.lock().unwrap() += 1; + mailbox.unseen.lock().unwrap().insert_new(env.hash()); } + prev_exists.insert_new(env.hash()); self.add_refresh_event(RefreshEvent { account_hash: self.uid_store.account_hash, mailbox_hash, @@ -179,9 +180,6 @@ impl ImapConnection { debug!(e); } } - *prev_exists = n; - } else if n < *prev_exists { - *prev_exists = n; } } UntaggedResponse::Recent(_) => { @@ -213,7 +211,6 @@ impl ImapConnection { uid, flags, body, .. } in v { - *mailbox.exists.lock().unwrap() += 1; if !self .uid_store .uid_index @@ -253,9 +250,14 @@ impl ImapConnection { } } if !env.is_seen() { - *mailbox.unseen.lock().unwrap() += 1; + mailbox + .unseen + .lock() + .unwrap() + .insert_new(env.hash()); } + mailbox.exists.lock().unwrap().insert_new(env.hash()); self.add_refresh_event(RefreshEvent { account_hash: self.uid_store.account_hash, mailbox_hash, @@ -307,6 +309,11 @@ impl ImapConnection { let env_hash = lck.get(&(mailbox_hash, uid)).map(|&h| h); drop(lck); if let Some(env_hash) = env_hash { + if !flags.0.intersects(crate::email::Flag::SEEN) { + mailbox.unseen.lock().unwrap().insert_new(env_hash); + } else { + mailbox.unseen.lock().unwrap().remove(env_hash); + } self.add_refresh_event(RefreshEvent { account_hash: self.uid_store.account_hash, mailbox_hash, diff --git a/melib/src/backends/imap/watch.rs b/melib/src/backends/imap/watch.rs index 038344d5..9452de37 100644 --- a/melib/src/backends/imap/watch.rs +++ b/melib/src/backends/imap/watch.rs @@ -155,7 +155,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { debug!("select response {}", &response); { let mut prev_exists = mailbox.exists.lock().unwrap(); - *prev_exists = match protocol_parser::select_response(&response) { + match protocol_parser::select_response(&response) { Ok(ok) => { { uidvalidity = ok.uidvalidity; @@ -168,7 +168,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { mailbox_hash, kind: RefreshEventKind::Rescan, }); - *prev_exists = 0; + prev_exists.clear(); /* uid_store.uid_index.lock().unwrap().clear(); uid_store.hash_index.lock().unwrap().clear(); @@ -194,7 +194,6 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { } } debug!(&ok); - ok.exists } Err(e) => { debug!("{:?}", e); @@ -322,7 +321,6 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { )) .unwrap(); ctr += 1; - *mailbox.exists.lock().unwrap() += 1; if !uid_store .uid_index .lock() @@ -361,7 +359,11 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { } } if !env.is_seen() { - *mailbox.unseen.lock().unwrap() += 1; + mailbox + .unseen + .lock() + .unwrap() + .insert_new(env.hash()); } if uid_store.cache_headers { cache::save_envelopes( @@ -371,6 +373,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { &[(uid, &env)], )?; } + mailbox.exists.lock().unwrap().insert_new(env.hash()); conn.add_refresh_event(RefreshEvent { account_hash: uid_store.account_hash, @@ -445,12 +448,12 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { format!( "got `{} EXISTS` notification (EXISTS was previously {} for {}", n, - *prev_exists, + prev_exists.len(), mailbox.path() ), )) .unwrap(); - if n > *prev_exists { + if n > prev_exists.len() { exit_on_error!( conn, mailbox_hash, @@ -460,7 +463,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { conn.send_command( &[ b"FETCH", - format!("{}:{}", *prev_exists + 1, n).as_bytes(), + format!("{}:{}", prev_exists.len() + 1, n).as_bytes(), b"(UID FLAGS RFC822)", ] .join(&b' '), @@ -523,7 +526,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { mailbox.path(), ); if !env.is_seen() { - *mailbox.unseen.lock().unwrap() += 1; + mailbox.unseen.lock().unwrap().insert_new(env.hash()); } if uid_store.cache_headers { cache::save_envelopes( @@ -533,6 +536,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { &[(uid, &env)], )?; } + prev_exists.insert_new(env.hash()); conn.add_refresh_event(RefreshEvent { account_hash: uid_store.account_hash, @@ -550,10 +554,6 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { debug!(e); } } - - *prev_exists = n; - } else if n < *prev_exists { - *prev_exists = n; } } Ok(Some(Fetch(msg_seq, flags))) => { @@ -588,6 +588,11 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { .unwrap() .get(&(mailbox_hash, uid)) { + if !flags.0.intersects(crate::email::Flag::SEEN) { + mailbox.unseen.lock().unwrap().insert_new(*env_hash); + } else { + mailbox.unseen.lock().unwrap().remove(*env_hash); + } conn.add_refresh_event(RefreshEvent { account_hash: uid_store.account_hash, mailbox_hash, @@ -767,7 +772,11 @@ pub fn examine_updates( } } if !env.is_seen() { - *mailbox.unseen.lock().unwrap() += 1; + mailbox + .unseen + .lock() + .unwrap() + .insert_new(env.hash()); } if uid_store.cache_headers { cache::save_envelopes( @@ -777,6 +786,7 @@ pub fn examine_updates( &[(uid, &env)], )?; } + prev_exists.insert_new(env.hash()); conn.add_refresh_event(RefreshEvent { account_hash: uid_store.account_hash, @@ -800,7 +810,7 @@ pub fn examine_updates( } } } - } else if n > *prev_exists { + } else if n > prev_exists.len() { /* UID FETCH ALL UID, cross-ref, then FETCH difference headers * */ debug!("exists {}", n); @@ -812,7 +822,7 @@ pub fn examine_updates( conn.send_command( &[ b"FETCH", - format!("{}:{}", *prev_exists + 1, n).as_bytes(), + format!("{}:{}", prev_exists.len() + 1, n).as_bytes(), b"(UID FLAGS RFC822)", ] .join(&b' '), @@ -863,7 +873,7 @@ pub fn examine_updates( mailbox.path(), ); if !env.is_seen() { - *mailbox.unseen.lock().unwrap() += 1; + mailbox.unseen.lock().unwrap().insert_new(env.hash()); } if uid_store.cache_headers { cache::save_envelopes( @@ -873,6 +883,7 @@ pub fn examine_updates( &[(uid, &env)], )?; } + prev_exists.insert_new(env.hash()); conn.add_refresh_event(RefreshEvent { account_hash: uid_store.account_hash, @@ -886,10 +897,6 @@ pub fn examine_updates( debug!(e); } } - - *prev_exists = n; - } else if n < *prev_exists { - *prev_exists = n; } } Err(e) => {