~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Martin Pool
  • Date: 2006-03-20 18:34:33 UTC
  • mto: This revision was merged to the branch mainline in revision 1621.
  • Revision ID: mbp@sourcefrog.net-20060320183433-ccd67ac1a77d40e2
Emit trace message about hash cache performance.

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