]> code.delx.au - offlineimap/blobdiff - offlineimap/folder/Maildir.py
Import md5 from hashlib if available.
[offlineimap] / offlineimap / folder / Maildir.py
index 74019cb658331a8338fac0e73559b5068ec15d16..d1d5077a903a35e23000be270446378e5ae23f52 100644 (file)
@@ -1,5 +1,5 @@
 # Maildir folder support
 # Maildir folder support
-# Copyright (C) 2002 - 2006 John Goerzen
+# Copyright (C) 2002 - 2007 John Goerzen
 # <jgoerzen@complete.org>
 #
 #    This program is free software; you can redistribute it and/or modify
 # <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
 
 #    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
 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]+)')
 
 uidmatchre = re.compile(',U=(\d+)')
 flagmatchre = re.compile(':.*2,([A-Z]+)')
 
@@ -46,8 +50,10 @@ def gettimeseq():
         timelock.release()
 
 class MaildirFolder(BaseFolder):
         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.name = name
+        self.config = config
+        self.dofsync = config.getdefaultboolean("general", "fsync", True)
         self.root = root
         self.sep = sep
         self.messagelist = None
         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.
         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)
         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
                 # 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
 
                            '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):
     def cachemessagelist(self):
-        self.messagelist = self._scanfolder()
+        if self.messagelist is None:
+            self.messagelist = self._scanfolder()
             
     def getmessagelist(self):
         return self.messagelist
             
     def getmessagelist(self):
         return self.messagelist
@@ -130,6 +149,8 @@ class MaildirFolder(BaseFolder):
         return st.st_mtime
 
     def savemessage(self, uid, content, flags, rtime):
         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)))
         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
             # 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
         tmpdir = os.path.join(self.getfullname(), 'tmp')
         messagename = None
         attempts = 0
@@ -169,16 +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)
         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()
         if rtime != None:
             os.utime(os.path.join(tmpdir,tmpmessagename), (rtime,rtime))
         ui.debug('maildir', 'savemessage: moving from %s to %s' % \
                  (tmpmessagename, messagename))
         file.close()
         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': [],
         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
         self.savemessageflags(uid, flags)
         ui.debug('maildir', 'savemessage: returning uid %d' % uid)
         return uid
@@ -189,6 +224,7 @@ class MaildirFolder(BaseFolder):
     def savemessageflags(self, uid, flags):
         oldfilename = self.messagelist[uid]['filename']
         newpath, newname = os.path.split(oldfilename)
     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]
         if 'S' in flags:
             # If a message has been seen, it goes into the cur
             # directory.  CR debian#152482, [complete.org #4]
@@ -211,6 +247,10 @@ class MaildirFolder(BaseFolder):
             self.messagelist[uid]['flags'] = flags
             self.messagelist[uid]['filename'] = newfilename
 
             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
     def deletemessage(self, uid):
         if not uid in self.messagelist:
             return