~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Alexander Belchenko
  • Date: 2007-09-22 17:29:16 UTC
  • mfrom: (2846 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2864.
  • Revision ID: bialix@ukr.net-20070922172916-yzl05wpf8ye852gw
Bug #140419 fixed by Robert Collins

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006 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
"""Implementation of Transport that uses memory for its storage.
 
18
 
 
19
The contents of the transport will be lost when the object is discarded,
 
20
so this is primarily useful for testing.
 
21
"""
 
22
 
 
23
import os
 
24
import errno
 
25
import re
 
26
from stat import S_IFREG, S_IFDIR
 
27
from cStringIO import StringIO
 
28
import warnings
 
29
 
 
30
from bzrlib.errors import (
 
31
    FileExists,
 
32
    LockError,
 
33
    InProcessTransport,
 
34
    NoSuchFile,
 
35
    TransportError,
 
36
    )
 
37
from bzrlib.trace import mutter
 
38
from bzrlib.transport import (
 
39
    AppendBasedFileStream,
 
40
    _file_streams,
 
41
    LateReadError,
 
42
    register_transport,
 
43
    Server,
 
44
    Transport,
 
45
    )
 
46
import bzrlib.urlutils as urlutils
 
47
 
 
48
 
 
49
 
 
50
class MemoryStat(object):
 
51
 
 
52
    def __init__(self, size, is_dir, perms):
 
53
        self.st_size = size
 
54
        if not is_dir:
 
55
            if perms is None:
 
56
                perms = 0644
 
57
            self.st_mode = S_IFREG | perms
 
58
        else:
 
59
            if perms is None:
 
60
                perms = 0755
 
61
            self.st_mode = S_IFDIR | perms
 
62
 
 
63
 
 
64
class MemoryTransport(Transport):
 
65
    """This is an in memory file system for transient data storage."""
 
66
 
 
67
    def __init__(self, url=""):
 
68
        """Set the 'base' path where files will be stored."""
 
69
        if url == "":
 
70
            url = "memory:///"
 
71
        if url[-1] != '/':
 
72
            url = url + '/'
 
73
        super(MemoryTransport, self).__init__(url)
 
74
        split = url.find(':') + 3
 
75
        self._scheme = url[:split]
 
76
        self._cwd = url[split:]
 
77
        # dictionaries from absolute path to file mode
 
78
        self._dirs = {'/':None}
 
79
        self._files = {}
 
80
        self._locks = {}
 
81
 
 
82
    def clone(self, offset=None):
 
83
        """See Transport.clone()."""
 
84
        path = self._combine_paths(self._cwd, offset)
 
85
        if len(path) == 0 or path[-1] != '/':
 
86
            path += '/'
 
87
        url = self._scheme + path
 
88
        result = MemoryTransport(url)
 
89
        result._dirs = self._dirs
 
90
        result._files = self._files
 
91
        result._locks = self._locks
 
92
        return result
 
93
 
 
94
    def abspath(self, relpath):
 
95
        """See Transport.abspath()."""
 
96
        # while a little slow, this is sufficiently fast to not matter in our
 
97
        # current environment - XXX RBC 20060404 move the clone '..' handling
 
98
        # into here and call abspath from clone
 
99
        temp_t = self.clone(relpath)
 
100
        if temp_t.base.count('/') == 3:
 
101
            return temp_t.base
 
102
        else:
 
103
            return temp_t.base[:-1]
 
104
 
 
105
    def append_file(self, relpath, f, mode=None):
 
106
        """See Transport.append_file()."""
 
107
        _abspath = self._abspath(relpath)
 
108
        self._check_parent(_abspath)
 
109
        orig_content, orig_mode = self._files.get(_abspath, ("", None))
 
110
        if mode is None:
 
111
            mode = orig_mode
 
112
        self._files[_abspath] = (orig_content + f.read(), mode)
 
113
        return len(orig_content)
 
114
 
 
115
    def _check_parent(self, _abspath):
 
116
        dir = os.path.dirname(_abspath)
 
117
        if dir != '/':
 
118
            if not dir in self._dirs:
 
119
                raise NoSuchFile(_abspath)
 
120
 
 
121
    def has(self, relpath):
 
122
        """See Transport.has()."""
 
123
        _abspath = self._abspath(relpath)
 
124
        return (_abspath in self._files) or (_abspath in self._dirs)
 
125
 
 
126
    def delete(self, relpath):
 
127
        """See Transport.delete()."""
 
128
        _abspath = self._abspath(relpath)
 
129
        if not _abspath in self._files:
 
130
            raise NoSuchFile(relpath)
 
131
        del self._files[_abspath]
 
132
 
 
133
    def external_url(self):
 
134
        """See bzrlib.transport.Transport.external_url."""
 
135
        # MemoryTransport's are only accessible in-process
 
136
        # so we raise here
 
137
        raise InProcessTransport(self)
 
138
 
 
139
    def get(self, relpath):
 
140
        """See Transport.get()."""
 
141
        _abspath = self._abspath(relpath)
 
142
        if not _abspath in self._files:
 
143
            if _abspath in self._dirs:
 
144
                return LateReadError(relpath)
 
145
            else:
 
146
                raise NoSuchFile(relpath)
 
147
        return StringIO(self._files[_abspath][0])
 
148
 
 
149
    def put_file(self, relpath, f, mode=None):
 
150
        """See Transport.put_file()."""
 
151
        _abspath = self._abspath(relpath)
 
152
        self._check_parent(_abspath)
 
153
        bytes = f.read()
 
154
        if type(bytes) is not str:
 
155
            # Although not strictly correct, we raise UnicodeEncodeError to be
 
156
            # compatible with other transports.
 
157
            raise UnicodeEncodeError(
 
158
                'undefined', bytes, 0, 1,
 
159
                'put_file must be given a file of bytes, not unicode.')
 
160
        self._files[_abspath] = (bytes, mode)
 
161
 
 
162
    def mkdir(self, relpath, mode=None):
 
163
        """See Transport.mkdir()."""
 
164
        _abspath = self._abspath(relpath)
 
165
        self._check_parent(_abspath)
 
166
        if _abspath in self._dirs:
 
167
            raise FileExists(relpath)
 
168
        self._dirs[_abspath]=mode
 
169
 
 
170
    def open_write_stream(self, relpath, mode=None):
 
171
        """See Transport.open_write_stream."""
 
172
        self.put_bytes(relpath, "", mode)
 
173
        result = AppendBasedFileStream(self, relpath)
 
174
        _file_streams[self.abspath(relpath)] = result
 
175
        return result
 
176
 
 
177
    def listable(self):
 
178
        """See Transport.listable."""
 
179
        return True
 
180
 
 
181
    def iter_files_recursive(self):
 
182
        for file in self._files:
 
183
            if file.startswith(self._cwd):
 
184
                yield urlutils.escape(file[len(self._cwd):])
 
185
    
 
186
    def list_dir(self, relpath):
 
187
        """See Transport.list_dir()."""
 
188
        _abspath = self._abspath(relpath)
 
189
        if _abspath != '/' and _abspath not in self._dirs:
 
190
            raise NoSuchFile(relpath)
 
191
        result = []
 
192
 
 
193
        if not _abspath.endswith('/'):
 
194
            _abspath += '/'
 
195
 
 
196
        for path_group in self._files, self._dirs:
 
197
            for path in path_group:
 
198
                if path.startswith(_abspath):
 
199
                    trailing = path[len(_abspath):]
 
200
                    if trailing and '/' not in trailing:
 
201
                        result.append(trailing)
 
202
        return map(urlutils.escape, result)
 
203
 
 
204
    def rename(self, rel_from, rel_to):
 
205
        """Rename a file or directory; fail if the destination exists"""
 
206
        abs_from = self._abspath(rel_from)
 
207
        abs_to = self._abspath(rel_to)
 
208
        def replace(x):
 
209
            if x == abs_from:
 
210
                x = abs_to
 
211
            elif x.startswith(abs_from + '/'):
 
212
                x = abs_to + x[len(abs_from):]
 
213
            return x
 
214
        def do_renames(container):
 
215
            for path in container:
 
216
                new_path = replace(path)
 
217
                if new_path != path:
 
218
                    if new_path in container:
 
219
                        raise FileExists(new_path)
 
220
                    container[new_path] = container[path]
 
221
                    del container[path]
 
222
        do_renames(self._files)
 
223
        do_renames(self._dirs)
 
224
    
 
225
    def rmdir(self, relpath):
 
226
        """See Transport.rmdir."""
 
227
        _abspath = self._abspath(relpath)
 
228
        if _abspath in self._files:
 
229
            self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
 
230
        for path in self._files:
 
231
            if path.startswith(_abspath + '/'):
 
232
                self._translate_error(IOError(errno.ENOTEMPTY, relpath),
 
233
                                      relpath)
 
234
        for path in self._dirs:
 
235
            if path.startswith(_abspath + '/') and path != _abspath:
 
236
                self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
 
237
        if not _abspath in self._dirs:
 
238
            raise NoSuchFile(relpath)
 
239
        del self._dirs[_abspath]
 
240
 
 
241
    def stat(self, relpath):
 
242
        """See Transport.stat()."""
 
243
        _abspath = self._abspath(relpath)
 
244
        if _abspath in self._files:
 
245
            return MemoryStat(len(self._files[_abspath][0]), False, 
 
246
                              self._files[_abspath][1])
 
247
        elif _abspath in self._dirs:
 
248
            return MemoryStat(0, True, self._dirs[_abspath])
 
249
        else:
 
250
            raise NoSuchFile(_abspath)
 
251
 
 
252
    def lock_read(self, relpath):
 
253
        """See Transport.lock_read()."""
 
254
        return _MemoryLock(self._abspath(relpath), self)
 
255
 
 
256
    def lock_write(self, relpath):
 
257
        """See Transport.lock_write()."""
 
258
        return _MemoryLock(self._abspath(relpath), self)
 
259
 
 
260
    def _abspath(self, relpath):
 
261
        """Generate an internal absolute path."""
 
262
        relpath = urlutils.unescape(relpath)
 
263
        if relpath.find('..') != -1:
 
264
            raise AssertionError('relpath contains ..')
 
265
        if relpath == '':
 
266
            return '/'
 
267
        if relpath[0] == '/':
 
268
            return relpath
 
269
        if relpath == '.':
 
270
            if (self._cwd == '/'):
 
271
                return self._cwd
 
272
            return self._cwd[:-1]
 
273
        if relpath.endswith('/'):
 
274
            relpath = relpath[:-1]
 
275
        if relpath.startswith('./'):
 
276
            relpath = relpath[2:]
 
277
        return self._cwd + relpath
 
278
 
 
279
 
 
280
class _MemoryLock(object):
 
281
    """This makes a lock."""
 
282
 
 
283
    def __init__(self, path, transport):
 
284
        assert isinstance(transport, MemoryTransport)
 
285
        self.path = path
 
286
        self.transport = transport
 
287
        if self.path in self.transport._locks:
 
288
            raise LockError('File %r already locked' % (self.path,))
 
289
        self.transport._locks[self.path] = self
 
290
 
 
291
    def __del__(self):
 
292
        # Should this warn, or actually try to cleanup?
 
293
        if self.transport:
 
294
            warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
 
295
            self.unlock()
 
296
 
 
297
    def unlock(self):
 
298
        del self.transport._locks[self.path]
 
299
        self.transport = None
 
300
 
 
301
 
 
302
class MemoryServer(Server):
 
303
    """Server for the MemoryTransport for testing with."""
 
304
 
 
305
    def setUp(self):
 
306
        """See bzrlib.transport.Server.setUp."""
 
307
        self._dirs = {'/':None}
 
308
        self._files = {}
 
309
        self._locks = {}
 
310
        self._scheme = "memory+%s:///" % id(self)
 
311
        def memory_factory(url):
 
312
            result = MemoryTransport(url)
 
313
            result._dirs = self._dirs
 
314
            result._files = self._files
 
315
            result._locks = self._locks
 
316
            return result
 
317
        register_transport(self._scheme, memory_factory)
 
318
 
 
319
    def tearDown(self):
 
320
        """See bzrlib.transport.Server.tearDown."""
 
321
        # unregister this server
 
322
 
 
323
    def get_url(self):
 
324
        """See bzrlib.transport.Server.get_url."""
 
325
        return self._scheme
 
326
 
 
327
 
 
328
def get_test_permutations():
 
329
    """Return the permutations to be used in testing."""
 
330
    return [(MemoryTransport, MemoryServer),
 
331
            ]