/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

Fix the FTP transport's handling of abspath('/')

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