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.
20
28
from cStringIO import StringIO
22
30
from bzrlib.trace import mutter
23
from bzrlib.transport import Transport, \
24
TransportError, NoSuchFile, FileExists
31
from bzrlib.errors import TransportError, NoSuchFile, FileExists, LockError
32
from bzrlib.transport import Transport, register_transport, Server
27
35
class MemoryStat(object):
29
def __init__(self, size):
37
def __init__(self, size, is_dir, perms):
30
38
self.st_size = size
42
self.st_mode = S_IFREG | perms
46
self.st_mode = S_IFDIR | perms
33
49
class MemoryTransport(Transport):
34
"""This is the transport agent for local filesystem access."""
50
"""This is an in memory file system for transient data storage."""
52
def __init__(self, url=""):
37
53
"""Set the 'base' path where files will be stored."""
38
super(MemoryTransport, self).__init__('in-memory:')
58
super(MemoryTransport, self).__init__(url)
59
self._cwd = url[url.find(':') + 1:]
60
# dictionaries from absolute path to file mode
42
65
def clone(self, offset=None):
43
66
"""See Transport.clone()."""
67
if offset is None or offset == '':
69
segments = offset.split('/')
70
cwdsegments = self._cwd.split('/')[:-1]
72
segment = segments.pop(0)
76
if len(cwdsegments) > 1:
79
cwdsegments.append(segment)
80
url = self.base[:self.base.find(':') + 1] + '/'.join(cwdsegments) + '/'
81
result = MemoryTransport(url)
82
result._dirs = self._dirs
83
result._files = self._files
84
result._locks = self._locks
46
87
def abspath(self, relpath):
47
88
"""See Transport.abspath()."""
48
return self.base + relpath
89
# while a little slow, this is sufficiently fast to not matter in our
90
# current environment - XXX RBC 20060404 move the clone '..' handling
91
# into here and call abspath from clone
92
temp_t = self.clone(relpath)
93
if temp_t.base.count('/') == 1:
96
return temp_t.base[:-1]
50
def append(self, relpath, f):
98
def append(self, relpath, f, mode=None):
51
99
"""See Transport.append()."""
52
self._check_parent(relpath)
53
self._files[relpath] = self._files.get(relpath, "") + f.read()
100
_abspath = self._abspath(relpath)
101
self._check_parent(_abspath)
102
orig_content, orig_mode = self._files.get(_abspath, ("", None))
105
self._files[_abspath] = (orig_content + f.read(), mode)
106
return len(orig_content)
55
def _check_parent(self, relpath):
56
dir = os.path.dirname(relpath)
108
def _check_parent(self, _abspath):
109
dir = os.path.dirname(_abspath)
58
111
if not dir in self._dirs:
59
raise NoSuchFile(relpath)
112
raise NoSuchFile(_abspath)
61
114
def has(self, relpath):
62
115
"""See Transport.has()."""
63
return relpath in self._files
116
_abspath = self._abspath(relpath)
117
return _abspath in self._files or _abspath in self._dirs
119
def delete(self, relpath):
120
"""See Transport.delete()."""
121
_abspath = self._abspath(relpath)
122
if not _abspath in self._files:
123
raise NoSuchFile(relpath)
124
del self._files[_abspath]
65
126
def get(self, relpath):
66
127
"""See Transport.get()."""
67
if not relpath in self._files:
128
_abspath = self._abspath(relpath)
129
if not _abspath in self._files:
68
130
raise NoSuchFile(relpath)
69
return StringIO(self._files[relpath])
131
return StringIO(self._files[_abspath][0])
71
def put(self, relpath, f):
133
def put(self, relpath, f, mode=None):
72
134
"""See Transport.put()."""
73
self._check_parent(relpath)
74
self._files[relpath] = f.read()
135
_abspath = self._abspath(relpath)
136
self._check_parent(_abspath)
137
self._files[_abspath] = (f.read(), mode)
76
def mkdir(self, relpath):
139
def mkdir(self, relpath, mode=None):
77
140
"""See Transport.mkdir()."""
78
self._check_parent(relpath)
79
if relpath in self._dirs:
141
_abspath = self._abspath(relpath)
142
self._check_parent(_abspath)
143
if _abspath in self._dirs:
80
144
raise FileExists(relpath)
81
self._dirs.add(relpath)
145
self._dirs[_abspath]=mode
83
147
def listable(self):
84
148
"""See Transport.listable."""
87
151
def iter_files_recursive(self):
88
return iter(self._files)
90
# def list_dir(self, relpath):
152
for file in self._files:
153
if file.startswith(self._cwd):
154
yield file[len(self._cwd):]
156
def list_dir(self, relpath):
157
"""See Transport.list_dir()."""
158
_abspath = self._abspath(relpath)
159
if _abspath != '/' and _abspath not in self._dirs:
160
raise NoSuchFile(relpath)
162
for path in self._files:
163
if (path.startswith(_abspath) and
164
path[len(_abspath) + 1:].find('/') == -1 and
165
len(path) > len(_abspath)):
166
result.append(path[len(_abspath) + 1:])
167
for path in self._dirs:
168
if (path.startswith(_abspath) and
169
path[len(_abspath) + 1:].find('/') == -1 and
170
len(path) > len(_abspath) and
171
path[len(_abspath)] == '/'):
172
result.append(path[len(_abspath) + 1:])
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)
196
def rmdir(self, relpath):
197
"""See Transport.rmdir."""
198
_abspath = self._abspath(relpath)
199
if _abspath in self._files:
200
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
201
for path in self._files:
202
if path.startswith(_abspath):
203
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
205
for path in self._dirs:
206
if path.startswith(_abspath) and path != _abspath:
207
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
208
if not _abspath in self._dirs:
209
raise NoSuchFile(relpath)
210
del self._dirs[_abspath]
93
212
def stat(self, relpath):
94
213
"""See Transport.stat()."""
95
return MemoryStat(len(self._files[relpath]))
97
# def lock_read(self, relpath):
100
# def lock_write(self, relpath):
214
_abspath = self._abspath(relpath)
215
if _abspath in self._files:
216
return MemoryStat(len(self._files[_abspath][0]), False,
217
self._files[_abspath][1])
219
return MemoryStat(0, True, None)
220
elif _abspath in self._dirs:
221
return MemoryStat(0, True, self._dirs[_abspath])
223
raise NoSuchFile(_abspath)
225
def lock_read(self, relpath):
226
"""See Transport.lock_read()."""
227
return _MemoryLock(self._abspath(relpath), self)
229
def lock_write(self, relpath):
230
"""See Transport.lock_write()."""
231
return _MemoryLock(self._abspath(relpath), self)
233
def _abspath(self, relpath):
234
"""Generate an internal absolute path."""
235
if relpath.find('..') != -1:
236
raise AssertionError('relpath contains ..')
238
return self._cwd[:-1]
239
if relpath.endswith('/'):
240
relpath = relpath[:-1]
241
if relpath.startswith('./'):
242
relpath = relpath[2:]
243
return self._cwd + relpath
246
class _MemoryLock(object):
247
"""This makes a lock."""
249
def __init__(self, path, transport):
250
assert isinstance(transport, MemoryTransport)
252
self.transport = transport
253
if self.path in self.transport._locks:
254
raise LockError('File %r already locked' % (self.path,))
255
self.transport._locks[self.path] = self
258
# Should this warn, or actually try to cleanup?
260
warn("MemoryLock %r not explicitly unlocked" % (self.path,))
264
del self.transport._locks[self.path]
265
self.transport = None
268
class MemoryServer(Server):
269
"""Server for the MemoryTransport for testing with."""
272
"""See bzrlib.transport.Server.setUp."""
276
self._scheme = "memory+%s:" % id(self)
277
def memory_factory(url):
278
result = MemoryTransport(url)
279
result._dirs = self._dirs
280
result._files = self._files
281
result._locks = self._locks
283
register_transport(self._scheme, memory_factory)
286
"""See bzrlib.transport.Server.tearDown."""
287
# unregister this server
290
"""See bzrlib.transport.Server.get_url."""
294
def get_test_permutations():
295
"""Return the permutations to be used in testing."""
296
return [(MemoryTransport, MemoryServer),