1
1
use std:: net:: { TcpStream , ToSocketAddrs } ;
2
2
use openssl:: ssl:: { SslContext , SslStream } ;
3
- use std:: io:: { Read , Write } ;
3
+ use std:: io:: { self , Read , Write } ;
4
+ use std:: time:: Duration ;
4
5
5
6
use super :: mailbox:: Mailbox ;
6
7
use super :: authenticator:: Authenticator ;
@@ -19,6 +20,149 @@ pub struct Client<T> {
19
20
pub debug : bool
20
21
}
21
22
23
+ /// `IdleHandle` allows a client to block waiting for changes to the remote mailbox.
24
+ ///
25
+ /// The handle blocks using the IMAP IDLE command specificed in [RFC
26
+ /// 2177](https://tools.ietf.org/html/rfc2177).
27
+ ///
28
+ /// As long a the handle is active, the mailbox cannot be otherwise accessed.
29
+ pub struct IdleHandle < ' a , T : Read + Write + ' a > {
30
+ client : & ' a mut Client < T > ,
31
+ keepalive : Duration ,
32
+ }
33
+
34
+ /// Must be implemented for a transport in order for a `Client` using that transport to support
35
+ /// operations with timeouts.
36
+ ///
37
+ /// Examples of where this is useful is for `IdleHandle::wait_keepalive` and
38
+ /// `IdleHandle::wait_timeout`.
39
+ pub trait SetReadTimeout {
40
+ /// Set the timeout for subsequent reads to the given one.
41
+ ///
42
+ /// If `timeout` is `None`, the read timeout should be removed.
43
+ ///
44
+ /// See also `std::net::TcpStream::set_read_timeout`.
45
+ fn set_read_timeout ( & mut self , timeout : Option < Duration > ) -> Result < ( ) > ;
46
+ }
47
+
48
+ impl < ' a , T : Read + Write + ' a > IdleHandle < ' a , T > {
49
+ fn new ( client : & ' a mut Client < T > ) -> Result < Self > {
50
+ let mut h = IdleHandle {
51
+ client : client,
52
+ keepalive : Duration :: from_secs ( 29 * 60 ) ,
53
+ } ;
54
+ try!( h. init ( ) ) ;
55
+ Ok ( h)
56
+ }
57
+
58
+ fn init ( & mut self ) -> Result < ( ) > {
59
+ // https://tools.ietf.org/html/rfc2177
60
+ //
61
+ // The IDLE command takes no arguments.
62
+ try!( self . client . run_command ( "IDLE" ) ) ;
63
+
64
+ // A tagged response will be sent either
65
+ //
66
+ // a) if there's an error, or
67
+ // b) *after* we send DONE
68
+ let tag = format ! ( "{}{} " , TAG_PREFIX , self . client. tag) ;
69
+ let raw_data = try!( self . client . readline ( ) ) ;
70
+ let line = String :: from_utf8 ( raw_data) . unwrap ( ) ;
71
+ if line. starts_with ( & tag) {
72
+ try!( parse_response ( vec ! [ line] ) ) ;
73
+ // We should *only* get a continuation on an error (i.e., it gives BAD or NO).
74
+ unreachable ! ( ) ;
75
+ } else if !line. starts_with ( "+" ) {
76
+ return Err ( Error :: BadResponse ( vec ! [ line] ) ) ;
77
+ }
78
+
79
+ Ok ( ( ) )
80
+ }
81
+
82
+ fn terminate ( & mut self ) -> Result < ( ) > {
83
+ try!( self . client . write_line ( b"DONE" ) ) ;
84
+ let lines = try!( self . client . read_response ( ) ) ;
85
+ parse_response_ok ( lines)
86
+ }
87
+
88
+ /// Block until the selected mailbox changes.
89
+ pub fn wait ( & mut self ) -> Result < ( ) > {
90
+ self . client . readline ( ) . map ( |_| ( ) )
91
+ }
92
+
93
+ /// Cancel the IDLE handle prematurely.
94
+ pub fn cancel ( self ) {
95
+ // causes Drop to be called
96
+ }
97
+ }
98
+
99
+ impl < ' a , T : SetReadTimeout + Read + Write + ' a > IdleHandle < ' a , T > {
100
+ /// Set the keep-alive interval to use when `wait_keepalive` is called.
101
+ ///
102
+ /// The interval defaults to 29 minutes as dictated by RFC 2177.
103
+ pub fn set_keepalive ( & mut self , interval : Duration ) {
104
+ self . keepalive = interval;
105
+ }
106
+
107
+ /// Block until the selected mailbox changes.
108
+ ///
109
+ /// This method differs from `IdleHandle::wait` in that it will periodically refresh the IDLE
110
+ /// connection, to prevent the server from timing out our connection. The keepalive interval is
111
+ /// set to 29 minutes by default, as dictated by RFC 2177, but can be changed using
112
+ /// `set_keepalive`.
113
+ ///
114
+ /// This is the recommended method to use for waiting.
115
+ pub fn wait_keepalive ( & mut self ) -> Result < ( ) > {
116
+ // The server MAY consider a client inactive if it has an IDLE command
117
+ // running, and if such a server has an inactivity timeout it MAY log
118
+ // the client off implicitly at the end of its timeout period. Because
119
+ // of that, clients using IDLE are advised to terminate the IDLE and
120
+ // re-issue it at least every 29 minutes to avoid being logged off.
121
+ // This still allows a client to receive immediate mailbox updates even
122
+ // though it need only "poll" at half hour intervals.
123
+ try!( self . client . stream . set_read_timeout ( Some ( self . keepalive ) ) ) ;
124
+ match self . wait ( ) {
125
+ Err ( Error :: Io ( ref e) ) if e. kind ( ) == io:: ErrorKind :: TimedOut ||
126
+ e. kind ( ) == io:: ErrorKind :: WouldBlock => {
127
+ // we need to refresh the IDLE connection
128
+ try!( self . terminate ( ) ) ;
129
+ try!( self . init ( ) ) ;
130
+ self . wait_keepalive ( )
131
+ }
132
+ r => {
133
+ try!( self . client . stream . set_read_timeout ( None ) ) ;
134
+ r
135
+ }
136
+ }
137
+ }
138
+
139
+ /// Block until the selected mailbox changes, or until the given amount of time has expired.
140
+ pub fn wait_timeout ( & mut self , timeout : Duration ) -> Result < ( ) > {
141
+ try!( self . client . stream . set_read_timeout ( Some ( timeout) ) ) ;
142
+ let res = self . wait ( ) ;
143
+ try!( self . client . stream . set_read_timeout ( None ) ) ;
144
+ res
145
+ }
146
+ }
147
+
148
+ impl < ' a , T : Read + Write + ' a > Drop for IdleHandle < ' a , T > {
149
+ fn drop ( & mut self ) {
150
+ self . terminate ( ) . expect ( "IDLE connection did not terminate cleanly" ) ;
151
+ }
152
+ }
153
+
154
+ impl < ' a > SetReadTimeout for TcpStream {
155
+ fn set_read_timeout ( & mut self , timeout : Option < Duration > ) -> Result < ( ) > {
156
+ TcpStream :: set_read_timeout ( self , timeout) . map_err ( |e| Error :: Io ( e) )
157
+ }
158
+ }
159
+
160
+ impl < ' a > SetReadTimeout for SslStream < TcpStream > {
161
+ fn set_read_timeout ( & mut self , timeout : Option < Duration > ) -> Result < ( ) > {
162
+ self . get_ref ( ) . set_read_timeout ( timeout) . map_err ( |e| Error :: Io ( e) )
163
+ }
164
+ }
165
+
22
166
impl Client < TcpStream > {
23
167
/// Creates a new client.
24
168
pub fn connect < A : ToSocketAddrs > ( addr : A ) -> Result < Client < TcpStream > > {
@@ -230,6 +374,12 @@ impl<T: Read+Write> Client<T> {
230
374
self . run_command_and_parse ( & format ! ( "STATUS {} {}" , mailbox_name, status_data_items) )
231
375
}
232
376
377
+ /// Returns a handle that can be used to block until the state of the currently selected
378
+ /// mailbox changes.
379
+ pub fn idle ( & mut self ) -> Result < IdleHandle < T > > {
380
+ IdleHandle :: new ( self )
381
+ }
382
+
233
383
/// Runs a command and checks if it returns OK.
234
384
pub fn run_command_and_check_ok ( & mut self , command : & str ) -> Result < ( ) > {
235
385
let lines = try!( self . run_command_and_read_response ( command) ) ;
0 commit comments