diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index 5cf8cf9..dc8b5a8 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -218,9 +218,7 @@ impl<'a> AuthenticatedContext<'a> { for attr in attributes.iter() { ret_attrs.push(match attr { StatusAttribute::Messages => StatusAttributeValue::Messages(view.exists()?), - StatusAttribute::Unseen => { - StatusAttributeValue::Unseen(view.unseen().map(|x| x.get()).unwrap_or(0)) - } + StatusAttribute::Unseen => StatusAttributeValue::Unseen(view.unseen_count() as u32), StatusAttribute::Recent => StatusAttributeValue::Recent(view.recent()?), StatusAttribute::UidNext => StatusAttributeValue::UidNext(view.uidnext()), StatusAttribute::UidValidity => { @@ -287,6 +285,11 @@ impl<'a> AuthenticatedContext<'a> { S: A142 OK [READ-WRITE] SELECT completed --- a mailbox with no unseen message -> no unseen entry + NOTES: + RFC3501 (imap4rev1) says if there is no OK [UNSEEN] response, client must make no assumption, + it is therefore correct to not return it even if there are unseen messages + RFC9051 (imap4rev2) says that OK [UNSEEN] responses are deprecated after SELECT and EXAMINE + For Aerogramme, we just don't send the OK [UNSEEN], it's correct to do in both specifications. 20 select "INBOX.achats" * FLAGS (\Answered \Flagged \Deleted \Seen \Draft $Forwarded JUNK $label1) diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs index f9485e4..25b3f3e 100644 --- a/src/imap/mailbox_view.rs +++ b/src/imap/mailbox_view.rs @@ -61,9 +61,6 @@ impl MailboxView { data.extend(new_view.flags_status()?.into_iter()); data.push(new_view.uidvalidity_status()?); data.push(new_view.uidnext_status()?); - if let Some(unseen) = new_view.unseen_status()? { - data.push(unseen); - } Ok((new_view, data)) } @@ -432,27 +429,6 @@ impl MailboxView { self.known_state.uidnext } - /// Produces an UNSEEN message (if relevant) corresponding to the - /// first unseen message id in `known_state` - fn unseen_status(&self) -> Result> { - if let Some(unseen) = self.unseen() { - let status_unseen = - Status::ok(None, Some(Code::Unseen(unseen.clone())), "First unseen UID") - .map_err(Error::msg)?; - Ok(Some(Body::Status(status_unseen))) - } else { - Ok(None) - } - } - - pub(crate) fn unseen(&self) -> Option { - self.known_state - .idx_by_flag - .get(&"$unseen".to_string()) - .and_then(|os| os.get_min()) - .cloned() - } - /// Produce an EXISTS message corresponding to the number of mails /// in `known_state` fn exists_status(&self) -> Result { @@ -504,6 +480,17 @@ impl MailboxView { Ok(ret) } + + pub(crate) fn unseen_count(&self) -> usize { + let total = self.known_state.table.len(); + let seen = self + .known_state + .idx_by_flag + .get(&Flag::Seen.to_string()) + .map(|x| x.len()) + .unwrap_or(0); + total - seen + } } fn string_to_flag(f: &str) -> Option { @@ -523,7 +510,6 @@ fn string_to_flag(f: &str) -> Option { Ok(a) => Some(Flag::Extension(a)), }, }, - Some('$') if f == "$unseen" => None, Some(_) => match Atom::try_from(f.clone()) { Err(_) => { tracing::error!(flag=%f, "Unable to encode flag as IMAP atom"); diff --git a/src/mail/mailbox.rs b/src/mail/mailbox.rs index 19a95e0..c61ab0c 100644 --- a/src/mail/mailbox.rs +++ b/src/mail/mailbox.rs @@ -374,10 +374,7 @@ impl MailboxInternal { )?; // Add mail to Bayou mail index - let add_mail_op = self - .uid_index - .state() - .op_mail_add(ident, vec!["\\Unseen".into()]); + let add_mail_op = self.uid_index.state().op_mail_add(ident, vec![]); self.uid_index.push(add_mail_op).await?; Ok(())