| 1 | """MH interface -- purely object-oriented (well, almost)
|
|---|
| 2 |
|
|---|
| 3 | Executive summary:
|
|---|
| 4 |
|
|---|
| 5 | import mhlib
|
|---|
| 6 |
|
|---|
| 7 | mh = mhlib.MH() # use default mailbox directory and profile
|
|---|
| 8 | mh = mhlib.MH(mailbox) # override mailbox location (default from profile)
|
|---|
| 9 | mh = mhlib.MH(mailbox, profile) # override mailbox and profile
|
|---|
| 10 |
|
|---|
| 11 | mh.error(format, ...) # print error message -- can be overridden
|
|---|
| 12 | s = mh.getprofile(key) # profile entry (None if not set)
|
|---|
| 13 | path = mh.getpath() # mailbox pathname
|
|---|
| 14 | name = mh.getcontext() # name of current folder
|
|---|
| 15 | mh.setcontext(name) # set name of current folder
|
|---|
| 16 |
|
|---|
| 17 | list = mh.listfolders() # names of top-level folders
|
|---|
| 18 | list = mh.listallfolders() # names of all folders, including subfolders
|
|---|
| 19 | list = mh.listsubfolders(name) # direct subfolders of given folder
|
|---|
| 20 | list = mh.listallsubfolders(name) # all subfolders of given folder
|
|---|
| 21 |
|
|---|
| 22 | mh.makefolder(name) # create new folder
|
|---|
| 23 | mh.deletefolder(name) # delete folder -- must have no subfolders
|
|---|
| 24 |
|
|---|
| 25 | f = mh.openfolder(name) # new open folder object
|
|---|
| 26 |
|
|---|
| 27 | f.error(format, ...) # same as mh.error(format, ...)
|
|---|
| 28 | path = f.getfullname() # folder's full pathname
|
|---|
| 29 | path = f.getsequencesfilename() # full pathname of folder's sequences file
|
|---|
| 30 | path = f.getmessagefilename(n) # full pathname of message n in folder
|
|---|
| 31 |
|
|---|
| 32 | list = f.listmessages() # list of messages in folder (as numbers)
|
|---|
| 33 | n = f.getcurrent() # get current message
|
|---|
| 34 | f.setcurrent(n) # set current message
|
|---|
| 35 | list = f.parsesequence(seq) # parse msgs syntax into list of messages
|
|---|
| 36 | n = f.getlast() # get last message (0 if no messagse)
|
|---|
| 37 | f.setlast(n) # set last message (internal use only)
|
|---|
| 38 |
|
|---|
| 39 | dict = f.getsequences() # dictionary of sequences in folder {name: list}
|
|---|
| 40 | f.putsequences(dict) # write sequences back to folder
|
|---|
| 41 |
|
|---|
| 42 | f.createmessage(n, fp) # add message from file f as number n
|
|---|
| 43 | f.removemessages(list) # remove messages in list from folder
|
|---|
| 44 | f.refilemessages(list, tofolder) # move messages in list to other folder
|
|---|
| 45 | f.movemessage(n, tofolder, ton) # move one message to a given destination
|
|---|
| 46 | f.copymessage(n, tofolder, ton) # copy one message to a given destination
|
|---|
| 47 |
|
|---|
| 48 | m = f.openmessage(n) # new open message object (costs a file descriptor)
|
|---|
| 49 | m is a derived class of mimetools.Message(rfc822.Message), with:
|
|---|
| 50 | s = m.getheadertext() # text of message's headers
|
|---|
| 51 | s = m.getheadertext(pred) # text of message's headers, filtered by pred
|
|---|
| 52 | s = m.getbodytext() # text of message's body, decoded
|
|---|
| 53 | s = m.getbodytext(0) # text of message's body, not decoded
|
|---|
| 54 | """
|
|---|
| 55 |
|
|---|
| 56 | # XXX To do, functionality:
|
|---|
| 57 | # - annotate messages
|
|---|
| 58 | # - send messages
|
|---|
| 59 | #
|
|---|
| 60 | # XXX To do, organization:
|
|---|
| 61 | # - move IntSet to separate file
|
|---|
| 62 | # - move most Message functionality to module mimetools
|
|---|
| 63 |
|
|---|
| 64 |
|
|---|
| 65 | # Customizable defaults
|
|---|
| 66 |
|
|---|
| 67 | MH_PROFILE = '~/.mh_profile'
|
|---|
| 68 | PATH = '~/Mail'
|
|---|
| 69 | MH_SEQUENCES = '.mh_sequences'
|
|---|
| 70 | FOLDER_PROTECT = 0700
|
|---|
| 71 |
|
|---|
| 72 |
|
|---|
| 73 | # Imported modules
|
|---|
| 74 |
|
|---|
| 75 | import os
|
|---|
| 76 | import sys
|
|---|
| 77 | import re
|
|---|
| 78 | import mimetools
|
|---|
| 79 | import multifile
|
|---|
| 80 | import shutil
|
|---|
| 81 | from bisect import bisect
|
|---|
| 82 |
|
|---|
| 83 | __all__ = ["MH","Error","Folder","Message"]
|
|---|
| 84 |
|
|---|
| 85 | # Exported constants
|
|---|
| 86 |
|
|---|
| 87 | class Error(Exception):
|
|---|
| 88 | pass
|
|---|
| 89 |
|
|---|
| 90 |
|
|---|
| 91 | class MH:
|
|---|
| 92 | """Class representing a particular collection of folders.
|
|---|
| 93 | Optional constructor arguments are the pathname for the directory
|
|---|
| 94 | containing the collection, and the MH profile to use.
|
|---|
| 95 | If either is omitted or empty a default is used; the default
|
|---|
| 96 | directory is taken from the MH profile if it is specified there."""
|
|---|
| 97 |
|
|---|
| 98 | def __init__(self, path = None, profile = None):
|
|---|
| 99 | """Constructor."""
|
|---|
| 100 | if profile is None: profile = MH_PROFILE
|
|---|
| 101 | self.profile = os.path.expanduser(profile)
|
|---|
| 102 | if path is None: path = self.getprofile('Path')
|
|---|
| 103 | if not path: path = PATH
|
|---|
| 104 | if not os.path.isabs(path) and path[0] != '~':
|
|---|
| 105 | path = os.path.join('~', path)
|
|---|
| 106 | path = os.path.expanduser(path)
|
|---|
| 107 | if not os.path.isdir(path): raise Error, 'MH() path not found'
|
|---|
| 108 | self.path = path
|
|---|
| 109 |
|
|---|
| 110 | def __repr__(self):
|
|---|
| 111 | """String representation."""
|
|---|
| 112 | return 'MH(%r, %r)' % (self.path, self.profile)
|
|---|
| 113 |
|
|---|
| 114 | def error(self, msg, *args):
|
|---|
| 115 | """Routine to print an error. May be overridden by a derived class."""
|
|---|
| 116 | sys.stderr.write('MH error: %s\n' % (msg % args))
|
|---|
| 117 |
|
|---|
| 118 | def getprofile(self, key):
|
|---|
| 119 | """Return a profile entry, None if not found."""
|
|---|
| 120 | return pickline(self.profile, key)
|
|---|
| 121 |
|
|---|
| 122 | def getpath(self):
|
|---|
| 123 | """Return the path (the name of the collection's directory)."""
|
|---|
| 124 | return self.path
|
|---|
| 125 |
|
|---|
| 126 | def getcontext(self):
|
|---|
| 127 | """Return the name of the current folder."""
|
|---|
| 128 | context = pickline(os.path.join(self.getpath(), 'context'),
|
|---|
| 129 | 'Current-Folder')
|
|---|
| 130 | if not context: context = 'inbox'
|
|---|
| 131 | return context
|
|---|
| 132 |
|
|---|
| 133 | def setcontext(self, context):
|
|---|
| 134 | """Set the name of the current folder."""
|
|---|
| 135 | fn = os.path.join(self.getpath(), 'context')
|
|---|
| 136 | f = open(fn, "w")
|
|---|
| 137 | f.write("Current-Folder: %s\n" % context)
|
|---|
| 138 | f.close()
|
|---|
| 139 |
|
|---|
| 140 | def listfolders(self):
|
|---|
| 141 | """Return the names of the top-level folders."""
|
|---|
| 142 | folders = []
|
|---|
| 143 | path = self.getpath()
|
|---|
| 144 | for name in os.listdir(path):
|
|---|
| 145 | fullname = os.path.join(path, name)
|
|---|
| 146 | if os.path.isdir(fullname):
|
|---|
| 147 | folders.append(name)
|
|---|
| 148 | folders.sort()
|
|---|
| 149 | return folders
|
|---|
| 150 |
|
|---|
| 151 | def listsubfolders(self, name):
|
|---|
| 152 | """Return the names of the subfolders in a given folder
|
|---|
| 153 | (prefixed with the given folder name)."""
|
|---|
| 154 | fullname = os.path.join(self.path, name)
|
|---|
| 155 | # Get the link count so we can avoid listing folders
|
|---|
| 156 | # that have no subfolders.
|
|---|
| 157 | nlinks = os.stat(fullname).st_nlink
|
|---|
| 158 | if nlinks <= 2:
|
|---|
| 159 | return []
|
|---|
| 160 | subfolders = []
|
|---|
| 161 | subnames = os.listdir(fullname)
|
|---|
| 162 | for subname in subnames:
|
|---|
| 163 | fullsubname = os.path.join(fullname, subname)
|
|---|
| 164 | if os.path.isdir(fullsubname):
|
|---|
| 165 | name_subname = os.path.join(name, subname)
|
|---|
| 166 | subfolders.append(name_subname)
|
|---|
| 167 | # Stop looking for subfolders when
|
|---|
| 168 | # we've seen them all
|
|---|
| 169 | nlinks = nlinks - 1
|
|---|
| 170 | if nlinks <= 2:
|
|---|
| 171 | break
|
|---|
|
|---|