]> code.delx.au - offlineimap/blob - head/offlineimap/folder/Maildir.py
/head: changeset 41
[offlineimap] / head / offlineimap / folder / Maildir.py
1 # Maildir folder support
2 # Copyright (C) 2002 John Goerzen
3 # <jgoerzen@complete.org>
4 #
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.
9 #
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.
14 #
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
18
19 from Base import BaseFolder
20 from offlineimap import imaputil
21 import os.path, os, re, time, socket, md5
22
23 timeseq = 0
24 lasttime = long(0)
25
26 def gettimeseq():
27 thistime = long(time.time())
28 if thistime == lasttime:
29 timeseq += 1
30 return timeseq
31 else:
32 lasttime = long(time.time())
33 timeseq = 0
34 return timeseq
35
36 class MaildirFolder(BaseFolder):
37 def __init__(self, root, name):
38 self.name = name
39 self.root = root
40 self.sep = '.'
41 self.uidfilename = os.path.join(self.getfullname(), "offlineimap.uidvalidity")
42 self.messagelist = None
43
44 def getfullname(self):
45 return os.path.join(self.getroot(), self.getname())
46
47 def getuidvalidity(self):
48 if not os.path.exists(self.uidfilename):
49 return None
50 file = open(self.uidfilename, "rt")
51 retval = long(file.readline().strip())
52 file.close()
53 return retval
54
55 def saveuidvalidity(self, newval):
56 file = open(self.uidfilename, "wt")
57 file.write("%d\n" % newval)
58 file.close()
59
60 def isuidvalidityok(self, remotefolder):
61 myval = self.getuidvalidity()
62 if myval != None:
63 return myval == remotefolder.getuidvalidity()
64 else:
65 self.saveuidvalidity(remotefolder.getuidvalidity())
66 return 1
67
68 def cachemessagelist(self):
69 """Cache the message list. Maildir flags are:
70 R (replied)
71 S (seen)
72 T (trashed)
73 D (draft)
74 F (flagged)
75 and must occur in ASCII order."""
76 self.messagelist = {}
77 files = []
78 nouidcounter = -1 # Messages without UIDs get
79 # negative UID numbers.
80 for dirannex in ['new', 'cur']:
81 fulldirname = os.path.join(self.getfullname(), dirannex)
82 files.extend([os.path.join(fulldirname, filename) for
83 filename in os.listdir(fulldirname)])
84 for file in files:
85 messagename = os.path.basename(file)
86 foldermatch = re.search(',FMD5=([0-9a-f]{32})', messagename)
87 if (not foldermatch) or \
88 md5.new(self.getvisiblename()).hexdigest() \
89 != foldermatch.group(1):
90 # If there is no folder MD5 specified, or if it mismatches,
91 # assume it is a foreign (new) message and generate a
92 # negative uid for it
93 uid = nouidcounter
94 nouidcountr -= 1
95 else: # It comes from our folder.
96 uidmatch = re.search(',U=(\d+)', messagename)
97 uid = None
98 if not uidmatch:
99 uid = nouidcounter
100 nouidcounter -= 1
101 else:
102 uid = long(uidmatch.group(1))
103 flagmatch = re.search(':.*2,([A-Z]+)', messagename)
104 flags = []
105 if flagmatch:
106 flags = [x for x in flagmatch.group(1)]
107 flags.sort()
108 self.messagelist[uid] = {'uid': uid,
109 'flags': flags,
110 'filename': file}
111
112 def getmessagelist(self):
113 return self.messagelist
114
115 def getmessage(self, uid):
116 filename = self.getmessagelist()[uid]['filename']
117 file = open(filename, 'rt')
118 retval = file.read()
119 file.close()
120 return retval
121
122 def savemessage(self, uid, content, flags):
123 if uid < 0:
124 # We cannot assign a new uid.
125 return uid
126 if uid in self.getmessagelist():
127 # We already have it.
128 self.savemessageflags(uid, flags)
129 return uid
130 newdir = os.path.join(self.getfullname(), 'new')
131 tmpdir = os.path.join(self.getfullname(), 'tmp')
132 messagename = None
133 attempts = 0
134 while 1:
135 if attempts > 15:
136 raise IOError, "Couldn't write to file %s" % messagename
137 messagename = '%d_%d.%d.%s,U=%d,FMD5=%s' % \
138 (long(time.time()),
139 gettimeseq(),
140 os.getpid(),
141 socket.gethostname(),
142 uid,
143 md5.new(self.getvisiblename()).hexdigest())
144 if os.path.exists(os.path.join(tmpdir, messagename)):
145 time.sleep(2)
146 attempts += 1
147 else:
148 break
149 file = open(os.path.join(tmpdir, messagename), "wt")
150 file.write(content)
151 file.close()
152 os.link(os.path.join(tmpdir, messagename),
153 os.path.join(newdir, messagename))
154 os.unlink(os.path.join(tmpdir, messagename))
155 self.messagelist[uid] = {'uid': uid, 'flags': [],
156 'filename': os.path.join(newdir, messagename)}
157 self.savemessageflags(uid, flags)
158 return uid
159
160 def getmessageflags(self, uid):
161 return self.getmessagelist()[uid]['flags']
162
163 def savemessageflags(self, uid, flags):
164 oldfilename = self.getmessagelist()[uid]['filename']
165 newpath, newname = os.path.split(oldfilename)
166 infostr = ':'
167 infomatch = re.search('(:.*)$', newname)
168 if infomatch: # If the info string is present..
169 infostr = infomatch.group(1)
170 newname = newname.split(':')[0] # Strip off the info string.
171 infostr = re.sub('2,[A-Z]*', '', infostr)
172 flags.sort()
173 infostr += '2,' + ''.join(flags)
174 newname += infostr
175
176 newfilename = os.path.join(newpath, newname)
177 if (newfilename != oldfilename):
178 os.rename(oldfilename, newfilename)
179 self.getmessagelist()[uid]['flags'] = flags
180 self.getmessagelist()[uid]['filename'] = newfilename
181
182 def getmessageflags(self, uid):
183 return self.getmessagelist()[uid]['flags']
184
185 def deletemessage(self, uid):
186 if not uid in self.messagelist:
187 return
188 filename = self.getmessagelist()[uid]['filename']
189 os.unlink(filename)
190 del(self.messagelist[uid])
191