]>
code.delx.au - offlineimap/blob - offlineimap/head/offlineimap/folder/Maildir.py
1 # Maildir folder support
2 # Copyright (C) 2002 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 foldermatchre
= re
.compile(',FMD5=([0-9a-f]{32})')
26 uidmatchre
= re
.compile(',U=(\d+)')
27 flagmatchre
= re
.compile(':.*2,([A-Z]+)')
34 global lasttime
, timeseq
, timelock
37 thistime
= long(time
.time())
38 if thistime
== lasttime
:
40 return (thistime
, timeseq
)
44 return (thistime
, timeseq
)
48 class MaildirFolder(BaseFolder
):
49 def __init__(self
, root
, name
, sep
, repository
, accountname
):
53 self
.messagelist
= None
54 self
.repository
= repository
55 self
.accountname
= accountname
56 BaseFolder
.__init
__(self
)
58 def getaccountname(self
):
59 return self
.accountname
61 def getfullname(self
):
62 return os
.path
.join(self
.getroot(), self
.getname())
64 def getuidvalidity(self
):
65 """Maildirs have no notion of uidvalidity, so we just return a magic
69 def _scanfolder(self
):
70 """Cache the message list. Maildir flags are:
76 and must occur in ASCII order."""
79 nouidcounter
= -1 # Messages without UIDs get
80 # negative UID numbers.
81 for dirannex
in ['new', 'cur']:
82 fulldirname
= os
.path
.join(self
.getfullname(), dirannex
)
83 files
.extend([os
.path
.join(fulldirname
, filename
) for
84 filename
in os
.listdir(fulldirname
)])
86 messagename
= os
.path
.basename(file)
87 foldermatch
= foldermatchre
.search(messagename
)
88 if (not foldermatch
) or \
89 md5
.new(self
.getvisiblename()).hexdigest() \
90 != foldermatch
.group(1):
91 # If there is no folder MD5 specified, or if it mismatches,
92 # assume it is a foreign (new) message and generate a
96 else: # It comes from our folder.
97 uidmatch
= uidmatchre
.search(messagename
)
103 uid
= long(uidmatch
.group(1))
104 flagmatch
= flagmatchre
.search(messagename
)
107 flags
= [x
for x
in flagmatch
.group(1)]
109 retval
[uid
] = {'uid': uid
,
114 def cachemessagelist(self
):
115 self
.messagelist
= self
._scanfolder
()
117 def getmessagelist(self
):
118 return self
.messagelist
120 def getmessage(self
, uid
):
121 filename
= self
.messagelist
[uid
]['filename']
122 file = open(filename
, 'rt')
125 return retval
.replace("\r\n", "\n")
127 def savemessage(self
, uid
, content
, flags
):
128 ui
= UIBase
.getglobalui()
129 ui
.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \
130 (repr(flags
), repr(content
)))
132 # We cannot assign a new uid.
134 if uid
in self
.messagelist
:
135 # We already have it.
136 self
.savemessageflags(uid
, flags
)
139 # If a message has been seen, it goes into the cur
140 # directory. CR debian#152482, [complete.org #4]
141 newdir
= os
.path
.join(self
.getfullname(), 'cur')
143 newdir
= os
.path
.join(self
.getfullname(), 'new')
144 tmpdir
= os
.path
.join(self
.getfullname(), 'tmp')
149 raise IOError, "Couldn't write to file %s" % messagename
150 timeval
, timeseq
= gettimeseq()
151 messagename
= '%d_%d.%d.%s,U=%d,FMD5=%s' % \
155 socket
.gethostname(),
157 md5
.new(self
.getvisiblename()).hexdigest())
158 if os
.path
.exists(os
.path
.join(tmpdir
, messagename
)):
163 tmpmessagename
= messagename
.split(',')[0]
164 ui
.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename
)
165 file = open(os
.path
.join(tmpdir
, tmpmessagename
), "wt")
168 ui
.debug('maildir', 'savemessage: moving from %s to %s' % \
169 (tmpmessagename
, messagename
))
170 os
.link(os
.path
.join(tmpdir
, tmpmessagename
),
171 os
.path
.join(newdir
, messagename
))
172 os
.unlink(os
.path
.join(tmpdir
, tmpmessagename
))
173 self
.messagelist
[uid
] = {'uid': uid
, 'flags': [],
174 'filename': os
.path
.join(newdir
, messagename
)}
175 self
.savemessageflags(uid
, flags
)
176 ui
.debug('maildir', 'savemessage: returning uid %d' % uid
)
179 def getmessageflags(self
, uid
):
180 return self
.messagelist
[uid
]['flags']
182 def savemessageflags(self
, uid
, flags
):
183 oldfilename
= self
.messagelist
[uid
]['filename']
184 newpath
, newname
= os
.path
.split(oldfilename
)
186 # If a message has been seen, it goes into the cur
187 # directory. CR debian#152482, [complete.org #4]
188 newpath
= os
.path
.join(self
.getfullname(), 'cur')
190 newpath
= os
.path
.join(self
.getfullname(), 'new')
192 infomatch
= re
.search('(:.*)$', newname
)
193 if infomatch
: # If the info string is present..
194 infostr
= infomatch
.group(1)
195 newname
= newname
.split(':')[0] # Strip off the info string.
196 infostr
= re
.sub('2,[A-Z]*', '', infostr
)
198 infostr
+= '2,' + ''.join(flags
)
201 newfilename
= os
.path
.join(newpath
, newname
)
202 if (newfilename
!= oldfilename
):
203 os
.rename(oldfilename
, newfilename
)
204 self
.messagelist
[uid
]['flags'] = flags
205 self
.messagelist
[uid
]['filename'] = newfilename
207 def deletemessage(self
, uid
):
208 if not uid
in self
.messagelist
:
210 filename
= self
.messagelist
[uid
]['filename']
214 # Can't find the file -- maybe already deleted?
215 newmsglist
= self
._scanfolder
()
216 if uid
in newmsglist
: # Nope, try new filename.
217 os
.unlink(newmsglist
[uid
]['filename'])
219 del(self
.messagelist
[uid
])