#ifndef ASH_UTILITY_PERSISTENT_PROTO_H_
#define ASH_UTILITY_PERSISTENT_PROTO_H_
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "ash/ash_export.h"
#include "base/callback_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/time.h"
namespace ash {
namespace internal {
enum class ReadStatus {
kOk = 0,
kMissing = 1,
kReadError = 2,
kParseError = 3,
kNoop = 4,
kMaxValue = kNoop,
};
enum class WriteStatus {
kOk = 0,
kWriteError = 1,
kSerializationError = 2,
kMaxValue = kSerializationError,
};
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)};
}
WriteStatus ASH_EXPORT Write(const base::FilePath& filepath,
std::string_view proto_str);
}
template <class T>
class ASH_EXPORT PersistentProto {
public:
using InitCallback = base::OnceClosure;
using WriteCallback = base::RepeatingCallback<void(bool)>;
PersistentProto(
const base::FilePath& path,
const base::TimeDelta write_delay,
base::TaskPriority task_priority = base::TaskPriority::BEST_EFFORT)
: path_(path),
write_delay_(write_delay),
on_init_callbacks_(
std::make_unique<base::OnceCallbackList<InitCallback::RunType>>()),
on_write_callbacks_(
std::make_unique<
base::RepeatingCallbackList<WriteCallback::RunType>>()),
task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{task_priority, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})) {}
~PersistentProto() = default;
PersistentProto(const PersistentProto&) = delete;
PersistentProto& operator=(const PersistentProto&) = delete;
PersistentProto(PersistentProto&& other) {
path_ = other.path_;
write_delay_ = other.write_delay_;
initialized_ = other.initialized_;
write_is_queued_ = false;
purge_after_reading_ = other.purge_after_reading_;
on_init_callbacks_ = std::move(other.on_init_callbacks_);
on_write_callbacks_ = std::move(other.on_write_callbacks_);
task_runner_ = std::move(other.task_runner_);
proto_ = std::move(other.proto_);
}
void Init() {
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&internal::Read<T>, path_),
base::BindOnce(&PersistentProto<T>::OnReadComplete,
weak_factory_.GetWeakPtr()));
}
[[nodiscard]] base::CallbackListSubscription RegisterOnInit(
InitCallback on_init) {
return on_init_callbacks_->Add(std::move(on_init));
}
void RegisterOnInitUnsafe(InitCallback on_init) {
on_init_callbacks_->AddUnsafe(std::move(on_init));
}
void RegisterOnWriteUnsafe(WriteCallback on_write) {
on_write_callbacks_->AddUnsafe(std::move(on_write));
}
T* get() { return proto_.get(); }
T* operator->() {
CHECK(proto_);
return proto_.get();
}
const T* operator->() const {
CHECK(proto_);
return proto_.get();
}
T operator*() {
CHECK(proto_);
return *proto_;
}
bool initialized() const { return initialized_; }
constexpr bool has_value() const { return proto_.get() != nullptr; }
constexpr explicit operator bool() const { return has_value(); }
void QueueWrite() {
DCHECK(proto_);
if (!proto_)
return;
if (write_is_queued_)
return;
write_is_queued_ = true;
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PersistentProto<T>::OnQueueWrite,
weak_factory_.GetWeakPtr()),
write_delay_);
}
void StartWrite() {
DCHECK(proto_);
if (!proto_)
return;
std::string proto_str;
if (!proto_->SerializeToString(&proto_str))
OnWriteComplete(internal::WriteStatus::kSerializationError);
task_runner_->PostTaskAndReplyWithResult(
FROM_HERE, base::BindOnce(&internal::Write, path_, proto_str),
base::BindOnce(&PersistentProto<T>::OnWriteComplete,
weak_factory_.GetWeakPtr()));
}
void Purge() {
if (proto_) {
proto_.reset();
proto_ = std::make_unique<T>();
StartWrite();
} else {
purge_after_reading_ = true;
}
}
private:
void OnReadComplete(
std::pair<internal::ReadStatus, std::unique_ptr<T>> result) {
const internal::ReadStatus status = result.first;
base::UmaHistogramEnumeration("Apps.AppList.PersistentProto.ReadStatus",
status);
if (status == internal::ReadStatus::kOk) {
proto_ = std::move(result.second);
} else {
proto_ = std::make_unique<T>();
QueueWrite();
}
if (purge_after_reading_) {
proto_.reset();
proto_ = std::make_unique<T>();
StartWrite();
purge_after_reading_ = false;
}
initialized_ = true;
on_init_callbacks_->Notify();
}
void OnWriteComplete(const internal::WriteStatus status) {
base::UmaHistogramEnumeration("Apps.AppList.PersistentProto.WriteStatus",
status);
on_write_callbacks_->Notify(status ==
internal::WriteStatus::kOk);
}
void OnQueueWrite() {
write_is_queued_ = false;
StartWrite();
}
base::FilePath path_;
base::TimeDelta write_delay_;
bool initialized_ = false;
bool write_is_queued_ = false;
bool purge_after_reading_ = false;
std::unique_ptr<base::OnceCallbackList<InitCallback::RunType>>
on_init_callbacks_;
std::unique_ptr<base::RepeatingCallbackList<WriteCallback::RunType>>
on_write_callbacks_;
std::unique_ptr<T> proto_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
base::WeakPtrFactory<PersistentProto> weak_factory_{this};
};
}
#endif