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
6
from osutils import file_iterator
8
class LockableFiles(object):
9
"""Object representing a set of lockable files
15
If _lock_mode is true, a positive count of the number of times the
19
Lock object from bzrlib.lock.
26
def __init__(self, transport, base, lock_name):
28
self._transport = transport
30
self.lock_name = lock_name
31
self._transaction = None
34
if self._lock_mode or self._lock:
35
# XXX: This should show something every time, and be suitable for
36
# headless operation and embedding
37
from warnings import warn
38
warn("file group %r was not explicitly unlocked" % self)
41
def _rel_controlfilename(self, file_or_path):
42
if not isinstance(file_or_path, basestring):
43
file_or_path = '/'.join(file_or_path)
44
if file_or_path == '':
45
return unicode(self.base)
46
return bzrlib.transport.urlescape(unicode(self.base + '/' +
49
def controlfilename(self, file_or_path):
50
"""Return location relative to branch."""
51
return self._transport.abspath(self._rel_controlfilename(file_or_path))
53
def controlfile(self, file_or_path, mode='r'):
54
"""Open a control file for this branch.
56
There are two classes of file in the control directory: text
57
and binary. binary files are untranslated byte streams. Text
58
control files are stored with Unix newlines and in UTF-8, even
59
if the platform or locale defaults are different.
61
Controlfiles should almost never be opened in write mode but
62
rather should be atomically copied and replaced using atomicfile.
66
relpath = self._rel_controlfilename(file_or_path)
67
#TODO: codecs.open() buffers linewise, so it was overloaded with
68
# a much larger buffer, do we need to do the same for getreader/getwriter?
70
return self._transport.get(relpath)
72
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
74
# XXX: Do we really want errors='replace'? Perhaps it should be
75
# an error, or at least reported, if there's incorrectly-encoded
77
# <https://launchpad.net/products/bzr/+bug/3823>
78
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
80
raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
82
raise BzrError("invalid controlfile mode %r" % mode)
84
def put(self, path, file):
87
:param path: The path to put the file, relative to the .bzr control
89
:param f: A file-like or string object whose contents should be copied.
91
if not self._lock_mode == 'w':
93
self._transport.put(self._rel_controlfilename(path), file)
95
def put_utf8(self, path, file, mode=None):
96
"""Write a file, encoding as utf-8.
98
:param path: The path to put the file, relative to the .bzr control
100
:param f: A file-like or string object whose contents should be copied.
103
from iterablefile import IterableFile
105
if hasattr(file, 'read'):
106
iterator = file_iterator(file)
109
# IterableFile would not be needed if Transport.put took iterables
110
# instead of files. ADHB 2005-12-25
111
# RBC 20060103 surely its not needed anyway, with codecs transcode
113
encoded_file = IterableFile(b.encode('utf-8', 'replace') for b in
115
self.put(path, encoded_file)
117
def lock_write(self):
118
mutter("lock write: %s (%s)", self, self._lock_count)
119
# TODO: Upgrade locking to support using a Transport,
120
# and potentially a remote locking protocol
122
if self._lock_mode != 'w':
123
raise LockError("can't upgrade to a write lock from %r" %
125
self._lock_count += 1
127
self._lock = self._transport.lock_write(
128
self._rel_controlfilename(self.lock_name))
129
self._lock_mode = 'w'
131
self._set_transaction(transactions.PassThroughTransaction())
134
mutter("lock read: %s (%s)", self, self._lock_count)
136
assert self._lock_mode in ('r', 'w'), \
137
"invalid lock mode %r" % self._lock_mode
138
self._lock_count += 1
140
self._lock = self._transport.lock_read(
141
self._rel_controlfilename(self.lock_name))
142
self._lock_mode = 'r'
144
self._set_transaction(transactions.ReadOnlyTransaction())
145
# 5K may be excessive, but hey, its a knob.
146
self.get_transaction().set_cache_size(5000)
149
mutter("unlock: %s (%s)", self, self._lock_count)
150
if not self._lock_mode:
151
raise LockError('branch %r is not locked' % (self))
153
if self._lock_count > 1:
154
self._lock_count -= 1
156
self._finish_transaction()
159
self._lock_mode = self._lock_count = None
161
def get_transaction(self):
162
"""Return the current active transaction.
164
If no transaction is active, this returns a passthrough object
165
for which all data is immediately flushed and no caching happens.
167
if self._transaction is None:
168
return transactions.PassThroughTransaction()
170
return self._transaction
172
def _set_transaction(self, new_transaction):
173
"""Set a new active transaction."""
174
if self._transaction is not None:
175
raise errors.LockError('Branch %s is in a transaction already.' %
177
self._transaction = new_transaction
179
def _finish_transaction(self):
180
"""Exit the current transaction."""
181
if self._transaction is None:
182
raise errors.LockError('Branch %s is not in a transaction' %
184
transaction = self._transaction
185
self._transaction = None