]> code.delx.au - offlineimap/blobdiff - offlineimap/folder/Maildir.py
Import md5 from hashlib if available.
[offlineimap] / offlineimap / folder / Maildir.py
index 6d61c3d08e957618664b413d3208761bc5867939..d1d5077a903a35e23000be270446378e5ae23f52 100644 (file)
@@ -1,5 +1,5 @@
 # Maildir folder support
-# Copyright (C) 2002 John Goerzen
+# Copyright (C) 2002 - 2007 John Goerzen
 # <jgoerzen@complete.org>
 #
 #    This program is free software; you can redistribute it and/or modify
 #    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