/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: Jelmer Vernooij
  • Date: 2019-01-01 21:08:01 UTC
  • mto: This revision was merged to the branch mainline in revision 7231.
  • Revision ID: jelmer@jelmer.uk-20190101210801-2dlsv7b1lvydmpkl
Fix tests.

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, S_IFLNK
 
32
 
 
33
from .. import (
 
34
    transport,
 
35
    urlutils,
 
36
    )
 
37
from ..errors import (
 
38
    FileExists,
 
39
    LockError,
 
40
    InProcessTransport,
 
41
    NoSuchFile,
 
42
    TransportNotPossible,
 
43
    )
 
44
from ..transport import (
 
45
    AppendBasedFileStream,
 
46
    _file_streams,
 
47
    LateReadError,
 
48
    )
 
49
 
 
50
 
 
51
class MemoryStat(object):
 
52
 
 
53
    def __init__(self, size, kind, perms):
 
54
        self.st_size = size
 
55
        if kind == 'file':
 
56
            if perms is None:
 
57
                perms = 0o644
 
58
            self.st_mode = S_IFREG | perms
 
59
        elif kind == 'directory':
 
60
            if perms is None:
 
61
                perms = 0o755
 
62
            self.st_mode = S_IFDIR | perms
 
63
        elif kind == 'symlink':
 
64
            self.st_mode = S_IFLNK | 0o644
 
65
        else:
 
66
            raise AssertionError('unknown kind %r' % kind)
 
67
 
 
68
 
 
69
class MemoryTransport(transport.Transport):
 
70
    """This is an in memory file system for transient data storage."""
 
71
 
 
72
    def __init__(self, url=""):
 
73
        """Set the 'base' path where files will be stored."""
 
74
        if url == "":
 
75
            url = "memory:///"
 
76
        if url[-1] != '/':
 
77
            url = url + '/'
 
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}
 
84
        self._symlinks = {}
 
85
        self._files = {}
 
86
        self._locks = {}
 
87
 
 
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] != '/':
 
92
            path += '/'
 
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
 
99
        return result
 
100
 
 
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:
 
108
            return temp_t.base
 
109
        else:
 
110
            return temp_t.base[:-1]
 
111
 
 
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))
 
117
        if mode is None:
 
118
            mode = orig_mode
 
119
        self._files[_abspath] = (orig_content + f.read(), mode)
 
120
        return len(orig_content)
 
121
 
 
122
    def _check_parent(self, _abspath):
 
123
        dir = os.path.dirname(_abspath)
 
124
        if dir != '/':
 
125
            if dir not in self._dirs:
 
126
                raise NoSuchFile(_abspath)
 
127
 
 
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))
 
134
 
 
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]
 
141
 
 
142
    def external_url(self):
 
143
        """See breezy.transport.Transport.external_url."""
 
144
        # MemoryTransport's are only accessible in-process
 
145
        # so we raise here
 
146
        raise InProcessTransport(self)
 
147
 
 
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)
 
154
            else:
 
155
                raise NoSuchFile(relpath)
 
156
        return BytesIO(self._files[_abspath][0])
 
157
 
 
158
    def put_file(self, relpath, f, mode=None):
 
159
        """See Transport.put_file()."""
 
160
        _abspath = self._abspath(relpath)
 
161
        self._check_parent(_abspath)
 
162
        raw_bytes = f.read()
 
163
        self._files[_abspath] = (raw_bytes, mode)
 
164
        return len(raw_bytes)
 
165
 
 
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
 
173
 
 
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
 
179
        return result
 
180
 
 
181
    def listable(self):
 
182
        """See Transport.listable."""
 
183
        return True
 
184
 
 
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):])
 
189
 
 
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)
 
195
        result = []
 
196
 
 
197
        if not _abspath.endswith('/'):
 
198
            _abspath += '/'
 
199
 
 
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))
 
206
        return result
 
207
 
 
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)
 
212
 
 
213
        def replace(x):
 
214
            if x == abs_from:
 
215
                x = abs_to
 
216
            elif x.startswith(abs_from + '/'):
 
217
                x = abs_to + x[len(abs_from):]
 
218
            return x
 
219
 
 
220
        def do_renames(container):
 
221
            renames = []
 
222
            for path in container:
 
223
                new_path = replace(path)
 
224
                if new_path != 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]
 
230
                del container[path]
 
231
 
 
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
 
240
        self._files.clear()
 
241
        self._files.update(renamed_files)
 
242
        self._dirs.clear()
 
243
        self._dirs.update(renamed_dirs)
 
244
 
 
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),
 
253
                                      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]
 
261
 
 
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)
 
272
        else:
 
273
            raise NoSuchFile(_abspath)
 
274
 
 
275
    def lock_read(self, relpath):
 
276
        """See Transport.lock_read()."""
 
277
        return _MemoryLock(self._abspath(relpath), self)
 
278
 
 
279
    def lock_write(self, relpath):
 
280
        """See Transport.lock_write()."""
 
281
        return _MemoryLock(self._abspath(relpath), self)
 
282
 
 
283
    def _abspath(self, relpath):
 
284
        """Generate an internal absolute path."""
 
285
        relpath = urlutils.unescape(relpath)
 
286
        if relpath[:1] == '/':
 
287
            return relpath
 
288
        cwd_parts = self._cwd.split('/')
 
289
        rel_parts = relpath.split('/')
 
290
        r = []
 
291
        for i in cwd_parts + rel_parts:
 
292
            if i == '..':
 
293
                if not r:
 
294
                    raise ValueError("illegal relpath %r under %r"
 
295
                                     % (relpath, self._cwd))
 
296
                r = r[:-1]
 
297
            elif i == '.' or i == '':
 
298
                pass
 
299
            else:
 
300
                r.append(i)
 
301
                r = self._symlinks.get('/'.join(r), r)
 
302
        return '/' + '/'.join(r)
 
303
 
 
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('/')
 
309
 
 
310
    def readlink(self, link_name):
 
311
        _abspath = self._abspath(link_name)
 
312
        try:
 
313
            return '/'.join(self._symlinks[_abspath])
 
314
        except KeyError:
 
315
            raise NoSuchFile(link_name)
 
316
 
 
317
 
 
318
class _MemoryLock(object):
 
319
    """This makes a lock."""
 
320
 
 
321
    def __init__(self, path, transport):
 
322
        self.path = path
 
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
 
327
 
 
328
    def unlock(self):
 
329
        del self.transport._locks[self.path]
 
330
        self.transport = None
 
331
 
 
332
 
 
333
class MemoryServer(transport.Server):
 
334
    """Server for the MemoryTransport for testing with."""
 
335
 
 
336
    def start_server(self):
 
337
        self._dirs = {'/': None}
 
338
        self._files = {}
 
339
        self._locks = {}
 
340
        self._scheme = "memory+%s:///" % id(self)
 
341
 
 
342
        def memory_factory(url):
 
343
            from . import memory
 
344
            result = memory.MemoryTransport(url)
 
345
            result._dirs = self._dirs
 
346
            result._files = self._files
 
347
            result._locks = self._locks
 
348
            return result
 
349
        self._memory_factory = memory_factory
 
350
        transport.register_transport(self._scheme, self._memory_factory)
 
351
 
 
352
    def stop_server(self):
 
353
        # unregister this server
 
354
        transport.unregister_transport(self._scheme, self._memory_factory)
 
355
 
 
356
    def get_url(self):
 
357
        """See breezy.transport.Server.get_url."""
 
358
        return self._scheme
 
359
 
 
360
    def get_bogus_url(self):
 
361
        raise NotImplementedError
 
362
 
 
363
 
 
364
def get_test_permutations():
 
365
    """Return the permutations to be used in testing."""
 
366
    return [(MemoryTransport, MemoryServer),
 
367
            ]