| Index: chrome/browser/sync/glue/extension_sync.cc
|
| diff --git a/chrome/browser/sync/glue/extension_sync.cc b/chrome/browser/sync/glue/extension_sync.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..3b31ab9e53707280281c38ebf3490025ca204b25
|
| --- /dev/null
|
| +++ b/chrome/browser/sync/glue/extension_sync.cc
|
| @@ -0,0 +1,495 @@
|
| +// Copyright (c) 2010 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/sync/glue/extension_sync.h"
|
| +
|
| +#include <utility>
|
| +
|
| +#include "base/logging.h"
|
| +#include "chrome/browser/extensions/extension_updater.h"
|
| +#include "chrome/browser/extensions/extensions_service.h"
|
| +#include "chrome/browser/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/profile_sync_service.h"
|
| +
|
| +namespace browser_sync {
|
| +
|
| +bool RootNodeHasChildren(const char* tag,
|
| + ProfileSyncService* sync_service,
|
| + bool* has_children) {
|
| + CHECK(has_children);
|
| + *has_children = false;
|
| + sync_api::ReadTransaction trans(
|
| + sync_service->backend()->GetUserShareHandle());
|
| + 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;
|
| +}
|
| +
|
| +ExtensionsService* GetExtensionsServiceFromProfile(
|
| + Profile* profile) {
|
| + CHECK(profile);
|
| + ExtensionsService* extensions_service = profile->GetExtensionsService();
|
| + CHECK(extensions_service);
|
| + return extensions_service;
|
| +}
|
| +
|
| +namespace {
|
| +
|
| +ExtensionsService* GetExtensionsServiceFromProfileSyncService(
|
| + ProfileSyncService* sync_service) {
|
| + CHECK(sync_service);
|
| + return GetExtensionsServiceFromProfile(sync_service->profile());
|
| +}
|
| +
|
| +// 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,
|
| + const ExtensionTypeSet& allowed_extension_types,
|
| + ExtensionsService* 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 (IsExtensionValidAndSyncable(extension, allowed_extension_types)) {
|
| + 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(
|
| + const ExtensionTypeSet& allowed_extension_types,
|
| + ExtensionsService* extensions_service,
|
| + std::set<std::string>* unsynced_extensions,
|
| + ExtensionDataMap* extension_data_map) {
|
| + const ExtensionList* extensions = extensions_service->extensions();
|
| + CHECK(extensions);
|
| + ReadClientDataFromExtensionList(
|
| + *extensions, allowed_extension_types, extensions_service,
|
| + unsynced_extensions, extension_data_map);
|
| +
|
| + const ExtensionList* disabled_extensions =
|
| + extensions_service->disabled_extensions();
|
| + CHECK(disabled_extensions);
|
| + ReadClientDataFromExtensionList(
|
| + *disabled_extensions, allowed_extension_types, 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,
|
| + ProfileSyncService* sync_service,
|
| + ExtensionDataMap* extension_data_map) {
|
| + sync_api::WriteTransaction trans(
|
| + sync_service->backend()->GetUserShareHandle());
|
| + 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,
|
| + ProfileSyncService* sync_service,
|
| + ExtensionDataMap* extension_data_map) {
|
| + ExtensionsService* extensions_service =
|
| + GetExtensionsServiceFromProfileSyncService(sync_service);
|
| + 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.allowed_extension_types, extensions_service,
|
| + &unsynced_extensions, extension_data_map);
|
| +
|
| + if (!SlurpServerData(
|
| + traits.root_node_tag, traits.extension_specifics_getter,
|
| + unsynced_extensions, sync_service, 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;
|
| +}
|
| +
|
| +// Tries to update the client data from the given extension data.
|
| +// extension_data->ServerNeedsUpdate() must not hold and
|
| +// extension_data->ClientNeedsUpdate() must hold before this function
|
| +// is called. If the update was successful,
|
| +// extension_data->ClientNeedsUpdate() will be false after this
|
| +// function is called. Otherwise, the extension needs updating to a
|
| +// new version.
|
| +void TryUpdateClient(
|
| + const ExtensionTypeSet& allowed_extension_types,
|
| + ExtensionsService* extensions_service,
|
| + ExtensionData* extension_data) {
|
| + DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER));
|
| + DCHECK(extension_data->NeedsUpdate(ExtensionData::CLIENT));
|
| + const sync_pb::ExtensionSpecifics& specifics =
|
| + extension_data->merged_data();
|
| + DcheckIsExtensionSpecificsValid(specifics);
|
| + const std::string& id = specifics.id();
|
| + Extension* extension = extensions_service->GetExtensionById(id, true);
|
| + if (extension) {
|
| + if (!IsExtensionValidAndSyncable(*extension, allowed_extension_types)) {
|
| + LOG(DFATAL) << "TryUpdateClient() called for non-syncable extension "
|
| + << extension->id();
|
| + return;
|
| + }
|
| + SetExtensionProperties(specifics, extensions_service, extension);
|
| + {
|
| + sync_pb::ExtensionSpecifics extension_specifics;
|
| + GetExtensionSpecifics(*extension, extensions_service,
|
| + &extension_specifics);
|
| + DCHECK(AreExtensionSpecificsUserPropertiesEqual(
|
| + specifics, extension_specifics))
|
| + << ExtensionSpecificsToString(specifics) << ", "
|
| + << ExtensionSpecificsToString(extension_specifics);
|
| + }
|
| + if (!IsExtensionOutdated(*extension, specifics)) {
|
| + extension_data->ResolveData(ExtensionData::CLIENT);
|
| + DCHECK(!extension_data->NeedsUpdate(ExtensionData::CLIENT));
|
| + }
|
| + } else {
|
| + GURL update_url(specifics.update_url());
|
| + // TODO(akalin): Replace silent update with a list of enabled
|
| + // permissions.
|
| + extensions_service->AddPendingExtension(
|
| + id, update_url, false, true,
|
| + specifics.enabled(), specifics.incognito_enabled());
|
| + }
|
| + DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER));
|
| +}
|
| +
|
| +// Kick off a run of the extension updater.
|
| +//
|
| +// TODO(akalin): Combine this with the similar function in
|
| +// theme_util.cc.
|
| +void NudgeExtensionUpdater(ExtensionsService* extensions_service) {
|
| + ExtensionUpdater* extension_updater = extensions_service->updater();
|
| + // Auto-updates should now be on always (see the construction of the
|
| + // ExtensionsService in ProfileImpl::InitExtensions()).
|
| + if (extension_updater) {
|
| + extension_updater->CheckNow();
|
| + } else {
|
| + LOG(DFATAL) << "Extension updater unexpectedly NULL; "
|
| + << "auto-updates may be turned off";
|
| + }
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +bool FlushExtensionData(const ExtensionSyncTraits& traits,
|
| + const ExtensionDataMap& extension_data_map,
|
| + ProfileSyncService* sync_service) {
|
| + sync_api::WriteTransaction trans(
|
| + sync_service->backend()->GetUserShareHandle());
|
| + sync_api::ReadNode root(&trans);
|
| + if (!root.InitByTagLookup(traits.root_node_tag)) {
|
| + LOG(ERROR) << GetRootNodeDoesNotExistError(traits.root_node_tag);
|
| + return false;
|
| + }
|
| +
|
| + ExtensionsService* extensions_service =
|
| + GetExtensionsServiceFromProfileSyncService(sync_service);
|
| +
|
| + // Update server and client as necessary.
|
| + bool should_nudge_extension_updater = false;
|
| + 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));
|
| + if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) {
|
| + TryUpdateClient(traits.allowed_extension_types,
|
| + extensions_service, &extension_data);
|
| + if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) {
|
| + should_nudge_extension_updater = true;
|
| + }
|
| + }
|
| + DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER));
|
| + }
|
| +
|
| + if (should_nudge_extension_updater) {
|
| + NudgeExtensionUpdater(extensions_service);
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool UpdateServerData(const ExtensionSyncTraits& traits,
|
| + const Extension& extension,
|
| + ProfileSyncService* sync_service,
|
| + std::string* error) {
|
| + const std::string& id = extension.id();
|
| + if (!IsExtensionValidAndSyncable(extension,
|
| + traits.allowed_extension_types)) {
|
| + *error =
|
| + std::string("UpdateServerData() called for invalid or "
|
| + "unsyncable extension ") + id;
|
| + LOG(DFATAL) << *error;
|
| + return false;
|
| + }
|
| +
|
| + ExtensionsService* extensions_service =
|
| + GetExtensionsServiceFromProfileSyncService(sync_service);
|
| + 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(
|
| + sync_service->backend()->GetUserShareHandle());
|
| +
|
| + 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 Extension& extension,
|
| + ProfileSyncService* sync_service) {
|
| + const std::string& id = extension.id();
|
| + if (!IsExtensionValidAndSyncable(extension,
|
| + traits.allowed_extension_types)) {
|
| + LOG(DFATAL) << "RemoveServerData() called for invalid or "
|
| + << "unsyncable extension " << id;
|
| + return;
|
| + }
|
| +
|
| + sync_api::WriteTransaction trans(
|
| + sync_service->backend()->GetUserShareHandle());
|
| + 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;
|
| + }
|
| +}
|
| +
|
| +void UpdateClient(const ExtensionSyncTraits& traits,
|
| + const sync_pb::ExtensionSpecifics& server_data,
|
| + ExtensionsService* extensions_service) {
|
| + DcheckIsExtensionSpecificsValid(server_data);
|
| + ExtensionData extension_data =
|
| + ExtensionData::FromData(ExtensionData::SERVER, server_data);
|
| + Extension* extension =
|
| + extensions_service->GetExtensionById(server_data.id(), true);
|
| + if (extension) {
|
| + if (!IsExtensionValidAndSyncable(
|
| + *extension, traits.allowed_extension_types)) {
|
| + LOG(WARNING) << "Ignoring server data for invalid or "
|
| + << "non-syncable extension " << extension->id();
|
| + return;
|
| + }
|
| + sync_pb::ExtensionSpecifics client_data;
|
| + GetExtensionSpecifics(*extension, extensions_service, &client_data);
|
| + DcheckIsExtensionSpecificsValid(client_data);
|
| + extension_data =
|
| + ExtensionData::FromData(ExtensionData::CLIENT, client_data);
|
| + extension_data.SetData(ExtensionData::SERVER, true, server_data);
|
| + }
|
| + DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER));
|
| + if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) {
|
| + TryUpdateClient(traits.allowed_extension_types,
|
| + extensions_service, &extension_data);
|
| + if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) {
|
| + NudgeExtensionUpdater(extensions_service);
|
| + }
|
| + }
|
| + DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER));
|
| +}
|
| +
|
| +void RemoveFromClient(const ExtensionSyncTraits& traits,
|
| + const std::string& id,
|
| + ExtensionsService* extensions_service) {
|
| + Extension* extension = extensions_service->GetExtensionById(id, true);
|
| + if (extension) {
|
| + if (IsExtensionValidAndSyncable(*extension,
|
| + traits.allowed_extension_types)) {
|
| + extensions_service->UninstallExtension(id, false);
|
| + } else {
|
| + LOG(WARNING) << "Ignoring server data for invalid or "
|
| + << "non-syncable extension " << extension->id();
|
| + }
|
| + } else {
|
| + LOG(ERROR) << "Trying to uninstall nonexistent extension " << id;
|
| + }
|
| +}
|
| +
|
| +} // namespace browser_sync
|
|
|