| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file |
| |
| import 'dart:async'; |
| import 'dart:collection'; |
| |
| import 'package:fidl/fidl.dart'; |
| import 'package:fidl_fuchsia_auth/fidl_async.dart' as fidl_auth; |
| import 'package:fidl_fuchsia_modular/fidl_async.dart' as fidl; |
| import 'package:fidl_fuchsia_sys/fidl_async.dart' as fidl_sys; |
| import 'package:fuchsia_logger/logger.dart'; |
| import 'package:fuchsia_modular/lifecycle.dart'; |
| import 'package:fuchsia_services/services.dart'; |
| |
| import '../agent.dart'; |
| import '../agent_task_handler.dart'; |
| import '_agent_context.dart'; |
| |
| /// A concrete implementation of the [Agent] interface. |
| /// |
| /// This class is not intended to be used directly by authors but instead |
| /// should be used by the [Agent] factory constructor. |
| class AgentImpl extends fidl.Agent implements Agent { |
| /// Holds the framework binding connection to this agent. |
| final fidl.AgentBinding _agentBinding = fidl.AgentBinding(); |
| |
| /// The service provider which can be used to expose outgoing services |
| final ServiceProviderImpl _serviceProvider; |
| |
| /// Holds the connection of other components to this agent's service provider |
| final List<fidl_sys.ServiceProviderBinding> _serviceProviderBindings = |
| <fidl_sys.ServiceProviderBinding>[]; |
| |
| /// Holds the outgoing connection of other components to the services provided |
| /// by this agent. |
| final List<AsyncBinding<Object>> _outgoingServicesBindings = |
| <AsyncBinding<Object>>[]; |
| |
| /// Returns the [fidl.AgentContext] for the running module. This variable |
| /// should not be used directly, use the [getContext()] method instead. |
| fidl.AgentContext _agentContext; |
| |
| /// Returns a [fidl_auth.TokenManagerProxy]. It's abstracted to aid with |
| /// testing and this variable should not be used directly, use the |
| /// [getTokenManager()] method instead. |
| fidl_auth.TokenManagerProxy _tokenManagerProxy; |
| |
| AgentTaskHandler _taskHandler; |
| |
| final Queue<String> _outstandingTasks = Queue<String>(); |
| |
| /// The default constructor for this instance. |
| AgentImpl({ |
| Lifecycle lifecycle, |
| StartupContext startupContext, |
| fidl.AgentContext agentContext, |
| ServiceProviderImpl serviceProviderImpl, |
| fidl_auth.TokenManagerProxy tokenManagerProxy, |
| }) : _agentContext = agentContext, |
| _tokenManagerProxy = tokenManagerProxy, |
| _serviceProvider = serviceProviderImpl ?? ServiceProviderImpl() { |
| (lifecycle ??= Lifecycle()).addTerminateListener(_terminate); |
| startupContext ??= StartupContext.fromStartupInfo(); |
| |
| _exposeAgent(startupContext); |
| } |
| |
| @override |
| Future<void> connect( |
| String requestorUrl, |
| InterfaceRequest<fidl_sys.ServiceProvider> services, |
| ) { |
| // Bind this agent's serviceProvider to the client request. |
| // |
| // Note: currently we're ignoring the [requestorUrl] and providing the same |
| // set of services to all clients. |
| _serviceProviderBindings.add( |
| fidl_sys.ServiceProviderBinding()..bind(_serviceProvider, services)); |
| return null; |
| } |
| |
| @override |
| void exposeService<T extends Service>(FutureOr<T> serviceImpl) { |
| if (serviceImpl == null) { |
| throw ArgumentError.notNull('serviceImpl'); |
| } |
| |
| if (serviceImpl is Future) { |
| Future.value(serviceImpl).then((T service) { |
| exposeServiceProvider(() => service, service.$serviceData); |
| }); |
| } else if (serviceImpl is T) { |
| exposeServiceProvider(() => serviceImpl, serviceImpl.$serviceData); |
| } |
| } |
| |
| @override |
| void exposeServiceProvider<T extends Service>( |
| FutureOr<ServiceProvider<T>> serviceProvider, ServiceData serviceData) { |
| if (serviceProvider == null) { |
| throw ArgumentError.notNull('serviceProvider'); |
| } |
| if (serviceData == null) { |
| throw ArgumentError.notNull('serviceData'); |
| } |
| |
| // Add this [serviceImpl] to this agent's serviceProvider so that it can |
| // be accessed the `connected clients of this agent. |
| _serviceProvider.addServiceForName( |
| (InterfaceRequest<T> request) { |
| Future.value(serviceProvider) |
| .then((ServiceProvider<T> providerFunc) => providerFunc()) |
| .then((T service) => _outgoingServicesBindings |
| .add(serviceData.getBinding()..bind(service, request))); |
| }, |
| serviceData.getName(), |
| ); |
| } |
| |
| @override |
| fidl_auth.TokenManagerProxy getTokenManager() { |
| final tokenManagerProxy = _getTokenManager(); |
| _getContext().getTokenManager(tokenManagerProxy.ctrl.request()); |
| return tokenManagerProxy; |
| } |
| |
| @override |
| void registerTaskHandler(AgentTaskHandler taskHandler) { |
| if (taskHandler == null) { |
| throw ArgumentError.notNull('taskHandler'); |
| } |
| |
| if (_taskHandler != null) { |
| throw Exception( |
| 'AgentTaskHandler registration failed because a handler is already ' |
| 'registered.'); |
| } |
| _taskHandler = taskHandler; |
| |
| _outstandingTasks |
| ..forEach(_taskHandler.runTask) |
| ..clear(); |
| } |
| |
| @override |
| Future<void> runTask(String taskId) async { |
| if (taskId == null) { |
| throw ArgumentError.notNull('taskId'); |
| } |
| |
| if (_taskHandler != null) { |
| return _taskHandler.runTask(taskId); |
| } |
| |
| log.warning('Attempting to run a task [$taskId] before a task handler was ' |
| 'registered. Queuing up this task to be executed as soon a task ' |
| 'handler is registered.'); |
| _outstandingTasks.add(taskId); |
| } |
| |
| /// Exposes this [fidl.Agent] instance to the |
| /// [StartupContext#addPublicService]. In other words, advertises this as an |
| /// [fidl.Agent] to the rest of the system via the [StartupContext]. |
| /// |
| /// This class be must called before the first iteration of the event loop. |
| void _exposeAgent(StartupContext startupContext) { |
| startupContext.outgoing.addPublicService( |
| (InterfaceRequest<fidl.Agent> request) { |
| assert(!_agentBinding.isBound); |
| _agentBinding.bind(this, request); |
| }, |
| fidl.Agent.$serviceName, |
| ); |
| } |
| |
| fidl.AgentContext _getContext() => _agentContext ??= getAgentContext(); |
| |
| fidl_auth.TokenManagerProxy _getTokenManager() { |
| return _tokenManagerProxy ??= fidl_auth.TokenManagerProxy(); |
| } |
| |
| // Any necessary cleanup should be done here. |
| Future<void> _terminate() async { |
| _agentBinding.close(); |
| for (fidl_sys.ServiceProviderBinding binding in _serviceProviderBindings) { |
| binding.close(); |
| } |
| for (AsyncBinding<Object> binding in _outgoingServicesBindings) { |
| binding.close(); |
| } |
| } |
| } |