~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Alexander Belchenko
  • Date: 2007-08-14 06:27:51 UTC
  • mto: (2733.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 2734.
  • Revision ID: bialix@ukr.net-20070814062751-tyyn1s5jraunqni9
teach windows python installer to find docs in all subdirectories

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