diff options
| author | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
| commit | eefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch) | |
| tree | 72c947fe16fce0e07e996ee74020b26585d7e846 /mailman/database | |
| parent | 07871212f74498abd56bef3919bf3e029eb8b930 (diff) | |
| download | mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip | |
Diffstat (limited to 'mailman/database')
| -rw-r--r-- | mailman/database/__init__.py | 153 | ||||
| -rw-r--r-- | mailman/database/address.py | 96 | ||||
| -rw-r--r-- | mailman/database/language.py | 40 | ||||
| -rw-r--r-- | mailman/database/listmanager.py | 82 | ||||
| -rw-r--r-- | mailman/database/mailinglist.py | 272 | ||||
| -rw-r--r-- | mailman/database/mailman.sql | 208 | ||||
| -rw-r--r-- | mailman/database/member.py | 105 | ||||
| -rw-r--r-- | mailman/database/message.py | 53 | ||||
| -rw-r--r-- | mailman/database/messagestore.py | 137 | ||||
| -rw-r--r-- | mailman/database/model.py | 56 | ||||
| -rw-r--r-- | mailman/database/pending.py | 177 | ||||
| -rw-r--r-- | mailman/database/preferences.py | 50 | ||||
| -rw-r--r-- | mailman/database/requests.py | 138 | ||||
| -rw-r--r-- | mailman/database/roster.py | 270 | ||||
| -rw-r--r-- | mailman/database/transaction.py | 53 | ||||
| -rw-r--r-- | mailman/database/types.py | 64 | ||||
| -rw-r--r-- | mailman/database/user.py | 94 | ||||
| -rw-r--r-- | mailman/database/usermanager.py | 103 | ||||
| -rw-r--r-- | mailman/database/version.py | 40 |
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 |
