<commit_before>// 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 "chrome/browser/sync/profile_sync_service.h"

#include <stddef.h>
#include <map>
#include <ostream>
#include <set>
#include <utility>

#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/string16.h"
#include "base/stringprintf.h"
#include "base/task.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/net/chrome_cookie_notification_details.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/backend_migrator.h"
#include "chrome/browser/sync/engine/configure_reason.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/change_processor.h"
#include "chrome/browser/sync/glue/data_type_controller.h"
#include "chrome/browser/sync/glue/data_type_manager.h"
#include "chrome/browser/sync/glue/session_data_type_controller.h"
#include "chrome/browser/sync/js_arg_list.h"
#include "chrome/browser/sync/js_event_details.h"
#include "chrome/browser/sync/profile_sync_factory.h"
#include "chrome/browser/sync/signin_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/chrome_notification_types.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 "chrome/common/time_format.h"
#include "chrome/common/url_constants.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "grit/generated_resources.h"
#include "net/base/cookie_monster.h"
#include "ui/base/l10n/l10n_util.h"

using browser_sync::ChangeProcessor;
using browser_sync::DataTypeController;
using browser_sync::DataTypeManager;
using browser_sync::SyncBackendHost;
using sync_api::SyncCredentials;

typedef GoogleServiceAuthError AuthError;

const char* ProfileSyncService::kSyncServerUrl =
    "https://clients4.google.com/chrome-sync";

const char* ProfileSyncService::kDevServerUrl =
    "https://clients4.google.com/chrome-sync/dev";

static const int kSyncClearDataTimeoutInSeconds = 60;  // 1 minute.

ProfileSyncService::ProfileSyncService(ProfileSyncFactory* factory,
                                       Profile* profile,
                                       SigninManager* signin_manager,
                                       const std::string& cros_user)
    : last_auth_error_(AuthError::None()),
      passphrase_required_reason_(sync_api::REASON_PASSPHRASE_NOT_REQUIRED),
      factory_(factory),
      profile_(profile),
      cros_user_(cros_user),
      sync_service_url_(kDevServerUrl),
      backend_initialized_(false),
      is_auth_in_progress_(false),
      wizard_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
      signin_(signin_manager),
      unrecoverable_error_detected_(false),
      scoped_runnable_method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
      expect_sync_configuration_aborted_(false),
      clear_server_data_state_(CLEAR_NOT_STARTED) {
  // By default, dev, canary, and unbranded Chromium users will go to the
  // development servers. Development servers have more features than standard
  // sync servers. Users with officially-branded Chrome stable and beta builds
  // will go to the standard sync servers.
  //
  // GetChannel hits the registry on Windows. See http://crbug.com/70380.
  base::ThreadRestrictions::ScopedAllowIO allow_io;
  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
  if (channel == chrome::VersionInfo::CHANNEL_STABLE ||
      channel == chrome::VersionInfo::CHANNEL_BETA) {
    sync_service_url_ = GURL(kSyncServerUrl);
  }
}

ProfileSyncService::~ProfileSyncService() {
  Shutdown(false);
}

bool ProfileSyncService::AreCredentialsAvailable() {
  if (IsManaged()) {
    return false;
  }

  // CrOS user is always logged in. Chrome uses signin_ to check logged in.
  if (!cros_user_.empty() || !signin_->GetUsername().empty()) {
    // TODO(chron): Verify CrOS unit test behavior.
    if (profile()->GetTokenService() &&
        profile()->GetTokenService()->HasTokenForService(
            GaiaConstants::kSyncService)) {
      return true;
    }
  }
  return false;
}

void ProfileSyncService::Initialize() {
  InitSettings();
  RegisterPreferences();

  // We clear this here (vs Shutdown) because we want to remember that an error
  // happened on shutdown so we can display details (message, location) about it
  // in about:sync.
  unrecoverable_error_detected_ = false;
  unrecoverable_error_message_.clear();
  unrecoverable_error_location_.reset();

  // Watch the preference that indicates sync is managed so we can take
  // appropriate action.
  pref_sync_managed_.Init(prefs::kSyncManaged, profile_->GetPrefs(), this);

  // For now, the only thing we can do through policy is to turn sync off.
  if (IsManaged()) {
    DisableForUser();
    return;
  }

  RegisterAuthNotifications();

  if (!HasSyncSetupCompleted())
    DisableForUser();  // Clean up in case of previous crash / setup abort.

  // In Chrome, we integrate a SigninManager which works with the sync
  // setup wizard to kick off the TokenService. CrOS does its own plumbing
  // for the TokenService in login and does not normally rely on signin_,
  // so only initialize this if the token service has not been initialized
  // (e.g. the browser crashed or is being debugged).
  if (cros_user_.empty() ||
      !profile_->GetTokenService()->Initialized()) {
    // Will load tokens from DB and broadcast Token events after.
    // Note: We rely on signin_ != NULL unless !cros_user_.empty().
    signin_->Initialize(profile_);
  }

  if (!HasSyncSetupCompleted()) {
    // Under ChromeOS, just autostart it anyway if creds are here and start
    // is not being suppressed by preferences.
    if (!cros_user_.empty() &&
        !profile_->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart) &&
        AreCredentialsAvailable()) {
      StartUp();
    }
  } else if (AreCredentialsAvailable()) {
    // If we have credentials and sync setup finished, autostart the backend.
    // Note that if we haven't finished setting up sync, backend bring up will
    // be done by the wizard.
    StartUp();
  }
}

void ProfileSyncService::RegisterAuthNotifications() {
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKEN_AVAILABLE,
                 Source<TokenService>(profile_->GetTokenService()));
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKEN_LOADING_FINISHED,
                 Source<TokenService>(profile_->GetTokenService()));
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
                 Source<TokenService>(profile_->GetTokenService()));
  registrar_.Add(this,
                 chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL,
                 Source<Profile>(profile_));
  registrar_.Add(this,
                 chrome::NOTIFICATION_GOOGLE_SIGNIN_FAILED,
                 Source<Profile>(profile_));
}

void ProfileSyncService::RegisterDataTypeController(
    DataTypeController* data_type_controller) {
  DCHECK_EQ(data_type_controllers_.count(data_type_controller->type()), 0U);
  data_type_controllers_[data_type_controller->type()] =
      data_type_controller;
}

browser_sync::SessionModelAssociator*
    ProfileSyncService::GetSessionModelAssociator() {
  if (data_type_controllers_.find(syncable::SESSIONS) ==
      data_type_controllers_.end() ||
      data_type_controllers_.find(syncable::SESSIONS)->second->state() !=
      DataTypeController::RUNNING) {
    return NULL;
  }
  return static_cast<browser_sync::SessionDataTypeController*>(
      data_type_controllers_.find(
      syncable::SESSIONS)->second.get())->GetModelAssociator();
}

void ProfileSyncService::ResetClearServerDataState() {
  clear_server_data_state_ = CLEAR_NOT_STARTED;
}

ProfileSyncService::ClearServerDataState
    ProfileSyncService::GetClearServerDataState() {
  return clear_server_data_state_;
}

void ProfileSyncService::GetDataTypeControllerStates(
  browser_sync::DataTypeController::StateMap* state_map) const {
    for (browser_sync::DataTypeController::TypeMap::const_iterator iter =
         data_type_controllers_.begin(); iter != data_type_controllers_.end();
         ++iter)
      (*state_map)[iter->first] = iter->second.get()->state();
}

void ProfileSyncService::InitSettings() {
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();

  // Override the sync server URL from the command-line, if sync server
  // command-line argument exists.
  if (command_line.HasSwitch(switches::kSyncServiceURL)) {
    std::string value(command_line.GetSwitchValueASCII(
        switches::kSyncServiceURL));
    if (!value.empty()) {
      GURL custom_sync_url(value);
      if (custom_sync_url.is_valid()) {
        sync_service_url_ = custom_sync_url;
      } else {
        LOG(WARNING) << "The following sync URL specified at the command-line "
                     << "is invalid: " << value;
      }
    }
  }
}

void ProfileSyncService::RegisterPreferences() {
  PrefService* pref_service = profile_->GetPrefs();
  if (pref_service->FindPreference(prefs::kSyncLastSyncedTime))
    return;
  pref_service->RegisterInt64Pref(prefs::kSyncLastSyncedTime,
                                  0,
                                  PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncHasSetupCompleted,
                                    false,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncSuppressStart,
                                    false,
                                    PrefService::UNSYNCABLE_PREF);

  // If you've never synced before, or if you're using Chrome OS, all datatypes
  // are on by default.
  // TODO(nick): Perhaps a better model would be to always default to false,
  // and explicitly call SetDataTypes() when the user shows the wizard.
#if defined(OS_CHROMEOS)
  bool enable_by_default = true;
#else
  bool enable_by_default =
      !pref_service->HasPrefPath(prefs::kSyncHasSetupCompleted);
#endif

  pref_service->RegisterBooleanPref(prefs::kSyncBookmarks,
                                    true,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncPasswords,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncPreferences,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncAutofill,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncThemes,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncTypedUrls,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncExtensions,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncApps,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncSessions,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kKeepEverythingSynced,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncManaged,
                                    false,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterStringPref(prefs::kEncryptionBootstrapToken,
                                   "",
                                   PrefService::UNSYNCABLE_PREF);

  pref_service->RegisterBooleanPref(prefs::kSyncAutofillProfile,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
}

void ProfileSyncService::ClearPreferences() {
  PrefService* pref_service = profile_->GetPrefs();
  pref_service->ClearPref(prefs::kSyncLastSyncedTime);
  pref_service->ClearPref(prefs::kSyncHasSetupCompleted);
  pref_service->ClearPref(prefs::kEncryptionBootstrapToken);

  // TODO(nick): The current behavior does not clear e.g. prefs::kSyncBookmarks.
  // Is that really what we want?
  pref_service->ScheduleSavePersistentPrefs();
}

SyncCredentials ProfileSyncService::GetCredentials() {
  SyncCredentials credentials;
  credentials.email = !cros_user_.empty() ? cros_user_ : signin_->GetUsername();
  DCHECK(!credentials.email.empty());
  TokenService* service = profile_->GetTokenService();
  credentials.sync_token = service->GetTokenForService(
      GaiaConstants::kSyncService);
  return credentials;
}

void ProfileSyncService::InitializeBackend(bool delete_sync_data_folder) {
  if (!backend_.get()) {
    NOTREACHED();
    return;
  }

  syncable::ModelTypeSet types;
  // If sync setup hasn't finished, we don't want to initialize routing info
  // for any data types so that we don't download updates for types that the
  // user chooses not to sync on the first DownloadUpdatesCommand.
  if (HasSyncSetupCompleted()) {
    GetPreferredDataTypes(&types);
  }

  SyncCredentials credentials = GetCredentials();

  scoped_refptr<net::URLRequestContextGetter> request_context_getter(
      profile_->GetRequestContext());

  backend_->Initialize(this,
                       sync_service_url_,
                       types,
                       credentials,
                       delete_sync_data_folder);
}

void ProfileSyncService::CreateBackend() {
  backend_.reset(new SyncBackendHost(profile_));
}

bool ProfileSyncService::IsEncryptedDatatypeEnabled() const {
  syncable::ModelTypeSet preferred_types;
  GetPreferredDataTypes(&preferred_types);
  syncable::ModelTypeSet encrypted_types;
  GetEncryptedDataTypes(&encrypted_types);
  syncable::ModelTypeBitSet preferred_types_bitset =
      syncable::ModelTypeBitSetFromSet(preferred_types);
  syncable::ModelTypeBitSet encrypted_types_bitset =
      syncable::ModelTypeBitSetFromSet(encrypted_types);
  DCHECK(encrypted_types.count(syncable::PASSWORDS));
  return (preferred_types_bitset & encrypted_types_bitset).any();
}

void ProfileSyncService::StartUp() {
  // Don't start up multiple times.
  if (backend_.get()) {
    VLOG(1) << "Skipping bringing up backend host.";
    return;
  }

  DCHECK(AreCredentialsAvailable());

  last_synced_time_ = base::Time::FromInternalValue(
      profile_->GetPrefs()->GetInt64(prefs::kSyncLastSyncedTime));

  CreateBackend();

  // Initialize the backend.  Every time we start up a new SyncBackendHost,
  // we'll want to start from a fresh SyncDB, so delete any old one that might
  // be there.
  InitializeBackend(!HasSyncSetupCompleted());
}

void ProfileSyncService::Shutdown(bool sync_disabled) {
  // Stop all data type controllers, if needed.
  if (data_type_manager_.get()) {
    if (data_type_manager_->state() != DataTypeManager::STOPPED) {
      data_type_manager_->Stop();
    }

    registrar_.Remove(this,
                      chrome::NOTIFICATION_SYNC_CONFIGURE_START,
                      Source<DataTypeManager>(data_type_manager_.get()));
    registrar_.Remove(this,
                      chrome::NOTIFICATION_SYNC_CONFIGURE_DONE,
                      Source<DataTypeManager>(data_type_manager_.get()));
    data_type_manager_.reset();
  }

  // Shutdown the migrator before the backend to ensure it doesn't pull a null
  // snapshot.
  migrator_.reset();
  js_event_handlers_.RemoveBackend();

  // Move aside the backend so nobody else tries to use it while we are
  // shutting it down.
  scoped_ptr<SyncBackendHost> doomed_backend(backend_.release());
  if (doomed_backend.get()) {
    doomed_backend->Shutdown(sync_disabled);

    doomed_backend.reset();
  }

  scoped_runnable_method_factory_.RevokeAll();

  // Clear various flags.
  expect_sync_configuration_aborted_ = false;
  is_auth_in_progress_ = false;
  backend_initialized_ = false;
  cached_passphrase_ = CachedPassphrase();
  gaia_password_.clear();
  pending_types_for_encryption_.clear();
  passphrase_required_reason_ = sync_api::REASON_PASSPHRASE_NOT_REQUIRED;
  last_attempted_user_email_.clear();
  last_auth_error_ = GoogleServiceAuthError::None();
}

void ProfileSyncService::ClearServerData() {
  clear_server_data_state_ = CLEAR_CLEARING;
  clear_server_data_timer_.Start(
      base::TimeDelta::FromSeconds(kSyncClearDataTimeoutInSeconds), this,
      &ProfileSyncService::OnClearServerDataTimeout);
  backend_->RequestClearServerData();
}

void ProfileSyncService::DisableForUser() {
  // Clear prefs (including SyncSetupHasCompleted) before shutting down so
  // PSS clients don't think we're set up while we're shutting down.
  ClearPreferences();
  Shutdown(true);

  signin_->SignOut();

  NotifyObservers();
}

bool ProfileSyncService::HasSyncSetupCompleted() const {
  return profile_->GetPrefs()->GetBoolean(prefs::kSyncHasSetupCompleted);
}

void ProfileSyncService::SetSyncSetupCompleted() {
  PrefService* prefs = profile()->GetPrefs();
  prefs->SetBoolean(prefs::kSyncHasSetupCompleted, true);
  prefs->SetBoolean(prefs::kSyncSuppressStart, false);

  prefs->ScheduleSavePersistentPrefs();
}

void ProfileSyncService::UpdateLastSyncedTime() {
  last_synced_time_ = base::Time::Now();
  profile_->GetPrefs()->SetInt64(prefs::kSyncLastSyncedTime,
      last_synced_time_.ToInternalValue());
  profile_->GetPrefs()->ScheduleSavePersistentPrefs();
}

void ProfileSyncService::NotifyObservers() {
  FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
  // TODO(akalin): Make an Observer subclass that listens and does the
  // event routing.
  js_event_handlers_.RouteJsEvent(
      "onServiceStateChanged", browser_sync::JsEventDetails());
}

// static
const char* ProfileSyncService::GetPrefNameForDataType(
    syncable::ModelType data_type) {
  switch (data_type) {
    case syncable::BOOKMARKS:
      return prefs::kSyncBookmarks;
    case syncable::PASSWORDS:
      return prefs::kSyncPasswords;
    case syncable::PREFERENCES:
      return prefs::kSyncPreferences;
    case syncable::AUTOFILL:
      return prefs::kSyncAutofill;
    case syncable::AUTOFILL_PROFILE:
      return prefs::kSyncAutofillProfile;
    case syncable::THEMES:
      return prefs::kSyncThemes;
    case syncable::TYPED_URLS:
      return prefs::kSyncTypedUrls;
    case syncable::EXTENSIONS:
      return prefs::kSyncExtensions;
    case syncable::APPS:
      return prefs::kSyncApps;
    case syncable::SESSIONS:
      return prefs::kSyncSessions;
    default:
      break;
  }
  NOTREACHED();
  return NULL;
}

// An invariant has been violated.  Transition to an error state where we try
// to do as little work as possible, to avoid further corruption or crashes.
void ProfileSyncService::OnUnrecoverableError(
    const tracked_objects::Location& from_here,
    const std::string& message) {
  unrecoverable_error_detected_ = true;
  unrecoverable_error_message_ = message;
  unrecoverable_error_location_.reset(
      new tracked_objects::Location(from_here.function_name(),
                                    from_here.file_name(),
                                    from_here.line_number(),
                                    from_here.program_counter()));

  // Tell the wizard so it can inform the user only if it is already open.
  wizard_.Step(SyncSetupWizard::FATAL_ERROR);

  NotifyObservers();
  LOG(ERROR) << "Unrecoverable error detected -- ProfileSyncService unusable."
      << message;
  std::string location;
  from_here.Write(true, true, &location);
  LOG(ERROR) << location;

  // Shut all data types down.
  MessageLoop::current()->PostTask(FROM_HERE,
        scoped_runnable_method_factory_.NewRunnableMethod(
        &ProfileSyncService::Shutdown, true));
}

void ProfileSyncService::OnBackendInitialized(bool success) {
  if (!success) {
    // If backend initialization failed, abort.  We only want to blow away
    // state (DBs, etc) if this was a first-time scenario that failed.
    Shutdown(!HasSyncSetupCompleted());
    return;
  }

  backend_initialized_ = true;

  js_event_handlers_.SetBackend(backend_->GetJsBackend());

  // The very first time the backend initializes is effectively the first time
  // we can say we successfully "synced".  last_synced_time_ will only be null
  // in this case, because the pref wasn't restored on StartUp.
  if (last_synced_time_.is_null()) {
    UpdateLastSyncedTime();
  }
  NotifyObservers();<commit_msg>sync: if backend initialization fails, make sure to notify the setup wizard.<commit_after>// 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 "chrome/browser/sync/profile_sync_service.h"

#include <stddef.h>
#include <map>
#include <ostream>
#include <set>
#include <utility>

#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/string16.h"
#include "base/stringprintf.h"
#include "base/task.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/browser/net/chrome_cookie_notification_details.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/backend_migrator.h"
#include "chrome/browser/sync/engine/configure_reason.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/change_processor.h"
#include "chrome/browser/sync/glue/data_type_controller.h"
#include "chrome/browser/sync/glue/data_type_manager.h"
#include "chrome/browser/sync/glue/session_data_type_controller.h"
#include "chrome/browser/sync/js_arg_list.h"
#include "chrome/browser/sync/js_event_details.h"
#include "chrome/browser/sync/profile_sync_factory.h"
#include "chrome/browser/sync/signin_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/chrome_notification_types.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 "chrome/common/time_format.h"
#include "chrome/common/url_constants.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
#include "grit/generated_resources.h"
#include "net/base/cookie_monster.h"
#include "ui/base/l10n/l10n_util.h"

using browser_sync::ChangeProcessor;
using browser_sync::DataTypeController;
using browser_sync::DataTypeManager;
using browser_sync::SyncBackendHost;
using sync_api::SyncCredentials;

typedef GoogleServiceAuthError AuthError;

const char* ProfileSyncService::kSyncServerUrl =
    "https://clients4.google.com/chrome-sync";

const char* ProfileSyncService::kDevServerUrl =
    "https://clients4.google.com/chrome-sync/dev";

static const int kSyncClearDataTimeoutInSeconds = 60;  // 1 minute.

ProfileSyncService::ProfileSyncService(ProfileSyncFactory* factory,
                                       Profile* profile,
                                       SigninManager* signin_manager,
                                       const std::string& cros_user)
    : last_auth_error_(AuthError::None()),
      passphrase_required_reason_(sync_api::REASON_PASSPHRASE_NOT_REQUIRED),
      factory_(factory),
      profile_(profile),
      cros_user_(cros_user),
      sync_service_url_(kDevServerUrl),
      backend_initialized_(false),
      is_auth_in_progress_(false),
      wizard_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
      signin_(signin_manager),
      unrecoverable_error_detected_(false),
      scoped_runnable_method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
      expect_sync_configuration_aborted_(false),
      clear_server_data_state_(CLEAR_NOT_STARTED) {
  // By default, dev, canary, and unbranded Chromium users will go to the
  // development servers. Development servers have more features than standard
  // sync servers. Users with officially-branded Chrome stable and beta builds
  // will go to the standard sync servers.
  //
  // GetChannel hits the registry on Windows. See http://crbug.com/70380.
  base::ThreadRestrictions::ScopedAllowIO allow_io;
  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
  if (channel == chrome::VersionInfo::CHANNEL_STABLE ||
      channel == chrome::VersionInfo::CHANNEL_BETA) {
    sync_service_url_ = GURL(kSyncServerUrl);
  }
}

ProfileSyncService::~ProfileSyncService() {
  Shutdown(false);
}

bool ProfileSyncService::AreCredentialsAvailable() {
  if (IsManaged()) {
    return false;
  }

  // CrOS user is always logged in. Chrome uses signin_ to check logged in.
  if (!cros_user_.empty() || !signin_->GetUsername().empty()) {
    // TODO(chron): Verify CrOS unit test behavior.
    if (profile()->GetTokenService() &&
        profile()->GetTokenService()->HasTokenForService(
            GaiaConstants::kSyncService)) {
      return true;
    }
  }
  return false;
}

void ProfileSyncService::Initialize() {
  InitSettings();
  RegisterPreferences();

  // We clear this here (vs Shutdown) because we want to remember that an error
  // happened on shutdown so we can display details (message, location) about it
  // in about:sync.
  unrecoverable_error_detected_ = false;
  unrecoverable_error_message_.clear();
  unrecoverable_error_location_.reset();

  // Watch the preference that indicates sync is managed so we can take
  // appropriate action.
  pref_sync_managed_.Init(prefs::kSyncManaged, profile_->GetPrefs(), this);

  // For now, the only thing we can do through policy is to turn sync off.
  if (IsManaged()) {
    DisableForUser();
    return;
  }

  RegisterAuthNotifications();

  if (!HasSyncSetupCompleted())
    DisableForUser();  // Clean up in case of previous crash / setup abort.

  // In Chrome, we integrate a SigninManager which works with the sync
  // setup wizard to kick off the TokenService. CrOS does its own plumbing
  // for the TokenService in login and does not normally rely on signin_,
  // so only initialize this if the token service has not been initialized
  // (e.g. the browser crashed or is being debugged).
  if (cros_user_.empty() ||
      !profile_->GetTokenService()->Initialized()) {
    // Will load tokens from DB and broadcast Token events after.
    // Note: We rely on signin_ != NULL unless !cros_user_.empty().
    signin_->Initialize(profile_);
  }

  if (!HasSyncSetupCompleted()) {
    // Under ChromeOS, just autostart it anyway if creds are here and start
    // is not being suppressed by preferences.
    if (!cros_user_.empty() &&
        !profile_->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart) &&
        AreCredentialsAvailable()) {
      StartUp();
    }
  } else if (AreCredentialsAvailable()) {
    // If we have credentials and sync setup finished, autostart the backend.
    // Note that if we haven't finished setting up sync, backend bring up will
    // be done by the wizard.
    StartUp();
  }
}

void ProfileSyncService::RegisterAuthNotifications() {
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKEN_AVAILABLE,
                 Source<TokenService>(profile_->GetTokenService()));
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKEN_LOADING_FINISHED,
                 Source<TokenService>(profile_->GetTokenService()));
  registrar_.Add(this,
                 chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
                 Source<TokenService>(profile_->GetTokenService()));
  registrar_.Add(this,
                 chrome::NOTIFICATION_GOOGLE_SIGNIN_SUCCESSFUL,
                 Source<Profile>(profile_));
  registrar_.Add(this,
                 chrome::NOTIFICATION_GOOGLE_SIGNIN_FAILED,
                 Source<Profile>(profile_));
}

void ProfileSyncService::RegisterDataTypeController(
    DataTypeController* data_type_controller) {
  DCHECK_EQ(data_type_controllers_.count(data_type_controller->type()), 0U);
  data_type_controllers_[data_type_controller->type()] =
      data_type_controller;
}

browser_sync::SessionModelAssociator*
    ProfileSyncService::GetSessionModelAssociator() {
  if (data_type_controllers_.find(syncable::SESSIONS) ==
      data_type_controllers_.end() ||
      data_type_controllers_.find(syncable::SESSIONS)->second->state() !=
      DataTypeController::RUNNING) {
    return NULL;
  }
  return static_cast<browser_sync::SessionDataTypeController*>(
      data_type_controllers_.find(
      syncable::SESSIONS)->second.get())->GetModelAssociator();
}

void ProfileSyncService::ResetClearServerDataState() {
  clear_server_data_state_ = CLEAR_NOT_STARTED;
}

ProfileSyncService::ClearServerDataState
    ProfileSyncService::GetClearServerDataState() {
  return clear_server_data_state_;
}

void ProfileSyncService::GetDataTypeControllerStates(
  browser_sync::DataTypeController::StateMap* state_map) const {
    for (browser_sync::DataTypeController::TypeMap::const_iterator iter =
         data_type_controllers_.begin(); iter != data_type_controllers_.end();
         ++iter)
      (*state_map)[iter->first] = iter->second.get()->state();
}

void ProfileSyncService::InitSettings() {
  const CommandLine& command_line = *CommandLine::ForCurrentProcess();

  // Override the sync server URL from the command-line, if sync server
  // command-line argument exists.
  if (command_line.HasSwitch(switches::kSyncServiceURL)) {
    std::string value(command_line.GetSwitchValueASCII(
        switches::kSyncServiceURL));
    if (!value.empty()) {
      GURL custom_sync_url(value);
      if (custom_sync_url.is_valid()) {
        sync_service_url_ = custom_sync_url;
      } else {
        LOG(WARNING) << "The following sync URL specified at the command-line "
                     << "is invalid: " << value;
      }
    }
  }
}

void ProfileSyncService::RegisterPreferences() {
  PrefService* pref_service = profile_->GetPrefs();
  if (pref_service->FindPreference(prefs::kSyncLastSyncedTime))
    return;
  pref_service->RegisterInt64Pref(prefs::kSyncLastSyncedTime,
                                  0,
                                  PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncHasSetupCompleted,
                                    false,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncSuppressStart,
                                    false,
                                    PrefService::UNSYNCABLE_PREF);

  // If you've never synced before, or if you're using Chrome OS, all datatypes
  // are on by default.
  // TODO(nick): Perhaps a better model would be to always default to false,
  // and explicitly call SetDataTypes() when the user shows the wizard.
#if defined(OS_CHROMEOS)
  bool enable_by_default = true;
#else
  bool enable_by_default =
      !pref_service->HasPrefPath(prefs::kSyncHasSetupCompleted);
#endif

  pref_service->RegisterBooleanPref(prefs::kSyncBookmarks,
                                    true,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncPasswords,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncPreferences,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncAutofill,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncThemes,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncTypedUrls,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncExtensions,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncApps,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncSessions,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kKeepEverythingSynced,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterBooleanPref(prefs::kSyncManaged,
                                    false,
                                    PrefService::UNSYNCABLE_PREF);
  pref_service->RegisterStringPref(prefs::kEncryptionBootstrapToken,
                                   "",
                                   PrefService::UNSYNCABLE_PREF);

  pref_service->RegisterBooleanPref(prefs::kSyncAutofillProfile,
                                    enable_by_default,
                                    PrefService::UNSYNCABLE_PREF);
}

void ProfileSyncService::ClearPreferences() {
  PrefService* pref_service = profile_->GetPrefs();
  pref_service->ClearPref(prefs::kSyncLastSyncedTime);
  pref_service->ClearPref(prefs::kSyncHasSetupCompleted);
  pref_service->ClearPref(prefs::kEncryptionBootstrapToken);

  // TODO(nick): The current behavior does not clear e.g. prefs::kSyncBookmarks.
  // Is that really what we want?
  pref_service->ScheduleSavePersistentPrefs();
}

SyncCredentials ProfileSyncService::GetCredentials() {
  SyncCredentials credentials;
  credentials.email = !cros_user_.empty() ? cros_user_ : signin_->GetUsername();
  DCHECK(!credentials.email.empty());
  TokenService* service = profile_->GetTokenService();
  credentials.sync_token = service->GetTokenForService(
      GaiaConstants::kSyncService);
  return credentials;
}

void ProfileSyncService::InitializeBackend(bool delete_sync_data_folder) {
  if (!backend_.get()) {
    NOTREACHED();
    return;
  }

  syncable::ModelTypeSet types;
  // If sync setup hasn't finished, we don't want to initialize routing info
  // for any data types so that we don't download updates for types that the
  // user chooses not to sync on the first DownloadUpdatesCommand.
  if (HasSyncSetupCompleted()) {
    GetPreferredDataTypes(&types);
  }

  SyncCredentials credentials = GetCredentials();

  scoped_refptr<net::URLRequestContextGetter> request_context_getter(
      profile_->GetRequestContext());

  backend_->Initialize(this,
                       sync_service_url_,
                       types,
                       credentials,
                       delete_sync_data_folder);
}

void ProfileSyncService::CreateBackend() {
  backend_.reset(new SyncBackendHost(profile_));
}

bool ProfileSyncService::IsEncryptedDatatypeEnabled() const {
  syncable::ModelTypeSet preferred_types;
  GetPreferredDataTypes(&preferred_types);
  syncable::ModelTypeSet encrypted_types;
  GetEncryptedDataTypes(&encrypted_types);
  syncable::ModelTypeBitSet preferred_types_bitset =
      syncable::ModelTypeBitSetFromSet(preferred_types);
  syncable::ModelTypeBitSet encrypted_types_bitset =
      syncable::ModelTypeBitSetFromSet(encrypted_types);
  DCHECK(encrypted_types.count(syncable::PASSWORDS));
  return (preferred_types_bitset & encrypted_types_bitset).any();
}

void ProfileSyncService::StartUp() {
  // Don't start up multiple times.
  if (backend_.get()) {
    VLOG(1) << "Skipping bringing up backend host.";
    return;
  }

  DCHECK(AreCredentialsAvailable());

  last_synced_time_ = base::Time::FromInternalValue(
      profile_->GetPrefs()->GetInt64(prefs::kSyncLastSyncedTime));

  CreateBackend();

  // Initialize the backend.  Every time we start up a new SyncBackendHost,
  // we'll want to start from a fresh SyncDB, so delete any old one that might
  // be there.
  InitializeBackend(!HasSyncSetupCompleted());
}

void ProfileSyncService::Shutdown(bool sync_disabled) {
  // Stop all data type controllers, if needed.
  if (data_type_manager_.get()) {
    if (data_type_manager_->state() != DataTypeManager::STOPPED) {
      data_type_manager_->Stop();
    }

    registrar_.Remove(this,
                      chrome::NOTIFICATION_SYNC_CONFIGURE_START,
                      Source<DataTypeManager>(data_type_manager_.get()));
    registrar_.Remove(this,
                      chrome::NOTIFICATION_SYNC_CONFIGURE_DONE,
                      Source<DataTypeManager>(data_type_manager_.get()));
    data_type_manager_.reset();
  }

  // Shutdown the migrator before the backend to ensure it doesn't pull a null
  // snapshot.
  migrator_.reset();
  js_event_handlers_.RemoveBackend();

  // Move aside the backend so nobody else tries to use it while we are
  // shutting it down.
  scoped_ptr<SyncBackendHost> doomed_backend(backend_.release());
  if (doomed_backend.get()) {
    doomed_backend->Shutdown(sync_disabled);

    doomed_backend.reset();
  }

  scoped_runnable_method_factory_.RevokeAll();

  // Clear various flags.
  expect_sync_configuration_aborted_ = false;
  is_auth_in_progress_ = false;
  backend_initialized_ = false;
  cached_passphrase_ = CachedPassphrase();
  gaia_password_.clear();
  pending_types_for_encryption_.clear();
  passphrase_required_reason_ = sync_api::REASON_PASSPHRASE_NOT_REQUIRED;
  last_attempted_user_email_.clear();
  last_auth_error_ = GoogleServiceAuthError::None();
}

void ProfileSyncService::ClearServerData() {
  clear_server_data_state_ = CLEAR_CLEARING;
  clear_server_data_timer_.Start(
      base::TimeDelta::FromSeconds(kSyncClearDataTimeoutInSeconds), this,
      &ProfileSyncService::OnClearServerDataTimeout);
  backend_->RequestClearServerData();
}

void ProfileSyncService::DisableForUser() {
  // Clear prefs (including SyncSetupHasCompleted) before shutting down so
  // PSS clients don't think we're set up while we're shutting down.
  ClearPreferences();
  Shutdown(true);

  signin_->SignOut();

  NotifyObservers();
}

bool ProfileSyncService::HasSyncSetupCompleted() const {
  return profile_->GetPrefs()->GetBoolean(prefs::kSyncHasSetupCompleted);
}

void ProfileSyncService::SetSyncSetupCompleted() {
  PrefService* prefs = profile()->GetPrefs();
  prefs->SetBoolean(prefs::kSyncHasSetupCompleted, true);
  prefs->SetBoolean(prefs::kSyncSuppressStart, false);

  prefs->ScheduleSavePersistentPrefs();
}

void ProfileSyncService::UpdateLastSyncedTime() {
  last_synced_time_ = base::Time::Now();
  profile_->GetPrefs()->SetInt64(prefs::kSyncLastSyncedTime,
      last_synced_time_.ToInternalValue());
  profile_->GetPrefs()->ScheduleSavePersistentPrefs();
}

void ProfileSyncService::NotifyObservers() {
  FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged());
  // TODO(akalin): Make an Observer subclass that listens and does the
  // event routing.
  js_event_handlers_.RouteJsEvent(
      "onServiceStateChanged", browser_sync::JsEventDetails());
}

// static
const char* ProfileSyncService::GetPrefNameForDataType(
    syncable::ModelType data_type) {
  switch (data_type) {
    case syncable::BOOKMARKS:
      return prefs::kSyncBookmarks;
    case syncable::PASSWORDS:
      return prefs::kSyncPasswords;
    case syncable::PREFERENCES:
      return prefs::kSyncPreferences;
    case syncable::AUTOFILL:
      return prefs::kSyncAutofill;
    case syncable::AUTOFILL_PROFILE:
      return prefs::kSyncAutofillProfile;
    case syncable::THEMES:
      return prefs::kSyncThemes;
    case syncable::TYPED_URLS:
      return prefs::kSyncTypedUrls;
    case syncable::EXTENSIONS:
      return prefs::kSyncExtensions;
    case syncable::APPS:
      return prefs::kSyncApps;
    case syncable::SESSIONS:
      return prefs::kSyncSessions;
    default:
      break;
  }
  NOTREACHED();
  return NULL;
}

// An invariant has been violated.  Transition to an error state where we try
// to do as little work as possible, to avoid further corruption or crashes.
void ProfileSyncService::OnUnrecoverableError(
    const tracked_objects::Location& from_here,
    const std::string& message) {
  unrecoverable_error_detected_ = true;
  unrecoverable_error_message_ = message;
  unrecoverable_error_location_.reset(
      new tracked_objects::Location(from_here.function_name(),
                                    from_here.file_name(),
                                    from_here.line_number(),
                                    from_here.program_counter()));

  // Tell the wizard so it can inform the user only if it is already open.
  wizard_.Step(SyncSetupWizard::FATAL_ERROR);

  NotifyObservers();
  LOG(ERROR) << "Unrecoverable error detected -- ProfileSyncService unusable."
      << message;
  std::string location;
  from_here.Write(true, true, &location);
  LOG(ERROR) << location;

  // Shut all data types down.
  MessageLoop::current()->PostTask(FROM_HERE,
        scoped_runnable_method_factory_.NewRunnableMethod(
        &ProfileSyncService::Shutdown, true));
}

void ProfileSyncService::OnBackendInitialized(bool success) {
  if (!success) {
    // If backend initialization failed, abort.  We only want to blow away
    // state (DBs, etc) if this was a first-time scenario that failed.
    wizard_.Step(SyncSetupWizard::FATAL_ERROR);
    Shutdown(!HasSyncSetupCompleted());
    return;
  }

  backend_initialized_ = true;

  js_event_handlers_.SetBackend(backend_->GetJsBackend());

  // The very first time the backend initializes is effectively the first time
  // we can say we successfully "synced".  last_synced_time_ will only be null
  // in this case, because the pref wasn't restored on StartUp.
  if (last_synced_time_.is_null()) {
    UpdateLastSyncedTime();
  }
  NotifyObservers();
{'fraction_non_alphanumeric': 0.10113454444212432, 'fraction_numerical': 0.0013456764344214752, 'mean_word_length': 3.4632908770839803, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 173, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 82, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3006510', 'n_tokens_mistral': 12565, 'n_tokens_neox': 12403, 'n_words': 2477}