mhlib.py revision d9d2625dbd284f91df2beab22f385e0f0bf60cab
1560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# MH interface -- purely object-oriented (well, almost)
2560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
3560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Executive summary:
4560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
5560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# import mhlib
6560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
7560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# mh = mhlib.MH()         # use default mailbox directory and profile
8560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# mh = mhlib.MH(mailbox)  # override mailbox location (default from profile)
9560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# mh = mhlib.MH(mailbox, profile) # override mailbox and profile
10560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
11560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# mh.error(format, ...)   # print error message -- can be overridden
12560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# s = mh.getprofile(key)  # profile entry (None if not set)
13560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# path = mh.getpath()     # mailbox pathname
14560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# name = mh.getcontext()  # name of current folder
15508a092e2e44332c2f1ac381bdb956e3b94e0cc2Guido van Rossum# mh.setcontext(name)     # set name of current folder
16560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
17560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# list = mh.listfolders() # names of top-level folders
18560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# list = mh.listallfolders() # names of all folders, including subfolders
19560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# list = mh.listsubfolders(name) # direct subfolders of given folder
20560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# list = mh.listallsubfolders(name) # all subfolders of given folder
21560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
22560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# mh.makefolder(name)     # create new folder
23560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# mh.deletefolder(name)   # delete folder -- must have no subfolders
24560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
25560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# f = mh.openfolder(name) # new open folder object
26560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
27560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# f.error(format, ...)    # same as mh.error(format, ...)
28560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# path = f.getfullname()  # folder's full pathname
29560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# path = f.getsequencesfilename() # full pathname of folder's sequences file
30560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# path = f.getmessagefilename(n)  # full pathname of message n in folder
31560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
32560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# list = f.listmessages() # list of messages in folder (as numbers)
33560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# n = f.getcurrent()      # get current message
34560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# f.setcurrent(n)         # set current message
35508a092e2e44332c2f1ac381bdb956e3b94e0cc2Guido van Rossum# list = f.parsesequence(seq)     # parse msgs syntax into list of messages
3640b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossum# n = f.getlast()         # get last message (0 if no messagse)
3740b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossum# f.setlast(n)            # set last message (internal use only)
38560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
39560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# dict = f.getsequences() # dictionary of sequences in folder {name: list}
40560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# f.putsequences(dict)    # write sequences back to folder
41560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
4240b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossum# f.removemessages(list)  # remove messages in list from folder
4340b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossum# f.refilemessages(list, tofolder) # move messages in list to other folder
4440b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossum# f.movemessage(n, tofolder, ton)  # move one message to a given destination
4540b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossum# f.copymessage(n, tofolder, ton)  # copy one message to a given destination
4640b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossum#
47560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# m = f.openmessage(n)    # new open message object (costs a file descriptor)
48853474194fa799ab96a135f900332eaca52a07e7Guido van Rossum# m is a derived class of mimetools.Message(rfc822.Message), with:
49560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# s = m.getheadertext()   # text of message's headers
50560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# s = m.getheadertext(pred) # text of message's headers, filtered by pred
51560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# s = m.getbodytext()     # text of message's body, decoded
52560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# s = m.getbodytext(0)    # text of message's body, not decoded
53560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
54560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# XXX To do, functionality:
55560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - annotate messages
56560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - create, send messages
57560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
5840b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossum# XXX To do, organization:
59560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - move IntSet to separate file
60560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - move most Message functionality to module mimetools
61560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
62560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
63560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Customizable defaults
64560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
65560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van RossumMH_PROFILE = '~/.mh_profile'
66560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van RossumPATH = '~/Mail'
67560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van RossumMH_SEQUENCES = '.mh_sequences'
68560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van RossumFOLDER_PROTECT = 0700
69560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
70560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
71560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Imported modules
72560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
73560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumimport os
74508a092e2e44332c2f1ac381bdb956e3b94e0cc2Guido van Rossumimport sys
75560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumfrom stat import ST_NLINK
769694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossumimport re
77560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumimport string
78560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumimport mimetools
79560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumimport multifile
8040b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossumimport shutil
817cfd31ee8a1447e177f129c4e3f1bfd937528043Guido van Rossumfrom bisect import bisect
82560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
83560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
84560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Exported constants
85560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
86560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van RossumError = 'mhlib.Error'
87560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
88560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
89560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Class representing a particular collection of folders.
90560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Optional constructor arguments are the pathname for the directory
91560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# containing the collection, and the MH profile to use.
92560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# If either is omitted or empty a default is used; the default
93560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# directory is taken from the MH profile if it is specified there.
94560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
95560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass MH:
96560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
970c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Constructor
980c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, path = None, profile = None):
9945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not profile: profile = MH_PROFILE
10045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.profile = os.path.expanduser(profile)
10145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not path: path = self.getprofile('Path')
10245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not path: path = PATH
10345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not os.path.isabs(path) and path[0] != '~':
10445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            path = os.path.join('~', path)
10545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = os.path.expanduser(path)
10645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not os.path.isdir(path): raise Error, 'MH() path not found'
10745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.path = path
1080c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1090c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # String representation
1100c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
11145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'MH(%s, %s)' % (`self.path`, `self.profile`)
1120c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1130c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Routine to print an error.  May be overridden by a derived class
1140c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def error(self, msg, *args):
11545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        sys.stderr.write('MH error: %s\n' % (msg % args))
1160c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1170c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return a profile entry, None if not found
1180c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getprofile(self, key):
11945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return pickline(self.profile, key)
1200c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1210c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the path (the name of the collection's directory)
1220c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getpath(self):
12345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.path
1240c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1250c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the name of the current folder
1260c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getcontext(self):
12745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        context = pickline(os.path.join(self.getpath(), 'context'),
12845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                  'Current-Folder')
12945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not context: context = 'inbox'
13045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return context
1310c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1320c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Set the name of the current folder
1330c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def setcontext(self, context):
13445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fn = os.path.join(self.getpath(), 'context')
13545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(fn, "w")
13645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.write("Current-Folder: %s\n" % context)
13745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.close()
1380c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1390c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the names of the top-level folders
1400c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listfolders(self):
14145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        folders = []
14245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = self.getpath()
14345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for name in os.listdir(path):
14445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fullname = os.path.join(path, name)
14545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if os.path.isdir(fullname):
14645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                folders.append(name)
14745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        folders.sort()
14845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return folders
1490c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1500c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the names of the subfolders in a given folder
1510c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # (prefixed with the given folder name)
1520c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listsubfolders(self, name):
15345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fullname = os.path.join(self.path, name)
15445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Get the link count so we can avoid listing folders
15545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # that have no subfolders.
15645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        st = os.stat(fullname)
15745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        nlinks = st[ST_NLINK]
15845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if nlinks <= 2:
15945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return []
16045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        subfolders = []
16145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        subnames = os.listdir(fullname)
16245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for subname in subnames:
16345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fullsubname = os.path.join(fullname, subname)
16445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if os.path.isdir(fullsubname):
16545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                name_subname = os.path.join(name, subname)
16645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                subfolders.append(name_subname)
16745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # Stop looking for subfolders when
16845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # we've seen them all
16945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                nlinks = nlinks - 1
17045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if nlinks <= 2:
17145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    break
17245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        subfolders.sort()
17345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return subfolders
1740c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1750c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the names of all folders, including subfolders, recursively
1760c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listallfolders(self):
17745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.listallsubfolders('')
1780c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1790c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the names of subfolders in a given folder, recursively
1800c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listallsubfolders(self, name):
18145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fullname = os.path.join(self.path, name)
18245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Get the link count so we can avoid listing folders
18345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # that have no subfolders.
18445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        st = os.stat(fullname)
18545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        nlinks = st[ST_NLINK]
18645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if nlinks <= 2:
18745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return []
18845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        subfolders = []
18945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        subnames = os.listdir(fullname)
19045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for subname in subnames:
19145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if subname[0] == ',' or isnumeric(subname): continue
19245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fullsubname = os.path.join(fullname, subname)
19345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if os.path.isdir(fullsubname):
19445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                name_subname = os.path.join(name, subname)
19545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                subfolders.append(name_subname)
19645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not os.path.islink(fullsubname):
19745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    subsubfolders = self.listallsubfolders(
19845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                              name_subname)
19945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    subfolders = subfolders + subsubfolders
20045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # Stop looking for subfolders when
20145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # we've seen them all
20245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                nlinks = nlinks - 1
20345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if nlinks <= 2:
20445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    break
20545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        subfolders.sort()
20645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return subfolders
2070c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2080c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return a new Folder object for the named folder
2090c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def openfolder(self, name):
21045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return Folder(self, name)
2110c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2120c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Create a new folder.  This raises os.error if the folder
2130c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # cannot be created
2140c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def makefolder(self, name):
21545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        protect = pickline(self.profile, 'Folder-Protect')
21645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if protect and isnumeric(protect):
21745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            mode = string.atoi(protect, 8)
21845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
21945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            mode = FOLDER_PROTECT
22045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        os.mkdir(os.path.join(self.getpath(), name), mode)
2210c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2220c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Delete a folder.  This removes files in the folder but not
2230c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # subdirectories.  If deleting the folder itself fails it
2240c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # raises os.error
2250c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def deletefolder(self, name):
22645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fullname = os.path.join(self.getpath(), name)
22745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for subname in os.listdir(fullname):
22845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fullsubname = os.path.join(fullname, subname)
22945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
23045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.unlink(fullsubname)
23145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error:
23245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                self.error('%s not deleted, continuing...' %
23345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                          fullsubname)
23445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        os.rmdir(fullname)
235560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
236560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
237560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Class representing a particular folder
238560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
2399694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossumnumericprog = re.compile('^[1-9][0-9]*$')
240560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumdef isnumeric(str):
2419694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    return numericprog.match(str) is not None
242560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
243560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass Folder:
244560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
2450c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Constructor
2460c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, mh, name):
24745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.mh = mh
24845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.name = name
24945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not os.path.isdir(self.getfullname()):
25045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, 'no folder %s' % name
2510c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2520c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # String representation
2530c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
25445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'Folder(%s, %s)' % (`self.mh`, `self.name`)
2550c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2560c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Error message handler
2570c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def error(self, *args):
25845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        apply(self.mh.error, args)
2590c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2600c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the full pathname of the folder
2610c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getfullname(self):
26245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return os.path.join(self.mh.path, self.name)
2630c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2640c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the full pathname of the folder's sequences file
2650c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getsequencesfilename(self):
26645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return os.path.join(self.getfullname(), MH_SEQUENCES)
2670c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2680c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the full pathname of a message in the folder
2690c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getmessagefilename(self, n):
27045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return os.path.join(self.getfullname(), str(n))
2710c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2720c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return list of direct subfolders
2730c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listsubfolders(self):
27445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.mh.listsubfolders(self.name)
2750c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2760c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return list of all subfolders
2770c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listallsubfolders(self):
27845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.mh.listallsubfolders(self.name)
2790c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2800c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the list of messages currently present in the folder.
2810c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # As a side effect, set self.last to the last message (or 0)
2820c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listmessages(self):
28345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        messages = []
28445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        match = numericprog.match
28545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        append = messages.append
28645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for name in os.listdir(self.getfullname()):
287d9d2625dbd284f91df2beab22f385e0f0bf60cabGuido van Rossum            if match(name):
28845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                append(name)
28945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        messages = map(string.atoi, messages)
29045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        messages.sort()
29145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if messages:
29245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.last = messages[-1]
29345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
29445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.last = 0
29545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return messages
296560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
2970c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the set of sequences for the folder
2980c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getsequences(self):
29945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        sequences = {}
30045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fullname = self.getsequencesfilename()
30145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
30245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f = open(fullname, 'r')
30345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except IOError:
30445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return sequences
30545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        while 1:
30645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            line = f.readline()
30745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not line: break
30845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fields = string.splitfields(line, ':')
30945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if len(fields) <> 2:
31045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                self.error('bad sequence in %s: %s' %
31145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                          (fullname, string.strip(line)))
31245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            key = string.strip(fields[0])
31345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            value = IntSet(string.strip(fields[1]), ' ').tolist()
31445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            sequences[key] = value
31545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return sequences
3160c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
3170c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Write the set of sequences back to the folder
3180c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def putsequences(self, sequences):
31945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fullname = self.getsequencesfilename()
32045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = None
32145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for key in sequences.keys():
32245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            s = IntSet('', ' ')
32345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            s.fromlist(sequences[key])
32445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not f: f = open(fullname, 'w')
32545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f.write('%s: %s\n' % (key, s.tostring()))
32645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not f:
32745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
32845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.unlink(fullname)
32945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error:
33045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                pass
33145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
33245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f.close()
333560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
3340c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the current message.  Raise KeyError when there is none
3350c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getcurrent(self):
33645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        seqs = self.getsequences()
33745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
33845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return max(seqs['cur'])
33945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except (ValueError, KeyError):
34045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, "no cur message"
3410c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
3420c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Set the current message
3430c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def setcurrent(self, n):
34445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        updateline(self.getsequencesfilename(), 'cur', str(n), 0)
3450c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
3460c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Parse an MH sequence specification into a message list.
3470c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Attempt to mimic mh-sequence(5) as close as possible.
3480c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Also attempt to mimic observed behavior regarding which
3490c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # conditions cause which error messages
3500c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def parsesequence(self, seq):
35145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # XXX Still not complete (see mh-format(5)).
35245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Missing are:
35345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # - 'prev', 'next' as count
35445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # - Sequence-Negation option
35545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        all = self.listmessages()
35645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Observed behavior: test for empty folder is done first
35745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not all:
35845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, "no messages in %s" % self.name
35945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Common case first: all is frequently the default
36045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'all':
36145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return all
36245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Test for X:Y before X-Y because 'seq:-n' matches both
36345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        i = string.find(seq, ':')
36445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if i >= 0:
36545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            head, dir, tail = seq[:i], '', seq[i+1:]
36645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if tail[:1] in '-+':
36745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                dir, tail = tail[:1], tail[1:]
36845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not isnumeric(tail):
36945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "bad message list %s" % seq
37045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
37145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                count = string.atoi(tail)
37245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except (ValueError, OverflowError):
37345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # Can't use sys.maxint because of i+count below
37445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                count = len(all)
37545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
37645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                anchor = self._parseindex(head, all)
37745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except Error, msg:
37845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                seqs = self.getsequences()
37945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not seqs.has_key(head):
38045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    if not msg:
38145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        msg = "bad message list %s" % seq
38245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    raise Error, msg, sys.exc_info()[2]
38345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                msgs = seqs[head]
38445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not msgs:
38545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    raise Error, "sequence %s empty" % head
38645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if dir == '-':
38745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    return msgs[-count:]
38845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                else:
38945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    return msgs[:count]
39045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
39145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not dir:
39245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    if head in ('prev', 'last'):
39345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        dir = '-'
39445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if dir == '-':
39545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    i = bisect(all, anchor)
39645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    return all[max(0, i-count):i]
39745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                else:
39845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    i = bisect(all, anchor-1)
39945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    return all[i:i+count]
40045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Test for X-Y next
40145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        i = string.find(seq, '-')
40245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if i >= 0:
40345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            begin = self._parseindex(seq[:i], all)
40445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            end = self._parseindex(seq[i+1:], all)
40545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            i = bisect(all, begin-1)
40645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            j = bisect(all, end)
40745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            r = all[i:j]
40845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not r:
40945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "bad message list %s" % seq
41045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return r
41145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence
41245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
41345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            n = self._parseindex(seq, all)
41445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except Error, msg:
41545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            seqs = self.getsequences()
41645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not seqs.has_key(seq):
41745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not msg:
41845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    msg = "bad message list %s" % seq
41945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, msg
42045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return seqs[seq]
42145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
42245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if n not in all:
42345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if isnumeric(seq):
42445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    raise Error, "message %d doesn't exist" % n
42545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                else:
42645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    raise Error, "no %s message" % seq
42745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
42845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return [n]
4290c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
4300c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Internal: parse a message number (or cur, first, etc.)
4310c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def _parseindex(self, seq, all):
43245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if isnumeric(seq):
43345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
43445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return string.atoi(seq)
43545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except (OverflowError, ValueError):
43645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return sys.maxint
43745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq in ('cur', '.'):
43845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.getcurrent()
43945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'first':
44045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return all[0]
44145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'last':
44245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return all[-1]
44345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'next':
44445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            n = self.getcurrent()
44545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            i = bisect(all, n)
44645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
44745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return all[i]
44845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except IndexError:
44945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "no next message"
45045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'prev':
45145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            n = self.getcurrent()
45245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            i = bisect(all, n-1)
45345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if i == 0:
45445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "no prev message"
45545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
45645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return all[i-1]
45745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except IndexError:
45845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "no prev message"
45945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        raise Error, None
4600c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
4610c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Open a message -- returns a Message object
4620c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def openmessage(self, n):
46345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return Message(self, n)
4640c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
4650c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Remove one or more messages -- may raise os.error
4660c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def removemessages(self, list):
46745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        errors = []
46845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        deleted = []
46945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for n in list:
47045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            path = self.getmessagefilename(n)
47145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            commapath = self.getmessagefilename(',' + str(n))
47245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
47345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.unlink(commapath)
47445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error:
47545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                pass
47645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
47745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.rename(path, commapath)
47845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error, msg:
47945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                errors.append(msg)
48045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
48145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                deleted.append(n)
48245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if deleted:
48345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.removefromallsequences(deleted)
48445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if errors:
48545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if len(errors) == 1:
48645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise os.error, errors[0]
48745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
48845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise os.error, ('multiple errors:', errors)
4890c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
4900c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Refile one or more messages -- may raise os.error.
4910c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # 'tofolder' is an open folder object
4920c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def refilemessages(self, list, tofolder, keepsequences=0):
49345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        errors = []
49445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        refiled = {}
49545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for n in list:
49645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            ton = tofolder.getlast() + 1
49745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            path = self.getmessagefilename(n)
49845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            topath = tofolder.getmessagefilename(ton)
49945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
50045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.rename(path, topath)
50145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error:
50245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # Try copying
50345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                try:
50445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    shutil.copy2(path, topath)
50545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    os.unlink(path)
50645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                except (IOError, os.error), msg:
50745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    errors.append(msg)
50845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    try:
50945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        os.unlink(topath)
51045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    except os.error:
51145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        pass
51245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    continue
51345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            tofolder.setlast(ton)
51445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            refiled[n] = ton
51545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if refiled:
51645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if keepsequences:
51745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                tofolder._copysequences(self, refiled.items())
51845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.removefromallsequences(refiled.keys())
51945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if errors:
52045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if len(errors) == 1:
52145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise os.error, errors[0]
52245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
52345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise os.error, ('multiple errors:', errors)
5240c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
5250c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Helper for refilemessages() to copy sequences
5260c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def _copysequences(self, fromfolder, refileditems):
52745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fromsequences = fromfolder.getsequences()
52845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        tosequences = self.getsequences()
52945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        changed = 0
53045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for name, seq in fromsequences.items():
53145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
53245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                toseq = tosequences[name]
53345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                new = 0
53445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except:
53545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                toseq = []
53645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                new = 1
53745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            for fromn, ton in refileditems:
53845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if fromn in seq:
53945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    toseq.append(ton)
54045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    changed = 1
54145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if new and toseq:
54245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                tosequences[name] = toseq
54345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if changed:
54445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.putsequences(tosequences)
5450c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
5460c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Move one message over a specific destination message,
5470c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # which may or may not already exist.
5480c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def movemessage(self, n, tofolder, ton):
54945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = self.getmessagefilename(n)
55045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Open it to check that it exists
55145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(path)
55245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.close()
55345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        del f
55445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        topath = tofolder.getmessagefilename(ton)
55545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        backuptopath = tofolder.getmessagefilename(',%d' % ton)
55645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
55745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.rename(topath, backuptopath)
55845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except os.error:
55945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            pass
56045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
56145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.rename(path, topath)
56245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except os.error:
56345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            # Try copying
56445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            ok = 0
56545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
56645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                tofolder.setlast(None)
56745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                shutil.copy2(path, topath)
56845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                ok = 1
56945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            finally:
57045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not ok:
57145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    try:
57245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        os.unlink(topath)
57345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    except os.error:
57445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        pass
57545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.unlink(path)
57645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.removefromallsequences([n])
5770c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
5780c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Copy one message over a specific destination message,
5790c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # which may or may not already exist.
5800c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def copymessage(self, n, tofolder, ton):
58145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = self.getmessagefilename(n)
58245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Open it to check that it exists
58345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(path)
58445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.close()
58545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        del f
58645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        topath = tofolder.getmessagefilename(ton)
58745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        backuptopath = tofolder.getmessagefilename(',%d' % ton)
58845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
58945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.rename(topath, backuptopath)
59045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except os.error:
59145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            pass
59245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        ok = 0
59345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
59445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            tofolder.setlast(None)
59545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            shutil.copy2(path, topath)
59645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            ok = 1
59745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        finally:
59845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not ok:
59945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                try:
60045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    os.unlink(topath)
60145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                except os.error:
60245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    pass
6030c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6044e5cbcf5afa411d654983cd4cf8c99739b935b26Guido van Rossum    # Create a message, with text from the open file txt.
6054e5cbcf5afa411d654983cd4cf8c99739b935b26Guido van Rossum    def createmessage(self, n, txt):
60645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = self.getmessagefilename(n)
60745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        backuppath = self.getmessagefilename(',%d' % n)
60845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
60945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.rename(path, backuppath)
61045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except os.error:
61145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            pass
61245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        ok = 0
61345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        BUFSIZE = 16*1024
61445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
61545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f = open(path, "w")
61645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            while 1:
61745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                buf = txt.read(BUFSIZE)
61845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not buf:
61945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    break
62045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                f.write(buf)
62145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f.close()
62245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            ok = 1
62345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        finally:
62445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not ok:
62545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                try:
62645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    os.unlink(path)
62745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                except os.error:
62845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    pass
6294e5cbcf5afa411d654983cd4cf8c99739b935b26Guido van Rossum
6300c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Remove one or more messages from all sequeuces (including last)
6310c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # -- but not from 'cur'!!!
6320c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def removefromallsequences(self, list):
63345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if hasattr(self, 'last') and self.last in list:
63445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            del self.last
63545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        sequences = self.getsequences()
63645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        changed = 0
63745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for name, seq in sequences.items():
63845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if name == 'cur':
63945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                continue
64045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            for n in list:
64145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if n in seq:
64245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    seq.remove(n)
64345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    changed = 1
64445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    if not seq:
64545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        del sequences[name]
64645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if changed:
64745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.putsequences(sequences)
6480c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6490c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the last message number
6500c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getlast(self):
65145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not hasattr(self, 'last'):
65245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            messages = self.listmessages()
65345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.last
6540c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6550c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Set the last message number
6560c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def setlast(self, last):
65745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if last is None:
65845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if hasattr(self, 'last'):
65945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                del self.last
66045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
66145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.last = last
662560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
663560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass Message(mimetools.Message):
664560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
6650c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Constructor
6660c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, f, n, fp = None):
66745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.folder = f
66845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.number = n
66945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not fp:
67045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            path = f.getmessagefilename(n)
67145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fp = open(path, 'r')
67245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mimetools.Message.__init__(self, fp)
6730c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6740c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # String representation
6750c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
67645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'Message(%s, %s)' % (repr(self.folder), self.number)
6770c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6780c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the message's header text as a string.  If an
6790c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # argument is specified, it is used as a filter predicate to
6800c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # decide which headers to return (its argument is the header
6810c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # name converted to lower case).
6820c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getheadertext(self, pred = None):
68345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not pred:
68445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return string.joinfields(self.headers, '')
68545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        headers = []
68645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        hit = 0
68745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for line in self.headers:
68845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if line[0] not in string.whitespace:
68945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                i = string.find(line, ':')
69045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if i > 0:
69145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    hit = pred(string.lower(line[:i]))
69245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if hit: headers.append(line)
69345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return string.joinfields(headers, '')
6940c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6950c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return the message's body text as string.  This undoes a
6960c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Content-Transfer-Encoding, but does not interpret other MIME
6970c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # features (e.g. multipart messages).  To suppress to
6980c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # decoding, pass a 0 as argument
6990c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbodytext(self, decode = 1):
70045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.fp.seek(self.startofbody)
70145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        encoding = self.getencoding()
70245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not decode or encoding in ('7bit', '8bit', 'binary'):
70345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.fp.read()
70445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        from StringIO import StringIO
70545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        output = StringIO()
70645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mimetools.decode(self.fp, output, encoding)
70745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return output.getvalue()
7080c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
7090c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Only for multipart messages: return the message's body as a
7100c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # list of SubMessage objects.  Each submessage object behaves
7110c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # (almost) as a Message object.
7120c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbodyparts(self):
71345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if self.getmaintype() != 'multipart':
71445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, 'Content-Type is not multipart/*'
71545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        bdry = self.getparam('boundary')
71645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not bdry:
71745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, 'multipart/* without boundary param'
71845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.fp.seek(self.startofbody)
71945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mf = multifile.MultiFile(self.fp)
72045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mf.push(bdry)
72145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        parts = []
72245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        while mf.next():
72345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            n = str(self.number) + '.' + `1 + len(parts)`
72445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            part = SubMessage(self.folder, n, mf)
72545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            parts.append(part)
72645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mf.pop()
72745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return parts
7280c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
7290c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Return body, either a string or a list of messages
7300c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbody(self):
73145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if self.getmaintype() == 'multipart':
73245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.getbodyparts()
73345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
73445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.getbodytext()
735560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
736560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
737560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass SubMessage(Message):
738560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7390c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # Constructor
7400c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, f, n, fp):
74145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        Message.__init__(self, f, n, fp)
74245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if self.getmaintype() == 'multipart':
74345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.body = Message.getbodyparts(self)
74445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
74545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.body = Message.getbodytext(self)
74645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            # XXX If this is big, should remember file pointers
747560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7480c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    # String representation
7490c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
75045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f, n, fp = self.folder, self.number, self.fp
75145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'SubMessage(%s, %s, %s)' % (f, n, fp)
752560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7530c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbodytext(self):
75445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if type(self.body) == type(''):
75545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.body
756560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7570c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbodyparts(self):
75845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if type(self.body) == type([]):
75945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.body
760560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7610c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbody(self):
76245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.body
763560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
764560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
765560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Class implementing sets of integers.
766560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
767560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# This is an efficient representation for sets consisting of several
768560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# continuous ranges, e.g. 1-100,200-400,402-1000 is represented
769560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# internally as a list of three pairs: [(1,100), (200,400),
770560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# (402,1000)].  The internal representation is always kept normalized.
771560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
772560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# The constructor has up to three arguments:
773560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - the string used to initialize the set (default ''),
774560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - the separator between ranges (default ',')
775560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - the separator between begin and end of a range (default '-')
77600f9fea288baed5a37e3866b492185b5045d30d4Guido van Rossum# The separators must be strings (not regexprs) and should be different.
777560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
778560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# The tostring() function yields a string that can be passed to another
779560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# IntSet constructor; __repr__() is a valid IntSet constructor itself.
780560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
781560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# XXX The default begin/end separator means that negative numbers are
782560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#     not supported very well.
783560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
784560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# XXX There are currently no operations to remove set elements.
785560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
786560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass IntSet:
787560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7880c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, data = None, sep = ',', rng = '-'):
78945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs = []
79045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.sep = sep
79145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.rng = rng
79245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if data: self.fromstring(data)
7930c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
7940c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def reset(self):
79545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs = []
7960c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
7970c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __cmp__(self, other):
79845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return cmp(self.pairs, other.pairs)
7990c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8000c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __hash__(self):
80145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return hash(self.pairs)
8020c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8030c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
80445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'IntSet(%s, %s, %s)' % (`self.tostring()`,
80545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                  `self.sep`, `self.rng`)
8060c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8070c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def normalize(self):
80845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs.sort()
80945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        i = 1
81045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        while i < len(self.pairs):
81145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            alo, ahi = self.pairs[i-1]
81245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            blo, bhi = self.pairs[i]
81345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if ahi >= blo-1:
81445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]
81545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
81645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                i = i+1
8170c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8180c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def tostring(self):
81945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        s = ''
82045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for lo, hi in self.pairs:
82145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if lo == hi: t = `lo`
82245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else: t = `lo` + self.rng + `hi`
82345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if s: s = s + (self.sep + t)
82445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else: s = t
82545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return s
8260c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8270c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def tolist(self):
82845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        l = []
82945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for lo, hi in self.pairs:
83045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            m = range(lo, hi+1)
83145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            l = l + m
83245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return l
8330c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8340c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def fromlist(self, list):
83545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for i in list:
83645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.append(i)
8370c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8380c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def clone(self):
83945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        new = IntSet()
84045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        new.pairs = self.pairs[:]
84145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return new
8420c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8430c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def min(self):
84445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.pairs[0][0]
8450c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8460c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def max(self):
84745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.pairs[-1][-1]
8480c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8490c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def contains(self, x):
85045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for lo, hi in self.pairs:
85145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if lo <= x <= hi: return 1
85245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 0
8530c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8540c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def append(self, x):
85545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for i in range(len(self.pairs)):
85645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            lo, hi = self.pairs[i]
85745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if x < lo: # Need to insert before
85845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if x+1 == lo:
85945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    self.pairs[i] = (x, hi)
86045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                else:
86145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    self.pairs.insert(i, (x, x))
86245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if i > 0 and x-1 == self.pairs[i-1][1]:
86345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    # Merge with previous
86445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    self.pairs[i-1:i+1] = [
86545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                            (self.pairs[i-1][0],
86645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                             self.pairs[i][1])
86745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                          ]
86845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return
86945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if x <= hi: # Already in set
87045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return
87145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        i = len(self.pairs) - 1
87245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if i >= 0:
87345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            lo, hi = self.pairs[i]
87445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if x-1 == hi:
87545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                self.pairs[i] = lo, x
87645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return
87745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs.append((x, x))
8780c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8790c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def addpair(self, xlo, xhi):
88045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if xlo > xhi: return
88145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs.append((xlo, xhi))
88245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.normalize()
8830c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8840c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def fromstring(self, data):
88545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        import string
88645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        new = []
88745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for part in string.splitfields(data, self.sep):
88845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            list = []
88945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            for subp in string.splitfields(part, self.rng):
89045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                s = string.strip(subp)
89145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                list.append(string.atoi(s))
89245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if len(list) == 1:
89345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                new.append((list[0], list[0]))
89445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            elif len(list) == 2 and list[0] <= list[1]:
89545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                new.append((list[0], list[1]))
89645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
89745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise ValueError, 'bad data passed to IntSet'
89845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs = self.pairs + new
89945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.normalize()
900560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
901560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
902560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Subroutines to read/write entries in .mh_profile and .mh_sequences
903560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
904560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumdef pickline(file, key, casefold = 1):
9050c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    try:
90645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(file, 'r')
9070c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    except IOError:
90845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return None
9099694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    pat = re.escape(key) + ':'
9109694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    prog = re.compile(pat, casefold and re.IGNORECASE)
9110c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    while 1:
91245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        line = f.readline()
91345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not line: break
91445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if prog.match(line):
91545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            text = line[len(key)+1:]
91645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            while 1:
91745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                line = f.readline()
91845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not line or line[0] not in string.whitespace:
91945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    break
92045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                text = text + line
92145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return string.strip(text)
9220c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    return None
923560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
924560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumdef updateline(file, key, value, casefold = 1):
9250c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    try:
92645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(file, 'r')
92745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        lines = f.readlines()
92845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.close()
9290c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    except IOError:
93045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        lines = []
9319694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    pat = re.escape(key) + ':(.*)\n'
9329694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    prog = re.compile(pat, casefold and re.IGNORECASE)
9330c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    if value is None:
93445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        newline = None
9350c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    else:
93645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        newline = '%s: %s\n' % (key, value)
9370c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for i in range(len(lines)):
93845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        line = lines[i]
93945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if prog.match(line):
94045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if newline is None:
94145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                del lines[i]
94245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
94345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                lines[i] = newline
94445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            break
9450c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    else:
94645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if newline is not None:
94745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            lines.append(newline)
9480c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    tempfile = file + "~"
9490c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f = open(tempfile, 'w')
9500c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for line in lines:
95145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.write(line)
9520c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f.close()
9530c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    os.rename(tempfile, file)
954560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
955560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
956560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Test program
957560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
958560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumdef test():
9590c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    global mh, f
9600c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    os.system('rm -rf $HOME/Mail/@test')
9610c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    mh = MH()
9620c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def do(s): print s; print eval(s)
9630c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.listfolders()')
9640c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.listallfolders()')
9650c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    testfolders = ['@test', '@test/test1', '@test/test2',
96645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                   '@test/test1/test11', '@test/test1/test12',
96745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                   '@test/test1/test11/test111']
9680c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for t in testfolders: do('mh.makefolder(%s)' % `t`)
9690c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.listsubfolders(\'@test\')')
9700c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.listallsubfolders(\'@test\')')
9710c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f = mh.openfolder('@test')
9720c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.listsubfolders()')
9730c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.listallsubfolders()')
9740c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.getsequences()')
9750c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    seqs = f.getsequences()
9760c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
9770c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    print seqs
9780c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f.putsequences(seqs)
9790c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.getsequences()')
9800c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    testfolders.reverse()
9810c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for t in testfolders: do('mh.deletefolder(%s)' % `t`)
9820c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.getcontext()')
9830c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    context = mh.getcontext()
9840c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f = mh.openfolder(context)
9850c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.getcurrent()')
9860c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for seq in ['first', 'last', 'cur', '.', 'prev', 'next',
98745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                'first:3', 'last:3', 'cur:3', 'cur:-3',
98845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                'prev:3', 'next:3',
98945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',
99045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                'all']:
99145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
99245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            do('f.parsesequence(%s)' % `seq`)
99345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except Error, msg:
99445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            print "Error:", msg
99545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        stuff = os.popen("pick %s 2>/dev/null" % `seq`).read()
99645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        list = map(string.atoi, string.split(stuff))
99745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        print list, "<-- pick"
9980c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.listmessages()')
999560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
1000560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
1001560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumif __name__ == '__main__':
10020c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    test()
1003