Commit cec569fc authored by Xing Liu's avatar Xing Liu Committed by Commit Bot

Notification scheduler: Introduce collection store.

Since we need to load multiple kinds of protos, this CL adds a generic
storage interface and its implementation to load protobuffers from
leveldb and converts them to in-memory structs. Actual store will be
subclass of the generic store class, and implement proto entry
conversions.

TBR=peter@chromium.org

Bug: 930968
Change-Id: I9b87ad14aeecd321ff2eccd4c11350354aae9b97
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1496527
Commit-Queue: Xing Liu <xingliu@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#637899}
parent 5059675d
...@@ -8,3 +8,10 @@ group("notifications") { ...@@ -8,3 +8,10 @@ group("notifications") {
"//chrome/browser/notifications/scheduler", "//chrome/browser/notifications/scheduler",
] ]
} }
group("unit_tests") {
testonly = true
deps = [
"//chrome/browser/notifications/scheduler:unit_tests",
]
}
...@@ -41,13 +41,32 @@ source_set("public") { ...@@ -41,13 +41,32 @@ source_set("public") {
# Internal library that embedders should not directly depend on. # Internal library that embedders should not directly depend on.
source_set("lib") { source_set("lib") {
sources = [ sources = [
"collection_store.h",
"notification_schedule_service_impl.cc", "notification_schedule_service_impl.cc",
"notification_schedule_service_impl.h", "notification_schedule_service_impl.h",
"proto_db_collection_store.cc",
"proto_db_collection_store.h",
] ]
deps = [ deps = [
":public", ":public",
"//base", "//base",
"//components/keyed_service/core", "//components/keyed_service/core",
"//components/leveldb_proto",
]
}
source_set("unit_tests") {
testonly = true
sources = [
"proto_db_collection_store_unittest.cc",
]
deps = [
":lib",
":public",
"//components/leveldb_proto:test_support",
"//testing/gmock",
"//testing/gtest",
] ]
} }
// Copyright 2019 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 CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_COLLECTION_STORE_H_
#define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_COLLECTION_STORE_H_
#include <memory>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
namespace notifications {
// A storage interface which loads a collection of data type T into memory
// during initialization.
template <typename T>
class CollectionStore {
public:
using Entries = std::unique_ptr<std::vector<T>>;
using LoadCallback = base::OnceCallback<void(bool, Entries)>;
using InitCallback = base::OnceCallback<void(bool)>;
using UpdateCallback = base::OnceCallback<void(bool)>;
protected:
// Initializes the database.
virtual void Init(InitCallback callback) = 0;
// Initializes the database and loads all entries into memory.
virtual void InitAndLoad(LoadCallback callback) = 0;
// Loads one entry into memory.
virtual void Load(const std::string& key, LoadCallback callback) = 0;
// Adds an entry to the storage.
virtual void Add(const std::string& key,
const T& entry,
UpdateCallback callback) = 0;
// Deletes an entry from storage.
virtual void Delete(const std::string& key, UpdateCallback callback) = 0;
CollectionStore() = default;
virtual ~CollectionStore() = default;
private:
DISALLOW_COPY_AND_ASSIGN(CollectionStore);
};
} // namespace notifications
#endif // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_COLLECTION_STORE_H_
// Copyright 2019 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.
#include "chrome/browser/notifications/scheduler/proto_db_collection_store.h"
namespace notifications {
bool ExactMatchKeyFilter(const std::string& key_to_load,
const std::string& current_key) {
return key_to_load == current_key;
}
} // namespace notifications
// Copyright 2019 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 CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_PROTO_DB_COLLECTION_STORE_H_
#define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_PROTO_DB_COLLECTION_STORE_H_
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/notifications/scheduler/collection_store.h"
#include "components/leveldb_proto/public/proto_database.h"
namespace notifications {
bool ExactMatchKeyFilter(const std::string& key_to_load,
const std::string& current_key);
// A storage that uses level db which persists protobuffer type P, to load
// collection of data type T into memory.
// Subclass should implement the proto, entry conversion.(T <==> P)
template <typename T, typename P>
class ProtoDbCollectionStore : public CollectionStore<T> {
public:
using Entries = typename CollectionStore<T>::Entries;
using InitCallback = typename CollectionStore<T>::InitCallback;
using LoadCallback = typename CollectionStore<T>::LoadCallback;
using UpdateCallback = typename CollectionStore<T>::UpdateCallback;
using KeyProtoVector = std::vector<std::pair<std::string, P>>;
using KeyVector = std::vector<std::string>;
// Database configuration.
struct DbConfig {
std::string client_name;
};
ProtoDbCollectionStore(std::unique_ptr<leveldb_proto::ProtoDatabase<P>> db,
const std::string& db_client_name)
: db_(std::move(db)),
db_client_name_(db_client_name),
weak_ptr_factory_(this) {
DCHECK(db_);
}
~ProtoDbCollectionStore() override = default;
// CollectionStore<T> implementation.
void Init(InitCallback callback) override {
db_->Init(
db_client_name_,
base::BindOnce(&ProtoDbCollectionStore::OnDbInitialized,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void InitAndLoad(LoadCallback callback) override {
db_->Init(
db_client_name_,
base::BindOnce(&ProtoDbCollectionStore::OnDbInitializedBeforeLoad,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void Load(const std::string& key, LoadCallback callback) override {
db_->LoadEntriesWithFilter(
base::BindRepeating(&ExactMatchKeyFilter, key),
base::BindOnce(&ProtoDbCollectionStore::OnEntriesLoaded,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void Add(const std::string& key,
const T& entry,
UpdateCallback callback) override {
auto protos_to_save = std::make_unique<KeyProtoVector>();
protos_to_save->emplace_back(std::make_pair(key, EntryToProto(entry)));
db_->UpdateEntries(std::move(protos_to_save), std::make_unique<KeyVector>(),
std::move(callback));
}
void Delete(const std::string& key, UpdateCallback callback) override {
auto keys_to_delete = std::make_unique<KeyVector>();
keys_to_delete->emplace_back(key);
db_->UpdateEntries(std::make_unique<KeyProtoVector>(),
std::move(keys_to_delete), std::move(callback));
}
protected:
// Conversion from in memory entry type T to protobuff type P.
virtual P EntryToProto(const T& entry) = 0;
// Conversion from protobuff type P to in memory entry type T.
virtual T ProtoToEntry(const P& proto) = 0;
private:
void OnDbInitialized(InitCallback callback,
leveldb_proto::Enums::InitStatus status) {
bool success = (status == leveldb_proto::Enums::InitStatus::kOK);
std::move(callback).Run(success);
}
void OnDbInitializedBeforeLoad(LoadCallback callback,
leveldb_proto::Enums::InitStatus status) {
bool success = (status == leveldb_proto::Enums::InitStatus::kOK);
// Failed to open db.
if (!success) {
std::move(callback).Run(false, Entries());
return;
}
db_->LoadEntries(base::BindOnce(&ProtoDbCollectionStore::OnEntriesLoaded,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void OnEntriesLoaded(LoadCallback callback,
bool success,
std::unique_ptr<std::vector<P>> protos) {
// Failed to load data.
if (!success) {
std::move(callback).Run(false, Entries());
return;
}
Entries entries = std::make_unique<std::vector<T>>();
for (const auto& proto : *protos)
entries->emplace_back(ProtoToEntry(proto));
std::move(callback).Run(true, std::move(entries));
}
std::unique_ptr<leveldb_proto::ProtoDatabase<P>> db_;
std::string db_client_name_;
base::WeakPtrFactory<ProtoDbCollectionStore<T, P>> weak_ptr_factory_;
};
} // namespace notifications
#endif // CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_PROTO_DB_COLLECTION_STORE_H_
// Copyright 2019 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.
#include "chrome/browser/notifications/scheduler/proto_db_collection_store.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "components/leveldb_proto/testing/fake_db.h"
#include "components/leveldb_proto/testing/proto/test_db.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace notifications {
namespace {
// Entry for test which will serialize into leveldb_proto.TestProto.
struct TestEntry {
bool operator==(const TestEntry& other) const {
return id == other.id && data == other.data;
}
std::string id;
std::string data;
};
// A implementation of ProtoDbCollectionStore used to test
// ProtoDbCollectionStore<T, P>.
class ProtoStoreForTest
: public ProtoDbCollectionStore<TestEntry, leveldb_proto::TestProto> {
public:
using Entries =
typename ProtoDbCollectionStore<TestEntry,
leveldb_proto::TestProto>::Entries;
ProtoStoreForTest(
std::unique_ptr<leveldb_proto::ProtoDatabase<leveldb_proto::TestProto>>
db)
: ProtoDbCollectionStore(std::move(db),
std::string("ProtoStoreForTest")) {}
~ProtoStoreForTest() override = default;
private:
// ProtoDbCollectionStore implementation.
leveldb_proto::TestProto EntryToProto(const TestEntry& entry) override {
leveldb_proto::TestProto proto;
proto.set_id(entry.id);
proto.set_data(entry.data);
return proto;
}
TestEntry ProtoToEntry(const leveldb_proto::TestProto& proto) override {
TestEntry entry;
entry.id = proto.id();
entry.data = proto.data();
return entry;
}
DISALLOW_COPY_AND_ASSIGN(ProtoStoreForTest);
};
// Verifies that |entry| and |proto| contains the same data.
void VerifyEntryProto(const TestEntry& entry,
const leveldb_proto::TestProto& proto) {
EXPECT_EQ(entry.id, proto.id());
EXPECT_EQ(entry.data, proto.data());
}
const char kProtoKey[] = "guid_1234";
const char kProtoId[] = "proto_id";
const char kProtoData[] = "data_1234";
class ProtoDbCollectionStoreTest : public testing::Test {
public:
ProtoDbCollectionStoreTest() : db_(nullptr) {}
~ProtoDbCollectionStoreTest() override = default;
void SetUp() override {
leveldb_proto::TestProto proto;
proto.set_id(kProtoId);
proto.set_data(kProtoData);
db_protos_.emplace(kProtoKey, proto);
auto db =
std::make_unique<leveldb_proto::test::FakeDB<leveldb_proto::TestProto>>(
&db_protos_);
db_ = db.get();
store_ = std::make_unique<ProtoStoreForTest>(std::move(db));
}
void OnDbInitAndLoad(base::RepeatingClosure quit_closure,
bool expected_success,
bool success,
ProtoStoreForTest::Entries entries) {
EXPECT_EQ(expected_success, success);
entries_ = std::move(entries);
quit_closure.Run();
}
protected:
ProtoStoreForTest* store() { return store_.get(); }
leveldb_proto::test::FakeDB<leveldb_proto::TestProto>* db() { return db_; }
const std::map<std::string, leveldb_proto::TestProto>& db_protos() const {
return db_protos_;
}
std::vector<TestEntry>* entries() const { return entries_.get(); }
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<ProtoStoreForTest> store_;
std::unique_ptr<std::vector<TestEntry>> entries_;
std::map<std::string, leveldb_proto::TestProto> db_protos_;
leveldb_proto::test::FakeDB<leveldb_proto::TestProto>* db_;
DISALLOW_COPY_AND_ASSIGN(ProtoDbCollectionStoreTest);
};
TEST_F(ProtoDbCollectionStoreTest, Init) {
store()->Init(base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
base::RunLoop().RunUntilIdle();
}
TEST_F(ProtoDbCollectionStoreTest, InitFailed) {
store()->Init(base::BindOnce([](bool success) { EXPECT_FALSE(success); }));
db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kCorrupt);
base::RunLoop().RunUntilIdle();
}
TEST_F(ProtoDbCollectionStoreTest, InitAndLoad) {
base::RunLoop run_loop;
store()->InitAndLoad(
base::BindOnce(&ProtoDbCollectionStoreTest::OnDbInitAndLoad,
base::Unretained(this), run_loop.QuitClosure(), true));
db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
db()->LoadCallback(true);
EXPECT_EQ(entries()->size(), 1u);
VerifyEntryProto(entries()->front(), db_protos().begin()->second);
}
TEST_F(ProtoDbCollectionStoreTest, InitAndLoadFailedInit) {
store()->InitAndLoad(
base::BindOnce([](bool success, ProtoStoreForTest::Entries entries) {
EXPECT_FALSE(success);
EXPECT_EQ(entries, nullptr);
}));
db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
}
TEST_F(ProtoDbCollectionStoreTest, InitAndLoadFailedLoad) {
store()->InitAndLoad(
base::BindOnce([](bool success, ProtoStoreForTest::Entries entries) {
EXPECT_FALSE(success);
EXPECT_EQ(entries, nullptr);
}));
db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
db()->LoadCallback(false);
}
TEST_F(ProtoDbCollectionStoreTest, LoadOne) {
store()->Init(base::DoNothing());
db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
base::RunLoop run_loop;
store()->Load(
kProtoKey,
base::BindOnce(&ProtoDbCollectionStoreTest::OnDbInitAndLoad,
base::Unretained(this), run_loop.QuitClosure(), true));
db()->LoadCallback(true);
VerifyEntryProto(entries()->front(), db_protos().begin()->second);
}
TEST_F(ProtoDbCollectionStoreTest, Add) {
store()->Init(base::DoNothing());
db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
// Add a new entry.
std::string new_key = std::string(kProtoKey) + "_new";
TestEntry new_entry;
new_entry.id = "new_id";
new_entry.data = "new_data";
store()->Add(new_key, new_entry,
base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
// Load the new entry just added.
db()->UpdateCallback(true);
store()->Load(new_key, base::BindOnce([](bool success,
ProtoStoreForTest::Entries entries) {
EXPECT_TRUE(success);
EXPECT_TRUE(entries);
EXPECT_EQ(entries->size(), 1u);
EXPECT_EQ(entries->front().id, "new_id");
EXPECT_EQ(entries->front().data, "new_data");
}));
db()->LoadCallback(true);
}
TEST_F(ProtoDbCollectionStoreTest, Delete) {
store()->InitAndLoad(base::DoNothing());
db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
db()->LoadCallback(true);
// Delete the only entry.
store()->Delete(kProtoKey,
base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
db()->UpdateCallback(true);
// Load the deleted entry.
store()->Load(
kProtoKey,
base::BindOnce([](bool success, ProtoStoreForTest::Entries entries) {
EXPECT_TRUE(success);
EXPECT_TRUE(entries);
EXPECT_EQ(entries->size(), 0u);
}));
db()->LoadCallback(true);
}
} // namespace
} // namespace notifications
...@@ -3078,6 +3078,7 @@ test("unit_tests") { ...@@ -3078,6 +3078,7 @@ test("unit_tests") {
"//chrome:resources", "//chrome:resources",
"//chrome:strings", "//chrome:strings",
"//chrome/browser/media/router:test_support", "//chrome/browser/media/router:test_support",
"//chrome/browser/notifications:unit_tests",
"//chrome/common:test_support", "//chrome/common:test_support",
"//chrome/common/media_router:test_support", "//chrome/common/media_router:test_support",
"//components/autofill/content/renderer:test_support", "//components/autofill/content/renderer:test_support",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment