~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport.py

  • Committer: John Arbash Meinel
  • Date: 2005-07-19 22:13:01 UTC
  • mto: (1185.11.1)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: john@arbash-meinel.com-20050719221300-9a7b9bc9d866a9b9
Removed encode/decode from Transport.put/get, added more exceptions that can be thrown.
Added readonly tests for Transports.
Added test for HttpTransport.

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
        _protocol_handlers[prefix] = klass
23
23
 
24
24
class TransportError(BzrError):
25
 
    pass
 
25
    """All errors thrown by Transport implementations should derive
 
26
    from this class.
 
27
    """
 
28
    def __init__(self, msg=None, orig_error=None):
 
29
        if msg is None and orig_error is not None:
 
30
            msg = str(orig_error)
 
31
        BzrError.__init__(self, msg)
 
32
        self.msg = msg
 
33
        self.orig_error = orig_error
26
34
 
27
35
class AsyncError(TransportError):
28
36
    pass
29
37
 
30
 
class TransportNotPossibleError(TransportError):
 
38
# A set of semi-meaningful errors which can be thrown
 
39
class TransportNotPossible(TransportError):
31
40
    """This is for transports where a specific function is explicitly not
32
41
    possible. Such as pushing files to an HTTP server.
33
42
    """
34
43
    pass
35
44
 
 
45
class NonRelativePath(TransportError):
 
46
    """An absolute path was supplied, that could not be decoded into
 
47
    a relative path.
 
48
    """
 
49
    pass
 
50
 
 
51
class NoSuchFile(TransportError, IOError):
 
52
    """A get() was issued for a file that doesn't exist."""
 
53
    def __init__(self, msg=None, orig_error=None):
 
54
        import errno
 
55
        TransportError.__init__(self, msg=msg, orig_error=orig_error)
 
56
        IOError.__init__(self, errno.ENOENT, self.msg)
 
57
 
 
58
class FileExists(TransportError, OSError):
 
59
    """An operation was attempted, which would overwrite an entry,
 
60
    but overwritting is not supported.
 
61
 
 
62
    mkdir() can throw this, but put() just overwites existing files.
 
63
    """
 
64
    def __init__(self, msg=None, orig_error=None):
 
65
        import errno
 
66
        TransportError.__init__(self, msg=msg, orig_error=orig_error)
 
67
        OSError.__init__(self, errno.EEXIST, self.msg)
 
68
 
 
69
class PermissionDenied(TransportError):
 
70
    """An operation cannot succeed because of a lack of permissions."""
 
71
    pass
 
72
 
 
73
class ConnectionReset(TransportError):
 
74
    """The connection has been closed."""
 
75
    pass
 
76
 
36
77
class AsyncFile(object):
37
78
    """This will be returned from a Transport object,
38
79
    whenever an asyncronous get is requested.
98
139
    implementations can do pipelining.
99
140
    In general implementations should support having a generator or a list
100
141
    as an argument (ie always iterate, never index)
101
 
 
102
 
    TODO: Worry about file encodings. For instance bzr control files should
103
 
          all be encoded in utf-8, but read as local encoding.
104
 
 
105
 
    TODO: Consider adding a lock/unlock functions.
106
142
    """
107
143
 
108
144
    def __init__(self, base):
 
145
        super(Transport, self).__init__()
109
146
        self.base = base
110
147
 
111
148
    def clone(self, offset=None):
195
232
            yield self.has(relpath)
196
233
            count += 1
197
234
 
198
 
    def get(self, relpath, decode=False):
 
235
    def get(self, relpath):
199
236
        """Get the file at the given relative path.
200
237
 
201
238
        :param relpath: The relative path to the file
202
 
        :param decode:  If True, assume the file is utf-8 encoded and
203
 
                        decode it into Unicode
204
239
        """
205
240
        raise NotImplementedError
206
241
 
207
 
    def get_multi(self, relpaths, decode=False, pb=None):
 
242
    def get_multi(self, relpaths, pb=None):
208
243
        """Get a list of file-like objects, one for each entry in relpaths.
209
244
 
210
245
        :param relpaths: A list of relative paths.
211
 
        :param decode:  If True, assume the file is utf-8 encoded and
212
 
                        decode it into Unicode
213
246
        :param pb:  An optional ProgressBar for indicating percent done.
214
247
        :return: A list or generator of file-like objects
215
248
        """
220
253
        count = 0
221
254
        for relpath in relpaths:
222
255
            self._update_pb(pb, 'get', count, total)
223
 
            yield self.get(relpath, decode=decode)
 
256
            yield self.get(relpath)
224
257
            count += 1
225
258
 
226
 
    def put(self, relpath, f, encode=False):
 
259
    def put(self, relpath, f):
227
260
        """Copy the file-like or string object into the location.
228
261
 
229
262
        :param relpath: Location to put the contents, relative to base.
230
263
        :param f:       File-like or string object.
231
 
        :param encode:  If True, translate the contents into utf-8 encoded text.
232
264
        """
233
265
        raise NotImplementedError
234
266
 
235
 
    def put_multi(self, files, encode=False, pb=None):
 
267
    def put_multi(self, files, pb=None):
236
268
        """Put a set of files or strings into the location.
237
269
 
238
270
        :param files: A list of tuples of relpath, file object [(path1, file1), (path2, file2),...]
240
272
        :return: The number of files copied.
241
273
        """
242
274
        def put(relpath, f):
243
 
            self.put(relpath, f, encode=encode)
 
275
            self.put(relpath, f)
244
276
        return self._iterate_over(files, put, pb, 'put', expand=True)
245
277
 
246
278
    def mkdir(self, relpath):
251
283
        """Create a group of directories"""
252
284
        return self._iterate_over(relpaths, self.mkdir, pb, 'mkdir', expand=False)
253
285
 
254
 
    def append(self, relpath, f, encode=False):
 
286
    def append(self, relpath, f):
255
287
        """Append the text in the file-like or string object to 
256
288
        the supplied location.
257
289
        """
286
318
        """
287
319
        # The dummy implementation just does a simple get + put
288
320
        def copy_entry(path):
289
 
            other.put(path, self.get(path, decode=False), encode=False)
 
321
            other.put(path, self.get(path))
290
322
 
291
323
        return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
292
324
 
386
418
    # which has a lookup of None
387
419
    return _protocol_handlers[None](base)
388
420
 
 
421
def transport_test_ro(tester, t):
 
422
    """Test a transport object, but only assume that it has Read functionality.
 
423
    The Transport object is connected to the current working directory.
 
424
    So that whatever is done through the transport, should show
 
425
    up in the working directory, and vice-versa.
 
426
 
 
427
    This also tests to make sure that the functions work with both
 
428
    generators and lists (assuming iter(list) is effectively a generator)
 
429
    """
 
430
    import tempfile, os
 
431
    from local_transport import LocalTransport
 
432
 
 
433
    # Test has
 
434
    files = ['a', 'b', 'e', 'g']
 
435
    tester.build_tree(files)
 
436
    tester.assertEqual(t.has('a'), True)
 
437
    tester.assertEqual(t.has('c'), False)
 
438
    tester.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])),
 
439
            [True, True, False, False, True, False, True, False])
 
440
    tester.assertEqual(list(t.has_multi(iter(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']))),
 
441
            [True, True, False, False, True, False, True, False])
 
442
 
 
443
    # Test get
 
444
    tester.assertEqual(t.get('a').read(), open('a').read())
 
445
    content_f = t.get_multi(files)
 
446
    for path,f in zip(files, content_f):
 
447
        tester.assertEqual(open(path).read(), f.read())
 
448
 
 
449
    content_f = t.get_multi(iter(files))
 
450
    for path,f in zip(files, content_f):
 
451
        tester.assertEqual(open(path).read(), f.read())
 
452
 
 
453
    tester.assertRaises(NoSuchFile, t.get, 'c')
 
454
    try:
 
455
        files = list(t.get_multi(['a', 'b', 'c']))
 
456
    except NoSuchFile:
 
457
        pass
 
458
    else:
 
459
        tester.fail('Failed to raise NoSuchFile for missing file in get_multi')
 
460
    try:
 
461
        files = list(t.get_multi(iter(['a', 'b', 'c', 'e'])))
 
462
    except NoSuchFile:
 
463
        pass
 
464
    else:
 
465
        tester.fail('Failed to raise NoSuchFile for missing file in get_multi')
 
466
 
 
467
    open('c', 'wb').write('some text for c\n')
 
468
    tester.assert_(os.path.exists('c'))
 
469
    tester.assertEqual(t.get('c').read(), 'some text for c\n')
 
470
    # Make sure 'has' is updated
 
471
    tester.assertEqual(list(t.has_multi(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])),
 
472
            [True, True, True, False, True, False, True, False])
 
473
 
 
474
    # Test mkdir
 
475
    os.mkdir('dir_a')
 
476
    tester.assertEqual(t.has('dir_a'), True)
 
477
    tester.assertEqual(t.has('dir_b'), False)
 
478
 
 
479
    os.mkdir('dir_b')
 
480
    tester.assertEqual(t.has('dir_b'), True)
 
481
    tester.assert_(os.path.isdir('dir_b'))
 
482
 
 
483
    os.mkdir('dir_c')
 
484
    os.mkdir('dir_d')
 
485
    tester.assertEqual(list(t.has_multi(['dir_a', 'dir_b', 'dir_c', 'dir_d', 'dir_e', 'dir_b'])),
 
486
            [True, True, True, True, False, True])
 
487
    for d in ['dir_a', 'dir_b', 'dir_c', 'dir_d']:
 
488
        tester.assert_(os.path.isdir(d))
 
489
 
 
490
    # Test get/put in sub-directories
 
491
    open('dir_a/a', 'wb').write('contents of dir_a/a')
 
492
    open('dir_b/b', 'wb').write('contents of dir_b/b')
 
493
    for f in ('dir_a/a', 'dir_b/b'):
 
494
        tester.assertEqual(t.get(f).read(), open(f).read())
 
495
 
 
496
    # Test copy_to
 
497
    dtmp = tempfile.mkdtemp(dir='.', prefix='test-transport-')
 
498
    dtmp_base = os.path.basename(dtmp)
 
499
    local_t = LocalTransport(dtmp)
 
500
 
 
501
    files = ['a', 'b', 'c']
 
502
    t.copy_to(files, local_t)
 
503
    for f in files:
 
504
        tester.assertEquals(open(f).read(), open(os.path.join(dtmp_base, f)).read())
 
505
 
389
506
def transport_test(tester, t):
390
507
    """Test a transport object. Basically, it assumes that the
391
508
    Transport object is connected to the current working directory.
418
535
    for path,f in zip(files, content_f):
419
536
        tester.assertEqual(open(path).read(), f.read())
420
537
 
421
 
    tester.assertRaises(TransportError, t.get, 'c')
 
538
    tester.assertRaises(NoSuchFile, t.get, 'c')
422
539
    try:
423
540
        files = list(t.get_multi(['a', 'b', 'c']))
424
541
    except TransportError:
454
571
    tester.assertEqual(open('a').read(), 'diff\ncontents for\na\n')
455
572
    tester.assertEqual(open('d').read(), 'another contents\nfor d\n')
456
573
 
457
 
    tester.assertRaises(TransportError, t.put, 'path/doesnt/exist/c', 'contents')
 
574
    tester.assertRaises(NoSuchFile, t.put, 'path/doesnt/exist/c', 'contents')
458
575
 
459
576
    # Test mkdir
460
577
    os.mkdir('dir_a')
471
588
    for d in ['dir_a', 'dir_b', 'dir_c', 'dir_d']:
472
589
        tester.assert_(os.path.isdir(d))
473
590
 
474
 
    tester.assertRaises(TransportError, t.mkdir, 'path/doesnt/exist')
475
 
    tester.assertRaises(TransportError, t.mkdir, 'dir_a') # Creating a directory again should fail
 
591
    tester.assertRaises(NoSuchFile, t.mkdir, 'path/doesnt/exist')
 
592
    tester.assertRaises(FileExists, t.mkdir, 'dir_a') # Creating a directory again should fail
476
593
 
477
 
    # This one may fail for some transports.
478
 
    # Specifically, I know RsyncTransport doesn't check for the directory
479
 
    # existing, before it creates it. The reason is that it seems to
480
 
    # expensive, it does check to see if the local directory already exists,
481
 
    # and will throw an exception for that
482
 
    # FIXME: Make this work everywhere
483
 
    #os.mkdir('dir_e')
484
 
    #tester.assertRaises(TransportError, t.mkdir, 'dir_e')
 
594
    os.mkdir('dir_e')
 
595
    tester.assertRaises(FileExists, t.mkdir, 'dir_e')
485
596
 
486
597
    # Test get/put in sub-directories
487
598
    tester.assertEqual(t.put_multi([('dir_a/a', 'contents of dir_a/a'),