~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

(vila) Calling super() instead of mentioning the base class in setUp avoid
 mistakes. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

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