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
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):
65
"""This is an in memory file system for transient data storage."""
40
class MemoryTransport(Transport):
41
"""This is the transport agent for local filesystem access."""
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
54
def clone(self, offset=None):
83
55
"""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)
58
segments = offset.split('/')
59
cwdsegments = self._cwd.split('/')[:-1]
61
segment = segments.pop(0)
65
if len(cwdsegments) > 1:
68
cwdsegments.append(segment)
69
url = self.base[:self.base.find(':') + 1] + '/'.join(cwdsegments) + '/'
70
result = MemoryTransport(url)
89
71
result._dirs = self._dirs
90
72
result._files = self._files
91
result._locks = self._locks
94
75
def abspath(self, relpath):
95
76
"""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]
77
return self.base[:-1] + self._abspath(relpath)
105
def append_file(self, relpath, f, mode=None):
106
"""See Transport.append_file()."""
79
def append(self, relpath, f):
80
"""See Transport.append()."""
107
81
_abspath = self._abspath(relpath)
108
82
self._check_parent(_abspath)
109
83
orig_content, orig_mode = self._files.get(_abspath, ("", None))
112
self._files[_abspath] = (orig_content + f.read(), mode)
113
return len(orig_content)
84
self._files[_abspath] = (orig_content + f.read(), orig_mode)
115
86
def _check_parent(self, _abspath):
116
87
dir = os.path.dirname(_abspath)
130
101
raise NoSuchFile(relpath)
131
102
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
104
def get(self, relpath):
140
105
"""See Transport.get()."""
141
106
_abspath = self._abspath(relpath)
142
107
if not _abspath in self._files:
143
if _abspath in self._dirs:
144
return LateReadError(relpath)
146
raise NoSuchFile(relpath)
108
raise NoSuchFile(relpath)
147
109
return StringIO(self._files[_abspath][0])
149
def put_file(self, relpath, f, mode=None):
150
"""See Transport.put_file()."""
111
def put(self, relpath, f, mode=None):
112
"""See Transport.put()."""
151
113
_abspath = self._abspath(relpath)
152
114
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)
115
self._files[_abspath] = (f.read(), mode)
163
117
def mkdir(self, relpath, mode=None):
164
118
"""See Transport.mkdir()."""
182
129
def iter_files_recursive(self):
183
130
for file in self._files:
184
131
if file.startswith(self._cwd):
185
yield urlutils.escape(file[len(self._cwd):])
132
yield file[len(self._cwd):]
187
134
def list_dir(self, relpath):
188
135
"""See Transport.list_dir()."""
189
136
_abspath = self._abspath(relpath)
190
137
if _abspath != '/' and _abspath not in self._dirs:
191
138
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)
226
def rmdir(self, relpath):
227
"""See Transport.rmdir."""
228
_abspath = self._abspath(relpath)
229
if _abspath in self._files:
230
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
231
140
for path in self._files:
232
if path.startswith(_abspath + '/'):
233
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
141
if (path.startswith(_abspath) and
142
path[len(_abspath) + 1:].find('/') == -1 and
143
len(path) > len(_abspath)):
144
result.append(path[len(_abspath) + 1:])
235
145
for path in self._dirs:
236
if path.startswith(_abspath + '/') and path != _abspath:
237
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
238
if not _abspath in self._dirs:
239
raise NoSuchFile(relpath)
240
del self._dirs[_abspath]
146
if (path.startswith(_abspath) and
147
path[len(_abspath) + 1:].find('/') == -1 and
148
len(path) > len(_abspath)):
149
result.append(path[len(_abspath) + 1:])
242
152
def stat(self, relpath):
243
153
"""See Transport.stat()."""
244
154
_abspath = self._abspath(relpath)
245
155
if _abspath in self._files:
246
return MemoryStat(len(self._files[_abspath][0]), False,
156
return MemoryStat(len(self._files[_abspath][0]), False,
247
157
self._files[_abspath][1])
159
return MemoryStat(0, True, None)
248
160
elif _abspath in self._dirs:
249
161
return MemoryStat(0, True, self._dirs[_abspath])
251
raise NoSuchFile(_abspath)
253
def lock_read(self, relpath):
254
"""See Transport.lock_read()."""
255
return _MemoryLock(self._abspath(relpath), self)
257
def lock_write(self, relpath):
258
"""See Transport.lock_write()."""
259
return _MemoryLock(self._abspath(relpath), self)
163
raise NoSuchFile(relpath)
165
# def lock_read(self, relpath):
168
# def lock_write(self, relpath):
261
171
def _abspath(self, relpath):
262
172
"""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)
282
class _MemoryLock(object):
283
"""This makes a lock."""
285
def __init__(self, path, transport):
287
self.transport = transport
288
if self.path in self.transport._locks:
289
raise LockError('File %r already locked' % (self.path,))
290
self.transport._locks[self.path] = self
293
# Should this warn, or actually try to cleanup?
295
warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
299
del self.transport._locks[self.path]
300
self.transport = None
303
class MemoryServer(transport.Server):
173
if relpath.find('..') != -1:
174
raise AssertionError('relpath contains ..')
176
return self._cwd[:-1]
177
if relpath.endswith('/'):
178
relpath = relpath[:-1]
179
return self._cwd + relpath
182
class MemoryServer(Server):
304
183
"""Server for the MemoryTransport for testing with."""
306
def start_server(self):
307
self._dirs = {'/':None}
310
self._scheme = "memory+%s:///" % id(self)
311
def memory_factory(url):
312
from bzrlib.transport import memory
313
result = memory.MemoryTransport(url)
314
result._dirs = self._dirs
315
result._files = self._files
316
result._locks = self._locks
318
self._memory_factory = memory_factory
319
transport.register_transport(self._scheme, self._memory_factory)
186
"""See bzrlib.transport.Server.setUp."""
187
self._scheme = "memory+%s:" % id(self)
188
register_transport(self._scheme, MemoryTransport)
321
def stop_server(self):
191
"""See bzrlib.transport.Server.tearDown."""
322
192
# unregister this server
323
transport.unregister_transport(self._scheme, self._memory_factory)
325
194
def get_url(self):
326
195
"""See bzrlib.transport.Server.get_url."""
327
196
return self._scheme
329
def get_bogus_url(self):
330
raise NotImplementedError
333
199
def get_test_permutations():
334
200
"""Return the permutations to be used in testing."""