[PATCH] Make flags a set rather than a list

Sebastian Spaeth Sebastian at SSpaeth.de
Tue Aug 16 11:16:46 BST 2011


As this is essentially what it is, a set of values. This allows as
to do set arithmetics to see, e.g. the intersection of 2 flag sets 
rather than clunkily having to do:

for flag in newflags:
  if flag not in oldflags:
    oldflags.append(flag)

Also some more code documenting.

Signed-off-by: Sebastian Spaeth <Sebastian at SSpaeth.de>
---
Here is a patch that I want to submit for consideration. Rather than
treating our flags as an array of characters that we have to sort etc,
we could simply treat them as a set() of values. This allows to use set
arithmetic (eg set - subset) and it would help us move to a direction
where we could use multi-character flags in the future. It would be
cool to be able to sync all IMAP flags (such as \Junk or \ToDo) at some
point in the future.

 offlineimap/folder/Base.py              |   48 +++++++++++++++----------------
 offlineimap/folder/Gmail.py             |    3 +-
 offlineimap/folder/IMAP.py              |   23 ++++++++-------
 offlineimap/folder/LocalStatus.py       |   15 ++++++----
 offlineimap/folder/LocalStatusSQLite.py |    8 ++++-
 offlineimap/folder/Maildir.py           |   17 +++++++----
 offlineimap/imaputil.py                 |   39 +++++++++++++++++--------
 7 files changed, 89 insertions(+), 64 deletions(-)

diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py
index 3da0de1..bf602a2 100644
--- a/offlineimap/folder/Base.py
+++ b/offlineimap/folder/Base.py
@@ -22,6 +22,10 @@ import os.path
 import re
 from sys import exc_info
 import traceback
+try: # python 2.6 has set() built in
+    set
+except NameError:
+    from sets import Set as set
 
 class BaseFolder(object):
     def __init__(self):
@@ -185,12 +189,9 @@ class BaseFolder(object):
 
     def addmessageflags(self, uid, flags):
         """Adds the specified flags to the message's flag set.  If a given
-        flag is already present, it will not be duplicated."""
-        newflags = self.getmessageflags(uid)
-        for flag in flags:
-            if not flag in newflags:
-                newflags.append(flag)
-        newflags.sort()
+        flag is already present, it will not be duplicated.
+        :param flags: A set() of flags"""
+        newflags = self.getmessageflags(uid) | flags
         self.savemessageflags(uid, newflags)
 
     def addmessagesflags(self, uidlist, flags):
@@ -200,11 +201,7 @@ class BaseFolder(object):
     def deletemessageflags(self, uid, flags):
         """Removes each flag given from the message's flag set.  If a given
         flag is already removed, no action will be taken for that flag."""
-        newflags = self.getmessageflags(uid)
-        for flag in flags:
-            if flag in newflags:
-                newflags.remove(flag)
-        newflags.sort()
+        newflags = self.getmessageflags(uid) - flags
         self.savemessageflags(uid, newflags)
 
     def deletemessagesflags(self, uidlist, flags):
@@ -357,8 +354,8 @@ class BaseFolder(object):
         addflaglist = {}
         delflaglist = {}
         for uid in self.getmessageuidlist():
-            # Ignore messages with negative UIDs missed by pass 1
-            # also don't do anything if the message has been deleted remotely
+            # Ignore messages with negative UIDs missed by pass 1 and
+            # don't do anything if the message has been deleted remotely
             if uid < 0 or not dstfolder.uidexists(uid):
                 continue
 
@@ -366,30 +363,31 @@ class BaseFolder(object):
             statusflags = statusfolder.getmessageflags(uid)
             #if we could not get message flags from LocalStatus, assume empty.
             if statusflags is None:
-                statusflags = []
-            addflags = [x for x in selfflags if x not in statusflags]
+                statusflags = set()
+
+            addflags = selfflags - statusflags
+            delflags = statusflags - selfflags
 
             for flag in addflags:
                 if not flag in addflaglist:
                     addflaglist[flag] = []
                 addflaglist[flag].append(uid)
 
-            delflags = [x for x in statusflags if x not in selfflags]
             for flag in delflags:
                 if not flag in delflaglist:
                     delflaglist[flag] = []
                 delflaglist[flag].append(uid)
 
-        for flag in addflaglist.keys():
-            self.ui.addingflags(addflaglist[flag], flag, dstfolder)
-            dstfolder.addmessagesflags(addflaglist[flag], [flag])
-            statusfolder.addmessagesflags(addflaglist[flag], [flag])
-
-        for flag in delflaglist.keys():
-            self.ui.deletingflags(delflaglist[flag], flag, dstfolder)
-            dstfolder.deletemessagesflags(delflaglist[flag], [flag])
-            statusfolder.deletemessagesflags(delflaglist[flag], [flag])
+        for flag, uids in addflaglist.items():
+            self.ui.addingflags(uids, flag, dstfolder)
+            dstfolder.addmessagesflags(uids, set(flag))
+            statusfolder.addmessagesflags(uids, set(flag))
 
+        for flag,uids in delflaglist.items():
+            self.ui.deletingflags(uids, flag, dstfolder)
+            dstfolder.deletemessagesflags(uids, set(flag))
+            statusfolder.deletemessagesflags(uids, set(flag))
+                
     def syncmessagesto(self, dstfolder, statusfolder):
         """Syncs messages in this folder to the destination dstfolder.
 
diff --git a/offlineimap/folder/Gmail.py b/offlineimap/folder/Gmail.py
index 7cbff4f..6cd3446 100644
--- a/offlineimap/folder/Gmail.py
+++ b/offlineimap/folder/Gmail.py
@@ -21,7 +21,6 @@
 
 from IMAP import IMAPFolder
 from offlineimap import imaputil
-from copy import copy
 
 
 class GmailFolder(IMAPFolder):
@@ -45,7 +44,7 @@ class GmailFolder(IMAPFolder):
     def deletemessages_noconvert(self, uidlist):
         uidlist = [uid for uid in uidlist if uid in self.messagelist]
         if not len(uidlist):
-            return        
+            return
 
         if self.realdelete and not (self.getname() in self.real_delete_folders):
             # IMAP expunge is just "remove label" in this folder,
diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py
index 7b11238..e4ab185 100644
--- a/offlineimap/folder/IMAP.py
+++ b/offlineimap/folder/IMAP.py
@@ -24,6 +24,11 @@ import time
 from copy import copy
 from Base import BaseFolder
 from offlineimap import imaputil, imaplibutil, OfflineImapError
+try: # python 2.6 has set() built in
+    set
+except NameError:
+    from sets import Set as set
+
 
 class IMAPFolder(BaseFolder):
     def __init__(self, imapserver, name, visiblename, accountname, repository):
@@ -176,7 +181,8 @@ class IMAPFolder(BaseFolder):
         finally:
             self.imapserver.releaseconnection(imapobj)
         for messagestr in response:
-            # Discard the message number.
+            # looks like: '1 (FLAGS (\\Seen Old) UID 4807)'
+            # Discard initial message number.
             messagestr = messagestr.split(' ', 1)[1]
             options = imaputil.flags2hash(messagestr)
             if not options.has_key('UID'):
@@ -579,23 +585,18 @@ class IMAPFolder(BaseFolder):
             if not ('UID' in attributehash and 'FLAGS' in attributehash):
                 # Compensate for servers that don't return a UID attribute.
                 continue
-            lflags = attributehash['FLAGS']
+            flagstr = attributehash['FLAGS']
             uid = long(attributehash['UID'])
-            self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(lflags)
+            self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flagstr)
             try:
                 needupdate.remove(uid)
             except ValueError:          # Let it slide if it's not in the list
                 pass
         for uid in needupdate:
             if operation == '+':
-                for flag in flags:
-                    if not flag in self.messagelist[uid]['flags']:
-                        self.messagelist[uid]['flags'].append(flag)
-                    self.messagelist[uid]['flags'].sort()
+                self.messagelist[uid]['flags'] |= flags
             elif operation == '-':
-                for flag in flags:
-                    if flag in self.messagelist[uid]['flags']:
-                        self.messagelist[uid]['flags'].remove(flag)
+                self.messagelist[uid]['flags'] -= flags
 
     def deletemessage(self, uid):
         self.deletemessages_noconvert([uid])
@@ -609,7 +610,7 @@ class IMAPFolder(BaseFolder):
         if not len(uidlist):
             return
 
-        self.addmessagesflags_noconvert(uidlist, ['T'])
+        self.addmessagesflags_noconvert(uidlist, set('T'))
         imapobj = self.imapserver.acquireconnection()
         try:
             try:
diff --git a/offlineimap/folder/LocalStatus.py b/offlineimap/folder/LocalStatus.py
index 5113345..ffa35df 100644
--- a/offlineimap/folder/LocalStatus.py
+++ b/offlineimap/folder/LocalStatus.py
@@ -1,6 +1,5 @@
 # Local status cache virtual folder
-# Copyright (C) 2002 - 2008 John Goerzen
-# <jgoerzen at complete.org>
+# Copyright (C) 2002 - 2011 John Goerzen & contributors
 #
 #    This program is free software; you can redistribute it and/or modify
 #    it under the terms of the GNU General Public License as published by
@@ -19,6 +18,10 @@
 from Base import BaseFolder
 import os
 import threading
+try: # python 2.6 has set() built in
+    set
+except NameError:
+    from sets import Set as set
 
 magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
 
@@ -80,11 +83,12 @@ class LocalStatusFolder(BaseFolder):
             try:
                 uid, flags = line.split(':')
                 uid = long(uid)
+                flags = set(flags)
             except ValueError, e:
-                errstr = "Corrupt line '%s' in cache file '%s'" % (line, self.filename)
+                errstr = "Corrupt line '%s' in cache file '%s'" % \
+                    (line, self.filename)
                 self.ui.warn(errstr)
                 raise ValueError(errstr)
-            flags = [x for x in flags]
             self.messagelist[uid] = {'uid': uid, 'flags': flags}
         file.close()
 
@@ -95,8 +99,7 @@ class LocalStatusFolder(BaseFolder):
             file.write(magicline + "\n")
             for msg in self.messagelist.values():
                 flags = msg['flags']
-                flags.sort()
-                flags = ''.join(flags)
+                flags = ''.join(sorted(flags))
                 file.write("%s:%s\n" % (msg['uid'], flags))
             file.flush()
             if self.doautosave:
diff --git a/offlineimap/folder/LocalStatusSQLite.py b/offlineimap/folder/LocalStatusSQLite.py
index a57992b..7715bad 100644
--- a/offlineimap/folder/LocalStatusSQLite.py
+++ b/offlineimap/folder/LocalStatusSQLite.py
@@ -23,6 +23,11 @@ try:
 except:
     pass #fail only if needed later on, not on import
 
+try: # python 2.6 has set() built in
+    set
+except NameError:
+    from sets import Set as set
+
 class LocalStatusSQLiteFolder(LocalStatusFolder):
     """LocalStatus backend implemented with an SQLite database
 
@@ -127,7 +132,6 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
                 for line in file.xreadlines():
                     uid, flags = line.strip().split(':')
                     uid = long(uid)
-                    flags = list(flags)
                     flags = ''.join(sorted(flags))
                     data.append((uid,flags))
                 self.connection.executemany('INSERT INTO status (id,flags) VALUES (?,?)',
@@ -167,7 +171,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
         self.messagelist = {}
         cursor = self.connection.execute('SELECT id,flags from status')
         for row in cursor:
-                flags = [x for x in row[1]]
+                flags = set(row[1])
                 self.messagelist[row[0]] = {'uid': row[0], 'flags': flags}
 
     def save(self):
diff --git a/offlineimap/folder/Maildir.py b/offlineimap/folder/Maildir.py
index 3b53156..3b564dc 100644
--- a/offlineimap/folder/Maildir.py
+++ b/offlineimap/folder/Maildir.py
@@ -28,6 +28,11 @@ try:
 except ImportError:
     from md5 import md5
 
+try: # python 2.6 has set() built in
+    set
+except NameError:
+    from sets import Set as set
+
 from offlineimap import OfflineImapError
 
 uidmatchre = re.compile(',U=(\d+)')
@@ -166,11 +171,12 @@ class MaildirFolder(BaseFolder):
                     nouidcounter -= 1
                 else:
                     uid = long(uidmatch.group(1))
+            #identify flags in the path name
             flagmatch = self.flagmatchre.search(messagename)
-            flags = []
             if flagmatch:
-                flags = [x for x in flagmatch.group(1)]
-            flags.sort()
+                flags = set(flagmatch.group(1))
+            else:
+                flags = set()
             retval[uid] = {'uid': uid,
                            'flags': flags,
                            'filename': file}
@@ -261,7 +267,7 @@ class MaildirFolder(BaseFolder):
         if rtime != None:
             os.utime(os.path.join(tmpdir, messagename), (rtime, rtime))
 
-        self.messagelist[uid] = {'uid': uid, 'flags': [],
+        self.messagelist[uid] = {'uid': uid, 'flags': set(),
                                  'filename': os.path.join('tmp', messagename)}
         # savemessageflags moves msg to 'cur' or 'new' as appropriate
         self.savemessageflags(uid, flags)
@@ -288,8 +294,7 @@ class MaildirFolder(BaseFolder):
             infostr = infomatch.group(1)
             newname = newname.split(self.infosep)[0] # Strip off the info string.
         infostr = re.sub('2,[A-Z]*', '', infostr)
-        flags.sort()
-        infostr += '2,' + ''.join(flags)
+        infostr += '2,' + ''.join(sorted(flags))
         newname += infostr
         
         newfilename = os.path.join(dir_prefix, newname)
diff --git a/offlineimap/imaputil.py b/offlineimap/imaputil.py
index 3905851..80a2486 100644
--- a/offlineimap/imaputil.py
+++ b/offlineimap/imaputil.py
@@ -20,6 +20,11 @@ import re
 import string
 import types
 from offlineimap.ui import getglobalui
+try: # python 2.6 has set() built in
+    set
+except NameError:
+    from sets import Set as set
+
 quotere = re.compile('^("(?:[^"]|\\\\")*")')
 
 def debug(*args):
@@ -42,11 +47,21 @@ def dequote(string):
     return string
 
 def flagsplit(string):
+    """Converts a string of IMAP flags to a list
+
+    :returns: E.g. '(\\Draft \\Deleted)' returns  ['\\Draft','\\Deleted'].
+        (FLAGS (\\Seen Old) UID 4807) returns
+        ['FLAGS,'(\\Seen Old)','UID', '4807']
+    """
     if string[0] != '(' or string[-1] != ')':
         raise ValueError, "Passed string '%s' is not a flag list" % string
     return imapsplit(string[1:-1])
 
 def options2hash(list):
+    """convert list [1,2,3,4,5,6] to {1:2, 3:4, 5:6}"""
+    # effectively this does dict(zip(l[::2],l[1::2])), however
+    # measurements seemed to have indicated that the manual variant is
+    # faster for mosly small lists.
     retval = {}
     counter = 0
     while (counter < len(list)):
@@ -55,8 +70,12 @@ def options2hash(list):
     debug("options2hash returning:", retval)
     return retval
 
-def flags2hash(string):
-    return options2hash(flagsplit(string))
+def flags2hash(flags):
+    """Converts IMAP response string from eg IMAP4.fetch() to a hash.
+    
+    E.g. '(FLAGS (\\Seen Old) UID 4807)' leads to
+    {'FLAGS': '(\\Seen Old)', 'UID': '4807'}"""
+    return options2hash(flagsplit(flags))
 
 def imapsplit(imapstring):
     """Takes a string from an IMAP conversation and returns a list containing
@@ -152,15 +171,16 @@ flagmap = [('\\Seen', 'S'),
            ('\\Draft', 'D')]
 
 def flagsimap2maildir(flagstring):
-    retval = []
-    imapflaglist = [x.lower() for x in flagstring[1:-1].split()]
+    """Convert string '(\\Draft \\Deleted)' into a flags set(DR)"""
+    retval = set()
+    imapflaglist = flagstring[1:-1].split()
     for imapflag, maildirflag in flagmap:
-        if imapflag.lower() in imapflaglist:
-            retval.append(maildirflag)
-    retval.sort()
+        if imapflag in imapflaglist:
+            retval.add(maildirflag)
     return retval
 
 def flagsmaildir2imap(maildirflaglist):
+    """Convert set of flags ([DR]) into a string '(\\Draft \\Deleted)'"""
     retval = []
     for imapflag, maildirflag in flagmap:
         if maildirflag in maildirflaglist:
@@ -198,8 +218,3 @@ def listjoin(list):
         retval.append(getlist(start, end))
 
     return ",".join(retval)
-
-
-
-            
-        
-- 
1.7.4.1





More information about the OfflineIMAP-project mailing list