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
34
class MemoryStat(object):
36
def __init__(self, size, is_dir, perms):
41
self.st_mode = S_IFREG | perms
45
self.st_mode = S_IFDIR | perms
48
class MemoryTransport(Transport):
49
"""This is an in memory file system for transient data storage."""
51
def __init__(self, url=""):
52
"""Set the 'base' path where files will be stored."""
57
super(MemoryTransport, self).__init__(url)
58
self._cwd = url[url.find(':') + 1:]
59
# dictionaries from absolute path to file mode
64
def clone(self, offset=None):
65
"""See Transport.clone()."""
68
segments = offset.split('/')
69
cwdsegments = self._cwd.split('/')[:-1]
71
segment = segments.pop(0)
75
if len(cwdsegments) > 1:
78
cwdsegments.append(segment)
79
url = self.base[:self.base.find(':') + 1] + '/'.join(cwdsegments) + '/'
80
result = MemoryTransport(url)
81
result._dirs = self._dirs
82
result._files = self._files
83
result._locks = self._locks
86
def abspath(self, relpath):
87
"""See Transport.abspath()."""
88
return self.base[:-1] + self._abspath(relpath)[len(self._cwd) - 1:]
90
def append(self, relpath, f):
91
"""See Transport.append()."""
92
_abspath = self._abspath(relpath)
93
self._check_parent(_abspath)
94
orig_content, orig_mode = self._files.get(_abspath, ("", None))
95
self._files[_abspath] = (orig_content + f.read(), orig_mode)
97
def _check_parent(self, _abspath):
98
dir = os.path.dirname(_abspath)
100
if not dir in self._dirs:
101
raise NoSuchFile(_abspath)
103
def has(self, relpath):
104
"""See Transport.has()."""
105
_abspath = self._abspath(relpath)
106
return _abspath in self._files or _abspath in self._dirs
108
def delete(self, relpath):
109
"""See Transport.delete()."""
110
_abspath = self._abspath(relpath)
111
if not _abspath in self._files:
112
raise NoSuchFile(relpath)
113
del self._files[_abspath]
115
def get(self, relpath):
116
"""See Transport.get()."""
117
_abspath = self._abspath(relpath)
118
if not _abspath in self._files:
119
raise NoSuchFile(relpath)
120
return StringIO(self._files[_abspath][0])
122
def put(self, relpath, f, mode=None):
123
"""See Transport.put()."""
124
_abspath = self._abspath(relpath)
125
self._check_parent(_abspath)
126
self._files[_abspath] = (f.read(), mode)
128
def mkdir(self, relpath, mode=None):
129
"""See Transport.mkdir()."""
130
_abspath = self._abspath(relpath)
131
self._check_parent(_abspath)
132
if _abspath in self._dirs:
133
raise FileExists(relpath)
134
self._dirs[_abspath]=mode
137
"""See Transport.listable."""
140
def iter_files_recursive(self):
141
for file in self._files:
142
if file.startswith(self._cwd):
143
yield file[len(self._cwd):]
145
def list_dir(self, relpath):
146
"""See Transport.list_dir()."""
147
_abspath = self._abspath(relpath)
148
if _abspath != '/' and _abspath not in self._dirs:
149
raise NoSuchFile(relpath)
151
for path in self._files:
152
if (path.startswith(_abspath) and
153
path[len(_abspath) + 1:].find('/') == -1 and
154
len(path) > len(_abspath)):
155
result.append(path[len(_abspath) + 1:])
156
for path in self._dirs:
157
if (path.startswith(_abspath) and
158
path[len(_abspath) + 1:].find('/') == -1 and
159
len(path) > len(_abspath) and
160
path[len(_abspath)] == '/'):
161
result.append(path[len(_abspath) + 1:])
164
def rename(self, rel_from, rel_to):
165
"""Rename a file or directory; fail if the destination exists"""
166
abs_from = self._abspath(rel_from)
167
abs_to = self._abspath(rel_to)
171
elif x.startswith(abs_from + '/'):
172
x = abs_to + x[len(abs_from):]
174
def do_renames(container):
175
for path in container:
176
new_path = replace(path)
178
if new_path in container:
179
raise FileExists(new_path)
180
container[new_path] = container[path]
182
do_renames(self._files)
183
do_renames(self._dirs)
185
def rmdir(self, relpath):
186
"""See Transport.rmdir."""
187
_abspath = self._abspath(relpath)
188
if _abspath in self._files:
189
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
190
for path in self._files:
191
if path.startswith(_abspath):
192
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
194
for path in self._dirs:
195
if path.startswith(_abspath) and path != _abspath:
196
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
197
if not _abspath in self._dirs:
198
raise NoSuchFile(relpath)
199
del self._dirs[_abspath]
201
def stat(self, relpath):
202
"""See Transport.stat()."""
203
_abspath = self._abspath(relpath)
204
if _abspath in self._files:
205
return MemoryStat(len(self._files[_abspath][0]), False,
206
self._files[_abspath][1])
208
return MemoryStat(0, True, None)
209
elif _abspath in self._dirs:
210
return MemoryStat(0, True, self._dirs[_abspath])
212
raise NoSuchFile(_abspath)
214
def lock_read(self, relpath):
215
"""See Transport.lock_read()."""
216
return _MemoryLock(self._abspath(relpath), self)
218
def lock_write(self, relpath):
219
"""See Transport.lock_write()."""
220
return _MemoryLock(self._abspath(relpath), self)
222
def _abspath(self, relpath):
223
"""Generate an internal absolute path."""
224
if relpath.find('..') != -1:
225
raise AssertionError('relpath contains ..')
227
return self._cwd[:-1]
228
if relpath.endswith('/'):
229
relpath = relpath[:-1]
230
if relpath.startswith('./'):
231
relpath = relpath[2:]
232
return self._cwd + relpath
235
class _MemoryLock(object):
236
"""This makes a lock."""
238
def __init__(self, path, transport):
239
assert isinstance(transport, MemoryTransport)
241
self.transport = transport
242
if self.path in self.transport._locks:
243
raise LockError('File %r already locked' % (self.path,))
244
self.transport._locks[self.path] = self
247
# Should this warn, or actually try to cleanup?
249
warn("MemoryLock %r not explicitly unlocked" % (self.path,))
253
del self.transport._locks[self.path]
254
self.transport = None
257
class MemoryServer(Server):
258
"""Server for the MemoryTransport for testing with."""
261
"""See bzrlib.transport.Server.setUp."""
265
self._scheme = "memory+%s:" % id(self)
266
def memory_factory(url):
267
result = MemoryTransport(url)
268
result._dirs = self._dirs
269
result._files = self._files
270
result._locks = self._locks
272
register_transport(self._scheme, memory_factory)
275
"""See bzrlib.transport.Server.tearDown."""
276
# unregister this server
279
"""See bzrlib.transport.Server.get_url."""
283
def get_test_permutations():
284
"""Return the permutations to be used in testing."""
285
return [(MemoryTransport, MemoryServer),