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