~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-02-01 13:53:24 UTC
  • mfrom: (4990.1.2 rm-obsolete)
  • Revision ID: pqm@pqm.ubuntu.com-20100201135324-cuhuolr97guf5xjp
(Jelmer) Remove obsolete (and broken) biobench and history2revfiles
        tools.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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
import os
 
24
import errno
 
25
import re
 
26
from stat import S_IFREG, S_IFDIR
 
27
from cStringIO import StringIO
 
28
import warnings
 
29
 
 
30
from bzrlib import (
 
31
    urlutils,
 
32
    )
 
33
from bzrlib.errors import (
 
34
    FileExists,
 
35
    LockError,
 
36
    InProcessTransport,
 
37
    NoSuchFile,
 
38
    TransportError,
 
39
    )
 
40
from bzrlib.trace import mutter
 
41
from bzrlib.transport import (
 
42
    AppendBasedFileStream,
 
43
    _file_streams,
 
44
    LateReadError,
 
45
    register_transport,
 
46
    Server,
 
47
    Transport,
 
48
    unregister_transport,
 
49
    )
 
50
 
 
51
 
 
52
 
 
53
class MemoryStat(object):
 
54
 
 
55
    def __init__(self, size, is_dir, perms):
 
56
        self.st_size = size
 
57
        if not is_dir:
 
58
            if perms is None:
 
59
                perms = 0644
 
60
            self.st_mode = S_IFREG | perms
 
61
        else:
 
62
            if perms is None:
 
63
                perms = 0755
 
64
            self.st_mode = S_IFDIR | perms
 
65
 
 
66
 
 
67
class MemoryTransport(Transport):
 
68
    """This is an in memory file system for transient data storage."""
 
69
 
 
70
    def __init__(self, url=""):
 
71
        """Set the 'base' path where files will be stored."""
 
72
        if url == "":
 
73
            url = "memory:///"
 
74
        if url[-1] != '/':
 
75
            url = url + '/'
 
76
        super(MemoryTransport, self).__init__(url)
 
77
        split = url.find(':') + 3
 
78
        self._scheme = url[:split]
 
79
        self._cwd = url[split:]
 
80
        # dictionaries from absolute path to file mode
 
81
        self._dirs = {'/':None}
 
82
        self._files = {}
 
83
        self._locks = {}
 
84
 
 
85
    def clone(self, offset=None):
 
86
        """See Transport.clone()."""
 
87
        path = self._combine_paths(self._cwd, offset)
 
88
        if len(path) == 0 or path[-1] != '/':
 
89
            path += '/'
 
90
        url = self._scheme + path
 
91
        result = self.__class__(url)
 
92
        result._dirs = self._dirs
 
93
        result._files = self._files
 
94
        result._locks = self._locks
 
95
        return result
 
96
 
 
97
    def abspath(self, relpath):
 
98
        """See Transport.abspath()."""
 
99
        # while a little slow, this is sufficiently fast to not matter in our
 
100
        # current environment - XXX RBC 20060404 move the clone '..' handling
 
101
        # into here and call abspath from clone
 
102
        temp_t = self.clone(relpath)
 
103
        if temp_t.base.count('/') == 3:
 
104
            return temp_t.base
 
105
        else:
 
106
            return temp_t.base[:-1]
 
107
 
 
108
    def append_file(self, relpath, f, mode=None):
 
109
        """See Transport.append_file()."""
 
110
        _abspath = self._abspath(relpath)
 
111
        self._check_parent(_abspath)
 
112
        orig_content, orig_mode = self._files.get(_abspath, ("", None))
 
113
        if mode is None:
 
114
            mode = orig_mode
 
115
        self._files[_abspath] = (orig_content + f.read(), mode)
 
116
        return len(orig_content)
 
117
 
 
118
    def _check_parent(self, _abspath):
 
119
        dir = os.path.dirname(_abspath)
 
120
        if dir != '/':
 
121
            if not dir in self._dirs:
 
122
                raise NoSuchFile(_abspath)
 
123
 
 
124
    def has(self, relpath):
 
125
        """See Transport.has()."""
 
126
        _abspath = self._abspath(relpath)
 
127
        return (_abspath in self._files) or (_abspath in self._dirs)
 
128
 
 
129
    def delete(self, relpath):
 
130
        """See Transport.delete()."""
 
131
        _abspath = self._abspath(relpath)
 
132
        if not _abspath in self._files:
 
133
            raise NoSuchFile(relpath)
 
134
        del self._files[_abspath]
 
135
 
 
136
    def external_url(self):
 
137
        """See bzrlib.transport.Transport.external_url."""
 
138
        # MemoryTransport's are only accessible in-process
 
139
        # so we raise here
 
140
        raise InProcessTransport(self)
 
141
 
 
142
    def get(self, relpath):
 
143
        """See Transport.get()."""
 
144
        _abspath = self._abspath(relpath)
 
145
        if not _abspath in self._files:
 
146
            if _abspath in self._dirs:
 
147
                return LateReadError(relpath)
 
148
            else:
 
149
                raise NoSuchFile(relpath)
 
150
        return StringIO(self._files[_abspath][0])
 
151
 
 
152
    def put_file(self, relpath, f, mode=None):
 
153
        """See Transport.put_file()."""
 
154
        _abspath = self._abspath(relpath)
 
155
        self._check_parent(_abspath)
 
156
        bytes = f.read()
 
157
        if type(bytes) is not str:
 
158
            # Although not strictly correct, we raise UnicodeEncodeError to be
 
159
            # compatible with other transports.
 
160
            raise UnicodeEncodeError(
 
161
                'undefined', bytes, 0, 1,
 
162
                'put_file must be given a file of bytes, not unicode.')
 
163
        self._files[_abspath] = (bytes, mode)
 
164
        return len(bytes)
 
165
 
 
166
    def mkdir(self, relpath, mode=None):
 
167
        """See Transport.mkdir()."""
 
168
        _abspath = self._abspath(relpath)
 
169
        self._check_parent(_abspath)
 
170
        if _abspath in self._dirs:
 
171
            raise FileExists(relpath)
 
172
        self._dirs[_abspath]=mode
 
173
 
 
174
    def open_write_stream(self, relpath, mode=None):
 
175
        """See Transport.open_write_stream."""
 
176
        self.put_bytes(relpath, "", mode)
 
177
        result = AppendBasedFileStream(self, relpath)
 
178
        _file_streams[self.abspath(relpath)] = result
 
179
        return result
 
180
 
 
181
    def listable(self):
 
182
        """See Transport.listable."""
 
183
        return True
 
184
 
 
185
    def iter_files_recursive(self):
 
186
        for file in self._files:
 
187
            if file.startswith(self._cwd):
 
188
                yield urlutils.escape(file[len(self._cwd):])
 
189
 
 
190
    def list_dir(self, relpath):
 
191
        """See Transport.list_dir()."""
 
192
        _abspath = self._abspath(relpath)
 
193
        if _abspath != '/' and _abspath not in self._dirs:
 
194
            raise NoSuchFile(relpath)
 
195
        result = []
 
196
 
 
197
        if not _abspath.endswith('/'):
 
198
            _abspath += '/'
 
199
 
 
200
        for path_group in self._files, self._dirs:
 
201
            for path in path_group:
 
202
                if path.startswith(_abspath):
 
203
                    trailing = path[len(_abspath):]
 
204
                    if trailing and '/' not in trailing:
 
205
                        result.append(trailing)
 
206
        return map(urlutils.escape, result)
 
207
 
 
208
    def rename(self, rel_from, rel_to):
 
209
        """Rename a file or directory; fail if the destination exists"""
 
210
        abs_from = self._abspath(rel_from)
 
211
        abs_to = self._abspath(rel_to)
 
212
        def replace(x):
 
213
            if x == abs_from:
 
214
                x = abs_to
 
215
            elif x.startswith(abs_from + '/'):
 
216
                x = abs_to + x[len(abs_from):]
 
217
            return x
 
218
        def do_renames(container):
 
219
            for path in container:
 
220
                new_path = replace(path)
 
221
                if new_path != path:
 
222
                    if new_path in container:
 
223
                        raise FileExists(new_path)
 
224
                    container[new_path] = container[path]
 
225
                    del container[path]
 
226
        do_renames(self._files)
 
227
        do_renames(self._dirs)
 
228
 
 
229
    def rmdir(self, relpath):
 
230
        """See Transport.rmdir."""
 
231
        _abspath = self._abspath(relpath)
 
232
        if _abspath in self._files:
 
233
            self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
 
234
        for path in self._files:
 
235
            if path.startswith(_abspath + '/'):
 
236
                self._translate_error(IOError(errno.ENOTEMPTY, relpath),
 
237
                                      relpath)
 
238
        for path in self._dirs:
 
239
            if path.startswith(_abspath + '/') and path != _abspath:
 
240
                self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
 
241
        if not _abspath in self._dirs:
 
242
            raise NoSuchFile(relpath)
 
243
        del self._dirs[_abspath]
 
244
 
 
245
    def stat(self, relpath):
 
246
        """See Transport.stat()."""
 
247
        _abspath = self._abspath(relpath)
 
248
        if _abspath in self._files:
 
249
            return MemoryStat(len(self._files[_abspath][0]), False,
 
250
                              self._files[_abspath][1])
 
251
        elif _abspath in self._dirs:
 
252
            return MemoryStat(0, True, self._dirs[_abspath])
 
253
        else:
 
254
            raise NoSuchFile(_abspath)
 
255
 
 
256
    def lock_read(self, relpath):
 
257
        """See Transport.lock_read()."""
 
258
        return _MemoryLock(self._abspath(relpath), self)
 
259
 
 
260
    def lock_write(self, relpath):
 
261
        """See Transport.lock_write()."""
 
262
        return _MemoryLock(self._abspath(relpath), self)
 
263
 
 
264
    def _abspath(self, relpath):
 
265
        """Generate an internal absolute path."""
 
266
        relpath = urlutils.unescape(relpath)
 
267
        if relpath[:1] == '/':
 
268
            return relpath
 
269
        cwd_parts = self._cwd.split('/')
 
270
        rel_parts = relpath.split('/')
 
271
        r = []
 
272
        for i in cwd_parts + rel_parts:
 
273
            if i == '..':
 
274
                if not r:
 
275
                    raise ValueError("illegal relpath %r under %r"
 
276
                        % (relpath, self._cwd))
 
277
                r = r[:-1]
 
278
            elif i == '.' or i == '':
 
279
                pass
 
280
            else:
 
281
                r.append(i)
 
282
        return '/' + '/'.join(r)
 
283
 
 
284
 
 
285
class _MemoryLock(object):
 
286
    """This makes a lock."""
 
287
 
 
288
    def __init__(self, path, transport):
 
289
        self.path = path
 
290
        self.transport = transport
 
291
        if self.path in self.transport._locks:
 
292
            raise LockError('File %r already locked' % (self.path,))
 
293
        self.transport._locks[self.path] = self
 
294
 
 
295
    def __del__(self):
 
296
        # Should this warn, or actually try to cleanup?
 
297
        if self.transport:
 
298
            warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
 
299
            self.unlock()
 
300
 
 
301
    def unlock(self):
 
302
        del self.transport._locks[self.path]
 
303
        self.transport = None
 
304
 
 
305
 
 
306
class MemoryServer(Server):
 
307
    """Server for the MemoryTransport for testing with."""
 
308
 
 
309
    def start_server(self):
 
310
        self._dirs = {'/':None}
 
311
        self._files = {}
 
312
        self._locks = {}
 
313
        self._scheme = "memory+%s:///" % id(self)
 
314
        def memory_factory(url):
 
315
            result = MemoryTransport(url)
 
316
            result._dirs = self._dirs
 
317
            result._files = self._files
 
318
            result._locks = self._locks
 
319
            return result
 
320
        self._memory_factory = memory_factory
 
321
        register_transport(self._scheme, self._memory_factory)
 
322
 
 
323
    def stop_server(self):
 
324
        # unregister this server
 
325
        unregister_transport(self._scheme, self._memory_factory)
 
326
 
 
327
    def get_url(self):
 
328
        """See bzrlib.transport.Server.get_url."""
 
329
        return self._scheme
 
330
 
 
331
 
 
332
def get_test_permutations():
 
333
    """Return the permutations to be used in testing."""
 
334
    return [(MemoryTransport, MemoryServer),
 
335
            ]