diff options
author | bwarsaw <> | 2003-09-26 03:33:20 +0000 |
---|---|---|
committer | bwarsaw <> | 2003-09-26 03:33:20 +0000 |
commit | 557df5921b350ff0d8162b7fad61aa8038df66d3 (patch) | |
tree | a54f6462f0f685e478587d58d72e51416aef1279 /Mailman/BDBMemberAdaptor.py | |
parent | 292c107fa76729addd14d5022e371f7ef8501cac (diff) | |
download | mailman2-557df5921b350ff0d8162b7fad61aa8038df66d3.tar.gz mailman2-557df5921b350ff0d8162b7fad61aa8038df66d3.tar.xz mailman2-557df5921b350ff0d8162b7fad61aa8038df66d3.zip |
BDBMemberAdaptor should not make it into a maintenance release.
Diffstat (limited to '')
-rw-r--r-- | Mailman/BDBMemberAdaptor.py | 637 |
1 files changed, 0 insertions, 637 deletions
diff --git a/Mailman/BDBMemberAdaptor.py b/Mailman/BDBMemberAdaptor.py deleted file mode 100644 index 589be626..00000000 --- a/Mailman/BDBMemberAdaptor.py +++ /dev/null @@ -1,637 +0,0 @@ -# Copyright (C) 2003 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. - -"""A MemberAdaptor based on the Berkeley database wrapper for Python. - -Requires Python 2.2.2 or newer, and PyBSDDB3 4.1.3 or newer. -""" - -# To use, put the following in a file called extend.py in the mailing list's -# directory: -# -# from Mailman.BDBMemberAdaptor import extend -# -# that's it! - -import os -import new -import time -import errno -import struct -import cPickle as pickle - -try: - # Python 2.3 - from bsddb import db -except ImportError: - # earlier Pythons - from bsddb3 import db - -from Mailman import mm_cfg -from Mailman import Utils -from Mailman import Errors -from Mailman import MemberAdaptor -from Mailman.MailList import MailList -from Mailman.Logging.Syslog import syslog - -STORAGE_VERSION = 'BA01' -FMT = '>BHB' -FMTSIZE = struct.calcsize(FMT) - -REGDELIV = 1 -DIGDELIV = 2 -REGFLAG = struct.pack('>B', REGDELIV) -DIGFLAG = struct.pack('>B', DIGDELIV) - -# Positional arguments for _unpack() -CPADDR = 0 -PASSWD = 1 -LANG = 2 -NAME = 3 -DIGEST = 4 -OPTIONS = 5 -STATUS = 6 - - - -class BDBMemberAdaptor(MemberAdaptor.MemberAdaptor): - def __init__(self, mlist): - self._mlist = mlist - # metainfo -- {key -> value} - # This table contains storage metadata information. The keys and - # values are simple strings of variable length. Here are the - # valid keys: - # - # version - the version of the database - # - # members -- {address | rec} - # For all regular delivery members, this maps from the member's - # key to their data record, which is a string concatenated of the - # following: - # - # -- fixed data (as a packed struct) - # + 1-byte digest or regular delivery flag - # + 2-byte option flags - # + 1-byte delivery status - # -- variable data (as a pickle of a tuple) - # + their case preserved address or '' - # + their plaintext password - # + their chosen language - # + their realname or '' - # - # status -- {address | status+time} - # Maps the member's key to their delivery status and change time. - # These are passed as a tuple and are pickled for storage. - # - # topics -- {address | topicstrings} - # Maps the member's key to their topic strings, concatenated and - # separated by SEP - # - # bounceinfo -- {address | bounceinfo} - # Maps the member's key to their bounceinfo, as a pickle - # - # Make sure the database directory exists - path = os.path.join(mlist.fullpath(), 'member.db') - exists = False - try: - os.mkdir(path, 02775) - except OSError, e: - if e.errno <> errno.EEXIST: raise - exists = True - # Create the environment - self._env = env = db.DBEnv() - if exists: - # We must join an existing environment, otherwise we'll get - # DB_RUNRECOVERY errors when the second process to open the - # environment begins a transaction. I don't get it. - env.open(path, db.DB_JOINENV) - else: - env.open(path, - db.DB_CREATE | - db.DB_RECOVER | - db.DB_INIT_MPOOL | - db.DB_INIT_TXN - ) - self._txn = None - self._tables = [] - self._metainfo = self._setupDB('metainfo') - self._members = self._setupDB('members') - self._status = self._setupDB('status') - self._topics = self._setupDB('topics') - self._bounceinfo = self._setupDB('bounceinfo') - # Check the database version number - version = self._metainfo.get('version') - if version is None: - # Initialize - try: - self.txn_begin() - self._metainfo.put('version', STORAGE_VERSION, txn=self._txn) - except: - self.txn_abort() - raise - else: - self.txn_commit() - else: - # Currently there's nothing to upgrade - assert version == STORAGE_VERSION - - def _setupDB(self, name): - d = db.DB(self._env) - openflags = db.DB_CREATE - # db 4.1 requires that databases be opened in a transaction. We'll - # use auto commit, but only if that flag exists (i.e. we're using at - # least db 4.1). - try: - openflags |= db.DB_AUTO_COMMIT - except AttributeError: - pass - d.open(name, db.DB_BTREE, openflags) - self._tables.append(d) - return d - - def _close(self): - self.txn_abort() - for d in self._tables: - d.close() - # Checkpoint the database twice, as recommended by Sleepycat - self._checkpoint() - self._checkpoint() - self._env.close() - - def _checkpoint(self): - self._env.txn_checkpoint(0, 0, db.DB_FORCE) - - def txn_begin(self): - assert self._txn is None - self._txn = self._env.txn_begin() - - def txn_commit(self): - assert self._txn is not None - self._txn.commit() - self._checkpoint() - self._txn = None - - def txn_abort(self): - if self._txn is not None: - self._txn.abort() - self._checkpoint() - self._txn = None - - def _unpack(self, member): - # Assume member is a LCE (i.e. lowercase key) - rec = self._members.get(member.lower()) - assert rec is not None - fixed = struct.unpack(FMT, rec[:FMTSIZE]) - vari = pickle.loads(rec[FMTSIZE:]) - return vari + fixed - - def _pack(self, member, cpaddr, passwd, lang, name, digest, flags, status): - # Assume member is a LCE (i.e. lowercase key) - fixed = struct.pack(FMT, digest, flags, status) - vari = pickle.dumps((cpaddr, passwd, lang, name)) - self._members.put(member.lower(), fixed+vari, txn=self._txn) - - # MemberAdaptor writeable interface - - def addNewMember(self, member, **kws): - assert self._mlist.Locked() - # Make sure this address isn't already a member - if self.isMember(member): - raise Errors.MMAlreadyAMember, member - # Parse the keywords - digest = False - password = Utils.MakeRandomPassword() - language = self._mlist.preferred_language - realname = None - if kws.has_key('digest'): - digest = kws['digest'] - del kws['digest'] - if kws.has_key('password'): - password = kws['password'] - del kws['password'] - if kws.has_key('language'): - language = kws['language'] - del kws['language'] - if kws.has_key('realname'): - realname = kws['realname'] - del kws['realname'] - # Assert that no other keywords are present - if kws: - raise ValueError, kws.keys() - # Should we store the case-preserved address? - if Utils.LCDomain(member) == member.lower(): - cpaddress = '' - else: - cpaddress = member - # Calculate the realname - if realname is None: - realname = '' - # Calculate the digest flag - if digest: - digest = DIGDELIV - else: - digest = REGDELIV - self._pack(member.lower(), - cpaddress, password, language, realname, - digest, self._mlist.new_member_options, - MemberAdaptor.ENABLED) - - def removeMember(self, member): - txn = self._txn - assert txn is not None - assert self._mlist.Locked() - self.__assertIsMember(member) - key = member.lower() - # Remove the table entries - self._members.delete(key, txn=txn) - if self._status.has_key(key): - self._status.delete(key, txn=txn) - if self._topics.has_key(key): - self._topics.delete(key, txn=txn) - if self._bounceinfo.has_key(key): - self._bounceinfo.delete(key, txn=txn) - - def changeMemberAddress(self, member, newaddress, nodelete=0): - assert self._mlist.Locked() - self.__assertIsMember(member) - okey = member.lower() - nkey = newaddress.lower() - txn = self._txn - assert txn is not None - # First, store a new member record, changing the case preserved addr. - # Then delete the old record. - cpaddr, passwd, lang, name, digest, flags, sts = self._unpack(okey) - self._pack(nkey, newaddress, passwd, lang, name, digest, flags, sts) - if not nodelete: - self._members.delete(okey, txn) - # Copy over the status times, topics, and bounce info, if present - timestr = self._status.get(okey) - if timestr is not None: - self._status.put(nkey, timestr, txn=txn) - if not nodelete: - self._status.delete(okey, txn) - topics = self._topics.get(okey) - if topics is not None: - self._topics.put(nkey, topics, txn=txn) - if not nodelete: - self._topics.delete(okey, txn) - binfo = self._bounceinfo.get(nkey) - if binfo is not None: - self._binfo.put(nkey, binfo, txn=txn) - if not nodelete: - self._binfo.delete(okey, txn) - - def setMemberPassword(self, member, password): - assert self._mlist.Locked() - self.__assertIsMember(member) - member = member.lower() - cpaddr, oldpw, lang, name, digest, flags, status = self._unpack(member) - self._pack(member, cpaddr, password, lang, name, digest, flags, status) - - def setMemberLanguage(self, member, language): - assert self._mlist.Locked() - self.__assertIsMember(member) - member = member.lower() - cpaddr, passwd, olang, name, digest, flags, sts = self._unpack(member) - self._pack(member, cpaddr, passwd, language, name, digest, flags, sts) - - def setMemberOption(self, member, flag, value): - assert self._mlist.Locked() - self.__assertIsMember(member) - member = member.lower() - cpaddr, passwd, lang, name, digest, options, sts = self._unpack(member) - # Sanity check for the digest flag - if flag == mm_cfg.Digests: - if value: - # Be sure the list supports digest delivery - if not self._mlist.digestable: - raise Errors.CantDigestError - digest = DIGDELIV - else: - # Be sure the list supports regular delivery - if not self._mlist.nondigestable: - raise Errors.MustDigestError - # When toggling off digest delivery, we want to be sure to set - # things up so that the user receives one last digest, - # otherwise they may lose some email - self._mlist.one_last_digest[member] = cpaddr - digest = REGDELIV - else: - if value: - options |= flag - else: - options &= ~flag - self._pack(member, cpaddr, passwd, lang, name, digest, options, sts) - - def setMemberName(self, member, realname): - assert self._mlist.Locked() - self.__assertIsMember(member) - member = member.lower() - cpaddr, passwd, lang, oldname, digest, flags, sts = self._unpack( - member) - self._pack(member, cpaddr, passwd, lang, realname, digest, flags, sts) - - def setMemberTopics(self, member, topics): - assert self._mlist.Locked() - self.__assertIsMember(member) - member = member.lower() - if topics: - self._topics.put(member, SEP.join(topics), txn=self._txn) - elif self._topics.has_key(member): - # No record is the same as no topics - self._topics.delete(member, self._txn) - - def setDeliveryStatus(self, member, status): - assert status in (MemberAdaptor.ENABLED, MemberAdaptor.UNKNOWN, - MemberAdaptor.BYUSER, MemberAdaptor.BYADMIN, - MemberAdaptor.BYBOUNCE) - assert self._mlist.Locked() - self.__assertIsMember(member) - if status == MemberAdaptor.ENABLED: - # Enable by resetting their bounce info - self.setBounceInfo(member, None) - else: - # Pickle up the status an the current time and store that in the - # database. Use binary mode. - data = pickle.dumps((status, time.time()), 1) - self._status.put(member.lower(), data, txn=self._txn) - - def setBounceInfo(self, member, info): - assert self._mlist.Locked() - self.__assertIsMember(member) - member = member.lower() - if info is None: - # This means to reset the bounce and delivery status information - if self._bounceinfo.has_key(member): - self._bounceinfo.delete(member, self._txn) - if self._status.has_key(member): - self._status.delete(member, self._txn) - else: - # Use binary mode - data = pickle.dumps(info, 1) - self._status.put(member, data, txn=self._txn) - - # The readable interface - - # BAW: It would be more efficient to simply return the iterator, but - # modules like admin.py can't handle that yet. They requires lists. - def getMembers(self): - return list(_AllMembersIterator(self._members)) - - def getRegularMemberKeys(self): - return list(_DeliveryMemberIterator(self._members, REGFLAG)) - - def getDigestMemberKeys(self): - return list(_DeliveryMemberIterator(self._members, DIGFLAG)) - - def __assertIsMember(self, member): - if not self.isMember(member): - raise Errors.NotAMemberError, member - - def isMember(self, member): - return self._members.has_key(member.lower()) - - def getMemberKey(self, member): - self.__assertIsMember(member) - return member.lower() - - def getMemberCPAddress(self, member): - self.__assertIsMember(member) - cpaddr = self._unpack(member)[CPADDR] - if cpaddr: - return cpaddr - return member - - def getMemberCPAddresses(self, members): - rtn = [] - for member in members: - member = member.lower() - if self._members.has_key(member): - rtn.append(self._unpack(member)[CPADDR]) - else: - rtn.append(None) - return rtn - - def authenticateMember(self, member, response): - self.__assertIsMember(member) - passwd = self._unpack(member)[PASSWD] - if passwd == response: - return passwd - return False - - def getMemberPassword(self, member): - self.__assertIsMember(member) - return self._unpack(member)[PASSWD] - - def getMemberLanguage(self, member): - if not self.isMember(member): - return self._mlist.preferred_language - lang = self._unpack(member)[LANG] - if lang in self._mlist.GetAvailableLanguages(): - return lang - return self._mlist.preferred_language - - def getMemberOption(self, member, flag): - self.__assertIsMember(member) - if flag == mm_cfg.Digests: - return self._unpack(member)[DIGEST] == DIGDELIV - options = self._unpack(member)[OPTIONS] - return bool(options & flag) - - def getMemberName(self, member): - self.__assertIsMember(member) - name = self._unpack(member)[NAME] - return name or None - - def getMemberTopics(self, member): - self.__assertIsMember(member) - topics = self._topics.get(member.lower(), '') - if not topics: - return [] - return topics.split(SEP) - - def getDeliveryStatus(self, member): - self.__assertIsMember(member) - data = self._status.get(member.lower()) - if data is None: - return MemberAdaptor.ENABLED - status, when = pickle.loads(data) - return status - - def getDeliveryStatusChangeTime(self, member): - self.__assertIsMember(member) - data = self._status.get(member.lower()) - if data is None: - return 0 - status, when = pickle.loads(data) - return when - - # BAW: see above, re iterators - def getDeliveryStatusMembers(self, status=(MemberAdaptor.UNKNOWN, - MemberAdaptor.BYUSER, - MemberAdaptor.BYADMIN, - MemberAdaptor.BYBOUNCE)): - return list(_StatusMemberIterator(self._members, self._status, status)) - - def getBouncingMembers(self): - return list(_BouncingMembersIterator(self._bounceinfo)) - - def getBounceInfo(self, member): - self.__assertIsMember(member) - return self._bounceinfo.get(member.lower()) - - - -class _MemberIterator: - def __init__(self, table): - self._table = table - self._c = table.cursor() - - def __iter__(self): - raise NotImplementedError - - def next(self): - raise NotImplementedError - - def close(self): - if self._c: - self._c.close() - self._c = None - - def __del__(self): - self.close() - - -class _AllMembersIterator(_MemberIterator): - def __iter__(self): - return _AllMembersIterator(self._table) - - def next(self): - rec = self._c.next() - if rec: - return rec[0] - self.close() - raise StopIteration - - -class _DeliveryMemberIterator(_MemberIterator): - def __init__(self, table, flag): - _MemberIterator.__init__(self, table) - self._flag = flag - - def __iter__(self): - return _DeliveryMemberIterator(self._table, self._flag) - - def next(self): - rec = self._c.next() - while rec: - addr, data = rec - if data[0] == self._flag: - return addr - rec = self._c.next() - self.close() - raise StopIteration - - -class _StatusMemberIterator(_MemberIterator): - def __init__(self, table, statustab, status): - _MemberIterator.__init__(self, table) - self._statustab = statustab - self._status = status - - def __iter__(self): - return _StatusMemberIterator(self._table, - self._statustab, - self._status) - - def next(self): - rec = self._c.next() - while rec: - addr = rec[0] - data = self._statustab.get(addr) - if data is None: - status = MemberAdaptor.ENABLED - else: - status, when = pickle.loads(data) - if status in self._status: - return addr - rec = self._c.next() - self.close() - raise StopIteration - - -class _BouncingMembersIterator(_MemberIterator): - def __iter__(self): - return _BouncingMembersIterator(self._table) - - def next(self): - rec = self._c.next() - if rec: - return rec[0] - self.close() - raise StopIteration - - - -# For extend.py -def fixlock(mlist): - def Lock(self, timeout=0): - MailList.Lock(self, timeout) - try: - self._memberadaptor.txn_begin() - except: - MailList.Unlock(self) - raise - mlist.Lock = new.instancemethod(Lock, mlist, MailList) - - -def fixsave(mlist): - def Save(self): - self._memberadaptor.txn_commit() - MailList.Save(self) - mlist.Save = new.instancemethod(Save, mlist, MailList) - - -def fixunlock(mlist): - def Unlock(self): - # It's fine to abort the transaction even if there isn't one in - # process, say because the Save() already committed it - self._memberadaptor.txn_abort() - MailList.Unlock(self) - mlist.Unlock = new.instancemethod(Unlock, mlist, MailList) - - -def extend(mlist): - mlist._memberadaptor = BDBMemberAdaptor(mlist) - fixlock(mlist) - fixsave(mlist) - fixunlock(mlist) - # To make sure we got everything, let's actually delete the - # OldStyleMemberships dictionaries. Assume if it has one, it has all - # attributes. - try: - del mlist.members - del mlist.digest_members - del mlist.passwords - del mlist.language - del mlist.user_options - del mlist.usernames - del mlist.topics_userinterest - del mlist.delivery_status - del mlist.bounce_info - except AttributeError: - pass - # BAW: How can we ensure that the BDBMemberAdaptor is closed? |