| // Copyright (c) 2011 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 <algorithm> |
| |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/file_util.h" |
| #include "base/task.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/net/gaia/token_service.h" |
| #include "chrome/browser/prefs/pref_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/sync/engine/syncapi.h" |
| #include "chrome/browser/sync/glue/autofill_model_associator.h" |
| #include "chrome/browser/sync/glue/autofill_profile_model_associator.h" |
| #include "chrome/browser/sync/glue/change_processor.h" |
| #include "chrome/browser/sync/glue/database_model_worker.h" |
| #include "chrome/browser/sync/glue/history_model_worker.h" |
| #include "chrome/browser/sync/glue/http_bridge.h" |
| #include "chrome/browser/sync/glue/password_model_worker.h" |
| #include "chrome/browser/sync/glue/sync_backend_host.h" |
| #include "chrome/browser/sync/js_arg_list.h" |
| #include "chrome/browser/sync/notifier/sync_notifier.h" |
| #include "chrome/browser/sync/notifier/sync_notifier_factory.h" |
| #include "chrome/browser/sync/sessions/session_state.h" |
| // TODO(tim): Remove this! We should have a syncapi pass-thru instead. |
| #include "chrome/browser/sync/syncable/directory_manager.h" // Cryptographer. |
| #include "chrome/browser/sync/syncable/model_type.h" |
| #include "chrome/browser/sync/syncable/nigori_util.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/chrome_version_info.h" |
| #include "chrome/common/net/gaia/gaia_constants.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/browser/browser_thread.h" |
| #include "content/common/notification_service.h" |
| #include "content/common/notification_type.h" |
| #include "googleurl/src/gurl.h" |
| #include "webkit/glue/webkit_glue.h" |
| |
| static const int kSaveChangesIntervalSeconds = 10; |
| static const FilePath::CharType kSyncDataFolderName[] = |
| FILE_PATH_LITERAL("Sync Data"); |
| |
| using browser_sync::DataTypeController; |
| using sync_notifier::SyncNotifierFactory; |
| typedef TokenService::TokenAvailableDetails TokenAvailableDetails; |
| |
| typedef GoogleServiceAuthError AuthError; |
| |
| namespace browser_sync { |
| |
| using sessions::SyncSessionSnapshot; |
| using sync_api::SyncCredentials; |
| |
| SyncBackendHost::SyncBackendHost(Profile* profile) |
| : core_(new Core(ALLOW_THIS_IN_INITIALIZER_LIST(this))), |
| core_thread_("Chrome_SyncCoreThread"), |
| frontend_loop_(MessageLoop::current()), |
| profile_(profile), |
| frontend_(NULL), |
| sync_data_folder_path_( |
| profile_->GetPath().Append(kSyncDataFolderName)), |
| last_auth_error_(AuthError::None()), |
| syncapi_initialized_(false) { |
| } |
| |
| SyncBackendHost::SyncBackendHost() |
| : core_thread_("Chrome_SyncCoreThread"), |
| frontend_loop_(MessageLoop::current()), |
| profile_(NULL), |
| frontend_(NULL), |
| last_auth_error_(AuthError::None()), |
| syncapi_initialized_(false) { |
| } |
| |
| SyncBackendHost::~SyncBackendHost() { |
| DCHECK(!core_ && !frontend_) << "Must call Shutdown before destructor."; |
| DCHECK(registrar_.workers.empty()); |
| } |
| |
| void SyncBackendHost::Initialize( |
| SyncFrontend* frontend, |
| const GURL& sync_service_url, |
| const syncable::ModelTypeSet& types, |
| net::URLRequestContextGetter* baseline_context_getter, |
| const SyncCredentials& credentials, |
| bool delete_sync_data_folder) { |
| if (!core_thread_.Start()) |
| return; |
| |
| frontend_ = frontend; |
| DCHECK(frontend); |
| |
| // Create a worker for the UI thread and route bookmark changes to it. |
| // TODO(tim): Pull this into a method to reuse. For now we don't even |
| // need to lock because we init before the syncapi exists and we tear down |
| // after the syncapi is destroyed. Make sure to NULL-check workers_ indices |
| // when a new type is synced as the worker may already exist and you just |
| // need to update routing_info_. |
| registrar_.workers[GROUP_DB] = new DatabaseModelWorker(); |
| registrar_.workers[GROUP_UI] = new UIModelWorker(); |
| registrar_.workers[GROUP_PASSIVE] = new ModelSafeWorker(); |
| |
| if (CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kEnableSyncTypedUrls) || types.count(syncable::TYPED_URLS)) { |
| // TODO(tim): Bug 53916. HistoryModelWorker crashes, so avoid adding it |
| // unless specifically requested until bug is fixed. |
| registrar_.workers[GROUP_HISTORY] = |
| new HistoryModelWorker( |
| profile_->GetHistoryService(Profile::IMPLICIT_ACCESS)); |
| } |
| |
| // Any datatypes that we want the syncer to pull down must |
| // be in the routing_info map. We set them to group passive, meaning that |
| // updates will be applied, but not dispatched to the UI thread yet. |
| for (syncable::ModelTypeSet::const_iterator it = types.begin(); |
| it != types.end(); ++it) { |
| registrar_.routing_info[(*it)] = GROUP_PASSIVE; |
| } |
| |
| PasswordStore* password_store = |
| profile_->GetPasswordStore(Profile::IMPLICIT_ACCESS); |
| if (password_store) { |
| registrar_.workers[GROUP_PASSWORD] = |
| new PasswordModelWorker(password_store); |
| } else { |
| LOG_IF(WARNING, types.count(syncable::PASSWORDS) > 0) << "Password store " |
| << "not initialized, cannot sync passwords"; |
| registrar_.routing_info.erase(syncable::PASSWORDS); |
| } |
| |
| // Nigori is populated by default now. |
| registrar_.routing_info[syncable::NIGORI] = GROUP_PASSIVE; |
| |
| // TODO(akalin): Create SyncNotifier here and pass it in as part of |
| // DoInitializeOptions. |
| core_->CreateSyncNotifier(baseline_context_getter); |
| |
| InitCore(Core::DoInitializeOptions( |
| sync_service_url, |
| MakeHttpBridgeFactory(baseline_context_getter), |
| credentials, |
| delete_sync_data_folder, |
| RestoreEncryptionBootstrapToken(), |
| false)); |
| } |
| |
| void SyncBackendHost::PersistEncryptionBootstrapToken( |
| const std::string& token) { |
| PrefService* prefs = profile_->GetPrefs(); |
| |
| prefs->SetString(prefs::kEncryptionBootstrapToken, token); |
| prefs->ScheduleSavePersistentPrefs(); |
| } |
| |
| std::string SyncBackendHost::RestoreEncryptionBootstrapToken() { |
| PrefService* prefs = profile_->GetPrefs(); |
| std::string token = prefs->GetString(prefs::kEncryptionBootstrapToken); |
| return token; |
| } |
| |
| bool SyncBackendHost::IsNigoriEnabled() const { |
| base::AutoLock lock(registrar_lock_); |
| // Note that NIGORI is only ever added/removed from routing_info once, |
| // during initialization / first configuration, so there is no real 'race' |
| // possible here or possibility of stale return value. |
| return registrar_.routing_info.find(syncable::NIGORI) != |
| registrar_.routing_info.end(); |
| } |
| |
| bool SyncBackendHost::IsUsingExplicitPassphrase() { |
| return IsNigoriEnabled() && syncapi_initialized_ && |
| core_->syncapi()->InitialSyncEndedForAllEnabledTypes() && |
| core_->syncapi()->IsUsingExplicitPassphrase(); |
| } |
| |
| bool SyncBackendHost::IsCryptographerReady( |
| const sync_api::BaseTransaction* trans) const { |
| return syncapi_initialized_ && trans->GetCryptographer()->is_ready(); |
| } |
| |
| JsBackend* SyncBackendHost::GetJsBackend() { |
| if (syncapi_initialized_) { |
| return core_.get(); |
| } else { |
| NOTREACHED(); |
| return NULL; |
| } |
| } |
| |
| sync_api::HttpPostProviderFactory* SyncBackendHost::MakeHttpBridgeFactory( |
| net::URLRequestContextGetter* getter) { |
| return new HttpBridgeFactory(getter); |
| } |
| |
| void SyncBackendHost::InitCore(const Core::DoInitializeOptions& options) { |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoInitialize, |
| options)); |
| } |
| |
| void SyncBackendHost::UpdateCredentials(const SyncCredentials& credentials) { |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), |
| &SyncBackendHost::Core::DoUpdateCredentials, |
| credentials)); |
| } |
| |
| void SyncBackendHost::StartSyncingWithServer() { |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoStartSyncing)); |
| } |
| |
| void SyncBackendHost::SetPassphrase(const std::string& passphrase, |
| bool is_explicit) { |
| if (!IsNigoriEnabled()) { |
| LOG(WARNING) << "Silently dropping SetPassphrase request."; |
| return; |
| } |
| |
| // This should only be called by the frontend. |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| if (core_->processing_passphrase()) { |
| VLOG(1) << "Attempted to call SetPassphrase while already waiting for " |
| << " result from previous SetPassphrase call. Silently dropping."; |
| return; |
| } |
| core_->set_processing_passphrase(); |
| |
| // If encryption is enabled and we've got a SetPassphrase |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoSetPassphrase, |
| passphrase, is_explicit)); |
| } |
| |
| void SyncBackendHost::Shutdown(bool sync_disabled) { |
| // Thread shutdown should occur in the following order: |
| // - SyncerThread |
| // - CoreThread |
| // - UI Thread (stops some time after we return from this call). |
| if (core_thread_.IsRunning()) { // Not running in tests. |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), |
| &SyncBackendHost::Core::DoShutdown, |
| sync_disabled)); |
| } |
| |
| // Before joining the core_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. |
| if (ui_worker()) |
| ui_worker()->Stop(); |
| |
| // Stop will return once the thread exits, which will be after DoShutdown |
| // runs. DoShutdown needs to run from core_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::ThreadRestrictions::ScopedAllowIO allow_io; |
| core_thread_.Stop(); |
| } |
| |
| registrar_.routing_info.clear(); |
| registrar_.workers[GROUP_DB] = NULL; |
| registrar_.workers[GROUP_HISTORY] = NULL; |
| registrar_.workers[GROUP_UI] = NULL; |
| registrar_.workers[GROUP_PASSIVE] = NULL; |
| registrar_.workers[GROUP_PASSWORD] = NULL; |
| registrar_.workers.erase(GROUP_DB); |
| registrar_.workers.erase(GROUP_HISTORY); |
| registrar_.workers.erase(GROUP_UI); |
| registrar_.workers.erase(GROUP_PASSIVE); |
| registrar_.workers.erase(GROUP_PASSWORD); |
| frontend_ = NULL; |
| core_ = NULL; // Releases reference to core_. |
| } |
| |
| syncable::AutofillMigrationState |
| SyncBackendHost::GetAutofillMigrationState() { |
| return core_->syncapi()->GetAutofillMigrationState(); |
| } |
| |
| void SyncBackendHost::SetAutofillMigrationState( |
| syncable::AutofillMigrationState state) { |
| return core_->syncapi()->SetAutofillMigrationState(state); |
| } |
| |
| syncable::AutofillMigrationDebugInfo |
| SyncBackendHost::GetAutofillMigrationDebugInfo() { |
| return core_->syncapi()->GetAutofillMigrationDebugInfo(); |
| } |
| |
| void SyncBackendHost::SetAutofillMigrationDebugInfo( |
| syncable::AutofillMigrationDebugInfo::PropertyToSet property_to_set, |
| const syncable::AutofillMigrationDebugInfo& info) { |
| return core_->syncapi()->SetAutofillMigrationDebugInfo(property_to_set, info); |
| } |
| |
| void SyncBackendHost::ConfigureAutofillMigration() { |
| if (GetAutofillMigrationState() == syncable::NOT_DETERMINED) { |
| sync_api::ReadTransaction trans(GetUserShare()); |
| sync_api::ReadNode autofil_root_node(&trans); |
| |
| // Check for the presence of autofill node. |
| if (!autofil_root_node.InitByTagLookup(browser_sync::kAutofillTag)) { |
| SetAutofillMigrationState(syncable::INSUFFICIENT_INFO_TO_DETERMINE); |
| return; |
| } |
| |
| // Check for children under autofill node. |
| if (autofil_root_node.GetFirstChildId() == static_cast<int64>(0)) { |
| SetAutofillMigrationState(syncable::INSUFFICIENT_INFO_TO_DETERMINE); |
| return; |
| } |
| |
| sync_api::ReadNode autofill_profile_root_node(&trans); |
| |
| // Check for the presence of autofill profile root node. |
| if (!autofill_profile_root_node.InitByTagLookup( |
| browser_sync::kAutofillProfileTag)) { |
| SetAutofillMigrationState(syncable::NOT_MIGRATED); |
| return; |
| } |
| |
| // If our state is not determined then we should not have the autofill |
| // profile node. |
| DCHECK(false); |
| |
| // just set it as not migrated. |
| SetAutofillMigrationState(syncable::NOT_MIGRATED); |
| return; |
| } |
| } |
| |
| SyncBackendHost::PendingConfigureDataTypesState:: |
| PendingConfigureDataTypesState() : deleted_type(false) {} |
| |
| SyncBackendHost::PendingConfigureDataTypesState:: |
| ~PendingConfigureDataTypesState() {} |
| |
| // static |
| SyncBackendHost::PendingConfigureDataTypesState* |
| SyncBackendHost::MakePendingConfigModeState( |
| const DataTypeController::TypeMap& data_type_controllers, |
| const syncable::ModelTypeSet& types, |
| CancelableTask* ready_task, |
| ModelSafeRoutingInfo* routing_info) { |
| PendingConfigureDataTypesState* state = new PendingConfigureDataTypesState(); |
| for (DataTypeController::TypeMap::const_iterator it = |
| data_type_controllers.begin(); |
| it != data_type_controllers.end(); ++it) { |
| syncable::ModelType type = it->first; |
| // If a type is not specified, remove it from the routing_info. |
| if (types.count(type) == 0) { |
| state->deleted_type = true; |
| routing_info->erase(type); |
| } else { |
| // Add a newly specified data type as GROUP_PASSIVE into the |
| // routing_info, if it does not already exist. |
| if (routing_info->count(type) == 0) { |
| (*routing_info)[type] = GROUP_PASSIVE; |
| state->added_types.set(type); |
| } |
| } |
| } |
| |
| state->ready_task.reset(ready_task); |
| state->initial_types = types; |
| return state; |
| } |
| |
| void SyncBackendHost::ConfigureDataTypes( |
| const DataTypeController::TypeMap& data_type_controllers, |
| const syncable::ModelTypeSet& types, |
| CancelableTask* ready_task) { |
| // Only one configure is allowed at a time. |
| DCHECK(!pending_config_mode_state_.get()); |
| DCHECK(!pending_download_state_.get()); |
| DCHECK(syncapi_initialized_); |
| |
| if (types.count(syncable::AUTOFILL_PROFILE) != 0) { |
| ConfigureAutofillMigration(); |
| } |
| |
| { |
| base::AutoLock lock(registrar_lock_); |
| pending_config_mode_state_.reset( |
| MakePendingConfigModeState(data_type_controllers, types, ready_task, |
| ®istrar_.routing_info)); |
| } |
| |
| StartConfiguration(NewCallback(core_.get(), |
| &SyncBackendHost::Core::FinishConfigureDataTypes)); |
| } |
| |
| void SyncBackendHost::StartConfiguration(Callback0::Type* callback) { |
| // Put syncer in the config mode. DTM will put us in normal mode once it is. |
| // done. This is to ensure we dont do a normal sync when we are doing model |
| // association. |
| core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( |
| core_.get(),&SyncBackendHost::Core::DoStartConfiguration, callback)); |
| } |
| |
| void SyncBackendHost::FinishConfigureDataTypesOnFrontendLoop() { |
| DCHECK_EQ(MessageLoop::current(), frontend_loop_); |
| // Nudge the syncer. This is necessary for both datatype addition/deletion. |
| // |
| // Deletions need a nudge in order to ensure the deletion occurs in a timely |
| // manner (see issue 56416). |
| // |
| // In the case of additions, on the next sync cycle, the syncer should |
| // notice that the routing info has changed and start the process of |
| // downloading updates for newly added data types. Once this is |
| // complete, the configure_state_.ready_task_ is run via an |
| // OnInitializationComplete notification. |
| |
| if (pending_config_mode_state_->deleted_type) { |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), |
| &SyncBackendHost::Core::DeferNudgeForCleanup)); |
| } |
| |
| if (pending_config_mode_state_->added_types.none() && |
| !core_->syncapi()->InitialSyncEndedForAllEnabledTypes()) { |
| LOG(WARNING) << "No new types, but initial sync not finished." |
| << "Possible sync db corruption / removal."; |
| // TODO(tim): Log / UMA / count this somehow? |
| // TODO(tim): If no added types, we could (should?) config only for |
| // types that are needed... but this is a rare corruption edge case or |
| // implies the user mucked around with their syncdb, so for now do all. |
| pending_config_mode_state_->added_types = |
| syncable::ModelTypeBitSetFromSet( |
| pending_config_mode_state_->initial_types); |
| } |
| |
| // If we've added types, we always want to request a nudge/config (even if |
| // the initial sync is ended), in case we could not decrypt the data. |
| if (pending_config_mode_state_->added_types.none()) { |
| // No new types - just notify the caller that the types are available. |
| pending_config_mode_state_->ready_task->Run(); |
| } else { |
| pending_download_state_.reset(pending_config_mode_state_.release()); |
| |
| syncable::ModelTypeBitSet types_copy(pending_download_state_->added_types); |
| if (IsNigoriEnabled()) |
| types_copy.set(syncable::NIGORI); |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), |
| &SyncBackendHost::Core::DoRequestConfig, |
| types_copy)); |
| } |
| |
| pending_config_mode_state_.reset(); |
| |
| // Notify the SyncManager about the new types. |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), |
| &SyncBackendHost::Core::DoUpdateEnabledTypes)); |
| } |
| |
| void SyncBackendHost::EncryptDataTypes( |
| const syncable::ModelTypeSet& encrypted_types) { |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), |
| &SyncBackendHost::Core::DoEncryptDataTypes, |
| encrypted_types)); |
| } |
| |
| void SyncBackendHost::RequestNudge(const tracked_objects::Location& location) { |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestNudge, |
| location)); |
| } |
| |
| void SyncBackendHost::ActivateDataType( |
| DataTypeController* data_type_controller, |
| ChangeProcessor* change_processor) { |
| base::AutoLock lock(registrar_lock_); |
| |
| // Ensure that the given data type is in the PASSIVE group. |
| browser_sync::ModelSafeRoutingInfo::iterator i = |
| registrar_.routing_info.find(data_type_controller->type()); |
| DCHECK(i != registrar_.routing_info.end()); |
| DCHECK((*i).second == GROUP_PASSIVE); |
| syncable::ModelType type = data_type_controller->type(); |
| // Change the data type's routing info to its group. |
| registrar_.routing_info[type] = data_type_controller->model_safe_group(); |
| |
| // Add the data type's change processor to the list of change |
| // processors so it can receive updates. |
| DCHECK_EQ(processors_.count(type), 0U); |
| processors_[type] = change_processor; |
| } |
| |
| void SyncBackendHost::DeactivateDataType( |
| DataTypeController* data_type_controller, |
| ChangeProcessor* change_processor) { |
| base::AutoLock lock(registrar_lock_); |
| registrar_.routing_info.erase(data_type_controller->type()); |
| |
| std::map<syncable::ModelType, ChangeProcessor*>::size_type erased = |
| processors_.erase(data_type_controller->type()); |
| DCHECK_EQ(erased, 1U); |
| } |
| |
| bool SyncBackendHost::RequestClearServerData() { |
| core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(core_.get(), |
| &SyncBackendHost::Core::DoRequestClearServerData)); |
| return true; |
| } |
| |
| SyncBackendHost::Core::~Core() { |
| } |
| |
| void SyncBackendHost::Core::NotifyPassphraseRequired(bool for_decryption) { |
| if (!host_ || !host_->frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| |
| if (processing_passphrase_) { |
| VLOG(1) << "Core received OnPassphraseRequired while processing a " |
| << "passphrase. Silently dropping."; |
| return; |
| } |
| host_->frontend_->OnPassphraseRequired(for_decryption); |
| } |
| |
| void SyncBackendHost::Core::NotifyPassphraseFailed() { |
| if (!host_ || !host_->frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| |
| // When a passphrase fails, we just unset our waiting flag and trigger a |
| // OnPassphraseRequired(true). |
| processing_passphrase_ = false; |
| host_->frontend_->OnPassphraseRequired(true); |
| } |
| |
| void SyncBackendHost::Core::NotifyPassphraseAccepted( |
| const std::string& bootstrap_token) { |
| if (!host_ || !host_->frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| |
| processing_passphrase_ = false; |
| host_->PersistEncryptionBootstrapToken(bootstrap_token); |
| host_->frontend_->OnPassphraseAccepted(); |
| } |
| |
| void SyncBackendHost::Core::NotifyUpdatedToken(const std::string& token) { |
| if (!host_) |
| return; |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| TokenAvailableDetails details(GaiaConstants::kSyncService, token); |
| NotificationService::current()->Notify( |
| NotificationType::TOKEN_UPDATED, |
| NotificationService::AllSources(), |
| Details<const TokenAvailableDetails>(&details)); |
| } |
| |
| void SyncBackendHost::Core::NotifyEncryptionComplete( |
| const syncable::ModelTypeSet& encrypted_types) { |
| if (!host_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| host_->frontend_->OnEncryptionComplete(encrypted_types); |
| } |
| |
| void SyncBackendHost::Core::FinishConfigureDataTypes() { |
| host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| &SyncBackendHost::Core::FinishConfigureDataTypesOnFrontendLoop)); |
| } |
| |
| void SyncBackendHost::Core::FinishConfigureDataTypesOnFrontendLoop() { |
| host_->FinishConfigureDataTypesOnFrontendLoop(); |
| } |
| |
| |
| void SyncBackendHost::Core::CreateSyncNotifier( |
| const scoped_refptr<net::URLRequestContextGetter>& request_context_getter) { |
| const std::string& client_info = webkit_glue::GetUserAgent(GURL()); |
| SyncNotifierFactory sync_notifier_factory(client_info); |
| sync_notifier_.reset(sync_notifier_factory.CreateSyncNotifier( |
| *CommandLine::ForCurrentProcess(), |
| request_context_getter)); |
| } |
| |
| SyncBackendHost::Core::DoInitializeOptions::DoInitializeOptions( |
| const GURL& service_url, |
| sync_api::HttpPostProviderFactory* http_bridge_factory, |
| const sync_api::SyncCredentials& credentials, |
| bool delete_sync_data_folder, |
| const std::string& restored_key_for_bootstrapping, |
| bool setup_for_test_mode) |
| : service_url(service_url), |
| http_bridge_factory(http_bridge_factory), |
| credentials(credentials), |
| delete_sync_data_folder(delete_sync_data_folder), |
| restored_key_for_bootstrapping(restored_key_for_bootstrapping), |
| setup_for_test_mode(setup_for_test_mode) { |
| } |
| |
| SyncBackendHost::Core::DoInitializeOptions::~DoInitializeOptions() {} |
| |
| sync_api::UserShare* SyncBackendHost::GetUserShare() const { |
| DCHECK(syncapi_initialized_); |
| return core_->syncapi()->GetUserShare(); |
| } |
| |
| SyncBackendHost::Status SyncBackendHost::GetDetailedStatus() { |
| DCHECK(syncapi_initialized_); |
| return core_->syncapi()->GetDetailedStatus(); |
| } |
| |
| SyncBackendHost::StatusSummary SyncBackendHost::GetStatusSummary() { |
| DCHECK(syncapi_initialized_); |
| return core_->syncapi()->GetStatusSummary(); |
| } |
| |
| string16 SyncBackendHost::GetAuthenticatedUsername() const { |
| DCHECK(syncapi_initialized_); |
| return UTF8ToUTF16(core_->syncapi()->GetAuthenticatedUsername()); |
| } |
| |
| const GoogleServiceAuthError& SyncBackendHost::GetAuthError() const { |
| return last_auth_error_; |
| } |
| |
| const SyncSessionSnapshot* SyncBackendHost::GetLastSessionSnapshot() const { |
| return last_snapshot_.get(); |
| } |
| |
| void SyncBackendHost::GetWorkers(std::vector<ModelSafeWorker*>* out) { |
| base::AutoLock lock(registrar_lock_); |
| out->clear(); |
| for (WorkerMap::const_iterator it = registrar_.workers.begin(); |
| it != registrar_.workers.end(); ++it) { |
| out->push_back((*it).second); |
| } |
| } |
| |
| void SyncBackendHost::GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) { |
| base::AutoLock lock(registrar_lock_); |
| ModelSafeRoutingInfo copy(registrar_.routing_info); |
| out->swap(copy); |
| } |
| |
| bool SyncBackendHost::HasUnsyncedItems() const { |
| DCHECK(syncapi_initialized_); |
| return core_->syncapi()->HasUnsyncedItems(); |
| } |
| |
| SyncBackendHost::Core::Core(SyncBackendHost* backend) |
| : host_(backend), |
| syncapi_(new sync_api::SyncManager()), |
| sync_manager_observer_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| parent_router_(NULL), |
| processing_passphrase_(false), |
| deferred_nudge_for_cleanup_requested_(false) { |
| } |
| |
| // Helper to construct a user agent string (ASCII) suitable for use by |
| // the syncapi for any HTTP communication. This string is used by the sync |
| // backend for classifying client types when calculating statistics. |
| std::string MakeUserAgentForSyncapi() { |
| std::string user_agent; |
| user_agent = "Chrome "; |
| #if defined(OS_WIN) |
| user_agent += "WIN "; |
| #elif defined(OS_LINUX) |
| user_agent += "LINUX "; |
| #elif defined(OS_FREEBSD) |
| user_agent += "FREEBSD "; |
| #elif defined(OS_OPENBSD) |
| user_agent += "OPENBSD "; |
| #elif defined(OS_MACOSX) |
| user_agent += "MAC "; |
| #endif |
| chrome::VersionInfo version_info; |
| if (!version_info.is_valid()) { |
| DLOG(ERROR) << "Unable to create chrome::VersionInfo object"; |
| return user_agent; |
| } |
| |
| user_agent += version_info.Version(); |
| user_agent += " (" + version_info.LastChange() + ")"; |
| if (!version_info.IsOfficialBuild()) |
| user_agent += "-devel"; |
| return user_agent; |
| } |
| |
| void SyncBackendHost::Core::DoInitialize(const DoInitializeOptions& options) { |
| DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); |
| processing_passphrase_ = false; |
| |
| // 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. |
| bool success = file_util::CreateDirectory(host_->sync_data_folder_path()); |
| DCHECK(success); |
| |
| syncapi_->AddObserver(this); |
| const FilePath& path_str = host_->sync_data_folder_path(); |
| success = syncapi_->Init( |
| path_str, |
| (options.service_url.host() + options.service_url.path()).c_str(), |
| options.service_url.EffectiveIntPort(), |
| options.service_url.SchemeIsSecure(), |
| options.http_bridge_factory, |
| host_, // ModelSafeWorkerRegistrar. |
| MakeUserAgentForSyncapi().c_str(), |
| options.credentials, |
| sync_notifier_.get(), |
| options.restored_key_for_bootstrapping, |
| options.setup_for_test_mode); |
| DCHECK(success) << "Syncapi initialization failed!"; |
| } |
| |
| void SyncBackendHost::Core::DoUpdateCredentials( |
| const SyncCredentials& credentials) { |
| DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); |
| syncapi_->UpdateCredentials(credentials); |
| } |
| |
| void SyncBackendHost::Core::DoUpdateEnabledTypes() { |
| DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); |
| syncapi_->UpdateEnabledTypes(); |
| } |
| |
| void SyncBackendHost::Core::DoStartSyncing() { |
| DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); |
| syncapi_->StartSyncing(); |
| if (deferred_nudge_for_cleanup_requested_) |
| syncapi_->RequestNudge(FROM_HERE); |
| deferred_nudge_for_cleanup_requested_ = false; |
| } |
| |
| void SyncBackendHost::Core::DoSetPassphrase(const std::string& passphrase, |
| bool is_explicit) { |
| DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); |
| syncapi_->SetPassphrase(passphrase, is_explicit); |
| } |
| |
| bool SyncBackendHost::Core::processing_passphrase() const { |
| DCHECK(MessageLoop::current() == host_->frontend_loop_); |
| return processing_passphrase_; |
| } |
| |
| void SyncBackendHost::Core::set_processing_passphrase() { |
| DCHECK(MessageLoop::current() == host_->frontend_loop_); |
| processing_passphrase_ = true; |
| } |
| |
| void SyncBackendHost::Core::DoEncryptDataTypes( |
| const syncable::ModelTypeSet& encrypted_types) { |
| DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); |
| syncapi_->EncryptDataTypes(encrypted_types); |
| } |
| |
| void SyncBackendHost::Core::DoRequestConfig( |
| const syncable::ModelTypeBitSet& added_types) { |
| syncapi_->RequestConfig(added_types); |
| } |
| |
| void SyncBackendHost::Core::DoStartConfiguration(Callback0::Type* callback) { |
| syncapi_->StartConfigurationMode(callback); |
| } |
| |
| UIModelWorker* SyncBackendHost::ui_worker() { |
| ModelSafeWorker* w = registrar_.workers[GROUP_UI]; |
| if (w == NULL) |
| return NULL; |
| if (w->GetModelSafeGroup() != GROUP_UI) |
| NOTREACHED(); |
| return static_cast<UIModelWorker*>(w); |
| } |
| |
| void SyncBackendHost::Core::DoShutdown(bool sync_disabled) { |
| DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); |
| |
| save_changes_timer_.Stop(); |
| syncapi_->Shutdown(); // Stops the SyncerThread. |
| syncapi_->RemoveObserver(this); |
| DisconnectChildJsEventRouter(); |
| host_->ui_worker()->OnSyncerShutdownComplete(); |
| |
| if (sync_disabled) |
| DeleteSyncDataFolder(); |
| |
| host_ = NULL; |
| } |
| |
| ChangeProcessor* SyncBackendHost::Core::GetProcessor( |
| syncable::ModelType model_type) { |
| std::map<syncable::ModelType, ChangeProcessor*>::const_iterator it = |
| host_->processors_.find(model_type); |
| |
| // Until model association happens for a datatype, it will not appear in |
| // the processors list. During this time, it is OK to drop changes on |
| // the floor (since model association has not happened yet). When the |
| // data type is activated, model association takes place then the change |
| // processor is added to the processors_ list. This all happens on |
| // the UI thread so we will never drop any changes after model |
| // association. |
| if (it == host_->processors_.end()) |
| return NULL; |
| |
| if (!IsCurrentThreadSafeForModel(model_type)) { |
| NOTREACHED() << "Changes applied on wrong thread."; |
| return NULL; |
| } |
| |
| // Now that we're sure we're on the correct thread, we can access the |
| // ChangeProcessor. |
| ChangeProcessor* processor = it->second; |
| |
| // Ensure the change processor is willing to accept changes. |
| if (!processor->IsRunning()) |
| return NULL; |
| |
| return processor; |
| } |
| |
| void SyncBackendHost::Core::OnChangesApplied( |
| syncable::ModelType model_type, |
| const sync_api::BaseTransaction* trans, |
| const sync_api::SyncManager::ChangeRecord* changes, |
| int change_count) { |
| if (!host_ || !host_->frontend_) { |
| DCHECK(false) << "OnChangesApplied called after Shutdown?"; |
| return; |
| } |
| ChangeProcessor* processor = GetProcessor(model_type); |
| if (!processor) |
| return; |
| |
| processor->ApplyChangesFromSyncModel(trans, changes, change_count); |
| } |
| |
| void SyncBackendHost::Core::OnChangesComplete( |
| syncable::ModelType model_type) { |
| if (!host_ || !host_->frontend_) { |
| DCHECK(false) << "OnChangesComplete called after Shutdown?"; |
| return; |
| } |
| |
| ChangeProcessor* processor = GetProcessor(model_type); |
| if (!processor) |
| return; |
| |
| // This call just notifies the processor that it can commit, it already |
| // buffered any changes it plans to makes so needs no further information. |
| processor->CommitChangesFromSyncModel(); |
| } |
| |
| |
| void SyncBackendHost::Core::OnSyncCycleCompleted( |
| const SyncSessionSnapshot* snapshot) { |
| host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| &Core::HandleSyncCycleCompletedOnFrontendLoop, |
| new SyncSessionSnapshot(*snapshot))); |
| } |
| |
| void SyncBackendHost::Core::HandleSyncCycleCompletedOnFrontendLoop( |
| SyncSessionSnapshot* snapshot) { |
| if (!host_ || !host_->frontend_) |
| return; |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| |
| host_->last_snapshot_.reset(snapshot); |
| |
| const syncable::ModelTypeSet& to_migrate = |
| snapshot->syncer_status.types_needing_local_migration; |
| if (!to_migrate.empty()) |
| host_->frontend_->OnMigrationNeededForTypes(to_migrate); |
| |
| // If we are waiting for a configuration change, check here to see |
| // if this sync cycle has initialized all of the types we've been |
| // waiting for. |
| if (host_->pending_download_state_.get()) { |
| bool found_all_added = true; |
| for (syncable::ModelTypeSet::const_iterator it = |
| host_->pending_download_state_->initial_types.begin(); |
| it != host_->pending_download_state_->initial_types.end(); |
| ++it) { |
| if (host_->pending_download_state_->added_types.test(*it)) |
| found_all_added &= snapshot->initial_sync_ended.test(*it); |
| } |
| if (!found_all_added) { |
| NOTREACHED() << "Update didn't return updates for all types requested."; |
| } else { |
| host_->pending_download_state_->ready_task->Run(); |
| } |
| host_->pending_download_state_.reset(); |
| } |
| host_->frontend_->OnSyncCycleCompleted(); |
| } |
| |
| void SyncBackendHost::Core::OnInitializationComplete() { |
| if (!host_ || !host_->frontend_) |
| return; // We may have been told to Shutdown before initialization |
| // completed. |
| |
| // We could be on some random sync backend thread, so MessageLoop::current() |
| // can definitely be null in here. |
| host_->frontend_loop_->PostTask(FROM_HERE, |
| NewRunnableMethod(this, |
| &Core::HandleInitalizationCompletedOnFrontendLoop)); |
| |
| // Initialization is complete, so we can schedule recurring SaveChanges. |
| host_->core_thread_.message_loop()->PostTask(FROM_HERE, |
| NewRunnableMethod(this, &Core::StartSavingChanges)); |
| } |
| |
| void SyncBackendHost::Core::HandleInitalizationCompletedOnFrontendLoop() { |
| if (!host_) |
| return; |
| host_->HandleInitializationCompletedOnFrontendLoop(); |
| } |
| |
| void SyncBackendHost::HandleInitializationCompletedOnFrontendLoop() { |
| if (!frontend_) |
| return; |
| syncapi_initialized_ = true; |
| frontend_->OnBackendInitialized(); |
| } |
| |
| bool SyncBackendHost::Core::IsCurrentThreadSafeForModel( |
| syncable::ModelType model_type) { |
| base::AutoLock lock(host_->registrar_lock_); |
| |
| browser_sync::ModelSafeRoutingInfo::const_iterator routing_it = |
| host_->registrar_.routing_info.find(model_type); |
| if (routing_it == host_->registrar_.routing_info.end()) |
| return false; |
| browser_sync::ModelSafeGroup group = routing_it->second; |
| WorkerMap::const_iterator worker_it = host_->registrar_.workers.find(group); |
| if (worker_it == host_->registrar_.workers.end()) |
| return false; |
| ModelSafeWorker* worker = worker_it->second; |
| return worker->CurrentThreadIsWorkThread(); |
| } |
| |
| void SyncBackendHost::Core::OnAuthError(const AuthError& auth_error) { |
| // Post to our core loop so we can modify state. Could be on another thread. |
| host_->frontend_loop_->PostTask(FROM_HERE, |
| NewRunnableMethod(this, &Core::HandleAuthErrorEventOnFrontendLoop, |
| auth_error)); |
| } |
| |
| void SyncBackendHost::Core::OnPassphraseRequired(bool for_decryption) { |
| host_->frontend_loop_->PostTask(FROM_HERE, |
| NewRunnableMethod(this, &Core::NotifyPassphraseRequired, for_decryption)); |
| } |
| |
| void SyncBackendHost::Core::OnPassphraseFailed() { |
| host_->frontend_loop_->PostTask(FROM_HERE, |
| NewRunnableMethod(this, &Core::NotifyPassphraseFailed)); |
| } |
| |
| void SyncBackendHost::Core::OnPassphraseAccepted( |
| const std::string& bootstrap_token) { |
| host_->frontend_loop_->PostTask(FROM_HERE, |
| NewRunnableMethod(this, &Core::NotifyPassphraseAccepted, |
| bootstrap_token)); |
| } |
| |
| void SyncBackendHost::Core::OnStopSyncingPermanently() { |
| host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| &Core::HandleStopSyncingPermanentlyOnFrontendLoop)); |
| } |
| |
| void SyncBackendHost::Core::OnUpdatedToken(const std::string& token) { |
| host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| &Core::NotifyUpdatedToken, token)); |
| } |
| |
| void SyncBackendHost::Core::OnClearServerDataSucceeded() { |
| host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| &Core::HandleClearServerDataSucceededOnFrontendLoop)); |
| } |
| |
| void SyncBackendHost::Core::OnClearServerDataFailed() { |
| host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, |
| &Core::HandleClearServerDataFailedOnFrontendLoop)); |
| } |
| |
| void SyncBackendHost::Core::OnEncryptionComplete( |
| const syncable::ModelTypeSet& encrypted_types) { |
| host_->frontend_loop_->PostTask( |
| FROM_HERE, |
| NewRunnableMethod(this, &Core::NotifyEncryptionComplete, |
| encrypted_types)); |
| } |
| |
| void SyncBackendHost::Core::RouteJsEvent( |
| const std::string& name, const JsArgList& args, |
| const JsEventHandler* target) { |
| host_->frontend_loop_->PostTask( |
| FROM_HERE, NewRunnableMethod( |
| this, &Core::RouteJsEventOnFrontendLoop, name, args, target)); |
| } |
| |
| void SyncBackendHost::Core::HandleStopSyncingPermanentlyOnFrontendLoop() { |
| if (!host_ || !host_->frontend_) |
| return; |
| host_->frontend_->OnStopSyncingPermanently(); |
| } |
| |
| void SyncBackendHost::Core::HandleClearServerDataSucceededOnFrontendLoop() { |
| if (!host_ || !host_->frontend_) |
| return; |
| host_->frontend_->OnClearServerDataSucceeded(); |
| } |
| |
| void SyncBackendHost::Core::HandleClearServerDataFailedOnFrontendLoop() { |
| if (!host_ || !host_->frontend_) |
| return; |
| host_->frontend_->OnClearServerDataFailed(); |
| } |
| |
| void SyncBackendHost::Core::HandleAuthErrorEventOnFrontendLoop( |
| const GoogleServiceAuthError& new_auth_error) { |
| if (!host_ || !host_->frontend_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| |
| host_->last_auth_error_ = new_auth_error; |
| host_->frontend_->OnAuthError(); |
| } |
| |
| void SyncBackendHost::Core::RouteJsEventOnFrontendLoop( |
| const std::string& name, const JsArgList& args, |
| const JsEventHandler* target) { |
| if (!host_ || !parent_router_) |
| return; |
| |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| |
| parent_router_->RouteJsEvent(name, args, target); |
| } |
| |
| void SyncBackendHost::Core::StartSavingChanges() { |
| save_changes_timer_.Start( |
| base::TimeDelta::FromSeconds(kSaveChangesIntervalSeconds), |
| this, &Core::SaveChanges); |
| } |
| |
| void SyncBackendHost::Core::DoRequestNudge( |
| const tracked_objects::Location& nudge_location) { |
| syncapi_->RequestNudge(nudge_location); |
| } |
| |
| void SyncBackendHost::Core::DoRequestClearServerData() { |
| syncapi_->RequestClearServerData(); |
| } |
| |
| void SyncBackendHost::Core::SaveChanges() { |
| syncapi_->SaveChanges(); |
| } |
| |
| void SyncBackendHost::Core::DeleteSyncDataFolder() { |
| if (file_util::DirectoryExists(host_->sync_data_folder_path())) { |
| if (!file_util::Delete(host_->sync_data_folder_path(), true)) |
| LOG(DFATAL) << "Could not delete the Sync Data folder."; |
| } |
| } |
| |
| void SyncBackendHost::Core::SetParentJsEventRouter(JsEventRouter* router) { |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| DCHECK(router); |
| parent_router_ = router; |
| MessageLoop* core_message_loop = host_->core_thread_.message_loop(); |
| CHECK(core_message_loop); |
| core_message_loop->PostTask( |
| FROM_HERE, |
| NewRunnableMethod(this, |
| &SyncBackendHost::Core::ConnectChildJsEventRouter)); |
| } |
| |
| void SyncBackendHost::Core::RemoveParentJsEventRouter() { |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| parent_router_ = NULL; |
| MessageLoop* core_message_loop = host_->core_thread_.message_loop(); |
| CHECK(core_message_loop); |
| core_message_loop->PostTask( |
| FROM_HERE, |
| NewRunnableMethod(this, |
| &SyncBackendHost::Core::DisconnectChildJsEventRouter)); |
| } |
| |
| const JsEventRouter* SyncBackendHost::Core::GetParentJsEventRouter() const { |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| return parent_router_; |
| } |
| |
| void SyncBackendHost::Core::ProcessMessage( |
| const std::string& name, const JsArgList& args, |
| const JsEventHandler* sender) { |
| DCHECK_EQ(MessageLoop::current(), host_->frontend_loop_); |
| MessageLoop* core_message_loop = host_->core_thread_.message_loop(); |
| CHECK(core_message_loop); |
| core_message_loop->PostTask( |
| FROM_HERE, |
| NewRunnableMethod(this, |
| &SyncBackendHost::Core::DoProcessMessage, |
| name, args, sender)); |
| } |
| |
| void SyncBackendHost::Core::ConnectChildJsEventRouter() { |
| DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop()); |
| // We need this check since AddObserver() can be called at most once |
| // for a given observer. |
| if (!syncapi_->GetJsBackend()->GetParentJsEventRouter()) { |
| syncapi_->GetJsBackend()->SetParentJsEventRouter(this); |
| syncapi_->AddObserver(&sync_manager_observer_); |
| } |
| } |
| |
| void SyncBackendHost::Core::DisconnectChildJsEventRouter() { |
| DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop()); |
| syncapi_->GetJsBackend()->RemoveParentJsEventRouter(); |
| syncapi_->RemoveObserver(&sync_manager_observer_); |
| } |
| |
| void SyncBackendHost::Core::DoProcessMessage( |
| const std::string& name, const JsArgList& args, |
| const JsEventHandler* sender) { |
| DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop()); |
| syncapi_->GetJsBackend()->ProcessMessage(name, args, sender); |
| } |
| |
| void SyncBackendHost::Core::DeferNudgeForCleanup() { |
| DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop()); |
| deferred_nudge_for_cleanup_requested_ = true; |
| } |
| |
| } // namespace browser_sync |