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.
27
from stat import S_IFREG, S_IFDIR
28
from cStringIO import StringIO
31
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
32
from bzrlib.trace import mutter
33
from bzrlib.transport import (Transport, register_transport, Server)
34
import bzrlib.urlutils as urlutils
38
class MemoryStat(object):
40
def __init__(self, size, is_dir, perms):
45
self.st_mode = S_IFREG | perms
49
self.st_mode = S_IFDIR | perms
52
class MemoryTransport(Transport):
53
"""This is an in memory file system for transient data storage."""
55
def __init__(self, url=""):
56
"""Set the 'base' path where files will be stored."""
61
super(MemoryTransport, self).__init__(url)
62
self._cwd = url[url.find(':') + 3:]
63
# dictionaries from absolute path to file mode
64
self._dirs = {'/':None}
68
def clone(self, offset=None):
69
"""See Transport.clone()."""
70
if offset is None or offset == '':
72
segments = offset.split('/')
73
cwdsegments = self._cwd.split('/')[:-1]
75
segment = segments.pop(0)
79
if len(cwdsegments) > 1:
82
cwdsegments.append(segment)
83
url = self.base[:self.base.find(':') + 3] + '/'.join(cwdsegments) + '/'
84
result = MemoryTransport(url)
85
result._dirs = self._dirs
86
result._files = self._files
87
result._locks = self._locks
90
def abspath(self, relpath):
91
"""See Transport.abspath()."""
92
# while a little slow, this is sufficiently fast to not matter in our
93
# current environment - XXX RBC 20060404 move the clone '..' handling
94
# into here and call abspath from clone
95
temp_t = self.clone(relpath)
96
if temp_t.base.count('/') == 3:
99
return temp_t.base[:-1]
101
def append(self, relpath, f, mode=None):
102
"""See Transport.append()."""
103
_abspath = self._abspath(relpath)
104
self._check_parent(_abspath)
105
orig_content, orig_mode = self._files.get(_abspath, ("", None))
108
self._files[_abspath] = (orig_content + f.read(), mode)
109
return len(orig_content)
111
def _check_parent(self, _abspath):
112
dir = os.path.dirname(_abspath)
114
if not dir in self._dirs:
115
raise NoSuchFile(_abspath)
117
def has(self, relpath):
118
"""See Transport.has()."""
119
_abspath = self._abspath(relpath)
120
return _abspath in self._files or _abspath in self._dirs
122
def delete(self, relpath):
123
"""See Transport.delete()."""
124
_abspath = self._abspath(relpath)
125
if not _abspath in self._files:
126
raise NoSuchFile(relpath)
127
del self._files[_abspath]
129
def get(self, relpath):
130
"""See Transport.get()."""
131
_abspath = self._abspath(relpath)
132
if not _abspath in self._files:
133
raise NoSuchFile(relpath)
134
return StringIO(self._files[_abspath][0])
136
def put(self, relpath, f, mode=None):
137
"""See Transport.put()."""
138
_abspath = self._abspath(relpath)
139
self._check_parent(_abspath)
140
self._files[_abspath] = (f.read(), mode)
142
def mkdir(self, relpath, mode=None):
143
"""See Transport.mkdir()."""
144
_abspath = self._abspath(relpath)
145
self._check_parent(_abspath)
146
if _abspath in self._dirs:
147
raise FileExists(relpath)
148
self._dirs[_abspath]=mode
151
"""See Transport.listable."""
154
def iter_files_recursive(self):
155
for file in self._files:
156
if file.startswith(self._cwd):
157
yield file[len(self._cwd):]
159
def list_dir(self, relpath):
160
"""See Transport.list_dir()."""
161
_abspath = self._abspath(relpath)
162
if _abspath != '/' and _abspath not in self._dirs:
163
raise NoSuchFile(relpath)
165
for path in self._files:
166
if (path.startswith(_abspath) and
167
path[len(_abspath) + 1:].find('/') == -1 and
168
len(path) > len(_abspath)):
169
result.append(path[len(_abspath) + 1:])
170
for path in self._dirs:
171
if (path.startswith(_abspath) and
172
path[len(_abspath) + 1:].find('/') == -1 and
173
len(path) > len(_abspath) and
174
path[len(_abspath)] == '/'):
175
result.append(path[len(_abspath) + 1:])
178
def rename(self, rel_from, rel_to):
179
"""Rename a file or directory; fail if the destination exists"""
180
abs_from = self._abspath(rel_from)
181
abs_to = self._abspath(rel_to)
185
elif x.startswith(abs_from + '/'):
186
x = abs_to + x[len(abs_from):]
188
def do_renames(container):
189
for path in container:
190
new_path = replace(path)
192
if new_path in container:
193
raise FileExists(new_path)
194
container[new_path] = container[path]
196
do_renames(self._files)
197
do_renames(self._dirs)
199
def rmdir(self, relpath):
200
"""See Transport.rmdir."""
201
_abspath = self._abspath(relpath)
202
if _abspath in self._files:
203
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
204
for path in self._files:
205
if path.startswith(_abspath):
206
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
208
for path in self._dirs:
209
if path.startswith(_abspath) and path != _abspath:
210
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
211
if not _abspath in self._dirs:
212
raise NoSuchFile(relpath)
213
del self._dirs[_abspath]
215
def stat(self, relpath):
216
"""See Transport.stat()."""
217
_abspath = self._abspath(relpath)
218
if _abspath in self._files:
219
return MemoryStat(len(self._files[_abspath][0]), False,
220
self._files[_abspath][1])
221
elif _abspath in self._dirs:
222
return MemoryStat(0, True, self._dirs[_abspath])
224
raise NoSuchFile(_abspath)
226
def lock_read(self, relpath):
227
"""See Transport.lock_read()."""
228
return _MemoryLock(self._abspath(relpath), self)
230
def lock_write(self, relpath):
231
"""See Transport.lock_write()."""
232
return _MemoryLock(self._abspath(relpath), self)
234
def _abspath(self, relpath):
235
"""Generate an internal absolute path."""
236
relpath = urlutils.unescape(relpath)
237
if relpath.find('..') != -1:
238
raise AssertionError('relpath contains ..')
240
if (self._cwd == '/'):
242
return self._cwd[:-1]
243
if relpath.endswith('/'):
244
relpath = relpath[:-1]
245
if relpath.startswith('./'):
246
relpath = relpath[2:]
247
return self._cwd + relpath
250
class _MemoryLock(object):
251
"""This makes a lock."""
253
def __init__(self, path, transport):
254
assert isinstance(transport, MemoryTransport)
256
self.transport = transport
257
if self.path in self.transport._locks:
258
raise LockError('File %r already locked' % (self.path,))
259
self.transport._locks[self.path] = self
262
# Should this warn, or actually try to cleanup?
264
warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
268
del self.transport._locks[self.path]
269
self.transport = None
272
class MemoryServer(Server):
273
"""Server for the MemoryTransport for testing with."""
276
"""See bzrlib.transport.Server.setUp."""
277
self._dirs = {'/':None}
280
self._scheme = "memory+%s:///" % id(self)
281
def memory_factory(url):
282
result = MemoryTransport(url)
283
result._dirs = self._dirs
284
result._files = self._files
285
result._locks = self._locks
287
register_transport(self._scheme, memory_factory)
290
"""See bzrlib.transport.Server.tearDown."""
291
# unregister this server
294
"""See bzrlib.transport.Server.get_url."""
298
def get_test_permutations():
299
"""Return the permutations to be used in testing."""
300
return [(MemoryTransport, MemoryServer),