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.errors import TransportError, NoSuchFile, FileExists, LockError
31
from bzrlib.trace import mutter
32
from bzrlib.transport import (Transport, register_transport, Server)
33
import bzrlib.urlutils as urlutils
37
class MemoryStat(object):
39
def __init__(self, size, is_dir, perms):
44
self.st_mode = S_IFREG | perms
48
self.st_mode = S_IFDIR | perms
51
class MemoryTransport(Transport):
52
"""This is an in memory file system for transient data storage."""
54
def __init__(self, url=""):
55
"""Set the 'base' path where files will be stored."""
60
super(MemoryTransport, self).__init__(url)
61
self._cwd = url[url.find(':') + 3:]
62
# dictionaries from absolute path to file mode
63
self._dirs = {'/':None}
67
def clone(self, offset=None):
68
"""See Transport.clone()."""
69
if offset is None or offset == '':
71
segments = offset.split('/')
72
cwdsegments = self._cwd.split('/')[:-1]
74
segment = segments.pop(0)
78
if len(cwdsegments) > 1:
81
cwdsegments.append(segment)
82
url = self.base[:self.base.find(':') + 3] + '/'.join(cwdsegments) + '/'
83
result = MemoryTransport(url)
84
result._dirs = self._dirs
85
result._files = self._files
86
result._locks = self._locks
89
def abspath(self, relpath):
90
"""See Transport.abspath()."""
91
# while a little slow, this is sufficiently fast to not matter in our
92
# current environment - XXX RBC 20060404 move the clone '..' handling
93
# into here and call abspath from clone
94
temp_t = self.clone(relpath)
95
if temp_t.base.count('/') == 3:
98
return temp_t.base[:-1]
100
def append(self, relpath, f, mode=None):
101
"""See Transport.append()."""
102
_abspath = self._abspath(relpath)
103
self._check_parent(_abspath)
104
orig_content, orig_mode = self._files.get(_abspath, ("", None))
107
self._files[_abspath] = (orig_content + f.read(), mode)
108
return len(orig_content)
110
def _check_parent(self, _abspath):
111
dir = os.path.dirname(_abspath)
113
if not dir in self._dirs:
114
raise NoSuchFile(_abspath)
116
def has(self, relpath):
117
"""See Transport.has()."""
118
_abspath = self._abspath(relpath)
119
return _abspath in self._files or _abspath in self._dirs
121
def delete(self, relpath):
122
"""See Transport.delete()."""
123
_abspath = self._abspath(relpath)
124
if not _abspath in self._files:
125
raise NoSuchFile(relpath)
126
del self._files[_abspath]
128
def get(self, relpath):
129
"""See Transport.get()."""
130
_abspath = self._abspath(relpath)
131
if not _abspath in self._files:
132
raise NoSuchFile(relpath)
133
return StringIO(self._files[_abspath][0])
135
def put(self, relpath, f, mode=None):
136
"""See Transport.put()."""
137
_abspath = self._abspath(relpath)
138
self._check_parent(_abspath)
139
self._files[_abspath] = (f.read(), mode)
141
def mkdir(self, relpath, mode=None):
142
"""See Transport.mkdir()."""
143
_abspath = self._abspath(relpath)
144
self._check_parent(_abspath)
145
if _abspath in self._dirs:
146
raise FileExists(relpath)
147
self._dirs[_abspath]=mode
150
"""See Transport.listable."""
153
def iter_files_recursive(self):
154
for file in self._files:
155
if file.startswith(self._cwd):
156
yield file[len(self._cwd):]
158
def list_dir(self, relpath):
159
"""See Transport.list_dir()."""
160
_abspath = self._abspath(relpath)
161
if _abspath != '/' and _abspath not in self._dirs:
162
raise NoSuchFile(relpath)
164
for path in self._files:
165
if (path.startswith(_abspath) and
166
path[len(_abspath) + 1:].find('/') == -1 and
167
len(path) > len(_abspath)):
168
result.append(path[len(_abspath) + 1:])
169
for path in self._dirs:
170
if (path.startswith(_abspath) and
171
path[len(_abspath) + 1:].find('/') == -1 and
172
len(path) > len(_abspath) and
173
path[len(_abspath)] == '/'):
174
result.append(path[len(_abspath) + 1:])
177
def rename(self, rel_from, rel_to):
178
"""Rename a file or directory; fail if the destination exists"""
179
abs_from = self._abspath(rel_from)
180
abs_to = self._abspath(rel_to)
184
elif x.startswith(abs_from + '/'):
185
x = abs_to + x[len(abs_from):]
187
def do_renames(container):
188
for path in container:
189
new_path = replace(path)
191
if new_path in container:
192
raise FileExists(new_path)
193
container[new_path] = container[path]
195
do_renames(self._files)
196
do_renames(self._dirs)
198
def rmdir(self, relpath):
199
"""See Transport.rmdir."""
200
_abspath = self._abspath(relpath)
201
if _abspath in self._files:
202
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
203
for path in self._files:
204
if path.startswith(_abspath):
205
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
207
for path in self._dirs:
208
if path.startswith(_abspath) and path != _abspath:
209
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
210
if not _abspath in self._dirs:
211
raise NoSuchFile(relpath)
212
del self._dirs[_abspath]
214
def stat(self, relpath):
215
"""See Transport.stat()."""
216
_abspath = self._abspath(relpath)
217
if _abspath in self._files:
218
return MemoryStat(len(self._files[_abspath][0]), False,
219
self._files[_abspath][1])
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
relpath = urlutils.unescape(relpath)
236
if relpath.find('..') != -1:
237
raise AssertionError('relpath contains ..')
239
if (self._cwd == '/'):
241
return self._cwd[:-1]
242
if relpath.endswith('/'):
243
relpath = relpath[:-1]
244
if relpath.startswith('./'):
245
relpath = relpath[2:]
246
return self._cwd + relpath
249
class _MemoryLock(object):
250
"""This makes a lock."""
252
def __init__(self, path, transport):
253
assert isinstance(transport, MemoryTransport)
255
self.transport = transport
256
if self.path in self.transport._locks:
257
raise LockError('File %r already locked' % (self.path,))
258
self.transport._locks[self.path] = self
261
# Should this warn, or actually try to cleanup?
263
warn("MemoryLock %r not explicitly unlocked" % (self.path,))
267
del self.transport._locks[self.path]
268
self.transport = None
271
class MemoryServer(Server):
272
"""Server for the MemoryTransport for testing with."""
275
"""See bzrlib.transport.Server.setUp."""
276
self._dirs = {'/':None}
279
self._scheme = "memory+%s:///" % id(self)
280
def memory_factory(url):
281
result = MemoryTransport(url)
282
result._dirs = self._dirs
283
result._files = self._files
284
result._locks = self._locks
286
register_transport(self._scheme, memory_factory)
289
"""See bzrlib.transport.Server.tearDown."""
290
# unregister this server
293
"""See bzrlib.transport.Server.get_url."""
297
def get_test_permutations():
298
"""Return the permutations to be used in testing."""
299
return [(MemoryTransport, MemoryServer),