/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

  • Committer: Aaron Bentley
  • Date: 2005-12-25 22:44:53 UTC
  • mto: (1185.67.11 bzr.revision-storage)
  • mto: This revision was merged to the branch mainline in revision 1550.
  • Revision ID: aaron.bentley@utoronto.ca-20051225224453-098a11d24b7edf84
Added write locks as appropriate

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
 
 
7
class LockableFiles(object):
 
8
    """Object representing a set of lockable files
 
9
 
 
10
    _lock_mode
 
11
        None, or 'r' or 'w'
 
12
 
 
13
    _lock_count
 
14
        If _lock_mode is true, a positive count of the number of times the
 
15
        lock has been taken.
 
16
 
 
17
    _lock
 
18
        Lock object from bzrlib.lock.
 
19
    """
 
20
 
 
21
    _lock_mode = None
 
22
    _lock_count = None
 
23
    _lock = None
 
24
 
 
25
    def __init__(self, transport, lock_name):
 
26
        object.__init__(self)
 
27
        self._transport = transport
 
28
        self.lock_name = lock_name
 
29
        self._transaction = None
 
30
 
 
31
    def __del__(self):
 
32
        if self._lock_mode or self._lock:
 
33
            # XXX: This should show something every time, and be suitable for
 
34
            # headless operation and embedding
 
35
            warn("file group %r was not explicitly unlocked" % self)
 
36
            self._lock.unlock()
 
37
 
 
38
    def _rel_controlfilename(self, file_or_path):
 
39
        if not isinstance(file_or_path, basestring):
 
40
            file_or_path = '/'.join(file_or_path)
 
41
        if file_or_path == '':
 
42
            return unicode(bzrlib.BZRDIR)
 
43
        return bzrlib.transport.urlescape(unicode(bzrlib.BZRDIR + '/' + file_or_path))
 
44
 
 
45
    def controlfilename(self, file_or_path):
 
46
        """Return location relative to branch."""
 
47
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
 
48
 
 
49
    def controlfile(self, file_or_path, mode='r'):
 
50
        """Open a control file for this branch.
 
51
 
 
52
        There are two classes of file in the control directory: text
 
53
        and binary.  binary files are untranslated byte streams.  Text
 
54
        control files are stored with Unix newlines and in UTF-8, even
 
55
        if the platform or locale defaults are different.
 
56
 
 
57
        Controlfiles should almost never be opened in write mode but
 
58
        rather should be atomically copied and replaced using atomicfile.
 
59
        """
 
60
        import codecs
 
61
 
 
62
        relpath = self._rel_controlfilename(file_or_path)
 
63
        #TODO: codecs.open() buffers linewise, so it was overloaded with
 
64
        # a much larger buffer, do we need to do the same for getreader/getwriter?
 
65
        if mode == 'rb': 
 
66
            return self._transport.get(relpath)
 
67
        elif mode == 'wb':
 
68
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put[_utf8]")
 
69
        elif mode == 'r':
 
70
            # XXX: Do we really want errors='replace'?   Perhaps it should be
 
71
            # an error, or at least reported, if there's incorrectly-encoded
 
72
            # data inside a file.
 
73
            # <https://launchpad.net/products/bzr/+bug/3823>
 
74
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
 
75
        elif mode == 'w':
 
76
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put[_utf8]")
 
77
        else:
 
78
            raise BzrError("invalid controlfile mode %r" % mode)
 
79
 
 
80
    def put(self, path, file):
 
81
        """Write a file.
 
82
        
 
83
        :param path: The path to put the file, relative to the .bzr control
 
84
                     directory
 
85
        :param f: A file-like or string object whose contents should be copied.
 
86
        """
 
87
        if not self._lock_mode == 'w':
 
88
            raise ReadOnlyError()
 
89
        self._transport.put(self._rel_controlfilename(path), file)
 
90
 
 
91
    def put_utf8(self, path, file):
 
92
        """Write a file, encoding as utf-8.
 
93
 
 
94
        :param path: The path to put the file, relative to the .bzr control
 
95
                     directory
 
96
        :param f: A file-like or string object whose contents should be copied.
 
97
        """
 
98
        import codecs
 
99
        ctrl_files = []
 
100
        if isinstance(file, basestring):
 
101
            file = file.encode('utf-8', 'replace')
 
102
        else:
 
103
            file = codecs.getwriter('utf-8')(file, errors='replace')
 
104
        self.put(path, file)
 
105
 
 
106
    def lock_write(self):
 
107
        mutter("lock write: %s (%s)", self, self._lock_count)
 
108
        # TODO: Upgrade locking to support using a Transport,
 
109
        # and potentially a remote locking protocol
 
110
        if self._lock_mode:
 
111
            if self._lock_mode != 'w':
 
112
                raise LockError("can't upgrade to a write lock from %r" %
 
113
                                self._lock_mode)
 
114
            self._lock_count += 1
 
115
        else:
 
116
            self._lock = self._transport.lock_write(
 
117
                    self._rel_controlfilename(self.lock_name))
 
118
            self._lock_mode = 'w'
 
119
            self._lock_count = 1
 
120
            self._set_transaction(transactions.PassThroughTransaction())
 
121
 
 
122
    def lock_read(self):
 
123
        mutter("lock read: %s (%s)", self, self._lock_count)
 
124
        if self._lock_mode:
 
125
            assert self._lock_mode in ('r', 'w'), \
 
126
                   "invalid lock mode %r" % self._lock_mode
 
127
            self._lock_count += 1
 
128
        else:
 
129
            self._lock = self._transport.lock_read(
 
130
                    self._rel_controlfilename('branch-lock'))
 
131
            self._lock_mode = 'r'
 
132
            self._lock_count = 1
 
133
            self._set_transaction(transactions.ReadOnlyTransaction())
 
134
            # 5K may be excessive, but hey, its a knob.
 
135
            self.get_transaction().set_cache_size(5000)
 
136
                        
 
137
    def unlock(self):
 
138
        mutter("unlock: %s (%s)", self, self._lock_count)
 
139
        if not self._lock_mode:
 
140
            raise LockError('branch %r is not locked' % (self))
 
141
 
 
142
        if self._lock_count > 1:
 
143
            self._lock_count -= 1
 
144
        else:
 
145
            self._finish_transaction()
 
146
            self._lock.unlock()
 
147
            self._lock = None
 
148
            self._lock_mode = self._lock_count = None
 
149
 
 
150
    def make_transport(self, relpath):
 
151
        return self._transport.clone(relpath)
 
152
 
 
153
    def get_transaction(self):
 
154
        """Return the current active transaction.
 
155
 
 
156
        If no transaction is active, this returns a passthrough object
 
157
        for which all data is immediately flushed and no caching happens.
 
158
        """
 
159
        if self._transaction is None:
 
160
            return transactions.PassThroughTransaction()
 
161
        else:
 
162
            return self._transaction
 
163
 
 
164
    def _set_transaction(self, new_transaction):
 
165
        """Set a new active transaction."""
 
166
        if self._transaction is not None:
 
167
            raise errors.LockError('Branch %s is in a transaction already.' %
 
168
                                   self)
 
169
        self._transaction = new_transaction
 
170
 
 
171
    def _finish_transaction(self):
 
172
        """Exit the current transaction."""
 
173
        if self._transaction is None:
 
174
            raise errors.LockError('Branch %s is not in a transaction' %
 
175
                                   self)
 
176
        transaction = self._transaction
 
177
        self._transaction = None
 
178
        transaction.finish()