Expose HIGHESTMODSEQ value in EXPUNGE response

If the `QRESYNC` extension (RFC 7162) is being used, `EXPUNGE` responses
will return the new highest mod sequence for the mailbox after the
expunge operation. Access to this value is quite valuable for caching
clients.
This commit is contained in:
Conrad Hoffmann 2022-07-14 11:59:44 +02:00
parent db29117463
commit 81ed9ff1cf
3 changed files with 61 additions and 34 deletions

View file

@ -784,8 +784,9 @@ impl<T: Read + Write> Session<T> {
/// removes all messages that have [`Flag::Deleted`] set from the currently selected mailbox.
/// The message sequence number of each message that is removed is returned.
pub fn expunge(&mut self) -> Result<Deleted> {
self.run_command_and_read_response("EXPUNGE")
.and_then(|lines| parse_expunge(lines, &mut self.unsolicited_responses_tx))
self.run_command("EXPUNGE")?;
self.read_response()
.and_then(|(lines, _)| parse_expunge(lines, &mut self.unsolicited_responses_tx))
}
/// The [`UID EXPUNGE` command](https://tools.ietf.org/html/rfc4315#section-2.1) permanently
@ -811,8 +812,9 @@ impl<T: Read + Write> Session<T> {
/// Alternatively, the client may fall back to using just [`Session::expunge`], risking the
/// unintended removal of some messages.
pub fn uid_expunge<S: AsRef<str>>(&mut self, uid_set: S) -> Result<Deleted> {
self.run_command_and_read_response(&format!("UID EXPUNGE {}", uid_set.as_ref()))
.and_then(|lines| parse_expunge(lines, &mut self.unsolicited_responses_tx))
self.run_command(&format!("UID EXPUNGE {}", uid_set.as_ref()))?;
self.read_response()
.and_then(|(lines, _)| parse_expunge(lines, &mut self.unsolicited_responses_tx))
}
/// The [`CHECK` command](https://tools.ietf.org/html/rfc3501#section-6.4.1) requests a

View file

@ -78,6 +78,7 @@ pub fn parse_expunge(
let mut lines: &[u8] = &lines;
let mut expunged = Vec::new();
let mut vanished = Vec::new();
let mut mod_seq: Option<u64> = None;
loop {
if lines.is_empty() {
@ -85,6 +86,13 @@ pub fn parse_expunge(
}
match imap_proto::parser::parse_response(lines) {
Ok((rest, Response::Done { status, code, .. })) => {
assert_eq!(status, imap_proto::Status::Ok);
lines = rest;
if let Some(ResponseCode::HighestModSeq(ms)) = code {
mod_seq = Some(ms);
};
}
Ok((rest, Response::Expunge(seq))) => {
lines = rest;
expunged.push(seq);
@ -110,9 +118,9 @@ pub fn parse_expunge(
// always one or the other.
// https://tools.ietf.org/html/rfc7162#section-3.2.10
if !vanished.is_empty() {
Ok(Deleted::from_vanished(vanished))
Ok(Deleted::from_vanished(vanished, mod_seq))
} else {
Ok(Deleted::from_expunged(expunged))
Ok(Deleted::from_expunged(expunged, mod_seq))
}
}

View file

@ -1,13 +1,15 @@
use super::{Seq, Uid};
use std::ops::RangeInclusive;
/// An enum representing message sequence numbers or UID sequence sets returned
/// in response to a `EXPUNGE` command.
/// A struct containing message sequence numbers or UID sequence sets and a mod
/// sequence returned in response to a `EXPUNGE` command.
///
/// The `EXPUNGE` command may return several `EXPUNGE` responses referencing
/// message sequence numbers, or it may return a `VANISHED` response referencing
/// multiple UID values in a sequence set if the client has enabled
/// [QRESYNC](https://tools.ietf.org/html/rfc7162#section-3.2.7).
/// [QRESYNC](https://tools.ietf.org/html/rfc7162#section-3.2.7). If `QRESYNC` is
/// enabled, the server will also return the mod sequence of the completed
/// operation.
///
/// `Deleted` implements some iterators to make it easy to use. If the caller
/// knows that they should be receiving an `EXPUNGE` or `VANISHED` response,
@ -39,7 +41,16 @@ use std::ops::RangeInclusive;
/// # }
/// ```
#[derive(Debug, Clone)]
pub enum Deleted {
pub struct Deleted {
/// The list of messages that were expunged
pub messages: DeletedMessages,
/// The mod sequence of the performed operation, if the `QRESYNC` extension
/// is enabled.
pub mod_seq: Option<u64>,
}
#[derive(Debug, Clone)]
pub enum DeletedMessages {
/// Message sequence numbers given in an `EXPUNGE` response.
Expunged(Vec<Seq>),
/// Message UIDs given in a `VANISHED` response.
@ -49,14 +60,20 @@ pub enum Deleted {
impl Deleted {
/// Construct a new `Deleted` value from a vector of message sequence
/// numbers returned in one or more `EXPUNGE` responses.
pub fn from_expunged(v: Vec<u32>) -> Self {
Deleted::Expunged(v)
pub fn from_expunged(v: Vec<u32>, mod_seq: Option<u64>) -> Self {
Self {
messages: DeletedMessages::Expunged(v),
mod_seq: mod_seq,
}
}
/// Construct a new `Deleted` value from a sequence-set of UIDs
/// returned in a `VANISHED` response
pub fn from_vanished(v: Vec<RangeInclusive<u32>>) -> Self {
Deleted::Vanished(v)
pub fn from_vanished(v: Vec<RangeInclusive<u32>>, mod_seq: Option<u64>) -> Self {
Self {
messages: DeletedMessages::Vanished(v),
mod_seq: mod_seq,
}
}
/// Return an iterator over message sequence numbers from an `EXPUNGE`
@ -64,9 +81,9 @@ impl Deleted {
/// can be used to ensure only sequence numbers returned in an `EXPUNGE`
/// response are processed.
pub fn seqs(&self) -> impl Iterator<Item = Seq> + '_ {
match self {
Deleted::Expunged(s) => s.iter(),
Deleted::Vanished(_) => [].iter(),
match &self.messages {
DeletedMessages::Expunged(s) => s.iter(),
DeletedMessages::Vanished(_) => [].iter(),
}
.copied()
}
@ -75,18 +92,18 @@ impl Deleted {
/// If the client is expecting UIDs this function can be used to ensure
/// only UIDs are processed.
pub fn uids(&self) -> impl Iterator<Item = Uid> + '_ {
match self {
Deleted::Expunged(_) => [].iter(),
Deleted::Vanished(s) => s.iter(),
match &self.messages {
DeletedMessages::Expunged(_) => [].iter(),
DeletedMessages::Vanished(s) => s.iter(),
}
.flat_map(|range| range.clone())
}
/// Return if the set is empty
pub fn is_empty(&self) -> bool {
match self {
Deleted::Expunged(v) => v.is_empty(),
Deleted::Vanished(v) => v.is_empty(),
match &self.messages {
DeletedMessages::Expunged(v) => v.is_empty(),
DeletedMessages::Vanished(v) => v.is_empty(),
}
}
}
@ -96,9 +113,9 @@ impl<'a> IntoIterator for &'a Deleted {
type IntoIter = Box<dyn Iterator<Item = u32> + 'a>;
fn into_iter(self) -> Self::IntoIter {
match self {
Deleted::Expunged(_) => Box::new(self.seqs()),
Deleted::Vanished(_) => Box::new(self.uids()),
match &self.messages {
DeletedMessages::Expunged(_) => Box::new(self.seqs()),
DeletedMessages::Vanished(_) => Box::new(self.uids()),
}
}
}
@ -109,7 +126,7 @@ mod test {
#[test]
fn seq() {
let seqs = Deleted::from_expunged(vec![3, 6, 9, 12]);
let seqs = Deleted::from_expunged(vec![3, 6, 9, 12], None);
let mut i = seqs.into_iter();
assert_eq!(Some(3), i.next());
assert_eq!(Some(6), i.next());
@ -117,14 +134,14 @@ mod test {
assert_eq!(Some(12), i.next());
assert_eq!(None, i.next());
let seqs = Deleted::from_expunged(vec![]);
let seqs = Deleted::from_expunged(vec![], None);
let mut i = seqs.into_iter();
assert_eq!(None, i.next());
}
#[test]
fn seq_set() {
let uids = Deleted::from_vanished(vec![1..=1, 3..=5, 8..=9, 12..=12]);
let uids = Deleted::from_vanished(vec![1..=1, 3..=5, 8..=9, 12..=12], None);
let mut i = uids.into_iter();
assert_eq!(Some(1), i.next());
assert_eq!(Some(3), i.next());
@ -135,13 +152,13 @@ mod test {
assert_eq!(Some(12), i.next());
assert_eq!(None, i.next());
let uids = Deleted::from_vanished(vec![]);
let uids = Deleted::from_vanished(vec![], None);
assert_eq!(None, uids.into_iter().next());
}
#[test]
fn seqs() {
let seqs: Deleted = Deleted::from_expunged(vec![3, 6, 9, 12]);
let seqs: Deleted = Deleted::from_expunged(vec![3, 6, 9, 12], None);
let mut count: u32 = 0;
for seq in seqs.seqs() {
count += 3;
@ -152,7 +169,7 @@ mod test {
#[test]
fn uids() {
let uids: Deleted = Deleted::from_vanished(vec![1..=6]);
let uids: Deleted = Deleted::from_vanished(vec![1..=6], None);
let mut count: u32 = 0;
for uid in uids.uids() {
count += 1;
@ -163,7 +180,7 @@ mod test {
#[test]
fn generic_iteration() {
let seqs: Deleted = Deleted::from_expunged(vec![3, 6, 9, 12]);
let seqs: Deleted = Deleted::from_expunged(vec![3, 6, 9, 12], None);
let mut count: u32 = 0;
for seq in &seqs {
count += 3;
@ -171,7 +188,7 @@ mod test {
}
assert_eq!(count, 12);
let uids: Deleted = Deleted::from_vanished(vec![1..=6]);
let uids: Deleted = Deleted::from_vanished(vec![1..=6], None);
let mut count: u32 = 0;
for uid in &uids {
count += 1;