[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