~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: John Arbash Meinel
  • Date: 2007-11-13 20:37:09 UTC
  • mto: This revision was merged to the branch mainline in revision 3001.
  • Revision ID: john@arbash-meinel.com-20071113203709-kysdte0emqv84pnj
Fix bug #162486, by having RemoteBranch properly initialize self._revision_id_to_revno_map.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006 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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
"""Implementation of Transport that uses memory for its storage."""
 
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.errors import (
 
31
    FileExists,
 
32
    LockError,
 
33
    InProcessTransport,
 
34
    NoSuchFile,
 
35
    TransportError,
 
36
    )
22
37
from bzrlib.trace import mutter
23
 
from bzrlib.errors import TransportError, NoSuchFile, FileExists
24
 
from bzrlib.transport import Transport
 
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
 
25
49
 
26
50
class MemoryStat(object):
27
51
 
28
 
    def __init__(self, size):
 
52
    def __init__(self, size, is_dir, perms):
29
53
        self.st_size = size
 
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
30
62
 
31
63
 
32
64
class MemoryTransport(Transport):
33
 
    """This is the transport agent for local filesystem access."""
 
65
    """This is an in memory file system for transient data storage."""
34
66
 
35
 
    def __init__(self):
 
67
    def __init__(self, url=""):
36
68
        """Set the 'base' path where files will be stored."""
37
 
        super(MemoryTransport, self).__init__('in-memory:')
38
 
        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}
39
79
        self._files = {}
 
80
        self._locks = {}
40
81
 
41
82
    def clone(self, offset=None):
42
83
        """See Transport.clone()."""
43
 
        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 = MemoryTransport(url)
 
89
        result._dirs = self._dirs
 
90
        result._files = self._files
 
91
        result._locks = self._locks
 
92
        return result
44
93
 
45
94
    def abspath(self, relpath):
46
95
        """See Transport.abspath()."""
47
 
        return self.base + relpath
48
 
 
49
 
    def append(self, relpath, f):
50
 
        """See Transport.append()."""
51
 
        self._check_parent(relpath)
52
 
        self._files[relpath] = self._files.get(relpath, "") + f.read()
53
 
 
54
 
    def _check_parent(self, relpath):
55
 
        dir = os.path.dirname(relpath)
56
 
        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 != '/':
57
118
            if not dir in self._dirs:
58
 
                raise NoSuchFile(relpath)
 
119
                raise NoSuchFile(_abspath)
59
120
 
60
121
    def has(self, relpath):
61
122
        """See Transport.has()."""
62
 
        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)
63
138
 
64
139
    def get(self, relpath):
65
140
        """See Transport.get()."""
66
 
        if not relpath in self._files:
67
 
            raise NoSuchFile(relpath)
68
 
        return StringIO(self._files[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])
69
148
 
70
 
    def put(self, relpath, f, mode=None):
71
 
        """See Transport.put()."""
72
 
        self._check_parent(relpath)
73
 
        self._files[relpath] = f.read()
 
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)
74
162
 
75
163
    def mkdir(self, relpath, mode=None):
76
164
        """See Transport.mkdir()."""
77
 
        self._check_parent(relpath)
78
 
        if relpath in self._dirs:
 
165
        _abspath = self._abspath(relpath)
 
166
        self._check_parent(_abspath)
 
167
        if _abspath in self._dirs:
79
168
            raise FileExists(relpath)
80
 
        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
81
177
 
82
178
    def listable(self):
83
179
        """See Transport.listable."""
84
180
        return True
85
181
 
86
182
    def iter_files_recursive(self):
87
 
        return iter(self._files)
88
 
    
89
 
#    def list_dir(self, relpath):
90
 
#    TODO if needed
91
 
    
 
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
 
92
242
    def stat(self, relpath):
93
243
        """See Transport.stat()."""
94
 
        return MemoryStat(len(self._files[relpath]))
95
 
 
96
 
#    def lock_read(self, relpath):
97
 
#   TODO if needed
98
 
#
99
 
#    def lock_write(self, relpath):
100
 
#   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 == '':
 
265
            return '/'
 
266
        if relpath[0] == '/':
 
267
            return relpath
 
268
        cwd_parts = self._cwd.split('/')
 
269
        rel_parts = relpath.split('/')
 
270
        r = []
 
271
        for i in cwd_parts + rel_parts:
 
272
            if i == '..':
 
273
                if not r:
 
274
                    raise ValueError("illegal relpath %r under %r"
 
275
                        % relpath, self._cwd)
 
276
                r = r[:-1]
 
277
            elif i == '.' or i == '':
 
278
                pass
 
279
            else:
 
280
                r.append(i)
 
281
        return '/' + '/'.join(r)
 
282
 
 
283
 
 
284
class _MemoryLock(object):
 
285
    """This makes a lock."""
 
286
 
 
287
    def __init__(self, path, transport):
 
288
        assert isinstance(transport, MemoryTransport)
 
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 setUp(self):
 
310
        """See bzrlib.transport.Server.setUp."""
 
311
        self._dirs = {'/':None}
 
312
        self._files = {}
 
313
        self._locks = {}
 
314
        self._scheme = "memory+%s:///" % id(self)
 
315
        def memory_factory(url):
 
316
            result = MemoryTransport(url)
 
317
            result._dirs = self._dirs
 
318
            result._files = self._files
 
319
            result._locks = self._locks
 
320
            return result
 
321
        register_transport(self._scheme, memory_factory)
 
322
 
 
323
    def tearDown(self):
 
324
        """See bzrlib.transport.Server.tearDown."""
 
325
        # unregister this server
 
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
            ]