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") {
"base/mcs_util.h",
"base/socket_stream.cc",
"base/socket_stream.h",
"engine/account_info.cc",
"engine/account_info.h",
"engine/checkin_request.cc",
"engine/checkin_request.h",
"engine/connection_factory.cc",
......@@ -105,6 +107,7 @@ test("gcm_unit_tests") {
"base/mcs_message_unittest.cc",
"base/mcs_util_unittest.cc",
"base/socket_stream_unittest.cc",
"engine/account_info_unittest.cc",
"engine/checkin_request_unittest.cc",
"engine/connection_factory_impl_unittest.cc",
"engine/connection_handler_impl_unittest.cc",
......
......@@ -14,6 +14,20 @@ 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() {}
......
......@@ -19,6 +19,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/time/time.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"
namespace gcm {
......@@ -33,11 +34,16 @@ class GCM_EXPORT GCMStore {
typedef std::map<std::string, linked_ptr<google::protobuf::MessageLite> >
OutgoingMessageMap;
// Map of account id to account info for account mappings.
typedef std::map<std::string, AccountInfo> AccountInfoMap;
// Container for Load(..) results.
struct GCM_EXPORT LoadResult {
LoadResult();
~LoadResult();
void Reset();
bool success;
uint64 device_android_id;
uint64 device_security_token;
......@@ -48,6 +54,7 @@ class GCM_EXPORT GCMStore {
std::string gservices_digest;
base::Time last_checkin_time;
std::set<std::string> last_checkin_accounts;
AccountInfoMap account_infos;
};
typedef std::vector<std::string> PersistentIdList;
......@@ -102,7 +109,7 @@ class GCM_EXPORT GCMStore {
virtual void RemoveOutgoingMessages(const PersistentIdList& persistent_ids,
const UpdateCallback& callback) = 0;
// Sets last device's checkin time.
// Sets last device's checkin information.
virtual void SetLastCheckinInfo(const base::Time& time,
const std::set<std::string>& accounts,
const UpdateCallback& callback) = 0;
......@@ -115,6 +122,12 @@ class GCM_EXPORT GCMStore {
const std::string& settings_digest,
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:
DISALLOW_COPY_AND_ASSIGN(GCMStore);
};
......
......@@ -68,6 +68,12 @@ const char kGServiceSettingsDigestKey[] = "gservices_digest";
const char kLastCheckinAccountsKey[] = "last_checkin_accounts_count";
// Key used to timestamp last checkin (marked with G services settings update).
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) {
return kRegistrationKeyStart + app_id;
......@@ -97,6 +103,14 @@ std::string ParseGServiceSettingKey(const std::string& key) {
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
// outlive the slice.
// For example: MakeSlice(MakeOutgoingKey(x)) is invalid.
......@@ -148,6 +162,10 @@ class GCMStoreImpl::Backend
const std::map<std::string, std::string>& settings,
const std::string& digest,
const UpdateCallback& callback);
void AddAccountMapping(const AccountInfo& account_info,
const UpdateCallback& callback);
void RemoveAccountMapping(const std::string& account_id,
const UpdateCallback& callback);
private:
friend class base::RefCountedThreadSafe<Backend>;
......@@ -161,6 +179,7 @@ class GCMStoreImpl::Backend
std::set<std::string>* accounts);
bool LoadGServicesSettings(std::map<std::string, std::string>* settings,
std::string* digest);
bool LoadAccountMappingInfo(AccountInfoMap* account_infos);
const base::FilePath path_;
scoped_refptr<base::SequencedTaskRunner> foreground_task_runner_;
......@@ -214,15 +233,9 @@ void GCMStoreImpl::Backend::Load(const LoadCallback& callback) {
!LoadLastCheckinInfo(&result->last_checkin_time,
&result->last_checkin_accounts) ||
!LoadGServicesSettings(&result->gservices_settings,
&result->gservices_digest)) {
result->device_android_id = 0;
result->device_security_token = 0;
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);
&result->gservices_digest) ||
!LoadAccountMappingInfo(&result->account_infos)) {
result->Reset();
foreground_task_runner_->PostTask(FROM_HERE,
base::Bind(callback,
base::Passed(&result)));
......@@ -561,6 +574,48 @@ void GCMStoreImpl::Backend::SetGServicesSettings(
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,
uint64* security_token) {
leveldb::ReadOptions read_options;
......@@ -734,6 +789,29 @@ bool GCMStoreImpl::Backend::LoadGServicesSettings(
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(
const base::FilePath& path,
scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
......@@ -933,6 +1011,26 @@ void GCMStoreImpl::SetGServicesSettings(
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,
scoped_ptr<LoadResult> result) {
if (!result->success) {
......
......@@ -76,7 +76,7 @@ class GCM_EXPORT GCMStoreImpl : public GCMStore {
virtual void RemoveOutgoingMessages(const PersistentIdList& persistent_ids,
const UpdateCallback& callback) OVERRIDE;
// Sets last device's checkin time.
// Sets last device's checkin information.
virtual void SetLastCheckinInfo(const base::Time& time,
const std::set<std::string>& accounts,
const UpdateCallback& callback) OVERRIDE;
......@@ -87,6 +87,12 @@ class GCM_EXPORT GCMStoreImpl : public GCMStore {
const std::string& settings_digest,
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:
typedef std::map<std::string, int> AppIdToMessageCountMap;
......
......@@ -45,8 +45,6 @@ class GCMStoreImplTest : public testing::Test {
GCMStoreImplTest();
virtual ~GCMStoreImplTest();
virtual void SetUp() OVERRIDE;
scoped_ptr<GCMStore> BuildGCMStore();
std::string GetNextPersistentId();
......@@ -74,10 +72,6 @@ GCMStoreImplTest::GCMStoreImplTest()
GCMStoreImplTest::~GCMStoreImplTest() {}
void GCMStoreImplTest::SetUp() {
testing::Test::SetUp();
}
scoped_ptr<GCMStore> GCMStoreImplTest::BuildGCMStore() {
return scoped_ptr<GCMStore>(new GCMStoreImpl(
temp_directory_.path(),
......@@ -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
// same time, they per-app message counts should not go up, as failures should
// 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