| Index: components/gcm_driver/gcm_account_mapper.cc
|
| diff --git a/components/gcm_driver/gcm_account_mapper.cc b/components/gcm_driver/gcm_account_mapper.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..0e54fd502dd1c9882f212bbadde7ae00046555a7
|
| --- /dev/null
|
| +++ b/components/gcm_driver/gcm_account_mapper.cc
|
| @@ -0,0 +1,327 @@
|
| +// 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 "components/gcm_driver/gcm_account_mapper.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/guid.h"
|
| +#include "base/time/clock.h"
|
| +#include "base/time/default_clock.h"
|
| +#include "components/gcm_driver/gcm_driver_desktop.h"
|
| +#include "google_apis/gcm/engine/gcm_store.h"
|
| +
|
| +namespace gcm {
|
| +
|
| +namespace {
|
| +
|
| +const char kGCMAccountMapperSenderId[] = "745476177629";
|
| +const char kGCMAccountMapperAppId[] = "com.google.android.gms";
|
| +const int kGCMAddMappingMessageTTL = 30 * 60; // 0.5 hours in seconds.
|
| +const int kGCMRemoveMappingMessageTTL = 24 * 60 * 60; // 1 day in seconds.
|
| +const int kGCMUpdateIntervalHours = 24;
|
| +// Because adding an account mapping dependents on a fresh OAuth2 token, we
|
| +// allow the update to happen earlier than update due time, if it is within
|
| +// the early start time to take advantage of that token.
|
| +const int kGCMUpdateEarlyStartHours = 6;
|
| +const char kRegistrationIdMessgaeKey[] = "id";
|
| +const char kTokenMessageKey[] = "t";
|
| +const char kAccountMessageKey[] = "a";
|
| +const char kRemoveAccountKey[] = "r";
|
| +const char kRemoveAccountValue[] = "1";
|
| +
|
| +std::string GenerateMessageID() {
|
| + return base::GenerateGUID();
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +GCMAccountMapper::GCMAccountMapper(GCMDriver* gcm_driver)
|
| + : gcm_driver_(gcm_driver),
|
| + clock_(new base::DefaultClock),
|
| + initialized_(false),
|
| + weak_ptr_factory_(this) {
|
| +}
|
| +
|
| +GCMAccountMapper::~GCMAccountMapper() {
|
| +}
|
| +
|
| +void GCMAccountMapper::Initialize(
|
| + const std::vector<AccountMapping>& account_mappings,
|
| + const std::string& registration_id) {
|
| + DCHECK(!initialized_);
|
| + initialized_ = true;
|
| + registration_id_ = registration_id;
|
| +
|
| + accounts_ = account_mappings;
|
| +
|
| + gcm_driver_->AddAppHandler(kGCMAccountMapperAppId, this);
|
| +
|
| + // TODO(fgorski): if no registration ID, get registration ID.
|
| +}
|
| +
|
| +void GCMAccountMapper::SetAccountTokens(
|
| + const std::vector<GCMClient::AccountTokenInfo> account_tokens) {
|
| + DCHECK(initialized_);
|
| +
|
| + // Start from removing the old tokens, from all of the known accounts.
|
| + for (AccountMappings::iterator iter = accounts_.begin();
|
| + iter != accounts_.end();
|
| + ++iter) {
|
| + iter->access_token.clear();
|
| + }
|
| +
|
| + // Update the internal collection of mappings with the new tokens.
|
| + for (std::vector<GCMClient::AccountTokenInfo>::const_iterator token_iter =
|
| + account_tokens.begin();
|
| + token_iter != account_tokens.end();
|
| + ++token_iter) {
|
| + AccountMapping* account_mapping =
|
| + FindMappingByAccountId(token_iter->account_id);
|
| + if (!account_mapping) {
|
| + AccountMapping new_mapping;
|
| + new_mapping.status = AccountMapping::NEW;
|
| + new_mapping.account_id = token_iter->account_id;
|
| + new_mapping.access_token = token_iter->access_token;
|
| + new_mapping.email = token_iter->email;
|
| + accounts_.push_back(new_mapping);
|
| + } else {
|
| + // Since we got a token for an account, drop the remove message and treat
|
| + // it as mapped.
|
| + if (account_mapping->status == AccountMapping::REMOVING) {
|
| + account_mapping->status = AccountMapping::MAPPED;
|
| + account_mapping->status_change_timestamp = base::Time();
|
| + account_mapping->last_message_id.clear();
|
| + }
|
| +
|
| + account_mapping->email = token_iter->email;
|
| + account_mapping->access_token = token_iter->access_token;
|
| + }
|
| + }
|
| +
|
| + // Decide what to do with each account (either start mapping, or start
|
| + // removing).
|
| + for (AccountMappings::iterator mappings_iter = accounts_.begin();
|
| + mappings_iter != accounts_.end();
|
| + ++mappings_iter) {
|
| + if (mappings_iter->access_token.empty()) {
|
| + // Send a remove message if the account was not previously being removed,
|
| + // or it doesn't have a pending message, or the pending message is
|
| + // already expired, but OnSendError event was lost.
|
| + if (mappings_iter->status != AccountMapping::REMOVING ||
|
| + mappings_iter->last_message_id.empty() ||
|
| + IsLastStatusChangeOlderThanTTL(*mappings_iter)) {
|
| + SendRemoveMappingMessage(*mappings_iter);
|
| + }
|
| + } else {
|
| + // A message is sent for all of the mappings considered NEW, or mappings
|
| + // that are ADDING, but have expired message (OnSendError event lost), or
|
| + // for those mapped accounts that can be refreshed.
|
| + if (mappings_iter->status == AccountMapping::NEW ||
|
| + (mappings_iter->status == AccountMapping::ADDING &&
|
| + IsLastStatusChangeOlderThanTTL(*mappings_iter)) ||
|
| + (mappings_iter->status == AccountMapping::MAPPED &&
|
| + CanTriggerUpdate(mappings_iter->status_change_timestamp))) {
|
| + mappings_iter->last_message_id.clear();
|
| + SendAddMappingMessage(*mappings_iter);
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +void GCMAccountMapper::ShutdownHandler() {
|
| + gcm_driver_->RemoveAppHandler(kGCMAccountMapperAppId);
|
| +}
|
| +
|
| +void GCMAccountMapper::OnMessage(const std::string& app_id,
|
| + const GCMClient::IncomingMessage& message) {
|
| + // Account message does not expect messages right now.
|
| +}
|
| +
|
| +void GCMAccountMapper::OnMessagesDeleted(const std::string& app_id) {
|
| + // Account message does not expect messages right now.
|
| +}
|
| +
|
| +void GCMAccountMapper::OnSendError(
|
| + const std::string& app_id,
|
| + const GCMClient::SendErrorDetails& send_error_details) {
|
| + DCHECK_EQ(app_id, kGCMAccountMapperAppId);
|
| +
|
| + AccountMappings::iterator account_mapping_it =
|
| + FindMappingByMessageId(send_error_details.message_id);
|
| +
|
| + if (account_mapping_it == accounts_.end())
|
| + return;
|
| +
|
| + if (send_error_details.result != GCMClient::TTL_EXCEEDED) {
|
| + DVLOG(1) << "Send error result different than TTL EXCEEDED: "
|
| + << send_error_details.result << ". "
|
| + << "Postponing the retry until a new batch of tokens arrives.";
|
| + return;
|
| + }
|
| +
|
| + if (account_mapping_it->status == AccountMapping::REMOVING) {
|
| + // Another message to remove mapping can be sent immediately, because TTL
|
| + // for those is one day. No need to back off.
|
| + SendRemoveMappingMessage(*account_mapping_it);
|
| + } else {
|
| + if (account_mapping_it->status == AccountMapping::ADDING) {
|
| + // There is no mapping established, so we can remove the entry.
|
| + // Getting a fresh token will trigger a new attempt.
|
| + gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
|
| + accounts_.erase(account_mapping_it);
|
| + } else {
|
| + // Account is already MAPPED, we have to wait for another token.
|
| + account_mapping_it->last_message_id.clear();
|
| + gcm_driver_->UpdateAccountMapping(*account_mapping_it);
|
| + }
|
| + }
|
| +}
|
| +
|
| +void GCMAccountMapper::OnSendAcknowledged(const std::string& app_id,
|
| + const std::string& message_id) {
|
| + DCHECK_EQ(app_id, kGCMAccountMapperAppId);
|
| + AccountMappings::iterator account_mapping_it =
|
| + FindMappingByMessageId(message_id);
|
| +
|
| + DVLOG(1) << "OnSendAcknowledged with message ID: " << message_id;
|
| +
|
| + if (account_mapping_it == accounts_.end())
|
| + return;
|
| +
|
| + // Here is where we advance a status of a mapping and persist or remove.
|
| + if (account_mapping_it->status == AccountMapping::REMOVING) {
|
| + // Message removing the account has been confirmed by the GCM, we can remove
|
| + // all the information related to the account (from memory and store).
|
| + gcm_driver_->RemoveAccountMapping(account_mapping_it->account_id);
|
| + accounts_.erase(account_mapping_it);
|
| + } else {
|
| + // Mapping status is ADDING only when it is a first time mapping.
|
| + DCHECK(account_mapping_it->status == AccountMapping::ADDING ||
|
| + account_mapping_it->status == AccountMapping::MAPPED);
|
| +
|
| + // Account is marked as mapped with the current time.
|
| + account_mapping_it->status = AccountMapping::MAPPED;
|
| + account_mapping_it->status_change_timestamp = clock_->Now();
|
| + // There is no pending message for the account.
|
| + account_mapping_it->last_message_id.clear();
|
| +
|
| + gcm_driver_->UpdateAccountMapping(*account_mapping_it);
|
| + }
|
| +}
|
| +
|
| +bool GCMAccountMapper::CanHandle(const std::string& app_id) const {
|
| + return app_id.compare(kGCMAccountMapperAppId) == 0;
|
| +}
|
| +
|
| +void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) {
|
| + CreateAndSendMessage(account_mapping);
|
| +}
|
| +
|
| +void GCMAccountMapper::SendRemoveMappingMessage(
|
| + AccountMapping& account_mapping) {
|
| + // We want to persist an account that is being removed as quickly as possible
|
| + // as well as clean up the last message information.
|
| + if (account_mapping.status != AccountMapping::REMOVING) {
|
| + account_mapping.status = AccountMapping::REMOVING;
|
| + account_mapping.status_change_timestamp = clock_->Now();
|
| + }
|
| +
|
| + account_mapping.last_message_id.clear();
|
| +
|
| + gcm_driver_->UpdateAccountMapping(account_mapping);
|
| +
|
| + CreateAndSendMessage(account_mapping);
|
| +}
|
| +
|
| +void GCMAccountMapper::CreateAndSendMessage(
|
| + const AccountMapping& account_mapping) {
|
| + GCMClient::OutgoingMessage outgoing_message;
|
| + outgoing_message.id = GenerateMessageID();
|
| + outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_;
|
| + outgoing_message.data[kAccountMessageKey] = account_mapping.email;
|
| +
|
| + if (account_mapping.status == AccountMapping::REMOVING) {
|
| + outgoing_message.time_to_live = kGCMRemoveMappingMessageTTL;
|
| + outgoing_message.data[kRemoveAccountKey] = kRemoveAccountValue;
|
| + } else {
|
| + outgoing_message.data[kTokenMessageKey] = account_mapping.access_token;
|
| + outgoing_message.time_to_live = kGCMAddMappingMessageTTL;
|
| + }
|
| +
|
| + gcm_driver_->Send(kGCMAccountMapperAppId,
|
| + kGCMAccountMapperSenderId,
|
| + outgoing_message,
|
| + base::Bind(&GCMAccountMapper::OnSendFinished,
|
| + weak_ptr_factory_.GetWeakPtr(),
|
| + account_mapping.account_id));
|
| +}
|
| +
|
| +
|
| +void GCMAccountMapper::OnSendFinished(const std::string& account_id,
|
| + const std::string& message_id,
|
| + GCMClient::Result result) {
|
| + // TODO(fgorski): Add another attempt, in case the QUEUE is not full.
|
| + if (result != GCMClient::SUCCESS)
|
| + return;
|
| +
|
| + AccountMapping* account_mapping = FindMappingByAccountId(account_id);
|
| + DCHECK(account_mapping);
|
| +
|
| + // If we are dealing with account with status NEW, it is the first time
|
| + // mapping, and we should mark it as ADDING.
|
| + if (account_mapping->status == AccountMapping::NEW) {
|
| + account_mapping->status = AccountMapping::ADDING;
|
| + account_mapping->status_change_timestamp = clock_->Now();
|
| + }
|
| +
|
| + account_mapping->last_message_id = message_id;
|
| +
|
| + gcm_driver_->UpdateAccountMapping(*account_mapping);
|
| +}
|
| +
|
| +bool GCMAccountMapper::CanTriggerUpdate(
|
| + const base::Time& last_update_time) const {
|
| + return last_update_time +
|
| + base::TimeDelta::FromHours(kGCMUpdateIntervalHours -
|
| + kGCMUpdateEarlyStartHours) <
|
| + clock_->Now();
|
| +}
|
| +
|
| +bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL(
|
| + const AccountMapping& account_mapping) const {
|
| + int ttl_seconds = account_mapping.status == AccountMapping::REMOVING ?
|
| + kGCMRemoveMappingMessageTTL : kGCMAddMappingMessageTTL;
|
| + return account_mapping.status_change_timestamp +
|
| + base::TimeDelta::FromSeconds(ttl_seconds) < clock_->Now();
|
| +}
|
| +
|
| +AccountMapping* GCMAccountMapper::FindMappingByAccountId(
|
| + const std::string& account_id) {
|
| + for (AccountMappings::iterator iter = accounts_.begin();
|
| + iter != accounts_.end();
|
| + ++iter) {
|
| + if (iter->account_id == account_id)
|
| + return &*iter;
|
| + }
|
| +
|
| + return NULL;
|
| +}
|
| +
|
| +GCMAccountMapper::AccountMappings::iterator
|
| +GCMAccountMapper::FindMappingByMessageId(const std::string& message_id) {
|
| + for (std::vector<AccountMapping>::iterator iter = accounts_.begin();
|
| + iter != accounts_.end();
|
| + ++iter) {
|
| + if (iter->last_message_id == message_id)
|
| + return iter;
|
| + }
|
| +
|
| + return accounts_.end();
|
| +}
|
| +
|
| +void GCMAccountMapper::SetClockForTesting(scoped_ptr<base::Clock> clock) {
|
| + clock_ = clock.Pass();
|
| +}
|
| +
|
| +} // namespace gcm
|
|
|