| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "sync/internal_api/sync_manager_impl.h" | 5 #include "sync/internal_api/sync_manager_impl.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/base64.h" | 9 #include "base/base64.h" |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| (...skipping 275 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 286 } | 286 } |
| 287 | 287 |
| 288 void SyncManagerImpl::ThrowUnrecoverableError() { | 288 void SyncManagerImpl::ThrowUnrecoverableError() { |
| 289 DCHECK(thread_checker_.CalledOnValidThread()); | 289 DCHECK(thread_checker_.CalledOnValidThread()); |
| 290 ReadTransaction trans(FROM_HERE, GetUserShare()); | 290 ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 291 trans.GetWrappedTrans()->OnUnrecoverableError( | 291 trans.GetWrappedTrans()->OnUnrecoverableError( |
| 292 FROM_HERE, "Simulating unrecoverable error for testing purposes."); | 292 FROM_HERE, "Simulating unrecoverable error for testing purposes."); |
| 293 } | 293 } |
| 294 | 294 |
| 295 ModelTypeSet SyncManagerImpl::InitialSyncEndedTypes() { | 295 ModelTypeSet SyncManagerImpl::InitialSyncEndedTypes() { |
| 296 DCHECK(initialized_); | |
| 297 return directory()->initial_sync_ended_types(); | 296 return directory()->initial_sync_ended_types(); |
| 298 } | 297 } |
| 299 | 298 |
| 300 ModelTypeSet SyncManagerImpl::GetTypesWithEmptyProgressMarkerToken( | 299 ModelTypeSet SyncManagerImpl::GetTypesWithEmptyProgressMarkerToken( |
| 301 ModelTypeSet types) { | 300 ModelTypeSet types) { |
| 302 DCHECK(initialized_); | |
| 303 ModelTypeSet result; | 301 ModelTypeSet result; |
| 304 for (ModelTypeSet::Iterator i = types.First(); i.Good(); i.Inc()) { | 302 for (ModelTypeSet::Iterator i = types.First(); i.Good(); i.Inc()) { |
| 305 sync_pb::DataTypeProgressMarker marker; | 303 sync_pb::DataTypeProgressMarker marker; |
| 306 directory()->GetDownloadProgress(i.Get(), &marker); | 304 directory()->GetDownloadProgress(i.Get(), &marker); |
| 307 | 305 |
| 308 if (marker.token().empty()) | 306 if (marker.token().empty()) |
| 309 result.Put(i.Get()); | 307 result.Put(i.Get()); |
| 310 | 308 |
| 311 } | 309 } |
| 312 return result; | 310 return result; |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 358 } | 356 } |
| 359 | 357 |
| 360 bool SyncManagerImpl::Init( | 358 bool SyncManagerImpl::Init( |
| 361 const FilePath& database_location, | 359 const FilePath& database_location, |
| 362 const WeakHandle<JsEventHandler>& event_handler, | 360 const WeakHandle<JsEventHandler>& event_handler, |
| 363 const std::string& sync_server_and_path, | 361 const std::string& sync_server_and_path, |
| 364 int port, | 362 int port, |
| 365 bool use_ssl, | 363 bool use_ssl, |
| 366 const scoped_refptr<base::TaskRunner>& blocking_task_runner, | 364 const scoped_refptr<base::TaskRunner>& blocking_task_runner, |
| 367 scoped_ptr<HttpPostProviderFactory> post_factory, | 365 scoped_ptr<HttpPostProviderFactory> post_factory, |
| 368 const ModelSafeRoutingInfo& model_safe_routing_info, | |
| 369 const std::vector<ModelSafeWorker*>& workers, | 366 const std::vector<ModelSafeWorker*>& workers, |
| 370 ExtensionsActivityMonitor* extensions_activity_monitor, | 367 ExtensionsActivityMonitor* extensions_activity_monitor, |
| 371 SyncManager::ChangeDelegate* change_delegate, | 368 SyncManager::ChangeDelegate* change_delegate, |
| 372 const SyncCredentials& credentials, | 369 const SyncCredentials& credentials, |
| 373 scoped_ptr<SyncNotifier> sync_notifier, | 370 scoped_ptr<SyncNotifier> sync_notifier, |
| 374 const std::string& restored_key_for_bootstrapping, | 371 const std::string& restored_key_for_bootstrapping, |
| 375 scoped_ptr<InternalComponentsFactory> internal_components_factory, | 372 scoped_ptr<InternalComponentsFactory> internal_components_factory, |
| 376 Encryptor* encryptor, | 373 Encryptor* encryptor, |
| 377 UnrecoverableErrorHandler* unrecoverable_error_handler, | 374 UnrecoverableErrorHandler* unrecoverable_error_handler, |
| 378 ReportUnrecoverableErrorFunction report_unrecoverable_error_function) { | 375 ReportUnrecoverableErrorFunction report_unrecoverable_error_function) { |
| 379 CHECK(!initialized_); | 376 CHECK(!initialized_); |
| 380 DCHECK(thread_checker_.CalledOnValidThread()); | 377 DCHECK(thread_checker_.CalledOnValidThread()); |
| 381 DCHECK(post_factory.get()); | 378 DCHECK(post_factory.get()); |
| 379 DCHECK(!credentials.email.empty()); |
| 380 DCHECK(!credentials.sync_token.empty()); |
| 382 DVLOG(1) << "SyncManager starting Init..."; | 381 DVLOG(1) << "SyncManager starting Init..."; |
| 383 | 382 |
| 384 weak_handle_this_ = MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()); | 383 weak_handle_this_ = MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()); |
| 385 | 384 |
| 386 blocking_task_runner_ = blocking_task_runner; | 385 blocking_task_runner_ = blocking_task_runner; |
| 387 | 386 |
| 388 change_delegate_ = change_delegate; | 387 change_delegate_ = change_delegate; |
| 389 | 388 |
| 390 sync_notifier_ = sync_notifier.Pass(); | 389 sync_notifier_ = sync_notifier.Pass(); |
| 391 | 390 |
| 392 AddObserver(&js_sync_manager_observer_); | 391 AddObserver(&js_sync_manager_observer_); |
| 393 SetJsEventHandler(event_handler); | 392 SetJsEventHandler(event_handler); |
| 394 | 393 |
| 395 AddObserver(&debug_info_event_listener_); | 394 AddObserver(&debug_info_event_listener_); |
| 396 | 395 |
| 397 database_path_ = database_location.Append( | 396 database_path_ = database_location.Append( |
| 398 syncable::Directory::kSyncDatabaseFilename); | 397 syncable::Directory::kSyncDatabaseFilename); |
| 399 encryptor_ = encryptor; | 398 encryptor_ = encryptor; |
| 400 unrecoverable_error_handler_ = unrecoverable_error_handler; | 399 unrecoverable_error_handler_ = unrecoverable_error_handler; |
| 401 report_unrecoverable_error_function_ = report_unrecoverable_error_function; | 400 report_unrecoverable_error_function_ = report_unrecoverable_error_function; |
| 402 | 401 |
| 403 FilePath absolute_db_path(database_path_); | 402 FilePath absolute_db_path(database_path_); |
| 404 file_util::AbsolutePath(&absolute_db_path); | 403 file_util::AbsolutePath(&absolute_db_path); |
| 405 scoped_ptr<syncable::DirectoryBackingStore> backing_store = | 404 scoped_ptr<syncable::DirectoryBackingStore> backing_store = |
| 406 internal_components_factory->BuildDirectoryBackingStore( | 405 internal_components_factory->BuildDirectoryBackingStore( |
| 407 credentials.email, absolute_db_path).Pass(); | 406 credentials.email, absolute_db_path).Pass(); |
| 408 | 407 |
| 409 DCHECK(backing_store.get()); | 408 DCHECK(backing_store.get()); |
| 409 share_.name = credentials.email; |
| 410 share_.directory.reset( | 410 share_.directory.reset( |
| 411 new syncable::Directory(encryptor_, | 411 new syncable::Directory(encryptor_, |
| 412 unrecoverable_error_handler_, | 412 unrecoverable_error_handler_, |
| 413 report_unrecoverable_error_function_, | 413 report_unrecoverable_error_function_, |
| 414 backing_store.release())); | 414 backing_store.release())); |
| 415 | 415 |
| 416 connection_manager_.reset(new SyncAPIServerConnectionManager( | 416 connection_manager_.reset(new SyncAPIServerConnectionManager( |
| 417 sync_server_and_path, port, use_ssl, post_factory.release())); | 417 sync_server_and_path, port, use_ssl, post_factory.release())); |
| 418 connection_manager_->AddListener(this); |
| 418 | 419 |
| 419 net::NetworkChangeNotifier::AddIPAddressObserver(this); | 420 DVLOG(1) << "Username: " << username_for_share(); |
| 420 observing_ip_address_changes_ = true; | 421 if (!OpenDirectory()) { |
| 422 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
| 423 OnInitializationComplete( |
| 424 MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()), |
| 425 false, syncer::ModelTypeSet())); |
| 426 return false; |
| 427 } |
| 421 | 428 |
| 422 connection_manager_->AddListener(this); | 429 // Retrieve and set the sync notifier state. |
| 430 std::string unique_id = directory()->cache_guid(); |
| 431 DVLOG(1) << "Read notification unique ID: " << unique_id; |
| 432 allstatus_.SetUniqueId(unique_id); |
| 433 sync_notifier_->SetUniqueId(unique_id); |
| 434 |
| 435 std::string state = directory()->GetNotificationState(); |
| 436 if (VLOG_IS_ON(1)) { |
| 437 std::string encoded_state; |
| 438 base::Base64Encode(state, &encoded_state); |
| 439 DVLOG(1) << "Read notification state: " << encoded_state; |
| 440 } |
| 441 |
| 442 // TODO(tim): Remove once invalidation state has been migrated to new |
| 443 // InvalidationStateTracker store. Bug 124140. |
| 444 sync_notifier_->SetStateDeprecated(state); |
| 423 | 445 |
| 424 // Build a SyncSessionContext and store the worker in it. | 446 // Build a SyncSessionContext and store the worker in it. |
| 425 DVLOG(1) << "Sync is bringing up SyncSessionContext."; | 447 DVLOG(1) << "Sync is bringing up SyncSessionContext."; |
| 426 std::vector<SyncEngineEventListener*> listeners; | 448 std::vector<SyncEngineEventListener*> listeners; |
| 427 listeners.push_back(&allstatus_); | 449 listeners.push_back(&allstatus_); |
| 428 listeners.push_back(this); | 450 listeners.push_back(this); |
| 429 session_context_ = internal_components_factory->BuildContext( | 451 session_context_ = internal_components_factory->BuildContext( |
| 430 connection_manager_.get(), | 452 connection_manager_.get(), |
| 431 directory(), | 453 directory(), |
| 432 model_safe_routing_info, | |
| 433 workers, | 454 workers, |
| 434 extensions_activity_monitor, | 455 extensions_activity_monitor, |
| 435 &throttled_data_type_tracker_, | 456 &throttled_data_type_tracker_, |
| 436 listeners, | 457 listeners, |
| 437 &debug_info_event_listener_, | 458 &debug_info_event_listener_, |
| 438 &traffic_recorder_).Pass(); | 459 &traffic_recorder_).Pass(); |
| 439 session_context_->set_account_name(credentials.email); | 460 session_context_->set_account_name(credentials.email); |
| 440 scheduler_ = internal_components_factory->BuildScheduler( | 461 scheduler_ = internal_components_factory->BuildScheduler( |
| 441 name_, session_context_.get()).Pass(); | 462 name_, session_context_.get()).Pass(); |
| 442 | 463 |
| 443 bool success = SignIn(credentials); | 464 scheduler_->Start(SyncScheduler::CONFIGURATION_MODE); |
| 444 | 465 |
| 445 if (success) { | 466 initialized_ = true; |
| 446 scheduler_->Start(SyncScheduler::CONFIGURATION_MODE); | |
| 447 | 467 |
| 448 initialized_ = true; | 468 net::NetworkChangeNotifier::AddIPAddressObserver(this); |
| 469 observing_ip_address_changes_ = true; |
| 449 | 470 |
| 450 // Unapplied datatypes (those that do not have initial sync ended set) get | 471 UpdateCredentials(credentials); |
| 451 // re-downloaded during any configuration. But, it's possible for a datatype | |
| 452 // to have a progress marker but not have initial sync ended yet, making | |
| 453 // it a candidate for migration. This is a problem, as the DataTypeManager | |
| 454 // does not support a migration while it's already in the middle of a | |
| 455 // configuration. As a result, any partially synced datatype can stall the | |
| 456 // DTM, waiting for the configuration to complete, which it never will due | |
| 457 // to the migration error. In addition, a partially synced nigori will | |
| 458 // trigger the migration logic before the backend is initialized, resulting | |
| 459 // in crashes. We therefore detect and purge any partially synced types as | |
| 460 // part of initialization. | |
| 461 if (!PurgePartiallySyncedTypes()) | |
| 462 success = false; | |
| 463 | 472 |
| 464 // Cryptographer should only be accessed while holding a | 473 // Cryptographer should only be accessed while holding a |
| 465 // transaction. Grabbing the user share for the transaction | 474 // transaction. Grabbing the user share for the transaction |
| 466 // checks the initialization state, so this must come after | 475 // checks the initialization state, so this must come after |
| 467 // |initialized_| is set to true. | 476 // |initialized_| is set to true. |
| 468 ReadTransaction trans(FROM_HERE, GetUserShare()); | 477 ReadTransaction trans(FROM_HERE, GetUserShare()); |
| 469 trans.GetCryptographer()->Bootstrap(restored_key_for_bootstrapping); | 478 trans.GetCryptographer()->Bootstrap(restored_key_for_bootstrapping); |
| 470 trans.GetCryptographer()->AddObserver(this); | 479 trans.GetCryptographer()->AddObserver(this); |
| 471 } | |
| 472 | 480 |
| 473 // Notify that initialization is complete. Note: This should be the last to | |
| 474 // execute if |signed_in| is false. Reason being in that case we would | |
| 475 // post a task to shutdown sync. But if this function posts any other tasks | |
| 476 // on the UI thread and if shutdown wins then that tasks would execute on | |
| 477 // a freed pointer. This is because UI thread is not shut down. | |
| 478 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, | 481 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, |
| 479 OnInitializationComplete( | 482 OnInitializationComplete( |
| 480 MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()), | 483 MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()), |
| 481 success)); | 484 true, InitialSyncEndedTypes())); |
| 482 if (!success) | 485 return true; |
| 483 return false; | |
| 484 | |
| 485 return success; | |
| 486 } | 486 } |
| 487 | 487 |
| 488 void SyncManagerImpl::RefreshNigori(const std::string& chrome_version, | 488 void SyncManagerImpl::RefreshNigori(const std::string& chrome_version, |
| 489 const base::Closure& done_callback) { | 489 const base::Closure& done_callback) { |
| 490 DCHECK(initialized_); | 490 DCHECK(initialized_); |
| 491 DCHECK(thread_checker_.CalledOnValidThread()); | 491 DCHECK(thread_checker_.CalledOnValidThread()); |
| 492 GetSessionName( | 492 GetSessionName( |
| 493 blocking_task_runner_, | 493 blocking_task_runner_, |
| 494 base::Bind( | 494 base::Bind( |
| 495 &SyncManagerImpl::UpdateCryptographerAndNigoriCallback, | 495 &SyncManagerImpl::UpdateCryptographerAndNigoriCallback, |
| (...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 651 MakeWeakHandle(js_mutation_event_observer_.AsWeakPtr())); | 651 MakeWeakHandle(js_mutation_event_observer_.AsWeakPtr())); |
| 652 | 652 |
| 653 syncable::DirOpenResult open_result = syncable::NOT_INITIALIZED; | 653 syncable::DirOpenResult open_result = syncable::NOT_INITIALIZED; |
| 654 open_result = directory()->Open(username_for_share(), this, | 654 open_result = directory()->Open(username_for_share(), this, |
| 655 transaction_observer); | 655 transaction_observer); |
| 656 if (open_result != syncable::OPENED) { | 656 if (open_result != syncable::OPENED) { |
| 657 LOG(ERROR) << "Could not open share for:" << username_for_share(); | 657 LOG(ERROR) << "Could not open share for:" << username_for_share(); |
| 658 return false; | 658 return false; |
| 659 } | 659 } |
| 660 | 660 |
| 661 // Unapplied datatypes (those that do not have initial sync ended set) get |
| 662 // re-downloaded during any configuration. But, it's possible for a datatype |
| 663 // to have a progress marker but not have initial sync ended yet, making |
| 664 // it a candidate for migration. This is a problem, as the DataTypeManager |
| 665 // does not support a migration while it's already in the middle of a |
| 666 // configuration. As a result, any partially synced datatype can stall the |
| 667 // DTM, waiting for the configuration to complete, which it never will due |
| 668 // to the migration error. In addition, a partially synced nigori will |
| 669 // trigger the migration logic before the backend is initialized, resulting |
| 670 // in crashes. We therefore detect and purge any partially synced types as |
| 671 // part of initialization. |
| 672 if (!PurgePartiallySyncedTypes()) |
| 673 return false; |
| 674 |
| 661 connection_manager_->set_client_id(directory()->cache_guid()); | 675 connection_manager_->set_client_id(directory()->cache_guid()); |
| 662 return true; | 676 return true; |
| 663 } | 677 } |
| 664 | 678 |
| 665 bool SyncManagerImpl::SignIn(const SyncCredentials& credentials) { | |
| 666 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 667 DCHECK(share_.name.empty()); | |
| 668 share_.name = credentials.email; | |
| 669 | |
| 670 DVLOG(1) << "Signing in user: " << username_for_share(); | |
| 671 if (!OpenDirectory()) | |
| 672 return false; | |
| 673 | |
| 674 // Retrieve and set the sync notifier state. This should be done | |
| 675 // only after OpenDirectory is called. | |
| 676 std::string unique_id = directory()->cache_guid(); | |
| 677 std::string state = directory()->GetNotificationState(); | |
| 678 DVLOG(1) << "Read notification unique ID: " << unique_id; | |
| 679 if (VLOG_IS_ON(1)) { | |
| 680 std::string encoded_state; | |
| 681 base::Base64Encode(state, &encoded_state); | |
| 682 DVLOG(1) << "Read notification state: " << encoded_state; | |
| 683 } | |
| 684 allstatus_.SetUniqueId(unique_id); | |
| 685 sync_notifier_->SetUniqueId(unique_id); | |
| 686 // TODO(tim): Remove once invalidation state has been migrated to new | |
| 687 // InvalidationStateTracker store. Bug 124140. | |
| 688 sync_notifier_->SetStateDeprecated(state); | |
| 689 | |
| 690 UpdateCredentials(credentials); | |
| 691 return true; | |
| 692 } | |
| 693 | |
| 694 bool SyncManagerImpl::PurgePartiallySyncedTypes() { | 679 bool SyncManagerImpl::PurgePartiallySyncedTypes() { |
| 695 ModelTypeSet partially_synced_types = ModelTypeSet::All(); | 680 ModelTypeSet partially_synced_types = ModelTypeSet::All(); |
| 696 partially_synced_types.RemoveAll(InitialSyncEndedTypes()); | 681 partially_synced_types.RemoveAll(InitialSyncEndedTypes()); |
| 697 partially_synced_types.RemoveAll(GetTypesWithEmptyProgressMarkerToken( | 682 partially_synced_types.RemoveAll(GetTypesWithEmptyProgressMarkerToken( |
| 698 ModelTypeSet::All())); | 683 ModelTypeSet::All())); |
| 699 | 684 |
| 700 UMA_HISTOGRAM_COUNTS("Sync.PartiallySyncedTypes", | 685 UMA_HISTOGRAM_COUNTS("Sync.PartiallySyncedTypes", |
| 701 partially_synced_types.Size()); | 686 partially_synced_types.Size()); |
| 702 if (partially_synced_types.Empty()) | 687 if (partially_synced_types.Empty()) |
| 703 return true; | 688 return true; |
| 704 return directory()->PurgeEntriesWithTypeIn(partially_synced_types); | 689 return directory()->PurgeEntriesWithTypeIn(partially_synced_types); |
| 705 } | 690 } |
| 706 | 691 |
| 707 void SyncManagerImpl::UpdateCredentials( | 692 void SyncManagerImpl::UpdateCredentials( |
| 708 const SyncCredentials& credentials) { | 693 const SyncCredentials& credentials) { |
| 709 DCHECK(thread_checker_.CalledOnValidThread()); | 694 DCHECK(thread_checker_.CalledOnValidThread()); |
| 695 DCHECK(initialized_); |
| 710 DCHECK_EQ(credentials.email, share_.name); | 696 DCHECK_EQ(credentials.email, share_.name); |
| 711 DCHECK(!credentials.email.empty()); | 697 DCHECK(!credentials.email.empty()); |
| 712 DCHECK(!credentials.sync_token.empty()); | 698 DCHECK(!credentials.sync_token.empty()); |
| 713 | 699 |
| 714 observing_ip_address_changes_ = true; | 700 observing_ip_address_changes_ = true; |
| 715 if (connection_manager_->set_auth_token(credentials.sync_token)) { | 701 if (!connection_manager_->set_auth_token(credentials.sync_token)) |
| 716 sync_notifier_->UpdateCredentials( | 702 return; // Auth token is known to be invalid, so exit early. |
| 717 credentials.email, credentials.sync_token); | 703 |
| 718 if (initialized_) { | 704 sync_notifier_->UpdateCredentials(credentials.email, credentials.sync_token); |
| 719 scheduler_->OnCredentialsUpdated(); | 705 scheduler_->OnCredentialsUpdated(); |
| 720 } | |
| 721 } | |
| 722 } | 706 } |
| 723 | 707 |
| 724 void SyncManagerImpl::UpdateEnabledTypes( | 708 void SyncManagerImpl::UpdateEnabledTypes( |
| 725 const ModelTypeSet& enabled_types) { | 709 const ModelTypeSet& enabled_types) { |
| 726 DCHECK(thread_checker_.CalledOnValidThread()); | 710 DCHECK(thread_checker_.CalledOnValidThread()); |
| 727 sync_notifier_->UpdateRegisteredIds(this, | 711 sync_notifier_->UpdateRegisteredIds(this, |
| 728 ModelTypeSetToObjectIdSet(enabled_types)); | 712 ModelTypeSetToObjectIdSet(enabled_types)); |
| 729 } | 713 } |
| 730 | 714 |
| 731 void SyncManagerImpl::SetEncryptionPassphrase( | 715 void SyncManagerImpl::SetEncryptionPassphrase( |
| (...skipping 1131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1863 int SyncManagerImpl::GetDefaultNudgeDelay() { | 1847 int SyncManagerImpl::GetDefaultNudgeDelay() { |
| 1864 return kDefaultNudgeDelayMilliseconds; | 1848 return kDefaultNudgeDelayMilliseconds; |
| 1865 } | 1849 } |
| 1866 | 1850 |
| 1867 // static. | 1851 // static. |
| 1868 int SyncManagerImpl::GetPreferencesNudgeDelay() { | 1852 int SyncManagerImpl::GetPreferencesNudgeDelay() { |
| 1869 return kPreferencesNudgeDelayMilliseconds; | 1853 return kPreferencesNudgeDelayMilliseconds; |
| 1870 } | 1854 } |
| 1871 | 1855 |
| 1872 } // namespace syncer | 1856 } // namespace syncer |
| OLD | NEW |