diff options
Diffstat (limited to 'Mailman/database/__init__.py')
| -rw-r--r-- | Mailman/database/__init__.py | 102 |
1 files changed, 69 insertions, 33 deletions
diff --git a/Mailman/database/__init__.py b/Mailman/database/__init__.py index 78b118be6..d332254c6 100644 --- a/Mailman/database/__init__.py +++ b/Mailman/database/__init__.py @@ -23,15 +23,23 @@ __all__ = [ ] import os +import Mailman.Version from locknix.lockfile import Lock -from storm.properties import PropertyPublisherMeta +from storm.locals import create_database, Store +from string import Template +from urlparse import urlparse from zope.interface import implements -from Mailman.interfaces import IDatabase +from Mailman.Errors import SchemaVersionMismatchError +from Mailman.configuration import config from Mailman.database.listmanager import ListManager -from Mailman.database.usermanager import UserManager 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 import IDatabase @@ -47,46 +55,74 @@ class StockDatabase: self._store = None def initialize(self, debug=None): - # Avoid circular imports. - from Mailman.configuration import config - from Mailman.database import model - from Mailman.database.model import Pendings - from Mailman.database.model import Requests # 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.store = model.initialize(debug) + self._create(debug) self.list_manager = ListManager() self.user_manager = UserManager() self.message_store = MessageStore() self.pendings = Pendings() self.requests = Requests() + def _create(self, debug): + # Calculate the engine url. + url = Template(config.DEFAULT_DATABASE_URL).safe_substitute( + config.paths) + # 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 = (config.DEFAULT_DATABASE_ECHO + if debug is None else debug) + # 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. + import Mailman.database + schema_file = os.path.join( + os.path.dirname(Mailman.database.__file__), + 'mailman.sql') + with open(schema_file) as fp: + sql = fp.read() + 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=u'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 + def _reset(self): - for model_class in _class_registry: - for row in self.store.find(model_class): - self.store.remove(row) + from Mailman.database.model import ModelMeta + ModelMeta._reset(self.store) -_class_registry = set() - - -class ModelMeta(PropertyPublisherMeta): - """Do more magic on table classes.""" - - 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 - _class_registry.add(self) - - -class Model(object): - """Like Storm's `Storm` subclass, but with a bit extra.""" - __metaclass__ = ModelMeta +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) |
