aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman/Pending.py
diff options
context:
space:
mode:
author <>2003-01-02 05:25:50 +0000
committer <>2003-01-02 05:25:50 +0000
commitb132a73f15e432eaf43310fce9196ca0c0651465 (patch)
treec15f816ba7c4de99fef510e3bd75af0890d47441 /Mailman/Pending.py
downloadmailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.gz
mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.tar.xz
mailman2-b132a73f15e432eaf43310fce9196ca0c0651465.zip
This commit was manufactured by cvs2svn to create branch
'Release_2_1-maint'.
Diffstat (limited to '')
-rw-r--r--Mailman/Pending.py204
1 files changed, 204 insertions, 0 deletions
diff --git a/Mailman/Pending.py b/Mailman/Pending.py
new file mode 100644
index 00000000..be1c6cac
--- /dev/null
+++ b/Mailman/Pending.py
@@ -0,0 +1,204 @@
+# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+""" 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.
+"""
+
+import os
+import time
+import sha
+import marshal
+import cPickle
+import random
+import errno
+
+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'
+UNSUBSCRIPTION = 'U'
+CHANGE_OF_ADDRESS = 'C'
+HELD_MESSAGE = 'H'
+RE_ENABLE = 'E'
+
+_ALLKEYS = [(x,) for x in (SUBSCRIPTION, UNSUBSCRIPTION,
+ CHANGE_OF_ADDRESS, HELD_MESSAGE,
+ RE_ENABLE,
+ )]
+
+
+
+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
+ # Acquire the pending database lock, letting TimeOutError percolate up.
+ lock = LockFile.LockFile(LOCKFILE)
+ lock.lock(timeout=30)
+ try:
+ # Load the current database
+ db = _load()
+ # Calculate a unique cookie
+ while 1:
+ n = random.random()
+ now = time.time()
+ hashfood = str(now) + str(n) + str(content)
+ cookie = sha.new(hashfood).hexdigest()
+ 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
+ _save(db)
+ return cookie
+ finally:
+ lock.unlock()
+
+
+
+def confirm(cookie, expunge=1):
+ """Return data for cookie, or None if not found.
+
+ If optional expunge is true (the default), the record is also removed from
+ the database.
+ """
+ # Acquire the pending database lock, letting TimeOutError percolate up.
+ # BAW: we perhaps shouldn't acquire the lock if expunge==0.
+ lock = LockFile.LockFile(LOCKFILE)
+ lock.lock(timeout=30)
+ try:
+ # Load the database
+ db = _load()
+ missing = []
+ content = db.get(cookie, missing)
+ if content is missing:
+ return None
+ # Remove the entry from the database
+ if expunge:
+ del db[cookie]
+ del db['evictions'][cookie]
+ _save(db)
+ return content
+ finally:
+ lock.unlock()
+
+
+
+def _load():
+ # The list's lock must be acquired.
+ #
+ # First try to load the pickle file
+ fp = None
+ try:
+ try:
+ fp = open(PCKFILE)
+ return cPickle.load(fp)
+ 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:
+ fp.close()
+
+
+def _save(db):
+ # Lock must be acquired.
+ 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 = PCKFILE + '.tmp'
+ fp = None
+ try:
+ fp = open(tmpfile, 'w')
+ cPickle.dump(db, fp)
+ fp.close()
+ fp = None
+ os.rename(tmpfile, PCKFILE)
+ if os.path.exists(DBFILE):
+ os.remove(DBFILE)
+ finally:
+ if fp:
+ fp.close()
+ os.umask(omask)
+
+
+
+def _update(olddb):
+ # Update an old pending_subscriptions.db database to the new format
+ lock = LockFile.LockFile(LOCKFILE)
+ lock.lock(timeout=30)
+ 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)
+ finally:
+ lock.unlock()