// Copyright (c) 2010 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 "base/file_util.h" #include "base/file_version_info.h" #include "base/task.h" #include "base/utf_string_conversions.h" #include "chrome/app/chrome_version_info.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/profile.h" #include "chrome/browser/sync/engine/syncapi.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/sync_backend_host.h" #include "chrome/browser/sync/glue/http_bridge.h" #include "chrome/browser/sync/glue/password_model_worker.h" #include "chrome/browser/sync/sessions/session_state.h" #include "chrome/common/notification_service.h" #include "chrome/common/notification_type.h" #include "webkit/glue/webkit_glue.h" static const int kSaveChangesIntervalSeconds = 10; static const char kGaiaServiceId[] = "chromiumsync"; static const char kGaiaSourceForChrome[] = "ChromiumBrowser"; static const FilePath::CharType kSyncDataFolderName[] = FILE_PATH_LITERAL("Sync Data"); using browser_sync::DataTypeController; typedef GoogleServiceAuthError AuthError; namespace browser_sync { using sessions::SyncSessionSnapshot; SyncBackendHost::SyncBackendHost( SyncFrontend* frontend, Profile* profile, const FilePath& profile_path, const DataTypeController::TypeMap& data_type_controllers) : core_thread_("Chrome_SyncCoreThread"), frontend_loop_(MessageLoop::current()), profile_(profile), frontend_(frontend), sync_data_folder_path_(profile_path.Append(kSyncDataFolderName)), data_type_controllers_(data_type_controllers), last_auth_error_(AuthError::None()) { core_ = new Core(this); } SyncBackendHost::SyncBackendHost() : core_thread_("Chrome_SyncCoreThread"), frontend_loop_(MessageLoop::current()), frontend_(NULL), last_auth_error_(AuthError::None()) { } SyncBackendHost::~SyncBackendHost() { DCHECK(!core_ && !frontend_) << "Must call Shutdown before destructor."; DCHECK(registrar_.workers.empty()); } void SyncBackendHost::Initialize( const GURL& sync_service_url, const syncable::ModelTypeSet& types, URLRequestContextGetter* baseline_context_getter, const std::string& lsid, bool delete_sync_data_folder, bool invalidate_sync_login, bool invalidate_sync_xmpp_login, NotificationMethod notification_method) { if (!core_thread_.Start()) return; // 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_HISTORY] = new HistoryModelWorker( profile_->GetHistoryService(Profile::IMPLICIT_ACCESS)); registrar_.workers[GROUP_UI] = new UIModelWorker(frontend_loop_); registrar_.workers[GROUP_PASSIVE] = new ModelSafeWorker(); registrar_.workers[GROUP_PASSWORD] = new PasswordModelWorker( profile_->GetPasswordStore(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; } core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoInitialize, Core::DoInitializeOptions( sync_service_url, true, new HttpBridgeFactory(baseline_context_getter), new HttpBridgeFactory(baseline_context_getter), lsid, delete_sync_data_folder, invalidate_sync_login, invalidate_sync_xmpp_login, notification_method))); } void SyncBackendHost::Authenticate(const std::string& username, const std::string& password, const std::string& captcha) { core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoAuthenticate, username, password, captcha)); } void SyncBackendHost::StartSyncingWithServer() { core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoStartSyncing)); } void SyncBackendHost::SetPassphrase(const std::string& passphrase) { core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoSetPassphrase, passphrase)); } 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. For now this only ever happens at sync-enabled-Chrome exit, // meaning bug 1482548 applies to prolonged "waiting" that may occur in // DoShutdown. 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_. } void SyncBackendHost::ConfigureDataTypes(const syncable::ModelTypeSet& types, CancelableTask* ready_task) { // Only one configure is allowed at a time. DCHECK(!configure_ready_task_.get()); AutoLock lock(registrar_lock_); bool has_new = false; 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) { registrar_.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 (registrar_.routing_info.count(type) == 0) { registrar_.routing_info[type] = GROUP_PASSIVE; has_new = true; } } } // If no new data types were added to the passive group, no need to // wait for the syncer. if (!has_new) { ready_task->Run(); delete ready_task; return; } // Save the task here so we can run it when the syncer finishes // initializing the new data types. It will be run only when the // set of initially sycned data types matches the types requested in // this configure. configure_ready_task_.reset(ready_task); configure_initial_sync_types_ = types; // Nudge the syncer. 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_ready_task_ is run via an // OnInitializationComplete notification. core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestNudge)); } void SyncBackendHost::ActivateDataType( DataTypeController* data_type_controller, ChangeProcessor* change_processor) { 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) { AutoLock lock(registrar_lock_); registrar_.routing_info.erase(data_type_controller->type()); std::map::size_type erased = processors_.erase(data_type_controller->type()); DCHECK_EQ(erased, 1U); // TODO(sync): At this point we need to purge the data associated // with this data type from the sync db. } bool SyncBackendHost::RequestPause() { core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestPause)); return true; } bool SyncBackendHost::RequestResume() { core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestResume)); return true; } void SyncBackendHost::Core::NotifyPaused() { NotificationService::current()->Notify(NotificationType::SYNC_PAUSED, NotificationService::AllSources(), NotificationService::NoDetails()); } void SyncBackendHost::Core::NotifyResumed() { NotificationService::current()->Notify(NotificationType::SYNC_RESUMED, NotificationService::AllSources(), NotificationService::NoDetails()); } void SyncBackendHost::Core::NotifyPassphraseRequired() { NotificationService::current()->Notify( NotificationType::SYNC_PASSPHRASE_REQUIRED, NotificationService::AllSources(), NotificationService::NoDetails()); } void SyncBackendHost::Core::NotifyPassphraseAccepted() { NotificationService::current()->Notify( NotificationType::SYNC_PASSPHRASE_ACCEPTED, NotificationService::AllSources(), NotificationService::NoDetails()); } SyncBackendHost::UserShareHandle SyncBackendHost::GetUserShareHandle() const { return core_->syncapi()->GetUserShare(); } SyncBackendHost::Status SyncBackendHost::GetDetailedStatus() { return core_->syncapi()->GetDetailedStatus(); } SyncBackendHost::StatusSummary SyncBackendHost::GetStatusSummary() { return core_->syncapi()->GetStatusSummary(); } string16 SyncBackendHost::GetAuthenticatedUsername() const { 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* out) { 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) { AutoLock lock(registrar_lock_); ModelSafeRoutingInfo copy(registrar_.routing_info); out->swap(copy); } SyncBackendHost::Core::Core(SyncBackendHost* backend) : host_(backend), syncapi_(new sync_api::SyncManager()) { } // 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 scoped_ptr version_info( chrome_app::GetChromeVersionInfo()); if (version_info == NULL) { DLOG(ERROR) << "Unable to create FileVersionInfo object"; return user_agent; } user_agent += WideToASCII(version_info->product_version()); user_agent += " (" + WideToASCII(version_info->last_change()) + ")"; if (!version_info->is_official_build()) user_agent += "-devel"; return user_agent; } void SyncBackendHost::Core::DoInitialize(const DoInitializeOptions& options) { DCHECK(MessageLoop::current() == host_->core_thread_.message_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. bool success = file_util::CreateDirectory(host_->sync_data_folder_path()); DCHECK(success); syncapi_->SetObserver(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(), kGaiaServiceId, kGaiaSourceForChrome, options.service_url.SchemeIsSecure(), options.http_bridge_factory, options.auth_http_bridge_factory, host_, // ModelSafeWorkerRegistrar. options.attempt_last_user_authentication, options.invalidate_sync_login, options.invalidate_sync_xmpp_login, MakeUserAgentForSyncapi().c_str(), options.lsid.c_str(), options.notification_method); DCHECK(success) << "Syncapi initialization failed!"; } void SyncBackendHost::Core::DoAuthenticate(const std::string& username, const std::string& password, const std::string& captcha) { DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); syncapi_->Authenticate(username.c_str(), password.c_str(), captcha.c_str()); } void SyncBackendHost::Core::DoStartSyncing() { DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); syncapi_->StartSyncing(); } void SyncBackendHost::Core::DoSetPassphrase(const std::string& passphrase) { DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); syncapi_->SetPassphrase(passphrase); } UIModelWorker* SyncBackendHost::ui_worker() { ModelSafeWorker* w = registrar_.workers[GROUP_UI]; if (w == NULL) return NULL; if (w->GetModelSafeGroup() != GROUP_UI) NOTREACHED(); return static_cast(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(); host_->ui_worker()->OnSyncerShutdownComplete(); if (sync_disabled) DeleteSyncDataFolder(); host_ = NULL; } 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; } std::map::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; if (!IsCurrentThreadSafeForModel(model_type)) { NOTREACHED() << "Changes applied on wrong thread."; return; } // 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; processor->ApplyChangesFromSyncModel(trans, changes, change_count); } 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); // 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_->configure_ready_task_.get()) { bool found_all = true; for (syncable::ModelTypeSet::const_iterator it = host_->configure_initial_sync_types_.begin(); it != host_->configure_initial_sync_types_.end(); ++it) { found_all &= snapshot->initial_sync_ended.test(*it); } if (found_all) { host_->configure_ready_task_->Run(); host_->configure_ready_task_.reset(); host_->configure_initial_sync_types_.clear(); } } 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() { host_->HandleInitializationCompletedOnFrontendLoop(); } void SyncBackendHost::HandleInitializationCompletedOnFrontendLoop() { frontend_->OnBackendInitialized(); } bool SyncBackendHost::Core::IsCurrentThreadSafeForModel( syncable::ModelType model_type) { 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) { // We could be on SyncEngine_AuthWatcherThread. Post to our core loop so // we can modify state. host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &Core::HandleAuthErrorEventOnFrontendLoop, auth_error)); } void SyncBackendHost::Core::OnPassphraseRequired() { host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &Core::NotifyPassphraseRequired)); } void SyncBackendHost::Core::OnPassphraseAccepted() { host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &Core::NotifyPassphraseAccepted)); } void SyncBackendHost::Core::OnPaused() { host_->frontend_loop_->PostTask( FROM_HERE, NewRunnableMethod(this, &Core::NotifyPaused)); } void SyncBackendHost::Core::OnResumed() { host_->frontend_loop_->PostTask( FROM_HERE, NewRunnableMethod(this, &Core::NotifyResumed)); } 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::StartSavingChanges() { save_changes_timer_.Start( base::TimeDelta::FromSeconds(kSaveChangesIntervalSeconds), this, &Core::SaveChanges); } void SyncBackendHost::Core::DoRequestNudge() { syncapi_->RequestNudge(); } void SyncBackendHost::Core::DoRequestResume() { syncapi_->RequestResume(); } void SyncBackendHost::Core::DoRequestPause() { syncapi()->RequestPause(); } 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."; } } } // namespace browser_sync