~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Robert Collins
  • Date: 2006-03-02 03:12:34 UTC
  • mto: (1594.2.4 integration)
  • mto: This revision was merged to the branch mainline in revision 1596.
  • Revision ID: robertc@robertcollins.net-20060302031234-cf6b75961f27c5df
InterVersionedFile implemented.

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