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 |