Update of /cvsroot/mailman/mailman/Mailman
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv16113

Modified Files:
      Tag: Release_2_1-maint
        Pending.py 
Log Message:
Simplification and re-implementation as a per-list (and thus MailList mixin
class), pending database.


Index: Pending.py
===================================================================
RCS file: /cvsroot/mailman/mailman/Mailman/Pending.py,v
retrieving revision 2.9.2.3
retrieving revision 2.9.2.4
diff -u -d -r2.9.2.3 -r2.9.2.4
--- Pending.py  1 Dec 2003 00:56:11 -0000       2.9.2.3
+++ Pending.py  10 Feb 2004 22:45:54 -0000      2.9.2.4
@@ -1,4 +1,4 @@
-# Copyright (C) 1998-2003 by the Free Software Foundation, Inc.
+# Copyright (C) 1998-2004 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
@@ -14,28 +14,16 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
-""" Track pending confirmation of subscriptions.
-
-new(stuff...) places an item's data in the db, returning its cookie.
-
-confirmed(cookie) returns a tuple for the data, removing the item
-from the db.  It returns None if the cookie is not registered.
-"""
+"""Track pending actions which require confirmation."""
 
 import os
-import time
 import sha
-import marshal
-import cPickle
-import random
+import time
 import errno
+import random
+import cPickle
 
 from Mailman import mm_cfg
-from Mailman import LockFile
-
-DBFILE = os.path.join(mm_cfg.DATA_DIR, 'pending.db')
-PCKFILE = os.path.join(mm_cfg.DATA_DIR, 'pending.pck')
-LOCKFILE = os.path.join(mm_cfg.LOCK_DIR, 'pending.lock')
 
 # Types of pending records
 SUBSCRIPTION = 'S'
@@ -43,11 +31,12 @@
 CHANGE_OF_ADDRESS = 'C'
 HELD_MESSAGE = 'H'
 RE_ENABLE = 'E'
+PROBE_BOUNCE = 'P'
 
-_ALLKEYS = [(x,) for x in (SUBSCRIPTION, UNSUBSCRIPTION,
-                           CHANGE_OF_ADDRESS, HELD_MESSAGE,
-                           RE_ENABLE,
-                           )]
+_ALLKEYS = (SUBSCRIPTION, UNSUBSCRIPTION,
+            CHANGE_OF_ADDRESS, HELD_MESSAGE,
+            RE_ENABLE, PROBE_BOUNCE,
+            )
 
 try:
     True, False
@@ -56,228 +45,140 @@
     False = 0
 
 
-
-def new(*content):
-    """Create a new entry in the pending database, returning cookie for it."""
-    # It's a programming error if this assertion fails!  We do it this way so
-    # the assert test won't fail if the sequence is empty.
-    assert content[:1] in _ALLKEYS
-    # Get a lock handle now, but only lock inside the loop.
-    lock = LockFile.LockFile(
-        LOCKFILE, withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
-    # We try the main loop several times. If we get a lock error somewhere
-    # (for instance because someone broke the lock) we simply try again.
-    retries = mm_cfg.PENDINGDB_LOCK_ATTEMPTS
-    try:
-        while retries:
-            retries -= 1
-            if not lock.locked():
-                try:
-                    lock.lock(timeout=mm_cfg.PENDINGDB_LOCK_TIMEOUT)
-                except LockFile.TimeOutError:
-                    continue
-            # Load the current database
-            db = _load()
-            # Calculate a unique cookie.  Algorithm vetted by the Timbot.
-            # time() has high resolution on Linux, clock() on Windows.  random
-            # gives us about 45 bits in Python 2.2, 53 bits on Python 2.3.
-            # The time and clock values basically help obscure the random
-            # number generator, as does the hash calculation.  The integral
-            # parts of the time values are discarded because they're the most
-            # predictable bits.
-            while True:
-                now = time.time()
-                x = random.random() + now % 1.0 + time.clock() % 1.0
-                hashfood = repr(x)
-                cookie = sha.new(hashfood).hexdigest()
-                # We'll never get a duplicate, but we'll be anal about
-                # checking anyway.
-                if not db.has_key(cookie):
-                    break
-            # Store the content, plus the time in the future when this entry
-            # will be evicted from the database, due to staleness.
-            db[cookie] = content
-            evictions = db.setdefault('evictions', {})
-            evictions[cookie] = now + mm_cfg.PENDING_REQUEST_LIFE
-            try:
-                _save(db, lock)
-            except LockFile.NotLockedError:
-                continue
-            return cookie
-        else:
-            # We failed to get the lock or keep it long enough to save the
-            # data!
-            raise LockFile.TimeOutError
-    finally:
-        if lock.locked():
-            lock.unlock()
+_missing = []
 
 
 
-def confirm(cookie, expunge=True):
-    """Return data for cookie, or None if not found.
-
-    If optional expunge is True (the default), the record is also removed from
-    the database.
-    """
-    if not expunge:
-        db = _load()
-        missing = []
-        content = db.get(cookie, missing)
-        if content is missing:
-            return None
-        return content
-    # Get a lock handle now, but only lock inside the loop.
-    lock = LockFile.LockFile(LOCKFILE,
-                             withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
-    # We try the main loop several times. If we get a lock error somewhere
-    # (for instance because someone broke the lock) we simply try again.
-    retries = mm_cfg.PENDINGDB_LOCK_ATTEMPTS
-    try:
-        while retries:
-            retries -= 1
-            if not lock.locked():
-               try:
-                   lock.lock(timeout=mm_cfg.PENDINGDB_LOCK_TIMEOUT)
-               except LockFile.TimeOutError:
-                   continue
-            # Load the database
-            db = _load()
-            missing = []
-            content = db.get(cookie, missing)
-            if content is missing:
-                return None
-            del db[cookie]
-            del db['evictions'][cookie]
-            try:
-                _save(db, lock)
-            except LockFile.NotLockedError:
-                continue
-            return content
-        else:
-            # We failed to get the lock and keep it long enough to save the
-            # data!
-            raise LockFile.TimeOutError
-    finally:
-        if lock.locked():
-            lock.unlock()
-
+class Pending:
+    def InitTempVars(self):
+        self.__pendfile = os.path.join(self.fullpath(), 'pending.pck')
 
-
-def repend(cookie, data):
-    # Get a lock
-    lock = LockFile.LockFile(
-        LOCKFILE, withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
-    lock.lock()
-    try:
+    def pend_new(self, op, *content, **kws):
+        """Create a new entry in the pending database, returning cookie for it.
+        """
+        assert op in _ALLKEYS, 'op: %s' % op
+        lifetime = kws.get('lifetime', mm_cfg.PENDING_REQUEST_LIFE)
+        # We try the main loop several times. If we get a lock error somewhere
+        # (for instance because someone broke the lock) we simply try again.
+        assert self.Locked()
         # Load the database
-        db = _load()
-        db[cookie] = data
-        db['evictions'][cookie] = time.time() + mm_cfg.PENDING_REQUEST_LIFE
-        _save(db, lock)
-    finally:
-        lock.unlock()
-
+        db = self.__load()
+        # Calculate a unique cookie.  Algorithm vetted by the Timbot.  time()
+        # has high resolution on Linux, clock() on Windows.  random gives us
+        # about 45 bits in Python 2.2, 53 bits on Python 2.3.  The time and
+        # clock values basically help obscure the random number generator, as
+        # does the hash calculation.  The integral parts of the time values
+        # are discarded because they're the most predictable bits.
+        while True:
+            now = time.time()
+            x = random.random() + now % 1.0 + time.clock() % 1.0
+            cookie = sha.new(repr(x)).hexdigest()
+            # We'll never get a duplicate, but we'll be anal about checking
+            # anyway.
+            if not db.has_key(cookie):
+                break
+        # Store the content, plus the time in the future when this entry will
+        # be evicted from the database, due to staleness.
+        db[cookie] = (op,) + content
+        evictions = db.setdefault('evictions', {})
+        evictions[cookie] = now + lifetime
+        self.__save(db)
+        return cookie
 
-
-def _load():
-    # The list's lock must be acquired if you wish to alter data and save.
-    #
-    # First try to load the pickle file
-    fp = None
-    try:
+    def __load(self):
         try:
-            fp = open(PCKFILE)
-            return cPickle.load(fp)
+            fp = open(self.__pendfile)
         except IOError, e:
             if e.errno <> errno.ENOENT: raise
-            try:
-                # Try to load the old DBFILE
-                fp = open(DBFILE)
-                return marshal.load(fp)
-            except IOError, e:
-                if e.errno <> errno.ENOENT: raise
-                # Fresh pendings database
-                return {'evictions': {}}
-    finally:
-        if fp:
+            return {'evictions': {}}
+        try:
+            return cPickle.load(fp)
+        finally:
             fp.close()
 
+    def __save(self, db):
+        evictions = db['evictions']
+        now = time.time()
+        for cookie, data in db.items():
+            if cookie in ('evictions', 'version'):
+                continue
+            timestamp = evictions[cookie]
+            if now > timestamp:
+                # The entry is stale, so remove it.
+                del db[cookie]
+                del evictions[cookie]
+        # Clean out any bogus eviction entries.
+        for cookie in evictions.keys():
+            if not db.has_key(cookie):
+                del evictions[cookie]
+        db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION
+        tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), now)
+        omask = os.umask(007)
+        try:
+            fp = open(tmpfile, 'w')
+            try:
+                cPickle.dump(db, fp)
+                fp.flush()
+                os.fsync(fp.fileno())
+            finally:
+                fp.close()
+            os.rename(tmpfile, self.__pendfile)
+        finally:
+            os.umask(omask)
 
-
-def _save(db, lock):
-    # Lock must be acquired before loading the data that is now being saved.
-    if not lock.locked():
-        raise LockFile.NotLockedError
-    evictions = db['evictions']
-    now = time.time()
-    for cookie, data in db.items():
-        if cookie in ('evictions', 'version'):
-            continue
-        timestamp = evictions[cookie]
-        if now > timestamp:
-            # The entry is stale, so remove it.
-            del db[cookie]
-            del evictions[cookie]
-    # Clean out any bogus eviction entries.
-    for cookie in evictions.keys():
-        if not db.has_key(cookie):
-            del evictions[cookie]
-    db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION
-    omask = os.umask(007)
-    # Always save this as a pickle (safely), and after that succeeds, blow
-    # away any old marshal file.
-    tmpfile = '%s.tmp.%d.%d' % (PCKFILE, os.getpid(), now)
-    fp = None
-    try:
-        fp = open(tmpfile, 'w')
-        cPickle.dump(db, fp)
-        fp.close()
-        fp = None
-        if not lock.locked():
-            # Our lock was broken?
-            os.remove(tmpfile)
-            raise LockFile.NotLockedError
-        os.rename(tmpfile, PCKFILE)
-        if os.path.exists(DBFILE):
-            os.remove(DBFILE)
-    finally:
-        if fp:
-            fp.close()
-        os.umask(omask)
+    def pend_confirm(self, cookie, expunge=True):
+        """Return data for cookie, or None if not found.
+
+        If optional expunge is True (the default), the record is also removed
+        from the database.
+        """
+        db = self.__load()
+        # If we're not expunging, the database is read-only.
+        if not expunge:
+            return db.get(cookie)
+        # Since we're going to modify the database, we must make sure the list
+        # is locked, since it's the list lock that protects pending.pck.
+        assert self.Locked()
+        content = db.get(cookie, _missing)
+        if content is _missing:
+            return None
+        # Do the expunge
+        del db[cookie]
+        del db['evictions'][cookie]
+        self.__save(db)
+        return content
+
+    def pend_repend(self, cookie, data, lifetime=mm_cfg.PENDING_REQUEST_LIFE):
+        assert self.Locked()
+        db = self.__load()
+        db[cookie] = data
+        db['evictions'][cookie] = time.time() + lifetime
+        self.__save(db)
 
 
 
 def _update(olddb):
-    # Update an old pending_subscriptions.db database to the new format
-    lock = LockFile.LockFile(LOCKFILE,
-                             withlogging=mm_cfg.PENDINGDB_LOCK_DEBUGGING)
-    lock.lock(timeout=mm_cfg.PENDINGDB_LOCK_TIMEOUT)
-    try:
-        # We don't need this entry anymore
-        if olddb.has_key('lastculltime'):
-            del olddb['lastculltime']
-        db = _load()
-        evictions = db.setdefault('evictions', {})
-        for cookie, data in olddb.items():
-            # The cookies used to be kept as a 6 digit integer.  We now keep
-            # the cookies as a string (sha in our case, but it doesn't matter
-            # for cookie matching).
-            cookie = str(cookie)
-            # The old format kept the content as a tuple and tacked the
-            # timestamp on as the last element of the tuple.  We keep the
-            # timestamps separate, but require the prepending of a record type
-            # indicator.  We know that the only things that were kept in the
-            # old format were subscription requests.  Also, the old request
-            # format didn't have the subscription language.  Best we can do
-            # here is use the server default.
-            db[cookie] = (SUBSCRIPTION,) + data[:-1] + \
-                         (mm_cfg.DEFAULT_SERVER_LANGUAGE,)
-            # The old database format kept the timestamp as the time the
-            # request was made.  The new format keeps it as the time the
-            # request should be evicted.
-            evictions[cookie] = data[-1] + mm_cfg.PENDING_REQUEST_LIFE
-        _save(db, lock)
-    finally:
-        if lock.locked():
-            lock.unlock()
+    db = {}
+    # We don't need this entry anymore
+    if olddb.has_key('lastculltime'):
+        del olddb['lastculltime']
+    evictions = db.setdefault('evictions', {})
+    for cookie, data in olddb.items():
+        # The cookies used to be kept as a 6 digit integer.  We now keep the
+        # cookies as a string (sha in our case, but it doesn't matter for
+        # cookie matching).
+        cookie = str(cookie)
+        # The old format kept the content as a tuple and tacked the timestamp
+        # on as the last element of the tuple.  We keep the timestamps
+        # separate, but require the prepending of a record type indicator.  We
+        # know that the only things that were kept in the old format were
+        # subscription requests.  Also, the old request format didn't have the
+        # subscription language.  Best we can do here is use the server
+        # default.
+        db[cookie] = (SUBSCRIPTION,) + data[:-1] + \
+                     (mm_cfg.DEFAULT_SERVER_LANGUAGE,)
+        # The old database format kept the timestamp as the time the request
+        # was made.  The new format keeps it as the time the request should be
+        # evicted.
+        evictions[cookie] = data[-1] + mm_cfg.PENDING_REQUEST_LIFE
+    return db


_______________________________________________
Mailman-checkins mailing list
[EMAIL PROTECTED]
Unsubscribe: http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org

Reply via email to