/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/lockable_files.py

Merge from integration, mode-changes are broken.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import bzrlib
 
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
 
7
 
 
8
class LockableFiles(object):
 
9
    """Object representing a set of lockable files
 
10
 
 
11
    _lock_mode
 
12
        None, or 'r' or 'w'
 
13
 
 
14
    _lock_count
 
15
        If _lock_mode is true, a positive count of the number of times the
 
16
        lock has been taken.
 
17
 
 
18
    _lock
 
19
        Lock object from bzrlib.lock.
 
20
    """
 
21
 
 
22
    _lock_mode = None
 
23
    _lock_count = None
 
24
    _lock = None
 
25
 
 
26
    def __init__(self, transport, base, lock_name):
 
27
        object.__init__(self)
 
28
        self._transport = transport
 
29
        self.base = base
 
30
        self.lock_name = lock_name
 
31
        self._transaction = None
 
32
 
 
33
    def __del__(self):
 
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)
 
39
            self._lock.unlock()
 
40
 
 
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 + '/' + 
 
47
                                                  file_or_path))
 
48
 
 
49
    def controlfilename(self, file_or_path):
 
50
        """Return location relative to branch."""
 
51
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
 
52
 
 
53
    def controlfile(self, file_or_path, mode='r'):
 
54
        """Open a control file for this branch.
 
55
 
 
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.
 
60
 
 
61
        Controlfiles should almost never be opened in write mode but
 
62
        rather should be atomically copied and replaced using atomicfile.
 
63
        """
 
64
        import codecs
 
65
 
 
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?
 
69
        if mode == 'rb': 
 
70
            return self._transport.get(relpath)
 
71
        elif mode == 'wb':
 
72
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
 
73
        elif mode == 'r':
 
74
            # XXX: Do we really want errors='replace'?   Perhaps it should be
 
75
            # an error, or at least reported, if there's incorrectly-encoded
 
76
            # data inside a file.
 
77
            # <https://launchpad.net/products/bzr/+bug/3823>
 
78
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
 
79
        elif mode == 'w':
 
80
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
 
81
        else:
 
82
            raise BzrError("invalid controlfile mode %r" % mode)
 
83
 
 
84
    def put(self, path, file):
 
85
        """Write a file.
 
86
        
 
87
        :param path: The path to put the file, relative to the .bzr control
 
88
                     directory
 
89
        :param f: A file-like or string object whose contents should be copied.
 
90
        """
 
91
        if not self._lock_mode == 'w':
 
92
            raise ReadOnlyError()
 
93
        self._transport.put(self._rel_controlfilename(path), file)
 
94
 
 
95
    def put_utf8(self, path, file, mode=None):
 
96
        """Write a file, encoding as utf-8.
 
97
 
 
98
        :param path: The path to put the file, relative to the .bzr control
 
99
                     directory
 
100
        :param f: A file-like or string object whose contents should be copied.
 
101
        """
 
102
        import codecs
 
103
        from iterablefile import IterableFile
 
104
        ctrl_files = []
 
105
        if hasattr(file, 'read'):
 
106
            iterator = file_iterator(file)
 
107
        else:
 
108
            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
 
112
        # file support ?
 
113
        encoded_file = IterableFile(b.encode('utf-8', 'replace') for b in 
 
114
                                    iterator)
 
115
        self.put(path, encoded_file)
 
116
 
 
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
 
121
        if self._lock_mode:
 
122
            if self._lock_mode != 'w':
 
123
                raise LockError("can't upgrade to a write lock from %r" %
 
124
                                self._lock_mode)
 
125
            self._lock_count += 1
 
126
        else:
 
127
            self._lock = self._transport.lock_write(
 
128
                    self._rel_controlfilename(self.lock_name))
 
129
            self._lock_mode = 'w'
 
130
            self._lock_count = 1
 
131
            self._set_transaction(transactions.PassThroughTransaction())
 
132
 
 
133
    def lock_read(self):
 
134
        mutter("lock read: %s (%s)", self, self._lock_count)
 
135
        if self._lock_mode:
 
136
            assert self._lock_mode in ('r', 'w'), \
 
137
                   "invalid lock mode %r" % self._lock_mode
 
138
            self._lock_count += 1
 
139
        else:
 
140
            self._lock = self._transport.lock_read(
 
141
                    self._rel_controlfilename(self.lock_name))
 
142
            self._lock_mode = 'r'
 
143
            self._lock_count = 1
 
144
            self._set_transaction(transactions.ReadOnlyTransaction())
 
145
            # 5K may be excessive, but hey, its a knob.
 
146
            self.get_transaction().set_cache_size(5000)
 
147
                        
 
148
    def unlock(self):
 
149
        mutter("unlock: %s (%s)", self, self._lock_count)
 
150
        if not self._lock_mode:
 
151
            raise LockError('branch %r is not locked' % (self))
 
152
 
 
153
        if self._lock_count > 1:
 
154
            self._lock_count -= 1
 
155
        else:
 
156
            self._finish_transaction()
 
157
            self._lock.unlock()
 
158
            self._lock = None
 
159
            self._lock_mode = self._lock_count = None
 
160
 
 
161
    def get_transaction(self):
 
162
        """Return the current active transaction.
 
163
 
 
164
        If no transaction is active, this returns a passthrough object
 
165
        for which all data is immediately flushed and no caching happens.
 
166
        """
 
167
        if self._transaction is None:
 
168
            return transactions.PassThroughTransaction()
 
169
        else:
 
170
            return self._transaction
 
171
 
 
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.' %
 
176
                                   self)
 
177
        self._transaction = new_transaction
 
178
 
 
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' %
 
183
                                   self)
 
184
        transaction = self._transaction
 
185
        self._transaction = None
 
186
        transaction.finish()