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 (
44
import bzrlib.urlutils as urlutils
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):
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 = self._combine_paths(self._cwd, offset)
83
if len(path) == 0 or path[-1] != '/':
85
url = self._scheme + path
86
result = MemoryTransport(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
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)
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
169
"""See Transport.listable."""
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):])
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)
184
if not _abspath.endswith('/'):
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)
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)
202
elif x.startswith(abs_from + '/'):
203
x = abs_to + x[len(abs_from):]
205
def do_renames(container):
206
for path in container:
207
new_path = replace(path)
209
if new_path in container:
210
raise FileExists(new_path)
211
container[new_path] = container[path]
213
do_renames(self._files)
214
do_renames(self._dirs)
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),
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]
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])
241
raise NoSuchFile(_abspath)
243
def lock_read(self, relpath):
244
"""See Transport.lock_read()."""
245
return _MemoryLock(self._abspath(relpath), self)
247
def lock_write(self, relpath):
248
"""See Transport.lock_write()."""
249
return _MemoryLock(self._abspath(relpath), self)
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 ..')
258
if relpath[0] == '/':
261
if (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
271
class _MemoryLock(object):
272
"""This makes a lock."""
274
def __init__(self, path, transport):
275
assert isinstance(transport, MemoryTransport)
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
283
# Should this warn, or actually try to cleanup?
285
warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
289
del self.transport._locks[self.path]
290
self.transport = None
293
class MemoryServer(Server):
294
"""Server for the MemoryTransport for testing with."""
297
"""See bzrlib.transport.Server.setUp."""
298
self._dirs = {'/':None}
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
308
register_transport(self._scheme, memory_factory)
311
"""See bzrlib.transport.Server.tearDown."""
312
# unregister this server
315
"""See bzrlib.transport.Server.get_url."""
319
def get_test_permutations():
320
"""Return the permutations to be used in testing."""
321
return [(MemoryTransport, MemoryServer),