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
28
from stat import S_IFREG, S_IFDIR
34
from ..errors import (
40
from ..sixish import (
43
from ..transport import (
44
AppendBasedFileStream,
51
class MemoryStat(object):
53
def __init__(self, size, is_dir, perms):
58
self.st_mode = S_IFREG | perms
62
self.st_mode = S_IFDIR | perms
65
class MemoryTransport(transport.Transport):
66
"""This is an in memory file system for transient data storage."""
68
def __init__(self, url=""):
69
"""Set the 'base' path where files will be stored."""
74
super(MemoryTransport, self).__init__(url)
75
split = url.find(':') + 3
76
self._scheme = url[:split]
77
self._cwd = url[split:]
78
# dictionaries from absolute path to file mode
79
self._dirs = {'/':None}
83
def clone(self, offset=None):
84
"""See Transport.clone()."""
85
path = urlutils.URL._combine_paths(self._cwd, offset)
86
if len(path) == 0 or path[-1] != '/':
88
url = self._scheme + path
89
result = self.__class__(url)
90
result._dirs = self._dirs
91
result._files = self._files
92
result._locks = self._locks
95
def abspath(self, relpath):
96
"""See Transport.abspath()."""
97
# while a little slow, this is sufficiently fast to not matter in our
98
# current environment - XXX RBC 20060404 move the clone '..' handling
99
# into here and call abspath from clone
100
temp_t = self.clone(relpath)
101
if temp_t.base.count('/') == 3:
104
return temp_t.base[:-1]
106
def append_file(self, relpath, f, mode=None):
107
"""See Transport.append_file()."""
108
_abspath = self._abspath(relpath)
109
self._check_parent(_abspath)
110
orig_content, orig_mode = self._files.get(_abspath, ("", None))
113
self._files[_abspath] = (orig_content + f.read(), mode)
114
return len(orig_content)
116
def _check_parent(self, _abspath):
117
dir = os.path.dirname(_abspath)
119
if not dir in self._dirs:
120
raise NoSuchFile(_abspath)
122
def has(self, relpath):
123
"""See Transport.has()."""
124
_abspath = self._abspath(relpath)
125
return (_abspath in self._files) or (_abspath in self._dirs)
127
def delete(self, relpath):
128
"""See Transport.delete()."""
129
_abspath = self._abspath(relpath)
130
if not _abspath in self._files:
131
raise NoSuchFile(relpath)
132
del self._files[_abspath]
134
def external_url(self):
135
"""See breezy.transport.Transport.external_url."""
136
# MemoryTransport's are only accessible in-process
138
raise InProcessTransport(self)
140
def get(self, relpath):
141
"""See Transport.get()."""
142
_abspath = self._abspath(relpath)
143
if not _abspath in self._files:
144
if _abspath in self._dirs:
145
return LateReadError(relpath)
147
raise NoSuchFile(relpath)
148
return BytesIO(self._files[_abspath][0])
150
def put_file(self, relpath, f, mode=None):
151
"""See Transport.put_file()."""
152
_abspath = self._abspath(relpath)
153
self._check_parent(_abspath)
155
self._files[_abspath] = (raw_bytes, mode)
156
return len(raw_bytes)
158
def mkdir(self, relpath, mode=None):
159
"""See Transport.mkdir()."""
160
_abspath = self._abspath(relpath)
161
self._check_parent(_abspath)
162
if _abspath in self._dirs:
163
raise FileExists(relpath)
164
self._dirs[_abspath]=mode
166
def open_write_stream(self, relpath, mode=None):
167
"""See Transport.open_write_stream."""
168
self.put_bytes(relpath, b"", mode)
169
result = AppendBasedFileStream(self, relpath)
170
_file_streams[self.abspath(relpath)] = result
174
"""See Transport.listable."""
177
def iter_files_recursive(self):
178
for file in self._files:
179
if file.startswith(self._cwd):
180
yield urlutils.escape(file[len(self._cwd):])
182
def list_dir(self, relpath):
183
"""See Transport.list_dir()."""
184
_abspath = self._abspath(relpath)
185
if _abspath != '/' and _abspath not in self._dirs:
186
raise NoSuchFile(relpath)
189
if not _abspath.endswith('/'):
192
for path_group in self._files, self._dirs:
193
for path in path_group:
194
if path.startswith(_abspath):
195
trailing = path[len(_abspath):]
196
if trailing and '/' not in trailing:
197
result.append(urlutils.escape(trailing))
200
def rename(self, rel_from, rel_to):
201
"""Rename a file or directory; fail if the destination exists"""
202
abs_from = self._abspath(rel_from)
203
abs_to = self._abspath(rel_to)
207
elif x.startswith(abs_from + '/'):
208
x = abs_to + x[len(abs_from):]
210
def do_renames(container):
211
for path in container:
212
new_path = replace(path)
214
if new_path in container:
215
raise FileExists(new_path)
216
container[new_path] = container[path]
218
do_renames(self._files)
219
do_renames(self._dirs)
221
def rmdir(self, relpath):
222
"""See Transport.rmdir."""
223
_abspath = self._abspath(relpath)
224
if _abspath in self._files:
225
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
226
for path in self._files:
227
if path.startswith(_abspath + '/'):
228
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
230
for path in self._dirs:
231
if path.startswith(_abspath + '/') and path != _abspath:
232
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
233
if not _abspath in self._dirs:
234
raise NoSuchFile(relpath)
235
del self._dirs[_abspath]
237
def stat(self, relpath):
238
"""See Transport.stat()."""
239
_abspath = self._abspath(relpath)
240
if _abspath in self._files:
241
return MemoryStat(len(self._files[_abspath][0]), False,
242
self._files[_abspath][1])
243
elif _abspath in self._dirs:
244
return MemoryStat(0, True, self._dirs[_abspath])
246
raise NoSuchFile(_abspath)
248
def lock_read(self, relpath):
249
"""See Transport.lock_read()."""
250
return _MemoryLock(self._abspath(relpath), self)
252
def lock_write(self, relpath):
253
"""See Transport.lock_write()."""
254
return _MemoryLock(self._abspath(relpath), self)
256
def _abspath(self, relpath):
257
"""Generate an internal absolute path."""
258
relpath = urlutils.unescape(relpath)
259
if relpath[:1] == '/':
261
cwd_parts = self._cwd.split('/')
262
rel_parts = relpath.split('/')
264
for i in cwd_parts + rel_parts:
267
raise ValueError("illegal relpath %r under %r"
268
% (relpath, self._cwd))
270
elif i == '.' or i == '':
274
return '/' + '/'.join(r)
277
class _MemoryLock(object):
278
"""This makes a lock."""
280
def __init__(self, path, transport):
282
self.transport = transport
283
if self.path in self.transport._locks:
284
raise LockError('File %r already locked' % (self.path,))
285
self.transport._locks[self.path] = self
288
del self.transport._locks[self.path]
289
self.transport = None
292
class MemoryServer(transport.Server):
293
"""Server for the MemoryTransport for testing with."""
295
def start_server(self):
296
self._dirs = {'/':None}
299
self._scheme = "memory+%s:///" % id(self)
300
def memory_factory(url):
302
result = memory.MemoryTransport(url)
303
result._dirs = self._dirs
304
result._files = self._files
305
result._locks = self._locks
307
self._memory_factory = memory_factory
308
transport.register_transport(self._scheme, self._memory_factory)
310
def stop_server(self):
311
# unregister this server
312
transport.unregister_transport(self._scheme, self._memory_factory)
315
"""See breezy.transport.Server.get_url."""
318
def get_bogus_url(self):
319
raise NotImplementedError
322
def get_test_permutations():
323
"""Return the permutations to be used in testing."""
324
return [(MemoryTransport, MemoryServer),