1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
# Copyright (C) 2006-2007 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
import os
import weakref
from sqlalchemy import *
from string import Template
from urlparse import urlparse
from Mailman import Version
from Mailman.configuration import config
from Mailman.database import address
from Mailman.database import listdata
from Mailman.database import version
from Mailman.database.txnsupport import txn
class MlistRef(weakref.ref):
def __init__(self, mlist, callback):
super(MlistRef, self).__init__(mlist, callback)
self.fqdn_listname = mlist.fqdn_listname
class DBContext(object):
def __init__(self):
self.tables = {}
self.metadata = None
self.session = None
# Special transaction used only for MailList.Lock() .Save() and
# .Unlock() interface.
self._mlist_txns = {}
def connect(self):
# Calculate the engine url
url = Template(config.SQLALCHEMY_ENGINE_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...
self._touch(url)
self.metadata = BoundMetaData(url)
self.metadata.engine.echo = config.SQLALCHEMY_ECHO
# Create all the table objects, and then let SA conditionally create
# them if they don't yet exist.
version_table = None
for module in (address, listdata, version):
table = module.make_table(self.metadata)
self.tables[table.name] = table
if module is version:
version_table = table
self.metadata.create_all()
# Validate schema version, updating if necessary (XXX)
from Mailman.interact import interact
r = version_table.select(version_table.c.component=='schema').execute()
row = r.fetchone()
if row is None:
# Database has not yet been initialized
version_table.insert().execute(
component='schema',
version=Version.DATABASE_SCHEMA_VERSION)
elif row.version <> Version.DATABASE_SCHEMA_VERSION:
# XXX Update schema
raise SchemaVersionMismatchError(row.version)
self.session = create_session()
def _touch(self, url):
parts = urlparse(url)
# XXX Python 2.5; use parts.scheme and parts.path
if parts[0] <> 'sqlite':
return
path = os.path.normpath(parts[2])
fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK | os.O_CREAT, 0666)
# Ignore errors
if fd > 0:
os.close(fd)
# Cooperative method for use with @txn decorator
def _withtxn(self, meth, *args, **kws):
try:
txn = self.session.create_transaction()
rtn = meth(*args, **kws)
except:
txn.rollback()
raise
else:
txn.commit()
return rtn
def _unlock_mref(self, mref):
txn = self._mlist_txns.pop(mref.fqdn_listname, None)
if txn is not None:
txn.rollback()
# Higher level interface
def api_lock(self, mlist):
# Don't try to re-lock a list
if mlist.fqdn_listname in self._mlist_txns:
return
txn = self.session.create_transaction()
mref = MlistRef(mlist, self._unlock_mref)
# If mlist.host_name is changed, its fqdn_listname attribute will no
# longer match, so its transaction will not get committed when the
# list is saved. To avoid this, store on the mlist object the key
# under which its transaction is stored.
txnkey = mlist._txnkey = mlist.fqdn_listname
self._mlist_txns[txnkey] = txn
def api_unlock(self, mlist):
try:
txnkey = mlist._txnkey
except AttributeError:
return
txn = self._mlist_txns.pop(txnkey, None)
if txn is not None:
txn.rollback()
del mlist._txnkey
def api_save(self, mlist):
# When dealing with MailLists, .Save() will always be followed by
# .Unlock(). However lists can also be unlocked without saving. But
# if it's been locked it will always be unlocked. So the rollback in
# unlock will essentially be no-op'd if we've already saved the list.
try:
txnkey = mlist._txnkey
except AttributeError:
return
txn = self._mlist_txns.pop(txnkey, None)
if txn is not None:
txn.commit()
@txn
def api_add_list(self, mlist):
self.session.save(mlist)
@txn
def api_remove_list(self, mlist):
self.session.delete(mlist)
@txn
def api_find_list(self, listname, hostname):
from Mailman.MailList import MailList
q = self.session.query(MailList)
mlists = q.select_by(list_name=listname, host_name=hostname)
assert len(mlists) <= 1, 'Duplicate mailing lists!'
if mlists:
return mlists[0]
return None
@txn
def api_get_list_names(self):
table = self.tables['Listdata']
results = table.select().execute()
return [(row[table.c.list_name], row[table.c.host_name])
for row in results.fetchall()]
dbcontext = DBContext()
|