From 466ded04d94396823bccdc56f70f51647ba61124 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Tue, 16 Aug 2011 12:16:46 +0200 Subject: [PATCH] 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 Signed-off-by: Nicolas Sebrecht --- 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 626156a..6d7e093 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): @@ -189,12 +193,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): @@ -204,11 +205,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): @@ -362,8 +359,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 @@ -371,30 +368,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 20940a2..40eb31c 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'): @@ -652,23 +658,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]) @@ -682,7 +683,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 375777e..be9d1e3 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 -# +# 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 77cdff3..11791d0 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) - - - - -