| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2011 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 "chrome/browser/sync/internal_api/sync_manager.h" |
| 6 |
| 7 #include <string> |
| 8 #include <vector> |
| 9 |
| 10 #include "base/base64.h" |
| 11 #include "base/json/json_writer.h" |
| 12 #include "base/string_number_conversions.h" |
| 13 #include "base/values.h" |
| 14 #include "chrome/browser/sync/engine/all_status.h" |
| 15 #include "chrome/browser/sync/engine/change_reorder_buffer.h" |
| 16 #include "chrome/browser/sync/engine/net/server_connection_manager.h" |
| 17 #include "chrome/browser/sync/engine/net/syncapi_server_connection_manager.h" |
| 18 #include "chrome/browser/sync/engine/nigori_util.h" |
| 19 #include "chrome/browser/sync/engine/syncapi_internal.h" |
| 20 #include "chrome/browser/sync/engine/syncer_types.h" |
| 21 #include "chrome/browser/sync/engine/sync_scheduler.h" |
| 22 #include "chrome/browser/sync/internal_api/base_node.h" |
| 23 #include "chrome/browser/sync/internal_api/read_node.h" |
| 24 #include "chrome/browser/sync/internal_api/read_transaction.h" |
| 25 #include "chrome/browser/sync/internal_api/syncapi_functions.h" |
| 26 #include "chrome/browser/sync/internal_api/user_share.h" |
| 27 #include "chrome/browser/sync/internal_api/write_node.h" |
| 28 #include "chrome/browser/sync/internal_api/write_transaction.h" |
| 29 #include "chrome/browser/sync/js/js_arg_list.h" |
| 30 #include "chrome/browser/sync/js/js_backend.h" |
| 31 #include "chrome/browser/sync/js/js_event_details.h" |
| 32 #include "chrome/browser/sync/js/js_event_handler.h" |
| 33 #include "chrome/browser/sync/js/js_reply_handler.h" |
| 34 #include "chrome/browser/sync/js/js_sync_manager_observer.h" |
| 35 #include "chrome/browser/sync/js/js_transaction_observer.h" |
| 36 #include "chrome/browser/sync/notifier/sync_notifier.h" |
| 37 #include "chrome/browser/sync/notifier/sync_notifier_observer.h" |
| 38 #include "chrome/browser/sync/protocol/proto_value_conversions.h" |
| 39 #include "chrome/browser/sync/syncable/directory_change_delegate.h" |
| 40 #include "chrome/browser/sync/syncable/directory_manager.h" |
| 41 #include "chrome/browser/sync/syncable/model_type.h" |
| 42 #include "chrome/browser/sync/syncable/syncable.h" |
| 43 #include "chrome/browser/sync/util/cryptographer.h" |
| 44 #include "chrome/browser/sync/weak_handle.h" |
| 45 #include "net/base/network_change_notifier.h" |
| 46 |
| 47 using std::string; |
| 48 using std::vector; |
| 49 |
| 50 using base::TimeDelta; |
| 51 using browser_sync::AllStatus; |
| 52 using browser_sync::Cryptographer; |
| 53 using browser_sync::JsArgList; |
| 54 using browser_sync::JsBackend; |
| 55 using browser_sync::JsEventDetails; |
| 56 using browser_sync::JsEventHandler; |
| 57 using browser_sync::JsEventHandler; |
| 58 using browser_sync::JsReplyHandler; |
| 59 using browser_sync::JsSyncManagerObserver; |
| 60 using browser_sync::JsTransactionObserver; |
| 61 using browser_sync::ModelSafeWorkerRegistrar; |
| 62 using browser_sync::kNigoriTag; |
| 63 using browser_sync::KeyParams; |
| 64 using browser_sync::ModelSafeRoutingInfo; |
| 65 using browser_sync::ServerConnectionEvent; |
| 66 using browser_sync::ServerConnectionEventListener; |
| 67 using browser_sync::SyncEngineEvent; |
| 68 using browser_sync::SyncEngineEventListener; |
| 69 using browser_sync::SyncScheduler; |
| 70 using browser_sync::Syncer; |
| 71 using browser_sync::WeakHandle; |
| 72 using browser_sync::sessions::SyncSessionContext; |
| 73 using syncable::DirectoryManager; |
| 74 using syncable::EntryKernelMutationSet; |
| 75 using syncable::ModelType; |
| 76 using syncable::ModelTypeBitSet; |
| 77 using syncable::SPECIFICS; |
| 78 |
| 79 typedef GoogleServiceAuthError AuthError; |
| 80 |
| 81 namespace { |
| 82 |
| 83 static const int kSyncSchedulerDelayMsec = 250; |
| 84 |
| 85 #if defined(OS_CHROMEOS) |
| 86 static const int kChromeOSNetworkChangeReactionDelayHackMsec = 5000; |
| 87 #endif // OS_CHROMEOS |
| 88 |
| 89 } // namespace |
| 90 |
| 91 namespace sync_api { |
| 92 |
| 93 SyncManager::ChangeRecord::ChangeRecord() |
| 94 : id(kInvalidId), action(ACTION_ADD) {} |
| 95 |
| 96 SyncManager::ChangeRecord::~ChangeRecord() {} |
| 97 |
| 98 DictionaryValue* SyncManager::ChangeRecord::ToValue( |
| 99 const BaseTransaction* trans) const { |
| 100 DictionaryValue* value = new DictionaryValue(); |
| 101 std::string action_str; |
| 102 switch (action) { |
| 103 case ACTION_ADD: |
| 104 action_str = "Add"; |
| 105 break; |
| 106 case ACTION_DELETE: |
| 107 action_str = "Delete"; |
| 108 break; |
| 109 case ACTION_UPDATE: |
| 110 action_str = "Update"; |
| 111 break; |
| 112 default: |
| 113 NOTREACHED(); |
| 114 action_str = "Unknown"; |
| 115 break; |
| 116 } |
| 117 value->SetString("action", action_str); |
| 118 Value* node_value = NULL; |
| 119 if (action == ACTION_DELETE) { |
| 120 DictionaryValue* node_dict = new DictionaryValue(); |
| 121 node_dict->SetString("id", base::Int64ToString(id)); |
| 122 node_dict->Set("specifics", |
| 123 browser_sync::EntitySpecificsToValue(specifics)); |
| 124 if (extra.get()) { |
| 125 node_dict->Set("extra", extra->ToValue()); |
| 126 } |
| 127 node_value = node_dict; |
| 128 } else { |
| 129 ReadNode node(trans); |
| 130 if (node.InitByIdLookup(id)) { |
| 131 node_value = node.GetDetailsAsValue(); |
| 132 } |
| 133 } |
| 134 if (!node_value) { |
| 135 NOTREACHED(); |
| 136 node_value = Value::CreateNullValue(); |
| 137 } |
| 138 value->Set("node", node_value); |
| 139 return value; |
| 140 } |
| 141 |
| 142 SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData() {} |
| 143 |
| 144 SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData( |
| 145 const sync_pb::PasswordSpecificsData& data) |
| 146 : unencrypted_(data) { |
| 147 } |
| 148 |
| 149 SyncManager::ExtraPasswordChangeRecordData::~ExtraPasswordChangeRecordData() {} |
| 150 |
| 151 DictionaryValue* SyncManager::ExtraPasswordChangeRecordData::ToValue() const { |
| 152 return browser_sync::PasswordSpecificsDataToValue(unencrypted_); |
| 153 } |
| 154 |
| 155 const sync_pb::PasswordSpecificsData& |
| 156 SyncManager::ExtraPasswordChangeRecordData::unencrypted() const { |
| 157 return unencrypted_; |
| 158 } |
| 159 |
| 160 ////////////////////////////////////////////////////////////////////////// |
| 161 // SyncManager's implementation: SyncManager::SyncInternal |
| 162 class SyncManager::SyncInternal |
| 163 : public net::NetworkChangeNotifier::IPAddressObserver, |
| 164 public sync_notifier::SyncNotifierObserver, |
| 165 public JsBackend, |
| 166 public SyncEngineEventListener, |
| 167 public ServerConnectionEventListener, |
| 168 public syncable::DirectoryChangeDelegate { |
| 169 static const int kDefaultNudgeDelayMilliseconds; |
| 170 static const int kPreferencesNudgeDelayMilliseconds; |
| 171 public: |
| 172 explicit SyncInternal(const std::string& name) |
| 173 : weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| 174 registrar_(NULL), |
| 175 initialized_(false), |
| 176 setup_for_test_mode_(false), |
| 177 observing_ip_address_changes_(false) { |
| 178 // Pre-fill |notification_info_map_|. |
| 179 for (int i = syncable::FIRST_REAL_MODEL_TYPE; |
| 180 i < syncable::MODEL_TYPE_COUNT; ++i) { |
| 181 notification_info_map_.insert( |
| 182 std::make_pair(syncable::ModelTypeFromInt(i), NotificationInfo())); |
| 183 } |
| 184 |
| 185 // Bind message handlers. |
| 186 BindJsMessageHandler( |
| 187 "getNotificationState", |
| 188 &SyncManager::SyncInternal::GetNotificationState); |
| 189 BindJsMessageHandler( |
| 190 "getNotificationInfo", |
| 191 &SyncManager::SyncInternal::GetNotificationInfo); |
| 192 BindJsMessageHandler( |
| 193 "getRootNodeDetails", |
| 194 &SyncManager::SyncInternal::GetRootNodeDetails); |
| 195 BindJsMessageHandler( |
| 196 "getNodeSummariesById", |
| 197 &SyncManager::SyncInternal::GetNodeSummariesById); |
| 198 BindJsMessageHandler( |
| 199 "getNodeDetailsById", |
| 200 &SyncManager::SyncInternal::GetNodeDetailsById); |
| 201 BindJsMessageHandler( |
| 202 "getChildNodeIds", |
| 203 &SyncManager::SyncInternal::GetChildNodeIds); |
| 204 BindJsMessageHandler( |
| 205 "findNodesContainingString", |
| 206 &SyncManager::SyncInternal::FindNodesContainingString); |
| 207 } |
| 208 |
| 209 virtual ~SyncInternal() { |
| 210 CHECK(!initialized_); |
| 211 } |
| 212 |
| 213 bool Init(const FilePath& database_location, |
| 214 const WeakHandle<JsEventHandler>& event_handler, |
| 215 const std::string& sync_server_and_path, |
| 216 int port, |
| 217 bool use_ssl, |
| 218 HttpPostProviderFactory* post_factory, |
| 219 ModelSafeWorkerRegistrar* model_safe_worker_registrar, |
| 220 const std::string& user_agent, |
| 221 const SyncCredentials& credentials, |
| 222 sync_notifier::SyncNotifier* sync_notifier, |
| 223 const std::string& restored_key_for_bootstrapping, |
| 224 bool setup_for_test_mode); |
| 225 |
| 226 // Sign into sync with given credentials. |
| 227 // We do not verify the tokens given. After this call, the tokens are set |
| 228 // and the sync DB is open. True if successful, false if something |
| 229 // went wrong. |
| 230 bool SignIn(const SyncCredentials& credentials); |
| 231 |
| 232 // Update tokens that we're using in Sync. Email must stay the same. |
| 233 void UpdateCredentials(const SyncCredentials& credentials); |
| 234 |
| 235 // Called when the user disables or enables a sync type. |
| 236 void UpdateEnabledTypes(); |
| 237 |
| 238 // Tell the sync engine to start the syncing process. |
| 239 void StartSyncingNormally(); |
| 240 |
| 241 // Whether or not the Nigori node is encrypted using an explicit passphrase. |
| 242 bool IsUsingExplicitPassphrase(); |
| 243 |
| 244 // Update the Cryptographer from the current nigori node. |
| 245 // Note: opens a transaction and can trigger an ON_PASSPHRASE_REQUIRED, so |
| 246 // should only be called after syncapi is fully initialized. |
| 247 // Returns true if cryptographer is ready, false otherwise. |
| 248 bool UpdateCryptographerFromNigori(); |
| 249 |
| 250 // Set the datatypes we want to encrypt and encrypt any nodes as necessary. |
| 251 // Note: |encrypted_types| will be unioned with the current set of encrypted |
| 252 // types, as we do not currently support decrypting datatypes. |
| 253 void EncryptDataTypes(const syncable::ModelTypeSet& encrypted_types); |
| 254 |
| 255 // Try to set the current passphrase to |passphrase|, and record whether |
| 256 // it is an explicit passphrase or implicitly using gaia in the Nigori |
| 257 // node. |
| 258 void SetPassphrase(const std::string& passphrase, bool is_explicit); |
| 259 |
| 260 // Call periodically from a database-safe thread to persist recent changes |
| 261 // to the syncapi model. |
| 262 void SaveChanges(); |
| 263 |
| 264 // DirectoryChangeDelegate implementation. |
| 265 // This listener is called upon completion of a syncable transaction, and |
| 266 // builds the list of sync-engine initiated changes that will be forwarded to |
| 267 // the SyncManager's Observers. |
| 268 virtual void HandleTransactionCompleteChangeEvent( |
| 269 const ModelTypeBitSet& models_with_changes); |
| 270 virtual ModelTypeBitSet HandleTransactionEndingChangeEvent( |
| 271 syncable::BaseTransaction* trans); |
| 272 virtual void HandleCalculateChangesChangeEventFromSyncApi( |
| 273 const EntryKernelMutationSet& mutations, |
| 274 syncable::BaseTransaction* trans); |
| 275 virtual void HandleCalculateChangesChangeEventFromSyncer( |
| 276 const EntryKernelMutationSet& mutations, |
| 277 syncable::BaseTransaction* trans); |
| 278 |
| 279 // Listens for notifications from the ServerConnectionManager |
| 280 void HandleServerConnectionEvent(const ServerConnectionEvent& event); |
| 281 |
| 282 // Open the directory named with username_for_share |
| 283 bool OpenDirectory(); |
| 284 |
| 285 // SyncNotifierObserver implementation. |
| 286 virtual void OnNotificationStateChange( |
| 287 bool notifications_enabled); |
| 288 |
| 289 virtual void OnIncomingNotification( |
| 290 const syncable::ModelTypePayloadMap& type_payloads); |
| 291 |
| 292 virtual void StoreState(const std::string& cookie); |
| 293 |
| 294 // Thread-safe observers_ accessors. |
| 295 void CopyObservers(ObserverList<SyncManager::Observer>* observers_copy); |
| 296 bool HaveObservers() const; |
| 297 void AddObserver(SyncManager::Observer* observer); |
| 298 void RemoveObserver(SyncManager::Observer* observer); |
| 299 |
| 300 // Accessors for the private members. |
| 301 DirectoryManager* dir_manager() { return share_.dir_manager.get(); } |
| 302 SyncAPIServerConnectionManager* connection_manager() { |
| 303 return connection_manager_.get(); |
| 304 } |
| 305 SyncScheduler* scheduler() { return scheduler_.get(); } |
| 306 UserShare* GetUserShare() { |
| 307 DCHECK(initialized_); |
| 308 return &share_; |
| 309 } |
| 310 |
| 311 // Return the currently active (validated) username for use with syncable |
| 312 // types. |
| 313 const std::string& username_for_share() const { |
| 314 return share_.name; |
| 315 } |
| 316 |
| 317 Status GetStatus(); |
| 318 |
| 319 void RequestNudge(const tracked_objects::Location& nudge_location); |
| 320 |
| 321 void RequestNudgeForDataType( |
| 322 const tracked_objects::Location& nudge_location, |
| 323 const ModelType& type); |
| 324 |
| 325 void RequestEarlyExit(); |
| 326 |
| 327 // See SyncManager::Shutdown for information. |
| 328 void Shutdown(); |
| 329 |
| 330 // If this is a deletion for a password, sets the legacy |
| 331 // ExtraPasswordChangeRecordData field of |buffer|. Otherwise sets |
| 332 // |buffer|'s specifics field to contain the unencrypted data. |
| 333 void SetExtraChangeRecordData(int64 id, |
| 334 syncable::ModelType type, |
| 335 ChangeReorderBuffer* buffer, |
| 336 Cryptographer* cryptographer, |
| 337 const syncable::EntryKernel& original, |
| 338 bool existed_before, |
| 339 bool exists_now); |
| 340 |
| 341 // Called only by our NetworkChangeNotifier. |
| 342 virtual void OnIPAddressChanged(); |
| 343 |
| 344 bool InitialSyncEndedForAllEnabledTypes() { |
| 345 syncable::ModelTypeSet types; |
| 346 ModelSafeRoutingInfo enabled_types; |
| 347 registrar_->GetModelSafeRoutingInfo(&enabled_types); |
| 348 for (ModelSafeRoutingInfo::const_iterator i = enabled_types.begin(); |
| 349 i != enabled_types.end(); ++i) { |
| 350 types.insert(i->first); |
| 351 } |
| 352 |
| 353 return InitialSyncEndedForTypes(types, &share_); |
| 354 } |
| 355 |
| 356 // SyncEngineEventListener implementation. |
| 357 virtual void OnSyncEngineEvent(const SyncEngineEvent& event); |
| 358 |
| 359 // ServerConnectionEventListener implementation. |
| 360 virtual void OnServerConnectionEvent(const ServerConnectionEvent& event); |
| 361 |
| 362 // JsBackend implementation. |
| 363 virtual void SetJsEventHandler( |
| 364 const WeakHandle<JsEventHandler>& event_handler) OVERRIDE; |
| 365 virtual void ProcessJsMessage( |
| 366 const std::string& name, const JsArgList& args, |
| 367 const WeakHandle<JsReplyHandler>& reply_handler) OVERRIDE; |
| 368 |
| 369 private: |
| 370 struct NotificationInfo { |
| 371 int total_count; |
| 372 std::string payload; |
| 373 |
| 374 NotificationInfo() : total_count(0) {} |
| 375 |
| 376 ~NotificationInfo() {} |
| 377 |
| 378 // Returned pointer owned by the caller. |
| 379 DictionaryValue* ToValue() const { |
| 380 DictionaryValue* value = new DictionaryValue(); |
| 381 value->SetInteger("totalCount", total_count); |
| 382 value->SetString("payload", payload); |
| 383 return value; |
| 384 } |
| 385 }; |
| 386 |
| 387 typedef std::map<syncable::ModelType, NotificationInfo> NotificationInfoMap; |
| 388 typedef JsArgList |
| 389 (SyncManager::SyncInternal::*UnboundJsMessageHandler)(const JsArgList&); |
| 390 typedef base::Callback<JsArgList(JsArgList)> JsMessageHandler; |
| 391 typedef std::map<std::string, JsMessageHandler> JsMessageHandlerMap; |
| 392 |
| 393 // Helper to call OnAuthError when no authentication credentials are |
| 394 // available. |
| 395 void RaiseAuthNeededEvent(); |
| 396 |
| 397 // Determine if the parents or predecessors differ between the old and new |
| 398 // versions of an entry stored in |a| and |b|. Note that a node's index may |
| 399 // change without its NEXT_ID changing if the node at NEXT_ID also moved (but |
| 400 // the relative order is unchanged). To handle such cases, we rely on the |
| 401 // caller to treat a position update on any sibling as updating the positions |
| 402 // of all siblings. |
| 403 static bool VisiblePositionsDiffer( |
| 404 const syncable::EntryKernelMutation& mutation) { |
| 405 const syncable::EntryKernel& a = mutation.original; |
| 406 const syncable::EntryKernel& b = mutation.mutated; |
| 407 // If the datatype isn't one where the browser model cares about position, |
| 408 // don't bother notifying that data model of position-only changes. |
| 409 if (!ShouldMaintainPosition( |
| 410 syncable::GetModelTypeFromSpecifics(b.ref(SPECIFICS)))) |
| 411 return false; |
| 412 if (a.ref(syncable::NEXT_ID) != b.ref(syncable::NEXT_ID)) |
| 413 return true; |
| 414 if (a.ref(syncable::PARENT_ID) != b.ref(syncable::PARENT_ID)) |
| 415 return true; |
| 416 return false; |
| 417 } |
| 418 |
| 419 // Determine if any of the fields made visible to clients of the Sync API |
| 420 // differ between the versions of an entry stored in |a| and |b|. A return |
| 421 // value of false means that it should be OK to ignore this change. |
| 422 static bool VisiblePropertiesDiffer( |
| 423 const syncable::EntryKernelMutation& mutation, |
| 424 Cryptographer* cryptographer) { |
| 425 const syncable::EntryKernel& a = mutation.original; |
| 426 const syncable::EntryKernel& b = mutation.mutated; |
| 427 const sync_pb::EntitySpecifics& a_specifics = a.ref(SPECIFICS); |
| 428 const sync_pb::EntitySpecifics& b_specifics = b.ref(SPECIFICS); |
| 429 DCHECK_EQ(syncable::GetModelTypeFromSpecifics(a_specifics), |
| 430 syncable::GetModelTypeFromSpecifics(b_specifics)); |
| 431 syncable::ModelType model_type = |
| 432 syncable::GetModelTypeFromSpecifics(b_specifics); |
| 433 // Suppress updates to items that aren't tracked by any browser model. |
| 434 if (model_type < syncable::FIRST_REAL_MODEL_TYPE || |
| 435 !a.ref(syncable::UNIQUE_SERVER_TAG).empty()) { |
| 436 return false; |
| 437 } |
| 438 if (a.ref(syncable::IS_DIR) != b.ref(syncable::IS_DIR)) |
| 439 return true; |
| 440 if (!AreSpecificsEqual(cryptographer, |
| 441 a.ref(syncable::SPECIFICS), |
| 442 b.ref(syncable::SPECIFICS))) { |
| 443 return true; |
| 444 } |
| 445 // We only care if the name has changed if neither specifics is encrypted |
| 446 // (encrypted nodes blow away the NON_UNIQUE_NAME). |
| 447 if (!a_specifics.has_encrypted() && !b_specifics.has_encrypted() && |
| 448 a.ref(syncable::NON_UNIQUE_NAME) != b.ref(syncable::NON_UNIQUE_NAME)) |
| 449 return true; |
| 450 if (VisiblePositionsDiffer(mutation)) |
| 451 return true; |
| 452 return false; |
| 453 } |
| 454 |
| 455 bool ChangeBuffersAreEmpty() { |
| 456 for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { |
| 457 if (!change_buffers_[i].IsEmpty()) |
| 458 return false; |
| 459 } |
| 460 return true; |
| 461 } |
| 462 |
| 463 void CheckServerReachable() { |
| 464 if (connection_manager()) { |
| 465 connection_manager()->CheckServerReachable(); |
| 466 } else { |
| 467 NOTREACHED() << "Should be valid connection manager!"; |
| 468 } |
| 469 } |
| 470 |
| 471 void ReEncryptEverything(WriteTransaction* trans); |
| 472 |
| 473 // Initializes (bootstraps) the Cryptographer if NIGORI has finished |
| 474 // initial sync so that it can immediately start encrypting / decrypting. |
| 475 // If the restored key is incompatible with the current version of the NIGORI |
| 476 // node (which could happen if a restart occurred just after an update to |
| 477 // NIGORI was downloaded and the user must enter a new passphrase to decrypt) |
| 478 // then we will raise OnPassphraseRequired and set pending keys for |
| 479 // decryption. Otherwise, the cryptographer is made ready (is_ready()). |
| 480 void BootstrapEncryption(const std::string& restored_key_for_bootstrapping); |
| 481 |
| 482 // Called for every notification. This updates the notification statistics |
| 483 // to be displayed in about:sync. |
| 484 void UpdateNotificationInfo( |
| 485 const syncable::ModelTypePayloadMap& type_payloads); |
| 486 |
| 487 // Checks for server reachabilty and requests a nudge. |
| 488 void OnIPAddressChangedImpl(); |
| 489 |
| 490 // Helper function used only by the constructor. |
| 491 void BindJsMessageHandler( |
| 492 const std::string& name, UnboundJsMessageHandler unbound_message_handler); |
| 493 |
| 494 // Returned pointer is owned by the caller. |
| 495 static DictionaryValue* NotificationInfoToValue( |
| 496 const NotificationInfoMap& notification_info); |
| 497 |
| 498 // JS message handlers. |
| 499 JsArgList GetNotificationState(const JsArgList& args); |
| 500 JsArgList GetNotificationInfo(const JsArgList& args); |
| 501 JsArgList GetRootNodeDetails(const JsArgList& args); |
| 502 JsArgList GetNodeSummariesById(const JsArgList& args); |
| 503 JsArgList GetNodeDetailsById(const JsArgList& args); |
| 504 JsArgList GetChildNodeIds(const JsArgList& args); |
| 505 JsArgList FindNodesContainingString(const JsArgList& args); |
| 506 |
| 507 const std::string name_; |
| 508 |
| 509 base::ThreadChecker thread_checker_; |
| 510 |
| 511 base::WeakPtrFactory<SyncInternal> weak_ptr_factory_; |
| 512 |
| 513 // Thread-safe handle used by |
| 514 // HandleCalculateChangesChangeEventFromSyncApi(), which can be |
| 515 // called from any thread. Valid only between between calls to |
| 516 // Init() and Shutdown(). |
| 517 // |
| 518 // TODO(akalin): Ideally, we wouldn't need to store this; instead, |
| 519 // we'd have another worker class which implements |
| 520 // HandleCalculateChangesChangeEventFromSyncApi() and we'd pass it a |
| 521 // WeakHandle when we construct it. |
| 522 WeakHandle<SyncInternal> weak_handle_this_; |
| 523 |
| 524 // We couple the DirectoryManager and username together in a UserShare member |
| 525 // so we can return a handle to share_ to clients of the API for use when |
| 526 // constructing any transaction type. |
| 527 UserShare share_; |
| 528 |
| 529 // We have to lock around every observers_ access because it can get accessed |
| 530 // from any thread and added to/removed from on the core thread. |
| 531 mutable base::Lock observers_lock_; |
| 532 ObserverList<SyncManager::Observer> observers_; |
| 533 |
| 534 // The ServerConnectionManager used to abstract communication between the |
| 535 // client (the Syncer) and the sync server. |
| 536 scoped_ptr<SyncAPIServerConnectionManager> connection_manager_; |
| 537 |
| 538 // The scheduler that runs the Syncer. Needs to be explicitly |
| 539 // Start()ed. |
| 540 scoped_ptr<SyncScheduler> scheduler_; |
| 541 |
| 542 // The SyncNotifier which notifies us when updates need to be downloaded. |
| 543 scoped_ptr<sync_notifier::SyncNotifier> sync_notifier_; |
| 544 |
| 545 // A multi-purpose status watch object that aggregates stats from various |
| 546 // sync components. |
| 547 AllStatus allstatus_; |
| 548 |
| 549 // Each element of this array is a store of change records produced by |
| 550 // HandleChangeEvent during the CALCULATE_CHANGES step. The changes are |
| 551 // segregated by model type, and are stored here to be processed and |
| 552 // forwarded to the observer slightly later, at the TRANSACTION_ENDING |
| 553 // step by HandleTransactionEndingChangeEvent. The list is cleared in the |
| 554 // TRANSACTION_COMPLETE step by HandleTransactionCompleteChangeEvent. |
| 555 ChangeReorderBuffer change_buffers_[syncable::MODEL_TYPE_COUNT]; |
| 556 |
| 557 // The entity that provides us with information about which types to sync. |
| 558 // The instance is shared between the SyncManager and the Syncer. |
| 559 ModelSafeWorkerRegistrar* registrar_; |
| 560 |
| 561 // Set to true once Init has been called. |
| 562 bool initialized_; |
| 563 |
| 564 // True if the SyncManager should be running in test mode (no sync |
| 565 // scheduler actually communicating with the server). |
| 566 bool setup_for_test_mode_; |
| 567 |
| 568 // Whether we should respond to an IP address change notification. |
| 569 bool observing_ip_address_changes_; |
| 570 |
| 571 // Map used to store the notification info to be displayed in |
| 572 // about:sync page. |
| 573 NotificationInfoMap notification_info_map_; |
| 574 |
| 575 // These are for interacting with chrome://sync-internals. |
| 576 JsMessageHandlerMap js_message_handlers_; |
| 577 WeakHandle<JsEventHandler> js_event_handler_; |
| 578 JsSyncManagerObserver js_sync_manager_observer_; |
| 579 JsTransactionObserver js_transaction_observer_; |
| 580 }; |
| 581 const int SyncManager::SyncInternal::kDefaultNudgeDelayMilliseconds = 200; |
| 582 const int SyncManager::SyncInternal::kPreferencesNudgeDelayMilliseconds = 2000; |
| 583 |
| 584 SyncManager::Observer::~Observer() {} |
| 585 |
| 586 SyncManager::SyncManager(const std::string& name) |
| 587 : data_(new SyncInternal(name)) {} |
| 588 |
| 589 SyncManager::Status::Status() |
| 590 : summary(INVALID), |
| 591 authenticated(false), |
| 592 server_up(false), |
| 593 server_reachable(false), |
| 594 server_broken(false), |
| 595 notifications_enabled(false), |
| 596 notifications_received(0), |
| 597 notifiable_commits(0), |
| 598 max_consecutive_errors(0), |
| 599 unsynced_count(0), |
| 600 conflicting_count(0), |
| 601 syncing(false), |
| 602 initial_sync_ended(false), |
| 603 syncer_stuck(false), |
| 604 updates_available(0), |
| 605 updates_received(0), |
| 606 tombstone_updates_received(0), |
| 607 disk_full(false), |
| 608 num_local_overwrites_total(0), |
| 609 num_server_overwrites_total(0), |
| 610 nonempty_get_updates(0), |
| 611 empty_get_updates(0), |
| 612 useless_sync_cycles(0), |
| 613 useful_sync_cycles(0), |
| 614 cryptographer_ready(false), |
| 615 crypto_has_pending_keys(false) { |
| 616 } |
| 617 |
| 618 SyncManager::Status::~Status() { |
| 619 } |
| 620 |
| 621 bool SyncManager::Init( |
| 622 const FilePath& database_location, |
| 623 const WeakHandle<JsEventHandler>& event_handler, |
| 624 const std::string& sync_server_and_path, |
| 625 int sync_server_port, |
| 626 bool use_ssl, |
| 627 HttpPostProviderFactory* post_factory, |
| 628 ModelSafeWorkerRegistrar* registrar, |
| 629 const std::string& user_agent, |
| 630 const SyncCredentials& credentials, |
| 631 sync_notifier::SyncNotifier* sync_notifier, |
| 632 const std::string& restored_key_for_bootstrapping, |
| 633 bool setup_for_test_mode) { |
| 634 DCHECK(post_factory); |
| 635 VLOG(1) << "SyncManager starting Init..."; |
| 636 string server_string(sync_server_and_path); |
| 637 return data_->Init(database_location, |
| 638 event_handler, |
| 639 server_string, |
| 640 sync_server_port, |
| 641 use_ssl, |
| 642 post_factory, |
| 643 registrar, |
| 644 user_agent, |
| 645 credentials, |
| 646 sync_notifier, |
| 647 restored_key_for_bootstrapping, |
| 648 setup_for_test_mode); |
| 649 } |
| 650 |
| 651 void SyncManager::UpdateCredentials(const SyncCredentials& credentials) { |
| 652 data_->UpdateCredentials(credentials); |
| 653 } |
| 654 |
| 655 void SyncManager::UpdateEnabledTypes() { |
| 656 data_->UpdateEnabledTypes(); |
| 657 } |
| 658 |
| 659 bool SyncManager::InitialSyncEndedForAllEnabledTypes() { |
| 660 return data_->InitialSyncEndedForAllEnabledTypes(); |
| 661 } |
| 662 |
| 663 void SyncManager::StartSyncingNormally() { |
| 664 data_->StartSyncingNormally(); |
| 665 } |
| 666 |
| 667 void SyncManager::SetPassphrase(const std::string& passphrase, |
| 668 bool is_explicit) { |
| 669 data_->SetPassphrase(passphrase, is_explicit); |
| 670 } |
| 671 |
| 672 void SyncManager::EncryptDataTypes( |
| 673 const syncable::ModelTypeSet& encrypted_types) { |
| 674 data_->EncryptDataTypes(encrypted_types); |
| 675 } |
| 676 |
| 677 bool SyncManager::IsUsingExplicitPassphrase() { |
| 678 return data_ && data_->IsUsingExplicitPassphrase(); |
| 679 } |
| 680 |
| 681 void SyncManager::RequestCleanupDisabledTypes() { |
| 682 if (data_->scheduler()) |
| 683 data_->scheduler()->ScheduleCleanupDisabledTypes(); |
| 684 } |
| 685 |
| 686 void SyncManager::RequestClearServerData() { |
| 687 if (data_->scheduler()) |
| 688 data_->scheduler()->ScheduleClearUserData(); |
| 689 } |
| 690 |
| 691 void SyncManager::RequestConfig(const syncable::ModelTypeBitSet& types, |
| 692 ConfigureReason reason) { |
| 693 if (!data_->scheduler()) { |
| 694 LOG(INFO) |
| 695 << "SyncManager::RequestConfig: bailing out because scheduler is " |
| 696 << "null"; |
| 697 return; |
| 698 } |
| 699 StartConfigurationMode(NULL); |
| 700 data_->scheduler()->ScheduleConfig(types, reason); |
| 701 } |
| 702 |
| 703 void SyncManager::StartConfigurationMode(ModeChangeCallback* callback) { |
| 704 if (!data_->scheduler()) { |
| 705 LOG(INFO) |
| 706 << "SyncManager::StartConfigurationMode: could not start " |
| 707 << "configuration mode because because scheduler is null"; |
| 708 return; |
| 709 } |
| 710 data_->scheduler()->Start( |
| 711 browser_sync::SyncScheduler::CONFIGURATION_MODE, callback); |
| 712 } |
| 713 |
| 714 const std::string& SyncManager::GetAuthenticatedUsername() { |
| 715 DCHECK(data_); |
| 716 return data_->username_for_share(); |
| 717 } |
| 718 |
| 719 bool SyncManager::SyncInternal::Init( |
| 720 const FilePath& database_location, |
| 721 const WeakHandle<JsEventHandler>& event_handler, |
| 722 const std::string& sync_server_and_path, |
| 723 int port, |
| 724 bool use_ssl, |
| 725 HttpPostProviderFactory* post_factory, |
| 726 ModelSafeWorkerRegistrar* model_safe_worker_registrar, |
| 727 const std::string& user_agent, |
| 728 const SyncCredentials& credentials, |
| 729 sync_notifier::SyncNotifier* sync_notifier, |
| 730 const std::string& restored_key_for_bootstrapping, |
| 731 bool setup_for_test_mode) { |
| 732 CHECK(!initialized_); |
| 733 |
| 734 DCHECK(thread_checker_.CalledOnValidThread()); |
| 735 |
| 736 VLOG(1) << "Starting SyncInternal initialization."; |
| 737 |
| 738 weak_handle_this_ = MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()); |
| 739 |
| 740 registrar_ = model_safe_worker_registrar; |
| 741 setup_for_test_mode_ = setup_for_test_mode; |
| 742 |
| 743 sync_notifier_.reset(sync_notifier); |
| 744 |
| 745 AddObserver(&js_sync_manager_observer_); |
| 746 SetJsEventHandler(event_handler); |
| 747 |
| 748 share_.dir_manager.reset(new DirectoryManager(database_location)); |
| 749 |
| 750 connection_manager_.reset(new SyncAPIServerConnectionManager( |
| 751 sync_server_and_path, port, use_ssl, user_agent, post_factory)); |
| 752 |
| 753 net::NetworkChangeNotifier::AddIPAddressObserver(this); |
| 754 observing_ip_address_changes_ = true; |
| 755 |
| 756 connection_manager()->AddListener(this); |
| 757 |
| 758 // TODO(akalin): CheckServerReachable() can block, which may cause jank if we |
| 759 // try to shut down sync. Fix this. |
| 760 MessageLoop::current()->PostTask( |
| 761 FROM_HERE, base::Bind(&SyncInternal::CheckServerReachable, |
| 762 weak_ptr_factory_.GetWeakPtr())); |
| 763 |
| 764 // Test mode does not use a syncer context or syncer thread. |
| 765 if (!setup_for_test_mode_) { |
| 766 // Build a SyncSessionContext and store the worker in it. |
| 767 VLOG(1) << "Sync is bringing up SyncSessionContext."; |
| 768 std::vector<SyncEngineEventListener*> listeners; |
| 769 listeners.push_back(&allstatus_); |
| 770 listeners.push_back(this); |
| 771 SyncSessionContext* context = new SyncSessionContext( |
| 772 connection_manager_.get(), |
| 773 dir_manager(), |
| 774 model_safe_worker_registrar, |
| 775 listeners); |
| 776 context->set_account_name(credentials.email); |
| 777 // The SyncScheduler takes ownership of |context|. |
| 778 scheduler_.reset(new SyncScheduler(name_, context, new Syncer())); |
| 779 } |
| 780 |
| 781 bool signed_in = SignIn(credentials); |
| 782 |
| 783 if (signed_in && scheduler()) { |
| 784 scheduler()->Start( |
| 785 browser_sync::SyncScheduler::CONFIGURATION_MODE, NULL); |
| 786 } |
| 787 |
| 788 initialized_ = true; |
| 789 |
| 790 // Notify that initialization is complete. |
| 791 ObserverList<SyncManager::Observer> temp_obs_list; |
| 792 CopyObservers(&temp_obs_list); |
| 793 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 794 OnInitializationComplete( |
| 795 WeakHandle<JsBackend>(weak_ptr_factory_.GetWeakPtr()))); |
| 796 |
| 797 // The following calls check that initialized_ is true. |
| 798 |
| 799 BootstrapEncryption(restored_key_for_bootstrapping); |
| 800 |
| 801 sync_notifier_->AddObserver(this); |
| 802 |
| 803 return signed_in; |
| 804 } |
| 805 |
| 806 void SyncManager::SyncInternal::BootstrapEncryption( |
| 807 const std::string& restored_key_for_bootstrapping) { |
| 808 // Cryptographer should only be accessed while holding a transaction. |
| 809 ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 810 Cryptographer* cryptographer = trans.GetCryptographer(); |
| 811 |
| 812 // Set the bootstrap token before bailing out if nigori node is not there. |
| 813 // This could happen if server asked us to migrate nigri. |
| 814 cryptographer->Bootstrap(restored_key_for_bootstrapping); |
| 815 } |
| 816 |
| 817 bool SyncManager::SyncInternal::UpdateCryptographerFromNigori() { |
| 818 DCHECK(initialized_); |
| 819 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 820 if (!lookup.good()) { |
| 821 NOTREACHED() << "BootstrapEncryption: lookup not good so bailing out"; |
| 822 return false; |
| 823 } |
| 824 if (!lookup->initial_sync_ended_for_type(syncable::NIGORI)) |
| 825 return false; // Should only happen during first time sync. |
| 826 |
| 827 ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 828 Cryptographer* cryptographer = trans.GetCryptographer(); |
| 829 |
| 830 ReadNode node(&trans); |
| 831 if (!node.InitByTagLookup(kNigoriTag)) { |
| 832 NOTREACHED(); |
| 833 return false; |
| 834 } |
| 835 Cryptographer::UpdateResult result = |
| 836 cryptographer->Update(node.GetNigoriSpecifics()); |
| 837 if (result == Cryptographer::NEEDS_PASSPHRASE) { |
| 838 ObserverList<SyncManager::Observer> temp_obs_list; |
| 839 CopyObservers(&temp_obs_list); |
| 840 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 841 OnPassphraseRequired(sync_api::REASON_DECRYPTION)); |
| 842 } |
| 843 |
| 844 allstatus_.SetCryptographerReady(cryptographer->is_ready()); |
| 845 allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys()); |
| 846 |
| 847 return cryptographer->is_ready(); |
| 848 } |
| 849 |
| 850 void SyncManager::SyncInternal::StartSyncingNormally() { |
| 851 // Start the sync scheduler. This won't actually result in any |
| 852 // syncing until at least the DirectoryManager broadcasts the OPENED |
| 853 // event, and a valid server connection is detected. |
| 854 if (scheduler()) // NULL during certain unittests. |
| 855 scheduler()->Start(SyncScheduler::NORMAL_MODE, NULL); |
| 856 } |
| 857 |
| 858 bool SyncManager::SyncInternal::OpenDirectory() { |
| 859 DCHECK(!initialized_) << "Should only happen once"; |
| 860 |
| 861 bool share_opened = dir_manager()->Open(username_for_share(), this); |
| 862 DCHECK(share_opened); |
| 863 if (!share_opened) { |
| 864 ObserverList<SyncManager::Observer> temp_obs_list; |
| 865 CopyObservers(&temp_obs_list); |
| 866 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 867 OnStopSyncingPermanently()); |
| 868 |
| 869 LOG(ERROR) << "Could not open share for:" << username_for_share(); |
| 870 return false; |
| 871 } |
| 872 |
| 873 // Database has to be initialized for the guid to be available. |
| 874 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 875 if (!lookup.good()) { |
| 876 NOTREACHED(); |
| 877 return false; |
| 878 } |
| 879 |
| 880 connection_manager()->set_client_id(lookup->cache_guid()); |
| 881 lookup->AddTransactionObserver(&js_transaction_observer_); |
| 882 return true; |
| 883 } |
| 884 |
| 885 bool SyncManager::SyncInternal::SignIn(const SyncCredentials& credentials) { |
| 886 DCHECK(thread_checker_.CalledOnValidThread()); |
| 887 DCHECK(share_.name.empty()); |
| 888 share_.name = credentials.email; |
| 889 |
| 890 VLOG(1) << "Signing in user: " << username_for_share(); |
| 891 if (!OpenDirectory()) |
| 892 return false; |
| 893 |
| 894 // Retrieve and set the sync notifier state. This should be done |
| 895 // only after OpenDirectory is called. |
| 896 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 897 std::string unique_id; |
| 898 std::string state; |
| 899 if (lookup.good()) { |
| 900 unique_id = lookup->cache_guid(); |
| 901 state = lookup->GetNotificationState(); |
| 902 VLOG(1) << "Read notification unique ID: " << unique_id; |
| 903 if (VLOG_IS_ON(1)) { |
| 904 std::string encoded_state; |
| 905 base::Base64Encode(state, &encoded_state); |
| 906 VLOG(1) << "Read notification state: " << encoded_state; |
| 907 } |
| 908 } else { |
| 909 LOG(ERROR) << "Could not read notification unique ID/state"; |
| 910 } |
| 911 sync_notifier_->SetUniqueId(unique_id); |
| 912 sync_notifier_->SetState(state); |
| 913 |
| 914 UpdateCredentials(credentials); |
| 915 UpdateEnabledTypes(); |
| 916 return true; |
| 917 } |
| 918 |
| 919 void SyncManager::SyncInternal::UpdateCredentials( |
| 920 const SyncCredentials& credentials) { |
| 921 DCHECK(thread_checker_.CalledOnValidThread()); |
| 922 DCHECK_EQ(credentials.email, share_.name); |
| 923 DCHECK(!credentials.email.empty()); |
| 924 DCHECK(!credentials.sync_token.empty()); |
| 925 |
| 926 observing_ip_address_changes_ = true; |
| 927 if (connection_manager()->set_auth_token(credentials.sync_token)) { |
| 928 sync_notifier_->UpdateCredentials( |
| 929 credentials.email, credentials.sync_token); |
| 930 if (!setup_for_test_mode_) { |
| 931 CheckServerReachable(); |
| 932 } |
| 933 } |
| 934 } |
| 935 |
| 936 void SyncManager::SyncInternal::UpdateEnabledTypes() { |
| 937 DCHECK(thread_checker_.CalledOnValidThread()); |
| 938 ModelSafeRoutingInfo routes; |
| 939 registrar_->GetModelSafeRoutingInfo(&routes); |
| 940 syncable::ModelTypeSet enabled_types; |
| 941 for (ModelSafeRoutingInfo::const_iterator it = routes.begin(); |
| 942 it != routes.end(); ++it) { |
| 943 enabled_types.insert(it->first); |
| 944 } |
| 945 sync_notifier_->UpdateEnabledTypes(enabled_types); |
| 946 } |
| 947 |
| 948 void SyncManager::SyncInternal::RaiseAuthNeededEvent() { |
| 949 ObserverList<SyncManager::Observer> temp_obs_list; |
| 950 CopyObservers(&temp_obs_list); |
| 951 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 952 OnAuthError(AuthError(AuthError::INVALID_GAIA_CREDENTIALS))); |
| 953 } |
| 954 |
| 955 void SyncManager::SyncInternal::SetPassphrase( |
| 956 const std::string& passphrase, bool is_explicit) { |
| 957 // We do not accept empty passphrases. |
| 958 if (passphrase.empty()) { |
| 959 VLOG(1) << "Rejecting empty passphrase."; |
| 960 ObserverList<SyncManager::Observer> temp_obs_list; |
| 961 CopyObservers(&temp_obs_list); |
| 962 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 963 OnPassphraseRequired(sync_api::REASON_SET_PASSPHRASE_FAILED)); |
| 964 return; |
| 965 } |
| 966 |
| 967 // All accesses to the cryptographer are protected by a transaction. |
| 968 WriteTransaction trans(FROM_HERE, GetUserShare()); |
| 969 Cryptographer* cryptographer = trans.GetCryptographer(); |
| 970 KeyParams params = {"localhost", "dummy", passphrase}; |
| 971 |
| 972 WriteNode node(&trans); |
| 973 if (!node.InitByTagLookup(kNigoriTag)) { |
| 974 // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. |
| 975 NOTREACHED(); |
| 976 return; |
| 977 } |
| 978 |
| 979 if (cryptographer->has_pending_keys()) { |
| 980 bool suceeded = false; |
| 981 |
| 982 // See if the explicit flag matches what is set in nigori. If not we dont |
| 983 // even try the passphrase. Note: This could mean that we wont try setting |
| 984 // the gaia password as passphrase if custom is elected by the user. Which |
| 985 // is fine because nigori node has all the old passwords in it. |
| 986 if (node.GetNigoriSpecifics().using_explicit_passphrase() == is_explicit) { |
| 987 if (cryptographer->DecryptPendingKeys(params)) { |
| 988 suceeded = true; |
| 989 } else { |
| 990 VLOG(1) << "Passphrase failed to decrypt pending keys."; |
| 991 } |
| 992 } else { |
| 993 VLOG(1) << "Not trying the passphrase because the explicit flags dont " |
| 994 << "match. Nigori node's explicit flag is " |
| 995 << node.GetNigoriSpecifics().using_explicit_passphrase(); |
| 996 } |
| 997 |
| 998 if (!suceeded) { |
| 999 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1000 CopyObservers(&temp_obs_list); |
| 1001 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1002 OnPassphraseRequired(sync_api::REASON_SET_PASSPHRASE_FAILED)); |
| 1003 return; |
| 1004 } |
| 1005 |
| 1006 // Nudge the syncer so that encrypted datatype updates that were waiting for |
| 1007 // this passphrase get applied as soon as possible. |
| 1008 RequestNudge(FROM_HERE); |
| 1009 } else { |
| 1010 VLOG(1) << "No pending keys, adding provided passphrase."; |
| 1011 |
| 1012 // Prevent an implicit SetPassphrase request from changing an explicitly |
| 1013 // set passphrase. |
| 1014 if (!is_explicit && node.GetNigoriSpecifics().using_explicit_passphrase()) |
| 1015 return; |
| 1016 |
| 1017 cryptographer->AddKey(params); |
| 1018 |
| 1019 // TODO(tim): Bug 58231. It would be nice if SetPassphrase didn't require |
| 1020 // messing with the Nigori node, because we can't call SetPassphrase until |
| 1021 // download conditions are met vs Cryptographer init. It seems like it's |
| 1022 // safe to defer this work. |
| 1023 sync_pb::NigoriSpecifics specifics(node.GetNigoriSpecifics()); |
| 1024 specifics.clear_encrypted(); |
| 1025 cryptographer->GetKeys(specifics.mutable_encrypted()); |
| 1026 specifics.set_using_explicit_passphrase(is_explicit); |
| 1027 node.SetNigoriSpecifics(specifics); |
| 1028 ReEncryptEverything(&trans); |
| 1029 } |
| 1030 |
| 1031 VLOG(1) << "Passphrase accepted, bootstrapping encryption."; |
| 1032 std::string bootstrap_token; |
| 1033 cryptographer->GetBootstrapToken(&bootstrap_token); |
| 1034 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1035 CopyObservers(&temp_obs_list); |
| 1036 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1037 OnPassphraseAccepted(bootstrap_token)); |
| 1038 } |
| 1039 |
| 1040 bool SyncManager::SyncInternal::IsUsingExplicitPassphrase() { |
| 1041 ReadTransaction trans(FROM_HERE, &share_); |
| 1042 ReadNode node(&trans); |
| 1043 if (!node.InitByTagLookup(kNigoriTag)) { |
| 1044 // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. |
| 1045 NOTREACHED(); |
| 1046 return false; |
| 1047 } |
| 1048 |
| 1049 return node.GetNigoriSpecifics().using_explicit_passphrase(); |
| 1050 } |
| 1051 |
| 1052 void SyncManager::SyncInternal::EncryptDataTypes( |
| 1053 const syncable::ModelTypeSet& encrypted_types) { |
| 1054 DCHECK(initialized_); |
| 1055 VLOG(1) << "Attempting to encrypt datatypes " |
| 1056 << syncable::ModelTypeSetToString(encrypted_types); |
| 1057 |
| 1058 WriteTransaction trans(FROM_HERE, GetUserShare()); |
| 1059 WriteNode node(&trans); |
| 1060 if (!node.InitByTagLookup(kNigoriTag)) { |
| 1061 NOTREACHED() << "Unable to set encrypted datatypes because Nigori node not " |
| 1062 << "found."; |
| 1063 return; |
| 1064 } |
| 1065 |
| 1066 Cryptographer* cryptographer = trans.GetCryptographer(); |
| 1067 |
| 1068 if (!cryptographer->is_initialized()) { |
| 1069 VLOG(1) << "Attempting to encrypt datatypes when cryptographer not " |
| 1070 << "initialized, prompting for passphrase."; |
| 1071 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1072 CopyObservers(&temp_obs_list); |
| 1073 // TODO(zea): this isn't really decryption, but that's the only way we have |
| 1074 // to prompt the user for a passsphrase. See http://crbug.com/91379. |
| 1075 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1076 OnPassphraseRequired(sync_api::REASON_DECRYPTION)); |
| 1077 return; |
| 1078 } |
| 1079 |
| 1080 // Update the Nigori node's set of encrypted datatypes. |
| 1081 // Note, we merge the current encrypted types with those requested. Once a |
| 1082 // datatypes is marked as needing encryption, it is never unmarked. |
| 1083 sync_pb::NigoriSpecifics nigori; |
| 1084 nigori.CopyFrom(node.GetNigoriSpecifics()); |
| 1085 syncable::ModelTypeSet current_encrypted_types = GetEncryptedTypes(&trans); |
| 1086 syncable::ModelTypeSet newly_encrypted_types; |
| 1087 std::set_union(current_encrypted_types.begin(), current_encrypted_types.end(), |
| 1088 encrypted_types.begin(), encrypted_types.end(), |
| 1089 std::inserter(newly_encrypted_types, |
| 1090 newly_encrypted_types.begin())); |
| 1091 allstatus_.SetEncryptedTypes(newly_encrypted_types); |
| 1092 if (newly_encrypted_types == current_encrypted_types) { |
| 1093 // Set of encrypted types has not changed, just notify and return. |
| 1094 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1095 CopyObservers(&temp_obs_list); |
| 1096 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1097 OnEncryptionComplete(current_encrypted_types)); |
| 1098 return; |
| 1099 } |
| 1100 syncable::FillNigoriEncryptedTypes(newly_encrypted_types, &nigori); |
| 1101 node.SetNigoriSpecifics(nigori); |
| 1102 |
| 1103 cryptographer->SetEncryptedTypes(nigori); |
| 1104 |
| 1105 // TODO(zea): only reencrypt this datatype? ReEncrypting everything is a |
| 1106 // safer approach, and should not impact anything that is already encrypted |
| 1107 // (redundant changes are ignored). |
| 1108 ReEncryptEverything(&trans); |
| 1109 return; |
| 1110 } |
| 1111 |
| 1112 // TODO(zea): Add unit tests that ensure no sync changes are made when not |
| 1113 // needed. |
| 1114 void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) { |
| 1115 syncable::ModelTypeSet encrypted_types = |
| 1116 GetEncryptedTypes(trans); |
| 1117 ModelSafeRoutingInfo routes; |
| 1118 registrar_->GetModelSafeRoutingInfo(&routes); |
| 1119 std::string tag; |
| 1120 for (syncable::ModelTypeSet::iterator iter = encrypted_types.begin(); |
| 1121 iter != encrypted_types.end(); ++iter) { |
| 1122 if (*iter == syncable::PASSWORDS || routes.count(*iter) == 0) |
| 1123 continue; |
| 1124 ReadNode type_root(trans); |
| 1125 tag = syncable::ModelTypeToRootTag(*iter); |
| 1126 if (!type_root.InitByTagLookup(tag)) { |
| 1127 NOTREACHED(); |
| 1128 return; |
| 1129 } |
| 1130 |
| 1131 // Iterate through all children of this datatype. |
| 1132 std::queue<int64> to_visit; |
| 1133 int64 child_id = type_root.GetFirstChildId(); |
| 1134 to_visit.push(child_id); |
| 1135 while (!to_visit.empty()) { |
| 1136 child_id = to_visit.front(); |
| 1137 to_visit.pop(); |
| 1138 if (child_id == kInvalidId) |
| 1139 continue; |
| 1140 |
| 1141 WriteNode child(trans); |
| 1142 if (!child.InitByIdLookup(child_id)) { |
| 1143 NOTREACHED(); |
| 1144 continue; |
| 1145 } |
| 1146 if (child.GetIsFolder()) { |
| 1147 to_visit.push(child.GetFirstChildId()); |
| 1148 } |
| 1149 if (child.GetEntry()->Get(syncable::UNIQUE_SERVER_TAG).empty()) { |
| 1150 // Rewrite the specifics of the node with encrypted data if necessary |
| 1151 // (only rewrite the non-unique folders). |
| 1152 child.ResetFromSpecifics(); |
| 1153 } |
| 1154 to_visit.push(child.GetSuccessorId()); |
| 1155 } |
| 1156 } |
| 1157 |
| 1158 if (routes.count(syncable::PASSWORDS) > 0) { |
| 1159 // Passwords are encrypted with their own legacy scheme. |
| 1160 ReadNode passwords_root(trans); |
| 1161 std::string passwords_tag = |
| 1162 syncable::ModelTypeToRootTag(syncable::PASSWORDS); |
| 1163 // It's possible we'll have the password routing info and not the password |
| 1164 // root if we attempted to SetPassphrase before passwords was enabled. |
| 1165 if (passwords_root.InitByTagLookup(passwords_tag)) { |
| 1166 int64 child_id = passwords_root.GetFirstChildId(); |
| 1167 while (child_id != kInvalidId) { |
| 1168 WriteNode child(trans); |
| 1169 if (!child.InitByIdLookup(child_id)) { |
| 1170 NOTREACHED(); |
| 1171 return; |
| 1172 } |
| 1173 child.SetPasswordSpecifics(child.GetPasswordSpecifics()); |
| 1174 child_id = child.GetSuccessorId(); |
| 1175 } |
| 1176 } |
| 1177 } |
| 1178 |
| 1179 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1180 CopyObservers(&temp_obs_list); |
| 1181 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1182 OnEncryptionComplete(encrypted_types)); |
| 1183 } |
| 1184 |
| 1185 SyncManager::~SyncManager() { |
| 1186 delete data_; |
| 1187 } |
| 1188 |
| 1189 void SyncManager::AddObserver(Observer* observer) { |
| 1190 data_->AddObserver(observer); |
| 1191 } |
| 1192 |
| 1193 void SyncManager::RemoveObserver(Observer* observer) { |
| 1194 data_->RemoveObserver(observer); |
| 1195 } |
| 1196 |
| 1197 void SyncManager::RequestEarlyExit() { |
| 1198 data_->RequestEarlyExit(); |
| 1199 } |
| 1200 |
| 1201 void SyncManager::SyncInternal::RequestEarlyExit() { |
| 1202 if (scheduler()) { |
| 1203 scheduler()->RequestEarlyExit(); |
| 1204 } |
| 1205 } |
| 1206 |
| 1207 void SyncManager::Shutdown() { |
| 1208 data_->Shutdown(); |
| 1209 } |
| 1210 |
| 1211 void SyncManager::SyncInternal::Shutdown() { |
| 1212 DCHECK(thread_checker_.CalledOnValidThread()); |
| 1213 |
| 1214 // Prevent any in-flight method calls from running. Also |
| 1215 // invalidates |weak_handle_this_|. |
| 1216 weak_ptr_factory_.InvalidateWeakPtrs(); |
| 1217 |
| 1218 // Automatically stops the scheduler. |
| 1219 scheduler_.reset(); |
| 1220 |
| 1221 SetJsEventHandler(WeakHandle<JsEventHandler>()); |
| 1222 RemoveObserver(&js_sync_manager_observer_); |
| 1223 |
| 1224 if (sync_notifier_.get()) { |
| 1225 sync_notifier_->RemoveObserver(this); |
| 1226 } |
| 1227 sync_notifier_.reset(); |
| 1228 |
| 1229 if (connection_manager_.get()) { |
| 1230 connection_manager_->RemoveListener(this); |
| 1231 } |
| 1232 connection_manager_.reset(); |
| 1233 |
| 1234 net::NetworkChangeNotifier::RemoveIPAddressObserver(this); |
| 1235 observing_ip_address_changes_ = false; |
| 1236 |
| 1237 if (dir_manager()) { |
| 1238 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 1239 if (lookup.good()) { |
| 1240 lookup->RemoveTransactionObserver(&js_transaction_observer_); |
| 1241 } else { |
| 1242 NOTREACHED(); |
| 1243 } |
| 1244 dir_manager()->FinalSaveChangesForAll(); |
| 1245 dir_manager()->Close(username_for_share()); |
| 1246 } |
| 1247 |
| 1248 // Reset the DirectoryManager and UserSettings so they relinquish sqlite |
| 1249 // handles to backing files. |
| 1250 share_.dir_manager.reset(); |
| 1251 |
| 1252 setup_for_test_mode_ = false; |
| 1253 registrar_ = NULL; |
| 1254 |
| 1255 initialized_ = false; |
| 1256 |
| 1257 // We reset this here, since only now we know it will not be |
| 1258 // accessed from other threads (since we shut down everything). |
| 1259 weak_handle_this_.Reset(); |
| 1260 } |
| 1261 |
| 1262 void SyncManager::SyncInternal::OnIPAddressChanged() { |
| 1263 VLOG(1) << "IP address change detected"; |
| 1264 if (!observing_ip_address_changes_) { |
| 1265 VLOG(1) << "IP address change dropped."; |
| 1266 return; |
| 1267 } |
| 1268 |
| 1269 #if defined (OS_CHROMEOS) |
| 1270 // TODO(tim): This is a hack to intentionally lose a race with flimflam at |
| 1271 // shutdown, so we don't cause shutdown to wait for our http request. |
| 1272 // http://crosbug.com/8429 |
| 1273 MessageLoop::current()->PostDelayedTask( |
| 1274 FROM_HERE, |
| 1275 base::Bind(&SyncInternal::OnIPAddressChangedImpl, |
| 1276 weak_ptr_factory_.GetWeakPtr()), |
| 1277 kChromeOSNetworkChangeReactionDelayHackMsec); |
| 1278 #else |
| 1279 OnIPAddressChangedImpl(); |
| 1280 #endif // defined(OS_CHROMEOS) |
| 1281 } |
| 1282 |
| 1283 void SyncManager::SyncInternal::OnIPAddressChangedImpl() { |
| 1284 // TODO(akalin): CheckServerReachable() can block, which may cause |
| 1285 // jank if we try to shut down sync. Fix this. |
| 1286 connection_manager()->CheckServerReachable(); |
| 1287 } |
| 1288 |
| 1289 void SyncManager::SyncInternal::OnServerConnectionEvent( |
| 1290 const ServerConnectionEvent& event) { |
| 1291 allstatus_.HandleServerConnectionEvent(event); |
| 1292 if (event.connection_code == |
| 1293 browser_sync::HttpResponse::SERVER_CONNECTION_OK) { |
| 1294 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1295 CopyObservers(&temp_obs_list); |
| 1296 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1297 OnAuthError(AuthError::None())); |
| 1298 } |
| 1299 |
| 1300 if (event.connection_code == browser_sync::HttpResponse::SYNC_AUTH_ERROR) { |
| 1301 observing_ip_address_changes_ = false; |
| 1302 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1303 CopyObservers(&temp_obs_list); |
| 1304 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1305 OnAuthError(AuthError(AuthError::INVALID_GAIA_CREDENTIALS))); |
| 1306 } |
| 1307 |
| 1308 if (event.connection_code == |
| 1309 browser_sync::HttpResponse::SYNC_SERVER_ERROR) { |
| 1310 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1311 CopyObservers(&temp_obs_list); |
| 1312 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1313 OnAuthError(AuthError(AuthError::CONNECTION_FAILED))); |
| 1314 } |
| 1315 } |
| 1316 |
| 1317 void SyncManager::SyncInternal::HandleTransactionCompleteChangeEvent( |
| 1318 const syncable::ModelTypeBitSet& models_with_changes) { |
| 1319 // This notification happens immediately after the transaction mutex is |
| 1320 // released. This allows work to be performed without blocking other threads |
| 1321 // from acquiring a transaction. |
| 1322 if (!HaveObservers()) |
| 1323 return; |
| 1324 |
| 1325 // Call commit. |
| 1326 for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { |
| 1327 if (models_with_changes.test(i)) { |
| 1328 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1329 CopyObservers(&temp_obs_list); |
| 1330 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1331 OnChangesComplete(syncable::ModelTypeFromInt(i))); |
| 1332 } |
| 1333 } |
| 1334 } |
| 1335 |
| 1336 ModelTypeBitSet SyncManager::SyncInternal::HandleTransactionEndingChangeEvent( |
| 1337 syncable::BaseTransaction* trans) { |
| 1338 // This notification happens immediately before a syncable WriteTransaction |
| 1339 // falls out of scope. It happens while the channel mutex is still held, |
| 1340 // and while the transaction mutex is held, so it cannot be re-entrant. |
| 1341 if (!HaveObservers() || ChangeBuffersAreEmpty()) |
| 1342 return ModelTypeBitSet(); |
| 1343 |
| 1344 // This will continue the WriteTransaction using a read only wrapper. |
| 1345 // This is the last chance for read to occur in the WriteTransaction |
| 1346 // that's closing. This special ReadTransaction will not close the |
| 1347 // underlying transaction. |
| 1348 ReadTransaction read_trans(GetUserShare(), trans); |
| 1349 |
| 1350 syncable::ModelTypeBitSet models_with_changes; |
| 1351 for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { |
| 1352 if (change_buffers_[i].IsEmpty()) |
| 1353 continue; |
| 1354 |
| 1355 vector<ChangeRecord> ordered_changes; |
| 1356 change_buffers_[i].GetAllChangesInTreeOrder(&read_trans, &ordered_changes); |
| 1357 if (!ordered_changes.empty()) { |
| 1358 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1359 CopyObservers(&temp_obs_list); |
| 1360 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1361 OnChangesApplied(syncable::ModelTypeFromInt(i), &read_trans, |
| 1362 &ordered_changes[0], ordered_changes.size())); |
| 1363 models_with_changes.set(i, true); |
| 1364 } |
| 1365 change_buffers_[i].Clear(); |
| 1366 } |
| 1367 return models_with_changes; |
| 1368 } |
| 1369 |
| 1370 void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncApi( |
| 1371 const EntryKernelMutationSet& mutations, |
| 1372 syncable::BaseTransaction* trans) { |
| 1373 if (!scheduler()) { |
| 1374 return; |
| 1375 } |
| 1376 |
| 1377 // We have been notified about a user action changing a sync model. |
| 1378 LOG_IF(WARNING, !ChangeBuffersAreEmpty()) << |
| 1379 "CALCULATE_CHANGES called with unapplied old changes."; |
| 1380 |
| 1381 // The mutated model type, or UNSPECIFIED if nothing was mutated. |
| 1382 syncable::ModelType mutated_model_type = syncable::UNSPECIFIED; |
| 1383 |
| 1384 // Find the first real mutation. We assume that only a single model |
| 1385 // type is mutated per transaction. |
| 1386 for (syncable::EntryKernelMutationSet::const_iterator it = |
| 1387 mutations.begin(); it != mutations.end(); ++it) { |
| 1388 if (!it->mutated.ref(syncable::IS_UNSYNCED)) { |
| 1389 continue; |
| 1390 } |
| 1391 |
| 1392 syncable::ModelType model_type = |
| 1393 syncable::GetModelTypeFromSpecifics(it->mutated.ref(SPECIFICS)); |
| 1394 if (model_type < syncable::FIRST_REAL_MODEL_TYPE) { |
| 1395 NOTREACHED() << "Permanent or underspecified item changed via syncapi."; |
| 1396 continue; |
| 1397 } |
| 1398 |
| 1399 // Found real mutation. |
| 1400 if (mutated_model_type == syncable::UNSPECIFIED) { |
| 1401 mutated_model_type = model_type; |
| 1402 break; |
| 1403 } |
| 1404 } |
| 1405 |
| 1406 // Nudge if necessary. |
| 1407 if (mutated_model_type != syncable::UNSPECIFIED) { |
| 1408 if (weak_handle_this_.IsInitialized()) { |
| 1409 weak_handle_this_.Call(FROM_HERE, |
| 1410 &SyncInternal::RequestNudgeForDataType, |
| 1411 FROM_HERE, |
| 1412 mutated_model_type); |
| 1413 } else { |
| 1414 NOTREACHED(); |
| 1415 } |
| 1416 } |
| 1417 } |
| 1418 |
| 1419 void SyncManager::SyncInternal::SetExtraChangeRecordData(int64 id, |
| 1420 syncable::ModelType type, ChangeReorderBuffer* buffer, |
| 1421 Cryptographer* cryptographer, const syncable::EntryKernel& original, |
| 1422 bool existed_before, bool exists_now) { |
| 1423 // If this is a deletion and the datatype was encrypted, we need to decrypt it |
| 1424 // and attach it to the buffer. |
| 1425 if (!exists_now && existed_before) { |
| 1426 sync_pb::EntitySpecifics original_specifics(original.ref(SPECIFICS)); |
| 1427 if (type == syncable::PASSWORDS) { |
| 1428 // Passwords must use their own legacy ExtraPasswordChangeRecordData. |
| 1429 scoped_ptr<sync_pb::PasswordSpecificsData> data( |
| 1430 DecryptPasswordSpecifics(original_specifics, cryptographer)); |
| 1431 if (!data.get()) { |
| 1432 NOTREACHED(); |
| 1433 return; |
| 1434 } |
| 1435 buffer->SetExtraDataForId(id, new ExtraPasswordChangeRecordData(*data)); |
| 1436 } else if (original_specifics.has_encrypted()) { |
| 1437 // All other datatypes can just create a new unencrypted specifics and |
| 1438 // attach it. |
| 1439 const sync_pb::EncryptedData& encrypted = original_specifics.encrypted(); |
| 1440 if (!cryptographer->Decrypt(encrypted, &original_specifics)) { |
| 1441 NOTREACHED(); |
| 1442 return; |
| 1443 } |
| 1444 } |
| 1445 buffer->SetSpecificsForId(id, original_specifics); |
| 1446 } |
| 1447 } |
| 1448 |
| 1449 void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer( |
| 1450 const EntryKernelMutationSet& mutations, |
| 1451 syncable::BaseTransaction* trans) { |
| 1452 // We only expect one notification per sync step, so change_buffers_ should |
| 1453 // contain no pending entries. |
| 1454 LOG_IF(WARNING, !ChangeBuffersAreEmpty()) << |
| 1455 "CALCULATE_CHANGES called with unapplied old changes."; |
| 1456 |
| 1457 Cryptographer* crypto = dir_manager()->GetCryptographer(trans); |
| 1458 for (syncable::EntryKernelMutationSet::const_iterator it = |
| 1459 mutations.begin(); it != mutations.end(); ++it) { |
| 1460 bool existed_before = !it->original.ref(syncable::IS_DEL); |
| 1461 bool exists_now = !it->mutated.ref(syncable::IS_DEL); |
| 1462 |
| 1463 // Omit items that aren't associated with a model. |
| 1464 syncable::ModelType type = |
| 1465 syncable::GetModelTypeFromSpecifics(it->mutated.ref(SPECIFICS)); |
| 1466 if (type < syncable::FIRST_REAL_MODEL_TYPE) |
| 1467 continue; |
| 1468 |
| 1469 int64 id = it->original.ref(syncable::META_HANDLE); |
| 1470 if (exists_now && !existed_before) |
| 1471 change_buffers_[type].PushAddedItem(id); |
| 1472 else if (!exists_now && existed_before) |
| 1473 change_buffers_[type].PushDeletedItem(id); |
| 1474 else if (exists_now && existed_before && |
| 1475 VisiblePropertiesDiffer(*it, crypto)) { |
| 1476 change_buffers_[type].PushUpdatedItem( |
| 1477 id, VisiblePositionsDiffer(*it)); |
| 1478 } |
| 1479 |
| 1480 SetExtraChangeRecordData(id, type, &change_buffers_[type], crypto, |
| 1481 it->original, existed_before, exists_now); |
| 1482 } |
| 1483 } |
| 1484 |
| 1485 SyncManager::Status SyncManager::SyncInternal::GetStatus() { |
| 1486 return allstatus_.status(); |
| 1487 } |
| 1488 |
| 1489 void SyncManager::SyncInternal::RequestNudge( |
| 1490 const tracked_objects::Location& location) { |
| 1491 if (scheduler()) |
| 1492 scheduler()->ScheduleNudge( |
| 1493 TimeDelta::FromMilliseconds(0), browser_sync::NUDGE_SOURCE_LOCAL, |
| 1494 ModelTypeBitSet(), location); |
| 1495 } |
| 1496 |
| 1497 void SyncManager::SyncInternal::RequestNudgeForDataType( |
| 1498 const tracked_objects::Location& nudge_location, |
| 1499 const ModelType& type) { |
| 1500 if (!scheduler()) { |
| 1501 NOTREACHED(); |
| 1502 return; |
| 1503 } |
| 1504 base::TimeDelta nudge_delay; |
| 1505 switch (type) { |
| 1506 case syncable::PREFERENCES: |
| 1507 nudge_delay = |
| 1508 TimeDelta::FromMilliseconds(kPreferencesNudgeDelayMilliseconds); |
| 1509 break; |
| 1510 case syncable::SESSIONS: |
| 1511 nudge_delay = scheduler()->sessions_commit_delay(); |
| 1512 break; |
| 1513 default: |
| 1514 nudge_delay = |
| 1515 TimeDelta::FromMilliseconds(kDefaultNudgeDelayMilliseconds); |
| 1516 break; |
| 1517 } |
| 1518 syncable::ModelTypeBitSet types; |
| 1519 types.set(type); |
| 1520 scheduler()->ScheduleNudge(nudge_delay, |
| 1521 browser_sync::NUDGE_SOURCE_LOCAL, |
| 1522 types, |
| 1523 nudge_location); |
| 1524 } |
| 1525 |
| 1526 void SyncManager::SyncInternal::OnSyncEngineEvent( |
| 1527 const SyncEngineEvent& event) { |
| 1528 DCHECK(thread_checker_.CalledOnValidThread()); |
| 1529 if (!HaveObservers()) { |
| 1530 LOG(INFO) |
| 1531 << "OnSyncEngineEvent returning because observers_.size() is zero"; |
| 1532 return; |
| 1533 } |
| 1534 |
| 1535 // Only send an event if this is due to a cycle ending and this cycle |
| 1536 // concludes a canonical "sync" process; that is, based on what is known |
| 1537 // locally we are "all happy" and up-to-date. There may be new changes on |
| 1538 // the server, but we'll get them on a subsequent sync. |
| 1539 // |
| 1540 // Notifications are sent at the end of every sync cycle, regardless of |
| 1541 // whether we should sync again. |
| 1542 if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED) { |
| 1543 ModelSafeRoutingInfo enabled_types; |
| 1544 registrar_->GetModelSafeRoutingInfo(&enabled_types); |
| 1545 { |
| 1546 // Check to see if we need to notify the frontend that we have newly |
| 1547 // encrypted types or that we require a passphrase. |
| 1548 sync_api::ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 1549 Cryptographer* cryptographer = trans.GetCryptographer(); |
| 1550 // If we've completed a sync cycle and the cryptographer isn't ready |
| 1551 // yet, prompt the user for a passphrase. |
| 1552 if (cryptographer->has_pending_keys()) { |
| 1553 VLOG(1) << "OnPassPhraseRequired Sent"; |
| 1554 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1555 CopyObservers(&temp_obs_list); |
| 1556 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1557 OnPassphraseRequired(sync_api::REASON_DECRYPTION)); |
| 1558 } else if (!cryptographer->is_ready() && |
| 1559 event.snapshot->initial_sync_ended.test(syncable::NIGORI)) { |
| 1560 VLOG(1) << "OnPassphraseRequired sent because cryptographer is not " |
| 1561 << "ready"; |
| 1562 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1563 CopyObservers(&temp_obs_list); |
| 1564 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1565 OnPassphraseRequired(sync_api::REASON_ENCRYPTION)); |
| 1566 } |
| 1567 |
| 1568 allstatus_.SetCryptographerReady(cryptographer->is_ready()); |
| 1569 allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys()); |
| 1570 allstatus_.SetEncryptedTypes(cryptographer->GetEncryptedTypes()); |
| 1571 |
| 1572 // If everything is in order(we have the passphrase) then there is no |
| 1573 // need to inform the listeners. They will just wait for sync |
| 1574 // completion event and if no errors have been raised it means |
| 1575 // encryption was succesful. |
| 1576 } |
| 1577 |
| 1578 if (!initialized_) { |
| 1579 LOG(INFO) << "OnSyncCycleCompleted not sent because sync api is not " |
| 1580 << "initialized"; |
| 1581 return; |
| 1582 } |
| 1583 |
| 1584 if (!event.snapshot->has_more_to_sync) { |
| 1585 VLOG(1) << "OnSyncCycleCompleted sent"; |
| 1586 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1587 CopyObservers(&temp_obs_list); |
| 1588 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1589 OnSyncCycleCompleted(event.snapshot)); |
| 1590 } |
| 1591 |
| 1592 // This is here for tests, which are still using p2p notifications. |
| 1593 // |
| 1594 // TODO(chron): Consider changing this back to track has_more_to_sync |
| 1595 // only notify peers if a successful commit has occurred. |
| 1596 bool is_notifiable_commit = |
| 1597 (event.snapshot->syncer_status.num_successful_commits > 0); |
| 1598 if (is_notifiable_commit) { |
| 1599 allstatus_.IncrementNotifiableCommits(); |
| 1600 if (sync_notifier_.get()) { |
| 1601 sync_notifier_->SendNotification(); |
| 1602 } else { |
| 1603 VLOG(1) << "Not sending notification: sync_notifier_ is NULL"; |
| 1604 } |
| 1605 } |
| 1606 } |
| 1607 |
| 1608 if (event.what_happened == SyncEngineEvent::STOP_SYNCING_PERMANENTLY) { |
| 1609 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1610 CopyObservers(&temp_obs_list); |
| 1611 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1612 OnStopSyncingPermanently()); |
| 1613 return; |
| 1614 } |
| 1615 |
| 1616 if (event.what_happened == SyncEngineEvent::CLEAR_SERVER_DATA_SUCCEEDED) { |
| 1617 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1618 CopyObservers(&temp_obs_list); |
| 1619 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1620 OnClearServerDataSucceeded()); |
| 1621 return; |
| 1622 } |
| 1623 |
| 1624 if (event.what_happened == SyncEngineEvent::CLEAR_SERVER_DATA_FAILED) { |
| 1625 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1626 CopyObservers(&temp_obs_list); |
| 1627 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1628 OnClearServerDataFailed()); |
| 1629 return; |
| 1630 } |
| 1631 |
| 1632 if (event.what_happened == SyncEngineEvent::UPDATED_TOKEN) { |
| 1633 ObserverList<SyncManager::Observer> temp_obs_list; |
| 1634 CopyObservers(&temp_obs_list); |
| 1635 FOR_EACH_OBSERVER(SyncManager::Observer, temp_obs_list, |
| 1636 OnUpdatedToken(event.updated_token)); |
| 1637 return; |
| 1638 } |
| 1639 } |
| 1640 |
| 1641 void SyncManager::SyncInternal::SetJsEventHandler( |
| 1642 const WeakHandle<JsEventHandler>& event_handler) { |
| 1643 js_event_handler_ = event_handler; |
| 1644 js_sync_manager_observer_.SetJsEventHandler(js_event_handler_); |
| 1645 js_transaction_observer_.SetJsEventHandler(js_event_handler_); |
| 1646 } |
| 1647 |
| 1648 void SyncManager::SyncInternal::ProcessJsMessage( |
| 1649 const std::string& name, const JsArgList& args, |
| 1650 const WeakHandle<JsReplyHandler>& reply_handler) { |
| 1651 if (!initialized_) { |
| 1652 NOTREACHED(); |
| 1653 return; |
| 1654 } |
| 1655 |
| 1656 if (!reply_handler.IsInitialized()) { |
| 1657 VLOG(1) << "Uninitialized reply handler; dropping unknown message " |
| 1658 << name << " with args " << args.ToString(); |
| 1659 return; |
| 1660 } |
| 1661 |
| 1662 JsMessageHandler js_message_handler = js_message_handlers_[name]; |
| 1663 if (js_message_handler.is_null()) { |
| 1664 VLOG(1) << "Dropping unknown message " << name |
| 1665 << " with args " << args.ToString(); |
| 1666 return; |
| 1667 } |
| 1668 |
| 1669 reply_handler.Call(FROM_HERE, |
| 1670 &JsReplyHandler::HandleJsReply, |
| 1671 name, js_message_handler.Run(args)); |
| 1672 } |
| 1673 |
| 1674 void SyncManager::SyncInternal::BindJsMessageHandler( |
| 1675 const std::string& name, |
| 1676 UnboundJsMessageHandler unbound_message_handler) { |
| 1677 js_message_handlers_[name] = |
| 1678 base::Bind(unbound_message_handler, base::Unretained(this)); |
| 1679 } |
| 1680 |
| 1681 DictionaryValue* SyncManager::SyncInternal::NotificationInfoToValue( |
| 1682 const NotificationInfoMap& notification_info) { |
| 1683 DictionaryValue* value = new DictionaryValue(); |
| 1684 |
| 1685 for (NotificationInfoMap::const_iterator it = notification_info.begin(); |
| 1686 it != notification_info.end(); ++it) { |
| 1687 const std::string& model_type_str = |
| 1688 syncable::ModelTypeToString(it->first); |
| 1689 value->Set(model_type_str, it->second.ToValue()); |
| 1690 } |
| 1691 |
| 1692 return value; |
| 1693 } |
| 1694 |
| 1695 JsArgList SyncManager::SyncInternal::GetNotificationState( |
| 1696 const JsArgList& args) { |
| 1697 bool notifications_enabled = allstatus_.status().notifications_enabled; |
| 1698 ListValue return_args; |
| 1699 return_args.Append(Value::CreateBooleanValue(notifications_enabled)); |
| 1700 return JsArgList(&return_args); |
| 1701 } |
| 1702 |
| 1703 JsArgList SyncManager::SyncInternal::GetNotificationInfo( |
| 1704 const JsArgList& args) { |
| 1705 ListValue return_args; |
| 1706 return_args.Append(NotificationInfoToValue(notification_info_map_)); |
| 1707 return JsArgList(&return_args); |
| 1708 } |
| 1709 |
| 1710 JsArgList SyncManager::SyncInternal::GetRootNodeDetails( |
| 1711 const JsArgList& args) { |
| 1712 ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 1713 ReadNode root(&trans); |
| 1714 root.InitByRootLookup(); |
| 1715 ListValue return_args; |
| 1716 return_args.Append(root.GetDetailsAsValue()); |
| 1717 return JsArgList(&return_args); |
| 1718 } |
| 1719 |
| 1720 namespace { |
| 1721 |
| 1722 int64 GetId(const ListValue& ids, int i) { |
| 1723 std::string id_str; |
| 1724 if (!ids.GetString(i, &id_str)) { |
| 1725 return kInvalidId; |
| 1726 } |
| 1727 int64 id = kInvalidId; |
| 1728 if (!base::StringToInt64(id_str, &id)) { |
| 1729 return kInvalidId; |
| 1730 } |
| 1731 return id; |
| 1732 } |
| 1733 |
| 1734 JsArgList GetNodeInfoById(const JsArgList& args, |
| 1735 UserShare* user_share, |
| 1736 DictionaryValue* (BaseNode::*info_getter)() const) { |
| 1737 CHECK(info_getter); |
| 1738 ListValue return_args; |
| 1739 ListValue* node_summaries = new ListValue(); |
| 1740 return_args.Append(node_summaries); |
| 1741 ListValue* id_list = NULL; |
| 1742 ReadTransaction trans(FROM_HERE, user_share); |
| 1743 if (args.Get().GetList(0, &id_list)) { |
| 1744 CHECK(id_list); |
| 1745 for (size_t i = 0; i < id_list->GetSize(); ++i) { |
| 1746 int64 id = GetId(*id_list, i); |
| 1747 if (id == kInvalidId) { |
| 1748 continue; |
| 1749 } |
| 1750 ReadNode node(&trans); |
| 1751 if (!node.InitByIdLookup(id)) { |
| 1752 continue; |
| 1753 } |
| 1754 node_summaries->Append((node.*info_getter)()); |
| 1755 } |
| 1756 } |
| 1757 return JsArgList(&return_args); |
| 1758 } |
| 1759 |
| 1760 } // namespace |
| 1761 |
| 1762 JsArgList SyncManager::SyncInternal::GetNodeSummariesById( |
| 1763 const JsArgList& args) { |
| 1764 return GetNodeInfoById(args, GetUserShare(), &BaseNode::GetSummaryAsValue); |
| 1765 } |
| 1766 |
| 1767 JsArgList SyncManager::SyncInternal::GetNodeDetailsById( |
| 1768 const JsArgList& args) { |
| 1769 return GetNodeInfoById(args, GetUserShare(), &BaseNode::GetDetailsAsValue); |
| 1770 } |
| 1771 |
| 1772 JsArgList SyncManager::SyncInternal::GetChildNodeIds( |
| 1773 const JsArgList& args) { |
| 1774 ListValue return_args; |
| 1775 ListValue* child_ids = new ListValue(); |
| 1776 return_args.Append(child_ids); |
| 1777 int64 id = GetId(args.Get(), 0); |
| 1778 if (id != kInvalidId) { |
| 1779 ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 1780 syncable::Directory::ChildHandles child_handles; |
| 1781 trans.GetLookup()->GetChildHandlesByHandle(trans.GetWrappedTrans(), |
| 1782 id, &child_handles); |
| 1783 for (syncable::Directory::ChildHandles::const_iterator it = |
| 1784 child_handles.begin(); it != child_handles.end(); ++it) { |
| 1785 child_ids->Append(Value::CreateStringValue( |
| 1786 base::Int64ToString(*it))); |
| 1787 } |
| 1788 } |
| 1789 return JsArgList(&return_args); |
| 1790 } |
| 1791 |
| 1792 JsArgList SyncManager::SyncInternal::FindNodesContainingString( |
| 1793 const JsArgList& args) { |
| 1794 std::string query; |
| 1795 ListValue return_args; |
| 1796 if (!args.Get().GetString(0, &query)) { |
| 1797 return_args.Append(new ListValue()); |
| 1798 return JsArgList(&return_args); |
| 1799 } |
| 1800 |
| 1801 // Convert the query string to lower case to perform case insensitive |
| 1802 // searches. |
| 1803 std::string lowercase_query = query; |
| 1804 StringToLowerASCII(&lowercase_query); |
| 1805 |
| 1806 ListValue* result = new ListValue(); |
| 1807 return_args.Append(result); |
| 1808 |
| 1809 ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 1810 std::vector<const syncable::EntryKernel*> entry_kernels; |
| 1811 trans.GetLookup()->GetAllEntryKernels(trans.GetWrappedTrans(), |
| 1812 &entry_kernels); |
| 1813 |
| 1814 for (std::vector<const syncable::EntryKernel*>::const_iterator it = |
| 1815 entry_kernels.begin(); it != entry_kernels.end(); ++it) { |
| 1816 if ((*it)->ContainsString(lowercase_query)) { |
| 1817 result->Append(new StringValue(base::Int64ToString( |
| 1818 (*it)->ref(syncable::META_HANDLE)))); |
| 1819 } |
| 1820 } |
| 1821 |
| 1822 return JsArgList(&return_args); |
| 1823 } |
| 1824 |
| 1825 void SyncManager::SyncInternal::OnNotificationStateChange( |
| 1826 bool notifications_enabled) { |
| 1827 VLOG(1) << "P2P: Notifications enabled = " |
| 1828 << (notifications_enabled ? "true" : "false"); |
| 1829 allstatus_.SetNotificationsEnabled(notifications_enabled); |
| 1830 if (scheduler()) { |
| 1831 scheduler()->set_notifications_enabled(notifications_enabled); |
| 1832 } |
| 1833 if (js_event_handler_.IsInitialized()) { |
| 1834 DictionaryValue details; |
| 1835 details.Set("enabled", Value::CreateBooleanValue(notifications_enabled)); |
| 1836 js_event_handler_.Call(FROM_HERE, |
| 1837 &JsEventHandler::HandleJsEvent, |
| 1838 "onNotificationStateChange", |
| 1839 JsEventDetails(&details)); |
| 1840 } |
| 1841 } |
| 1842 |
| 1843 void SyncManager::SyncInternal::UpdateNotificationInfo( |
| 1844 const syncable::ModelTypePayloadMap& type_payloads) { |
| 1845 for (syncable::ModelTypePayloadMap::const_iterator it = type_payloads.begin(); |
| 1846 it != type_payloads.end(); ++it) { |
| 1847 NotificationInfo* info = ¬ification_info_map_[it->first]; |
| 1848 info->total_count++; |
| 1849 info->payload = it->second; |
| 1850 } |
| 1851 } |
| 1852 |
| 1853 void SyncManager::SyncInternal::OnIncomingNotification( |
| 1854 const syncable::ModelTypePayloadMap& type_payloads) { |
| 1855 if (!type_payloads.empty()) { |
| 1856 if (scheduler()) { |
| 1857 scheduler()->ScheduleNudgeWithPayloads( |
| 1858 TimeDelta::FromMilliseconds(kSyncSchedulerDelayMsec), |
| 1859 browser_sync::NUDGE_SOURCE_NOTIFICATION, |
| 1860 type_payloads, FROM_HERE); |
| 1861 } |
| 1862 allstatus_.IncrementNotificationsReceived(); |
| 1863 UpdateNotificationInfo(type_payloads); |
| 1864 } else { |
| 1865 LOG(WARNING) << "Sync received notification without any type information."; |
| 1866 } |
| 1867 |
| 1868 if (js_event_handler_.IsInitialized()) { |
| 1869 DictionaryValue details; |
| 1870 ListValue* changed_types = new ListValue(); |
| 1871 details.Set("changedTypes", changed_types); |
| 1872 for (syncable::ModelTypePayloadMap::const_iterator |
| 1873 it = type_payloads.begin(); |
| 1874 it != type_payloads.end(); ++it) { |
| 1875 const std::string& model_type_str = |
| 1876 syncable::ModelTypeToString(it->first); |
| 1877 changed_types->Append(Value::CreateStringValue(model_type_str)); |
| 1878 } |
| 1879 js_event_handler_.Call(FROM_HERE, |
| 1880 &JsEventHandler::HandleJsEvent, |
| 1881 "onIncomingNotification", |
| 1882 JsEventDetails(&details)); |
| 1883 } |
| 1884 } |
| 1885 |
| 1886 void SyncManager::SyncInternal::StoreState( |
| 1887 const std::string& state) { |
| 1888 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 1889 if (!lookup.good()) { |
| 1890 LOG(ERROR) << "Could not write notification state"; |
| 1891 // TODO(akalin): Propagate result callback all the way to this |
| 1892 // function and call it with "false" to signal failure. |
| 1893 return; |
| 1894 } |
| 1895 if (VLOG_IS_ON(1)) { |
| 1896 std::string encoded_state; |
| 1897 base::Base64Encode(state, &encoded_state); |
| 1898 VLOG(1) << "Writing notification state: " << encoded_state; |
| 1899 } |
| 1900 lookup->SetNotificationState(state); |
| 1901 lookup->SaveChanges(); |
| 1902 } |
| 1903 |
| 1904 // Note: it is possible that an observer will remove itself after we have made |
| 1905 // a copy, but before the copy is consumed. This could theoretically result |
| 1906 // in accessing a garbage pointer, but can only occur when an about:sync window |
| 1907 // is closed in the middle of a notification. |
| 1908 // See crbug.com/85481. |
| 1909 void SyncManager::SyncInternal::CopyObservers( |
| 1910 ObserverList<SyncManager::Observer>* observers_copy) { |
| 1911 DCHECK_EQ(0U, observers_copy->size()); |
| 1912 base::AutoLock lock(observers_lock_); |
| 1913 if (observers_.size() == 0) |
| 1914 return; |
| 1915 ObserverListBase<SyncManager::Observer>::Iterator it(observers_); |
| 1916 SyncManager::Observer* obs; |
| 1917 while ((obs = it.GetNext()) != NULL) |
| 1918 observers_copy->AddObserver(obs); |
| 1919 } |
| 1920 |
| 1921 bool SyncManager::SyncInternal::HaveObservers() const { |
| 1922 base::AutoLock lock(observers_lock_); |
| 1923 return observers_.size() > 0; |
| 1924 } |
| 1925 |
| 1926 void SyncManager::SyncInternal::AddObserver( |
| 1927 SyncManager::Observer* observer) { |
| 1928 base::AutoLock lock(observers_lock_); |
| 1929 observers_.AddObserver(observer); |
| 1930 } |
| 1931 |
| 1932 void SyncManager::SyncInternal::RemoveObserver( |
| 1933 SyncManager::Observer* observer) { |
| 1934 base::AutoLock lock(observers_lock_); |
| 1935 observers_.RemoveObserver(observer); |
| 1936 } |
| 1937 |
| 1938 SyncManager::Status::Summary SyncManager::GetStatusSummary() const { |
| 1939 return data_->GetStatus().summary; |
| 1940 } |
| 1941 |
| 1942 SyncManager::Status SyncManager::GetDetailedStatus() const { |
| 1943 return data_->GetStatus(); |
| 1944 } |
| 1945 |
| 1946 SyncManager::SyncInternal* SyncManager::GetImpl() const { return data_; } |
| 1947 |
| 1948 void SyncManager::SaveChanges() { |
| 1949 data_->SaveChanges(); |
| 1950 } |
| 1951 |
| 1952 void SyncManager::SyncInternal::SaveChanges() { |
| 1953 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 1954 if (!lookup.good()) { |
| 1955 DCHECK(false) << "ScopedDirLookup creation failed; Unable to SaveChanges"; |
| 1956 return; |
| 1957 } |
| 1958 lookup->SaveChanges(); |
| 1959 } |
| 1960 |
| 1961 UserShare* SyncManager::GetUserShare() const { |
| 1962 return data_->GetUserShare(); |
| 1963 } |
| 1964 |
| 1965 void SyncManager::RefreshEncryption() { |
| 1966 if (data_->UpdateCryptographerFromNigori()) |
| 1967 data_->EncryptDataTypes(syncable::ModelTypeSet()); |
| 1968 } |
| 1969 |
| 1970 syncable::ModelTypeSet SyncManager::GetEncryptedDataTypes() const { |
| 1971 sync_api::ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 1972 return GetEncryptedTypes(&trans); |
| 1973 } |
| 1974 |
| 1975 bool SyncManager::HasUnsyncedItems() const { |
| 1976 sync_api::ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 1977 return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0); |
| 1978 } |
| 1979 |
| 1980 void SyncManager::LogUnsyncedItems(int level) const { |
| 1981 std::vector<int64> unsynced_handles; |
| 1982 sync_api::ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 1983 trans.GetWrappedTrans()->directory()->GetUnsyncedMetaHandles( |
| 1984 trans.GetWrappedTrans(), &unsynced_handles); |
| 1985 |
| 1986 for (std::vector<int64>::const_iterator it = unsynced_handles.begin(); |
| 1987 it != unsynced_handles.end(); ++it) { |
| 1988 ReadNode node(&trans); |
| 1989 if (node.InitByIdLookup(*it)) { |
| 1990 scoped_ptr<DictionaryValue> value(node.GetDetailsAsValue()); |
| 1991 std::string info; |
| 1992 base::JSONWriter::Write(value.get(), true, &info); |
| 1993 VLOG(level) << info; |
| 1994 } |
| 1995 } |
| 1996 } |
| 1997 |
| 1998 void SyncManager::TriggerOnNotificationStateChangeForTest( |
| 1999 bool notifications_enabled) { |
| 2000 data_->OnNotificationStateChange(notifications_enabled); |
| 2001 } |
| 2002 |
| 2003 void SyncManager::TriggerOnIncomingNotificationForTest( |
| 2004 const syncable::ModelTypeBitSet& model_types) { |
| 2005 syncable::ModelTypePayloadMap model_types_with_payloads = |
| 2006 syncable::ModelTypePayloadMapFromBitSet(model_types, |
| 2007 std::string()); |
| 2008 |
| 2009 data_->OnIncomingNotification(model_types_with_payloads); |
| 2010 } |
| 2011 |
| 2012 } // namespace sync_api |
| OLD | NEW |