blob: 62b5f5ceafa0988315af20391ba950f720a877e8 [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/acquirers/story_info/link_watcher_impl.h"
#include <set>
#include <sstream>
#include <lib/context/cpp/context_metadata_builder.h>
#include <lib/entity/cpp/json.h>
#include <lib/fidl/cpp/clone.h>
#include <lib/fidl/cpp/optional.h>
#include <lib/fsl/vmo/strings.h>
#include <lib/fxl/functional/make_copyable.h>
#include "peridot/bin/acquirers/story_info/story_watcher_impl.h"
#include "peridot/bin/sessionmgr/storage/constants_and_utils.h" // MakeLinkKey
#include "peridot/lib/fidl/json_xdr.h"
#include "peridot/lib/rapidjson/rapidjson.h"
namespace maxwell {
LinkWatcherImpl::LinkWatcherImpl(
StoryWatcherImpl* const owner,
fuchsia::modular::StoryController* const story_controller,
const std::string& story_id,
fuchsia::modular::ContextValueWriter* const story_value,
fuchsia::modular::LinkPath link_path)
: owner_(owner),
story_controller_(story_controller),
story_id_(story_id),
link_path_(std::move(link_path)),
link_watcher_binding_(this) {
// We hold onto a LinkPtr for the lifetime of this LinkWatcherImpl so that
// our watcher handle stays alive. Incidentally, this also means that the
// observed link remains "active" in the FW forever.
// TODO(thatguy): Use the new PuppetMaster observation API. MI4-1084
story_controller_->GetLink(fidl::Clone(link_path_), link_ptr_.NewRequest());
story_value->CreateChildValue(link_node_writer_.NewRequest(),
fuchsia::modular::ContextValueType::LINK);
link_node_writer_->Set(
nullptr, fidl::MakeOptional(ContextMetadataBuilder()
.SetLinkPath(link_path_.module_path,
link_path_.link_name)
.Build()));
link_ptr_->Watch(link_watcher_binding_.NewBinding());
// If the link becomes inactive, we stop watching it. It might still receive
// updates from other devices, but nothing can tell us as it isn't kept in
// memory on the current device.
//
// The fuchsia::modular::Link itself is not kept here, because otherwise it
// never becomes inactive (i.e. loses all its fuchsia::modular::Link
// connections).
link_watcher_binding_.set_error_handler([this](zx_status_t status) {
owner_->DropLink(modular::MakeLinkKey(link_path_));
});
}
LinkWatcherImpl::~LinkWatcherImpl() = default;
void LinkWatcherImpl::Notify(fuchsia::mem::Buffer json) {
std::string json_string;
FXL_CHECK(fsl::StringFromVmo(json, &json_string));
ProcessNewValue(json_string);
}
void LinkWatcherImpl::ProcessNewValue(const fidl::StringPtr& value) {
// We are looking for the following |value| structures:
//
// 1) |value| contains a JSON-style entity:
// { "@type": ..., ... }
// 2) |value| contains a JSON-encoded fuchsia::modular::Entity reference
// (EntityReferenceFromJson() will return true).
// 3) |value| is a JSON dictionary, and any of the members satisfies either
// (1) or (2).
//
// TODO(thatguy): Moving to Bundles allows us to ignore (3), and using
// Entities everywhere allows us to ignore (1).
modular::JsonDoc doc;
doc.Parse(value);
FXL_CHECK(!doc.HasParseError());
if (!doc.IsObject()) {
return;
}
// (1) & (2)
std::vector<std::string> types;
std::string ref;
if (modular::ExtractEntityTypesFromJson(doc, &types) ||
modular::EntityReferenceFromJson(doc, &ref)) {
// There is only *one* fuchsia::modular::Entity in this
// fuchsia::modular::Link.
entity_node_writers_.clear();
if (!single_entity_node_writer_.is_bound()) {
link_node_writer_->CreateChildValue(
single_entity_node_writer_.NewRequest(),
fuchsia::modular::ContextValueType::ENTITY);
}
// TODO(thatguy): The context engine expects an fuchsia::modular::Entity
// reference to be written directly as the content, versus the way Links
// wrap the reference in JSON. It'd be good to normalize on one encoded
// representation for fuchsia::modular::Entity references in the context
// engine.
if (ref.empty()) {
single_entity_node_writer_->Set(value, nullptr);
} else {
single_entity_node_writer_->Set(ref, nullptr);
}
return;
} else {
// There is not simply a *single* fuchsia::modular::Entity in this
// fuchsia::modular::Link. There may be multiple Entities (see below).
single_entity_node_writer_.Unbind();
}
// (3)
std::set<std::string> keys_that_have_entities;
for (auto it = doc.MemberBegin(); it != doc.MemberEnd(); ++it) {
if (modular::ExtractEntityTypesFromJson(it->value, &types) ||
modular::EntityReferenceFromJson(it->value, &ref)) {
keys_that_have_entities.insert(it->name.GetString());
auto value_it = entity_node_writers_.find(it->name.GetString());
if (value_it == entity_node_writers_.end()) {
fuchsia::modular::ContextValueWriterPtr writer;
link_node_writer_->CreateChildValue(
writer.NewRequest(), fuchsia::modular::ContextValueType::ENTITY);
value_it = entity_node_writers_
.emplace(it->name.GetString(), std::move(writer))
.first;
}
value_it->second->Set(modular::JsonValueToString(it->value), nullptr);
}
}
// Clean up any old entries in |entity_node_writers_|.
std::set<std::string> to_remove;
for (const auto& entry : entity_node_writers_) {
if (keys_that_have_entities.count(entry.first) == 0) {
to_remove.insert(entry.first);
}
}
for (const auto& key : to_remove) {
entity_node_writers_.erase(key);
}
}
} // namespace maxwell