~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

Factor out another win32 special case and add platform independent tests for it.

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