@@ -138,6 +138,11 @@ fn validate_sequence_set(
138138#[ derive( Debug ) ]
139139pub struct Session < T : Read + Write > {
140140 conn : Connection < T > ,
141+
142+ // Capabilities are almost guaranteed to chance if encryption state or authentication state
143+ // changes, so caching them in `Connection` is inappropiate.
144+ capability_cache : Option < Capabilities > ,
145+
141146 pub ( crate ) unsolicited_responses_tx : mpsc:: Sender < UnsolicitedResponse > ,
142147
143148 /// Server responses that are not related to the current command. See also the note on
@@ -153,6 +158,10 @@ pub struct Session<T: Read + Write> {
153158#[ derive( Debug ) ]
154159pub struct Client < T : Read + Write > {
155160 conn : Connection < T > ,
161+
162+ // Capabilities are almost guaranteed to chance if encryption state or authentication state
163+ // changes, so caching them in `Connection` is inappropiate.
164+ capability_cache : Option < Capabilities > ,
156165}
157166
158167/// The underlying primitives type. Both `Client`(unauthenticated) and `Session`(after succesful
@@ -333,6 +342,7 @@ impl<T: Read + Write> Client<T> {
333342 debug : false ,
334343 greeting_read : false ,
335344 } ,
345+ capability_cache : None ,
336346 }
337347 }
338348
@@ -345,6 +355,47 @@ impl<T: Read + Write> Client<T> {
345355 Ok ( res)
346356 }
347357
358+ /// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
359+ /// listing of capabilities that the server supports. The server will include "IMAP4rev1" as
360+ /// one of the listed capabilities. See [`Capabilities`] for further details.
361+ ///
362+ /// This method will always bypass the local capabilities cache and send a `CAPABILITY` command
363+ /// to the server. The [`Self::capabilities()`] method can be used when returning a cached
364+ /// response is acceptable.
365+ pub fn capabilities_refresh ( & mut self ) -> Result < & Capabilities > {
366+ let ( mut tx, _rx) = mpsc:: channel ( ) ;
367+ let caps = self . run_command_and_read_response ( "CAPABILITY" )
368+ . and_then ( |lines| Capabilities :: parse ( lines, & mut tx) ) ?;
369+ self . capability_cache = Some ( caps) ;
370+
371+ self . capability_cache . as_ref ( )
372+ // This path will not be hit; if the cache is not populated the above calls will either
373+ // populate it or return with an early error.
374+ . ok_or_else ( || panic ! ( "CAPABILITY call did not populate capability cache!" ) )
375+ }
376+
377+ /// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
378+ /// listing of capabilities that the server supports. The server will include "IMAP4rev1" as
379+ /// one of the listed capabilities. See [`Capabilities`] for further details.
380+ ///
381+ /// This function will not query the server if a set of capabilities was cached, but request
382+ /// and cache capabilities from the server otherwise. The [`Self::capabilities_refresh`] method
383+ /// can be used to refresh the cache by forcing a `CAPABILITY` command to be send.
384+ pub fn capabilities_ref ( & mut self ) -> Result < & Capabilities > {
385+ let ( mut tx, _rx) = mpsc:: channel ( ) ;
386+ if self . capability_cache . is_none ( ) {
387+ let caps = self . run_command_and_read_response ( "CAPABILITY" )
388+ . and_then ( |lines| Capabilities :: parse ( lines, & mut tx) ) ?;
389+
390+ self . capability_cache = Some ( caps) ;
391+ }
392+
393+ self . capability_cache . as_ref ( )
394+ // This path will not be hit; if the cache is not populated the above `if` will either
395+ // populate it or return with an early error.
396+ . ok_or_else ( || panic ! ( "CAPABILITY call did not populate capability cache!" ) )
397+ }
398+
348399 /// Log in to the IMAP server. Upon success a [`Session`](struct.Session.html) instance is
349400 /// returned; on error the original `Client` instance is returned in addition to the error.
350401 /// This is because `login` takes ownership of `self`, so in order to try again (e.g. after
@@ -502,6 +553,7 @@ impl<T: Read + Write> Session<T> {
502553 let ( tx, rx) = mpsc:: channel ( ) ;
503554 Session {
504555 conn,
556+ capability_cache : None ,
505557 unsolicited_responses : rx,
506558 unsolicited_responses_tx : tx,
507559 }
@@ -771,12 +823,52 @@ impl<T: Read + Write> Session<T> {
771823 self . run_command_and_check_ok ( & format ! ( "UNSUBSCRIBE {}" , quote!( mailbox. as_ref( ) ) ) )
772824 }
773825
826+ /// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
827+ /// listing of capabilities that the server supports. The server will include "IMAP4rev1" as
828+ /// one of the listed capabilities. See [`Capabilities`] for further details.
829+ ///
830+ /// This method will always bypass the local capabilities cache and send a `CAPABILITY` command
831+ /// to the server. The [`Self::capabilities()`] method can be used when returning a cached
832+ /// response is acceptable.
833+ pub fn capabilities_refresh ( & mut self ) -> Result < & Capabilities > {
834+ let caps = self . run_command_and_read_response ( "CAPABILITY" )
835+ . and_then ( |lines| Capabilities :: parse ( lines, & mut self . unsolicited_responses_tx ) ) ?;
836+ self . capability_cache = Some ( caps) ;
837+
838+ self . capability_cache . as_ref ( )
839+ // This path will not be hit; if the cache is not populated the above calls will either
840+ // populate it or return with an early error.
841+ . ok_or_else ( || panic ! ( "CAPABILITY call did not populate capability cache!" ) )
842+ }
843+
844+ /// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
845+ /// listing of capabilities that the server supports. The server will include "IMAP4rev1" as
846+ /// one of the listed capabilities. See [`Capabilities`] for further details.
847+ ///
848+ /// This function will not query the server if a set of capabilities was cached, but request
849+ /// and cache capabilities from the server otherwise. The [`Self::capabilities_refresh`] method
850+ /// can be used to refresh the cache by forcing a `CAPABILITY` command to be send.
851+ pub fn capabilities_ref ( & mut self ) -> Result < & Capabilities > {
852+ if self . capability_cache . is_none ( ) {
853+ let caps = self . run_command_and_read_response ( "CAPABILITY" )
854+ . and_then ( |lines| Capabilities :: parse ( lines, & mut self . unsolicited_responses_tx ) ) ?;
855+
856+ self . capability_cache = Some ( caps) ;
857+ }
858+
859+ self . capability_cache . as_ref ( )
860+ // This path will not be hit; if the cache is not populated the above `if` will either
861+ // populate it or return with an early error.
862+ . ok_or_else ( || panic ! ( "CAPABILITY call did not populate capability cache!" ) )
863+ }
864+
774865 /// The [`CAPABILITY` command](https://tools.ietf.org/html/rfc3501#section-6.1.1) requests a
775866 /// listing of capabilities that the server supports. The server will include "IMAP4rev1" as
776867 /// one of the listed capabilities. See [`Capabilities`] for further details.
777868 pub fn capabilities ( & mut self ) -> Result < Capabilities > {
778- self . run_command_and_read_response ( "CAPABILITY" )
779- . and_then ( |lines| Capabilities :: parse ( lines, & mut self . unsolicited_responses_tx ) )
869+ // TODO: This emulated the same behaviour as before, with each call issuing a command.
870+ // It may be sensible to allow hitting the cache here.
871+ self . capabilities_refresh ( ) . map ( |caps| caps. clone ( ) )
780872 }
781873
782874 /// The [`EXPUNGE` command](https://tools.ietf.org/html/rfc3501#section-6.4.3) permanently
0 commit comments