20
20
so this is primarily useful for testing.
23
from __future__ import absolute_import
32
from stat import S_IFREG, S_IFDIR, S_IFLNK, S_ISDIR
38
from ..errors import (
45
from ..transport import (
46
AppendBasedFileStream,
28
from cStringIO import StringIO
30
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
31
from bzrlib.trace import mutter
32
from bzrlib.transport import (Transport, register_transport, Server,
33
urlescape, urlunescape)
52
37
class MemoryStat(object):
54
def __init__(self, size, kind, perms=None):
39
def __init__(self, size, is_dir, perms):
55
40
self.st_size = size
59
self.st_mode = kind | perms
44
self.st_mode = S_IFREG | perms
63
self.st_mode = kind | perms
66
class MemoryTransport(transport.Transport):
48
self.st_mode = S_IFDIR | perms
51
class MemoryTransport(Transport):
67
52
"""This is an in memory file system for transient data storage."""
69
54
def __init__(self, url=""):
70
55
"""Set the 'base' path where files will be stored."""
75
60
super(MemoryTransport, self).__init__(url)
76
split = url.find(':') + 3
77
self._scheme = url[:split]
78
self._cwd = url[split:]
61
self._cwd = url[url.find(':') + 1:]
79
62
# dictionaries from absolute path to file mode
80
self._dirs = {'/': None}
85
67
def clone(self, offset=None):
86
68
"""See Transport.clone()."""
87
path = urlutils.URL._combine_paths(self._cwd, offset)
88
if len(path) == 0 or path[-1] != '/':
90
url = self._scheme + path
91
result = self.__class__(url)
69
if offset is None or offset == '':
71
segments = offset.split('/')
72
cwdsegments = self._cwd.split('/')[:-1]
74
segment = segments.pop(0)
78
if len(cwdsegments) > 1:
81
cwdsegments.append(segment)
82
url = self.base[:self.base.find(':') + 1] + '/'.join(cwdsegments) + '/'
83
result = MemoryTransport(url)
92
84
result._dirs = self._dirs
93
result._symlinks = self._symlinks
94
85
result._files = self._files
95
86
result._locks = self._locks
119
110
def _check_parent(self, _abspath):
120
111
dir = os.path.dirname(_abspath)
122
if dir not in self._dirs:
113
if not dir in self._dirs:
123
114
raise NoSuchFile(_abspath)
125
116
def has(self, relpath):
126
117
"""See Transport.has()."""
127
118
_abspath = self._abspath(relpath)
128
for container in (self._files, self._dirs, self._symlinks):
129
if _abspath in container.keys():
119
return _abspath in self._files or _abspath in self._dirs
133
121
def delete(self, relpath):
134
122
"""See Transport.delete()."""
135
123
_abspath = self._abspath(relpath)
136
if _abspath in self._files:
137
del self._files[_abspath]
138
elif _abspath in self._symlinks:
139
del self._symlinks[_abspath]
124
if not _abspath in self._files:
141
125
raise NoSuchFile(relpath)
143
def external_url(self):
144
"""See breezy.transport.Transport.external_url."""
145
# MemoryTransport's are only accessible in-process
147
raise InProcessTransport(self)
126
del self._files[_abspath]
149
128
def get(self, relpath):
150
129
"""See Transport.get()."""
151
_abspath = self._resolve_symlinks(relpath)
152
if _abspath not in self._files:
153
if _abspath in self._dirs:
154
return LateReadError(relpath)
156
raise NoSuchFile(relpath)
157
return BytesIO(self._files[_abspath][0])
159
def put_file(self, relpath, f, mode=None):
160
"""See Transport.put_file()."""
161
_abspath = self._resolve_symlinks(relpath)
162
self._check_parent(_abspath)
164
self._files[_abspath] = (raw_bytes, mode)
165
return len(raw_bytes)
167
def symlink(self, source, target):
168
_abspath = self._resolve_symlinks(target)
169
self._check_parent(_abspath)
170
self._symlinks[_abspath] = self._abspath(source)
130
_abspath = self._abspath(relpath)
131
if not _abspath in self._files:
132
raise NoSuchFile(relpath)
133
return StringIO(self._files[_abspath][0])
135
def put(self, relpath, f, mode=None):
136
"""See Transport.put()."""
137
_abspath = self._abspath(relpath)
138
self._check_parent(_abspath)
139
self._files[_abspath] = (f.read(), mode)
172
141
def mkdir(self, relpath, mode=None):
173
142
"""See Transport.mkdir()."""
174
_abspath = self._resolve_symlinks(relpath)
143
_abspath = self._abspath(relpath)
175
144
self._check_parent(_abspath)
176
145
if _abspath in self._dirs:
177
146
raise FileExists(relpath)
178
self._dirs[_abspath] = mode
180
def open_write_stream(self, relpath, mode=None):
181
"""See Transport.open_write_stream."""
182
self.put_bytes(relpath, b"", mode)
183
result = AppendBasedFileStream(self, relpath)
184
_file_streams[self.abspath(relpath)] = result
147
self._dirs[_abspath]=mode
187
149
def listable(self):
188
150
"""See Transport.listable."""
191
153
def iter_files_recursive(self):
192
for file in itertools.chain(self._files, self._symlinks):
154
for file in self._files:
193
155
if file.startswith(self._cwd):
194
yield urlutils.escape(file[len(self._cwd):])
156
yield file[len(self._cwd):]
196
158
def list_dir(self, relpath):
197
159
"""See Transport.list_dir()."""
198
_abspath = self._resolve_symlinks(relpath)
160
_abspath = self._abspath(relpath)
199
161
if _abspath != '/' and _abspath not in self._dirs:
200
162
raise NoSuchFile(relpath)
203
if not _abspath.endswith('/'):
206
for path_group in self._files, self._dirs, self._symlinks:
207
for path in path_group:
208
if path.startswith(_abspath):
209
trailing = path[len(_abspath):]
210
if trailing and '/' not in trailing:
211
result.append(urlutils.escape(trailing))
164
for path in self._files:
165
if (path.startswith(_abspath) and
166
path[len(_abspath) + 1:].find('/') == -1 and
167
len(path) > len(_abspath)):
168
result.append(path[len(_abspath) + 1:])
169
for path in self._dirs:
170
if (path.startswith(_abspath) and
171
path[len(_abspath) + 1:].find('/') == -1 and
172
len(path) > len(_abspath) and
173
path[len(_abspath)] == '/'):
174
result.append(path[len(_abspath) + 1:])
214
177
def rename(self, rel_from, rel_to):
215
178
"""Rename a file or directory; fail if the destination exists"""
216
abs_from = self._resolve_symlinks(rel_from)
217
abs_to = self._resolve_symlinks(rel_to)
179
abs_from = self._abspath(rel_from)
180
abs_to = self._abspath(rel_to)
220
182
if x == abs_from:
222
184
elif x.startswith(abs_from + '/'):
223
185
x = abs_to + x[len(abs_from):]
226
187
def do_renames(container):
228
188
for path in container:
229
189
new_path = replace(path)
230
190
if new_path != path:
231
191
if new_path in container:
232
192
raise FileExists(new_path)
233
renames.append((path, new_path))
234
for path, new_path in renames:
235
container[new_path] = container[path]
238
# If we modify the existing dicts, we're not atomic anymore and may
239
# fail differently depending on dict order. So work on copy, fail on
240
# error on only replace dicts if all goes well.
241
renamed_files = self._files.copy()
242
renamed_symlinks = self._symlinks.copy()
243
renamed_dirs = self._dirs.copy()
244
do_renames(renamed_files)
245
do_renames(renamed_symlinks)
246
do_renames(renamed_dirs)
247
# We may have been cloned so modify in place
249
self._files.update(renamed_files)
250
self._symlinks.clear()
251
self._symlinks.update(renamed_symlinks)
253
self._dirs.update(renamed_dirs)
193
container[new_path] = container[path]
195
do_renames(self._files)
196
do_renames(self._dirs)
255
198
def rmdir(self, relpath):
256
199
"""See Transport.rmdir."""
257
_abspath = self._resolve_symlinks(relpath)
200
_abspath = self._abspath(relpath)
258
201
if _abspath in self._files:
259
202
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
260
for path in itertools.chain(self._files, self._symlinks):
261
if path.startswith(_abspath + '/'):
203
for path in self._files:
204
if path.startswith(_abspath):
262
205
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
264
207
for path in self._dirs:
265
if path.startswith(_abspath + '/') and path != _abspath:
266
self._translate_error(
267
IOError(errno.ENOTEMPTY, relpath), relpath)
268
if _abspath not in self._dirs:
208
if path.startswith(_abspath) and path != _abspath:
209
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
210
if not _abspath in self._dirs:
269
211
raise NoSuchFile(relpath)
270
212
del self._dirs[_abspath]
272
214
def stat(self, relpath):
273
215
"""See Transport.stat()."""
274
216
_abspath = self._abspath(relpath)
275
if _abspath in self._files.keys():
276
return MemoryStat(len(self._files[_abspath][0]), S_IFREG,
217
if _abspath in self._files:
218
return MemoryStat(len(self._files[_abspath][0]), False,
277
219
self._files[_abspath][1])
278
elif _abspath in self._dirs.keys():
279
return MemoryStat(0, S_IFDIR, self._dirs[_abspath])
280
elif _abspath in self._symlinks.keys():
281
return MemoryStat(0, S_IFLNK)
221
return MemoryStat(0, True, None)
222
elif _abspath in self._dirs:
223
return MemoryStat(0, True, self._dirs[_abspath])
283
225
raise NoSuchFile(_abspath)
290
232
"""See Transport.lock_write()."""
291
233
return _MemoryLock(self._abspath(relpath), self)
293
def _resolve_symlinks(self, relpath):
294
path = self._abspath(relpath)
295
while path in self._symlinks.keys():
296
path = self._symlinks[path]
299
235
def _abspath(self, relpath):
300
236
"""Generate an internal absolute path."""
301
relpath = urlutils.unescape(relpath)
302
if relpath[:1] == '/':
304
cwd_parts = self._cwd.split('/')
305
rel_parts = relpath.split('/')
307
for i in cwd_parts + rel_parts:
310
raise ValueError("illegal relpath %r under %r"
311
% (relpath, self._cwd))
313
elif i == '.' or i == '':
317
r = self._symlinks.get('/'.join(r), r)
318
return '/' + '/'.join(r)
320
def symlink(self, source, link_name):
321
"""Create a symlink pointing to source named link_name."""
322
_abspath = self._abspath(link_name)
323
self._check_parent(_abspath)
324
self._symlinks[_abspath] = source.split('/')
326
def readlink(self, link_name):
327
_abspath = self._abspath(link_name)
329
return '/'.join(self._symlinks[_abspath])
331
raise NoSuchFile(link_name)
237
relpath = urlunescape(relpath)
238
if relpath.find('..') != -1:
239
raise AssertionError('relpath contains ..')
241
return self._cwd[:-1]
242
if relpath.endswith('/'):
243
relpath = relpath[:-1]
244
if relpath.startswith('./'):
245
relpath = relpath[2:]
246
return self._cwd + relpath
334
249
class _MemoryLock(object):
335
250
"""This makes a lock."""
337
252
def __init__(self, path, transport):
253
assert isinstance(transport, MemoryTransport)
339
255
self.transport = transport
340
256
if self.path in self.transport._locks:
341
257
raise LockError('File %r already locked' % (self.path,))
342
258
self.transport._locks[self.path] = self
261
# Should this warn, or actually try to cleanup?
263
warn("MemoryLock %r not explicitly unlocked" % (self.path,))
344
266
def unlock(self):
345
267
del self.transport._locks[self.path]
346
268
self.transport = None
349
class MemoryServer(transport.Server):
271
class MemoryServer(Server):
350
272
"""Server for the MemoryTransport for testing with."""
352
def start_server(self):
353
self._dirs = {'/': None}
275
"""See bzrlib.transport.Server.setUp."""
357
self._scheme = "memory+%s:///" % id(self)
279
self._scheme = "memory+%s:" % id(self)
359
280
def memory_factory(url):
361
result = memory.MemoryTransport(url)
281
result = MemoryTransport(url)
362
282
result._dirs = self._dirs
363
283
result._files = self._files
364
result._symlinks = self._symlinks
365
284
result._locks = self._locks
367
self._memory_factory = memory_factory
368
transport.register_transport(self._scheme, self._memory_factory)
286
register_transport(self._scheme, memory_factory)
370
def stop_server(self):
289
"""See bzrlib.transport.Server.tearDown."""
371
290
# unregister this server
372
transport.unregister_transport(self._scheme, self._memory_factory)
374
292
def get_url(self):
375
"""See breezy.transport.Server.get_url."""
293
"""See bzrlib.transport.Server.get_url."""
376
294
return self._scheme
378
def get_bogus_url(self):
379
raise NotImplementedError
382
297
def get_test_permutations():
383
298
"""Return the permutations to be used in testing."""