1
# Copyright (C) 2005-2010 Canonical Ltd
1
# Copyright (C) 2005 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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.
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
"""Implementation of Transport that uses memory for its storage."""
26
from stat import S_IFREG, S_IFDIR
27
22
from cStringIO import StringIO
34
from bzrlib.errors import (
41
24
from bzrlib.trace import mutter
42
from bzrlib.transport import (
43
AppendBasedFileStream,
25
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
26
from bzrlib.transport import Transport, register_transport, Server
50
28
class MemoryStat(object):
52
30
def __init__(self, size, is_dir, perms):
53
31
self.st_size = size
57
35
self.st_mode = S_IFREG | perms
61
37
self.st_mode = S_IFDIR | perms
64
class MemoryTransport(transport.Transport):
40
class MemoryTransport(Transport):
65
41
"""This is an in memory file system for transient data storage."""
67
43
def __init__(self, url=""):
68
44
"""Set the 'base' path where files will be stored."""
73
49
super(MemoryTransport, self).__init__(url)
74
split = url.find(':') + 3
75
self._scheme = url[:split]
76
self._cwd = url[split:]
77
# dictionaries from absolute path to file mode
78
self._dirs = {'/':None}
50
self._cwd = url[url.find(':') + 1:]
82
55
def clone(self, offset=None):
83
56
"""See Transport.clone()."""
84
path = self._combine_paths(self._cwd, offset)
85
if len(path) == 0 or path[-1] != '/':
87
url = self._scheme + path
88
result = self.__class__(url)
59
segments = offset.split('/')
60
cwdsegments = self._cwd.split('/')[:-1]
62
segment = segments.pop(0)
66
if len(cwdsegments) > 1:
69
cwdsegments.append(segment)
70
url = self.base[:self.base.find(':') + 1] + '/'.join(cwdsegments) + '/'
71
result = MemoryTransport(url)
89
72
result._dirs = self._dirs
90
73
result._files = self._files
91
74
result._locks = self._locks
94
77
def abspath(self, relpath):
95
78
"""See Transport.abspath()."""
96
# while a little slow, this is sufficiently fast to not matter in our
97
# current environment - XXX RBC 20060404 move the clone '..' handling
98
# into here and call abspath from clone
99
temp_t = self.clone(relpath)
100
if temp_t.base.count('/') == 3:
103
return temp_t.base[:-1]
79
return self.base[:-1] + self._abspath(relpath)[len(self._cwd) - 1:]
105
def append_file(self, relpath, f, mode=None):
106
"""See Transport.append_file()."""
81
def append(self, relpath, f):
82
"""See Transport.append()."""
107
83
_abspath = self._abspath(relpath)
108
84
self._check_parent(_abspath)
109
85
orig_content, orig_mode = self._files.get(_abspath, ("", None))
112
self._files[_abspath] = (orig_content + f.read(), mode)
113
return len(orig_content)
86
self._files[_abspath] = (orig_content + f.read(), orig_mode)
115
88
def _check_parent(self, _abspath):
116
89
dir = os.path.dirname(_abspath)
130
103
raise NoSuchFile(relpath)
131
104
del self._files[_abspath]
133
def external_url(self):
134
"""See bzrlib.transport.Transport.external_url."""
135
# MemoryTransport's are only accessible in-process
137
raise InProcessTransport(self)
139
106
def get(self, relpath):
140
107
"""See Transport.get()."""
141
108
_abspath = self._abspath(relpath)
142
109
if not _abspath in self._files:
143
if _abspath in self._dirs:
144
return LateReadError(relpath)
146
raise NoSuchFile(relpath)
110
raise NoSuchFile(relpath)
147
111
return StringIO(self._files[_abspath][0])
149
def put_file(self, relpath, f, mode=None):
150
"""See Transport.put_file()."""
113
def put(self, relpath, f, mode=None):
114
"""See Transport.put()."""
151
115
_abspath = self._abspath(relpath)
152
116
self._check_parent(_abspath)
154
if type(bytes) is not str:
155
# Although not strictly correct, we raise UnicodeEncodeError to be
156
# compatible with other transports.
157
raise UnicodeEncodeError(
158
'undefined', bytes, 0, 1,
159
'put_file must be given a file of bytes, not unicode.')
160
self._files[_abspath] = (bytes, mode)
117
self._files[_abspath] = (f.read(), mode)
163
119
def mkdir(self, relpath, mode=None):
164
120
"""See Transport.mkdir()."""
168
124
raise FileExists(relpath)
169
125
self._dirs[_abspath]=mode
171
def open_write_stream(self, relpath, mode=None):
172
"""See Transport.open_write_stream."""
173
self.put_bytes(relpath, "", mode)
174
result = AppendBasedFileStream(self, relpath)
175
_file_streams[self.abspath(relpath)] = result
178
127
def listable(self):
179
128
"""See Transport.listable."""
182
131
def iter_files_recursive(self):
183
132
for file in self._files:
184
133
if file.startswith(self._cwd):
185
yield urlutils.escape(file[len(self._cwd):])
134
yield file[len(self._cwd):]
187
136
def list_dir(self, relpath):
188
137
"""See Transport.list_dir()."""
189
138
_abspath = self._abspath(relpath)
190
139
if _abspath != '/' and _abspath not in self._dirs:
191
140
raise NoSuchFile(relpath)
194
if not _abspath.endswith('/'):
197
for path_group in self._files, self._dirs:
198
for path in path_group:
199
if path.startswith(_abspath):
200
trailing = path[len(_abspath):]
201
if trailing and '/' not in trailing:
202
result.append(trailing)
203
return map(urlutils.escape, result)
205
def rename(self, rel_from, rel_to):
206
"""Rename a file or directory; fail if the destination exists"""
207
abs_from = self._abspath(rel_from)
208
abs_to = self._abspath(rel_to)
212
elif x.startswith(abs_from + '/'):
213
x = abs_to + x[len(abs_from):]
215
def do_renames(container):
216
for path in container:
217
new_path = replace(path)
219
if new_path in container:
220
raise FileExists(new_path)
221
container[new_path] = container[path]
223
do_renames(self._files)
224
do_renames(self._dirs)
142
for path in self._files:
143
if (path.startswith(_abspath) and
144
path[len(_abspath) + 1:].find('/') == -1 and
145
len(path) > len(_abspath)):
146
result.append(path[len(_abspath) + 1:])
147
for path in self._dirs:
148
if (path.startswith(_abspath) and
149
path[len(_abspath) + 1:].find('/') == -1 and
150
len(path) > len(_abspath) and
151
path[len(_abspath)] == '/'):
152
result.append(path[len(_abspath) + 1:])
226
155
def rmdir(self, relpath):
227
156
"""See Transport.rmdir."""
228
157
_abspath = self._abspath(relpath)
229
158
if _abspath in self._files:
230
159
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
231
160
for path in self._files:
232
if path.startswith(_abspath + '/'):
233
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
161
if path.startswith(_abspath):
162
self._translate_error(IOError(errno.EBUSY, relpath), relpath)
235
163
for path in self._dirs:
236
if path.startswith(_abspath + '/') and path != _abspath:
237
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
164
if path.startswith(_abspath) and path != _abspath:
165
self._translate_error(IOError(errno.EBUSY, relpath), relpath)
238
166
if not _abspath in self._dirs:
239
167
raise NoSuchFile(relpath)
240
168
del self._dirs[_abspath]
243
171
"""See Transport.stat()."""
244
172
_abspath = self._abspath(relpath)
245
173
if _abspath in self._files:
246
return MemoryStat(len(self._files[_abspath][0]), False,
174
return MemoryStat(len(self._files[_abspath][0]), False,
247
175
self._files[_abspath][1])
177
return MemoryStat(0, True, None)
248
178
elif _abspath in self._dirs:
249
179
return MemoryStat(0, True, self._dirs[_abspath])
261
191
def _abspath(self, relpath):
262
192
"""Generate an internal absolute path."""
263
relpath = urlutils.unescape(relpath)
264
if relpath[:1] == '/':
266
cwd_parts = self._cwd.split('/')
267
rel_parts = relpath.split('/')
269
for i in cwd_parts + rel_parts:
272
raise ValueError("illegal relpath %r under %r"
273
% (relpath, self._cwd))
275
elif i == '.' or i == '':
279
return '/' + '/'.join(r)
193
if relpath.find('..') != -1:
194
raise AssertionError('relpath contains ..')
196
return self._cwd[:-1]
197
if relpath.endswith('/'):
198
relpath = relpath[:-1]
199
if relpath.startswith('./'):
200
relpath = relpath[2:]
201
return self._cwd + relpath
282
204
class _MemoryLock(object):
283
205
"""This makes a lock."""
285
207
def __init__(self, path, transport):
208
assert isinstance(transport, MemoryTransport)
287
210
self.transport = transport
288
211
if self.path in self.transport._locks:
300
223
self.transport = None
303
class MemoryServer(transport.Server):
226
class MemoryServer(Server):
304
227
"""Server for the MemoryTransport for testing with."""
306
def start_server(self):
307
self._dirs = {'/':None}
230
"""See bzrlib.transport.Server.setUp."""
310
self._scheme = "memory+%s:///" % id(self)
234
self._scheme = "memory+%s:" % id(self)
311
235
def memory_factory(url):
312
from bzrlib.transport import memory
313
result = memory.MemoryTransport(url)
236
result = MemoryTransport(url)
314
237
result._dirs = self._dirs
315
238
result._files = self._files
316
239
result._locks = self._locks
318
self._memory_factory = memory_factory
319
transport.register_transport(self._scheme, self._memory_factory)
241
register_transport(self._scheme, memory_factory)
321
def stop_server(self):
244
"""See bzrlib.transport.Server.tearDown."""
322
245
# unregister this server
323
transport.unregister_transport(self._scheme, self._memory_factory)
325
247
def get_url(self):
326
248
"""See bzrlib.transport.Server.get_url."""
327
249
return self._scheme
329
def get_bogus_url(self):
330
raise NotImplementedError
333
252
def get_test_permutations():
334
253
"""Return the permutations to be used in testing."""