blob: 096120990e79e6486ed4d396a795377867ae0802 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/leveldb_proto/internal/shared_proto_database_client.h"
#include <memory>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "components/leveldb_proto/internal/proto_leveldb_wrapper.h"
#include "components/leveldb_proto/internal/shared_proto_database.h"
#include "components/leveldb_proto/public/proto_database.h"
#include "components/leveldb_proto/public/shared_proto_database_client_list.h"
namespace leveldb_proto {
namespace {
const ProtoDbType* g_obsolete_client_list_for_testing = nullptr;
// Holds the db wrapper alive and callback is called at destruction. This class
// is used to post multiple update tasks on |db_wrapper| and keep the instance
// alive till all the callbacks are returned.
class ObsoleteClientsDbHolder
: public base::RefCounted<ObsoleteClientsDbHolder> {
public:
ObsoleteClientsDbHolder(std::unique_ptr<ProtoLevelDBWrapper> db_wrapper,
Callbacks::UpdateCallback callback)
: success_(true),
owned_db_wrapper_(std::move(db_wrapper)),
callback_(std::move(callback)) {}
void set_success(bool success) { success_ &= success; }
private:
friend class base::RefCounted<ObsoleteClientsDbHolder>;
~ObsoleteClientsDbHolder() { std::move(callback_).Run(success_); }
bool success_;
std::unique_ptr<ProtoLevelDBWrapper> owned_db_wrapper_;
Callbacks::UpdateCallback callback_;
};
PhysicalKey MakePhysicalKey(const KeyPrefix& prefix, const LogicalKey& key) {
return PhysicalKey(base::StrCat({prefix.value(), key.value()}));
}
} // namespace
// static
bool SharedProtoDatabaseClient::HasPrefix(const PhysicalKey& key,
const KeyPrefix& prefix) {
return base::StartsWith(key.value(), prefix.value(),
base::CompareCase::SENSITIVE);
}
// static
std::optional<LogicalKey> SharedProtoDatabaseClient::StripPrefix(
const PhysicalKey& key,
const KeyPrefix& prefix) {
if (!HasPrefix(key, prefix))
return std::nullopt;
return LogicalKey(key.value().substr(prefix.value().length()));
}
// static
std::unique_ptr<KeyVector> SharedProtoDatabaseClient::PrefixStrings(
std::unique_ptr<KeyVector> strings,
const KeyPrefix& prefix) {
for (auto& str : *strings)
str.assign(MakePhysicalKey(prefix, LogicalKey(str)).value());
return strings;
}
// static
bool SharedProtoDatabaseClient::KeyFilterStripPrefix(
const KeyFilter& key_filter,
const KeyPrefix& prefix,
const PhysicalKey& key) {
if (key_filter.is_null())
return true;
std::optional<LogicalKey> stripped = StripPrefix(key, prefix);
if (!stripped)
return false;
return key_filter.Run(stripped->value());
}
// static
bool SharedProtoDatabaseClient::KeyStringFilterStripPrefix(
const KeyFilter& key_filter,
const KeyPrefix& prefix,
const std::string& key) {
return KeyFilterStripPrefix(key_filter, prefix, PhysicalKey(key));
}
// static
Enums::KeyIteratorAction
SharedProtoDatabaseClient::KeyIteratorControllerStripPrefix(
const KeyIteratorController& controller,
const KeyPrefix& prefix,
const PhysicalKey& key) {
DCHECK(!controller.is_null());
std::optional<LogicalKey> stripped = StripPrefix(key, prefix);
if (!stripped)
return Enums::kSkipAndStop;
return controller.Run(stripped->value());
}
// static
Enums::KeyIteratorAction
SharedProtoDatabaseClient::KeyStringIteratorControllerStripPrefix(
const KeyIteratorController& controller,
const KeyPrefix& prefix,
const std::string& key) {
return KeyIteratorControllerStripPrefix(controller, prefix, PhysicalKey(key));
}
// static
void SharedProtoDatabaseClient::GetSharedDatabaseInitStatusAsync(
const std::string& client_db_id,
const scoped_refptr<SharedProtoDatabase>& shared_db,
Callbacks::InitStatusCallback callback) {
shared_db->GetDatabaseInitStatusAsync(client_db_id, std::move(callback));
}
// static
void SharedProtoDatabaseClient::UpdateClientMetadataAsync(
const scoped_refptr<SharedProtoDatabase>& shared_db,
const std::string& client_db_id,
SharedDBMetadataProto::MigrationStatus migration_status,
ClientCorruptCallback callback) {
shared_db->UpdateClientMetadataAsync(client_db_id, migration_status,
std::move(callback));
}
// static
void SharedProtoDatabaseClient::DestroyObsoleteSharedProtoDatabaseClients(
std::unique_ptr<ProtoLevelDBWrapper> db_wrapper,
Callbacks::UpdateCallback callback) {
ProtoLevelDBWrapper* db_wrapper_ptr = db_wrapper.get();
scoped_refptr<ObsoleteClientsDbHolder> db_holder =
new ObsoleteClientsDbHolder(std::move(db_wrapper), std::move(callback));
const ProtoDbType* list = g_obsolete_client_list_for_testing
? g_obsolete_client_list_for_testing
: kObsoleteSharedProtoDbTypeClients;
for (size_t i = 0; UNSAFE_TODO(list[i]) != ProtoDbType::LAST; ++i) {
// Callback keeps a ref pointer to db_holder alive till the changes are
// done. |db_holder| will be destroyed once all the RemoveKeys() calls
// return.
Callbacks::UpdateCallback callback_wrapper =
base::BindOnce([](scoped_refptr<ObsoleteClientsDbHolder> db_holder,
bool success) { db_holder->set_success(success); },
db_holder);
// Remove all type prefixes for the client.
// TODO(ssid): Support cleanup of namespaces for clients. This code assumes
// the prefix contains the client namespace at the beginning.
db_wrapper_ptr->RemoveKeys(
base::BindRepeating([](const std::string& key) { return true; }),
SharedProtoDatabaseClient::PrefixForDatabase(UNSAFE_TODO(list[i]))
.value(),
std::move(callback_wrapper));
}
}
// static
void SharedProtoDatabaseClient::SetObsoleteClientListForTesting(
const ProtoDbType* list) {
g_obsolete_client_list_for_testing = list;
}
// static
KeyPrefix SharedProtoDatabaseClient::PrefixForDatabase(ProtoDbType db_type) {
return KeyPrefix(base::StringPrintf("%d_", static_cast<int>(db_type)));
}
SharedProtoDatabaseClient::SharedProtoDatabaseClient(
std::unique_ptr<ProtoLevelDBWrapper> db_wrapper,
ProtoDbType db_type,
const scoped_refptr<SharedProtoDatabase>& parent_db)
: UniqueProtoDatabase(std::move(db_wrapper)),
prefix_(PrefixForDatabase(db_type)),
parent_db_(parent_db) {
SetMetricsId(SharedProtoDatabaseClientList::ProtoDbTypeToString(db_type));
DETACH_FROM_SEQUENCE(sequence_checker_);
}
SharedProtoDatabaseClient::~SharedProtoDatabaseClient() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void SharedProtoDatabaseClient::Init(const std::string& client_uma_name,
Callbacks::InitStatusCallback callback) {
// Should never be called from from the selector, and init is not necessary.
NOTREACHED();
}
void SharedProtoDatabaseClient::InitWithDatabase(
LevelDB* database,
const base::FilePath& database_dir,
const leveldb_env::Options& options,
bool destroy_on_corruption,
Callbacks::InitStatusCallback callback) {
NOTREACHED();
}
void SharedProtoDatabaseClient::UpdateEntries(
std::unique_ptr<KeyValueVector> entries_to_save,
std::unique_ptr<KeyVector> keys_to_remove,
Callbacks::UpdateCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UniqueProtoDatabase::UpdateEntries(
PrefixKeyEntryVector(std::move(entries_to_save), prefix_),
PrefixStrings(std::move(keys_to_remove), prefix_), std::move(callback));
}
void SharedProtoDatabaseClient::UpdateEntriesWithRemoveFilter(
std::unique_ptr<KeyValueVector> entries_to_save,
const KeyFilter& delete_key_filter,
Callbacks::UpdateCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UpdateEntriesWithRemoveFilter(std::move(entries_to_save), delete_key_filter,
std::string(), std::move(callback));
}
void SharedProtoDatabaseClient::UpdateEntriesWithRemoveFilter(
std::unique_ptr<KeyValueVector> entries_to_save,
const KeyFilter& delete_key_filter,
const std::string& target_prefix,
Callbacks::UpdateCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UniqueProtoDatabase::UpdateEntriesWithRemoveFilter(
PrefixKeyEntryVector(std::move(entries_to_save), prefix_),
base::BindRepeating(&KeyStringFilterStripPrefix, delete_key_filter,
prefix_),
MakePhysicalKey(prefix_, LogicalKey(target_prefix)).value(),
std::move(callback));
}
void SharedProtoDatabaseClient::LoadEntries(Callbacks::LoadCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LoadEntriesWithFilter(KeyFilter(), std::move(callback));
}
void SharedProtoDatabaseClient::LoadEntriesWithFilter(
const KeyFilter& filter,
Callbacks::LoadCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LoadEntriesWithFilter(filter, leveldb::ReadOptions(), std::string(),
std::move(callback));
}
void SharedProtoDatabaseClient::LoadEntriesWithFilter(
const KeyFilter& filter,
const leveldb::ReadOptions& options,
const std::string& target_prefix,
Callbacks::LoadCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UniqueProtoDatabase::LoadEntriesWithFilter(
base::BindRepeating(&KeyStringFilterStripPrefix, filter, prefix_),
options, MakePhysicalKey(prefix_, LogicalKey(target_prefix)).value(),
std::move(callback));
}
void SharedProtoDatabaseClient::LoadKeys(Callbacks::LoadKeysCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LoadKeys(std::string(), std::move(callback));
}
void SharedProtoDatabaseClient::LoadKeys(const std::string& target_prefix,
Callbacks::LoadKeysCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UniqueProtoDatabase::LoadKeys(
MakePhysicalKey(prefix_, LogicalKey(target_prefix)).value(),
base::BindOnce(&SharedProtoDatabaseClient::StripPrefixLoadKeysCallback,
std::move(callback), prefix_));
}
void SharedProtoDatabaseClient::LoadKeysAndEntries(
Callbacks::LoadKeysAndEntriesCallback callback) {
LoadKeysAndEntriesWithFilter(KeyFilter(), std::move(callback));
}
void SharedProtoDatabaseClient::LoadKeysAndEntriesWithFilter(
const KeyFilter& filter,
Callbacks::LoadKeysAndEntriesCallback callback) {
LoadKeysAndEntriesWithFilter(filter, leveldb::ReadOptions(), std::string(),
std::move(callback));
}
void SharedProtoDatabaseClient::LoadKeysAndEntriesWithFilter(
const KeyFilter& filter,
const leveldb::ReadOptions& options,
const std::string& target_prefix,
Callbacks::LoadKeysAndEntriesCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UniqueProtoDatabase::LoadKeysAndEntriesWithFilter(
base::BindRepeating(&KeyStringFilterStripPrefix, filter, prefix_),
options, MakePhysicalKey(prefix_, LogicalKey(target_prefix)).value(),
base::BindOnce(
&SharedProtoDatabaseClient::StripPrefixLoadKeysAndEntriesCallback,
std::move(callback), prefix_));
}
void SharedProtoDatabaseClient::LoadKeysAndEntriesInRange(
const std::string& start,
const std::string& end,
Callbacks::LoadKeysAndEntriesCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UniqueProtoDatabase::LoadKeysAndEntriesInRange(
MakePhysicalKey(prefix_, LogicalKey(start)).value(),
MakePhysicalKey(prefix_, LogicalKey(end)).value(),
base::BindOnce(
&SharedProtoDatabaseClient::StripPrefixLoadKeysAndEntriesCallback,
std::move(callback), prefix_));
}
void SharedProtoDatabaseClient::LoadKeysAndEntriesWhile(
const std::string& start,
const leveldb_proto::KeyIteratorController& controller,
Callbacks::LoadKeysAndEntriesCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UniqueProtoDatabase::LoadKeysAndEntriesWhile(
MakePhysicalKey(prefix_, LogicalKey(start)).value(),
base::BindRepeating(&KeyStringIteratorControllerStripPrefix, controller,
prefix_),
base::BindOnce(
&SharedProtoDatabaseClient::StripPrefixLoadKeysAndEntriesCallback,
std::move(callback), prefix_));
}
void SharedProtoDatabaseClient::GetEntry(const std::string& key,
Callbacks::GetCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UniqueProtoDatabase::GetEntry(
MakePhysicalKey(prefix_, LogicalKey(key)).value(), std::move(callback));
}
void SharedProtoDatabaseClient::Destroy(Callbacks::DestroyCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
UpdateEntriesWithRemoveFilter(
std::make_unique<KeyValueVector>(),
base::BindRepeating([](const std::string& key) { return true; }),
base::BindOnce([](Callbacks::DestroyCallback callback,
bool success) { std::move(callback).Run(success); },
std::move(callback)));
}
void SharedProtoDatabaseClient::UpdateClientInitMetadata(
SharedDBMetadataProto::MigrationStatus migration_status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
migration_status_ = migration_status;
// Tell the SharedProtoDatabase that we've seen the corruption state so it's
// safe to update its records for this client.
UpdateClientMetadataAsync(parent_db_, client_db_id(), migration_status_,
base::BindOnce([](bool success) {
// TODO(thildebr): Should we do anything special
// here? If the shared DB can't update the
// client's corruption counter to match its own,
// then the client will think it's corrupt on the
// next Init as well.
}));
}
// static
void SharedProtoDatabaseClient::StripPrefixLoadKeysCallback(
Callbacks::LoadKeysCallback callback,
const KeyPrefix& prefix,
bool success,
std::unique_ptr<leveldb_proto::KeyVector> keys) {
auto stripped_keys = std::make_unique<leveldb_proto::KeyVector>();
for (auto& key : *keys) {
std::optional<LogicalKey> stripped = StripPrefix(PhysicalKey(key), prefix);
if (!stripped)
continue;
stripped_keys->emplace_back(stripped->value());
}
std::move(callback).Run(success, std::move(stripped_keys));
}
// static
void SharedProtoDatabaseClient::StripPrefixLoadKeysAndEntriesCallback(
Callbacks::LoadKeysAndEntriesCallback callback,
const KeyPrefix& prefix,
bool success,
std::unique_ptr<KeyValueMap> keys_entries) {
auto stripped_keys_map = std::make_unique<KeyValueMap>();
for (auto& key_entry : *keys_entries) {
std::optional<LogicalKey> stripped_key =
StripPrefix(PhysicalKey(key_entry.first), prefix);
if (!stripped_key)
continue;
stripped_keys_map->insert(
std::make_pair(stripped_key->value(), key_entry.second));
}
std::move(callback).Run(success, std::move(stripped_keys_map));
}
// static
std::unique_ptr<KeyValueVector> SharedProtoDatabaseClient::PrefixKeyEntryVector(
std::unique_ptr<KeyValueVector> kev,
const KeyPrefix& prefix) {
for (auto& key_entry_pair : *kev) {
key_entry_pair.first =
MakePhysicalKey(prefix, LogicalKey(key_entry_pair.first)).value();
}
return kev;
}
} // namespace leveldb_proto