blob: 8b9cd4fda6765a1429de57e1e0b30720ac7b4a59 [file] [log] [blame]
// Copyright (c) 2012 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 "sync/notifier/chrome_invalidation_client.h"
#include <string>
#include <vector>
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/tracked_objects.h"
#include "google/cacheinvalidation/include/invalidation-client-factory.h"
#include "google/cacheinvalidation/include/invalidation-client.h"
#include "google/cacheinvalidation/include/types.h"
#include "google/cacheinvalidation/types.pb.h"
#include "jingle/notifier/listener/push_client.h"
#include "sync/notifier/invalidation_util.h"
#include "sync/notifier/registration_manager.h"
namespace {
const char kApplicationName[] = "chrome-sync";
} // namespace
namespace syncer {
ChromeInvalidationClient::Listener::~Listener() {}
ChromeInvalidationClient::ChromeInvalidationClient(
scoped_ptr<notifier::PushClient> push_client)
: push_client_(push_client.get()),
chrome_system_resources_(push_client.Pass(),
ALLOW_THIS_IN_INITIALIZER_LIST(this)),
listener_(NULL),
ticl_state_(DEFAULT_NOTIFICATION_ERROR),
push_client_state_(DEFAULT_NOTIFICATION_ERROR) {
DCHECK(CalledOnValidThread());
push_client_->AddObserver(this);
}
ChromeInvalidationClient::~ChromeInvalidationClient() {
DCHECK(CalledOnValidThread());
push_client_->RemoveObserver(this);
Stop();
DCHECK(!listener_);
}
void ChromeInvalidationClient::Start(
const std::string& client_id, const std::string& client_info,
const std::string& state,
const InvalidationVersionMap& initial_max_invalidation_versions,
const WeakHandle<InvalidationStateTracker>& invalidation_state_tracker,
Listener* listener) {
DCHECK(CalledOnValidThread());
Stop();
chrome_system_resources_.set_platform(client_info);
chrome_system_resources_.Start();
// The Storage resource is implemented as a write-through cache. We populate
// it with the initial state on startup, so subsequent writes go to disk and
// update the in-memory cache, while reads just return the cached state.
chrome_system_resources_.storage()->SetInitialState(state);
max_invalidation_versions_ = initial_max_invalidation_versions;
if (max_invalidation_versions_.empty()) {
DVLOG(2) << "No initial max invalidation versions for any id";
} else {
for (InvalidationVersionMap::const_iterator it =
max_invalidation_versions_.begin();
it != max_invalidation_versions_.end(); ++it) {
DVLOG(2) << "Initial max invalidation version for "
<< ObjectIdToString(it->first) << " is "
<< it->second;
}
}
invalidation_state_tracker_ = invalidation_state_tracker;
DCHECK(invalidation_state_tracker_.IsInitialized());
DCHECK(!listener_);
DCHECK(listener);
listener_ = listener;
int client_type = ipc::invalidation::ClientType::CHROME_SYNC;
invalidation_client_.reset(
invalidation::CreateInvalidationClient(
&chrome_system_resources_, client_type, client_id,
kApplicationName, this));
invalidation_client_->Start();
registration_manager_.reset(
new RegistrationManager(invalidation_client_.get()));
}
void ChromeInvalidationClient::UpdateCredentials(
const std::string& email, const std::string& token) {
DCHECK(CalledOnValidThread());
chrome_system_resources_.network()->UpdateCredentials(email, token);
}
void ChromeInvalidationClient::RegisterIds(const ObjectIdSet& ids) {
DCHECK(CalledOnValidThread());
registered_ids_ = ids;
// |ticl_state_| can go to NO_NOTIFICATION_ERROR even without a
// working XMPP connection (as observed by us), so check it instead
// of GetState() (see http://crbug.com/139424).
if (ticl_state_ == NO_NOTIFICATION_ERROR && registration_manager_.get()) {
registration_manager_->SetRegisteredIds(registered_ids_);
}
// TODO(akalin): Clear invalidation versions for unregistered types.
}
void ChromeInvalidationClient::Ready(
invalidation::InvalidationClient* client) {
ticl_state_ = NO_NOTIFICATION_ERROR;
EmitStateChange();
registration_manager_->SetRegisteredIds(registered_ids_);
}
void ChromeInvalidationClient::Invalidate(
invalidation::InvalidationClient* client,
const invalidation::Invalidation& invalidation,
const invalidation::AckHandle& ack_handle) {
DCHECK(CalledOnValidThread());
DVLOG(1) << "Invalidate: " << InvalidationToString(invalidation);
const invalidation::ObjectId& id = invalidation.object_id();
// The invalidation API spec allows for the possibility of redundant
// invalidations, so keep track of the max versions and drop
// invalidations with old versions.
//
// TODO(akalin): Now that we keep track of registered ids, we
// should drop invalidations for unregistered ids. We may also
// have to filter it at a higher level, as invalidations for
// newly-unregistered ids may already be in flight.
InvalidationVersionMap::const_iterator it =
max_invalidation_versions_.find(id);
if ((it != max_invalidation_versions_.end()) &&
(invalidation.version() <= it->second)) {
// Drop redundant invalidations.
client->Acknowledge(ack_handle);
return;
}
DVLOG(2) << "Setting max invalidation version for " << ObjectIdToString(id)
<< " to " << invalidation.version();
max_invalidation_versions_[id] = invalidation.version();
invalidation_state_tracker_.Call(
FROM_HERE,
&InvalidationStateTracker::SetMaxVersion,
id, invalidation.version());
std::string payload;
// payload() CHECK()'s has_payload(), so we must check it ourselves first.
if (invalidation.has_payload())
payload = invalidation.payload();
ObjectIdPayloadMap id_payloads;
id_payloads[id] = payload;
EmitInvalidation(id_payloads);
// TODO(akalin): We should really acknowledge only after we get the
// updates from the sync server. (see http://crbug.com/78462).
client->Acknowledge(ack_handle);
}
void ChromeInvalidationClient::InvalidateUnknownVersion(
invalidation::InvalidationClient* client,
const invalidation::ObjectId& object_id,
const invalidation::AckHandle& ack_handle) {
DCHECK(CalledOnValidThread());
DVLOG(1) << "InvalidateUnknownVersion";
ObjectIdPayloadMap id_payloads;
id_payloads[object_id] = std::string();
EmitInvalidation(id_payloads);
// TODO(akalin): We should really acknowledge only after we get the
// updates from the sync server. (see http://crbug.com/78462).
client->Acknowledge(ack_handle);
}
// This should behave as if we got an invalidation with version
// UNKNOWN_OBJECT_VERSION for all known data types.
void ChromeInvalidationClient::InvalidateAll(
invalidation::InvalidationClient* client,
const invalidation::AckHandle& ack_handle) {
DCHECK(CalledOnValidThread());
DVLOG(1) << "InvalidateAll";
ObjectIdPayloadMap id_payloads;
for (ObjectIdSet::const_iterator it = registered_ids_.begin();
it != registered_ids_.end(); ++it) {
id_payloads[*it] = std::string();
}
EmitInvalidation(id_payloads);
// TODO(akalin): We should really acknowledge only after we get the
// updates from the sync server. (see http://crbug.com/76482).
client->Acknowledge(ack_handle);
}
void ChromeInvalidationClient::EmitInvalidation(
const ObjectIdPayloadMap& id_payloads) {
DCHECK(CalledOnValidThread());
listener_->OnInvalidate(id_payloads);
}
void ChromeInvalidationClient::InformRegistrationStatus(
invalidation::InvalidationClient* client,
const invalidation::ObjectId& object_id,
InvalidationListener::RegistrationState new_state) {
DCHECK(CalledOnValidThread());
DVLOG(1) << "InformRegistrationStatus: "
<< ObjectIdToString(object_id) << " " << new_state;
if (new_state != InvalidationListener::REGISTERED) {
// Let |registration_manager_| handle the registration backoff policy.
registration_manager_->MarkRegistrationLost(object_id);
}
}
void ChromeInvalidationClient::InformRegistrationFailure(
invalidation::InvalidationClient* client,
const invalidation::ObjectId& object_id,
bool is_transient,
const std::string& error_message) {
DCHECK(CalledOnValidThread());
DVLOG(1) << "InformRegistrationFailure: "
<< ObjectIdToString(object_id)
<< "is_transient=" << is_transient
<< ", message=" << error_message;
if (is_transient) {
// We don't care about |unknown_hint|; we let
// |registration_manager_| handle the registration backoff policy.
registration_manager_->MarkRegistrationLost(object_id);
} else {
// Non-transient failures are permanent, so block any future
// registration requests for |model_type|. (This happens if the
// server doesn't recognize the data type, which could happen for
// brand-new data types.)
registration_manager_->DisableId(object_id);
}
}
void ChromeInvalidationClient::ReissueRegistrations(
invalidation::InvalidationClient* client,
const std::string& prefix,
int prefix_length) {
DCHECK(CalledOnValidThread());
DVLOG(1) << "AllRegistrationsLost";
registration_manager_->MarkAllRegistrationsLost();
}
void ChromeInvalidationClient::InformError(
invalidation::InvalidationClient* client,
const invalidation::ErrorInfo& error_info) {
LOG(ERROR) << "Ticl error " << error_info.error_reason() << ": "
<< error_info.error_message()
<< " (transient = " << error_info.is_transient() << ")";
if (error_info.error_reason() == invalidation::ErrorReason::AUTH_FAILURE) {
ticl_state_ = NOTIFICATION_CREDENTIALS_REJECTED;
} else {
ticl_state_ = TRANSIENT_NOTIFICATION_ERROR;
}
EmitStateChange();
}
void ChromeInvalidationClient::WriteState(const std::string& state) {
DCHECK(CalledOnValidThread());
DVLOG(1) << "WriteState";
invalidation_state_tracker_.Call(
FROM_HERE, &InvalidationStateTracker::SetInvalidationState, state);
}
void ChromeInvalidationClient::Stop() {
DCHECK(CalledOnValidThread());
if (!invalidation_client_.get()) {
return;
}
registration_manager_.reset();
chrome_system_resources_.Stop();
invalidation_client_->Stop();
invalidation_client_.reset();
listener_ = NULL;
invalidation_state_tracker_.Reset();
max_invalidation_versions_.clear();
ticl_state_ = DEFAULT_NOTIFICATION_ERROR;
push_client_state_ = DEFAULT_NOTIFICATION_ERROR;
}
NotificationsDisabledReason ChromeInvalidationClient::GetState() const {
DCHECK(CalledOnValidThread());
if (ticl_state_ == NOTIFICATION_CREDENTIALS_REJECTED ||
push_client_state_ == NOTIFICATION_CREDENTIALS_REJECTED) {
// If either the ticl or the push client rejected our credentials,
// return NOTIFICATION_CREDENTIALS_REJECTED.
return NOTIFICATION_CREDENTIALS_REJECTED;
}
if (ticl_state_ == NO_NOTIFICATION_ERROR &&
push_client_state_ == NO_NOTIFICATION_ERROR) {
// If the ticl is ready and the push client notifications are
// enabled, return NO_NOTIFICATION_ERROR.
return NO_NOTIFICATION_ERROR;
}
// Otherwise, we have a transient error.
return TRANSIENT_NOTIFICATION_ERROR;
}
void ChromeInvalidationClient::EmitStateChange() {
DCHECK(CalledOnValidThread());
if (GetState() == NO_NOTIFICATION_ERROR) {
listener_->OnNotificationsEnabled();
} else {
listener_->OnNotificationsDisabled(GetState());
}
}
void ChromeInvalidationClient::OnNotificationsEnabled() {
DCHECK(CalledOnValidThread());
push_client_state_ = NO_NOTIFICATION_ERROR;
EmitStateChange();
}
void ChromeInvalidationClient::OnNotificationsDisabled(
notifier::NotificationsDisabledReason reason) {
DCHECK(CalledOnValidThread());
push_client_state_ = FromNotifierReason(reason);
EmitStateChange();
}
void ChromeInvalidationClient::OnIncomingNotification(
const notifier::Notification& notification) {
DCHECK(CalledOnValidThread());
// Do nothing, since this is already handled by |invalidation_client_|.
}
} // namespace syncer