/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: 2020-01-31 17:43:44 UTC
  • mto: This revision was merged to the branch mainline in revision 7478.
  • Revision ID: jelmer@jelmer.uk-20200131174344-qjhgqm7bdkuqj9sj
Default to running 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 itertools
 
30
import os
 
31
import errno
 
32
from stat import S_IFREG, S_IFDIR, S_IFLNK, S_ISDIR
 
33
 
 
34
from .. import (
 
35
    transport,
 
36
    urlutils,
 
37
    )
 
38
from ..errors import (
 
39
    FileExists,
 
40
    LockError,
 
41
    InProcessTransport,
 
42
    NoSuchFile,
 
43
    TransportNotPossible,
 
44
    )
 
45
from ..transport import (
 
46
    AppendBasedFileStream,
 
47
    _file_streams,
 
48
    LateReadError,
 
49
    )
 
50
 
 
51
 
 
52
class MemoryStat(object):
 
53
 
 
54
    def __init__(self, size, kind, perms=None):
 
55
        self.st_size = size
 
56
        if not S_ISDIR(kind):
 
57
            if perms is None:
 
58
                perms = 0o644
 
59
            self.st_mode = kind | perms
 
60
        else:
 
61
            if perms is None:
 
62
                perms = 0o755
 
63
            self.st_mode = kind | perms
 
64
 
 
65
 
 
66
class MemoryTransport(transport.Transport):
 
67
    """This is an in memory file system for transient data storage."""
 
68
 
 
69
    def __init__(self, url=""):
 
70
        """Set the 'base' path where files will be stored."""
 
71
        if url == "":
 
72
            url = "memory:///"
 
73
        if url[-1] != '/':
 
74
            url = url + '/'
 
75
        super(MemoryTransport, self).__init__(url)
 
76
        split = url.find(':') + 3
 
77
        self._scheme = url[:split]
 
78
        self._cwd = url[split:]
 
79
        # dictionaries from absolute path to file mode
 
80
        self._dirs = {'/': None}
 
81
        self._symlinks = {}
 
82
        self._files = {}
 
83
        self._locks = {}
 
84
 
 
85
    def clone(self, offset=None):
 
86
        """See Transport.clone()."""
 
87
        path = urlutils.URL._combine_paths(self._cwd, offset)
 
88
        if len(path) == 0 or path[-1] != '/':
 
89
            path += '/'
 
90
        url = self._scheme + path
 
91
        result = self.__class__(url)
 
92
        result._dirs = self._dirs
 
93
        result._symlinks = self._symlinks
 
94
        result._files = self._files
 
95
        result._locks = self._locks
 
96
        return result
 
97
 
 
98
    def abspath(self, relpath):
 
99
        """See Transport.abspath()."""
 
100
        # while a little slow, this is sufficiently fast to not matter in our
 
101
        # current environment - XXX RBC 20060404 move the clone '..' handling
 
102
        # into here and call abspath from clone
 
103
        temp_t = self.clone(relpath)
 
104
        if temp_t.base.count('/') == 3:
 
105
            return temp_t.base
 
106
        else:
 
107
            return temp_t.base[:-1]
 
108
 
 
109
    def append_file(self, relpath, f, mode=None):
 
110
        """See Transport.append_file()."""
 
111
        _abspath = self._resolve_symlinks(relpath)
 
112
        self._check_parent(_abspath)
 
113
        orig_content, orig_mode = self._files.get(_abspath, (b"", None))
 
114
        if mode is None:
 
115
            mode = orig_mode
 
116
        self._files[_abspath] = (orig_content + f.read(), mode)
 
117
        return len(orig_content)
 
118
 
 
119
    def _check_parent(self, _abspath):
 
120
        dir = os.path.dirname(_abspath)
 
121
        if dir != '/':
 
122
            if dir not in self._dirs:
 
123
                raise NoSuchFile(_abspath)
 
124
 
 
125
    def has(self, relpath):
 
126
        """See Transport.has()."""
 
127
        _abspath = self._abspath(relpath)
 
128
        for container in (self._files, self._dirs, self._symlinks):
 
129
            if _abspath in container.keys():
 
130
                return True
 
131
        return False
 
132
 
 
133
    def delete(self, relpath):
 
134
        """See Transport.delete()."""
 
135
        _abspath = self._abspath(relpath)
 
136
        if _abspath in self._files:
 
137
            del self._files[_abspath]
 
138
        elif _abspath in self._symlinks:
 
139
            del self._symlinks[_abspath]
 
140
        else:
 
141
            raise NoSuchFile(relpath)
 
142
 
 
143
    def external_url(self):
 
144
        """See breezy.transport.Transport.external_url."""
 
145
        # MemoryTransport's are only accessible in-process
 
146
        # so we raise here
 
147
        raise InProcessTransport(self)
 
148
 
 
149
    def get(self, relpath):
 
150
        """See Transport.get()."""
 
151
        _abspath = self._resolve_symlinks(relpath)
 
152
        if _abspath not in self._files:
 
153
            if _abspath in self._dirs:
 
154
                return LateReadError(relpath)
 
155
            else:
 
156
                raise NoSuchFile(relpath)
 
157
        return BytesIO(self._files[_abspath][0])
 
158
 
 
159
    def put_file(self, relpath, f, mode=None):
 
160
        """See Transport.put_file()."""
 
161
        _abspath = self._resolve_symlinks(relpath)
 
162
        self._check_parent(_abspath)
 
163
        raw_bytes = f.read()
 
164
        self._files[_abspath] = (raw_bytes, mode)
 
165
        return len(raw_bytes)
 
166
 
 
167
    def symlink(self, source, target):
 
168
        _abspath = self._resolve_symlinks(target)
 
169
        self._check_parent(_abspath)
 
170
        self._symlinks[_abspath] = self._abspath(source)
 
171
 
 
172
    def mkdir(self, relpath, mode=None):
 
173
        """See Transport.mkdir()."""
 
174
        _abspath = self._resolve_symlinks(relpath)
 
175
        self._check_parent(_abspath)
 
176
        if _abspath in self._dirs:
 
177
            raise FileExists(relpath)
 
178
        self._dirs[_abspath] = mode
 
179
 
 
180
    def open_write_stream(self, relpath, mode=None):
 
181
        """See Transport.open_write_stream."""
 
182
        self.put_bytes(relpath, b"", mode)
 
183
        result = AppendBasedFileStream(self, relpath)
 
184
        _file_streams[self.abspath(relpath)] = result
 
185
        return result
 
186
 
 
187
    def listable(self):
 
188
        """See Transport.listable."""
 
189
        return True
 
190
 
 
191
    def iter_files_recursive(self):
 
192
        for file in itertools.chain(self._files, self._symlinks):
 
193
            if file.startswith(self._cwd):
 
194
                yield urlutils.escape(file[len(self._cwd):])
 
195
 
 
196
    def list_dir(self, relpath):
 
197
        """See Transport.list_dir()."""
 
198
        _abspath = self._resolve_symlinks(relpath)
 
199
        if _abspath != '/' and _abspath not in self._dirs:
 
200
            raise NoSuchFile(relpath)
 
201
        result = []
 
202
 
 
203
        if not _abspath.endswith('/'):
 
204
            _abspath += '/'
 
205
 
 
206
        for path_group in self._files, self._dirs, self._symlinks:
 
207
            for path in path_group:
 
208
                if path.startswith(_abspath):
 
209
                    trailing = path[len(_abspath):]
 
210
                    if trailing and '/' not in trailing:
 
211
                        result.append(urlutils.escape(trailing))
 
212
        return result
 
213
 
 
214
    def rename(self, rel_from, rel_to):
 
215
        """Rename a file or directory; fail if the destination exists"""
 
216
        abs_from = self._resolve_symlinks(rel_from)
 
217
        abs_to = self._resolve_symlinks(rel_to)
 
218
 
 
219
        def replace(x):
 
220
            if x == abs_from:
 
221
                x = abs_to
 
222
            elif x.startswith(abs_from + '/'):
 
223
                x = abs_to + x[len(abs_from):]
 
224
            return x
 
225
 
 
226
        def do_renames(container):
 
227
            renames = []
 
228
            for path in container:
 
229
                new_path = replace(path)
 
230
                if new_path != path:
 
231
                    if new_path in container:
 
232
                        raise FileExists(new_path)
 
233
                    renames.append((path, new_path))
 
234
            for path, new_path in renames:
 
235
                container[new_path] = container[path]
 
236
                del container[path]
 
237
 
 
238
        # If we modify the existing dicts, we're not atomic anymore and may
 
239
        # fail differently depending on dict order. So work on copy, fail on
 
240
        # error on only replace dicts if all goes well.
 
241
        renamed_files = self._files.copy()
 
242
        renamed_symlinks = self._symlinks.copy()
 
243
        renamed_dirs = self._dirs.copy()
 
244
        do_renames(renamed_files)
 
245
        do_renames(renamed_symlinks)
 
246
        do_renames(renamed_dirs)
 
247
        # We may have been cloned so modify in place
 
248
        self._files.clear()
 
249
        self._files.update(renamed_files)
 
250
        self._symlinks.clear()
 
251
        self._symlinks.update(renamed_symlinks)
 
252
        self._dirs.clear()
 
253
        self._dirs.update(renamed_dirs)
 
254
 
 
255
    def rmdir(self, relpath):
 
256
        """See Transport.rmdir."""
 
257
        _abspath = self._resolve_symlinks(relpath)
 
258
        if _abspath in self._files:
 
259
            self._translate_error(IOError(errno.ENOTDIR, relpath), relpath)
 
260
        for path in itertools.chain(self._files, self._symlinks):
 
261
            if path.startswith(_abspath + '/'):
 
262
                self._translate_error(IOError(errno.ENOTEMPTY, relpath),
 
263
                                      relpath)
 
264
        for path in self._dirs:
 
265
            if path.startswith(_abspath + '/') and path != _abspath:
 
266
                self._translate_error(
 
267
                    IOError(errno.ENOTEMPTY, relpath), relpath)
 
268
        if _abspath not in self._dirs:
 
269
            raise NoSuchFile(relpath)
 
270
        del self._dirs[_abspath]
 
271
 
 
272
    def stat(self, relpath):
 
273
        """See Transport.stat()."""
 
274
        _abspath = self._abspath(relpath)
 
275
        if _abspath in self._files.keys():
 
276
            return MemoryStat(len(self._files[_abspath][0]), S_IFREG,
 
277
                              self._files[_abspath][1])
 
278
        elif _abspath in self._dirs.keys():
 
279
            return MemoryStat(0, S_IFDIR, self._dirs[_abspath])
 
280
        elif _abspath in self._symlinks.keys():
 
281
            return MemoryStat(0, S_IFLNK)
 
282
        else:
 
283
            raise NoSuchFile(_abspath)
 
284
 
 
285
    def lock_read(self, relpath):
 
286
        """See Transport.lock_read()."""
 
287
        return _MemoryLock(self._abspath(relpath), self)
 
288
 
 
289
    def lock_write(self, relpath):
 
290
        """See Transport.lock_write()."""
 
291
        return _MemoryLock(self._abspath(relpath), self)
 
292
 
 
293
    def _resolve_symlinks(self, relpath):
 
294
        path = self._abspath(relpath)
 
295
        while path in self._symlinks.keys():
 
296
            path = self._symlinks[path]
 
297
        return path
 
298
 
 
299
    def _abspath(self, relpath):
 
300
        """Generate an internal absolute path."""
 
301
        relpath = urlutils.unescape(relpath)
 
302
        if relpath[:1] == '/':
 
303
            return relpath
 
304
        cwd_parts = self._cwd.split('/')
 
305
        rel_parts = relpath.split('/')
 
306
        r = []
 
307
        for i in cwd_parts + rel_parts:
 
308
            if i == '..':
 
309
                if not r:
 
310
                    raise ValueError("illegal relpath %r under %r"
 
311
                                     % (relpath, self._cwd))
 
312
                r = r[:-1]
 
313
            elif i == '.' or i == '':
 
314
                pass
 
315
            else:
 
316
                r.append(i)
 
317
                r = self._symlinks.get('/'.join(r), r)
 
318
        return '/' + '/'.join(r)
 
319
 
 
320
    def symlink(self, source, link_name):
 
321
        """Create a symlink pointing to source named link_name."""
 
322
        _abspath = self._abspath(link_name)
 
323
        self._check_parent(_abspath)
 
324
        self._symlinks[_abspath] = source.split('/')
 
325
 
 
326
    def readlink(self, link_name):
 
327
        _abspath = self._abspath(link_name)
 
328
        try:
 
329
            return '/'.join(self._symlinks[_abspath])
 
330
        except KeyError:
 
331
            raise NoSuchFile(link_name)
 
332
 
 
333
 
 
334
class _MemoryLock(object):
 
335
    """This makes a lock."""
 
336
 
 
337
    def __init__(self, path, transport):
 
338
        self.path = path
 
339
        self.transport = transport
 
340
        if self.path in self.transport._locks:
 
341
            raise LockError('File %r already locked' % (self.path,))
 
342
        self.transport._locks[self.path] = self
 
343
 
 
344
    def unlock(self):
 
345
        del self.transport._locks[self.path]
 
346
        self.transport = None
 
347
 
 
348
 
 
349
class MemoryServer(transport.Server):
 
350
    """Server for the MemoryTransport for testing with."""
 
351
 
 
352
    def start_server(self):
 
353
        self._dirs = {'/': None}
 
354
        self._files = {}
 
355
        self._symlinks = {}
 
356
        self._locks = {}
 
357
        self._scheme = "memory+%s:///" % id(self)
 
358
 
 
359
        def memory_factory(url):
 
360
            from . import memory
 
361
            result = memory.MemoryTransport(url)
 
362
            result._dirs = self._dirs
 
363
            result._files = self._files
 
364
            result._symlinks = self._symlinks
 
365
            result._locks = self._locks
 
366
            return result
 
367
        self._memory_factory = memory_factory
 
368
        transport.register_transport(self._scheme, self._memory_factory)
 
369
 
 
370
    def stop_server(self):
 
371
        # unregister this server
 
372
        transport.unregister_transport(self._scheme, self._memory_factory)
 
373
 
 
374
    def get_url(self):
 
375
        """See breezy.transport.Server.get_url."""
 
376
        return self._scheme
 
377
 
 
378
    def get_bogus_url(self):
 
379
        raise NotImplementedError
 
380
 
 
381
 
 
382
def get_test_permutations():
 
383
    """Return the permutations to be used in testing."""
 
384
    return [(MemoryTransport, MemoryServer),
 
385
            ]