[PATCH 04/13] Update to match semantics of new imaplib2

Ethan Glasser-Camp ethan at betacantrips.com
Tue Mar 8 15:05:17 GMT 2011


The biggest change here is that imapobj.untagged_responses is no
longer a dictionary, but a list. To access it, I use the semi-private
_get_untagged_response method.

* offlineimap/folder/IMAP.py (IMAPFolder.quickchanged,
  IMAPFolder.cachemessagelist): imaplib2 now explicitly removes its
  EXISTS response on select(), so instead we use the return values from
  select() to get the number of messages.

* offlineimap/imapserver.py (UsefulIMAPMixIn.select): imaplib2 now
  stores untagged_responses for different mailboxes, which confuses us
  because it seems like our mailboxes are "still" in read-only mode when
  we just re-opened them.  Additionally, we have to return the value
  from imaplib2's select() so that the above thing works.

* offlineimap/imapserver.py (UsefulIMAPMixIn._mesg): imaplib2 now
  calls _mesg with the name of a thread, so we display this
  information in debug output. This requires a corresponding change to
  imaplibutil.new_mesg.

* offlineimap/imaplibutil.py: We override IMAP4_SSL.open, whose
  default arguments have changed, so update the default arguments. We
  also subclass imaplib.IMAP4 in a few different places, which now
  relies on having a read_fd file descriptor to poll on.

Signed-off-by: Ethan Glasser-Camp <ethan at betacantrips.com>
---
 offlineimap/folder/IMAP.py |   50 ++++++++++++++++++++++---------------------
 offlineimap/imaplibutil.py |   17 ++++++++++++--
 offlineimap/imapserver.py  |    7 ++++-
 3 files changed, 45 insertions(+), 29 deletions(-)

diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py
index db1bdae..3984e97 100644
--- a/offlineimap/folder/IMAP.py
+++ b/offlineimap/folder/IMAP.py
@@ -74,7 +74,7 @@ class IMAPFolder(BaseFolder):
         try:
             # Primes untagged_responses
             self.selectro(imapobj)
-            return long(imapobj.untagged_responses['UIDVALIDITY'][0])
+            return long(imapobj._get_untagged_response('UIDVALIDITY', True)[0])
         finally:
             self.imapserver.releaseconnection(imapobj)
     
@@ -85,17 +85,16 @@ class IMAPFolder(BaseFolder):
         imapobj = self.imapserver.acquireconnection()
         try:
             # Primes untagged_responses
-            imapobj.select(self.getfullname(), readonly = 1, force = 1)
-            try:
-                # 1. Some mail servers do not return an EXISTS response
-                # if the folder is empty.  2. ZIMBRA servers can return
-                # multiple EXISTS replies in the form 500, 1000, 1500,
-                # 1623 so check for potentially multiple replies.
-                maxmsgid = 0
-                for msgid in imapobj.untagged_responses['EXISTS']:
-                    maxmsgid = max(long(msgid), maxmsgid)
-            except KeyError:
+            imaptype, imapdata = imapobj.select(self.getfullname(), readonly = 1, force = 1)
+            # 1. Some mail servers do not return an EXISTS response
+            # if the folder is empty.  2. ZIMBRA servers can return
+            # multiple EXISTS replies in the form 500, 1000, 1500,
+            # 1623 so check for potentially multiple replies.
+            if imapdata == [None]:
                 return True
+            maxmsgid = 0
+            for msgid in imapdata:
+                maxmsgid = max(long(msgid), maxmsgid)
 
             # Different number of messages than last time?
             if maxmsgid != len(statusfolder.getmessagelist()):
@@ -130,7 +129,7 @@ class IMAPFolder(BaseFolder):
 
         try:
             # Primes untagged_responses
-            imapobj.select(self.getfullname(), readonly = 1, force = 1)
+            imaptype, imapdata = imapobj.select(self.getfullname(), readonly = 1, force = 1)
 
             maxage = self.config.getdefaultint("Account " + self.accountname, "maxage", -1)
             maxsize = self.config.getdefaultint("Account " + self.accountname, "maxsize", -1)
@@ -172,17 +171,20 @@ class IMAPFolder(BaseFolder):
                     # No messages; return
                     return
             else:
-                try:
-                    # 1. Some mail servers do not return an EXISTS response
-                    # if the folder is empty.  2. ZIMBRA servers can return
-                    # multiple EXISTS replies in the form 500, 1000, 1500,
-                    # 1623 so check for potentially multiple replies.
-                    maxmsgid = 0
-                    for msgid in imapobj.untagged_responses['EXISTS']:
-                        maxmsgid = max(long(msgid), maxmsgid)
-                    messagesToFetch = '1:%d' % maxmsgid;
-                except KeyError:
+                # 1. Some mail servers do not return an EXISTS response
+                # if the folder is empty.  2. ZIMBRA servers can return
+                # multiple EXISTS replies in the form 500, 1000, 1500,
+                # 1623 so check for potentially multiple replies.
+                if imapdata == [None]:
                     return
+
+                maxmsgid = 0
+                for msgid in imapdata:
+                    maxmsgid = max(long(msgid), maxmsgid)
+
+                maxmsgid = long(imapdata[0])
+                messagesToFetch = '1:%d' % maxmsgid;
+
                 if maxmsgid < 1:
                     #no messages; return
                     return
@@ -257,8 +259,8 @@ class IMAPFolder(BaseFolder):
         return leader + newline + trailer
 
     def savemessage_searchforheader(self, imapobj, headername, headervalue):
-        if imapobj.untagged_responses.has_key('APPENDUID'):
-            return long(imapobj.untagged_responses['APPENDUID'][-1].split(' ')[1])
+        if imapobj._get_untagged_response('APPENDUID', True):
+            return long(imapobj._get_untagged_response('APPENDUID', True)[-1].split(' ')[1])
 
         self.ui.debug('imap', 'savemessage_searchforheader called for %s: %s' % \
                  (headername, headervalue))
diff --git a/offlineimap/imaplibutil.py b/offlineimap/imaplibutil.py
index 7f1b26c..823f404 100644
--- a/offlineimap/imaplibutil.py
+++ b/offlineimap/imaplibutil.py
@@ -17,6 +17,7 @@
 
 import re, socket, time, subprocess
 from offlineimap.ui import getglobalui
+import threading
 from offlineimap.imaplib2 import *
 
 # Import the symbols we need that aren't exported by default
@@ -44,6 +45,8 @@ class IMAP4_Tunnel(IMAP4):
         self.process = subprocess.Popen(host, shell=True, close_fds=True,
                         stdin=subprocess.PIPE, stdout=subprocess.PIPE)
         (self.outfd, self.infd) = (self.process.stdin, self.process.stdout)
+        # imaplib2 polls on this fd
+        self.read_fd = self.infd.fileno()
 
     def read(self, size):
         retval = ''
@@ -63,11 +66,13 @@ class IMAP4_Tunnel(IMAP4):
         self.process.wait()
 
 
-def new_mesg(self, s, secs=None):
+def new_mesg(self, s, tn=None, secs=None):
             if secs is None:
                 secs = time.time()
+            if tn is None:
+                tn = threading.currentThread().getName()
             tm = time.strftime('%M:%S', time.localtime(secs))
-            getglobalui().debug('imap', '  %s.%02d %s' % (tm, (secs*100)%100, s))
+            getglobalui().debug('imap', '  %s.%02d %s %s' % (tm, (secs*100)%100, tn, s))
 
 class WrappedIMAP4_SSL(IMAP4_SSL):
     """Provides an improved version of the standard IMAP4_SSL
@@ -82,7 +87,7 @@ class WrappedIMAP4_SSL(IMAP4_SSL):
             del kwargs['cacertfile']
         IMAP4_SSL.__init__(self, *args, **kwargs)
 
-    def open(self, host = '', port = IMAP4_SSL_PORT):
+    def open(self, host=None, port=None):
         """Do whatever IMAP4_SSL would do in open, but call sslwrap
         with cert verification"""
         #IMAP4_SSL.open(self, host, port) uses the below 2 lines:
@@ -141,6 +146,9 @@ class WrappedIMAP4_SSL(IMAP4_SSL):
                 if error:
                     raise ssl.SSLError("SSL Certificate host name mismatch: %s" % error)
 
+        # imaplib2 uses this to poll()
+        self.read_fd = self.sslobj.fileno()
+
         #TODO: Done for now. We should implement a mutt-like behavior
         #that offers the users to accept a certificate (presenting a
         #fingerprint of it) (get via self.sslobj.getpeercert()), and
@@ -239,6 +247,9 @@ class WrappedIMAP4(IMAP4):
             raise socket.error(last_error)
         self.file = self.sock.makefile('rb')
 
+        # imaplib2 uses this to poll()
+        self.read_fd = self.sock.fileno()
+
 mustquote = re.compile(r"[^\w!#$%&'+,.:;<=>?^`|~-]")
 
 def Internaldate2epoch(resp):
diff --git a/offlineimap/imapserver.py b/offlineimap/imapserver.py
index 10d8fe3..a5c1a65 100644
--- a/offlineimap/imapserver.py
+++ b/offlineimap/imapserver.py
@@ -48,6 +48,8 @@ class UsefulIMAPMixIn:
            and self.is_readonly == readonly:
             # No change; return.
             return
+        # Wipe out all old responses, to maintain semantics with old imaplib2
+        del self.untagged_responses[:]
         result = self.__class__.__bases__[1].select(self, mailbox, readonly)
         if result[0] != 'OK':
             raise ValueError, "Error from select: %s" % str(result)
@@ -55,9 +57,10 @@ class UsefulIMAPMixIn:
             self.selectedfolder = mailbox
         else:
             self.selectedfolder = None
+        return result
 
-    def _mesg(self, s, secs=None):
-        imaplibutil.new_mesg(self, s, secs)
+    def _mesg(self, s, tn=None, secs=None):
+        imaplibutil.new_mesg(self, s, tn, secs)
 
 class UsefulIMAP4(UsefulIMAPMixIn, imaplibutil.WrappedIMAP4):
 
-- 
1.7.1





More information about the OfflineIMAP-project mailing list