blob: 278076f33edca17bf7f595519e1081792656e3d7 [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/v2/types.pb.h"
#include "jingle/notifier/listener/push_client.h"
#include "sync/internal_api/public/syncable/model_type.h"
#include "sync/notifier/invalidation_util.h"
#include "sync/notifier/registration_manager.h"
namespace {
const char kApplicationName[] = "chrome-sync";
} // namespace
namespace sync_notifier {
ChromeInvalidationClient::Listener::~Listener() {}
ChromeInvalidationClient::ChromeInvalidationClient(
scoped_ptr<notifier::PushClient> push_client)
: chrome_system_resources_(push_client.Pass(),
ALLOW_THIS_IN_INITIALIZER_LIST(this)),
listener_(NULL),
ticl_ready_(false) {
DCHECK(CalledOnValidThread());
}
ChromeInvalidationClient::~ChromeInvalidationClient() {
DCHECK(CalledOnValidThread());
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 browser_sync::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 type";
} else {
for (InvalidationVersionMap::const_iterator it =
max_invalidation_versions_.begin();
it != max_invalidation_versions_.end(); ++it) {
DVLOG(2) << "Initial max invalidation version for "
<< syncable::ModelTypeToString(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::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();
}
void ChromeInvalidationClient::RegisterTypes(syncable::ModelTypeSet types) {
DCHECK(CalledOnValidThread());
registered_types_ = types;
if (ticl_ready_ && registration_manager_.get()) {
registration_manager_->SetRegisteredTypes(registered_types_);
}
// TODO(akalin): Clear invalidation versions for unregistered types.
}
void ChromeInvalidationClient::Ready(
invalidation::InvalidationClient* client) {
ticl_ready_ = true;
listener_->OnSessionStatusChanged(true);
registration_manager_->SetRegisteredTypes(registered_types_);
}
void ChromeInvalidationClient::Invalidate(
invalidation::InvalidationClient* client,
const invalidation::Invalidation& invalidation,
const invalidation::AckHandle& ack_handle) {
DCHECK(CalledOnValidThread());
DVLOG(1) << "Invalidate: " << InvalidationToString(invalidation);
syncable::ModelType model_type;
if (!ObjectIdToRealModelType(invalidation.object_id(), &model_type)) {
LOG(WARNING) << "Could not get invalidation model type; "
<< "invalidating everything";
EmitInvalidation(registered_types_, std::string());
client->Acknowledge(ack_handle);
return;
}
// 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 types, we
// should drop invalidations for unregistered types. We may also
// have to filter it at a higher level, as invalidations for
// newly-unregistered types may already be in flight.
InvalidationVersionMap::const_iterator it =
max_invalidation_versions_.find(model_type);
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 "
<< syncable::ModelTypeToString(model_type) << " to "
<< invalidation.version();
max_invalidation_versions_[model_type] = invalidation.version();
invalidation_state_tracker_.Call(
FROM_HERE,
&InvalidationStateTracker::SetMaxVersion,
model_type, invalidation.version());
std::string payload;
// payload() CHECK()'s has_payload(), so we must check it ourselves first.
if (invalidation.has_payload())
payload = invalidation.payload();
EmitInvalidation(syncable::ModelTypeSet(model_type), payload);
// 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";
syncable::ModelType model_type;
if (!ObjectIdToRealModelType(object_id, &model_type)) {
LOG(WARNING) << "Could not get invalidation model type; "
<< "invalidating everything";
EmitInvalidation(registered_types_, std::string());
client->Acknowledge(ack_handle);
return;
}
EmitInvalidation(syncable::ModelTypeSet(model_type), "");
// 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";
EmitInvalidation(registered_types_, std::string());
// 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(
syncable::ModelTypeSet types, const std::string& payload) {
DCHECK(CalledOnValidThread());
syncable::ModelTypePayloadMap type_payloads =
syncable::ModelTypePayloadMapFromEnumSet(types, payload);
listener_->OnInvalidate(type_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;
syncable::ModelType model_type;
if (!ObjectIdToRealModelType(object_id, &model_type)) {
LOG(WARNING) << "Could not get object id model type; ignoring";
return;
}
if (new_state != InvalidationListener::REGISTERED) {
// Let |registration_manager_| handle the registration backoff policy.
registration_manager_->MarkRegistrationLost(model_type);
}
}
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;
syncable::ModelType model_type;
if (!ObjectIdToRealModelType(object_id, &model_type)) {
LOG(WARNING) << "Could not get object id model type; ignoring";
return;
}
if (is_transient) {
// We don't care about |unknown_hint|; we let
// |registration_manager_| handle the registration backoff policy.
registration_manager_->MarkRegistrationLost(model_type);
} 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_->DisableType(model_type);
}
}
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) {
listener_->OnSessionStatusChanged(false);
LOG(ERROR) << "Invalidation client encountered an error";
// TODO(ghc): handle the error.
}
void ChromeInvalidationClient::WriteState(const std::string& state) {
DCHECK(CalledOnValidThread());
DVLOG(1) << "WriteState";
invalidation_state_tracker_.Call(
FROM_HERE, &InvalidationStateTracker::SetInvalidationState, state);
}
} // namespace sync_notifier