~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Martin Pool
  • Date: 2006-05-17 07:09:13 UTC
  • mfrom: (1668.1.15 bzr-0.8.mbp)
  • mto: This revision was merged to the branch mainline in revision 1710.
  • Revision ID: mbp@sourcefrog.net-20060517070913-723e387ac91d55c0
merge 0.8 fix branch, including register-branch plugin

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