mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
467 lines
15 KiB
Python
467 lines
15 KiB
Python
# Copyright 2015 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""
|
|
The metaclasses used by the mojo python bindings for interfaces.
|
|
|
|
It is splitted from mojo_bindings.reflection because it uses some generated code
|
|
that would create a cyclic dependency.
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
|
|
# pylint: disable=F0401
|
|
import interface_control_messages_mojom
|
|
import mojo_bindings.messaging as messaging
|
|
import mojo_bindings.promise as promise
|
|
import mojo_bindings.reflection as reflection
|
|
import mojo_bindings.serialization as serialization
|
|
import mojo_system
|
|
|
|
|
|
class MojoInterfaceType(type):
|
|
"""Meta class for interfaces.
|
|
|
|
Usage:
|
|
class MyInterface(object):
|
|
__metaclass__ = MojoInterfaceType
|
|
DESCRIPTOR = {
|
|
'fully_qualified_name': 'service::MyInterface'
|
|
'version': 3,
|
|
'methods': [
|
|
{
|
|
'name': 'FireAndForget',
|
|
'ordinal': 0,
|
|
'parameters': [
|
|
SingleFieldGroup('x', _descriptor.TYPE_INT32, 0, 0),
|
|
]
|
|
},
|
|
{
|
|
'name': 'Ping',
|
|
'ordinal': 1,
|
|
'parameters': [
|
|
SingleFieldGroup('x', _descriptor.TYPE_INT32, 0, 0),
|
|
],
|
|
'responses': [
|
|
SingleFieldGroup('x', _descriptor.TYPE_INT32, 0, 0),
|
|
],
|
|
},
|
|
],
|
|
}
|
|
"""
|
|
|
|
def __new__(mcs, name, bases, dictionary):
|
|
# If one of the base class is already an interface type, do not edit the
|
|
# class.
|
|
for base in bases:
|
|
if isinstance(base, mcs):
|
|
return type.__new__(mcs, name, bases, dictionary)
|
|
|
|
descriptor = dictionary.pop('DESCRIPTOR', {})
|
|
|
|
methods = [_MethodDescriptor(x) for x in descriptor.get('methods', [])]
|
|
for method in methods:
|
|
dictionary[method.name] = _NotImplemented
|
|
fully_qualified_name = descriptor['fully_qualified_name']
|
|
|
|
interface_manager = InterfaceManager(
|
|
fully_qualified_name, descriptor['version'], methods)
|
|
dictionary.update({
|
|
'manager': None,
|
|
'_interface_manager': interface_manager,
|
|
})
|
|
|
|
interface_class = type.__new__(mcs, name, bases, dictionary)
|
|
interface_manager.interface_class = interface_class
|
|
return interface_class
|
|
|
|
@property
|
|
def manager(cls):
|
|
return cls._interface_manager
|
|
|
|
# Prevent adding new attributes, or mutating constants.
|
|
def __setattr__(cls, key, value):
|
|
raise AttributeError('can\'t set attribute')
|
|
|
|
# Prevent deleting constants.
|
|
def __delattr__(cls, key):
|
|
raise AttributeError('can\'t delete attribute')
|
|
|
|
|
|
class InterfaceManager(object):
|
|
"""
|
|
Manager for an interface class. The manager contains the operation that allows
|
|
to bind an implementation to a pipe, or to generate a proxy for an interface
|
|
over a pipe.
|
|
"""
|
|
|
|
def __init__(self, name, version, methods):
|
|
self.name = name
|
|
self.version = version
|
|
self.methods = methods
|
|
self.interface_class = None
|
|
self._proxy_class = None
|
|
self._stub_class = None
|
|
|
|
def Proxy(self, handle, version=0):
|
|
router = messaging.Router(handle)
|
|
error_handler = _ProxyErrorHandler()
|
|
router.SetErrorHandler(error_handler)
|
|
router.Start()
|
|
return self._InternalProxy(router, error_handler, version)
|
|
|
|
# pylint: disable=W0212
|
|
def Bind(self, impl, handle):
|
|
router = messaging.Router(handle)
|
|
router.SetIncomingMessageReceiver(self._Stub(impl))
|
|
error_handler = _ProxyErrorHandler()
|
|
router.SetErrorHandler(error_handler)
|
|
|
|
# Retain the router, until an error happen.
|
|
retainer = _Retainer(router)
|
|
def Cleanup(_):
|
|
retainer.release()
|
|
error_handler.AddCallback(Cleanup)
|
|
|
|
# Give an instance manager to the implementation to allow it to close
|
|
# the connection.
|
|
impl.manager = InstanceManager(self, router, error_handler)
|
|
|
|
router.Start()
|
|
|
|
def NewRequest(self):
|
|
pipe = mojo_system.MessagePipe()
|
|
return (self.Proxy(pipe.handle0), reflection.InterfaceRequest(pipe.handle1))
|
|
|
|
def _InternalProxy(self, router, error_handler, version):
|
|
if error_handler is None:
|
|
error_handler = _ProxyErrorHandler()
|
|
|
|
if not self._proxy_class:
|
|
dictionary = {
|
|
'__module__': __name__,
|
|
'__init__': _ProxyInit,
|
|
}
|
|
for method in self.methods:
|
|
dictionary[method.name] = _ProxyMethodCall(method)
|
|
self._proxy_class = type(
|
|
'%sProxy' % self.name,
|
|
(self.interface_class, reflection.InterfaceProxy),
|
|
dictionary)
|
|
|
|
proxy = self._proxy_class(router, error_handler)
|
|
# Give an instance manager to the proxy to allow to close the connection.
|
|
proxy.manager = ProxyInstanceManager(
|
|
self, proxy, router, error_handler, version)
|
|
return proxy
|
|
|
|
def _Stub(self, impl):
|
|
if not self._stub_class:
|
|
accept_method = _StubAccept(self.methods)
|
|
dictionary = {
|
|
'__module__': __name__,
|
|
'__init__': _StubInit,
|
|
'Accept': accept_method,
|
|
'AcceptWithResponder': accept_method,
|
|
}
|
|
self._stub_class = type('%sStub' % self.name,
|
|
(messaging.MessageReceiverWithResponder,),
|
|
dictionary)
|
|
return self._stub_class(impl)
|
|
|
|
|
|
class InstanceManager(object):
|
|
"""
|
|
Manager for the implementation of an interface or a proxy. The manager allows
|
|
to control the connection over the pipe.
|
|
"""
|
|
def __init__(self, interface_manager, router, error_handler):
|
|
self.interface_manager = interface_manager
|
|
self._router = router
|
|
self._error_handler = error_handler
|
|
assert self._error_handler is not None
|
|
|
|
def Close(self):
|
|
self._error_handler.OnClose()
|
|
self._router.Close()
|
|
|
|
def PassMessagePipe(self):
|
|
self._error_handler.OnClose()
|
|
return self._router.PassMessagePipe()
|
|
|
|
def AddOnErrorCallback(self, callback):
|
|
self._error_handler.AddCallback(lambda _: callback(), False)
|
|
|
|
|
|
class ProxyInstanceManager(InstanceManager):
|
|
"""
|
|
Manager for the implementation of a proxy. The manager allows to control the
|
|
connection over the pipe.
|
|
"""
|
|
def __init__(self, interface_manager, proxy, router, error_handler, version):
|
|
super(ProxyInstanceManager, self).__init__(
|
|
interface_manager, router, error_handler)
|
|
self.proxy = proxy
|
|
self.version = version
|
|
self._run_method = _ProxyMethodCall(_BaseMethodDescriptor(
|
|
'Run',
|
|
interface_control_messages_mojom.RUN_MESSAGE_ID,
|
|
interface_control_messages_mojom.RunMessageParams,
|
|
interface_control_messages_mojom.RunResponseMessageParams))
|
|
self._run_or_close_pipe_method = _ProxyMethodCall(_BaseMethodDescriptor(
|
|
'RunOrClosePipe',
|
|
interface_control_messages_mojom.RUN_OR_CLOSE_PIPE_MESSAGE_ID,
|
|
interface_control_messages_mojom.RunOrClosePipeMessageParams,
|
|
None))
|
|
|
|
def QueryVersion(self):
|
|
params = interface_control_messages_mojom.RunMessageParams()
|
|
params.reserved0 = 16
|
|
params.reserved1 = 0
|
|
params.query_version = (
|
|
interface_control_messages_mojom.QueryVersion())
|
|
def ToVersion(r):
|
|
self.version = r.query_version_result.version
|
|
return self.version
|
|
return self._run_method(self.proxy, **params.AsDict()).Then(ToVersion)
|
|
|
|
def RequireVersion(self, version):
|
|
if self.version >= version:
|
|
return
|
|
self.version = version
|
|
params = interface_control_messages_mojom.RunOrClosePipeMessageParams()
|
|
params.reserved0 = 16
|
|
params.reserved1 = 0
|
|
params.require_version = interface_control_messages_mojom.RequireVersion()
|
|
params.require_version.version = version
|
|
return self._run_or_close_pipe_method(self.proxy, **params.AsDict())
|
|
|
|
|
|
class _BaseMethodDescriptor(object):
|
|
def __init__(self, name, ordinal, parameters_struct, response_struct):
|
|
self.name = name
|
|
self.ordinal = ordinal
|
|
self.parameters_struct = parameters_struct
|
|
self.response_struct = response_struct
|
|
|
|
|
|
class _MethodDescriptor(_BaseMethodDescriptor):
|
|
def __init__(self, descriptor):
|
|
name = descriptor['name']
|
|
super(_MethodDescriptor, self).__init__(
|
|
name,
|
|
descriptor['ordinal'],
|
|
_ConstructParameterStruct(
|
|
descriptor['parameters'], name, "Parameters"),
|
|
_ConstructParameterStruct(
|
|
descriptor.get('responses'), name, "Responses"))
|
|
|
|
|
|
def _ConstructParameterStruct(descriptor, name, suffix):
|
|
if descriptor is None:
|
|
return None
|
|
parameter_dictionary = {
|
|
'__metaclass__': reflection.MojoStructType,
|
|
'__module__': __name__,
|
|
'DESCRIPTOR': descriptor,
|
|
}
|
|
return reflection.MojoStructType(
|
|
'%s%s' % (name, suffix),
|
|
(object,),
|
|
parameter_dictionary)
|
|
|
|
|
|
class _ProxyErrorHandler(messaging.ConnectionErrorHandler):
|
|
def __init__(self):
|
|
messaging.ConnectionErrorHandler.__init__(self)
|
|
self._callbacks = dict()
|
|
|
|
def OnError(self, result):
|
|
if self._callbacks is None:
|
|
return
|
|
exception = messaging.MessagingException('Mojo error: %d' % result)
|
|
for (callback, _) in self._callbacks.iteritems():
|
|
callback(exception)
|
|
self._callbacks = None
|
|
|
|
def OnClose(self):
|
|
if self._callbacks is None:
|
|
return
|
|
exception = messaging.MessagingException('Router has been closed.')
|
|
for (callback, call_on_close) in self._callbacks.iteritems():
|
|
if call_on_close:
|
|
callback(exception)
|
|
self._callbacks = None
|
|
|
|
def AddCallback(self, callback, call_on_close=True):
|
|
if self._callbacks is not None:
|
|
self._callbacks[callback] = call_on_close
|
|
|
|
def RemoveCallback(self, callback):
|
|
if self._callbacks:
|
|
del self._callbacks[callback]
|
|
|
|
|
|
class _Retainer(object):
|
|
|
|
# Set to force instances to be retained.
|
|
_RETAINED = set()
|
|
|
|
def __init__(self, retained):
|
|
self._retained = retained
|
|
_Retainer._RETAINED.add(self)
|
|
|
|
def release(self):
|
|
self._retained = None
|
|
_Retainer._RETAINED.remove(self)
|
|
|
|
|
|
def _ProxyInit(self, router, error_handler):
|
|
self._router = router
|
|
self._error_handler = error_handler
|
|
|
|
|
|
# pylint: disable=W0212
|
|
def _ProxyMethodCall(method):
|
|
flags = messaging.NO_FLAG
|
|
if method.response_struct:
|
|
flags = messaging.MESSAGE_EXPECTS_RESPONSE_FLAG
|
|
def _Call(self, *args, **kwargs):
|
|
def GenerationMethod(resolve, reject):
|
|
message = _GetMessage(method, flags, None, *args, **kwargs)
|
|
if method.response_struct:
|
|
def Accept(message):
|
|
try:
|
|
assert message.header.message_type == method.ordinal
|
|
payload = message.payload
|
|
response = method.response_struct.Deserialize(
|
|
serialization.RootDeserializationContext(payload.data,
|
|
payload.handles))
|
|
as_dict = response.AsDict()
|
|
if len(as_dict) == 1:
|
|
value = as_dict.values()[0]
|
|
if not isinstance(value, dict):
|
|
response = value
|
|
resolve(response)
|
|
return True
|
|
except Exception as e:
|
|
# Adding traceback similarly to python 3.0 (pep-3134)
|
|
e.__traceback__ = sys.exc_info()[2]
|
|
reject(e)
|
|
return False
|
|
finally:
|
|
self._error_handler.RemoveCallback(reject)
|
|
|
|
self._error_handler.AddCallback(reject)
|
|
if not self._router.AcceptWithResponder(
|
|
message, messaging.ForwardingMessageReceiver(Accept)):
|
|
self._error_handler.RemoveCallback(reject)
|
|
reject(messaging.MessagingException("Unable to send message."))
|
|
else:
|
|
if (self._router.Accept(message)):
|
|
resolve(None)
|
|
else:
|
|
reject(messaging.MessagingException("Unable to send message."))
|
|
return promise.Promise(GenerationMethod)
|
|
return _Call
|
|
|
|
|
|
def _GetMessageWithStruct(struct, ordinal, flags, request_id):
|
|
header = messaging.MessageHeader(
|
|
ordinal, flags, 0 if request_id is None else request_id)
|
|
data = header.Serialize()
|
|
(payload, handles) = struct.Serialize()
|
|
data.extend(payload)
|
|
return messaging.Message(data, handles, header)
|
|
|
|
|
|
def _GetMessage(method, flags, request_id, *args, **kwargs):
|
|
if flags == messaging.MESSAGE_IS_RESPONSE_FLAG:
|
|
struct = method.response_struct(*args, **kwargs)
|
|
else:
|
|
struct = method.parameters_struct(*args, **kwargs)
|
|
return _GetMessageWithStruct(struct, method.ordinal, flags, request_id)
|
|
|
|
|
|
def _StubInit(self, impl):
|
|
self.impl = impl
|
|
|
|
|
|
def _StubAccept(methods):
|
|
methods_by_ordinal = dict((m.ordinal, m) for m in methods)
|
|
def Accept(self, message, responder=None):
|
|
try:
|
|
header = message.header
|
|
assert header.expects_response == bool(responder)
|
|
if header.message_type == interface_control_messages_mojom.RUN_MESSAGE_ID:
|
|
return _RunMessage(self.impl.manager, message, responder)
|
|
if (header.message_type ==
|
|
interface_control_messages_mojom.RUN_OR_CLOSE_PIPE_MESSAGE_ID):
|
|
return _RunMessageOrClosePipe(self.impl.manager, message)
|
|
assert header.message_type in methods_by_ordinal
|
|
method = methods_by_ordinal[header.message_type]
|
|
payload = message.payload
|
|
parameters = method.parameters_struct.Deserialize(
|
|
serialization.RootDeserializationContext(
|
|
payload.data, payload.handles)).AsDict()
|
|
response = getattr(self.impl, method.name)(**parameters)
|
|
if header.expects_response:
|
|
@promise.async
|
|
def SendResponse(response):
|
|
if isinstance(response, dict):
|
|
response_message = _GetMessage(method,
|
|
messaging.MESSAGE_IS_RESPONSE_FLAG,
|
|
header.request_id,
|
|
**response)
|
|
else:
|
|
response_message = _GetMessage(method,
|
|
messaging.MESSAGE_IS_RESPONSE_FLAG,
|
|
header.request_id,
|
|
response)
|
|
return responder.Accept(response_message)
|
|
p = SendResponse(response)
|
|
if self.impl.manager:
|
|
# Close the connection in case of error.
|
|
p.Catch(lambda _: self.impl.manager.Close())
|
|
return True
|
|
# pylint: disable=W0702
|
|
except:
|
|
# Close the connection in case of error.
|
|
logging.warning(
|
|
'Error occured in accept method. Connection will be closed.')
|
|
logging.debug("Exception", exc_info=True)
|
|
if self.impl.manager:
|
|
self.impl.manager.Close()
|
|
return False
|
|
return Accept
|
|
|
|
|
|
def _RunMessage(manager, message, responder):
|
|
response = interface_control_messages_mojom.RunResponseMessageParams()
|
|
response.reserved0 = 16
|
|
response.reserved1 = 0
|
|
response.query_version_result = (
|
|
interface_control_messages_mojom.QueryVersionResult())
|
|
response.query_version_result.version = manager.interface_manager.version
|
|
response_message = _GetMessageWithStruct(
|
|
response,
|
|
interface_control_messages_mojom.RUN_MESSAGE_ID,
|
|
messaging.MESSAGE_IS_RESPONSE_FLAG,
|
|
message.header.request_id)
|
|
return responder.Accept(response_message)
|
|
|
|
|
|
def _RunMessageOrClosePipe(manager, message):
|
|
payload = message.payload
|
|
query = (
|
|
interface_control_messages_mojom.RunOrClosePipeMessageParams.Deserialize(
|
|
serialization.RootDeserializationContext(payload.data,
|
|
payload.handles)))
|
|
return query.require_version.version <= manager.interface_manager.version
|
|
|
|
|
|
def _NotImplemented(*_1, **_2):
|
|
raise NotImplementedError()
|