1
# Copyright (C) 2005, 2006 Canonical Ltd
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.
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.
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
17
"""Implementation of Transport that uses memory for its storage.
19
The contents of the transport will be lost when the object is discarded,
20
so this is primarily useful for testing.
26
from stat import S_IFREG, S_IFDIR
27
from cStringIO import StringIO
30
from bzrlib.errors import (
37
from bzrlib.trace import mutter
38
from bzrlib.transport import (
39
AppendBasedFileStream,
46
import bzrlib.urlutils as urlutils
50
class MemoryStat(object):
52
def __init__(self, size, is_dir, perms):
57
self.st_mode = S_IFREG | perms
61
self.st_mode = S_IFDIR | perms
64
class MemoryTransport(Transport):
65
"""This is an in memory file system for transient data storage."""
67
def __init__(self, url=""):
68
"""Set the 'base' path where files will be stored."""
73
super(MemoryTransport, self).__init__(url)
74
split = url.find(':') + 3
75
self._scheme = url[:split]
76
self._cwd = url[split:]
77
# dictionaries from absolute path to file mode
78
self._dirs = {'/':None}
82
def clone(self, offset=None):
83
"""See Transport.clone()."""
84
path = self._combine_paths(self._cwd, offset)
85
if len(path) == 0 or path[-1] != '/':
87
url = self._scheme + path
88
result = MemoryTransport(url)
89
result._dirs = self._dirs
90
result._files = self._files
91
result._locks = self._locks
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:
103
return temp_t.base[:-1]
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))
112
self._files[_abspath] = (orig_content + f.read(), mode)
113
return len(orig_content)
115
def _check_parent(self, _abspath):
116
dir = os.path.dirname(_abspath)
118
if not dir in self._dirs:
119
raise NoSuchFile(_abspath)
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)
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]
133
def external_url(self):
134
"""See bzrlib.transport.Transport.external_url."""
135
# MemoryTransport's are only accessible in-process
137
raise InProcessTransport(self)
139
def get(self, relpath):
140
"""See Transport.get()."""
141
_abspath = self._abspath(relpath)
142
if not _abspath in self._files:
143
if _abspath in self._dirs:
144
return LateReadError(relpath)
146
raise NoSuchFile(relpath)
147
return StringIO(self._files[_abspath][0])
149
def put_file(self, relpath, f, mode=None):
150
"""See Transport.put_file()."""
151
_abspath = self._abspath(relpath)
152
self._check_parent(_abspath)
154
if type(bytes) is not str:
155
# Although not strictly correct, we raise UnicodeEncodeError to be
156
# compatible with other transports.
157
raise UnicodeEncodeError(
158
'undefined', bytes, 0, 1,
159
'put_file must be given a file of bytes, not unicode.')
160
self._files[_abspath] = (bytes, mode)
162
def mkdir(self, relpath, mode=None):
163
"""See Transport.mkdir()."""
164
_abspath = self._abspath(relpath)
165
self._check_parent(_abspath)
166
if _abspath in self._dirs:
167
raise FileExists(relpath)
168
self._dirs[_abspath]=mode
170
def open_write_stream(self, relpath, mode=None):
171
"""See Transport.open_write_stream."""
172
self.put_bytes(relpath, "", mode)
173
result = AppendBasedFileStream(self, relpath)
174
_file_streams[self.abspath(relpath)] = result
178
"""See Transport.listable."""
181
def iter_files_recursive(self):
182
for file in self._files:
183
if file.startswith(self._cwd):
184
yield urlutils.escape(file[len(self._cwd):])
186
def list_dir(self, relpath):
187
"""See Transport.list_dir()."""
188
_abspath = self._abspath(relpath)
189
if _abspath != '/' and _abspath not in self._dirs:
190
raise NoSuchFile(relpath)
193
if not _abspath.endswith('/'):
196
for path_group in self._files, self._dirs:
197
for path in path_group:
198
if path.startswith(_abspath):
199
trailing = path[len(_abspath):]
200
if trailing and '/' not in trailing:
201
result.append(trailing)
202
return map(urlutils.escape, result)
204
def rename(self, rel_from, rel_to):
205
"""Rename a file or directory; fail if the destination exists"""
206
abs_from = self._abspath(rel_from)
207
abs_to = self._abspath(rel_to)
211
elif x.startswith(abs_from + '/'):
212
x = abs_to + x[len(abs_from):]
214
def do_renames(container):
215
for path in container:
216
new_path = replace(path)
218
if new_path in container:
219
raise FileExists(new_path)
220
container[new_path] = container[path]
222
do_renames(self._files)
223
do_renames(self._dirs)
225
def rmdir(self, relpath):
226
"""See Transport.rmdir."""
227
_abspath = self._abspath(relpath)
228
if _abspath in self._files:
229
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
230
for path in self._files:
231
if path.startswith(_abspath + '/'):
232
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
234
for path in self._dirs:
235
if path.startswith(_abspath + '/') and path != _abspath:
236
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
237
if not _abspath in self._dirs:
238
raise NoSuchFile(relpath)
239
del self._dirs[_abspath]
241
def stat(self, relpath):
242
"""See Transport.stat()."""
243
_abspath = self._abspath(relpath)
244
if _abspath in self._files:
245
return MemoryStat(len(self._files[_abspath][0]), False,
246
self._files[_abspath][1])
247
elif _abspath in self._dirs:
248
return MemoryStat(0, True, self._dirs[_abspath])
250
raise NoSuchFile(_abspath)
252
def lock_read(self, relpath):
253
"""See Transport.lock_read()."""
254
return _MemoryLock(self._abspath(relpath), self)
256
def lock_write(self, relpath):
257
"""See Transport.lock_write()."""
258
return _MemoryLock(self._abspath(relpath), self)
260
def _abspath(self, relpath):
261
"""Generate an internal absolute path."""
262
relpath = urlutils.unescape(relpath)
263
if relpath.find('..') != -1:
264
raise AssertionError('relpath contains ..')
267
if relpath[0] == '/':
270
if (self._cwd == '/'):
272
return self._cwd[:-1]
273
if relpath.endswith('/'):
274
relpath = relpath[:-1]
275
if relpath.startswith('./'):
276
relpath = relpath[2:]
277
return self._cwd + relpath
280
class _MemoryLock(object):
281
"""This makes a lock."""
283
def __init__(self, path, transport):
284
assert isinstance(transport, MemoryTransport)
286
self.transport = transport
287
if self.path in self.transport._locks:
288
raise LockError('File %r already locked' % (self.path,))
289
self.transport._locks[self.path] = self
292
# Should this warn, or actually try to cleanup?
294
warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
298
del self.transport._locks[self.path]
299
self.transport = None
302
class MemoryServer(Server):
303
"""Server for the MemoryTransport for testing with."""
306
"""See bzrlib.transport.Server.setUp."""
307
self._dirs = {'/':None}
310
self._scheme = "memory+%s:///" % id(self)
311
def memory_factory(url):
312
result = MemoryTransport(url)
313
result._dirs = self._dirs
314
result._files = self._files
315
result._locks = self._locks
317
register_transport(self._scheme, memory_factory)
320
"""See bzrlib.transport.Server.tearDown."""
321
# unregister this server
324
"""See bzrlib.transport.Server.get_url."""
328
def get_test_permutations():
329
"""Return the permutations to be used in testing."""
330
return [(MemoryTransport, MemoryServer),