X-Git-Url: https://code.delx.au/offlineimap/blobdiff_plain/9ad972a56718c175d9e764dbddf56e1e0aee1dfe..c6d95bd47132f58a9836586451db6f98aa3dd3d4:/offlineimap/folder/Maildir.py diff --git a/offlineimap/folder/Maildir.py b/offlineimap/folder/Maildir.py index 6d61c3d..d1d5077 100644 --- a/offlineimap/folder/Maildir.py +++ b/offlineimap/folder/Maildir.py @@ -1,5 +1,5 @@ # Maildir folder support -# Copyright (C) 2002 John Goerzen +# Copyright (C) 2002 - 2007 John Goerzen # # # This program is free software; you can redistribute it and/or modify @@ -16,13 +16,17 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +import os.path, os, re, time, socket from Base import BaseFolder from offlineimap import imaputil from offlineimap.ui import UIBase from threading import Lock -import os.path, os, re, time, socket, md5 -foldermatchre = re.compile(',FMD5=([0-9a-f]{32})') +try: + from hashlib import md5 +except ImportError: + import md5 + uidmatchre = re.compile(',U=(\d+)') flagmatchre = re.compile(':.*2,([A-Z]+)') @@ -46,8 +50,10 @@ def gettimeseq(): timelock.release() class MaildirFolder(BaseFolder): - def __init__(self, root, name, sep, repository, accountname): + def __init__(self, root, name, sep, repository, accountname, config): self.name = name + self.config = config + self.dofsync = config.getdefaultboolean("general", "fsync", True) self.root = root self.sep = sep self.messagelist = None @@ -78,16 +84,16 @@ class MaildirFolder(BaseFolder): files = [] nouidcounter = -1 # Messages without UIDs get # negative UID numbers. + foldermd5 = md5.new(self.getvisiblename()).hexdigest() + folderstr = ',FMD5=' + foldermd5 for dirannex in ['new', 'cur']: fulldirname = os.path.join(self.getfullname(), dirannex) files.extend([os.path.join(fulldirname, filename) for filename in os.listdir(fulldirname)]) for file in files: messagename = os.path.basename(file) - foldermatch = foldermatchre.search(messagename) - if (not foldermatch) or \ - md5.new(self.getvisiblename()).hexdigest() \ - != foldermatch.group(1): + foldermatch = messagename.find(folderstr) != -1 + if not foldermatch: # If there is no folder MD5 specified, or if it mismatches, # assume it is a foreign (new) message and generate a # negative uid for it @@ -111,8 +117,21 @@ class MaildirFolder(BaseFolder): 'filename': file} return retval + def quickchanged(self, statusfolder): + self.cachemessagelist() + savedmessages = statusfolder.getmessagelist() + if len(self.messagelist) != len(savedmessages): + return True + for uid in self.messagelist.keys(): + if uid not in savedmessages: + return True + if self.messagelist[uid]['flags'] != savedmessages[uid]['flags']: + return True + return False + def cachemessagelist(self): - self.messagelist = self._scanfolder() + if self.messagelist is None: + self.messagelist = self._scanfolder() def getmessagelist(self): return self.messagelist @@ -130,6 +149,8 @@ class MaildirFolder(BaseFolder): return st.st_mtime def savemessage(self, uid, content, flags, rtime): + # This function only ever saves to tmp/, + # but it calls savemessageflags() to actually save to cur/ or new/. ui = UIBase.getglobalui() ui.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \ (repr(flags), repr(content))) @@ -140,12 +161,9 @@ class MaildirFolder(BaseFolder): # We already have it. self.savemessageflags(uid, flags) return uid - if 'S' in flags: - # If a message has been seen, it goes into the cur - # directory. CR debian#152482, [complete.org #4] - newdir = os.path.join(self.getfullname(), 'cur') - else: - newdir = os.path.join(self.getfullname(), 'new') + + # Otherwise, save the message in tmp/ and then call savemessageflags() + # to give it a permanent home. tmpdir = os.path.join(self.getfullname(), 'tmp') messagename = None attempts = 0 @@ -169,15 +187,33 @@ class MaildirFolder(BaseFolder): ui.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename) file = open(os.path.join(tmpdir, tmpmessagename), "wt") file.write(content) + + # Make sure the data hits the disk + file.flush() + if self.dofsync: + os.fsync(file.fileno()) + file.close() - os.utime(os.path.join(tmpdir,tmpmessagename), (rtime,rtime)) + if rtime != None: + os.utime(os.path.join(tmpdir,tmpmessagename), (rtime,rtime)) ui.debug('maildir', 'savemessage: moving from %s to %s' % \ (tmpmessagename, messagename)) - os.link(os.path.join(tmpdir, tmpmessagename), - os.path.join(newdir, messagename)) - os.unlink(os.path.join(tmpdir, tmpmessagename)) + if tmpmessagename != messagename: # then rename it + os.link(os.path.join(tmpdir, tmpmessagename), + os.path.join(tmpdir, messagename)) + os.unlink(os.path.join(tmpdir, tmpmessagename)) + + if self.dofsync: + try: + # fsync the directory (safer semantics in Linux) + fd = os.open(tmpdir, os.O_RDONLY) + os.fsync(fd) + os.close(fd) + except: + pass + self.messagelist[uid] = {'uid': uid, 'flags': [], - 'filename': os.path.join(newdir, messagename)} + 'filename': os.path.join(tmpdir, messagename)} self.savemessageflags(uid, flags) ui.debug('maildir', 'savemessage: returning uid %d' % uid) return uid @@ -188,6 +224,7 @@ class MaildirFolder(BaseFolder): def savemessageflags(self, uid, flags): oldfilename = self.messagelist[uid]['filename'] newpath, newname = os.path.split(oldfilename) + tmpdir = os.path.join(self.getfullname(), 'tmp') if 'S' in flags: # If a message has been seen, it goes into the cur # directory. CR debian#152482, [complete.org #4] @@ -210,6 +247,10 @@ class MaildirFolder(BaseFolder): self.messagelist[uid]['flags'] = flags self.messagelist[uid]['filename'] = newfilename + # By now, the message had better not be in tmp/ land! + final_dir, final_name = os.path.split(self.messagelist[uid]['filename']) + assert final_dir != tmpdir + def deletemessage(self, uid): if not uid in self.messagelist: return