1
# Copyright (C) 2005, 2006 Canonical Ltd
 
 
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.
 
 
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.
 
 
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
 
 
17
"""Implementation of Transport that uses memory for its storage.
 
 
19
The contents of the transport will be lost when the object is discarded,
 
 
20
so this is primarily useful for testing.
 
 
26
from stat import S_IFREG, S_IFDIR
 
 
27
from cStringIO import StringIO
 
 
30
from bzrlib.errors import (
 
 
37
from bzrlib.trace import mutter
 
 
38
from bzrlib.transport import (
 
 
44
import bzrlib.urlutils as urlutils
 
 
48
class MemoryStat(object):
 
 
50
    def __init__(self, size, is_dir, perms):
 
 
55
            self.st_mode = S_IFREG | perms
 
 
59
            self.st_mode = S_IFDIR | perms
 
 
62
class MemoryTransport(Transport):
 
 
63
    """This is an in memory file system for transient data storage."""
 
 
65
    def __init__(self, url=""):
 
 
66
        """Set the 'base' path where files will be stored."""
 
 
71
        super(MemoryTransport, self).__init__(url)
 
 
72
        split = url.find(':') + 3
 
 
73
        self._scheme = url[:split]
 
 
74
        self._cwd = url[split:]
 
 
75
        # dictionaries from absolute path to file mode
 
 
76
        self._dirs = {'/':None}
 
 
80
    def clone(self, offset=None):
 
 
81
        """See Transport.clone()."""
 
 
82
        path = self._combine_paths(self._cwd, offset)
 
 
83
        if len(path) == 0 or path[-1] != '/':
 
 
85
        url = self._scheme + path
 
 
86
        result = MemoryTransport(url)
 
 
87
        result._dirs = self._dirs
 
 
88
        result._files = self._files
 
 
89
        result._locks = self._locks
 
 
92
    def abspath(self, relpath):
 
 
93
        """See Transport.abspath()."""
 
 
94
        # while a little slow, this is sufficiently fast to not matter in our
 
 
95
        # current environment - XXX RBC 20060404 move the clone '..' handling
 
 
96
        # into here and call abspath from clone
 
 
97
        temp_t = self.clone(relpath)
 
 
98
        if temp_t.base.count('/') == 3:
 
 
101
            return temp_t.base[:-1]
 
 
103
    def append_file(self, relpath, f, mode=None):
 
 
104
        """See Transport.append_file()."""
 
 
105
        _abspath = self._abspath(relpath)
 
 
106
        self._check_parent(_abspath)
 
 
107
        orig_content, orig_mode = self._files.get(_abspath, ("", None))
 
 
110
        self._files[_abspath] = (orig_content + f.read(), mode)
 
 
111
        return len(orig_content)
 
 
113
    def _check_parent(self, _abspath):
 
 
114
        dir = os.path.dirname(_abspath)
 
 
116
            if not dir in self._dirs:
 
 
117
                raise NoSuchFile(_abspath)
 
 
119
    def has(self, relpath):
 
 
120
        """See Transport.has()."""
 
 
121
        _abspath = self._abspath(relpath)
 
 
122
        return (_abspath in self._files) or (_abspath in self._dirs)
 
 
124
    def delete(self, relpath):
 
 
125
        """See Transport.delete()."""
 
 
126
        _abspath = self._abspath(relpath)
 
 
127
        if not _abspath in self._files:
 
 
128
            raise NoSuchFile(relpath)
 
 
129
        del self._files[_abspath]
 
 
131
    def external_url(self):
 
 
132
        """See bzrlib.transport.Transport.external_url."""
 
 
133
        # MemoryTransport's are only accessible in-process
 
 
135
        raise InProcessTransport(self)
 
 
137
    def get(self, relpath):
 
 
138
        """See Transport.get()."""
 
 
139
        _abspath = self._abspath(relpath)
 
 
140
        if not _abspath in self._files:
 
 
141
            if _abspath in self._dirs:
 
 
142
                return LateReadError(relpath)
 
 
144
                raise NoSuchFile(relpath)
 
 
145
        return StringIO(self._files[_abspath][0])
 
 
147
    def put_file(self, relpath, f, mode=None):
 
 
148
        """See Transport.put_file()."""
 
 
149
        _abspath = self._abspath(relpath)
 
 
150
        self._check_parent(_abspath)
 
 
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)
 
 
160
    def mkdir(self, relpath, mode=None):
 
 
161
        """See Transport.mkdir()."""
 
 
162
        _abspath = self._abspath(relpath)
 
 
163
        self._check_parent(_abspath)
 
 
164
        if _abspath in self._dirs:
 
 
165
            raise FileExists(relpath)
 
 
166
        self._dirs[_abspath]=mode
 
 
169
        """See Transport.listable."""
 
 
172
    def iter_files_recursive(self):
 
 
173
        for file in self._files:
 
 
174
            if file.startswith(self._cwd):
 
 
175
                yield urlutils.escape(file[len(self._cwd):])
 
 
177
    def list_dir(self, relpath):
 
 
178
        """See Transport.list_dir()."""
 
 
179
        _abspath = self._abspath(relpath)
 
 
180
        if _abspath != '/' and _abspath not in self._dirs:
 
 
181
            raise NoSuchFile(relpath)
 
 
184
        if not _abspath.endswith('/'):
 
 
187
        for path_group in self._files, self._dirs:
 
 
188
            for path in path_group:
 
 
189
                if path.startswith(_abspath):
 
 
190
                    trailing = path[len(_abspath):]
 
 
191
                    if trailing and '/' not in trailing:
 
 
192
                        result.append(trailing)
 
 
193
        return map(urlutils.escape, result)
 
 
195
    def rename(self, rel_from, rel_to):
 
 
196
        """Rename a file or directory; fail if the destination exists"""
 
 
197
        abs_from = self._abspath(rel_from)
 
 
198
        abs_to = self._abspath(rel_to)
 
 
202
            elif x.startswith(abs_from + '/'):
 
 
203
                x = abs_to + x[len(abs_from):]
 
 
205
        def do_renames(container):
 
 
206
            for path in container:
 
 
207
                new_path = replace(path)
 
 
209
                    if new_path in container:
 
 
210
                        raise FileExists(new_path)
 
 
211
                    container[new_path] = container[path]
 
 
213
        do_renames(self._files)
 
 
214
        do_renames(self._dirs)
 
 
216
    def rmdir(self, relpath):
 
 
217
        """See Transport.rmdir."""
 
 
218
        _abspath = self._abspath(relpath)
 
 
219
        if _abspath in self._files:
 
 
220
            self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
 
 
221
        for path in self._files:
 
 
222
            if path.startswith(_abspath + '/'):
 
 
223
                self._translate_error(IOError(errno.ENOTEMPTY, relpath),
 
 
225
        for path in self._dirs:
 
 
226
            if path.startswith(_abspath + '/') and path != _abspath:
 
 
227
                self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
 
 
228
        if not _abspath in self._dirs:
 
 
229
            raise NoSuchFile(relpath)
 
 
230
        del self._dirs[_abspath]
 
 
232
    def stat(self, relpath):
 
 
233
        """See Transport.stat()."""
 
 
234
        _abspath = self._abspath(relpath)
 
 
235
        if _abspath in self._files:
 
 
236
            return MemoryStat(len(self._files[_abspath][0]), False, 
 
 
237
                              self._files[_abspath][1])
 
 
238
        elif _abspath in self._dirs:
 
 
239
            return MemoryStat(0, True, self._dirs[_abspath])
 
 
241
            raise NoSuchFile(_abspath)
 
 
243
    def lock_read(self, relpath):
 
 
244
        """See Transport.lock_read()."""
 
 
245
        return _MemoryLock(self._abspath(relpath), self)
 
 
247
    def lock_write(self, relpath):
 
 
248
        """See Transport.lock_write()."""
 
 
249
        return _MemoryLock(self._abspath(relpath), self)
 
 
251
    def _abspath(self, relpath):
 
 
252
        """Generate an internal absolute path."""
 
 
253
        relpath = urlutils.unescape(relpath)
 
 
254
        if relpath.find('..') != -1:
 
 
255
            raise AssertionError('relpath contains ..')
 
 
258
        if relpath[0] == '/':
 
 
261
            if (self._cwd == '/'):
 
 
263
            return self._cwd[:-1]
 
 
264
        if relpath.endswith('/'):
 
 
265
            relpath = relpath[:-1]
 
 
266
        if relpath.startswith('./'):
 
 
267
            relpath = relpath[2:]
 
 
268
        return self._cwd + relpath
 
 
271
class _MemoryLock(object):
 
 
272
    """This makes a lock."""
 
 
274
    def __init__(self, path, transport):
 
 
275
        assert isinstance(transport, MemoryTransport)
 
 
277
        self.transport = transport
 
 
278
        if self.path in self.transport._locks:
 
 
279
            raise LockError('File %r already locked' % (self.path,))
 
 
280
        self.transport._locks[self.path] = self
 
 
283
        # Should this warn, or actually try to cleanup?
 
 
285
            warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
 
 
289
        del self.transport._locks[self.path]
 
 
290
        self.transport = None
 
 
293
class MemoryServer(Server):
 
 
294
    """Server for the MemoryTransport for testing with."""
 
 
297
        """See bzrlib.transport.Server.setUp."""
 
 
298
        self._dirs = {'/':None}
 
 
301
        self._scheme = "memory+%s:///" % id(self)
 
 
302
        def memory_factory(url):
 
 
303
            result = MemoryTransport(url)
 
 
304
            result._dirs = self._dirs
 
 
305
            result._files = self._files
 
 
306
            result._locks = self._locks
 
 
308
        register_transport(self._scheme, memory_factory)
 
 
311
        """See bzrlib.transport.Server.tearDown."""
 
 
312
        # unregister this server
 
 
315
        """See bzrlib.transport.Server.get_url."""
 
 
319
def get_test_permutations():
 
 
320
    """Return the permutations to be used in testing."""
 
 
321
    return [(MemoryTransport, MemoryServer),