/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/smart/client.py

  • Committer: Andrew Bennetts
  • Date: 2008-05-19 11:43:41 UTC
  • mto: This revision was merged to the branch mainline in revision 3439.
  • Revision ID: andrew.bennetts@canonical.com-20080519114341-qe8cj0ejegu2u000
Fix incidental breakage in blackbox/test_serve.py

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2008 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import urllib
 
18
 
 
19
import bzrlib
 
20
from bzrlib.smart import message, protocol
 
21
from bzrlib.trace import warning
 
22
from bzrlib import urlutils, errors
 
23
 
 
24
 
 
25
class _SmartClient(object):
 
26
 
 
27
    def __init__(self, medium, headers=None):
 
28
        """Constructor.
 
29
 
 
30
        :param medium: a SmartClientMedium
 
31
        """
 
32
        self._medium = medium
 
33
        if headers is None:
 
34
            self._headers = {'Software version': bzrlib.__version__}
 
35
        else:
 
36
            self._headers = dict(headers)
 
37
 
 
38
    def _send_request(self, protocol_version, method, args, body=None,
 
39
                      readv_body=None):
 
40
        encoder, response_handler = self._construct_protocol(
 
41
            protocol_version)
 
42
        encoder.set_headers(self._headers)
 
43
        if body is not None:
 
44
            if readv_body is not None:
 
45
                raise AssertionError(
 
46
                    "body and readv_body are mutually exclusive.")
 
47
            encoder.call_with_body_bytes((method, ) + args, body)
 
48
        elif readv_body is not None:
 
49
            encoder.call_with_body_readv_array((method, ) + args,
 
50
                    readv_body)
 
51
        else:
 
52
            encoder.call(method, *args)
 
53
        return response_handler
 
54
 
 
55
    def _call_and_read_response(self, method, args, body=None, readv_body=None,
 
56
            expect_response_body=True):
 
57
        if self._medium._protocol_version is not None:
 
58
            response_handler = self._send_request(
 
59
                self._medium._protocol_version, method, args, body=body,
 
60
                readv_body=readv_body)
 
61
            return (response_handler.read_response_tuple(
 
62
                        expect_body=expect_response_body),
 
63
                    response_handler)
 
64
        else:
 
65
            for protocol_version in [3, 2]:
 
66
                response_handler = self._send_request(
 
67
                    protocol_version, method, args, body=body,
 
68
                    readv_body=readv_body)
 
69
                try:
 
70
                    response_tuple = response_handler.read_response_tuple(
 
71
                        expect_body=expect_response_body)
 
72
                except errors.UnexpectedProtocolVersionMarker, err:
 
73
                    # TODO: We could recover from this without disconnecting if
 
74
                    # we recognise the protocol version.
 
75
                    warning(
 
76
                        'Server does not understand Bazaar network protocol %d,'
 
77
                        ' reconnecting.  (Upgrade the server to avoid this.)'
 
78
                        % (protocol_version,))
 
79
                    self._medium.disconnect()
 
80
                    continue
 
81
                else:
 
82
                    self._medium._protocol_version = protocol_version
 
83
                    return response_tuple, response_handler
 
84
            raise errors.SmartProtocolError(
 
85
                'Server is not a Bazaar server: ' + str(err))
 
86
 
 
87
    def _construct_protocol(self, version):
 
88
        request = self._medium.get_request()
 
89
        if version == 3:
 
90
            request_encoder = protocol.ProtocolThreeRequester(request)
 
91
            response_handler = message.ConventionalResponseHandler()
 
92
            response_proto = protocol.ProtocolThreeDecoder(
 
93
                response_handler, expect_version_marker=True)
 
94
            response_handler.setProtoAndMediumRequest(response_proto, request)
 
95
        elif version == 2:
 
96
            request_encoder = protocol.SmartClientRequestProtocolTwo(request)
 
97
            response_handler = request_encoder
 
98
        else:
 
99
            request_encoder = protocol.SmartClientRequestProtocolOne(request)
 
100
            response_handler = request_encoder
 
101
        return request_encoder, response_handler
 
102
 
 
103
    def call(self, method, *args):
 
104
        """Call a method on the remote server."""
 
105
        result, protocol = self.call_expecting_body(method, *args)
 
106
        protocol.cancel_read_body()
 
107
        return result
 
108
 
 
109
    def call_expecting_body(self, method, *args):
 
110
        """Call a method and return the result and the protocol object.
 
111
        
 
112
        The body can be read like so::
 
113
 
 
114
            result, smart_protocol = smart_client.call_expecting_body(...)
 
115
            body = smart_protocol.read_body_bytes()
 
116
        """
 
117
        return self._call_and_read_response(
 
118
            method, args, expect_response_body=True)
 
119
 
 
120
    def call_with_body_bytes(self, method, args, body):
 
121
        """Call a method on the remote server with body bytes."""
 
122
        if type(method) is not str:
 
123
            raise TypeError('method must be a byte string, not %r' % (method,))
 
124
        for arg in args:
 
125
            if type(arg) is not str:
 
126
                raise TypeError('args must be byte strings, not %r' % (args,))
 
127
        if type(body) is not str:
 
128
            raise TypeError('body must be byte string, not %r' % (body,))
 
129
        response, response_handler = self._call_and_read_response(
 
130
            method, args, body=body, expect_response_body=False)
 
131
        return response
 
132
 
 
133
    def call_with_body_bytes_expecting_body(self, method, args, body):
 
134
        """Call a method on the remote server with body bytes."""
 
135
        if type(method) is not str:
 
136
            raise TypeError('method must be a byte string, not %r' % (method,))
 
137
        for arg in args:
 
138
            if type(arg) is not str:
 
139
                raise TypeError('args must be byte strings, not %r' % (args,))
 
140
        if type(body) is not str:
 
141
            raise TypeError('body must be byte string, not %r' % (body,))
 
142
        response, response_handler = self._call_and_read_response(
 
143
            method, args, body=body, expect_response_body=True)
 
144
        return (response, response_handler)
 
145
 
 
146
    def call_with_body_readv_array(self, args, body):
 
147
        response, response_handler = self._call_and_read_response(
 
148
                args[0], args[1:], readv_body=body, expect_response_body=True)
 
149
        return (response, response_handler)
 
150
 
 
151
    def remote_path_from_transport(self, transport):
 
152
        """Convert transport into a path suitable for using in a request.
 
153
        
 
154
        Note that the resulting remote path doesn't encode the host name or
 
155
        anything but path, so it is only safe to use it in requests sent over
 
156
        the medium from the matching transport.
 
157
        """
 
158
        base = self._medium.base
 
159
        if base.startswith('bzr+'):
 
160
            base = base[4:]
 
161
        if (base.startswith('http://') or base.startswith('https://')):
 
162
            # XXX: There seems to be a bug here: http+urllib:// and
 
163
            # http+pycurl:// ought to be treated the same as http://, I think.
 
164
            #   - Andrew Bennetts, 2008-05-19.
 
165
            medium_base = base
 
166
        else:
 
167
            medium_base = urlutils.join(base, '/')
 
168
 
 
169
        transport_base = transport.base
 
170
        if transport_base.startswith('bzr+'):
 
171
            transport_base = transport_base[4:]
 
172
            
 
173
        rel_url = urlutils.relative_url(medium_base, transport_base)
 
174
        return urllib.unquote(rel_url)
 
175