普通文本  |  2843行  |  98.89 KB

// 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/engine/syncapi.h"

#include <algorithm>
#include <bitset>
#include <iomanip>
#include <list>
#include <queue>
#include <string>
#include <vector>

#include "base/base64.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/observer_list.h"
#include "base/sha1.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/synchronization/lock.h"
#include "base/task.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/sync/engine/all_status.h"
#include "chrome/browser/sync/engine/change_reorder_buffer.h"
#include "chrome/browser/sync/engine/model_safe_worker.h"
#include "chrome/browser/sync/engine/nudge_source.h"
#include "chrome/browser/sync/engine/net/server_connection_manager.h"
#include "chrome/browser/sync/engine/net/syncapi_server_connection_manager.h"
#include "chrome/browser/sync/engine/syncer.h"
#include "chrome/browser/sync/engine/syncer_thread.h"
#include "chrome/browser/sync/engine/http_post_provider_factory.h"
#include "chrome/browser/sync/js_arg_list.h"
#include "chrome/browser/sync/js_backend.h"
#include "chrome/browser/sync/js_event_router.h"
#include "chrome/browser/sync/notifier/sync_notifier.h"
#include "chrome/browser/sync/notifier/sync_notifier_observer.h"
#include "chrome/browser/sync/protocol/app_specifics.pb.h"
#include "chrome/browser/sync/protocol/autofill_specifics.pb.h"
#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
#include "chrome/browser/sync/protocol/nigori_specifics.pb.h"
#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
#include "chrome/browser/sync/protocol/proto_value_conversions.h"
#include "chrome/browser/sync/protocol/service_constants.h"
#include "chrome/browser/sync/protocol/session_specifics.pb.h"
#include "chrome/browser/sync/protocol/sync.pb.h"
#include "chrome/browser/sync/protocol/theme_specifics.pb.h"
#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
#include "chrome/browser/sync/sessions/sync_session.h"
#include "chrome/browser/sync/sessions/sync_session_context.h"
#include "chrome/browser/sync/syncable/autofill_migration.h"
#include "chrome/browser/sync/syncable/directory_change_listener.h"
#include "chrome/browser/sync/syncable/directory_manager.h"
#include "chrome/browser/sync/syncable/model_type_payload_map.h"
#include "chrome/browser/sync/syncable/model_type.h"
#include "chrome/browser/sync/syncable/nigori_util.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/browser/sync/util/crypto_helpers.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/deprecated/event_sys.h"
#include "chrome/common/net/gaia/gaia_authenticator.h"
#include "content/browser/browser_thread.h"
#include "net/base/network_change_notifier.h"

using base::TimeDelta;
using browser_sync::AllStatus;
using browser_sync::Cryptographer;
using browser_sync::KeyParams;
using browser_sync::ModelSafeRoutingInfo;
using browser_sync::ModelSafeWorker;
using browser_sync::ModelSafeWorkerRegistrar;
using browser_sync::ServerConnectionEvent;
using browser_sync::ServerConnectionEvent2;
using browser_sync::ServerConnectionEventListener;
using browser_sync::SyncEngineEvent;
using browser_sync::SyncEngineEventListener;
using browser_sync::Syncer;
using browser_sync::SyncerThread;
using browser_sync::kNigoriTag;
using browser_sync::sessions::SyncSessionContext;
using std::list;
using std::hex;
using std::string;
using std::vector;
using syncable::Directory;
using syncable::DirectoryManager;
using syncable::Entry;
using syncable::ModelTypeBitSet;
using syncable::OriginalEntries;
using syncable::WriterTag;
using syncable::SPECIFICS;
using sync_pb::AutofillProfileSpecifics;

typedef GoogleServiceAuthError AuthError;

static const int kThreadExitTimeoutMsec = 60000;
static const int kSSLPort = 443;
static const int kSyncerThreadDelayMsec = 250;

#if defined(OS_CHROMEOS)
static const int kChromeOSNetworkChangeReactionDelayHackMsec = 5000;
#endif  // OS_CHROMEOS

// We manage the lifetime of sync_api::SyncManager::SyncInternal ourselves.
DISABLE_RUNNABLE_METHOD_REFCOUNT(sync_api::SyncManager::SyncInternal);

namespace sync_api {

static const FilePath::CharType kBookmarkSyncUserSettingsDatabase[] =
    FILE_PATH_LITERAL("BookmarkSyncSettings.sqlite3");
static const char kDefaultNameForNewNodes[] = " ";

// The list of names which are reserved for use by the server.
static const char* kForbiddenServerNames[] = { "", ".", ".." };

//////////////////////////////////////////////////////////////////////////
// Static helper functions.

// Helper function to look up the int64 metahandle of an object given the ID
// string.
static int64 IdToMetahandle(syncable::BaseTransaction* trans,
                            const syncable::Id& id) {
  syncable::Entry entry(trans, syncable::GET_BY_ID, id);
  if (!entry.good())
    return kInvalidId;
  return entry.Get(syncable::META_HANDLE);
}

// Checks whether |name| is a server-illegal name followed by zero or more space
// characters.  The three server-illegal names are the empty string, dot, and
// dot-dot.  Very long names (>255 bytes in UTF-8 Normalization Form C) are
// also illegal, but are not considered here.
static bool IsNameServerIllegalAfterTrimming(const std::string& name) {
  size_t untrimmed_count = name.find_last_not_of(' ') + 1;
  for (size_t i = 0; i < arraysize(kForbiddenServerNames); ++i) {
    if (name.compare(0, untrimmed_count, kForbiddenServerNames[i]) == 0)
      return true;
  }
  return false;
}

static bool EndsWithSpace(const std::string& string) {
  return !string.empty() && *string.rbegin() == ' ';
}

// When taking a name from the syncapi, append a space if it matches the
// pattern of a server-illegal name followed by zero or more spaces.
static void SyncAPINameToServerName(const std::wstring& sync_api_name,
                                    std::string* out) {
  *out = WideToUTF8(sync_api_name);
  if (IsNameServerIllegalAfterTrimming(*out))
    out->append(" ");
}

// In the reverse direction, if a server name matches the pattern of a
// server-illegal name followed by one or more spaces, remove the trailing
// space.
static void ServerNameToSyncAPIName(const std::string& server_name,
                                    std::wstring* out) {
  int length_to_copy = server_name.length();
  if (IsNameServerIllegalAfterTrimming(server_name) &&
      EndsWithSpace(server_name))
    --length_to_copy;
  if (!UTF8ToWide(server_name.c_str(), length_to_copy, out)) {
    NOTREACHED() << "Could not convert server name from UTF8 to wide";
  }
}

UserShare::UserShare() {}

UserShare::~UserShare() {}

////////////////////////////////////
// BaseNode member definitions.

BaseNode::BaseNode() {}

BaseNode::~BaseNode() {}

std::string BaseNode::GenerateSyncableHash(
    syncable::ModelType model_type, const std::string& client_tag) {
  // blank PB with just the extension in it has termination symbol,
  // handy for delimiter
  sync_pb::EntitySpecifics serialized_type;
  syncable::AddDefaultExtensionValue(model_type, &serialized_type);
  std::string hash_input;
  serialized_type.AppendToString(&hash_input);
  hash_input.append(client_tag);

  std::string encode_output;
  CHECK(base::Base64Encode(base::SHA1HashString(hash_input), &encode_output));
  return encode_output;
}

sync_pb::PasswordSpecificsData* DecryptPasswordSpecifics(
    const sync_pb::EntitySpecifics& specifics, Cryptographer* crypto) {
  if (!specifics.HasExtension(sync_pb::password))
    return NULL;
  const sync_pb::PasswordSpecifics& password_specifics =
      specifics.GetExtension(sync_pb::password);
  if (!password_specifics.has_encrypted())
    return NULL;
  const sync_pb::EncryptedData& encrypted = password_specifics.encrypted();
  scoped_ptr<sync_pb::PasswordSpecificsData> data(
      new sync_pb::PasswordSpecificsData);
  if (!crypto->Decrypt(encrypted, data.get()))
    return NULL;
  return data.release();
}

bool BaseNode::DecryptIfNecessary(Entry* entry) {
  if (GetIsFolder()) return true;  // Ignore the top-level datatype folder.
  const sync_pb::EntitySpecifics& specifics =
      entry->Get(syncable::SPECIFICS);
  if (specifics.HasExtension(sync_pb::password)) {
    // Passwords have their own legacy encryption structure.
    scoped_ptr<sync_pb::PasswordSpecificsData> data(DecryptPasswordSpecifics(
        specifics, GetTransaction()->GetCryptographer()));
    if (!data.get())
      return false;
    password_data_.swap(data);
    return true;
  }

  // We assume any node with the encrypted field set has encrypted data.
  if (!specifics.has_encrypted())
    return true;

  const sync_pb::EncryptedData& encrypted =
      specifics.encrypted();
  std::string plaintext_data = GetTransaction()->GetCryptographer()->
      DecryptToString(encrypted);
  if (plaintext_data.length() == 0)
    return false;
  if (!unencrypted_data_.ParseFromString(plaintext_data)) {
    LOG(ERROR) << "Failed to decrypt encrypted node of type " <<
      syncable::ModelTypeToString(entry->GetModelType()) << ".";
    return false;
  }
  return true;
}

const sync_pb::EntitySpecifics& BaseNode::GetUnencryptedSpecifics(
    const syncable::Entry* entry) const {
  const sync_pb::EntitySpecifics& specifics = entry->Get(SPECIFICS);
  if (specifics.has_encrypted()) {
    DCHECK(syncable::GetModelTypeFromSpecifics(unencrypted_data_) !=
           syncable::UNSPECIFIED);
    return unencrypted_data_;
  } else {
    DCHECK(syncable::GetModelTypeFromSpecifics(unencrypted_data_) ==
           syncable::UNSPECIFIED);
    return specifics;
  }
}

int64 BaseNode::GetParentId() const {
  return IdToMetahandle(GetTransaction()->GetWrappedTrans(),
                        GetEntry()->Get(syncable::PARENT_ID));
}

int64 BaseNode::GetId() const {
  return GetEntry()->Get(syncable::META_HANDLE);
}

int64 BaseNode::GetModificationTime() const {
  return GetEntry()->Get(syncable::MTIME);
}

bool BaseNode::GetIsFolder() const {
  return GetEntry()->Get(syncable::IS_DIR);
}

std::wstring BaseNode::GetTitle() const {
  std::wstring result;
  ServerNameToSyncAPIName(GetEntry()->Get(syncable::NON_UNIQUE_NAME), &result);
  return result;
}

GURL BaseNode::GetURL() const {
  return GURL(GetBookmarkSpecifics().url());
}

int64 BaseNode::GetPredecessorId() const {
  syncable::Id id_string = GetEntry()->Get(syncable::PREV_ID);
  if (id_string.IsRoot())
    return kInvalidId;
  return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
}

int64 BaseNode::GetSuccessorId() const {
  syncable::Id id_string = GetEntry()->Get(syncable::NEXT_ID);
  if (id_string.IsRoot())
    return kInvalidId;
  return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
}

int64 BaseNode::GetFirstChildId() const {
  syncable::Directory* dir = GetTransaction()->GetLookup();
  syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans();
  syncable::Id id_string =
      dir->GetFirstChildId(trans, GetEntry()->Get(syncable::ID));
  if (id_string.IsRoot())
    return kInvalidId;
  return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
}

DictionaryValue* BaseNode::ToValue() const {
  DictionaryValue* node_info = new DictionaryValue();
  node_info->SetString("id", base::Int64ToString(GetId()));
  // TODO(akalin): Return time in a better format.
  node_info->SetString("modificationTime",
                       base::Int64ToString(GetModificationTime()));
  node_info->SetString("parentId", base::Int64ToString(GetParentId()));
  node_info->SetBoolean("isFolder", GetIsFolder());
  // TODO(akalin): Add a std::string accessor for the title.
  node_info->SetString("title", WideToUTF8(GetTitle()));
  node_info->Set("type", ModelTypeToValue(GetModelType()));
  // Specifics are already in the Entry value, so no need to duplicate
  // it here.
  node_info->SetString("externalId",
                       base::Int64ToString(GetExternalId()));
  node_info->SetString("predecessorId",
                       base::Int64ToString(GetPredecessorId()));
  node_info->SetString("successorId",
                       base::Int64ToString(GetSuccessorId()));
  node_info->SetString("firstChildId",
                       base::Int64ToString(GetFirstChildId()));
  node_info->Set("entry", GetEntry()->ToValue());
  return node_info;
}

void BaseNode::GetFaviconBytes(std::vector<unsigned char>* output) const {
  if (!output)
    return;
  const std::string& favicon = GetBookmarkSpecifics().favicon();
  output->assign(reinterpret_cast<const unsigned char*>(favicon.data()),
      reinterpret_cast<const unsigned char*>(favicon.data() +
                                             favicon.length()));
}

int64 BaseNode::GetExternalId() const {
  return GetEntry()->Get(syncable::LOCAL_EXTERNAL_ID);
}

const sync_pb::AppSpecifics& BaseNode::GetAppSpecifics() const {
  DCHECK_EQ(syncable::APPS, GetModelType());
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::app);
}

const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const {
  DCHECK_EQ(syncable::AUTOFILL, GetModelType());
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::autofill);
}

const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const {
  DCHECK_EQ(GetModelType(), syncable::AUTOFILL_PROFILE);
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::autofill_profile);
}

const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const {
  DCHECK_EQ(syncable::BOOKMARKS, GetModelType());
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::bookmark);
}

const sync_pb::NigoriSpecifics& BaseNode::GetNigoriSpecifics() const {
  DCHECK_EQ(syncable::NIGORI, GetModelType());
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::nigori);
}

const sync_pb::PasswordSpecificsData& BaseNode::GetPasswordSpecifics() const {
  DCHECK_EQ(syncable::PASSWORDS, GetModelType());
  DCHECK(password_data_.get());
  return *password_data_;
}

const sync_pb::PreferenceSpecifics& BaseNode::GetPreferenceSpecifics() const {
  DCHECK_EQ(syncable::PREFERENCES, GetModelType());
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::preference);
}

const sync_pb::ThemeSpecifics& BaseNode::GetThemeSpecifics() const {
  DCHECK_EQ(syncable::THEMES, GetModelType());
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::theme);
}

const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const {
  DCHECK_EQ(syncable::TYPED_URLS, GetModelType());
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::typed_url);
}

const sync_pb::ExtensionSpecifics& BaseNode::GetExtensionSpecifics() const {
  DCHECK_EQ(syncable::EXTENSIONS, GetModelType());
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::extension);
}

const sync_pb::SessionSpecifics& BaseNode::GetSessionSpecifics() const {
  DCHECK_EQ(syncable::SESSIONS, GetModelType());
  const sync_pb::EntitySpecifics& unencrypted =
      GetUnencryptedSpecifics(GetEntry());
  return unencrypted.GetExtension(sync_pb::session);
}

syncable::ModelType BaseNode::GetModelType() const {
  return GetEntry()->GetModelType();
}

////////////////////////////////////
// WriteNode member definitions
void WriteNode::EncryptIfNecessary(sync_pb::EntitySpecifics* unencrypted) {
  syncable::ModelType type = syncable::GetModelTypeFromSpecifics(*unencrypted);
  DCHECK_NE(type, syncable::UNSPECIFIED);
  DCHECK_NE(type, syncable::PASSWORDS);  // Passwords use their own encryption.
  DCHECK_NE(type, syncable::NIGORI);     // Nigori is encrypted separately.

  syncable::ModelTypeSet encrypted_types =
      GetEncryptedDataTypes(GetTransaction()->GetWrappedTrans());
  if (encrypted_types.count(type) == 0) {
    // This datatype does not require encryption.
    return;
  }

  if (unencrypted->has_encrypted()) {
    // This specifics is already encrypted, our work is done.
    LOG(WARNING) << "Attempted to encrypt an already encrypted entity"
      << " specifics of type " << syncable::ModelTypeToString(type)
      << ". Dropping.";
    return;
  }
  sync_pb::EntitySpecifics encrypted;
  syncable::AddDefaultExtensionValue(type, &encrypted);
  VLOG(2) << "Encrypted specifics of type " << syncable::ModelTypeToString(type)
          << " with content: " << unencrypted->SerializeAsString() << "\n";
  if (!GetTransaction()->GetCryptographer()->Encrypt(
      *unencrypted,
      encrypted.mutable_encrypted())) {
    LOG(ERROR) << "Could not encrypt data for node of type " <<
      syncable::ModelTypeToString(type);
    NOTREACHED();
  }
  unencrypted->CopyFrom(encrypted);
}

void WriteNode::SetIsFolder(bool folder) {
  if (entry_->Get(syncable::IS_DIR) == folder)
    return;  // Skip redundant changes.

  entry_->Put(syncable::IS_DIR, folder);
  MarkForSyncing();
}

void WriteNode::SetTitle(const std::wstring& title) {
  std::string server_legal_name;
  SyncAPINameToServerName(title, &server_legal_name);

  string old_name = entry_->Get(syncable::NON_UNIQUE_NAME);

  if (server_legal_name == old_name)
    return;  // Skip redundant changes.

  entry_->Put(syncable::NON_UNIQUE_NAME, server_legal_name);
  MarkForSyncing();
}

void WriteNode::SetURL(const GURL& url) {
  sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics();
  new_value.set_url(url.spec());
  SetBookmarkSpecifics(new_value);
}

void WriteNode::SetAppSpecifics(
    const sync_pb::AppSpecifics& new_value) {
  DCHECK_EQ(syncable::APPS, GetModelType());
  PutAppSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::SetAutofillSpecifics(
    const sync_pb::AutofillSpecifics& new_value) {
  DCHECK_EQ(syncable::AUTOFILL, GetModelType());
  PutAutofillSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::PutAutofillSpecificsAndMarkForSyncing(
    const sync_pb::AutofillSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::autofill)->CopyFrom(new_value);
  EncryptIfNecessary(&entity_specifics);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::SetAutofillProfileSpecifics(
    const sync_pb::AutofillProfileSpecifics& new_value) {
  DCHECK_EQ(GetModelType(), syncable::AUTOFILL_PROFILE);
  PutAutofillProfileSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::PutAutofillProfileSpecificsAndMarkForSyncing(
    const sync_pb::AutofillProfileSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::autofill_profile)->CopyFrom(
      new_value);
  EncryptIfNecessary(&entity_specifics);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::SetBookmarkSpecifics(
    const sync_pb::BookmarkSpecifics& new_value) {
  DCHECK_EQ(syncable::BOOKMARKS, GetModelType());
  PutBookmarkSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::PutBookmarkSpecificsAndMarkForSyncing(
    const sync_pb::BookmarkSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::bookmark)->CopyFrom(new_value);
  EncryptIfNecessary(&entity_specifics);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::SetNigoriSpecifics(
    const sync_pb::NigoriSpecifics& new_value) {
  DCHECK_EQ(syncable::NIGORI, GetModelType());
  PutNigoriSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::PutNigoriSpecificsAndMarkForSyncing(
    const sync_pb::NigoriSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::nigori)->CopyFrom(new_value);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::SetPasswordSpecifics(
    const sync_pb::PasswordSpecificsData& data) {
  DCHECK_EQ(syncable::PASSWORDS, GetModelType());

  Cryptographer* cryptographer = GetTransaction()->GetCryptographer();

  // Idempotency check to prevent unnecessary syncing: if the plaintexts match
  // and the old ciphertext is encrypted with the most current key, there's
  // nothing to do here.  Because each encryption is seeded with a different
  // random value, checking for equivalence post-encryption doesn't suffice.
  const sync_pb::EncryptedData& old_ciphertext =
      GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::password).encrypted();
  scoped_ptr<sync_pb::PasswordSpecificsData> old_plaintext(
      DecryptPasswordSpecifics(GetEntry()->Get(SPECIFICS), cryptographer));
  if (old_plaintext.get() &&
      old_plaintext->SerializeAsString() == data.SerializeAsString() &&
      cryptographer->CanDecryptUsingDefaultKey(old_ciphertext)) {
    return;
  }

  sync_pb::PasswordSpecifics new_value;
  if (!cryptographer->Encrypt(data, new_value.mutable_encrypted())) {
    NOTREACHED();
  }
  PutPasswordSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::SetPreferenceSpecifics(
    const sync_pb::PreferenceSpecifics& new_value) {
  DCHECK_EQ(syncable::PREFERENCES, GetModelType());
  PutPreferenceSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::SetThemeSpecifics(
    const sync_pb::ThemeSpecifics& new_value) {
  DCHECK_EQ(syncable::THEMES, GetModelType());
  PutThemeSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::SetSessionSpecifics(
    const sync_pb::SessionSpecifics& new_value) {
  DCHECK_EQ(syncable::SESSIONS, GetModelType());
  PutSessionSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::ResetFromSpecifics() {
  sync_pb::EntitySpecifics new_data;
  new_data.CopyFrom(GetUnencryptedSpecifics(GetEntry()));
  EncryptIfNecessary(&new_data);
  PutSpecificsAndMarkForSyncing(new_data);
}

void WriteNode::PutPasswordSpecificsAndMarkForSyncing(
    const sync_pb::PasswordSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::password)->CopyFrom(new_value);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::PutPreferenceSpecificsAndMarkForSyncing(
    const sync_pb::PreferenceSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::preference)->CopyFrom(new_value);
  EncryptIfNecessary(&entity_specifics);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::SetTypedUrlSpecifics(
    const sync_pb::TypedUrlSpecifics& new_value) {
  DCHECK_EQ(syncable::TYPED_URLS, GetModelType());
  PutTypedUrlSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::SetExtensionSpecifics(
    const sync_pb::ExtensionSpecifics& new_value) {
  DCHECK_EQ(syncable::EXTENSIONS, GetModelType());
  PutExtensionSpecificsAndMarkForSyncing(new_value);
}

void WriteNode::PutAppSpecificsAndMarkForSyncing(
    const sync_pb::AppSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::app)->CopyFrom(new_value);
  EncryptIfNecessary(&entity_specifics);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::PutThemeSpecificsAndMarkForSyncing(
    const sync_pb::ThemeSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::theme)->CopyFrom(new_value);
  EncryptIfNecessary(&entity_specifics);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::PutTypedUrlSpecificsAndMarkForSyncing(
    const sync_pb::TypedUrlSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::typed_url)->CopyFrom(new_value);
  EncryptIfNecessary(&entity_specifics);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::PutExtensionSpecificsAndMarkForSyncing(
    const sync_pb::ExtensionSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::extension)->CopyFrom(new_value);
  EncryptIfNecessary(&entity_specifics);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::PutSessionSpecificsAndMarkForSyncing(
    const sync_pb::SessionSpecifics& new_value) {
  sync_pb::EntitySpecifics entity_specifics;
  entity_specifics.MutableExtension(sync_pb::session)->CopyFrom(new_value);
  EncryptIfNecessary(&entity_specifics);
  PutSpecificsAndMarkForSyncing(entity_specifics);
}

void WriteNode::PutSpecificsAndMarkForSyncing(
    const sync_pb::EntitySpecifics& specifics) {
  // Skip redundant changes.
  if (specifics.SerializeAsString() ==
      entry_->Get(SPECIFICS).SerializeAsString()) {
    return;
  }
  entry_->Put(SPECIFICS, specifics);
  MarkForSyncing();
}

void WriteNode::SetExternalId(int64 id) {
  if (GetExternalId() != id)
    entry_->Put(syncable::LOCAL_EXTERNAL_ID, id);
}

WriteNode::WriteNode(WriteTransaction* transaction)
    : entry_(NULL), transaction_(transaction) {
  DCHECK(transaction);
}

WriteNode::~WriteNode() {
  delete entry_;
}

// Find an existing node matching the ID |id|, and bind this WriteNode to it.
// Return true on success.
bool WriteNode::InitByIdLookup(int64 id) {
  DCHECK(!entry_) << "Init called twice";
  DCHECK_NE(id, kInvalidId);
  entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
                                      syncable::GET_BY_HANDLE, id);
  return (entry_->good() && !entry_->Get(syncable::IS_DEL) &&
          DecryptIfNecessary(entry_));
}

// Find a node by client tag, and bind this WriteNode to it.
// Return true if the write node was found, and was not deleted.
// Undeleting a deleted node is possible by ClientTag.
bool WriteNode::InitByClientTagLookup(syncable::ModelType model_type,
                                      const std::string& tag) {
  DCHECK(!entry_) << "Init called twice";
  if (tag.empty())
    return false;

  const std::string hash = GenerateSyncableHash(model_type, tag);

  entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
                                      syncable::GET_BY_CLIENT_TAG, hash);
  return (entry_->good() && !entry_->Get(syncable::IS_DEL) &&
          DecryptIfNecessary(entry_));
}

bool WriteNode::InitByTagLookup(const std::string& tag) {
  DCHECK(!entry_) << "Init called twice";
  if (tag.empty())
    return false;
  entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
                                      syncable::GET_BY_SERVER_TAG, tag);
  if (!entry_->good())
    return false;
  if (entry_->Get(syncable::IS_DEL))
    return false;
  syncable::ModelType model_type = GetModelType();
  DCHECK_EQ(syncable::NIGORI, model_type);
  return true;
}

void WriteNode::PutModelType(syncable::ModelType model_type) {
  // Set an empty specifics of the appropriate datatype.  The presence
  // of the specific extension will identify the model type.
  DCHECK(GetModelType() == model_type ||
         GetModelType() == syncable::UNSPECIFIED);  // Immutable once set.

  sync_pb::EntitySpecifics specifics;
  syncable::AddDefaultExtensionValue(model_type, &specifics);
  PutSpecificsAndMarkForSyncing(specifics);
  DCHECK_EQ(model_type, GetModelType());
}

// Create a new node with default properties, and bind this WriteNode to it.
// Return true on success.
bool WriteNode::InitByCreation(syncable::ModelType model_type,
                               const BaseNode& parent,
                               const BaseNode* predecessor) {
  DCHECK(!entry_) << "Init called twice";
  // |predecessor| must be a child of |parent| or NULL.
  if (predecessor && predecessor->GetParentId() != parent.GetId()) {
    DCHECK(false);
    return false;
  }

  syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID);

  // Start out with a dummy name.  We expect
  // the caller to set a meaningful name after creation.
  string dummy(kDefaultNameForNewNodes);

  entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
                                      syncable::CREATE, parent_id, dummy);

  if (!entry_->good())
    return false;

  // Entries are untitled folders by default.
  entry_->Put(syncable::IS_DIR, true);

  PutModelType(model_type);

  // Now set the predecessor, which sets IS_UNSYNCED as necessary.
  PutPredecessor(predecessor);

  return true;
}

// Create a new node with default properties and a client defined unique tag,
// and bind this WriteNode to it.
// Return true on success. If the tag exists in the database, then
// we will attempt to undelete the node.
// TODO(chron): Code datatype into hash tag.
// TODO(chron): Is model type ever lost?
bool WriteNode::InitUniqueByCreation(syncable::ModelType model_type,
                                     const BaseNode& parent,
                                     const std::string& tag) {
  DCHECK(!entry_) << "Init called twice";

  const std::string hash = GenerateSyncableHash(model_type, tag);

  syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID);

  // Start out with a dummy name.  We expect
  // the caller to set a meaningful name after creation.
  string dummy(kDefaultNameForNewNodes);

  // Check if we have this locally and need to undelete it.
  scoped_ptr<syncable::MutableEntry> existing_entry(
      new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
                                 syncable::GET_BY_CLIENT_TAG, hash));

  if (existing_entry->good()) {
    if (existing_entry->Get(syncable::IS_DEL)) {
      // Rules for undelete:
      // BASE_VERSION: Must keep the same.
      // ID: Essential to keep the same.
      // META_HANDLE: Must be the same, so we can't "split" the entry.
      // IS_DEL: Must be set to false, will cause reindexing.
      //         This one is weird because IS_DEL is true for "update only"
      //         items. It should be OK to undelete an update only.
      // MTIME/CTIME: Seems reasonable to just leave them alone.
      // IS_UNSYNCED: Must set this to true or face database insurrection.
      //              We do this below this block.
      // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION
      //                      to SERVER_VERSION. We keep it the same here.
      // IS_DIR: We'll leave it the same.
      // SPECIFICS: Reset it.

      existing_entry->Put(syncable::IS_DEL, false);

      // Client tags are immutable and must be paired with the ID.
      // If a server update comes down with an ID and client tag combo,
      // and it already exists, always overwrite it and store only one copy.
      // We have to undelete entries because we can't disassociate IDs from
      // tags and updates.

      existing_entry->Put(syncable::NON_UNIQUE_NAME, dummy);
      existing_entry->Put(syncable::PARENT_ID, parent_id);
      entry_ = existing_entry.release();
    } else {
      return false;
    }
  } else {
    entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
                                        syncable::CREATE, parent_id, dummy);
    if (!entry_->good()) {
      return false;
    }

    // Only set IS_DIR for new entries. Don't bitflip undeleted ones.
    entry_->Put(syncable::UNIQUE_CLIENT_TAG, hash);
  }

  // We don't support directory and tag combinations.
  entry_->Put(syncable::IS_DIR, false);

  // Will clear specifics data.
  PutModelType(model_type);

  // Now set the predecessor, which sets IS_UNSYNCED as necessary.
  PutPredecessor(NULL);

  return true;
}

bool WriteNode::SetPosition(const BaseNode& new_parent,
                            const BaseNode* predecessor) {
  // |predecessor| must be a child of |new_parent| or NULL.
  if (predecessor && predecessor->GetParentId() != new_parent.GetId()) {
    DCHECK(false);
    return false;
  }

  syncable::Id new_parent_id = new_parent.GetEntry()->Get(syncable::ID);

  // Filter out redundant changes if both the parent and the predecessor match.
  if (new_parent_id == entry_->Get(syncable::PARENT_ID)) {
    const syncable::Id& old = entry_->Get(syncable::PREV_ID);
    if ((!predecessor && old.IsRoot()) ||
        (predecessor && (old == predecessor->GetEntry()->Get(syncable::ID)))) {
      return true;
    }
  }

  // Atomically change the parent. This will fail if it would
  // introduce a cycle in the hierarchy.
  if (!entry_->Put(syncable::PARENT_ID, new_parent_id))
    return false;

  // Now set the predecessor, which sets IS_UNSYNCED as necessary.
  PutPredecessor(predecessor);

  return true;
}

const syncable::Entry* WriteNode::GetEntry() const {
  return entry_;
}

const BaseTransaction* WriteNode::GetTransaction() const {
  return transaction_;
}

void WriteNode::Remove() {
  entry_->Put(syncable::IS_DEL, true);
  MarkForSyncing();
}

void WriteNode::PutPredecessor(const BaseNode* predecessor) {
  syncable::Id predecessor_id = predecessor ?
      predecessor->GetEntry()->Get(syncable::ID) : syncable::Id();
  entry_->PutPredecessor(predecessor_id);
  // Mark this entry as unsynced, to wake up the syncer.
  MarkForSyncing();
}

void WriteNode::SetFaviconBytes(const vector<unsigned char>& bytes) {
  sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics();
  new_value.set_favicon(bytes.empty() ? NULL : &bytes[0], bytes.size());
  SetBookmarkSpecifics(new_value);
}

void WriteNode::MarkForSyncing() {
  syncable::MarkForSyncing(entry_);
}

//////////////////////////////////////////////////////////////////////////
// ReadNode member definitions
ReadNode::ReadNode(const BaseTransaction* transaction)
    : entry_(NULL), transaction_(transaction) {
  DCHECK(transaction);
}

ReadNode::ReadNode() {
  entry_ = NULL;
  transaction_ = NULL;
}

ReadNode::~ReadNode() {
  delete entry_;
}

void ReadNode::InitByRootLookup() {
  DCHECK(!entry_) << "Init called twice";
  syncable::BaseTransaction* trans = transaction_->GetWrappedTrans();
  entry_ = new syncable::Entry(trans, syncable::GET_BY_ID, trans->root_id());
  if (!entry_->good())
    DCHECK(false) << "Could not lookup root node for reading.";
}

bool ReadNode::InitByIdLookup(int64 id) {
  DCHECK(!entry_) << "Init called twice";
  DCHECK_NE(id, kInvalidId);
  syncable::BaseTransaction* trans = transaction_->GetWrappedTrans();
  entry_ = new syncable::Entry(trans, syncable::GET_BY_HANDLE, id);
  if (!entry_->good())
    return false;
  if (entry_->Get(syncable::IS_DEL))
    return false;
  syncable::ModelType model_type = GetModelType();
  LOG_IF(WARNING, model_type == syncable::UNSPECIFIED ||
                  model_type == syncable::TOP_LEVEL_FOLDER)
      << "SyncAPI InitByIdLookup referencing unusual object.";
  return DecryptIfNecessary(entry_);
}

bool ReadNode::InitByClientTagLookup(syncable::ModelType model_type,
                                     const std::string& tag) {
  DCHECK(!entry_) << "Init called twice";
  if (tag.empty())
    return false;

  const std::string hash = GenerateSyncableHash(model_type, tag);

  entry_ = new syncable::Entry(transaction_->GetWrappedTrans(),
                               syncable::GET_BY_CLIENT_TAG, hash);
  return (entry_->good() && !entry_->Get(syncable::IS_DEL) &&
          DecryptIfNecessary(entry_));
}

const syncable::Entry* ReadNode::GetEntry() const {
  return entry_;
}

const BaseTransaction* ReadNode::GetTransaction() const {
  return transaction_;
}

bool ReadNode::InitByTagLookup(const std::string& tag) {
  DCHECK(!entry_) << "Init called twice";
  if (tag.empty())
    return false;
  syncable::BaseTransaction* trans = transaction_->GetWrappedTrans();
  entry_ = new syncable::Entry(trans, syncable::GET_BY_SERVER_TAG, tag);
  if (!entry_->good())
    return false;
  if (entry_->Get(syncable::IS_DEL))
    return false;
  syncable::ModelType model_type = GetModelType();
  LOG_IF(WARNING, model_type == syncable::UNSPECIFIED ||
                  model_type == syncable::TOP_LEVEL_FOLDER)
      << "SyncAPI InitByTagLookup referencing unusually typed object.";
  return DecryptIfNecessary(entry_);
}

//////////////////////////////////////////////////////////////////////////
// ReadTransaction member definitions
ReadTransaction::ReadTransaction(UserShare* share)
    : BaseTransaction(share),
      transaction_(NULL),
      close_transaction_(true) {
  transaction_ = new syncable::ReadTransaction(GetLookup(), __FILE__, __LINE__);
}

ReadTransaction::ReadTransaction(UserShare* share,
                                 syncable::BaseTransaction* trans)
    : BaseTransaction(share),
      transaction_(trans),
      close_transaction_(false) {}

ReadTransaction::~ReadTransaction() {
  if (close_transaction_) {
    delete transaction_;
  }
}

syncable::BaseTransaction* ReadTransaction::GetWrappedTrans() const {
  return transaction_;
}

//////////////////////////////////////////////////////////////////////////
// WriteTransaction member definitions
WriteTransaction::WriteTransaction(UserShare* share)
    : BaseTransaction(share),
      transaction_(NULL) {
  transaction_ = new syncable::WriteTransaction(GetLookup(), syncable::SYNCAPI,
                                                __FILE__, __LINE__);
}

WriteTransaction::~WriteTransaction() {
  delete transaction_;
}

syncable::BaseTransaction* WriteTransaction::GetWrappedTrans() const {
  return transaction_;
}

SyncManager::ChangeRecord::ChangeRecord()
    : id(kInvalidId), action(ACTION_ADD) {}

SyncManager::ChangeRecord::~ChangeRecord() {}

DictionaryValue* SyncManager::ChangeRecord::ToValue(
    const BaseTransaction* trans) const {
  DictionaryValue* value = new DictionaryValue();
  std::string action_str;
  switch (action) {
    case ACTION_ADD:
      action_str = "Add";
      break;
    case ACTION_DELETE:
      action_str = "Delete";
      break;
    case ACTION_UPDATE:
      action_str = "Update";
      break;
    default:
      NOTREACHED();
      action_str = "Unknown";
      break;
  }
  value->SetString("action", action_str);
  Value* node_value = NULL;
  if (action == ACTION_DELETE) {
    DictionaryValue* node_dict = new DictionaryValue();
    node_dict->SetString("id", base::Int64ToString(id));
    node_dict->Set("specifics",
                    browser_sync::EntitySpecificsToValue(specifics));
    if (extra.get()) {
      node_dict->Set("extra", extra->ToValue());
    }
    node_value = node_dict;
  } else {
    ReadNode node(trans);
    if (node.InitByIdLookup(id)) {
      node_value = node.ToValue();
    }
  }
  if (!node_value) {
    NOTREACHED();
    node_value = Value::CreateNullValue();
  }
  value->Set("node", node_value);
  return value;
}

bool BaseNode::ContainsString(const std::string& lowercase_query) const {
  DCHECK(GetEntry());
  // TODO(lipalani) - figure out what to do if the node is encrypted.
  const sync_pb::EntitySpecifics& specifics = GetEntry()->Get(SPECIFICS);
  std::string temp;
  // The protobuf serialized string contains the original strings. So
  // we will just serialize it and search it.
  specifics.SerializeToString(&temp);

  // Now convert to lower case.
  StringToLowerASCII(&temp);

  return temp.find(lowercase_query) != std::string::npos;
}

SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData() {}

SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData(
    const sync_pb::PasswordSpecificsData& data)
    : unencrypted_(data) {
}

SyncManager::ExtraPasswordChangeRecordData::~ExtraPasswordChangeRecordData() {}

DictionaryValue* SyncManager::ExtraPasswordChangeRecordData::ToValue() const {
  return browser_sync::PasswordSpecificsDataToValue(unencrypted_);
}

const sync_pb::PasswordSpecificsData&
    SyncManager::ExtraPasswordChangeRecordData::unencrypted() const {
  return unencrypted_;
}

namespace {

struct NotificationInfo {
  int total_count;
  std::string payload;

  NotificationInfo() : total_count(0) {}

  ~NotificationInfo() {}

  // Returned pointer owned by the caller.
  DictionaryValue* ToValue() const {
    DictionaryValue* value = new DictionaryValue();
    value->SetInteger("totalCount", total_count);
    value->SetString("payload", payload);
    return value;
  }
};

typedef std::map<syncable::ModelType, NotificationInfo> NotificationInfoMap;

// returned pointer is owned by the caller.
DictionaryValue* NotificationInfoToValue(
    const NotificationInfoMap& notification_info) {
  DictionaryValue* value = new DictionaryValue();

  for (NotificationInfoMap::const_iterator it = notification_info.begin();
      it != notification_info.end(); ++it) {
    const std::string& model_type_str =
        syncable::ModelTypeToString(it->first);
    value->Set(model_type_str, it->second.ToValue());
  }

  return value;
}

}  // namespace

//////////////////////////////////////////////////////////////////////////
// SyncManager's implementation: SyncManager::SyncInternal
class SyncManager::SyncInternal
    : public net::NetworkChangeNotifier::IPAddressObserver,
      public sync_notifier::SyncNotifierObserver,
      public browser_sync::JsBackend,
      public SyncEngineEventListener,
      public ServerConnectionEventListener,
      public syncable::DirectoryChangeListener {
  static const int kDefaultNudgeDelayMilliseconds;
  static const int kPreferencesNudgeDelayMilliseconds;
 public:
  explicit SyncInternal(SyncManager* sync_manager)
      : core_message_loop_(NULL),
        parent_router_(NULL),
        sync_manager_(sync_manager),
        registrar_(NULL),
        initialized_(false),
        ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  }

  virtual ~SyncInternal() {
    CHECK(!core_message_loop_);
    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  }

  bool Init(const FilePath& database_location,
            const std::string& sync_server_and_path,
            int port,
            bool use_ssl,
            HttpPostProviderFactory* post_factory,
            ModelSafeWorkerRegistrar* model_safe_worker_registrar,
            const char* user_agent,
            const SyncCredentials& credentials,
            sync_notifier::SyncNotifier* sync_notifier,
            const std::string& restored_key_for_bootstrapping,
            bool setup_for_test_mode);

  // Sign into sync with given credentials.
  // We do not verify the tokens given. After this call, the tokens are set
  // and the sync DB is open. True if successful, false if something
  // went wrong.
  bool SignIn(const SyncCredentials& credentials);

  // Update tokens that we're using in Sync. Email must stay the same.
  void UpdateCredentials(const SyncCredentials& credentials);

  // Called when the user disables or enables a sync type.
  void UpdateEnabledTypes();

  // Tell the sync engine to start the syncing process.
  void StartSyncing();

  // Whether or not the Nigori node is encrypted using an explicit passphrase.
  bool IsUsingExplicitPassphrase();

  // Set the datatypes we want to encrypt and encrypt any nodes as necessary.
  void EncryptDataTypes(const syncable::ModelTypeSet& encrypted_types);

  // Try to set the current passphrase to |passphrase|, and record whether
  // it is an explicit passphrase or implicitly using gaia in the Nigori
  // node.
  void SetPassphrase(const std::string& passphrase, bool is_explicit);

  // Call periodically from a database-safe thread to persist recent changes
  // to the syncapi model.
  void SaveChanges();

  // DirectoryChangeListener implementation.
  // This listener is called upon completion of a syncable transaction, and
  // builds the list of sync-engine initiated changes that will be forwarded to
  // the SyncManager's Observers.
  virtual void HandleTransactionCompleteChangeEvent(
      const ModelTypeBitSet& models_with_changes);
  virtual ModelTypeBitSet HandleTransactionEndingChangeEvent(
      syncable::BaseTransaction* trans);
  virtual void HandleCalculateChangesChangeEventFromSyncApi(
      const OriginalEntries& originals,
      const WriterTag& writer,
      syncable::BaseTransaction* trans);
  virtual void HandleCalculateChangesChangeEventFromSyncer(
      const OriginalEntries& originals,
      const WriterTag& writer,
      syncable::BaseTransaction* trans);

  // Listens for notifications from the ServerConnectionManager
  void HandleServerConnectionEvent(const ServerConnectionEvent& event);

  // Open the directory named with username_for_share
  bool OpenDirectory();

  // SyncNotifierObserver implementation.
  virtual void OnNotificationStateChange(
      bool notifications_enabled);

  virtual void OnIncomingNotification(
      const syncable::ModelTypePayloadMap& type_payloads);

  virtual void StoreState(const std::string& cookie);

  void AddObserver(SyncManager::Observer* observer);

  void RemoveObserver(SyncManager::Observer* observer);

  // Accessors for the private members.
  DirectoryManager* dir_manager() { return share_.dir_manager.get(); }
  SyncAPIServerConnectionManager* connection_manager() {
    return connection_manager_.get();
  }
  SyncerThread* syncer_thread() { return syncer_thread_.get(); }
  UserShare* GetUserShare() { return &share_; }

  // Return the currently active (validated) username for use with syncable
  // types.
  const std::string& username_for_share() const {
    return share_.name;
  }

  Status GetStatus();

  void RequestNudge(const tracked_objects::Location& nudge_location);

  void RequestNudgeWithDataTypes(const TimeDelta& delay,
      browser_sync::NudgeSource source, const ModelTypeBitSet& types,
      const tracked_objects::Location& nudge_location);

  // See SyncManager::Shutdown for information.
  void Shutdown();

  // Whether we're initialized to the point of being able to accept changes
  // (and hence allow transaction creation). See initialized_ for details.
  bool initialized() const {
    base::AutoLock lock(initialized_mutex_);
    return initialized_;
  }

  // If this is a deletion for a password, sets the legacy
  // ExtraPasswordChangeRecordData field of |buffer|. Otherwise sets
  // |buffer|'s specifics field to contain the unencrypted data.
  void SetExtraChangeRecordData(int64 id,
                                syncable::ModelType type,
                                ChangeReorderBuffer* buffer,
                                Cryptographer* cryptographer,
                                const syncable::EntryKernel& original,
                                bool existed_before,
                                bool exists_now);

  // Called only by our NetworkChangeNotifier.
  virtual void OnIPAddressChanged();

  bool InitialSyncEndedForAllEnabledTypes() {
    syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
    if (!lookup.good()) {
      DCHECK(false) << "ScopedDirLookup failed when checking initial sync";
      return false;
    }

    ModelSafeRoutingInfo enabled_types;
    registrar_->GetModelSafeRoutingInfo(&enabled_types);
    for (ModelSafeRoutingInfo::const_iterator i = enabled_types.begin();
        i != enabled_types.end(); ++i) {
      if (!lookup->initial_sync_ended_for_type(i->first))
        return false;
    }
    return true;
  }

  syncable::AutofillMigrationState GetAutofillMigrationState() {
    syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
    if (!lookup.good()) {
      DCHECK(false) << "ScopedDirLookup failed when checking initial sync";
      return syncable::NOT_MIGRATED;
    }

    return lookup->get_autofill_migration_state();
  }

  void SetAutofillMigrationState(syncable::AutofillMigrationState state) {
    syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
    if (!lookup.good()) {
      DCHECK(false) << "ScopedDirLookup failed when checking initial sync";
      return;
    }

    return lookup->set_autofill_migration_state(state);
  }

  void SetAutofillMigrationDebugInfo(
      syncable::AutofillMigrationDebugInfo::PropertyToSet property_to_set,
      const syncable::AutofillMigrationDebugInfo& info) {
    syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
    if (!lookup.good()) {
      DCHECK(false) << "ScopedDirLookup failed when checking initial sync";
      return;
    }

    return lookup->set_autofill_migration_state_debug_info(
        property_to_set, info);
  }

  syncable::AutofillMigrationDebugInfo
      GetAutofillMigrationDebugInfo() {
    syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
    if (!lookup.good()) {
      DCHECK(false) << "ScopedDirLookup failed when checking initial sync";
      syncable::AutofillMigrationDebugInfo null_value = {0};
      return null_value;
    }
    return lookup->get_autofill_migration_debug_info();
  }

  // SyncEngineEventListener implementation.
  virtual void OnSyncEngineEvent(const SyncEngineEvent& event);

  // ServerConnectionEventListener implementation.
  virtual void OnServerConnectionEvent(const ServerConnectionEvent2& event);

  // browser_sync::JsBackend implementation.
  virtual void SetParentJsEventRouter(browser_sync::JsEventRouter* router);
  virtual void RemoveParentJsEventRouter();
  virtual const browser_sync::JsEventRouter* GetParentJsEventRouter() const;
  virtual void ProcessMessage(const std::string& name,
                              const browser_sync::JsArgList& args,
                              const browser_sync::JsEventHandler* sender);

  ListValue* FindNodesContainingString(const std::string& query);

 private:
  // Helper to call OnAuthError when no authentication credentials are
  // available.
  void RaiseAuthNeededEvent();

  // Helper to set initialized_ to true and raise an event to clients to notify
  // that initialization is complete and it is safe to send us changes. If
  // already initialized, this is a no-op.
  void MarkAndNotifyInitializationComplete();

  // Sends notifications to peers.
  void SendNotification();

  // Determine if the parents or predecessors differ between the old and new
  // versions of an entry stored in |a| and |b|.  Note that a node's index may
  // change without its NEXT_ID changing if the node at NEXT_ID also moved (but
  // the relative order is unchanged).  To handle such cases, we rely on the
  // caller to treat a position update on any sibling as updating the positions
  // of all siblings.
  static bool VisiblePositionsDiffer(const syncable::EntryKernel& a,
                                     const syncable::Entry& b) {
    // If the datatype isn't one where the browser model cares about position,
    // don't bother notifying that data model of position-only changes.
    if (!b.ShouldMaintainPosition())
      return false;
    if (a.ref(syncable::NEXT_ID) != b.Get(syncable::NEXT_ID))
      return true;
    if (a.ref(syncable::PARENT_ID) != b.Get(syncable::PARENT_ID))
      return true;
    return false;
  }

  // Determine if any of the fields made visible to clients of the Sync API
  // differ between the versions of an entry stored in |a| and |b|. A return
  // value of false means that it should be OK to ignore this change.
  static bool VisiblePropertiesDiffer(const syncable::EntryKernel& a,
                                      const syncable::Entry& b,
                                      Cryptographer* cryptographer) {
    syncable::ModelType model_type = b.GetModelType();
    // Suppress updates to items that aren't tracked by any browser model.
    if (model_type == syncable::UNSPECIFIED ||
        model_type == syncable::TOP_LEVEL_FOLDER) {
      return false;
    }
    if (a.ref(syncable::NON_UNIQUE_NAME) != b.Get(syncable::NON_UNIQUE_NAME))
      return true;
    if (a.ref(syncable::IS_DIR) != b.Get(syncable::IS_DIR))
      return true;
    // Check if data has changed (account for encryption).
    std::string a_str, b_str;
    if (a.ref(SPECIFICS).has_encrypted()) {
      const sync_pb::EncryptedData& encrypted = a.ref(SPECIFICS).encrypted();
      a_str = cryptographer->DecryptToString(encrypted);
    } else {
      a_str = a.ref(SPECIFICS).SerializeAsString();
    }
    if (b.Get(SPECIFICS).has_encrypted()) {
      const sync_pb::EncryptedData& encrypted = b.Get(SPECIFICS).encrypted();
      b_str = cryptographer->DecryptToString(encrypted);
    } else {
      b_str = b.Get(SPECIFICS).SerializeAsString();
    }
    if (a_str != b_str) {
      return true;
    }
    if (VisiblePositionsDiffer(a, b))
      return true;
    return false;
  }

  bool ChangeBuffersAreEmpty() {
    for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) {
      if (!change_buffers_[i].IsEmpty())
        return false;
    }
    return true;
  }

  void CheckServerReachable() {
    if (connection_manager()) {
      connection_manager()->CheckServerReachable();
    } else {
      NOTREACHED() << "Should be valid connection manager!";
    }
  }

  void ReEncryptEverything(WriteTransaction* trans);

  // Initializes (bootstraps) the Cryptographer if NIGORI has finished
  // initial sync so that it can immediately start encrypting / decrypting.
  // If the restored key is incompatible with the current version of the NIGORI
  // node (which could happen if a restart occurred just after an update to
  // NIGORI was downloaded and the user must enter a new passphrase to decrypt)
  // then we will raise OnPassphraseRequired and set pending keys for
  // decryption.  Otherwise, the cryptographer is made ready (is_ready()).
  void BootstrapEncryption(const std::string& restored_key_for_bootstrapping);

  // Called for every notification. This updates the notification statistics
  // to be displayed in about:sync.
  void UpdateNotificationInfo(
      const syncable::ModelTypePayloadMap& type_payloads);

  // Helper for migration to new nigori proto to set
  // 'using_explicit_passphrase' in the NigoriSpecifics.
  // TODO(tim): Bug 62103.  Remove this after it has been pushed out to dev
  // channel users.
  void SetUsingExplicitPassphrasePrefForMigration(
      WriteTransaction* const trans);

  // Checks for server reachabilty and requests a nudge.
  void OnIPAddressChangedImpl();

  // Functions called by ProcessMessage().
  browser_sync::JsArgList ProcessGetNodeByIdMessage(
      const browser_sync::JsArgList& args);

  browser_sync::JsArgList ProcessFindNodesContainingString(
      const browser_sync::JsArgList& args);

  // We couple the DirectoryManager and username together in a UserShare member
  // so we can return a handle to share_ to clients of the API for use when
  // constructing any transaction type.
  UserShare share_;

  MessageLoop* core_message_loop_;

  ObserverList<SyncManager::Observer> observers_;

  browser_sync::JsEventRouter* parent_router_;

  // The ServerConnectionManager used to abstract communication between the
  // client (the Syncer) and the sync server.
  scoped_ptr<SyncAPIServerConnectionManager> connection_manager_;

  // The thread that runs the Syncer. Needs to be explicitly Start()ed.
  scoped_ptr<SyncerThread> syncer_thread_;

  // The SyncNotifier which notifies us when updates need to be downloaded.
  sync_notifier::SyncNotifier* sync_notifier_;

  // A multi-purpose status watch object that aggregates stats from various
  // sync components.
  AllStatus allstatus_;

  // Each element of this array is a store of change records produced by
  // HandleChangeEvent during the CALCULATE_CHANGES step.  The changes are
  // segregated by model type, and are stored here to be processed and
  // forwarded to the observer slightly later, at the TRANSACTION_ENDING
  // step by HandleTransactionEndingChangeEvent. The list is cleared in the
  // TRANSACTION_COMPLETE step by HandleTransactionCompleteChangeEvent.
  ChangeReorderBuffer change_buffers_[syncable::MODEL_TYPE_COUNT];

  // Event listener hookup for the ServerConnectionManager.
  scoped_ptr<EventListenerHookup> connection_manager_hookup_;

  // The sync dir_manager to which we belong.
  SyncManager* const sync_manager_;

  // The entity that provides us with information about which types to sync.
  // The instance is shared between the SyncManager and the Syncer.
  ModelSafeWorkerRegistrar* registrar_;

  // Set to true once Init has been called, and we know of an authenticated
  // valid) username either from a fresh authentication attempt (as in
  // first-use case) or from a previous attempt stored in our UserSettings
  // (as in the steady-state), and the syncable::Directory has been opened,
  // meaning we are ready to accept changes.  Protected by initialized_mutex_
  // as it can get read/set by both the SyncerThread and the AuthWatcherThread.
  bool initialized_;
  mutable base::Lock initialized_mutex_;

  // True if the SyncManager should be running in test mode (no syncer thread
  // actually communicating with the server).
  bool setup_for_test_mode_;

  ScopedRunnableMethodFactory<SyncManager::SyncInternal> method_factory_;

  // Map used to store the notification info to be displayed in about:sync page.
  // TODO(lipalani) - prefill the map with enabled data types.
  NotificationInfoMap notification_info_map_;
};
const int SyncManager::SyncInternal::kDefaultNudgeDelayMilliseconds = 200;
const int SyncManager::SyncInternal::kPreferencesNudgeDelayMilliseconds = 2000;

SyncManager::Observer::~Observer() {}

SyncManager::SyncManager() {
  data_ = new SyncInternal(this);
}

bool SyncManager::Init(const FilePath& database_location,
                       const char* sync_server_and_path,
                       int sync_server_port,
                       bool use_ssl,
                       HttpPostProviderFactory* post_factory,
                       ModelSafeWorkerRegistrar* registrar,
                       const char* user_agent,
                       const SyncCredentials& credentials,
                       sync_notifier::SyncNotifier* sync_notifier,
                       const std::string& restored_key_for_bootstrapping,
                       bool setup_for_test_mode) {
  DCHECK(post_factory);
  VLOG(1) << "SyncManager starting Init...";
  string server_string(sync_server_and_path);
  return data_->Init(database_location,
                     server_string,
                     sync_server_port,
                     use_ssl,
                     post_factory,
                     registrar,
                     user_agent,
                     credentials,
                     sync_notifier,
                     restored_key_for_bootstrapping,
                     setup_for_test_mode);
}

void SyncManager::UpdateCredentials(const SyncCredentials& credentials) {
  data_->UpdateCredentials(credentials);
}

void SyncManager::UpdateEnabledTypes() {
  data_->UpdateEnabledTypes();
}


bool SyncManager::InitialSyncEndedForAllEnabledTypes() {
  return data_->InitialSyncEndedForAllEnabledTypes();
}

void SyncManager::StartSyncing() {
  data_->StartSyncing();
}

syncable::AutofillMigrationState
    SyncManager::GetAutofillMigrationState() {
  return data_->GetAutofillMigrationState();
}

void SyncManager::SetAutofillMigrationState(
    syncable::AutofillMigrationState state) {
  return data_->SetAutofillMigrationState(state);
}

syncable::AutofillMigrationDebugInfo
    SyncManager::GetAutofillMigrationDebugInfo() {
  return data_->GetAutofillMigrationDebugInfo();
}

void SyncManager::SetAutofillMigrationDebugInfo(
    syncable::AutofillMigrationDebugInfo::PropertyToSet property_to_set,
    const syncable::AutofillMigrationDebugInfo& info) {
  return data_->SetAutofillMigrationDebugInfo(property_to_set, info);
}

void SyncManager::SetPassphrase(const std::string& passphrase,
     bool is_explicit) {
  data_->SetPassphrase(passphrase, is_explicit);
}

void SyncManager::EncryptDataTypes(
    const syncable::ModelTypeSet& encrypted_types) {
  data_->EncryptDataTypes(encrypted_types);
}

bool SyncManager::IsUsingExplicitPassphrase() {
  return data_ && data_->IsUsingExplicitPassphrase();
}

void SyncManager::RequestNudge(const tracked_objects::Location& location) {
  data_->RequestNudge(location);
}

void SyncManager::RequestClearServerData() {
  if (data_->syncer_thread())
    data_->syncer_thread()->ScheduleClearUserData();
}

void SyncManager::RequestConfig(const syncable::ModelTypeBitSet& types) {
  if (!data_->syncer_thread())
    return;
  StartConfigurationMode(NULL);
  data_->syncer_thread()->ScheduleConfig(types);
}

void SyncManager::StartConfigurationMode(ModeChangeCallback* callback) {
  if (!data_->syncer_thread())
    return;
  data_->syncer_thread()->Start(
      browser_sync::SyncerThread::CONFIGURATION_MODE, callback);
}

const std::string& SyncManager::GetAuthenticatedUsername() {
  DCHECK(data_);
  return data_->username_for_share();
}

bool SyncManager::SyncInternal::Init(
    const FilePath& database_location,
    const std::string& sync_server_and_path,
    int port,
    bool use_ssl,
    HttpPostProviderFactory* post_factory,
    ModelSafeWorkerRegistrar* model_safe_worker_registrar,
    const char* user_agent,
    const SyncCredentials& credentials,
    sync_notifier::SyncNotifier* sync_notifier,
    const std::string& restored_key_for_bootstrapping,
    bool setup_for_test_mode) {

  VLOG(1) << "Starting SyncInternal initialization.";

  core_message_loop_ = MessageLoop::current();
  DCHECK(core_message_loop_);
  registrar_ = model_safe_worker_registrar;
  setup_for_test_mode_ = setup_for_test_mode;

  sync_notifier_ = sync_notifier;
  sync_notifier_->AddObserver(this);

  share_.dir_manager.reset(new DirectoryManager(database_location));

  connection_manager_.reset(new SyncAPIServerConnectionManager(
      sync_server_and_path, port, use_ssl, user_agent, post_factory));

  net::NetworkChangeNotifier::AddIPAddressObserver(this);

  connection_manager()->AddListener(this);

  // TODO(akalin): CheckServerReachable() can block, which may cause jank if we
  // try to shut down sync.  Fix this.
  core_message_loop_->PostTask(FROM_HERE,
      method_factory_.NewRunnableMethod(&SyncInternal::CheckServerReachable));

  // Test mode does not use a syncer context or syncer thread.
  if (!setup_for_test_mode_) {
    // Build a SyncSessionContext and store the worker in it.
    VLOG(1) << "Sync is bringing up SyncSessionContext.";
    std::vector<SyncEngineEventListener*> listeners;
    listeners.push_back(&allstatus_);
    listeners.push_back(this);
    SyncSessionContext* context = new SyncSessionContext(
        connection_manager_.get(),
        dir_manager(),
        model_safe_worker_registrar,
        listeners);
    context->set_account_name(credentials.email);
    // The SyncerThread takes ownership of |context|.
    syncer_thread_.reset(new SyncerThread(context, new Syncer()));
  }

  bool signed_in = SignIn(credentials);

  if (signed_in && syncer_thread()) {
    syncer_thread()->Start(
        browser_sync::SyncerThread::CONFIGURATION_MODE, NULL);
  }

  // Do this once the directory is opened.
  BootstrapEncryption(restored_key_for_bootstrapping);
  MarkAndNotifyInitializationComplete();
  return signed_in;
}

void SyncManager::SyncInternal::BootstrapEncryption(
    const std::string& restored_key_for_bootstrapping) {
  syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
  if (!lookup.good()) {
    NOTREACHED();
    return;
  }

  if (!lookup->initial_sync_ended_for_type(syncable::NIGORI))
    return;

  sync_pb::NigoriSpecifics nigori;
  {
    // Cryptographer should only be accessed while holding a transaction.
    ReadTransaction trans(GetUserShare());
    Cryptographer* cryptographer = trans.GetCryptographer();
    cryptographer->Bootstrap(restored_key_for_bootstrapping);

    ReadNode node(&trans);
    if (!node.InitByTagLookup(kNigoriTag)) {
      NOTREACHED();
      return;
    }

    nigori.CopyFrom(node.GetNigoriSpecifics());
    if (!nigori.encrypted().blob().empty()) {
      if (cryptographer->CanDecrypt(nigori.encrypted())) {
        cryptographer->SetKeys(nigori.encrypted());
      } else {
        cryptographer->SetPendingKeys(nigori.encrypted());
        FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                          OnPassphraseRequired(true));
      }
    }
  }

  // Refresh list of encrypted datatypes.
  syncable::ModelTypeSet encrypted_types =
      syncable::GetEncryptedDataTypesFromNigori(nigori);

  // Ensure any datatypes that need encryption are encrypted.
  EncryptDataTypes(encrypted_types);
}

void SyncManager::SyncInternal::StartSyncing() {
  // Start the syncer thread. This won't actually
  // result in any syncing until at least the
  // DirectoryManager broadcasts the OPENED event,
  // and a valid server connection is detected.
  if (syncer_thread())  // NULL during certain unittests.
    syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL);
}

void SyncManager::SyncInternal::MarkAndNotifyInitializationComplete() {
  // There is only one real time we need this mutex.  If we get an auth
  // success, and before the initial sync ends we get an auth failure.  In this
  // case we'll be listening to both the AuthWatcher and Syncer, and it's a race
  // between their respective threads to call MarkAndNotify.  We need to make
  // sure the observer is notified once and only once.
  {
    base::AutoLock lock(initialized_mutex_);
    if (initialized_)
      return;
    initialized_ = true;
  }

  // Notify that initialization is complete.
  FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                    OnInitializationComplete());
}

void SyncManager::SyncInternal::SendNotification() {
  DCHECK_EQ(MessageLoop::current(), core_message_loop_);
  if (!sync_notifier_) {
    VLOG(1) << "Not sending notification: sync_notifier_ is NULL";
    return;
  }
  allstatus_.IncrementNotificationsSent();
  sync_notifier_->SendNotification();
}

bool SyncManager::SyncInternal::OpenDirectory() {
  DCHECK(!initialized()) << "Should only happen once";

  bool share_opened = dir_manager()->Open(username_for_share());
  DCHECK(share_opened);
  if (!share_opened) {
    FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                      OnStopSyncingPermanently());

    LOG(ERROR) << "Could not open share for:" << username_for_share();
    return false;
  }

  // Database has to be initialized for the guid to be available.
  syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
  if (!lookup.good()) {
    NOTREACHED();
    return false;
  }

  connection_manager()->set_client_id(lookup->cache_guid());

  lookup->SetChangeListener(this);
  return true;
}

bool SyncManager::SyncInternal::SignIn(const SyncCredentials& credentials) {
  DCHECK_EQ(MessageLoop::current(), core_message_loop_);
  DCHECK(share_.name.empty());
  share_.name = credentials.email;

  VLOG(1) << "Signing in user: " << username_for_share();
  if (!OpenDirectory())
    return false;

  // Retrieve and set the sync notifier state. This should be done
  // only after OpenDirectory is called.
  syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
  std::string state;
  if (lookup.good()) {
    state = lookup->GetAndClearNotificationState();
  } else {
    LOG(ERROR) << "Could not read notification state";
  }
  if (VLOG_IS_ON(1)) {
    std::string encoded_state;
    base::Base64Encode(state, &encoded_state);
    VLOG(1) << "Read notification state: " << encoded_state;
  }
  sync_notifier_->SetState(state);

  UpdateCredentials(credentials);
  UpdateEnabledTypes();
  return true;
}

void SyncManager::SyncInternal::UpdateCredentials(
    const SyncCredentials& credentials) {
  DCHECK_EQ(MessageLoop::current(), core_message_loop_);
  DCHECK_EQ(credentials.email, share_.name);
  DCHECK(!credentials.email.empty());
  DCHECK(!credentials.sync_token.empty());
  connection_manager()->set_auth_token(credentials.sync_token);
  sync_notifier_->UpdateCredentials(
      credentials.email, credentials.sync_token);
  if (!setup_for_test_mode_) {
    CheckServerReachable();
  }
}

void SyncManager::SyncInternal::UpdateEnabledTypes() {
  DCHECK_EQ(MessageLoop::current(), core_message_loop_);
  ModelSafeRoutingInfo routes;
  registrar_->GetModelSafeRoutingInfo(&routes);
  syncable::ModelTypeSet enabled_types;
  for (ModelSafeRoutingInfo::const_iterator it = routes.begin();
       it != routes.end(); ++it) {
    enabled_types.insert(it->first);
  }
  sync_notifier_->UpdateEnabledTypes(enabled_types);
}

void SyncManager::SyncInternal::RaiseAuthNeededEvent() {
  FOR_EACH_OBSERVER(
      SyncManager::Observer, observers_,
      OnAuthError(AuthError(AuthError::INVALID_GAIA_CREDENTIALS)));
}

void SyncManager::SyncInternal::SetUsingExplicitPassphrasePrefForMigration(
    WriteTransaction* const trans) {
  WriteNode node(trans);
  if (!node.InitByTagLookup(kNigoriTag)) {
    // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS.
    NOTREACHED();
    return;
  }
  sync_pb::NigoriSpecifics specifics(node.GetNigoriSpecifics());
  specifics.set_using_explicit_passphrase(true);
  node.SetNigoriSpecifics(specifics);
}

void SyncManager::SyncInternal::SetPassphrase(
    const std::string& passphrase, bool is_explicit) {
  // All accesses to the cryptographer are protected by a transaction.
  WriteTransaction trans(GetUserShare());
  Cryptographer* cryptographer = trans.GetCryptographer();
  KeyParams params = {"localhost", "dummy", passphrase};

  if (cryptographer->has_pending_keys()) {
    if (!cryptographer->DecryptPendingKeys(params)) {
      VLOG(1) << "Passphrase failed to decrypt pending keys.";
      FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                        OnPassphraseFailed());
      return;
    }

    // TODO(tim): If this is the first time the user has entered a passphrase
    // since the protocol changed to store passphrase preferences in the cloud,
    // make sure we update this preference. See bug 62103.
    if (is_explicit)
      SetUsingExplicitPassphrasePrefForMigration(&trans);

    // Nudge the syncer so that encrypted datatype updates that were waiting for
    // this passphrase get applied as soon as possible.
    RequestNudge(FROM_HERE);
  } else {
    VLOG(1) << "No pending keys, adding provided passphrase.";
    WriteNode node(&trans);
    if (!node.InitByTagLookup(kNigoriTag)) {
      // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS.
      NOTREACHED();
      return;
    }

    // Prevent an implicit SetPassphrase request from changing an explicitly
    // set passphrase.
    if (!is_explicit && node.GetNigoriSpecifics().using_explicit_passphrase())
      return;

    cryptographer->AddKey(params);

    // TODO(tim): Bug 58231. It would be nice if SetPassphrase didn't require
    // messing with the Nigori node, because we can't call SetPassphrase until
    // download conditions are met vs Cryptographer init.  It seems like it's
    // safe to defer this work.
    sync_pb::NigoriSpecifics specifics(node.GetNigoriSpecifics());
    specifics.clear_encrypted();
    cryptographer->GetKeys(specifics.mutable_encrypted());
    specifics.set_using_explicit_passphrase(is_explicit);
    node.SetNigoriSpecifics(specifics);
    ReEncryptEverything(&trans);
  }

  std::string bootstrap_token;
  cryptographer->GetBootstrapToken(&bootstrap_token);
  FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                    OnPassphraseAccepted(bootstrap_token));
}

bool SyncManager::SyncInternal::IsUsingExplicitPassphrase() {
  ReadTransaction trans(&share_);
  ReadNode node(&trans);
  if (!node.InitByTagLookup(kNigoriTag)) {
    // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS.
    NOTREACHED();
    return false;
  }

  return node.GetNigoriSpecifics().using_explicit_passphrase();
}

void SyncManager::SyncInternal::EncryptDataTypes(
    const syncable::ModelTypeSet& encrypted_types) {
  VLOG(1) << "Attempting to encrypt datatypes "
          << syncable::ModelTypeSetToString(encrypted_types);

  WriteTransaction trans(GetUserShare());
  WriteNode node(&trans);
  if (!node.InitByTagLookup(kNigoriTag)) {
    LOG(ERROR) << "Unable to set encrypted datatypes because Nigori node not "
               << "found.";
    NOTREACHED();
    return;
  }

  // Update the Nigori node set of encrypted datatypes so other machines notice.
  // Note, we merge the current encrypted types with those requested. Once a
  // datatypes is marked as needing encryption, it is never unmarked.
  sync_pb::NigoriSpecifics nigori;
  nigori.CopyFrom(node.GetNigoriSpecifics());
  syncable::ModelTypeSet current_encrypted_types =
      syncable::GetEncryptedDataTypesFromNigori(nigori);
  syncable::ModelTypeSet newly_encrypted_types;
  std::set_union(current_encrypted_types.begin(), current_encrypted_types.end(),
                 encrypted_types.begin(), encrypted_types.end(),
                 std::inserter(newly_encrypted_types,
                               newly_encrypted_types.begin()));
  syncable::FillNigoriEncryptedTypes(newly_encrypted_types, &nigori);
  node.SetNigoriSpecifics(nigori);

  // TODO(zea): only reencrypt this datatype? ReEncrypting everything is a
  // safer approach, and should not impact anything that is already encrypted
  // (redundant changes are ignored).
  ReEncryptEverything(&trans);
  return;
}

namespace {

void FindChildNodesContainingString(const std::string& lowercase_query,
    const ReadNode& parent_node,
    sync_api::ReadTransaction* trans,
    ListValue* result) {
  int64 child_id = parent_node.GetFirstChildId();
  while (child_id != kInvalidId) {
    ReadNode node(trans);
    if (node.InitByIdLookup(child_id)) {
      if (node.ContainsString(lowercase_query)) {
        result->Append(new StringValue(base::Int64ToString(child_id)));
      }
      FindChildNodesContainingString(lowercase_query, node, trans, result);
      child_id = node.GetSuccessorId();
    } else {
      LOG(WARNING) << "Lookup of node failed. Id: " << child_id;
      return;
    }
  }
}
}  // namespace

// Returned pointer owned by the caller.
ListValue* SyncManager::SyncInternal::FindNodesContainingString(
    const std::string& query) {
  // Convert the query string to lower case to perform case insensitive
  // searches.
  std::string lowercase_query = query;
  StringToLowerASCII(&lowercase_query);
  ReadTransaction trans(GetUserShare());
  ReadNode root(&trans);
  root.InitByRootLookup();

  ListValue* result = new ListValue();

  base::Time start_time = base::Time::Now();
  FindChildNodesContainingString(lowercase_query, root, &trans, result);
  base::Time end_time = base::Time::Now();

  base::TimeDelta delta = end_time - start_time;
  VLOG(1) << "Time taken in milliseconds to search " << delta.InMilliseconds();

  return result;
}

void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) {
  syncable::ModelTypeSet encrypted_types =
      GetEncryptedDataTypes(trans->GetWrappedTrans());
  ModelSafeRoutingInfo routes;
  registrar_->GetModelSafeRoutingInfo(&routes);
  std::string tag;
  for (syncable::ModelTypeSet::iterator iter = encrypted_types.begin();
       iter != encrypted_types.end(); ++iter) {
    if (*iter == syncable::PASSWORDS || routes.count(*iter) == 0)
      continue;
    ReadNode type_root(trans);
    tag = syncable::ModelTypeToRootTag(*iter);
    if (!type_root.InitByTagLookup(tag)) {
      NOTREACHED();
      return;
    }

    // Iterate through all children of this datatype.
    std::queue<int64> to_visit;
    int64 child_id = type_root.GetFirstChildId();
    to_visit.push(child_id);
    while (!to_visit.empty()) {
      child_id = to_visit.front();
      to_visit.pop();
      if (child_id == kInvalidId)
        continue;

      WriteNode child(trans);
      if (!child.InitByIdLookup(child_id)) {
        NOTREACHED();
        return;
      }
      if (child.GetIsFolder()) {
        to_visit.push(child.GetFirstChildId());
      } else {
        // Rewrite the specifics of the node with encrypted data if necessary.
        child.ResetFromSpecifics();
      }
      to_visit.push(child.GetSuccessorId());
    }
  }

  if (routes.count(syncable::PASSWORDS) > 0) {
    // Passwords are encrypted with their own legacy scheme.
    encrypted_types.insert(syncable::PASSWORDS);
    ReadNode passwords_root(trans);
    std::string passwords_tag =
        syncable::ModelTypeToRootTag(syncable::PASSWORDS);
    if (!passwords_root.InitByTagLookup(passwords_tag)) {
      LOG(WARNING) << "No passwords to reencrypt.";
      return;
    }

    int64 child_id = passwords_root.GetFirstChildId();
    while (child_id != kInvalidId) {
      WriteNode child(trans);
      if (!child.InitByIdLookup(child_id)) {
        NOTREACHED();
        return;
      }
      child.SetPasswordSpecifics(child.GetPasswordSpecifics());
      child_id = child.GetSuccessorId();
    }
  }

  FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                    OnEncryptionComplete(encrypted_types));
}

SyncManager::~SyncManager() {
  delete data_;
}

void SyncManager::AddObserver(Observer* observer) {
  data_->AddObserver(observer);
}

void SyncManager::RemoveObserver(Observer* observer) {
  data_->RemoveObserver(observer);
}

browser_sync::JsBackend* SyncManager::GetJsBackend() {
  return data_;
}

void SyncManager::Shutdown() {
  data_->Shutdown();
}

void SyncManager::SyncInternal::Shutdown() {
  method_factory_.RevokeAll();

  if (syncer_thread()) {
    syncer_thread()->Stop();
    syncer_thread_.reset();
  }

  // We NULL out sync_notifer_ so that any pending tasks do not
  // trigger further notifications.
  // TODO(akalin): NULL the other member variables defensively, too.
  if (sync_notifier_) {
    sync_notifier_->RemoveObserver(this);
  }

  // |this| is about to be destroyed, so we have to ensure any messages
  // that were posted to core_thread_ before or during syncer thread shutdown
  // are flushed out, else they refer to garbage memory.  SendNotification
  // is an example.
  // TODO(tim): Remove this monstrosity, perhaps with ObserverListTS once core
  // thread is removed. Bug 78190.
  {
    CHECK(core_message_loop_);
    bool old_state = core_message_loop_->NestableTasksAllowed();
    core_message_loop_->SetNestableTasksAllowed(true);
    core_message_loop_->RunAllPending();
    core_message_loop_->SetNestableTasksAllowed(old_state);
  }

  net::NetworkChangeNotifier::RemoveIPAddressObserver(this);

  connection_manager_hookup_.reset();

  if (dir_manager()) {
    dir_manager()->FinalSaveChangesForAll();
    dir_manager()->Close(username_for_share());
  }

  // Reset the DirectoryManager and UserSettings so they relinquish sqlite
  // handles to backing files.
  share_.dir_manager.reset();

  core_message_loop_ = NULL;
}

void SyncManager::SyncInternal::OnIPAddressChanged() {
  VLOG(1) << "IP address change detected";
#if defined (OS_CHROMEOS)
  // TODO(tim): This is a hack to intentionally lose a race with flimflam at
  // shutdown, so we don't cause shutdown to wait for our http request.
  // http://crosbug.com/8429
  MessageLoop::current()->PostDelayedTask(FROM_HERE,
      method_factory_.NewRunnableMethod(&SyncInternal::OnIPAddressChangedImpl),
      kChromeOSNetworkChangeReactionDelayHackMsec);
#else
  OnIPAddressChangedImpl();
#endif  // defined(OS_CHROMEOS)
}

void SyncManager::SyncInternal::OnIPAddressChangedImpl() {
  // TODO(akalin): CheckServerReachable() can block, which may cause
  // jank if we try to shut down sync.  Fix this.
  connection_manager()->CheckServerReachable();
  RequestNudge(FROM_HERE);
}

void SyncManager::SyncInternal::OnServerConnectionEvent(
    const ServerConnectionEvent2& event) {
  ServerConnectionEvent legacy;
  legacy.what_happened = ServerConnectionEvent::STATUS_CHANGED;
  legacy.connection_code = event.connection_code;
  legacy.server_reachable = event.server_reachable;
  HandleServerConnectionEvent(legacy);
}

void SyncManager::SyncInternal::HandleServerConnectionEvent(
    const ServerConnectionEvent& event) {
  allstatus_.HandleServerConnectionEvent(event);
  if (event.what_happened == ServerConnectionEvent::STATUS_CHANGED) {
    if (event.connection_code ==
        browser_sync::HttpResponse::SERVER_CONNECTION_OK) {
      FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                        OnAuthError(AuthError::None()));
    }

    if (event.connection_code == browser_sync::HttpResponse::SYNC_AUTH_ERROR) {
      FOR_EACH_OBSERVER(
          SyncManager::Observer, observers_,
          OnAuthError(AuthError(AuthError::INVALID_GAIA_CREDENTIALS)));
    }
  }
}

void SyncManager::SyncInternal::HandleTransactionCompleteChangeEvent(
    const syncable::ModelTypeBitSet& models_with_changes) {
  // This notification happens immediately after the transaction mutex is
  // released. This allows work to be performed without blocking other threads
  // from acquiring a transaction.
  if (observers_.size() <= 0)
    return;

  // Call commit.
  for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) {
    if (models_with_changes.test(i)) {
      FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                        OnChangesComplete(syncable::ModelTypeFromInt(i)));
    }
  }
}

ModelTypeBitSet SyncManager::SyncInternal::HandleTransactionEndingChangeEvent(
    syncable::BaseTransaction* trans) {
  // This notification happens immediately before a syncable WriteTransaction
  // falls out of scope. It happens while the channel mutex is still held,
  // and while the transaction mutex is held, so it cannot be re-entrant.
  if (observers_.size() <= 0 || ChangeBuffersAreEmpty())
    return ModelTypeBitSet();

  // This will continue the WriteTransaction using a read only wrapper.
  // This is the last chance for read to occur in the WriteTransaction
  // that's closing. This special ReadTransaction will not close the
  // underlying transaction.
  ReadTransaction read_trans(GetUserShare(), trans);

  syncable::ModelTypeBitSet models_with_changes;
  for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) {
    if (change_buffers_[i].IsEmpty())
      continue;

    vector<ChangeRecord> ordered_changes;
    change_buffers_[i].GetAllChangesInTreeOrder(&read_trans, &ordered_changes);
    if (!ordered_changes.empty()) {
      FOR_EACH_OBSERVER(
          SyncManager::Observer, observers_,
          OnChangesApplied(syncable::ModelTypeFromInt(i), &read_trans,
                           &ordered_changes[0], ordered_changes.size()));
      models_with_changes.set(i, true);
    }
    change_buffers_[i].Clear();
  }
  return models_with_changes;
}

void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncApi(
    const OriginalEntries& originals,
    const WriterTag& writer,
    syncable::BaseTransaction* trans) {
  // We have been notified about a user action changing a sync model.
  DCHECK(writer == syncable::SYNCAPI ||
         writer == syncable::UNITTEST);
  LOG_IF(WARNING, !ChangeBuffersAreEmpty()) <<
      "CALCULATE_CHANGES called with unapplied old changes.";

  bool exists_unsynced_items = false;
  bool only_preference_changes = true;
  syncable::ModelTypeBitSet model_types;
  for (syncable::OriginalEntries::const_iterator i = originals.begin();
       i != originals.end() && !exists_unsynced_items;
       ++i) {
    int64 id = i->ref(syncable::META_HANDLE);
    syncable::Entry e(trans, syncable::GET_BY_HANDLE, id);
    DCHECK(e.good());

    syncable::ModelType model_type = e.GetModelType();

    if (e.Get(syncable::IS_UNSYNCED)) {
      if (model_type == syncable::TOP_LEVEL_FOLDER ||
          model_type == syncable::UNSPECIFIED) {
        NOTREACHED() << "Permanent or underspecified item changed via syncapi.";
        continue;
      }
      // Unsynced items will cause us to nudge the the syncer.
      exists_unsynced_items = true;

      model_types[model_type] = true;
      if (model_type != syncable::PREFERENCES)
        only_preference_changes = false;
    }
  }
  if (exists_unsynced_items && syncer_thread()) {
    int nudge_delay = only_preference_changes ?
        kPreferencesNudgeDelayMilliseconds : kDefaultNudgeDelayMilliseconds;
    core_message_loop_->PostTask(FROM_HERE,
        NewRunnableMethod(this, &SyncInternal::RequestNudgeWithDataTypes,
        TimeDelta::FromMilliseconds(nudge_delay),
        browser_sync::NUDGE_SOURCE_LOCAL,
        model_types,
        FROM_HERE));
  }
}

void SyncManager::SyncInternal::SetExtraChangeRecordData(int64 id,
    syncable::ModelType type, ChangeReorderBuffer* buffer,
    Cryptographer* cryptographer, const syncable::EntryKernel& original,
    bool existed_before, bool exists_now) {
  // If this is a deletion and the datatype was encrypted, we need to decrypt it
  // and attach it to the buffer.
  if (!exists_now && existed_before) {
    sync_pb::EntitySpecifics original_specifics(original.ref(SPECIFICS));
    if (type == syncable::PASSWORDS) {
      // Passwords must use their own legacy ExtraPasswordChangeRecordData.
      scoped_ptr<sync_pb::PasswordSpecificsData> data(
          DecryptPasswordSpecifics(original_specifics, cryptographer));
      if (!data.get()) {
        NOTREACHED();
        return;
      }
      buffer->SetExtraDataForId(id, new ExtraPasswordChangeRecordData(*data));
    } else if (original_specifics.has_encrypted()) {
      // All other datatypes can just create a new unencrypted specifics and
      // attach it.
      const sync_pb::EncryptedData& encrypted = original_specifics.encrypted();
      if (!cryptographer->Decrypt(encrypted, &original_specifics)) {
        NOTREACHED();
        return;
      }
    }
    buffer->SetSpecificsForId(id, original_specifics);
  }
}

void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer(
    const OriginalEntries& originals,
    const WriterTag& writer,
    syncable::BaseTransaction* trans) {
  // We only expect one notification per sync step, so change_buffers_ should
  // contain no pending entries.
  DCHECK(writer == syncable::SYNCER ||
         writer == syncable::UNITTEST);
  LOG_IF(WARNING, !ChangeBuffersAreEmpty()) <<
      "CALCULATE_CHANGES called with unapplied old changes.";

  Cryptographer* crypto = dir_manager()->GetCryptographer(trans);
  for (syncable::OriginalEntries::const_iterator i = originals.begin();
       i != originals.end(); ++i) {
    int64 id = i->ref(syncable::META_HANDLE);
    syncable::Entry e(trans, syncable::GET_BY_HANDLE, id);
    bool existed_before = !i->ref(syncable::IS_DEL);
    bool exists_now = e.good() && !e.Get(syncable::IS_DEL);
    DCHECK(e.good());

    // Omit items that aren't associated with a model.
    syncable::ModelType type = e.GetModelType();
    if (type == syncable::TOP_LEVEL_FOLDER || type == syncable::UNSPECIFIED)
      continue;

    if (exists_now && !existed_before)
      change_buffers_[type].PushAddedItem(id);
    else if (!exists_now && existed_before)
      change_buffers_[type].PushDeletedItem(id);
    else if (exists_now && existed_before &&
             VisiblePropertiesDiffer(*i, e, crypto)) {
      change_buffers_[type].PushUpdatedItem(id, VisiblePositionsDiffer(*i, e));
    }

    SetExtraChangeRecordData(id, type, &change_buffers_[type], crypto, *i,
                             existed_before, exists_now);
  }
}

SyncManager::Status SyncManager::SyncInternal::GetStatus() {
  return allstatus_.status();
}

void SyncManager::SyncInternal::RequestNudge(
    const tracked_objects::Location& location) {
  if (syncer_thread())
     syncer_thread()->ScheduleNudge(
        TimeDelta::FromMilliseconds(0), browser_sync::NUDGE_SOURCE_LOCAL,
        ModelTypeBitSet(), location);
}

void SyncManager::SyncInternal::RequestNudgeWithDataTypes(
    const TimeDelta& delay,
    browser_sync::NudgeSource source, const ModelTypeBitSet& types,
    const tracked_objects::Location& nudge_location) {
  if (syncer_thread())
     syncer_thread()->ScheduleNudge(delay, source, types, nudge_location);
}

void SyncManager::SyncInternal::OnSyncEngineEvent(
    const SyncEngineEvent& event) {
  if (observers_.size() <= 0)
    return;

  // Only send an event if this is due to a cycle ending and this cycle
  // concludes a canonical "sync" process; that is, based on what is known
  // locally we are "all happy" and up-to-date.  There may be new changes on
  // the server, but we'll get them on a subsequent sync.
  //
  // Notifications are sent at the end of every sync cycle, regardless of
  // whether we should sync again.
  if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED) {
    ModelSafeRoutingInfo enabled_types;
    registrar_->GetModelSafeRoutingInfo(&enabled_types);
    {
      // Check to see if we need to notify the frontend that we have newly
      // encrypted types or that we require a passphrase.
      sync_api::ReadTransaction trans(GetUserShare());
      sync_api::ReadNode node(&trans);
      if (!node.InitByTagLookup(kNigoriTag)) {
        DCHECK(!event.snapshot->is_share_usable);
        return;
      }
      const sync_pb::NigoriSpecifics& nigori = node.GetNigoriSpecifics();
      syncable::ModelTypeSet encrypted_types =
          syncable::GetEncryptedDataTypesFromNigori(nigori);
      // If passwords are enabled, they're automatically considered encrypted.
      if (enabled_types.count(syncable::PASSWORDS) > 0)
        encrypted_types.insert(syncable::PASSWORDS);
      if (!encrypted_types.empty()) {
        Cryptographer* cryptographer = trans.GetCryptographer();
        if (!cryptographer->is_ready() && !cryptographer->has_pending_keys()) {
          if (!nigori.encrypted().blob().empty()) {
            DCHECK(!cryptographer->CanDecrypt(nigori.encrypted()));
            cryptographer->SetPendingKeys(nigori.encrypted());
          }
        }

        // If we've completed a sync cycle and the cryptographer isn't ready
        // yet, prompt the user for a passphrase.
        if (cryptographer->has_pending_keys()) {
          FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                            OnPassphraseRequired(true));
        } else if (!cryptographer->is_ready()) {
          FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                            OnPassphraseRequired(false));
        } else {
          FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                            OnEncryptionComplete(encrypted_types));
        }
      }
    }

    if (!initialized())
      return;

    if (!event.snapshot->has_more_to_sync) {
      FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                        OnSyncCycleCompleted(event.snapshot));
    }

    // This is here for tests, which are still using p2p notifications.
    // SendNotification does not do anything if we are using server based
    // notifications.
    // TODO(chron): Consider changing this back to track has_more_to_sync
    // only notify peers if a successful commit has occurred.
    bool new_notification =
        (event.snapshot->syncer_status.num_successful_commits > 0);
    if (new_notification) {
      core_message_loop_->PostTask(
          FROM_HERE,
          NewRunnableMethod(
              this,
              &SyncManager::SyncInternal::SendNotification));
    }
  }

  if (event.what_happened == SyncEngineEvent::STOP_SYNCING_PERMANENTLY) {
    FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                      OnStopSyncingPermanently());
    return;
  }

  if (event.what_happened == SyncEngineEvent::CLEAR_SERVER_DATA_SUCCEEDED) {
    FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                      OnClearServerDataSucceeded());
    return;
  }

  if (event.what_happened == SyncEngineEvent::CLEAR_SERVER_DATA_FAILED) {
    FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                      OnClearServerDataFailed());
    return;
  }

  if (event.what_happened == SyncEngineEvent::UPDATED_TOKEN) {
    FOR_EACH_OBSERVER(SyncManager::Observer, observers_,
                      OnUpdatedToken(event.updated_token));
    return;
  }
}

void SyncManager::SyncInternal::SetParentJsEventRouter(
    browser_sync::JsEventRouter* router) {
  DCHECK(router);
  parent_router_ = router;
}

void SyncManager::SyncInternal::RemoveParentJsEventRouter() {
  parent_router_ = NULL;
}

const browser_sync::JsEventRouter*
    SyncManager::SyncInternal::GetParentJsEventRouter() const {
  return parent_router_;
}

namespace {

void LogNoRouter(const std::string& name,
                 const browser_sync::JsArgList& args) {
  VLOG(1) << "No parent router; not replying to message " << name
          << " with args " << args.ToString();
}

}  // namespace

void SyncManager::SyncInternal::ProcessMessage(
    const std::string& name, const browser_sync::JsArgList& args,
    const browser_sync::JsEventHandler* sender) {
  DCHECK(initialized_);
  if (name == "getNotificationState") {
    if (!parent_router_) {
      LogNoRouter(name, args);
      return;
    }
    bool notifications_enabled = allstatus_.status().notifications_enabled;
    ListValue return_args;
    return_args.Append(Value::CreateBooleanValue(notifications_enabled));
    parent_router_->RouteJsEvent(
        "onGetNotificationStateFinished",
        browser_sync::JsArgList(return_args), sender);
  } else if (name == "getNotificationInfo") {
    if (!parent_router_) {
      LogNoRouter(name, args);
      return;
    }

    ListValue return_args;
    return_args.Append(NotificationInfoToValue(notification_info_map_));
    parent_router_->RouteJsEvent("onGetNotificationInfoFinished",
        browser_sync::JsArgList(return_args), sender);
  } else if (name == "getRootNode") {
    if (!parent_router_) {
      LogNoRouter(name, args);
      return;
    }
    ReadTransaction trans(GetUserShare());
    ReadNode root(&trans);
    root.InitByRootLookup();
    ListValue return_args;
    return_args.Append(root.ToValue());
    parent_router_->RouteJsEvent(
        "onGetRootNodeFinished",
        browser_sync::JsArgList(return_args), sender);
  } else if (name == "getNodeById") {
    if (!parent_router_) {
      LogNoRouter(name, args);
      return;
    }
    parent_router_->RouteJsEvent(
        "onGetNodeByIdFinished", ProcessGetNodeByIdMessage(args), sender);
  } else if (name == "findNodesContainingString") {
    if (!parent_router_) {
      LogNoRouter(name, args);
      return;
    }
    parent_router_->RouteJsEvent(
        "onFindNodesContainingStringFinished",
        ProcessFindNodesContainingString(args), sender);
  } else {
    VLOG(1) << "Dropping unknown message " << name
              << " with args " << args.ToString();
  }
}

browser_sync::JsArgList SyncManager::SyncInternal::ProcessGetNodeByIdMessage(
    const browser_sync::JsArgList& args) {
  ListValue null_return_args_list;
  null_return_args_list.Append(Value::CreateNullValue());
  browser_sync::JsArgList null_return_args(null_return_args_list);
  std::string id_str;
  if (!args.Get().GetString(0, &id_str)) {
    return null_return_args;
  }
  int64 id;
  if (!base::StringToInt64(id_str, &id)) {
    return null_return_args;
  }
  if (id == kInvalidId) {
    return null_return_args;
  }
  ReadTransaction trans(GetUserShare());
  ReadNode node(&trans);
  if (!node.InitByIdLookup(id)) {
    return null_return_args;
  }
  ListValue return_args;
  return_args.Append(node.ToValue());
  return browser_sync::JsArgList(return_args);
}

browser_sync::JsArgList SyncManager::SyncInternal::
    ProcessFindNodesContainingString(
    const browser_sync::JsArgList& args) {
  std::string query;
  ListValue return_args;
  if (!args.Get().GetString(0, &query)) {
    return_args.Append(new ListValue());
    return browser_sync::JsArgList(return_args);
  }

  ListValue* result = FindNodesContainingString(query);
  return_args.Append(result);
  return browser_sync::JsArgList(return_args);
}

void SyncManager::SyncInternal::OnNotificationStateChange(
    bool notifications_enabled) {
  VLOG(1) << "P2P: Notifications enabled = "
          << (notifications_enabled ? "true" : "false");
  allstatus_.SetNotificationsEnabled(notifications_enabled);
  if (syncer_thread()) {
    syncer_thread()->set_notifications_enabled(notifications_enabled);
  }
  if (parent_router_) {
    ListValue args;
    args.Append(Value::CreateBooleanValue(notifications_enabled));
    // TODO(akalin): Tidy up grammar in event names.
    parent_router_->RouteJsEvent("onSyncNotificationStateChange",
                                 browser_sync::JsArgList(args), NULL);
  }
}

void SyncManager::SyncInternal::UpdateNotificationInfo(
    const syncable::ModelTypePayloadMap& type_payloads) {
  for (syncable::ModelTypePayloadMap::const_iterator it = type_payloads.begin();
       it != type_payloads.end(); ++it) {
    NotificationInfo* info = &notification_info_map_[it->first];
    info->total_count++;
    info->payload = it->second;
  }
}

void SyncManager::SyncInternal::OnIncomingNotification(
    const syncable::ModelTypePayloadMap& type_payloads) {
  if (!type_payloads.empty()) {
    if (syncer_thread()) {
      syncer_thread()->ScheduleNudgeWithPayloads(
          TimeDelta::FromMilliseconds(kSyncerThreadDelayMsec),
          browser_sync::NUDGE_SOURCE_NOTIFICATION,
          type_payloads, FROM_HERE);
    }
    allstatus_.IncrementNotificationsReceived();
    UpdateNotificationInfo(type_payloads);
  } else {
    LOG(WARNING) << "Sync received notification without any type information.";
  }

  if (parent_router_) {
    ListValue args;
    ListValue* changed_types = new ListValue();
    args.Append(changed_types);
    for (syncable::ModelTypePayloadMap::const_iterator
             it = type_payloads.begin();
         it != type_payloads.end(); ++it) {
      const std::string& model_type_str =
          syncable::ModelTypeToString(it->first);
      changed_types->Append(Value::CreateStringValue(model_type_str));
    }
    parent_router_->RouteJsEvent("onSyncIncomingNotification",
                                 browser_sync::JsArgList(args), NULL);
  }
}

void SyncManager::SyncInternal::StoreState(
    const std::string& state) {
  syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
  if (!lookup.good()) {
    LOG(ERROR) << "Could not write notification state";
    // TODO(akalin): Propagate result callback all the way to this
    // function and call it with "false" to signal failure.
    return;
  }
  if (VLOG_IS_ON(1)) {
    std::string encoded_state;
    base::Base64Encode(state, &encoded_state);
    VLOG(1) << "Writing notification state: " << encoded_state;
  }
  lookup->SetNotificationState(state);
  lookup->SaveChanges();
}

void SyncManager::SyncInternal::AddObserver(
    SyncManager::Observer* observer) {
  observers_.AddObserver(observer);
}

void SyncManager::SyncInternal::RemoveObserver(
    SyncManager::Observer* observer) {
  observers_.RemoveObserver(observer);
}

SyncManager::Status::Summary SyncManager::GetStatusSummary() const {
  return data_->GetStatus().summary;
}

SyncManager::Status SyncManager::GetDetailedStatus() const {
  return data_->GetStatus();
}

SyncManager::SyncInternal* SyncManager::GetImpl() const { return data_; }

void SyncManager::SaveChanges() {
  data_->SaveChanges();
}

void SyncManager::SyncInternal::SaveChanges() {
  syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
  if (!lookup.good()) {
    DCHECK(false) << "ScopedDirLookup creation failed; Unable to SaveChanges";
    return;
  }
  lookup->SaveChanges();
}

//////////////////////////////////////////////////////////////////////////
// BaseTransaction member definitions
BaseTransaction::BaseTransaction(UserShare* share)
    : lookup_(NULL) {
  DCHECK(share && share->dir_manager.get());
  lookup_ = new syncable::ScopedDirLookup(share->dir_manager.get(),
                                          share->name);
  cryptographer_ = share->dir_manager->GetCryptographer(this);
  if (!(lookup_->good()))
    DCHECK(false) << "ScopedDirLookup failed on valid DirManager.";
}
BaseTransaction::~BaseTransaction() {
  delete lookup_;
}

UserShare* SyncManager::GetUserShare() const {
  DCHECK(data_->initialized()) << "GetUserShare requires initialization!";
  return data_->GetUserShare();
}

bool SyncManager::HasUnsyncedItems() const {
  sync_api::ReadTransaction trans(GetUserShare());
  return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0);
}

void SyncManager::TriggerOnNotificationStateChangeForTest(
    bool notifications_enabled) {
  data_->OnNotificationStateChange(notifications_enabled);
}

void SyncManager::TriggerOnIncomingNotificationForTest(
    const syncable::ModelTypeBitSet& model_types) {
  syncable::ModelTypePayloadMap model_types_with_payloads =
      syncable::ModelTypePayloadMapFromBitSet(model_types,
          std::string());

  data_->OnIncomingNotification(model_types_with_payloads);
}

}  // namespace sync_api