# 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
from threading import Lock
import os.path, os, re, time, socket, md5
-foldermatchre = re.compile(',FMD5=([0-9a-f]{32})')
uidmatchre = re.compile(',U=(\d+)')
flagmatchre = re.compile(':.*2,([A-Z]+)')
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
'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
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)))
# 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
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()
+ 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))
- 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))
+
+ 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
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]
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