~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Olaf Conradi
  • Date: 2006-03-28 23:30:02 UTC
  • mto: (1661.1.1 bzr.mbp.remember)
  • mto: This revision was merged to the branch mainline in revision 1663.
  • Revision ID: olaf@conradi.org-20060328233002-f6262df0e19c1963
Added testcases for using pull with --remember. Moved remember code to
beginning of cmd_pull. This remembers the location in case of a failure
during pull.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Canonical Ltd
 
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
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."""
17
 
 
 
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
18
24
import os
19
25
import errno
 
26
import re
 
27
from stat import *
20
28
from cStringIO import StringIO
21
29
 
22
30
from bzrlib.trace import mutter
23
 
from bzrlib.transport import Transport, \
24
 
    TransportError, NoSuchFile, FileExists
25
 
 
 
31
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
 
32
from bzrlib.transport import Transport, register_transport, Server
26
33
 
27
34
class MemoryStat(object):
28
35
 
29
 
    def __init__(self, size):
 
36
    def __init__(self, size, is_dir, perms):
30
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
31
46
 
32
47
 
33
48
class MemoryTransport(Transport):
34
 
    """This is the transport agent for local filesystem access."""
 
49
    """This is an in memory file system for transient data storage."""
35
50
 
36
 
    def __init__(self):
 
51
    def __init__(self, url=""):
37
52
        """Set the 'base' path where files will be stored."""
38
 
        super(MemoryTransport, self).__init__('in-memory:')
39
 
        self._dirs = set()
 
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 = {}
40
61
        self._files = {}
 
62
        self._locks = {}
41
63
 
42
64
    def clone(self, offset=None):
43
65
        """See Transport.clone()."""
44
 
        return self
 
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
45
85
 
46
86
    def abspath(self, relpath):
47
87
        """See Transport.abspath()."""
48
 
        return self.base + relpath
 
88
        return self.base[:-1] + self._abspath(relpath)[len(self._cwd) - 1:]
49
89
 
50
90
    def append(self, relpath, f):
51
91
        """See Transport.append()."""
52
 
        self._check_parent(relpath)
53
 
        self._files[relpath] = self._files.get(relpath, "") + f.read()
 
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)
54
97
 
55
 
    def _check_parent(self, relpath):
56
 
        dir = os.path.dirname(relpath)
57
 
        if dir != '':
 
98
    def _check_parent(self, _abspath):
 
99
        dir = os.path.dirname(_abspath)
 
100
        if dir != '/':
58
101
            if not dir in self._dirs:
59
 
                raise NoSuchFile(relpath)
 
102
                raise NoSuchFile(_abspath)
60
103
 
61
104
    def has(self, relpath):
62
105
        """See Transport.has()."""
63
 
        return relpath in self._files
 
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]
64
115
 
65
116
    def get(self, relpath):
66
117
        """See Transport.get()."""
67
 
        if not relpath in self._files:
 
118
        _abspath = self._abspath(relpath)
 
119
        if not _abspath in self._files:
68
120
            raise NoSuchFile(relpath)
69
 
        return StringIO(self._files[relpath])
 
121
        return StringIO(self._files[_abspath][0])
70
122
 
71
 
    def put(self, relpath, f):
 
123
    def put(self, relpath, f, mode=None):
72
124
        """See Transport.put()."""
73
 
        self._check_parent(relpath)
74
 
        self._files[relpath] = f.read()
 
125
        _abspath = self._abspath(relpath)
 
126
        self._check_parent(_abspath)
 
127
        self._files[_abspath] = (f.read(), mode)
75
128
 
76
 
    def mkdir(self, relpath):
 
129
    def mkdir(self, relpath, mode=None):
77
130
        """See Transport.mkdir()."""
78
 
        self._check_parent(relpath)
79
 
        if relpath in self._dirs:
 
131
        _abspath = self._abspath(relpath)
 
132
        self._check_parent(_abspath)
 
133
        if _abspath in self._dirs:
80
134
            raise FileExists(relpath)
81
 
        self._dirs.add(relpath)
 
135
        self._dirs[_abspath]=mode
82
136
 
83
137
    def listable(self):
84
138
        """See Transport.listable."""
85
139
        return True
86
140
 
87
141
    def iter_files_recursive(self):
88
 
        return iter(self._files)
89
 
    
90
 
#    def list_dir(self, relpath):
91
 
#    TODO if needed
92
 
    
 
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
 
93
202
    def stat(self, relpath):
94
203
        """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
 
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
            ]