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.
26
from stat import S_IFREG, S_IFDIR
27
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
split = url.find(':') + 3
62
self._scheme = url[:split]
63
self._cwd = url[split:]
64
# dictionaries from absolute path to file mode
65
self._dirs = {'/':None}
69
def clone(self, offset=None):
70
"""See Transport.clone()."""
71
path = self._combine_paths(self._cwd, offset)
72
if len(path) == 0 or path[-1] != '/':
74
url = self._scheme + path
75
result = MemoryTransport(url)
76
result._dirs = self._dirs
77
result._files = self._files
78
result._locks = self._locks
81
def abspath(self, relpath):
82
"""See Transport.abspath()."""
83
# while a little slow, this is sufficiently fast to not matter in our
84
# current environment - XXX RBC 20060404 move the clone '..' handling
85
# into here and call abspath from clone
86
temp_t = self.clone(relpath)
87
if temp_t.base.count('/') == 3:
90
return temp_t.base[:-1]
92
def append_file(self, relpath, f, mode=None):
93
"""See Transport.append_file()."""
94
_abspath = self._abspath(relpath)
95
self._check_parent(_abspath)
96
orig_content, orig_mode = self._files.get(_abspath, ("", None))
99
self._files[_abspath] = (orig_content + f.read(), mode)
100
return len(orig_content)
102
def _check_parent(self, _abspath):
103
dir = os.path.dirname(_abspath)
105
if not dir in self._dirs:
106
raise NoSuchFile(_abspath)
108
def has(self, relpath):
109
"""See Transport.has()."""
110
_abspath = self._abspath(relpath)
111
return (_abspath in self._files) or (_abspath in self._dirs)
113
def delete(self, relpath):
114
"""See Transport.delete()."""
115
_abspath = self._abspath(relpath)
116
if not _abspath in self._files:
117
raise NoSuchFile(relpath)
118
del self._files[_abspath]
120
def get(self, relpath):
121
"""See Transport.get()."""
122
_abspath = self._abspath(relpath)
123
if not _abspath in self._files:
124
raise NoSuchFile(relpath)
125
return StringIO(self._files[_abspath][0])
127
def put_file(self, relpath, f, mode=None):
128
"""See Transport.put_file()."""
129
_abspath = self._abspath(relpath)
130
self._check_parent(_abspath)
132
if type(bytes) is not str:
133
# Although not strictly correct, we raise UnicodeEncodeError to be
134
# compatible with other transports.
135
raise UnicodeEncodeError(
136
'undefined', bytes, 0, 1,
137
'put_file must be given a file of bytes, not unicode.')
138
self._files[_abspath] = (bytes, mode)
140
def mkdir(self, relpath, mode=None):
141
"""See Transport.mkdir()."""
142
_abspath = self._abspath(relpath)
143
self._check_parent(_abspath)
144
if _abspath in self._dirs:
145
raise FileExists(relpath)
146
self._dirs[_abspath]=mode
149
"""See Transport.listable."""
152
def iter_files_recursive(self):
153
for file in self._files:
154
if file.startswith(self._cwd):
155
yield urlutils.escape(file[len(self._cwd):])
157
def list_dir(self, relpath):
158
"""See Transport.list_dir()."""
159
_abspath = self._abspath(relpath)
160
if _abspath != '/' and _abspath not in self._dirs:
161
raise NoSuchFile(relpath)
164
if not _abspath.endswith('/'):
167
for path_group in self._files, self._dirs:
168
for path in path_group:
169
if path.startswith(_abspath):
170
trailing = path[len(_abspath):]
171
if trailing and '/' not in trailing:
172
result.append(trailing)
173
return map(urlutils.escape, result)
175
def rename(self, rel_from, rel_to):
176
"""Rename a file or directory; fail if the destination exists"""
177
abs_from = self._abspath(rel_from)
178
abs_to = self._abspath(rel_to)
182
elif x.startswith(abs_from + '/'):
183
x = abs_to + x[len(abs_from):]
185
def do_renames(container):
186
for path in container:
187
new_path = replace(path)
189
if new_path in container:
190
raise FileExists(new_path)
191
container[new_path] = container[path]
193
do_renames(self._files)
194
do_renames(self._dirs)
196
def rmdir(self, relpath):
197
"""See Transport.rmdir."""
198
_abspath = self._abspath(relpath)
199
if _abspath in self._files:
200
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
201
for path in self._files:
202
if path.startswith(_abspath + '/'):
203
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
205
for path in self._dirs:
206
if path.startswith(_abspath + '/') and path != _abspath:
207
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
208
if not _abspath in self._dirs:
209
raise NoSuchFile(relpath)
210
del self._dirs[_abspath]
212
def stat(self, relpath):
213
"""See Transport.stat()."""
214
_abspath = self._abspath(relpath)
215
if _abspath in self._files:
216
return MemoryStat(len(self._files[_abspath][0]), False,
217
self._files[_abspath][1])
218
elif _abspath in self._dirs:
219
return MemoryStat(0, True, self._dirs[_abspath])
221
raise NoSuchFile(_abspath)
223
def lock_read(self, relpath):
224
"""See Transport.lock_read()."""
225
return _MemoryLock(self._abspath(relpath), self)
227
def lock_write(self, relpath):
228
"""See Transport.lock_write()."""
229
return _MemoryLock(self._abspath(relpath), self)
231
def _abspath(self, relpath):
232
"""Generate an internal absolute path."""
233
relpath = urlutils.unescape(relpath)
234
if relpath.find('..') != -1:
235
raise AssertionError('relpath contains ..')
238
if relpath[0] == '/':
241
if (self._cwd == '/'):
243
return self._cwd[:-1]
244
if relpath.endswith('/'):
245
relpath = relpath[:-1]
246
if relpath.startswith('./'):
247
relpath = relpath[2:]
248
return self._cwd + relpath
251
class _MemoryLock(object):
252
"""This makes a lock."""
254
def __init__(self, path, transport):
255
assert isinstance(transport, MemoryTransport)
257
self.transport = transport
258
if self.path in self.transport._locks:
259
raise LockError('File %r already locked' % (self.path,))
260
self.transport._locks[self.path] = self
263
# Should this warn, or actually try to cleanup?
265
warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
269
del self.transport._locks[self.path]
270
self.transport = None
273
class MemoryServer(Server):
274
"""Server for the MemoryTransport for testing with."""
277
"""See bzrlib.transport.Server.setUp."""
278
self._dirs = {'/':None}
281
self._scheme = "memory+%s:///" % id(self)
282
def memory_factory(url):
283
result = MemoryTransport(url)
284
result._dirs = self._dirs
285
result._files = self._files
286
result._locks = self._locks
288
register_transport(self._scheme, memory_factory)
291
"""See bzrlib.transport.Server.tearDown."""
292
# unregister this server
295
"""See bzrlib.transport.Server.get_url."""
299
def get_test_permutations():
300
"""Return the permutations to be used in testing."""
301
return [(MemoryTransport, MemoryServer),