Bump copyright years.
[mailman.git] / src / mailman / model / pending.py
blob17513015cdd61e2e13d4829f688201e54c73d591
1 # Copyright (C) 2007-2014 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
8 # any later version.
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Implementations of the IPendable and IPending interfaces."""
20 from __future__ import absolute_import, print_function, unicode_literals
22 __metaclass__ = type
23 __all__ = [
24 'Pended',
25 'Pendings',
29 import time
30 import random
31 import hashlib
33 from lazr.config import as_timedelta
34 from storm.locals import DateTime, Int, RawStr, ReferenceSet, Unicode
35 from zope.interface import implementer
36 from zope.interface.verify import verifyObject
38 from mailman.config import config
39 from mailman.database.model import Model
40 from mailman.database.transaction import dbconnection
41 from mailman.interfaces.pending import (
42 IPendable, IPended, IPendedKeyValue, IPendings)
43 from mailman.utilities.datetime import now
44 from mailman.utilities.modules import call_name
48 @implementer(IPendedKeyValue)
49 class PendedKeyValue(Model):
50 """A pended key/value pair, tied to a token."""
52 def __init__(self, key, value):
53 self.key = key
54 self.value = value
56 id = Int(primary=True)
57 key = Unicode()
58 value = Unicode()
59 pended_id = Int()
63 @implementer(IPended)
64 class Pended(Model):
65 """A pended event, tied to a token."""
67 def __init__(self, token, expiration_date):
68 super(Pended, self).__init__()
69 self.token = token
70 self.expiration_date = expiration_date
72 id = Int(primary=True)
73 token = RawStr()
74 expiration_date = DateTime()
75 key_values = ReferenceSet(id, PendedKeyValue.pended_id)
79 @implementer(IPendable)
80 class UnpendedPendable(dict):
81 pass
85 @implementer(IPendings)
86 class Pendings:
87 """Implementation of the IPending interface."""
89 @dbconnection
90 def add(self, store, pendable, lifetime=None):
91 verifyObject(IPendable, pendable)
92 # Calculate the token and the lifetime.
93 if lifetime is None:
94 lifetime = as_timedelta(config.mailman.pending_request_life)
95 # Calculate a unique token. Algorithm vetted by the Timbot. time()
96 # has high resolution on Linux, clock() on Windows. random gives us
97 # about 45 bits in Python 2.2, 53 bits on Python 2.3. The time and
98 # clock values basically help obscure the random number generator, as
99 # does the hash calculation. The integral parts of the time values
100 # are discarded because they're the most predictable bits.
101 for attempts in range(3):
102 right_now = time.time()
103 x = random.random() + right_now % 1.0 + time.clock() % 1.0
104 # Use sha1 because it produces shorter strings.
105 token = hashlib.sha1(repr(x)).hexdigest()
106 # In practice, we'll never get a duplicate, but we'll be anal
107 # about checking anyway.
108 if store.find(Pended, token=token).count() == 0:
109 break
110 else:
111 raise AssertionError('Could not find a valid pendings token')
112 # Create the record, and then the individual key/value pairs.
113 pending = Pended(
114 token=token,
115 expiration_date=now() + lifetime)
116 for key, value in pendable.items():
117 if isinstance(key, str):
118 key = unicode(key, 'utf-8')
119 if isinstance(value, str):
120 value = unicode(value, 'utf-8')
121 elif type(value) is int:
122 value = '__builtin__.int\1%s' % value
123 elif type(value) is float:
124 value = '__builtin__.float\1%s' % value
125 elif type(value) is bool:
126 value = '__builtin__.bool\1%s' % value
127 elif type(value) is list:
128 # We expect this to be a list of strings.
129 value = ('mailman.model.pending.unpack_list\1' +
130 '\2'.join(value))
131 keyval = PendedKeyValue(key=key, value=value)
132 pending.key_values.add(keyval)
133 store.add(pending)
134 return token
136 @dbconnection
137 def confirm(self, store, token, expunge=True):
138 # Token can come in as a unicode, but it's stored in the database as
139 # bytes. They must be ascii.
140 pendings = store.find(Pended, token=str(token))
141 if pendings.count() == 0:
142 return None
143 assert pendings.count() == 1, (
144 'Unexpected token count: {0}'.format(pendings.count()))
145 pending = pendings[0]
146 pendable = UnpendedPendable()
147 # Find all PendedKeyValue entries that are associated with the pending
148 # object's ID. Watch out for type conversions.
149 for keyvalue in store.find(PendedKeyValue,
150 PendedKeyValue.pended_id == pending.id):
151 if keyvalue.value is not None and '\1' in keyvalue.value:
152 type_name, value = keyvalue.value.split('\1', 1)
153 pendable[keyvalue.key] = call_name(type_name, value)
154 else:
155 pendable[keyvalue.key] = keyvalue.value
156 if expunge:
157 store.remove(keyvalue)
158 if expunge:
159 store.remove(pending)
160 return pendable
162 @dbconnection
163 def evict(self, store):
164 right_now = now()
165 for pending in store.find(Pended):
166 if pending.expiration_date < right_now:
167 # Find all PendedKeyValue entries that are associated with the
168 # pending object's ID.
169 q = store.find(PendedKeyValue,
170 PendedKeyValue.pended_id == pending.id)
171 for keyvalue in q:
172 store.remove(keyvalue)
173 store.remove(pending)
177 def unpack_list(value):
178 return value.split('\2')