imap_patch_for_async_imap_lite/extensions/idle.rs
1//! Adds support for the IMAP IDLE command specificed in [RFC
2//! 2177](https://tools.ietf.org/html/rfc2177).
3
4use crate::client::Session;
5use crate::error::{Error, Result};
6#[cfg(feature = "tls")]
7use native_tls::TlsStream;
8use std::io::{self, Read, Write};
9use std::net::TcpStream;
10use std::time::Duration;
11
12/// `Handle` allows a client to block waiting for changes to the remote mailbox.
13///
14/// The handle blocks using the [`IDLE` command](https://tools.ietf.org/html/rfc2177#section-3)
15/// specificed in [RFC 2177](https://tools.ietf.org/html/rfc2177) until the underlying server state
16/// changes in some way. While idling does inform the client what changes happened on the server,
17/// this implementation will currently just block until _anything_ changes, and then notify the
18///
19/// Note that the server MAY consider a client inactive if it has an IDLE command running, and if
20/// such a server has an inactivity timeout it MAY log the client off implicitly at the end of its
21/// timeout period. Because of that, clients using IDLE are advised to terminate the IDLE and
22/// re-issue it at least every 29 minutes to avoid being logged off. [`Handle::wait_keepalive`]
23/// does this. This still allows a client to receive immediate mailbox updates even though it need
24/// only "poll" at half hour intervals.
25///
26/// As long as a [`Handle`] is active, the mailbox cannot be otherwise accessed.
27#[derive(Debug)]
28pub struct Handle<'a, T: Read + Write> {
29 session: &'a mut Session<T>,
30 keepalive: Duration,
31 done: bool,
32}
33
34/// The result of a wait on a [`Handle`]
35#[derive(Debug, PartialEq, Eq)]
36pub enum WaitOutcome {
37 /// The wait timed out
38 TimedOut,
39 /// The mailbox was modified
40 MailboxChanged,
41}
42
43/// Must be implemented for a transport in order for a `Session` using that transport to support
44/// operations with timeouts.
45///
46/// Examples of where this is useful is for `Handle::wait_keepalive` and
47/// `Handle::wait_timeout`.
48pub trait SetReadTimeout {
49 /// Set the timeout for subsequent reads to the given one.
50 ///
51 /// If `timeout` is `None`, the read timeout should be removed.
52 ///
53 /// See also `std::net::TcpStream::set_read_timeout`.
54 fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<()>;
55}
56
57impl<'a, T: Read + Write + 'a> Handle<'a, T> {
58 pub(crate) fn make(session: &'a mut Session<T>) -> Result<Self> {
59 let mut h = Handle {
60 session,
61 keepalive: Duration::from_secs(29 * 60),
62 done: false,
63 };
64 h.init()?;
65 Ok(h)
66 }
67
68 fn init(&mut self) -> Result<()> {
69 // https://tools.ietf.org/html/rfc2177
70 //
71 // The IDLE command takes no arguments.
72 self.session.run_command("IDLE")?;
73
74 // A tagged response will be sent either
75 //
76 // a) if there's an error, or
77 // b) *after* we send DONE
78 let mut v = Vec::new();
79 self.session.readline(&mut v)?;
80 if v.starts_with(b"+") {
81 self.done = false;
82 return Ok(());
83 }
84
85 self.session.read_response_onto(&mut v)?;
86 // We should *only* get a continuation on an error (i.e., it gives BAD or NO).
87 unreachable!();
88 }
89
90 fn terminate(&mut self) -> Result<()> {
91 if !self.done {
92 self.done = true;
93 self.session.write_line(b"DONE")?;
94 self.session.read_response().map(|_| ())
95 } else {
96 Ok(())
97 }
98 }
99
100 /// Internal helper that doesn't consume self.
101 ///
102 /// This is necessary so that we can keep using the inner `Session` in `wait_keepalive`.
103 fn wait_inner(&mut self, reconnect: bool) -> Result<WaitOutcome> {
104 let mut v = Vec::new();
105 loop {
106 let result = match self.session.readline(&mut v).map(|_| ()) {
107 Err(Error::Io(ref e))
108 if e.kind() == io::ErrorKind::TimedOut
109 || e.kind() == io::ErrorKind::WouldBlock =>
110 {
111 if reconnect {
112 self.terminate()?;
113 self.init()?;
114 return self.wait_inner(reconnect);
115 }
116 Ok(WaitOutcome::TimedOut)
117 }
118 Ok(()) => Ok(WaitOutcome::MailboxChanged),
119 Err(r) => Err(r),
120 }?;
121
122 // Handle Dovecot's imap_idle_notify_interval message
123 if v.eq_ignore_ascii_case(b"* OK Still here\r\n") {
124 v.clear();
125 } else {
126 break Ok(result);
127 }
128 }
129 }
130
131 /// Block until the selected mailbox changes.
132 pub fn wait(mut self) -> Result<()> {
133 self.wait_inner(true).map(|_| ())
134 }
135}
136
137impl<'a, T: SetReadTimeout + Read + Write + 'a> Handle<'a, T> {
138 /// Set the keep-alive interval to use when `wait_keepalive` is called.
139 ///
140 /// The interval defaults to 29 minutes as dictated by RFC 2177.
141 pub fn set_keepalive(&mut self, interval: Duration) {
142 self.keepalive = interval;
143 }
144
145 /// Block until the selected mailbox changes.
146 ///
147 /// This method differs from [`Handle::wait`] in that it will periodically refresh the IDLE
148 /// connection, to prevent the server from timing out our connection. The keepalive interval is
149 /// set to 29 minutes by default, as dictated by RFC 2177, but can be changed using
150 /// [`Handle::set_keepalive`].
151 ///
152 /// This is the recommended method to use for waiting.
153 pub fn wait_keepalive(self) -> Result<()> {
154 // The server MAY consider a client inactive if it has an IDLE command
155 // running, and if such a server has an inactivity timeout it MAY log
156 // the client off implicitly at the end of its timeout period. Because
157 // of that, clients using IDLE are advised to terminate the IDLE and
158 // re-issue it at least every 29 minutes to avoid being logged off.
159 // This still allows a client to receive immediate mailbox updates even
160 // though it need only "poll" at half hour intervals.
161 let keepalive = self.keepalive;
162 self.timed_wait(keepalive, true).map(|_| ())
163 }
164
165 /// Block until the selected mailbox changes, or until the given amount of time has expired.
166 #[deprecated(note = "use wait_with_timeout instead")]
167 pub fn wait_timeout(self, timeout: Duration) -> Result<()> {
168 self.wait_with_timeout(timeout).map(|_| ())
169 }
170
171 /// Block until the selected mailbox changes, or until the given amount of time has expired.
172 pub fn wait_with_timeout(self, timeout: Duration) -> Result<WaitOutcome> {
173 self.timed_wait(timeout, false)
174 }
175
176 fn timed_wait(mut self, timeout: Duration, reconnect: bool) -> Result<WaitOutcome> {
177 self.session
178 .stream
179 .get_mut()
180 .set_read_timeout(Some(timeout))?;
181 let res = self.wait_inner(reconnect);
182 let _ = self.session.stream.get_mut().set_read_timeout(None).is_ok();
183 res
184 }
185}
186
187impl<'a, T: Read + Write + 'a> Drop for Handle<'a, T> {
188 fn drop(&mut self) {
189 // we don't want to panic here if we can't terminate the Idle
190 let _ = self.terminate().is_ok();
191 }
192}
193
194impl<'a> SetReadTimeout for TcpStream {
195 fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<()> {
196 TcpStream::set_read_timeout(self, timeout).map_err(Error::Io)
197 }
198}
199
200#[cfg(feature = "tls")]
201impl<'a> SetReadTimeout for TlsStream<TcpStream> {
202 fn set_read_timeout(&mut self, timeout: Option<Duration>) -> Result<()> {
203 self.get_ref().set_read_timeout(timeout).map_err(Error::Io)
204 }
205}