1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005, 2006 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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
"""Implementation of Transport that uses memory for its storage."""
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
20
27
from cStringIO import StringIO
30
from bzrlib.errors import (
22
37
from bzrlib.trace import mutter
23
from bzrlib.errors import TransportError, NoSuchFile, FileExists
24
from bzrlib.transport import Transport
38
from bzrlib.transport import (
39
AppendBasedFileStream,
46
import bzrlib.urlutils as urlutils
26
50
class MemoryStat(object):
28
def __init__(self, size):
52
def __init__(self, size, is_dir, perms):
29
53
self.st_size = size
57
self.st_mode = S_IFREG | perms
61
self.st_mode = S_IFDIR | perms
32
64
class MemoryTransport(Transport):
33
"""This is the transport agent for local filesystem access."""
65
"""This is an in memory file system for transient data storage."""
67
def __init__(self, url=""):
36
68
"""Set the 'base' path where files will be stored."""
37
super(MemoryTransport, self).__init__('in-memory:')
73
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}
41
82
def clone(self, offset=None):
42
83
"""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 = MemoryTransport(url)
89
result._dirs = self._dirs
90
result._files = self._files
91
result._locks = self._locks
45
94
def abspath(self, relpath):
46
95
"""See Transport.abspath()."""
47
return self.base + relpath
49
def append(self, relpath, f):
50
"""See Transport.append()."""
51
self._check_parent(relpath)
52
self._files[relpath] = self._files.get(relpath, "") + f.read()
54
def _check_parent(self, relpath):
55
dir = os.path.dirname(relpath)
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]
105
def append_file(self, relpath, f, mode=None):
106
"""See Transport.append_file()."""
107
_abspath = self._abspath(relpath)
108
self._check_parent(_abspath)
109
orig_content, orig_mode = self._files.get(_abspath, ("", None))
112
self._files[_abspath] = (orig_content + f.read(), mode)
113
return len(orig_content)
115
def _check_parent(self, _abspath):
116
dir = os.path.dirname(_abspath)
57
118
if not dir in self._dirs:
58
raise NoSuchFile(relpath)
119
raise NoSuchFile(_abspath)
60
121
def has(self, relpath):
61
122
"""See Transport.has()."""
62
return relpath in self._files
123
_abspath = self._abspath(relpath)
124
return (_abspath in self._files) or (_abspath in self._dirs)
126
def delete(self, relpath):
127
"""See Transport.delete()."""
128
_abspath = self._abspath(relpath)
129
if not _abspath in self._files:
130
raise NoSuchFile(relpath)
131
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)
64
139
def get(self, relpath):
65
140
"""See Transport.get()."""
66
if not relpath in self._files:
67
raise NoSuchFile(relpath)
68
return StringIO(self._files[relpath])
141
_abspath = self._abspath(relpath)
142
if not _abspath in self._files:
143
if _abspath in self._dirs:
144
return LateReadError(relpath)
146
raise NoSuchFile(relpath)
147
return StringIO(self._files[_abspath][0])
70
def put(self, relpath, f, mode=None):
71
"""See Transport.put()."""
72
self._check_parent(relpath)
73
self._files[relpath] = f.read()
149
def put_file(self, relpath, f, mode=None):
150
"""See Transport.put_file()."""
151
_abspath = self._abspath(relpath)
152
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)
75
163
def mkdir(self, relpath, mode=None):
76
164
"""See Transport.mkdir()."""
77
self._check_parent(relpath)
78
if relpath in self._dirs:
165
_abspath = self._abspath(relpath)
166
self._check_parent(_abspath)
167
if _abspath in self._dirs:
79
168
raise FileExists(relpath)
80
self._dirs.add(relpath)
169
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
82
178
def listable(self):
83
179
"""See Transport.listable."""
86
182
def iter_files_recursive(self):
87
return iter(self._files)
89
# def list_dir(self, relpath):
183
for file in self._files:
184
if file.startswith(self._cwd):
185
yield urlutils.escape(file[len(self._cwd):])
187
def list_dir(self, relpath):
188
"""See Transport.list_dir()."""
189
_abspath = self._abspath(relpath)
190
if _abspath != '/' and _abspath not in self._dirs:
191
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
for path in self._files:
232
if path.startswith(_abspath + '/'):
233
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
235
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]
92
242
def stat(self, relpath):
93
243
"""See Transport.stat()."""
94
return MemoryStat(len(self._files[relpath]))
96
# def lock_read(self, relpath):
99
# def lock_write(self, relpath):
244
_abspath = self._abspath(relpath)
245
if _abspath in self._files:
246
return MemoryStat(len(self._files[_abspath][0]), False,
247
self._files[_abspath][1])
248
elif _abspath in self._dirs:
249
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)
261
def _abspath(self, relpath):
262
"""Generate an internal absolute path."""
263
relpath = urlutils.unescape(relpath)
266
if relpath[0] == '/':
268
cwd_parts = self._cwd.split('/')
269
rel_parts = relpath.split('/')
271
for i in cwd_parts + rel_parts:
274
raise ValueError("illegal relpath %r under %r"
275
% relpath, self._cwd)
277
elif i == '.' or i == '':
281
return '/' + '/'.join(r)
284
class _MemoryLock(object):
285
"""This makes a lock."""
287
def __init__(self, path, transport):
288
assert isinstance(transport, MemoryTransport)
290
self.transport = transport
291
if self.path in self.transport._locks:
292
raise LockError('File %r already locked' % (self.path,))
293
self.transport._locks[self.path] = self
296
# Should this warn, or actually try to cleanup?
298
warnings.warn("MemoryLock %r not explicitly unlocked" % (self.path,))
302
del self.transport._locks[self.path]
303
self.transport = None
306
class MemoryServer(Server):
307
"""Server for the MemoryTransport for testing with."""
310
"""See bzrlib.transport.Server.setUp."""
311
self._dirs = {'/':None}
314
self._scheme = "memory+%s:///" % id(self)
315
def memory_factory(url):
316
result = MemoryTransport(url)
317
result._dirs = self._dirs
318
result._files = self._files
319
result._locks = self._locks
321
register_transport(self._scheme, memory_factory)
324
"""See bzrlib.transport.Server.tearDown."""
325
# unregister this server
328
"""See bzrlib.transport.Server.get_url."""
332
def get_test_permutations():
333
"""Return the permutations to be used in testing."""
334
return [(MemoryTransport, MemoryServer),