1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005-2010 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
"""Implementation of Transport that uses memory for its storage."""
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.
23
from __future__ import absolute_import
27
from stat import S_IFREG, S_IFDIR
20
28
from cStringIO import StringIO
22
from bzrlib.trace import mutter
23
from bzrlib.errors import TransportError, NoSuchFile, FileExists
24
from bzrlib.transport import Transport
34
from bzrlib.errors import (
40
from bzrlib.transport import (
41
AppendBasedFileStream,
26
48
class MemoryStat(object):
28
def __init__(self, size):
50
def __init__(self, size, is_dir, perms):
29
51
self.st_size = size
32
class MemoryTransport(Transport):
33
"""This is the transport agent for local filesystem access."""
55
self.st_mode = S_IFREG | perms
59
self.st_mode = S_IFDIR | perms
62
class MemoryTransport(transport.Transport):
63
"""This is an in memory file system for transient data storage."""
65
def __init__(self, url=""):
36
66
"""Set the 'base' path where files will be stored."""
37
super(MemoryTransport, self).__init__('in-memory:')
71
super(MemoryTransport, self).__init__(url)
72
split = url.find(':') + 3
73
self._scheme = url[:split]
74
self._cwd = url[split:]
75
# dictionaries from absolute path to file mode
76
self._dirs = {'/':None}
41
80
def clone(self, offset=None):
42
81
"""See Transport.clone()."""
82
path = urlutils.URL._combine_paths(self._cwd, offset)
83
if len(path) == 0 or path[-1] != '/':
85
url = self._scheme + path
86
result = self.__class__(url)
87
result._dirs = self._dirs
88
result._files = self._files
89
result._locks = self._locks
45
92
def abspath(self, relpath):
46
93
"""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)
94
# while a little slow, this is sufficiently fast to not matter in our
95
# current environment - XXX RBC 20060404 move the clone '..' handling
96
# into here and call abspath from clone
97
temp_t = self.clone(relpath)
98
if temp_t.base.count('/') == 3:
101
return temp_t.base[:-1]
103
def append_file(self, relpath, f, mode=None):
104
"""See Transport.append_file()."""
105
_abspath = self._abspath(relpath)
106
self._check_parent(_abspath)
107
orig_content, orig_mode = self._files.get(_abspath, ("", None))
110
self._files[_abspath] = (orig_content + f.read(), mode)
111
return len(orig_content)
113
def _check_parent(self, _abspath):
114
dir = os.path.dirname(_abspath)
57
116
if not dir in self._dirs:
58
raise NoSuchFile(relpath)
117
raise NoSuchFile(_abspath)
60
119
def has(self, relpath):
61
120
"""See Transport.has()."""
62
return relpath in self._files
121
_abspath = self._abspath(relpath)
122
return (_abspath in self._files) or (_abspath in self._dirs)
124
def delete(self, relpath):
125
"""See Transport.delete()."""
126
_abspath = self._abspath(relpath)
127
if not _abspath in self._files:
128
raise NoSuchFile(relpath)
129
del self._files[_abspath]
131
def external_url(self):
132
"""See bzrlib.transport.Transport.external_url."""
133
# MemoryTransport's are only accessible in-process
135
raise InProcessTransport(self)
64
137
def get(self, relpath):
65
138
"""See Transport.get()."""
66
if not relpath in self._files:
67
raise NoSuchFile(relpath)
68
return StringIO(self._files[relpath])
139
_abspath = self._abspath(relpath)
140
if not _abspath in self._files:
141
if _abspath in self._dirs:
142
return LateReadError(relpath)
144
raise NoSuchFile(relpath)
145
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()
147
def put_file(self, relpath, f, mode=None):
148
"""See Transport.put_file()."""
149
_abspath = self._abspath(relpath)
150
self._check_parent(_abspath)
152
if type(bytes) is not str:
153
# Although not strictly correct, we raise UnicodeEncodeError to be
154
# compatible with other transports.
155
raise UnicodeEncodeError(
156
'undefined', bytes, 0, 1,
157
'put_file must be given a file of bytes, not unicode.')
158
self._files[_abspath] = (bytes, mode)
75
161
def mkdir(self, relpath, mode=None):
76
162
"""See Transport.mkdir()."""
77
self._check_parent(relpath)
78
if relpath in self._dirs:
163
_abspath = self._abspath(relpath)
164
self._check_parent(_abspath)
165
if _abspath in self._dirs:
79
166
raise FileExists(relpath)
80
self._dirs.add(relpath)
167
self._dirs[_abspath]=mode
169
def open_write_stream(self, relpath, mode=None):
170
"""See Transport.open_write_stream."""
171
self.put_bytes(relpath, "", mode)
172
result = AppendBasedFileStream(self, relpath)
173
_file_streams[self.abspath(relpath)] = result
82
176
def listable(self):
83
177
"""See Transport.listable."""
86
180
def iter_files_recursive(self):
87
return iter(self._files)
89
# def list_dir(self, relpath):
181
for file in self._files:
182
if file.startswith(self._cwd):
183
yield urlutils.escape(file[len(self._cwd):])
185
def list_dir(self, relpath):
186
"""See Transport.list_dir()."""
187
_abspath = self._abspath(relpath)
188
if _abspath != '/' and _abspath not in self._dirs:
189
raise NoSuchFile(relpath)
192
if not _abspath.endswith('/'):
195
for path_group in self._files, self._dirs:
196
for path in path_group:
197
if path.startswith(_abspath):
198
trailing = path[len(_abspath):]
199
if trailing and '/' not in trailing:
200
result.append(trailing)
201
return map(urlutils.escape, result)
203
def rename(self, rel_from, rel_to):
204
"""Rename a file or directory; fail if the destination exists"""
205
abs_from = self._abspath(rel_from)
206
abs_to = self._abspath(rel_to)
210
elif x.startswith(abs_from + '/'):
211
x = abs_to + x[len(abs_from):]
213
def do_renames(container):
214
for path in container:
215
new_path = replace(path)
217
if new_path in container:
218
raise FileExists(new_path)
219
container[new_path] = container[path]
221
do_renames(self._files)
222
do_renames(self._dirs)
224
def rmdir(self, relpath):
225
"""See Transport.rmdir."""
226
_abspath = self._abspath(relpath)
227
if _abspath in self._files:
228
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
229
for path in self._files:
230
if path.startswith(_abspath + '/'):
231
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
233
for path in self._dirs:
234
if path.startswith(_abspath + '/') and path != _abspath:
235
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
236
if not _abspath in self._dirs:
237
raise NoSuchFile(relpath)
238
del self._dirs[_abspath]
92
240
def stat(self, relpath):
93
241
"""See Transport.stat()."""
94
return MemoryStat(len(self._files[relpath]))
96
# def lock_read(self, relpath):
99
# def lock_write(self, relpath):
242
_abspath = self._abspath(relpath)
243
if _abspath in self._files:
244
return MemoryStat(len(self._files[_abspath][0]), False,
245
self._files[_abspath][1])
246
elif _abspath in self._dirs:
247
return MemoryStat(0, True, self._dirs[_abspath])
249
raise NoSuchFile(_abspath)
251
def lock_read(self, relpath):
252
"""See Transport.lock_read()."""
253
return _MemoryLock(self._abspath(relpath), self)
255
def lock_write(self, relpath):
256
"""See Transport.lock_write()."""
257
return _MemoryLock(self._abspath(relpath), self)
259
def _abspath(self, relpath):
260
"""Generate an internal absolute path."""
261
relpath = urlutils.unescape(relpath)
262
if relpath[:1] == '/':
264
cwd_parts = self._cwd.split('/')
265
rel_parts = relpath.split('/')
267
for i in cwd_parts + rel_parts:
270
raise ValueError("illegal relpath %r under %r"
271
% (relpath, self._cwd))
273
elif i == '.' or i == '':
277
return '/' + '/'.join(r)
280
class _MemoryLock(object):
281
"""This makes a lock."""
283
def __init__(self, path, transport):
285
self.transport = transport
286
if self.path in self.transport._locks:
287
raise LockError('File %r already locked' % (self.path,))
288
self.transport._locks[self.path] = self
291
del self.transport._locks[self.path]
292
self.transport = None
295
class MemoryServer(transport.Server):
296
"""Server for the MemoryTransport for testing with."""
298
def start_server(self):
299
self._dirs = {'/':None}
302
self._scheme = "memory+%s:///" % id(self)
303
def memory_factory(url):
304
from bzrlib.transport import memory
305
result = memory.MemoryTransport(url)
306
result._dirs = self._dirs
307
result._files = self._files
308
result._locks = self._locks
310
self._memory_factory = memory_factory
311
transport.register_transport(self._scheme, self._memory_factory)
313
def stop_server(self):
314
# unregister this server
315
transport.unregister_transport(self._scheme, self._memory_factory)
318
"""See bzrlib.transport.Server.get_url."""
321
def get_bogus_url(self):
322
raise NotImplementedError
325
def get_test_permutations():
326
"""Return the permutations to be used in testing."""
327
return [(MemoryTransport, MemoryServer),