~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockable_files.py

[merge] fix \t in commit messages

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
 
from cStringIO import StringIO
18
 
import codecs
19
 
 
20
 
import bzrlib
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.symbol_versioning import deprecated_method, zero_eight
27
 
from bzrlib.trace import mutter
28
 
import bzrlib.transactions as transactions
29
 
 
30
 
 
31
 
class LockableFiles(object):
32
 
    """Object representing a set of files locked within the same scope
33
 
 
34
 
    _lock_mode
35
 
        None, or 'r' or 'w'
36
 
 
37
 
    _lock_count
38
 
        If _lock_mode is true, a positive count of the number of times the
39
 
        lock has been taken *by this process*.  Others may have compatible 
40
 
        read locks.
41
 
 
42
 
    _lock
43
 
        Lock object from bzrlib.lock.
44
 
    """
45
 
 
46
 
    _lock_mode = None
47
 
    _lock_count = None
48
 
    _lock = None
49
 
    # If set to False (by a plugin, etc) BzrBranch will not set the
50
 
    # mode on created files or directories
51
 
    _set_file_mode = True
52
 
    _set_dir_mode = True
53
 
 
54
 
    def __init__(self, transport, lock_name):
55
 
        object.__init__(self)
56
 
        self._transport = transport
57
 
        self.lock_name = lock_name
58
 
        self._transaction = None
59
 
        self._find_modes()
60
 
 
61
 
    def __del__(self):
62
 
        if self._lock_mode or self._lock:
63
 
            # XXX: This should show something every time, and be suitable for
64
 
            # headless operation and embedding
65
 
            from warnings import warn
66
 
            warn("file group %r was not explicitly unlocked" % self)
67
 
            self._lock.unlock()
68
 
 
69
 
    def _escape(self, file_or_path):
70
 
        if not isinstance(file_or_path, basestring):
71
 
            file_or_path = '/'.join(file_or_path)
72
 
        if file_or_path == '':
73
 
            return u''
74
 
        return bzrlib.transport.urlescape(safe_unicode(file_or_path))
75
 
 
76
 
    def _find_modes(self):
77
 
        """Determine the appropriate modes for files and directories."""
78
 
        try:
79
 
            st = self._transport.stat('.')
80
 
        except errors.TransportNotPossible:
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
 
    @deprecated_method(zero_eight)
97
 
    def controlfile(self, file_or_path, mode='r'):
98
 
        """Open a control file for this branch.
99
 
 
100
 
        There are two classes of file in a lockable directory: text
101
 
        and binary.  binary files are untranslated byte streams.  Text
102
 
        control files are stored with Unix newlines and in UTF-8, even
103
 
        if the platform or locale defaults are different.
104
 
 
105
 
        Such files are not openable in write mode : they are managed via
106
 
        put and put_utf8 which atomically replace old versions using
107
 
        atomicfile.
108
 
        """
109
 
 
110
 
        relpath = self._escape(file_or_path)
111
 
        #TODO: codecs.open() buffers linewise, so it was overloaded with
112
 
        # a much larger buffer, do we need to do the same for getreader/getwriter?
113
 
        if mode == 'rb': 
114
 
            return self.get(relpath)
115
 
        elif mode == 'wb':
116
 
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
117
 
        elif mode == 'r':
118
 
            return self.get_utf8(relpath)
119
 
        elif mode == 'w':
120
 
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
121
 
        else:
122
 
            raise BzrError("invalid controlfile mode %r" % mode)
123
 
 
124
 
    @needs_read_lock
125
 
    def get(self, relpath):
126
 
        """Get a file as a bytestream."""
127
 
        relpath = self._escape(relpath)
128
 
        return self._transport.get(relpath)
129
 
 
130
 
    @needs_read_lock
131
 
    def get_utf8(self, relpath):
132
 
        """Get a file as a unicode stream."""
133
 
        relpath = self._escape(relpath)
134
 
        # DO NOT introduce an errors=replace here.
135
 
        return codecs.getreader('utf-8')(self._transport.get(relpath))
136
 
 
137
 
    @needs_write_lock
138
 
    def put(self, path, file):
139
 
        """Write a file.
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
 
        self._transport.put(self._escape(path), file, mode=self._file_mode)
146
 
 
147
 
    @needs_write_lock
148
 
    def put_utf8(self, path, a_string):
149
 
        """Write a string, encoding as utf-8.
150
 
 
151
 
        :param path: The path to put the string, relative to the transport root.
152
 
        :param string: A file-like or string object whose contents should be copied.
153
 
        """
154
 
        # IterableFile would not be needed if Transport.put took iterables
155
 
        # instead of files.  ADHB 2005-12-25
156
 
        # RBC 20060103 surely its not needed anyway, with codecs transcode
157
 
        # file support ?
158
 
        # JAM 20060103 We definitely don't want encode(..., 'replace')
159
 
        # these are valuable files which should have exact contents.
160
 
        if not isinstance(a_string, basestring):
161
 
            raise errors.BzrBadParameterNotString(a_string)
162
 
        self.put(path, StringIO(a_string.encode('utf-8')))
163
 
 
164
 
    def lock_write(self):
165
 
        mutter("lock write: %s (%s)", self, self._lock_count)
166
 
        # TODO: Upgrade locking to support using a Transport,
167
 
        # and potentially a remote locking protocol
168
 
        if self._lock_mode:
169
 
            if self._lock_mode != 'w':
170
 
                raise ReadOnlyError("can't upgrade to a write lock from %r" %
171
 
                                self._lock_mode)
172
 
            self._lock_count += 1
173
 
        else:
174
 
            self._lock = self._transport.lock_write(
175
 
                    self._escape(self.lock_name))
176
 
            self._lock_mode = 'w'
177
 
            self._lock_count = 1
178
 
            self._set_transaction(transactions.PassThroughTransaction())
179
 
 
180
 
    def lock_read(self):
181
 
        mutter("lock read: %s (%s)", self, self._lock_count)
182
 
        if self._lock_mode:
183
 
            assert self._lock_mode in ('r', 'w'), \
184
 
                   "invalid lock mode %r" % self._lock_mode
185
 
            self._lock_count += 1
186
 
        else:
187
 
            self._lock = self._transport.lock_read(
188
 
                    self._escape(self.lock_name))
189
 
            self._lock_mode = 'r'
190
 
            self._lock_count = 1
191
 
            self._set_transaction(transactions.ReadOnlyTransaction())
192
 
            # 5K may be excessive, but hey, its a knob.
193
 
            self.get_transaction().set_cache_size(5000)
194
 
                        
195
 
    def unlock(self):
196
 
        mutter("unlock: %s (%s)", self, self._lock_count)
197
 
        if not self._lock_mode:
198
 
            raise LockError('branch %r is not locked' % (self))
199
 
 
200
 
        if self._lock_count > 1:
201
 
            self._lock_count -= 1
202
 
        else:
203
 
            self._finish_transaction()
204
 
            self._lock.unlock()
205
 
            self._lock = None
206
 
            self._lock_mode = self._lock_count = None
207
 
 
208
 
    def get_transaction(self):
209
 
        """Return the current active transaction.
210
 
 
211
 
        If no transaction is active, this returns a passthrough object
212
 
        for which all data is immediately flushed and no caching happens.
213
 
        """
214
 
        if self._transaction is None:
215
 
            return transactions.PassThroughTransaction()
216
 
        else:
217
 
            return self._transaction
218
 
 
219
 
    def _set_transaction(self, new_transaction):
220
 
        """Set a new active transaction."""
221
 
        if self._transaction is not None:
222
 
            raise errors.LockError('Branch %s is in a transaction already.' %
223
 
                                   self)
224
 
        self._transaction = new_transaction
225
 
 
226
 
    def _finish_transaction(self):
227
 
        """Exit the current transaction."""
228
 
        if self._transaction is None:
229
 
            raise errors.LockError('Branch %s is not in a transaction' %
230
 
                                   self)
231
 
        transaction = self._transaction
232
 
        self._transaction = None
233
 
        transaction.finish()