~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Robert Collins
  • Date: 2009-09-07 03:08:30 UTC
  • mto: This revision was merged to the branch mainline in revision 4690.
  • Revision ID: robertc@robertcollins.net-20090907030830-rf59kt28d550eauj
Milestones language tightning, internal consistency.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
 
 
 
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Implementation of Transport that uses memory for its storage.
18
18
 
20
20
so this is primarily useful for testing.
21
21
"""
22
22
 
23
 
from copy import copy
24
23
import os
25
24
import errno
26
25
import re
27
 
from stat import *
 
26
from stat import S_IFREG, S_IFDIR
28
27
from cStringIO import StringIO
 
28
import warnings
29
29
 
 
30
from bzrlib.errors import (
 
31
    FileExists,
 
32
    LockError,
 
33
    InProcessTransport,
 
34
    NoSuchFile,
 
35
    TransportError,
 
36
    )
30
37
from bzrlib.trace import mutter
31
 
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
32
 
from bzrlib.transport import Transport, register_transport, Server
 
38
from bzrlib.transport import (
 
39
    AppendBasedFileStream,
 
40
    _file_streams,
 
41
    LateReadError,
 
42
    register_transport,
 
43
    Server,
 
44
    Transport,
 
45
    )
 
46
import bzrlib.urlutils as urlutils
 
47
 
 
48
 
33
49
 
34
50
class MemoryStat(object):
35
51
 
36
52
    def __init__(self, size, is_dir, perms):
37
53
        self.st_size = size
38
 
        if perms is None:
39
 
            perms = 0644
40
54
        if not is_dir:
 
55
            if perms is None:
 
56
                perms = 0644
41
57
            self.st_mode = S_IFREG | perms
42
58
        else:
 
59
            if perms is None:
 
60
                perms = 0755
43
61
            self.st_mode = S_IFDIR | perms
44
62
 
45
63
 
49
67
    def __init__(self, url=""):
50
68
        """Set the 'base' path where files will be stored."""
51
69
        if url == "":
52
 
            url = "memory:/"
 
70
            url = "memory:///"
53
71
        if url[-1] != '/':
54
72
            url = url + '/'
55
73
        super(MemoryTransport, self).__init__(url)
56
 
        self._cwd = url[url.find(':') + 1:]
 
74
        split = url.find(':') + 3
 
75
        self._scheme = url[:split]
 
76
        self._cwd = url[split:]
57
77
        # dictionaries from absolute path to file mode
58
 
        self._dirs = {}
 
78
        self._dirs = {'/':None}
59
79
        self._files = {}
60
80
        self._locks = {}
61
81
 
62
82
    def clone(self, offset=None):
63
83
        """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)
 
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)
79
89
        result._dirs = self._dirs
80
90
        result._files = self._files
81
91
        result._locks = self._locks
83
93
 
84
94
    def abspath(self, relpath):
85
95
        """See Transport.abspath()."""
86
 
        return self.base[:-1] + self._abspath(relpath)[len(self._cwd) - 1:]
 
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]
87
104
 
88
 
    def append(self, relpath, f):
89
 
        """See Transport.append()."""
 
105
    def append_file(self, relpath, f, mode=None):
 
106
        """See Transport.append_file()."""
90
107
        _abspath = self._abspath(relpath)
91
108
        self._check_parent(_abspath)
92
109
        orig_content, orig_mode = self._files.get(_abspath, ("", None))
93
 
        self._files[_abspath] = (orig_content + f.read(), orig_mode)
 
110
        if mode is None:
 
111
            mode = orig_mode
 
112
        self._files[_abspath] = (orig_content + f.read(), mode)
 
113
        return len(orig_content)
94
114
 
95
115
    def _check_parent(self, _abspath):
96
116
        dir = os.path.dirname(_abspath)
101
121
    def has(self, relpath):
102
122
        """See Transport.has()."""
103
123
        _abspath = self._abspath(relpath)
104
 
        return _abspath in self._files or _abspath in self._dirs
 
124
        return (_abspath in self._files) or (_abspath in self._dirs)
105
125
 
106
126
    def delete(self, relpath):
107
127
        """See Transport.delete()."""
110
130
            raise NoSuchFile(relpath)
111
131
        del self._files[_abspath]
112
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)
 
138
 
113
139
    def get(self, relpath):
114
140
        """See Transport.get()."""
115
141
        _abspath = self._abspath(relpath)
116
142
        if not _abspath in self._files:
117
 
            raise NoSuchFile(relpath)
 
143
            if _abspath in self._dirs:
 
144
                return LateReadError(relpath)
 
145
            else:
 
146
                raise NoSuchFile(relpath)
118
147
        return StringIO(self._files[_abspath][0])
119
148
 
120
 
    def put(self, relpath, f, mode=None):
121
 
        """See Transport.put()."""
 
149
    def put_file(self, relpath, f, mode=None):
 
150
        """See Transport.put_file()."""
122
151
        _abspath = self._abspath(relpath)
123
152
        self._check_parent(_abspath)
124
 
        self._files[_abspath] = (f.read(), mode)
 
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)
125
162
 
126
163
    def mkdir(self, relpath, mode=None):
127
164
        """See Transport.mkdir()."""
131
168
            raise FileExists(relpath)
132
169
        self._dirs[_abspath]=mode
133
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
 
177
 
134
178
    def listable(self):
135
179
        """See Transport.listable."""
136
180
        return True
138
182
    def iter_files_recursive(self):
139
183
        for file in self._files:
140
184
            if file.startswith(self._cwd):
141
 
                yield file[len(self._cwd):]
142
 
    
 
185
                yield urlutils.escape(file[len(self._cwd):])
 
186
 
143
187
    def list_dir(self, relpath):
144
188
        """See Transport.list_dir()."""
145
189
        _abspath = self._abspath(relpath)
146
190
        if _abspath != '/' and _abspath not in self._dirs:
147
191
            raise NoSuchFile(relpath)
148
192
        result = []
149
 
        for path in self._files:
150
 
            if (path.startswith(_abspath) and 
151
 
                path[len(_abspath) + 1:].find('/') == -1 and
152
 
                len(path) > len(_abspath)):
153
 
                result.append(path[len(_abspath) + 1:])
154
 
        for path in self._dirs:
155
 
            if (path.startswith(_abspath) and 
156
 
                path[len(_abspath) + 1:].find('/') == -1 and
157
 
                len(path) > len(_abspath) and
158
 
                path[len(_abspath)] == '/'):
159
 
                result.append(path[len(_abspath) + 1:])
160
 
        return 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)
161
204
 
162
205
    def rename(self, rel_from, rel_to):
163
206
        """Rename a file or directory; fail if the destination exists"""
179
222
                    del container[path]
180
223
        do_renames(self._files)
181
224
        do_renames(self._dirs)
182
 
    
 
225
 
183
226
    def rmdir(self, relpath):
184
227
        """See Transport.rmdir."""
185
228
        _abspath = self._abspath(relpath)
186
229
        if _abspath in self._files:
187
230
            self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
188
231
        for path in self._files:
189
 
            if path.startswith(_abspath):
 
232
            if path.startswith(_abspath + '/'):
190
233
                self._translate_error(IOError(errno.ENOTEMPTY, relpath),
191
234
                                      relpath)
192
235
        for path in self._dirs:
193
 
            if path.startswith(_abspath) and path != _abspath:
 
236
            if path.startswith(_abspath + '/') and path != _abspath:
194
237
                self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
195
238
        if not _abspath in self._dirs:
196
239
            raise NoSuchFile(relpath)
200
243
        """See Transport.stat()."""
201
244
        _abspath = self._abspath(relpath)
202
245
        if _abspath in self._files:
203
 
            return MemoryStat(len(self._files[_abspath][0]), False, 
 
246
            return MemoryStat(len(self._files[_abspath][0]), False,
204
247
                              self._files[_abspath][1])
205
 
        elif _abspath == '':
206
 
            return MemoryStat(0, True, None)
207
248
        elif _abspath in self._dirs:
208
249
            return MemoryStat(0, True, self._dirs[_abspath])
209
250
        else:
219
260
 
220
261
    def _abspath(self, relpath):
221
262
        """Generate an internal absolute path."""
222
 
        if relpath.find('..') != -1:
223
 
            raise AssertionError('relpath contains ..')
224
 
        if relpath == '.':
225
 
            return self._cwd[:-1]
226
 
        if relpath.endswith('/'):
227
 
            relpath = relpath[:-1]
228
 
        if relpath.startswith('./'):
229
 
            relpath = relpath[2:]
230
 
        return self._cwd + relpath
 
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)
231
280
 
232
281
 
233
282
class _MemoryLock(object):
234
283
    """This makes a lock."""
235
284
 
236
285
    def __init__(self, path, transport):
237
 
        assert isinstance(transport, MemoryTransport)
238
286
        self.path = path
239
287
        self.transport = transport
240
288
        if self.path in self.transport._locks:
244
292
    def __del__(self):
245
293
        # Should this warn, or actually try to cleanup?
246
294
        if self.transport:
247
 
            warn("MemoryLock %r not explicitly unlocked" % (self.path,))
 
295
            warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
248
296
            self.unlock()
249
297
 
250
298
    def unlock(self):
257
305
 
258
306
    def setUp(self):
259
307
        """See bzrlib.transport.Server.setUp."""
260
 
        self._dirs = {}
 
308
        self._dirs = {'/':None}
261
309
        self._files = {}
262
310
        self._locks = {}
263
 
        self._scheme = "memory+%s:" % id(self)
 
311
        self._scheme = "memory+%s:///" % id(self)
264
312
        def memory_factory(url):
265
313
            result = MemoryTransport(url)
266
314
            result._dirs = self._dirs