[prev in list] [next in list] [prev in thread] [next in thread]
List: mailman-cvs
Subject: [Mailman-checkins] SF.net SVN: mailman: [7945]
From: mindlace23 () users ! sourceforge ! net
Date: 2006-07-17 4:01:57
Message-ID: E1G2KIv-0008GP-A3 () sc8-pr-svn1 ! sourceforge ! net
[Download RAW message or body]
Revision: 7945
Author: mindlace23
Date: 2006-07-16 21:01:43 -0700 (Sun, 16 Jul 2006)
ViewCVS: http://svn.sourceforge.net/mailman/?rev=7945&view=rev
Log Message:
-----------
merging changes from trunk
Modified Paths:
--------------
branches/soc2006-webui/Mailman/Defaults.py.in
branches/soc2006-webui/Mailman/MailList.py
branches/soc2006-webui/Mailman/MemberAdaptor.py
branches/soc2006-webui/Mailman/OldStyleMemberships.py
branches/soc2006-webui/Mailman/Queue/Makefile.in
branches/soc2006-webui/Mailman/Queue/Runner.py
branches/soc2006-webui/Mailman/Queue/Switchboard.py
branches/soc2006-webui/Mailman/bin/unshunt.py
branches/soc2006-webui/Mailman/configuration.py
branches/soc2006-webui/Mailman/testing/base.py
branches/soc2006-webui/Mailman/testing/emailbase.py
branches/soc2006-webui/Makefile.in
branches/soc2006-webui/configure
branches/soc2006-webui/configure.in
branches/soc2006-webui/misc/mailman.cfg.sample
Added Paths:
-----------
branches/soc2006-webui/Mailman/Queue/tests/
branches/soc2006-webui/Mailman/Queue/tests/Makefile.in
branches/soc2006-webui/Mailman/Queue/tests/__init__.py
branches/soc2006-webui/Mailman/Queue/tests/test_runners.py
branches/soc2006-webui/Mailman/SAMemberships.py
Removed Paths:
-------------
branches/soc2006-webui/Mailman/Queue/tests/Makefile.in
branches/soc2006-webui/Mailman/Queue/tests/__init__.py
branches/soc2006-webui/Mailman/Queue/tests/test_runners.py
branches/soc2006-webui/Mailman/testing/test_runners.py
Modified: branches/soc2006-webui/Mailman/Defaults.py.in
===================================================================
--- branches/soc2006-webui/Mailman/Defaults.py.in 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/Mailman/Defaults.py.in 2006-07-17 04:01:43 UTC (rev 7945)
@@ -97,6 +97,27 @@
#####
+# Database options
+#####
+
+# Specify the name of the membership adaptor class that implements the
+# MemberAdaptor interface you want to use. The first is traditional
+# pickle-based adapter that was standard for Mailman 2.1 and earlier.
+MEMBER_ADAPTOR_CLASS = 'Mailman.OldStyleMemberships.OldStyleMemberships'
+
+# This is the SQLAlchemy-based adaptor which lets you store all membership
+# data in any of several supported RDBMs.
+#MEMBER_ADAPTOR_CLASS = 'Mailman.SAMemberships.SAMemberships'
+
+# If you choose the SAMemberships adaptor, set this variable to specify the
+# connection url to the backend database engine. Specify the placeholder
+# $listdir for the directory that list data is stored in, and $listname for
+# the name of the mailing list.
+SQLALCHEMY_ENGINE_URL = 'sqlite:///$listdir/members.db'
+
+
+
+#####
# Virtual domains
#####
Modified: branches/soc2006-webui/Mailman/MailList.py
===================================================================
--- branches/soc2006-webui/Mailman/MailList.py 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/Mailman/MailList.py 2006-07-17 04:01:43 UTC (rev 7945)
@@ -68,10 +68,10 @@
from Mailman import i18n
from Mailman import MemberAdaptor
from Mailman import Message
-from Mailman.OldStyleMemberships import OldStyleMemberships
_ = i18n._
+DOT = '.'
EMPTYSTRING = ''
OR = '|'
@@ -89,7 +89,7 @@
#
# A MailList object's basic Python object model support
#
- def __init__(self, name=None, lock=1):
+ def __init__(self, name=None, lock=True):
# No timeout by default. If you want to timeout, open the list
# unlocked, then lock explicitly.
#
@@ -99,8 +99,13 @@
baseclass.__init__(self)
# Initialize volatile attributes
self.InitTempVars(name)
- # Default membership adaptor class
- self._memberadaptor = OldStyleMemberships(self)
+ # Attach a membership adaptor instance.
+ parts = config.MEMBER_ADAPTOR_CLASS.split(DOT)
+ adaptor_class = parts.pop()
+ adaptor_module = DOT.join(parts)
+ __import__(adaptor_module)
+ mod = sys.modules[adaptor_module]
+ self._memberadaptor = getattr(mod, adaptor_class)(self)
# This extension mechanism allows list-specific overrides of any
# method (well, except __init__(), InitTempVars(), and InitVars()
# I think). Note that fullpath() will return None when we're creating
@@ -157,6 +162,7 @@
#
def Lock(self, timeout=0):
self.__lock.lock(timeout)
+ self._memberadaptor.lock()
# Must reload our database for consistency. Watch out for lists that
# don't exist.
try:
@@ -166,7 +172,8 @@
raise
def Unlock(self):
- self.__lock.unlock(unconditionally=1)
+ self.__lock.unlock(unconditionally=True)
+ self._memberadaptor.unlock()
def Locked(self):
return self.__lock.locked()
@@ -280,9 +287,10 @@
lifetime=config.LIST_LOCK_LIFETIME)
# XXX FIXME Sometimes name is fully qualified, sometimes it's not.
if name and '@' in name:
- self._internal_name, email_host = name.split('@', 1)
+ self._internal_name, self.host_name = name.split('@', 1)
else:
self._internal_name = name
+ self.host_name = config.DEFAULT_EMAIL_HOST
if name:
self._full_path = os.path.join(config.LIST_DATA_DIR, name)
else:
@@ -571,6 +579,7 @@
# the lock (which is a serious problem!). TBD: do we need to be more
# defensive?
self.__lock.refresh()
+ self._memberadaptor.save()
# copy all public attributes to serializable dictionary
dict = {}
for key, value in self.__dict__.items():
@@ -639,6 +648,7 @@
fqdn_listname = self.fqdn_listname
if not Utils.list_exists(fqdn_listname):
raise Errors.MMUnknownListError
+ self._memberadaptor.load()
# We first try to load config.pck, which contains the up-to-date
# version of the database. If that fails, perhaps because it's
# corrupted or missing, we'll try to load the backup file
Modified: branches/soc2006-webui/Mailman/MemberAdaptor.py
===================================================================
--- branches/soc2006-webui/Mailman/MemberAdaptor.py 2006-07-16 23:21:32 UTC (rev \
7944)
+++ branches/soc2006-webui/Mailman/MemberAdaptor.py 2006-07-17 04:01:43 UTC (rev \
7945) @@ -1,4 +1,4 @@
-# Copyright (C) 2001-2003 by the Free Software Foundation, Inc.
+# Copyright (C) 2001-2006 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
@@ -12,7 +12,8 @@
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
"""This is an interface to list-specific membership information.
@@ -56,7 +57,48 @@
class MemberAdaptor:
+ def __init__(self, mlist):
+ """Create the adaptor, attached to the given mailing list."""
+ raise NotImplementedError
+
#
+ # Transaction interface
+ #
+ def load(self):
+ """Called when the mailing list data is loaded.
+
+ The adaptor should refresh/clear all in-memory objects. The reason is
+ that other processes may have changed the database since the last time
+ the adaptor has been accessed.
+ """
+
+ def lock(self):
+ """Called when the mailing list is locked.
+
+ This should correspond to a database 'begin'.
+ """
+ raise NotImplementedError
+
+ def save(self):
+ """Called when a locked mailing list is saved.
+
+ This should correspond to a database 'commit' except that the adaptor
+ should record that the database has been saved, and this flag should
+ be checked in any subsequent unlock() calls.
+ """
+ raise NotImplementedError
+
+ def unlock(self):
+ """Called when a locked mailing list is unlocked.
+
+ Generally, this can be no-op'd if save() was previously called, but if
+ not, then a database 'rollback' should be issued. There is no
+ explicit transaction rollback operation in the MailList API, but
+ processes will unlock without saving to mean the same thing.
+ """
+ raise NotImplementedError
+
+ #
# The readable interface
#
def getMembers(self):
@@ -181,6 +223,9 @@
def getDeliveryStatusChangeTime(self, member):
"""Return the time of the last disabled delivery status change.
+ Time is returned in float seconds since the epoch. XXX this should be
+ a Python datetime.
+
If the current delivery status is ENABLED, the status change time will
be zero. If member is not a member of the list, raise
NotAMemberError.
@@ -254,7 +299,7 @@
"""
raise NotImplementedError
- def changeMemberAddress(self, memberkey, newaddress, nodelete=0):
+ def changeMemberAddress(self, memberkey, newaddress, nodelete=False):
"""Change the address for the member KEY.
memberkey will be a KEY, not an LCE. newaddress should be the
@@ -273,8 +318,6 @@
"""Set the password for member LCE/KEY.
If member does not refer to a valid member, raise NotAMemberError.
- Also raise BadPasswordError if the password is illegal (e.g. too
- short or easily guessed via a dictionary attack).
"""
raise NotImplementedError
@@ -282,8 +325,6 @@
"""Set the language for the member LCE/KEY.
If member does not refer to a valid member, raise NotAMemberError.
- Also raise BadLanguageError if the language is invalid (e.g. the list
- is not configured to support the given language).
"""
raise NotImplementedError
@@ -294,8 +335,6 @@
Default.py, and value is a boolean.
If member does not refer to a valid member, raise NotAMemberError.
- Also raise BadOptionError if the flag does not refer to a valid
- option.
"""
raise NotImplementedError
Modified: branches/soc2006-webui/Mailman/OldStyleMemberships.py
===================================================================
--- branches/soc2006-webui/Mailman/OldStyleMemberships.py 2006-07-16 23:21:32 UTC \
(rev 7944)
+++ branches/soc2006-webui/Mailman/OldStyleMemberships.py 2006-07-17 04:01:43 UTC \
(rev 7945) @@ -47,6 +47,17 @@
self.__mlist = mlist
#
+ # Transaction interface
+ #
+
+ # These are all no-op'd because the data is all attached to and managed by
+ # the MailList object.
+ def load(self): pass
+ def lock(self): pass
+ def save(self): pass
+ def unlock(self): pass
+
+ #
# Read interface
#
def getMembers(self):
@@ -271,6 +282,10 @@
# toggling the Digests flag, then we need to move their entry from
# mlist.members to mlist.digest_members or vice versa. Blarg. Do
# this before the flag setting below in case it fails.
+ #
+ # XXX Adaptors should not be doing these semantic integrity checks,
+ # but for backward compatibility I'm not changing this. New adaptors
+ # should not mimic this behavior.
if flag == mm_cfg.Digests:
if value:
# Be sure the list supports digest delivery
Modified: branches/soc2006-webui/Mailman/Queue/Makefile.in
===================================================================
--- branches/soc2006-webui/Mailman/Queue/Makefile.in 2006-07-16 23:21:32 UTC (rev \
7944)
+++ branches/soc2006-webui/Mailman/Queue/Makefile.in 2006-07-17 04:01:43 UTC (rev \
7945) @@ -41,6 +41,7 @@
SHELL= /bin/sh
MODULES= *.py
+SUBDIRS= tests
# Modes for directories and executables created by the install
# process. Default to group-writable directories but
@@ -54,17 +55,37 @@
# Rules
all:
+ for d in $(SUBDIRS); \
+ do \
+ (cd $$d; $(MAKE)); \
+ done
install:
for f in $(MODULES); \
do \
$(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \
done
+ for d in $(SUBDIRS); \
+ do \
+ (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) install); \
+ done
finish:
+ @for d in $(SUBDIRS); \
+ do \
+ (cd $$d; $(MAKE) DESTDIR=$(DESTDIR) finish); \
+ done
clean:
+ for d in $(SUBDIRS); \
+ do \
+ (cd $$d; $(MAKE) clean); \
+ done
distclean:
-rm *.pyc
-rm Makefile
+ for d in $(SUBDIRS); \
+ do \
+ (cd $$d; $(MAKE) distclean); \
+ done
Modified: branches/soc2006-webui/Mailman/Queue/Runner.py
===================================================================
--- branches/soc2006-webui/Mailman/Queue/Runner.py 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/Mailman/Queue/Runner.py 2006-07-17 04:01:43 UTC (rev 7945)
@@ -19,8 +19,8 @@
import time
import weakref
+import logging
import traceback
-import logging
import email.Errors
from cStringIO import StringIO
@@ -44,7 +44,7 @@
self._kids = {}
# Create our own switchboard. Don't use the switchboard cache because
# we want to provide slice and numslice arguments.
- self._switchboard = Switchboard(self.QDIR, slice, numslices)
+ self._switchboard = Switchboard(self.QDIR, slice, numslices, True)
# Create the shunt switchboard
self._shunt = Switchboard(config.SHUNTQUEUE_DIR)
self._stop = False
@@ -104,6 +104,7 @@
continue
try:
self._onefile(msg, msgdata)
+ self._switchboard.finish(filebase)
except Exception, e:
# All runners that implement _dispose() must guarantee that
# exceptions are caught and dealt with properly. Still, there
@@ -114,8 +115,9 @@
self._log(e)
# Put a marker in the metadata for unshunting
msgdata['whichq'] = self._switchboard.whichq()
- filebase = self._shunt.enqueue(msg, msgdata)
- log.error('SHUNTING: %s', filebase)
+ new_filebase = self._shunt.enqueue(msg, msgdata)
+ log.error('SHUNTING: %s', new_filebase)
+ self._switchboard.finish(filebase)
# Other work we want to do each time through the loop
Utils.reap(self._kids, once=True)
self._doperiodic()
Modified: branches/soc2006-webui/Mailman/Queue/Switchboard.py
===================================================================
--- branches/soc2006-webui/Mailman/Queue/Switchboard.py 2006-07-16 23:21:32 UTC (rev \
7944)
+++ branches/soc2006-webui/Mailman/Queue/Switchboard.py 2006-07-17 04:01:43 UTC (rev \
7945) @@ -39,6 +39,7 @@
import email
import errno
import cPickle
+import logging
import marshal
from Mailman import Message
@@ -59,7 +60,7 @@
class Switchboard:
- def __init__(self, whichq, slice=None, numslices=1):
+ def __init__(self, whichq, slice=None, numslices=1, recover=False):
self.__whichq = whichq
# Create the directory if it doesn't yet exist.
# FIXME
@@ -78,6 +79,8 @@
if numslices <> 1:
self.__lower = ((shamax+1) * slice) / numslices
self.__upper = (((shamax+1) * (slice+1)) / numslices) - 1
+ if recover:
+ self.recover_backup_files()
def whichq(self):
return self.__whichq
@@ -135,9 +138,16 @@
def dequeue(self, filebase):
# Calculate the filename from the given filebase.
filename = os.path.join(self.__whichq, filebase + '.pck')
+ backfile = os.path.join(self.__whichq, filebase + '.bak')
# Read the message object and metadata.
fp = open(filename)
- os.unlink(filename)
+ # Move the file to the backup file name for processing. If this
+ # process crashes uncleanly the .bak file will be used to re-instate
+ # the .pck file in order to try again. XXX what if something caused
+ # Python to constantly crash? Is it possible that we'd end up mail
+ # bombing recipients or crushing the archiver? How would we defend
+ # against that?
+ os.rename(filename, backfile)
try:
msg = cPickle.load(fp)
data = cPickle.load(fp)
@@ -147,26 +157,42 @@
msg = email.message_from_string(msg, Message.Message)
return msg, data
- def files(self):
+ def finish(self, filebase):
+ bakfile = os.path.join(self.__whichq, filebase + '.bak')
+ try:
+ os.unlink(bakfile)
+ except EnvironmentError, e:
+ log.exception('Failed to unlink backup file: %s', bakfile)
+
+ def files(self, extension='.pck'):
times = {}
lower = self.__lower
upper = self.__upper
for f in os.listdir(self.__whichq):
# By ignoring anything that doesn't end in .pck, we ignore
# tempfiles and avoid a race condition.
- if not f.endswith('.pck'):
+ filebase, ext = os.path.splitext(f)
+ if ext <> extension:
continue
- filebase = os.path.splitext(f)[0]
when, digest = filebase.split('+')
# Throw out any files which don't match our bitrange. BAW: test
# performance and end-cases of this algorithm. MAS: both
# comparisons need to be <= to get complete range.
if lower is None or (lower <= long(digest, 16) <= upper):
key = float(when)
- while times.has_key(key):
+ while key in times:
key += DELTA
times[key] = filebase
# FIFO sort
keys = times.keys()
keys.sort()
return [times[k] for k in keys]
+
+ def recover_backup_files(self):
+ # Move all .bak files in our slice to .pck. It's impossible for both
+ # to exist at the same time, so the move is enough to ensure that our
+ # normal dequeuing process will handle them.
+ for filebase in self.files('.bak'):
+ src = os.path.join(self.__whichq, filebase + '.bak')
+ dst = os.path.join(self.__whichq, filebase + '.pck')
+ os.rename(src, dst)
Copied: branches/soc2006-webui/Mailman/Queue/tests (from rev 7944, \
trunk/mailman/Mailman/Queue/tests)
Property changes on: branches/soc2006-webui/Mailman/Queue/tests
___________________________________________________________________
Name: svn:ignore
+ Makefile
Deleted: branches/soc2006-webui/Mailman/Queue/tests/Makefile.in
===================================================================
--- trunk/mailman/Mailman/Queue/tests/Makefile.in 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/Mailman/Queue/tests/Makefile.in 2006-07-17 04:01:43 UTC \
(rev 7945) @@ -1,71 +0,0 @@
-# Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-# NOTE: Makefile.in is converted into Makefile by the configure script
-# in the parent directory. Once configure has run, you can recreate
-# the Makefile by running just config.status.
-
-# Variables set by configure
-
-VPATH= @srcdir@
-srcdir= @srcdir@
-bindir= @bindir@
-prefix= @prefix@
-exec_prefix= @exec_prefix@
-DESTDIR=
-
-CC= @CC@
-CHMOD= @CHMOD@
-INSTALL= @INSTALL@
-
-DEFS= @DEFS@
-
-# Customizable but not set by configure
-
-OPT= @OPT@
-CFLAGS= $(OPT) $(DEFS)
-PACKAGEDIR= $(prefix)/Mailman/Queue/tests
-SHELL= /bin/sh
-
-MODULES= *.py
-
-# Modes for directories and executables created by the install
-# process. Default to group-writable directories but
-# user-only-writable for executables.
-DIRMODE= 775
-EXEMODE= 755
-FILEMODE= 644
-INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE)
-
-
-# Rules
-
-all:
-
-install:
- for f in $(MODULES); \
- do \
- $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \
- done
-
-finish:
-
-clean:
-
-distclean:
- -rm *.pyc
- -rm Makefile
Copied: branches/soc2006-webui/Mailman/Queue/tests/Makefile.in (from rev 7944, \
trunk/mailman/Mailman/Queue/tests/Makefile.in) \
===================================================================
--- branches/soc2006-webui/Mailman/Queue/tests/Makefile.in \
(rev 0)
+++ branches/soc2006-webui/Mailman/Queue/tests/Makefile.in 2006-07-17 04:01:43 UTC \
(rev 7945) @@ -0,0 +1,71 @@
+# Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+# NOTE: Makefile.in is converted into Makefile by the configure script
+# in the parent directory. Once configure has run, you can recreate
+# the Makefile by running just config.status.
+
+# Variables set by configure
+
+VPATH= @srcdir@
+srcdir= @srcdir@
+bindir= @bindir@
+prefix= @prefix@
+exec_prefix= @exec_prefix@
+DESTDIR=
+
+CC= @CC@
+CHMOD= @CHMOD@
+INSTALL= @INSTALL@
+
+DEFS= @DEFS@
+
+# Customizable but not set by configure
+
+OPT= @OPT@
+CFLAGS= $(OPT) $(DEFS)
+PACKAGEDIR= $(prefix)/Mailman/Queue/tests
+SHELL= /bin/sh
+
+MODULES= *.py
+
+# Modes for directories and executables created by the install
+# process. Default to group-writable directories but
+# user-only-writable for executables.
+DIRMODE= 775
+EXEMODE= 755
+FILEMODE= 644
+INSTALL_PROGRAM=$(INSTALL) -m $(EXEMODE)
+
+
+# Rules
+
+all:
+
+install:
+ for f in $(MODULES); \
+ do \
+ $(INSTALL) -m $(FILEMODE) $(srcdir)/$$f $(DESTDIR)$(PACKAGEDIR); \
+ done
+
+finish:
+
+clean:
+
+distclean:
+ -rm *.pyc
+ -rm Makefile
Deleted: branches/soc2006-webui/Mailman/Queue/tests/__init__.py
===================================================================
Copied: branches/soc2006-webui/Mailman/Queue/tests/__init__.py (from rev 7944, \
trunk/mailman/Mailman/Queue/tests/__init__.py) \
===================================================================
Deleted: branches/soc2006-webui/Mailman/Queue/tests/test_runners.py
===================================================================
--- trunk/mailman/Mailman/Queue/tests/test_runners.py 2006-07-16 23:21:32 UTC (rev \
7944)
+++ branches/soc2006-webui/Mailman/Queue/tests/test_runners.py 2006-07-17 04:01:43 \
UTC (rev 7945) @@ -1,236 +0,0 @@
-# Copyright (C) 2001-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Unit tests for the various Mailman qrunner modules."""
-
-import os
-import email
-import shutil
-import tempfile
-import unittest
-
-from Mailman.Message import Message
-from Mailman.Queue.NewsRunner import prepare_message
-from Mailman.Queue.Runner import Runner
-from Mailman.Queue.Switchboard import Switchboard
-from Mailman.testing.base import TestBase
-
-
-
-class TestPrepMessage(TestBase):
- def test_remove_unacceptables(self):
- eq = self.assertEqual
- msg = email.message_from_string("""\
-From: aperson@dom.ain
-To: _xtest@dom.ain
-NNTP-Posting-Host: news.dom.ain
-NNTP-Posting-Date: today
-X-Trace: blah blah
-X-Complaints-To: abuse@dom.ain
-Xref: blah blah
-Xref: blah blah
-Date-Received: yesterday
-Posted: tomorrow
-Posting-Version: 99.99
-Relay-Version: 88.88
-Received: blah blah
-
-A message
-""")
- msgdata = {}
- prepare_message(self._mlist, msg, msgdata)
- eq(msgdata.get('prepped'), 1)
- eq(msg['from'], 'aperson@dom.ain')
- eq(msg['to'], '_xtest@dom.ain')
- eq(msg['nntp-posting-host'], None)
- eq(msg['nntp-posting-date'], None)
- eq(msg['x-trace'], None)
- eq(msg['x-complaints-to'], None)
- eq(msg['xref'], None)
- eq(msg['date-received'], None)
- eq(msg['posted'], None)
- eq(msg['posting-version'], None)
- eq(msg['relay-version'], None)
- eq(msg['received'], None)
-
- def test_munge_duplicates_no_duplicates(self):
- eq = self.assertEqual
- msg = email.message_from_string("""\
-From: aperson@dom.ain
-To: _xtest@dom.ain
-Cc: someother@dom.ain
-Content-Transfer-Encoding: yes
-
-A message
-""")
- msgdata = {}
- prepare_message(self._mlist, msg, msgdata)
- eq(msgdata.get('prepped'), 1)
- eq(msg['from'], 'aperson@dom.ain')
- eq(msg['to'], '_xtest@dom.ain')
- eq(msg['cc'], 'someother@dom.ain')
- eq(msg['content-transfer-encoding'], 'yes')
-
- def test_munge_duplicates(self):
- eq = self.assertEqual
- msg = email.message_from_string("""\
-From: aperson@dom.ain
-To: _xtest@dom.ain
-To: two@dom.ain
-Cc: three@dom.ain
-Cc: four@dom.ain
-Cc: five@dom.ain
-Content-Transfer-Encoding: yes
-Content-Transfer-Encoding: no
-Content-Transfer-Encoding: maybe
-
-A message
-""")
- msgdata = {}
- prepare_message(self._mlist, msg, msgdata)
- eq(msgdata.get('prepped'), 1)
- eq(msg.get_all('from'), ['aperson@dom.ain'])
- eq(msg.get_all('to'), ['_xtest@dom.ain'])
- eq(msg.get_all('cc'), ['three@dom.ain'])
- eq(msg.get_all('content-transfer-encoding'), ['yes'])
- eq(msg.get_all('x-original-to'), ['two@dom.ain'])
- eq(msg.get_all('x-original-cc'), ['four@dom.ain', 'five@dom.ain'])
- eq(msg.get_all('x-original-content-transfer-encoding'),
- ['no', 'maybe'])
-
-
-
-class TestSwitchboard(TestBase):
- def setUp(self):
- TestBase.setUp(self)
- self._tmpdir = tempfile.mkdtemp()
- self._msg = email.message_from_string("""\
-From: aperson@dom.ain
-To: _xtest@dom.ain
-
-A test message.
-""")
- self._sb = Switchboard(self._tmpdir)
-
- def tearDown(self):
- shutil.rmtree(self._tmpdir, True)
- TestBase.tearDown(self)
-
- def _count_qfiles(self):
- files = {}
- for qfile in os.listdir(self._tmpdir):
- root, ext = os.path.splitext(qfile)
- files[ext] = files.get(ext, 0) + 1
- return files
-
- def test_whichq(self):
- self.assertEqual(self._sb.whichq(), self._tmpdir)
-
- def test_enqueue(self):
- eq = self.assertEqual
- self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
- files = self._count_qfiles()
- eq(len(files), 1)
- eq(files['.pck'], 1)
-
- def test_dequeue(self):
- eq = self.assertEqual
- self._sb.enqueue(self._msg, listname='_xtest@dom.ain', foo='yes')
- count = 0
- for filebase in self._sb.files():
- msg, data = self._sb.dequeue(filebase)
- self._sb.finish(filebase)
- count += 1
- eq(msg['from'], self._msg['from'])
- eq(msg['to'], self._msg['to'])
- eq(data['foo'], 'yes')
- eq(count, 1)
- files = self._count_qfiles()
- eq(len(files), 0)
-
- def test_bakfile(self):
- eq = self.assertEqual
- self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
- self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
- self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
- for filebase in self._sb.files():
- self._sb.dequeue(filebase)
- files = self._count_qfiles()
- eq(len(files), 1)
- eq(files['.bak'], 3)
-
- def test_recover(self):
- eq = self.assertEqual
- self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
- for filebase in self._sb.files():
- self._sb.dequeue(filebase)
- # Not calling sb.finish() leaves .bak files
- sb2 = Switchboard(self._tmpdir, recover=True)
- files = self._count_qfiles()
- eq(len(files), 1)
- eq(files['.pck'], 1)
-
-
-
-class TestableRunner(Runner):
- def _dispose(self, mlist, msg, msgdata):
- self.msg = msg
- self.data = msgdata
- return False
-
- def _doperiodic(self):
- self.stop()
-
- def _snooze(self, filecnt):
- return
-
-
-class TestRunner(TestBase):
- def setUp(self):
- TestBase.setUp(self)
- self._tmpdir = tempfile.mkdtemp()
- self._msg = email.message_from_string("""\
-From: aperson@dom.ain
-To: _xtest@dom.ain
-
-A test message.
-""", Message)
- class MyRunner(TestableRunner):
- QDIR = self._tmpdir
- self._runner = MyRunner()
-
- def tearDown(self):
- shutil.rmtree(self._tmpdir, True)
- TestBase.tearDown(self)
-
- def test_run_loop(self):
- eq = self.assertEqual
- sb = Switchboard(self._tmpdir)
- sb.enqueue(self._msg, listname='_xtest@example.com', foo='yes')
- self._runner.run()
- eq(self._runner.msg['from'], self._msg['from'])
- eq(self._runner.msg['to'], self._msg['to'])
- eq(self._runner.data['foo'], 'yes')
-
-
-
-def test_suite():
- suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(TestPrepMessage))
- suite.addTest(unittest.makeSuite(TestSwitchboard))
- suite.addTest(unittest.makeSuite(TestRunner))
- return suite
Copied: branches/soc2006-webui/Mailman/Queue/tests/test_runners.py (from rev 7944, \
trunk/mailman/Mailman/Queue/tests/test_runners.py) \
===================================================================
--- branches/soc2006-webui/Mailman/Queue/tests/test_runners.py \
(rev 0)
+++ branches/soc2006-webui/Mailman/Queue/tests/test_runners.py 2006-07-17 04:01:43 \
UTC (rev 7945) @@ -0,0 +1,236 @@
+# Copyright (C) 2001-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""Unit tests for the various Mailman qrunner modules."""
+
+import os
+import email
+import shutil
+import tempfile
+import unittest
+
+from Mailman.Message import Message
+from Mailman.Queue.NewsRunner import prepare_message
+from Mailman.Queue.Runner import Runner
+from Mailman.Queue.Switchboard import Switchboard
+from Mailman.testing.base import TestBase
+
+
+
+class TestPrepMessage(TestBase):
+ def test_remove_unacceptables(self):
+ eq = self.assertEqual
+ msg = email.message_from_string("""\
+From: aperson@dom.ain
+To: _xtest@dom.ain
+NNTP-Posting-Host: news.dom.ain
+NNTP-Posting-Date: today
+X-Trace: blah blah
+X-Complaints-To: abuse@dom.ain
+Xref: blah blah
+Xref: blah blah
+Date-Received: yesterday
+Posted: tomorrow
+Posting-Version: 99.99
+Relay-Version: 88.88
+Received: blah blah
+
+A message
+""")
+ msgdata = {}
+ prepare_message(self._mlist, msg, msgdata)
+ eq(msgdata.get('prepped'), 1)
+ eq(msg['from'], 'aperson@dom.ain')
+ eq(msg['to'], '_xtest@dom.ain')
+ eq(msg['nntp-posting-host'], None)
+ eq(msg['nntp-posting-date'], None)
+ eq(msg['x-trace'], None)
+ eq(msg['x-complaints-to'], None)
+ eq(msg['xref'], None)
+ eq(msg['date-received'], None)
+ eq(msg['posted'], None)
+ eq(msg['posting-version'], None)
+ eq(msg['relay-version'], None)
+ eq(msg['received'], None)
+
+ def test_munge_duplicates_no_duplicates(self):
+ eq = self.assertEqual
+ msg = email.message_from_string("""\
+From: aperson@dom.ain
+To: _xtest@dom.ain
+Cc: someother@dom.ain
+Content-Transfer-Encoding: yes
+
+A message
+""")
+ msgdata = {}
+ prepare_message(self._mlist, msg, msgdata)
+ eq(msgdata.get('prepped'), 1)
+ eq(msg['from'], 'aperson@dom.ain')
+ eq(msg['to'], '_xtest@dom.ain')
+ eq(msg['cc'], 'someother@dom.ain')
+ eq(msg['content-transfer-encoding'], 'yes')
+
+ def test_munge_duplicates(self):
+ eq = self.assertEqual
+ msg = email.message_from_string("""\
+From: aperson@dom.ain
+To: _xtest@dom.ain
+To: two@dom.ain
+Cc: three@dom.ain
+Cc: four@dom.ain
+Cc: five@dom.ain
+Content-Transfer-Encoding: yes
+Content-Transfer-Encoding: no
+Content-Transfer-Encoding: maybe
+
+A message
+""")
+ msgdata = {}
+ prepare_message(self._mlist, msg, msgdata)
+ eq(msgdata.get('prepped'), 1)
+ eq(msg.get_all('from'), ['aperson@dom.ain'])
+ eq(msg.get_all('to'), ['_xtest@dom.ain'])
+ eq(msg.get_all('cc'), ['three@dom.ain'])
+ eq(msg.get_all('content-transfer-encoding'), ['yes'])
+ eq(msg.get_all('x-original-to'), ['two@dom.ain'])
+ eq(msg.get_all('x-original-cc'), ['four@dom.ain', 'five@dom.ain'])
+ eq(msg.get_all('x-original-content-transfer-encoding'),
+ ['no', 'maybe'])
+
+
+
+class TestSwitchboard(TestBase):
+ def setUp(self):
+ TestBase.setUp(self)
+ self._tmpdir = tempfile.mkdtemp()
+ self._msg = email.message_from_string("""\
+From: aperson@dom.ain
+To: _xtest@dom.ain
+
+A test message.
+""")
+ self._sb = Switchboard(self._tmpdir)
+
+ def tearDown(self):
+ shutil.rmtree(self._tmpdir, True)
+ TestBase.tearDown(self)
+
+ def _count_qfiles(self):
+ files = {}
+ for qfile in os.listdir(self._tmpdir):
+ root, ext = os.path.splitext(qfile)
+ files[ext] = files.get(ext, 0) + 1
+ return files
+
+ def test_whichq(self):
+ self.assertEqual(self._sb.whichq(), self._tmpdir)
+
+ def test_enqueue(self):
+ eq = self.assertEqual
+ self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
+ files = self._count_qfiles()
+ eq(len(files), 1)
+ eq(files['.pck'], 1)
+
+ def test_dequeue(self):
+ eq = self.assertEqual
+ self._sb.enqueue(self._msg, listname='_xtest@dom.ain', foo='yes')
+ count = 0
+ for filebase in self._sb.files():
+ msg, data = self._sb.dequeue(filebase)
+ self._sb.finish(filebase)
+ count += 1
+ eq(msg['from'], self._msg['from'])
+ eq(msg['to'], self._msg['to'])
+ eq(data['foo'], 'yes')
+ eq(count, 1)
+ files = self._count_qfiles()
+ eq(len(files), 0)
+
+ def test_bakfile(self):
+ eq = self.assertEqual
+ self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
+ self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
+ self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
+ for filebase in self._sb.files():
+ self._sb.dequeue(filebase)
+ files = self._count_qfiles()
+ eq(len(files), 1)
+ eq(files['.bak'], 3)
+
+ def test_recover(self):
+ eq = self.assertEqual
+ self._sb.enqueue(self._msg, listname='_xtest@dom.ain')
+ for filebase in self._sb.files():
+ self._sb.dequeue(filebase)
+ # Not calling sb.finish() leaves .bak files
+ sb2 = Switchboard(self._tmpdir, recover=True)
+ files = self._count_qfiles()
+ eq(len(files), 1)
+ eq(files['.pck'], 1)
+
+
+
+class TestableRunner(Runner):
+ def _dispose(self, mlist, msg, msgdata):
+ self.msg = msg
+ self.data = msgdata
+ return False
+
+ def _doperiodic(self):
+ self.stop()
+
+ def _snooze(self, filecnt):
+ return
+
+
+class TestRunner(TestBase):
+ def setUp(self):
+ TestBase.setUp(self)
+ self._tmpdir = tempfile.mkdtemp()
+ self._msg = email.message_from_string("""\
+From: aperson@dom.ain
+To: _xtest@dom.ain
+
+A test message.
+""", Message)
+ class MyRunner(TestableRunner):
+ QDIR = self._tmpdir
+ self._runner = MyRunner()
+
+ def tearDown(self):
+ shutil.rmtree(self._tmpdir, True)
+ TestBase.tearDown(self)
+
+ def test_run_loop(self):
+ eq = self.assertEqual
+ sb = Switchboard(self._tmpdir)
+ sb.enqueue(self._msg, listname='_xtest@example.com', foo='yes')
+ self._runner.run()
+ eq(self._runner.msg['from'], self._msg['from'])
+ eq(self._runner.msg['to'], self._msg['to'])
+ eq(self._runner.data['foo'], 'yes')
+
+
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(TestPrepMessage))
+ suite.addTest(unittest.makeSuite(TestSwitchboard))
+ suite.addTest(unittest.makeSuite(TestRunner))
+ return suite
Copied: branches/soc2006-webui/Mailman/SAMemberships.py (from rev 7944, \
trunk/mailman/Mailman/SAMemberships.py) \
===================================================================
--- branches/soc2006-webui/Mailman/SAMemberships.py (rev 0)
+++ branches/soc2006-webui/Mailman/SAMemberships.py 2006-07-17 04:01:43 UTC (rev \
7945) @@ -0,0 +1,330 @@
+# Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
+
+"""An experimental SQLAlchemy-based membership adaptor."""
+
+# XXX THIS FILE DOES NOT YET WORK!
+
+import os
+import re
+import time
+
+from sqlalchemy import *
+from string import Template
+
+from Mailman import Defaults
+from Mailman import Errors
+from Mailman import MemberAdaptor
+from Mailman import Utils
+from Mailman.configuration import config
+
+NUL = '\0'
+
+
+
+# Python classes representing the data in the SQLAlchemy database. These will
+# be associated with tables via object mappers.
+
+class Member(object):
+ def __init__(self, mlist,
+ address, realname=None, password=None,
+ digests_p=False, language=None):
+ self.lckey = address.lower()
+ self.address = address
+ self.realname = realname
+ self.password = password or Utils.MakeRandomPassword()
+ self.language = language or mlist.preferred_language
+ self.digests_p = digests_p
+ self.options = mlist.new_member_options
+ self.topics = ''
+ self.status = MemberAdaptor.ENABLED
+ # XXX This should really be a datetime
+ self.disable_time = 0
+
+
+
+_table = None
+_metadata = None
+_mapper = None
+
+def create_table():
+ global _table, _metadata, _mapper
+
+ if _table:
+ return
+ _metadata = MetaData('table metadata')
+ _table = Table(
+ 'members', _metadata,
+ Column('member_id', Integer, primary_key=True),
+ Column('lckey', Unicode(), index=True, nullable=False),
+ Column('address', Unicode(), index=True, nullable=False),
+ Column('realname', Unicode()),
+ Column('password', Unicode()),
+ Column('language', String(2)),
+ Column('digest', Boolean),
+ Column('options', Integer),
+ Column('status', Integer),
+ Column('disable_time', Float),
+ )
+ _mapper = mapper(Member, _table)
+
+
+
+class SAMemberships(MemberAdaptor.MemberAdaptor):
+ def __init__(self, mlist):
+ self._mlist = mlist
+ self._metadata = None
+ self._session = None
+ self._txn = None
+
+ def _connect(self):
+ create_table()
+ # We cannot connect in the __init__() because our adaptor requires the
+ # fqdn_listname to exist. In MailList.Create() that won't be the case.
+ #
+ # Calculate the engine url, expanding placeholder variables.
+ engine_url = Template(config.SQLALCHEMY_ENGINE_URL).substitute(
+ {'listname' : self._mlist.fqdn_listname,
+ 'listdir' : os.path.join(config.LIST_DATA_DIR,
+ self._mlist.fqdn_listname),
+ })
+ print 'engine_url:', engine_url
+ self._engine = create_engine(engine_url)
+ self._session = create_session(bind_to=self._engine)
+ self._session.bind_table(_table, self._engine)
+ self._session.bind_mapper(_mapper, self._engine)
+ # XXX There must be a better way to figure out whether the tables need
+ # to be created or not.
+ try:
+ _table.create()
+ except exceptions.SQLError:
+ pass
+
+ #
+ # The transaction interface
+ #
+
+ def load(self):
+ if self._session is None:
+ self._connect()
+ assert self._txn is None
+ self._session.clear()
+ self._txn = self._session.create_transaction()
+
+ def lock(self):
+ pass
+
+ def save(self):
+ # When a MailList is first Create()'d, the load() callback doesn't get
+ # called, so there will be no transaction.
+ if self._txn:
+ self._txn.commit()
+ self._txn = None
+
+ def unlock(self):
+ if self._txn is not None:
+ # The MailList has not been saved, but it is being unlocked, so
+ # throw away all pending changes.
+ self._txn.rollback()
+ self._txn = None
+
+ #
+ # The readable interface
+ #
+
+ def getMembers(self):
+ return [m.lckey for m in self._session.query(Member).select_by()]
+
+ def getRegularMemberKeys(self):
+ query = self._session.query(Member)
+ return [m.lckey for m in query.select(Member.c.digests_p == False)]
+
+ def getDigestMemberKeys(self):
+ query = self._session.query(Member)
+ return [m.lckey for m in query.select(Member.c.digests_p == True)]
+
+ def _get_member(self, member):
+ members = self._session.query(Member).select_by(lckey=member.lower())
+ if not members:
+ return None
+ assert len(members) == 1
+ return members[0]
+
+ def _get_member_strict(self, member):
+ member_obj = self._get_member(member)
+ if not member_obj:
+ raise Errors.NotAMemberError(member)
+ return member_obj
+
+ def isMember(self, member):
+ return bool(self._get_member(member))
+
+ def getMemberKey(self, member):
+ self._get_member_strict(member)
+ return member.lower()
+
+ def getMemberCPAddress(self, member):
+ return self._get_member(member).address
+
+ def getMemberCPAddresses(self, members):
+ query = self._session.query(Member)
+ return [user.address for user in query.select(
+ in_(Member.c.lckey, [m.lower() for m in members]))]
+
+ def getMemberPassword(self, member):
+ return self._get_member_strict(member).password
+
+ def authenticateMember(self, member, response):
+ return self._get_member_strict(member).password == response
+
+ def getMemberLanguage(self, member):
+ member = self._get_member(member)
+ if member and member.language in self._mlist.GetAvailableLanguages():
+ return member.language
+ return self._mlist.preferred_language
+
+ def getMemberOption(self, member, flag):
+ return bool(self._get_member_strict(member).options & flag)
+
+ def getMemberName(self, member):
+ return self._get_member_strict(member).realname
+
+ def getMemberTopics(self, member):
+ topics = self._get_member_strict(member).topics
+ if not topics:
+ return []
+ return topics.split(NUL)
+
+ def getDeliveryStatus(self, member):
+ return self._get_member_strict(member).status
+
+ def getDeliveryStatusChangeTime(self, member):
+ member = self._get_member_strict(member)
+ if member.status == MemberAdaptor.ENABLED:
+ return 0
+ return member.disable_time
+
+ def getDeliveryStatusMembers(self, status=(MemberAdaptor.UNKNOWN,
+ MemberAdaptor.BYUSER,
+ MemberAdaptor.BYADMIN,
+ MemberAdaptor.BYBOUNCE)):
+ query = self._session.query(Member)
+ return [user.lckey for user in query.select(
+ in_(Member.c.status, status))]
+
+ def getBouncingMembers(self):
+ "XXX"
+
+ def getBounceInfo(self):
+ "XXX"
+
+ #
+ # The writable interface
+ #
+
+ def addNewMember(self, member, **kws):
+ assert self._mlist.Locked()
+ if self.isMember(member):
+ raise Errors.MMAlreadyAMember(member)
+ try:
+ new_member = Member(self._mlist, member, **kws)
+ self._session.save(new_member)
+ self._session.flush()
+ except TypeError:
+ # Transform exception to API specification
+ raise ValueError
+
+ def removeMember(self, memberkey):
+ assert self._mlist.Locked()
+ member = self._get_member_strict(memberkey)
+ self._session.delete(member)
+
+ def changeMemberAddress(self, memberkey, newaddress, nodelete=False):
+ assert self._mlist.Locked()
+ member = self._get_member_strict(memberkey)
+ # First, add the new member from the previous data
+ self.addNewMember(newaddress, member.realname, member.password,
+ member.digests_p, member.language)
+ new_member = self._get_member(newaddress)
+ assert new_member
+ new_member.options = member.options
+ new_member.topics = member.topics
+ new_member.status = MemberAdaptor.ENABLED
+ new_member.disable_time = 0
+ if not nodelete:
+ self._session.delete(member)
+
+ def setMemberPassword(self, member, password):
+ assert self._mlist.Locked()
+ self._get_member_strict(member).password = password
+
+ def setMemberLanguage(self, member, language):
+ assert self._mlist.Locked()
+ self._get_member_strict(member).language = language
+
+ def setMemberOption(self, member, flag, value):
+ assert self._mlist.Locked()
+ member = self._get_member_strict(member)
+ # XXX the OldStyleMemberships adaptor will raise CantDigestError,
+ # MustDigestError, AlreadyReceivingDigests, and
+ # AlreadyReceivingRegularDeliveries in certain cases depending on the
+ # configuration of the mailing list and the member's delivery status.
+ # These semantics are not defined in the API so to keep things simple,
+ # I am not reproducing them here. Ideally, adaptors should not be
+ # doing semantic integrity checks, but I'm also not going to change
+ # the OldStyleMemberships adaptor.
+ #
+ # We still need to handle digests differently, because they aren't
+ # really represented as a unique flag in the options bitfield.
+ if flag == Defaults.Digests:
+ member.digests_p = bool(value)
+ else:
+ if value:
+ member.options |= flag
+ else:
+ member.options &= ~flag
+
+ def setMemberName(self, member, realname):
+ assert self._mlist.Locked()
+ self._get_member_strict(member).realname = realname
+
+ def setMemberTopics(self, member, topics):
+ assert self._mlist.Locked()
+ # For simplicity, we represent a user's topics of interest as a
+ # null-joined string, which will be split properly by the accessor.
+ if not topics:
+ topics = None
+ else:
+ topics = NUL.join(topics)
+ self._get_member_strict(member).topics = topics
+
+ def setDeliveryStatus(self, member, status):
+ assert status in (MemberAdaptor.ENABLED, MemberAdaptor.UNKNOWN,
+ MemberAdaptor.BYUSER, MemberAdaptor.BYADMIN,
+ MemberAdaptor.BYBOUNCE)
+ assert self._mlist.Locked()
+ member = self._get_member_strict(member)
+ if status == MemberAdaptor.ENABLED:
+ # XXX zap bounce info
+ disable_time = 0
+ else:
+ disable_time = time.time()
+ member.disable_time = disable_time
+ member.status = status
+
+ def setBounceInfo(self, member, info):
+ "XXX"
Modified: branches/soc2006-webui/Mailman/bin/unshunt.py
===================================================================
--- branches/soc2006-webui/Mailman/bin/unshunt.py 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/Mailman/bin/unshunt.py 2006-07-17 04:01:43 UTC (rev 7945)
@@ -51,6 +51,7 @@
qdir = mm_cfg.SHUNTQUEUE_DIR
sb = get_switchboard(qdir)
+ sb.recover_backup_files()
for filebase in sb.files():
try:
msg, msgdata = sb.dequeue(filebase)
@@ -62,6 +63,9 @@
# other shunted messages.
print >> sys.stderr, _(
'Cannot unshunt message $filebase, skipping:\n$e')
+ else:
+ # Unlink the .bak file left by dequeue()
+ sb.finish(filebase)
Modified: branches/soc2006-webui/Mailman/configuration.py
===================================================================
--- branches/soc2006-webui/Mailman/configuration.py 2006-07-16 23:21:32 UTC (rev \
7944)
+++ branches/soc2006-webui/Mailman/configuration.py 2006-07-17 04:01:43 UTC (rev \
7945) @@ -58,7 +58,7 @@
raise
# The file didn't exist, so try mm_cfg.py
from Mailman import mm_cfg
- ns = mm_cfg.__dict__.copy()
+ ns.update(mm_cfg.__dict__)
# Pull out the defaults
PREFIX = ns['PREFIX']
VAR_PREFIX = ns['VAR_PREFIX']
Modified: branches/soc2006-webui/Mailman/testing/base.py
===================================================================
--- branches/soc2006-webui/Mailman/testing/base.py 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/Mailman/testing/base.py 2006-07-17 04:01:43 UTC (rev 7945)
@@ -37,6 +37,9 @@
class TestBase(unittest.TestCase):
def _configure(self, fp):
+## print >> fp, \
+## "MEMBER_ADAPTOR_CLASS = 'Mailman.SAMemberships.SAMemberships'"
+## config.MEMBER_ADAPTOR_CLASS = 'Mailman.SAMemberships.SAMemberships'
print >> fp, 'add_domain("example.com", "www.example.com")'
# Only add this domain once to the current process
if 'example.com' not in config.domains:
@@ -66,6 +69,11 @@
mlist = MailList.MailList()
mlist.Create('_xtest@example.com', 'owner@example.com', 'xxxxx')
mlist.Save()
+ # We need to reload the mailing list to ensure that the member
+ # adaptors are all sync'd up. This isn't strictly necessary with the
+ # OldStyleMemberships adaptor, but it may be required for other
+ # adaptors
+ mlist.Load()
# This leaves the list in a locked state
self._mlist = mlist
@@ -83,3 +91,4 @@
os.unlink(dir)
elif os.path.isdir(dir):
shutil.rmtree(dir)
+ os.unlink(self._config)
Modified: branches/soc2006-webui/Mailman/testing/emailbase.py
===================================================================
--- branches/soc2006-webui/Mailman/testing/emailbase.py 2006-07-16 23:21:32 UTC (rev \
7944)
+++ branches/soc2006-webui/Mailman/testing/emailbase.py 2006-07-17 04:01:43 UTC (rev \
7945) @@ -86,7 +86,6 @@
else:
raise
TestBase.tearDown(self)
- os.remove(self._config)
def _readmsg(self):
global MSGTEXT
Deleted: branches/soc2006-webui/Mailman/testing/test_runners.py
===================================================================
--- branches/soc2006-webui/Mailman/testing/test_runners.py 2006-07-16 23:21:32 UTC \
(rev 7944)
+++ branches/soc2006-webui/Mailman/testing/test_runners.py 2006-07-17 04:01:43 UTC \
(rev 7945) @@ -1,114 +0,0 @@
-# Copyright (C) 2001-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""Unit tests for the various Mailman qrunner modules."""
-
-import email
-import unittest
-
-from Mailman.Queue.NewsRunner import prepare_message
-from Mailman.testing.base import TestBase
-
-
-
-class TestPrepMessage(TestBase):
- def test_remove_unacceptables(self):
- eq = self.assertEqual
- msg = email.message_from_string("""\
-From: aperson@dom.ain
-To: _xtest@dom.ain
-NNTP-Posting-Host: news.dom.ain
-NNTP-Posting-Date: today
-X-Trace: blah blah
-X-Complaints-To: abuse@dom.ain
-Xref: blah blah
-Xref: blah blah
-Date-Received: yesterday
-Posted: tomorrow
-Posting-Version: 99.99
-Relay-Version: 88.88
-Received: blah blah
-
-A message
-""")
- msgdata = {}
- prepare_message(self._mlist, msg, msgdata)
- eq(msgdata.get('prepped'), 1)
- eq(msg['from'], 'aperson@dom.ain')
- eq(msg['to'], '_xtest@dom.ain')
- eq(msg['nntp-posting-host'], None)
- eq(msg['nntp-posting-date'], None)
- eq(msg['x-trace'], None)
- eq(msg['x-complaints-to'], None)
- eq(msg['xref'], None)
- eq(msg['date-received'], None)
- eq(msg['posted'], None)
- eq(msg['posting-version'], None)
- eq(msg['relay-version'], None)
- eq(msg['received'], None)
-
- def test_munge_duplicates_no_duplicates(self):
- eq = self.assertEqual
- msg = email.message_from_string("""\
-From: aperson@dom.ain
-To: _xtest@dom.ain
-Cc: someother@dom.ain
-Content-Transfer-Encoding: yes
-
-A message
-""")
- msgdata = {}
- prepare_message(self._mlist, msg, msgdata)
- eq(msgdata.get('prepped'), 1)
- eq(msg['from'], 'aperson@dom.ain')
- eq(msg['to'], '_xtest@dom.ain')
- eq(msg['cc'], 'someother@dom.ain')
- eq(msg['content-transfer-encoding'], 'yes')
-
- def test_munge_duplicates(self):
- eq = self.assertEqual
- msg = email.message_from_string("""\
-From: aperson@dom.ain
-To: _xtest@dom.ain
-To: two@dom.ain
-Cc: three@dom.ain
-Cc: four@dom.ain
-Cc: five@dom.ain
-Content-Transfer-Encoding: yes
-Content-Transfer-Encoding: no
-Content-Transfer-Encoding: maybe
-
-A message
-""")
- msgdata = {}
- prepare_message(self._mlist, msg, msgdata)
- eq(msgdata.get('prepped'), 1)
- eq(msg.get_all('from'), ['aperson@dom.ain'])
- eq(msg.get_all('to'), ['_xtest@dom.ain'])
- eq(msg.get_all('cc'), ['three@dom.ain'])
- eq(msg.get_all('content-transfer-encoding'), ['yes'])
- eq(msg.get_all('x-original-to'), ['two@dom.ain'])
- eq(msg.get_all('x-original-cc'), ['four@dom.ain', 'five@dom.ain'])
- eq(msg.get_all('x-original-content-transfer-encoding'),
- ['no', 'maybe'])
-
-
-
-def test_suite():
- suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(TestPrepMessage))
- return suite
Modified: branches/soc2006-webui/Makefile.in
===================================================================
--- branches/soc2006-webui/Makefile.in 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/Makefile.in 2006-07-17 04:01:43 UTC (rev 7945)
@@ -50,7 +50,8 @@
ARCH_INDEP_DIRS= \
bin etc templates scripts cron pythonlib \
Mailman Mailman/bin Mailman/Cgi Mailman/Archiver \
- Mailman/Handlers Mailman/Queue Mailman/Bouncers \
+ Mailman/Handlers Mailman/Queue Mailman/Queue/tests \
+ Mailman/Bouncers \
Mailman/MTA Mailman/Gui Mailman/Commands messages icons \
Mailman/testing Mailman/testing/bounces tests tests/msgs
Modified: branches/soc2006-webui/configure
===================================================================
--- branches/soc2006-webui/configure 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/configure 2006-07-17 04:01:43 UTC (rev 7945)
@@ -1,5 +1,5 @@
#! /bin/sh
-# From configure.in Revision: 7899 .
+# From configure.in Revision: 7926 .
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.59 for GNU Mailman 2.2.0a0.
#
@@ -4316,7 +4316,7 @@
# scripts. They're removed on a make distclean, so we make them here.
mkdir -p build/bin build/contrib build/cron
- \
ac_config_files="$ac_config_files misc/paths.py Mailman/Defaults.py \
Mailman/mm_cfg.py.dist src/Makefile misc/Makefile bin/Makefile Mailman/bin/Makefile \
Mailman/Makefile Mailman/Cgi/Makefile Mailman/Archiver/Makefile \
Mailman/Commands/Makefile Mailman/Handlers/Makefile Mailman/Bouncers/Makefile \
Mailman/Queue/Makefile Mailman/MTA/Makefile Mailman/Gui/Makefile templates/Makefile \
cron/Makefile scripts/Makefile messages/Makefile cron/crontab.in misc/mailman \
Makefile Mailman/testing/Makefile Mailman/testing/bounces/Makefile tests/Makefile \
tests/msgs/Makefile $SCRIPTS" + \
ac_config_files="$ac_config_files misc/paths.py Mailman/Defaults.py \
Mailman/mm_cfg.py.dist src/Makefile misc/Makefile bin/Makefile Mailman/bin/Makefile \
Mailman/Makefile Mailman/Cgi/Makefile Mailman/Archiver/Makefile \
Mailman/Commands/Makefile Mailman/Handlers/Makefile Mailman/Bouncers/Makefile \
Mailman/Queue/Makefile Mailman/Queue/tests/Makefile Mailman/MTA/Makefile \
Mailman/Gui/Makefile templates/Makefile cron/Makefile scripts/Makefile \
messages/Makefile cron/crontab.in misc/mailman Makefile Mailman/testing/Makefile \
Mailman/testing/bounces/Makefile tests/Makefile tests/msgs/Makefile $SCRIPTS" \
ac_config_commands="$ac_config_commands default" cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
@@ -4886,6 +4886,7 @@
"Mailman/Handlers/Makefile" ) CONFIG_FILES="$CONFIG_FILES \
Mailman/Handlers/Makefile" ;; "Mailman/Bouncers/Makefile" ) \
CONFIG_FILES="$CONFIG_FILES Mailman/Bouncers/Makefile" ;; "Mailman/Queue/Makefile" ) \
CONFIG_FILES="$CONFIG_FILES Mailman/Queue/Makefile" ;; + \
"Mailman/Queue/tests/Makefile" ) CONFIG_FILES="$CONFIG_FILES \
Mailman/Queue/tests/Makefile" ;; "Mailman/MTA/Makefile" ) \
CONFIG_FILES="$CONFIG_FILES Mailman/MTA/Makefile" ;; "Mailman/Gui/Makefile" ) \
CONFIG_FILES="$CONFIG_FILES Mailman/Gui/Makefile" ;; "templates/Makefile" ) \
CONFIG_FILES="$CONFIG_FILES templates/Makefile" ;;
Modified: branches/soc2006-webui/configure.in
===================================================================
--- branches/soc2006-webui/configure.in 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/configure.in 2006-07-17 04:01:43 UTC (rev 7945)
@@ -12,7 +12,8 @@
#
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+# USA.
dnl Process this file with autoconf to produce a configure script.
AC_REVISION($Revision$)
@@ -642,7 +643,8 @@
Mailman/Makefile Mailman/Cgi/Makefile
Mailman/Archiver/Makefile Mailman/Commands/Makefile
Mailman/Handlers/Makefile Mailman/Bouncers/Makefile
- Mailman/Queue/Makefile Mailman/MTA/Makefile Mailman/Gui/Makefile
+ Mailman/Queue/Makefile Mailman/Queue/tests/Makefile
+ Mailman/MTA/Makefile Mailman/Gui/Makefile
templates/Makefile cron/Makefile scripts/Makefile messages/Makefile
cron/crontab.in misc/mailman Makefile
Mailman/testing/Makefile Mailman/testing/bounces/Makefile
Modified: branches/soc2006-webui/misc/mailman.cfg.sample
===================================================================
--- branches/soc2006-webui/misc/mailman.cfg.sample 2006-07-16 23:21:32 UTC (rev 7944)
+++ branches/soc2006-webui/misc/mailman.cfg.sample 2006-07-17 04:01:43 UTC (rev 7945)
@@ -31,36 +31,3 @@
Mailman's installation procedure will never overwrite mailman.cfg.
"""
-# -*- python -*-
-
-# Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
-# USA.
-
-"""This module contains your site-specific settings.
-
-Use this to override the default settings in Mailman/Defaults.py. You only
-need to include those settings that you want to change, and unlike the old
-mm_cfg.py file, you do /not/ need to import Defaults. Its variables will
-automatically be available in this module's namespace.
-
-You should consult Defaults.py though for a complete listing of configuration
-variables that you can change.
-
-To use this, copy this file to $VAR_PREFIX/etc/mailman.cfg
-
-Mailman's installation procedure will never overwrite mailman.cfg.
-"""
This was sent by the SourceForge.net collaborative development platform, the world's \
largest Open Source development site.
_______________________________________________
Mailman-checkins mailing list
Mailman-checkins@python.org
Unsubscribe: http://mail.python.org/mailman/options/mailman-checkins/mailman-cvs%40progressive-comp.com
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic