2
import bzrlib.errors as errors
3
from bzrlib.errors import LockError, ReadOnlyError
4
from bzrlib.trace import mutter
5
import bzrlib.transactions as transactions
7
class LockableFiles(object):
8
"""Object representing a set of lockable files
14
If _lock_mode is true, a positive count of the number of times the
18
Lock object from bzrlib.lock.
25
def __init__(self, transport, lock_name):
27
self._transport = transport
28
self.lock_name = lock_name
29
self._transaction = None
32
if self._lock_mode or self._lock:
33
# XXX: This should show something every time, and be suitable for
34
# headless operation and embedding
35
warn("file group %r was not explicitly unlocked" % self)
38
def _rel_controlfilename(self, file_or_path):
39
if not isinstance(file_or_path, basestring):
40
file_or_path = '/'.join(file_or_path)
41
if file_or_path == '':
42
return unicode(bzrlib.BZRDIR)
43
return bzrlib.transport.urlescape(unicode(bzrlib.BZRDIR + '/' + file_or_path))
45
def controlfilename(self, file_or_path):
46
"""Return location relative to branch."""
47
return self._transport.abspath(self._rel_controlfilename(file_or_path))
49
def controlfile(self, file_or_path, mode='r'):
50
"""Open a control file for this branch.
52
There are two classes of file in the control directory: text
53
and binary. binary files are untranslated byte streams. Text
54
control files are stored with Unix newlines and in UTF-8, even
55
if the platform or locale defaults are different.
57
Controlfiles should almost never be opened in write mode but
58
rather should be atomically copied and replaced using atomicfile.
62
relpath = self._rel_controlfilename(file_or_path)
63
#TODO: codecs.open() buffers linewise, so it was overloaded with
64
# a much larger buffer, do we need to do the same for getreader/getwriter?
66
return self._transport.get(relpath)
68
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
70
# XXX: Do we really want errors='replace'? Perhaps it should be
71
# an error, or at least reported, if there's incorrectly-encoded
73
# <https://launchpad.net/products/bzr/+bug/3823>
74
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
76
raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
78
raise BzrError("invalid controlfile mode %r" % mode)
80
def put(self, path, file):
83
:param path: The path to put the file, relative to the .bzr control
85
:param f: A file-like or string object whose contents should be copied.
87
if not self._lock_mode == 'w':
89
self._transport.put(self._rel_controlfilename(path), file)
91
def put_utf8(self, path, file):
92
"""Write a file, encoding as utf-8.
94
:param path: The path to put the file, relative to the .bzr control
96
:param f: A file-like or string object whose contents should be copied.
99
from iterablefile import IterableFile
101
def file_iterator(unicode_file, bufsize=32768):
103
b = unicode_file.read(bufsize)
107
if hasattr(file, 'read'):
108
iterator = file_iterator(file)
111
encoded_file = IterableFile(b.encode('utf-8', 'replace') for b in
113
self.put(path, encoded_file)
115
def lock_write(self):
116
mutter("lock write: %s (%s)", self, self._lock_count)
117
# TODO: Upgrade locking to support using a Transport,
118
# and potentially a remote locking protocol
120
if self._lock_mode != 'w':
121
raise LockError("can't upgrade to a write lock from %r" %
123
self._lock_count += 1
125
self._lock = self._transport.lock_write(
126
self._rel_controlfilename(self.lock_name))
127
self._lock_mode = 'w'
129
self._set_transaction(transactions.PassThroughTransaction())
132
mutter("lock read: %s (%s)", self, self._lock_count)
134
assert self._lock_mode in ('r', 'w'), \
135
"invalid lock mode %r" % self._lock_mode
136
self._lock_count += 1
138
self._lock = self._transport.lock_read(
139
self._rel_controlfilename('branch-lock'))
140
self._lock_mode = 'r'
142
self._set_transaction(transactions.ReadOnlyTransaction())
143
# 5K may be excessive, but hey, its a knob.
144
self.get_transaction().set_cache_size(5000)
147
mutter("unlock: %s (%s)", self, self._lock_count)
148
if not self._lock_mode:
149
raise LockError('branch %r is not locked' % (self))
151
if self._lock_count > 1:
152
self._lock_count -= 1
154
self._finish_transaction()
157
self._lock_mode = self._lock_count = None
159
def make_transport(self, relpath):
160
return self._transport.clone(relpath)
162
def get_transaction(self):
163
"""Return the current active transaction.
165
If no transaction is active, this returns a passthrough object
166
for which all data is immediately flushed and no caching happens.
168
if self._transaction is None:
169
return transactions.PassThroughTransaction()
171
return self._transaction
173
def _set_transaction(self, new_transaction):
174
"""Set a new active transaction."""
175
if self._transaction is not None:
176
raise errors.LockError('Branch %s is in a transaction already.' %
178
self._transaction = new_transaction
180
def _finish_transaction(self):
181
"""Exit the current transaction."""
182
if self._transaction is None:
183
raise errors.LockError('Branch %s is not in a transaction' %
185
transaction = self._transaction
186
self._transaction = None