1
# Copyright (C) 2005, 2006, 2007, 2008, 2009, 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
33
from bzrlib.errors import (
40
24
from bzrlib.trace import mutter
41
from bzrlib.transport import (
42
AppendBasedFileStream,
25
from bzrlib.errors import TransportError, NoSuchFile, FileExists
26
from bzrlib.transport import Transport, register_transport, Server
53
28
class MemoryStat(object):
55
30
def __init__(self, size, is_dir, perms):
56
31
self.st_size = size
60
35
self.st_mode = S_IFREG | perms
64
37
self.st_mode = S_IFDIR | perms
67
40
class MemoryTransport(Transport):
68
"""This is an in memory file system for transient data storage."""
41
"""This is the transport agent for local filesystem access."""
70
43
def __init__(self, url=""):
71
44
"""Set the 'base' path where files will be stored."""
76
49
super(MemoryTransport, self).__init__(url)
77
split = url.find(':') + 3
78
self._scheme = url[:split]
79
self._cwd = url[split:]
80
# dictionaries from absolute path to file mode
81
self._dirs = {'/':None}
50
self._cwd = url[url.find(':') + 1:]
85
54
def clone(self, offset=None):
86
55
"""See Transport.clone()."""
87
path = self._combine_paths(self._cwd, offset)
88
if len(path) == 0 or path[-1] != '/':
90
url = self._scheme + path
91
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)
92
71
result._dirs = self._dirs
93
72
result._files = self._files
94
result._locks = self._locks
97
75
def abspath(self, relpath):
98
76
"""See Transport.abspath()."""
99
# while a little slow, this is sufficiently fast to not matter in our
100
# current environment - XXX RBC 20060404 move the clone '..' handling
101
# into here and call abspath from clone
102
temp_t = self.clone(relpath)
103
if temp_t.base.count('/') == 3:
106
return temp_t.base[:-1]
77
return self.base[:-1] + self._abspath(relpath)
108
def append_file(self, relpath, f, mode=None):
109
"""See Transport.append_file()."""
79
def append(self, relpath, f):
80
"""See Transport.append()."""
110
81
_abspath = self._abspath(relpath)
111
82
self._check_parent(_abspath)
112
83
orig_content, orig_mode = self._files.get(_abspath, ("", None))
115
self._files[_abspath] = (orig_content + f.read(), mode)
116
return len(orig_content)
84
self._files[_abspath] = (orig_content + f.read(), orig_mode)
118
86
def _check_parent(self, _abspath):
119
87
dir = os.path.dirname(_abspath)
133
101
raise NoSuchFile(relpath)
134
102
del self._files[_abspath]
136
def external_url(self):
137
"""See bzrlib.transport.Transport.external_url."""
138
# MemoryTransport's are only accessible in-process
140
raise InProcessTransport(self)
142
104
def get(self, relpath):
143
105
"""See Transport.get()."""
144
106
_abspath = self._abspath(relpath)
145
107
if not _abspath in self._files:
146
if _abspath in self._dirs:
147
return LateReadError(relpath)
149
raise NoSuchFile(relpath)
108
raise NoSuchFile(relpath)
150
109
return StringIO(self._files[_abspath][0])
152
def put_file(self, relpath, f, mode=None):
153
"""See Transport.put_file()."""
111
def put(self, relpath, f, mode=None):
112
"""See Transport.put()."""
154
113
_abspath = self._abspath(relpath)
155
114
self._check_parent(_abspath)
157
if type(bytes) is not str:
158
# Although not strictly correct, we raise UnicodeEncodeError to be
159
# compatible with other transports.
160
raise UnicodeEncodeError(
161
'undefined', bytes, 0, 1,
162
'put_file must be given a file of bytes, not unicode.')
163
self._files[_abspath] = (bytes, mode)
115
self._files[_abspath] = (f.read(), mode)
166
117
def mkdir(self, relpath, mode=None):
167
118
"""See Transport.mkdir()."""
185
129
def iter_files_recursive(self):
186
130
for file in self._files:
187
131
if file.startswith(self._cwd):
188
yield urlutils.escape(file[len(self._cwd):])
132
yield file[len(self._cwd):]
190
134
def list_dir(self, relpath):
191
135
"""See Transport.list_dir()."""
192
136
_abspath = self._abspath(relpath)
193
137
if _abspath != '/' and _abspath not in self._dirs:
194
138
raise NoSuchFile(relpath)
197
if not _abspath.endswith('/'):
200
for path_group in self._files, self._dirs:
201
for path in path_group:
202
if path.startswith(_abspath):
203
trailing = path[len(_abspath):]
204
if trailing and '/' not in trailing:
205
result.append(trailing)
206
return map(urlutils.escape, result)
208
def rename(self, rel_from, rel_to):
209
"""Rename a file or directory; fail if the destination exists"""
210
abs_from = self._abspath(rel_from)
211
abs_to = self._abspath(rel_to)
215
elif x.startswith(abs_from + '/'):
216
x = abs_to + x[len(abs_from):]
218
def do_renames(container):
219
for path in container:
220
new_path = replace(path)
222
if new_path in container:
223
raise FileExists(new_path)
224
container[new_path] = container[path]
226
do_renames(self._files)
227
do_renames(self._dirs)
229
def rmdir(self, relpath):
230
"""See Transport.rmdir."""
231
_abspath = self._abspath(relpath)
232
if _abspath in self._files:
233
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
234
140
for path in self._files:
235
if path.startswith(_abspath + '/'):
236
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:])
238
145
for path in self._dirs:
239
if path.startswith(_abspath + '/') and path != _abspath:
240
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
241
if not _abspath in self._dirs:
242
raise NoSuchFile(relpath)
243
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:])
245
152
def stat(self, relpath):
246
153
"""See Transport.stat()."""
247
154
_abspath = self._abspath(relpath)
248
155
if _abspath in self._files:
249
return MemoryStat(len(self._files[_abspath][0]), False,
156
return MemoryStat(len(self._files[_abspath][0]), False,
250
157
self._files[_abspath][1])
159
return MemoryStat(0, True, None)
251
160
elif _abspath in self._dirs:
252
161
return MemoryStat(0, True, self._dirs[_abspath])
254
raise NoSuchFile(_abspath)
256
def lock_read(self, relpath):
257
"""See Transport.lock_read()."""
258
return _MemoryLock(self._abspath(relpath), self)
260
def lock_write(self, relpath):
261
"""See Transport.lock_write()."""
262
return _MemoryLock(self._abspath(relpath), self)
163
raise NoSuchFile(relpath)
165
# def lock_read(self, relpath):
168
# def lock_write(self, relpath):
264
171
def _abspath(self, relpath):
265
172
"""Generate an internal absolute path."""
266
relpath = urlutils.unescape(relpath)
267
if relpath[:1] == '/':
269
cwd_parts = self._cwd.split('/')
270
rel_parts = relpath.split('/')
272
for i in cwd_parts + rel_parts:
275
raise ValueError("illegal relpath %r under %r"
276
% (relpath, self._cwd))
278
elif i == '.' or i == '':
282
return '/' + '/'.join(r)
285
class _MemoryLock(object):
286
"""This makes a lock."""
288
def __init__(self, path, transport):
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
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
306
182
class MemoryServer(Server):
307
183
"""Server for the MemoryTransport for testing with."""
309
def start_server(self):
310
self._dirs = {'/':None}
313
self._scheme = "memory+%s:///" % id(self)
314
def memory_factory(url):
315
result = MemoryTransport(url)
316
result._dirs = self._dirs
317
result._files = self._files
318
result._locks = self._locks
320
self._memory_factory = memory_factory
321
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)
323
def stop_server(self):
191
"""See bzrlib.transport.Server.tearDown."""
324
192
# unregister this server
325
unregister_transport(self._scheme, self._memory_factory)
327
194
def get_url(self):
328
195
"""See bzrlib.transport.Server.get_url."""