123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461 |
- // Copyright 2020 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #ifndef COMPONENTS_SESSION_PROTO_DB_SESSION_PROTO_DB_H_
- #define COMPONENTS_SESSION_PROTO_DB_SESSION_PROTO_DB_H_
- #include <queue>
- #include <string>
- #include <vector>
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/containers/contains.h"
- #include "base/memory/raw_ptr.h"
- #include "base/memory/weak_ptr.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/task/sequenced_task_runner.h"
- #include "base/task/thread_pool.h"
- #include "base/threading/thread_task_runner_handle.h"
- #include "components/commerce/core/proto/persisted_state_db_content.pb.h"
- #include "components/keyed_service/core/keyed_service.h"
- #include "components/leveldb_proto/public/proto_database.h"
- #include "components/leveldb_proto/public/proto_database_provider.h"
- #include "components/session_proto_db/session_proto_storage.h"
- #include "content/public/browser/browser_context.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- #include "third_party/leveldatabase/src/include/leveldb/options.h"
- namespace {
- const char kOrphanedDataCountHistogramName[] =
- "Tabs.PersistedTabData.Storage.LevelDB.OrphanedDataCount";
- } // namespace
- class SessionProtoDBTest;
- template <typename T>
- class SessionProtoDBFactory;
- // General purpose per session (BrowserContext/BrowserState), per proto key ->
- // proto database where the template is the proto which is being stored. A
- // SessionProtoDB should be acquired using SessionProtoDBFactory. SessionProtoDB
- // is a wrapper on top of leveldb_proto which:
- // - Is specifically for databases which are per session
- // (BrowserContext/BrowserState)
- // and per proto (leveldb_proto is a proto database which may or may not be
- // per BrowserContext/BrowserState).
- // - Provides a simplified interface for the use cases that surround
- // SessionProtoDB such as providing LoadContentWithPrefix instead of the
- // more generic API in
- // leveldb_proto which requires a filter to be passed in.
- // - Is a KeyedService to support the per session (BrowserContext/BrowserState)
- // nature of the database.
- template <typename T>
- class SessionProtoDB : public KeyedService, public SessionProtoStorage<T> {
- public:
- using KeyAndValue = std::pair<std::string, T>;
- // Callback which is used when content is acquired.
- using LoadCallback = base::OnceCallback<void(bool, std::vector<KeyAndValue>)>;
- // Used for confirming an operation was completed successfully (e.g.
- // insert, delete). This will be invoked on a different SequenceRunner
- // to SessionProtoDB.
- using OperationCallback = base::OnceCallback<void(bool)>;
- // Represents an entry in the database.
- using ContentEntry = typename leveldb_proto::ProtoDatabase<T>::KeyEntryVector;
- SessionProtoDB(const SessionProtoDB&) = delete;
- SessionProtoDB& operator=(const SessionProtoDB&) = delete;
- ~SessionProtoDB() override;
- // SessionProtoStorage implementation:
- void LoadOneEntry(const std::string& key, LoadCallback callback) override;
- void LoadAllEntries(LoadCallback callback) override;
- void LoadContentWithPrefix(const std::string& key_prefix,
- LoadCallback callback) override;
- void PerformMaintenance(const std::vector<std::string>& keys_to_keep,
- const std::string& key_substring_to_match,
- OperationCallback callback) override;
- void InsertContent(const std::string& key,
- const T& value,
- OperationCallback callback) override;
- void DeleteOneEntry(const std::string& key,
- OperationCallback callback) override;
- void DeleteContentWithPrefix(const std::string& key_prefix,
- OperationCallback callback) override;
- void DeleteAllContent(OperationCallback callback) override;
- void Destroy() const override;
- private:
- friend class ::SessionProtoDBTest;
- template <typename U>
- friend class ::SessionProtoDBFactory;
- // Initializes the database.
- SessionProtoDB(content::BrowserContext* browser_context,
- leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
- const base::FilePath& database_dir,
- leveldb_proto::ProtoDbType proto_db_type);
- // Used for testing.
- SessionProtoDB(
- std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database,
- scoped_refptr<base::SequencedTaskRunner> task_runner);
- // Passes back database status following database initialization.
- void OnDatabaseInitialized(leveldb_proto::Enums::InitStatus status);
- // Callback when one entry is loaded.
- void OnLoadOneEntry(LoadCallback callback,
- bool success,
- std::unique_ptr<T> entry);
- // Callback when content is loaded.
- void OnLoadContent(LoadCallback callback,
- bool success,
- std::unique_ptr<std::vector<T>> content);
- // Callback when PerformMaintenance is complete.
- void OnPerformMaintenance(OperationCallback callback,
- bool success,
- std::unique_ptr<std::vector<T>> entries_to_delete);
- // Callback when an operation (e.g. insert or delete) is called.
- void OnOperationCommitted(OperationCallback callback, bool success);
- // Returns true if initialization status of database is not yet known.
- bool InitStatusUnknown() const;
- // Returns true if the database failed to initialize.
- bool FailedToInit() const;
- static bool DatabasePrefixFilter(const std::string& key_prefix,
- const std::string& key) {
- return base::StartsWith(key, key_prefix, base::CompareCase::SENSITIVE);
- }
- // Browser context associated with SessionProtoDB (SessionProtoDB are per
- // BrowserContext).
- raw_ptr<content::BrowserContext> browser_context_;
- // Status of the database initialization.
- absl::optional<leveldb_proto::Enums::InitStatus> database_status_;
- // The database for storing content storage information.
- std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database_;
- // Store operations until the database is initialized at which point
- // |deferred_operations_| is flushed and all operations are executed.
- std::vector<base::OnceClosure> deferred_operations_;
- base::WeakPtrFactory<SessionProtoDB> weak_ptr_factory_{this};
- };
- template <typename T>
- SessionProtoDB<T>::~SessionProtoDB() = default;
- template <typename T>
- void SessionProtoDB<T>::LoadOneEntry(const std::string& key,
- LoadCallback callback) {
- if (InitStatusUnknown()) {
- deferred_operations_.push_back(base::BindOnce(
- &SessionProtoDB::LoadOneEntry, weak_ptr_factory_.GetWeakPtr(), key,
- std::move(callback)));
- } else if (FailedToInit()) {
- base::ThreadPool::PostTask(
- FROM_HERE,
- base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>()));
- } else {
- storage_database_->GetEntry(
- key,
- base::BindOnce(&SessionProtoDB::OnLoadOneEntry,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- }
- template <typename T>
- void SessionProtoDB<T>::LoadAllEntries(LoadCallback callback) {
- if (InitStatusUnknown()) {
- deferred_operations_.push_back(
- base::BindOnce(&SessionProtoDB::LoadAllEntries,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- } else if (FailedToInit()) {
- base::ThreadPool::PostTask(
- FROM_HERE,
- base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>()));
- } else {
- storage_database_->LoadEntries(
- base::BindOnce(&SessionProtoDB::OnLoadContent,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- }
- template <typename T>
- void SessionProtoDB<T>::LoadContentWithPrefix(const std::string& key_prefix,
- LoadCallback callback) {
- if (InitStatusUnknown()) {
- deferred_operations_.push_back(base::BindOnce(
- &SessionProtoDB::LoadContentWithPrefix, weak_ptr_factory_.GetWeakPtr(),
- key_prefix, std::move(callback)));
- } else if (FailedToInit()) {
- base::ThreadPool::PostTask(
- FROM_HERE,
- base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>()));
- } else {
- storage_database_->LoadEntriesWithFilter(
- base::BindRepeating(&DatabasePrefixFilter, key_prefix),
- {.fill_cache = false},
- /* target_prefix */ "",
- base::BindOnce(&SessionProtoDB::OnLoadContent,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- }
- template <typename T>
- void SessionProtoDB<T>::PerformMaintenance(
- const std::vector<std::string>& keys_to_keep,
- const std::string& key_substring_to_match,
- OperationCallback callback) {
- if (InitStatusUnknown()) {
- deferred_operations_.push_back(base::BindOnce(
- &SessionProtoDB::PerformMaintenance, weak_ptr_factory_.GetWeakPtr(),
- keys_to_keep, key_substring_to_match, std::move(callback)));
- } else if (FailedToInit()) {
- base::ThreadPool::PostTask(FROM_HERE,
- base::BindOnce(std::move(callback), false));
- } else {
- // The following could be achieved with UpdateEntriesWithRemoveFilter rather
- // than LoadEntriesWithFilter followed by UpdateEntries, however, that would
- // not allow metrics to be recorded regarding how much orphaned data was
- // identified.
- storage_database_->LoadEntriesWithFilter(
- base::BindRepeating(
- [](const std::vector<std::string>& keys_to_keep,
- const std::string& key_substring_to_match,
- const std::string& key) {
- // Return all keys which where key_substring_to_match is a
- // substring of said keys and hasn't been explicitly marked
- // not to be removed in keys_to_keep.
- return base::Contains(key, key_substring_to_match) &&
- !base::Contains(keys_to_keep, key);
- },
- keys_to_keep, key_substring_to_match),
- base::BindOnce(&SessionProtoDB::OnPerformMaintenance,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- }
- // Inserts a value for a given key and passes the result (success/failure) to
- // OperationCallback.
- template <typename T>
- void SessionProtoDB<T>::InsertContent(const std::string& key,
- const T& value,
- OperationCallback callback) {
- if (InitStatusUnknown()) {
- deferred_operations_.push_back(base::BindOnce(
- &SessionProtoDB::InsertContent, weak_ptr_factory_.GetWeakPtr(), key,
- std::move(value), std::move(callback)));
- } else if (FailedToInit()) {
- base::ThreadPool::PostTask(FROM_HERE,
- base::BindOnce(std::move(callback), false));
- } else {
- auto contents_to_save = std::make_unique<ContentEntry>();
- contents_to_save->emplace_back(key, value);
- storage_database_->UpdateEntries(
- std::move(contents_to_save),
- std::make_unique<std::vector<std::string>>(),
- base::BindOnce(&SessionProtoDB::OnOperationCommitted,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- }
- template <typename T>
- void SessionProtoDB<T>::DeleteOneEntry(const std::string& key,
- OperationCallback callback) {
- if (InitStatusUnknown()) {
- deferred_operations_.push_back(base::BindOnce(
- &SessionProtoDB::DeleteOneEntry, weak_ptr_factory_.GetWeakPtr(), key,
- std::move(callback)));
- } else if (FailedToInit()) {
- base::ThreadPool::PostTask(FROM_HERE,
- base::BindOnce(std::move(callback), false));
- } else {
- auto keys = std::make_unique<std::vector<std::string>>();
- keys->push_back(key);
- storage_database_->UpdateEntries(
- std::make_unique<ContentEntry>(), std::move(keys),
- base::BindOnce(&SessionProtoDB::OnOperationCommitted,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- }
- // Deletes content in the database, matching all keys which have a prefix
- // that matches the key.
- template <typename T>
- void SessionProtoDB<T>::DeleteContentWithPrefix(const std::string& key_prefix,
- OperationCallback callback) {
- if (InitStatusUnknown()) {
- deferred_operations_.push_back(base::BindOnce(
- &SessionProtoDB::DeleteContentWithPrefix,
- weak_ptr_factory_.GetWeakPtr(), key_prefix, std::move(callback)));
- } else if (FailedToInit()) {
- base::ThreadPool::PostTask(FROM_HERE,
- base::BindOnce(std::move(callback), false));
- } else {
- storage_database_->UpdateEntriesWithRemoveFilter(
- std::make_unique<ContentEntry>(),
- std::move(base::BindRepeating(&DatabasePrefixFilter, key_prefix)),
- base::BindOnce(&SessionProtoDB::OnOperationCommitted,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- }
- // Delete all content in the database.
- template <typename T>
- void SessionProtoDB<T>::DeleteAllContent(OperationCallback callback) {
- if (InitStatusUnknown()) {
- deferred_operations_.push_back(
- base::BindOnce(&SessionProtoDB::DeleteAllContent,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- } else if (FailedToInit()) {
- base::ThreadPool::PostTask(FROM_HERE,
- base::BindOnce(std::move(callback), false));
- } else {
- storage_database_->Destroy(std::move(callback));
- }
- }
- template <typename T>
- void SessionProtoDB<T>::Destroy() const {
- // TODO(davidjm): Consider calling the factory's disassociate method here.
- // This isn't strictly necessary since it will be called when
- // the context is destroyed anyway.
- }
- template <typename T>
- SessionProtoDB<T>::SessionProtoDB(
- content::BrowserContext* browser_context,
- leveldb_proto::ProtoDatabaseProvider* proto_database_provider,
- const base::FilePath& database_dir,
- leveldb_proto::ProtoDbType proto_db_type)
- : SessionProtoStorage<T>(),
- browser_context_(browser_context),
- database_status_(absl::nullopt),
- storage_database_(proto_database_provider->GetDB<T>(
- proto_db_type,
- database_dir,
- base::ThreadPool::CreateSequencedTaskRunner(
- {base::MayBlock(), base::TaskPriority::USER_VISIBLE}))) {
- static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value,
- "T must implement 'google::protobuf::MessageLite'");
- storage_database_->Init(base::BindOnce(&SessionProtoDB::OnDatabaseInitialized,
- weak_ptr_factory_.GetWeakPtr()));
- }
- // Used for tests.
- template <typename T>
- SessionProtoDB<T>::SessionProtoDB(
- std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database,
- scoped_refptr<base::SequencedTaskRunner> task_runner)
- : SessionProtoStorage<T>(),
- database_status_(absl::nullopt),
- storage_database_(std::move(storage_database)) {
- static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value,
- "T must implement 'google::protobuf::MessageLite'");
- storage_database_->Init(base::BindOnce(&SessionProtoDB::OnDatabaseInitialized,
- weak_ptr_factory_.GetWeakPtr()));
- }
- // Passes back database status following database initialization.
- template <typename T>
- void SessionProtoDB<T>::OnDatabaseInitialized(
- leveldb_proto::Enums::InitStatus status) {
- database_status_ =
- absl::make_optional<leveldb_proto::Enums::InitStatus>(status);
- for (auto& deferred_operation : deferred_operations_) {
- std::move(deferred_operation).Run();
- }
- deferred_operations_.clear();
- }
- // Callback when one entry is loaded.
- template <typename T>
- void SessionProtoDB<T>::OnLoadOneEntry(LoadCallback callback,
- bool success,
- std::unique_ptr<T> entry) {
- std::vector<KeyAndValue> results;
- if (success && entry) {
- results.emplace_back(entry->key(), *entry);
- }
- std::move(callback).Run(success, std::move(results));
- }
- // Callback when content is loaded.
- template <typename T>
- void SessionProtoDB<T>::OnLoadContent(LoadCallback callback,
- bool success,
- std::unique_ptr<std::vector<T>> content) {
- std::vector<KeyAndValue> results;
- if (success) {
- for (const auto& proto : *content) {
- // TODO(crbug.com/1157881) relax requirement for proto to have a key field
- // and return key value pairs OnLoadContent.
- results.emplace_back(proto.key(), proto);
- }
- }
- std::move(callback).Run(success, std::move(results));
- }
- template <typename T>
- void SessionProtoDB<T>::OnPerformMaintenance(
- OperationCallback callback,
- bool success,
- std::unique_ptr<std::vector<T>> entries_to_delete) {
- auto keys_to_delete = std::make_unique<std::vector<std::string>>();
- if (success) {
- for (const auto& proto : *entries_to_delete) {
- keys_to_delete->emplace_back(proto.key());
- }
- base::UmaHistogramCounts100(kOrphanedDataCountHistogramName,
- keys_to_delete->size());
- }
- auto save_no_entries =
- std::make_unique<std::vector<std::pair<std::string, T>>>();
- storage_database_->UpdateEntries(
- std::move(save_no_entries), std::move(keys_to_delete),
- base::BindOnce(&SessionProtoDB::OnOperationCommitted,
- weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
- }
- // Callback when an operation (e.g. insert or delete) is called.
- template <typename T>
- void SessionProtoDB<T>::OnOperationCommitted(OperationCallback callback,
- bool success) {
- std::move(callback).Run(success);
- }
- // Returns true if initialization status of database is not yet known.
- template <typename T>
- bool SessionProtoDB<T>::InitStatusUnknown() const {
- return database_status_ == absl::nullopt;
- }
- // Returns true if the database failed to initialize.
- template <typename T>
- bool SessionProtoDB<T>::FailedToInit() const {
- return database_status_.has_value() &&
- database_status_.value() != leveldb_proto::Enums::InitStatus::kOK;
- }
- #endif // COMPONENTS_SESSION_PROTO_DB_SESSION_PROTO_DB_H_
|