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.
 
 
27
from stat import S_IFREG, S_IFDIR
 
 
28
from cStringIO import StringIO
 
 
31
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
 
 
32
from bzrlib.trace import mutter
 
 
33
from bzrlib.transport import (Transport, register_transport, Server)
 
 
34
import bzrlib.urlutils as urlutils
 
 
38
class MemoryStat(object):
 
 
40
    def __init__(self, size, is_dir, perms):
 
 
45
            self.st_mode = S_IFREG | perms
 
 
49
            self.st_mode = S_IFDIR | perms
 
 
52
class MemoryTransport(Transport):
 
 
53
    """This is an in memory file system for transient data storage."""
 
 
55
    def __init__(self, url=""):
 
 
56
        """Set the 'base' path where files will be stored."""
 
 
61
        super(MemoryTransport, self).__init__(url)
 
 
62
        self._cwd = url[url.find(':') + 3:]
 
 
63
        # dictionaries from absolute path to file mode
 
 
64
        self._dirs = {'/':None}
 
 
68
    def clone(self, offset=None):
 
 
69
        """See Transport.clone()."""
 
 
70
        if offset is None or offset == '':
 
 
72
        segments = offset.split('/')
 
 
73
        cwdsegments = self._cwd.split('/')[:-1]
 
 
75
            segment = segments.pop(0)
 
 
79
                if len(cwdsegments) > 1:
 
 
82
            cwdsegments.append(segment)
 
 
83
        url = self.base[:self.base.find(':') + 3] + '/'.join(cwdsegments) + '/'
 
 
84
        result = MemoryTransport(url)
 
 
85
        result._dirs = self._dirs
 
 
86
        result._files = self._files
 
 
87
        result._locks = self._locks
 
 
90
    def abspath(self, relpath):
 
 
91
        """See Transport.abspath()."""
 
 
92
        # while a little slow, this is sufficiently fast to not matter in our
 
 
93
        # current environment - XXX RBC 20060404 move the clone '..' handling
 
 
94
        # into here and call abspath from clone
 
 
95
        temp_t = self.clone(relpath)
 
 
96
        if temp_t.base.count('/') == 3:
 
 
99
            return temp_t.base[:-1]
 
 
101
    def append_file(self, relpath, f, mode=None):
 
 
102
        """See Transport.append_file()."""
 
 
103
        _abspath = self._abspath(relpath)
 
 
104
        self._check_parent(_abspath)
 
 
105
        orig_content, orig_mode = self._files.get(_abspath, ("", None))
 
 
108
        self._files[_abspath] = (orig_content + f.read(), mode)
 
 
109
        return len(orig_content)
 
 
111
    def _check_parent(self, _abspath):
 
 
112
        dir = os.path.dirname(_abspath)
 
 
114
            if not dir in self._dirs:
 
 
115
                raise NoSuchFile(_abspath)
 
 
117
    def has(self, relpath):
 
 
118
        """See Transport.has()."""
 
 
119
        _abspath = self._abspath(relpath)
 
 
120
        return (_abspath in self._files) or (_abspath in self._dirs)
 
 
122
    def delete(self, relpath):
 
 
123
        """See Transport.delete()."""
 
 
124
        _abspath = self._abspath(relpath)
 
 
125
        if not _abspath in self._files:
 
 
126
            raise NoSuchFile(relpath)
 
 
127
        del self._files[_abspath]
 
 
129
    def get(self, relpath):
 
 
130
        """See Transport.get()."""
 
 
131
        _abspath = self._abspath(relpath)
 
 
132
        if not _abspath in self._files:
 
 
133
            raise NoSuchFile(relpath)
 
 
134
        return StringIO(self._files[_abspath][0])
 
 
136
    def put_file(self, relpath, f, mode=None):
 
 
137
        """See Transport.put_file()."""
 
 
138
        _abspath = self._abspath(relpath)
 
 
139
        self._check_parent(_abspath)
 
 
140
        self._files[_abspath] = (f.read(), mode)
 
 
142
    def mkdir(self, relpath, mode=None):
 
 
143
        """See Transport.mkdir()."""
 
 
144
        _abspath = self._abspath(relpath)
 
 
145
        self._check_parent(_abspath)
 
 
146
        if _abspath in self._dirs:
 
 
147
            raise FileExists(relpath)
 
 
148
        self._dirs[_abspath]=mode
 
 
151
        """See Transport.listable."""
 
 
154
    def iter_files_recursive(self):
 
 
155
        for file in self._files:
 
 
156
            if file.startswith(self._cwd):
 
 
157
                yield urlutils.escape(file[len(self._cwd):])
 
 
159
    def list_dir(self, relpath):
 
 
160
        """See Transport.list_dir()."""
 
 
161
        _abspath = self._abspath(relpath)
 
 
162
        if _abspath != '/' and _abspath not in self._dirs:
 
 
163
            raise NoSuchFile(relpath)
 
 
165
        for path in self._files:
 
 
166
            if (path.startswith(_abspath) and 
 
 
167
                path[len(_abspath) + 1:].find('/') == -1 and
 
 
168
                len(path) > len(_abspath)):
 
 
169
                result.append(path[len(_abspath) + 1:])
 
 
170
        for path in self._dirs:
 
 
171
            if (path.startswith(_abspath) and 
 
 
172
                path[len(_abspath) + 1:].find('/') == -1 and
 
 
173
                len(path) > len(_abspath) and
 
 
174
                path[len(_abspath)] == '/'):
 
 
175
                result.append(path[len(_abspath) + 1:])
 
 
176
        return map(urlutils.escape, result)
 
 
178
    def rename(self, rel_from, rel_to):
 
 
179
        """Rename a file or directory; fail if the destination exists"""
 
 
180
        abs_from = self._abspath(rel_from)
 
 
181
        abs_to = self._abspath(rel_to)
 
 
185
            elif x.startswith(abs_from + '/'):
 
 
186
                x = abs_to + x[len(abs_from):]
 
 
188
        def do_renames(container):
 
 
189
            for path in container:
 
 
190
                new_path = replace(path)
 
 
192
                    if new_path in container:
 
 
193
                        raise FileExists(new_path)
 
 
194
                    container[new_path] = container[path]
 
 
196
        do_renames(self._files)
 
 
197
        do_renames(self._dirs)
 
 
199
    def rmdir(self, relpath):
 
 
200
        """See Transport.rmdir."""
 
 
201
        _abspath = self._abspath(relpath)
 
 
202
        if _abspath in self._files:
 
 
203
            self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
 
 
204
        for path in self._files:
 
 
205
            if path.startswith(_abspath):
 
 
206
                self._translate_error(IOError(errno.ENOTEMPTY, relpath),
 
 
208
        for path in self._dirs:
 
 
209
            if path.startswith(_abspath) and path != _abspath:
 
 
210
                self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
 
 
211
        if not _abspath in self._dirs:
 
 
212
            raise NoSuchFile(relpath)
 
 
213
        del self._dirs[_abspath]
 
 
215
    def stat(self, relpath):
 
 
216
        """See Transport.stat()."""
 
 
217
        _abspath = self._abspath(relpath)
 
 
218
        if _abspath in self._files:
 
 
219
            return MemoryStat(len(self._files[_abspath][0]), False, 
 
 
220
                              self._files[_abspath][1])
 
 
221
        elif _abspath in self._dirs:
 
 
222
            return MemoryStat(0, True, self._dirs[_abspath])
 
 
224
            raise NoSuchFile(_abspath)
 
 
226
    def lock_read(self, relpath):
 
 
227
        """See Transport.lock_read()."""
 
 
228
        return _MemoryLock(self._abspath(relpath), self)
 
 
230
    def lock_write(self, relpath):
 
 
231
        """See Transport.lock_write()."""
 
 
232
        return _MemoryLock(self._abspath(relpath), self)
 
 
234
    def _abspath(self, relpath):
 
 
235
        """Generate an internal absolute path."""
 
 
236
        relpath = urlutils.unescape(relpath)
 
 
237
        if relpath.find('..') != -1:
 
 
238
            raise AssertionError('relpath contains ..')
 
 
240
            if (self._cwd == '/'):
 
 
242
            return self._cwd[:-1]
 
 
243
        if relpath.endswith('/'):
 
 
244
            relpath = relpath[:-1]
 
 
245
        if relpath.startswith('./'):
 
 
246
            relpath = relpath[2:]
 
 
247
        return self._cwd + relpath
 
 
250
class _MemoryLock(object):
 
 
251
    """This makes a lock."""
 
 
253
    def __init__(self, path, transport):
 
 
254
        assert isinstance(transport, MemoryTransport)
 
 
256
        self.transport = transport
 
 
257
        if self.path in self.transport._locks:
 
 
258
            raise LockError('File %r already locked' % (self.path,))
 
 
259
        self.transport._locks[self.path] = self
 
 
262
        # Should this warn, or actually try to cleanup?
 
 
264
            warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
 
 
268
        del self.transport._locks[self.path]
 
 
269
        self.transport = None
 
 
272
class MemoryServer(Server):
 
 
273
    """Server for the MemoryTransport for testing with."""
 
 
276
        """See bzrlib.transport.Server.setUp."""
 
 
277
        self._dirs = {'/':None}
 
 
280
        self._scheme = "memory+%s:///" % id(self)
 
 
281
        def memory_factory(url):
 
 
282
            result = MemoryTransport(url)
 
 
283
            result._dirs = self._dirs
 
 
284
            result._files = self._files
 
 
285
            result._locks = self._locks
 
 
287
        register_transport(self._scheme, memory_factory)
 
 
290
        """See bzrlib.transport.Server.tearDown."""
 
 
291
        # unregister this server
 
 
294
        """See bzrlib.transport.Server.get_url."""
 
 
298
def get_test_permutations():
 
 
299
    """Return the permutations to be used in testing."""
 
 
300
    return [(MemoryTransport, MemoryServer),