blob: 48f1bd5346fd0d2fe29b1ad56d0fa56222e98ad6 [file] [log] [blame]
// 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();
}
}
}