// 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_change_processor.h"
#include <sstream>
#include <string>
#include "base/logging.h"
#include "base/stl_util-inl.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/glue/extension_sync.h"
#include "chrome/browser/sync/glue/extension_util.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
#include "chrome/common/extensions/extension.h"
#include "content/browser/browser_thread.h"
#include "content/common/notification_details.h"
#include "content/common/notification_source.h"
namespace browser_sync {
ExtensionChangeProcessor::ExtensionChangeProcessor(
const ExtensionSyncTraits& traits,
UnrecoverableErrorHandler* error_handler)
: ChangeProcessor(error_handler),
traits_(traits),
profile_(NULL),
extension_service_(NULL),
user_share_(NULL) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(error_handler);
}
ExtensionChangeProcessor::~ExtensionChangeProcessor() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
// TODO(akalin): We need to make sure events we receive from either
// the browser or the syncapi are done in order; this is tricky since
// some events (e.g., extension installation) are done asynchronously.
void ExtensionChangeProcessor::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(running());
DCHECK(profile_);
if ((type != NotificationType::EXTENSION_INSTALLED) &&
(type != NotificationType::EXTENSION_UNINSTALLED) &&
(type != NotificationType::EXTENSION_LOADED) &&
(type != NotificationType::EXTENSION_UPDATE_DISABLED) &&
(type != NotificationType::EXTENSION_UNLOADED)) {
LOG(DFATAL) << "Received unexpected notification of type "
<< type.value;
return;
}
DCHECK_EQ(Source<Profile>(source).ptr(), profile_);
if (type == NotificationType::EXTENSION_UNINSTALLED) {
const UninstalledExtensionInfo* uninstalled_extension_info =
Details<UninstalledExtensionInfo>(details).ptr();
CHECK(uninstalled_extension_info);
if (traits_.should_handle_extension_uninstall(
*uninstalled_extension_info)) {
const std::string& id = uninstalled_extension_info->extension_id;
VLOG(1) << "Removing server data for uninstalled extension " << id
<< " of type " << uninstalled_extension_info->extension_type;
RemoveServerData(traits_, id, user_share_);
}
} else {
const Extension* extension = NULL;
if (type == NotificationType::EXTENSION_UNLOADED) {
extension = Details<UnloadedExtensionInfo>(details)->extension;
} else {
extension = Details<const Extension>(details).ptr();
}
CHECK(extension);
VLOG(1) << "Updating server data for extension " << extension->id()
<< " (notification type = " << type.value << ")";
if (!traits_.is_valid_and_syncable(*extension)) {
return;
}
std::string error;
if (!UpdateServerData(traits_, *extension, *extension_service_,
user_share_, &error)) {
error_handler()->OnUnrecoverableError(FROM_HERE, error);
}
}
}
void ExtensionChangeProcessor::ApplyChangesFromSyncModel(
const sync_api::BaseTransaction* trans,
const sync_api::SyncManager::ChangeRecord* changes,
int change_count) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!running()) {
return;
}
for (int i = 0; i < change_count; ++i) {
const sync_api::SyncManager::ChangeRecord& change = changes[i];
sync_pb::ExtensionSpecifics specifics;
switch (change.action) {
case sync_api::SyncManager::ChangeRecord::ACTION_ADD:
case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: {
sync_api::ReadNode node(trans);
if (!node.InitByIdLookup(change.id)) {
std::stringstream error;
error << "Extension node lookup failed for change " << change.id
<< " of action type " << change.action;
error_handler()->OnUnrecoverableError(FROM_HERE, error.str());
return;
}
DCHECK_EQ(node.GetModelType(), traits_.model_type);
specifics = (*traits_.extension_specifics_getter)(node);
break;
}
case sync_api::SyncManager::ChangeRecord::ACTION_DELETE: {
if (!(*traits_.extension_specifics_entity_getter)(
change.specifics, &specifics)) {
std::stringstream error;
error << "Could not get extension specifics from deleted node "
<< change.id;
error_handler()->OnUnrecoverableError(FROM_HERE, error.str());
LOG(DFATAL) << error.str();
}
break;
}
}
ExtensionSyncData sync_data;
if (!GetExtensionSyncData(specifics, &sync_data)) {
// TODO(akalin): Should probably recover or drop.
std::string error =
std::string("Invalid server specifics: ") +
ExtensionSpecificsToString(specifics);
error_handler()->OnUnrecoverableError(FROM_HERE, error);
return;
}
sync_data.uninstalled =
(change.action == sync_api::SyncManager::ChangeRecord::ACTION_DELETE);
StopObserving();
extension_service_->ProcessSyncData(sync_data,
traits_.is_valid_and_syncable);
StartObserving();
}
}
void ExtensionChangeProcessor::StartImpl(Profile* profile) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
profile_ = profile;
extension_service_ = profile_->GetExtensionService();
user_share_ = profile_->GetProfileSyncService()->GetUserShare();
DCHECK(profile_);
DCHECK(extension_service_);
DCHECK(user_share_);
StartObserving();
}
void ExtensionChangeProcessor::StopImpl() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
StopObserving();
profile_ = NULL;
extension_service_ = NULL;
user_share_ = NULL;
}
void ExtensionChangeProcessor::StartObserving() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(profile_);
notification_registrar_.Add(
this, NotificationType::EXTENSION_INSTALLED,
Source<Profile>(profile_));
notification_registrar_.Add(
this, NotificationType::EXTENSION_UNINSTALLED,
Source<Profile>(profile_));
notification_registrar_.Add(
this, NotificationType::EXTENSION_LOADED,
Source<Profile>(profile_));
// Despite the name, this notification is exactly like
// EXTENSION_LOADED but with an initial state of DISABLED.
notification_registrar_.Add(
this, NotificationType::EXTENSION_UPDATE_DISABLED,
Source<Profile>(profile_));
notification_registrar_.Add(
this, NotificationType::EXTENSION_UNLOADED,
Source<Profile>(profile_));
}
void ExtensionChangeProcessor::StopObserving() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(profile_);
VLOG(1) << "Unobserving all notifications";
notification_registrar_.RemoveAll();
}
} // namespace browser_sync