Merge pull request #234 from bitfehler/bitfehler/expunge-mod-seq
Expose HIGHESTMODSEQ value in EXPUNGE response
This commit is contained in:
commit
f3a03ed693
3 changed files with 88 additions and 34 deletions
|
|
@ -784,8 +784,9 @@ impl<T: Read + Write> Session<T> {
|
||||||
/// removes all messages that have [`Flag::Deleted`] set from the currently selected mailbox.
|
/// 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.
|
/// The message sequence number of each message that is removed is returned.
|
||||||
pub fn expunge(&mut self) -> Result<Deleted> {
|
pub fn expunge(&mut self) -> Result<Deleted> {
|
||||||
self.run_command_and_read_response("EXPUNGE")
|
self.run_command("EXPUNGE")?;
|
||||||
.and_then(|lines| parse_expunge(lines, &mut self.unsolicited_responses_tx))
|
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
|
/// 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
|
/// Alternatively, the client may fall back to using just [`Session::expunge`], risking the
|
||||||
/// unintended removal of some messages.
|
/// unintended removal of some messages.
|
||||||
pub fn uid_expunge<S: AsRef<str>>(&mut self, uid_set: S) -> Result<Deleted> {
|
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()))
|
self.run_command(&format!("UID EXPUNGE {}", uid_set.as_ref()))?;
|
||||||
.and_then(|lines| parse_expunge(lines, &mut self.unsolicited_responses_tx))
|
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
|
/// The [`CHECK` command](https://tools.ietf.org/html/rfc3501#section-6.4.1) requests a
|
||||||
|
|
|
||||||
38
src/parse.rs
38
src/parse.rs
|
|
@ -78,6 +78,7 @@ pub fn parse_expunge(
|
||||||
let mut lines: &[u8] = &lines;
|
let mut lines: &[u8] = &lines;
|
||||||
let mut expunged = Vec::new();
|
let mut expunged = Vec::new();
|
||||||
let mut vanished = Vec::new();
|
let mut vanished = Vec::new();
|
||||||
|
let mut mod_seq: Option<u64> = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if lines.is_empty() {
|
if lines.is_empty() {
|
||||||
|
|
@ -85,6 +86,13 @@ pub fn parse_expunge(
|
||||||
}
|
}
|
||||||
|
|
||||||
match imap_proto::parser::parse_response(lines) {
|
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))) => {
|
Ok((rest, Response::Expunge(seq))) => {
|
||||||
lines = rest;
|
lines = rest;
|
||||||
expunged.push(seq);
|
expunged.push(seq);
|
||||||
|
|
@ -110,9 +118,9 @@ pub fn parse_expunge(
|
||||||
// always one or the other.
|
// always one or the other.
|
||||||
// https://tools.ietf.org/html/rfc7162#section-3.2.10
|
// https://tools.ietf.org/html/rfc7162#section-3.2.10
|
||||||
if !vanished.is_empty() {
|
if !vanished.is_empty() {
|
||||||
Ok(Deleted::from_vanished(vanished))
|
Ok(Deleted::from_vanished(vanished, mod_seq))
|
||||||
} else {
|
} else {
|
||||||
Ok(Deleted::from_expunged(expunged))
|
Ok(Deleted::from_expunged(expunged, mod_seq))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -664,4 +672,30 @@ mod tests {
|
||||||
assert_eq!(first.body(), None);
|
assert_eq!(first.body(), None);
|
||||||
assert_eq!(first.header(), None);
|
assert_eq!(first.header(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_expunged_mod_seq_test() {
|
||||||
|
// VANISHED can appear if the user has enabled QRESYNC (RFC 7162), in response to
|
||||||
|
// SELECT/EXAMINE (QRESYNC); UID FETCH (VANISHED); or EXPUNGE commands. In the latter
|
||||||
|
// case, the VANISHED responses will be parsed with the response and the list of
|
||||||
|
// expunged message is included in the returned struct.
|
||||||
|
let (mut send, recv) = mpsc::channel();
|
||||||
|
|
||||||
|
// Test VANISHED mixed with FETCH
|
||||||
|
let lines = b"* VANISHED 3:5,12\r\n\
|
||||||
|
B202 OK [HIGHESTMODSEQ 20010715194045319] expunged\r\n";
|
||||||
|
|
||||||
|
let deleted = parse_expunge(lines.to_vec(), &mut send).unwrap();
|
||||||
|
|
||||||
|
// No unsolicited responses, they are aggregated in the returned type
|
||||||
|
assert!(recv.try_recv().is_err());
|
||||||
|
|
||||||
|
assert_eq!(deleted.mod_seq, Some(20010715194045319));
|
||||||
|
let mut del = deleted.uids();
|
||||||
|
assert_eq!(del.next(), Some(3));
|
||||||
|
assert_eq!(del.next(), Some(4));
|
||||||
|
assert_eq!(del.next(), Some(5));
|
||||||
|
assert_eq!(del.next(), Some(12));
|
||||||
|
assert_eq!(del.next(), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
use super::{Seq, Uid};
|
use super::{Seq, Uid};
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
/// An enum representing message sequence numbers or UID sequence sets returned
|
/// A struct containing message sequence numbers or UID sequence sets and a mod
|
||||||
/// in response to a `EXPUNGE` command.
|
/// sequence returned in response to a `EXPUNGE` command.
|
||||||
///
|
///
|
||||||
/// The `EXPUNGE` command may return several `EXPUNGE` responses referencing
|
/// The `EXPUNGE` command may return several `EXPUNGE` responses referencing
|
||||||
/// message sequence numbers, or it may return a `VANISHED` response referencing
|
/// message sequence numbers, or it may return a `VANISHED` response referencing
|
||||||
/// multiple UID values in a sequence set if the client has enabled
|
/// 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
|
/// `Deleted` implements some iterators to make it easy to use. If the caller
|
||||||
/// knows that they should be receiving an `EXPUNGE` or `VANISHED` response,
|
/// knows that they should be receiving an `EXPUNGE` or `VANISHED` response,
|
||||||
|
|
@ -39,7 +41,17 @@ use std::ops::RangeInclusive;
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Deleted {
|
#[non_exhaustive]
|
||||||
|
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.
|
/// Message sequence numbers given in an `EXPUNGE` response.
|
||||||
Expunged(Vec<Seq>),
|
Expunged(Vec<Seq>),
|
||||||
/// Message UIDs given in a `VANISHED` response.
|
/// Message UIDs given in a `VANISHED` response.
|
||||||
|
|
@ -49,14 +61,20 @@ pub enum Deleted {
|
||||||
impl Deleted {
|
impl Deleted {
|
||||||
/// Construct a new `Deleted` value from a vector of message sequence
|
/// Construct a new `Deleted` value from a vector of message sequence
|
||||||
/// numbers returned in one or more `EXPUNGE` responses.
|
/// numbers returned in one or more `EXPUNGE` responses.
|
||||||
pub fn from_expunged(v: Vec<u32>) -> Self {
|
pub fn from_expunged(v: Vec<u32>, mod_seq: Option<u64>) -> Self {
|
||||||
Deleted::Expunged(v)
|
Self {
|
||||||
|
messages: DeletedMessages::Expunged(v),
|
||||||
|
mod_seq: mod_seq,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a new `Deleted` value from a sequence-set of UIDs
|
/// Construct a new `Deleted` value from a sequence-set of UIDs
|
||||||
/// returned in a `VANISHED` response
|
/// returned in a `VANISHED` response
|
||||||
pub fn from_vanished(v: Vec<RangeInclusive<u32>>) -> Self {
|
pub fn from_vanished(v: Vec<RangeInclusive<u32>>, mod_seq: Option<u64>) -> Self {
|
||||||
Deleted::Vanished(v)
|
Self {
|
||||||
|
messages: DeletedMessages::Vanished(v),
|
||||||
|
mod_seq: mod_seq,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return an iterator over message sequence numbers from an `EXPUNGE`
|
/// Return an iterator over message sequence numbers from an `EXPUNGE`
|
||||||
|
|
@ -64,9 +82,9 @@ impl Deleted {
|
||||||
/// can be used to ensure only sequence numbers returned in an `EXPUNGE`
|
/// can be used to ensure only sequence numbers returned in an `EXPUNGE`
|
||||||
/// response are processed.
|
/// response are processed.
|
||||||
pub fn seqs(&self) -> impl Iterator<Item = Seq> + '_ {
|
pub fn seqs(&self) -> impl Iterator<Item = Seq> + '_ {
|
||||||
match self {
|
match &self.messages {
|
||||||
Deleted::Expunged(s) => s.iter(),
|
DeletedMessages::Expunged(s) => s.iter(),
|
||||||
Deleted::Vanished(_) => [].iter(),
|
DeletedMessages::Vanished(_) => [].iter(),
|
||||||
}
|
}
|
||||||
.copied()
|
.copied()
|
||||||
}
|
}
|
||||||
|
|
@ -75,18 +93,18 @@ impl Deleted {
|
||||||
/// If the client is expecting UIDs this function can be used to ensure
|
/// If the client is expecting UIDs this function can be used to ensure
|
||||||
/// only UIDs are processed.
|
/// only UIDs are processed.
|
||||||
pub fn uids(&self) -> impl Iterator<Item = Uid> + '_ {
|
pub fn uids(&self) -> impl Iterator<Item = Uid> + '_ {
|
||||||
match self {
|
match &self.messages {
|
||||||
Deleted::Expunged(_) => [].iter(),
|
DeletedMessages::Expunged(_) => [].iter(),
|
||||||
Deleted::Vanished(s) => s.iter(),
|
DeletedMessages::Vanished(s) => s.iter(),
|
||||||
}
|
}
|
||||||
.flat_map(|range| range.clone())
|
.flat_map(|range| range.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return if the set is empty
|
/// Return if the set is empty
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
match self {
|
match &self.messages {
|
||||||
Deleted::Expunged(v) => v.is_empty(),
|
DeletedMessages::Expunged(v) => v.is_empty(),
|
||||||
Deleted::Vanished(v) => v.is_empty(),
|
DeletedMessages::Vanished(v) => v.is_empty(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,9 +114,9 @@ impl<'a> IntoIterator for &'a Deleted {
|
||||||
type IntoIter = Box<dyn Iterator<Item = u32> + 'a>;
|
type IntoIter = Box<dyn Iterator<Item = u32> + 'a>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
match self {
|
match &self.messages {
|
||||||
Deleted::Expunged(_) => Box::new(self.seqs()),
|
DeletedMessages::Expunged(_) => Box::new(self.seqs()),
|
||||||
Deleted::Vanished(_) => Box::new(self.uids()),
|
DeletedMessages::Vanished(_) => Box::new(self.uids()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +127,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn seq() {
|
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();
|
let mut i = seqs.into_iter();
|
||||||
assert_eq!(Some(3), i.next());
|
assert_eq!(Some(3), i.next());
|
||||||
assert_eq!(Some(6), i.next());
|
assert_eq!(Some(6), i.next());
|
||||||
|
|
@ -117,14 +135,14 @@ mod test {
|
||||||
assert_eq!(Some(12), i.next());
|
assert_eq!(Some(12), i.next());
|
||||||
assert_eq!(None, 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();
|
let mut i = seqs.into_iter();
|
||||||
assert_eq!(None, i.next());
|
assert_eq!(None, i.next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn seq_set() {
|
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();
|
let mut i = uids.into_iter();
|
||||||
assert_eq!(Some(1), i.next());
|
assert_eq!(Some(1), i.next());
|
||||||
assert_eq!(Some(3), i.next());
|
assert_eq!(Some(3), i.next());
|
||||||
|
|
@ -135,13 +153,13 @@ mod test {
|
||||||
assert_eq!(Some(12), i.next());
|
assert_eq!(Some(12), i.next());
|
||||||
assert_eq!(None, 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());
|
assert_eq!(None, uids.into_iter().next());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn seqs() {
|
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;
|
let mut count: u32 = 0;
|
||||||
for seq in seqs.seqs() {
|
for seq in seqs.seqs() {
|
||||||
count += 3;
|
count += 3;
|
||||||
|
|
@ -152,7 +170,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uids() {
|
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;
|
let mut count: u32 = 0;
|
||||||
for uid in uids.uids() {
|
for uid in uids.uids() {
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
@ -163,7 +181,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn generic_iteration() {
|
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;
|
let mut count: u32 = 0;
|
||||||
for seq in &seqs {
|
for seq in &seqs {
|
||||||
count += 3;
|
count += 3;
|
||||||
|
|
@ -171,7 +189,7 @@ mod test {
|
||||||
}
|
}
|
||||||
assert_eq!(count, 12);
|
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;
|
let mut count: u32 = 0;
|
||||||
for uid in &uids {
|
for uid in &uids {
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue