home *** CD-ROM | disk | FTP | other *** search
/ PC Extra 07 & 08 / pca1507.iso / Software / psp8 / Data1.cab / mailbox.py < prev    next >
Encoding:
Python Source  |  2003-04-22  |  9.3 KB  |  312 lines

  1. #! /usr/bin/env python
  2.  
  3. """Classes to handle Unix style, MMDF style, and MH style mailboxes."""
  4.  
  5.  
  6. import rfc822
  7. import os
  8.  
  9. __all__ = ["UnixMailbox","MmdfMailbox","MHMailbox","Maildir","BabylMailbox"]
  10.  
  11. class _Mailbox:
  12.     def __init__(self, fp, factory=rfc822.Message):
  13.         self.fp = fp
  14.         self.seekp = 0
  15.         self.factory = factory
  16.  
  17.     def __iter__(self):
  18.         return iter(self.next, None)
  19.  
  20.     def next(self):
  21.         while 1:
  22.             self.fp.seek(self.seekp)
  23.             try:
  24.                 self._search_start()
  25.             except EOFError:
  26.                 self.seekp = self.fp.tell()
  27.                 return None
  28.             start = self.fp.tell()
  29.             self._search_end()
  30.             self.seekp = stop = self.fp.tell()
  31.             if start != stop:
  32.                 break
  33.         return self.factory(_Subfile(self.fp, start, stop))
  34.  
  35.  
  36. class _Subfile:
  37.     def __init__(self, fp, start, stop):
  38.         self.fp = fp
  39.         self.start = start
  40.         self.stop = stop
  41.         self.pos = self.start
  42.  
  43.     def read(self, length = None):
  44.         if self.pos >= self.stop:
  45.             return ''
  46.         remaining = self.stop - self.pos
  47.         if length is None or length < 0:
  48.             length = remaining
  49.         elif length > remaining:
  50.             length = remaining
  51.         self.fp.seek(self.pos)
  52.         data = self.fp.read(length)
  53.         self.pos = self.fp.tell()
  54.         return data
  55.  
  56.     def readline(self, length = None):
  57.         if self.pos >= self.stop:
  58.             return ''
  59.         if length is None:
  60.             length = self.stop - self.pos
  61.         self.fp.seek(self.pos)
  62.         data = self.fp.readline(length)
  63.         self.pos = self.fp.tell()
  64.         return data
  65.  
  66.     def readlines(self, sizehint = -1):
  67.         lines = []
  68.         while 1:
  69.             line = self.readline()
  70.             if not line:
  71.                 break
  72.             lines.append(line)
  73.             if sizehint >= 0:
  74.                 sizehint = sizehint - len(line)
  75.                 if sizehint <= 0:
  76.                     break
  77.         return lines
  78.  
  79.     def tell(self):
  80.         return self.pos - self.start
  81.  
  82.     def seek(self, pos, whence=0):
  83.         if whence == 0:
  84.             self.pos = self.start + pos
  85.         elif whence == 1:
  86.             self.pos = self.pos + pos
  87.         elif whence == 2:
  88.             self.pos = self.stop + pos
  89.  
  90.     def close(self):
  91.         del self.fp
  92.  
  93.  
  94. class UnixMailbox(_Mailbox):
  95.     def _search_start(self):
  96.         while 1:
  97.             pos = self.fp.tell()
  98.             line = self.fp.readline()
  99.             if not line:
  100.                 raise EOFError
  101.             if line[:5] == 'From ' and self._isrealfromline(line):
  102.                 self.fp.seek(pos)
  103.                 return
  104.  
  105.     def _search_end(self):
  106.         self.fp.readline()      # Throw away header line
  107.         while 1:
  108.             pos = self.fp.tell()
  109.             line = self.fp.readline()
  110.             if not line:
  111.                 return
  112.             if line[:5] == 'From ' and self._isrealfromline(line):
  113.                 self.fp.seek(pos)
  114.                 return
  115.  
  116.     # An overridable mechanism to test for From-line-ness.  You can either
  117.     # specify a different regular expression or define a whole new
  118.     # _isrealfromline() method.  Note that this only gets called for lines
  119.     # starting with the 5 characters "From ".
  120.     #
  121.     # BAW: According to
  122.     #http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/content-length.html
  123.     # the only portable, reliable way to find message delimiters in a BSD (i.e
  124.     # Unix mailbox) style folder is to search for "\n\nFrom .*\n", or at the
  125.     # beginning of the file, "^From .*\n".  While _fromlinepattern below seems
  126.     # like a good idea, in practice, there are too many variations for more
  127.     # strict parsing of the line to be completely accurate.
  128.     #
  129.     # _strict_isrealfromline() is the old version which tries to do stricter
  130.     # parsing of the From_ line.  _portable_isrealfromline() simply returns
  131.     # true, since it's never called if the line doesn't already start with
  132.     # "From ".
  133.     #
  134.     # This algorithm, and the way it interacts with _search_start() and
  135.     # _search_end() may not be completely correct, because it doesn't check
  136.     # that the two characters preceding "From " are \n\n or the beginning of
  137.     # the file.  Fixing this would require a more extensive rewrite than is
  138.     # necessary.  For convenience, we've added a StrictUnixMailbox class which
  139.     # uses the older, more strict _fromlinepattern regular expression.
  140.  
  141.     _fromlinepattern = r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" \
  142.                        r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*$"
  143.     _regexp = None
  144.  
  145.     def _strict_isrealfromline(self, line):
  146.         if not self._regexp:
  147.             import re
  148.             self._regexp = re.compile(self._fromlinepattern)
  149.         return self._regexp.match(line)
  150.  
  151.     def _portable_isrealfromline(self, line):
  152.         return 1
  153.  
  154.     _isrealfromline = _strict_isrealfromline
  155.  
  156.  
  157. class PortableUnixMailbox(UnixMailbox):
  158.     _isrealfromline = UnixMailbox._portable_isrealfromline
  159.  
  160.  
  161. class MmdfMailbox(_Mailbox):
  162.     def _search_start(self):
  163.         while 1:
  164.             line = self.fp.readline()
  165.             if not line:
  166.                 raise EOFError
  167.             if line[:5] == '\001\001\001\001\n':
  168.                 return
  169.  
  170.     def _search_end(self):
  171.         while 1:
  172.             pos = self.fp.tell()
  173.             line = self.fp.readline()
  174.             if not line:
  175.                 return
  176.             if line == '\001\001\001\001\n':
  177.                 self.fp.seek(pos)
  178.                 return
  179.  
  180.  
  181. class MHMailbox:
  182.     def __init__(self, dirname, factory=rfc822.Message):
  183.         import re
  184.         pat = re.compile('^[1-9][0-9]*$')
  185.         self.dirname = dirname
  186.         # the three following lines could be combined into:
  187.         # list = map(long, filter(pat.match, os.listdir(self.dirname)))
  188.         list = os.listdir(self.dirname)
  189.         list = filter(pat.match, list)
  190.         list = map(long, list)
  191.         list.sort()
  192.         # This only works in Python 1.6 or later;
  193.         # before that str() added 'L':
  194.         self.boxes = map(str, list)
  195.         self.factory = factory
  196.  
  197.     def __iter__(self):
  198.         return iter(self.next, None)
  199.  
  200.     def next(self):
  201.         if not self.boxes:
  202.             return None
  203.         fn = self.boxes[0]
  204.         del self.boxes[0]
  205.         fp = open(os.path.join(self.dirname, fn))
  206.         return self.factory(fp)
  207.  
  208.  
  209. class Maildir:
  210.     # Qmail directory mailbox
  211.  
  212.     def __init__(self, dirname, factory=rfc822.Message):
  213.         self.dirname = dirname
  214.         self.factory = factory
  215.  
  216.         # check for new mail
  217.         newdir = os.path.join(self.dirname, 'new')
  218.         boxes = [os.path.join(newdir, f)
  219.                  for f in os.listdir(newdir) if f[0] != '.']
  220.  
  221.         # Now check for current mail in this maildir
  222.         curdir = os.path.join(self.dirname, 'cur')
  223.         boxes += [os.path.join(curdir, f)
  224.                   for f in os.listdir(curdir) if f[0] != '.']
  225.  
  226.         self.boxes = boxes
  227.  
  228.     def __iter__(self):
  229.         return iter(self.next, None)
  230.  
  231.     def next(self):
  232.         if not self.boxes:
  233.             return None
  234.         fn = self.boxes[0]
  235.         del self.boxes[0]
  236.         fp = open(fn)
  237.         return self.factory(fp)
  238.  
  239.  
  240. class BabylMailbox(_Mailbox):
  241.     def _search_start(self):
  242.         while 1:
  243.             line = self.fp.readline()
  244.             if not line:
  245.                 raise EOFError
  246.             if line == '*** EOOH ***\n':
  247.                 return
  248.  
  249.     def _search_end(self):
  250.         while 1:
  251.             pos = self.fp.tell()
  252.             line = self.fp.readline()
  253.             if not line:
  254.                 return
  255.             if line == '\037\014\n':
  256.                 self.fp.seek(pos)
  257.                 return
  258.  
  259.  
  260. def _test():
  261.     import sys
  262.  
  263.     args = sys.argv[1:]
  264.     if not args:
  265.         for key in 'MAILDIR', 'MAIL', 'LOGNAME', 'USER':
  266.             if os.environ.has_key(key):
  267.                 mbox = os.environ[key]
  268.                 break
  269.         else:
  270.             print "$MAIL, $LOGNAME nor $USER set -- who are you?"
  271.             return
  272.     else:
  273.         mbox = args[0]
  274.     if mbox[:1] == '+':
  275.         mbox = os.environ['HOME'] + '/Mail/' + mbox[1:]
  276.     elif not '/' in mbox:
  277.         mbox = '/usr/mail/' + mbox
  278.     if os.path.isdir(mbox):
  279.         if os.path.isdir(os.path.join(mbox, 'cur')):
  280.             mb = Maildir(mbox)
  281.         else:
  282.             mb = MHMailbox(mbox)
  283.     else:
  284.         fp = open(mbox, 'r')
  285.         mb = UnixMailbox(fp)
  286.  
  287.     msgs = []
  288.     while 1:
  289.         msg = mb.next()
  290.         if msg is None:
  291.             break
  292.         msgs.append(msg)
  293.         if len(args) <= 1:
  294.             msg.fp = None
  295.     if len(args) > 1:
  296.         num = int(args[1])
  297.         print 'Message %d body:'%num
  298.         msg = msgs[num-1]
  299.         msg.rewindbody()
  300.         sys.stdout.write(msg.fp.read())
  301.     else:
  302.         print 'Mailbox',mbox,'has',len(msgs),'messages:'
  303.         for msg in msgs:
  304.             f = msg.getheader('from') or ""
  305.             s = msg.getheader('subject') or ""
  306.             d = msg.getheader('date') or ""
  307.             print '-%20.20s   %20.20s   %-30.30s'%(f, d[5:], s)
  308.  
  309.  
  310. if __name__ == '__main__':
  311.     _test()
  312.