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
37
from ..errors 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, (b"", 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)
208
elif x.startswith(abs_from + '/'):
209
x = abs_to + x[len(abs_from):]
212
def do_renames(container):
214
for path in container:
215
new_path = replace(path)
217
if new_path in container:
218
raise FileExists(new_path)
219
renames.append((path, new_path))
220
for path, new_path in renames:
221
container[new_path] = container[path]
224
# If we modify the existing dicts, we're not atomic anymore and may
225
# fail differently depending on dict order. So work on copy, fail on
226
# error on only replace dicts if all goes well.
227
renamed_files = self._files.copy()
228
renamed_dirs = self._dirs.copy()
229
do_renames(renamed_files)
230
do_renames(renamed_dirs)
231
# We may have been cloned so modify in place
233
self._files.update(renamed_files)
235
self._dirs.update(renamed_dirs)
237
def rmdir(self, relpath):
238
"""See Transport.rmdir."""
239
_abspath = self._abspath(relpath)
240
if _abspath in self._files:
241
self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
242
for path in self._files:
243
if path.startswith(_abspath + '/'):
244
self._translate_error(IOError(errno.ENOTEMPTY, relpath),
246
for path in self._dirs:
247
if path.startswith(_abspath + '/') and path != _abspath:
248
self._translate_error(IOError(errno.ENOTEMPTY, relpath), relpath)
249
if not _abspath in self._dirs:
250
raise NoSuchFile(relpath)
251
del self._dirs[_abspath]
253
def stat(self, relpath):
254
"""See Transport.stat()."""
255
_abspath = self._abspath(relpath)
256
if _abspath in self._files:
257
return MemoryStat(len(self._files[_abspath][0]), False,
258
self._files[_abspath][1])
259
elif _abspath in self._dirs:
260
return MemoryStat(0, True, self._dirs[_abspath])
262
raise NoSuchFile(_abspath)
264
def lock_read(self, relpath):
265
"""See Transport.lock_read()."""
266
return _MemoryLock(self._abspath(relpath), self)
268
def lock_write(self, relpath):
269
"""See Transport.lock_write()."""
270
return _MemoryLock(self._abspath(relpath), self)
272
def _abspath(self, relpath):
273
"""Generate an internal absolute path."""
274
relpath = urlutils.unescape(relpath)
275
if relpath[:1] == '/':
277
cwd_parts = self._cwd.split('/')
278
rel_parts = relpath.split('/')
280
for i in cwd_parts + rel_parts:
283
raise ValueError("illegal relpath %r under %r"
284
% (relpath, self._cwd))
286
elif i == '.' or i == '':
290
return '/' + '/'.join(r)
293
class _MemoryLock(object):
294
"""This makes a lock."""
296
def __init__(self, path, transport):
298
self.transport = transport
299
if self.path in self.transport._locks:
300
raise LockError('File %r already locked' % (self.path,))
301
self.transport._locks[self.path] = self
304
del self.transport._locks[self.path]
305
self.transport = None
308
class MemoryServer(transport.Server):
309
"""Server for the MemoryTransport for testing with."""
311
def start_server(self):
312
self._dirs = {'/':None}
315
self._scheme = "memory+%s:///" % id(self)
316
def memory_factory(url):
318
result = memory.MemoryTransport(url)
319
result._dirs = self._dirs
320
result._files = self._files
321
result._locks = self._locks
323
self._memory_factory = memory_factory
324
transport.register_transport(self._scheme, self._memory_factory)
326
def stop_server(self):
327
# unregister this server
328
transport.unregister_transport(self._scheme, self._memory_factory)
331
"""See breezy.transport.Server.get_url."""
334
def get_bogus_url(self):
335
raise NotImplementedError
338
def get_test_permutations():
339
"""Return the permutations to be used in testing."""
340
return [(MemoryTransport, MemoryServer),