普通文本  |  346行  |  12.97 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/glue/extension_sync.h"

#include <utility>

#include "base/logging.h"
#include "chrome/browser/extensions/extension_updater.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_sync_data.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/extension_data.h"
#include "chrome/browser/sync/glue/extension_sync_traits.h"
#include "chrome/browser/sync/glue/extension_util.h"
#include "chrome/browser/sync/profile_sync_service.h"

namespace browser_sync {

bool RootNodeHasChildren(const char* tag,
                         sync_api::UserShare* user_share,
                         bool* has_children) {
  CHECK(has_children);
  *has_children = false;
  sync_api::ReadTransaction trans(user_share);
  sync_api::ReadNode node(&trans);
  if (!node.InitByTagLookup(tag)) {
    LOG(ERROR) << "Root node with tag " << tag << " does not exist";
    return false;
  }
  *has_children = node.GetFirstChildId() != sync_api::kInvalidId;
  return true;
}

namespace {

// Updates the value in |extension_data_map| from the given data,
// creating an entry if necessary.  Returns a pointer to the
// updated/created ExtensionData object.
ExtensionData* SetOrCreateExtensionData(
    ExtensionDataMap* extension_data_map,
    ExtensionData::Source source,
    bool merge_user_properties,
    const sync_pb::ExtensionSpecifics& data) {
  DcheckIsExtensionSpecificsValid(data);
  const std::string& extension_id = data.id();
  std::pair<ExtensionDataMap::iterator, bool> result =
      extension_data_map->insert(
          std::make_pair(extension_id,
                         ExtensionData::FromData(source, data)));
  ExtensionData* extension_data = &result.first->second;
  if (result.second) {
    // The value was just inserted, so it shouldn't need an update
    // from source.
    DCHECK(!extension_data->NeedsUpdate(source));
  } else {
    extension_data->SetData(source, merge_user_properties, data);
  }
  return extension_data;
}

// Reads the client data for each extension in |extensions| to be
// synced and updates |extension_data_map|.  Puts all unsynced
// extensions in |unsynced_extensions|.
void ReadClientDataFromExtensionList(
    const ExtensionList& extensions,
    IsValidAndSyncablePredicate is_valid_and_syncable,
    const ExtensionServiceInterface& extensions_service,
    std::set<std::string>* unsynced_extensions,
    ExtensionDataMap* extension_data_map) {
  for (ExtensionList::const_iterator it = extensions.begin();
       it != extensions.end(); ++it) {
    CHECK(*it);
    const Extension& extension = **it;
    if (is_valid_and_syncable(extension)) {
      sync_pb::ExtensionSpecifics client_specifics;
      GetExtensionSpecifics(extension, extensions_service,
                            &client_specifics);
      DcheckIsExtensionSpecificsValid(client_specifics);
      const ExtensionData& extension_data =
          *SetOrCreateExtensionData(
              extension_data_map, ExtensionData::CLIENT,
              true, client_specifics);
      DcheckIsExtensionSpecificsValid(extension_data.merged_data());
      // Assumes this is called before any server data is read.
      DCHECK(extension_data.NeedsUpdate(ExtensionData::SERVER));
      DCHECK(!extension_data.NeedsUpdate(ExtensionData::CLIENT));
    } else {
      unsynced_extensions->insert(extension.id());
    }
  }
}

// Simply calls ReadClientDataFromExtensionList() on the list of
// enabled and disabled extensions from |extensions_service|.
void SlurpClientData(
    IsValidAndSyncablePredicate is_valid_and_syncable,
    const ExtensionServiceInterface& extensions_service,
    std::set<std::string>* unsynced_extensions,
    ExtensionDataMap* extension_data_map) {
  const ExtensionList* extensions = extensions_service.extensions();
  CHECK(extensions);
  ReadClientDataFromExtensionList(
      *extensions, is_valid_and_syncable, extensions_service,
      unsynced_extensions, extension_data_map);

  const ExtensionList* disabled_extensions =
      extensions_service.disabled_extensions();
  CHECK(disabled_extensions);
  ReadClientDataFromExtensionList(
      *disabled_extensions, is_valid_and_syncable, extensions_service,
      unsynced_extensions, extension_data_map);
}

// Gets the boilerplate error message for not being able to find a
// root node.
//
// TODO(akalin): Put this somewhere where all data types can use it.
std::string GetRootNodeDoesNotExistError(const char* root_node_tag) {
  return
      std::string("Server did not create the top-level ") +
      root_node_tag +
      " node. We might be running against an out-of-date server.";
}

// Gets the data from the server for extensions to be synced and
// updates |extension_data_map|.  Skips all extensions in
// |unsynced_extensions|.
bool SlurpServerData(
    const char* root_node_tag,
    const ExtensionSpecificsGetter extension_specifics_getter,
    const std::set<std::string>& unsynced_extensions,
    sync_api::UserShare* user_share,
    ExtensionDataMap* extension_data_map) {
  sync_api::WriteTransaction trans(user_share);
  sync_api::ReadNode root(&trans);
  if (!root.InitByTagLookup(root_node_tag)) {
    LOG(ERROR) << GetRootNodeDoesNotExistError(root_node_tag);
    return false;
  }

  int64 id = root.GetFirstChildId();
  while (id != sync_api::kInvalidId) {
    sync_api::ReadNode sync_node(&trans);
    if (!sync_node.InitByIdLookup(id)) {
      LOG(ERROR) << "Failed to fetch sync node for id " << id;
      return false;
    }
    const sync_pb::ExtensionSpecifics& server_data =
        (*extension_specifics_getter)(sync_node);
    if (!IsExtensionSpecificsValid(server_data)) {
      LOG(ERROR) << "Invalid extensions specifics for id " << id;
      return false;
    }
    // Don't process server data for extensions we know are
    // unsyncable.  This doesn't catch everything, as if we don't
    // have the extension already installed we can't check, but we
    // also check at extension install time.
    if (unsynced_extensions.find(server_data.id()) ==
        unsynced_extensions.end()) {
      // Pass in false for merge_user_properties so client user
      // settings always take precedence.
      const ExtensionData& extension_data =
          *SetOrCreateExtensionData(
              extension_data_map, ExtensionData::SERVER, false, server_data);
      DcheckIsExtensionSpecificsValid(extension_data.merged_data());
    }
    id = sync_node.GetSuccessorId();
  }
  return true;
}

}  // namespace

bool SlurpExtensionData(const ExtensionSyncTraits& traits,
                        const ExtensionServiceInterface& extensions_service,
                        sync_api::UserShare* user_share,
                        ExtensionDataMap* extension_data_map) {
  std::set<std::string> unsynced_extensions;

  // Read client-side data first so server data takes precedence, and
  // also so we have an idea of which extensions are unsyncable.
  SlurpClientData(
      traits.is_valid_and_syncable, extensions_service,
      &unsynced_extensions, extension_data_map);

  if (!SlurpServerData(
          traits.root_node_tag, traits.extension_specifics_getter,
          unsynced_extensions, user_share, extension_data_map)) {
    return false;
  }
  return true;
}

namespace {

// Updates the server data from the given extension data.
// extension_data->ServerNeedsUpdate() must hold before this function
// is called.  Returns whether or not the update was successful.  If
// the update was successful, extension_data->ServerNeedsUpdate() will
// be false after this function is called.  This function leaves
// extension_data->ClientNeedsUpdate() unchanged.
bool UpdateServer(
    const ExtensionSyncTraits& traits,
    ExtensionData* extension_data,
    sync_api::WriteTransaction* trans) {
  DCHECK(extension_data->NeedsUpdate(ExtensionData::SERVER));
  const sync_pb::ExtensionSpecifics& specifics =
      extension_data->merged_data();
  const std::string& id = specifics.id();
  sync_api::WriteNode write_node(trans);
  if (write_node.InitByClientTagLookup(traits.model_type, id)) {
    (*traits.extension_specifics_setter)(specifics, &write_node);
  } else {
    sync_api::ReadNode root(trans);
    if (!root.InitByTagLookup(traits.root_node_tag)) {
      LOG(ERROR) << GetRootNodeDoesNotExistError(traits.root_node_tag);
      return false;
    }
    sync_api::WriteNode create_node(trans);
    if (!create_node.InitUniqueByCreation(traits.model_type, root, id)) {
      LOG(ERROR) << "Could not create node for extension " << id;
      return false;
    }
    (*traits.extension_specifics_setter)(specifics, &create_node);
  }
  bool old_client_needs_update =
      extension_data->NeedsUpdate(ExtensionData::CLIENT);
  extension_data->ResolveData(ExtensionData::SERVER);
  DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER));
  DCHECK_EQ(extension_data->NeedsUpdate(ExtensionData::CLIENT),
            old_client_needs_update);
  return true;
}

}  // namespace

bool FlushExtensionData(const ExtensionSyncTraits& traits,
                        const ExtensionDataMap& extension_data_map,
                        ExtensionServiceInterface* extensions_service,
                        sync_api::UserShare* user_share) {
  sync_api::WriteTransaction trans(user_share);
  sync_api::ReadNode root(&trans);
  if (!root.InitByTagLookup(traits.root_node_tag)) {
    LOG(ERROR) << GetRootNodeDoesNotExistError(traits.root_node_tag);
    return false;
  }

  // Update server and client as necessary.
  for (ExtensionDataMap::const_iterator it = extension_data_map.begin();
       it != extension_data_map.end(); ++it) {
    ExtensionData extension_data = it->second;
    // Update server first.
    if (extension_data.NeedsUpdate(ExtensionData::SERVER)) {
      if (!UpdateServer(traits, &extension_data, &trans)) {
        LOG(ERROR) << "Could not update server data for extension "
                   << it->first;
        return false;
      }
    }
    DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER));
    ExtensionSyncData sync_data;
    if (!GetExtensionSyncData(extension_data.merged_data(), &sync_data)) {
      // TODO(akalin): Should probably recover or drop.
      NOTREACHED();
      return false;
    }
    extensions_service->ProcessSyncData(sync_data,
                                        traits.is_valid_and_syncable);
  }
  return true;
}

bool UpdateServerData(const ExtensionSyncTraits& traits,
                      const Extension& extension,
                      const ExtensionServiceInterface& extensions_service,
                      sync_api::UserShare* user_share,
                      std::string* error) {
  const std::string& id = extension.id();
  if (!traits.is_valid_and_syncable(extension)) {
    *error =
        std::string("UpdateServerData() called for invalid or "
                    "unsyncable extension ") + id;
    LOG(DFATAL) << *error;
    return false;
  }

  sync_pb::ExtensionSpecifics client_data;
  GetExtensionSpecifics(extension, extensions_service,
                        &client_data);
  DcheckIsExtensionSpecificsValid(client_data);
  ExtensionData extension_data =
      ExtensionData::FromData(ExtensionData::CLIENT, client_data);

  sync_api::WriteTransaction trans(user_share);

  sync_api::ReadNode node(&trans);
  if (node.InitByClientTagLookup(traits.model_type, id)) {
    sync_pb::ExtensionSpecifics server_data =
        (*traits.extension_specifics_getter)(node);
    if (IsExtensionSpecificsValid(server_data)) {
      // If server node exists and is valid, update |extension_data|
      // from it (but with it taking precedence).
      extension_data =
          ExtensionData::FromData(ExtensionData::SERVER, server_data);
      extension_data.SetData(ExtensionData::CLIENT, true, client_data);
    } else {
      LOG(ERROR) << "Invalid extensions specifics for id " << id
                 << "; treating as empty";
    }
  }

  if (extension_data.NeedsUpdate(ExtensionData::SERVER)) {
    if (!UpdateServer(traits, &extension_data, &trans)) {
      *error =
          std::string("Could not update server data for extension ") + id;
      LOG(ERROR) << *error;
      return false;
    }
  }
  DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER));
  // Client may still need updating, e.g. if we disable an extension
  // while it's being auto-updated.  If so, then we'll be called
  // again once the auto-update is finished.
  //
  // TODO(akalin): Figure out a way to tell when the above happens,
  // so we know exactly what NeedsUpdate(CLIENT) should return.
  return true;
}

void RemoveServerData(const ExtensionSyncTraits& traits,
                      const std::string& id,
                      sync_api::UserShare* user_share) {
  sync_api::WriteTransaction trans(user_share);
  sync_api::WriteNode write_node(&trans);
  if (write_node.InitByClientTagLookup(traits.model_type, id)) {
    write_node.Remove();
  } else {
    LOG(ERROR) << "Server data does not exist for extension " << id;
  }
}

}  // namespace browser_sync