blob: d5e6f8e1a25f916101ed66a4428adc4149d4578f [file] [log] [blame]
// Copyright 2021 The Chromium OS 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 "metrics/structured/persistent_proto.h"
#include <utility>
#include <base/files/file_util.h>
#include <base/files/important_file_writer.h>
#include <base/logging.h>
#include <base/rand_util.h>
#include <base/task/post_task.h>
#include <base/task/task_traits.h>
#include <base/task/thread_pool.h>
#include <base/task_runner_util.h>
#include <base/threading/scoped_blocking_call.h>
#include <base/threading/sequenced_task_runner_handle.h>
#include "metrics/structured/proto/storage.pb.h"
namespace metrics {
namespace structured {
namespace {
template <class T>
std::pair<ReadStatus, std::unique_ptr<T>> Read(const base::FilePath& filepath) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
if (!base::PathExists(filepath))
return {ReadStatus::kMissing, nullptr};
std::string proto_str;
if (!base::ReadFileToString(filepath, &proto_str))
return {ReadStatus::kReadError, nullptr};
auto proto = std::make_unique<T>();
if (!proto->ParseFromString(proto_str))
return {ReadStatus::kParseError, nullptr};
return {ReadStatus::kOk, std::move(proto)};
}
template <class T>
WriteStatus Write(const base::FilePath& filepath, const T* proto) {
const auto directory = filepath.DirName();
if (!base::DirectoryExists(directory))
base::CreateDirectory(directory);
std::string proto_str;
if (!proto->SerializeToString(&proto_str))
return WriteStatus::kSerializationError;
bool write_result;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
write_result = base::ImportantFileWriter::WriteFileAtomically(
filepath, proto_str, "StructuredMetricsPersistentProto");
}
if (!write_result)
return WriteStatus::kWriteError;
return WriteStatus::kOk;
}
} // namespace
template <class T>
PersistentProto<T>::PersistentProto(
const base::FilePath& path,
const base::TimeDelta write_delay,
typename PersistentProto<T>::ReadCallback on_read,
typename PersistentProto<T>::WriteCallback on_write)
: path_(path),
write_delay_(write_delay),
on_read_(std::move(on_read)),
on_write_(std::move(on_write)) {
task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&Read<T>, path_),
base::BindOnce(&PersistentProto<T>::OnReadComplete,
weak_factory_.GetWeakPtr()));
}
template <class T>
PersistentProto<T>::~PersistentProto() = default;
template <class T>
void PersistentProto<T>::OnReadComplete(
std::pair<ReadStatus, std::unique_ptr<T>> result) {
if (result.first == ReadStatus::kOk) {
proto_ = std::move(result.second);
} else {
proto_ = std::make_unique<T>();
QueueWrite();
}
if (wipe_after_reading_) {
proto_.reset();
proto_ = std::make_unique<T>();
QueueWrite();
wipe_after_reading_ = false;
}
std::move(on_read_).Run(result.first);
}
template <class T>
void PersistentProto<T>::QueueWrite() {
// If a save is already queued, do nothing.
if (write_is_queued_)
return;
write_is_queued_ = true;
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PersistentProto<T>::OnQueueWrite,
weak_factory_.GetWeakPtr()),
write_delay_);
}
template <class T>
void PersistentProto<T>::OnQueueWrite() {
// Reset the queued flag before posting the task. Last-moment updates to
// |proto_| will post another task to write the proto, avoiding race
// conditions.
write_is_queued_ = false;
StartWrite();
}
template <class T>
void PersistentProto<T>::StartWrite() {
// The SequentialTaskRunner ensures the writes won't trip over each other, so
// we can schedule without checking whether another write is currently active.
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&Write<T>, path_, proto_.get()),
base::BindOnce(&PersistentProto<T>::OnWriteComplete,
weak_factory_.GetWeakPtr()));
}
template <class T>
void PersistentProto<T>::OnWriteComplete(const WriteStatus status) {
on_write_.Run(status);
}
template <class T>
void PersistentProto<T>::Wipe() {
if (proto_) {
proto_.reset();
proto_ = std::make_unique<T>();
QueueWrite();
} else {
wipe_after_reading_ = true;
}
}
// A list of all types that the PersistentProto can be used with.
template class PersistentProto<EventsProto>;
template class PersistentProto<KeyDataProto>;
template class PersistentProto<KeyProto>;
} // namespace structured
} // namespace metrics