1
# Copyright (C) 2005-2011, 2016 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
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.
23
from __future__ import absolute_import
31
from stat import S_IFREG, S_IFDIR, S_IFLNK
37
from ..errors import (
44
from ..transport import (
45
AppendBasedFileStream,
51
class MemoryStat(object):
53
def __init__(self, size, kind, perms):
58
self.st_mode = S_IFREG | perms
59
elif kind == 'directory':
62
self.st_mode = S_IFDIR | perms
63
elif kind == 'symlink':
64
self.st_mode = S_IFLNK | 0o644
66
raise AssertionError('unknown kind %r' % kind)
69
class MemoryTransport(transport.Transport):
70
"""This is an in memory file system for transient data storage."""
72
def __init__(self, url=""):
73
"""Set the 'base' path where files will be stored."""
78
super(MemoryTransport, self).__init__(url)
79
split = url.find(':') + 3
80
self._scheme = url[:split]
81
self._cwd = url[split:]
82
# dictionaries from absolute path to file mode
83
self._dirs = {'/': None}
88
def clone(self, offset=None):
89
"""See Transport.clone()."""
90
path = urlutils.URL._combine_paths(self._cwd, offset)
91
if len(path) == 0 or path[-1] != '/':
93
url = self._scheme + path
94
result = self.__class__(url)
95
result._dirs = self._dirs
96
result._symlinks = self._symlinks
97
result._files = self._files
98
result._locks = self._locks
101
def abspath(self, relpath):
102
"""See Transport.abspath()."""
103
# while a little slow, this is sufficiently fast to not matter in our
104
# current environment - XXX RBC 20060404 move the clone '..' handling
105
# into here and call abspath from clone
106
temp_t = self.clone(relpath)
107
if temp_t.base.count('/') == 3:
110
return temp_t.base[:-1]
112
def append_file(self, relpath, f, mode=None):
113
"""See Transport.append_file()."""
114
_abspath = self._abspath(relpath)
115
self._check_parent(_abspath)
116
orig_content, orig_mode = self._files.get(_abspath, (b"", None))
119
self._files[_abspath] = (orig_content + f.read(), mode)
120
return len(orig_content)
122
def _check_parent(self, _abspath):
123
dir = os.path.dirname(_abspath)
125
if dir not in self._dirs:
126
raise NoSuchFile(_abspath)
128
def has(self, relpath):
129
"""See Transport.has()."""
130
_abspath = self._abspath(relpath)
131
return ((_abspath in self._files)
132
or (_abspath in self._dirs)
133
or (_abspath in self._symlinks))
135
def delete(self, relpath):
136
"""See Transport.delete()."""
137
_abspath = self._abspath(relpath)
138
if _abspath not in self._files:
139
raise NoSuchFile(relpath)
140
del self._files[_abspath]
142
def external_url(self):
143
"""See breezy.transport.Transport.external_url."""
144
# MemoryTransport's are only accessible in-process
146
raise InProcessTransport(self)
148
def get(self, relpath):
149
"""See Transport.get()."""
150
_abspath = self._abspath(relpath)
151
if _abspath not in self._files:
152
if _abspath in self._dirs:
153
return LateReadError(relpath)
155
raise NoSuchFile(relpath)
156
return BytesIO(self._files[_abspath][0])
158
def put_file(self, relpath, f, mode=None):
159
"""See Transport.put_file()."""
160
_abspath = self._abspath(relpath)
161
self._check_parent(_abspath)
163
self._files[_abspath] = (raw_bytes, mode)
164
return len(raw_bytes)
166
def mkdir(self, relpath, mode=None):
167
"""See Transport.mkdir()."""
168
_abspath = self._abspath(relpath)
169
self._check_parent(_abspath)
170
if _abspath in self._dirs:
171
raise FileExists(relpath)
172
self._dirs[_abspath] = mode
174
def open_write_stream(self, relpath, mode=None):
175
"""See Transport.open_write_stream."""
176
self.put_bytes(relpath, b"", mode)
177
result = AppendBasedFileStream(self, relpath)
178
_file_streams[self.abspath(relpath)] = result
182
"""See Transport.listable."""
185
def iter_files_recursive(self):
186
for file in self._files:
187
if file.startswith(self._cwd):
188
yield urlutils.escape(file[len(self._cwd):])
190
def list_dir(self, relpath):
191
"""See Transport.list_dir()."""
192
_abspath = self._abspath(relpath)
193
if _abspath != '/' and _abspath not in self._dirs:
194
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(urlutils.escape(trailing))
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)
216
elif x.startswith(abs_from + '/'):
217
x = abs_to + x[len(abs_from):]
220
def do_renames(container):
222
for path in container:
223
new_path = replace(path)
225
if new_path in container:
226
raise FileExists(new_path)
227
renames.append((path, new_path))
228
for path, new_path in renames:
229
container[new_path] = container[path]
232
# If we modify the existing dicts, we're not atomic anymore and may
233
# fail differently depending on dict order. So work on copy, fail on
234
# error on only replace dicts if all goes well.
235
renamed_files = self._files.copy()
236
renamed_dirs = self._dirs.copy()
237
do_renames(renamed_files)
238
do_renames(renamed_dirs)
239
# We may have been cloned so modify in place
241
self._files.update(renamed_files)
243
self._dirs.update(renamed_dirs)
245
def rmdir(self, relpath):
246
"""See Transport.rmdir."""
247
_abspath = self._abspath(relpath)
248
if _abspath in self._files:
249
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
250
for path in self._files:
251
if path.startswith(_abspath + '/'):
252
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
254
for path in self._dirs:
255
if path.startswith(_abspath + '/') and path != _abspath:
256
self._translate_error(
257
IOError(errno.ENOTEMPTY, relpath), relpath)
258
if _abspath not in self._dirs:
259
raise NoSuchFile(relpath)
260
del self._dirs[_abspath]
262
def stat(self, relpath):
263
"""See Transport.stat()."""
264
_abspath = self._abspath(relpath)
265
if _abspath in self._files:
266
return MemoryStat(len(self._files[_abspath][0]), 'file',
267
self._files[_abspath][1])
268
elif _abspath in self._dirs:
269
return MemoryStat(0, 'directory', self._dirs[_abspath])
270
elif _abspath in self._symlinks:
271
return MemoryStat(0, 'symlink', 0)
273
raise NoSuchFile(_abspath)
275
def lock_read(self, relpath):
276
"""See Transport.lock_read()."""
277
return _MemoryLock(self._abspath(relpath), self)
279
def lock_write(self, relpath):
280
"""See Transport.lock_write()."""
281
return _MemoryLock(self._abspath(relpath), self)
283
def _abspath(self, relpath):
284
"""Generate an internal absolute path."""
285
relpath = urlutils.unescape(relpath)
286
if relpath[:1] == '/':
288
cwd_parts = self._cwd.split('/')
289
rel_parts = relpath.split('/')
291
for i in cwd_parts + rel_parts:
294
raise ValueError("illegal relpath %r under %r"
295
% (relpath, self._cwd))
297
elif i == '.' or i == '':
301
r = self._symlinks.get('/'.join(r), r)
302
return '/' + '/'.join(r)
304
def symlink(self, source, link_name):
305
"""Create a symlink pointing to source named link_name."""
306
_abspath = self._abspath(link_name)
307
self._check_parent(_abspath)
308
self._symlinks[_abspath] = source.split('/')
310
def readlink(self, link_name):
311
_abspath = self._abspath(link_name)
313
return '/'.join(self._symlinks[_abspath])
315
raise NoSuchFile(link_name)
318
class _MemoryLock(object):
319
"""This makes a lock."""
321
def __init__(self, path, transport):
323
self.transport = transport
324
if self.path in self.transport._locks:
325
raise LockError('File %r already locked' % (self.path,))
326
self.transport._locks[self.path] = self
329
del self.transport._locks[self.path]
330
self.transport = None
333
class MemoryServer(transport.Server):
334
"""Server for the MemoryTransport for testing with."""
336
def start_server(self):
337
self._dirs = {'/': None}
340
self._scheme = "memory+%s:///" % id(self)
342
def memory_factory(url):
344
result = memory.MemoryTransport(url)
345
result._dirs = self._dirs
346
result._files = self._files
347
result._locks = self._locks
349
self._memory_factory = memory_factory
350
transport.register_transport(self._scheme, self._memory_factory)
352
def stop_server(self):
353
# unregister this server
354
transport.unregister_transport(self._scheme, self._memory_factory)
357
"""See breezy.transport.Server.get_url."""
360
def get_bogus_url(self):
361
raise NotImplementedError
364
def get_test_permutations():
365
"""Return the permutations to be used in testing."""
366
return [(MemoryTransport, MemoryServer),