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 (
38
import bzrlib.urlutils as urlutils
42
class MemoryStat(object):
44
def __init__(self, size, is_dir, perms):
49
self.st_mode = S_IFREG | perms
53
self.st_mode = S_IFDIR | perms
56
class MemoryTransport(Transport):
57
"""This is an in memory file system for transient data storage."""
59
def __init__(self, url=""):
60
"""Set the 'base' path where files will be stored."""
65
super(MemoryTransport, self).__init__(url)
66
split = url.find(':') + 3
67
self._scheme = url[:split]
68
self._cwd = url[split:]
69
# dictionaries from absolute path to file mode
70
self._dirs = {'/':None}
74
def clone(self, offset=None):
75
"""See Transport.clone()."""
76
path = self._combine_paths(self._cwd, offset)
77
if len(path) == 0 or path[-1] != '/':
79
url = self._scheme + path
80
result = MemoryTransport(url)
81
result._dirs = self._dirs
82
result._files = self._files
83
result._locks = self._locks
86
def abspath(self, relpath):
87
"""See Transport.abspath()."""
88
# while a little slow, this is sufficiently fast to not matter in our
89
# current environment - XXX RBC 20060404 move the clone '..' handling
90
# into here and call abspath from clone
91
temp_t = self.clone(relpath)
92
if temp_t.base.count('/') == 3:
95
return temp_t.base[:-1]
97
def append_file(self, relpath, f, mode=None):
98
"""See Transport.append_file()."""
99
_abspath = self._abspath(relpath)
100
self._check_parent(_abspath)
101
orig_content, orig_mode = self._files.get(_abspath, ("", None))
104
self._files[_abspath] = (orig_content + f.read(), mode)
105
return len(orig_content)
107
def _check_parent(self, _abspath):
108
dir = os.path.dirname(_abspath)
110
if not dir in self._dirs:
111
raise NoSuchFile(_abspath)
113
def has(self, relpath):
114
"""See Transport.has()."""
115
_abspath = self._abspath(relpath)
116
return (_abspath in self._files) or (_abspath in self._dirs)
118
def delete(self, relpath):
119
"""See Transport.delete()."""
120
_abspath = self._abspath(relpath)
121
if not _abspath in self._files:
122
raise NoSuchFile(relpath)
123
del self._files[_abspath]
125
def get(self, relpath):
126
"""See Transport.get()."""
127
_abspath = self._abspath(relpath)
128
if not _abspath in self._files:
129
if _abspath in self._dirs:
130
return LateReadError(relpath)
132
raise NoSuchFile(relpath)
133
return StringIO(self._files[_abspath][0])
135
def put_file(self, relpath, f, mode=None):
136
"""See Transport.put_file()."""
137
_abspath = self._abspath(relpath)
138
self._check_parent(_abspath)
140
if type(bytes) is not str:
141
# Although not strictly correct, we raise UnicodeEncodeError to be
142
# compatible with other transports.
143
raise UnicodeEncodeError(
144
'undefined', bytes, 0, 1,
145
'put_file must be given a file of bytes, not unicode.')
146
self._files[_abspath] = (bytes, mode)
148
def mkdir(self, relpath, mode=None):
149
"""See Transport.mkdir()."""
150
_abspath = self._abspath(relpath)
151
self._check_parent(_abspath)
152
if _abspath in self._dirs:
153
raise FileExists(relpath)
154
self._dirs[_abspath]=mode
157
"""See Transport.listable."""
160
def iter_files_recursive(self):
161
for file in self._files:
162
if file.startswith(self._cwd):
163
yield urlutils.escape(file[len(self._cwd):])
165
def list_dir(self, relpath):
166
"""See Transport.list_dir()."""
167
_abspath = self._abspath(relpath)
168
if _abspath != '/' and _abspath not in self._dirs:
169
raise NoSuchFile(relpath)
172
if not _abspath.endswith('/'):
175
for path_group in self._files, self._dirs:
176
for path in path_group:
177
if path.startswith(_abspath):
178
trailing = path[len(_abspath):]
179
if trailing and '/' not in trailing:
180
result.append(trailing)
181
return map(urlutils.escape, result)
183
def rename(self, rel_from, rel_to):
184
"""Rename a file or directory; fail if the destination exists"""
185
abs_from = self._abspath(rel_from)
186
abs_to = self._abspath(rel_to)
190
elif x.startswith(abs_from + '/'):
191
x = abs_to + x[len(abs_from):]
193
def do_renames(container):
194
for path in container:
195
new_path = replace(path)
197
if new_path in container:
198
raise FileExists(new_path)
199
container[new_path] = container[path]
201
do_renames(self._files)
202
do_renames(self._dirs)
204
def rmdir(self, relpath):
205
"""See Transport.rmdir."""
206
_abspath = self._abspath(relpath)
207
if _abspath in self._files:
208
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
209
for path in self._files:
210
if path.startswith(_abspath + '/'):
211
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
213
for path in self._dirs:
214
if path.startswith(_abspath + '/') and path != _abspath:
215
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
216
if not _abspath in self._dirs:
217
raise NoSuchFile(relpath)
218
del self._dirs[_abspath]
220
def stat(self, relpath):
221
"""See Transport.stat()."""
222
_abspath = self._abspath(relpath)
223
if _abspath in self._files:
224
return MemoryStat(len(self._files[_abspath][0]), False,
225
self._files[_abspath][1])
226
elif _abspath in self._dirs:
227
return MemoryStat(0, True, self._dirs[_abspath])
229
raise NoSuchFile(_abspath)
231
def lock_read(self, relpath):
232
"""See Transport.lock_read()."""
233
return _MemoryLock(self._abspath(relpath), self)
235
def lock_write(self, relpath):
236
"""See Transport.lock_write()."""
237
return _MemoryLock(self._abspath(relpath), self)
239
def _abspath(self, relpath):
240
"""Generate an internal absolute path."""
241
relpath = urlutils.unescape(relpath)
242
if relpath.find('..') != -1:
243
raise AssertionError('relpath contains ..')
246
if relpath[0] == '/':
249
if (self._cwd == '/'):
251
return self._cwd[:-1]
252
if relpath.endswith('/'):
253
relpath = relpath[:-1]
254
if relpath.startswith('./'):
255
relpath = relpath[2:]
256
return self._cwd + relpath
259
class _MemoryLock(object):
260
"""This makes a lock."""
262
def __init__(self, path, transport):
263
assert isinstance(transport, MemoryTransport)
265
self.transport = transport
266
if self.path in self.transport._locks:
267
raise LockError('File %r already locked' % (self.path,))
268
self.transport._locks[self.path] = self
271
# Should this warn, or actually try to cleanup?
273
warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
277
del self.transport._locks[self.path]
278
self.transport = None
281
class MemoryServer(Server):
282
"""Server for the MemoryTransport for testing with."""
285
"""See bzrlib.transport.Server.setUp."""
286
self._dirs = {'/':None}
289
self._scheme = "memory+%s:///" % id(self)
290
def memory_factory(url):
291
result = MemoryTransport(url)
292
result._dirs = self._dirs
293
result._files = self._files
294
result._locks = self._locks
296
register_transport(self._scheme, memory_factory)
299
"""See bzrlib.transport.Server.tearDown."""
300
# unregister this server
303
"""See bzrlib.transport.Server.get_url."""
307
def get_test_permutations():
308
"""Return the permutations to be used in testing."""
309
return [(MemoryTransport, MemoryServer),