summaryrefslogtreecommitdiff
path: root/mailman/database
diff options
context:
space:
mode:
Diffstat (limited to 'mailman/database')
-rw-r--r--mailman/database/__init__.py153
-rw-r--r--mailman/database/address.py96
-rw-r--r--mailman/database/language.py40
-rw-r--r--mailman/database/listmanager.py82
-rw-r--r--mailman/database/mailinglist.py272
-rw-r--r--mailman/database/mailman.sql208
-rw-r--r--mailman/database/member.py105
-rw-r--r--mailman/database/message.py53
-rw-r--r--mailman/database/messagestore.py137
-rw-r--r--mailman/database/model.py56
-rw-r--r--mailman/database/pending.py177
-rw-r--r--mailman/database/preferences.py50
-rw-r--r--mailman/database/requests.py138
-rw-r--r--mailman/database/roster.py270
-rw-r--r--mailman/database/transaction.py53
-rw-r--r--mailman/database/types.py64
-rw-r--r--mailman/database/user.py94
-rw-r--r--mailman/database/usermanager.py103
-rw-r--r--mailman/database/version.py40
19 files changed, 0 insertions, 2191 deletions
diff --git a/mailman/database/__init__.py b/mailman/database/__init__.py
deleted file mode 100644
index 8b7f584c2..000000000
--- a/mailman/database/__init__.py
+++ /dev/null
@@ -1,153 +0,0 @@
-# Copyright (C) 2006-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'StockDatabase',
- ]
-
-import os
-import logging
-
-from locknix.lockfile import Lock
-from lazr.config import as_boolean
-from pkg_resources import resource_string
-from storm.locals import create_database, Store
-from urlparse import urlparse
-from zope.interface import implements
-
-import mailman.version
-
-from mailman.config import config
-from mailman.database.listmanager import ListManager
-from mailman.database.messagestore import MessageStore
-from mailman.database.pending import Pendings
-from mailman.database.requests import Requests
-from mailman.database.usermanager import UserManager
-from mailman.database.version import Version
-from mailman.interfaces.database import IDatabase, SchemaVersionMismatchError
-from mailman.utilities.string import expand
-
-log = logging.getLogger('mailman.config')
-
-
-
-class StockDatabase:
- """The standard database, using Storm on top of SQLite."""
-
- implements(IDatabase)
-
- def __init__(self):
- self.list_manager = None
- self.user_manager = None
- self.message_store = None
- self.pendings = None
- self.requests = None
- self._store = None
-
- def initialize(self, debug=None):
- """See `IDatabase`."""
- # Serialize this so we don't get multiple processes trying to create
- # the database at the same time.
- with Lock(os.path.join(config.LOCK_DIR, 'dbcreate.lck')):
- self._create(debug)
- self.list_manager = ListManager()
- self.user_manager = UserManager()
- self.message_store = MessageStore()
- self.pendings = Pendings()
- self.requests = Requests()
-
- def begin(self):
- """See `IDatabase`."""
- # Storm takes care of this for us.
- pass
-
- def commit(self):
- """See `IDatabase`."""
- self.store.commit()
-
- def abort(self):
- """See `IDatabase`."""
- self.store.rollback()
-
- def _create(self, debug):
- # Calculate the engine url.
- url = expand(config.database.url, config.paths)
- log.debug('Database url: %s', url)
- # XXX By design of SQLite, database file creation does not honor
- # umask. See their ticket #1193:
- # http://www.sqlite.org/cvstrac/tktview?tn=1193,31
- #
- # This sucks for us because the mailman.db file /must/ be group
- # writable, however even though we guarantee our umask is 002 here, it
- # still gets created without the necessary g+w permission, due to
- # SQLite's policy. This should only affect SQLite engines because its
- # the only one that creates a little file on the local file system.
- # This kludges around their bug by "touch"ing the database file before
- # SQLite has any chance to create it, thus honoring the umask and
- # ensuring the right permissions. We only try to do this for SQLite
- # engines, and yes, we could have chmod'd the file after the fact, but
- # half dozen and all...
- touch(url)
- database = create_database(url)
- store = Store(database)
- database.DEBUG = (as_boolean(config.database.debug)
- if debug is None else debug)
- # Check the sqlite master database to see if the version file exists.
- # If so, then we assume the database schema is correctly initialized.
- # Storm does not currently have schema creation. This is not an ideal
- # way to handle creating the database, but it's cheap and easy for
- # now.
- table_names = [item[0] for item in
- store.execute('select tbl_name from sqlite_master;')]
- if 'version' not in table_names:
- # Initialize the database.
- sql = resource_string('mailman.database', 'mailman.sql')
- for statement in sql.split(';'):
- store.execute(statement + ';')
- # Validate schema version.
- v = store.find(Version, component=u'schema').one()
- if not v:
- # Database has not yet been initialized
- v = Version(component='schema',
- version=mailman.version.DATABASE_SCHEMA_VERSION)
- store.add(v)
- elif v.version <> mailman.version.DATABASE_SCHEMA_VERSION:
- # XXX Update schema
- raise SchemaVersionMismatchError(v.version)
- self.store = store
- store.commit()
-
- def _reset(self):
- """See `IDatabase`."""
- from mailman.database.model import ModelMeta
- self.store.rollback()
- ModelMeta._reset(self.store)
-
-
-
-def touch(url):
- parts = urlparse(url)
- if parts.scheme <> 'sqlite':
- return
- path = os.path.normpath(parts.path)
- fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK | os.O_CREAT, 0666)
- # Ignore errors
- if fd > 0:
- os.close(fd)
diff --git a/mailman/database/address.py b/mailman/database/address.py
deleted file mode 100644
index 528d3af51..000000000
--- a/mailman/database/address.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# Copyright (C) 2006-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Model for addresses."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Address',
- ]
-
-
-from email.utils import formataddr
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.member import Member
-from mailman.database.model import Model
-from mailman.database.preferences import Preferences
-from mailman.interfaces.member import AlreadySubscribedError
-from mailman.interfaces.address import IAddress
-
-
-
-class Address(Model):
- implements(IAddress)
-
- id = Int(primary=True)
- address = Unicode()
- _original = Unicode()
- real_name = Unicode()
- verified_on = DateTime()
- registered_on = DateTime()
-
- user_id = Int()
- user = Reference(user_id, 'User.id')
- preferences_id = Int()
- preferences = Reference(preferences_id, 'Preferences.id')
-
- def __init__(self, address, real_name):
- super(Address, self).__init__()
- lower_case = address.lower()
- self.address = lower_case
- self.real_name = real_name
- self._original = (None if lower_case == address else address)
-
- def __str__(self):
- addr = (self.address if self._original is None else self._original)
- return formataddr((self.real_name, addr))
-
- def __repr__(self):
- verified = ('verified' if self.verified_on else 'not verified')
- address_str = str(self)
- if self._original is None:
- return '<Address: {0} [{1}] at {2:#x}>'.format(
- address_str, verified, id(self))
- else:
- return '<Address: {0} [{1}] key: {2} at {3:#x}>'.format(
- address_str, verified, self.address, id(self))
-
- def subscribe(self, mailing_list, role):
- # This member has no preferences by default.
- member = config.db.store.find(
- Member,
- Member.role == role,
- Member.mailing_list == mailing_list.fqdn_listname,
- Member.address == self).one()
- if member:
- raise AlreadySubscribedError(
- mailing_list.fqdn_listname, self.address, role)
- member = Member(role=role,
- mailing_list=mailing_list.fqdn_listname,
- address=self)
- member.preferences = Preferences()
- config.db.store.add(member)
- return member
-
- @property
- def original_address(self):
- return (self.address if self._original is None else self._original)
diff --git a/mailman/database/language.py b/mailman/database/language.py
deleted file mode 100644
index 8adc5c4a5..000000000
--- a/mailman/database/language.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2006-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Model for languages."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Language',
- ]
-
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.database import Model
-from mailman.interfaces import ILanguage
-
-
-
-class Language(Model):
- implements(ILanguage)
-
- id = Int(primary=True)
- code = Unicode()
diff --git a/mailman/database/listmanager.py b/mailman/database/listmanager.py
deleted file mode 100644
index 790a2509a..000000000
--- a/mailman/database/listmanager.py
+++ /dev/null
@@ -1,82 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""A mailing list manager."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'ListManager',
- ]
-
-
-import datetime
-
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.mailinglist import MailingList
-from mailman.interfaces.listmanager import IListManager, ListAlreadyExistsError
-
-
-
-class ListManager(object):
- """An implementation of the `IListManager` interface."""
-
- implements(IListManager)
-
- def create(self, fqdn_listname):
- """See `IListManager`."""
- listname, hostname = fqdn_listname.split('@', 1)
- mlist = config.db.store.find(
- MailingList,
- MailingList.list_name == listname,
- MailingList.host_name == hostname).one()
- if mlist:
- raise ListAlreadyExistsError(fqdn_listname)
- mlist = MailingList(fqdn_listname)
- mlist.created_at = datetime.datetime.now()
- config.db.store.add(mlist)
- return mlist
-
- def get(self, fqdn_listname):
- """See `IListManager`."""
- listname, hostname = fqdn_listname.split('@', 1)
- mlist = config.db.store.find(MailingList,
- list_name=listname,
- host_name=hostname).one()
- if mlist is not None:
- # XXX Fixme
- mlist._restore()
- return mlist
-
- def delete(self, mlist):
- """See `IListManager`."""
- config.db.store.remove(mlist)
-
- @property
- def mailing_lists(self):
- """See `IListManager`."""
- for fqdn_listname in self.names:
- yield self.get(fqdn_listname)
-
- @property
- def names(self):
- """See `IListManager`."""
- for mlist in config.db.store.find(MailingList):
- yield '{0}@{1}'.format(mlist.list_name, mlist.host_name)
diff --git a/mailman/database/mailinglist.py b/mailman/database/mailinglist.py
deleted file mode 100644
index 8803a5fa4..000000000
--- a/mailman/database/mailinglist.py
+++ /dev/null
@@ -1,272 +0,0 @@
-# Copyright (C) 2006-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Model for mailing lists."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'MailingList',
- ]
-
-
-import os
-import string
-
-from storm.locals import *
-from urlparse import urljoin
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database import roster
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.mailinglist import IMailingList, Personalization
-from mailman.utilities.filesystem import makedirs
-from mailman.utilities.string import expand
-
-
-SPACE = ' '
-UNDERSCORE = '_'
-
-
-
-class MailingList(Model):
- implements(IMailingList)
-
- id = Int(primary=True)
-
- # List identity
- list_name = Unicode()
- host_name = Unicode()
- # Attributes not directly modifiable via the web u/i
- created_at = DateTime()
- admin_member_chunksize = Int()
- hold_and_cmd_autoresponses = Pickle()
- # Attributes which are directly modifiable via the web u/i. The more
- # complicated attributes are currently stored as pickles, though that
- # will change as the schema and implementation is developed.
- next_request_id = Int()
- next_digest_number = Int()
- admin_responses = Pickle()
- postings_responses = Pickle()
- request_responses = Pickle()
- digest_last_sent_at = Float()
- one_last_digest = Pickle()
- volume = Int()
- last_post_time = DateTime()
- # Attributes which are directly modifiable via the web u/i. The more
- # complicated attributes are currently stored as pickles, though that
- # will change as the schema and implementation is developed.
- accept_these_nonmembers = Pickle()
- acceptable_aliases = Pickle()
- admin_immed_notify = Bool()
- admin_notify_mchanges = Bool()
- administrivia = Bool()
- advertised = Bool()
- anonymous_list = Bool()
- archive = Bool()
- archive_private = Bool()
- archive_volume_frequency = Int()
- autorespond_admin = Bool()
- autorespond_postings = Bool()
- autorespond_requests = Int()
- autoresponse_admin_text = Unicode()
- autoresponse_graceperiod = TimeDelta()
- autoresponse_postings_text = Unicode()
- autoresponse_request_text = Unicode()
- ban_list = Pickle()
- bounce_info_stale_after = TimeDelta()
- bounce_matching_headers = Unicode()
- bounce_notify_owner_on_disable = Bool()
- bounce_notify_owner_on_removal = Bool()
- bounce_processing = Bool()
- bounce_score_threshold = Int()
- bounce_unrecognized_goes_to_list_owner = Bool()
- bounce_you_are_disabled_warnings = Int()
- bounce_you_are_disabled_warnings_interval = TimeDelta()
- collapse_alternatives = Bool()
- convert_html_to_plaintext = Bool()
- default_member_moderation = Bool()
- description = Unicode()
- digest_footer = Unicode()
- digest_header = Unicode()
- digest_is_default = Bool()
- digest_send_periodic = Bool()
- digest_size_threshold = Int()
- digest_volume_frequency = Int()
- digestable = Bool()
- discard_these_nonmembers = Pickle()
- emergency = Bool()
- encode_ascii_prefixes = Bool()
- filter_action = Int()
- filter_content = Bool()
- filter_filename_extensions = Pickle()
- filter_mime_types = Pickle()
- first_strip_reply_to = Bool()
- forward_auto_discards = Bool()
- gateway_to_mail = Bool()
- gateway_to_news = Bool()
- generic_nonmember_action = Int()
- goodbye_msg = Unicode()
- header_matches = Pickle()
- hold_these_nonmembers = Pickle()
- include_list_post_header = Bool()
- include_rfc2369_headers = Bool()
- info = Unicode()
- linked_newsgroup = Unicode()
- max_days_to_hold = Int()
- max_message_size = Int()
- max_num_recipients = Int()
- member_moderation_action = Enum()
- member_moderation_notice = Unicode()
- mime_is_default_digest = Bool()
- moderator_password = Unicode()
- msg_footer = Unicode()
- msg_header = Unicode()
- new_member_options = Int()
- news_moderation = Enum()
- news_prefix_subject_too = Bool()
- nntp_host = Unicode()
- nondigestable = Bool()
- nonmember_rejection_notice = Unicode()
- obscure_addresses = Bool()
- pass_filename_extensions = Pickle()
- pass_mime_types = Pickle()
- personalize = Enum()
- pipeline = Unicode()
- post_id = Int()
- preferred_language = Unicode()
- private_roster = Bool()
- real_name = Unicode()
- reject_these_nonmembers = Pickle()
- reply_goes_to_list = Enum()
- reply_to_address = Unicode()
- require_explicit_destination = Bool()
- respond_to_post_requests = Bool()
- scrub_nondigest = Bool()
- send_goodbye_msg = Bool()
- send_reminders = Bool()
- send_welcome_msg = Bool()
- start_chain = Unicode()
- subject_prefix = Unicode()
- subscribe_auto_approval = Pickle()
- subscribe_policy = Int()
- topics = Pickle()
- topics_bodylines_limit = Int()
- topics_enabled = Bool()
- unsubscribe_policy = Int()
- welcome_msg = Unicode()
-
- def __init__(self, fqdn_listname):
- super(MailingList, self).__init__()
- listname, hostname = fqdn_listname.split('@', 1)
- self.list_name = listname
- self.host_name = hostname
- # For the pending database
- self.next_request_id = 1
- self._restore()
- # Max autoresponses per day. A mapping between addresses and a
- # 2-tuple of the date of the last autoresponse and the number of
- # autoresponses sent on that date.
- self.hold_and_cmd_autoresponses = {}
- self.personalization = Personalization.none
- self.real_name = string.capwords(
- SPACE.join(listname.split(UNDERSCORE)))
- makedirs(self.data_path)
-
- # XXX FIXME
- def _restore(self):
- self.owners = roster.OwnerRoster(self)
- self.moderators = roster.ModeratorRoster(self)
- self.administrators = roster.AdministratorRoster(self)
- self.members = roster.MemberRoster(self)
- self.regular_members = roster.RegularMemberRoster(self)
- self.digest_members = roster.DigestMemberRoster(self)
- self.subscribers = roster.Subscribers(self)
-
- @property
- def fqdn_listname(self):
- """See `IMailingList`."""
- return '{0}@{1}'.format(self.list_name, self.host_name)
-
- @property
- def web_host(self):
- """See `IMailingList`."""
- return config.domains[self.host_name]
-
- def script_url(self, target, context=None):
- """See `IMailingList`."""
- # Find the domain for this mailing list.
- domain = config.domains[self.host_name]
- # XXX Handle the case for when context is not None; those would be
- # relative URLs.
- return urljoin(domain.base_url, target + '/' + self.fqdn_listname)
-
- @property
- def data_path(self):
- """See `IMailingList`."""
- return os.path.join(config.LIST_DATA_DIR, self.fqdn_listname)
-
- # IMailingListAddresses
-
- @property
- def posting_address(self):
- return self.fqdn_listname
-
- @property
- def no_reply_address(self):
- return '{0}@{1}'.format(config.mailman.noreply_address, self.host_name)
-
- @property
- def owner_address(self):
- return '{0}-owner@{1}'.format(self.list_name, self.host_name)
-
- @property
- def request_address(self):
- return '{0}-request@{1}'.format(self.list_name, self.host_name)
-
- @property
- def bounces_address(self):
- return '{0}-bounces@{1}'.format(self.list_name, self.host_name)
-
- @property
- def join_address(self):
- return '{0}-join@{1}'.format(self.list_name, self.host_name)
-
- @property
- def leave_address(self):
- return '{0}-leave@{1}'.format(self.list_name, self.host_name)
-
- @property
- def subscribe_address(self):
- return '{0}-subscribe@{1}'.format(self.list_name, self.host_name)
-
- @property
- def unsubscribe_address(self):
- return '{0}-unsubscribe@{1}'.format(self.list_name, self.host_name)
-
- def confirm_address(self, cookie):
- local_part = expand(config.mta.verp_confirm_format, dict(
- address = '{0}-confirm'.format(self.list_name),
- cookie = cookie))
- return '{0}@{1}'.format(local_part, self.host_name)
-
- def __repr__(self):
- return '<mailing list "{0}" at {1:#x}>'.format(
- self.fqdn_listname, id(self))
diff --git a/mailman/database/mailman.sql b/mailman/database/mailman.sql
deleted file mode 100644
index b098ed13b..000000000
--- a/mailman/database/mailman.sql
+++ /dev/null
@@ -1,208 +0,0 @@
-CREATE TABLE _request (
- id INTEGER NOT NULL,
- "key" TEXT,
- request_type TEXT,
- data_hash TEXT,
- mailing_list_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT _request_mailing_list_id_fk FOREIGN KEY(mailing_list_id) REFERENCES mailinglist (id)
-);
-CREATE TABLE address (
- id INTEGER NOT NULL,
- address TEXT,
- _original TEXT,
- real_name TEXT,
- verified_on TIMESTAMP,
- registered_on TIMESTAMP,
- user_id INTEGER,
- preferences_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT address_user_id_fk FOREIGN KEY(user_id) REFERENCES user (id),
- CONSTRAINT address_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id)
-);
-CREATE TABLE language (
- id INTEGER NOT NULL,
- code TEXT,
- PRIMARY KEY (id)
-);
-CREATE TABLE mailinglist (
- id INTEGER NOT NULL,
- list_name TEXT,
- host_name TEXT,
- created_at TIMESTAMP,
- admin_member_chunksize INTEGER,
- hold_and_cmd_autoresponses BLOB,
- next_request_id INTEGER,
- next_digest_number INTEGER,
- admin_responses BLOB,
- postings_responses BLOB,
- request_responses BLOB,
- digest_last_sent_at NUMERIC(10, 2),
- one_last_digest BLOB,
- volume INTEGER,
- last_post_time TIMESTAMP,
- accept_these_nonmembers BLOB,
- acceptable_aliases BLOB,
- admin_immed_notify BOOLEAN,
- admin_notify_mchanges BOOLEAN,
- administrivia BOOLEAN,
- advertised BOOLEAN,
- anonymous_list BOOLEAN,
- archive BOOLEAN,
- archive_private BOOLEAN,
- archive_volume_frequency INTEGER,
- autorespond_admin BOOLEAN,
- autorespond_postings BOOLEAN,
- autorespond_requests INTEGER,
- autoresponse_admin_text TEXT,
- autoresponse_graceperiod TEXT,
- autoresponse_postings_text TEXT,
- autoresponse_request_text TEXT,
- ban_list BLOB,
- bounce_info_stale_after TEXT,
- bounce_matching_headers TEXT,
- bounce_notify_owner_on_disable BOOLEAN,
- bounce_notify_owner_on_removal BOOLEAN,
- bounce_processing BOOLEAN,
- bounce_score_threshold INTEGER,
- bounce_unrecognized_goes_to_list_owner BOOLEAN,
- bounce_you_are_disabled_warnings INTEGER,
- bounce_you_are_disabled_warnings_interval TEXT,
- collapse_alternatives BOOLEAN,
- convert_html_to_plaintext BOOLEAN,
- default_member_moderation BOOLEAN,
- description TEXT,
- digest_footer TEXT,
- digest_header TEXT,
- digest_is_default BOOLEAN,
- digest_send_periodic BOOLEAN,
- digest_size_threshold INTEGER,
- digest_volume_frequency INTEGER,
- digestable BOOLEAN,
- discard_these_nonmembers BLOB,
- emergency BOOLEAN,
- encode_ascii_prefixes BOOLEAN,
- filter_action INTEGER,
- filter_content BOOLEAN,
- filter_filename_extensions BLOB,
- filter_mime_types BLOB,
- first_strip_reply_to BOOLEAN,
- forward_auto_discards BOOLEAN,
- gateway_to_mail BOOLEAN,
- gateway_to_news BOOLEAN,
- generic_nonmember_action INTEGER,
- goodbye_msg TEXT,
- header_matches BLOB,
- hold_these_nonmembers BLOB,
- include_list_post_header BOOLEAN,
- include_rfc2369_headers BOOLEAN,
- info TEXT,
- linked_newsgroup TEXT,
- max_days_to_hold INTEGER,
- max_message_size INTEGER,
- max_num_recipients INTEGER,
- member_moderation_action BOOLEAN,
- member_moderation_notice TEXT,
- mime_is_default_digest BOOLEAN,
- moderator_password TEXT,
- msg_footer TEXT,
- msg_header TEXT,
- new_member_options INTEGER,
- news_moderation TEXT,
- news_prefix_subject_too BOOLEAN,
- nntp_host TEXT,
- nondigestable BOOLEAN,
- nonmember_rejection_notice TEXT,
- obscure_addresses BOOLEAN,
- pass_filename_extensions BLOB,
- pass_mime_types BLOB,
- personalize TEXT,
- pipeline TEXT,
- post_id INTEGER,
- preferred_language TEXT,
- private_roster BOOLEAN,
- real_name TEXT,
- reject_these_nonmembers BLOB,
- reply_goes_to_list TEXT,
- reply_to_address TEXT,
- require_explicit_destination BOOLEAN,
- respond_to_post_requests BOOLEAN,
- scrub_nondigest BOOLEAN,
- send_goodbye_msg BOOLEAN,
- send_reminders BOOLEAN,
- send_welcome_msg BOOLEAN,
- start_chain TEXT,
- subject_prefix TEXT,
- subscribe_auto_approval BLOB,
- subscribe_policy INTEGER,
- topics BLOB,
- topics_bodylines_limit INTEGER,
- topics_enabled BOOLEAN,
- unsubscribe_policy INTEGER,
- welcome_msg TEXT,
- PRIMARY KEY (id)
-);
-CREATE TABLE member (
- id INTEGER NOT NULL,
- role TEXT,
- mailing_list TEXT,
- is_moderated BOOLEAN,
- address_id INTEGER,
- preferences_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT member_address_id_fk FOREIGN KEY(address_id) REFERENCES address (id),
- CONSTRAINT member_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id)
-);
-CREATE TABLE message (
- id INTEGER NOT NULL,
- message_id_hash TEXT,
- path TEXT,
- message_id TEXT,
- PRIMARY KEY (id)
-);
-CREATE TABLE pended (
- id INTEGER NOT NULL,
- token TEXT,
- expiration_date TIMESTAMP,
- PRIMARY KEY (id)
-);
-CREATE TABLE pendedkeyvalue (
- id INTEGER NOT NULL,
- "key" TEXT,
- value TEXT,
- pended_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT pendedkeyvalue_pended_id_fk FOREIGN KEY(pended_id) REFERENCES pended (id)
-);
-CREATE TABLE preferences (
- id INTEGER NOT NULL,
- acknowledge_posts BOOLEAN,
- hide_address BOOLEAN,
- preferred_language TEXT,
- receive_list_copy BOOLEAN,
- receive_own_postings BOOLEAN,
- delivery_mode TEXT,
- delivery_status TEXT,
- PRIMARY KEY (id)
-);
-CREATE TABLE user (
- id INTEGER NOT NULL,
- real_name TEXT,
- password TEXT,
- preferences_id INTEGER,
- PRIMARY KEY (id),
- CONSTRAINT user_preferences_id_fk FOREIGN KEY(preferences_id) REFERENCES preferences (id)
-);
-CREATE TABLE version (
- id INTEGER NOT NULL,
- component TEXT,
- version INTEGER,
- PRIMARY KEY (id)
-);
-CREATE INDEX ix__request_mailing_list_id ON _request (mailing_list_id);
-CREATE INDEX ix_address_preferences_id ON address (preferences_id);
-CREATE INDEX ix_address_user_id ON address (user_id);
-CREATE INDEX ix_member_address_id ON member (address_id);
-CREATE INDEX ix_member_preferences_id ON member (preferences_id);
-CREATE INDEX ix_pendedkeyvalue_pended_id ON pendedkeyvalue (pended_id);
-CREATE INDEX ix_user_preferences_id ON user (preferences_id);
diff --git a/mailman/database/member.py b/mailman/database/member.py
deleted file mode 100644
index 22bf042f6..000000000
--- a/mailman/database/member.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Model for members."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Member',
- ]
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.constants import SystemDefaultPreferences
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.member import IMember
-
-
-
-class Member(Model):
- implements(IMember)
-
- id = Int(primary=True)
- role = Enum()
- mailing_list = Unicode()
- is_moderated = Bool()
-
- address_id = Int()
- address = Reference(address_id, 'Address.id')
- preferences_id = Int()
- preferences = Reference(preferences_id, 'Preferences.id')
-
- def __init__(self, role, mailing_list, address):
- self.role = role
- self.mailing_list = mailing_list
- self.address = address
- self.is_moderated = False
-
- def __repr__(self):
- return '<Member: {0} on {1} as {2}>'.format(
- self.address, self.mailing_list, self.role)
-
- def _lookup(self, preference):
- pref = getattr(self.preferences, preference)
- if pref is not None:
- return pref
- pref = getattr(self.address.preferences, preference)
- if pref is not None:
- return pref
- if self.address.user:
- pref = getattr(self.address.user.preferences, preference)
- if pref is not None:
- return pref
- return getattr(SystemDefaultPreferences, preference)
-
- @property
- def acknowledge_posts(self):
- return self._lookup('acknowledge_posts')
-
- @property
- def preferred_language(self):
- return self._lookup('preferred_language')
-
- @property
- def receive_list_copy(self):
- return self._lookup('receive_list_copy')
-
- @property
- def receive_own_postings(self):
- return self._lookup('receive_own_postings')
-
- @property
- def delivery_mode(self):
- return self._lookup('delivery_mode')
-
- @property
- def delivery_status(self):
- return self._lookup('delivery_status')
-
- @property
- def options_url(self):
- # XXX Um, this is definitely wrong
- return 'http://example.com/' + self.address.address
-
- def unsubscribe(self):
- config.db.store.remove(self.preferences)
- config.db.store.remove(self)
diff --git a/mailman/database/message.py b/mailman/database/message.py
deleted file mode 100644
index e77e11429..000000000
--- a/mailman/database/message.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Model for messages."""
-
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Message',
- ]
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.interfaces.messages import IMessage
-
-
-
-class Message(Model):
- """A message in the message store."""
-
- implements(IMessage)
-
- id = Int(primary=True, default=AutoReload)
- message_id = Unicode()
- message_id_hash = RawStr()
- path = RawStr()
- # This is a Messge-ID field representation, not a database row id.
-
- def __init__(self, message_id, message_id_hash, path):
- super(Message, self).__init__()
- self.message_id = message_id
- self.message_id_hash = message_id_hash
- self.path = path
- config.db.store.add(self)
diff --git a/mailman/database/messagestore.py b/mailman/database/messagestore.py
deleted file mode 100644
index a129f47ec..000000000
--- a/mailman/database/messagestore.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Model for message stores."""
-
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'MessageStore',
- ]
-
-import os
-import errno
-import base64
-import hashlib
-import cPickle as pickle
-
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.message import Message
-from mailman.interfaces.messages import IMessageStore
-from mailman.utilities.filesystem import makedirs
-
-
-# It could be very bad if you have already stored files and you change this
-# value. We'd need a script to reshuffle and resplit.
-MAX_SPLITS = 2
-EMPTYSTRING = ''
-
-
-
-class MessageStore:
- implements(IMessageStore)
-
- def add(self, message):
- # Ensure that the message has the requisite headers.
- message_ids = message.get_all('message-id', [])
- if len(message_ids) <> 1:
- raise ValueError('Exactly one Message-ID header required')
- # Calculate and insert the X-Message-ID-Hash.
- message_id = message_ids[0]
- # Complain if the Message-ID already exists in the storage.
- existing = config.db.store.find(Message,
- Message.message_id == message_id).one()
- if existing is not None:
- raise ValueError(
- 'Message ID already exists in message store: {0}'.format(
- message_id))
- shaobj = hashlib.sha1(message_id)
- hash32 = base64.b32encode(shaobj.digest())
- del message['X-Message-ID-Hash']
- message['X-Message-ID-Hash'] = hash32
- # Calculate the path on disk where we're going to store this message
- # object, in pickled format.
- parts = []
- split = list(hash32)
- while split and len(parts) < MAX_SPLITS:
- parts.append(split.pop(0) + split.pop(0))
- parts.append(hash32)
- relpath = os.path.join(*parts)
- # Store the message in the database. This relies on the database
- # providing a unique serial number, but to get this information, we
- # have to use a straight insert instead of relying on Elixir to create
- # the object.
- row = Message(message_id=message_id,
- message_id_hash=hash32,
- path=relpath)
- # Now calculate the full file system path.
- path = os.path.join(config.MESSAGES_DIR, relpath)
- # Write the file to the path, but catch the appropriate exception in
- # case the parent directories don't yet exist. In that case, create
- # them and try again.
- while True:
- try:
- with open(path, 'w') as fp:
- # -1 says to use the highest protocol available.
- pickle.dump(message, fp, -1)
- break
- except IOError as error:
- if error.errno <> errno.ENOENT:
- raise
- makedirs(os.path.dirname(path))
- return hash32
-
- def _get_message(self, row):
- path = os.path.join(config.MESSAGES_DIR, row.path)
- with open(path) as fp:
- return pickle.load(fp)
-
- def get_message_by_id(self, message_id):
- row = config.db.store.find(Message, message_id=message_id).one()
- if row is None:
- return None
- return self._get_message(row)
-
- def get_message_by_hash(self, message_id_hash):
- # It's possible the hash came from a message header, in which case it
- # will be a Unicode. However when coming from source code, it may be
- # an 8-string. Coerce to the latter if necessary; it must be
- # US-ASCII.
- if isinstance(message_id_hash, unicode):
- message_id_hash = message_id_hash.encode('ascii')
- row = config.db.store.find(Message,
- message_id_hash=message_id_hash).one()
- if row is None:
- return None
- return self._get_message(row)
-
- @property
- def messages(self):
- for row in config.db.store.find(Message):
- yield self._get_message(row)
-
- def delete_message(self, message_id):
- row = config.db.store.find(Message, message_id=message_id).one()
- if row is None:
- raise LookupError(message_id)
- path = os.path.join(config.MESSAGES_DIR, row.path)
- os.remove(path)
- config.db.store.remove(row)
diff --git a/mailman/database/model.py b/mailman/database/model.py
deleted file mode 100644
index 85fa033c7..000000000
--- a/mailman/database/model.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Copyright (C) 2006-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Base class for all database classes."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Model',
- ]
-
-from storm.properties import PropertyPublisherMeta
-
-
-
-class ModelMeta(PropertyPublisherMeta):
- """Do more magic on table classes."""
-
- _class_registry = set()
-
- def __init__(self, name, bases, dict):
- # Before we let the base class do it's thing, force an __storm_table__
- # property to enforce our table naming convention.
- self.__storm_table__ = name.lower()
- super(ModelMeta, self).__init__(name, bases, dict)
- # Register the model class so that it can be more easily cleared.
- # This is required by the test framework.
- if name == 'Model':
- return
- ModelMeta._class_registry.add(self)
-
- @staticmethod
- def _reset(store):
- for model_class in ModelMeta._class_registry:
- store.find(model_class).remove()
-
-
-
-class Model:
- """Like Storm's `Storm` subclass, but with a bit extra."""
- __metaclass__ = ModelMeta
diff --git a/mailman/database/pending.py b/mailman/database/pending.py
deleted file mode 100644
index f4c2057e0..000000000
--- a/mailman/database/pending.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Implementations of the IPendable and IPending interfaces."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Pended',
- 'Pendings',
- ]
-
-import sys
-import time
-import random
-import hashlib
-import datetime
-
-from lazr.config import as_timedelta
-from storm.locals import *
-from zope.interface import implements
-from zope.interface.verify import verifyObject
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.interfaces.pending import (
- IPendable, IPended, IPendedKeyValue, IPendings)
-
-
-
-class PendedKeyValue(Model):
- """A pended key/value pair, tied to a token."""
-
- implements(IPendedKeyValue)
-
- def __init__(self, key, value):
- self.key = key
- self.value = value
-
- id = Int(primary=True)
- key = Unicode()
- value = Unicode()
- pended_id = Int()
-
-
-class Pended(Model):
- """A pended event, tied to a token."""
-
- implements(IPended)
-
- def __init__(self, token, expiration_date):
- super(Pended, self).__init__()
- self.token = token
- self.expiration_date = expiration_date
-
- id = Int(primary=True)
- token = RawStr()
- expiration_date = DateTime()
- key_values = ReferenceSet(id, PendedKeyValue.pended_id)
-
-
-
-class UnpendedPendable(dict):
- implements(IPendable)
-
-
-
-class Pendings:
- """Implementation of the IPending interface."""
-
- implements(IPendings)
-
- def add(self, pendable, lifetime=None):
- verifyObject(IPendable, pendable)
- # Calculate the token and the lifetime.
- if lifetime is None:
- lifetime = as_timedelta(config.mailman.pending_request_life)
- # Calculate a unique token. 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.
- for attempts in range(3):
- now = time.time()
- x = random.random() + now % 1.0 + time.clock() % 1.0
- # Use sha1 because it produces shorter strings.
- token = hashlib.sha1(repr(x)).hexdigest()
- # In practice, we'll never get a duplicate, but we'll be anal
- # about checking anyway.
- if config.db.store.find(Pended, token=token).count() == 0:
- break
- else:
- raise AssertionError('Could not find a valid pendings token')
- # Create the record, and then the individual key/value pairs.
- pending = Pended(
- token=token,
- expiration_date=datetime.datetime.now() + lifetime)
- for key, value in pendable.items():
- if isinstance(key, str):
- key = unicode(key, 'utf-8')
- if isinstance(value, str):
- value = unicode(value, 'utf-8')
- elif type(value) is int:
- value = '__builtin__.int\1%s' % value
- elif type(value) is float:
- value = '__builtin__.float\1%s' % value
- elif type(value) is bool:
- value = '__builtin__.bool\1%s' % value
- elif type(value) is list:
- # We expect this to be a list of strings.
- value = ('mailman.database.pending.unpack_list\1' +
- '\2'.join(value))
- keyval = PendedKeyValue(key=key, value=value)
- pending.key_values.add(keyval)
- config.db.store.add(pending)
- return token
-
- def confirm(self, token, expunge=True):
- store = config.db.store
- pendings = store.find(Pended, token=token)
- if pendings.count() == 0:
- return None
- assert pendings.count() == 1, (
- 'Unexpected token count: {0}'.format(pendings.count()))
- pending = pendings[0]
- pendable = UnpendedPendable()
- # Find all PendedKeyValue entries that are associated with the pending
- # object's ID. Watch out for type conversions.
- for keyvalue in store.find(PendedKeyValue,
- PendedKeyValue.pended_id == pending.id):
- if keyvalue.value is not None and '\1' in keyvalue.value:
- typename, value = keyvalue.value.split('\1', 1)
- package, classname = typename.rsplit('.', 1)
- __import__(package)
- module = sys.modules[package]
- pendable[keyvalue.key] = getattr(module, classname)(value)
- else:
- pendable[keyvalue.key] = keyvalue.value
- if expunge:
- store.remove(keyvalue)
- if expunge:
- store.remove(pending)
- return pendable
-
- def evict(self):
- store = config.db.store
- now = datetime.datetime.now()
- for pending in store.find(Pended):
- if pending.expiration_date < now:
- # Find all PendedKeyValue entries that are associated with the
- # pending object's ID.
- q = store.find(PendedKeyValue,
- PendedKeyValue.pended_id == pending.id)
- for keyvalue in q:
- store.remove(keyvalue)
- store.remove(pending)
-
-
-
-def unpack_list(value):
- return value.split('\2')
diff --git a/mailman/database/preferences.py b/mailman/database/preferences.py
deleted file mode 100644
index f3ee55673..000000000
--- a/mailman/database/preferences.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2006-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Model for preferences."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Preferences',
- ]
-
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.preferences import IPreferences
-
-
-
-class Preferences(Model):
- implements(IPreferences)
-
- id = Int(primary=True)
- acknowledge_posts = Bool()
- hide_address = Bool()
- preferred_language = Unicode()
- receive_list_copy = Bool()
- receive_own_postings = Bool()
- delivery_mode = Enum()
- delivery_status = Enum()
-
- def __repr__(self):
- return '<Preferences object at {0:#x}>'.format(id(self))
diff --git a/mailman/database/requests.py b/mailman/database/requests.py
deleted file mode 100644
index 249feb6b6..000000000
--- a/mailman/database/requests.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Implementations of the IRequests and IListRequests interfaces."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Requests',
- ]
-
-
-from datetime import timedelta
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.database.types import Enum
-from mailman.interfaces.pending import IPendable
-from mailman.interfaces.requests import IListRequests, IRequests, RequestType
-
-
-
-class DataPendable(dict):
- implements(IPendable)
-
-
-
-class ListRequests:
- implements(IListRequests)
-
- def __init__(self, mailing_list):
- self.mailing_list = mailing_list
-
- @property
- def count(self):
- return config.db.store.find(
- _Request, mailing_list=self.mailing_list).count()
-
- def count_of(self, request_type):
- return config.db.store.find(
- _Request,
- mailing_list=self.mailing_list, request_type=request_type).count()
-
- @property
- def held_requests(self):
- results = config.db.store.find(
- _Request, mailing_list=self.mailing_list)
- for request in results:
- yield request
-
- def of_type(self, request_type):
- results = config.db.store.find(
- _Request,
- mailing_list=self.mailing_list, request_type=request_type)
- for request in results:
- yield request
-
- def hold_request(self, request_type, key, data=None):
- if request_type not in RequestType:
- raise TypeError(request_type)
- if data is None:
- data_hash = None
- else:
- # We're abusing the pending database as a way of storing arbitrary
- # key/value pairs, where both are strings. This isn't ideal but
- # it lets us get auxiliary data almost for free. We may need to
- # lock this down more later.
- pendable = DataPendable()
- pendable.update(data)
- token = config.db.pendings.add(pendable, timedelta(days=5000))
- data_hash = token
- request = _Request(key, request_type, self.mailing_list, data_hash)
- config.db.store.add(request)
- return request.id
-
- def get_request(self, request_id):
- result = config.db.store.get(_Request, request_id)
- if result is None:
- return None
- if result.data_hash is None:
- return result.key, result.data_hash
- pendable = config.db.pendings.confirm(result.data_hash, expunge=False)
- data = dict()
- data.update(pendable)
- return result.key, data
-
- def delete_request(self, request_id):
- request = config.db.store.get(_Request, request_id)
- if request is None:
- raise KeyError(request_id)
- # Throw away the pended data.
- config.db.pendings.confirm(request.data_hash)
- config.db.store.remove(request)
-
-
-
-class Requests:
- implements(IRequests)
-
- def get_list_requests(self, mailing_list):
- return ListRequests(mailing_list)
-
-
-
-class _Request(Model):
- """Table for mailing list hold requests."""
-
- id = Int(primary=True, default=AutoReload)
- key = Unicode()
- request_type = Enum()
- data_hash = RawStr()
-
- mailing_list_id = Int()
- mailing_list = Reference(mailing_list_id, 'MailingList.id')
-
- def __init__(self, key, request_type, mailing_list, data_hash):
- super(_Request, self).__init__()
- self.key = key
- self.request_type = request_type
- self.mailing_list = mailing_list
- self.data_hash = data_hash
diff --git a/mailman/database/roster.py b/mailman/database/roster.py
deleted file mode 100644
index fc0a24c7d..000000000
--- a/mailman/database/roster.py
+++ /dev/null
@@ -1,270 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""An implementation of an IRoster.
-
-These are hard-coded rosters which know how to filter a set of members to find
-the ones that fit a particular role. These are used as the member, owner,
-moderator, and administrator roster filters.
-"""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'AdministratorRoster',
- 'DigestMemberRoster',
- 'MemberRoster',
- 'Memberships',
- 'ModeratorRoster',
- 'OwnerRoster',
- 'RegularMemberRoster',
- 'Subscribers',
- ]
-
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.address import Address
-from mailman.database.member import Member
-from mailman.interfaces.member import DeliveryMode, MemberRole
-from mailman.interfaces.roster import IRoster
-
-
-
-class AbstractRoster:
- """An abstract IRoster class.
-
- This class takes the simple approach of implemented the 'users' and
- 'addresses' properties in terms of the 'members' property. This may not
- be the most efficient way, but it works.
-
- This requires that subclasses implement the 'members' property.
- """
- implements(IRoster)
-
- role = None
-
- def __init__(self, mlist):
- self._mlist = mlist
-
- @property
- def members(self):
- for member in config.db.store.find(
- Member,
- mailing_list=self._mlist.fqdn_listname,
- role=self.role):
- yield member
-
- @property
- def users(self):
- # Members are linked to addresses, which in turn are linked to users.
- # So while the 'members' attribute does most of the work, we have to
- # keep a set of unique users. It's possible for the same user to be
- # subscribed to a mailing list multiple times with different
- # addresses.
- users = set(member.address.user for member in self.members)
- for user in users:
- yield user
-
- @property
- def addresses(self):
- # Every Member is linked to exactly one address so the 'members'
- # attribute does most of the work.
- for member in self.members:
- yield member.address
-
- def get_member(self, address):
- results = config.db.store.find(
- Member,
- Member.mailing_list == self._mlist.fqdn_listname,
- Member.role == self.role,
- Address.address == address,
- Member.address_id == Address.id)
- if results.count() == 0:
- return None
- elif results.count() == 1:
- return results[0]
- else:
- raise AssertionError(
- 'Too many matching member results: {0}'.format(
- results.count()))
-
-
-
-class MemberRoster(AbstractRoster):
- """Return all the members of a list."""
-
- name = 'member'
- role = MemberRole.member
-
-
-
-class OwnerRoster(AbstractRoster):
- """Return all the owners of a list."""
-
- name = 'owner'
- role = MemberRole.owner
-
-
-
-class ModeratorRoster(AbstractRoster):
- """Return all the owners of a list."""
-
- name = 'moderator'
- role = MemberRole.moderator
-
-
-
-class AdministratorRoster(AbstractRoster):
- """Return all the administrators of a list."""
-
- name = 'administrator'
-
- @property
- def members(self):
- # Administrators are defined as the union of the owners and the
- # moderators.
- members = config.db.store.find(
- Member,
- Member.mailing_list == self._mlist.fqdn_listname,
- Or(Member.role == MemberRole.owner,
- Member.role == MemberRole.moderator))
- for member in members:
- yield member
-
- def get_member(self, address):
- results = config.db.store.find(
- Member,
- Member.mailing_list == self._mlist.fqdn_listname,
- Or(Member.role == MemberRole.moderator,
- Member.role == MemberRole.owner),
- Address.address == address,
- Member.address_id == Address.id)
- if results.count() == 0:
- return None
- elif results.count() == 1:
- return results[0]
- else:
- raise AssertionError(
- 'Too many matching member results: {0}'.format(results))
-
-
-
-class RegularMemberRoster(AbstractRoster):
- """Return all the regular delivery members of a list."""
-
- name = 'regular_members'
-
- @property
- def members(self):
- # Query for all the Members which have a role of MemberRole.member and
- # are subscribed to this mailing list. Then return only those members
- # that have a regular delivery mode.
- for member in config.db.store.find(
- Member,
- mailing_list=self._mlist.fqdn_listname,
- role=MemberRole.member):
- if member.delivery_mode == DeliveryMode.regular:
- yield member
-
-
-
-_digest_modes = (
- DeliveryMode.mime_digests,
- DeliveryMode.plaintext_digests,
- DeliveryMode.summary_digests,
- )
-
-
-
-class DigestMemberRoster(AbstractRoster):
- """Return all the regular delivery members of a list."""
-
- name = 'digest_members'
-
- @property
- def members(self):
- # Query for all the Members which have a role of MemberRole.member and
- # are subscribed to this mailing list. Then return only those members
- # that have one of the digest delivery modes.
- for member in config.db.store.find(
- Member,
- mailing_list=self._mlist.fqdn_listname,
- role=MemberRole.member):
- if member.delivery_mode in _digest_modes:
- yield member
-
-
-
-class Subscribers(AbstractRoster):
- """Return all subscribed members regardless of their role."""
-
- name = 'subscribers'
-
- @property
- def members(self):
- for member in config.db.store.find(
- Member,
- mailing_list=self._mlist.fqdn_listname):
- yield member
-
-
-
-class Memberships:
- """A roster of a single user's memberships."""
-
- implements(IRoster)
-
- name = 'memberships'
-
- def __init__(self, user):
- self._user = user
-
- @property
- def members(self):
- results = config.db.store.find(
- Member,
- Address.user_id == self._user.id,
- Member.address_id == Address.id)
- for member in results:
- yield member
-
- @property
- def users(self):
- yield self._user
-
- @property
- def addresses(self):
- for address in self._user.addresses:
- yield address
-
- def get_member(self, address):
- results = config.db.store.find(
- Member,
- Member.address_id == Address.id,
- Address.user_id == self._user.id)
- if results.count() == 0:
- return None
- elif results.count() == 1:
- return results[0]
- else:
- raise AssertionError(
- 'Too many matching member results: {0}'.format(
- results.count()))
diff --git a/mailman/database/transaction.py b/mailman/database/transaction.py
deleted file mode 100644
index d42562389..000000000
--- a/mailman/database/transaction.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 2006-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Transactional support."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'txn',
- ]
-
-
-from mailman.config import config
-
-
-
-class txn(object):
- """Decorator for transactional support.
-
- When the function this decorator wraps exits cleanly, the current
- transaction is committed. When it exits uncleanly (i.e. because of an
- exception, the transaction is aborted.
-
- Either way, the current transaction is completed.
- """
- def __init__(self, function):
- self._function = function
-
- def __get__(self, obj, type=None):
- def wrapper(*args, **kws):
- try:
- rtn = self._function(obj, *args, **kws)
- config.db.commit()
- return rtn
- except:
- config.db.abort()
- raise
- return wrapper
diff --git a/mailman/database/types.py b/mailman/database/types.py
deleted file mode 100644
index 2f901fe49..000000000
--- a/mailman/database/types.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Storm type conversions."""
-
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Enum',
- ]
-
-
-import sys
-
-from storm.properties import SimpleProperty
-from storm.variables import Variable
-
-
-
-class _EnumVariable(Variable):
- """Storm variable."""
-
- def parse_set(self, value, from_db):
- if value is None:
- return None
- if not from_db:
- return value
- path, intvalue = value.rsplit(':', 1)
- modulename, classname = path.rsplit('.', 1)
- __import__(modulename)
- cls = getattr(sys.modules[modulename], classname)
- return cls[int(intvalue)]
-
- def parse_get(self, value, to_db):
- if value is None:
- return None
- if not to_db:
- return value
- return '{0}.{1}:{2}'.format(
- value.enumclass.__module__,
- value.enumclass.__name__,
- int(value))
-
-
-class Enum(SimpleProperty):
- """Custom munepy.Enum type for Storm."""
-
- variable_class = _EnumVariable
diff --git a/mailman/database/user.py b/mailman/database/user.py
deleted file mode 100644
index 23701686b..000000000
--- a/mailman/database/user.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Model for users."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'User',
- ]
-
-from storm.locals import *
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.model import Model
-from mailman.database.address import Address
-from mailman.database.preferences import Preferences
-from mailman.database.roster import Memberships
-from mailman.interfaces.address import (
- AddressAlreadyLinkedError, AddressNotLinkedError)
-from mailman.interfaces.user import IUser
-
-
-
-class User(Model):
- """Mailman users."""
-
- implements(IUser)
-
- id = Int(primary=True)
- real_name = Unicode()
- password = Unicode()
-
- addresses = ReferenceSet(id, 'Address.user_id')
- preferences_id = Int()
- preferences = Reference(preferences_id, 'Preferences.id')
-
- def __repr__(self):
- return '<User "{0}" at {1:#x}>'.format(self.real_name, id(self))
-
- def link(self, address):
- """See `IUser`."""
- if address.user is not None:
- raise AddressAlreadyLinkedError(address)
- address.user = self
-
- def unlink(self, address):
- """See `IUser`."""
- if address.user is None:
- raise AddressNotLinkedError(address)
- address.user = None
-
- def controls(self, address):
- """See `IUser`."""
- found = config.db.store.find(Address, address=address)
- if found.count() == 0:
- return False
- assert found.count() == 1, 'Unexpected count'
- return found[0].user is self
-
- def register(self, address, real_name=None):
- """See `IUser`."""
- # First, see if the address already exists
- addrobj = config.db.store.find(Address, address=address).one()
- if addrobj is None:
- if real_name is None:
- real_name = ''
- addrobj = Address(address=address, real_name=real_name)
- addrobj.preferences = Preferences()
- # Link the address to the user if it is not already linked.
- if addrobj.user is not None:
- raise AddressAlreadyLinkedError(addrobj)
- addrobj.user = self
- return addrobj
-
- @property
- def memberships(self):
- return Memberships(self)
diff --git a/mailman/database/usermanager.py b/mailman/database/usermanager.py
deleted file mode 100644
index 3b0c8b534..000000000
--- a/mailman/database/usermanager.py
+++ /dev/null
@@ -1,103 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""A user manager."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'UserManager',
- ]
-
-from zope.interface import implements
-
-from mailman.config import config
-from mailman.database.address import Address
-from mailman.database.preferences import Preferences
-from mailman.database.user import User
-from mailman.interfaces.address import ExistingAddressError
-from mailman.interfaces.usermanager import IUserManager
-
-
-
-class UserManager(object):
- implements(IUserManager)
-
- def create_user(self, address=None, real_name=None):
- user = User()
- user.real_name = ('' if real_name is None else real_name)
- if address:
- addrobj = Address(address, user.real_name)
- addrobj.preferences = Preferences()
- user.link(addrobj)
- user.preferences = Preferences()
- config.db.store.add(user)
- return user
-
- def delete_user(self, user):
- config.db.store.remove(user)
-
- @property
- def users(self):
- for user in config.db.store.find(User):
- yield user
-
- def get_user(self, address):
- addresses = config.db.store.find(Address, address=address.lower())
- if addresses.count() == 0:
- return None
- elif addresses.count() == 1:
- return addresses[0].user
- else:
- raise AssertionError('Unexpected query count')
-
- def create_address(self, address, real_name=None):
- addresses = config.db.store.find(Address, address=address.lower())
- if addresses.count() == 1:
- found = addresses[0]
- raise ExistingAddressError(found.original_address)
- assert addresses.count() == 0, 'Unexpected results'
- if real_name is None:
- real_name = ''
- # It's okay not to lower case the 'address' argument because the
- # constructor will do the right thing.
- address = Address(address, real_name)
- address.preferences = Preferences()
- config.db.store.add(address)
- return address
-
- def delete_address(self, address):
- # If there's a user controlling this address, it has to first be
- # unlinked before the address can be deleted.
- if address.user:
- address.user.unlink(address)
- config.db.store.remove(address)
-
- def get_address(self, address):
- addresses = config.db.store.find(Address, address=address.lower())
- if addresses.count() == 0:
- return None
- elif addresses.count() == 1:
- return addresses[0]
- else:
- raise AssertionError('Unexpected query count')
-
- @property
- def addresses(self):
- for address in config.db.store.find(Address):
- yield address
diff --git a/mailman/database/version.py b/mailman/database/version.py
deleted file mode 100644
index d15065395..000000000
--- a/mailman/database/version.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman 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 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman 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
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Model class for version numbers."""
-
-from __future__ import absolute_import, unicode_literals
-
-__metaclass__ = type
-__all__ = [
- 'Version',
- ]
-
-from storm.locals import *
-from mailman.database.model import Model
-
-
-
-class Version(Model):
- id = Int(primary=True)
- component = Unicode()
- version = Int()
-
- def __init__(self, component, version):
- super(Version, self).__init__()
- self.component = component
- self.version = version