summaryrefslogtreecommitdiff
path: root/Mailman/Queue/Switchboard.py
diff options
context:
space:
mode:
authorbwarsaw2001-02-15 04:52:18 +0000
committerbwarsaw2001-02-15 04:52:18 +0000
commit09d60e694bd1d3ff77fe0666bfe2ac8454974421 (patch)
tree1b5d1985690e75a90b4ee36b44fcfeef37add051 /Mailman/Queue/Switchboard.py
parent7527eb57a5ae9920a4c95f4a360c89d3d083f156 (diff)
downloadmailman-09d60e694bd1d3ff77fe0666bfe2ac8454974421.tar.gz
mailman-09d60e694bd1d3ff77fe0666bfe2ac8454974421.tar.zst
mailman-09d60e694bd1d3ff77fe0666bfe2ac8454974421.zip
Diffstat (limited to 'Mailman/Queue/Switchboard.py')
-rw-r--r--Mailman/Queue/Switchboard.py230
1 files changed, 230 insertions, 0 deletions
diff --git a/Mailman/Queue/Switchboard.py b/Mailman/Queue/Switchboard.py
new file mode 100644
index 000000000..6f8023fb4
--- /dev/null
+++ b/Mailman/Queue/Switchboard.py
@@ -0,0 +1,230 @@
+# Copyright (C) 2000,2001 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+"""Reading and writing message objects and message metadata.
+"""
+
+# enqueue() and dequeue() are not symmetric. enqueue() takes a Message
+# object. dequeue() returns a mimelib.Message object tree.
+#
+# Message metadata is represented internally as a Python dictionary. Keys and
+# values must be strings. When written to a queue directory, the metadata is
+# written into an externally represented format, as defined here. Because
+# components of the Mailman system may be written in something other than
+# Python, the external interchange format should be chosen based on what those
+# other components can read and write.
+#
+# Most efficient, and recommended if everything is Python, is Python marshal
+# format. Also supported by default is Berkeley db format (using the default
+# bsddb module compiled into your Python executable -- usually Berkeley db
+# 2), and rfc822 style plain text. You can write your own if you have other
+# needs.
+
+import os
+import time
+import sha
+import marshal
+from errno import EEXIST
+
+from mimelib.Parser import Parser
+
+from Mailman import mm_cfg
+from Mailman import Utils
+from Mailman import Message
+from Mailman.Logging.Syslog import syslog
+
+# 20 bytes of all bits set, maximum sha.digest() value
+shamax = 0xffffffffffffffffffffffffffffffffffffffffL
+
+
+
+class _Switchboard:
+ def __init__(self, whichq, slice=None, numslices=1):
+ self.__whichq = whichq
+ # Create the directory if it doesn't yet exist.
+ # FIXME
+ omask = os.umask(0) # rwxrws---
+ try:
+ try:
+ os.mkdir(self.__whichq, 0770)
+ except OSError, e:
+ if e.errno <> EEXIST: raise
+ finally:
+ os.umask(omask)
+ # Fast track for no slices
+ self.__lower = None
+ self.__upper = None
+ # BAW: test performance and end-cases of this algorithm
+ if numslices <> 1:
+ self.__lower = (shamax * slice) / numslices
+ self.__upper = (shamax * (slice+1)) / numslices
+
+ def enqueue(self, _msg, _metadata={}, **_kws):
+ # Calculate the SHA hexdigest of the message to get a unique base
+ # filename.
+ data = _metadata.copy()
+ data.update(_kws)
+ listname = data.get('listname', '--nolist--')
+ now = `time.time()`
+ msgtext = str(_msg)
+ hashfood = msgtext + listname + now
+ filebase = sha.new(hashfood).hexdigest()
+ # Figure out which queue files the message is to be written to.
+ msgfile = os.path.join(self.__whichq, filebase + '.msg')
+ dbfile = os.path.join(self.__whichq, filebase + '.db')
+ # Always add the metadata schema version number
+ data['version'] = mm_cfg.QFILE_SCHEMA_VERSION
+ # Filter out volatile entries
+ for k in data.keys():
+ if k[0] == '_':
+ del data[k]
+ # Now write the message text to one file and the metadata to another
+ # file. The metadata is always written second to avoid race
+ # conditions with the various queue runners (which key off of the .db
+ # filename).
+ omask = os.umask(007) # -rw-rw----
+ try:
+ msgfp = open(msgfile, 'w')
+ finally:
+ os.umask(omask)
+ msgfp.write(msgtext)
+ msgfp.close()
+ # Now write the metadata using the appropriate external metadata
+ # format. We play rename-switcheroo here to further plug the race
+ # condition holes.
+ tmpfile = dbfile + '.tmp'
+ self._ext_write(tmpfile, data)
+ os.rename(tmpfile, dbfile)
+
+ def dequeue(self, filebase):
+ # Calculate the .db and .msg filenames from the given filebase.
+ msgfile = os.path.join(self.__whichq, filebase + '.msg')
+ dbfile = os.path.join(self.__whichq, filebase + '.db')
+ # Read the message text and parse it into a message object tree. When
+ # done, unlink the msg file.
+ msgfp = open(msgfile)
+ p = Parser(_class=Message.Message)
+ msg = p.parse(msgfp)
+ msgfp.close()
+ os.unlink(msgfile)
+ # Now, read the metadata using the appropriate external metadata
+ # format. When done, unlink the metadata file.
+ data = self._ext_read(dbfile)
+ os.unlink(dbfile)
+ return msg, data
+
+ def files(self):
+ all = [os.path.splitext(f)[0] for f in os.listdir(self.__whichq)
+ if f.endswith('.db')]
+ # Fast track exit
+ if self.__lower is None:
+ return all
+ # BAW: test performance and end-cases of this algorithm
+ return [f for f in all if self.__lower <= long(f, 16) < self.__upper]
+
+ def _ext_write(self, tmpfile, data):
+ raise UnimplementedError
+
+ def _ext_read(self, dbfile):
+ raise UnimplementedError
+
+
+
+class MarshalSwitchboard(_Switchboard):
+ """Python marshal format."""
+ def _ext_write(self, filename, dict):
+ omask = os.umask(007) # -rw-rw----
+ try:
+ fp = open(filename, 'w')
+ finally:
+ os.umask(omask)
+ marshal.dump(dict, fp)
+ fp.close()
+
+ def _ext_read(self, filename):
+ fp = open(filename)
+ data = marshal.load(fp)
+ fp.close()
+ return data
+
+
+
+class BSDDBSwitchboard(_Switchboard):
+ """Native (i.e. compiled-in) Berkeley db format."""
+ def _ext_write(self, filename, dict):
+ import bsddb
+ omask = os.umask(0)
+ try:
+ hashfile = bsddb.hashopen(filename, 'n', 0660)
+ finally:
+ os.umask(omask)
+ # values must be strings
+ for k, v in dict.items():
+ hashfile[k] = marshal.dumps(v)
+ hashfile.sync()
+ hashfile.close()
+
+ def _ext_read(self, filename):
+ import bsddb
+ dict = {}
+ hashfile = bsddb.hashopen(filename, 'r')
+ for k in hashfile.keys():
+ dict[k] = marshal.loads(hashfile[k])
+ hashfile.close()
+ return dict
+
+
+
+class ASCIISwitchboard(_Switchboard):
+ """Human readable .db file format.
+
+ key/value pairs are written as
+
+ key = value
+
+ as real Python code which can be execfile'd.
+ """
+
+ def _ext_write(self, filename, dict):
+ omask = os.umask(007) # -rw-rw----
+ try:
+ fp = open(filename, 'w')
+ finally:
+ os.umask(omask)
+ for k, v in dict.items():
+ print >> fp, '%s = %s' % (k, repr(v))
+ fp.close()
+
+ def _ext_read(self, filename):
+ dict = {'__builtins__': {}}
+ execfile(filename, dict)
+ del dict['__builtins__']
+ return dict
+
+
+
+# Here are the various types of external file formats available. The format
+# chosen is given defined in the mm_cfg.py configuration file.
+if mm_cfg.METADATA_FORMAT == mm_cfg.METAFMT_MARSHAL:
+ Switchboard = MarshalSwitchboard
+elif mm_cfg.METADATA_FORMAT == mm_cfg.METAFMT_BSDDB_NATIVE:
+ Switchboard = BSDDBSwitchboard
+elif mm_cfg.METADATA_FORMAT == mm_cfg.METAFMT_ASCII:
+ Switchboard = ASCIISwitchboard
+else:
+ syslog('error', 'Undefined metadata format: %d (using marshals)' %
+ mm_cfg.METADATA_FORMAT)
+ Switchboard = MarshalSwitchboard