]>
code.delx.au - offlineimap/blob - offlineimap/folder/Maildir.py
d1921b88b03ece0c9f27a1be2809a79b7258105e
1 # Maildir folder support
2 # Copyright (C) 2002 - 2007 John Goerzen
3 # <jgoerzen@complete.org>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 from Base
import BaseFolder
20 from offlineimap
import imaputil
21 from offlineimap
.ui
import UIBase
22 from threading
import Lock
23 import os
.path
, os
, re
, time
, socket
, md5
25 uidmatchre
= re
.compile(',U=(\d+)')
26 flagmatchre
= re
.compile(':.*2,([A-Z]+)')
33 global lasttime
, timeseq
, timelock
36 thistime
= long(time
.time())
37 if thistime
== lasttime
:
39 return (thistime
, timeseq
)
43 return (thistime
, timeseq
)
47 class MaildirFolder(BaseFolder
):
48 def __init__(self
, root
, name
, sep
, repository
, accountname
, config
):
51 self
.dofsync
= config
.getdefaultboolean("general", "fsync", True)
54 self
.messagelist
= None
55 self
.repository
= repository
56 self
.accountname
= accountname
57 BaseFolder
.__init
__(self
)
59 def getaccountname(self
):
60 return self
.accountname
62 def getfullname(self
):
63 return os
.path
.join(self
.getroot(), self
.getname())
65 def getuidvalidity(self
):
66 """Maildirs have no notion of uidvalidity, so we just return a magic
70 def _scanfolder(self
):
71 """Cache the message list. Maildir flags are:
77 and must occur in ASCII order."""
80 nouidcounter
= -1 # Messages without UIDs get
81 # negative UID numbers.
82 foldermd5
= md5
.new(self
.getvisiblename()).hexdigest()
83 folderstr
= ',FMD5=' + foldermd5
84 for dirannex
in ['new', 'cur']:
85 fulldirname
= os
.path
.join(self
.getfullname(), dirannex
)
86 files
.extend([os
.path
.join(fulldirname
, filename
) for
87 filename
in os
.listdir(fulldirname
)])
89 messagename
= os
.path
.basename(file)
90 foldermatch
= messagename
.find(folderstr
) != -1
92 # If there is no folder MD5 specified, or if it mismatches,
93 # assume it is a foreign (new) message and generate a
97 else: # It comes from our folder.
98 uidmatch
= uidmatchre
.search(messagename
)
104 uid
= long(uidmatch
.group(1))
105 flagmatch
= flagmatchre
.search(messagename
)
108 flags
= [x
for x
in flagmatch
.group(1)]
110 retval
[uid
] = {'uid': uid
,
115 def quickchanged(self
, statusfolder
):
116 self
.cachemessagelist()
117 savedmessages
= statusfolder
.getmessagelist()
118 if len(self
.messagelist
) != len(savedmessages
):
120 for uid
in self
.messagelist
.keys():
121 if uid
not in savedmessages
:
123 if self
.messagelist
[uid
]['flags'] != savedmessages
[uid
]['flags']:
127 def cachemessagelist(self
):
128 if self
.messagelist
is None:
129 self
.messagelist
= self
._scanfolder
()
131 def getmessagelist(self
):
132 return self
.messagelist
134 def getmessage(self
, uid
):
135 filename
= self
.messagelist
[uid
]['filename']
136 file = open(filename
, 'rt')
139 return retval
.replace("\r\n", "\n")
141 def getmessagetime( self
, uid
):
142 filename
= self
.messagelist
[uid
]['filename']
143 st
= os
.stat(filename
)
146 def savemessage(self
, uid
, content
, flags
, rtime
):
147 # This function only ever saves to tmp/,
148 # but it calls savemessageflags() to actually save to cur/ or new/.
149 ui
= UIBase
.getglobalui()
150 ui
.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \
151 (repr(flags
), repr(content
)))
153 # We cannot assign a new uid.
155 if uid
in self
.messagelist
:
156 # We already have it.
157 self
.savemessageflags(uid
, flags
)
160 # Otherwise, save the message in tmp/ and then call savemessageflags()
161 # to give it a permanent home.
162 tmpdir
= os
.path
.join(self
.getfullname(), 'tmp')
167 raise IOError, "Couldn't write to file %s" % messagename
168 timeval
, timeseq
= gettimeseq()
169 messagename
= '%d_%d.%d.%s,U=%d,FMD5=%s' % \
173 socket
.gethostname(),
175 md5
.new(self
.getvisiblename()).hexdigest())
176 if os
.path
.exists(os
.path
.join(tmpdir
, messagename
)):
181 tmpmessagename
= messagename
.split(',')[0]
182 ui
.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename
)
183 file = open(os
.path
.join(tmpdir
, tmpmessagename
), "wt")
186 # Make sure the data hits the disk
189 os
.fsync(file.fileno())
193 os
.utime(os
.path
.join(tmpdir
,tmpmessagename
), (rtime
,rtime
))
194 ui
.debug('maildir', 'savemessage: moving from %s to %s' % \
195 (tmpmessagename
, messagename
))
196 if tmpmessagename
!= messagename
: # then rename it
197 os
.link(os
.path
.join(tmpdir
, tmpmessagename
),
198 os
.path
.join(tmpdir
, messagename
))
199 os
.unlink(os
.path
.join(tmpdir
, tmpmessagename
))
203 # fsync the directory (safer semantics in Linux)
204 fd
= os
.open(tmpdir
, os
.O_RDONLY
)
210 self
.messagelist
[uid
] = {'uid': uid
, 'flags': [],
211 'filename': os
.path
.join(tmpdir
, messagename
)}
212 self
.savemessageflags(uid
, flags
)
213 ui
.debug('maildir', 'savemessage: returning uid %d' % uid
)
216 def getmessageflags(self
, uid
):
217 return self
.messagelist
[uid
]['flags']
219 def savemessageflags(self
, uid
, flags
):
220 oldfilename
= self
.messagelist
[uid
]['filename']
221 newpath
, newname
= os
.path
.split(oldfilename
)
222 tmpdir
= os
.path
.join(self
.getfullname(), 'tmp')
224 # If a message has been seen, it goes into the cur
225 # directory. CR debian#152482, [complete.org #4]
226 newpath
= os
.path
.join(self
.getfullname(), 'cur')
228 newpath
= os
.path
.join(self
.getfullname(), 'new')
230 infomatch
= re
.search('(:.*)$', newname
)
231 if infomatch
: # If the info string is present..
232 infostr
= infomatch
.group(1)
233 newname
= newname
.split(':')[0] # Strip off the info string.
234 infostr
= re
.sub('2,[A-Z]*', '', infostr
)
236 infostr
+= '2,' + ''.join(flags
)
239 newfilename
= os
.path
.join(newpath
, newname
)
240 if (newfilename
!= oldfilename
):
241 os
.rename(oldfilename
, newfilename
)
242 self
.messagelist
[uid
]['flags'] = flags
243 self
.messagelist
[uid
]['filename'] = newfilename
245 # By now, the message had better not be in tmp/ land!
246 final_dir
, final_name
= os
.path
.split(self
.messagelist
[uid
]['filename'])
247 assert final_dir
!= tmpdir
249 def deletemessage(self
, uid
):
250 if not uid
in self
.messagelist
:
252 filename
= self
.messagelist
[uid
]['filename']
256 # Can't find the file -- maybe already deleted?
257 newmsglist
= self
._scanfolder
()
258 if uid
in newmsglist
: # Nope, try new filename.
259 os
.unlink(newmsglist
[uid
]['filename'])
261 del(self
.messagelist
[uid
])