blob: 2af02279330dd95e6c5058f93fdf0e92f9a8a465 [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.
// The file defines Operations commonly executed on Ledger pages.
#ifndef PERIDOT_LIB_LEDGER_CLIENT_OPERATIONS_H_
#define PERIDOT_LIB_LEDGER_CLIENT_OPERATIONS_H_
#include <string>
#include <fuchsia/ledger/cpp/fidl.h>
#include <lib/async/cpp/operation.h>
#include <lib/fidl/cpp/array.h>
#include <lib/fsl/vmo/strings.h>
#include "peridot/lib/fidl/array_to_string.h"
#include "peridot/lib/fidl/json_xdr.h"
#include "peridot/lib/ledger_client/page_client.h"
namespace modular {
// Common base class for all Operation classes that operate on a ledger Page.
//
// The ledger page is always passed as a naked pointer fuchsia::ledger::Page*
// rather than as a FIDL pointer fuchsia::ledger::PagePtr to the Operation
// instance, because the connection to the page is shared between different
// Operation instances executed by different actors in the framework. The
// PagePtr is held by LedgerClient and handed out as Page* to PageClient, which
// passes it on to the respective Operations.
//
// As a result, callbacks for methods invoked on the Page* are not cancelled
// when the Operation instance is deleted, as they would be if the FIDL pointer
// were owned by the Operation instance. Therefore, such callbacks must be
// explicitly guarded using a weak pointer to the Operation instance against
// execution after their Operation instance was destroyed.
//
// This base class provides the method Protect() for the purpose.
//
// In derived class that are themselves template classes, the method must be
// invoked by explicit qualification with this-> (or alternatively with the base
// class name), because of template name lookup rules.
//
// Also, it is not possible to pass a PageSnapshotPtr to the Operation instead,
// because the snapshot must be taken at the time the Operation is executed, not
// at the time the Operation is enqueued, because it must reflect the effect of
// the execution of preceding operations.
template <typename... Args>
class PageOperation : public Operation<Args...> {
public:
using ResultCall = std::function<void(Args...)>;
PageOperation(const char* const trace_name, fuchsia::ledger::Page* const page,
ResultCall result_call, const std::string& trace_info = "")
: Operation<Args...>(trace_name, std::move(result_call), trace_info),
page_(page) {}
protected:
fuchsia::ledger::Page* page() const { return page_; }
using PageCallback = std::function<void(fuchsia::ledger::Status)>;
PageCallback Protect(PageCallback callback) {
return [weak_this = this->GetWeakPtr(),
callback = std::move(callback)](fuchsia::ledger::Status status) {
if (weak_this) {
callback(status);
}
};
}
private:
fuchsia::ledger::Page* const page_;
};
// Like PageOperation, but also takes a naked Ledger pointer, which has the same
// problems.
//
// This could be unified more with PageOperation, but only worthwhile once there
// are more situations to support. For now, it's very nice to label Operation
// classes explicitly with their base classes.
template <typename... Args>
class LedgerOperation : public Operation<Args...> {
public:
using ResultCall = std::function<void(Args...)>;
LedgerOperation(const char* const trace_name, fuchsia::ledger::Ledger* ledger,
fuchsia::ledger::Page* const page, ResultCall result_call,
const std::string& trace_info = "")
: Operation<Args...>(trace_name, std::move(result_call), trace_info),
ledger_(ledger),
page_(page) {}
protected:
fuchsia::ledger::Ledger* ledger() const { return ledger_; }
fuchsia::ledger::Page* page() const { return page_; }
using LedgerCallback = std::function<void(fuchsia::ledger::Status)>;
LedgerCallback Protect(LedgerCallback callback) {
return [weak_this = this->GetWeakPtr(),
callback = std::move(callback)](fuchsia::ledger::Status status) {
if (weak_this) {
callback(status);
}
};
}
private:
fuchsia::ledger::Ledger* const ledger_;
fuchsia::ledger::Page* const page_;
};
template <typename Data, typename DataPtr = std::unique_ptr<Data>,
typename DataFilter = XdrFilterList<Data>>
class ReadDataCall : public PageOperation<DataPtr> {
public:
using ResultCall = std::function<void(DataPtr)>;
using FlowToken = typename Operation<DataPtr>::FlowToken;
ReadDataCall(fuchsia::ledger::Page* const page, const std::string& key,
const bool not_found_is_ok, DataFilter filter,
ResultCall result_call)
: PageOperation<DataPtr>("ReadDataCall", page, std::move(result_call),
key),
key_(key),
not_found_is_ok_(not_found_is_ok),
filter_(filter) {}
private:
void Run() override {
FlowToken flow{this, &result_};
this->page()->GetSnapshot(
page_snapshot_.NewRequest(), fidl::VectorPtr<uint8_t>::New(0), nullptr,
this->Protect([this, flow](fuchsia::ledger::Status status) {
if (status != fuchsia::ledger::Status::OK) {
FXL_LOG(ERROR) << this->trace_name() << " " << key_ << " "
<< "Page.GetSnapshot() "
<< fidl::ToUnderlying(status);
return;
}
Cont(flow);
}));
}
void Cont(FlowToken flow) {
page_snapshot_->Get(to_array(key_), [this, flow](
fuchsia::ledger::Status status,
fuchsia::mem::BufferPtr value) {
if (status != fuchsia::ledger::Status::OK) {
if (status != fuchsia::ledger::Status::KEY_NOT_FOUND ||
!not_found_is_ok_) {
FXL_LOG(ERROR) << this->trace_name() << " " << key_ << " "
<< "PageSnapshot.Get() " << fidl::ToUnderlying(status);
}
return;
}
if (!value) {
FXL_LOG(ERROR) << this->trace_name() << " " << key_ << " "
<< "PageSnapshot.Get() null vmo";
}
std::string value_as_string;
if (!fsl::StringFromVmo(*value, &value_as_string)) {
FXL_LOG(ERROR) << this->trace_name() << " " << key_
<< " Unable to extract data.";
return;
}
if (!XdrRead(value_as_string, &result_, filter_)) {
result_.reset();
return;
}
FXL_DCHECK(result_);
});
}
const std::string key_;
const bool not_found_is_ok_;
DataFilter const filter_;
fuchsia::ledger::PageSnapshotPtr page_snapshot_;
DataPtr result_;
FXL_DISALLOW_COPY_AND_ASSIGN(ReadDataCall);
};
template <typename Data, typename DataArray = std::vector<Data>,
typename DataFilter = XdrFilterList<Data>>
class ReadAllDataCall : public PageOperation<DataArray> {
public:
using ResultCall = std::function<void(DataArray)>;
using FlowToken = typename Operation<DataArray>::FlowToken;
ReadAllDataCall(fuchsia::ledger::Page* const page, std::string prefix,
DataFilter const filter, ResultCall result_call)
: PageOperation<DataArray>("ReadAllDataCall", page,
std::move(result_call), prefix),
prefix_(std::move(prefix)),
filter_(filter) {
data_.resize(0);
}
private:
void Run() override {
FlowToken flow{this, &data_};
this->page()->GetSnapshot(
page_snapshot_.NewRequest(), to_array(prefix_), nullptr,
this->Protect([this, flow](fuchsia::ledger::Status status) {
if (status != fuchsia::ledger::Status::OK) {
FXL_LOG(ERROR) << this->trace_name() << " "
<< "Page.GetSnapshot() "
<< fidl::ToUnderlying(status);
return;
}
Cont1(flow);
}));
}
void Cont1(FlowToken flow) {
GetEntries(page_snapshot_.get(), &entries_,
[this, flow](fuchsia::ledger::Status status) {
if (status != fuchsia::ledger::Status::OK) {
FXL_LOG(ERROR)
<< this->trace_name() << " "
<< "GetEntries() " << fidl::ToUnderlying(status);
return;
}
Cont2(flow);
});
}
void Cont2(FlowToken /*flow*/) {
for (auto& entry : entries_) {
std::string value_as_string;
if (!fsl::StringFromVmo(*entry.value, &value_as_string)) {
FXL_LOG(ERROR) << this->trace_name() << " "
<< "Unable to extract data.";
continue;
}
Data data;
if (!XdrRead(value_as_string, &data, filter_)) {
continue;
}
data_.push_back(std::move(data));
}
}
fuchsia::ledger::PageSnapshotPtr page_snapshot_;
const std::string prefix_;
DataFilter const filter_;
std::vector<fuchsia::ledger::Entry> entries_;
DataArray data_;
FXL_DISALLOW_COPY_AND_ASSIGN(ReadAllDataCall);
};
template <typename Data, typename DataPtr = std::unique_ptr<Data>,
typename DataFilter = XdrFilterList<Data>>
class WriteDataCall : public PageOperation<> {
public:
WriteDataCall(fuchsia::ledger::Page* const page, const std::string& key,
DataFilter filter, DataPtr data, ResultCall result_call)
: PageOperation("WriteDataCall", page, std::move(result_call), key),
key_(key),
filter_(filter),
data_(std::move(data)) {}
private:
void Run() override {
FlowToken flow{this};
std::string json;
XdrWrite(&json, &data_, filter_);
fsl::SizedVmo vmo;
FXL_CHECK(fsl::VmoFromString(json, &vmo));
page()->CreateReferenceFromBuffer(
std::move(vmo).ToTransport(),
[this, weak_ptr = GetWeakPtr(), flow](
fuchsia::ledger::Status status,
std::unique_ptr<fuchsia::ledger::Reference> reference) {
if (!weak_ptr) {
return;
}
PutReference(std::move(reference), flow);
});
}
void PutReference(std::unique_ptr<fuchsia::ledger::Reference> reference,
FlowToken flow) {
if (!reference) {
FXL_LOG(ERROR) << trace_name() << " " << key_ << " "
<< "Page.Put() could not construct reference.";
return;
}
page()->PutReference(
to_array(key_), std::move(*reference), fuchsia::ledger::Priority::EAGER,
Protect([this, flow](fuchsia::ledger::Status status) {
if (status != fuchsia::ledger::Status::OK) {
FXL_LOG(ERROR) << trace_name() << " " << key_ << " "
<< "Page.Put() " << fidl::ToUnderlying(status);
}
}));
}
const std::string key_;
DataFilter const filter_;
DataPtr data_;
FXL_DISALLOW_COPY_AND_ASSIGN(WriteDataCall);
};
class DumpPageSnapshotCall : public PageOperation<std::string> {
public:
DumpPageSnapshotCall(fuchsia::ledger::Page* const page,
ResultCall result_call)
: PageOperation("DumpPageSnapshotCall", page, std::move(result_call)) {}
private:
void Run() override {
FlowToken flow{this, &dump_};
page()->GetSnapshot(page_snapshot_.NewRequest(),
fidl::VectorPtr<uint8_t>::New(0), nullptr,
Protect([this, flow](fuchsia::ledger::Status status) {
if (status != fuchsia::ledger::Status::OK) {
FXL_LOG(ERROR) << this->trace_name() << " "
<< "Page.GetSnapshot() "
<< fidl::ToUnderlying(status);
return;
}
Cont1(flow);
}));
}
void Cont1(FlowToken flow) {
GetEntries(page_snapshot_.get(), &entries_,
[this, flow](fuchsia::ledger::Status status) {
if (status != fuchsia::ledger::Status::OK) {
FXL_LOG(ERROR)
<< this->trace_name() << " "
<< "GetEntries() " << fidl::ToUnderlying(status);
return;
}
Cont2(flow);
});
}
void Cont2(FlowToken /*flow*/) {
std::ostringstream stream;
for (auto& entry : entries_) {
stream << "key: " << to_hex_string(entry.key) << std::endl;
std::string value_as_string;
if (!fsl::StringFromVmo(*entry.value, &value_as_string)) {
FXL_LOG(ERROR) << this->trace_name() << " "
<< "Unable to extract data.";
continue;
}
stream << "value: " << value_as_string << std::endl;
}
dump_ = stream.str();
}
fuchsia::ledger::PageSnapshotPtr page_snapshot_;
std::vector<fuchsia::ledger::Entry> entries_;
std::string dump_;
FXL_DISALLOW_COPY_AND_ASSIGN(DumpPageSnapshotCall);
};
} // namespace modular
#endif // PERIDOT_LIB_LEDGER_CLIENT_OPERATIONS_H_