| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "sync/notifier/chrome_invalidation_client.h" | |
| 6 | |
| 7 #include <string> | |
| 8 #include <vector> | |
| 9 | |
| 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" | |
| 20 | |
| 21 namespace { | |
| 22 | |
| 23 const char kApplicationName[] = "chrome-sync"; | |
| 24 | |
| 25 } // namespace | |
| 26 | |
| 27 namespace syncer { | |
| 28 | |
| 29 ChromeInvalidationClient::Listener::~Listener() {} | |
| 30 | |
| 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)), | |
| 36 listener_(NULL), | |
| 37 ticl_state_(DEFAULT_NOTIFICATION_ERROR), | |
| 38 push_client_state_(DEFAULT_NOTIFICATION_ERROR) { | |
| 39 DCHECK(CalledOnValidThread()); | |
| 40 push_client_->AddObserver(this); | |
| 41 } | |
| 42 | |
| 43 ChromeInvalidationClient::~ChromeInvalidationClient() { | |
| 44 DCHECK(CalledOnValidThread()); | |
| 45 push_client_->RemoveObserver(this); | |
| 46 Stop(); | |
| 47 DCHECK(!listener_); | |
| 48 } | |
| 49 | |
| 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, | |
| 57 Listener* listener) { | |
| 58 DCHECK(CalledOnValidThread()); | |
| 59 Stop(); | |
| 60 | |
| 61 chrome_system_resources_.set_platform(client_info); | |
| 62 chrome_system_resources_.Start(); | |
| 63 | |
| 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); | |
| 68 | |
| 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"; | |
| 72 } else { | |
| 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 " | |
| 78 << it->second; | |
| 79 } | |
| 80 } | |
| 81 invalidation_state_tracker_ = invalidation_state_tracker; | |
| 82 DCHECK(invalidation_state_tracker_.IsInitialized()); | |
| 83 | |
| 84 DCHECK(!listener_); | |
| 85 DCHECK(listener); | |
| 86 listener_ = listener; | |
| 87 | |
| 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(); | |
| 94 | |
| 95 registration_manager_.reset( | |
| 96 new RegistrationManager(invalidation_client_.get())); | |
| 97 } | |
| 98 | |
| 99 void ChromeInvalidationClient::UpdateCredentials( | |
| 100 const std::string& email, const std::string& token) { | |
| 101 DCHECK(CalledOnValidThread()); | |
| 102 chrome_system_resources_.network()->UpdateCredentials(email, token); | |
| 103 } | |
| 104 | |
| 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_); | |
| 113 } | |
| 114 // TODO(akalin): Clear invalidation versions for unregistered types. | |
| 115 } | |
| 116 | |
| 117 void ChromeInvalidationClient::Ready( | |
| 118 invalidation::InvalidationClient* client) { | |
| 119 DCHECK(CalledOnValidThread()); | |
| 120 DCHECK_EQ(client, invalidation_client_.get()); | |
| 121 ticl_state_ = NO_NOTIFICATION_ERROR; | |
| 122 EmitStateChange(); | |
| 123 registration_manager_->UpdateRegisteredIds(registered_ids_); | |
| 124 } | |
| 125 | |
| 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); | |
| 133 | |
| 134 const invalidation::ObjectId& id = invalidation.object_id(); | |
| 135 | |
| 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. | |
| 139 // | |
| 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); | |
| 150 return; | |
| 151 } | |
| 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( | |
| 156 FROM_HERE, | |
| 157 &InvalidationStateTracker::SetMaxVersion, | |
| 158 id, invalidation.version()); | |
| 159 | |
| 160 std::string payload; | |
| 161 // payload() CHECK()'s has_payload(), so we must check it ourselves first. | |
| 162 if (invalidation.has_payload()) | |
| 163 payload = invalidation.payload(); | |
| 164 | |
| 165 ObjectIdStateMap id_state_map; | |
| 166 id_state_map[id].payload = payload; | |
| 167 EmitInvalidation(id_state_map); | |
| 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); | |
| 171 } | |
| 172 | |
| 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"; | |
| 180 | |
| 181 ObjectIdStateMap id_state_map; | |
| 182 id_state_map[object_id].payload = std::string(); | |
| 183 EmitInvalidation(id_state_map); | |
| 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); | |
| 187 } | |
| 188 | |
| 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"; | |
| 197 | |
| 198 ObjectIdStateMap id_state_map; | |
| 199 for (ObjectIdSet::const_iterator it = registered_ids_.begin(); | |
| 200 it != registered_ids_.end(); ++it) { | |
| 201 id_state_map[*it].payload = std::string(); | |
| 202 } | |
| 203 EmitInvalidation(id_state_map); | |
| 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); | |
| 207 } | |
| 208 | |
| 209 void ChromeInvalidationClient::EmitInvalidation( | |
| 210 const ObjectIdStateMap& id_state_map) { | |
| 211 DCHECK(CalledOnValidThread()); | |
| 212 listener_->OnInvalidate(id_state_map); | |
| 213 } | |
| 214 | |
| 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; | |
| 223 | |
| 224 if (new_state != InvalidationListener::REGISTERED) { | |
| 225 // Let |registration_manager_| handle the registration backoff policy. | |
| 226 registration_manager_->MarkRegistrationLost(object_id); | |
| 227 } | |
| 228 } | |
| 229 | |
| 230 void ChromeInvalidationClient::InformRegistrationFailure( | |
| 231 invalidation::InvalidationClient* client, | |
| 232 const invalidation::ObjectId& object_id, | |
| 233 bool is_transient, | |
| 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; | |
| 241 | |
| 242 if (is_transient) { | |
| 243 // We don't care about |unknown_hint|; we let | |
| 244 // |registration_manager_| handle the registration backoff policy. | |
| 245 registration_manager_->MarkRegistrationLost(object_id); | |
| 246 } else { | |
| 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); | |
| 252 } | |
| 253 } | |
| 254 | |
| 255 void ChromeInvalidationClient::ReissueRegistrations( | |
| 256 invalidation::InvalidationClient* client, | |
| 257 const std::string& prefix, | |
| 258 int prefix_length) { | |
| 259 DCHECK(CalledOnValidThread()); | |
| 260 DCHECK_EQ(client, invalidation_client_.get()); | |
| 261 DVLOG(1) << "AllRegistrationsLost"; | |
| 262 registration_manager_->MarkAllRegistrationsLost(); | |
| 263 } | |
| 264 | |
| 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; | |
| 275 } else { | |
| 276 ticl_state_ = TRANSIENT_NOTIFICATION_ERROR; | |
| 277 } | |
| 278 EmitStateChange(); | |
| 279 } | |
| 280 | |
| 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); | |
| 286 } | |
| 287 | |
| 288 void ChromeInvalidationClient::StopForTest() { | |
| 289 DCHECK(CalledOnValidThread()); | |
| 290 Stop(); | |
| 291 } | |
| 292 | |
| 293 void ChromeInvalidationClient::Stop() { | |
| 294 DCHECK(CalledOnValidThread()); | |
| 295 if (!invalidation_client_.get()) { | |
| 296 return; | |
| 297 } | |
| 298 | |
| 299 registration_manager_.reset(); | |
| 300 chrome_system_resources_.Stop(); | |
| 301 invalidation_client_->Stop(); | |
| 302 | |
| 303 invalidation_client_.reset(); | |
| 304 listener_ = NULL; | |
| 305 | |
| 306 invalidation_state_tracker_.Reset(); | |
| 307 max_invalidation_versions_.clear(); | |
| 308 ticl_state_ = DEFAULT_NOTIFICATION_ERROR; | |
| 309 push_client_state_ = DEFAULT_NOTIFICATION_ERROR; | |
| 310 } | |
| 311 | |
| 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; | |
| 319 } | |
| 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; | |
| 325 } | |
| 326 // Otherwise, we have a transient error. | |
| 327 return TRANSIENT_NOTIFICATION_ERROR; | |
| 328 } | |
| 329 | |
| 330 void ChromeInvalidationClient::EmitStateChange() { | |
| 331 DCHECK(CalledOnValidThread()); | |
| 332 if (GetState() == NO_NOTIFICATION_ERROR) { | |
| 333 listener_->OnNotificationsEnabled(); | |
| 334 } else { | |
| 335 listener_->OnNotificationsDisabled(GetState()); | |
| 336 } | |
| 337 } | |
| 338 | |
| 339 void ChromeInvalidationClient::OnNotificationsEnabled() { | |
| 340 DCHECK(CalledOnValidThread()); | |
| 341 push_client_state_ = NO_NOTIFICATION_ERROR; | |
| 342 EmitStateChange(); | |
| 343 } | |
| 344 | |
| 345 void ChromeInvalidationClient::OnNotificationsDisabled( | |
| 346 notifier::NotificationsDisabledReason reason) { | |
| 347 DCHECK(CalledOnValidThread()); | |
| 348 push_client_state_ = FromNotifierReason(reason); | |
| 349 EmitStateChange(); | |
| 350 } | |
| 351 | |
| 352 void ChromeInvalidationClient::OnIncomingNotification( | |
| 353 const notifier::Notification& notification) { | |
| 354 DCHECK(CalledOnValidThread()); | |
| 355 // Do nothing, since this is already handled by |invalidation_client_|. | |
| 356 } | |
| 357 | |
| 358 } // namespace syncer | |
| OLD | NEW |