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
split = url.find(':') + 3
63
self._scheme = url[:split]
64
self._cwd = url[split:]
65
# dictionaries from absolute path to file mode
66
self._dirs = {'/':None}
70
def clone(self, offset=None):
71
"""See Transport.clone()."""
72
path = self._combine_paths(self._cwd, offset)
73
if len(path) == 0 or path[-1] != '/':
75
url = self._scheme + path
76
result = MemoryTransport(url)
77
result._dirs = self._dirs
78
result._files = self._files
79
result._locks = self._locks
82
def abspath(self, relpath):
83
"""See Transport.abspath()."""
84
# while a little slow, this is sufficiently fast to not matter in our
85
# current environment - XXX RBC 20060404 move the clone '..' handling
86
# into here and call abspath from clone
87
temp_t = self.clone(relpath)
88
if temp_t.base.count('/') == 3:
91
return temp_t.base[:-1]
93
def append_file(self, relpath, f, mode=None):
94
"""See Transport.append_file()."""
95
_abspath = self._abspath(relpath)
96
self._check_parent(_abspath)
97
orig_content, orig_mode = self._files.get(_abspath, ("", None))
100
self._files[_abspath] = (orig_content + f.read(), mode)
101
return len(orig_content)
103
def _check_parent(self, _abspath):
104
dir = os.path.dirname(_abspath)
106
if not dir in self._dirs:
107
raise NoSuchFile(_abspath)
109
def has(self, relpath):
110
"""See Transport.has()."""
111
_abspath = self._abspath(relpath)
112
return (_abspath in self._files) or (_abspath in self._dirs)
114
def delete(self, relpath):
115
"""See Transport.delete()."""
116
_abspath = self._abspath(relpath)
117
if not _abspath in self._files:
118
raise NoSuchFile(relpath)
119
del self._files[_abspath]
121
def get(self, relpath):
122
"""See Transport.get()."""
123
_abspath = self._abspath(relpath)
124
if not _abspath in self._files:
125
raise NoSuchFile(relpath)
126
return StringIO(self._files[_abspath][0])
128
def put_file(self, relpath, f, mode=None):
129
"""See Transport.put_file()."""
130
_abspath = self._abspath(relpath)
131
self._check_parent(_abspath)
132
self._files[_abspath] = (f.read(), mode)
134
def mkdir(self, relpath, mode=None):
135
"""See Transport.mkdir()."""
136
_abspath = self._abspath(relpath)
137
self._check_parent(_abspath)
138
if _abspath in self._dirs:
139
raise FileExists(relpath)
140
self._dirs[_abspath]=mode
143
"""See Transport.listable."""
146
def iter_files_recursive(self):
147
for file in self._files:
148
if file.startswith(self._cwd):
149
yield urlutils.escape(file[len(self._cwd):])
151
def list_dir(self, relpath):
152
"""See Transport.list_dir()."""
153
_abspath = self._abspath(relpath)
154
if _abspath != '/' and _abspath not in self._dirs:
155
raise NoSuchFile(relpath)
157
for path in self._files:
158
if (path.startswith(_abspath) and
159
path[len(_abspath) + 1:].find('/') == -1 and
160
len(path) > len(_abspath)):
161
result.append(path[len(_abspath) + 1:])
162
for path in self._dirs:
163
if (path.startswith(_abspath) and
164
path[len(_abspath) + 1:].find('/') == -1 and
165
len(path) > len(_abspath) and
166
path[len(_abspath)] == '/'):
167
result.append(path[len(_abspath) + 1:])
168
return map(urlutils.escape, result)
170
def rename(self, rel_from, rel_to):
171
"""Rename a file or directory; fail if the destination exists"""
172
abs_from = self._abspath(rel_from)
173
abs_to = self._abspath(rel_to)
177
elif x.startswith(abs_from + '/'):
178
x = abs_to + x[len(abs_from):]
180
def do_renames(container):
181
for path in container:
182
new_path = replace(path)
184
if new_path in container:
185
raise FileExists(new_path)
186
container[new_path] = container[path]
188
do_renames(self._files)
189
do_renames(self._dirs)
191
def rmdir(self, relpath):
192
"""See Transport.rmdir."""
193
_abspath = self._abspath(relpath)
194
if _abspath in self._files:
195
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
196
for path in self._files:
197
if path.startswith(_abspath):
198
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
200
for path in self._dirs:
201
if path.startswith(_abspath) and path != _abspath:
202
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
203
if not _abspath in self._dirs:
204
raise NoSuchFile(relpath)
205
del self._dirs[_abspath]
207
def stat(self, relpath):
208
"""See Transport.stat()."""
209
_abspath = self._abspath(relpath)
210
if _abspath in self._files:
211
return MemoryStat(len(self._files[_abspath][0]), False,
212
self._files[_abspath][1])
213
elif _abspath in self._dirs:
214
return MemoryStat(0, True, self._dirs[_abspath])
216
raise NoSuchFile(_abspath)
218
def lock_read(self, relpath):
219
"""See Transport.lock_read()."""
220
return _MemoryLock(self._abspath(relpath), self)
222
def lock_write(self, relpath):
223
"""See Transport.lock_write()."""
224
return _MemoryLock(self._abspath(relpath), self)
226
def _abspath(self, relpath):
227
"""Generate an internal absolute path."""
228
relpath = urlutils.unescape(relpath)
229
if relpath.find('..') != -1:
230
raise AssertionError('relpath contains ..')
231
if relpath[0] == '/':
234
if (self._cwd == '/'):
236
return self._cwd[:-1]
237
if relpath.endswith('/'):
238
relpath = relpath[:-1]
239
if relpath.startswith('./'):
240
relpath = relpath[2:]
241
return self._cwd + relpath
244
class _MemoryLock(object):
245
"""This makes a lock."""
247
def __init__(self, path, transport):
248
assert isinstance(transport, MemoryTransport)
250
self.transport = transport
251
if self.path in self.transport._locks:
252
raise LockError('File %r already locked' % (self.path,))
253
self.transport._locks[self.path] = self
256
# Should this warn, or actually try to cleanup?
258
warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
262
del self.transport._locks[self.path]
263
self.transport = None
266
class MemoryServer(Server):
267
"""Server for the MemoryTransport for testing with."""
270
"""See bzrlib.transport.Server.setUp."""
271
self._dirs = {'/':None}
274
self._scheme = "memory+%s:///" % id(self)
275
def memory_factory(url):
276
result = MemoryTransport(url)
277
result._dirs = self._dirs
278
result._files = self._files
279
result._locks = self._locks
281
register_transport(self._scheme, memory_factory)
284
"""See bzrlib.transport.Server.tearDown."""
285
# unregister this server
288
"""See bzrlib.transport.Server.get_url."""
292
def get_test_permutations():
293
"""Return the permutations to be used in testing."""
294
return [(MemoryTransport, MemoryServer),