Make flags a set rather than a list

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@SSpaeth.de>
Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
Sebastian Spaeth 2011-08-16 12:16:46 +02:00 committed by Nicolas Sebrecht
parent 373e7cdbc1
commit 466ded04d9
7 changed files with 89 additions and 64 deletions

View File

@ -22,6 +22,10 @@ import os.path
import re import re
from sys import exc_info from sys import exc_info
import traceback import traceback
try: # python 2.6 has set() built in
set
except NameError:
from sets import Set as set
class BaseFolder(object): class BaseFolder(object):
def __init__(self): def __init__(self):
@ -189,12 +193,9 @@ class BaseFolder(object):
def addmessageflags(self, uid, flags): def addmessageflags(self, uid, flags):
"""Adds the specified flags to the message's flag set. If a given """Adds the specified flags to the message's flag set. If a given
flag is already present, it will not be duplicated.""" flag is already present, it will not be duplicated.
newflags = self.getmessageflags(uid) :param flags: A set() of flags"""
for flag in flags: newflags = self.getmessageflags(uid) | flags
if not flag in newflags:
newflags.append(flag)
newflags.sort()
self.savemessageflags(uid, newflags) self.savemessageflags(uid, newflags)
def addmessagesflags(self, uidlist, flags): def addmessagesflags(self, uidlist, flags):
@ -204,11 +205,7 @@ class BaseFolder(object):
def deletemessageflags(self, uid, flags): def deletemessageflags(self, uid, flags):
"""Removes each flag given from the message's flag set. If a given """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.""" flag is already removed, no action will be taken for that flag."""
newflags = self.getmessageflags(uid) newflags = self.getmessageflags(uid) - flags
for flag in flags:
if flag in newflags:
newflags.remove(flag)
newflags.sort()
self.savemessageflags(uid, newflags) self.savemessageflags(uid, newflags)
def deletemessagesflags(self, uidlist, flags): def deletemessagesflags(self, uidlist, flags):
@ -362,8 +359,8 @@ class BaseFolder(object):
addflaglist = {} addflaglist = {}
delflaglist = {} delflaglist = {}
for uid in self.getmessageuidlist(): for uid in self.getmessageuidlist():
# Ignore messages with negative UIDs missed by pass 1 # Ignore messages with negative UIDs missed by pass 1 and
# also don't do anything if the message has been deleted remotely # don't do anything if the message has been deleted remotely
if uid < 0 or not dstfolder.uidexists(uid): if uid < 0 or not dstfolder.uidexists(uid):
continue continue
@ -371,30 +368,31 @@ class BaseFolder(object):
statusflags = statusfolder.getmessageflags(uid) statusflags = statusfolder.getmessageflags(uid)
#if we could not get message flags from LocalStatus, assume empty. #if we could not get message flags from LocalStatus, assume empty.
if statusflags is None: if statusflags is None:
statusflags = [] statusflags = set()
addflags = [x for x in selfflags if x not in statusflags]
addflags = selfflags - statusflags
delflags = statusflags - selfflags
for flag in addflags: for flag in addflags:
if not flag in addflaglist: if not flag in addflaglist:
addflaglist[flag] = [] addflaglist[flag] = []
addflaglist[flag].append(uid) addflaglist[flag].append(uid)
delflags = [x for x in statusflags if x not in selfflags]
for flag in delflags: for flag in delflags:
if not flag in delflaglist: if not flag in delflaglist:
delflaglist[flag] = [] delflaglist[flag] = []
delflaglist[flag].append(uid) delflaglist[flag].append(uid)
for flag in addflaglist.keys(): for flag, uids in addflaglist.items():
self.ui.addingflags(addflaglist[flag], flag, dstfolder) self.ui.addingflags(uids, flag, dstfolder)
dstfolder.addmessagesflags(addflaglist[flag], [flag]) dstfolder.addmessagesflags(uids, set(flag))
statusfolder.addmessagesflags(addflaglist[flag], [flag]) statusfolder.addmessagesflags(uids, set(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 delflaglist.items():
self.ui.deletingflags(uids, flag, dstfolder)
dstfolder.deletemessagesflags(uids, set(flag))
statusfolder.deletemessagesflags(uids, set(flag))
def syncmessagesto(self, dstfolder, statusfolder): def syncmessagesto(self, dstfolder, statusfolder):
"""Syncs messages in this folder to the destination dstfolder. """Syncs messages in this folder to the destination dstfolder.

View File

@ -21,7 +21,6 @@
from IMAP import IMAPFolder from IMAP import IMAPFolder
from offlineimap import imaputil from offlineimap import imaputil
from copy import copy
class GmailFolder(IMAPFolder): class GmailFolder(IMAPFolder):
@ -45,7 +44,7 @@ class GmailFolder(IMAPFolder):
def deletemessages_noconvert(self, uidlist): def deletemessages_noconvert(self, uidlist):
uidlist = [uid for uid in uidlist if uid in self.messagelist] uidlist = [uid for uid in uidlist if uid in self.messagelist]
if not len(uidlist): if not len(uidlist):
return return
if self.realdelete and not (self.getname() in self.real_delete_folders): if self.realdelete and not (self.getname() in self.real_delete_folders):
# IMAP expunge is just "remove label" in this folder, # IMAP expunge is just "remove label" in this folder,

View File

@ -24,6 +24,11 @@ import time
from copy import copy from copy import copy
from Base import BaseFolder from Base import BaseFolder
from offlineimap import imaputil, imaplibutil, OfflineImapError 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): class IMAPFolder(BaseFolder):
def __init__(self, imapserver, name, visiblename, accountname, repository): def __init__(self, imapserver, name, visiblename, accountname, repository):
@ -176,7 +181,8 @@ class IMAPFolder(BaseFolder):
finally: finally:
self.imapserver.releaseconnection(imapobj) self.imapserver.releaseconnection(imapobj)
for messagestr in response: 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] messagestr = messagestr.split(' ', 1)[1]
options = imaputil.flags2hash(messagestr) options = imaputil.flags2hash(messagestr)
if not options.has_key('UID'): if not options.has_key('UID'):
@ -652,23 +658,18 @@ class IMAPFolder(BaseFolder):
if not ('UID' in attributehash and 'FLAGS' in attributehash): if not ('UID' in attributehash and 'FLAGS' in attributehash):
# Compensate for servers that don't return a UID attribute. # Compensate for servers that don't return a UID attribute.
continue continue
lflags = attributehash['FLAGS'] flagstr = attributehash['FLAGS']
uid = long(attributehash['UID']) uid = long(attributehash['UID'])
self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(lflags) self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flagstr)
try: try:
needupdate.remove(uid) needupdate.remove(uid)
except ValueError: # Let it slide if it's not in the list except ValueError: # Let it slide if it's not in the list
pass pass
for uid in needupdate: for uid in needupdate:
if operation == '+': if operation == '+':
for flag in flags: self.messagelist[uid]['flags'] |= flags
if not flag in self.messagelist[uid]['flags']:
self.messagelist[uid]['flags'].append(flag)
self.messagelist[uid]['flags'].sort()
elif operation == '-': elif operation == '-':
for flag in flags: self.messagelist[uid]['flags'] -= flags
if flag in self.messagelist[uid]['flags']:
self.messagelist[uid]['flags'].remove(flag)
def deletemessage(self, uid): def deletemessage(self, uid):
self.deletemessages_noconvert([uid]) self.deletemessages_noconvert([uid])
@ -682,7 +683,7 @@ class IMAPFolder(BaseFolder):
if not len(uidlist): if not len(uidlist):
return return
self.addmessagesflags_noconvert(uidlist, ['T']) self.addmessagesflags_noconvert(uidlist, set('T'))
imapobj = self.imapserver.acquireconnection() imapobj = self.imapserver.acquireconnection()
try: try:
try: try:

View File

@ -1,6 +1,5 @@
# Local status cache virtual folder # Local status cache virtual folder
# Copyright (C) 2002 - 2008 John Goerzen # Copyright (C) 2002 - 2011 John Goerzen & contributors
# <jgoerzen@complete.org>
# #
# This program is free software; you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
@ -19,6 +18,10 @@
from Base import BaseFolder from Base import BaseFolder
import os import os
import threading 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" magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
@ -80,11 +83,12 @@ class LocalStatusFolder(BaseFolder):
try: try:
uid, flags = line.split(':') uid, flags = line.split(':')
uid = long(uid) uid = long(uid)
flags = set(flags)
except ValueError, e: 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) self.ui.warn(errstr)
raise ValueError(errstr) raise ValueError(errstr)
flags = [x for x in flags]
self.messagelist[uid] = {'uid': uid, 'flags': flags} self.messagelist[uid] = {'uid': uid, 'flags': flags}
file.close() file.close()
@ -95,8 +99,7 @@ class LocalStatusFolder(BaseFolder):
file.write(magicline + "\n") file.write(magicline + "\n")
for msg in self.messagelist.values(): for msg in self.messagelist.values():
flags = msg['flags'] flags = msg['flags']
flags.sort() flags = ''.join(sorted(flags))
flags = ''.join(flags)
file.write("%s:%s\n" % (msg['uid'], flags)) file.write("%s:%s\n" % (msg['uid'], flags))
file.flush() file.flush()
if self.doautosave: if self.doautosave:

View File

@ -23,6 +23,11 @@ try:
except: except:
pass #fail only if needed later on, not on import 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): class LocalStatusSQLiteFolder(LocalStatusFolder):
"""LocalStatus backend implemented with an SQLite database """LocalStatus backend implemented with an SQLite database
@ -127,7 +132,6 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
for line in file.xreadlines(): for line in file.xreadlines():
uid, flags = line.strip().split(':') uid, flags = line.strip().split(':')
uid = long(uid) uid = long(uid)
flags = list(flags)
flags = ''.join(sorted(flags)) flags = ''.join(sorted(flags))
data.append((uid,flags)) data.append((uid,flags))
self.connection.executemany('INSERT INTO status (id,flags) VALUES (?,?)', self.connection.executemany('INSERT INTO status (id,flags) VALUES (?,?)',
@ -167,7 +171,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
self.messagelist = {} self.messagelist = {}
cursor = self.connection.execute('SELECT id,flags from status') cursor = self.connection.execute('SELECT id,flags from status')
for row in cursor: for row in cursor:
flags = [x for x in row[1]] flags = set(row[1])
self.messagelist[row[0]] = {'uid': row[0], 'flags': flags} self.messagelist[row[0]] = {'uid': row[0], 'flags': flags}
def save(self): def save(self):

View File

@ -28,6 +28,11 @@ try:
except ImportError: except ImportError:
from md5 import md5 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 from offlineimap import OfflineImapError
uidmatchre = re.compile(',U=(\d+)') uidmatchre = re.compile(',U=(\d+)')
@ -166,11 +171,12 @@ class MaildirFolder(BaseFolder):
nouidcounter -= 1 nouidcounter -= 1
else: else:
uid = long(uidmatch.group(1)) uid = long(uidmatch.group(1))
#identify flags in the path name
flagmatch = self.flagmatchre.search(messagename) flagmatch = self.flagmatchre.search(messagename)
flags = []
if flagmatch: if flagmatch:
flags = [x for x in flagmatch.group(1)] flags = set(flagmatch.group(1))
flags.sort() else:
flags = set()
retval[uid] = {'uid': uid, retval[uid] = {'uid': uid,
'flags': flags, 'flags': flags,
'filename': file} 'filename': file}
@ -261,7 +267,7 @@ class MaildirFolder(BaseFolder):
if rtime != None: if rtime != None:
os.utime(os.path.join(tmpdir, messagename), (rtime, rtime)) 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)} 'filename': os.path.join('tmp', messagename)}
# savemessageflags moves msg to 'cur' or 'new' as appropriate # savemessageflags moves msg to 'cur' or 'new' as appropriate
self.savemessageflags(uid, flags) self.savemessageflags(uid, flags)
@ -288,8 +294,7 @@ class MaildirFolder(BaseFolder):
infostr = infomatch.group(1) infostr = infomatch.group(1)
newname = newname.split(self.infosep)[0] # Strip off the info string. newname = newname.split(self.infosep)[0] # Strip off the info string.
infostr = re.sub('2,[A-Z]*', '', infostr) infostr = re.sub('2,[A-Z]*', '', infostr)
flags.sort() infostr += '2,' + ''.join(sorted(flags))
infostr += '2,' + ''.join(flags)
newname += infostr newname += infostr
newfilename = os.path.join(dir_prefix, newname) newfilename = os.path.join(dir_prefix, newname)

View File

@ -20,6 +20,11 @@ import re
import string import string
import types import types
from offlineimap.ui import getglobalui 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('^("(?:[^"]|\\\\")*")') quotere = re.compile('^("(?:[^"]|\\\\")*")')
def debug(*args): def debug(*args):
@ -42,11 +47,21 @@ def dequote(string):
return string return string
def flagsplit(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] != ')': if string[0] != '(' or string[-1] != ')':
raise ValueError, "Passed string '%s' is not a flag list" % string raise ValueError, "Passed string '%s' is not a flag list" % string
return imapsplit(string[1:-1]) return imapsplit(string[1:-1])
def options2hash(list): 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 = {} retval = {}
counter = 0 counter = 0
while (counter < len(list)): while (counter < len(list)):
@ -55,8 +70,12 @@ def options2hash(list):
debug("options2hash returning:", retval) debug("options2hash returning:", retval)
return retval return retval
def flags2hash(string): def flags2hash(flags):
return options2hash(flagsplit(string)) """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): def imapsplit(imapstring):
"""Takes a string from an IMAP conversation and returns a list containing """Takes a string from an IMAP conversation and returns a list containing
@ -152,15 +171,16 @@ flagmap = [('\\Seen', 'S'),
('\\Draft', 'D')] ('\\Draft', 'D')]
def flagsimap2maildir(flagstring): def flagsimap2maildir(flagstring):
retval = [] """Convert string '(\\Draft \\Deleted)' into a flags set(DR)"""
imapflaglist = [x.lower() for x in flagstring[1:-1].split()] retval = set()
imapflaglist = flagstring[1:-1].split()
for imapflag, maildirflag in flagmap: for imapflag, maildirflag in flagmap:
if imapflag.lower() in imapflaglist: if imapflag in imapflaglist:
retval.append(maildirflag) retval.add(maildirflag)
retval.sort()
return retval return retval
def flagsmaildir2imap(maildirflaglist): def flagsmaildir2imap(maildirflaglist):
"""Convert set of flags ([DR]) into a string '(\\Draft \\Deleted)'"""
retval = [] retval = []
for imapflag, maildirflag in flagmap: for imapflag, maildirflag in flagmap:
if maildirflag in maildirflaglist: if maildirflag in maildirflaglist:
@ -198,8 +218,3 @@ def listjoin(list):
retval.append(getlist(start, end)) retval.append(getlist(start, end))
return ",".join(retval) return ",".join(retval)