1
# Copyright (C) 2005-2011, 2016 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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.
23
from __future__ import absolute_import
27
from stat import S_IFREG, S_IFDIR
28
from cStringIO import StringIO
34
from bzrlib.errors import (
40
from bzrlib.transport import (
41
AppendBasedFileStream,
48
class MemoryStat(object):
50
def __init__(self, size, is_dir, perms):
55
self.st_mode = S_IFREG | perms
59
self.st_mode = S_IFDIR | perms
62
class MemoryTransport(transport.Transport):
63
"""This is an in memory file system for transient data storage."""
65
def __init__(self, url=""):
66
"""Set the 'base' path where files will be stored."""
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}
80
def clone(self, offset=None):
81
"""See Transport.clone()."""
82
path = urlutils.URL._combine_paths(self._cwd, offset)
83
if len(path) == 0 or path[-1] != '/':
85
url = self._scheme + path
86
result = self.__class__(url)
87
result._dirs = self._dirs
88
result._files = self._files
89
result._locks = self._locks
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:
101
return temp_t.base[:-1]
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))
110
self._files[_abspath] = (orig_content + f.read(), mode)
111
return len(orig_content)
113
def _check_parent(self, _abspath):
114
dir = os.path.dirname(_abspath)
116
if not dir in self._dirs:
117
raise NoSuchFile(_abspath)
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)
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]
131
def external_url(self):
132
"""See bzrlib.transport.Transport.external_url."""
133
# MemoryTransport's are only accessible in-process
135
raise InProcessTransport(self)
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)
144
raise NoSuchFile(relpath)
145
return StringIO(self._files[_abspath][0])
147
def put_file(self, relpath, f, mode=None):
148
"""See Transport.put_file()."""
149
_abspath = self._abspath(relpath)
150
self._check_parent(_abspath)
152
self._files[_abspath] = (raw_bytes, mode)
153
return len(raw_bytes)
155
def mkdir(self, relpath, mode=None):
156
"""See Transport.mkdir()."""
157
_abspath = self._abspath(relpath)
158
self._check_parent(_abspath)
159
if _abspath in self._dirs:
160
raise FileExists(relpath)
161
self._dirs[_abspath]=mode
163
def open_write_stream(self, relpath, mode=None):
164
"""See Transport.open_write_stream."""
165
self.put_bytes(relpath, "", mode)
166
result = AppendBasedFileStream(self, relpath)
167
_file_streams[self.abspath(relpath)] = result
171
"""See Transport.listable."""
174
def iter_files_recursive(self):
175
for file in self._files:
176
if file.startswith(self._cwd):
177
yield urlutils.escape(file[len(self._cwd):])
179
def list_dir(self, relpath):
180
"""See Transport.list_dir()."""
181
_abspath = self._abspath(relpath)
182
if _abspath != '/' and _abspath not in self._dirs:
183
raise NoSuchFile(relpath)
186
if not _abspath.endswith('/'):
189
for path_group in self._files, self._dirs:
190
for path in path_group:
191
if path.startswith(_abspath):
192
trailing = path[len(_abspath):]
193
if trailing and '/' not in trailing:
194
result.append(trailing)
195
return map(urlutils.escape, result)
197
def rename(self, rel_from, rel_to):
198
"""Rename a file or directory; fail if the destination exists"""
199
abs_from = self._abspath(rel_from)
200
abs_to = self._abspath(rel_to)
204
elif x.startswith(abs_from + '/'):
205
x = abs_to + x[len(abs_from):]
207
def do_renames(container):
208
for path in container:
209
new_path = replace(path)
211
if new_path in container:
212
raise FileExists(new_path)
213
container[new_path] = container[path]
215
do_renames(self._files)
216
do_renames(self._dirs)
218
def rmdir(self, relpath):
219
"""See Transport.rmdir."""
220
_abspath = self._abspath(relpath)
221
if _abspath in self._files:
222
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
223
for path in self._files:
224
if path.startswith(_abspath + '/'):
225
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
227
for path in self._dirs:
228
if path.startswith(_abspath + '/') and path != _abspath:
229
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
230
if not _abspath in self._dirs:
231
raise NoSuchFile(relpath)
232
del self._dirs[_abspath]
234
def stat(self, relpath):
235
"""See Transport.stat()."""
236
_abspath = self._abspath(relpath)
237
if _abspath in self._files:
238
return MemoryStat(len(self._files[_abspath][0]), False,
239
self._files[_abspath][1])
240
elif _abspath in self._dirs:
241
return MemoryStat(0, True, self._dirs[_abspath])
243
raise NoSuchFile(_abspath)
245
def lock_read(self, relpath):
246
"""See Transport.lock_read()."""
247
return _MemoryLock(self._abspath(relpath), self)
249
def lock_write(self, relpath):
250
"""See Transport.lock_write()."""
251
return _MemoryLock(self._abspath(relpath), self)
253
def _abspath(self, relpath):
254
"""Generate an internal absolute path."""
255
relpath = urlutils.unescape(relpath)
256
if relpath[:1] == '/':
258
cwd_parts = self._cwd.split('/')
259
rel_parts = relpath.split('/')
261
for i in cwd_parts + rel_parts:
264
raise ValueError("illegal relpath %r under %r"
265
% (relpath, self._cwd))
267
elif i == '.' or i == '':
271
return '/' + '/'.join(r)
274
class _MemoryLock(object):
275
"""This makes a lock."""
277
def __init__(self, path, transport):
279
self.transport = transport
280
if self.path in self.transport._locks:
281
raise LockError('File %r already locked' % (self.path,))
282
self.transport._locks[self.path] = self
285
del self.transport._locks[self.path]
286
self.transport = None
289
class MemoryServer(transport.Server):
290
"""Server for the MemoryTransport for testing with."""
292
def start_server(self):
293
self._dirs = {'/':None}
296
self._scheme = "memory+%s:///" % id(self)
297
def memory_factory(url):
298
from bzrlib.transport import memory
299
result = memory.MemoryTransport(url)
300
result._dirs = self._dirs
301
result._files = self._files
302
result._locks = self._locks
304
self._memory_factory = memory_factory
305
transport.register_transport(self._scheme, self._memory_factory)
307
def stop_server(self):
308
# unregister this server
309
transport.unregister_transport(self._scheme, self._memory_factory)
312
"""See bzrlib.transport.Server.get_url."""
315
def get_bogus_url(self):
316
raise NotImplementedError
319
def get_test_permutations():
320
"""Return the permutations to be used in testing."""
321
return [(MemoryTransport, MemoryServer),