~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/memory.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-03-12 17:30:53 UTC
  • mfrom: (2338.3.1 hide-nested)
  • Revision ID: pqm@pqm.ubuntu.com-20070312173053-4cdb4cd14190d29e
Hide nested-tree commands and improve their docs

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