/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 bzrlib/transport/remote.py

1st cut merge of bzr.dev r3907

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
    config,
29
29
    debug,
30
30
    errors,
 
31
    remote,
31
32
    trace,
32
33
    transport,
33
34
    urlutils,
60
61
    RemoteTCPTransport, etc.
61
62
    """
62
63
 
 
64
    # When making a readv request, cap it at requesting 5MB of data
 
65
    _max_readv_bytes = 5*1024*1024
 
66
 
63
67
    # IMPORTANT FOR IMPLEMENTORS: RemoteTransport MUST NOT be given encoding
64
68
    # responsibilities: Put those on SmartClient or similar. This is vital for
65
69
    # the ability to support multiple versions of the smart protocol over time:
143
147
        elif resp == ('no', ):
144
148
            return False
145
149
        else:
146
 
            self._translate_error(resp)
147
 
        raise errors.UnexpectedSmartServerResponse(resp)
 
150
            raise errors.UnexpectedSmartServerResponse(resp)
148
151
 
149
152
    def get_smart_client(self):
150
153
        return self._get_connection()
161
164
        return self._combine_paths(self._path, relpath)
162
165
 
163
166
    def _call(self, method, *args):
164
 
        try:
165
 
            resp = self._call2(method, *args)
166
 
        except errors.ErrorFromSmartServer, err:
167
 
            self._translate_error(err.error_tuple)
168
 
        self._translate_error(resp)
 
167
        resp = self._call2(method, *args)
 
168
        self._ensure_ok(resp)
169
169
 
170
170
    def _call2(self, method, *args):
171
171
        """Call a method on the remote server."""
172
172
        try:
173
173
            return self._client.call(method, *args)
174
174
        except errors.ErrorFromSmartServer, err:
175
 
            self._translate_error(err.error_tuple)
 
175
            # The first argument, if present, is always a path.
 
176
            if args:
 
177
                context = {'relpath': args[0]}
 
178
            else:
 
179
                context = {}
 
180
            self._translate_error(err, **context)
176
181
 
177
182
    def _call_with_body_bytes(self, method, args, body):
178
183
        """Call a method on the remote server with body bytes."""
179
184
        try:
180
185
            return self._client.call_with_body_bytes(method, args, body)
181
186
        except errors.ErrorFromSmartServer, err:
182
 
            self._translate_error(err.error_tuple)
 
187
            # The first argument, if present, is always a path.
 
188
            if args:
 
189
                context = {'relpath': args[0]}
 
190
            else:
 
191
                context = {}
 
192
            self._translate_error(err, **context)
183
193
 
184
194
    def has(self, relpath):
185
195
        """Indicate whether a remote file of the given name exists or not.
192
202
        elif resp == ('no', ):
193
203
            return False
194
204
        else:
195
 
            self._translate_error(resp)
 
205
            raise errors.UnexpectedSmartServerResponse(resp)
196
206
 
197
207
    def get(self, relpath):
198
208
        """Return file-like object reading the contents of a remote file.
206
216
        try:
207
217
            resp, response_handler = self._client.call_expecting_body('get', remote)
208
218
        except errors.ErrorFromSmartServer, err:
209
 
            self._translate_error(err.error_tuple, relpath)
 
219
            self._translate_error(err, relpath)
210
220
        if resp != ('ok', ):
211
221
            response_handler.cancel_read_body()
212
222
            raise errors.UnexpectedSmartServerResponse(resp)
221
231
    def mkdir(self, relpath, mode=None):
222
232
        resp = self._call2('mkdir', self._remote_path(relpath),
223
233
            self._serialise_optional_mode(mode))
224
 
        self._translate_error(resp)
225
234
 
226
235
    def open_write_stream(self, relpath, mode=None):
227
236
        """See Transport.open_write_stream."""
243
252
        resp = self._call_with_body_bytes('put',
244
253
            (self._remote_path(relpath), self._serialise_optional_mode(mode)),
245
254
            upload_contents)
246
 
        self._translate_error(resp)
 
255
        self._ensure_ok(resp)
247
256
        return len(upload_contents)
248
257
 
249
258
    def put_bytes_non_atomic(self, relpath, bytes, mode=None,
260
269
            (self._remote_path(relpath), self._serialise_optional_mode(mode),
261
270
             create_parent_str, self._serialise_optional_mode(dir_mode)),
262
271
            bytes)
263
 
        self._translate_error(resp)
 
272
        self._ensure_ok(resp)
264
273
 
265
274
    def put_file(self, relpath, upload_file, mode=None):
266
275
        # its not ideal to seek back, but currently put_non_atomic_file depends
290
299
            bytes)
291
300
        if resp[0] == 'appended':
292
301
            return int(resp[1])
293
 
        self._translate_error(resp)
 
302
        raise errors.UnexpectedSmartServerResponse(resp)
294
303
 
295
304
    def delete(self, relpath):
296
305
        resp = self._call2('delete', self._remote_path(relpath))
297
 
        self._translate_error(resp)
 
306
        self._ensure_ok(resp)
298
307
 
299
308
    def external_url(self):
300
309
        """See bzrlib.transport.Transport.external_url."""
312
321
        offsets = list(offsets)
313
322
 
314
323
        sorted_offsets = sorted(offsets)
315
 
        # turn the list of offsets into a stack
316
 
        offset_stack = iter(offsets)
317
 
        cur_offset_and_size = offset_stack.next()
318
324
        coalesced = list(self._coalesce_offsets(sorted_offsets,
319
325
                               limit=self._max_readv_combine,
320
 
                               fudge_factor=self._bytes_to_read_before_seek))
321
 
 
322
 
        try:
323
 
            result = self._client.call_with_body_readv_array(
324
 
                ('readv', self._remote_path(relpath),),
325
 
                [(c.start, c.length) for c in coalesced])
326
 
            resp, response_handler = result
327
 
        except errors.ErrorFromSmartServer, err:
328
 
            self._translate_error(err.error_tuple)
329
 
 
330
 
        if resp[0] != 'readv':
331
 
            # This should raise an exception
332
 
            response_handler.cancel_read_body()
333
 
            raise errors.UnexpectedSmartServerResponse(resp)
334
 
 
 
326
                               fudge_factor=self._bytes_to_read_before_seek,
 
327
                               max_size=self._max_readv_bytes))
 
328
 
 
329
        # now that we've coallesced things, avoid making enormous requests
 
330
        requests = []
 
331
        cur_request = []
 
332
        cur_len = 0
 
333
        for c in coalesced:
 
334
            if c.length + cur_len > self._max_readv_bytes:
 
335
                requests.append(cur_request)
 
336
                cur_request = [c]
 
337
                cur_len = c.length
 
338
                continue
 
339
            cur_request.append(c)
 
340
            cur_len += c.length
 
341
        if cur_request:
 
342
            requests.append(cur_request)
 
343
        if 'hpss' in debug.debug_flags:
 
344
            trace.mutter('%s.readv %s offsets => %s coalesced'
 
345
                         ' => %s requests (%s)',
 
346
                         self.__class__.__name__, len(offsets), len(coalesced),
 
347
                         len(requests), sum(map(len, requests)))
 
348
        # Cache the results, but only until they have been fulfilled
 
349
        data_map = {}
 
350
        # turn the list of offsets into a single stack to iterate
 
351
        offset_stack = iter(offsets)
 
352
        # using a list so it can be modified when passing down and coming back
 
353
        next_offset = [offset_stack.next()]
 
354
        for cur_request in requests:
 
355
            try:
 
356
                result = self._client.call_with_body_readv_array(
 
357
                    ('readv', self._remote_path(relpath),),
 
358
                    [(c.start, c.length) for c in cur_request])
 
359
                resp, response_handler = result
 
360
            except errors.ErrorFromSmartServer, err:
 
361
                self._translate_error(err, relpath)
 
362
 
 
363
            if resp[0] != 'readv':
 
364
                # This should raise an exception
 
365
                response_handler.cancel_read_body()
 
366
                raise errors.UnexpectedSmartServerResponse(resp)
 
367
 
 
368
            for res in self._handle_response(offset_stack, cur_request,
 
369
                                             response_handler,
 
370
                                             data_map,
 
371
                                             next_offset):
 
372
                yield res
 
373
 
 
374
    def _handle_response(self, offset_stack, coalesced, response_handler,
 
375
                         data_map, next_offset):
 
376
        cur_offset_and_size = next_offset[0]
335
377
        # FIXME: this should know how many bytes are needed, for clarity.
336
378
        data = response_handler.read_body_bytes()
337
 
        # Cache the results, but only until they have been fulfilled
338
 
        data_map = {}
 
379
        data_offset = 0
339
380
        for c_offset in coalesced:
340
381
            if len(data) < c_offset.length:
341
382
                raise errors.ShortReadvError(relpath, c_offset.start,
342
383
                            c_offset.length, actual=len(data))
343
384
            for suboffset, subsize in c_offset.ranges:
344
385
                key = (c_offset.start+suboffset, subsize)
345
 
                data_map[key] = data[suboffset:suboffset+subsize]
346
 
            data = data[c_offset.length:]
 
386
                this_data = data[data_offset+suboffset:
 
387
                                 data_offset+suboffset+subsize]
 
388
                # Special case when the data is in-order, rather than packing
 
389
                # into a map and then back out again. Benchmarking shows that
 
390
                # this has 100% hit rate, but leave in the data_map work just
 
391
                # in case.
 
392
                # TODO: Could we get away with using buffer() to avoid the
 
393
                #       memory copy?  Callers would need to realize they may
 
394
                #       not have a real string.
 
395
                if key == cur_offset_and_size:
 
396
                    yield cur_offset_and_size[0], this_data
 
397
                    cur_offset_and_size = next_offset[0] = offset_stack.next()
 
398
                else:
 
399
                    data_map[key] = this_data
 
400
            data_offset += c_offset.length
347
401
 
348
402
            # Now that we've read some data, see if we can yield anything back
349
403
            while cur_offset_and_size in data_map:
350
404
                this_data = data_map.pop(cur_offset_and_size)
351
405
                yield cur_offset_and_size[0], this_data
352
 
                cur_offset_and_size = offset_stack.next()
 
406
                cur_offset_and_size = next_offset[0] = offset_stack.next()
353
407
 
354
408
    def rename(self, rel_from, rel_to):
355
409
        self._call('rename',
364
418
    def rmdir(self, relpath):
365
419
        resp = self._call('rmdir', self._remote_path(relpath))
366
420
 
367
 
    def _translate_error(self, resp, orig_path=None):
368
 
        """Raise an exception from a response"""
369
 
        if resp is None:
370
 
            what = None
371
 
        else:
372
 
            what = resp[0]
373
 
        if what == 'ok':
374
 
            return
375
 
        elif what == 'NoSuchFile':
376
 
            if orig_path is not None:
377
 
                error_path = orig_path
378
 
            else:
379
 
                error_path = resp[1]
380
 
            raise errors.NoSuchFile(error_path)
381
 
        elif what == 'error':
382
 
            raise errors.SmartProtocolError(unicode(resp[1]))
383
 
        elif what == 'FileExists':
384
 
            raise errors.FileExists(resp[1])
385
 
        elif what == 'DirectoryNotEmpty':
386
 
            raise errors.DirectoryNotEmpty(resp[1])
387
 
        elif what == 'ShortReadvError':
388
 
            raise errors.ShortReadvError(resp[1], int(resp[2]),
389
 
                                         int(resp[3]), int(resp[4]))
390
 
        elif what in ('UnicodeEncodeError', 'UnicodeDecodeError'):
391
 
            encoding = str(resp[1]) # encoding must always be a string
392
 
            val = resp[2]
393
 
            start = int(resp[3])
394
 
            end = int(resp[4])
395
 
            reason = str(resp[5]) # reason must always be a string
396
 
            if val.startswith('u:'):
397
 
                val = val[2:].decode('utf-8')
398
 
            elif val.startswith('s:'):
399
 
                val = val[2:].decode('base64')
400
 
            if what == 'UnicodeDecodeError':
401
 
                raise UnicodeDecodeError(encoding, val, start, end, reason)
402
 
            elif what == 'UnicodeEncodeError':
403
 
                raise UnicodeEncodeError(encoding, val, start, end, reason)
404
 
        elif what == "ReadOnlyError":
405
 
            raise errors.TransportNotPossible('readonly transport')
406
 
        elif what == "ReadError":
407
 
            if orig_path is not None:
408
 
                error_path = orig_path
409
 
            else:
410
 
                error_path = resp[1]
411
 
            raise errors.ReadError(error_path)
412
 
        elif what == "PermissionDenied":
413
 
            if orig_path is not None:
414
 
                error_path = orig_path
415
 
            else:
416
 
                error_path = resp[1]
417
 
            raise errors.PermissionDenied(error_path)
418
 
        else:
419
 
            raise errors.SmartProtocolError('unexpected smart server error: %r' % (resp,))
 
421
    def _ensure_ok(self, resp):
 
422
        if resp[0] != 'ok':
 
423
            raise errors.UnexpectedSmartServerResponse(resp)
 
424
        
 
425
    def _translate_error(self, err, relpath=None):
 
426
        remote._translate_error(err, path=relpath)
420
427
 
421
428
    def disconnect(self):
422
429
        self.get_smart_medium().disconnect()
423
430
 
424
 
    def delete_tree(self, relpath):
425
 
        raise errors.TransportNotPossible('readonly transport')
426
 
 
427
431
    def stat(self, relpath):
428
432
        resp = self._call2('stat', self._remote_path(relpath))
429
433
        if resp[0] == 'stat':
430
434
            return _SmartStat(int(resp[1]), int(resp[2], 8))
431
 
        else:
432
 
            self._translate_error(resp)
 
435
        raise errors.UnexpectedSmartServerResponse(resp)
433
436
 
434
437
    ## def lock_read(self, relpath):
435
438
    ##     """Lock the given file for shared (read) access.
451
454
        resp = self._call2('list_dir', self._remote_path(relpath))
452
455
        if resp[0] == 'names':
453
456
            return [name.encode('ascii') for name in resp[1:]]
454
 
        else:
455
 
            self._translate_error(resp)
 
457
        raise errors.UnexpectedSmartServerResponse(resp)
456
458
 
457
459
    def iter_files_recursive(self):
458
460
        resp = self._call2('iter_files_recursive', self._remote_path(''))
459
461
        if resp[0] == 'names':
460
462
            return resp[1:]
461
 
        else:
462
 
            self._translate_error(resp)
 
463
        raise errors.UnexpectedSmartServerResponse(resp)
463
464
 
464
465
 
465
466
class RemoteTCPTransport(RemoteTransport):
498
499
    """
499
500
 
500
501
    def _build_medium(self):
501
 
        # ssh will prompt the user for a password if needed and if none is
502
 
        # provided but it will not give it back, so no credentials can be
503
 
        # stored.
504
502
        location_config = config.LocationConfig(self.base)
505
503
        bzr_remote_path = location_config.get_bzr_remote_path()
 
504
        user = self._user
 
505
        if user is None:
 
506
            auth = config.AuthenticationConfig()
 
507
            user = auth.get_user('ssh', self._host, self._port)
506
508
        client_medium = medium.SmartSSHClientMedium(self._host, self._port,
507
 
            self._user, self._password, self.base,
 
509
            user, self._password, self.base,
508
510
            bzr_remote_path=bzr_remote_path)
509
 
        return client_medium, None
 
511
        return client_medium, (user, self._password)
510
512
 
511
513
 
512
514
class RemoteHTTPTransport(RemoteTransport):
566
568
                                   _from_transport=self,
567
569
                                   http_transport=self._http_transport)
568
570
 
 
571
    def _redirected_to(self, source, target):
 
572
        """See transport._redirected_to"""
 
573
        redirected = self._http_transport._redirected_to(source, target)
 
574
        if (redirected is not None
 
575
            and isinstance(redirected, type(self._http_transport))):
 
576
            return RemoteHTTPTransport('bzr+' + redirected.external_url(),
 
577
                                       http_transport=redirected)
 
578
        else:
 
579
            # Either None or a transport for a different protocol
 
580
            return redirected
 
581
 
569
582
 
570
583
def get_test_permutations():
571
584
    """Return (transport, server) permutations for testing."""