~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

- refactor handling of short option names

Show diffs side-by-side

added added

removed removed

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