/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/transport/memory.py

  • Committer: Martin
  • Date: 2018-07-03 17:23:08 UTC
  • mto: (7027.4.10 python3-blackbox)
  • mto: This revision was merged to the branch mainline in revision 7028.
  • Revision ID: gzlist@googlemail.com-20180703172308-lre82d15bfkdv48e
Fixup low level knit tests for Python 3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005-2011, 2016 Canonical Ltd
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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
 
16
 
 
17
"""Implementation of Transport that uses memory for its storage.
 
18
 
 
19
The contents of the transport will be lost when the object is discarded,
 
20
so this is primarily useful for testing.
 
21
"""
 
22
 
 
23
from __future__ import absolute_import
 
24
 
 
25
import contextlib
 
26
from io import (
 
27
    BytesIO,
 
28
    )
 
29
import os
 
30
import errno
 
31
from stat import S_IFREG, S_IFDIR
 
32
 
 
33
from .. import (
 
34
    transport,
 
35
    urlutils,
 
36
    )
 
37
from ..errors import (
 
38
    FileExists,
 
39
    LockError,
 
40
    InProcessTransport,
 
41
    NoSuchFile,
 
42
    )
 
43
from ..transport import (
 
44
    AppendBasedFileStream,
 
45
    _file_streams,
 
46
    LateReadError,
 
47
    )
 
48
 
 
49
 
 
50
 
 
51
class MemoryStat(object):
 
52
 
 
53
    def __init__(self, size, is_dir, perms):
 
54
        self.st_size = size
 
55
        if not is_dir:
 
56
            if perms is None:
 
57
                perms = 0o644
 
58
            self.st_mode = S_IFREG | perms
 
59
        else:
 
60
            if perms is None:
 
61
                perms = 0o755
 
62
            self.st_mode = S_IFDIR | perms
 
63
 
 
64
 
 
65
class MemoryTransport(transport.Transport):
 
66
    """This is an in memory file system for transient data storage."""
 
67
 
 
68
    def __init__(self, url=""):
 
69
        """Set the 'base' path where files will be stored."""
 
70
        if url == "":
 
71
            url = "memory:///"
 
72
        if url[-1] != '/':
 
73
            url = url + '/'
 
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}
 
80
        self._files = {}
 
81
        self._locks = {}
 
82
 
 
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] != '/':
 
87
            path += '/'
 
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
 
93
        return result
 
94
 
 
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:
 
102
            return temp_t.base
 
103
        else:
 
104
            return temp_t.base[:-1]
 
105
 
 
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))
 
111
        if mode is None:
 
112
            mode = orig_mode
 
113
        self._files[_abspath] = (orig_content + f.read(), mode)
 
114
        return len(orig_content)
 
115
 
 
116
    def _check_parent(self, _abspath):
 
117
        dir = os.path.dirname(_abspath)
 
118
        if dir != '/':
 
119
            if not dir in self._dirs:
 
120
                raise NoSuchFile(_abspath)
 
121
 
 
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)
 
126
 
 
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]
 
133
 
 
134
    def external_url(self):
 
135
        """See breezy.transport.Transport.external_url."""
 
136
        # MemoryTransport's are only accessible in-process
 
137
        # so we raise here
 
138
        raise InProcessTransport(self)
 
139
 
 
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)
 
146
            else:
 
147
                raise NoSuchFile(relpath)
 
148
        return BytesIO(self._files[_abspath][0])
 
149
 
 
150
    def put_file(self, relpath, f, mode=None):
 
151
        """See Transport.put_file()."""
 
152
        _abspath = self._abspath(relpath)
 
153
        self._check_parent(_abspath)
 
154
        raw_bytes = f.read()
 
155
        self._files[_abspath] = (raw_bytes, mode)
 
156
        return len(raw_bytes)
 
157
 
 
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
 
165
 
 
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
 
171
        return result
 
172
 
 
173
    def listable(self):
 
174
        """See Transport.listable."""
 
175
        return True
 
176
 
 
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):])
 
181
 
 
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)
 
187
        result = []
 
188
 
 
189
        if not _abspath.endswith('/'):
 
190
            _abspath += '/'
 
191
 
 
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))
 
198
        return result
 
199
 
 
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)
 
204
 
 
205
        def replace(x):
 
206
            if x == abs_from:
 
207
                x = abs_to
 
208
            elif x.startswith(abs_from + '/'):
 
209
                x = abs_to + x[len(abs_from):]
 
210
            return x
 
211
 
 
212
        def do_renames(container):
 
213
            renames = []
 
214
            for path in container:
 
215
                new_path = replace(path)
 
216
                if new_path != 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]
 
222
                del container[path]
 
223
 
 
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
 
232
        self._files.clear()
 
233
        self._files.update(renamed_files)
 
234
        self._dirs.clear()
 
235
        self._dirs.update(renamed_dirs)
 
236
 
 
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),
 
245
                                      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]
 
252
 
 
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])
 
261
        else:
 
262
            raise NoSuchFile(_abspath)
 
263
 
 
264
    def lock_read(self, relpath):
 
265
        """See Transport.lock_read()."""
 
266
        return _MemoryLock(self._abspath(relpath), self)
 
267
 
 
268
    def lock_write(self, relpath):
 
269
        """See Transport.lock_write()."""
 
270
        return _MemoryLock(self._abspath(relpath), self)
 
271
 
 
272
    def _abspath(self, relpath):
 
273
        """Generate an internal absolute path."""
 
274
        relpath = urlutils.unescape(relpath)
 
275
        if relpath[:1] == '/':
 
276
            return relpath
 
277
        cwd_parts = self._cwd.split('/')
 
278
        rel_parts = relpath.split('/')
 
279
        r = []
 
280
        for i in cwd_parts + rel_parts:
 
281
            if i == '..':
 
282
                if not r:
 
283
                    raise ValueError("illegal relpath %r under %r"
 
284
                        % (relpath, self._cwd))
 
285
                r = r[:-1]
 
286
            elif i == '.' or i == '':
 
287
                pass
 
288
            else:
 
289
                r.append(i)
 
290
        return '/' + '/'.join(r)
 
291
 
 
292
 
 
293
class _MemoryLock(object):
 
294
    """This makes a lock."""
 
295
 
 
296
    def __init__(self, path, transport):
 
297
        self.path = path
 
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
 
302
 
 
303
    def unlock(self):
 
304
        del self.transport._locks[self.path]
 
305
        self.transport = None
 
306
 
 
307
 
 
308
class MemoryServer(transport.Server):
 
309
    """Server for the MemoryTransport for testing with."""
 
310
 
 
311
    def start_server(self):
 
312
        self._dirs = {'/':None}
 
313
        self._files = {}
 
314
        self._locks = {}
 
315
        self._scheme = "memory+%s:///" % id(self)
 
316
        def memory_factory(url):
 
317
            from . import memory
 
318
            result = memory.MemoryTransport(url)
 
319
            result._dirs = self._dirs
 
320
            result._files = self._files
 
321
            result._locks = self._locks
 
322
            return result
 
323
        self._memory_factory = memory_factory
 
324
        transport.register_transport(self._scheme, self._memory_factory)
 
325
 
 
326
    def stop_server(self):
 
327
        # unregister this server
 
328
        transport.unregister_transport(self._scheme, self._memory_factory)
 
329
 
 
330
    def get_url(self):
 
331
        """See breezy.transport.Server.get_url."""
 
332
        return self._scheme
 
333
 
 
334
    def get_bogus_url(self):
 
335
        raise NotImplementedError
 
336
 
 
337
 
 
338
def get_test_permutations():
 
339
    """Return the permutations to be used in testing."""
 
340
    return [(MemoryTransport, MemoryServer),
 
341
            ]