Skip to content

Commit

Permalink
Merge pull request #186 from mordak/idle-responses
Browse files Browse the repository at this point in the history
Pass IDLE responses to caller.
  • Loading branch information
jonhoo committed Apr 20, 2021
2 parents 4108917 + 48db461 commit 382c025
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 209 deletions.
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ default = ["tls"]
native-tls = { version = "0.2.2", optional = true }
regex = "1.0"
bufstream = "0.1"
imap-proto = "0.14.0"
imap-proto = "0.14.1"
nom = { version = "6.0", default-features = false }
base64 = "0.13"
chrono = { version = "0.4", default-features = false }
Expand All @@ -31,6 +31,7 @@ lazy_static = "1.4"
lettre = "0.9"
lettre_email = "0.9"
rustls-connector = "0.13.0"
structopt = "0.3"

[[example]]
name = "basic"
Expand All @@ -40,6 +41,10 @@ required-features = ["default"]
name = "gmail_oauth2"
required-features = ["default"]

[[example]]
name = "idle"
required-features = ["default"]

[[test]]
name = "imap_integration"
required-features = ["default"]
84 changes: 84 additions & 0 deletions examples/idle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use native_tls::TlsConnector;
use structopt::StructOpt;

#[derive(StructOpt, Debug)]
#[structopt(name = "idle")]
struct Opt {
// The server name to connect to
#[structopt(short, long)]
server: String,

// The port to use
#[structopt(short, long, default_value = "993")]
port: u16,

// The account username
#[structopt(short, long)]
username: String,

// The account password. In a production system passwords
// would normally be in a config or fetched at runtime from
// a password manager or user prompt and not passed on the
// command line.
#[structopt(short = "w", long)]
password: String,

// The mailbox to IDLE on
#[structopt(short, long, default_value = "INBOX")]
mailbox: String,

#[structopt(
short = "x",
long,
help = "The number of responses to receive before exiting",
default_value = "5"
)]
max_responses: usize,
}

fn main() {
let opt = Opt::from_args();

let ssl_conn = TlsConnector::builder().build().unwrap();
let client = imap::connect((opt.server.clone(), opt.port), opt.server, &ssl_conn)
.expect("Could not connect to imap server");
let mut imap = client
.login(opt.username, opt.password)
.expect("Could not authenticate");

// Turn on debug output so we can see the actual traffic coming
// from the server and how it is handled in our callback.
// This wouldn't be turned on in a production build, but is helpful
// in examples and for debugging.
imap.debug = true;

imap.select(opt.mailbox).expect("Could not select mailbox");

let idle = imap.idle().expect("Could not IDLE");

// Implement a trivial counter that causes the IDLE callback to end the IDLE
// after a fixed number of responses.
//
// A threaded client could use channels or shared data to interact with the
// rest of the program and update mailbox state, decide to exit the IDLE, etc.
let mut num_responses = 0;
let max_responses = opt.max_responses;
let idle_result = idle.wait_keepalive_while(|response| {
num_responses += 1;
println!("IDLE response #{}: {:?}", num_responses, response);
if num_responses >= max_responses {
// Stop IDLE
false
} else {
// Continue IDLE
true
}
});

match idle_result {
Ok(()) => println!("IDLE finished normally"),
Err(e) => println!("IDLE finished with error {:?}", e),
}

imap.logout().expect("Could not log out");
}
12 changes: 7 additions & 5 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ pub enum Error {
Validate(ValidateError),
/// Error appending an e-mail.
Append,
/// An unexpected response was received. This could be a response from a command,
/// or an unsolicited response that could not be converted into a local type in
/// [`UnsolicitedResponse`].
Unexpected(Response<'static>),
}

impl From<IoError> for Error {
Expand Down Expand Up @@ -112,7 +116,7 @@ impl From<TlsError> for Error {

impl<'a> From<Response<'a>> for Error {
fn from(err: Response<'a>) -> Error {
Error::Parse(ParseError::Unexpected(format!("{:?}", err)))
Error::Unexpected(err.into_owned())
}
}

Expand All @@ -130,6 +134,7 @@ impl fmt::Display for Error {
Error::Bad(ref data) => write!(f, "Bad Response: {}", data),
Error::ConnectionLost => f.write_str("Connection Lost"),
Error::Append => f.write_str("Could not append mail to mailbox"),
Error::Unexpected(ref r) => write!(f, "Unexpected Response: {:?}", r),
}
}
}
Expand All @@ -149,6 +154,7 @@ impl StdError for Error {
Error::No(_) => "No Response",
Error::ConnectionLost => "Connection lost",
Error::Append => "Could not append mail to mailbox",
Error::Unexpected(_) => "Unexpected Response",
}
}

Expand All @@ -170,8 +176,6 @@ impl StdError for Error {
pub enum ParseError {
/// Indicates an error parsing the status response. Such as OK, NO, and BAD.
Invalid(Vec<u8>),
/// An unexpected response was encountered.
Unexpected(String),
/// The client could not find or decode the server's authentication challenge.
Authentication(String, Option<DecodeError>),
/// The client received data that was not UTF-8 encoded.
Expand All @@ -182,7 +186,6 @@ impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
ParseError::Invalid(_) => f.write_str("Unable to parse status response"),
ParseError::Unexpected(_) => f.write_str("Encountered unexpected parse response"),
ParseError::Authentication(_, _) => {
f.write_str("Unable to parse authentication response")
}
Expand All @@ -195,7 +198,6 @@ impl StdError for ParseError {
fn description(&self) -> &str {
match *self {
ParseError::Invalid(_) => "Unable to parse status response",
ParseError::Unexpected(_) => "Encountered unexpected parsed response",
ParseError::Authentication(_, _) => "Unable to parse authentication response",
ParseError::DataNotUtf8(_, _) => "Unable to parse data as UTF-8 text",
}
Expand Down
Loading

0 comments on commit 382c025

Please sign in to comment.