blob: c60249ba2e1357d3b6e7a17718402da5342b0054 [file] [log] [blame]
// Copyright 2016 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/examples/counter_cpp/store.h"
#include <iterator>
#include <utility>
#include <fuchsia/cpp/modular.h>
#include <fuchsia/cpp/modular_calculator_example.h>
#include "lib/fxl/logging.h"
#include "peridot/lib/rapidjson/rapidjson.h"
using fidl::InterfaceHandle;
using fidl::StringPtr;
using modular::Link;
using modular::LinkWatcher;
namespace modular_example {
Counter::Counter(const rapidjson::Value& /*name*/,
const rapidjson::Value& value) {
// Updates may be incremental, so don't assume that all fields are present.
auto itr = value.FindMember(kSenderKey);
if (itr != value.MemberEnd()) {
FXL_CHECK(itr->value.IsString());
sender = itr->value.GetString();
}
itr = value.FindMember(kCounterKey);
if (itr != value.MemberEnd()) {
FXL_CHECK(itr->value.IsInt());
counter = itr->value.GetInt();
}
// For the last iteration, test that Module2 removes the sender.
if (counter <= 10) {
FXL_CHECK(!sender.empty());
} else {
FXL_CHECK(sender.empty());
}
FXL_CHECK(is_valid());
}
rapidjson::Document Counter::ToDocument(const std::string& module_name) {
rapidjson::Document counter_doc;
auto& allocator1 = counter_doc.GetAllocator();
counter_doc.SetObject();
counter_doc.AddMember(kCounterKey, counter, allocator1);
if (counter >= 11) {
// TODO(jimbe) remove the sender property to prove that property removal
// works. This requires calling Erase(), but this code isn't structured
// to work that way right now because it just returns a json string.
// A related open question is how we should implement deletion based on
// the JSON string we return. One proposal is for UpdateObject() to also
// take a list of keys to delete.
counter_doc.AddMember(kSenderKey, "", allocator1);
} else {
counter_doc.AddMember(kSenderKey, module_name, allocator1);
}
return counter_doc;
}
Store::Store(std::string module_name)
: module_name_(std::move(module_name)), watcher_binding_(this) {}
void Store::Initialize(InterfaceHandle<Link> link) {
link_.Bind(std::move(link));
InterfaceHandle<LinkWatcher> watcher;
watcher_binding_.Bind(watcher.NewRequest());
link_->Watch(std::move(watcher));
}
void Store::AddCallback(Callback c) {
callbacks_.emplace_back(std::move(c));
}
void Store::Stop() {
terminating_ = true;
watcher_binding_.Unbind();
link_.Unbind();
}
void Store::Notify(fidl::StringPtr json) {
FXL_LOG(INFO) << "Store::Notify() " << module_name_;
if (!terminating_) {
ApplyLinkData(json.get());
}
}
modular_example::Counter Store::ParseCounterJson(
const std::string& json,
const std::string& /*module_name*/) {
rapidjson::Document doc;
doc.Parse(json);
FXL_CHECK(!doc.HasParseError());
if (doc.IsNull()) {
// This circumstance is expected to happen, so
// constructing a Counter() like this will cause
// its is_valid() function to return false.
return Counter();
}
rapidjson::Pointer ptr(modular_example::kJsonPath);
rapidjson::Value* const value = ptr.Get(doc);
if (!value) {
return Counter();
}
auto itr = value->GetObject().MemberBegin();
FXL_CHECK(itr != value->GetObject().MemberEnd());
return modular_example::Counter(itr->name, itr->value);
}
// Process an update from the Link and write it to our local copy.
// The update is ignored if:
// - it's missing the desired document.
// - the data in the update is stale (can happen on rehydrate).
void Store::ApplyLinkData(const std::string& json) {
modular_example::Counter new_counter = ParseCounterJson(json, module_name_);
// Received an invalid update, which means we are starting a new story.
// Don't do anything now, the recipe will gives us the initial data.
if (!new_counter.is_valid()) {
return;
}
// Redundant update, ignore it.
if (new_counter.counter <= counter.counter) {
return;
}
// If we sent it, then we are getting a message from a restored session.
// We don't know if it was ever actually delivered, so send it again.
if (new_counter.sender == module_name_) {
MarkDirty();
}
counter = std::move(new_counter);
ModelChanged();
}
void Store::ModelChanged() {
// If this triggers, the calling function needs to check store.terminating().
FXL_CHECK(!terminating_);
for (auto& c : callbacks_) {
c();
}
SendIfDirty();
}
void Store::SendIfDirty() {
if (link_ && dirty_) {
rapidjson::Document doc = counter.ToDocument(module_name_);
FXL_CHECK(link_);
FXL_CHECK(doc.IsObject());
std::vector<fidl::StringPtr> segments{modular_example::kJsonSegment,
modular_example::kDocId};
link_->UpdateObject(fidl::VectorPtr<fidl::StringPtr>(segments),
modular::JsonValueToString(doc));
dirty_ = false;
}
}
} // namespace modular_example