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
43
self.st_mode = S_IFDIR | perms
46
class MemoryTransport(Transport):
47
"""This is an in memory file system for transient data storage."""
49
def __init__(self, url=""):
50
"""Set the 'base' path where files will be stored."""
55
super(MemoryTransport, self).__init__(url)
56
self._cwd = url[url.find(':') + 1:]
57
# dictionaries from absolute path to file mode
62
def clone(self, offset=None):
63
"""See Transport.clone()."""
66
segments = offset.split('/')
67
cwdsegments = self._cwd.split('/')[:-1]
69
segment = segments.pop(0)
73
if len(cwdsegments) > 1:
76
cwdsegments.append(segment)
77
url = self.base[:self.base.find(':') + 1] + '/'.join(cwdsegments) + '/'
78
result = MemoryTransport(url)
79
result._dirs = self._dirs
80
result._files = self._files
81
result._locks = self._locks
84
def abspath(self, relpath):
85
"""See Transport.abspath()."""
86
return self.base[:-1] + self._abspath(relpath)[len(self._cwd) - 1:]
88
def append(self, relpath, f):
89
"""See Transport.append()."""
90
_abspath = self._abspath(relpath)
91
self._check_parent(_abspath)
92
orig_content, orig_mode = self._files.get(_abspath, ("", None))
93
self._files[_abspath] = (orig_content + f.read(), orig_mode)
95
def _check_parent(self, _abspath):
96
dir = os.path.dirname(_abspath)
98
if not dir in self._dirs:
99
raise NoSuchFile(_abspath)
101
def has(self, relpath):
102
"""See Transport.has()."""
103
_abspath = self._abspath(relpath)
104
return _abspath in self._files or _abspath in self._dirs
106
def delete(self, relpath):
107
"""See Transport.delete()."""
108
_abspath = self._abspath(relpath)
109
if not _abspath in self._files:
110
raise NoSuchFile(relpath)
111
del self._files[_abspath]
113
def get(self, relpath):
114
"""See Transport.get()."""
115
_abspath = self._abspath(relpath)
116
if not _abspath in self._files:
117
raise NoSuchFile(relpath)
118
return StringIO(self._files[_abspath][0])
120
def put(self, relpath, f, mode=None):
121
"""See Transport.put()."""
122
_abspath = self._abspath(relpath)
123
self._check_parent(_abspath)
124
self._files[_abspath] = (f.read(), mode)
126
def mkdir(self, relpath, mode=None):
127
"""See Transport.mkdir()."""
128
_abspath = self._abspath(relpath)
129
self._check_parent(_abspath)
130
if _abspath in self._dirs:
131
raise FileExists(relpath)
132
self._dirs[_abspath]=mode
135
"""See Transport.listable."""
138
def iter_files_recursive(self):
139
for file in self._files:
140
if file.startswith(self._cwd):
141
yield file[len(self._cwd):]
143
def list_dir(self, relpath):
144
"""See Transport.list_dir()."""
145
_abspath = self._abspath(relpath)
146
if _abspath != '/' and _abspath not in self._dirs:
147
raise NoSuchFile(relpath)
149
for path in self._files:
150
if (path.startswith(_abspath) and
151
path[len(_abspath) + 1:].find('/') == -1 and
152
len(path) > len(_abspath)):
153
result.append(path[len(_abspath) + 1:])
154
for path in self._dirs:
155
if (path.startswith(_abspath) and
156
path[len(_abspath) + 1:].find('/') == -1 and
157
len(path) > len(_abspath) and
158
path[len(_abspath)] == '/'):
159
result.append(path[len(_abspath) + 1:])
162
def rename(self, rel_from, rel_to):
163
"""Rename a file or directory; fail if the destination exists"""
164
abs_from = self._abspath(rel_from)
165
abs_to = self._abspath(rel_to)
169
elif x.startswith(abs_from + '/'):
170
x = abs_to + x[len(abs_from):]
172
def do_renames(container):
173
for path in container:
174
new_path = replace(path)
176
if new_path in container:
177
raise FileExists(new_path)
178
container[new_path] = container[path]
180
do_renames(self._files)
181
do_renames(self._dirs)
183
def rmdir(self, relpath):
184
"""See Transport.rmdir."""
185
_abspath = self._abspath(relpath)
186
if _abspath in self._files:
187
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
188
for path in self._files:
189
if path.startswith(_abspath):
190
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
192
for path in self._dirs:
193
if path.startswith(_abspath) and path != _abspath:
194
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
195
if not _abspath in self._dirs:
196
raise NoSuchFile(relpath)
197
del self._dirs[_abspath]
199
def stat(self, relpath):
200
"""See Transport.stat()."""
201
_abspath = self._abspath(relpath)
202
if _abspath in self._files:
203
return MemoryStat(len(self._files[_abspath][0]), False,
204
self._files[_abspath][1])
206
return MemoryStat(0, True, None)
207
elif _abspath in self._dirs:
208
return MemoryStat(0, True, self._dirs[_abspath])
210
raise NoSuchFile(_abspath)
212
def lock_read(self, relpath):
213
"""See Transport.lock_read()."""
214
return _MemoryLock(self._abspath(relpath), self)
216
def lock_write(self, relpath):
217
"""See Transport.lock_write()."""
218
return _MemoryLock(self._abspath(relpath), self)
220
def _abspath(self, relpath):
221
"""Generate an internal absolute path."""
222
if relpath.find('..') != -1:
223
raise AssertionError('relpath contains ..')
225
return self._cwd[:-1]
226
if relpath.endswith('/'):
227
relpath = relpath[:-1]
228
if relpath.startswith('./'):
229
relpath = relpath[2:]
230
return self._cwd + relpath
233
class _MemoryLock(object):
234
"""This makes a lock."""
236
def __init__(self, path, transport):
237
assert isinstance(transport, MemoryTransport)
239
self.transport = transport
240
if self.path in self.transport._locks:
241
raise LockError('File %r already locked' % (self.path,))
242
self.transport._locks[self.path] = self
245
# Should this warn, or actually try to cleanup?
247
warn("MemoryLock %r not explicitly unlocked" % (self.path,))
251
del self.transport._locks[self.path]
252
self.transport = None
255
class MemoryServer(Server):
256
"""Server for the MemoryTransport for testing with."""
259
"""See bzrlib.transport.Server.setUp."""
263
self._scheme = "memory+%s:" % id(self)
264
def memory_factory(url):
265
result = MemoryTransport(url)
266
result._dirs = self._dirs
267
result._files = self._files
268
result._locks = self._locks
270
register_transport(self._scheme, memory_factory)
273
"""See bzrlib.transport.Server.tearDown."""
274
# unregister this server
277
"""See bzrlib.transport.Server.get_url."""
281
def get_test_permutations():
282
"""Return the permutations to be used in testing."""
283
return [(MemoryTransport, MemoryServer),