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()."""
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
return self.base[:-1] + self._abspath(relpath)[len(self._cwd) - 1:]
91
def append(self, relpath, f):
92
"""See Transport.append()."""
93
_abspath = self._abspath(relpath)
94
self._check_parent(_abspath)
95
orig_content, orig_mode = self._files.get(_abspath, ("", None))
96
self._files[_abspath] = (orig_content + f.read(), orig_mode)
97
return len(orig_content)
99
def _check_parent(self, _abspath):
100
dir = os.path.dirname(_abspath)
102
if not dir in self._dirs:
103
raise NoSuchFile(_abspath)
105
def has(self, relpath):
106
"""See Transport.has()."""
107
_abspath = self._abspath(relpath)
108
return _abspath in self._files or _abspath in self._dirs
110
def delete(self, relpath):
111
"""See Transport.delete()."""
112
_abspath = self._abspath(relpath)
113
if not _abspath in self._files:
114
raise NoSuchFile(relpath)
115
del self._files[_abspath]
117
def get(self, relpath):
118
"""See Transport.get()."""
119
_abspath = self._abspath(relpath)
120
if not _abspath in self._files:
121
raise NoSuchFile(relpath)
122
return StringIO(self._files[_abspath][0])
124
def put(self, relpath, f, mode=None):
125
"""See Transport.put()."""
126
_abspath = self._abspath(relpath)
127
self._check_parent(_abspath)
128
self._files[_abspath] = (f.read(), mode)
130
def mkdir(self, relpath, mode=None):
131
"""See Transport.mkdir()."""
132
_abspath = self._abspath(relpath)
133
self._check_parent(_abspath)
134
if _abspath in self._dirs:
135
raise FileExists(relpath)
136
self._dirs[_abspath]=mode
139
"""See Transport.listable."""
142
def iter_files_recursive(self):
143
for file in self._files:
144
if file.startswith(self._cwd):
145
yield file[len(self._cwd):]
147
def list_dir(self, relpath):
148
"""See Transport.list_dir()."""
149
_abspath = self._abspath(relpath)
150
if _abspath != '/' and _abspath not in self._dirs:
151
raise NoSuchFile(relpath)
153
for path in self._files:
154
if (path.startswith(_abspath) and
155
path[len(_abspath) + 1:].find('/') == -1 and
156
len(path) > len(_abspath)):
157
result.append(path[len(_abspath) + 1:])
158
for path in self._dirs:
159
if (path.startswith(_abspath) and
160
path[len(_abspath) + 1:].find('/') == -1 and
161
len(path) > len(_abspath) and
162
path[len(_abspath)] == '/'):
163
result.append(path[len(_abspath) + 1:])
166
def rename(self, rel_from, rel_to):
167
"""Rename a file or directory; fail if the destination exists"""
168
abs_from = self._abspath(rel_from)
169
abs_to = self._abspath(rel_to)
173
elif x.startswith(abs_from + '/'):
174
x = abs_to + x[len(abs_from):]
176
def do_renames(container):
177
for path in container:
178
new_path = replace(path)
180
if new_path in container:
181
raise FileExists(new_path)
182
container[new_path] = container[path]
184
do_renames(self._files)
185
do_renames(self._dirs)
187
def rmdir(self, relpath):
188
"""See Transport.rmdir."""
189
_abspath = self._abspath(relpath)
190
if _abspath in self._files:
191
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
192
for path in self._files:
193
if path.startswith(_abspath):
194
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
196
for path in self._dirs:
197
if path.startswith(_abspath) and path != _abspath:
198
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
199
if not _abspath in self._dirs:
200
raise NoSuchFile(relpath)
201
del self._dirs[_abspath]
203
def stat(self, relpath):
204
"""See Transport.stat()."""
205
_abspath = self._abspath(relpath)
206
if _abspath in self._files:
207
return MemoryStat(len(self._files[_abspath][0]), False,
208
self._files[_abspath][1])
210
return MemoryStat(0, True, None)
211
elif _abspath in self._dirs:
212
return MemoryStat(0, True, self._dirs[_abspath])
214
raise NoSuchFile(_abspath)
216
def lock_read(self, relpath):
217
"""See Transport.lock_read()."""
218
return _MemoryLock(self._abspath(relpath), self)
220
def lock_write(self, relpath):
221
"""See Transport.lock_write()."""
222
return _MemoryLock(self._abspath(relpath), self)
224
def _abspath(self, relpath):
225
"""Generate an internal absolute path."""
226
if relpath.find('..') != -1:
227
raise AssertionError('relpath contains ..')
229
return self._cwd[:-1]
230
if relpath.endswith('/'):
231
relpath = relpath[:-1]
232
if relpath.startswith('./'):
233
relpath = relpath[2:]
234
return self._cwd + relpath
237
class _MemoryLock(object):
238
"""This makes a lock."""
240
def __init__(self, path, transport):
241
assert isinstance(transport, MemoryTransport)
243
self.transport = transport
244
if self.path in self.transport._locks:
245
raise LockError('File %r already locked' % (self.path,))
246
self.transport._locks[self.path] = self
249
# Should this warn, or actually try to cleanup?
251
warn("MemoryLock %r not explicitly unlocked" % (self.path,))
255
del self.transport._locks[self.path]
256
self.transport = None
259
class MemoryServer(Server):
260
"""Server for the MemoryTransport for testing with."""
263
"""See bzrlib.transport.Server.setUp."""
267
self._scheme = "memory+%s:" % id(self)
268
def memory_factory(url):
269
result = MemoryTransport(url)
270
result._dirs = self._dirs
271
result._files = self._files
272
result._locks = self._locks
274
register_transport(self._scheme, memory_factory)
277
"""See bzrlib.transport.Server.tearDown."""
278
# unregister this server
281
"""See bzrlib.transport.Server.get_url."""
285
def get_test_permutations():
286
"""Return the permutations to be used in testing."""
287
return [(MemoryTransport, MemoryServer),