extension_change_processor.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/sync/glue/extension_change_processor.h"
6
7#include <sstream>
8#include <string>
9
10#include "base/logging.h"
11#include "base/stl_util-inl.h"
12#include "chrome/browser/extensions/extension_service.h"
13#include "chrome/browser/extensions/extension_sync_data.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/sync/glue/extension_sync.h"
16#include "chrome/browser/sync/glue/extension_util.h"
17#include "chrome/browser/sync/profile_sync_service.h"
18#include "chrome/browser/sync/protocol/extension_specifics.pb.h"
19#include "chrome/common/extensions/extension.h"
20#include "content/browser/browser_thread.h"
21#include "content/common/notification_details.h"
22#include "content/common/notification_source.h"
23
24namespace browser_sync {
25
26ExtensionChangeProcessor::ExtensionChangeProcessor(
27    const ExtensionSyncTraits& traits,
28    UnrecoverableErrorHandler* error_handler)
29    : ChangeProcessor(error_handler),
30      traits_(traits),
31      profile_(NULL),
32      extension_service_(NULL),
33      user_share_(NULL) {
34  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
35  DCHECK(error_handler);
36}
37
38ExtensionChangeProcessor::~ExtensionChangeProcessor() {
39  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
40}
41
42// TODO(akalin): We need to make sure events we receive from either
43// the browser or the syncapi are done in order; this is tricky since
44// some events (e.g., extension installation) are done asynchronously.
45
46void ExtensionChangeProcessor::Observe(NotificationType type,
47                                       const NotificationSource& source,
48                                       const NotificationDetails& details) {
49  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
50  DCHECK(running());
51  DCHECK(profile_);
52  if ((type != NotificationType::EXTENSION_INSTALLED) &&
53      (type != NotificationType::EXTENSION_UNINSTALLED) &&
54      (type != NotificationType::EXTENSION_LOADED) &&
55      (type != NotificationType::EXTENSION_UPDATE_DISABLED) &&
56      (type != NotificationType::EXTENSION_UNLOADED)) {
57    LOG(DFATAL) << "Received unexpected notification of type "
58                << type.value;
59    return;
60  }
61
62  DCHECK_EQ(Source<Profile>(source).ptr(), profile_);
63  if (type == NotificationType::EXTENSION_UNINSTALLED) {
64    const UninstalledExtensionInfo* uninstalled_extension_info =
65        Details<UninstalledExtensionInfo>(details).ptr();
66    CHECK(uninstalled_extension_info);
67    if (traits_.should_handle_extension_uninstall(
68            *uninstalled_extension_info)) {
69      const std::string& id = uninstalled_extension_info->extension_id;
70      VLOG(1) << "Removing server data for uninstalled extension " << id
71              << " of type " << uninstalled_extension_info->extension_type;
72      RemoveServerData(traits_, id, user_share_);
73    }
74  } else {
75    const Extension* extension = NULL;
76    if (type == NotificationType::EXTENSION_UNLOADED) {
77      extension = Details<UnloadedExtensionInfo>(details)->extension;
78    } else {
79      extension = Details<const Extension>(details).ptr();
80    }
81    CHECK(extension);
82    VLOG(1) << "Updating server data for extension " << extension->id()
83            << " (notification type = " << type.value << ")";
84    if (!traits_.is_valid_and_syncable(*extension)) {
85      return;
86    }
87    std::string error;
88    if (!UpdateServerData(traits_, *extension, *extension_service_,
89                          user_share_, &error)) {
90      error_handler()->OnUnrecoverableError(FROM_HERE, error);
91    }
92  }
93}
94
95void ExtensionChangeProcessor::ApplyChangesFromSyncModel(
96    const sync_api::BaseTransaction* trans,
97    const sync_api::SyncManager::ChangeRecord* changes,
98    int change_count) {
99  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
100  if (!running()) {
101    return;
102  }
103  for (int i = 0; i < change_count; ++i) {
104    const sync_api::SyncManager::ChangeRecord& change = changes[i];
105    sync_pb::ExtensionSpecifics specifics;
106    switch (change.action) {
107      case sync_api::SyncManager::ChangeRecord::ACTION_ADD:
108      case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: {
109        sync_api::ReadNode node(trans);
110        if (!node.InitByIdLookup(change.id)) {
111          std::stringstream error;
112          error << "Extension node lookup failed for change " << change.id
113                << " of action type " << change.action;
114          error_handler()->OnUnrecoverableError(FROM_HERE, error.str());
115          return;
116        }
117        DCHECK_EQ(node.GetModelType(), traits_.model_type);
118        specifics = (*traits_.extension_specifics_getter)(node);
119        break;
120      }
121      case sync_api::SyncManager::ChangeRecord::ACTION_DELETE: {
122        if (!(*traits_.extension_specifics_entity_getter)(
123                change.specifics, &specifics)) {
124          std::stringstream error;
125          error << "Could not get extension specifics from deleted node "
126                << change.id;
127          error_handler()->OnUnrecoverableError(FROM_HERE, error.str());
128          LOG(DFATAL) << error.str();
129        }
130        break;
131      }
132    }
133    ExtensionSyncData sync_data;
134    if (!GetExtensionSyncData(specifics, &sync_data)) {
135      // TODO(akalin): Should probably recover or drop.
136      std::string error =
137          std::string("Invalid server specifics: ") +
138          ExtensionSpecificsToString(specifics);
139      error_handler()->OnUnrecoverableError(FROM_HERE, error);
140      return;
141    }
142    sync_data.uninstalled =
143        (change.action == sync_api::SyncManager::ChangeRecord::ACTION_DELETE);
144    StopObserving();
145    extension_service_->ProcessSyncData(sync_data,
146                                        traits_.is_valid_and_syncable);
147    StartObserving();
148  }
149}
150
151void ExtensionChangeProcessor::StartImpl(Profile* profile) {
152  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
153  profile_ = profile;
154  extension_service_ = profile_->GetExtensionService();
155  user_share_ = profile_->GetProfileSyncService()->GetUserShare();
156  DCHECK(profile_);
157  DCHECK(extension_service_);
158  DCHECK(user_share_);
159  StartObserving();
160}
161
162void ExtensionChangeProcessor::StopImpl() {
163  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
164  StopObserving();
165  profile_ = NULL;
166  extension_service_ = NULL;
167  user_share_ = NULL;
168}
169
170void ExtensionChangeProcessor::StartObserving() {
171  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
172  DCHECK(profile_);
173  notification_registrar_.Add(
174      this, NotificationType::EXTENSION_INSTALLED,
175      Source<Profile>(profile_));
176  notification_registrar_.Add(
177      this, NotificationType::EXTENSION_UNINSTALLED,
178      Source<Profile>(profile_));
179
180  notification_registrar_.Add(
181      this, NotificationType::EXTENSION_LOADED,
182      Source<Profile>(profile_));
183  // Despite the name, this notification is exactly like
184  // EXTENSION_LOADED but with an initial state of DISABLED.
185  notification_registrar_.Add(
186      this, NotificationType::EXTENSION_UPDATE_DISABLED,
187      Source<Profile>(profile_));
188
189  notification_registrar_.Add(
190      this, NotificationType::EXTENSION_UNLOADED,
191      Source<Profile>(profile_));
192}
193
194void ExtensionChangeProcessor::StopObserving() {
195  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
196  DCHECK(profile_);
197  VLOG(1) << "Unobserving all notifications";
198  notification_registrar_.RemoveAll();
199}
200
201}  // namespace browser_sync
202