[PATCH 4/4] Implement APPENDUID support

Sebastian Spaeth Sebastian at SSpaeth.de
Tue Jan 25 09:25:55 UTC 2011


Rather than inserting our own home-grown header, everytime we save a
message to an IMAP server, we check if we suport the UIDPLUS extension
which provides us with an APPENDUID reply. Use that to find the new UID
if possible, but keep the old way if we don't that extension.

Signed-off-by: Sebastian Spaeth <Sebastian at SSpaeth.de>
---
 offlineimap/folder/IMAP.py |  103 +++++++++++++++++++-------------------------
 1 files changed, 45 insertions(+), 58 deletions(-)

diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py
index 9a39e09..cc0b6a1 100644
--- a/offlineimap/folder/IMAP.py
+++ b/offlineimap/folder/IMAP.py
@@ -275,9 +275,6 @@ class IMAPFolder(BaseFolder):
 
 
     def savemessage_searchforheader(self, imapobj, headername, headervalue):
-        if imapobj.untagged_responses.has_key('APPENDUID'):
-            return long(imapobj.untagged_responses['APPENDUID'][-1].split(' ')[1])
-
         self.ui.debug('imap', 'savemessage_searchforheader called for %s: %s' % \
                  (headername, headervalue))
         # Now find the UID it got.
@@ -357,64 +354,42 @@ class IMAPFolder(BaseFolder):
         This function will update the self.messagelist dict to contain
         the new message after sucessfully saving it.
 
-        :param rtime: A timestamp to be
+        :param rtime: A timestamp to be used as the mail date
         :returns: the UID of the new message as assigned by the
                   server. If the folder is read-only it will return 0."""
-        imapobj = self.imapserver.acquireconnection()
         self.ui.debug('imap', 'savemessage: called')
 
         try:
+            imapobj = self.imapserver.acquireconnection()
+
             try:
-                imapobj.select(self.getfullname()) # Needed for search
+                imapobj.select(self.getfullname()) # Needed for search and making the box READ-WRITE
             except imapobj.readonly:
+                # readonly exception. Return original uid to notify that
+                # we did not save the message. (see savemessage in Base.py)
                 self.ui.msgtoreadonly(self, uid, content, flags)
-                # Return indicating message taken, but no UID assigned.
-                return 0
-            
-            # get the date of the message file, so we can pass it to the server.
-            date = self.getmessagedate(content, rtime)
+                return uid
 
-            # If time isn't known
-            if rtime == None and datetuple_msg == None:
-                datetuple = time.localtime()
-            elif rtime == None:
-                datetuple = datetuple_msg
-            else:
-                datetuple = time.localtime(rtime)
-
-            try:
-                if datetuple[0] < 1981:
-                    raise ValueError
-
-                # Check for invalid date
-                datetuple_check = time.localtime(time.mktime(datetuple))
-                if datetuple[:2] != datetuple_check[:2]:
-                    raise ValueError
-
-                # This could raise a value error if it's not a valid format.
-                date = imaplib.Time2Internaldate(datetuple) 
-            except (ValueError, OverflowError):
-                # Argh, sometimes it's a valid format but year is 0102
-                # or something.  Argh.  It seems that Time2Internaldate
-                # will rause a ValueError if the year is 0102 but not 1902,
-                # but some IMAP servers nonetheless choke on 1902.
-                date = imaplib.Time2Internaldate(time.localtime())
+            # UIDPLUS extension provides us with an APPENDUID response to our append()
+            use_uidplus = 'UIDPLUS' in imapobj.capabilities
 
+            # get the date of the message file, so we can pass it to the server.
+            date = self.getmessagedate(content, rtime)
             self.ui.debug('imap', 'savemessage: using date %s' % date)
+    
             content = re.sub("(?<!\r)\n", "\r\n", content)
-            self.ui.debug('imap', 'savemessage: initial content is: ' + repr(content))
-
-            (headername, headervalue) = self.generate_randomheader(content)
-            ui.debug('imap', 'savemessage: new headers are: %s: %s' % \
-                     (headername, headervalue))
-            content = self.savemessage_addheader(content, headername,
-                                                 headervalue)
-            self.ui.debug('imap', 'savemessage: new content is: ' + repr(content))
-            self.ui.debug('imap', 'savemessage: new content length is ' + \
-                     str(len(content)))
-
-            # TODO: append could raise a ValueError if the date is not in
-            #       valid format...?
+    
+            if not use_uidplus:
+                # insert a random unique header that we can fetch later
+                (headername, headervalue) = self.generate_randomheader(content)
+                self.ui.debug('imap', 'savemessage: new headers are: %s: %s' % \
+                             (headername, headervalue))
+                content = self.savemessage_addheader(content, headername,
+                                                     headervalue)    
+            self.ui.debug('imap', 'savemessage: content is: ' + repr(content))
+
+            # TODO: - append could raise a ValueError if the date is not in
+            #         valid format...?
             (typ,dat) = imapobj.append(self.getfullname(),
                                        imaputil.flagsmaildir2imap(flags),
                                        date, content)
@@ -424,25 +399,37 @@ class IMAPFolder(BaseFolder):
             (typ,dat) = imapobj.check()
             assert(typ == 'OK')
 
-            # Keep trying until we get the UID.
-            self.ui.debug('imap', 'savemessage: first attempt to get new UID')
-            uid = self.savemessage_searchforheader(imapobj, headername,
-                                                   headervalue)
-            # See docs for savemessage in Base.py for explanation of this and other return values
-            if uid <= 0:
-                self.ui.debug('imap', 'savemessage: first attempt to get new UID failed.  Going to run a NOOP and try again.')
-                assert(imapobj.noop()[0] == 'OK')
+            # get the UID.
+            if use_uidplus:
+                # get the new UID from the APPENDUID response, it could look like
+                # OK [APPENDUID 38505 3955] APPEND completed
+                # with 38505 bein folder UIDvalidity and 3955 the new UID
+                if not imapobj.untagged_responses.has_key('APPENDUID'):
+                    self.ui.warn("Server supports UIDPLUS but got no APPENDUID appending a message.")
+                    return 0
+                uid = long(imapobj.untagged_responses['APPENDUID'][-1].split(' ')[1])
+
+            else:
+                # we don't support UIDPLUS
                 uid = self.savemessage_searchforheader(imapobj, headername,
                                                        headervalue)
+                # See docs for savemessage in Base.py for explanation of this and other return values
+                if uid == 0:
+                    self.ui.debug('imap', 'savemessage: first attempt to get new UID failed.  Going to run a NOOP and try again.')
+                    assert(imapobj.noop()[0] == 'OK')
+                    uid = self.savemessage_searchforheader(imapobj, headername,
+                                                       headervalue)
+
         finally:
             self.imapserver.releaseconnection(imapobj)
 
         if uid: # avoid UID FETCH 0 crash happening later on
             self.messagelist[uid] = {'uid': uid, 'flags': flags}
 
-        self.ui.debug('imap', 'savemessage: returning %d' % uid)
+        self.ui.debug('imap', 'savemessage: returning new UID %d' % uid)
         return uid
 
+
     def savemessageflags(self, uid, flags):
         imapobj = self.imapserver.acquireconnection()
         try:
-- 
1.7.1




More information about the OfflineIMAP-project mailing list