blob: 2fbfece447e9bf18bed9cd4660cf6aa7e3b44642 [file] [log] [blame]
// Copyright 2017 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.
#include <fuchsia/modular/cpp/fidl.h>
#include <fuchsia/net/oldhttp/cpp/fidl.h>
#include <lib/app_driver/cpp/app_driver.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/default.h>
#include <lib/component/cpp/connect.h>
#include <lib/component/cpp/startup_context.h>
#include <lib/entity/cpp/json.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/optional.h>
#include <lib/fxl/command_line.h>
#include <lib/fxl/functional/make_copyable.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/macros.h>
#include "peridot/bin/module_resolver/local_module_resolver.h"
#include "peridot/lib/module_manifest_source/firebase_source.h"
#include "peridot/lib/module_manifest_source/module_package_source.h"
namespace modular {
namespace {
namespace http = ::fuchsia::net::oldhttp;
constexpr char kContextListenerEntitiesKey[] = "entities";
class ModuleResolverApp : fuchsia::modular::ContextListener {
public:
ModuleResolverApp(component::StartupContext* const context, bool is_test)
: context_listener_binding_(this) {
fuchsia::modular::ComponentContextPtr component_context;
context->ConnectToEnvironmentService<fuchsia::modular::ComponentContext>(
component_context.NewRequest());
context->ConnectToEnvironmentService(intelligence_services_.NewRequest());
intelligence_services_->GetContextReader(context_reader_.NewRequest());
resolver_impl_ = std::make_unique<LocalModuleResolver>();
// Set up |resolver_impl_|.
resolver_impl_->AddSource("module_package",
std::make_unique<ModulePackageSource>(context));
// Make |resolver_impl_| a query (ask) handler.
fidl::InterfaceHandle<fuchsia::modular::QueryHandler> query_handler;
resolver_impl_->BindQueryHandler(query_handler.NewRequest());
intelligence_services_->RegisterQueryHandler(std::move(query_handler));
intelligence_services_->GetProposalPublisher(
proposal_publisher_.NewRequest());
fuchsia::modular::ContextQuery query;
fuchsia::modular::ContextSelector selector;
selector.type = fuchsia::modular::ContextValueType::ENTITY;
fuchsia::modular::ContextQueryEntry selector_entry;
selector_entry.key = kContextListenerEntitiesKey;
selector_entry.value = std::move(selector);
fidl::VectorPtr<fuchsia::modular::ContextQueryEntry> selector_array;
selector_array.push_back(std::move(selector_entry));
query.selector = std::move(selector_array);
context_reader_->Subscribe(std::move(query),
context_listener_binding_.NewBinding());
context->outgoing().AddPublicService<fuchsia::modular::ModuleResolver>(
[this](
fidl::InterfaceRequest<fuchsia::modular::ModuleResolver> request) {
resolver_impl_->Connect(std::move(request));
});
}
void Terminate(const std::function<void()>& done) { done(); }
private:
using QueryParamName = std::string;
// |ContextListener|
void OnContextUpdate(fuchsia::modular::ContextUpdate update) override {
fidl::VectorPtr<fuchsia::modular::ContextValue> values;
for (auto& entry : *update.values) {
if (entry.key == kContextListenerEntitiesKey) {
values = std::move(entry.value);
break;
}
}
if (values->empty()) {
return;
}
fuchsia::modular::FindModulesByTypesQuery query;
// The story id to be extracted from the context update.
std::string story_id;
std::map<QueryParamName, fuchsia::modular::LinkMetadata> remember;
for (const auto& value : *values) {
if (!value.meta.story || !value.meta.link || !value.meta.entity) {
continue;
}
story_id = value.meta.story->id;
value.meta.link->Clone(&remember[value.meta.link->name]);
query.parameter_constraints.resize(0);
query.parameter_constraints.push_back(
CreateParameterConstraintFromContextValue(value));
}
resolver_impl_->FindModulesByTypes(
std::move(query),
fxl::MakeCopyable(
[this, story_id, remember = std::move(remember)](
const fuchsia::modular::FindModulesByTypesResponse&
response) mutable {
std::vector<fuchsia::modular::Proposal> new_proposals;
std::vector<fuchsia::modular::Intent>
new_intents; // Only for comparison.
int proposal_count = 0;
for (const auto& module_result : *response.results) {
fuchsia::modular::Intent intent;
new_proposals.push_back(CreateProposalFromModuleResolverResult(
module_result, story_id, proposal_count++, remember,
&intent));
new_intents.push_back(std::move(intent));
}
// This is a proxy for comparing the set of proposals themselves,
// because proposals cannot be cloned, which makes it hard to
// compare them.
if (new_intents == current_proposal_intents_) {
return;
}
// Make sure to remove any existing proposal before creating new
// ones. This is outside the find call to make sure that stale
// suggestions are cleared regardless of the resolver results.
for (const auto& proposal_id : current_proposal_ids_) {
proposal_publisher_->Remove(proposal_id);
}
current_proposal_ids_.clear();
for (uint32_t i = 0; i < new_proposals.size(); ++i) {
current_proposal_ids_.push_back(new_proposals[i].id);
proposal_publisher_->Propose(std::move(new_proposals[i]));
}
current_proposal_intents_ = std::move(new_intents);
}));
}
// Creates a new proposal from the contents of the provided module resolver
// result.
//
// |story_id| is the id of the story that the proposal should add modules to.
// |proposal_id| is the id of the created proposal, which will also be cached
// in |current_proposal_ids_|.
fuchsia::modular::Proposal CreateProposalFromModuleResolverResult(
const fuchsia::modular::FindModulesByTypesResult& module_result,
const std::string& story_id, int proposal_id,
const std::map<QueryParamName, fuchsia::modular::LinkMetadata>& remember,
fuchsia::modular::Intent* intent_out) {
fuchsia::modular::Intent intent;
intent.handler = module_result.module_id;
fidl::VectorPtr<fuchsia::modular::IntentParameter> parameters;
fidl::VectorPtr<fidl::StringPtr> parent_mod_path;
for (const auto& mapping : *module_result.parameter_mappings) {
fuchsia::modular::IntentParameter parameter;
parameter.name = mapping.result_param_name;
const auto& metadata = remember.at(mapping.query_constraint_name.get());
fuchsia::modular::LinkPath link_path;
link_path.module_path = metadata.module_path.Clone();
link_path.link_name = metadata.name;
parameter.data.set_link_path(std::move(link_path));
parameters.push_back(std::move(parameter));
if (!parent_mod_path) {
// TODO(thatguy): Mod parent-child relationships are critical for the
// story shell, and right now the Framework only guarantees mod
// startup ordering based only on Module parent-child relationships:
// parent mods are always restarted before child mods. The Story
// Shell relies on this ordering to be deterministic: if we added
// modA before modB the first time around when creating the story,
// modB *must* be a descendant of modA. This method of using the
// link's module_path of the first link-based parameter we find
// expresses, in short "use the owner of the first shared link
// between this mod and another mod as the parent".
// MS-1473
parent_mod_path = metadata.module_path.Clone();
}
}
intent.parameters = std::move(parameters);
fuchsia::modular::AddMod add_mod;
fidl::Clone(intent, intent_out);
add_mod.intent = std::move(intent);
add_mod.mod_name.push_back(module_result.module_id);
add_mod.surface_parent_mod_name = std::move(parent_mod_path);
fuchsia::modular::StoryCommand command;
command.set_add_mod(std::move(add_mod));
fuchsia::modular::Proposal proposal;
proposal.id = std::to_string(proposal_id);
proposal.affinity.resize(0);
proposal.story_name = story_id;
proposal.on_selected.push_back(std::move(command));
fuchsia::modular::SuggestionDisplay display;
if (module_result.manifest && module_result.manifest->suggestion_headline) {
display.headline = module_result.manifest->suggestion_headline;
display.subheadline = module_result.module_id;
} else {
display.headline = module_result.module_id;
}
display.color = 0x00aa00aa; // argb purple
display.annoyance = fuchsia::modular::AnnoyanceType::NONE;
proposal.display = std::move(display);
return proposal;
}
// Creates a resolver parameter constraint from the contents of the context
// value.
//
// |value| must contain |entity| and |link| in its |meta|. This is to ensure
// that link_info can be constructed for the parameter constraint.
fuchsia::modular::FindModulesByTypesParameterConstraint
CreateParameterConstraintFromContextValue(
const fuchsia::modular::ContextValue& value) {
fuchsia::modular::FindModulesByTypesParameterConstraint entry;
entry.constraint_name = value.meta.link->name;
entry.param_types = value.meta.entity->type.Clone();
return entry;
}
private:
std::unique_ptr<LocalModuleResolver> resolver_impl_;
// The proposal publisher that is used to make proposals based on the current
// context.
fuchsia::modular::ProposalPublisherPtr proposal_publisher_;
// A vector of the ids last passed to the proposal publisher.
std::vector<std::string> current_proposal_ids_;
// Used to compare the old proposal to the new proposals.
// NOTE(thatguy): This is only necessary because context can change
// frequently but not result in new proposals, causing churn in the
// "Next" section of suggestions at a high rate.
std::vector<fuchsia::modular::Intent> current_proposal_intents_;
fuchsia::modular::IntelligenceServicesPtr intelligence_services_;
fuchsia::modular::ContextReaderPtr context_reader_;
fidl::Binding<fuchsia::modular::ContextListener> context_listener_binding_;
FXL_DISALLOW_COPY_AND_ASSIGN(ModuleResolverApp);
};
} // namespace
} // namespace modular
const char kUsage[] = R"USAGE(%s [--test])USAGE";
int main(int argc, const char** argv) {
async::Loop loop(&kAsyncLoopConfigAttachToThread);
auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
if (command_line.HasOption("help")) {
printf(kUsage, argv[0]);
return 0;
}
auto is_test = command_line.HasOption("test");
auto context = component::StartupContext::CreateFromStartupInfo();
modular::AppDriver<modular::ModuleResolverApp> driver(
context->outgoing().deprecated_services(),
std::make_unique<modular::ModuleResolverApp>(context.get(), is_test),
[&loop] { loop.Quit(); });
loop.Run();
return 0;
}