1
# Copyright (C) 2005, 2006 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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
16
"""Implementation of Transport that uses memory for its storage."""
26
from stat import S_IFREG, S_IFDIR
27
22
from cStringIO import StringIO
24
from bzrlib.trace import mutter
30
25
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
31
from bzrlib.trace import mutter
32
from bzrlib.transport import (Transport, register_transport, Server)
33
import bzrlib.urlutils as urlutils
26
from bzrlib.transport import Transport, register_transport, Server
37
28
class MemoryStat(object):
39
30
def __init__(self, size, is_dir, perms):
40
31
self.st_size = size
44
35
self.st_mode = S_IFREG | perms
48
37
self.st_mode = S_IFDIR | perms
54
43
def __init__(self, url=""):
55
44
"""Set the 'base' path where files will be stored."""
60
49
super(MemoryTransport, self).__init__(url)
61
split = url.find(':') + 3
62
self._scheme = url[:split]
63
self._cwd = url[split:]
64
# dictionaries from absolute path to file mode
65
self._dirs = {'/':None}
50
self._cwd = url[url.find(':') + 1:]
69
55
def clone(self, offset=None):
70
56
"""See Transport.clone()."""
71
path = self._combine_paths(self._cwd, offset)
72
if len(path) == 0 or path[-1] != '/':
74
url = self._scheme + path
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) + '/'
75
71
result = MemoryTransport(url)
76
72
result._dirs = self._dirs
77
73
result._files = self._files
81
77
def abspath(self, relpath):
82
78
"""See Transport.abspath()."""
83
# while a little slow, this is sufficiently fast to not matter in our
84
# current environment - XXX RBC 20060404 move the clone '..' handling
85
# into here and call abspath from clone
86
temp_t = self.clone(relpath)
87
if temp_t.base.count('/') == 3:
90
return temp_t.base[:-1]
79
return self.base[:-1] + self._abspath(relpath)[len(self._cwd) - 1:]
92
def append_file(self, relpath, f, mode=None):
93
"""See Transport.append_file()."""
81
def append(self, relpath, f):
82
"""See Transport.append()."""
94
83
_abspath = self._abspath(relpath)
95
84
self._check_parent(_abspath)
96
85
orig_content, orig_mode = self._files.get(_abspath, ("", None))
99
self._files[_abspath] = (orig_content + f.read(), mode)
100
return len(orig_content)
86
self._files[_abspath] = (orig_content + f.read(), orig_mode)
102
88
def _check_parent(self, _abspath):
103
89
dir = os.path.dirname(_abspath)
108
94
def has(self, relpath):
109
95
"""See Transport.has()."""
110
96
_abspath = self._abspath(relpath)
111
return (_abspath in self._files) or (_abspath in self._dirs)
97
return _abspath in self._files or _abspath in self._dirs
113
99
def delete(self, relpath):
114
100
"""See Transport.delete()."""
124
110
raise NoSuchFile(relpath)
125
111
return StringIO(self._files[_abspath][0])
127
def put_file(self, relpath, f, mode=None):
128
"""See Transport.put_file()."""
113
def put(self, relpath, f, mode=None):
114
"""See Transport.put()."""
129
115
_abspath = self._abspath(relpath)
130
116
self._check_parent(_abspath)
132
if type(bytes) is not str:
133
# Although not strictly correct, we raise UnicodeEncodeError to be
134
# compatible with other transports.
135
raise UnicodeEncodeError(
136
'undefined', bytes, 0, 1,
137
'put_file must be given a file of bytes, not unicode.')
138
self._files[_abspath] = (bytes, mode)
117
self._files[_abspath] = (f.read(), mode)
140
119
def mkdir(self, relpath, mode=None):
141
120
"""See Transport.mkdir()."""
152
131
def iter_files_recursive(self):
153
132
for file in self._files:
154
133
if file.startswith(self._cwd):
155
yield urlutils.escape(file[len(self._cwd):])
134
yield file[len(self._cwd):]
157
136
def list_dir(self, relpath):
158
137
"""See Transport.list_dir()."""
160
139
if _abspath != '/' and _abspath not in self._dirs:
161
140
raise NoSuchFile(relpath)
164
if not _abspath.endswith('/'):
167
for path_group in self._files, self._dirs:
168
for path in path_group:
169
if path.startswith(_abspath):
170
trailing = path[len(_abspath):]
171
if trailing and '/' not in trailing:
172
result.append(trailing)
173
return map(urlutils.escape, result)
175
def rename(self, rel_from, rel_to):
176
"""Rename a file or directory; fail if the destination exists"""
177
abs_from = self._abspath(rel_from)
178
abs_to = self._abspath(rel_to)
182
elif x.startswith(abs_from + '/'):
183
x = abs_to + x[len(abs_from):]
185
def do_renames(container):
186
for path in container:
187
new_path = replace(path)
189
if new_path in container:
190
raise FileExists(new_path)
191
container[new_path] = container[path]
193
do_renames(self._files)
194
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:])
196
155
def rmdir(self, relpath):
197
156
"""See Transport.rmdir."""
199
158
if _abspath in self._files:
200
159
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
201
160
for path in self._files:
202
if path.startswith(_abspath + '/'):
203
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
161
if path.startswith(_abspath):
162
self._translate_error(IOError(errno.EBUSY, relpath), relpath)
205
163
for path in self._dirs:
206
if path.startswith(_abspath + '/') and path != _abspath:
207
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)
208
166
if not _abspath in self._dirs:
209
167
raise NoSuchFile(relpath)
210
168
del self._dirs[_abspath]
215
173
if _abspath in self._files:
216
174
return MemoryStat(len(self._files[_abspath][0]), False,
217
175
self._files[_abspath][1])
177
return MemoryStat(0, True, None)
218
178
elif _abspath in self._dirs:
219
179
return MemoryStat(0, True, self._dirs[_abspath])
231
191
def _abspath(self, relpath):
232
192
"""Generate an internal absolute path."""
233
relpath = urlutils.unescape(relpath)
234
193
if relpath.find('..') != -1:
235
194
raise AssertionError('relpath contains ..')
238
if relpath[0] == '/':
240
195
if relpath == '.':
241
if (self._cwd == '/'):
243
196
return self._cwd[:-1]
244
197
if relpath.endswith('/'):
245
198
relpath = relpath[:-1]
277
230
"""See bzrlib.transport.Server.setUp."""
278
self._dirs = {'/':None}
281
self._scheme = "memory+%s:///" % id(self)
234
self._scheme = "memory+%s:" % id(self)
282
235
def memory_factory(url):
283
236
result = MemoryTransport(url)
284
237
result._dirs = self._dirs