blob: f7c39fa0eebb50a70dff25049b3d1efb130b1de4 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""MH interface -- purely object-oriented (well, almost)
2
3Executive summary:
4
5import mhlib
6
7mh = mhlib.MH() # use default mailbox directory and profile
8mh = mhlib.MH(mailbox) # override mailbox location (default from profile)
9mh = mhlib.MH(mailbox, profile) # override mailbox and profile
10
11mh.error(format, ...) # print error message -- can be overridden
12s = mh.getprofile(key) # profile entry (None if not set)
13path = mh.getpath() # mailbox pathname
14name = mh.getcontext() # name of current folder
15mh.setcontext(name) # set name of current folder
16
17list = mh.listfolders() # names of top-level folders
18list = mh.listallfolders() # names of all folders, including subfolders
19list = mh.listsubfolders(name) # direct subfolders of given folder
20list = mh.listallsubfolders(name) # all subfolders of given folder
21
22mh.makefolder(name) # create new folder
23mh.deletefolder(name) # delete folder -- must have no subfolders
24
25f = mh.openfolder(name) # new open folder object
26
27f.error(format, ...) # same as mh.error(format, ...)
28path = f.getfullname() # folder's full pathname
29path = f.getsequencesfilename() # full pathname of folder's sequences file
30path = f.getmessagefilename(n) # full pathname of message n in folder
31
32list = f.listmessages() # list of messages in folder (as numbers)
33n = f.getcurrent() # get current message
34f.setcurrent(n) # set current message
35list = f.parsesequence(seq) # parse msgs syntax into list of messages
36n = f.getlast() # get last message (0 if no messagse)
37f.setlast(n) # set last message (internal use only)
38
39dict = f.getsequences() # dictionary of sequences in folder {name: list}
40f.putsequences(dict) # write sequences back to folder
41
42f.createmessage(n, fp) # add message from file f as number n
43f.removemessages(list) # remove messages in list from folder
44f.refilemessages(list, tofolder) # move messages in list to other folder
45f.movemessage(n, tofolder, ton) # move one message to a given destination
46f.copymessage(n, tofolder, ton) # copy one message to a given destination
47
48m = f.openmessage(n) # new open message object (costs a file descriptor)
49m is a derived class of mimetools.Message(rfc822.Message), with:
50s = m.getheadertext() # text of message's headers
51s = m.getheadertext(pred) # text of message's headers, filtered by pred
52s = m.getbodytext() # text of message's body, decoded
53s = m.getbodytext(0) # text of message's body, not decoded
54"""
55
Guido van Rossum56013131994-06-23 12:06:02 +000056# XXX To do, functionality:
Guido van Rossum56013131994-06-23 12:06:02 +000057# - annotate messages
Guido van Rossum4fe6caa1999-02-24 16:25:17 +000058# - send messages
Guido van Rossum56013131994-06-23 12:06:02 +000059#
Guido van Rossum40b2cfb1995-01-02 18:38:23 +000060# XXX To do, organization:
Guido van Rossum56013131994-06-23 12:06:02 +000061# - move IntSet to separate file
62# - move most Message functionality to module mimetools
63
64
65# Customizable defaults
66
67MH_PROFILE = '~/.mh_profile'
68PATH = '~/Mail'
69MH_SEQUENCES = '.mh_sequences'
70FOLDER_PROTECT = 0700
71
72
73# Imported modules
74
75import os
Guido van Rossum508a0921996-05-28 22:59:37 +000076import sys
Guido van Rossum56013131994-06-23 12:06:02 +000077from stat import ST_NLINK
Guido van Rossum9694fca1997-10-22 21:00:49 +000078import re
Guido van Rossum56013131994-06-23 12:06:02 +000079import string
80import mimetools
81import multifile
Guido van Rossum40b2cfb1995-01-02 18:38:23 +000082import shutil
Guido van Rossum7cfd31e1997-04-16 02:45:08 +000083from bisect import bisect
Guido van Rossum56013131994-06-23 12:06:02 +000084
85
86# Exported constants
87
88Error = 'mhlib.Error'
89
90
Guido van Rossum56013131994-06-23 12:06:02 +000091class MH:
Guido van Rossum54f22ed2000-02-04 15:10:34 +000092 """Class representing a particular collection of folders.
93 Optional constructor arguments are the pathname for the directory
94 containing the collection, and the MH profile to use.
95 If either is omitted or empty a default is used; the default
96 directory is taken from the MH profile if it is specified there."""
Guido van Rossum56013131994-06-23 12:06:02 +000097
Guido van Rossum0c5e0491997-04-16 02:47:12 +000098 def __init__(self, path = None, profile = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000099 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000100 if not profile: profile = MH_PROFILE
101 self.profile = os.path.expanduser(profile)
102 if not path: path = self.getprofile('Path')
103 if not path: path = PATH
104 if not os.path.isabs(path) and path[0] != '~':
105 path = os.path.join('~', path)
106 path = os.path.expanduser(path)
107 if not os.path.isdir(path): raise Error, 'MH() path not found'
108 self.path = path
Guido van Rossum56013131994-06-23 12:06:02 +0000109
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000110 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000111 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000112 return 'MH(%s, %s)' % (`self.path`, `self.profile`)
Guido van Rossum56013131994-06-23 12:06:02 +0000113
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000114 def error(self, msg, *args):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000115 """Routine to print an error. May be overridden by a derived class."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000116 sys.stderr.write('MH error: %s\n' % (msg % args))
Guido van Rossum56013131994-06-23 12:06:02 +0000117
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000118 def getprofile(self, key):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000119 """Return a profile entry, None if not found."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000120 return pickline(self.profile, key)
Guido van Rossum56013131994-06-23 12:06:02 +0000121
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000122 def getpath(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000123 """Return the path (the name of the collection's directory)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000124 return self.path
Guido van Rossum56013131994-06-23 12:06:02 +0000125
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000126 def getcontext(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000127 """Return the name of the current folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000128 context = pickline(os.path.join(self.getpath(), 'context'),
129 'Current-Folder')
130 if not context: context = 'inbox'
131 return context
Guido van Rossum56013131994-06-23 12:06:02 +0000132
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000133 def setcontext(self, context):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000134 """Set the name of the current folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000135 fn = os.path.join(self.getpath(), 'context')
136 f = open(fn, "w")
137 f.write("Current-Folder: %s\n" % context)
138 f.close()
Guido van Rossum508a0921996-05-28 22:59:37 +0000139
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000140 def listfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000141 """Return the names of the top-level folders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000142 folders = []
143 path = self.getpath()
144 for name in os.listdir(path):
145 fullname = os.path.join(path, name)
146 if os.path.isdir(fullname):
147 folders.append(name)
148 folders.sort()
149 return folders
Guido van Rossum56013131994-06-23 12:06:02 +0000150
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000151 def listsubfolders(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000152 """Return the names of the subfolders in a given folder
153 (prefixed with the given folder name)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000154 fullname = os.path.join(self.path, name)
155 # Get the link count so we can avoid listing folders
156 # that have no subfolders.
157 st = os.stat(fullname)
158 nlinks = st[ST_NLINK]
159 if nlinks <= 2:
160 return []
161 subfolders = []
162 subnames = os.listdir(fullname)
163 for subname in subnames:
164 fullsubname = os.path.join(fullname, subname)
165 if os.path.isdir(fullsubname):
166 name_subname = os.path.join(name, subname)
167 subfolders.append(name_subname)
168 # Stop looking for subfolders when
169 # we've seen them all
170 nlinks = nlinks - 1
171 if nlinks <= 2:
172 break
173 subfolders.sort()
174 return subfolders
Guido van Rossum56013131994-06-23 12:06:02 +0000175
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000176 def listallfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000177 """Return the names of all folders and subfolders, recursively."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000178 return self.listallsubfolders('')
Guido van Rossum56013131994-06-23 12:06:02 +0000179
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000180 def listallsubfolders(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000181 """Return the names of subfolders in a given folder, recursively."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000182 fullname = os.path.join(self.path, name)
183 # Get the link count so we can avoid listing folders
184 # that have no subfolders.
185 st = os.stat(fullname)
186 nlinks = st[ST_NLINK]
187 if nlinks <= 2:
188 return []
189 subfolders = []
190 subnames = os.listdir(fullname)
191 for subname in subnames:
192 if subname[0] == ',' or isnumeric(subname): continue
193 fullsubname = os.path.join(fullname, subname)
194 if os.path.isdir(fullsubname):
195 name_subname = os.path.join(name, subname)
196 subfolders.append(name_subname)
197 if not os.path.islink(fullsubname):
198 subsubfolders = self.listallsubfolders(
199 name_subname)
200 subfolders = subfolders + subsubfolders
201 # Stop looking for subfolders when
202 # we've seen them all
203 nlinks = nlinks - 1
204 if nlinks <= 2:
205 break
206 subfolders.sort()
207 return subfolders
Guido van Rossum56013131994-06-23 12:06:02 +0000208
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000209 def openfolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000210 """Return a new Folder object for the named folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000211 return Folder(self, name)
Guido van Rossum56013131994-06-23 12:06:02 +0000212
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000213 def makefolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000214 """Create a new folder (or raise os.error if it cannot be created)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000215 protect = pickline(self.profile, 'Folder-Protect')
216 if protect and isnumeric(protect):
217 mode = string.atoi(protect, 8)
218 else:
219 mode = FOLDER_PROTECT
220 os.mkdir(os.path.join(self.getpath(), name), mode)
Guido van Rossum56013131994-06-23 12:06:02 +0000221
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000222 def deletefolder(self, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000223 """Delete a folder. This removes files in the folder but not
224 subdirectories. Raise os.error if deleting the folder itself fails."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000225 fullname = os.path.join(self.getpath(), name)
226 for subname in os.listdir(fullname):
227 fullsubname = os.path.join(fullname, subname)
228 try:
229 os.unlink(fullsubname)
230 except os.error:
231 self.error('%s not deleted, continuing...' %
232 fullsubname)
233 os.rmdir(fullname)
Guido van Rossum56013131994-06-23 12:06:02 +0000234
235
Guido van Rossum9694fca1997-10-22 21:00:49 +0000236numericprog = re.compile('^[1-9][0-9]*$')
Guido van Rossum56013131994-06-23 12:06:02 +0000237def isnumeric(str):
Guido van Rossum9694fca1997-10-22 21:00:49 +0000238 return numericprog.match(str) is not None
Guido van Rossum56013131994-06-23 12:06:02 +0000239
240class Folder:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000241 """Class representing a particular folder."""
Guido van Rossum56013131994-06-23 12:06:02 +0000242
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000243 def __init__(self, mh, name):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000244 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000245 self.mh = mh
246 self.name = name
247 if not os.path.isdir(self.getfullname()):
248 raise Error, 'no folder %s' % name
Guido van Rossum56013131994-06-23 12:06:02 +0000249
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000250 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000251 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000252 return 'Folder(%s, %s)' % (`self.mh`, `self.name`)
Guido van Rossum56013131994-06-23 12:06:02 +0000253
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000254 def error(self, *args):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000255 """Error message handler."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000256 apply(self.mh.error, args)
Guido van Rossum56013131994-06-23 12:06:02 +0000257
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000258 def getfullname(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000259 """Return the full pathname of the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000260 return os.path.join(self.mh.path, self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000261
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000262 def getsequencesfilename(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000263 """Return the full pathname of the folder's sequences file."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000264 return os.path.join(self.getfullname(), MH_SEQUENCES)
Guido van Rossum56013131994-06-23 12:06:02 +0000265
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000266 def getmessagefilename(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000267 """Return the full pathname of a message in the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000268 return os.path.join(self.getfullname(), str(n))
Guido van Rossum56013131994-06-23 12:06:02 +0000269
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000270 def listsubfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000271 """Return list of direct subfolders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000272 return self.mh.listsubfolders(self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000273
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000274 def listallsubfolders(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000275 """Return list of all subfolders."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000276 return self.mh.listallsubfolders(self.name)
Guido van Rossum56013131994-06-23 12:06:02 +0000277
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000278 def listmessages(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000279 """Return the list of messages currently present in the folder.
280 As a side effect, set self.last to the last message (or 0)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000281 messages = []
282 match = numericprog.match
283 append = messages.append
284 for name in os.listdir(self.getfullname()):
Guido van Rossumd9d26251998-06-23 14:43:06 +0000285 if match(name):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000286 append(name)
287 messages = map(string.atoi, messages)
288 messages.sort()
289 if messages:
290 self.last = messages[-1]
291 else:
292 self.last = 0
293 return messages
Guido van Rossum56013131994-06-23 12:06:02 +0000294
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000295 def getsequences(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000296 """Return the set of sequences for the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000297 sequences = {}
298 fullname = self.getsequencesfilename()
299 try:
300 f = open(fullname, 'r')
301 except IOError:
302 return sequences
303 while 1:
304 line = f.readline()
305 if not line: break
306 fields = string.splitfields(line, ':')
307 if len(fields) <> 2:
308 self.error('bad sequence in %s: %s' %
309 (fullname, string.strip(line)))
310 key = string.strip(fields[0])
311 value = IntSet(string.strip(fields[1]), ' ').tolist()
312 sequences[key] = value
313 return sequences
Guido van Rossum56013131994-06-23 12:06:02 +0000314
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000315 def putsequences(self, sequences):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000316 """Write the set of sequences back to the folder."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000317 fullname = self.getsequencesfilename()
318 f = None
319 for key in sequences.keys():
320 s = IntSet('', ' ')
321 s.fromlist(sequences[key])
322 if not f: f = open(fullname, 'w')
323 f.write('%s: %s\n' % (key, s.tostring()))
324 if not f:
325 try:
326 os.unlink(fullname)
327 except os.error:
328 pass
329 else:
330 f.close()
Guido van Rossum56013131994-06-23 12:06:02 +0000331
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000332 def getcurrent(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000333 """Return the current message. Raise KeyError when there is none."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000334 seqs = self.getsequences()
335 try:
336 return max(seqs['cur'])
337 except (ValueError, KeyError):
338 raise Error, "no cur message"
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000339
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000340 def setcurrent(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000341 """Set the current message."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000342 updateline(self.getsequencesfilename(), 'cur', str(n), 0)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000343
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000344 def parsesequence(self, seq):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000345 """Parse an MH sequence specification into a message list.
346 Attempt to mimic mh-sequence(5) as close as possible.
347 Also attempt to mimic observed behavior regarding which
348 conditions cause which error messages."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000349 # XXX Still not complete (see mh-format(5)).
350 # Missing are:
351 # - 'prev', 'next' as count
352 # - Sequence-Negation option
353 all = self.listmessages()
354 # Observed behavior: test for empty folder is done first
355 if not all:
356 raise Error, "no messages in %s" % self.name
357 # Common case first: all is frequently the default
358 if seq == 'all':
359 return all
360 # Test for X:Y before X-Y because 'seq:-n' matches both
361 i = string.find(seq, ':')
362 if i >= 0:
363 head, dir, tail = seq[:i], '', seq[i+1:]
364 if tail[:1] in '-+':
365 dir, tail = tail[:1], tail[1:]
366 if not isnumeric(tail):
367 raise Error, "bad message list %s" % seq
368 try:
369 count = string.atoi(tail)
370 except (ValueError, OverflowError):
371 # Can't use sys.maxint because of i+count below
372 count = len(all)
373 try:
374 anchor = self._parseindex(head, all)
375 except Error, msg:
376 seqs = self.getsequences()
377 if not seqs.has_key(head):
378 if not msg:
379 msg = "bad message list %s" % seq
380 raise Error, msg, sys.exc_info()[2]
381 msgs = seqs[head]
382 if not msgs:
383 raise Error, "sequence %s empty" % head
384 if dir == '-':
385 return msgs[-count:]
386 else:
387 return msgs[:count]
388 else:
389 if not dir:
390 if head in ('prev', 'last'):
391 dir = '-'
392 if dir == '-':
393 i = bisect(all, anchor)
394 return all[max(0, i-count):i]
395 else:
396 i = bisect(all, anchor-1)
397 return all[i:i+count]
398 # Test for X-Y next
399 i = string.find(seq, '-')
400 if i >= 0:
401 begin = self._parseindex(seq[:i], all)
402 end = self._parseindex(seq[i+1:], all)
403 i = bisect(all, begin-1)
404 j = bisect(all, end)
405 r = all[i:j]
406 if not r:
407 raise Error, "bad message list %s" % seq
408 return r
409 # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence
410 try:
411 n = self._parseindex(seq, all)
412 except Error, msg:
413 seqs = self.getsequences()
414 if not seqs.has_key(seq):
415 if not msg:
416 msg = "bad message list %s" % seq
417 raise Error, msg
418 return seqs[seq]
419 else:
420 if n not in all:
421 if isnumeric(seq):
422 raise Error, "message %d doesn't exist" % n
423 else:
424 raise Error, "no %s message" % seq
425 else:
426 return [n]
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000427
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000428 def _parseindex(self, seq, all):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000429 """Internal: parse a message number (or cur, first, etc.)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000430 if isnumeric(seq):
431 try:
432 return string.atoi(seq)
433 except (OverflowError, ValueError):
434 return sys.maxint
435 if seq in ('cur', '.'):
436 return self.getcurrent()
437 if seq == 'first':
438 return all[0]
439 if seq == 'last':
440 return all[-1]
441 if seq == 'next':
442 n = self.getcurrent()
443 i = bisect(all, n)
444 try:
445 return all[i]
446 except IndexError:
447 raise Error, "no next message"
448 if seq == 'prev':
449 n = self.getcurrent()
450 i = bisect(all, n-1)
451 if i == 0:
452 raise Error, "no prev message"
453 try:
454 return all[i-1]
455 except IndexError:
456 raise Error, "no prev message"
457 raise Error, None
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000458
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000459 def openmessage(self, n):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000460 """Open a message -- returns a Message object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000461 return Message(self, n)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000462
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000463 def removemessages(self, list):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000464 """Remove one or more messages -- may raise os.error."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000465 errors = []
466 deleted = []
467 for n in list:
468 path = self.getmessagefilename(n)
469 commapath = self.getmessagefilename(',' + str(n))
470 try:
471 os.unlink(commapath)
472 except os.error:
473 pass
474 try:
475 os.rename(path, commapath)
476 except os.error, msg:
477 errors.append(msg)
478 else:
479 deleted.append(n)
480 if deleted:
481 self.removefromallsequences(deleted)
482 if errors:
483 if len(errors) == 1:
484 raise os.error, errors[0]
485 else:
486 raise os.error, ('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000487
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000488 def refilemessages(self, list, tofolder, keepsequences=0):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000489 """Refile one or more messages -- may raise os.error.
490 'tofolder' is an open folder object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000491 errors = []
492 refiled = {}
493 for n in list:
494 ton = tofolder.getlast() + 1
495 path = self.getmessagefilename(n)
496 topath = tofolder.getmessagefilename(ton)
497 try:
498 os.rename(path, topath)
499 except os.error:
500 # Try copying
501 try:
502 shutil.copy2(path, topath)
503 os.unlink(path)
504 except (IOError, os.error), msg:
505 errors.append(msg)
506 try:
507 os.unlink(topath)
508 except os.error:
509 pass
510 continue
511 tofolder.setlast(ton)
512 refiled[n] = ton
513 if refiled:
514 if keepsequences:
515 tofolder._copysequences(self, refiled.items())
516 self.removefromallsequences(refiled.keys())
517 if errors:
518 if len(errors) == 1:
519 raise os.error, errors[0]
520 else:
521 raise os.error, ('multiple errors:', errors)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000522
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000523 def _copysequences(self, fromfolder, refileditems):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000524 """Helper for refilemessages() to copy sequences."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000525 fromsequences = fromfolder.getsequences()
526 tosequences = self.getsequences()
527 changed = 0
528 for name, seq in fromsequences.items():
529 try:
530 toseq = tosequences[name]
531 new = 0
532 except:
533 toseq = []
534 new = 1
535 for fromn, ton in refileditems:
536 if fromn in seq:
537 toseq.append(ton)
538 changed = 1
539 if new and toseq:
540 tosequences[name] = toseq
541 if changed:
542 self.putsequences(tosequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000543
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000544 def movemessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000545 """Move one message over a specific destination message,
546 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000547 path = self.getmessagefilename(n)
548 # Open it to check that it exists
549 f = open(path)
550 f.close()
551 del f
552 topath = tofolder.getmessagefilename(ton)
553 backuptopath = tofolder.getmessagefilename(',%d' % ton)
554 try:
555 os.rename(topath, backuptopath)
556 except os.error:
557 pass
558 try:
559 os.rename(path, topath)
560 except os.error:
561 # Try copying
562 ok = 0
563 try:
564 tofolder.setlast(None)
565 shutil.copy2(path, topath)
566 ok = 1
567 finally:
568 if not ok:
569 try:
570 os.unlink(topath)
571 except os.error:
572 pass
573 os.unlink(path)
574 self.removefromallsequences([n])
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000575
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000576 def copymessage(self, n, tofolder, ton):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000577 """Copy one message over a specific destination message,
578 which may or may not already exist."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000579 path = self.getmessagefilename(n)
580 # Open it to check that it exists
581 f = open(path)
582 f.close()
583 del f
584 topath = tofolder.getmessagefilename(ton)
585 backuptopath = tofolder.getmessagefilename(',%d' % ton)
586 try:
587 os.rename(topath, backuptopath)
588 except os.error:
589 pass
590 ok = 0
591 try:
592 tofolder.setlast(None)
593 shutil.copy2(path, topath)
594 ok = 1
595 finally:
596 if not ok:
597 try:
598 os.unlink(topath)
599 except os.error:
600 pass
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000601
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000602 def createmessage(self, n, txt):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000603 """Create a message, with text from the open file txt."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000604 path = self.getmessagefilename(n)
605 backuppath = self.getmessagefilename(',%d' % n)
606 try:
607 os.rename(path, backuppath)
608 except os.error:
609 pass
610 ok = 0
611 BUFSIZE = 16*1024
612 try:
613 f = open(path, "w")
614 while 1:
615 buf = txt.read(BUFSIZE)
616 if not buf:
617 break
618 f.write(buf)
619 f.close()
620 ok = 1
621 finally:
622 if not ok:
623 try:
624 os.unlink(path)
625 except os.error:
626 pass
Guido van Rossum4e5cbcf1997-07-25 14:59:10 +0000627
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000628 def removefromallsequences(self, list):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000629 """Remove one or more messages from all sequeuces (including last)
630 -- but not from 'cur'!!!"""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000631 if hasattr(self, 'last') and self.last in list:
632 del self.last
633 sequences = self.getsequences()
634 changed = 0
635 for name, seq in sequences.items():
636 if name == 'cur':
637 continue
638 for n in list:
639 if n in seq:
640 seq.remove(n)
641 changed = 1
642 if not seq:
643 del sequences[name]
644 if changed:
645 self.putsequences(sequences)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000646
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000647 def getlast(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000648 """Return the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000649 if not hasattr(self, 'last'):
650 messages = self.listmessages()
651 return self.last
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000652
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000653 def setlast(self, last):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000654 """Set the last message number."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000655 if last is None:
656 if hasattr(self, 'last'):
657 del self.last
658 else:
659 self.last = last
Guido van Rossum56013131994-06-23 12:06:02 +0000660
661class Message(mimetools.Message):
662
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000663 def __init__(self, f, n, fp = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000664 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000665 self.folder = f
666 self.number = n
667 if not fp:
668 path = f.getmessagefilename(n)
669 fp = open(path, 'r')
670 mimetools.Message.__init__(self, fp)
Guido van Rossum56013131994-06-23 12:06:02 +0000671
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000672 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000673 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000674 return 'Message(%s, %s)' % (repr(self.folder), self.number)
Guido van Rossum56013131994-06-23 12:06:02 +0000675
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000676 def getheadertext(self, pred = None):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000677 """Return the message's header text as a string. If an
678 argument is specified, it is used as a filter predicate to
679 decide which headers to return (its argument is the header
680 name converted to lower case)."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000681 if not pred:
682 return string.joinfields(self.headers, '')
683 headers = []
684 hit = 0
685 for line in self.headers:
686 if line[0] not in string.whitespace:
687 i = string.find(line, ':')
688 if i > 0:
689 hit = pred(string.lower(line[:i]))
690 if hit: headers.append(line)
691 return string.joinfields(headers, '')
Guido van Rossum56013131994-06-23 12:06:02 +0000692
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000693 def getbodytext(self, decode = 1):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000694 """Return the message's body text as string. This undoes a
695 Content-Transfer-Encoding, but does not interpret other MIME
696 features (e.g. multipart messages). To suppress decoding,
697 pass 0 as an argument."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000698 self.fp.seek(self.startofbody)
699 encoding = self.getencoding()
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000700 if not decode or encoding in ('', '7bit', '8bit', 'binary'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000701 return self.fp.read()
702 from StringIO import StringIO
703 output = StringIO()
704 mimetools.decode(self.fp, output, encoding)
705 return output.getvalue()
Guido van Rossum56013131994-06-23 12:06:02 +0000706
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000707 def getbodyparts(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000708 """Only for multipart messages: return the message's body as a
709 list of SubMessage objects. Each submessage object behaves
710 (almost) as a Message object."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000711 if self.getmaintype() != 'multipart':
712 raise Error, 'Content-Type is not multipart/*'
713 bdry = self.getparam('boundary')
714 if not bdry:
715 raise Error, 'multipart/* without boundary param'
716 self.fp.seek(self.startofbody)
717 mf = multifile.MultiFile(self.fp)
718 mf.push(bdry)
719 parts = []
720 while mf.next():
721 n = str(self.number) + '.' + `1 + len(parts)`
722 part = SubMessage(self.folder, n, mf)
723 parts.append(part)
724 mf.pop()
725 return parts
Guido van Rossum56013131994-06-23 12:06:02 +0000726
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000727 def getbody(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000728 """Return body, either a string or a list of messages."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000729 if self.getmaintype() == 'multipart':
730 return self.getbodyparts()
731 else:
732 return self.getbodytext()
Guido van Rossum56013131994-06-23 12:06:02 +0000733
734
735class SubMessage(Message):
736
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000737 def __init__(self, f, n, fp):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000738 """Constructor."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000739 Message.__init__(self, f, n, fp)
740 if self.getmaintype() == 'multipart':
741 self.body = Message.getbodyparts(self)
742 else:
743 self.body = Message.getbodytext(self)
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000744 self.bodyencoded = Message.getbodytext(self, decode=0)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000745 # XXX If this is big, should remember file pointers
Guido van Rossum56013131994-06-23 12:06:02 +0000746
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000747 def __repr__(self):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000748 """String representation."""
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000749 f, n, fp = self.folder, self.number, self.fp
750 return 'SubMessage(%s, %s, %s)' % (f, n, fp)
Guido van Rossum56013131994-06-23 12:06:02 +0000751
Guido van Rossum4fe6caa1999-02-24 16:25:17 +0000752 def getbodytext(self, decode = 1):
753 if not decode:
754 return self.bodyencoded
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000755 if type(self.body) == type(''):
756 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000757
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000758 def getbodyparts(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000759 if type(self.body) == type([]):
760 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000761
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000762 def getbody(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000763 return self.body
Guido van Rossum56013131994-06-23 12:06:02 +0000764
765
Guido van Rossum56013131994-06-23 12:06:02 +0000766class IntSet:
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000767 """Class implementing sets of integers.
768
769 This is an efficient representation for sets consisting of several
770 continuous ranges, e.g. 1-100,200-400,402-1000 is represented
771 internally as a list of three pairs: [(1,100), (200,400),
772 (402,1000)]. The internal representation is always kept normalized.
773
774 The constructor has up to three arguments:
775 - the string used to initialize the set (default ''),
776 - the separator between ranges (default ',')
777 - the separator between begin and end of a range (default '-')
778 The separators must be strings (not regexprs) and should be different.
779
780 The tostring() function yields a string that can be passed to another
781 IntSet constructor; __repr__() is a valid IntSet constructor itself.
782 """
783
784 # XXX The default begin/end separator means that negative numbers are
785 # not supported very well.
786 #
787 # XXX There are currently no operations to remove set elements.
Guido van Rossum56013131994-06-23 12:06:02 +0000788
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000789 def __init__(self, data = None, sep = ',', rng = '-'):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000790 self.pairs = []
791 self.sep = sep
792 self.rng = rng
793 if data: self.fromstring(data)
Guido van Rossum56013131994-06-23 12:06:02 +0000794
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000795 def reset(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000796 self.pairs = []
Guido van Rossum56013131994-06-23 12:06:02 +0000797
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000798 def __cmp__(self, other):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000799 return cmp(self.pairs, other.pairs)
Guido van Rossum56013131994-06-23 12:06:02 +0000800
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000801 def __hash__(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000802 return hash(self.pairs)
Guido van Rossum56013131994-06-23 12:06:02 +0000803
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000804 def __repr__(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000805 return 'IntSet(%s, %s, %s)' % (`self.tostring()`,
806 `self.sep`, `self.rng`)
Guido van Rossum56013131994-06-23 12:06:02 +0000807
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000808 def normalize(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000809 self.pairs.sort()
810 i = 1
811 while i < len(self.pairs):
812 alo, ahi = self.pairs[i-1]
813 blo, bhi = self.pairs[i]
814 if ahi >= blo-1:
815 self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]
816 else:
817 i = i+1
Guido van Rossum56013131994-06-23 12:06:02 +0000818
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000819 def tostring(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000820 s = ''
821 for lo, hi in self.pairs:
822 if lo == hi: t = `lo`
823 else: t = `lo` + self.rng + `hi`
824 if s: s = s + (self.sep + t)
825 else: s = t
826 return s
Guido van Rossum56013131994-06-23 12:06:02 +0000827
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000828 def tolist(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000829 l = []
830 for lo, hi in self.pairs:
831 m = range(lo, hi+1)
832 l = l + m
833 return l
Guido van Rossum56013131994-06-23 12:06:02 +0000834
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000835 def fromlist(self, list):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000836 for i in list:
837 self.append(i)
Guido van Rossum56013131994-06-23 12:06:02 +0000838
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000839 def clone(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000840 new = IntSet()
841 new.pairs = self.pairs[:]
842 return new
Guido van Rossum56013131994-06-23 12:06:02 +0000843
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000844 def min(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000845 return self.pairs[0][0]
Guido van Rossum56013131994-06-23 12:06:02 +0000846
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000847 def max(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000848 return self.pairs[-1][-1]
Guido van Rossum56013131994-06-23 12:06:02 +0000849
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000850 def contains(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000851 for lo, hi in self.pairs:
852 if lo <= x <= hi: return 1
853 return 0
Guido van Rossum56013131994-06-23 12:06:02 +0000854
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000855 def append(self, x):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000856 for i in range(len(self.pairs)):
857 lo, hi = self.pairs[i]
858 if x < lo: # Need to insert before
859 if x+1 == lo:
860 self.pairs[i] = (x, hi)
861 else:
862 self.pairs.insert(i, (x, x))
863 if i > 0 and x-1 == self.pairs[i-1][1]:
864 # Merge with previous
865 self.pairs[i-1:i+1] = [
866 (self.pairs[i-1][0],
867 self.pairs[i][1])
868 ]
869 return
870 if x <= hi: # Already in set
871 return
872 i = len(self.pairs) - 1
873 if i >= 0:
874 lo, hi = self.pairs[i]
875 if x-1 == hi:
876 self.pairs[i] = lo, x
877 return
878 self.pairs.append((x, x))
Guido van Rossum56013131994-06-23 12:06:02 +0000879
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000880 def addpair(self, xlo, xhi):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000881 if xlo > xhi: return
882 self.pairs.append((xlo, xhi))
883 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000884
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000885 def fromstring(self, data):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000886 import string
887 new = []
888 for part in string.splitfields(data, self.sep):
889 list = []
890 for subp in string.splitfields(part, self.rng):
891 s = string.strip(subp)
892 list.append(string.atoi(s))
893 if len(list) == 1:
894 new.append((list[0], list[0]))
895 elif len(list) == 2 and list[0] <= list[1]:
896 new.append((list[0], list[1]))
897 else:
898 raise ValueError, 'bad data passed to IntSet'
899 self.pairs = self.pairs + new
900 self.normalize()
Guido van Rossum56013131994-06-23 12:06:02 +0000901
902
903# Subroutines to read/write entries in .mh_profile and .mh_sequences
904
905def pickline(file, key, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000906 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000907 f = open(file, 'r')
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000908 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000909 return None
Guido van Rossum9694fca1997-10-22 21:00:49 +0000910 pat = re.escape(key) + ':'
911 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000912 while 1:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000913 line = f.readline()
914 if not line: break
915 if prog.match(line):
916 text = line[len(key)+1:]
917 while 1:
918 line = f.readline()
919 if not line or line[0] not in string.whitespace:
920 break
921 text = text + line
922 return string.strip(text)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000923 return None
Guido van Rossum56013131994-06-23 12:06:02 +0000924
925def updateline(file, key, value, casefold = 1):
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000926 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000927 f = open(file, 'r')
928 lines = f.readlines()
929 f.close()
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000930 except IOError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000931 lines = []
Guido van Rossum9694fca1997-10-22 21:00:49 +0000932 pat = re.escape(key) + ':(.*)\n'
933 prog = re.compile(pat, casefold and re.IGNORECASE)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000934 if value is None:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000935 newline = None
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000936 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000937 newline = '%s: %s\n' % (key, value)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000938 for i in range(len(lines)):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000939 line = lines[i]
940 if prog.match(line):
941 if newline is None:
942 del lines[i]
943 else:
944 lines[i] = newline
945 break
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000946 else:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000947 if newline is not None:
948 lines.append(newline)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000949 tempfile = file + "~"
950 f = open(tempfile, 'w')
951 for line in lines:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000952 f.write(line)
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000953 f.close()
954 os.rename(tempfile, file)
Guido van Rossum56013131994-06-23 12:06:02 +0000955
956
957# Test program
958
959def test():
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000960 global mh, f
961 os.system('rm -rf $HOME/Mail/@test')
962 mh = MH()
963 def do(s): print s; print eval(s)
964 do('mh.listfolders()')
965 do('mh.listallfolders()')
966 testfolders = ['@test', '@test/test1', '@test/test2',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000967 '@test/test1/test11', '@test/test1/test12',
968 '@test/test1/test11/test111']
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000969 for t in testfolders: do('mh.makefolder(%s)' % `t`)
970 do('mh.listsubfolders(\'@test\')')
971 do('mh.listallsubfolders(\'@test\')')
972 f = mh.openfolder('@test')
973 do('f.listsubfolders()')
974 do('f.listallsubfolders()')
975 do('f.getsequences()')
976 seqs = f.getsequences()
977 seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
978 print seqs
979 f.putsequences(seqs)
980 do('f.getsequences()')
981 testfolders.reverse()
982 for t in testfolders: do('mh.deletefolder(%s)' % `t`)
983 do('mh.getcontext()')
984 context = mh.getcontext()
985 f = mh.openfolder(context)
986 do('f.getcurrent()')
987 for seq in ['first', 'last', 'cur', '.', 'prev', 'next',
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000988 'first:3', 'last:3', 'cur:3', 'cur:-3',
989 'prev:3', 'next:3',
990 '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',
991 'all']:
992 try:
993 do('f.parsesequence(%s)' % `seq`)
994 except Error, msg:
995 print "Error:", msg
996 stuff = os.popen("pick %s 2>/dev/null" % `seq`).read()
997 list = map(string.atoi, string.split(stuff))
998 print list, "<-- pick"
Guido van Rossum0c5e0491997-04-16 02:47:12 +0000999 do('f.listmessages()')
Guido van Rossum56013131994-06-23 12:06:02 +00001000
1001
1002if __name__ == '__main__':
Guido van Rossum0c5e0491997-04-16 02:47:12 +00001003 test()