~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockable_files.py

  • Committer: Michael Ellerman
  • Date: 2006-02-28 14:45:51 UTC
  • mto: (1558.1.18 Aaron's integration)
  • mto: This revision was merged to the branch mainline in revision 1586.
  • Revision ID: michael@ellerman.id.au-20060228144551-3d9941ecde4a0b0a
Update contrib/pwk for -p1 diffs from bzr

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
from cStringIO import StringIO
18
18
import codecs
19
 
#import traceback
20
19
 
21
20
import bzrlib
22
 
from bzrlib.decorators import (needs_read_lock,
23
 
        needs_write_lock)
 
21
from bzrlib.decorators import *
24
22
import bzrlib.errors as errors
25
 
from bzrlib.errors import BzrError
 
23
from bzrlib.errors import LockError, ReadOnlyError
26
24
from bzrlib.osutils import file_iterator, safe_unicode
27
 
from bzrlib.symbol_versioning import (deprecated_method, 
28
 
        zero_eight)
29
 
from bzrlib.trace import mutter, note
 
25
from bzrlib.symbol_versioning import *
 
26
from bzrlib.symbol_versioning import deprecated_method, zero_eight
 
27
from bzrlib.trace import mutter
30
28
import bzrlib.transactions as transactions
31
 
import bzrlib.urlutils as urlutils
32
 
 
33
 
 
34
 
# XXX: The tracking here of lock counts and whether the lock is held is
35
 
# somewhat redundant with what's done in LockDir; the main difference is that
36
 
# LockableFiles permits reentrancy.
 
29
 
37
30
 
38
31
class LockableFiles(object):
39
 
    """Object representing a set of related files locked within the same scope.
40
 
 
41
 
    These files are used by a WorkingTree, Repository or Branch, and should
42
 
    generally only be touched by that object.
43
 
 
44
 
    LockableFiles also provides some policy on top of Transport for encoding
45
 
    control files as utf-8.
46
 
 
47
 
    LockableFiles manage a lock count and can be locked repeatedly by
48
 
    a single caller.  (The underlying lock implementation generally does not
49
 
    support this.)
50
 
 
51
 
    Instances of this class are often called control_files.
52
 
    
53
 
    This object builds on top of a Transport, which is used to actually write
54
 
    the files to disk, and an OSLock or LockDir, which controls how access to
55
 
    the files is controlled.  The particular type of locking used is set when
56
 
    the object is constructed.  In older formats OSLocks are used everywhere.
57
 
    in newer formats a LockDir is used for Repositories and Branches, and 
58
 
    OSLocks for the local filesystem.
 
32
    """Object representing a set of files locked within the same scope
 
33
 
 
34
    _lock_mode
 
35
        None, or 'r' or 'w'
 
36
 
 
37
    _lock_count
 
38
        If _lock_mode is true, a positive count of the number of times the
 
39
        lock has been taken *by this process*.  Others may have compatible 
 
40
        read locks.
 
41
 
 
42
    _lock
 
43
        Lock object from bzrlib.lock.
59
44
    """
60
45
 
61
 
    # _lock_mode: None, or 'r' or 'w'
62
 
 
63
 
    # _lock_count: If _lock_mode is true, a positive count of the number of
64
 
    # times the lock has been taken *by this process*.   
65
 
    
 
46
    _lock_mode = None
 
47
    _lock_count = None
 
48
    _lock = None
66
49
    # If set to False (by a plugin, etc) BzrBranch will not set the
67
50
    # mode on created files or directories
68
51
    _set_file_mode = True
69
52
    _set_dir_mode = True
70
53
 
71
 
    def __init__(self, transport, lock_name, lock_class):
72
 
        """Create a LockableFiles group
73
 
 
74
 
        :param transport: Transport pointing to the directory holding the 
75
 
            control files and lock.
76
 
        :param lock_name: Name of the lock guarding these files.
77
 
        :param lock_class: Class of lock strategy to use: typically
78
 
            either LockDir or TransportLock.
79
 
        """
 
54
    def __init__(self, transport, lock_name):
 
55
        object.__init__(self)
80
56
        self._transport = transport
81
57
        self.lock_name = lock_name
82
58
        self._transaction = None
83
 
        self._lock_mode = None
84
 
        self._lock_count = 0
85
59
        self._find_modes()
86
 
        esc_name = self._escape(lock_name)
87
 
        self._lock = lock_class(transport, esc_name,
88
 
                                file_modebits=self._file_mode,
89
 
                                dir_modebits=self._dir_mode)
90
 
 
91
 
    def create_lock(self):
92
 
        """Create the lock.
93
 
 
94
 
        This should normally be called only when the LockableFiles directory
95
 
        is first created on disk.
96
 
        """
97
 
        self._lock.create(mode=self._dir_mode)
98
 
 
99
 
    def __repr__(self):
100
 
        return '%s(%r)' % (self.__class__.__name__,
101
 
                           self._transport)
102
 
    def __str__(self):
103
 
        return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
104
60
 
105
61
    def __del__(self):
106
 
        if self.is_locked():
 
62
        if self._lock_mode or self._lock:
107
63
            # XXX: This should show something every time, and be suitable for
108
64
            # headless operation and embedding
109
65
            from warnings import warn
110
66
            warn("file group %r was not explicitly unlocked" % self)
111
67
            self._lock.unlock()
112
68
 
113
 
    def break_lock(self):
114
 
        """Break the lock of this lockable files group if it is held.
115
 
 
116
 
        The current ui factory will be used to prompt for user conformation.
117
 
        """
118
 
        self._lock.break_lock()
119
 
 
120
69
    def _escape(self, file_or_path):
121
70
        if not isinstance(file_or_path, basestring):
122
71
            file_or_path = '/'.join(file_or_path)
123
72
        if file_or_path == '':
124
73
            return u''
125
 
        return urlutils.escape(safe_unicode(file_or_path))
 
74
        return bzrlib.transport.urlescape(safe_unicode(file_or_path))
126
75
 
127
76
    def _find_modes(self):
128
77
        """Determine the appropriate modes for files and directories."""
159
108
        """
160
109
 
161
110
        relpath = self._escape(file_or_path)
162
 
        # TODO: codecs.open() buffers linewise, so it was overloaded with
 
111
        #TODO: codecs.open() buffers linewise, so it was overloaded with
163
112
        # a much larger buffer, do we need to do the same for getreader/getwriter?
164
113
        if mode == 'rb': 
165
114
            return self.get(relpath)
217
166
        # TODO: Upgrade locking to support using a Transport,
218
167
        # and potentially a remote locking protocol
219
168
        if self._lock_mode:
220
 
            if self._lock_mode != 'w' or not self.get_transaction().writeable():
221
 
                raise errors.ReadOnlyError(self)
 
169
            if self._lock_mode != 'w':
 
170
                raise ReadOnlyError(self)
222
171
            self._lock_count += 1
223
172
        else:
224
 
            self._lock.lock_write()
225
 
            #note('write locking %s', self)
226
 
            #traceback.print_stack()
 
173
            self._lock = self._transport.lock_write(
 
174
                    self._escape(self.lock_name))
227
175
            self._lock_mode = 'w'
228
176
            self._lock_count = 1
229
 
            self._set_transaction(transactions.WriteTransaction())
 
177
            self._set_transaction(transactions.PassThroughTransaction())
230
178
 
231
179
    def lock_read(self):
232
180
        # mutter("lock read: %s (%s)", self, self._lock_count)
235
183
                   "invalid lock mode %r" % self._lock_mode
236
184
            self._lock_count += 1
237
185
        else:
238
 
            self._lock.lock_read()
239
 
            #note('read locking %s', self)
240
 
            #traceback.print_stack()
 
186
            self._lock = self._transport.lock_read(
 
187
                    self._escape(self.lock_name))
241
188
            self._lock_mode = 'r'
242
189
            self._lock_count = 1
243
190
            self._set_transaction(transactions.ReadOnlyTransaction())
247
194
    def unlock(self):
248
195
        # mutter("unlock: %s (%s)", self, self._lock_count)
249
196
        if not self._lock_mode:
250
 
            raise errors.LockNotHeld(self)
 
197
            raise errors.BranchNotLocked(self)
251
198
        if self._lock_count > 1:
252
199
            self._lock_count -= 1
253
200
        else:
254
 
            #note('unlocking %s', self)
255
 
            #traceback.print_stack()
256
201
            self._finish_transaction()
257
 
            try:
258
 
                self._lock.unlock()
259
 
            finally:
260
 
                self._lock_mode = self._lock_count = None
261
 
 
262
 
    def is_locked(self):
263
 
        """Return true if this LockableFiles group is locked"""
264
 
        return self._lock_count >= 1
265
 
 
266
 
    def get_physical_lock_status(self):
267
 
        """Return physical lock status.
268
 
        
269
 
        Returns true if a lock is held on the transport. If no lock is held, or
270
 
        the underlying locking mechanism does not support querying lock
271
 
        status, false is returned.
272
 
        """
273
 
        try:
274
 
            return self._lock.peek() is not None
275
 
        except NotImplementedError:
276
 
            return False
 
202
            self._lock.unlock()
 
203
            self._lock = None
 
204
            self._lock_mode = self._lock_count = None
277
205
 
278
206
    def get_transaction(self):
279
207
        """Return the current active transaction.
301
229
        transaction = self._transaction
302
230
        self._transaction = None
303
231
        transaction.finish()
304
 
 
305
 
 
306
 
class TransportLock(object):
307
 
    """Locking method which uses transport-dependent locks.
308
 
 
309
 
    On the local filesystem these transform into OS-managed locks.
310
 
 
311
 
    These do not guard against concurrent access via different
312
 
    transports.
313
 
 
314
 
    This is suitable for use only in WorkingTrees (which are at present
315
 
    always local).
316
 
    """
317
 
    def __init__(self, transport, escaped_name, file_modebits, dir_modebits):
318
 
        self._transport = transport
319
 
        self._escaped_name = escaped_name
320
 
        self._file_modebits = file_modebits
321
 
        self._dir_modebits = dir_modebits
322
 
 
323
 
    def break_lock(self):
324
 
        raise NotImplementedError(self.break_lock)
325
 
 
326
 
    def lock_write(self):
327
 
        self._lock = self._transport.lock_write(self._escaped_name)
328
 
 
329
 
    def lock_read(self):
330
 
        self._lock = self._transport.lock_read(self._escaped_name)
331
 
 
332
 
    def unlock(self):
333
 
        self._lock.unlock()
334
 
        self._lock = None
335
 
 
336
 
    def peek(self):
337
 
        raise NotImplementedError()
338
 
 
339
 
    def create(self, mode=None):
340
 
        """Create lock mechanism"""
341
 
        # for old-style locks, create the file now
342
 
        self._transport.put(self._escaped_name, StringIO(), 
343
 
                            mode=self._file_modebits)