| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "build/build_config.h" |
| |
| #include "chrome/browser/sync/glue/sync_backend_host.h" |
| |
| #include <algorithm> |
| #include <map> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/file_util.h" |
| #include "base/files/file_path.h" |
| #include "base/location.h" |
| #include "base/metrics/histogram.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/timer.h" |
| #include "base/tracked_objects.h" |
| #include "base/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/token_service.h" |
| #include "chrome/browser/signin/token_service_factory.h" |
| #include "chrome/browser/sync/glue/android_invalidator_bridge.h" |
| #include "chrome/browser/sync/glue/android_invalidator_bridge_proxy.h" |
| #include "chrome/browser/sync/glue/change_processor.h" |
| #include "chrome/browser/sync/glue/chrome_encryptor.h" |
| #include "chrome/browser/sync/glue/device_info.h" |
| #include "chrome/browser/sync/glue/sync_backend_registrar.h" |
| #include "chrome/browser/sync/glue/synced_device_tracker.h" |
| #include "chrome/browser/sync/invalidations/invalidator_storage.h" |
| #include "chrome/browser/sync/sync_prefs.h" |
| #include "chrome/common/chrome_notification_types.h" |
| #include "chrome/common/chrome_notification_types.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/chrome_version_info.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/common/content_client.h" |
| #include "google_apis/gaia/gaia_constants.h" |
| #include "jingle/notifier/base/notification_method.h" |
| #include "jingle/notifier/base/notifier_options.h" |
| #include "net/base/host_port_pair.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "sync/internal_api/public/base_transaction.h" |
| #include "sync/internal_api/public/engine/model_safe_worker.h" |
| #include "sync/internal_api/public/http_bridge.h" |
| #include "sync/internal_api/public/internal_components_factory_impl.h" |
| #include "sync/internal_api/public/read_transaction.h" |
| #include "sync/internal_api/public/sync_manager_factory.h" |
| #include "sync/internal_api/public/util/experiments.h" |
| #include "sync/internal_api/public/util/sync_string_conversions.h" |
| #include "sync/notifier/invalidator.h" |
| #include "sync/protocol/encryption.pb.h" |
| #include "sync/protocol/sync.pb.h" |
| #include "sync/util/nigori.h" |
| |
| static const int kSaveChangesIntervalSeconds = 10; |
| static const base::FilePath::CharType kSyncDataFolderName[] = |
| FILE_PATH_LITERAL("Sync Data"); |
| |
| typedef TokenService::TokenAvailableDetails TokenAvailableDetails; |
| |
| typedef GoogleServiceAuthError AuthError; |
| |
| namespace browser_sync { |
| |
| using content::BrowserThread; |
| using syncer::InternalComponentsFactory; |
| using syncer::InternalComponentsFactoryImpl; |
| using syncer::sessions::SyncSessionSnapshot; |
| using syncer::SyncCredentials; |
| |
| // Helper macros to log with the syncer thread name; useful when there |
| // are multiple syncers involved. |
| |
| #define SLOG(severity) LOG(severity) << name_ << ": " |
| |
| #define SDVLOG(verbose_level) DVLOG(verbose_level) << name_ << ": " |
| |
| class SyncBackendHost::Core |
| : public base::RefCountedThreadSafe<SyncBackendHost::Core>, |
| public syncer::SyncEncryptionHandler::Observer, |
| public syncer::SyncManager::Observer, |
| public syncer::InvalidationHandler { |
| public: |
| Core(const std::string& name, |
| const base::FilePath& sync_data_folder_path, |
| const base::WeakPtr<SyncBackendHost>& backend); |
| |
| // SyncManager::Observer implementation. The Core just acts like an air |
| // traffic controller here, forwarding incoming messages to appropriate |
| // landing threads. |
| virtual void OnSyncCycleCompleted( |
| const syncer::sessions::SyncSessionSnapshot& snapshot) OVERRIDE; |
| virtual void OnInitializationComplete( |
| const syncer::WeakHandle<syncer::JsBackend>& js_backend, |
| const syncer::WeakHandle<syncer::DataTypeDebugInfoListener>& |
| debug_info_listener, |
| bool success, |
| syncer::ModelTypeSet restored_types) OVERRIDE; |
| virtual void OnConnectionStatusChange( |
| syncer::ConnectionStatus status) OVERRIDE; |
| virtual void OnStopSyncingPermanently() OVERRIDE; |
| virtual void OnUpdatedToken(const std::string& token) OVERRIDE; |
| virtual void OnActionableError( |
| const syncer::SyncProtocolError& sync_error) OVERRIDE; |
| |
| // SyncEncryptionHandler::Observer implementation. |
| virtual void OnPassphraseRequired( |
| syncer::PassphraseRequiredReason reason, |
| const sync_pb::EncryptedData& pending_keys) OVERRIDE; |
| virtual void OnPassphraseAccepted() OVERRIDE; |
| virtual void OnBootstrapTokenUpdated( |
| const std::string& bootstrap_token, |
| syncer::BootstrapTokenType type) OVERRIDE; |
| virtual void OnEncryptedTypesChanged( |
| syncer::ModelTypeSet encrypted_types, |
| bool encrypt_everything) OVERRIDE; |
| virtual void OnEncryptionComplete() OVERRIDE; |
| virtual void OnCryptographerStateChanged( |
| syncer::Cryptographer* cryptographer) OVERRIDE; |
| virtual void OnPassphraseTypeChanged(syncer::PassphraseType type, |
| base::Time passphrase_time) OVERRIDE; |
| |
| // syncer::InvalidationHandler implementation. |
| virtual void OnInvalidatorStateChange( |
| syncer::InvalidatorState state) OVERRIDE; |
| virtual void OnIncomingInvalidation( |
| const syncer::ObjectIdInvalidationMap& invalidation_map) OVERRIDE; |
| |
| // Note: |
| // |
| // The Do* methods are the various entry points from our |
| // SyncBackendHost. They are all called on the sync thread to |
| // actually perform synchronous (and potentially blocking) syncapi |
| // operations. |
| // |
| // Called to perform initialization of the syncapi on behalf of |
| // SyncBackendHost::Initialize. |
| void DoInitialize(const DoInitializeOptions& options); |
| |
| // Called to perform credential update on behalf of |
| // SyncBackendHost::UpdateCredentials. |
| void DoUpdateCredentials(const syncer::SyncCredentials& credentials); |
| |
| // Called to update the given registered ids on behalf of |
| // SyncBackendHost::UpdateRegisteredInvalidationIds. |
| void DoUpdateRegisteredInvalidationIds(const syncer::ObjectIdSet& ids); |
| |
| // Called to tell the syncapi to start syncing (generally after |
| // initialization and authentication). |
| void DoStartSyncing(const syncer::ModelSafeRoutingInfo& routing_info); |
| |
| // Called to set the passphrase for encryption. |
| void DoSetEncryptionPassphrase(const std::string& passphrase, |
| bool is_explicit); |
| |
| // Called to decrypt the pending keys. |
| void DoSetDecryptionPassphrase(const std::string& passphrase); |
| |
| // Called to turn on encryption of all sync data as well as |
| // reencrypt everything. |
| void DoEnableEncryptEverything(); |
| |
| // Called at startup to download the control types. Will invoke |
| // DoInitialProcessControlTypes on success, and OnControlTypesDownloadRetry |
| // if an error occurred. |
| void DoDownloadControlTypes(); |
| |
| // Ask the syncer to check for updates for the specified types. |
| void DoRefreshTypes(syncer::ModelTypeSet types); |
| |
| // Invoked if we failed to download the necessary control types at startup. |
| // Invokes SyncBackendHost::HandleControlTypesDownloadRetry. |
| void OnControlTypesDownloadRetry(); |
| |
| // Called to perform tasks which require the control data to be downloaded. |
| // This includes refreshing encryption, setting up the device info change |
| // processor, etc. |
| void DoInitialProcessControlTypes(); |
| |
| // Some parts of DoInitialProcessControlTypes() may be executed on a different |
| // thread. This function asynchronously continues the work started in |
| // DoInitialProcessControlTypes() once that other thread gets back to us. |
| void DoFinishInitialProcessControlTypes(); |
| |
| // The shutdown order is a bit complicated: |
| // 1) From |sync_thread_|, invoke the syncapi Shutdown call to do |
| // a final SaveChanges, and close sqlite handles. |
| // 2) Then, from |frontend_loop_|, halt the sync_thread_ (which is |
| // a blocking call). This causes syncapi thread-exit handlers |
| // to run and make use of cached pointers to various components |
| // owned implicitly by us. |
| // 3) Destroy this Core. That will delete syncapi components in a |
| // safe order because the thread that was using them has exited |
| // (in step 2). |
| void DoStopSyncManagerForShutdown(const base::Closure& closure); |
| void DoShutdown(bool stopping_sync); |
| void DoDestroySyncManager(); |
| |
| // Configuration methods that must execute on sync loop. |
| void DoConfigureSyncer( |
| syncer::ConfigureReason reason, |
| syncer::ModelTypeSet types_to_config, |
| syncer::ModelTypeSet failed_types, |
| const syncer::ModelSafeRoutingInfo routing_info, |
| const base::Callback<void(syncer::ModelTypeSet)>& ready_task, |
| const base::Closure& retry_callback); |
| void DoFinishConfigureDataTypes( |
| syncer::ModelTypeSet types_to_config, |
| const base::Callback<void(syncer::ModelTypeSet)>& ready_task); |
| void DoRetryConfiguration( |
| const base::Closure& retry_callback); |
| |
| // Set the base request context to use when making HTTP calls. |
| // This method will add a reference to the context to persist it |
| // on the IO thread. Must be removed from IO thread. |
| |
| syncer::SyncManager* sync_manager() { return sync_manager_.get(); } |
| |
| SyncedDeviceTracker* synced_device_tracker() { |
| return synced_device_tracker_.get(); |
| } |
| |
| // Delete the sync data folder to cleanup backend data. Happens the first |
| // time sync is enabled for a user (to prevent accidentally reusing old |
| // sync databases), as well as shutdown when you're no longer syncing. |
| void DeleteSyncDataFolder(); |
| |
| private: |
| friend class base::RefCountedThreadSafe<SyncBackendHost::Core>; |
| friend class SyncBackendHostForProfileSyncTest; |
| |
| virtual ~Core(); |
| |
| // Invoked when initialization of syncapi is complete and we can start |
| // our timer. |
| // This must be called from the thread on which SaveChanges is intended to |
| // be run on; the host's |sync_thread_|. |
| void StartSavingChanges(); |
| |
| // Invoked periodically to tell the syncapi to persist its state |
| // by writing to disk. |
| // This is called from the thread we were created on (which is the |
| // SyncBackendHost |sync_thread_|), using a repeating timer that is kicked |
| // off as soon as the SyncManager tells us it completed |
| // initialization. |
| void SaveChanges(); |
| |
| // Name used for debugging. |
| const std::string name_; |
| |
| // Path of the folder that stores the sync data files. |
| const base::FilePath sync_data_folder_path_; |
| |
| // Our parent SyncBackendHost. |
| syncer::WeakHandle<SyncBackendHost> host_; |
| |
| // The loop where all the sync backend operations happen. |
| // Non-NULL only between calls to DoInitialize() and DoShutdown(). |
| MessageLoop* sync_loop_; |
| |
| // Our parent's registrar (not owned). Non-NULL only between |
| // calls to DoInitialize() and DoShutdown(). |
| SyncBackendRegistrar* registrar_; |
| |
| // The timer used to periodically call SaveChanges. |
| scoped_ptr<base::RepeatingTimer<Core> > save_changes_timer_; |
| |
| // Our encryptor, which uses Chrome's encryption functions. |
| ChromeEncryptor encryptor_; |
| |
| // A special ChangeProcessor that tracks the DEVICE_INFO type for us. |
| scoped_ptr<SyncedDeviceTracker> synced_device_tracker_; |
| |
| // The top-level syncapi entry point. Lives on the sync thread. |
| scoped_ptr<syncer::SyncManager> sync_manager_; |
| |
| // Whether or not we registered with |sync_manager_| as an invalidation |
| // handler. Necessary since we may end up trying to unregister before we |
| // register in tests (in synchronous initialization mode). |
| // |
| // TODO(akalin): Fix this behavior (see http://crbug.com/140354). |
| bool registered_as_invalidation_handler_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Core); |
| }; |
| |
| namespace { |
| |
| // Parses the given command line for notifier options. |
| notifier::NotifierOptions ParseNotifierOptions( |
| const CommandLine& command_line, |
| const scoped_refptr<net::URLRequestContextGetter>& |
| request_context_getter) { |
| notifier::NotifierOptions notifier_options; |
| notifier_options.request_context_getter = request_context_getter; |
| |
| if (command_line.HasSwitch(switches::kSyncNotificationHostPort)) { |
| notifier_options.xmpp_host_port = |
| net::HostPortPair::FromString( |
| command_line.GetSwitchValueASCII( |
| switches::kSyncNotificationHostPort)); |
| DVLOG(1) << "Using " << notifier_options.xmpp_host_port.ToString() |
| << " for test sync notification server."; |
| } |
| |
| notifier_options.try_ssltcp_first = |
| command_line.HasSwitch(switches::kSyncTrySsltcpFirstForXmpp); |
| DVLOG_IF(1, notifier_options.try_ssltcp_first) |
| << "Trying SSL/TCP port before XMPP port for notifications."; |
| |
| notifier_options.invalidate_xmpp_login = |
| command_line.HasSwitch(switches::kSyncInvalidateXmppLogin); |
| DVLOG_IF(1, notifier_options.invalidate_xmpp_login) |
| << "Invalidating sync XMPP login."; |
| |
| notifier_options.allow_insecure_connection = |
| command_line.HasSwitch(switches::kSyncAllowInsecureXmppConnection); |
| DVLOG_IF(1, notifier_options.allow_insecure_connection) |
| << "Allowing insecure XMPP connections."; |
| |
| if (command_line.HasSwitch(switches::kSyncNotificationMethod)) { |
| const std::string notification_method_str( |
| command_line.GetSwitchValueASCII(switches::kSyncNotificationMethod)); |
| notifier_options.notification_method = |
| notifier::StringToNotificationMethod(notification_method_str); |
| } |
| |
| return notifier_options; |
| } |
| |
| } // namespace |
| |
| SyncBackendHost::SyncBackendHost( |
| const std::string& name, |
| Profile* profile, |
| const base::WeakPtr<SyncPrefs>& sync_prefs, |
| const base::WeakPtr<InvalidatorStorage>& invalidator_storage) |
| : weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| sync_thread_("Chrome_SyncThread"), |
| frontend_loop_(MessageLoop::current()), |
| profile_(profile), |
| name_(name), |
| core_(new Core(name, profile_->GetPath().Append(kSyncDataFolderName), |
| weak_ptr_factory_.GetWeakPtr())), |
| initialization_state_(NOT_ATTEMPTED), |
| sync_prefs_(sync_prefs), |
| invalidator_factory_( |
| ParseNotifierOptions(*CommandLine::ForCurrentProcess(), |
| profile_->GetRequestContext()), |
| content::GetUserAgent(GURL()), |
| invalidator_storage), |
| frontend_(NULL), |
| cached_passphrase_type_(syncer::IMPLICIT_PASSPHRASE) { |
| } |
| |
| SyncBackendHost::SyncBackendHost(Profile* profile) |
| : weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| sync_thread_("Chrome_SyncThread"), |
| frontend_loop_(MessageLoop::current()), |
| profile_(profile), |
| name_("Unknown"), |
| initialization_state_(NOT_ATTEMPTED), |
| invalidator_factory_( |
| ParseNotifierOptions(*CommandLine::ForCurrentProcess(), |
| profile_->GetRequestContext()), |
| content::GetUserAgent(GURL()), |
| base::WeakPtr<syncer::InvalidationStateTracker>()), |
| frontend_(NULL), |
| cached_passphrase_type_(syncer::IMPLICIT_PASSPHRASE) { |
| } |
| |
| SyncBackendHost::~SyncBackendHost() { |
| DCHECK(!core_ && !frontend_) << "Must call Shutdown before destructor."; |
| DCHECK(!android_invalidator_bridge_.get()); |
| DCHECK(!registrar_.get()); |
| } |
| |
| namespace { |
| |
| scoped_ptr<syncer::HttpPostProviderFactory> MakeHttpBridgeFactory( |
| const scoped_refptr<net::URLRequestContextGetter>& getter) { |
| chrome::VersionInfo version_info; |
| return scoped_ptr<syncer::HttpPostProviderFactory>( |
| new syncer::HttpBridgeFactory( |
| getter, DeviceInfo::MakeUserAgentForSyncApi(version_info))); |
| } |
| |
| } // namespace |
| |
| void SyncBackendHost::Initialize( |
| SyncFrontend* frontend, |
| const syncer::WeakHandle<syncer::JsEventHandler>& event_handler, |
| const GURL& sync_service_url, |
| const SyncCredentials& credentials, |
| bool delete_sync_data_folder, |
| syncer::SyncManagerFactory* sync_manager_factory, |
| syncer::UnrecoverableErrorHandler* unrecoverable_error_handler, |
| syncer::ReportUnrecoverableErrorFunction |
| report_unrecoverable_error_function) { |
| if (!sync_thread_.Start()) |
| return; |
| |
| android_invalidator_bridge_.reset( |
| new AndroidInvalidatorBridge( |
| profile_, sync_thread_.message_loop_proxy())); |
| |
| frontend_ = frontend; |
| DCHECK(frontend); |
| |
| registrar_.reset(new SyncBackendRegistrar(name_, |
| profile_, |
| sync_thread_.message_loop())); |
| syncer::ModelSafeRoutingInfo routing_info; |
| std::vector<syncer::ModelSafeWorker*> workers; |
| registrar_->GetModelSafeRoutingInfo(&routing_info); |
| registrar_->GetWorkers(&workers); |
| |
| InternalComponentsFactory::Switches factory_switches = { |
| InternalComponentsFactory::ENCRYPTION_LEGACY, |
| InternalComponentsFactory::BACKOFF_NORMAL |
| }; |
| |
| CommandLine* cl = CommandLine::ForCurrentProcess(); |
| if (cl->HasSwitch(switches::kSyncKeystoreEncryption)) { |
| factory_switches.encryption_method = |
| InternalComponentsFactoryImpl::ENCRYPTION_KEYSTORE; |
| } |
| if (cl->HasSwitch(switches::kSyncShortInitialRetryOverride)) { |
| factory_switches.backoff_override = |
| InternalComponentsFactoryImpl::BACKOFF_SHORT_INITIAL_RETRY_OVERRIDE; |
| } |
| |
| initialization_state_ = CREATING_SYNC_MANAGER; |
| InitCore(DoInitializeOptions( |
| sync_thread_.message_loop(), |
| registrar_.get(), |
| routing_info, |
| workers, |
| &extensions_activity_monitor_, |
| event_handler, |
| sync_service_url, |
| base::Bind(&MakeHttpBridgeFactory, |
| make_scoped_refptr(profile_->GetRequestContext())), |
| credentials, |
| android_invalidator_bridge_.get(), |
| &invalidator_factory_, |
| sync_manager_factory, |
| delete_sync_data_folder, |
| sync_prefs_->GetEncryptionBootstrapToken(), |
| sync_prefs_->GetKeystoreEncryptionBootstrapToken(), |
| new InternalComponentsFactoryImpl(factory_switches), |
| unrecoverable_error_handler, |
| report_unrecoverable_error_function)); |
| } |
| |
| void SyncBackendHost::UpdateCredentials(const SyncCredentials& credentials) { |
| DCHECK(sync_thread_.IsRunning()); |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoUpdateCredentials, core_.get(), |
| credentials)); |
| } |
| |
| void SyncBackendHost::UpdateRegisteredInvalidationIds( |
| const syncer::ObjectIdSet& ids) { |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| DCHECK(sync_thread_.IsRunning()); |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoUpdateRegisteredInvalidationIds, |
| core_.get(), ids)); |
| } |
| |
| void SyncBackendHost::StartSyncingWithServer() { |
| SDVLOG(1) << "SyncBackendHost::StartSyncingWithServer called."; |
| |
| syncer::ModelSafeRoutingInfo routing_info; |
| registrar_->GetModelSafeRoutingInfo(&routing_info); |
| |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoStartSyncing, |
| core_.get(), routing_info)); |
| } |
| |
| void SyncBackendHost::SetEncryptionPassphrase(const std::string& passphrase, |
| bool is_explicit) { |
| DCHECK(sync_thread_.IsRunning()); |
| if (!IsNigoriEnabled()) { |
| NOTREACHED() << "SetEncryptionPassphrase must never be called when nigori" |
| " is disabled."; |
| return; |
| } |
| |
| // We should never be called with an empty passphrase. |
| DCHECK(!passphrase.empty()); |
| |
| // This should only be called by the frontend. |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| |
| // SetEncryptionPassphrase should never be called if we are currently |
| // encrypted with an explicit passphrase. |
| DCHECK(cached_passphrase_type_ == syncer::KEYSTORE_PASSPHRASE || |
| cached_passphrase_type_ == syncer::IMPLICIT_PASSPHRASE); |
| |
| // Post an encryption task on the syncer thread. |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoSetEncryptionPassphrase, core_.get(), |
| passphrase, is_explicit)); |
| } |
| |
| bool SyncBackendHost::SetDecryptionPassphrase(const std::string& passphrase) { |
| if (!IsNigoriEnabled()) { |
| NOTREACHED() << "SetDecryptionPassphrase must never be called when nigori" |
| " is disabled."; |
| return false; |
| } |
| |
| // We should never be called with an empty passphrase. |
| DCHECK(!passphrase.empty()); |
| |
| // This should only be called by the frontend. |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| |
| // This should only be called when we have cached pending keys. |
| DCHECK(cached_pending_keys_.has_blob()); |
| |
| // Check the passphrase that was provided against our local cache of the |
| // cryptographer's pending keys. If this was unsuccessful, the UI layer can |
| // immediately call OnPassphraseRequired without showing the user a spinner. |
| if (!CheckPassphraseAgainstCachedPendingKeys(passphrase)) |
| return false; |
| |
| // Post a decryption task on the syncer thread. |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoSetDecryptionPassphrase, core_.get(), |
| passphrase)); |
| |
| // Since we were able to decrypt the cached pending keys with the passphrase |
| // provided, we immediately alert the UI layer that the passphrase was |
| // accepted. This will avoid the situation where a user enters a passphrase, |
| // clicks OK, immediately reopens the advanced settings dialog, and gets an |
| // unnecessary prompt for a passphrase. |
| // Note: It is not guaranteed that the passphrase will be accepted by the |
| // syncer thread, since we could receive a new nigori node while the task is |
| // pending. This scenario is a valid race, and SetDecryptionPassphrase can |
| // trigger a new OnPassphraseRequired if it needs to. |
| NotifyPassphraseAccepted(); |
| return true; |
| } |
| |
| void SyncBackendHost::StopSyncManagerForShutdown( |
| const base::Closure& closure) { |
| DCHECK_GT(initialization_state_, NOT_ATTEMPTED); |
| if (initialization_state_ == CREATING_SYNC_MANAGER) { |
| // We post here to implicitly wait for the SyncManager to be created, |
| // if needed. We have to wait, since we need to shutdown immediately, |
| // and we need to tell the SyncManager so it can abort any activity |
| // (net I/O, data application). |
| DCHECK(sync_thread_.IsRunning()); |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind( |
| &SyncBackendHost::Core::DoStopSyncManagerForShutdown, |
| core_.get(), |
| closure)); |
| } else { |
| core_->DoStopSyncManagerForShutdown(closure); |
| } |
| } |
| |
| void SyncBackendHost::StopSyncingForShutdown() { |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| |
| // Immediately stop sending messages to the frontend. |
| frontend_ = NULL; |
| |
| // Stop listening for and forwarding locally-triggered sync refresh requests. |
| notification_registrar_.RemoveAll(); |
| |
| // Thread shutdown should occur in the following order: |
| // - Sync Thread |
| // - UI Thread (stops some time after we return from this call). |
| // |
| // In order to achieve this, we first shutdown components from the UI thread |
| // and send signals to abort components that may be busy on the sync thread. |
| // The callback (OnSyncerShutdownComplete) will happen on the sync thread, |
| // after which we'll shutdown components on the sync thread, and then be |
| // able to stop the sync loop. |
| if (sync_thread_.IsRunning()) { |
| StopSyncManagerForShutdown( |
| base::Bind(&SyncBackendRegistrar::OnSyncerShutdownComplete, |
| base::Unretained(registrar_.get()))); |
| |
| // Before joining the sync_thread_, we wait for the UIModelWorker to |
| // give us the green light that it is not depending on the frontend_loop_ |
| // to process any more tasks. Stop() blocks until this termination |
| // condition is true. |
| base::Time stop_registrar_start_time = base::Time::Now(); |
| if (registrar_.get()) |
| registrar_->StopOnUIThread(); |
| base::TimeDelta stop_registrar_time = base::Time::Now() - |
| stop_registrar_start_time; |
| UMA_HISTOGRAM_TIMES("Sync.Shutdown.StopRegistrarTime", |
| stop_registrar_time); |
| } else { |
| // If the sync thread isn't running, then the syncer is effectively |
| // stopped. Moreover, it implies that we never attempted initialization, |
| // so the registrar won't need stopping either. |
| DCHECK_EQ(initialization_state_, NOT_ATTEMPTED); |
| DCHECK(!registrar_.get()); |
| } |
| } |
| |
| void SyncBackendHost::Shutdown(bool sync_disabled) { |
| // StopSyncingForShutdown() (which nulls out |frontend_|) should be |
| // called first. |
| DCHECK(!frontend_); |
| // TODO(tim): DCHECK(registrar_->StoppedOnUIThread()) would be nice. |
| if (sync_thread_.IsRunning()) { |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoShutdown, core_.get(), |
| sync_disabled)); |
| |
| if (android_invalidator_bridge_.get()) |
| android_invalidator_bridge_->StopForShutdown(); |
| } |
| |
| // Stop will return once the thread exits, which will be after DoShutdown |
| // runs. DoShutdown needs to run from sync_thread_ because the sync backend |
| // requires any thread that opened sqlite handles to relinquish them |
| // personally. We need to join threads, because otherwise the main Chrome |
| // thread (ui loop) can exit before DoShutdown finishes, at which point |
| // virtually anything the sync backend does (or the post-back to |
| // frontend_loop_ by our Core) will epically fail because the CRT won't be |
| // initialized. |
| // Since we are blocking the UI thread here, we need to turn ourselves in |
| // with the ThreadRestriction police. For sentencing and how we plan to fix |
| // this, see bug 19757. |
| base::Time stop_thread_start_time = base::Time::Now(); |
| { |
| base::ThreadRestrictions::ScopedAllowIO allow_io; |
| sync_thread_.Stop(); |
| } |
| base::TimeDelta stop_sync_thread_time = base::Time::Now() - |
| stop_thread_start_time; |
| UMA_HISTOGRAM_TIMES("Sync.Shutdown.StopSyncThreadTime", |
| stop_sync_thread_time); |
| |
| registrar_.reset(); |
| js_backend_.Reset(); |
| android_invalidator_bridge_.reset(); |
| core_ = NULL; // Releases reference to core_. |
| } |
| |
| void SyncBackendHost::ConfigureDataTypes( |
| syncer::ConfigureReason reason, |
| const DataTypeConfigStateMap& config_state_map, |
| const base::Callback<void(syncer::ModelTypeSet)>& ready_task, |
| const base::Callback<void()>& retry_callback) { |
| // Only one configure is allowed at a time. This is guaranteed by our |
| // callers. The SyncBackendHost requests one configure as the backend is |
| // initializing and waits for it to complete. After initialization, all |
| // configurations will pass through the DataTypeManager, which is careful to |
| // never send a new configure request until the current request succeeds. |
| |
| DCHECK_EQ(initialization_state_, INITIALIZED); |
| |
| // The SyncBackendRegistrar's routing info will be updated by adding the |
| // types_to_add to the list then removing types_to_remove. Any types which |
| // are not in either of those sets will remain untouched. |
| // |
| // Types which were not in the list previously are not fully downloaded, so we |
| // must ask the syncer to download them. Any newly supported datatypes will |
| // not have been in that routing info list, so they will be among the types |
| // downloaded if they are enabled. |
| // |
| // The SyncBackendRegistrar's state was initially derived from the types |
| // detected to have been downloaded in the database. Afterwards it is |
| // modified only by this function. We expect it to remain in sync with the |
| // backend because configuration requests are never aborted; they are retried |
| // until they succeed or the backend is shut down. |
| |
| syncer::ModelTypeSet types_to_download = registrar_->ConfigureDataTypes( |
| GetDataTypesInState(ENABLED, config_state_map), |
| syncer::Union(GetDataTypesInState(DISABLED, config_state_map), |
| GetDataTypesInState(FAILED, config_state_map))); |
| types_to_download.RemoveAll(syncer::ProxyTypes()); |
| if (!types_to_download.Empty()) |
| types_to_download.Put(syncer::NIGORI); |
| |
| // TODO(sync): crbug.com/137550. |
| // It's dangerous to configure types that have progress markers. Types with |
| // progress markers can trigger a MIGRATION_DONE response. We are not |
| // prepared to handle a migration during a configure, so we must ensure that |
| // all our types_to_download actually contain no data before we sync them. |
| // |
| // One common way to end up in this situation used to be types which |
| // downloaded some or all of their data but have not applied it yet. We avoid |
| // problems with those types by purging the data of any such partially synced |
| // types soon after we load the directory. |
| // |
| // Another possible scenario is that we have newly supported or newly enabled |
| // data types being downloaded here but the nigori type, which is always |
| // included in any GetUpdates request, requires migration. The server has |
| // code to detect this scenario based on the configure reason, the fact that |
| // the nigori type is the only requested type which requires migration, and |
| // that the requested types list includes at least one non-nigori type. It |
| // will not send a MIGRATION_DONE response in that case. We still need to be |
| // careful to not send progress markers for non-nigori types, though. If a |
| // non-nigori type in the request requires migration, a MIGRATION_DONE |
| // response will be sent. |
| |
| syncer::ModelSafeRoutingInfo routing_info; |
| registrar_->GetModelSafeRoutingInfo(&routing_info); |
| |
| SDVLOG(1) << "Types " |
| << syncer::ModelTypeSetToString(types_to_download) |
| << " added; calling DoConfigureSyncer"; |
| // TODO(zea): figure out how to bypass this call if no types are being |
| // configured and GetKey is not needed. For now we rely on determining the |
| // need for GetKey as part of the SyncManager::ConfigureSyncer logic. |
| RequestConfigureSyncer(reason, |
| types_to_download, |
| GetDataTypesInState(FAILED, config_state_map), |
| routing_info, |
| ready_task, |
| retry_callback); |
| } |
| |
| void SyncBackendHost::EnableEncryptEverything() { |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoEnableEncryptEverything, |
| core_.get())); |
| } |
| |
| void SyncBackendHost::ActivateDataType( |
| syncer::ModelType type, syncer::ModelSafeGroup group, |
| ChangeProcessor* change_processor) { |
| registrar_->ActivateDataType(type, group, change_processor, GetUserShare()); |
| } |
| |
| void SyncBackendHost::DeactivateDataType(syncer::ModelType type) { |
| registrar_->DeactivateDataType(type); |
| } |
| |
| syncer::UserShare* SyncBackendHost::GetUserShare() const { |
| return core_->sync_manager()->GetUserShare(); |
| } |
| |
| SyncBackendHost::Status SyncBackendHost::GetDetailedStatus() { |
| DCHECK(initialized()); |
| return core_->sync_manager()->GetDetailedStatus(); |
| } |
| |
| SyncSessionSnapshot SyncBackendHost::GetLastSessionSnapshot() const { |
| return last_snapshot_; |
| } |
| |
| bool SyncBackendHost::HasUnsyncedItems() const { |
| DCHECK(initialized()); |
| return core_->sync_manager()->HasUnsyncedItems(); |
| } |
| |
| bool SyncBackendHost::IsNigoriEnabled() const { |
| return registrar_.get() && registrar_->IsNigoriEnabled(); |
| } |
| |
| syncer::PassphraseType SyncBackendHost::GetPassphraseType() const { |
| return cached_passphrase_type_; |
| } |
| |
| base::Time SyncBackendHost::GetExplicitPassphraseTime() const { |
| return cached_explicit_passphrase_time_; |
| } |
| |
| bool SyncBackendHost::IsCryptographerReady( |
| const syncer::BaseTransaction* trans) const { |
| return initialized() && trans->GetCryptographer()->is_ready(); |
| } |
| |
| void SyncBackendHost::GetModelSafeRoutingInfo( |
| syncer::ModelSafeRoutingInfo* out) const { |
| if (initialized()) { |
| CHECK(registrar_.get()); |
| registrar_->GetModelSafeRoutingInfo(out); |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| SyncedDeviceTracker* SyncBackendHost::GetSyncedDeviceTracker() const { |
| if (!initialized()) |
| return NULL; |
| return core_->synced_device_tracker(); |
| } |
| |
| void SyncBackendHost::InitCore(const DoInitializeOptions& options) { |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoInitialize, core_.get(), options)); |
| } |
| |
| void SyncBackendHost::RequestConfigureSyncer( |
| syncer::ConfigureReason reason, |
| syncer::ModelTypeSet types_to_config, |
| syncer::ModelTypeSet failed_types, |
| const syncer::ModelSafeRoutingInfo& routing_info, |
| const base::Callback<void(syncer::ModelTypeSet)>& ready_task, |
| const base::Closure& retry_callback) { |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoConfigureSyncer, |
| core_.get(), |
| reason, |
| types_to_config, |
| failed_types, |
| routing_info, |
| ready_task, |
| retry_callback)); |
| } |
| |
| void SyncBackendHost::FinishConfigureDataTypesOnFrontendLoop( |
| syncer::ModelTypeSet failed_configuration_types, |
| const base::Callback<void(syncer::ModelTypeSet)>& ready_task) { |
| if (!frontend_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| if (!ready_task.is_null()) |
| ready_task.Run(failed_configuration_types); |
| } |
| |
| void SyncBackendHost::HandleSyncManagerInitializationOnFrontendLoop( |
| const syncer::WeakHandle<syncer::JsBackend>& js_backend, |
| const syncer::WeakHandle<syncer::DataTypeDebugInfoListener>& |
| debug_info_listener, |
| syncer::ModelTypeSet restored_types) { |
| DCHECK_EQ(initialization_state_, CREATING_SYNC_MANAGER); |
| DCHECK(!js_backend_.IsInitialized()); |
| |
| initialization_state_ = INITIALIZATING_CONTROL_TYPES; |
| |
| js_backend_ = js_backend; |
| debug_info_listener_ = debug_info_listener; |
| |
| // Inform the registrar of those types that have been fully downloaded and |
| // applied. |
| registrar_->SetInitialTypes(restored_types); |
| |
| // Start forwarding refresh requests to the SyncManager |
| notification_registrar_.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL, |
| content::Source<Profile>(profile_)); |
| |
| // Kick off the next step in SyncBackendHost initialization by downloading |
| // any necessary control types. |
| sync_thread_.message_loop()->PostTask( |
| FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoDownloadControlTypes, |
| core_.get())); |
| } |
| |
| void SyncBackendHost::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK_EQ(type, chrome::NOTIFICATION_SYNC_REFRESH_LOCAL); |
| |
| content::Details<const syncer::ModelTypeInvalidationMap> |
| state_details(details); |
| const syncer::ModelTypeInvalidationMap& invalidation_map = |
| *(state_details.ptr()); |
| const syncer::ModelTypeSet types = |
| ModelTypeInvalidationMapToSet(invalidation_map); |
| sync_thread_.message_loop()->PostTask(FROM_HERE, |
| base::Bind(&SyncBackendHost::Core::DoRefreshTypes, core_.get(), types)); |
| } |
| |
| SyncBackendHost::DoInitializeOptions::DoInitializeOptions( |
| MessageLoop* sync_loop, |
| SyncBackendRegistrar* registrar, |
| const syncer::ModelSafeRoutingInfo& routing_info, |
| const std::vector<syncer::ModelSafeWorker*>& workers, |
| syncer::ExtensionsActivityMonitor* extensions_activity_monitor, |
| const syncer::WeakHandle<syncer::JsEventHandler>& event_handler, |
| const GURL& service_url, |
| MakeHttpBridgeFactoryFn make_http_bridge_factory_fn, |
| const syncer::SyncCredentials& credentials, |
| AndroidInvalidatorBridge* android_invalidator_bridge, |
| syncer::InvalidatorFactory* invalidator_factory, |
| syncer::SyncManagerFactory* sync_manager_factory, |
| bool delete_sync_data_folder, |
| const std::string& restored_key_for_bootstrapping, |
| const std::string& restored_keystore_key_for_bootstrapping, |
| InternalComponentsFactory* internal_components_factory, |
| syncer::UnrecoverableErrorHandler* unrecoverable_error_handler, |
| syncer::ReportUnrecoverableErrorFunction |
| report_unrecoverable_error_function) |
| : sync_loop(sync_loop), |
| registrar(registrar), |
| routing_info(routing_info), |
| workers(workers), |
| extensions_activity_monitor(extensions_activity_monitor), |
| event_handler(event_handler), |
| service_url(service_url), |
| make_http_bridge_factory_fn(make_http_bridge_factory_fn), |
| credentials(credentials), |
| android_invalidator_bridge(android_invalidator_bridge), |
| invalidator_factory(invalidator_factory), |
| sync_manager_factory(sync_manager_factory), |
| delete_sync_data_folder(delete_sync_data_folder), |
| restored_key_for_bootstrapping(restored_key_for_bootstrapping), |
| restored_keystore_key_for_bootstrapping( |
| restored_keystore_key_for_bootstrapping), |
| internal_components_factory(internal_components_factory), |
| unrecoverable_error_handler(unrecoverable_error_handler), |
| report_unrecoverable_error_function( |
| report_unrecoverable_error_function) { |
| } |
| |
| SyncBackendHost::DoInitializeOptions::~DoInitializeOptions() {} |
| |
| SyncBackendHost::Core::Core(const std::string& name, |
| const base::FilePath& sync_data_folder_path, |
| const base::WeakPtr<SyncBackendHost>& backend) |
| : name_(name), |
| sync_data_folder_path_(sync_data_folder_path), |
| host_(backend), |
| sync_loop_(NULL), |
| registrar_(NULL), |
| registered_as_invalidation_handler_(false) { |
| DCHECK(backend.get()); |
| } |
| |
| SyncBackendHost::Core::~Core() { |
| DCHECK(!sync_manager_.get()); |
| DCHECK(!sync_loop_); |
| } |
| |
| void SyncBackendHost::Core::OnSyncCycleCompleted( |
| const SyncSessionSnapshot& snapshot) { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::HandleSyncCycleCompletedOnFrontendLoop, |
| snapshot); |
| } |
| |
| void SyncBackendHost::Core::DoDownloadControlTypes() { |
| syncer::ModelTypeSet new_control_types = registrar_->ConfigureDataTypes( |
| syncer::ControlTypes(), syncer::ModelTypeSet()); |
| syncer::ModelSafeRoutingInfo routing_info; |
| registrar_->GetModelSafeRoutingInfo(&routing_info); |
| SDVLOG(1) << "Control Types " |
| << syncer::ModelTypeSetToString(new_control_types) |
| << " added; calling DoConfigureSyncer"; |
| |
| sync_manager_->ConfigureSyncer( |
| syncer::CONFIGURE_REASON_NEW_CLIENT, |
| new_control_types, |
| syncer::ModelTypeSet(), |
| routing_info, |
| base::Bind(&SyncBackendHost::Core::DoInitialProcessControlTypes, |
| this), |
| base::Bind(&SyncBackendHost::Core::OnControlTypesDownloadRetry, |
| this)); |
| } |
| |
| void SyncBackendHost::Core::DoRefreshTypes(syncer::ModelTypeSet types) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| sync_manager_->RefreshTypes(types); |
| } |
| |
| void SyncBackendHost::Core::OnControlTypesDownloadRetry() { |
| host_.Call(FROM_HERE, |
| &SyncBackendHost::HandleControlTypesDownloadRetry); |
| } |
| |
| void SyncBackendHost::Core::OnInitializationComplete( |
| const syncer::WeakHandle<syncer::JsBackend>& js_backend, |
| const syncer::WeakHandle<syncer::DataTypeDebugInfoListener>& |
| debug_info_listener, |
| bool success, |
| const syncer::ModelTypeSet restored_types) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| |
| if (!success) { |
| DoDestroySyncManager(); |
| host_.Call(FROM_HERE, |
| &SyncBackendHost::HandleInitializationCompletedOnFrontendLoop, |
| false); |
| return; |
| } |
| |
| // Register for encryption related changes now. We have to do this before |
| // the initializing downloading control types or initializing the encryption |
| // handler in order to receive notifications triggered during encryption |
| // startup. |
| sync_manager_->GetEncryptionHandler()->AddObserver(this); |
| |
| // Sync manager initialization is complete, so we can schedule recurring |
| // SaveChanges. |
| sync_loop_->PostTask(FROM_HERE, |
| base::Bind(&Core::StartSavingChanges, this)); |
| |
| host_.Call(FROM_HERE, |
| &SyncBackendHost::HandleSyncManagerInitializationOnFrontendLoop, |
| js_backend, |
| debug_info_listener, |
| restored_types); |
| } |
| |
| void SyncBackendHost::Core::OnConnectionStatusChange( |
| syncer::ConnectionStatus status) { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::HandleConnectionStatusChangeOnFrontendLoop, status); |
| } |
| |
| void SyncBackendHost::Core::OnPassphraseRequired( |
| syncer::PassphraseRequiredReason reason, |
| const sync_pb::EncryptedData& pending_keys) { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::NotifyPassphraseRequired, reason, pending_keys); |
| } |
| |
| void SyncBackendHost::Core::OnPassphraseAccepted() { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::NotifyPassphraseAccepted); |
| } |
| |
| void SyncBackendHost::Core::OnBootstrapTokenUpdated( |
| const std::string& bootstrap_token, |
| syncer::BootstrapTokenType type) { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call(FROM_HERE, |
| &SyncBackendHost::PersistEncryptionBootstrapToken, |
| bootstrap_token, |
| type); |
| } |
| |
| void SyncBackendHost::Core::OnStopSyncingPermanently() { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::HandleStopSyncingPermanentlyOnFrontendLoop); |
| } |
| |
| void SyncBackendHost::Core::OnUpdatedToken(const std::string& token) { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::NotifyUpdatedToken, token); |
| } |
| |
| void SyncBackendHost::Core::OnEncryptedTypesChanged( |
| syncer::ModelTypeSet encrypted_types, |
| bool encrypt_everything) { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| // NOTE: We're in a transaction. |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::NotifyEncryptedTypesChanged, |
| encrypted_types, encrypt_everything); |
| } |
| |
| void SyncBackendHost::Core::OnEncryptionComplete() { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| // NOTE: We're in a transaction. |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::NotifyEncryptionComplete); |
| } |
| |
| void SyncBackendHost::Core::OnCryptographerStateChanged( |
| syncer::Cryptographer* cryptographer) { |
| // Do nothing. |
| } |
| |
| void SyncBackendHost::Core::OnPassphraseTypeChanged( |
| syncer::PassphraseType type, base::Time passphrase_time) { |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::HandlePassphraseTypeChangedOnFrontendLoop, |
| type, passphrase_time); |
| } |
| |
| void SyncBackendHost::Core::OnActionableError( |
| const syncer::SyncProtocolError& sync_error) { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::HandleActionableErrorEventOnFrontendLoop, |
| sync_error); |
| } |
| |
| void SyncBackendHost::Core::OnInvalidatorStateChange( |
| syncer::InvalidatorState state) { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call(FROM_HERE, |
| &SyncBackendHost::HandleInvalidatorStateChangeOnFrontendLoop, |
| state); |
| } |
| |
| void SyncBackendHost::Core::OnIncomingInvalidation( |
| const syncer::ObjectIdInvalidationMap& invalidation_map) { |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call(FROM_HERE, |
| &SyncBackendHost::HandleIncomingInvalidationOnFrontendLoop, |
| invalidation_map); |
| } |
| |
| void SyncBackendHost::Core::DoInitialize(const DoInitializeOptions& options) { |
| DCHECK(!sync_loop_); |
| sync_loop_ = options.sync_loop; |
| DCHECK(sync_loop_); |
| |
| // Blow away the partial or corrupt sync data folder before doing any more |
| // initialization, if necessary. |
| if (options.delete_sync_data_folder) { |
| DeleteSyncDataFolder(); |
| } |
| |
| // Make sure that the directory exists before initializing the backend. |
| // If it already exists, this will do no harm. |
| if (!file_util::CreateDirectory(sync_data_folder_path_)) { |
| DLOG(FATAL) << "Sync Data directory creation failed."; |
| } |
| |
| DCHECK(!registrar_); |
| registrar_ = options.registrar; |
| DCHECK(registrar_); |
| |
| sync_manager_ = options.sync_manager_factory->CreateSyncManager(name_); |
| sync_manager_->AddObserver(this); |
| sync_manager_->Init( |
| sync_data_folder_path_, |
| options.event_handler, |
| options.service_url.host() + options.service_url.path(), |
| options.service_url.EffectiveIntPort(), |
| options.service_url.SchemeIsSecure(), |
| options.make_http_bridge_factory_fn.Run().Pass(), |
| options.workers, |
| options.extensions_activity_monitor, |
| options.registrar /* as SyncManager::ChangeDelegate */, |
| options.credentials, |
| #if defined(OS_ANDROID) |
| scoped_ptr<syncer::Invalidator>( |
| new AndroidInvalidatorBridgeProxy( |
| options.android_invalidator_bridge)), |
| #else |
| scoped_ptr<syncer::Invalidator>( |
| options.invalidator_factory->CreateInvalidator()), |
| #endif |
| options.restored_key_for_bootstrapping, |
| options.restored_keystore_key_for_bootstrapping, |
| scoped_ptr<InternalComponentsFactory>( |
| options.internal_components_factory), |
| &encryptor_, |
| options.unrecoverable_error_handler, |
| options.report_unrecoverable_error_function); |
| |
| // |sync_manager_| may end up being NULL here in tests (in |
| // synchronous initialization mode). |
| // |
| // TODO(akalin): Fix this behavior (see http://crbug.com/140354). |
| if (sync_manager_.get()) { |
| sync_manager_->RegisterInvalidationHandler(this); |
| registered_as_invalidation_handler_ = true; |
| |
| // Now check the command line to see if we need to simulate an |
| // unrecoverable error for testing purpose. Note the error is thrown |
| // only if the initialization succeeded. Also it makes sense to use this |
| // flag only when restarting the browser with an account already setup. If |
| // you use this before setting up the setup would not succeed as an error |
| // would be encountered. |
| if (CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kSyncThrowUnrecoverableError)) { |
| sync_manager_->ThrowUnrecoverableError(); |
| } |
| } |
| } |
| |
| void SyncBackendHost::Core::DoUpdateCredentials( |
| const SyncCredentials& credentials) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| // UpdateCredentials can be called during backend initialization, possibly |
| // when backend initialization has failed but hasn't notified the UI thread |
| // yet. In that case, the sync manager may have been destroyed on the sync |
| // thread before this task was executed, so we do nothing. |
| if (sync_manager_.get()) { |
| sync_manager_->UpdateCredentials(credentials); |
| } |
| } |
| |
| void SyncBackendHost::Core::DoUpdateRegisteredInvalidationIds( |
| const syncer::ObjectIdSet& ids) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| // |sync_manager_| may end up being NULL here in tests (in |
| // synchronous initialization mode) since this is called during |
| // shutdown. |
| // |
| // TODO(akalin): Fix this behavior (see http://crbug.com/140354). |
| if (sync_manager_.get()) { |
| sync_manager_->UpdateRegisteredInvalidationIds(this, ids); |
| } |
| } |
| |
| void SyncBackendHost::Core::DoStartSyncing( |
| const syncer::ModelSafeRoutingInfo& routing_info) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| sync_manager_->StartSyncingNormally(routing_info); |
| } |
| |
| void SyncBackendHost::Core::DoSetEncryptionPassphrase( |
| const std::string& passphrase, |
| bool is_explicit) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| sync_manager_->GetEncryptionHandler()->SetEncryptionPassphrase( |
| passphrase, is_explicit); |
| } |
| |
| void SyncBackendHost::Core::DoInitialProcessControlTypes() { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| |
| DVLOG(1) << "Initilalizing Control Types"; |
| |
| // Initialize encryption. |
| sync_manager_->GetEncryptionHandler()->Init(); |
| |
| // Note: experiments are currently handled via SBH::AddExperimentalTypes, |
| // which is called at the end of every sync cycle. |
| // TODO(zea): eventually add an experiment handler and initialize it here. |
| |
| if (!sync_manager_->GetUserShare()) { // NULL in some tests. |
| DVLOG(1) << "Skipping initialization of DeviceInfo"; |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::HandleInitializationCompletedOnFrontendLoop, |
| true); |
| return; |
| } |
| |
| if (!sync_manager_->InitialSyncEndedTypes().HasAll(syncer::ControlTypes())) { |
| LOG(ERROR) << "Failed to download control types"; |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::HandleInitializationCompletedOnFrontendLoop, |
| false); |
| return; |
| } |
| |
| // Initialize device info. This is asynchronous on some platforms, so we |
| // provide a callback for when it finishes. |
| synced_device_tracker_.reset( |
| new SyncedDeviceTracker(sync_manager_->GetUserShare(), |
| sync_manager_->cache_guid())); |
| synced_device_tracker_->InitLocalDeviceInfo( |
| base::Bind(&SyncBackendHost::Core::DoFinishInitialProcessControlTypes, |
| this)); |
| } |
| |
| void SyncBackendHost::Core::DoFinishInitialProcessControlTypes() { |
| registrar_->ActivateDataType(syncer::DEVICE_INFO, |
| syncer::GROUP_PASSIVE, |
| synced_device_tracker_.get(), |
| sync_manager_->GetUserShare()); |
| |
| host_.Call( |
| FROM_HERE, |
| &SyncBackendHost::HandleInitializationCompletedOnFrontendLoop, |
| true); |
| } |
| |
| void SyncBackendHost::Core::DoSetDecryptionPassphrase( |
| const std::string& passphrase) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| sync_manager_->GetEncryptionHandler()->SetDecryptionPassphrase( |
| passphrase); |
| } |
| |
| void SyncBackendHost::Core::DoEnableEncryptEverything() { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| sync_manager_->GetEncryptionHandler()->EnableEncryptEverything(); |
| } |
| |
| void SyncBackendHost::Core::DoStopSyncManagerForShutdown( |
| const base::Closure& closure) { |
| if (sync_manager_.get()) { |
| sync_manager_->StopSyncingForShutdown(closure); |
| } else { |
| sync_loop_->PostTask(FROM_HERE, closure); |
| } |
| } |
| |
| void SyncBackendHost::Core::DoShutdown(bool sync_disabled) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| // It's safe to do this even if the type was never activated. |
| registrar_->DeactivateDataType(syncer::DEVICE_INFO); |
| synced_device_tracker_.reset(); |
| |
| DoDestroySyncManager(); |
| |
| registrar_ = NULL; |
| |
| if (sync_disabled) |
| DeleteSyncDataFolder(); |
| |
| sync_loop_ = NULL; |
| |
| host_.Reset(); |
| } |
| |
| void SyncBackendHost::Core::DoDestroySyncManager() { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| if (sync_manager_.get()) { |
| save_changes_timer_.reset(); |
| if (registered_as_invalidation_handler_) { |
| sync_manager_->UnregisterInvalidationHandler(this); |
| registered_as_invalidation_handler_ = false; |
| } |
| sync_manager_->RemoveObserver(this); |
| sync_manager_->ShutdownOnSyncThread(); |
| sync_manager_.reset(); |
| } |
| } |
| |
| void SyncBackendHost::Core::DoConfigureSyncer( |
| syncer::ConfigureReason reason, |
| syncer::ModelTypeSet types_to_config, |
| syncer::ModelTypeSet failed_types, |
| const syncer::ModelSafeRoutingInfo routing_info, |
| const base::Callback<void(syncer::ModelTypeSet)>& ready_task, |
| const base::Closure& retry_callback) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| sync_manager_->ConfigureSyncer( |
| reason, |
| types_to_config, |
| failed_types, |
| routing_info, |
| base::Bind(&SyncBackendHost::Core::DoFinishConfigureDataTypes, |
| this, |
| types_to_config, |
| ready_task), |
| base::Bind(&SyncBackendHost::Core::DoRetryConfiguration, |
| this, |
| retry_callback)); |
| } |
| |
| void SyncBackendHost::Core::DoFinishConfigureDataTypes( |
| syncer::ModelTypeSet types_to_config, |
| const base::Callback<void(syncer::ModelTypeSet)>& ready_task) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| |
| // Update the enabled types for the bridge and sync manager. |
| syncer::ModelSafeRoutingInfo routing_info; |
| registrar_->GetModelSafeRoutingInfo(&routing_info); |
| syncer::ModelTypeSet enabled_types = GetRoutingInfoTypes(routing_info); |
| enabled_types.RemoveAll(syncer::ProxyTypes()); |
| sync_manager_->UpdateEnabledTypes(enabled_types); |
| |
| const syncer::ModelTypeSet failed_configuration_types = |
| Difference(types_to_config, sync_manager_->InitialSyncEndedTypes()); |
| host_.Call(FROM_HERE, |
| &SyncBackendHost::FinishConfigureDataTypesOnFrontendLoop, |
| failed_configuration_types, |
| ready_task); |
| } |
| |
| void SyncBackendHost::Core::DoRetryConfiguration( |
| const base::Closure& retry_callback) { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| host_.Call(FROM_HERE, |
| &SyncBackendHost::RetryConfigurationOnFrontendLoop, |
| retry_callback); |
| } |
| |
| void SyncBackendHost::Core::DeleteSyncDataFolder() { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| if (file_util::DirectoryExists(sync_data_folder_path_)) { |
| if (!file_util::Delete(sync_data_folder_path_, true)) |
| SLOG(DFATAL) << "Could not delete the Sync Data folder."; |
| } |
| } |
| |
| void SyncBackendHost::Core::StartSavingChanges() { |
| // We may already be shut down. |
| if (!sync_loop_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| DCHECK(!save_changes_timer_.get()); |
| save_changes_timer_.reset(new base::RepeatingTimer<Core>()); |
| save_changes_timer_->Start(FROM_HERE, |
| base::TimeDelta::FromSeconds(kSaveChangesIntervalSeconds), |
| this, &Core::SaveChanges); |
| } |
| |
| void SyncBackendHost::Core::SaveChanges() { |
| DCHECK_EQ(MessageLoop::current(), sync_loop_); |
| sync_manager_->SaveChanges(); |
| } |
| |
| void SyncBackendHost::AddExperimentalTypes() { |
| CHECK(initialized()); |
| syncer::Experiments experiments; |
| if (core_->sync_manager()->ReceivedExperiment(&experiments)) |
| frontend_->OnExperimentsChanged(experiments); |
| } |
| |
| void SyncBackendHost::HandleControlTypesDownloadRetry() { |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| if (!frontend_) |
| return; |
| |
| frontend_->OnSyncConfigureRetry(); |
| } |
| |
| void SyncBackendHost::HandleInitializationCompletedOnFrontendLoop( |
| bool success) { |
| DCHECK_NE(initialization_state_, NOT_ATTEMPTED); |
| if (!frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| if (!success) { |
| js_backend_.Reset(); |
| initialization_state_ = NOT_INITIALIZED; |
| frontend_->OnBackendInitialized( |
| syncer::WeakHandle<syncer::JsBackend>(), |
| syncer::WeakHandle<syncer::DataTypeDebugInfoListener>(), |
| false); |
| return; |
| } |
| |
| initialization_state_ = INITIALIZED; |
| |
| // Now that we've downloaded the control types, we can see if there are any |
| // experimental types to enable. This should be done before we inform |
| // the frontend to ensure they're visible in the customize screen. |
| AddExperimentalTypes(); |
| frontend_->OnBackendInitialized(js_backend_, |
| debug_info_listener_, |
| true); |
| js_backend_.Reset(); |
| } |
| |
| void SyncBackendHost::HandleSyncCycleCompletedOnFrontendLoop( |
| const SyncSessionSnapshot& snapshot) { |
| if (!frontend_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| |
| last_snapshot_ = snapshot; |
| |
| SDVLOG(1) << "Got snapshot " << snapshot.ToString(); |
| |
| const syncer::ModelTypeSet to_migrate = |
| snapshot.model_neutral_state().types_needing_local_migration; |
| if (!to_migrate.Empty()) |
| frontend_->OnMigrationNeededForTypes(to_migrate); |
| |
| // Process any changes to the datatypes we're syncing. |
| // TODO(sync): add support for removing types. |
| if (initialized()) |
| AddExperimentalTypes(); |
| |
| if (initialized()) |
| frontend_->OnSyncCycleCompleted(); |
| } |
| |
| void SyncBackendHost::RetryConfigurationOnFrontendLoop( |
| const base::Closure& retry_callback) { |
| SDVLOG(1) << "Failed to complete configuration, informing of retry."; |
| retry_callback.Run(); |
| } |
| |
| void SyncBackendHost::PersistEncryptionBootstrapToken( |
| const std::string& token, |
| syncer::BootstrapTokenType token_type) { |
| CHECK(sync_prefs_.get()); |
| DCHECK(!token.empty()); |
| if (token_type == syncer::PASSPHRASE_BOOTSTRAP_TOKEN) |
| sync_prefs_->SetEncryptionBootstrapToken(token); |
| else |
| sync_prefs_->SetKeystoreEncryptionBootstrapToken(token); |
| } |
| |
| void SyncBackendHost::HandleActionableErrorEventOnFrontendLoop( |
| const syncer::SyncProtocolError& sync_error) { |
| if (!frontend_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| frontend_->OnActionableError(sync_error); |
| } |
| |
| void SyncBackendHost::HandleInvalidatorStateChangeOnFrontendLoop( |
| syncer::InvalidatorState state) { |
| if (!frontend_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| frontend_->OnInvalidatorStateChange(state); |
| } |
| |
| void SyncBackendHost::HandleIncomingInvalidationOnFrontendLoop( |
| const syncer::ObjectIdInvalidationMap& invalidation_map) { |
| if (!frontend_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| frontend_->OnIncomingInvalidation(invalidation_map); |
| } |
| |
| bool SyncBackendHost::CheckPassphraseAgainstCachedPendingKeys( |
| const std::string& passphrase) const { |
| DCHECK(cached_pending_keys_.has_blob()); |
| DCHECK(!passphrase.empty()); |
| syncer::Nigori nigori; |
| nigori.InitByDerivation("localhost", "dummy", passphrase); |
| std::string plaintext; |
| bool result = nigori.Decrypt(cached_pending_keys_.blob(), &plaintext); |
| DVLOG_IF(1, result) << "Passphrase failed to decrypt pending keys."; |
| return result; |
| } |
| |
| void SyncBackendHost::NotifyPassphraseRequired( |
| syncer::PassphraseRequiredReason reason, |
| sync_pb::EncryptedData pending_keys) { |
| if (!frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| |
| // Update our cache of the cryptographer's pending keys. |
| cached_pending_keys_ = pending_keys; |
| |
| frontend_->OnPassphraseRequired(reason, pending_keys); |
| } |
| |
| void SyncBackendHost::NotifyPassphraseAccepted() { |
| if (!frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| |
| // Clear our cache of the cryptographer's pending keys. |
| cached_pending_keys_.clear_blob(); |
| frontend_->OnPassphraseAccepted(); |
| } |
| |
| void SyncBackendHost::NotifyUpdatedToken(const std::string& token) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| TokenAvailableDetails details(GaiaConstants::kSyncService, token); |
| |
| TokenService* token_service = TokenServiceFactory::GetForProfile(profile_); |
| CHECK(token_service); |
| token_service->AddAuthTokenManually(details.service(), details.token()); |
| } |
| |
| void SyncBackendHost::NotifyEncryptedTypesChanged( |
| syncer::ModelTypeSet encrypted_types, |
| bool encrypt_everything) { |
| if (!frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| frontend_->OnEncryptedTypesChanged( |
| encrypted_types, encrypt_everything); |
| } |
| |
| void SyncBackendHost::NotifyEncryptionComplete() { |
| if (!frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| frontend_->OnEncryptionComplete(); |
| } |
| |
| void SyncBackendHost::HandlePassphraseTypeChangedOnFrontendLoop( |
| syncer::PassphraseType type, |
| base::Time explicit_passphrase_time) { |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| DVLOG(1) << "Passphrase type changed to " |
| << syncer::PassphraseTypeToString(type); |
| cached_passphrase_type_ = type; |
| cached_explicit_passphrase_time_ = explicit_passphrase_time; |
| } |
| |
| void SyncBackendHost::HandleStopSyncingPermanentlyOnFrontendLoop() { |
| if (!frontend_) |
| return; |
| frontend_->OnStopSyncingPermanently(); |
| } |
| |
| void SyncBackendHost::HandleConnectionStatusChangeOnFrontendLoop( |
| syncer::ConnectionStatus status) { |
| if (!frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| |
| DVLOG(1) << "Connection status changed: " |
| << syncer::ConnectionStatusToString(status); |
| frontend_->OnConnectionStatusChange(status); |
| } |
| |
| #undef SDVLOG |
| |
| #undef SLOG |
| |
| } // namespace browser_sync |