183
# TODO: this only actually accomodates a single block; possibly should support
185
def _recv_bulk(from_file):
186
chunk_len = from_file.readline()
188
chunk_len = int(chunk_len)
190
raise BzrProtocolError("bad chunk length line %r" % chunk_len)
191
bulk = from_file.read(chunk_len)
192
if len(bulk) != chunk_len:
193
raise BzrProtocolError("short read fetching bulk data chunk")
197
class SmartStreamServer(object):
183
class SmartProtocolBase(object):
184
"""Methods common to client and server"""
186
def _send_bulk_data(self, body):
187
"""Send chunked body data"""
188
assert isinstance(body, str)
189
self._out.write('%d\n' % len(body))
190
self._out.write(body)
191
self._out.write('done\n')
194
# TODO: this only actually accomodates a single block; possibly should support
196
def _recv_bulk(self):
197
chunk_len = self._in.readline()
199
chunk_len = int(chunk_len)
201
raise BzrProtocolError("bad chunk length line %r" % chunk_len)
202
bulk = self._in.read(chunk_len)
203
if len(bulk) != chunk_len:
204
raise BzrProtocolError("short read fetching bulk data chunk")
208
def _recv_tuple(self):
209
return _recv_tuple(self._in)
211
def _recv_trailer(self):
212
resp = self._recv_tuple()
213
if resp == ('done', ):
216
self._translate_error(resp)
219
class SmartStreamServer(SmartProtocolBase):
198
220
"""Handles smart commands coming over a stream.
200
222
The stream may be a pipe connected to sshd, or a tcp socket, or an
229
254
"""Send response header"""
230
255
return _send_tuple(self._out, args)
232
def _send_bulk_data(self, body):
233
"""Send chunked body data"""
234
assert isinstance(body, str)
235
self._out.write('%d\n' % len(body))
236
self._out.write(body)
237
self._out.write('done\n')
240
257
def _send_error_and_disconnect(self, exception):
241
258
self._send_tuple(('error', str(exception)))
242
259
self._out.flush()
246
263
def _serve_one_request(self):
247
264
"""Read one request from input, process, send back a response.
259
276
self._send_bulk_data(response.body)
260
277
except errors.NoSuchFile, e:
261
278
self._send_tuple(('enoent', e.path))
279
except errors.FileExists, e:
280
self._send_tuple(('FileExists', e.path))
262
281
except KeyboardInterrupt:
264
283
except Exception, e:
306
328
backing_file = self._backing_transport.get(relpath)
307
329
return SmartServerResponse(('ok',), backing_file.read())
331
def _optional_mode(self, mode):
337
def do_mkdir(self, relpath, mode):
338
self._backing_transport.mkdir(relpath, self._optional_mode(mode))
339
return SmartServerResponse(('ok',))
341
def do_put(self, relpath, mode):
342
self._backing_transport.put(relpath,
343
StringIO(self._recv_body()),
344
self._optional_mode(mode))
345
return SmartServerResponse(('ok',))
347
def do_append(self, relpath, mode):
348
old_length = self._backing_transport.append(relpath, StringIO(self._recv_body()),
349
self._optional_mode(mode))
350
return SmartServerResponse(('appended', '%d' % old_length))
309
352
def do_get_bundle(self, path, revision_id):
310
353
# open transport relative to our base
311
354
t = self._backing_transport.clone(path)
456
499
return SmartTransport(new_url, clone_from=self)
458
501
def is_readonly(self):
459
"""Smart protocol currently only supports readonly operations."""
462
def get_smart_client(self):
465
def _unparse_url(self, path):
466
"""Return URL for a path.
502
"""Smart server transport can do read/write file operations."""
505
def get_smart_client(self):
508
def _unparse_url(self, path):
509
"""Return URL for a path. l
468
511
:see: SFTPUrlHandling._unparse_url
503
546
mutter("%s.get %s", self, relpath)
504
547
remote = self._remote_path(relpath)
505
548
mutter(" remote path: %s", remote)
506
resp = self._call('get', remote)
549
resp = self._client._call('get', remote)
507
550
if resp != ('ok', ):
508
551
self._translate_error(resp)
509
body = self._recv_bulk()
512
## print ' got %d bytes: %s' % (len(body), body[:30])
552
return StringIO(self._client._recv_bulk())
515
def _recv_trailer(self):
516
resp = self._recv_tuple()
517
if resp == ('done', ):
554
def _optional_mode(self, mode):
520
self._translate_error(resp)
522
def _call(self, *args):
523
self._send_tuple(args)
524
return self._recv_tuple()
560
def mkdir(self, relpath, mode=None):
561
resp = self._client._call('mkdir',
562
self._remote_path(relpath),
563
self._optional_mode(mode))
564
self._translate_error(resp)
566
def put(self, relpath, upload_file, mode=None):
567
# FIXME: upload_file is probably not safe for non-ascii characters -
568
# should probably just pass all parameters as length-delimited
570
resp = self._client._call_with_upload('put',
571
(self._remote_path(relpath),
572
self._optional_mode(mode)),
574
self._translate_error(resp)
576
def append(self, relpath, from_file, mode=None):
577
resp = self._client._call_with_upload('append',
578
(self._remote_path(relpath),
579
self._optional_mode(mode)),
581
if resp[0] == 'appended':
583
self._translate_error(resp)
526
585
def _translate_error(self, resp):
527
586
"""Raise an exception from a response"""
590
elif what == 'enoent':
530
591
raise errors.NoSuchFile(resp[1])
592
elif what == 'error':
593
raise BzrProtocolError(unicode(resp[1]))
594
elif what == 'FileExists':
595
raise errors.FileExists(resp[1])
532
raise BzrProtocolError('bad trailer on get: %r' % (resp,))
534
def _recv_bulk(self):
535
return self._client._recv_bulk()
597
raise BzrProtocolError('unexpected smart server error: %r' % (resp,))
537
599
def _send_tuple(self, args):
538
600
self._client._send_tuple(args)
543
605
def disconnect(self):
544
606
self._client.disconnect()
546
def append(self, relpath, from_file):
547
raise errors.TransportNotPossible("writing to smart servers not supported yet")
549
608
def delete(self, relpath):
550
609
raise errors.TransportNotPossible('readonly transport')
552
611
def delete_tree(self, relpath):
553
612
raise errors.TransportNotPossible('readonly transport')
555
def put(self, relpath, f, mode=None):
556
raise errors.TransportNotPossible('readonly transport')
558
def mkdir(self, relpath, mode=None):
559
raise errors.TransportNotPossible('readonly transport')
561
614
def rmdir(self, relpath):
562
615
raise errors.TransportNotPossible('readonly transport')
584
637
return BogusLock(relpath)
587
class SmartStreamClient(object):
640
class SmartStreamClient(SmartProtocolBase):
588
641
"""Connection to smart server over two streams"""
590
643
def __init__(self, from_server, to_server):
591
self._from_server = from_server
592
self._to_server = to_server
644
self._in = from_server
645
self._out = to_server
594
647
def __del__(self):
595
648
self.disconnect()
597
650
def _send_tuple(self, args):
598
_send_tuple(self._to_server, args)
651
_send_tuple(self._out, args)
600
653
def disconnect(self):
601
654
"""Close connection to the server"""
602
self._to_server.close()
603
self._from_server.close()
605
def _recv_bulk(self):
606
return _recv_bulk(self._from_server)
608
def _recv_tuple(self):
609
return _recv_tuple(self._from_server)
655
if getattr(self, '_out'):
657
if getattr(self, '_in'):
660
def _call(self, *args):
661
self._send_tuple(args)
662
return self._recv_tuple()
664
def _call_with_upload(self, method, args, body):
665
"""Call an rpc, supplying bulk upload data.
667
:param method: method name to call
668
:param args: parameter args tuple
669
:param body: upload body as a byte string
671
self._send_tuple((method,) + args)
672
self._send_bulk_data(body)
673
return self._recv_tuple()
611
675
def query_version(self):
612
676
"""Return protocol version number of the server."""