1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "sync/notifier/chrome_invalidation_client.h"
10 #include "base/callback.h"
11 #include "base/compiler_specific.h"
12 #include "base/logging.h"
13 #include "base/tracked_objects.h"
14 #include "google/cacheinvalidation/include/invalidation-client.h"
15 #include "google/cacheinvalidation/include/types.h"
16 #include "google/cacheinvalidation/types.pb.h"
17 #include "jingle/notifier/listener/push_client.h"
18 #include "sync/notifier/invalidation_util.h"
19 #include "sync/notifier/registration_manager.h"
23 const char kApplicationName
[] = "chrome-sync";
29 ChromeInvalidationClient::Listener::~Listener() {}
31 ChromeInvalidationClient::ChromeInvalidationClient(
32 scoped_ptr
<notifier::PushClient
> push_client
)
33 : push_client_(push_client
.get()),
34 chrome_system_resources_(push_client
.Pass(),
35 ALLOW_THIS_IN_INITIALIZER_LIST(this)),
37 ticl_state_(DEFAULT_NOTIFICATION_ERROR
),
38 push_client_state_(DEFAULT_NOTIFICATION_ERROR
) {
39 DCHECK(CalledOnValidThread());
40 push_client_
->AddObserver(this);
43 ChromeInvalidationClient::~ChromeInvalidationClient() {
44 DCHECK(CalledOnValidThread());
45 push_client_
->RemoveObserver(this);
50 void ChromeInvalidationClient::Start(
51 const CreateInvalidationClientCallback
&
52 create_invalidation_client_callback
,
53 const std::string
& client_id
, const std::string
& client_info
,
54 const std::string
& state
,
55 const InvalidationVersionMap
& initial_max_invalidation_versions
,
56 const WeakHandle
<InvalidationStateTracker
>& invalidation_state_tracker
,
58 DCHECK(CalledOnValidThread());
61 chrome_system_resources_
.set_platform(client_info
);
62 chrome_system_resources_
.Start();
64 // The Storage resource is implemented as a write-through cache. We populate
65 // it with the initial state on startup, so subsequent writes go to disk and
66 // update the in-memory cache, while reads just return the cached state.
67 chrome_system_resources_
.storage()->SetInitialState(state
);
69 max_invalidation_versions_
= initial_max_invalidation_versions
;
70 if (max_invalidation_versions_
.empty()) {
71 DVLOG(2) << "No initial max invalidation versions for any id";
73 for (InvalidationVersionMap::const_iterator it
=
74 max_invalidation_versions_
.begin();
75 it
!= max_invalidation_versions_
.end(); ++it
) {
76 DVLOG(2) << "Initial max invalidation version for "
77 << ObjectIdToString(it
->first
) << " is "
81 invalidation_state_tracker_
= invalidation_state_tracker
;
82 DCHECK(invalidation_state_tracker_
.IsInitialized());
88 int client_type
= ipc::invalidation::ClientType::CHROME_SYNC
;
89 invalidation_client_
.reset(
90 create_invalidation_client_callback
.Run(
91 &chrome_system_resources_
, client_type
, client_id
,
92 kApplicationName
, this));
93 invalidation_client_
->Start();
95 registration_manager_
.reset(
96 new RegistrationManager(invalidation_client_
.get()));
99 void ChromeInvalidationClient::UpdateCredentials(
100 const std::string
& email
, const std::string
& token
) {
101 DCHECK(CalledOnValidThread());
102 chrome_system_resources_
.network()->UpdateCredentials(email
, token
);
105 void ChromeInvalidationClient::UpdateRegisteredIds(const ObjectIdSet
& ids
) {
106 DCHECK(CalledOnValidThread());
107 registered_ids_
= ids
;
108 // |ticl_state_| can go to NO_NOTIFICATION_ERROR even without a
109 // working XMPP connection (as observed by us), so check it instead
110 // of GetState() (see http://crbug.com/139424).
111 if (ticl_state_
== NO_NOTIFICATION_ERROR
&& registration_manager_
.get()) {
112 registration_manager_
->UpdateRegisteredIds(registered_ids_
);
114 // TODO(akalin): Clear invalidation versions for unregistered types.
117 void ChromeInvalidationClient::Ready(
118 invalidation::InvalidationClient
* client
) {
119 DCHECK(CalledOnValidThread());
120 DCHECK_EQ(client
, invalidation_client_
.get());
121 ticl_state_
= NO_NOTIFICATION_ERROR
;
123 registration_manager_
->UpdateRegisteredIds(registered_ids_
);
126 void ChromeInvalidationClient::Invalidate(
127 invalidation::InvalidationClient
* client
,
128 const invalidation::Invalidation
& invalidation
,
129 const invalidation::AckHandle
& ack_handle
) {
130 DCHECK(CalledOnValidThread());
131 DCHECK_EQ(client
, invalidation_client_
.get());
132 DVLOG(1) << "Invalidate: " << InvalidationToString(invalidation
);
134 const invalidation::ObjectId
& id
= invalidation
.object_id();
136 // The invalidation API spec allows for the possibility of redundant
137 // invalidations, so keep track of the max versions and drop
138 // invalidations with old versions.
140 // TODO(akalin): Now that we keep track of registered ids, we
141 // should drop invalidations for unregistered ids. We may also
142 // have to filter it at a higher level, as invalidations for
143 // newly-unregistered ids may already be in flight.
144 InvalidationVersionMap::const_iterator it
=
145 max_invalidation_versions_
.find(id
);
146 if ((it
!= max_invalidation_versions_
.end()) &&
147 (invalidation
.version() <= it
->second
)) {
148 // Drop redundant invalidations.
149 client
->Acknowledge(ack_handle
);
152 DVLOG(2) << "Setting max invalidation version for " << ObjectIdToString(id
)
153 << " to " << invalidation
.version();
154 max_invalidation_versions_
[id
] = invalidation
.version();
155 invalidation_state_tracker_
.Call(
157 &InvalidationStateTracker::SetMaxVersion
,
158 id
, invalidation
.version());
161 // payload() CHECK()'s has_payload(), so we must check it ourselves first.
162 if (invalidation
.has_payload())
163 payload
= invalidation
.payload();
165 ObjectIdPayloadMap id_payloads
;
166 id_payloads
[id
] = payload
;
167 EmitInvalidation(id_payloads
);
168 // TODO(akalin): We should really acknowledge only after we get the
169 // updates from the sync server. (see http://crbug.com/78462).
170 client
->Acknowledge(ack_handle
);
173 void ChromeInvalidationClient::InvalidateUnknownVersion(
174 invalidation::InvalidationClient
* client
,
175 const invalidation::ObjectId
& object_id
,
176 const invalidation::AckHandle
& ack_handle
) {
177 DCHECK(CalledOnValidThread());
178 DCHECK_EQ(client
, invalidation_client_
.get());
179 DVLOG(1) << "InvalidateUnknownVersion";
181 ObjectIdPayloadMap id_payloads
;
182 id_payloads
[object_id
] = std::string();
183 EmitInvalidation(id_payloads
);
184 // TODO(akalin): We should really acknowledge only after we get the
185 // updates from the sync server. (see http://crbug.com/78462).
186 client
->Acknowledge(ack_handle
);
189 // This should behave as if we got an invalidation with version
190 // UNKNOWN_OBJECT_VERSION for all known data types.
191 void ChromeInvalidationClient::InvalidateAll(
192 invalidation::InvalidationClient
* client
,
193 const invalidation::AckHandle
& ack_handle
) {
194 DCHECK(CalledOnValidThread());
195 DCHECK_EQ(client
, invalidation_client_
.get());
196 DVLOG(1) << "InvalidateAll";
198 ObjectIdPayloadMap id_payloads
;
199 for (ObjectIdSet::const_iterator it
= registered_ids_
.begin();
200 it
!= registered_ids_
.end(); ++it
) {
201 id_payloads
[*it
] = std::string();
203 EmitInvalidation(id_payloads
);
204 // TODO(akalin): We should really acknowledge only after we get the
205 // updates from the sync server. (see http://crbug.com/76482).
206 client
->Acknowledge(ack_handle
);
209 void ChromeInvalidationClient::EmitInvalidation(
210 const ObjectIdPayloadMap
& id_payloads
) {
211 DCHECK(CalledOnValidThread());
212 listener_
->OnInvalidate(id_payloads
);
215 void ChromeInvalidationClient::InformRegistrationStatus(
216 invalidation::InvalidationClient
* client
,
217 const invalidation::ObjectId
& object_id
,
218 InvalidationListener::RegistrationState new_state
) {
219 DCHECK(CalledOnValidThread());
220 DCHECK_EQ(client
, invalidation_client_
.get());
221 DVLOG(1) << "InformRegistrationStatus: "
222 << ObjectIdToString(object_id
) << " " << new_state
;
224 if (new_state
!= InvalidationListener::REGISTERED
) {
225 // Let |registration_manager_| handle the registration backoff policy.
226 registration_manager_
->MarkRegistrationLost(object_id
);
230 void ChromeInvalidationClient::InformRegistrationFailure(
231 invalidation::InvalidationClient
* client
,
232 const invalidation::ObjectId
& object_id
,
234 const std::string
& error_message
) {
235 DCHECK(CalledOnValidThread());
236 DCHECK_EQ(client
, invalidation_client_
.get());
237 DVLOG(1) << "InformRegistrationFailure: "
238 << ObjectIdToString(object_id
)
239 << "is_transient=" << is_transient
240 << ", message=" << error_message
;
243 // We don't care about |unknown_hint|; we let
244 // |registration_manager_| handle the registration backoff policy.
245 registration_manager_
->MarkRegistrationLost(object_id
);
247 // Non-transient failures are permanent, so block any future
248 // registration requests for |model_type|. (This happens if the
249 // server doesn't recognize the data type, which could happen for
250 // brand-new data types.)
251 registration_manager_
->DisableId(object_id
);
255 void ChromeInvalidationClient::ReissueRegistrations(
256 invalidation::InvalidationClient
* client
,
257 const std::string
& prefix
,
259 DCHECK(CalledOnValidThread());
260 DCHECK_EQ(client
, invalidation_client_
.get());
261 DVLOG(1) << "AllRegistrationsLost";
262 registration_manager_
->MarkAllRegistrationsLost();
265 void ChromeInvalidationClient::InformError(
266 invalidation::InvalidationClient
* client
,
267 const invalidation::ErrorInfo
& error_info
) {
268 DCHECK(CalledOnValidThread());
269 DCHECK_EQ(client
, invalidation_client_
.get());
270 LOG(ERROR
) << "Ticl error " << error_info
.error_reason() << ": "
271 << error_info
.error_message()
272 << " (transient = " << error_info
.is_transient() << ")";
273 if (error_info
.error_reason() == invalidation::ErrorReason::AUTH_FAILURE
) {
274 ticl_state_
= NOTIFICATION_CREDENTIALS_REJECTED
;
276 ticl_state_
= TRANSIENT_NOTIFICATION_ERROR
;
281 void ChromeInvalidationClient::WriteState(const std::string
& state
) {
282 DCHECK(CalledOnValidThread());
283 DVLOG(1) << "WriteState";
284 invalidation_state_tracker_
.Call(
285 FROM_HERE
, &InvalidationStateTracker::SetInvalidationState
, state
);
288 void ChromeInvalidationClient::StopForTest() {
289 DCHECK(CalledOnValidThread());
293 void ChromeInvalidationClient::Stop() {
294 DCHECK(CalledOnValidThread());
295 if (!invalidation_client_
.get()) {
299 registration_manager_
.reset();
300 chrome_system_resources_
.Stop();
301 invalidation_client_
->Stop();
303 invalidation_client_
.reset();
306 invalidation_state_tracker_
.Reset();
307 max_invalidation_versions_
.clear();
308 ticl_state_
= DEFAULT_NOTIFICATION_ERROR
;
309 push_client_state_
= DEFAULT_NOTIFICATION_ERROR
;
312 NotificationsDisabledReason
ChromeInvalidationClient::GetState() const {
313 DCHECK(CalledOnValidThread());
314 if (ticl_state_
== NOTIFICATION_CREDENTIALS_REJECTED
||
315 push_client_state_
== NOTIFICATION_CREDENTIALS_REJECTED
) {
316 // If either the ticl or the push client rejected our credentials,
317 // return NOTIFICATION_CREDENTIALS_REJECTED.
318 return NOTIFICATION_CREDENTIALS_REJECTED
;
320 if (ticl_state_
== NO_NOTIFICATION_ERROR
&&
321 push_client_state_
== NO_NOTIFICATION_ERROR
) {
322 // If the ticl is ready and the push client notifications are
323 // enabled, return NO_NOTIFICATION_ERROR.
324 return NO_NOTIFICATION_ERROR
;
326 // Otherwise, we have a transient error.
327 return TRANSIENT_NOTIFICATION_ERROR
;
330 void ChromeInvalidationClient::EmitStateChange() {
331 DCHECK(CalledOnValidThread());
332 if (GetState() == NO_NOTIFICATION_ERROR
) {
333 listener_
->OnNotificationsEnabled();
335 listener_
->OnNotificationsDisabled(GetState());
339 void ChromeInvalidationClient::OnNotificationsEnabled() {
340 DCHECK(CalledOnValidThread());
341 push_client_state_
= NO_NOTIFICATION_ERROR
;
345 void ChromeInvalidationClient::OnNotificationsDisabled(
346 notifier::NotificationsDisabledReason reason
) {
347 DCHECK(CalledOnValidThread());
348 push_client_state_
= FromNotifierReason(reason
);
352 void ChromeInvalidationClient::OnIncomingNotification(
353 const notifier::Notification
& notification
) {
354 DCHECK(CalledOnValidThread());
355 // Do nothing, since this is already handled by |invalidation_client_|.
358 } // namespace syncer