diff -r ac176a69d188 Lib/imaplib.py --- a/Lib/imaplib.py Sun Dec 28 22:14:23 2014 -0600 +++ b/Lib/imaplib.py Mon Dec 29 16:25:38 2014 +0100 @@ -1,6 +1,6 @@ """IMAP4 client. -Based on RFC 2060. +Based on RFC 2060, RFC 2177. Public class: IMAP4 Public variable: Debug @@ -22,7 +22,7 @@ __version__ = "2.58" -import binascii, errno, random, re, socket, subprocess, sys, time, calendar +import binascii, errno, random, re, select, socket, subprocess, sys, time, calendar from datetime import datetime, timezone, timedelta from io import DEFAULT_BUFFER_SIZE @@ -65,6 +65,7 @@ 'CREATE': ('AUTH', 'SELECTED'), 'DELETE': ('AUTH', 'SELECTED'), 'DELETEACL': ('AUTH', 'SELECTED'), + 'DONE': ('IDLE'), 'EXAMINE': ('AUTH', 'SELECTED'), 'EXPUNGE': ('SELECTED',), 'FETCH': ('SELECTED',), @@ -72,6 +73,7 @@ 'GETANNOTATION':('AUTH', 'SELECTED'), 'GETQUOTA': ('AUTH', 'SELECTED'), 'GETQUOTAROOT': ('AUTH', 'SELECTED'), + 'IDLE': ('AUTH', 'SELECTED'), 'MYRIGHTS': ('AUTH', 'SELECTED'), 'LIST': ('AUTH', 'SELECTED'), 'LOGIN': ('NONAUTH',), @@ -828,6 +830,88 @@ name = 'FETCH' return self._untagged_response(typ, dat, name) + def idle(self): + """Implements IMAP IDLE extension as described in RFC 2177. + + This commands enters the idle mode and changes the state to IDLING. + It parses the continuation response as well as update messages from the + server. To check for update messages or leave the idle mode, + use idle_poll(), idle_wait() or idle_done(). + + (typ, {data}) = .idle(timeout) + + Returns typ 'IDLING' and the untagged responses received from the server. + """ + if not "IDLE" in self.capabilities: + raise self.error("server does not support IDLE command.") + + self.untagged_responses = {} + self.__idle_tag = self._command("IDLE") + self._get_response() # read continuation response + update messages + self.__state_before_idle = self.state # store previous state + self.state = 'IDLE' + return 'IDLING', self.untagged_responses + + def idle_poll(self): + """Implements IMAP IDLE extension as described in RFC 2177. + + Retrieves any update messages that have been received during this idle + session since the last call to idle_poll(). The update messages are + cleared after each poll, thus they are not aggregated. + This method asserts that the client is in idle mode, i.e. that idle() + was called and neither idle_wait() or idle_done() has been called since. + + (typ, {data}) = .idle_poll() + + Returns typ 'IDLING' and the untagged responses received from the server. + """ + assert(self.state == 'IDLE') + self.untagged_responses = {} + while True: + # check if something arrived, do not block! + s,_,_ = select.select([self.sock],[],[],0) + if len(s) == 0: break + self._get_response() + return 'IDLING', self.untagged_responses + + def idle_wait(self, timeout = 29 * 60): + """Implements IMAP IDLE extension as described in RFC 2177. + + Blocks until an update message is received from the server or the + timeout is reached. The default timeout is 29 minutes, being the + maximum idle time advised by RFC 2177. Then, it calls idle_done() and + passes on its result. + This method asserts that the client is in idle mode, i.e. that idle() + was called and neither idle_wait() or idle_done() has been called since. + + (typ, {data}) = .idle_wait() + + Returns typ 'IDLING' and the untagged responses received from the server. + """ + assert(self.state == 'IDLE') + # block until something happens + select.select([self.sock],[],[],timeout) + return self.idle_done() + + def idle_done(self): + """Implements IMAP IDLE extension as described in RFC 2177. + + Executes DONE to leave the idle mode. + The state that the client was in before entering the idle mode is + restored. Parses all update messages from the server and returns them. + This method asserts that the client is in idle mode, i.e. that idle() + was called and neither idle_wait() or idle_done() has been called since. + + (typ, {data}) = .idle_done() + + Returns the untagged responses received from the server. + """ + assert(self.state == 'IDLE') + self.untagged_responses = {} + self.send(b"DONE" + CRLF) # send raw bytes "DONE" + typ,data = self._command_complete("IDLE", self.__idle_tag) + self.state = self.__state_before_idle # restore state + return typ, self.untagged_responses def unsubscribe(self, mailbox): """Unsubscribe from old mailbox.