preference_change_processor.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
1// Copyright (c) 2010 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/preference_change_processor.h"
6
7#include <set>
8#include <string>
9
10#include "base/json/json_reader.h"
11#include "base/utf_string_conversions.h"
12#include "chrome/browser/chrome_thread.h"
13#include "chrome/browser/profile.h"
14#include "chrome/browser/sync/glue/preference_model_associator.h"
15#include "chrome/browser/sync/profile_sync_service.h"
16#include "chrome/browser/sync/protocol/preference_specifics.pb.h"
17#include "chrome/common/json_value_serializer.h"
18#include "chrome/common/notification_service.h"
19#include "chrome/common/pref_names.h"
20
21namespace browser_sync {
22
23PreferenceChangeProcessor::PreferenceChangeProcessor(
24    PreferenceModelAssociator* model_associator,
25    UnrecoverableErrorHandler* error_handler)
26    : ChangeProcessor(error_handler),
27      pref_service_(NULL),
28      model_associator_(model_associator) {
29  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
30  DCHECK(model_associator);
31  DCHECK(error_handler);
32}
33
34PreferenceChangeProcessor::~PreferenceChangeProcessor() {
35  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
36}
37
38void PreferenceChangeProcessor::Observe(NotificationType type,
39                                        const NotificationSource& source,
40                                        const NotificationDetails& details) {
41  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
42  DCHECK(running());
43  DCHECK(NotificationType::PREF_CHANGED == type);
44  DCHECK_EQ(pref_service_, Source<PrefService>(source).ptr());
45
46  std::wstring* name = Details<std::wstring>(details).ptr();
47  const PrefService::Preference* preference =
48      pref_service_->FindPreference((*name).c_str());
49  DCHECK(preference);
50
51  // TODO(mnissler): Detect preference->IsUserControlled() state changes here
52  // and call into PreferenceModelAssociator to associate/disassociate sync
53  // nodes when the state changes.
54
55  // Do not pollute sync data with values coming from policy, extensions or the
56  // commandline.
57  if (!preference->IsUserModifiable())
58    return;
59
60  sync_api::WriteTransaction trans(share_handle());
61  sync_api::WriteNode node(&trans);
62
63  // Since we don't create sync nodes for preferences that still have
64  // their default values, this changed preference may not have a sync
65  // node yet.  If not, create it.
66  int64 sync_id = model_associator_->GetSyncIdFromChromeId(*name);
67  if (sync_api::kInvalidId == sync_id) {
68    sync_api::ReadNode root(&trans);
69    if (!root.InitByTagLookup(browser_sync::kPreferencesTag)) {
70      error_handler()->OnUnrecoverableError(FROM_HERE, "Can't find root.");
71      return;
72    }
73
74    std::string tag = WideToUTF8(*name);
75    if (!node.InitUniqueByCreation(syncable::PREFERENCES, root, tag)) {
76      error_handler()->OnUnrecoverableError(
77          FROM_HERE,
78          "Failed to create preference sync node.");
79      return;
80    }
81
82    model_associator_->Associate(preference, node.GetId());
83  } else {
84    if (!node.InitByIdLookup(sync_id)) {
85      error_handler()->OnUnrecoverableError(FROM_HERE,
86                                            "Preference node lookup failed.");
87      return;
88    }
89  }
90
91  if (!PreferenceModelAssociator::WritePreferenceToNode(
92          preference->name(),
93          *preference->GetValue(),
94          &node)) {
95    error_handler()->OnUnrecoverableError(FROM_HERE,
96                                          "Failed to update preference node.");
97    return;
98  }
99}
100
101void PreferenceChangeProcessor::ApplyChangesFromSyncModel(
102    const sync_api::BaseTransaction* trans,
103    const sync_api::SyncManager::ChangeRecord* changes,
104    int change_count) {
105  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
106  if (!running())
107    return;
108  StopObserving();
109
110  for (int i = 0; i < change_count; ++i) {
111    sync_api::ReadNode node(trans);
112    // TODO(ncarter): Can't look up the name for deletions: lookup of
113    // deleted items fails at the syncapi layer.  However, the node should
114    // generally still exist in the syncable database; we just need to
115    // plumb the syncapi so that it succeeds.
116    if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE ==
117        changes[i].action) {
118      // Until the above is fixed, we have no choice but to ignore deletions.
119      LOG(ERROR) << "No way to handle pref deletion";
120      continue;
121    }
122
123    if (!node.InitByIdLookup(changes[i].id)) {
124      error_handler()->OnUnrecoverableError(FROM_HERE,
125                                            "Preference node lookup failed.");
126      return;
127    }
128    DCHECK(syncable::PREFERENCES == node.GetModelType());
129
130    std::wstring name;
131    scoped_ptr<Value> value(ReadPreference(&node, &name));
132    // Skip values we can't deserialize.
133    if (!value.get())
134      continue;
135
136    // It is possible that we may receive a change to a preference we
137    // do not want to sync.  For example if the user is syncing a Mac
138    // client and a Windows client, the Windows client does not
139    // support kShowPageOptionsButtons.  Ignore updates from these
140    // preferences.
141    const wchar_t* pref_name = name.c_str();
142    if (model_associator_->synced_preferences().count(pref_name) == 0)
143      continue;
144
145    // Don't try to overwrite preferences not controllable by the user.
146    const PrefService::Preference* pref =
147        pref_service_->FindPreference(pref_name);
148    DCHECK(pref);
149    if (!pref->IsUserModifiable())
150      continue;
151
152    if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE ==
153        changes[i].action) {
154      pref_service_->ClearPref(pref_name);
155    } else {
156      pref_service_->Set(pref_name, *value);
157
158      // If this is a newly added node, associate.
159      if (sync_api::SyncManager::ChangeRecord::ACTION_ADD ==
160          changes[i].action) {
161        const PrefService::Preference* preference =
162            pref_service_->FindPreference(name.c_str());
163        model_associator_->Associate(preference, changes[i].id);
164      }
165
166      model_associator_->AfterUpdateOperations(name);
167    }
168  }
169  StartObserving();
170}
171
172Value* PreferenceChangeProcessor::ReadPreference(
173    sync_api::ReadNode* node,
174    std::wstring* name) {
175  const sync_pb::PreferenceSpecifics& preference(
176      node->GetPreferenceSpecifics());
177  base::JSONReader reader;
178  scoped_ptr<Value> value(reader.JsonToValue(preference.value(), false, false));
179  if (!value.get()) {
180    std::string err = "Failed to deserialize preference value: " +
181        reader.GetErrorMessage();
182    error_handler()->OnUnrecoverableError(FROM_HERE, err);
183    return NULL;
184  }
185  *name = UTF8ToWide(preference.name());
186  return value.release();
187}
188
189void PreferenceChangeProcessor::StartImpl(Profile* profile) {
190  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
191  pref_service_ = profile->GetPrefs();
192  StartObserving();
193}
194
195void PreferenceChangeProcessor::StopImpl() {
196  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
197  StopObserving();
198  pref_service_ = NULL;
199}
200
201
202void PreferenceChangeProcessor::StartObserving() {
203  DCHECK(pref_service_);
204  for (std::set<std::wstring>::const_iterator it =
205      model_associator_->synced_preferences().begin();
206      it != model_associator_->synced_preferences().end(); ++it) {
207    pref_service_->AddPrefObserver((*it).c_str(), this);
208  }
209}
210
211void PreferenceChangeProcessor::StopObserving() {
212  DCHECK(pref_service_);
213  for (std::set<std::wstring>::const_iterator it =
214      model_associator_->synced_preferences().begin();
215      it != model_associator_->synced_preferences().end(); ++it) {
216    pref_service_->RemovePrefObserver((*it).c_str(), this);
217  }
218}
219
220}  // namespace browser_sync
221