// 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