Commit f388235a authored by fgorski@chromium.org's avatar fgorski@chromium.org

[GCM] Persistence of account mappings.

* Adding ability to add, remove and load account info
to device mappings in GCMStore with Tests.
* Moving the code resetting load results to LoadResult
structure (fixed a bug where last_checkin_accounts
where not cleared upon failure to load)

R=zea@chromium.org
BUG=374969

Review URL: https://codereview.chromium.org/429073002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287391 0039d316-1c4b-4281-b951-d872f2087c98
parent acad4965
...@@ -12,6 +12,8 @@ component("gcm") { ...@@ -12,6 +12,8 @@ component("gcm") {
"base/mcs_util.h", "base/mcs_util.h",
"base/socket_stream.cc", "base/socket_stream.cc",
"base/socket_stream.h", "base/socket_stream.h",
"engine/account_info.cc",
"engine/account_info.h",
"engine/checkin_request.cc", "engine/checkin_request.cc",
"engine/checkin_request.h", "engine/checkin_request.h",
"engine/connection_factory.cc", "engine/connection_factory.cc",
...@@ -105,6 +107,7 @@ test("gcm_unit_tests") { ...@@ -105,6 +107,7 @@ test("gcm_unit_tests") {
"base/mcs_message_unittest.cc", "base/mcs_message_unittest.cc",
"base/mcs_util_unittest.cc", "base/mcs_util_unittest.cc",
"base/socket_stream_unittest.cc", "base/socket_stream_unittest.cc",
"engine/account_info_unittest.cc",
"engine/checkin_request_unittest.cc", "engine/checkin_request_unittest.cc",
"engine/connection_factory_impl_unittest.cc", "engine/connection_factory_impl_unittest.cc",
"engine/connection_handler_impl_unittest.cc", "engine/connection_handler_impl_unittest.cc",
......
...@@ -14,6 +14,20 @@ GCMStore::LoadResult::LoadResult() ...@@ -14,6 +14,20 @@ GCMStore::LoadResult::LoadResult()
GCMStore::LoadResult::~LoadResult() {} GCMStore::LoadResult::~LoadResult() {}
void GCMStore::LoadResult::Reset() {
device_android_id = 0;
device_security_token = 0;
registrations.clear();
incoming_messages.clear();
outgoing_messages.clear();
gservices_settings.clear();
gservices_digest.clear();
last_checkin_time = base::Time::FromInternalValue(0LL);
last_checkin_accounts.clear();
account_infos.clear();
success = false;
}
GCMStore::GCMStore() {} GCMStore::GCMStore() {}
GCMStore::~GCMStore() {} GCMStore::~GCMStore() {}
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "google_apis/gcm/base/gcm_export.h" #include "google_apis/gcm/base/gcm_export.h"
#include "google_apis/gcm/engine/account_info.h"
#include "google_apis/gcm/engine/registration_info.h" #include "google_apis/gcm/engine/registration_info.h"
namespace gcm { namespace gcm {
...@@ -33,11 +34,16 @@ class GCM_EXPORT GCMStore { ...@@ -33,11 +34,16 @@ class GCM_EXPORT GCMStore {
typedef std::map<std::string, linked_ptr<google::protobuf::MessageLite> > typedef std::map<std::string, linked_ptr<google::protobuf::MessageLite> >
OutgoingMessageMap; OutgoingMessageMap;
// Map of account id to account info for account mappings.
typedef std::map<std::string, AccountInfo> AccountInfoMap;
// Container for Load(..) results. // Container for Load(..) results.
struct GCM_EXPORT LoadResult { struct GCM_EXPORT LoadResult {
LoadResult(); LoadResult();
~LoadResult(); ~LoadResult();
void Reset();
bool success; bool success;
uint64 device_android_id; uint64 device_android_id;
uint64 device_security_token; uint64 device_security_token;
...@@ -48,6 +54,7 @@ class GCM_EXPORT GCMStore { ...@@ -48,6 +54,7 @@ class GCM_EXPORT GCMStore {
std::string gservices_digest; std::string gservices_digest;
base::Time last_checkin_time; base::Time last_checkin_time;
std::set<std::string> last_checkin_accounts; std::set<std::string> last_checkin_accounts;
AccountInfoMap account_infos;
}; };
typedef std::vector<std::string> PersistentIdList; typedef std::vector<std::string> PersistentIdList;
...@@ -102,7 +109,7 @@ class GCM_EXPORT GCMStore { ...@@ -102,7 +109,7 @@ class GCM_EXPORT GCMStore {
virtual void RemoveOutgoingMessages(const PersistentIdList& persistent_ids, virtual void RemoveOutgoingMessages(const PersistentIdList& persistent_ids,
const UpdateCallback& callback) = 0; const UpdateCallback& callback) = 0;
// Sets last device's checkin time. // Sets last device's checkin information.
virtual void SetLastCheckinInfo(const base::Time& time, virtual void SetLastCheckinInfo(const base::Time& time,
const std::set<std::string>& accounts, const std::set<std::string>& accounts,
const UpdateCallback& callback) = 0; const UpdateCallback& callback) = 0;
...@@ -115,6 +122,12 @@ class GCM_EXPORT GCMStore { ...@@ -115,6 +122,12 @@ class GCM_EXPORT GCMStore {
const std::string& settings_digest, const std::string& settings_digest,
const UpdateCallback& callback) = 0; const UpdateCallback& callback) = 0;
// Sets the account information related to device to account mapping.
virtual void AddAccountMapping(const AccountInfo& account_info,
const UpdateCallback& callback) = 0;
virtual void RemoveAccountMapping(const std::string& account_id,
const UpdateCallback& callback) = 0;
private: private:
DISALLOW_COPY_AND_ASSIGN(GCMStore); DISALLOW_COPY_AND_ASSIGN(GCMStore);
}; };
......
...@@ -68,6 +68,12 @@ const char kGServiceSettingsDigestKey[] = "gservices_digest"; ...@@ -68,6 +68,12 @@ const char kGServiceSettingsDigestKey[] = "gservices_digest";
const char kLastCheckinAccountsKey[] = "last_checkin_accounts_count"; const char kLastCheckinAccountsKey[] = "last_checkin_accounts_count";
// Key used to timestamp last checkin (marked with G services settings update). // Key used to timestamp last checkin (marked with G services settings update).
const char kLastCheckinTimeKey[] = "last_checkin_time"; const char kLastCheckinTimeKey[] = "last_checkin_time";
// Lowest lexicographically ordered account key.
// Used for prefixing account information.
const char kAccountKeyStart[] = "account1-";
// Key guaranteed to be higher than all account keys.
// Used for limiting iteration.
const char kAccountKeyEnd[] = "account2-";
std::string MakeRegistrationKey(const std::string& app_id) { std::string MakeRegistrationKey(const std::string& app_id) {
return kRegistrationKeyStart + app_id; return kRegistrationKeyStart + app_id;
...@@ -97,6 +103,14 @@ std::string ParseGServiceSettingKey(const std::string& key) { ...@@ -97,6 +103,14 @@ std::string ParseGServiceSettingKey(const std::string& key) {
return key.substr(arraysize(kGServiceSettingKeyStart) - 1); return key.substr(arraysize(kGServiceSettingKeyStart) - 1);
} }
std::string MakeAccountKey(const std::string& account_id) {
return kAccountKeyStart + account_id;
}
std::string ParseAccountKey(const std::string& key) {
return key.substr(arraysize(kAccountKeyStart) - 1);
}
// Note: leveldb::Slice keeps a pointer to the data in |s|, which must therefore // Note: leveldb::Slice keeps a pointer to the data in |s|, which must therefore
// outlive the slice. // outlive the slice.
// For example: MakeSlice(MakeOutgoingKey(x)) is invalid. // For example: MakeSlice(MakeOutgoingKey(x)) is invalid.
...@@ -148,6 +162,10 @@ class GCMStoreImpl::Backend ...@@ -148,6 +162,10 @@ class GCMStoreImpl::Backend
const std::map<std::string, std::string>& settings, const std::map<std::string, std::string>& settings,
const std::string& digest, const std::string& digest,
const UpdateCallback& callback); const UpdateCallback& callback);
void AddAccountMapping(const AccountInfo& account_info,
const UpdateCallback& callback);
void RemoveAccountMapping(const std::string& account_id,
const UpdateCallback& callback);
private: private:
friend class base::RefCountedThreadSafe<Backend>; friend class base::RefCountedThreadSafe<Backend>;
...@@ -161,6 +179,7 @@ class GCMStoreImpl::Backend ...@@ -161,6 +179,7 @@ class GCMStoreImpl::Backend
std::set<std::string>* accounts); std::set<std::string>* accounts);
bool LoadGServicesSettings(std::map<std::string, std::string>* settings, bool LoadGServicesSettings(std::map<std::string, std::string>* settings,
std::string* digest); std::string* digest);
bool LoadAccountMappingInfo(AccountInfoMap* account_infos);
const base::FilePath path_; const base::FilePath path_;
scoped_refptr<base::SequencedTaskRunner> foreground_task_runner_; scoped_refptr<base::SequencedTaskRunner> foreground_task_runner_;
...@@ -214,15 +233,9 @@ void GCMStoreImpl::Backend::Load(const LoadCallback& callback) { ...@@ -214,15 +233,9 @@ void GCMStoreImpl::Backend::Load(const LoadCallback& callback) {
!LoadLastCheckinInfo(&result->last_checkin_time, !LoadLastCheckinInfo(&result->last_checkin_time,
&result->last_checkin_accounts) || &result->last_checkin_accounts) ||
!LoadGServicesSettings(&result->gservices_settings, !LoadGServicesSettings(&result->gservices_settings,
&result->gservices_digest)) { &result->gservices_digest) ||
result->device_android_id = 0; !LoadAccountMappingInfo(&result->account_infos)) {
result->device_security_token = 0; result->Reset();
result->registrations.clear();
result->incoming_messages.clear();
result->outgoing_messages.clear();
result->gservices_settings.clear();
result->gservices_digest.clear();
result->last_checkin_time = base::Time::FromInternalValue(0LL);
foreground_task_runner_->PostTask(FROM_HERE, foreground_task_runner_->PostTask(FROM_HERE,
base::Bind(callback, base::Bind(callback,
base::Passed(&result))); base::Passed(&result)));
...@@ -561,6 +574,48 @@ void GCMStoreImpl::Backend::SetGServicesSettings( ...@@ -561,6 +574,48 @@ void GCMStoreImpl::Backend::SetGServicesSettings(
foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok())); foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok()));
} }
void GCMStoreImpl::Backend::AddAccountMapping(const AccountInfo& account_info,
const UpdateCallback& callback) {
DVLOG(1) << "Saving account info for account with email: "
<< account_info.email;
if (!db_.get()) {
LOG(ERROR) << "GCMStore db doesn't exist.";
foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
return;
}
leveldb::WriteOptions write_options;
write_options.sync = true;
std::string data = account_info.SerializeAsString();
std::string key = MakeAccountKey(account_info.account_id);
const leveldb::Status s =
db_->Put(write_options, MakeSlice(key), MakeSlice(data));
if (!s.ok())
LOG(ERROR) << "LevelDB adding account mapping failed: " << s.ToString();
foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok()));
}
void GCMStoreImpl::Backend::RemoveAccountMapping(
const std::string& account_id,
const UpdateCallback& callback) {
if (!db_.get()) {
LOG(ERROR) << "GCMStore db doesn't exist.";
foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, false));
return;
}
leveldb::WriteOptions write_options;
write_options.sync = true;
leveldb::Status s =
db_->Delete(write_options, MakeSlice(MakeAccountKey(account_id)));
if (!s.ok())
LOG(ERROR) << "LevelDB removal of account mapping failed: " << s.ToString();
foreground_task_runner_->PostTask(FROM_HERE, base::Bind(callback, s.ok()));
}
bool GCMStoreImpl::Backend::LoadDeviceCredentials(uint64* android_id, bool GCMStoreImpl::Backend::LoadDeviceCredentials(uint64* android_id,
uint64* security_token) { uint64* security_token) {
leveldb::ReadOptions read_options; leveldb::ReadOptions read_options;
...@@ -734,6 +789,29 @@ bool GCMStoreImpl::Backend::LoadGServicesSettings( ...@@ -734,6 +789,29 @@ bool GCMStoreImpl::Backend::LoadGServicesSettings(
return true; return true;
} }
bool GCMStoreImpl::Backend::LoadAccountMappingInfo(
AccountInfoMap* account_infos) {
leveldb::ReadOptions read_options;
read_options.verify_checksums = true;
scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(read_options));
for (iter->Seek(MakeSlice(kAccountKeyStart));
iter->Valid() && iter->key().ToString() < kAccountKeyEnd;
iter->Next()) {
AccountInfo account_info;
account_info.account_id = ParseAccountKey(iter->key().ToString());
if (!account_info.ParseFromString(iter->value().ToString())) {
DVLOG(1) << "Failed to parse account info with ID: "
<< account_info.account_id;
return false;
}
DVLOG(1) << "Found account mapping with ID: " << account_info.account_id;
(*account_infos)[account_info.account_id] = account_info;
}
return true;
}
GCMStoreImpl::GCMStoreImpl( GCMStoreImpl::GCMStoreImpl(
const base::FilePath& path, const base::FilePath& path,
scoped_refptr<base::SequencedTaskRunner> blocking_task_runner, scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
...@@ -933,6 +1011,26 @@ void GCMStoreImpl::SetGServicesSettings( ...@@ -933,6 +1011,26 @@ void GCMStoreImpl::SetGServicesSettings(
callback)); callback));
} }
void GCMStoreImpl::AddAccountMapping(const AccountInfo& account_info,
const UpdateCallback& callback) {
blocking_task_runner_->PostTask(
FROM_HERE,
base::Bind(&GCMStoreImpl::Backend::AddAccountMapping,
backend_,
account_info,
callback));
}
void GCMStoreImpl::RemoveAccountMapping(const std::string& account_id,
const UpdateCallback& callback) {
blocking_task_runner_->PostTask(
FROM_HERE,
base::Bind(&GCMStoreImpl::Backend::RemoveAccountMapping,
backend_,
account_id,
callback));
}
void GCMStoreImpl::LoadContinuation(const LoadCallback& callback, void GCMStoreImpl::LoadContinuation(const LoadCallback& callback,
scoped_ptr<LoadResult> result) { scoped_ptr<LoadResult> result) {
if (!result->success) { if (!result->success) {
......
...@@ -76,7 +76,7 @@ class GCM_EXPORT GCMStoreImpl : public GCMStore { ...@@ -76,7 +76,7 @@ class GCM_EXPORT GCMStoreImpl : public GCMStore {
virtual void RemoveOutgoingMessages(const PersistentIdList& persistent_ids, virtual void RemoveOutgoingMessages(const PersistentIdList& persistent_ids,
const UpdateCallback& callback) OVERRIDE; const UpdateCallback& callback) OVERRIDE;
// Sets last device's checkin time. // Sets last device's checkin information.
virtual void SetLastCheckinInfo(const base::Time& time, virtual void SetLastCheckinInfo(const base::Time& time,
const std::set<std::string>& accounts, const std::set<std::string>& accounts,
const UpdateCallback& callback) OVERRIDE; const UpdateCallback& callback) OVERRIDE;
...@@ -87,6 +87,12 @@ class GCM_EXPORT GCMStoreImpl : public GCMStore { ...@@ -87,6 +87,12 @@ class GCM_EXPORT GCMStoreImpl : public GCMStore {
const std::string& settings_digest, const std::string& settings_digest,
const UpdateCallback& callback) OVERRIDE; const UpdateCallback& callback) OVERRIDE;
// Sets the account information related to device to account mapping.
virtual void AddAccountMapping(const AccountInfo& account_info,
const UpdateCallback& callback) OVERRIDE;
virtual void RemoveAccountMapping(const std::string& account_id,
const UpdateCallback& callback) OVERRIDE;
private: private:
typedef std::map<std::string, int> AppIdToMessageCountMap; typedef std::map<std::string, int> AppIdToMessageCountMap;
......
...@@ -45,8 +45,6 @@ class GCMStoreImplTest : public testing::Test { ...@@ -45,8 +45,6 @@ class GCMStoreImplTest : public testing::Test {
GCMStoreImplTest(); GCMStoreImplTest();
virtual ~GCMStoreImplTest(); virtual ~GCMStoreImplTest();
virtual void SetUp() OVERRIDE;
scoped_ptr<GCMStore> BuildGCMStore(); scoped_ptr<GCMStore> BuildGCMStore();
std::string GetNextPersistentId(); std::string GetNextPersistentId();
...@@ -74,10 +72,6 @@ GCMStoreImplTest::GCMStoreImplTest() ...@@ -74,10 +72,6 @@ GCMStoreImplTest::GCMStoreImplTest()
GCMStoreImplTest::~GCMStoreImplTest() {} GCMStoreImplTest::~GCMStoreImplTest() {}
void GCMStoreImplTest::SetUp() {
testing::Test::SetUp();
}
scoped_ptr<GCMStore> GCMStoreImplTest::BuildGCMStore() { scoped_ptr<GCMStore> GCMStoreImplTest::BuildGCMStore() {
return scoped_ptr<GCMStore>(new GCMStoreImpl( return scoped_ptr<GCMStore>(new GCMStoreImpl(
temp_directory_.path(), temp_directory_.path(),
...@@ -506,6 +500,81 @@ TEST_F(GCMStoreImplTest, PerAppMessageLimits) { ...@@ -506,6 +500,81 @@ TEST_F(GCMStoreImplTest, PerAppMessageLimits) {
} }
} }
TEST_F(GCMStoreImplTest, AccountMapping) {
scoped_ptr<GCMStore> gcm_store(BuildGCMStore());
scoped_ptr<GCMStore::LoadResult> load_result;
gcm_store->Load(base::Bind(
&GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
// Add account mappings.
AccountInfo account_info1;
account_info1.account_id = "account_id_1";
account_info1.email = "account_id_1@gmail.com";
account_info1.last_message_type = AccountInfo::MSG_ADD;
account_info1.last_message_id = "message_1";
account_info1.last_message_timestamp =
base::Time::FromInternalValue(1305797421259935LL);
AccountInfo account_info2;
account_info2.account_id = "account_id_2";
account_info2.email = "account_id_2@gmail.com";
account_info2.last_message_type = AccountInfo::MSG_REMOVE;
account_info2.last_message_id = "message_2";
account_info2.last_message_timestamp =
base::Time::FromInternalValue(1305734521259935LL);
gcm_store->AddAccountMapping(
account_info1,
base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
PumpLoop();
gcm_store->AddAccountMapping(
account_info2,
base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
PumpLoop();
gcm_store = BuildGCMStore().Pass();
gcm_store->Load(base::Bind(
&GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
PumpLoop();
EXPECT_EQ(2UL, load_result->account_infos.size());
GCMStore::AccountInfoMap::iterator iter = load_result->account_infos.begin();
EXPECT_EQ("account_id_1", iter->first);
EXPECT_EQ(account_info1.account_id, iter->second.account_id);
EXPECT_EQ(account_info1.email, iter->second.email);
EXPECT_EQ(account_info1.last_message_type, iter->second.last_message_type);
EXPECT_EQ(account_info1.last_message_id, iter->second.last_message_id);
EXPECT_EQ(account_info1.last_message_timestamp,
iter->second.last_message_timestamp);
++iter;
EXPECT_EQ("account_id_2", iter->first);
EXPECT_EQ(account_info2.account_id, iter->second.account_id);
EXPECT_EQ(account_info2.email, iter->second.email);
EXPECT_EQ(account_info2.last_message_type, iter->second.last_message_type);
EXPECT_EQ(account_info2.last_message_id, iter->second.last_message_id);
EXPECT_EQ(account_info2.last_message_timestamp,
iter->second.last_message_timestamp);
gcm_store->RemoveAccountMapping(
account_info1.account_id,
base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this)));
PumpLoop();
gcm_store = BuildGCMStore().Pass();
gcm_store->Load(base::Bind(
&GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result));
PumpLoop();
EXPECT_EQ(1UL, load_result->account_infos.size());
iter = load_result->account_infos.begin();
EXPECT_EQ("account_id_2", iter->first);
EXPECT_EQ(account_info2.account_id, iter->second.account_id);
EXPECT_EQ(account_info2.email, iter->second.email);
EXPECT_EQ(account_info2.last_message_type, iter->second.last_message_type);
EXPECT_EQ(account_info2.last_message_id, iter->second.last_message_id);
EXPECT_EQ(account_info2.last_message_timestamp,
iter->second.last_message_timestamp);
}
// When the database is destroyed, all database updates should fail. At the // When the database is destroyed, all database updates should fail. At the
// same time, they per-app message counts should not go up, as failures should // same time, they per-app message counts should not go up, as failures should
// result in decrementing the counts. // result in decrementing the counts.
......
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