blob: b81780d31682d09687032b22a179d35730a2b444 [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/bin/sessionmgr/story_runner/link_impl.h"
#include <functional>
#include <fuchsia/modular/cpp/fidl.h>
#include <lib/entity/cpp/json.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/fidl/cpp/optional.h>
#include <lib/fostr/fidl/fuchsia/modular/formatting.h>
#include <lib/fsl/vmo/strings.h>
#include <lib/fxl/functional/make_copyable.h>
#include <lib/fxl/logging.h>
#include "peridot/lib/rapidjson/rapidjson.h"
#include "peridot/lib/util/debug.h"
using fuchsia::modular::LinkWatcherPtr;
namespace modular {
namespace {
// Applies a JSON mutation operation using |apply_fn|. Its parameters are
// references because we treat |apply_fn| as part of ApplyOp's body.
void ApplyOp(
fidl::StringPtr* value_str, const std::vector<std::string>& path,
std::function<void(CrtJsonDoc& doc, CrtJsonPointer& pointer)> apply_fn) {
CrtJsonDoc value;
if (!value_str->is_null()) {
value.Parse(*value_str);
}
auto pointer = CreatePointer(value, path);
apply_fn(value, pointer);
*value_str = JsonValueToString(value);
}
bool ApplySetOp(fidl::StringPtr* value_str,
const fidl::VectorPtr<std::string>& path,
const fidl::StringPtr& new_value_at_path_str) {
CrtJsonDoc new_value_at_path;
new_value_at_path.Parse(new_value_at_path_str);
if (new_value_at_path.HasParseError()) {
return false;
}
auto apply_fn = [&new_value_at_path](CrtJsonDoc& doc, CrtJsonPointer& p) {
p.Set(doc, std::move(new_value_at_path));
};
ApplyOp(value_str, path, apply_fn);
return true;
}
void ApplyEraseOp(fidl::StringPtr* value_str,
const std::vector<std::string>& path) {
auto apply_fn = [](CrtJsonDoc& doc, CrtJsonPointer& p) { p.Erase(doc); };
ApplyOp(value_str, path, apply_fn);
}
fuchsia::mem::Buffer StringToVmo(const std::string& string) {
fsl::SizedVmo vmo;
FXL_CHECK(fsl::VmoFromString(string, &vmo));
return std::move(vmo).ToTransport();
}
} // namespace
LinkImpl::LinkImpl(StoryStorage* const story_storage, LinkPath link_path)
: story_storage_(story_storage),
link_path_(std::move(link_path)),
weak_factory_(this),
link_watcher_auto_cancel_(nullptr) {
FXL_DCHECK(story_storage != nullptr);
// When |link_watcher_auto_cancel_| goes out of scope, |story_storage_| will
// stop calling OnLinkValueChanged.
link_watcher_auto_cancel_ = story_storage_->WatchLink(
link_path_, [this](const fidl::StringPtr& value, const void* context) {
OnLinkValueChanged(value, context);
});
}
LinkImpl::~LinkImpl() = default;
void LinkImpl::Get(fidl::VectorPtr<std::string> path,
GetCallback callback) {
// TODO: Need error reporting. MI4-1082
story_storage_->GetLinkValue(link_path_)
->WeakMap(
GetWeakPtr(),
fxl::MakeCopyable([this /* for link_path_ */, path = std::move(path)](
StoryStorage::Status status,
fidl::StringPtr value) mutable {
if (status != StoryStorage::Status::OK) {
FXL_LOG(ERROR) << "Getting link " << link_path_
<< " failed: " << static_cast<int>(status);
return std::string("null");
}
else if (!path || path->empty()) {
// Common case requires no parsing of the JSON.
return *value;
} else {
// Extract just the |path| portion of the value.
CrtJsonDoc json;
json.Parse(value);
if (json.HasParseError()) {
return std::string("null");
}
auto& value_at_path = CreatePointer(json, *path)
.GetWithDefault(json, CrtJsonValue());
return JsonValueToString(value_at_path);
}
}))
->WeakThen(GetWeakPtr(),
[callback = std::move(callback)](std::string json) {
fsl::SizedVmo vmo;
FXL_CHECK(fsl::VmoFromString(json, &vmo));
auto vmo_ptr = std::make_unique<fuchsia::mem::Buffer>(
std::move(vmo).ToTransport());
callback(std::move(vmo_ptr));
});
}
void LinkImpl::Set(fidl::VectorPtr<std::string> path,
fuchsia::mem::Buffer json) {
std::string json_string;
FXL_CHECK(fsl::StringFromVmo(json, &json_string));
Set(std::move(path), json_string);
}
void LinkImpl::Set(fidl::VectorPtr<std::string> path,
const std::string& json) {
// TODO: Need error reporting. MI4-1082
story_storage_
->UpdateLinkValue(
link_path_,
fxl::MakeCopyable([this /* for link_path_ */, path = std::move(path),
json = std::move(json)](fidl::StringPtr* value) {
if (!ApplySetOp(value, path, json)) {
FXL_LOG(ERROR) << "LinkImpl.Set failed for link " << link_path_
<< " with json " << json;
}
}),
this /* context */)
->Then([](StoryStorage::Status status) {
// TODO: Error reporting. MI4-1082
});
}
void LinkImpl::Erase(std::vector<std::string> path) {
// TODO: Need error reporting. MI4-1082
story_storage_
->UpdateLinkValue(
link_path_,
fxl::MakeCopyable([this /* for link_path_ */,
path = std::move(path)](fidl::StringPtr* value) {
ApplyEraseOp(value, path);
}),
this /* context */)
->Then([](StoryStorage::Status status) {
// TODO: Error reporting. MI4-1082
});
}
void LinkImpl::GetEntity(GetEntityCallback callback) {
// TODO: Need error reporting. MI4-1082
story_storage_->GetLinkValue(link_path_)
->WeakThen(GetWeakPtr(), [this, callback](StoryStorage::Status status,
fidl::StringPtr value) {
if (status != StoryStorage::Status::OK) {
FXL_LOG(ERROR) << "Getting link " << link_path_
<< " failed: " << static_cast<int>(status);
callback(nullptr);
}
// Convert the contents to an Entity reference, if possible.
std::string ref;
if (!EntityReferenceFromJson(value, &ref)) {
FXL_LOG(ERROR) << "Link value for " << link_path_
<< " is not an entity reference.";
callback(nullptr);
return;
}
callback(ref);
});
}
void LinkImpl::SetEntity(fidl::StringPtr entity_reference) {
// SetEntity() is just a variation on Set(), so delegate to Set().
Set(nullptr, EntityReferenceToJson(entity_reference));
}
void LinkImpl::Sync(SyncCallback callback) {
story_storage_->Sync()->WeakThen(GetWeakPtr(), callback);
}
void LinkImpl::OnLinkValueChanged(const fidl::StringPtr& value,
const void* context) {
// If context == this, the change came from us. Otherwise, it either came
// from a different LinkImpl (in which case context != nullptr), or a
// different StoryStorage altogether (even on a different device).
if (context != this) {
for (auto& dst : normal_watchers_.ptrs()) {
(*dst)->Notify(StringToVmo(value));
}
}
// No matter what, everyone in |everything_watchers_| sees everything.
for (auto& dst : everything_watchers_.ptrs()) {
(*dst)->Notify(StringToVmo(value));
}
}
void LinkImpl::Watch(fidl::InterfaceHandle<LinkWatcher> watcher) {
// Move |watcher| into the callback for Get(): we are guaranteed
// that no other operation will run on |story_storage_| until our callback
// is complete, which means the next mutation that happens will be sent to
// |watcher|.
Get(nullptr, fxl::MakeCopyable(
[this, watcher = std::move(watcher)](
std::unique_ptr<fuchsia::mem::Buffer> value) mutable {
auto ptr = watcher.Bind();
ptr->Notify(std::move(*value));
normal_watchers_.AddInterfacePtr(std::move(ptr));
}));
}
void LinkImpl::WatchAll(
fidl::InterfaceHandle<fuchsia::modular::LinkWatcher> watcher) {
Get(nullptr, fxl::MakeCopyable(
[this, watcher = std::move(watcher)](
std::unique_ptr<fuchsia::mem::Buffer> value) mutable {
auto ptr = watcher.Bind();
ptr->Notify(std::move(*value));
everything_watchers_.AddInterfacePtr(std::move(ptr));
}));
}
fxl::WeakPtr<LinkImpl> LinkImpl::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace modular