Idle builder (#202)

This commit is contained in:
mordak 2021-05-26 20:55:42 -05:00 committed by GitHub
parent 07121152d7
commit d8d69a363b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 74 deletions

View file

@ -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<Deleted>` instead of `Result<Vec<u32>>`.
- 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.

View file

@ -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),
}

View file

@ -1093,7 +1093,7 @@ impl<T: Read + Write> Session<T> {
/// command, as specified in the base IMAP specification.
///
/// See [`extensions::idle::Handle`] for details.
pub fn idle(&mut self) -> Result<extensions::idle::Handle<'_, T>> {
pub fn idle(&mut self) -> extensions::idle::Handle<'_, T> {
extensions::idle::Handle::make(self)
}

View file

@ -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<T>,
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<T>) -> Result<Self> {
let mut h = Handle {
pub(crate) fn make(session: &'a mut Session<T>) -> 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<F>(&mut self, reconnect: bool, mut callback: F) -> Result<WaitOutcome>
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<F>(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<F>(self, callback: F) -> Result<()>
pub fn wait_while<F>(&mut self, callback: F) -> Result<WaitOutcome>
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<F>(self, timeout: Duration, callback: F) -> Result<WaitOutcome>
where
F: FnMut(UnsolicitedResponse) -> bool,
{
self.timed_wait(timeout, false, callback)
}
fn timed_wait<F>(
mut self,
timeout: Duration,
reconnect: bool,
callback: F,
) -> Result<WaitOutcome>
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
}