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
27
from stat import S_IFREG, S_IFDIR
33
from ..errors import (
39
from ..sixish import (
42
from ..transport import (
43
AppendBasedFileStream,
50
class MemoryStat(object):
52
def __init__(self, size, is_dir, perms):
57
self.st_mode = S_IFREG | perms
61
self.st_mode = S_IFDIR | perms
64
class MemoryTransport(transport.Transport):
65
"""This is an in memory file system for transient data storage."""
67
def __init__(self, url=""):
68
"""Set the 'base' path where files will be stored."""
73
super(MemoryTransport, self).__init__(url)
74
split = url.find(':') + 3
75
self._scheme = url[:split]
76
self._cwd = url[split:]
77
# dictionaries from absolute path to file mode
78
self._dirs = {'/':None}
82
def clone(self, offset=None):
83
"""See Transport.clone()."""
84
path = urlutils.URL._combine_paths(self._cwd, offset)
85
if len(path) == 0 or path[-1] != '/':
87
url = self._scheme + path
88
result = self.__class__(url)
89
result._dirs = self._dirs
90
result._files = self._files
91
result._locks = self._locks
94
def abspath(self, relpath):
95
"""See Transport.abspath()."""
96
# while a little slow, this is sufficiently fast to not matter in our
97
# current environment - XXX RBC 20060404 move the clone '..' handling
98
# into here and call abspath from clone
99
temp_t = self.clone(relpath)
100
if temp_t.base.count('/') == 3:
103
return temp_t.base[:-1]
105
def append_file(self, relpath, f, mode=None):
106
"""See Transport.append_file()."""
107
_abspath = self._abspath(relpath)
108
self._check_parent(_abspath)
109
orig_content, orig_mode = self._files.get(_abspath, ("", None))
112
self._files[_abspath] = (orig_content + f.read(), mode)
113
return len(orig_content)
115
def _check_parent(self, _abspath):
116
dir = os.path.dirname(_abspath)
118
if not dir in self._dirs:
119
raise NoSuchFile(_abspath)
121
def has(self, relpath):
122
"""See Transport.has()."""
123
_abspath = self._abspath(relpath)
124
return (_abspath in self._files) or (_abspath in self._dirs)
126
def delete(self, relpath):
127
"""See Transport.delete()."""
128
_abspath = self._abspath(relpath)
129
if not _abspath in self._files:
130
raise NoSuchFile(relpath)
131
del self._files[_abspath]
133
def external_url(self):
134
"""See breezy.transport.Transport.external_url."""
135
# MemoryTransport's are only accessible in-process
137
raise InProcessTransport(self)
139
def get(self, relpath):
140
"""See Transport.get()."""
141
_abspath = self._abspath(relpath)
142
if not _abspath in self._files:
143
if _abspath in self._dirs:
144
return LateReadError(relpath)
146
raise NoSuchFile(relpath)
147
return BytesIO(self._files[_abspath][0])
149
def put_file(self, relpath, f, mode=None):
150
"""See Transport.put_file()."""
151
_abspath = self._abspath(relpath)
152
self._check_parent(_abspath)
154
self._files[_abspath] = (raw_bytes, mode)
155
return len(raw_bytes)
157
def mkdir(self, relpath, mode=None):
158
"""See Transport.mkdir()."""
159
_abspath = self._abspath(relpath)
160
self._check_parent(_abspath)
161
if _abspath in self._dirs:
162
raise FileExists(relpath)
163
self._dirs[_abspath]=mode
165
def open_write_stream(self, relpath, mode=None):
166
"""See Transport.open_write_stream."""
167
self.put_bytes(relpath, "", mode)
168
result = AppendBasedFileStream(self, relpath)
169
_file_streams[self.abspath(relpath)] = result
173
"""See Transport.listable."""
176
def iter_files_recursive(self):
177
for file in self._files:
178
if file.startswith(self._cwd):
179
yield urlutils.escape(file[len(self._cwd):])
181
def list_dir(self, relpath):
182
"""See Transport.list_dir()."""
183
_abspath = self._abspath(relpath)
184
if _abspath != '/' and _abspath not in self._dirs:
185
raise NoSuchFile(relpath)
188
if not _abspath.endswith('/'):
191
for path_group in self._files, self._dirs:
192
for path in path_group:
193
if path.startswith(_abspath):
194
trailing = path[len(_abspath):]
195
if trailing and '/' not in trailing:
196
result.append(urlutils.escape(trailing))
199
def rename(self, rel_from, rel_to):
200
"""Rename a file or directory; fail if the destination exists"""
201
abs_from = self._abspath(rel_from)
202
abs_to = self._abspath(rel_to)
206
elif x.startswith(abs_from + '/'):
207
x = abs_to + x[len(abs_from):]
209
def do_renames(container):
210
for path in container:
211
new_path = replace(path)
213
if new_path in container:
214
raise FileExists(new_path)
215
container[new_path] = container[path]
217
do_renames(self._files)
218
do_renames(self._dirs)
220
def rmdir(self, relpath):
221
"""See Transport.rmdir."""
222
_abspath = self._abspath(relpath)
223
if _abspath in self._files:
224
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
225
for path in self._files:
226
if path.startswith(_abspath + '/'):
227
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
229
for path in self._dirs:
230
if path.startswith(_abspath + '/') and path != _abspath:
231
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
232
if not _abspath in self._dirs:
233
raise NoSuchFile(relpath)
234
del self._dirs[_abspath]
236
def stat(self, relpath):
237
"""See Transport.stat()."""
238
_abspath = self._abspath(relpath)
239
if _abspath in self._files:
240
return MemoryStat(len(self._files[_abspath][0]), False,
241
self._files[_abspath][1])
242
elif _abspath in self._dirs:
243
return MemoryStat(0, True, self._dirs[_abspath])
245
raise NoSuchFile(_abspath)
247
def lock_read(self, relpath):
248
"""See Transport.lock_read()."""
249
return _MemoryLock(self._abspath(relpath), self)
251
def lock_write(self, relpath):
252
"""See Transport.lock_write()."""
253
return _MemoryLock(self._abspath(relpath), self)
255
def _abspath(self, relpath):
256
"""Generate an internal absolute path."""
257
relpath = urlutils.unescape(relpath)
258
if relpath[:1] == '/':
260
cwd_parts = self._cwd.split('/')
261
rel_parts = relpath.split('/')
263
for i in cwd_parts + rel_parts:
266
raise ValueError("illegal relpath %r under %r"
267
% (relpath, self._cwd))
269
elif i == '.' or i == '':
273
return '/' + '/'.join(r)
276
class _MemoryLock(object):
277
"""This makes a lock."""
279
def __init__(self, path, transport):
281
self.transport = transport
282
if self.path in self.transport._locks:
283
raise LockError('File %r already locked' % (self.path,))
284
self.transport._locks[self.path] = self
287
del self.transport._locks[self.path]
288
self.transport = None
291
class MemoryServer(transport.Server):
292
"""Server for the MemoryTransport for testing with."""
294
def start_server(self):
295
self._dirs = {'/':None}
298
self._scheme = "memory+%s:///" % id(self)
299
def memory_factory(url):
301
result = memory.MemoryTransport(url)
302
result._dirs = self._dirs
303
result._files = self._files
304
result._locks = self._locks
306
self._memory_factory = memory_factory
307
transport.register_transport(self._scheme, self._memory_factory)
309
def stop_server(self):
310
# unregister this server
311
transport.unregister_transport(self._scheme, self._memory_factory)
314
"""See breezy.transport.Server.get_url."""
317
def get_bogus_url(self):
318
raise NotImplementedError
321
def get_test_permutations():
322
"""Return the permutations to be used in testing."""
323
return [(MemoryTransport, MemoryServer),