~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lockable_files.py

add a clean target

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