]>
code.delx.au - offlineimap/blob - 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., 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 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 getmessagetime( self
, uid
):
128 filename
= self
.messagelist
[uid
]['filename']
129 st
= os
.stat(filename
)
132 def savemessage(self
, uid
, content
, flags
, rtime
):
133 ui
= UIBase
.getglobalui()
134 ui
.debug('maildir', 'savemessage: called to write with flags %s and content %s' % \
135 (repr(flags
), repr(content
)))
137 # We cannot assign a new uid.
139 if uid
in self
.messagelist
:
140 # We already have it.
141 self
.savemessageflags(uid
, flags
)
144 # If a message has been seen, it goes into the cur
145 # directory. CR debian#152482, [complete.org #4]
146 newdir
= os
.path
.join(self
.getfullname(), 'cur')
148 newdir
= os
.path
.join(self
.getfullname(), 'new')
149 tmpdir
= os
.path
.join(self
.getfullname(), 'tmp')
154 raise IOError, "Couldn't write to file %s" % messagename
155 timeval
, timeseq
= gettimeseq()
156 messagename
= '%d_%d.%d.%s,U=%d,FMD5=%s' % \
160 socket
.gethostname(),
162 md5
.new(self
.getvisiblename()).hexdigest())
163 if os
.path
.exists(os
.path
.join(tmpdir
, messagename
)):
168 tmpmessagename
= messagename
.split(',')[0]
169 ui
.debug('maildir', 'savemessage: using temporary name %s' % tmpmessagename
)
170 file = open(os
.path
.join(tmpdir
, tmpmessagename
), "wt")
173 os
.utime(os
.path
.join(tmpdir
,tmpmessagename
), (rtime
,rtime
))
174 ui
.debug('maildir', 'savemessage: moving from %s to %s' % \
175 (tmpmessagename
, messagename
))
176 os
.link(os
.path
.join(tmpdir
, tmpmessagename
),
177 os
.path
.join(newdir
, messagename
))
178 os
.unlink(os
.path
.join(tmpdir
, tmpmessagename
))
179 self
.messagelist
[uid
] = {'uid': uid
, 'flags': [],
180 'filename': os
.path
.join(newdir
, messagename
)}
181 self
.savemessageflags(uid
, flags
)
182 ui
.debug('maildir', 'savemessage: returning uid %d' % uid
)
185 def getmessageflags(self
, uid
):
186 return self
.messagelist
[uid
]['flags']
188 def savemessageflags(self
, uid
, flags
):
189 oldfilename
= self
.messagelist
[uid
]['filename']
190 newpath
, newname
= os
.path
.split(oldfilename
)
192 # If a message has been seen, it goes into the cur
193 # directory. CR debian#152482, [complete.org #4]
194 newpath
= os
.path
.join(self
.getfullname(), 'cur')
196 newpath
= os
.path
.join(self
.getfullname(), 'new')
198 infomatch
= re
.search('(:.*)$', newname
)
199 if infomatch
: # If the info string is present..
200 infostr
= infomatch
.group(1)
201 newname
= newname
.split(':')[0] # Strip off the info string.
202 infostr
= re
.sub('2,[A-Z]*', '', infostr
)
204 infostr
+= '2,' + ''.join(flags
)
207 newfilename
= os
.path
.join(newpath
, newname
)
208 if (newfilename
!= oldfilename
):
209 os
.rename(oldfilename
, newfilename
)
210 self
.messagelist
[uid
]['flags'] = flags
211 self
.messagelist
[uid
]['filename'] = newfilename
213 def deletemessage(self
, uid
):
214 if not uid
in self
.messagelist
:
216 filename
= self
.messagelist
[uid
]['filename']
220 # Can't find the file -- maybe already deleted?
221 newmsglist
= self
._scanfolder
()
222 if uid
in newmsglist
: # Nope, try new filename.
223 os
.unlink(newmsglist
[uid
]['filename'])
225 del(self
.messagelist
[uid
])