Bump copyright years.
[mailman.git] / src / mailman / utilities / uid.py
blobc1df36789dd69baccdff9ea1a121c4effba661c9
1 # Copyright (C) 2011-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 """Unique ID generation.
20 Use these functions to create unique ids rather than inlining calls to hashlib
21 and whatnot. These are better instrumented for testing purposes.
22 """
24 from __future__ import absolute_import, unicode_literals
26 __metaclass__ = type
27 __all__ = [
28 'UniqueIDFactory',
29 'factory',
33 import os
34 import uuid
35 import errno
37 from flufl.lock import Lock
39 from mailman.config import config
40 from mailman.model.uid import UID
41 from mailman.testing import layers
45 class UniqueIDFactory:
46 """A factory for unique ids."""
48 def __init__(self, context=None):
49 # We can't call reset() when the factory is created below, because
50 # config.VAR_DIR will not be set at that time. So initialize it at
51 # the first use.
52 self._uid_file = None
53 self._lock_file = None
54 self._lockobj = None
55 self._context = context
56 layers.MockAndMonkeyLayer.register_reset(self.reset)
58 @property
59 def _lock(self):
60 if self._lockobj is None:
61 # These will get automatically cleaned up by the test
62 # infrastructure.
63 self._uid_file = os.path.join(config.VAR_DIR, '.uid')
64 if self._context:
65 self._uid_file += '.' + self._context
66 self._lock_file = self._uid_file + '.lock'
67 self._lockobj = Lock(self._lock_file)
68 return self._lockobj
70 def new_uid(self):
71 """Return a new UID.
73 :return: The new uid
74 :rtype: int
75 """
76 if layers.is_testing():
77 # When in testing mode we want to produce predictable id, but we
78 # need to coordinate this among separate processes. We could use
79 # the database, but I don't want to add schema just to handle this
80 # case, and besides transactions could get aborted, causing some
81 # ids to be recycled. So we'll use a data file with a lock. This
82 # may still not be ideal due to race conditions, but I think the
83 # tests will be serialized enough (and the ids reset between
84 # tests) that it will not be a problem. Maybe.
85 return self._next_uid()
86 while True:
87 uid = uuid.uuid4()
88 try:
89 UID.record(uid)
90 except ValueError:
91 pass
92 else:
93 return uid
95 def _next_uid(self):
96 with self._lock:
97 try:
98 with open(self._uid_file) as fp:
99 uid = int(fp.read().strip())
100 next_uid = uid + 1
101 with open(self._uid_file, 'w') as fp:
102 fp.write(str(next_uid))
103 return uuid.UUID(int=uid)
104 except IOError as error:
105 if error.errno != errno.ENOENT:
106 raise
107 with open(self._uid_file, 'w') as fp:
108 fp.write('2')
109 return uuid.UUID(int=1)
111 def reset(self):
112 with self._lock:
113 with open(self._uid_file, 'w') as fp:
114 fp.write('1')
118 factory = UniqueIDFactory()