1
# Copyright (C) 2006 Canonical Ltd
1
# Copyright (C) 2006-2012, 2016 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
17
17
"""RemoteTransport client for the smart-server.
19
19
This module shouldn't be accessed directly. The classes defined here should be
20
imported from bzrlib.smart.
20
imported from breezy.bzr.smart.
23
from __future__ import absolute_import
23
25
__all__ = ['RemoteTransport', 'RemoteTCPTransport', 'RemoteSSHTransport']
25
from cStringIO import StringIO
36
from bzrlib.smart import client, medium
37
from bzrlib.symbol_versioning import (
38
from ..sixish import (
41
from ..bzr.smart import client, medium
42
44
class _SmartStat(object):
171
173
def _remote_path(self, relpath):
172
174
"""Returns the Unicode version of the absolute path for relpath."""
173
return self._combine_paths(self._path, relpath)
175
return urlutils.URL._combine_paths(self._parsed_url.path, relpath)
175
177
def _call(self, method, *args):
176
178
resp = self._call2(method, *args)
180
182
"""Call a method on the remote server."""
182
184
return self._client.call(method, *args)
183
except errors.ErrorFromSmartServer, err:
185
except errors.ErrorFromSmartServer as err:
184
186
# The first argument, if present, is always a path.
186
188
context = {'relpath': args[0]}
192
194
"""Call a method on the remote server with body bytes."""
194
196
return self._client.call_with_body_bytes(method, args, body)
195
except errors.ErrorFromSmartServer, err:
197
except errors.ErrorFromSmartServer as err:
196
198
# The first argument, if present, is always a path.
198
200
context = {'relpath': args[0]}
219
221
:see: Transport.get_bytes()/get_file()
221
return StringIO(self.get_bytes(relpath))
223
return BytesIO(self.get_bytes(relpath))
223
225
def get_bytes(self, relpath):
224
226
remote = self._remote_path(relpath)
226
228
resp, response_handler = self._client.call_expecting_body('get', remote)
227
except errors.ErrorFromSmartServer, err:
229
except errors.ErrorFromSmartServer as err:
228
230
self._translate_error(err, relpath)
229
231
if resp != ('ok', ):
230
232
response_handler.cancel_read_body()
248
250
transport._file_streams[self.abspath(relpath)] = result
251
def put_bytes(self, relpath, upload_contents, mode=None):
252
# FIXME: upload_file is probably not safe for non-ascii characters -
253
# should probably just pass all parameters as length-delimited
255
if type(upload_contents) is unicode:
256
# Although not strictly correct, we raise UnicodeEncodeError to be
257
# compatible with other transports.
258
raise UnicodeEncodeError(
259
'undefined', upload_contents, 0, 1,
260
'put_bytes must be given bytes, not unicode.')
261
resp = self._call_with_body_bytes('put',
253
def put_bytes(self, relpath, raw_bytes, mode=None):
254
if not isinstance(raw_bytes, str):
256
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
257
resp = self._call_with_body_bytes(
262
259
(self._remote_path(relpath), self._serialise_optional_mode(mode)),
264
261
self._ensure_ok(resp)
265
return len(upload_contents)
262
return len(raw_bytes)
267
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
264
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
268
265
create_parent_dir=False,
270
267
"""See Transport.put_bytes_non_atomic."""
277
274
'put_non_atomic',
278
275
(self._remote_path(relpath), self._serialise_optional_mode(mode),
279
276
create_parent_str, self._serialise_optional_mode(dir_mode)),
281
278
self._ensure_ok(resp)
283
280
def put_file(self, relpath, upload_file, mode=None):
359
356
# turn the list of offsets into a single stack to iterate
360
357
offset_stack = iter(offsets)
361
358
# using a list so it can be modified when passing down and coming back
362
next_offset = [offset_stack.next()]
359
next_offset = [next(offset_stack)]
363
360
for cur_request in requests:
365
362
result = self._client.call_with_body_readv_array(
366
363
('readv', self._remote_path(relpath),),
367
364
[(c.start, c.length) for c in cur_request])
368
365
resp, response_handler = result
369
except errors.ErrorFromSmartServer, err:
366
except errors.ErrorFromSmartServer as err:
370
367
self._translate_error(err, relpath)
372
369
if resp[0] != 'readv':
403
400
# not have a real string.
404
401
if key == cur_offset_and_size:
405
402
yield cur_offset_and_size[0], this_data
406
cur_offset_and_size = next_offset[0] = offset_stack.next()
403
cur_offset_and_size = next_offset[0] = next(offset_stack)
408
405
data_map[key] = this_data
409
406
data_offset += c_offset.length
412
409
while cur_offset_and_size in data_map:
413
410
this_data = data_map.pop(cur_offset_and_size)
414
411
yield cur_offset_and_size[0], this_data
415
cur_offset_and_size = next_offset[0] = offset_stack.next()
412
cur_offset_and_size = next_offset[0] = next(offset_stack)
417
414
def rename(self, rel_from, rel_to):
418
415
self._call('rename',
435
432
remote._translate_error(err, path=relpath)
437
434
def disconnect(self):
438
self.get_smart_medium().disconnect()
435
m = self.get_smart_medium()
440
439
def stat(self, relpath):
441
440
resp = self._call2('stat', self._remote_path(relpath))
482
481
def _build_medium(self):
483
482
client_medium = medium.SmartTCPClientMedium(
484
self._host, self._port, self.base)
483
self._parsed_url.host, self._parsed_url.port, self.base)
485
484
return client_medium, None
495
494
def _build_medium(self):
496
495
client_medium = medium.SmartTCPClientMedium(
497
self._host, self._port, self.base)
496
self._parsed_url.host, self._parsed_url.port, self.base)
498
497
client_medium._protocol_version = 2
499
498
client_medium._remember_remote_is_before((1, 6))
500
499
return client_medium, None
510
509
def _build_medium(self):
511
510
location_config = config.LocationConfig(self.base)
512
511
bzr_remote_path = location_config.get_bzr_remote_path()
512
user = self._parsed_url.user
515
514
auth = config.AuthenticationConfig()
516
user = auth.get_user('ssh', self._host, self._port)
517
client_medium = medium.SmartSSHClientMedium(self._host, self._port,
518
user, self._password, self.base,
519
bzr_remote_path=bzr_remote_path)
520
return client_medium, (user, self._password)
515
user = auth.get_user('ssh', self._parsed_url.host,
516
self._parsed_url.port)
517
ssh_params = medium.SSHParams(self._parsed_url.host,
518
self._parsed_url.port, user, self._parsed_url.password,
520
client_medium = medium.SmartSSHClientMedium(self.base, ssh_params)
521
return client_medium, (user, self._parsed_url.password)
523
524
class RemoteHTTPTransport(RemoteTransport):
537
538
# url only for an intial construction (when the url came from the
539
540
http_url = base[len('bzr+'):]
540
self._http_transport = transport.get_transport(http_url)
541
self._http_transport = transport.get_transport_from_url(http_url)
542
543
self._http_transport = http_transport
543
544
super(RemoteHTTPTransport, self).__init__(
601
602
"""Return (transport, server) permutations for testing."""
602
603
### We may need a little more test framework support to construct an
603
604
### appropriate RemoteTransport in the future.
604
from bzrlib.tests import test_server
605
from ..tests import test_server
605
606
return [(RemoteTCPTransport, test_server.SmartTCPServer_for_testing)]