mhlib.py revision 68468eba635570400f607e140425a222018e56f9
154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum"""MH interface -- purely object-oriented (well, almost)
254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van RossumExecutive summary:
454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumimport mhlib
654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossummh = mhlib.MH()         # use default mailbox directory and profile
854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossummh = mhlib.MH(mailbox)  # override mailbox location (default from profile)
954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossummh = mhlib.MH(mailbox, profile) # override mailbox and profile
1054f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
1154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossummh.error(format, ...)   # print error message -- can be overridden
1254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossums = mh.getprofile(key)  # profile entry (None if not set)
1354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumpath = mh.getpath()     # mailbox pathname
1454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumname = mh.getcontext()  # name of current folder
1554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossummh.setcontext(name)     # set name of current folder
1654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
1754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumlist = mh.listfolders() # names of top-level folders
1854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumlist = mh.listallfolders() # names of all folders, including subfolders
1954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumlist = mh.listsubfolders(name) # direct subfolders of given folder
2054f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumlist = mh.listallsubfolders(name) # all subfolders of given folder
2154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
2254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossummh.makefolder(name)     # create new folder
2354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossummh.deletefolder(name)   # delete folder -- must have no subfolders
2454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
2554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf = mh.openfolder(name) # new open folder object
2654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
2754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf.error(format, ...)    # same as mh.error(format, ...)
2854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumpath = f.getfullname()  # folder's full pathname
2954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumpath = f.getsequencesfilename() # full pathname of folder's sequences file
3054f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumpath = f.getmessagefilename(n)  # full pathname of message n in folder
3154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
3254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumlist = f.listmessages() # list of messages in folder (as numbers)
3354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumn = f.getcurrent()      # get current message
3454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf.setcurrent(n)         # set current message
3554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumlist = f.parsesequence(seq)     # parse msgs syntax into list of messages
3654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumn = f.getlast()         # get last message (0 if no messagse)
3754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf.setlast(n)            # set last message (internal use only)
3854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
3954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumdict = f.getsequences() # dictionary of sequences in folder {name: list}
4054f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf.putsequences(dict)    # write sequences back to folder
4154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
4254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf.createmessage(n, fp)  # add message from file f as number n
4354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf.removemessages(list)  # remove messages in list from folder
4454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf.refilemessages(list, tofolder) # move messages in list to other folder
4554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf.movemessage(n, tofolder, ton)  # move one message to a given destination
4654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumf.copymessage(n, tofolder, ton)  # copy one message to a given destination
4754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
4854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumm = f.openmessage(n)    # new open message object (costs a file descriptor)
4954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossumm is a derived class of mimetools.Message(rfc822.Message), with:
5054f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossums = m.getheadertext()   # text of message's headers
5154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossums = m.getheadertext(pred) # text of message's headers, filtered by pred
5254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossums = m.getbodytext()     # text of message's body, decoded
5354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossums = m.getbodytext(0)    # text of message's body, not decoded
5454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum"""
5554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
56560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# XXX To do, functionality:
57560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - annotate messages
584fe6caaaf082429e778a09043f6dce78b253b701Guido van Rossum# - send messages
59560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum#
6040b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossum# XXX To do, organization:
61560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - move IntSet to separate file
62560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# - move most Message functionality to module mimetools
63560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
64560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
65560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Customizable defaults
66560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
67560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van RossumMH_PROFILE = '~/.mh_profile'
68560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van RossumPATH = '~/Mail'
69560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van RossumMH_SEQUENCES = '.mh_sequences'
70560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van RossumFOLDER_PROTECT = 0700
71560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
72560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
73560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Imported modules
74560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
75560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumimport os
76508a092e2e44332c2f1ac381bdb956e3b94e0cc2Guido van Rossumimport sys
779694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossumimport re
78560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumimport mimetools
79560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumimport multifile
8040b2cfb3f39e3db4c5f04f0545f9af7d299c2f4fGuido van Rossumimport shutil
817cfd31ee8a1447e177f129c4e3f1bfd937528043Guido van Rossumfrom bisect import bisect
82560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
8317ab123cf1f0597e7e257c1ce83a6e87b85ffd7bSkip Montanaro__all__ = ["MH","Error","Folder","Message"]
84560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
85560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Exported constants
86560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
87ffdc48f45c7f942192ad19c26766207826ba99fdFred Drakeclass Error(Exception):
88ffdc48f45c7f942192ad19c26766207826ba99fdFred Drake    pass
89560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
90560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
91560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass MH:
9254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    """Class representing a particular collection of folders.
9354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    Optional constructor arguments are the pathname for the directory
9454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    containing the collection, and the MH profile to use.
9554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    If either is omitted or empty a default is used; the default
9654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    directory is taken from the MH profile if it is specified there."""
97560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
980c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, path = None, profile = None):
9954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Constructor."""
10016e3c427f35589ac3b83e8c13a8ec6495ec6cfa1Raymond Hettinger        if profile is None: profile = MH_PROFILE
10145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.profile = os.path.expanduser(profile)
10216e3c427f35589ac3b83e8c13a8ec6495ec6cfa1Raymond Hettinger        if path is None: path = self.getprofile('Path')
10345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not path: path = PATH
10445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not os.path.isabs(path) and path[0] != '~':
10545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            path = os.path.join('~', path)
10645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = os.path.expanduser(path)
10745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not os.path.isdir(path): raise Error, 'MH() path not found'
10845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.path = path
1090c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1100c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
11154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """String representation."""
11245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'MH(%s, %s)' % (`self.path`, `self.profile`)
1130c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1140c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def error(self, msg, *args):
11554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Routine to print an error.  May be overridden by a derived class."""
11645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        sys.stderr.write('MH error: %s\n' % (msg % args))
1170c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1180c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getprofile(self, key):
11954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return a profile entry, None if not found."""
12045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return pickline(self.profile, key)
1210c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1220c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getpath(self):
12354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the path (the name of the collection's directory)."""
12445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.path
1250c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1260c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getcontext(self):
12754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the name of the current folder."""
12845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        context = pickline(os.path.join(self.getpath(), 'context'),
12945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                  'Current-Folder')
13045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not context: context = 'inbox'
13145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return context
1320c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1330c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def setcontext(self, context):
13454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Set the name of the current folder."""
13545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fn = os.path.join(self.getpath(), 'context')
13645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(fn, "w")
13745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.write("Current-Folder: %s\n" % context)
13845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.close()
1390c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1400c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listfolders(self):
14154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the names of the top-level folders."""
14245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        folders = []
14345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = self.getpath()
14445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for name in os.listdir(path):
14545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fullname = os.path.join(path, name)
14645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if os.path.isdir(fullname):
14745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                folders.append(name)
14845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        folders.sort()
14945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return folders
1500c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1510c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listsubfolders(self, name):
15254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the names of the subfolders in a given folder
15354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        (prefixed with the given folder name)."""
15445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fullname = os.path.join(self.path, name)
15545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Get the link count so we can avoid listing folders
15645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # that have no subfolders.
15732200aeac697fcb3f2b4528127a2fbf0a22a8f17Raymond Hettinger        nlinks = os.stat(fullname).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    def listallfolders(self):
17654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the names of all folders and subfolders, recursively."""
17745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.listallsubfolders('')
1780c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
1790c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listallsubfolders(self, name):
18054f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the names of subfolders in a given folder, recursively."""
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.
18432200aeac697fcb3f2b4528127a2fbf0a22a8f17Raymond Hettinger        nlinks = os.stat(fullname).st_nlink
18545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if nlinks <= 2:
18645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return []
18745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        subfolders = []
18845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        subnames = os.listdir(fullname)
18945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for subname in subnames:
19045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if subname[0] == ',' or isnumeric(subname): continue
19145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fullsubname = os.path.join(fullname, subname)
19245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if os.path.isdir(fullsubname):
19345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                name_subname = os.path.join(name, subname)
19445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                subfolders.append(name_subname)
19545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not os.path.islink(fullsubname):
19645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    subsubfolders = self.listallsubfolders(
19745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                              name_subname)
19845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    subfolders = subfolders + subsubfolders
19945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # Stop looking for subfolders when
20045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # we've seen them all
20145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                nlinks = nlinks - 1
20245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if nlinks <= 2:
20345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    break
20445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        subfolders.sort()
20545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return subfolders
2060c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2070c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def openfolder(self, name):
20854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return a new Folder object for the named folder."""
20945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return Folder(self, name)
2100c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2110c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def makefolder(self, name):
21254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Create a new folder (or raise os.error if it cannot be created)."""
21345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        protect = pickline(self.profile, 'Folder-Protect')
21445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if protect and isnumeric(protect):
21566d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond            mode = int(protect, 8)
21645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
21745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            mode = FOLDER_PROTECT
21845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        os.mkdir(os.path.join(self.getpath(), name), mode)
2190c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2200c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def deletefolder(self, name):
22154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Delete a folder.  This removes files in the folder but not
22254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        subdirectories.  Raise os.error if deleting the folder itself fails."""
22345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fullname = os.path.join(self.getpath(), name)
22445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for subname in os.listdir(fullname):
22545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fullsubname = os.path.join(fullname, subname)
22645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
22745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.unlink(fullsubname)
22845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error:
22945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                self.error('%s not deleted, continuing...' %
23045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                          fullsubname)
23145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        os.rmdir(fullname)
232560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
233560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
2349694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossumnumericprog = re.compile('^[1-9][0-9]*$')
235560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumdef isnumeric(str):
2369694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    return numericprog.match(str) is not None
237560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
238560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass Folder:
23954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    """Class representing a particular folder."""
240560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
2410c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, mh, name):
24254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Constructor."""
24345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.mh = mh
24445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.name = name
24545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not os.path.isdir(self.getfullname()):
24645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, 'no folder %s' % name
2470c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2480c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
24954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """String representation."""
25045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'Folder(%s, %s)' % (`self.mh`, `self.name`)
2510c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2520c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def error(self, *args):
25354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Error message handler."""
25468468eba635570400f607e140425a222018e56f9Guido van Rossum        self.mh.error(*args)
2550c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2560c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getfullname(self):
25754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the full pathname of the folder."""
25845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return os.path.join(self.mh.path, self.name)
2590c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2600c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getsequencesfilename(self):
26154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the full pathname of the folder's sequences file."""
26245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return os.path.join(self.getfullname(), MH_SEQUENCES)
2630c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2640c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getmessagefilename(self, n):
26554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the full pathname of a message in the folder."""
26645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return os.path.join(self.getfullname(), str(n))
2670c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2680c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listsubfolders(self):
26954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return list of direct subfolders."""
27045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.mh.listsubfolders(self.name)
2710c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2720c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listallsubfolders(self):
27354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return list of all subfolders."""
27445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.mh.listallsubfolders(self.name)
2750c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
2760c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def listmessages(self):
27754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the list of messages currently present in the folder.
27854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        As a side effect, set self.last to the last message (or 0)."""
27945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        messages = []
28045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        match = numericprog.match
28145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        append = messages.append
28245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for name in os.listdir(self.getfullname()):
283d9d2625dbd284f91df2beab22f385e0f0bf60cabGuido van Rossum            if match(name):
28445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                append(name)
28566d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond        messages = map(int, messages)
28645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        messages.sort()
28745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if messages:
28845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.last = messages[-1]
28945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
29045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.last = 0
29145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return messages
292560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
2930c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getsequences(self):
29454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the set of sequences for the folder."""
29545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        sequences = {}
29645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fullname = self.getsequencesfilename()
29745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
29845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f = open(fullname, 'r')
29945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except IOError:
30045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return sequences
30145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        while 1:
30245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            line = f.readline()
30345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not line: break
30466d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond            fields = line.split(':')
3058152d32375c40bba9ccbe43b780ebe96d9617781Fred Drake            if len(fields) != 2:
30645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                self.error('bad sequence in %s: %s' %
30766d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond                          (fullname, line.strip()))
30866d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond            key = fields[0].strip()
30966d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond            value = IntSet(fields[1].strip(), ' ').tolist()
31045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            sequences[key] = value
31145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return sequences
3120c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
3130c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def putsequences(self, sequences):
31454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Write the set of sequences back to the folder."""
31545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fullname = self.getsequencesfilename()
31645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = None
31788f72ff95592730d3931d90028d0df934fa1a13dRaymond Hettinger        for key, seq in sequences.iteritems():
31845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            s = IntSet('', ' ')
31988f72ff95592730d3931d90028d0df934fa1a13dRaymond Hettinger            s.fromlist(seq)
32045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not f: f = open(fullname, 'w')
32145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f.write('%s: %s\n' % (key, s.tostring()))
32245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not f:
32345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
32445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.unlink(fullname)
32545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error:
32645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                pass
32745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
32845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f.close()
329560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
3300c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getcurrent(self):
331ffdc48f45c7f942192ad19c26766207826ba99fdFred Drake        """Return the current message.  Raise Error when there is none."""
33245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        seqs = self.getsequences()
33345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
33445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return max(seqs['cur'])
33545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except (ValueError, KeyError):
33645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, "no cur message"
3370c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
3380c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def setcurrent(self, n):
33954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Set the current message."""
34045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        updateline(self.getsequencesfilename(), 'cur', str(n), 0)
3410c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
3420c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def parsesequence(self, seq):
34354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Parse an MH sequence specification into a message list.
34454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        Attempt to mimic mh-sequence(5) as close as possible.
34554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        Also attempt to mimic observed behavior regarding which
34654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        conditions cause which error messages."""
34745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # XXX Still not complete (see mh-format(5)).
34845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Missing are:
34945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # - 'prev', 'next' as count
35045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # - Sequence-Negation option
35145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        all = self.listmessages()
35245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Observed behavior: test for empty folder is done first
35345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not all:
35445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, "no messages in %s" % self.name
35545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Common case first: all is frequently the default
35645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'all':
35745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return all
35845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Test for X:Y before X-Y because 'seq:-n' matches both
35966d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond        i = seq.find(':')
36045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if i >= 0:
36145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            head, dir, tail = seq[:i], '', seq[i+1:]
36245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if tail[:1] in '-+':
36345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                dir, tail = tail[:1], tail[1:]
36445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not isnumeric(tail):
36545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "bad message list %s" % seq
36645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
36766d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond                count = int(tail)
36845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except (ValueError, OverflowError):
36945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # Can't use sys.maxint because of i+count below
37045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                count = len(all)
37145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
37245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                anchor = self._parseindex(head, all)
37345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except Error, msg:
37445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                seqs = self.getsequences()
37554f0222547b1e92cd018ef132307a6f793dc9505Raymond Hettinger                if not head in seqs:
37645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    if not msg:
37745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        msg = "bad message list %s" % seq
37845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    raise Error, msg, sys.exc_info()[2]
37945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                msgs = seqs[head]
38045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not msgs:
38145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    raise Error, "sequence %s empty" % head
38245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if dir == '-':
38345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    return msgs[-count:]
38445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                else:
38545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    return msgs[:count]
38645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
38745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not dir:
38845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    if head in ('prev', 'last'):
38945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        dir = '-'
39045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if dir == '-':
39145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    i = bisect(all, anchor)
39245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    return all[max(0, i-count):i]
39345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                else:
39445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    i = bisect(all, anchor-1)
39545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    return all[i:i+count]
39645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Test for X-Y next
39766d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond        i = seq.find('-')
39845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if i >= 0:
39945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            begin = self._parseindex(seq[:i], all)
40045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            end = self._parseindex(seq[i+1:], all)
40145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            i = bisect(all, begin-1)
40245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            j = bisect(all, end)
40345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            r = all[i:j]
40445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not r:
40545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "bad message list %s" % seq
40645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return r
40745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Neither X:Y nor X-Y; must be a number or a (pseudo-)sequence
40845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
40945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            n = self._parseindex(seq, all)
41045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except Error, msg:
41145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            seqs = self.getsequences()
41254f0222547b1e92cd018ef132307a6f793dc9505Raymond Hettinger            if not seq in seqs:
41345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not msg:
41445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    msg = "bad message list %s" % seq
41545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, msg
41645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return seqs[seq]
41745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
41845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if n not in all:
41945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if isnumeric(seq):
42045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    raise Error, "message %d doesn't exist" % n
42145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                else:
42245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    raise Error, "no %s message" % seq
42345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
42445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return [n]
4250c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
4260c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def _parseindex(self, seq, all):
42754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Internal: parse a message number (or cur, first, etc.)."""
42845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if isnumeric(seq):
42945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
43066d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond                return int(seq)
43145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except (OverflowError, ValueError):
43245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return sys.maxint
43345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq in ('cur', '.'):
43445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.getcurrent()
43545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'first':
43645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return all[0]
43745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'last':
43845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return all[-1]
43945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'next':
44045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            n = self.getcurrent()
44145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            i = bisect(all, n)
44245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
44345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return all[i]
44445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except IndexError:
44545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "no next message"
44645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if seq == 'prev':
44745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            n = self.getcurrent()
44845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            i = bisect(all, n-1)
44945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if i == 0:
45045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "no prev message"
45145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
45245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return all[i-1]
45345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except IndexError:
45445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise Error, "no prev message"
45545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        raise Error, None
4560c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
4570c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def openmessage(self, n):
45854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Open a message -- returns a Message object."""
45945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return Message(self, n)
4600c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
4610c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def removemessages(self, list):
46254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Remove one or more messages -- may raise os.error."""
46345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        errors = []
46445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        deleted = []
46545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for n in list:
46645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            path = self.getmessagefilename(n)
46745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            commapath = self.getmessagefilename(',' + str(n))
46845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
46945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.unlink(commapath)
47045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error:
47145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                pass
47245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
47345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.rename(path, commapath)
47445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error, msg:
47545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                errors.append(msg)
47645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
47745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                deleted.append(n)
47845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if deleted:
47945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.removefromallsequences(deleted)
48045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if errors:
48145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if len(errors) == 1:
48245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise os.error, errors[0]
48345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
48445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise os.error, ('multiple errors:', errors)
4850c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
4860c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def refilemessages(self, list, tofolder, keepsequences=0):
48754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Refile one or more messages -- may raise os.error.
48854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        'tofolder' is an open folder object."""
48945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        errors = []
49045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        refiled = {}
49145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for n in list:
49245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            ton = tofolder.getlast() + 1
49345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            path = self.getmessagefilename(n)
49445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            topath = tofolder.getmessagefilename(ton)
49545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
49645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                os.rename(path, topath)
49745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            except os.error:
49845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                # Try copying
49945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                try:
50045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    shutil.copy2(path, topath)
50145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    os.unlink(path)
50245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                except (IOError, os.error), msg:
50345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    errors.append(msg)
50445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    try:
50545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        os.unlink(topath)
50645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    except os.error:
50745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        pass
50845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    continue
50945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            tofolder.setlast(ton)
51045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            refiled[n] = ton
51145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if refiled:
51245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if keepsequences:
51345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                tofolder._copysequences(self, refiled.items())
51445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.removefromallsequences(refiled.keys())
51545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if errors:
51645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if len(errors) == 1:
51745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise os.error, errors[0]
51845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
51945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise os.error, ('multiple errors:', errors)
5200c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
5210c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def _copysequences(self, fromfolder, refileditems):
52254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Helper for refilemessages() to copy sequences."""
52345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        fromsequences = fromfolder.getsequences()
52445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        tosequences = self.getsequences()
52545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        changed = 0
52645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for name, seq in fromsequences.items():
52745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
52845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                toseq = tosequences[name]
52945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                new = 0
5303db163aa1952a53712d402f8f460228dde1192ceunknown            except KeyError:
53145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                toseq = []
53245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                new = 1
53345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            for fromn, ton in refileditems:
53445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if fromn in seq:
53545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    toseq.append(ton)
53645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    changed = 1
53745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if new and toseq:
53845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                tosequences[name] = toseq
53945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if changed:
54045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.putsequences(tosequences)
5410c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
5420c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def movemessage(self, n, tofolder, ton):
54354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Move one message over a specific destination message,
54454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        which may or may not already exist."""
54545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = self.getmessagefilename(n)
54645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Open it to check that it exists
54745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(path)
54845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.close()
54945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        del f
55045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        topath = tofolder.getmessagefilename(ton)
55145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        backuptopath = tofolder.getmessagefilename(',%d' % ton)
55245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
55345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.rename(topath, backuptopath)
55445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except os.error:
55545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            pass
55645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
55745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.rename(path, topath)
55845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except os.error:
55945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            # Try copying
56045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            ok = 0
56145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            try:
56245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                tofolder.setlast(None)
56345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                shutil.copy2(path, topath)
56445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                ok = 1
56545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            finally:
56645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not ok:
56745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    try:
56845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        os.unlink(topath)
56945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    except os.error:
57045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        pass
57145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.unlink(path)
57245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.removefromallsequences([n])
5730c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
5740c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def copymessage(self, n, tofolder, ton):
57554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Copy one message over a specific destination message,
57654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        which may or may not already exist."""
57745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = self.getmessagefilename(n)
57845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        # Open it to check that it exists
57945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(path)
58045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.close()
58145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        del f
58245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        topath = tofolder.getmessagefilename(ton)
58345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        backuptopath = tofolder.getmessagefilename(',%d' % ton)
58445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
58545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.rename(topath, backuptopath)
58645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except os.error:
58745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            pass
58845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        ok = 0
58945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
59045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            tofolder.setlast(None)
59145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            shutil.copy2(path, topath)
59245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            ok = 1
59345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        finally:
59445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not ok:
59545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                try:
59645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    os.unlink(topath)
59745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                except os.error:
59845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    pass
5990c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6004e5cbcf5afa411d654983cd4cf8c99739b935b26Guido van Rossum    def createmessage(self, n, txt):
60154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Create a message, with text from the open file txt."""
60245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        path = self.getmessagefilename(n)
60345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        backuppath = self.getmessagefilename(',%d' % n)
60445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
60545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            os.rename(path, backuppath)
60645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except os.error:
60745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            pass
60845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        ok = 0
60945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        BUFSIZE = 16*1024
61045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
61145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f = open(path, "w")
61245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            while 1:
61345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                buf = txt.read(BUFSIZE)
61445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if not buf:
61545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    break
61645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                f.write(buf)
61745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            f.close()
61845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            ok = 1
61945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        finally:
62045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if not ok:
62145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                try:
62245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    os.unlink(path)
62345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                except os.error:
62445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    pass
6254e5cbcf5afa411d654983cd4cf8c99739b935b26Guido van Rossum
6260c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def removefromallsequences(self, list):
6277e47402264cf87b9bbb61fc9ff610af08add7c7bThomas Wouters        """Remove one or more messages from all sequences (including last)
62854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        -- but not from 'cur'!!!"""
62945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if hasattr(self, 'last') and self.last in list:
63045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            del self.last
63145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        sequences = self.getsequences()
63245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        changed = 0
63345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for name, seq in sequences.items():
63445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if name == 'cur':
63545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                continue
63645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            for n in list:
63745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if n in seq:
63845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    seq.remove(n)
63945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    changed = 1
64045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    if not seq:
64145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                        del sequences[name]
64245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if changed:
64345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.putsequences(sequences)
6440c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6450c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getlast(self):
64654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the last message number."""
64745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not hasattr(self, 'last'):
648f93befc2094f5adea7a7b7aa8b979d52241b2062Guido van Rossum            self.listmessages() # Set self.last
64945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.last
6500c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6510c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def setlast(self, last):
65254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Set the last message number."""
65345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if last is None:
65445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if hasattr(self, 'last'):
65545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                del self.last
65645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
65745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.last = last
658560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
659560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass Message(mimetools.Message):
660560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
6610c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, f, n, fp = None):
66254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Constructor."""
66345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.folder = f
66445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.number = n
66516e3c427f35589ac3b83e8c13a8ec6495ec6cfa1Raymond Hettinger        if fp is None:
66645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            path = f.getmessagefilename(n)
66745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            fp = open(path, 'r')
66845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mimetools.Message.__init__(self, fp)
6690c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6700c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
67154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """String representation."""
67245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'Message(%s, %s)' % (repr(self.folder), self.number)
6730c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6740c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getheadertext(self, pred = None):
67554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the message's header text as a string.  If an
67654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        argument is specified, it is used as a filter predicate to
67754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        decide which headers to return (its argument is the header
67854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        name converted to lower case)."""
67916e3c427f35589ac3b83e8c13a8ec6495ec6cfa1Raymond Hettinger        if pred is None:
68066d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond            return ''.join(self.headers)
68145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        headers = []
68245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        hit = 0
68345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for line in self.headers:
6846e025bcde84ec358734a116cefcc4acc357ee0b1Eric S. Raymond            if not line[0].isspace():
68566d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond                i = line.find(':')
68645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if i > 0:
68766d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond                    hit = pred(line[:i].lower())
68845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if hit: headers.append(line)
689c9838f9fcbf138b454606d606f7b53a5be21b5aeEric S. Raymond        return ''.join(headers)
6900c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
6910c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbodytext(self, decode = 1):
69254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return the message's body text as string.  This undoes a
69354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        Content-Transfer-Encoding, but does not interpret other MIME
69454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        features (e.g. multipart messages).  To suppress decoding,
69554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        pass 0 as an argument."""
69645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.fp.seek(self.startofbody)
69745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        encoding = self.getencoding()
6984fe6caaaf082429e778a09043f6dce78b253b701Guido van Rossum        if not decode or encoding in ('', '7bit', '8bit', 'binary'):
69945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.fp.read()
70045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        from StringIO import StringIO
70145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        output = StringIO()
70245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mimetools.decode(self.fp, output, encoding)
70345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return output.getvalue()
7040c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
7050c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbodyparts(self):
70654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Only for multipart messages: return the message's body as a
70754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        list of SubMessage objects.  Each submessage object behaves
70854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        (almost) as a Message object."""
70945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if self.getmaintype() != 'multipart':
71045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, 'Content-Type is not multipart/*'
71145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        bdry = self.getparam('boundary')
71245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not bdry:
71345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            raise Error, 'multipart/* without boundary param'
71445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.fp.seek(self.startofbody)
71545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mf = multifile.MultiFile(self.fp)
71645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mf.push(bdry)
71745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        parts = []
71845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        while mf.next():
71945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            n = str(self.number) + '.' + `1 + len(parts)`
72045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            part = SubMessage(self.folder, n, mf)
72145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            parts.append(part)
72245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        mf.pop()
72345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return parts
7240c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
7250c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbody(self):
72654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Return body, either a string or a list of messages."""
72745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if self.getmaintype() == 'multipart':
72845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.getbodyparts()
72945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
73045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.getbodytext()
731560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
732560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
733560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass SubMessage(Message):
734560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7350c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, f, n, fp):
73654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """Constructor."""
73745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        Message.__init__(self, f, n, fp)
73845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if self.getmaintype() == 'multipart':
73945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.body = Message.getbodyparts(self)
74045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        else:
74145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.body = Message.getbodytext(self)
7424fe6caaaf082429e778a09043f6dce78b253b701Guido van Rossum        self.bodyencoded = Message.getbodytext(self, decode=0)
74345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            # XXX If this is big, should remember file pointers
744560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7450c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
74654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum        """String representation."""
74745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f, n, fp = self.folder, self.number, self.fp
74845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'SubMessage(%s, %s, %s)' % (f, n, fp)
749560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7504fe6caaaf082429e778a09043f6dce78b253b701Guido van Rossum    def getbodytext(self, decode = 1):
7514fe6caaaf082429e778a09043f6dce78b253b701Guido van Rossum        if not decode:
7524fe6caaaf082429e778a09043f6dce78b253b701Guido van Rossum            return self.bodyencoded
75345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if type(self.body) == type(''):
75445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.body
755560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7560c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbodyparts(self):
75745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if type(self.body) == type([]):
75845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            return self.body
759560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7600c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def getbody(self):
76145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.body
762560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
763560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
764560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumclass IntSet:
76554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    """Class implementing sets of integers.
76654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
76754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    This is an efficient representation for sets consisting of several
76854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    continuous ranges, e.g. 1-100,200-400,402-1000 is represented
76954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    internally as a list of three pairs: [(1,100), (200,400),
77054f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    (402,1000)].  The internal representation is always kept normalized.
77154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
77254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    The constructor has up to three arguments:
77354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    - the string used to initialize the set (default ''),
77454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    - the separator between ranges (default ',')
77554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    - the separator between begin and end of a range (default '-')
77654f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    The separators must be strings (not regexprs) and should be different.
77754f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
77854f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    The tostring() function yields a string that can be passed to another
77954f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    IntSet constructor; __repr__() is a valid IntSet constructor itself.
78054f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    """
78154f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum
78254f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    # XXX The default begin/end separator means that negative numbers are
78354f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    #     not supported very well.
78454f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    #
78554f22ed30bab2e64909ba2d79205cb4b87c69db2Guido van Rossum    # XXX There are currently no operations to remove set elements.
786560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
7870c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __init__(self, data = None, sep = ',', rng = '-'):
78845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs = []
78945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.sep = sep
79045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.rng = rng
7911ccccc08c2913b04fe9563b283dd1339d079a9bdMichael W. Hudson        if data: self.fromstring(data)
7920c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
7930c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def reset(self):
79445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs = []
7950c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
7960c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __cmp__(self, other):
79745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return cmp(self.pairs, other.pairs)
7980c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
7990c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __hash__(self):
80045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return hash(self.pairs)
8010c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8020c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def __repr__(self):
80345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return 'IntSet(%s, %s, %s)' % (`self.tostring()`,
80445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                  `self.sep`, `self.rng`)
8050c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8060c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def normalize(self):
80745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs.sort()
80845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        i = 1
80945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        while i < len(self.pairs):
81045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            alo, ahi = self.pairs[i-1]
81145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            blo, bhi = self.pairs[i]
81245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if ahi >= blo-1:
81345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                self.pairs[i-1:i+1] = [(alo, max(ahi, bhi))]
81445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
81545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                i = i+1
8160c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8170c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def tostring(self):
81845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        s = ''
81945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for lo, hi in self.pairs:
82045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if lo == hi: t = `lo`
82145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else: t = `lo` + self.rng + `hi`
82245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if s: s = s + (self.sep + t)
82345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else: s = t
82445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return s
8250c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8260c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def tolist(self):
82745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        l = []
82845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for lo, hi in self.pairs:
82945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            m = range(lo, hi+1)
83045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            l = l + m
83145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return l
8320c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8330c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def fromlist(self, list):
83445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for i in list:
83545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            self.append(i)
8360c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8370c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def clone(self):
83845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        new = IntSet()
83945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        new.pairs = self.pairs[:]
84045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return new
8410c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8420c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def min(self):
84345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.pairs[0][0]
8440c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8450c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def max(self):
84645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return self.pairs[-1][-1]
8470c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8480c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def contains(self, x):
84945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for lo, hi in self.pairs:
850bc0e9108261693b6278687f4fb4709ff76c2e543Tim Peters            if lo <= x <= hi: return True
851bc0e9108261693b6278687f4fb4709ff76c2e543Tim Peters        return False
8520c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8530c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def append(self, x):
85445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        for i in range(len(self.pairs)):
85545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            lo, hi = self.pairs[i]
85645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if x < lo: # Need to insert before
85745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if x+1 == lo:
85845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    self.pairs[i] = (x, hi)
85945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                else:
86045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    self.pairs.insert(i, (x, x))
86145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                if i > 0 and x-1 == self.pairs[i-1][1]:
86245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    # Merge with previous
86345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    self.pairs[i-1:i+1] = [
86445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                            (self.pairs[i-1][0],
86545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                             self.pairs[i][1])
86645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                          ]
86745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return
86845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if x <= hi: # Already in set
86945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return
87045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        i = len(self.pairs) - 1
87145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if i >= 0:
87245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            lo, hi = self.pairs[i]
87345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if x-1 == hi:
87445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                self.pairs[i] = lo, x
87545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                return
87645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs.append((x, x))
8770c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8780c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def addpair(self, xlo, xhi):
87945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if xlo > xhi: return
88045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs.append((xlo, xhi))
88145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.normalize()
8820c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum
8830c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def fromstring(self, data):
88445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        new = []
88566d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond        for part in data.split(self.sep):
88645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            list = []
88766d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond            for subp in part.split(self.rng):
88866d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond                s = subp.strip()
88966d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond                list.append(int(s))
89045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if len(list) == 1:
89145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                new.append((list[0], list[0]))
89245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            elif len(list) == 2 and list[0] <= list[1]:
89345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                new.append((list[0], list[1]))
89445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
89545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                raise ValueError, 'bad data passed to IntSet'
89645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.pairs = self.pairs + new
89745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        self.normalize()
898560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
899560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
900560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Subroutines to read/write entries in .mh_profile and .mh_sequences
901560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
902560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumdef pickline(file, key, casefold = 1):
9030c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    try:
90445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(file, 'r')
9050c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    except IOError:
90645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        return None
9079694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    pat = re.escape(key) + ':'
9089694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    prog = re.compile(pat, casefold and re.IGNORECASE)
9090c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    while 1:
91045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        line = f.readline()
91145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if not line: break
91245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if prog.match(line):
91345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            text = line[len(key)+1:]
91445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            while 1:
91545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                line = f.readline()
9166e025bcde84ec358734a116cefcc4acc357ee0b1Eric S. Raymond                if not line or not line[0].isspace():
91745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                    break
91845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                text = text + line
91966d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond            return text.strip()
9200c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    return None
921560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
922560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumdef updateline(file, key, value, casefold = 1):
9230c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    try:
92445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f = open(file, 'r')
92545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        lines = f.readlines()
92645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.close()
9270c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    except IOError:
92845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        lines = []
9299694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    pat = re.escape(key) + ':(.*)\n'
9309694fcab5332f27dc28b195ba1391e5491d2eaefGuido van Rossum    prog = re.compile(pat, casefold and re.IGNORECASE)
9310c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    if value is None:
93245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        newline = None
9330c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    else:
93445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        newline = '%s: %s\n' % (key, value)
9350c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for i in range(len(lines)):
93645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        line = lines[i]
93745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if prog.match(line):
93845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            if newline is None:
93945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                del lines[i]
94045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            else:
94145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                lines[i] = newline
94245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            break
9430c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    else:
94445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        if newline is not None:
94545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            lines.append(newline)
9460c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    tempfile = file + "~"
9470c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f = open(tempfile, 'w')
9480c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for line in lines:
94945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        f.write(line)
9500c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f.close()
9510c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    os.rename(tempfile, file)
952560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
953560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
954560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum# Test program
955560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
956560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumdef test():
9570c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    global mh, f
9580c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    os.system('rm -rf $HOME/Mail/@test')
9590c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    mh = MH()
9600c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    def do(s): print s; print eval(s)
9610c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.listfolders()')
9620c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.listallfolders()')
9630c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    testfolders = ['@test', '@test/test1', '@test/test2',
96445e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                   '@test/test1/test11', '@test/test1/test12',
96545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                   '@test/test1/test11/test111']
9660c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for t in testfolders: do('mh.makefolder(%s)' % `t`)
9670c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.listsubfolders(\'@test\')')
9680c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.listallsubfolders(\'@test\')')
9690c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f = mh.openfolder('@test')
9700c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.listsubfolders()')
9710c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.listallsubfolders()')
9720c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.getsequences()')
9730c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    seqs = f.getsequences()
9740c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    seqs['foo'] = IntSet('1-10 12-20', ' ').tolist()
9750c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    print seqs
9760c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f.putsequences(seqs)
9770c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.getsequences()')
9780c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    testfolders.reverse()
9790c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for t in testfolders: do('mh.deletefolder(%s)' % `t`)
9800c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('mh.getcontext()')
9810c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    context = mh.getcontext()
9820c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    f = mh.openfolder(context)
9830c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.getcurrent()')
9840c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    for seq in ['first', 'last', 'cur', '.', 'prev', 'next',
98545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                'first:3', 'last:3', 'cur:3', 'cur:-3',
98645e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                'prev:3', 'next:3',
98745e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                '1:3', '1:-3', '100:3', '100:-3', '10000:3', '10000:-3',
98845e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum                'all']:
98945e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        try:
99045e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            do('f.parsesequence(%s)' % `seq`)
99145e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        except Error, msg:
99245e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum            print "Error:", msg
99345e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        stuff = os.popen("pick %s 2>/dev/null" % `seq`).read()
99466d9919cab903e152d206b7edb853799eca564d0Eric S. Raymond        list = map(int, stuff.split())
99545e2fbc2e70ef28b1f0327207f33dab3a4e825c5Guido van Rossum        print list, "<-- pick"
9960c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    do('f.listmessages()')
997560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
998560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossum
999560131328c9ddfa25555048b1bf4d760c02e1dd7Guido van Rossumif __name__ == '__main__':
10000c5e049c751c45aca40e26e3f1f76597798b86d9Guido van Rossum    test()
1001