blob: b82cb9dc4db5a9a5154e55f422b4df2404afe1ed [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 "peridot/bin/module_resolver/local_module_resolver.h"
#include <algorithm>
#include <fuchsia/modular/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/context/cpp/context_helper.h>
#include <lib/entity/cpp/json.h>
#include <src/lib/fxl/strings/split_string.h>
#include "peridot/lib/fidl/clone.h"
namespace modular {
namespace {
// << operator for LocalModuleResolver::ManifestId.
std::ostream& operator<<(std::ostream& o,
const std::pair<std::string, std::string>& id) {
return o << id.first << ":" << id.second;
}
} // namespace
LocalModuleResolver::LocalModuleResolver()
: already_checking_if_sources_are_ready_(false), weak_factory_(this) {}
LocalModuleResolver::~LocalModuleResolver() = default;
std::set<LocalModuleResolver::ManifestId> LocalModuleResolver::FindHandlers(
ModuleUri handler) {
// Search through each known repository source for |handler|, and return a
// set<ManifestId> containing them.
std::set<ManifestId> manifests;
for (auto& source : ready_sources_) {
auto it = manifests_.find(ManifestId(source, handler));
if (it != manifests_.end()) {
manifests.insert(it->first);
}
}
return manifests;
}
void LocalModuleResolver::AddSource(
std::string source_name, std::unique_ptr<ModuleManifestSource> repo) {
FXL_CHECK(bindings_.size() == 0);
auto ptr = repo.get();
sources_.emplace(source_name, std::move(repo));
ptr->Watch(
async_get_default_dispatcher(),
[this, source_name]() { OnSourceIdle(source_name); },
[this, source_name](std::string module_uri,
fuchsia::modular::ModuleManifest manifest) {
OnNewManifestEntry(source_name, module_uri, std::move(manifest));
},
[this, source_name](std::string module_uri) {
OnRemoveManifestEntry(source_name, std::move(module_uri));
});
}
void LocalModuleResolver::Connect(
fidl::InterfaceRequest<fuchsia::modular::ModuleResolver> request) {
if (!AllSourcesAreReady()) {
PeriodicCheckIfSourcesAreReady();
pending_bindings_.push_back(std::move(request));
} else {
bindings_.AddBinding(this, std::move(request));
}
}
class LocalModuleResolver::FindModulesCall
: public Operation<fuchsia::modular::FindModulesResponse> {
public:
FindModulesCall(LocalModuleResolver* local_module_resolver,
fuchsia::modular::FindModulesQuery query,
ResultCall result_call)
: Operation("LocalModuleResolver::FindModulesCall",
std::move(result_call)),
local_module_resolver_(local_module_resolver),
query_(std::move(query)) {}
// Finds all modules that match |query_|.
//
// The specified action is used to filter potential modules, and
// the associated parameters are required to match in both name and type. If
// |query_.module_handler| is specified, then the search for the action and
// parameters are restricted to the specified handler.
void Run() override {
FlowToken flow{this, &response_};
// 1. If a handler is specified, use only that for |candidates_|.
if (!query_.handler.is_null()) {
auto found_handlers =
local_module_resolver_->FindHandlers(query_.handler);
if (found_handlers.empty()) {
response_ = CreateEmptyResponseWithStatus(
fuchsia::modular::FindModulesStatus::UNKNOWN_HANDLER);
return;
}
candidates_ = found_handlers;
}
// 2. Find all modules that can handle the action and then take an
// intersection |candidates_| if its non-empty.
auto action_it =
local_module_resolver_->action_to_manifests_.find(query_.action);
if (action_it != local_module_resolver_->action_to_manifests_.end()) {
if (!candidates_.empty()) {
std::set<ManifestId> new_candidates;
if (action_it != local_module_resolver_->action_to_manifests_.end()) {
candidates_ = action_it->second;
std::set_intersection(
candidates_.begin(), candidates_.end(), action_it->second.begin(),
action_it->second.end(),
std::inserter(new_candidates, new_candidates.begin()));
candidates_ = new_candidates;
}
} else {
candidates_ = action_it->second;
}
}
// 3. For each parameter in the FindModulesQuery, try to filter
// |candidates_| to only the modules that provide the types in the
// parameter constraints.
if (!candidates_.empty()) {
for (const auto& parameter_entry : query_.parameter_constraints) {
ProcessParameterTypes(parameter_entry.param_name,
parameter_entry.param_types);
}
}
FinalizeResponse(flow);
}
private:
// |parameter_name| and |types| come from the FindModulesQuery.
void ProcessParameterTypes(const std::string& parameter_name,
const std::vector<std::string>& types) {
std::set<ManifestId> parameter_type_entries;
for (const auto& type : types) {
std::set<ManifestId> found_entries =
GetManifestsMatchingParameterByTypeAndName(type, parameter_name);
parameter_type_entries.insert(found_entries.begin(), found_entries.end());
}
std::set<ManifestId> new_result_entries;
// All parameters in the query must be handled by the candidates. For each
// parameter that is processed, filter out any existing results that can't
// also handle the new parameter type.
std::set_intersection(
candidates_.begin(), candidates_.end(), parameter_type_entries.begin(),
parameter_type_entries.end(),
std::inserter(new_result_entries, new_result_entries.begin()));
candidates_.swap(new_result_entries);
}
// Returns the ManifestIds of all entries with a parameter that matches the
// provided name and type.
std::set<ManifestId> GetManifestsMatchingParameterByTypeAndName(
const std::string& parameter_type, const std::string& parameter_name) {
std::set<ManifestId> found_entries;
auto found_manifests_it =
local_module_resolver_->parameter_type_and_name_to_manifests_.find(
std::make_pair(parameter_type, parameter_name));
if (found_manifests_it !=
local_module_resolver_->parameter_type_and_name_to_manifests_.end()) {
found_entries.insert(found_manifests_it->second.begin(),
found_manifests_it->second.end());
}
return found_entries;
}
// At this point |candidates_| contains all the modules that satisfy the
// query. The purpose of this method is to create a response using these
// candidates.
void FinalizeResponse(FlowToken flow) {
response_ = CreateEmptyResponseWithStatus(
fuchsia::modular::FindModulesStatus::SUCCESS);
if (candidates_.empty()) {
return;
}
for (auto manifest_id : candidates_) {
auto entry_it = local_module_resolver_->manifests_.find(manifest_id);
FXL_CHECK(entry_it != local_module_resolver_->manifests_.end())
<< manifest_id;
const auto& manifest = entry_it->second;
fuchsia::modular::FindModulesResult result;
result.module_id = manifest.binary;
result.manifest = fuchsia::modular::ModuleManifest::New();
result.manifest->intent_filters.resize(0);
fidl::Clone(manifest, result.manifest.get());
response_.results.push_back(std::move(result));
}
}
fuchsia::modular::FindModulesResponse CreateEmptyResponseWithStatus(
fuchsia::modular::FindModulesStatus status) {
fuchsia::modular::FindModulesResponse response;
response.status = status;
response.results.resize(0);
return response;
}
fuchsia::modular::FindModulesResponse response_;
LocalModuleResolver* const local_module_resolver_;
fuchsia::modular::FindModulesQuery query_;
std::set<ManifestId> candidates_;
};
void LocalModuleResolver::FindModules(fuchsia::modular::FindModulesQuery query,
FindModulesCallback callback) {
operations_.Add(std::make_unique<FindModulesCall>(this, std::move(query),
std::move(callback)));
}
void LocalModuleResolver::OnSourceIdle(const std::string& source_name) {
auto res = ready_sources_.insert(source_name);
if (!res.second) {
// It's OK for us to get an idle notification twice from a repo. This
// happens, for instance, if there's a network problem and we have to
// re-establish it.
return;
}
if (AllSourcesAreReady()) {
// They are all ready. Bind any pending Connect() calls.
for (auto& request : pending_bindings_) {
bindings_.AddBinding(this, std::move(request));
}
pending_bindings_.clear();
}
}
void LocalModuleResolver::OnNewManifestEntry(
const std::string& source_name, std::string module_uri,
fuchsia::modular::ModuleManifest new_manifest) {
FXL_LOG(INFO) << "New Module manifest for binary " << module_uri << " with "
<< new_manifest.intent_filters->size() << " intent filters.";
auto manifest_id = ManifestId(source_name, module_uri);
// Add this new manifest info to our local index.
if (manifests_.count(manifest_id) > 0) {
// Remove this existing manifest first, then add it back in.
OnRemoveManifestEntry(source_name, module_uri);
}
if (new_manifest.intent_filters->size() == 0) {
new_manifest.intent_filters.resize(0);
}
auto ret = manifests_.emplace(manifest_id, std::move(new_manifest));
FXL_CHECK(ret.second);
const auto& manifest = ret.first->second;
for (const auto& intent_filter : *manifest.intent_filters) {
action_to_manifests_[intent_filter.action].insert(manifest_id);
for (const auto& constraint : intent_filter.parameter_constraints) {
parameter_type_and_name_to_manifests_[std::make_pair(constraint.type,
constraint.name)]
.insert(manifest_id);
parameter_type_to_manifests_[constraint.type].insert(manifest_id);
}
}
}
void LocalModuleResolver::OnRemoveManifestEntry(const std::string& source_name,
std::string module_uri) {
ManifestId manifest_id(source_name, module_uri);
auto it = manifests_.find(manifest_id);
if (it == manifests_.end()) {
FXL_LOG(WARNING) << "Asked to remove non-existent manifest: "
<< manifest_id;
return;
}
const auto& manifest = it->second;
for (const auto& intent_filter : *manifest.intent_filters) {
action_to_manifests_[intent_filter.action].erase(manifest_id);
for (const auto& constraint : intent_filter.parameter_constraints) {
parameter_type_and_name_to_manifests_[std::make_pair(constraint.type,
constraint.name)]
.erase(manifest_id);
parameter_type_to_manifests_[constraint.type].erase(manifest_id);
}
}
manifests_.erase(manifest_id);
}
void LocalModuleResolver::PeriodicCheckIfSourcesAreReady() {
if (!AllSourcesAreReady()) {
for (const auto& it : sources_) {
if (ready_sources_.count(it.first) == 0) {
FXL_LOG(WARNING) << "Still waiting on source: " << it.first;
}
}
if (already_checking_if_sources_are_ready_) {
return;
}
already_checking_if_sources_are_ready_ = true;
async::PostDelayedTask(
async_get_default_dispatcher(),
[weak_this = weak_factory_.GetWeakPtr()]() {
if (weak_this) {
weak_this->already_checking_if_sources_are_ready_ = false;
weak_this->PeriodicCheckIfSourcesAreReady();
}
},
zx::sec(10));
}
}
} // namespace modular