diff --git a/CHANGELOG.md b/CHANGELOG.md index 5287012..0fc7a82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - MSRV increased to 1.43 for nom6 and bitvec - `expunge` and `uid_expunge` return `Result` instead of `Result>`. - - Idle `wait_keepalive_while` replaces `wait_keepalive` and takes a callback with an `UnsolicitedResponse` in parameter. + - IDLE capability now provides a builder interface. All `wait_*` functions are merged into `wait_while` which takes a callback with an `UnsolicitedResponse` in parameter. - All `Session.append_with_*` methods are obsoleted by `append` which returns now an `AppendCmd` builder. - Envelope `&'a [u8]` attributes are replaced by `Cow<'a, [u8]>`. - `Flag`, `Mailbox`, `UnsolicitedResponse` and `Error` are now declared as non exhaustive. diff --git a/examples/idle.rs b/examples/idle.rs index a329a4c..2cc9685 100644 --- a/examples/idle.rs +++ b/examples/idle.rs @@ -54,8 +54,6 @@ fn main() { 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. // @@ -63,7 +61,7 @@ fn main() { // 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| { + let idle_result = imap.idle().wait_while(|response| { num_responses += 1; println!("IDLE response #{}: {:?}", num_responses, response); if num_responses >= max_responses { @@ -76,7 +74,7 @@ fn main() { }); match idle_result { - Ok(()) => println!("IDLE finished normally"), + Ok(reason) => println!("IDLE finished normally {:?}", reason), Err(e) => println!("IDLE finished with error {:?}", e), } diff --git a/src/client.rs b/src/client.rs index 75baf7f..46a9808 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1093,7 +1093,7 @@ impl Session { /// command, as specified in the base IMAP specification. /// /// See [`extensions::idle::Handle`] for details. - pub fn idle(&mut self) -> Result> { + pub fn idle(&mut self) -> extensions::idle::Handle<'_, T> { extensions::idle::Handle::make(self) } diff --git a/src/extensions/idle.rs b/src/extensions/idle.rs index 5c6f978..559fc77 100644 --- a/src/extensions/idle.rs +++ b/src/extensions/idle.rs @@ -19,10 +19,11 @@ use std::time::Duration; /// specificed in [RFC 2177](https://tools.ietf.org/html/rfc2177) until the underlying server state /// changes in some way. /// -/// Each of the `wait` functions takes a callback function which receives any responses +/// The `wait_while` function takes a callback function which receives any responses /// that arrive on the channel while IDLE. The callback function implements whatever /// logic is needed to handle the IDLE response, and then returns a boolean /// to continue idling (`true`) or stop (`false`). +/// /// For users that want the IDLE to exit on any change (the behavior proior to version 3.0), /// a convenience callback function [`stop_on_any`] is provided. /// @@ -37,25 +38,24 @@ use std::time::Duration; /// imap.select("INBOX") /// .expect("Could not select mailbox"); /// -/// let idle = imap.idle().expect("Could not IDLE"); -/// -/// // Exit on any mailbox change -/// let result = idle.wait_keepalive_while(idle::stop_on_any); +/// // Exit on any mailbox change. By default, connections will be periodically +/// // refreshed in the background. +/// let result = imap.idle().wait_while(idle::stop_on_any); /// # } /// ``` /// /// Note that the server MAY consider a client inactive if it has an IDLE command running, and if /// such a server has an inactivity timeout it MAY log the client off implicitly at the end of its -/// timeout period. Because of that, clients using IDLE are advised to terminate the IDLE and -/// re-issue it at least every 29 minutes to avoid being logged off. [`Handle::wait_keepalive_while`] -/// does this. This still allows a client to receive immediate mailbox updates even though it need -/// only "poll" at half hour intervals. +/// timeout period. Because of that, clients using IDLE are advised to terminate the IDLE and +/// re-issue it at least every 29 minutes to avoid being logged off. This is done by default, but +/// can be disabled by calling [`Handle::keepalive`] /// /// As long as a [`Handle`] is active, the mailbox cannot be otherwise accessed. #[derive(Debug)] pub struct Handle<'a, T: Read + Write> { session: &'a mut Session, - keepalive: Duration, + timeout: Duration, + keepalive: bool, done: bool, } @@ -73,11 +73,7 @@ pub fn stop_on_any(_response: UnsolicitedResponse) -> bool { false } -/// Must be implemented for a transport in order for a `Session` using that transport to support -/// operations with timeouts. -/// -/// Examples of where this is useful is for `Handle::wait_keepalive_while` and -/// `Handle::wait_timeout_while`. +/// Must be implemented for a transport in order for a `Session` to use IDLE. pub trait SetReadTimeout { /// Set the timeout for subsequent reads to the given one. /// @@ -88,14 +84,13 @@ pub trait SetReadTimeout { } impl<'a, T: Read + Write + 'a> Handle<'a, T> { - pub(crate) fn make(session: &'a mut Session) -> Result { - let mut h = Handle { + pub(crate) fn make(session: &'a mut Session) -> Self { + Handle { session, - keepalive: Duration::from_secs(29 * 60), + timeout: Duration::from_secs(29 * 60), + keepalive: true, done: false, - }; - h.init()?; - Ok(h) + } } fn init(&mut self) -> Result<()> { @@ -132,7 +127,7 @@ impl<'a, T: Read + Write + 'a> Handle<'a, T> { /// Internal helper that doesn't consume self. /// - /// This is necessary so that we can keep using the inner `Session` in `wait_keepalive_while`. + /// This is necessary so that we can keep using the inner `Session` in `wait_while`. fn wait_inner(&mut self, reconnect: bool, mut callback: F) -> Result where F: FnMut(UnsolicitedResponse) -> bool, @@ -196,38 +191,36 @@ impl<'a, T: Read + Write + 'a> Handle<'a, T> { (_, result) => result, } } - - /// Block until the given callback returns `false`, or until a response - /// arrives that is not explicitly handled by [`UnsolicitedResponse`]. - pub fn wait_while(mut self, callback: F) -> Result<()> - where - F: FnMut(UnsolicitedResponse) -> bool, - { - self.wait_inner(true, callback).map(|_| ()) - } } impl<'a, T: SetReadTimeout + Read + Write + 'a> Handle<'a, T> { - /// Set the keep-alive interval to use when `wait_keepalive_while` is called. + /// Set the timeout duration on the connection. This will also set the frequency + /// at which the connection is refreshed. /// - /// The interval defaults to 29 minutes as dictated by RFC 2177. - pub fn set_keepalive(&mut self, interval: Duration) { - self.keepalive = interval; + /// The interval defaults to 29 minutes as given in RFC 2177. + pub fn timeout(&mut self, interval: Duration) -> &mut Self { + self.timeout = interval; + self + } + + /// Do not continuously refresh the IDLE connection in the background. + /// + /// By default, connections will periodically be refreshed in the background using the + /// timeout duration set by [`Handle::timeout`]. If you do not want this behaviour, call + /// this function and the connection will simply IDLE until `wait_while` returns or + /// the timeout expires. + pub fn keepalive(&mut self, keepalive: bool) -> &mut Self { + self.keepalive = keepalive; + self } /// Block until the given callback returns `false`, or until a response /// arrives that is not explicitly handled by [`UnsolicitedResponse`]. - /// - /// This method differs from [`Handle::wait_while`] in that it will periodically refresh the IDLE - /// connection, to prevent the server from timing out our connection. The keepalive interval is - /// set to 29 minutes by default, as dictated by RFC 2177, but can be changed using - /// [`Handle::set_keepalive`]. - /// - /// This is the recommended method to use for waiting. - pub fn wait_keepalive_while(self, callback: F) -> Result<()> + pub fn wait_while(&mut self, callback: F) -> Result where F: FnMut(UnsolicitedResponse) -> bool, { + self.init()?; // The server MAY consider a client inactive if it has an IDLE command // running, and if such a server has an inactivity timeout it MAY log // the client off implicitly at the end of its timeout period. Because @@ -235,34 +228,11 @@ impl<'a, T: SetReadTimeout + Read + Write + 'a> Handle<'a, T> { // re-issue it at least every 29 minutes to avoid being logged off. // This still allows a client to receive immediate mailbox updates even // though it need only "poll" at half hour intervals. - let keepalive = self.keepalive; - self.timed_wait(keepalive, true, callback).map(|_| ()) - } - - /// Block until the given given amount of time has elapsed, the given callback - /// returns `false`, or until a response arrives that is not explicitly handled - /// by [`UnsolicitedResponse`]. - pub fn wait_with_timeout_while(self, timeout: Duration, callback: F) -> Result - where - F: FnMut(UnsolicitedResponse) -> bool, - { - self.timed_wait(timeout, false, callback) - } - - fn timed_wait( - mut self, - timeout: Duration, - reconnect: bool, - callback: F, - ) -> Result - where - F: FnMut(UnsolicitedResponse) -> bool, - { self.session .stream .get_mut() - .set_read_timeout(Some(timeout))?; - let res = self.wait_inner(reconnect, callback); + .set_read_timeout(Some(self.timeout))?; + let res = self.wait_inner(self.keepalive, callback); let _ = self.session.stream.get_mut().set_read_timeout(None).is_ok(); res }