/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 bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
 
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
 
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
import bzrlib
 
19
import bzrlib.errors as errors
 
20
from bzrlib.errors import LockError, ReadOnlyError
 
21
from bzrlib.trace import mutter
 
22
import bzrlib.transactions as transactions
 
23
from osutils import file_iterator
 
24
 
 
25
class LockableFiles(object):
 
26
    """Object representing a set of lockable files
 
27
 
 
28
    _lock_mode
 
29
        None, or 'r' or 'w'
 
30
 
 
31
    _lock_count
 
32
        If _lock_mode is true, a positive count of the number of times the
 
33
        lock has been taken.
 
34
 
 
35
    _lock
 
36
        Lock object from bzrlib.lock.
 
37
    """
 
38
 
 
39
    _lock_mode = None
 
40
    _lock_count = None
 
41
    _lock = None
 
42
    # If set to False (by a plugin, etc) BzrBranch will not set the
 
43
    # mode on created files or directories
 
44
    _set_file_mode = True
 
45
    _set_dir_mode = True
 
46
 
 
47
    def __init__(self, transport, lock_name):
 
48
        object.__init__(self)
 
49
        self._transport = transport
 
50
        self.lock_name = lock_name
 
51
        self._transaction = None
 
52
        self._find_modes()
 
53
 
 
54
    def __del__(self):
 
55
        if self._lock_mode or self._lock:
 
56
            # XXX: This should show something every time, and be suitable for
 
57
            # headless operation and embedding
 
58
            from warnings import warn
 
59
            warn("file group %r was not explicitly unlocked" % self)
 
60
            self._lock.unlock()
 
61
 
 
62
    def _escape(self, file_or_path):
 
63
        if not isinstance(file_or_path, basestring):
 
64
            file_or_path = '/'.join(file_or_path)
 
65
        if file_or_path == '':
 
66
            return u''
 
67
        return bzrlib.transport.urlescape(unicode(file_or_path))
 
68
 
 
69
    def _find_modes(self):
 
70
        """Determine the appropriate modes for files and directories."""
 
71
        try:
 
72
            try:
 
73
                st = self._transport.stat(u'.')
 
74
            except errors.NoSuchFile:
 
75
                # The .bzr/ directory doesn't exist, try to
 
76
                # inherit the permissions from the parent directory
 
77
                # but only try 1 level up
 
78
                temp_transport = self._transport.clone('..')
 
79
                st = temp_transport.stat(u'.')
 
80
        except (errors.TransportNotPossible, errors.NoSuchFile):
 
81
            self._dir_mode = 0755
 
82
            self._file_mode = 0644
 
83
        else:
 
84
            self._dir_mode = st.st_mode & 07777
 
85
            # Remove the sticky and execute bits for files
 
86
            self._file_mode = self._dir_mode & ~07111
 
87
        if not self._set_dir_mode:
 
88
            self._dir_mode = None
 
89
        if not self._set_file_mode:
 
90
            self._file_mode = None
 
91
 
 
92
    def controlfilename(self, file_or_path):
 
93
        """Return location relative to branch."""
 
94
        return self._transport.abspath(self._escape(file_or_path))
 
95
 
 
96
    def controlfile(self, file_or_path, mode='r'):
 
97
        """Open a control file for this branch.
 
98
 
 
99
        There are two classes of file in the control directory: text
 
100
        and binary.  binary files are untranslated byte streams.  Text
 
101
        control files are stored with Unix newlines and in UTF-8, even
 
102
        if the platform or locale defaults are different.
 
103
 
 
104
        Controlfiles should almost never be opened in write mode but
 
105
        rather should be atomically copied and replaced using atomicfile.
 
106
        """
 
107
        import codecs
 
108
 
 
109
        relpath = self._escape(file_or_path)
 
110
        #TODO: codecs.open() buffers linewise, so it was overloaded with
 
111
        # a much larger buffer, do we need to do the same for getreader/getwriter?
 
112
        if mode == 'rb': 
 
113
            return self._transport.get(relpath)
 
114
        elif mode == 'wb':
 
115
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
 
116
        elif mode == 'r':
 
117
            # XXX: Do we really want errors='replace'?   Perhaps it should be
 
118
            # an error, or at least reported, if there's incorrectly-encoded
 
119
            # data inside a file.
 
120
            # <https://launchpad.net/products/bzr/+bug/3823>
 
121
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
 
122
        elif mode == 'w':
 
123
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
 
124
        else:
 
125
            raise BzrError("invalid controlfile mode %r" % mode)
 
126
 
 
127
    def put(self, path, file):
 
128
        """Write a file.
 
129
        
 
130
        :param path: The path to put the file, relative to the .bzr control
 
131
                     directory
 
132
        :param f: A file-like or string object whose contents should be copied.
 
133
        """
 
134
        if not self._lock_mode == 'w':
 
135
            raise ReadOnlyError()
 
136
        self._transport.put(self._escape(path), file, mode=self._file_mode)
 
137
 
 
138
    def put_utf8(self, path, file, mode=None):
 
139
        """Write a file, encoding as utf-8.
 
140
 
 
141
        :param path: The path to put the file, relative to the .bzr control
 
142
                     directory
 
143
        :param f: A file-like or string object whose contents should be copied.
 
144
        """
 
145
        import codecs
 
146
        from iterablefile import IterableFile
 
147
        ctrl_files = []
 
148
        if hasattr(file, 'read'):
 
149
            iterator = file_iterator(file)
 
150
        else:
 
151
            iterator = file
 
152
        # IterableFile would not be needed if Transport.put took iterables
 
153
        # instead of files.  ADHB 2005-12-25
 
154
        # RBC 20060103 surely its not needed anyway, with codecs transcode
 
155
        # file support ?
 
156
        # JAM 20060103 We definitely don't want encode(..., 'replace')
 
157
        # these are valuable files which should have exact contents.
 
158
        encoded_file = IterableFile(b.encode('utf-8') for b in 
 
159
                                    iterator)
 
160
        self.put(path, encoded_file)
 
161
 
 
162
    def lock_write(self):
 
163
        mutter("lock write: %s (%s)", self, self._lock_count)
 
164
        # TODO: Upgrade locking to support using a Transport,
 
165
        # and potentially a remote locking protocol
 
166
        if self._lock_mode:
 
167
            if self._lock_mode != 'w':
 
168
                raise LockError("can't upgrade to a write lock from %r" %
 
169
                                self._lock_mode)
 
170
            self._lock_count += 1
 
171
        else:
 
172
            self._lock = self._transport.lock_write(
 
173
                    self._escape(self.lock_name))
 
174
            self._lock_mode = 'w'
 
175
            self._lock_count = 1
 
176
            self._set_transaction(transactions.PassThroughTransaction())
 
177
 
 
178
    def lock_read(self):
 
179
        mutter("lock read: %s (%s)", self, self._lock_count)
 
180
        if self._lock_mode:
 
181
            assert self._lock_mode in ('r', 'w'), \
 
182
                   "invalid lock mode %r" % self._lock_mode
 
183
            self._lock_count += 1
 
184
        else:
 
185
            self._lock = self._transport.lock_read(
 
186
                    self._escape(self.lock_name))
 
187
            self._lock_mode = 'r'
 
188
            self._lock_count = 1
 
189
            self._set_transaction(transactions.ReadOnlyTransaction())
 
190
            # 5K may be excessive, but hey, its a knob.
 
191
            self.get_transaction().set_cache_size(5000)
 
192
                        
 
193
    def unlock(self):
 
194
        mutter("unlock: %s (%s)", self, self._lock_count)
 
195
        if not self._lock_mode:
 
196
            raise LockError('branch %r is not locked' % (self))
 
197
 
 
198
        if self._lock_count > 1:
 
199
            self._lock_count -= 1
 
200
        else:
 
201
            self._finish_transaction()
 
202
            self._lock.unlock()
 
203
            self._lock = None
 
204
            self._lock_mode = self._lock_count = None
 
205
 
 
206
    def get_transaction(self):
 
207
        """Return the current active transaction.
 
208
 
 
209
        If no transaction is active, this returns a passthrough object
 
210
        for which all data is immediately flushed and no caching happens.
 
211
        """
 
212
        if self._transaction is None:
 
213
            return transactions.PassThroughTransaction()
 
214
        else:
 
215
            return self._transaction
 
216
 
 
217
    def _set_transaction(self, new_transaction):
 
218
        """Set a new active transaction."""
 
219
        if self._transaction is not None:
 
220
            raise errors.LockError('Branch %s is in a transaction already.' %
 
221
                                   self)
 
222
        self._transaction = new_transaction
 
223
 
 
224
    def _finish_transaction(self):
 
225
        """Exit the current transaction."""
 
226
        if self._transaction is None:
 
227
            raise errors.LockError('Branch %s is not in a transaction' %
 
228
                                   self)
 
229
        transaction = self._transaction
 
230
        self._transaction = None
 
231
        transaction.finish()