Update to match semantics of new imaplib2

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@betacantrips.com>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
Ethan Glasser-Camp 2011-03-10 15:36:20 -05:00 committed by Nicolas Sebrecht
parent f9413226b8
commit 1bf4bee5e6
3 changed files with 47 additions and 30 deletions

View File

@ -71,7 +71,7 @@ class IMAPFolder(BaseFolder):
try: try:
# Primes untagged_responses # Primes untagged_responses
self.selectro(imapobj) self.selectro(imapobj)
return long(imapobj.untagged_responses['UIDVALIDITY'][0]) return long(imapobj._get_untagged_response('UIDVALIDITY', True)[0])
finally: finally:
self.imapserver.releaseconnection(imapobj) self.imapserver.releaseconnection(imapobj)
@ -82,17 +82,16 @@ class IMAPFolder(BaseFolder):
imapobj = self.imapserver.acquireconnection() imapobj = self.imapserver.acquireconnection()
try: try:
# Primes untagged_responses # Primes untagged_responses
imapobj.select(self.getfullname(), readonly = 1, force = 1) imaptype, imapdata = imapobj.select(self.getfullname(), readonly = 1, force = 1)
try: # 1. Some mail servers do not return an EXISTS response
# 1. Some mail servers do not return an EXISTS response # if the folder is empty. 2. ZIMBRA servers can return
# if the folder is empty. 2. ZIMBRA servers can return # multiple EXISTS replies in the form 500, 1000, 1500,
# multiple EXISTS replies in the form 500, 1000, 1500, # 1623 so check for potentially multiple replies.
# 1623 so check for potentially multiple replies. if imapdata == [None]:
maxmsgid = 0
for msgid in imapobj.untagged_responses['EXISTS']:
maxmsgid = max(long(msgid), maxmsgid)
except KeyError:
return True return True
maxmsgid = 0
for msgid in imapdata:
maxmsgid = max(long(msgid), maxmsgid)
# Different number of messages than last time? # Different number of messages than last time?
if maxmsgid != len(statusfolder.getmessagelist()): if maxmsgid != len(statusfolder.getmessagelist()):
@ -127,7 +126,7 @@ class IMAPFolder(BaseFolder):
try: try:
# Primes untagged_responses # 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) maxage = self.config.getdefaultint("Account " + self.accountname, "maxage", -1)
maxsize = self.config.getdefaultint("Account " + self.accountname, "maxsize", -1) maxsize = self.config.getdefaultint("Account " + self.accountname, "maxsize", -1)
@ -169,17 +168,20 @@ class IMAPFolder(BaseFolder):
# No messages; return # No messages; return
return return
else: else:
try: # 1. Some mail servers do not return an EXISTS response
# 1. Some mail servers do not return an EXISTS response # if the folder is empty. 2. ZIMBRA servers can return
# if the folder is empty. 2. ZIMBRA servers can return # multiple EXISTS replies in the form 500, 1000, 1500,
# multiple EXISTS replies in the form 500, 1000, 1500, # 1623 so check for potentially multiple replies.
# 1623 so check for potentially multiple replies. if imapdata == [None]:
maxmsgid = 0
for msgid in imapobj.untagged_responses['EXISTS']:
maxmsgid = max(long(msgid), maxmsgid)
messagesToFetch = '1:%d' % maxmsgid;
except KeyError:
return return
maxmsgid = 0
for msgid in imapdata:
maxmsgid = max(long(msgid), maxmsgid)
maxmsgid = long(imapdata[0])
messagesToFetch = '1:%d' % maxmsgid;
if maxmsgid < 1: if maxmsgid < 1:
#no messages; return #no messages; return
return return
@ -437,10 +439,11 @@ class IMAPFolder(BaseFolder):
# get the new UID from the APPENDUID response, it could look like # get the new UID from the APPENDUID response, it could look like
# OK [APPENDUID 38505 3955] APPEND completed # OK [APPENDUID 38505 3955] APPEND completed
# with 38505 bein folder UIDvalidity and 3955 the new UID # with 38505 bein folder UIDvalidity and 3955 the new UID
if not imapobj.untagged_responses.has_key('APPENDUID'): if not imapobj._get_untagged_response('APPENDUID', True):
self.ui.warn("Server supports UIDPLUS but got no APPENDUID appending a message.") self.ui.warn("Server supports UIDPLUS but got no APPENDUID "
"appending a message.")
return 0 return 0
uid = long(imapobj.untagged_responses['APPENDUID'][-1].split(' ')[1]) uid = long(imapobj._get_untagged_response('APPENDUID', True)[-1].split(' ')[1])
else: else:
# we don't support UIDPLUS # we don't support UIDPLUS

View File

@ -17,6 +17,7 @@
import re, socket, time, subprocess import re, socket, time, subprocess
from offlineimap.ui import getglobalui from offlineimap.ui import getglobalui
import threading
from offlineimap.imaplib2 import * from offlineimap.imaplib2 import *
# Import the symbols we need that aren't exported by default # 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, self.process = subprocess.Popen(host, shell=True, close_fds=True,
stdin=subprocess.PIPE, stdout=subprocess.PIPE) stdin=subprocess.PIPE, stdout=subprocess.PIPE)
(self.outfd, self.infd) = (self.process.stdin, self.process.stdout) (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): def read(self, size):
retval = '' retval = ''
@ -66,11 +69,13 @@ class IMAP4_Tunnel(IMAP4):
self.process.wait() self.process.wait()
def new_mesg(self, s, secs=None): def new_mesg(self, s, tn=None, secs=None):
if secs is None: if secs is None:
secs = time.time() secs = time.time()
if tn is None:
tn = threading.currentThread().getName()
tm = time.strftime('%M:%S', time.localtime(secs)) 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): class WrappedIMAP4_SSL(IMAP4_SSL):
"""Provides an improved version of the standard IMAP4_SSL """Provides an improved version of the standard IMAP4_SSL
@ -85,7 +90,7 @@ class WrappedIMAP4_SSL(IMAP4_SSL):
del kwargs['cacertfile'] del kwargs['cacertfile']
IMAP4_SSL.__init__(self, *args, **kwargs) 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 """Do whatever IMAP4_SSL would do in open, but call sslwrap
with cert verification""" with cert verification"""
#IMAP4_SSL.open(self, host, port) uses the below 2 lines: #IMAP4_SSL.open(self, host, port) uses the below 2 lines:
@ -148,6 +153,9 @@ class WrappedIMAP4_SSL(IMAP4_SSL):
if error: if error:
raise ssl.SSLError("SSL Certificate host name mismatch: %s" % error) raise ssl.SSLError("SSL Certificate host name mismatch: %s" % error)
# imaplib2 uses this to poll()
self.read_fd = self.sock.fileno()
#TODO: Done for now. We should implement a mutt-like behavior #TODO: Done for now. We should implement a mutt-like behavior
#that offers the users to accept a certificate (presenting a #that offers the users to accept a certificate (presenting a
#fingerprint of it) (get via self.sslobj.getpeercert()), and #fingerprint of it) (get via self.sslobj.getpeercert()), and
@ -263,6 +271,9 @@ class WrappedIMAP4(IMAP4):
raise socket.error(last_error) raise socket.error(last_error)
self.file = self.sock.makefile('rb') self.file = self.sock.makefile('rb')
# imaplib2 uses this to poll()
self.read_fd = self.sock.fileno()
mustquote = re.compile(r"[^\w!#$%&'+,.:;<=>?^`|~-]") mustquote = re.compile(r"[^\w!#$%&'+,.:;<=>?^`|~-]")
def Internaldate2epoch(resp): def Internaldate2epoch(resp):

View File

@ -48,6 +48,8 @@ class UsefulIMAPMixIn:
and self.is_readonly == readonly: and self.is_readonly == readonly:
# No change; return. # No change; return.
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) result = self.__class__.__bases__[1].select(self, mailbox, readonly)
if result[0] != 'OK': if result[0] != 'OK':
raise ValueError, "Error from select: %s" % str(result) raise ValueError, "Error from select: %s" % str(result)
@ -55,9 +57,10 @@ class UsefulIMAPMixIn:
self.selectedfolder = mailbox self.selectedfolder = mailbox
else: else:
self.selectedfolder = None self.selectedfolder = None
return result
def _mesg(self, s, secs=None): def _mesg(self, s, tn=None, secs=None):
imaplibutil.new_mesg(self, s, secs) imaplibutil.new_mesg(self, s, tn, secs)
class UsefulIMAP4(UsefulIMAPMixIn, imaplibutil.WrappedIMAP4): class UsefulIMAP4(UsefulIMAPMixIn, imaplibutil.WrappedIMAP4):
# This is a hack around Darwin's implementation of realloc() (which # This is a hack around Darwin's implementation of realloc() (which