diff options
author | fgorski@chromium.org <fgorski@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-16 16:05:36 +0000 |
---|---|---|
committer | fgorski@chromium.org <fgorski@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-16 16:05:36 +0000 |
commit | 79994f49494568039b0c1e310bb6ffd165d62c01 (patch) | |
tree | 27c560961dd22b9747952c4e0cb2a018b52dc685 /google_apis/gcm | |
parent | 348a2a79dd91a7f2f4007137bae4a48d57debd27 (diff) | |
download | chromium_src-79994f49494568039b0c1e310bb6ffd165d62c01.zip chromium_src-79994f49494568039b0c1e310bb6ffd165d62c01.tar.gz chromium_src-79994f49494568039b0c1e310bb6ffd165d62c01.tar.bz2 |
Adding a user list (to be consumed by GCM Client Implementation)
The goal of the list is to store mappings between usernames, serial numbers and delegates.
Patch includes:
* implementation of the user list
* tests for the user list
BUG=284553
Review URL: https://codereview.chromium.org/135303002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@245207 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'google_apis/gcm')
-rw-r--r-- | google_apis/gcm/engine/gcm_store.cc | 11 | ||||
-rw-r--r-- | google_apis/gcm/engine/gcm_store.h | 12 | ||||
-rw-r--r-- | google_apis/gcm/engine/gcm_store_impl.cc | 11 | ||||
-rw-r--r-- | google_apis/gcm/engine/gcm_store_impl_unittest.cc | 25 | ||||
-rw-r--r-- | google_apis/gcm/engine/user_list.cc | 197 | ||||
-rw-r--r-- | google_apis/gcm/engine/user_list.h | 102 | ||||
-rw-r--r-- | google_apis/gcm/engine/user_list_unittest.cc | 370 | ||||
-rw-r--r-- | google_apis/gcm/gcm.gyp | 3 | ||||
-rw-r--r-- | google_apis/gcm/gcm_client_impl.cc | 13 | ||||
-rw-r--r-- | google_apis/gcm/gcm_client_impl.h | 17 |
10 files changed, 743 insertions, 18 deletions
diff --git a/google_apis/gcm/engine/gcm_store.cc b/google_apis/gcm/engine/gcm_store.cc index b8559b0..f6c81dd 100644 --- a/google_apis/gcm/engine/gcm_store.cc +++ b/google_apis/gcm/engine/gcm_store.cc @@ -6,11 +6,18 @@ namespace gcm { +GCMStore::SerialNumberMappings::SerialNumberMappings() + : next_serial_number(1LL) { +} + +GCMStore::SerialNumberMappings::~SerialNumberMappings() { +} + GCMStore::LoadResult::LoadResult() : success(false), device_android_id(0), - device_security_token(0), - next_serial_number(1LL) {} + device_security_token(0) { +} GCMStore::LoadResult::~LoadResult() {} diff --git a/google_apis/gcm/engine/gcm_store.h b/google_apis/gcm/engine/gcm_store.h index 06189a9..d26ca18 100644 --- a/google_apis/gcm/engine/gcm_store.h +++ b/google_apis/gcm/engine/gcm_store.h @@ -28,6 +28,15 @@ class MCSMessage; // as well as store device and user checkin information. class GCM_EXPORT GCMStore { public: + // Part of load results storing user serial number mapping related values. + struct GCM_EXPORT SerialNumberMappings { + SerialNumberMappings(); + ~SerialNumberMappings(); + + int64 next_serial_number; + std::map<std::string, int64> user_serial_numbers; + }; + // Container for Load(..) results. struct GCM_EXPORT LoadResult { LoadResult(); @@ -38,8 +47,7 @@ class GCM_EXPORT GCMStore { uint64 device_security_token; std::vector<std::string> incoming_messages; std::map<std::string, google::protobuf::MessageLite*> outgoing_messages; - int64 next_serial_number; - std::map<std::string, int64> user_serial_numbers; + SerialNumberMappings serial_number_mappings; }; typedef std::vector<std::string> PersistentIdList; diff --git a/google_apis/gcm/engine/gcm_store_impl.cc b/google_apis/gcm/engine/gcm_store_impl.cc index 9edb219..90019b9 100644 --- a/google_apis/gcm/engine/gcm_store_impl.cc +++ b/google_apis/gcm/engine/gcm_store_impl.cc @@ -169,8 +169,10 @@ void GCMStoreImpl::Backend::Load(const LoadCallback& callback) { &result.device_security_token) || !LoadIncomingMessages(&result.incoming_messages) || !LoadOutgoingMessages(&result.outgoing_messages) || - !LoadNextSerialNumber(&result.next_serial_number) || - !LoadUserSerialNumberMap(&result.user_serial_numbers)) { + !LoadNextSerialNumber( + &result.serial_number_mappings.next_serial_number) || + !LoadUserSerialNumberMap( + &result.serial_number_mappings.user_serial_numbers)) { result.device_android_id = 0; result.device_security_token = 0; result.incoming_messages.clear(); @@ -192,10 +194,11 @@ void GCMStoreImpl::Backend::Load(const LoadCallback& callback) { result.outgoing_messages.size()); UMA_HISTOGRAM_COUNTS("GCM.RestoredIncomingMessages", result.incoming_messages.size()); - UMA_HISTOGRAM_COUNTS("GCM.NumUsers", result.user_serial_numbers.size()); + UMA_HISTOGRAM_COUNTS( + "GCM.NumUsers", + result.serial_number_mappings.user_serial_numbers.size()); } - DVLOG(1) << "Succeeded in loading " << result.incoming_messages.size() << " unacknowledged incoming messages and " << result.outgoing_messages.size() diff --git a/google_apis/gcm/engine/gcm_store_impl_unittest.cc b/google_apis/gcm/engine/gcm_store_impl_unittest.cc index 5b28ef7..c3dbcb3 100644 --- a/google_apis/gcm/engine/gcm_store_impl_unittest.cc +++ b/google_apis/gcm/engine/gcm_store_impl_unittest.cc @@ -109,8 +109,8 @@ TEST_F(GCMStoreImplTest, LoadNew) { EXPECT_EQ(0U, load_result.device_security_token); EXPECT_TRUE(load_result.incoming_messages.empty()); EXPECT_TRUE(load_result.outgoing_messages.empty()); - EXPECT_EQ(1LL, load_result.next_serial_number); - EXPECT_TRUE(load_result.user_serial_numbers.empty()); + EXPECT_EQ(1LL, load_result.serial_number_mappings.next_serial_number); + EXPECT_TRUE(load_result.serial_number_mappings.user_serial_numbers.empty()); } TEST_F(GCMStoreImplTest, DeviceCredentials) { @@ -314,7 +314,8 @@ TEST_F(GCMStoreImplTest, NextSerialNumber) { &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); PumpLoop(); - EXPECT_EQ(kNextSerialNumber, load_result.next_serial_number); + EXPECT_EQ(kNextSerialNumber, + load_result.serial_number_mappings.next_serial_number); } // Verify that user serial number mappings are persisted properly. @@ -345,13 +346,17 @@ TEST_F(GCMStoreImplTest, UserSerialNumberMappings) { &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); PumpLoop(); - ASSERT_EQ(2u, load_result.user_serial_numbers.size()); - ASSERT_NE(load_result.user_serial_numbers.end(), - load_result.user_serial_numbers.find(username1)); - EXPECT_EQ(serial_number1, load_result.user_serial_numbers[username1]); - ASSERT_NE(load_result.user_serial_numbers.end(), - load_result.user_serial_numbers.find(username2)); - EXPECT_EQ(serial_number2, load_result.user_serial_numbers[username2]); + ASSERT_EQ(2u, load_result.serial_number_mappings.user_serial_numbers.size()); + ASSERT_NE( + load_result.serial_number_mappings.user_serial_numbers.end(), + load_result.serial_number_mappings.user_serial_numbers.find(username1)); + EXPECT_EQ(serial_number1, + load_result.serial_number_mappings.user_serial_numbers[username1]); + ASSERT_NE( + load_result.serial_number_mappings.user_serial_numbers.end(), + load_result.serial_number_mappings.user_serial_numbers.find(username2)); + EXPECT_EQ(serial_number2, + load_result.serial_number_mappings.user_serial_numbers[username2]); } // Test that per-app message limits are enforced, persisted across restarts, diff --git a/google_apis/gcm/engine/user_list.cc b/google_apis/gcm/engine/user_list.cc new file mode 100644 index 0000000..829c66f --- /dev/null +++ b/google_apis/gcm/engine/user_list.cc @@ -0,0 +1,197 @@ +// Copyright 2014 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 "google_apis/gcm/engine/user_list.h" + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/sequenced_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "third_party/leveldatabase/src/include/leveldb/db.h" + +namespace gcm { +const int64 kInvalidSerialNumber = -1; + +UserList::UserInfo::UserInfo() + : serial_number(kInvalidSerialNumber), + delegate(NULL) { +} + +UserList::UserInfo::UserInfo(int64 serial_number) + : serial_number(serial_number), + delegate(NULL) { +} + +UserList::UserInfo::UserInfo(GCMClient::Delegate* delegate, + const SetDelegateCallback& callback) + : serial_number(kInvalidSerialNumber), + delegate(delegate), + callback(callback) { +} + +UserList::UserInfo::~UserInfo() { +} + +UserList::UserList(GCMStore* gcm_store) + : initialized_(false), + next_serial_number_(kInvalidSerialNumber), + gcm_store_(gcm_store) { +} + +UserList::~UserList() { +} + +void UserList::Initialize(const GCMStore::SerialNumberMappings& result) { + DCHECK(!initialized_); + DCHECK_GT(result.next_serial_number, 0); + + initialized_ = true; + next_serial_number_ = result.next_serial_number; + for (std::map<std::string, int64>::const_iterator iter = + result.user_serial_numbers.begin(); + iter != result.user_serial_numbers.end(); + ++iter) { + SetSerialNumber(iter->first, iter->second); + } + + for (UserInfoMap::iterator iter = delegates_.begin(); + iter != delegates_.end(); + ++iter) { + // Process only users with delegate and callback present. + if (iter->second.delegate && !iter->second.callback.is_null()) { + if (iter->second.serial_number == kInvalidSerialNumber) + AssignSerialNumber(iter->first); + else + OnSerialNumberReady(iter); + } + } +} + +void UserList::SetDelegate(const std::string& username, + GCMClient::Delegate* delegate, + const SetDelegateCallback& callback) { + DCHECK(!username.empty()); + DCHECK(delegate); + + UserInfoMap::iterator iter = delegates_.find(username); + if (iter != delegates_.end()) { + DCHECK(initialized_); + DCHECK(!iter->second.delegate); + iter->second.delegate = delegate; + iter->second.callback = callback; + } else { + delegates_.insert( + iter, + UserInfoMap::value_type(username, UserInfo(delegate, callback))); + } + + if (initialized_) { + UserInfoMap::iterator iter = delegates_.find(username); + if (iter->second.serial_number == kInvalidSerialNumber) + AssignSerialNumber(iter->first); + else + OnSerialNumberReady(iter); + } +} + +void UserList::AssignSerialNumber(const std::string& username) { + DCHECK(initialized_); + DCHECK_EQ(delegates_.count(username), 1UL); + DCHECK_EQ(delegates_[username].serial_number, kInvalidSerialNumber); + + // Start by incrementing the |next_serial_number_| and persist it in the GCM + // store. + int64 serial_number = next_serial_number_++; + gcm_store_->SetNextSerialNumber( + serial_number, + base::Bind(&UserList::IncrementSerialNumberCompleted, + base::Unretained(this), + username, + serial_number)); +} + +void UserList::IncrementSerialNumberCompleted(const std::string& username, + int64 user_serial_number, + bool success) { + // TODO(fgorski): Introduce retry logic. + if (!success) + DCHECK(success) << "Updating the next serial number failed."; + + SetSerialNumber(username, user_serial_number); + gcm_store_->AddUserSerialNumber( + username, + user_serial_number, + base::Bind(&UserList::AssignSerialNumberCompleted, + base::Unretained(this), + username)); +} + +void UserList::AssignSerialNumberCompleted(const std::string& username, + bool success) { + // TODO(fgorski): Introduce retry logic. + if (!success) { + DVLOG(1) << "It was not possible to persist username to serial number" + << " mapping for username: " << username; + return; + } + + UserInfoMap::iterator iter = delegates_.find(username); + DCHECK(iter != delegates_.end()); + OnSerialNumberReady(iter); +} + +void UserList::OnSerialNumberReady(const UserInfoMap::iterator& iter) { + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(iter->second.callback, + iter->first, + iter->second.serial_number)); + iter->second.callback.Reset(); +} + +void UserList::SetSerialNumber(const std::string& username, + int64 serial_number) { + DCHECK(!username.empty()); + DCHECK_GT(serial_number, 0L); + + UserInfoMap::iterator iter = delegates_.find(username); + if (iter != delegates_.end()) { + DCHECK_EQ(iter->second.serial_number, kInvalidSerialNumber); + iter->second.serial_number = serial_number; + } else { + delegates_.insert( + iter, UserInfoMap::value_type(username, UserInfo(serial_number))); + } +} + +GCMClient::Delegate* UserList::GetDelegateBySerialNumber(int64 serial_number) + const { + for (UserInfoMap::const_iterator iter = delegates_.begin(); + iter != delegates_.end(); + ++iter) { + if (iter->second.serial_number == serial_number) + return iter->second.delegate; + } + return NULL; +} + +GCMClient::Delegate* UserList::GetDelegateByUsername( + const std::string& username) const { + UserInfoMap::const_iterator iter = delegates_.find(username); + return iter != delegates_.end() ? iter->second.delegate : NULL; +} + +int64 UserList::GetSerialNumberForUsername(const std::string& username) const { + UserInfoMap::const_iterator iter = delegates_.find(username); + return iter != delegates_.end() ? iter->second.serial_number + : kInvalidSerialNumber; +} + +} // namespace gcm diff --git a/google_apis/gcm/engine/user_list.h b/google_apis/gcm/engine/user_list.h new file mode 100644 index 0000000..e22215d --- /dev/null +++ b/google_apis/gcm/engine/user_list.h @@ -0,0 +1,102 @@ +// Copyright 2014 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 GOOGLE_APIS_GCM_ENGINE_USER_LIST_H_ +#define GOOGLE_APIS_GCM_ENGINE_USER_LIST_H_ + +#include <map> +#include <string> + +#include "base/callback.h" +#include "google_apis/gcm/engine/gcm_store.h" +#include "google_apis/gcm/gcm_client.h" + +namespace gcm { + +GCM_EXPORT extern const int64 kInvalidSerialNumber; + +// UserList stores mappings between usernames, serial numbers and delegates to +// enable dispatching messages to applications. +class GCM_EXPORT UserList { + public: + // A callback used by SetDelegate method to return a |user_serial_number| + // assigned to the delegate identified by |username|. + typedef base::Callback<void(const std::string& username, + int64 user_serial_number)> SetDelegateCallback; + + explicit UserList(GCMStore* gcm_store); + ~UserList(); + + // Initializes the User List with a set of mappings and next serial number. + void Initialize(const GCMStore::SerialNumberMappings& result); + + // Sets a user delegate for |username|. It will create a new entry for the + // user if one does not exist. + void SetDelegate(const std::string& username, + GCMClient::Delegate* delegate, + const SetDelegateCallback& callback); + + // Returns a delegate for the user identified by |serial_number| or NULL, if + // a matching delegate was not found. + GCMClient::Delegate* GetDelegateBySerialNumber(int64 serial_number) const; + + // Returns a delegate for the user identified by |username| or NULL, if a + // matching delegate was not found. + GCMClient::Delegate* GetDelegateByUsername(const std::string& username) const; + + // Gets the serial number assigned to a specified |username|, if one is + // assigned, the value will be positive. If there is no matching delegate or + // it is not yet assigned a serial number, the result will be equal to + // kInvalidSerialNumber. + int64 GetSerialNumberForUsername(const std::string& username) const; + + private: + friend class UserListTest; + + struct UserInfo { + UserInfo(); + explicit UserInfo(int64 serial_number); + UserInfo(GCMClient::Delegate* delegate, + const SetDelegateCallback& callback); + ~UserInfo(); + + int64 serial_number; + // Delegate related to the username. Not owned by the UserDelegate. + GCMClient::Delegate* delegate; + SetDelegateCallback callback; + }; + typedef std::map<std::string, UserInfo> UserInfoMap; + + // Assigns a serial number to the user identitified by |username|. + void AssignSerialNumber(const std::string& username); + + // A callback invoked once the Backend is done updating the next serial + // number. + void IncrementSerialNumberCompleted(const std::string& username, + int64 user_serial_number, + bool success); + + // Callback for serial number completion. + void AssignSerialNumberCompleted(const std::string& username, bool success); + + // Concludes the process of setting a delegate by running a callback with + // |username| and |serial_number| assigned to that |username|. It will also + // reset the callback, so that it is not called again. + void OnSerialNumberReady(const UserInfoMap::iterator& iter); + + // Sets the serial number related to the username. It expects the entry to not + // exist yet and will create it. + void SetSerialNumber(const std::string& username, int64 serial_number); + + bool initialized_; + int64 next_serial_number_; + UserInfoMap delegates_; + GCMStore* gcm_store_; + + DISALLOW_COPY_AND_ASSIGN(UserList); +}; + +} // namespace gcm + +#endif // GOOGLE_APIS_GCM_ENGINE_USER_LIST_H_ diff --git a/google_apis/gcm/engine/user_list_unittest.cc b/google_apis/gcm/engine/user_list_unittest.cc new file mode 100644 index 0000000..09947b9 --- /dev/null +++ b/google_apis/gcm/engine/user_list_unittest.cc @@ -0,0 +1,370 @@ +// Copyright 2014 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 "google_apis/gcm/engine/user_list.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "google_apis/gcm/engine/gcm_store_impl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace gcm { + +namespace { + +class GCMClientDelegate : public GCMClient::Delegate { + public: + explicit GCMClientDelegate(const std::string& username); + virtual ~GCMClientDelegate(); + + const std::string& GetUsername() const { return username_; } + + // Overrides of GCMClientDelegate: + virtual void OnCheckInFinished(const GCMClient::CheckinInfo& checkin_info, + GCMClient::Result result) OVERRIDE {} + virtual void OnRegisterFinished(const std::string& app_id, + const std::string& registration_id, + GCMClient::Result result) OVERRIDE {} + virtual void OnSendFinished(const std::string& app_id, + const std::string& message_id, + GCMClient::Result result) OVERRIDE {} + virtual void OnMessageReceived(const std::string& app_id, + const GCMClient::IncomingMessage& message) + OVERRIDE {} + virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE {} + virtual void OnMessageSendError(const std::string& app_id, + const std::string& message_id, + GCMClient::Result result) OVERRIDE {} + virtual GCMClient::CheckinInfo GetCheckinInfo() const OVERRIDE { + return GCMClient::CheckinInfo(); + } + virtual void OnLoadingCompleted() OVERRIDE {} + virtual base::TaskRunner* GetFileTaskRunner() OVERRIDE { return NULL; } + + private: + std::string username_; +}; + +} // namespace + +class UserListTest : public testing::Test { + public: + UserListTest(); + virtual ~UserListTest(); + + virtual void SetUp() OVERRIDE; + + static size_t GetListSize(const UserList* user_list); + void SetDelegateCallback(const std::string& username, + int64 user_serial_number); + + scoped_ptr<UserList> BuildUserList(); + + void PumpLoop(); + + void UpdateCallback(bool success); + + void Initialize(UserList* user_list, const GCMStore::LoadResult& result); + + void ResetLoop(); + + protected: + int64 last_assigned_serial_number_; + std::string last_assigned_username_; + scoped_ptr<GCMStore> gcm_store_; + + private: + base::ScopedTempDir temp_directory_; + base::MessageLoop message_loop_; + scoped_ptr<base::RunLoop> run_loop_; +}; + +UserListTest::UserListTest() + : last_assigned_serial_number_(gcm::kInvalidSerialNumber) {} + +UserListTest::~UserListTest() {} + +void UserListTest::SetUp() { + ASSERT_TRUE(temp_directory_.CreateUniqueTempDir()); +} + +// static +size_t UserListTest::GetListSize(const UserList* user_list) { + return user_list->delegates_.size(); +} + +void UserListTest::SetDelegateCallback(const std::string& username, + int64 user_serial_number) { + last_assigned_username_ = username; + last_assigned_serial_number_ = user_serial_number; + ResetLoop(); +} + +scoped_ptr<UserList> UserListTest::BuildUserList() { + gcm_store_.reset(new GCMStoreImpl(temp_directory_.path(), + message_loop_.message_loop_proxy())); + return scoped_ptr<UserList>(new UserList(gcm_store_.get())); +} + +void UserListTest::Initialize(UserList* user_list, + const GCMStore::LoadResult& result) { + ASSERT_TRUE(result.success); + user_list->Initialize(result.serial_number_mappings); + ResetLoop(); +} + +void UserListTest::ResetLoop() { + if (run_loop_ && run_loop_->running()) + run_loop_->Quit(); +} + +void UserListTest::PumpLoop() { + run_loop_.reset(new base::RunLoop); + run_loop_->Run(); +} + +void UserListTest::UpdateCallback(bool success) { ASSERT_TRUE(success); } + +GCMClientDelegate::GCMClientDelegate(const std::string& username) + : username_(username) {} + +GCMClientDelegate::~GCMClientDelegate() {} + +// Make sure it is possible to add a delegate, and that it is assigned a serial +// number. +TEST_F(UserListTest, SetDelegateAndCheckSerialNumberAssignment) { + scoped_ptr<UserList> user_list(BuildUserList()); + gcm_store_->Load(base::Bind( + &UserListTest::Initialize, base::Unretained(this), user_list.get())); + PumpLoop(); + + // First adding a delegate. + scoped_ptr<GCMClientDelegate> delegate(new GCMClientDelegate("test_user_1")); + user_list->SetDelegate( + delegate->GetUsername(), + delegate.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + PumpLoop(); + + // Verify the record was created. + EXPECT_EQ(1u, GetListSize(user_list.get())); + // Verify username and serial number were assigned. + EXPECT_EQ(delegate->GetUsername(), last_assigned_username_); + EXPECT_NE(gcm::kInvalidSerialNumber, last_assigned_serial_number_); + // Check that a serial number was assigned to delegate. + EXPECT_EQ(last_assigned_serial_number_, + user_list->GetSerialNumberForUsername(delegate->GetUsername())); +} + +// Get the delegate that was added to the list by both serial number and +// username. +TEST_F(UserListTest, GetDelegate) { + scoped_ptr<UserList> user_list(BuildUserList()); + gcm_store_->Load(base::Bind( + &UserListTest::Initialize, base::Unretained(this), user_list.get())); + PumpLoop(); + + // Start by adding a delegate and a serial number. + scoped_ptr<GCMClientDelegate> delegate(new GCMClientDelegate("test_user_1")); + user_list->SetDelegate( + delegate->GetUsername(), + delegate.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + PumpLoop(); + + EXPECT_EQ(delegate.get(), + user_list->GetDelegateBySerialNumber(last_assigned_serial_number_)); + EXPECT_EQ(delegate.get(), + user_list->GetDelegateByUsername(last_assigned_username_)); +} + +// Make sure that correct mapping between username of the delegate and a serial +// number is preserved when loading the user list. Also verify that setting a +// delegate after load works correctly. (Finds the existing mapping entry.) +TEST_F(UserListTest, LoadUserEntrySetDelegate) { + scoped_ptr<UserList> user_list(BuildUserList()); + gcm_store_->Load(base::Bind( + &UserListTest::Initialize, base::Unretained(this), user_list.get())); + PumpLoop(); + + // Start by adding a delegate and a serial number. + scoped_ptr<GCMClientDelegate> delegate(new GCMClientDelegate("test_user_1")); + user_list->SetDelegate( + delegate->GetUsername(), + delegate.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + PumpLoop(); + + // Reload the GCM User List. + user_list = BuildUserList().Pass(); + gcm_store_->Load(base::Bind( + &UserListTest::Initialize, base::Unretained(this), user_list.get())); + PumpLoop(); + + // Verify a single record was loaded, with matching username, but no delegate. + EXPECT_EQ(1u, GetListSize(user_list.get())); + int64 serial_number = + user_list->GetSerialNumberForUsername(delegate->GetUsername()); + EXPECT_EQ(last_assigned_serial_number_, serial_number); + EXPECT_EQ(NULL, user_list->GetDelegateBySerialNumber(serial_number)); + EXPECT_EQ(NULL, user_list->GetDelegateByUsername(delegate->GetUsername())); + + // After loading is complete, Delegates will start adding itself looking for + // their serial numbers. Check that correct matches are found and new records + // not created. + user_list->SetDelegate( + delegate->GetUsername(), + delegate.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + PumpLoop(); + + EXPECT_EQ(1u, GetListSize(user_list.get())); + serial_number = + user_list->GetSerialNumberForUsername(delegate->GetUsername()); + EXPECT_EQ(last_assigned_serial_number_, serial_number); + EXPECT_EQ(delegate.get(), + user_list->GetDelegateBySerialNumber(serial_number)); + EXPECT_EQ(delegate.get(), + user_list->GetDelegateByUsername(delegate->GetUsername())); +} + +// Check that it is possible to add multiple delegates to the user list. +TEST_F(UserListTest, AddMultipleDelegates) { + scoped_ptr<UserList> user_list(BuildUserList()); + gcm_store_->Load(base::Bind( + &UserListTest::Initialize, base::Unretained(this), user_list.get())); + PumpLoop(); + + // Start by adding a delegate and a serial number. + scoped_ptr<GCMClientDelegate> delegate1(new GCMClientDelegate("test_user_1")); + user_list->SetDelegate( + delegate1->GetUsername(), + delegate1.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + PumpLoop(); + + int64 serial_number1 = last_assigned_serial_number_; + + scoped_ptr<GCMClientDelegate> delegate2(new GCMClientDelegate("test_user_2")); + user_list->SetDelegate( + delegate2->GetUsername(), + delegate2.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + PumpLoop(); + + int64 serial_number2 = last_assigned_serial_number_; + + // Ensuring that serial numbers are different. + EXPECT_EQ(2u, GetListSize(user_list.get())); + + // Reading the user entries. + user_list = BuildUserList().Pass(); + gcm_store_->Load(base::Bind( + &UserListTest::Initialize, base::Unretained(this), user_list.get())); + PumpLoop(); + + // Serial numbers stay the same, but there are no delegates assigned. + EXPECT_EQ(2u, GetListSize(user_list.get())); + EXPECT_EQ(NULL, user_list->GetDelegateByUsername(delegate1->GetUsername())); + EXPECT_EQ(NULL, user_list->GetDelegateBySerialNumber(serial_number1)); + EXPECT_EQ(NULL, user_list->GetDelegateByUsername(delegate2->GetUsername())); + EXPECT_EQ(NULL, user_list->GetDelegateBySerialNumber(serial_number2)); + + // Setting and checking a delegate on the second user. + user_list->SetDelegate( + delegate2->GetUsername(), + delegate2.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + PumpLoop(); + + // First user still does not have a delegate. + EXPECT_EQ(NULL, user_list->GetDelegateByUsername(delegate1->GetUsername())); + EXPECT_EQ(NULL, user_list->GetDelegateBySerialNumber(serial_number1)); + // Second user has a delegate set. + EXPECT_EQ(delegate2.get(), + user_list->GetDelegateByUsername(delegate2->GetUsername())); + EXPECT_EQ(delegate2.get(), + user_list->GetDelegateBySerialNumber(serial_number2)); +} + +// Adding a delegate before the user list is initialized. Verifies that serial +// number assignment is postponed until after initialization. +TEST_F(UserListTest, AddDelegateThenInitializeWithoutSerialNumber) { + scoped_ptr<UserList> user_list(BuildUserList()); + + // Add a delegate first. + scoped_ptr<GCMClientDelegate> delegate(new GCMClientDelegate("test_user_1")); + user_list->SetDelegate( + delegate->GetUsername(), + delegate.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + + EXPECT_EQ(gcm::kInvalidSerialNumber, last_assigned_serial_number_); + EXPECT_EQ("", last_assigned_username_); + + // Now run the initialization. + gcm_store_->Load(base::Bind( + &UserListTest::Initialize, base::Unretained(this), user_list.get())); + // Need to pump twice, due to initialization triggering additional set of + // callbacks to be run. + PumpLoop(); + PumpLoop(); + + EXPECT_EQ(last_assigned_serial_number_, + user_list->GetSerialNumberForUsername(delegate->GetUsername())); + EXPECT_EQ(delegate->GetUsername(), last_assigned_username_); +} + +// Adding a delegate that already has a serial number on a subsequent restart of +// the user list and prior to initialization. Expects to assing correct serial +// number based on existing mappings. +TEST_F(UserListTest, AddDelegateThenInitializeWithSerialNumber) { + scoped_ptr<UserList> user_list(BuildUserList()); + gcm_store_->Load(base::Bind( + &UserListTest::Initialize, base::Unretained(this), user_list.get())); + PumpLoop(); + + // First add a delegate to the list so that serial number is persisted. + scoped_ptr<GCMClientDelegate> delegate(new GCMClientDelegate("test_user_1")); + user_list->SetDelegate( + delegate->GetUsername(), + delegate.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + PumpLoop(); + + last_assigned_serial_number_ = gcm::kInvalidSerialNumber; + last_assigned_username_ = ""; + + // Resetting the user list to make sure it is not initialized. + user_list = BuildUserList().Pass(); + + // Add a delegate again, this time no callback is expected until the list is + // initialized. + user_list->SetDelegate( + delegate->GetUsername(), + delegate.get(), + base::Bind(&UserListTest::SetDelegateCallback, base::Unretained(this))); + + // Now run the initialization. + gcm_store_->Load(base::Bind( + &UserListTest::Initialize, base::Unretained(this), user_list.get())); + // Need to pump twice, due to initialization triggering additional set of + // callbacks to be run. + PumpLoop(); + PumpLoop(); + + EXPECT_EQ(last_assigned_serial_number_, + user_list->GetSerialNumberForUsername(delegate->GetUsername())); + EXPECT_EQ(delegate->GetUsername(), last_assigned_username_); +} + +} // namespace gcm diff --git a/google_apis/gcm/gcm.gyp b/google_apis/gcm/gcm.gyp index ad4989a..f1727e0 100644 --- a/google_apis/gcm/gcm.gyp +++ b/google_apis/gcm/gcm.gyp @@ -64,6 +64,8 @@ 'engine/mcs_client.h', 'engine/registration_request.cc', 'engine/registration_request.h', + 'engine/user_list.cc', + 'engine/user_list.h', 'gcm_client.cc', 'gcm_client.h', 'gcm_client_impl.cc', @@ -133,6 +135,7 @@ 'engine/heartbeat_manager_unittest.cc', 'engine/mcs_client_unittest.cc', 'engine/registration_request_unittest.cc', + 'engine/user_list_unittest.cc', ] }, ], diff --git a/google_apis/gcm/gcm_client_impl.cc b/google_apis/gcm/gcm_client_impl.cc index 86da9d5..8d09050 100644 --- a/google_apis/gcm/gcm_client_impl.cc +++ b/google_apis/gcm/gcm_client_impl.cc @@ -4,6 +4,12 @@ #include "google_apis/gcm/gcm_client_impl.h" +#include "base/files/file_path.h" +#include "base/sequenced_task_runner.h" +#include "google_apis/gcm/engine/gcm_store.h" +#include "google_apis/gcm/engine/gcm_store_impl.h" +#include "google_apis/gcm/engine/user_list.h" + namespace gcm { GCMClientImpl::GCMClientImpl() { @@ -12,6 +18,13 @@ GCMClientImpl::GCMClientImpl() { GCMClientImpl::~GCMClientImpl() { } +void GCMClientImpl::Initialize( + const base::FilePath& path, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) { + gcm_store_.reset(new GCMStoreImpl(path, blocking_task_runner)); + user_list_.reset(new UserList(gcm_store_.get())); +} + void GCMClientImpl::SetUserDelegate(const std::string& username, Delegate* delegate) { } diff --git a/google_apis/gcm/gcm_client_impl.h b/google_apis/gcm/gcm_client_impl.h index 797c325..475de85 100644 --- a/google_apis/gcm/gcm_client_impl.h +++ b/google_apis/gcm/gcm_client_impl.h @@ -6,15 +6,29 @@ #define GOOGLE_APIS_GCM_GCM_CLIENT_IMPL_H_ #include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "google_apis/gcm/gcm_client.h" +namespace base { +class FilePath; +class SequencedTaskRunner; +} // namespace base + namespace gcm { +class GCMStore; +class UserList; + class GCMClientImpl : public GCMClient { public: GCMClientImpl(); virtual ~GCMClientImpl(); + void Initialize( + const base::FilePath& path, + scoped_refptr<base::SequencedTaskRunner> blocking_task_runner); + // Overridden from GCMClient: virtual void SetUserDelegate(const std::string& username, Delegate* delegate) OVERRIDE; @@ -32,6 +46,9 @@ class GCMClientImpl : public GCMClient { virtual bool IsLoading() const OVERRIDE; private: + scoped_ptr<GCMStore> gcm_store_; + scoped_ptr<UserList> user_list_; + DISALLOW_COPY_AND_ASSIGN(GCMClientImpl); }; |