[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