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, mode=None):
99
"""See Transport.append()."""
100
_abspath = self._abspath(relpath)
101
self._check_parent(_abspath)
102
orig_content, orig_mode = self._files.get(_abspath, ("", None))
105
self._files[_abspath] = (orig_content + f.read(), mode)
106
return len(orig_content)
108
def _check_parent(self, _abspath):
109
dir = os.path.dirname(_abspath)
111
if not dir in self._dirs:
112
raise NoSuchFile(_abspath)
114
def has(self, relpath):
115
"""See Transport.has()."""
116
_abspath = self._abspath(relpath)
117
return _abspath in self._files or _abspath in self._dirs
119
def delete(self, relpath):
120
"""See Transport.delete()."""
121
_abspath = self._abspath(relpath)
122
if not _abspath in self._files:
123
raise NoSuchFile(relpath)
124
del self._files[_abspath]
126
def get(self, relpath):
127
"""See Transport.get()."""
128
_abspath = self._abspath(relpath)
129
if not _abspath in self._files:
130
raise NoSuchFile(relpath)
131
return StringIO(self._files[_abspath][0])
133
def put(self, relpath, f, mode=None):
134
"""See Transport.put()."""
135
_abspath = self._abspath(relpath)
136
self._check_parent(_abspath)
137
self._files[_abspath] = (f.read(), mode)
139
def mkdir(self, relpath, mode=None):
140
"""See Transport.mkdir()."""
141
_abspath = self._abspath(relpath)
142
self._check_parent(_abspath)
143
if _abspath in self._dirs:
144
raise FileExists(relpath)
145
self._dirs[_abspath]=mode
148
"""See Transport.listable."""
151
def iter_files_recursive(self):
152
for file in self._files:
153
if file.startswith(self._cwd):
154
yield file[len(self._cwd):]
156
def list_dir(self, relpath):
157
"""See Transport.list_dir()."""
158
_abspath = self._abspath(relpath)
159
if _abspath != '/' and _abspath not in self._dirs:
160
raise NoSuchFile(relpath)
162
for path in self._files:
163
if (path.startswith(_abspath) and
164
path[len(_abspath) + 1:].find('/') == -1 and
165
len(path) > len(_abspath)):
166
result.append(path[len(_abspath) + 1:])
167
for path in self._dirs:
168
if (path.startswith(_abspath) and
169
path[len(_abspath) + 1:].find('/') == -1 and
170
len(path) > len(_abspath) and
171
path[len(_abspath)] == '/'):
172
result.append(path[len(_abspath) + 1:])
175
def rename(self, rel_from, rel_to):
176
"""Rename a file or directory; fail if the destination exists"""
177
abs_from = self._abspath(rel_from)
178
abs_to = self._abspath(rel_to)
182
elif x.startswith(abs_from + '/'):
183
x = abs_to + x[len(abs_from):]
185
def do_renames(container):
186
for path in container:
187
new_path = replace(path)
189
if new_path in container:
190
raise FileExists(new_path)
191
container[new_path] = container[path]
193
do_renames(self._files)
194
do_renames(self._dirs)
196
def rmdir(self, relpath):
197
"""See Transport.rmdir."""
198
_abspath = self._abspath(relpath)
199
if _abspath in self._files:
200
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
201
for path in self._files:
202
if path.startswith(_abspath):
203
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
205
for path in self._dirs:
206
if path.startswith(_abspath) and path != _abspath:
207
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
208
if not _abspath in self._dirs:
209
raise NoSuchFile(relpath)
210
del self._dirs[_abspath]
212
def stat(self, relpath):
213
"""See Transport.stat()."""
214
_abspath = self._abspath(relpath)
215
if _abspath in self._files:
216
return MemoryStat(len(self._files[_abspath][0]), False,
217
self._files[_abspath][1])
219
return MemoryStat(0, True, None)
220
elif _abspath in self._dirs:
221
return MemoryStat(0, True, self._dirs[_abspath])
223
raise NoSuchFile(_abspath)
225
def lock_read(self, relpath):
226
"""See Transport.lock_read()."""
227
return _MemoryLock(self._abspath(relpath), self)
229
def lock_write(self, relpath):
230
"""See Transport.lock_write()."""
231
return _MemoryLock(self._abspath(relpath), self)
233
def _abspath(self, relpath):
234
"""Generate an internal absolute path."""
235
if relpath.find('..') != -1:
236
raise AssertionError('relpath contains ..')
238
return self._cwd[:-1]
239
if relpath.endswith('/'):
240
relpath = relpath[:-1]
241
if relpath.startswith('./'):
242
relpath = relpath[2:]
243
return self._cwd + relpath
246
class _MemoryLock(object):
247
"""This makes a lock."""
249
def __init__(self, path, transport):
250
assert isinstance(transport, MemoryTransport)
252
self.transport = transport
253
if self.path in self.transport._locks:
254
raise LockError('File %r already locked' % (self.path,))
255
self.transport._locks[self.path] = self
258
# Should this warn, or actually try to cleanup?
260
warn("MemoryLock %r not explicitly unlocked" % (self.path,))
264
del self.transport._locks[self.path]
265
self.transport = None
268
class MemoryServer(Server):
269
"""Server for the MemoryTransport for testing with."""
272
"""See bzrlib.transport.Server.setUp."""
276
self._scheme = "memory+%s:" % id(self)
277
def memory_factory(url):
278
result = MemoryTransport(url)
279
result._dirs = self._dirs
280
result._files = self._files
281
result._locks = self._locks
283
register_transport(self._scheme, memory_factory)
286
"""See bzrlib.transport.Server.tearDown."""
287
# unregister this server
290
"""See bzrlib.transport.Server.get_url."""
294
def get_test_permutations():
295
"""Return the permutations to be used in testing."""
296
return [(MemoryTransport, MemoryServer),