Chromium Code Reviews| 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..a113b3e30d5babb15a7d865fa108af015f38fc95 |
| --- /dev/null |
| +++ b/components/gcm_driver/gcm_account_mapper.cc |
| @@ -0,0 +1,339 @@ |
| +// 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 kGCMAccountMapperMessageTTL = 60 * 60; // 1 day in seconds. |
| +const int kGCMUpdateIntervalHours = 24; |
| +const int kGCMUpdateEarlyStartHours = 6; |
|
jianli
2014/08/28 18:43:51
Please comment on this.
fgorski
2014/08/29 02:29:17
Done.
|
| +const char kRegistrationIdMessgaeKey[] = "id"; |
| +const char kTokenMessageKey[] = "t"; |
| +const char kAccountMessageKey[] = "a"; |
| +const char kRemoveAccountMessageKey[] = "r"; |
| +const char kRemoveAccountMessageValue[] = "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(); |
| + // Sending a message triggers a status change, this is a check if a message |
| + // expired and a relevant event was missed. |
|
jianli
2014/08/28 18:43:51
What do you mean by "relevant event was missed"?
fgorski
2014/08/29 02:29:17
Done.
|
| + if ((iter->status == AccountMapping::ADDING || |
| + iter->status == AccountMapping::REMOVING) && |
| + IsLastStatusChangeOlderThanTTL(iter->status_change_timestamp)) { |
| + if (iter->status == AccountMapping::ADDING) |
| + iter->status = AccountMapping::NEW; |
| + iter->last_message_type = AccountMapping::MSG_NONE; |
| + iter->last_message_id.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; |
| + new_mapping.last_message_type = AccountMapping::MSG_NONE; |
| + 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->last_message_type == AccountMapping::MSG_REMOVE) { |
| + account_mapping->status = AccountMapping::MAPPED; |
| + account_mapping->status_change_timestamp = base::Time(); |
| + account_mapping->last_message_type = AccountMapping::MSG_NONE; |
| + 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()) { |
| + if (mappings_iter->last_message_id.empty() || |
|
jianli
2014/08/28 18:43:50
Please add comment saying something like:
// No
fgorski
2014/08/29 02:29:17
Done.
|
| + mappings_iter->last_message_type != AccountMapping::MSG_REMOVE) { |
| + SendRemoveMappingMessage(*mappings_iter); |
| + } |
| + } else { |
| + // A message is sent for all of the mappings considered NEW, or for those |
| + // mapped accounts that are due for update. |
| + if (mappings_iter->status == AccountMapping::NEW || |
| + (mappings_iter->status == AccountMapping::MAPPED && |
| + IsUpdateDue(mappings_iter->status_change_timestamp))) { |
| + 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; |
| + |
| + // TODO(fgorski): What other status can we get here if any? What should we do |
| + // about that. (likely TTL_EXCEEDED is the only one) |
| + DCHECK_EQ(send_error_details.result, GCMClient::TTL_EXCEEDED); |
| + |
| + if (account_mapping_it->last_message_type == AccountMapping::MSG_REMOVE) { |
| + SendRemoveMappingMessage(*account_mapping_it); |
|
jianli
2014/08/28 18:43:51
Do you mean keeping resending remove message when
fgorski
2014/08/29 02:29:17
Done.
|
| + } else { |
| + DCHECK_EQ(account_mapping_it->last_message_type, AccountMapping::MSG_ADD); |
| + 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(); |
| + account_mapping_it->last_message_type = AccountMapping::MSG_NONE; |
| + 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->last_message_type == AccountMapping::MSG_REMOVE) { |
| + DCHECK_EQ(account_mapping_it->status, AccountMapping::REMOVING); |
|
jianli
2014/08/28 18:43:51
It seems to me that the mapping status could be fo
fgorski
2014/08/29 02:29:17
REMOVED is removed. I am seeing if I can remove th
|
| + // 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 { |
| + DCHECK_EQ(account_mapping_it->last_message_type, AccountMapping::MSG_ADD); |
| + // 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_type = AccountMapping::MSG_NONE; |
| + account_mapping_it->last_message_id.clear(); |
| + |
| + gcm_driver_->UpdateAccountMapping(*account_mapping_it); |
| + } |
| +} |
| + |
| +void GCMAccountMapper::OnConnected(const net::IPEndPoint& ip_endpoint) { |
| + // Account mapper ignores connection status. |
| +} |
| + |
| +void GCMAccountMapper::OnDisconnected() { |
| + // Account mapper ignores connection status. |
| +} |
| + |
| +bool GCMAccountMapper::CanHandle(const std::string& app_id) const { |
| + return app_id.compare(kGCMAccountMapperAppId) == 0; |
| +} |
| + |
| +void GCMAccountMapper::SendAddMappingMessage(AccountMapping& account_mapping) { |
| + GCMClient::OutgoingMessage outgoing_message; |
| + outgoing_message.id = GenerateMessageID(); |
| + outgoing_message.time_to_live = kGCMAccountMapperMessageTTL; |
| + outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_; |
| + outgoing_message.data[kTokenMessageKey] = account_mapping.access_token; |
| + outgoing_message.data[kAccountMessageKey] = account_mapping.email; |
| + |
| + gcm_driver_->Send(kGCMAccountMapperAppId, |
| + kGCMAccountMapperSenderId, |
| + outgoing_message, |
| + base::Bind(&GCMAccountMapper::OnSendFinished, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + account_mapping.account_id, |
| + AccountMapping::MSG_ADD)); |
| +} |
| + |
| +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(); |
| + account_mapping.last_message_type = AccountMapping::MSG_NONE; |
| + |
| + gcm_driver_->UpdateAccountMapping(account_mapping); |
| + |
| + GCMClient::OutgoingMessage outgoing_message; |
| + outgoing_message.id = GenerateMessageID(); |
| + outgoing_message.time_to_live = kGCMAccountMapperMessageTTL; |
| + outgoing_message.data[kRegistrationIdMessgaeKey] = registration_id_; |
| + outgoing_message.data[kAccountMessageKey] = account_mapping.email; |
| + outgoing_message.data[kRemoveAccountMessageKey] = kRemoveAccountMessageValue; |
| + |
| + gcm_driver_->Send(kGCMAccountMapperAppId, |
| + kGCMAccountMapperSenderId, |
| + outgoing_message, |
| + base::Bind(&GCMAccountMapper::OnSendFinished, |
| + weak_ptr_factory_.GetWeakPtr(), |
| + account_mapping.account_id, |
| + AccountMapping::MSG_REMOVE)); |
| +} |
| + |
| +void GCMAccountMapper::OnSendFinished(const std::string& account_id, |
| + AccountMapping::MessageType message_type, |
| + const std::string& message_id, |
| + GCMClient::Result result) { |
| + // TODO(fgorski): Add another attempt, in case the QUEUE is not full. |
|
jianli
2014/08/28 18:43:51
remove "not"?
fgorski
2014/08/29 02:29:17
I actually mean it. If the queue is full, no need
|
| + if (result != GCMClient::SUCCESS) |
| + return; |
| + |
| + AccountMapping* account_mapping_it = FindMappingByAccountId(account_id); |
|
jianli
2014/08/28 18:43:51
Remove "_it" since it is not an iterator.
fgorski
2014/08/29 02:29:17
Done.
|
| + DCHECK(account_mapping_it); |
| + |
| + // 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_it->status == AccountMapping::NEW) { |
| + DCHECK(message_type == AccountMapping::MSG_ADD); |
| + account_mapping_it->status = AccountMapping::ADDING; |
| + account_mapping_it->status_change_timestamp = clock_->Now(); |
| + } |
| + |
| + account_mapping_it->last_message_type = message_type; |
| + account_mapping_it->last_message_id = message_id; |
| + |
| + gcm_driver_->UpdateAccountMapping(*account_mapping_it); |
| +} |
| + |
| +bool GCMAccountMapper::IsUpdateDue(const base::Time& last_update_time) const { |
| + return last_update_time + |
| + base::TimeDelta::FromHours(kGCMUpdateIntervalHours - |
| + kGCMUpdateEarlyStartHours) < |
| + clock_->Now(); |
| +} |
| + |
| +bool GCMAccountMapper::IsLastStatusChangeOlderThanTTL( |
| + const base::Time& estimated_send_time) const { |
| + return estimated_send_time + |
| + base::TimeDelta::FromSeconds(kGCMAccountMapperMessageTTL) < |
| + 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 |