~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Martin Pool
  • Date: 2010-02-23 07:43:11 UTC
  • mfrom: (4797.2.20 2.1)
  • mto: This revision was merged to the branch mainline in revision 5055.
  • Revision ID: mbp@sourcefrog.net-20100223074311-gnj55xdhrgz9l94e
Merge 2.1 back to trunk

Show diffs side-by-side

added added

removed removed

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