1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
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
19
from bzrlib.lazy_import import lazy_import
20
lazy_import(globals(), """
32
from bzrlib.decorators import (
36
from bzrlib.symbol_versioning import (
21
from bzrlib.decorators import *
22
import bzrlib.errors as errors
23
from bzrlib.errors import LockError, ReadOnlyError
24
from bzrlib.osutils import file_iterator, safe_unicode
25
from bzrlib.symbol_versioning import *
26
from bzrlib.trace import mutter
27
import bzrlib.transactions as transactions
42
29
# XXX: The tracking here of lock counts and whether the lock is held is
43
30
# somewhat redundant with what's done in LockDir; the main difference is that
84
72
:param lock_class: Class of lock strategy to use: typically
85
73
either LockDir or TransportLock.
87
76
self._transport = transport
88
77
self.lock_name = lock_name
89
78
self._transaction = None
90
80
self._lock_mode = None
91
81
self._lock_count = 0
93
82
esc_name = self._escape(lock_name)
94
self._lock = lock_class(transport, esc_name,
95
file_modebits=self._file_mode,
96
dir_modebits=self._dir_mode)
98
def create_lock(self):
101
This should normally be called only when the LockableFiles directory
102
is first created on disk.
104
self._lock.create(mode=self._dir_mode)
107
return '%s(%r)' % (self.__class__.__name__,
110
return 'LockableFiles(%s, %s)' % (self.lock_name, self._transport.base)
114
# do not automatically unlock; there should have been a
115
# try/finally to unlock this.
116
warnings.warn("%r was gc'd while locked" % self)
118
def break_lock(self):
119
"""Break the lock of this lockable files group if it is held.
121
The current ui factory will be used to prompt for user conformation.
123
self._lock.break_lock()
83
if lock_class is None:
84
warn("LockableFiles: lock_class is now a mandatory parameter",
85
DeprecationWarning, stacklevel=2)
86
lock_class = TransportLock
87
self._lock = lock_class(transport, esc_name)
125
89
def _escape(self, file_or_path):
126
90
if not isinstance(file_or_path, basestring):
127
91
file_or_path = '/'.join(file_or_path)
128
92
if file_or_path == '':
130
return urlutils.escape(osutils.safe_unicode(file_or_path))
94
return bzrlib.transport.urlescape(safe_unicode(file_or_path))
132
96
def _find_modes(self):
133
"""Determine the appropriate modes for files and directories.
135
:deprecated: Replaced by BzrDir._find_modes.
97
"""Determine the appropriate modes for files and directories."""
138
99
st = self._transport.stat('.')
139
100
except errors.TransportNotPossible:
140
101
self._dir_mode = 0755
141
102
self._file_mode = 0644
143
# Check the directory mode, but also make sure the created
144
# directories and files are read-write for this user. This is
145
# mostly a workaround for filesystems which lie about being able to
146
# write to a directory (cygwin & win32)
147
self._dir_mode = (st.st_mode & 07777) | 00700
104
self._dir_mode = st.st_mode & 07777
148
105
# Remove the sticky and execute bits for files
149
106
self._file_mode = self._dir_mode & ~07111
107
if not self._set_dir_mode:
108
self._dir_mode = None
109
if not self._set_file_mode:
110
self._file_mode = None
151
@deprecated_method(deprecated_in((1, 6, 0)))
152
112
def controlfilename(self, file_or_path):
153
"""Return location relative to branch.
155
:deprecated: Use Transport methods instead.
113
"""Return location relative to branch."""
157
114
return self._transport.abspath(self._escape(file_or_path))
116
@deprecated_method(zero_eight)
117
def controlfile(self, file_or_path, mode='r'):
118
"""Open a control file for this branch.
120
There are two classes of file in a lockable directory: text
121
and binary. binary files are untranslated byte streams. Text
122
control files are stored with Unix newlines and in UTF-8, even
123
if the platform or locale defaults are different.
125
Such files are not openable in write mode : they are managed via
126
put and put_utf8 which atomically replace old versions using
130
relpath = self._escape(file_or_path)
131
# TODO: codecs.open() buffers linewise, so it was overloaded with
132
# a much larger buffer, do we need to do the same for getreader/getwriter?
134
return self.get(relpath)
136
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
138
return self.get_utf8(relpath)
140
raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
142
raise BzrError("invalid controlfile mode %r" % mode)
160
@deprecated_method(deprecated_in((1, 5, 0)))
161
145
def get(self, relpath):
162
"""Get a file as a bytestream.
164
:deprecated: Use a Transport instead of LockableFiles.
146
"""Get a file as a bytestream."""
166
147
relpath = self._escape(relpath)
167
148
return self._transport.get(relpath)
170
@deprecated_method(deprecated_in((1, 5, 0)))
171
151
def get_utf8(self, relpath):
172
"""Get a file as a unicode stream.
174
:deprecated: Use a Transport instead of LockableFiles.
152
"""Get a file as a unicode stream."""
176
153
relpath = self._escape(relpath)
177
154
# DO NOT introduce an errors=replace here.
178
155
return codecs.getreader('utf-8')(self._transport.get(relpath))
180
157
@needs_write_lock
181
@deprecated_method(deprecated_in((1, 6, 0)))
182
158
def put(self, path, file):
185
161
:param path: The path to put the file, relative to the .bzr control
187
:param file: A file-like or string object whose contents should be copied.
189
:deprecated: Use Transport methods instead.
191
self._transport.put_file(self._escape(path), file, mode=self._file_mode)
194
@deprecated_method(deprecated_in((1, 6, 0)))
195
def put_bytes(self, path, a_string):
196
"""Write a string of bytes.
198
:param path: The path to put the bytes, relative to the transport root.
199
:param a_string: A string object, whose exact bytes are to be copied.
201
:deprecated: Use Transport methods instead.
203
self._transport.put_bytes(self._escape(path), a_string,
204
mode=self._file_mode)
207
@deprecated_method(deprecated_in((1, 6, 0)))
163
:param f: A file-like or string object whose contents should be copied.
165
self._transport.put(self._escape(path), file, mode=self._file_mode)
208
168
def put_utf8(self, path, a_string):
209
169
"""Write a string, encoding as utf-8.
211
171
:param path: The path to put the string, relative to the transport root.
212
:param string: A string or unicode object whose contents should be copied.
214
:deprecated: Use Transport methods instead.
172
:param string: A file-like or string object whose contents should be copied.
216
174
# IterableFile would not be needed if Transport.put took iterables
217
175
# instead of files. ADHB 2005-12-25
221
179
# these are valuable files which should have exact contents.
222
180
if not isinstance(a_string, basestring):
223
181
raise errors.BzrBadParameterNotString(a_string)
224
self.put_bytes(path, a_string.encode('utf-8'))
226
def leave_in_place(self):
227
"""Set this LockableFiles to not clear the physical lock on unlock."""
228
self._lock.leave_in_place()
230
def dont_leave_in_place(self):
231
"""Set this LockableFiles to clear the physical lock on unlock."""
232
self._lock.dont_leave_in_place()
234
def lock_write(self, token=None):
235
"""Lock this group of files for writing.
237
:param token: if this is already locked, then lock_write will fail
238
unless the token matches the existing lock.
239
:returns: a token if this instance supports tokens, otherwise None.
240
:raises TokenLockingNotSupported: when a token is given but this
241
instance doesn't support using token locks.
242
:raises MismatchedToken: if the specified token doesn't match the token
243
of the existing lock.
245
A token should be passed in if you know that you have locked the object
246
some other way, and need to synchronise this object's state with that
182
self.put(path, StringIO(a_string.encode('utf-8')))
184
def lock_write(self):
185
# mutter("lock write: %s (%s)", self, self._lock_count)
249
186
# TODO: Upgrade locking to support using a Transport,
250
187
# and potentially a remote locking protocol
251
188
if self._lock_mode:
252
if self._lock_mode != 'w' or not self.get_transaction().writeable():
253
raise errors.ReadOnlyError(self)
254
self._lock.validate_token(token)
189
if self._lock_mode != 'w':
190
raise ReadOnlyError(self)
255
191
self._lock_count += 1
256
return self._token_from_lock
258
token_from_lock = self._lock.lock_write(token=token)
259
#traceback.print_stack()
193
self._lock.lock_write()
260
194
self._lock_mode = 'w'
261
195
self._lock_count = 1
262
self._set_transaction(transactions.WriteTransaction())
263
self._token_from_lock = token_from_lock
264
return token_from_lock
196
self._set_transaction(transactions.PassThroughTransaction())
266
198
def lock_read(self):
199
# mutter("lock read: %s (%s)", self, self._lock_count)
267
200
if self._lock_mode:
268
if self._lock_mode not in ('r', 'w'):
269
raise ValueError("invalid lock mode %r" % (self._lock_mode,))
201
assert self._lock_mode in ('r', 'w'), \
202
"invalid lock mode %r" % self._lock_mode
270
203
self._lock_count += 1
272
205
self._lock.lock_read()
273
#traceback.print_stack()
274
206
self._lock_mode = 'r'
275
207
self._lock_count = 1
276
208
self._set_transaction(transactions.ReadOnlyTransaction())