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.
28
from cStringIO import StringIO
30
from bzrlib.trace import mutter
31
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
32
from bzrlib.transport import Transport, register_transport, Server
35
class MemoryStat(object):
37
def __init__(self, size, is_dir, perms):
42
self.st_mode = S_IFREG | perms
46
self.st_mode = S_IFDIR | perms
49
class MemoryTransport(Transport):
50
"""This is an in memory file system for transient data storage."""
52
def __init__(self, url=""):
53
"""Set the 'base' path where files will be stored."""
58
super(MemoryTransport, self).__init__(url)
59
self._cwd = url[url.find(':') + 1:]
60
# dictionaries from absolute path to file mode
65
def clone(self, offset=None):
66
"""See Transport.clone()."""
67
if offset is None or offset == '':
69
segments = offset.split('/')
70
cwdsegments = self._cwd.split('/')[:-1]
72
segment = segments.pop(0)
76
if len(cwdsegments) > 1:
79
cwdsegments.append(segment)
80
url = self.base[:self.base.find(':') + 1] + '/'.join(cwdsegments) + '/'
81
result = MemoryTransport(url)
82
result._dirs = self._dirs
83
result._files = self._files
84
result._locks = self._locks
87
def abspath(self, relpath):
88
"""See Transport.abspath()."""
89
# while a little slow, this is sufficiently fast to not matter in our
90
# current environment - XXX RBC 20060404 move the clone '..' handling
91
# into here and call abspath from clone
92
temp_t = self.clone(relpath)
93
if temp_t.base.count('/') == 1:
96
return temp_t.base[:-1]
98
def append(self, relpath, f):
99
"""See Transport.append()."""
100
_abspath = self._abspath(relpath)
101
self._check_parent(_abspath)
102
orig_content, orig_mode = self._files.get(_abspath, ("", None))
103
self._files[_abspath] = (orig_content + f.read(), orig_mode)
104
return len(orig_content)
106
def _check_parent(self, _abspath):
107
dir = os.path.dirname(_abspath)
109
if not dir in self._dirs:
110
raise NoSuchFile(_abspath)
112
def has(self, relpath):
113
"""See Transport.has()."""
114
_abspath = self._abspath(relpath)
115
return _abspath in self._files or _abspath in self._dirs
117
def delete(self, relpath):
118
"""See Transport.delete()."""
119
_abspath = self._abspath(relpath)
120
if not _abspath in self._files:
121
raise NoSuchFile(relpath)
122
del self._files[_abspath]
124
def get(self, relpath):
125
"""See Transport.get()."""
126
_abspath = self._abspath(relpath)
127
if not _abspath in self._files:
128
raise NoSuchFile(relpath)
129
return StringIO(self._files[_abspath][0])
131
def put(self, relpath, f, mode=None):
132
"""See Transport.put()."""
133
_abspath = self._abspath(relpath)
134
self._check_parent(_abspath)
135
self._files[_abspath] = (f.read(), mode)
137
def mkdir(self, relpath, mode=None):
138
"""See Transport.mkdir()."""
139
_abspath = self._abspath(relpath)
140
self._check_parent(_abspath)
141
if _abspath in self._dirs:
142
raise FileExists(relpath)
143
self._dirs[_abspath]=mode
146
"""See Transport.listable."""
149
def iter_files_recursive(self):
150
for file in self._files:
151
if file.startswith(self._cwd):
152
yield file[len(self._cwd):]
154
def list_dir(self, relpath):
155
"""See Transport.list_dir()."""
156
_abspath = self._abspath(relpath)
157
if _abspath != '/' and _abspath not in self._dirs:
158
raise NoSuchFile(relpath)
160
for path in self._files:
161
if (path.startswith(_abspath) and
162
path[len(_abspath) + 1:].find('/') == -1 and
163
len(path) > len(_abspath)):
164
result.append(path[len(_abspath) + 1:])
165
for path in self._dirs:
166
if (path.startswith(_abspath) and
167
path[len(_abspath) + 1:].find('/') == -1 and
168
len(path) > len(_abspath) and
169
path[len(_abspath)] == '/'):
170
result.append(path[len(_abspath) + 1:])
173
def rename(self, rel_from, rel_to):
174
"""Rename a file or directory; fail if the destination exists"""
175
abs_from = self._abspath(rel_from)
176
abs_to = self._abspath(rel_to)
180
elif x.startswith(abs_from + '/'):
181
x = abs_to + x[len(abs_from):]
183
def do_renames(container):
184
for path in container:
185
new_path = replace(path)
187
if new_path in container:
188
raise FileExists(new_path)
189
container[new_path] = container[path]
191
do_renames(self._files)
192
do_renames(self._dirs)
194
def rmdir(self, relpath):
195
"""See Transport.rmdir."""
196
_abspath = self._abspath(relpath)
197
if _abspath in self._files:
198
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
199
for path in self._files:
200
if path.startswith(_abspath):
201
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
203
for path in self._dirs:
204
if path.startswith(_abspath) and path != _abspath:
205
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
206
if not _abspath in self._dirs:
207
raise NoSuchFile(relpath)
208
del self._dirs[_abspath]
210
def stat(self, relpath):
211
"""See Transport.stat()."""
212
_abspath = self._abspath(relpath)
213
if _abspath in self._files:
214
return MemoryStat(len(self._files[_abspath][0]), False,
215
self._files[_abspath][1])
217
return MemoryStat(0, True, None)
218
elif _abspath in self._dirs:
219
return MemoryStat(0, True, self._dirs[_abspath])
221
raise NoSuchFile(_abspath)
223
def lock_read(self, relpath):
224
"""See Transport.lock_read()."""
225
return _MemoryLock(self._abspath(relpath), self)
227
def lock_write(self, relpath):
228
"""See Transport.lock_write()."""
229
return _MemoryLock(self._abspath(relpath), self)
231
def _abspath(self, relpath):
232
"""Generate an internal absolute path."""
233
if relpath.find('..') != -1:
234
raise AssertionError('relpath contains ..')
236
return self._cwd[:-1]
237
if relpath.endswith('/'):
238
relpath = relpath[:-1]
239
if relpath.startswith('./'):
240
relpath = relpath[2:]
241
return self._cwd + relpath
244
class _MemoryLock(object):
245
"""This makes a lock."""
247
def __init__(self, path, transport):
248
assert isinstance(transport, MemoryTransport)
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
256
# Should this warn, or actually try to cleanup?
258
warn("MemoryLock %r not explicitly unlocked" % (self.path,))
262
del self.transport._locks[self.path]
263
self.transport = None
266
class MemoryServer(Server):
267
"""Server for the MemoryTransport for testing with."""
270
"""See bzrlib.transport.Server.setUp."""
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
281
register_transport(self._scheme, memory_factory)
284
"""See bzrlib.transport.Server.tearDown."""
285
# unregister this server
288
"""See bzrlib.transport.Server.get_url."""
292
def get_test_permutations():
293
"""Return the permutations to be used in testing."""
294
return [(MemoryTransport, MemoryServer),