1
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
from cStringIO import StringIO
22
from bzrlib.decorators import (needs_read_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,
29
from bzrlib.trace import mutter, note
25
from bzrlib.symbol_versioning import *
26
from bzrlib.trace import mutter
30
27
import bzrlib.transactions as transactions
31
import bzrlib.urlutils as urlutils
34
29
# XXX: The tracking here of lock counts and whether the lock is held is
35
30
# somewhat redundant with what's done in LockDir; the main difference is that
77
72
:param lock_class: Class of lock strategy to use: typically
78
73
either LockDir or TransportLock.
80
76
self._transport = transport
81
77
self.lock_name = lock_name
82
78
self._transaction = None
83
80
self._lock_mode = None
84
81
self._lock_count = 0
86
82
esc_name = self._escape(lock_name)
87
self._lock = lock_class(transport, esc_name,
83
self._lock = lock_class(transport, esc_name,
88
84
file_modebits=self._file_mode,
89
85
dir_modebits=self._dir_mode)
94
90
This should normally be called only when the LockableFiles directory
95
91
is first created on disk.
97
self._lock.create(mode=self._dir_mode)
99
95
def __repr__(self):
100
96
return '%s(%r)' % (self.__class__.__name__,
103
return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
107
# XXX: This should show something every time, and be suitable for
108
# headless operation and embedding
109
from warnings import warn
110
warn("file group %r was not explicitly unlocked" % self)
113
def break_lock(self):
114
"""Break the lock of this lockable files group if it is held.
116
The current ui factory will be used to prompt for user conformation.
118
self._lock.break_lock()
120
99
def _escape(self, file_or_path):
121
100
if not isinstance(file_or_path, basestring):
122
101
file_or_path = '/'.join(file_or_path)
123
102
if file_or_path == '':
125
return urlutils.escape(safe_unicode(file_or_path))
104
return bzrlib.transport.urlescape(safe_unicode(file_or_path))
127
106
def _find_modes(self):
128
107
"""Determine the appropriate modes for files and directories."""
194
173
:param f: A file-like or string object whose contents should be copied.
196
self._transport.put_file(self._escape(path), file, mode=self._file_mode)
199
def put_bytes(self, path, a_string):
200
"""Write a string of bytes.
202
:param path: The path to put the bytes, relative to the transport root.
203
:param string: A string object, whose exact bytes are to be copied.
205
self._transport.put_bytes(self._escape(path), a_string,
206
mode=self._file_mode)
175
self._transport.put(self._escape(path), file, mode=self._file_mode)
208
177
@needs_write_lock
209
178
def put_utf8(self, path, a_string):
210
179
"""Write a string, encoding as utf-8.
212
181
:param path: The path to put the string, relative to the transport root.
213
:param string: A string or unicode object whose contents should be copied.
182
:param string: A file-like or string object whose contents should be copied.
215
184
# IterableFile would not be needed if Transport.put took iterables
216
185
# instead of files. ADHB 2005-12-25
220
189
# these are valuable files which should have exact contents.
221
190
if not isinstance(a_string, basestring):
222
191
raise errors.BzrBadParameterNotString(a_string)
223
self.put_bytes(path, a_string.encode('utf-8'))
225
def leave_in_place(self):
226
"""Set this LockableFiles to not clear the physical lock on unlock."""
227
self._lock.leave_in_place()
229
def dont_leave_in_place(self):
230
"""Set this LockableFiles to clear the physical lock on unlock."""
231
self._lock.dont_leave_in_place()
233
def lock_write(self, token=None):
234
"""Lock this group of files for writing.
236
:param token: if this is already locked, then lock_write will fail
237
unless the token matches the existing lock.
238
:returns: a token if this instance supports tokens, otherwise None.
239
:raises TokenLockingNotSupported: when a token is given but this
240
instance doesn't support using token locks.
241
:raises MismatchedToken: if the specified token doesn't match the token
242
of the existing lock.
244
A token should be passed in if you know that you have locked the object
245
some other way, and need to synchronise this object's state with that
192
self.put(path, StringIO(a_string.encode('utf-8')))
194
def lock_write(self):
248
195
# mutter("lock write: %s (%s)", self, self._lock_count)
249
196
# TODO: Upgrade locking to support using a Transport,
250
197
# and potentially a remote locking protocol
251
198
if self._lock_mode:
252
199
if self._lock_mode != 'w' or not self.get_transaction().writeable():
253
raise errors.ReadOnlyError(self)
254
self._lock.validate_token(token)
200
raise ReadOnlyError(self)
255
201
self._lock_count += 1
256
return self._token_from_lock
258
token_from_lock = self._lock.lock_write(token=token)
259
#note('write locking %s', self)
260
#traceback.print_stack()
203
self._lock.lock_write()
261
204
self._lock_mode = 'w'
262
205
self._lock_count = 1
263
206
self._set_transaction(transactions.WriteTransaction())
264
self._token_from_lock = token_from_lock
265
return token_from_lock
267
208
def lock_read(self):
268
209
# mutter("lock read: %s (%s)", self, self._lock_count)
287
226
if self._lock_count > 1:
288
227
self._lock_count -= 1
290
#note('unlocking %s', self)
291
#traceback.print_stack()
292
229
self._finish_transaction()
296
self._lock_mode = self._lock_count = None
231
self._lock_mode = self._lock_count = None
298
233
def is_locked(self):
299
234
"""Return true if this LockableFiles group is locked"""
300
235
return self._lock_count >= 1
302
def get_physical_lock_status(self):
303
"""Return physical lock status.
305
Returns true if a lock is held on the transport. If no lock is held, or
306
the underlying locking mechanism does not support querying lock
307
status, false is returned.
310
return self._lock.peek() is not None
311
except NotImplementedError:
314
237
def get_transaction(self):
315
238
"""Return the current active transaction.
356
279
self._file_modebits = file_modebits
357
280
self._dir_modebits = dir_modebits
359
def break_lock(self):
360
raise NotImplementedError(self.break_lock)
362
def leave_in_place(self):
363
raise NotImplementedError(self.leave_in_place)
365
def dont_leave_in_place(self):
366
raise NotImplementedError(self.dont_leave_in_place)
368
def lock_write(self, token=None):
369
if token is not None:
370
raise errors.TokenLockingNotSupported(self)
282
def lock_write(self):
371
283
self._lock = self._transport.lock_write(self._escaped_name)
373
285
def lock_read(self):
377
289
self._lock.unlock()
378
290
self._lock = None
381
raise NotImplementedError()
383
def create(self, mode=None):
384
293
"""Create lock mechanism"""
385
294
# for old-style locks, create the file now
386
self._transport.put_bytes(self._escaped_name, '',
295
self._transport.put(self._escaped_name, StringIO(),
387
296
mode=self._file_modebits)
389
def validate_token(self, token):
390
if token is not None:
391
raise errors.TokenLockingNotSupported(self)