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
23 namespace browser_sync {
24
PreferenceChangeProcessor(PreferenceModelAssociator * model_associator,UnrecoverableErrorHandler * error_handler)25 PreferenceChangeProcessor::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
~PreferenceChangeProcessor()37 PreferenceChangeProcessor::~PreferenceChangeProcessor() {
38 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
39 }
40
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)41 void 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
ApplyChangesFromSyncModel(const sync_api::BaseTransaction * trans,const sync_api::SyncManager::ChangeRecord * changes,int change_count)108 void 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
ReadPreference(sync_api::ReadNode * node,std::string * name)179 Value* 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
StartImpl(Profile * profile)196 void PreferenceChangeProcessor::StartImpl(Profile* profile) {
197 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
198 pref_service_ = profile->GetPrefs();
199 registrar_.Init(pref_service_);
200 StartObserving();
201 }
202
StopImpl()203 void PreferenceChangeProcessor::StopImpl() {
204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
205 StopObserving();
206 pref_service_ = NULL;
207 }
208
209
StartObserving()210 void 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
StopObserving()219 void 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