Skip to content

Commit f02950a

Browse files
authored
Merge pull request #91 from dario23/master
send unsolicited responses to a channel instead of discarding them
2 parents 5c91f4c + 8d7b527 commit f02950a

File tree

3 files changed

+209
-53
lines changed

3 files changed

+209
-53
lines changed

src/client.rs

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::collections::HashSet;
44
use std::io::{self, Read, Write};
55
use std::net::{TcpStream, ToSocketAddrs};
66
use std::ops::{Deref, DerefMut};
7+
use std::sync::mpsc;
78
use std::time::Duration;
89

910
use super::authenticator::Authenticator;
@@ -44,6 +45,10 @@ fn validate_str(value: &str) -> Result<String> {
4445
#[derive(Debug)]
4546
pub struct Session<T: Read + Write> {
4647
conn: Connection<T>,
48+
/// Server responses that are not related to the current command. See also the note on
49+
/// [unilateral server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7).
50+
pub unsolicited_responses: mpsc::Receiver<UnsolicitedResponse>,
51+
unsolicited_responses_tx: mpsc::Sender<UnsolicitedResponse>,
4752
}
4853

4954
/// An (unauthenticated) handle to talk to an IMAP server. This is what you get when first
@@ -396,7 +401,7 @@ impl<T: Read + Write> Client<T> {
396401
);
397402
} else {
398403
ok_or_unauth_client_err!(self.read_response_onto(&mut line), self);
399-
return Ok(Session { conn: self.conn });
404+
return Ok(Session::new(self.conn));
400405
}
401406
}
402407
}
@@ -443,27 +448,34 @@ impl<T: Read + Write> Client<T> {
443448
self
444449
);
445450

446-
Ok(Session { conn: self.conn })
451+
Ok(Session::new(self.conn))
447452
}
448453
}
449454

450455
impl<T: Read + Write> Session<T> {
456+
// not public, just to avoid duplicating the channel creation code
457+
fn new(conn: Connection<T>) -> Self {
458+
let (tx, rx) = mpsc::channel();
459+
Session { conn, unsolicited_responses: rx, unsolicited_responses_tx: tx }
460+
}
461+
451462
/// Selects a mailbox
452463
///
453464
/// Note that the server *is* allowed to unilaterally send things to the client for messages in
454465
/// a selected mailbox whose status has changed. See the note on [unilateral server responses
455466
/// in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7). This means that if you use
456467
/// [`Connection::run_command_and_read_response`], you *may* see additional untagged `RECENT`,
457-
/// `EXISTS`, `FETCH`, and `EXPUNGE` responses!
468+
/// `EXISTS`, `FETCH`, and `EXPUNGE` responses. You can get them from the
469+
/// `unsolicited_responses` channel of the [`Session`](struct.Session.html).
458470
pub fn select(&mut self, mailbox_name: &str) -> Result<Mailbox> {
459471
self.run_command_and_read_response(&format!("SELECT {}", validate_str(mailbox_name)?))
460-
.and_then(|lines| parse_mailbox(&lines[..]))
472+
.and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
461473
}
462474

463475
/// Examine is identical to Select, but the selected mailbox is identified as read-only
464476
pub fn examine(&mut self, mailbox_name: &str) -> Result<Mailbox> {
465477
self.run_command_and_read_response(&format!("EXAMINE {}", validate_str(mailbox_name)?))
466-
.and_then(|lines| parse_mailbox(&lines[..]))
478+
.and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
467479
}
468480

469481
/// Fetch retreives data associated with a set of messages in the mailbox.
@@ -473,7 +485,7 @@ impl<T: Read + Write> Session<T> {
473485
/// server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7).
474486
pub fn fetch(&mut self, sequence_set: &str, query: &str) -> ZeroCopyResult<Vec<Fetch>> {
475487
self.run_command_and_read_response(&format!("FETCH {} {}", sequence_set, query))
476-
.and_then(parse_fetches)
488+
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
477489
}
478490

479491
/// Fetch retreives data associated with a set of messages by UID in the mailbox.
@@ -483,7 +495,7 @@ impl<T: Read + Write> Session<T> {
483495
/// server responses in RFC 3501](https://tools.ietf.org/html/rfc3501#section-7).
484496
pub fn uid_fetch(&mut self, uid_set: &str, query: &str) -> ZeroCopyResult<Vec<Fetch>> {
485497
self.run_command_and_read_response(&format!("UID FETCH {} {}", uid_set, query))
486-
.and_then(parse_fetches)
498+
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
487499
}
488500

489501
/// Noop always succeeds, and it does nothing.
@@ -530,7 +542,7 @@ impl<T: Read + Write> Session<T> {
530542
/// Capability requests a listing of capabilities that the server supports.
531543
pub fn capabilities(&mut self) -> ZeroCopyResult<Capabilities> {
532544
self.run_command_and_read_response("CAPABILITY")
533-
.and_then(parse_capabilities)
545+
.and_then(|lines| parse_capabilities(lines, &mut self.unsolicited_responses_tx))
534546
}
535547

536548
/// Expunge permanently removes all messages that have the \Deleted flag set from the currently
@@ -560,12 +572,12 @@ impl<T: Read + Write> Session<T> {
560572
/// Store alters data associated with a message in the mailbox.
561573
pub fn store(&mut self, sequence_set: &str, query: &str) -> ZeroCopyResult<Vec<Fetch>> {
562574
self.run_command_and_read_response(&format!("STORE {} {}", sequence_set, query))
563-
.and_then(parse_fetches)
575+
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
564576
}
565577

566578
pub fn uid_store(&mut self, uid_set: &str, query: &str) -> ZeroCopyResult<Vec<Fetch>> {
567579
self.run_command_and_read_response(&format!("UID STORE {} {}", uid_set, query))
568-
.and_then(parse_fetches)
580+
.and_then(|lines| parse_fetches(lines, &mut self.unsolicited_responses_tx))
569581
}
570582

571583
/// Copy copies the specified message to the end of the specified destination mailbox.
@@ -612,7 +624,7 @@ impl<T: Read + Write> Session<T> {
612624
quote!(reference_name),
613625
mailbox_search_pattern
614626
))
615-
.and_then(parse_names)
627+
.and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx))
616628
}
617629

618630
/// The LSUB command returns a subset of names from the set of names
@@ -627,7 +639,7 @@ impl<T: Read + Write> Session<T> {
627639
quote!(reference_name),
628640
mailbox_search_pattern
629641
))
630-
.and_then(parse_names)
642+
.and_then(|lines| parse_names(lines, &mut self.unsolicited_responses_tx))
631643
}
632644

633645
/// The STATUS command requests the status of the indicated mailbox.
@@ -637,7 +649,7 @@ impl<T: Read + Write> Session<T> {
637649
validate_str(mailbox_name)?,
638650
status_data_items
639651
))
640-
.and_then(|lines| parse_mailbox(&lines[..]))
652+
.and_then(|lines| parse_mailbox(&lines[..], &mut self.unsolicited_responses_tx))
641653
}
642654

643655
/// Returns a handle that can be used to block until the state of the currently selected
@@ -664,14 +676,14 @@ impl<T: Read + Write> Session<T> {
664676
/// the list of message sequence numbers of those messages.
665677
pub fn search(&mut self, query: &str) -> Result<HashSet<u32>> {
666678
self.run_command_and_read_response(&format!("SEARCH {}", query))
667-
.and_then(parse_ids)
679+
.and_then(|lines| parse_ids(lines, &mut self.unsolicited_responses_tx))
668680
}
669681

670682
/// Searches the mailbox for messages that match the given criteria and returns
671683
/// the list of unique identifier numbers of those messages.
672684
pub fn uid_search(&mut self, query: &str) -> Result<HashSet<u32>> {
673685
self.run_command_and_read_response(&format!("UID SEARCH {}", query))
674-
.and_then(parse_ids)
686+
.and_then(|lines| parse_ids(lines, &mut self.unsolicited_responses_tx))
675687
}
676688

677689
// these are only here because they are public interface, the rest is in `Connection`
@@ -836,9 +848,7 @@ mod tests {
836848

837849
macro_rules! mock_session {
838850
($s:expr) => {
839-
Session {
840-
conn: Client::new($s).conn,
841-
}
851+
Session::new(Client::new($s).conn)
842852
};
843853
}
844854

0 commit comments

Comments
 (0)