Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(53)

Side by Side Diff: chrome/browser/prefs/pref_model_associator.cc

Issue 6905044: Refactor preference syncing. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Self review Created 9 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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/prefs/pref_model_associator.h"
6
7 #include "base/auto_reset.h"
8 #include "base/json/json_reader.h"
9 #include "base/logging.h"
10 #include "base/utf_string_conversions.h"
11 #include "base/values.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/sync/engine/syncapi.h"
14 #include "chrome/browser/sync/glue/generic_change_processor.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_service.h"
21
22 PrefModelAssociator::PrefModelAssociator()
23 : pref_service_(NULL),
24 sync_service_(NULL),
25 models_associated_(false),
26 processing_syncer_changes_(false),
27 processing_syncapi_changes_(false),
28 change_processor_(NULL) {
29 }
30
31 PrefModelAssociator::PrefModelAssociator(
32 PrefService* pref_service)
33 : pref_service_(pref_service),
34 sync_service_(NULL),
35 models_associated_(false),
36 processing_syncer_changes_(false),
37 processing_syncapi_changes_(false),
38 change_processor_(NULL) {
39 DCHECK(CalledOnValidThread());
40 }
41
42 PrefModelAssociator::~PrefModelAssociator() {
43 DCHECK(CalledOnValidThread());
44 change_processor_ = NULL;
45 sync_service_ = NULL;
46 pref_service_ = NULL;
47 }
48
49 bool PrefModelAssociator::InitPrefNodeAndAssociate(
50 sync_api::WriteTransaction* trans,
51 const sync_api::BaseNode& root,
52 const PrefService::Preference* pref) {
53 DCHECK(pref);
54
55 base::JSONReader reader;
56 std::string tag = pref->name();
57 sync_api::WriteNode node(trans);
58 if (node.InitByClientTagLookup(syncable::PREFERENCES, tag)) {
59 // The server has a value for the preference.
60 const sync_pb::PreferenceSpecifics& preference(
61 node.GetPreferenceSpecifics());
62 DCHECK_EQ(tag, preference.name());
63
64 if (!pref->IsUserModifiable()) {
65 Associate(pref, node.GetId());
66 return true;
67 }
68
69 scoped_ptr<Value> value(
70 reader.JsonToValue(preference.value(), false, false));
71 std::string pref_name = preference.name();
72 if (!value.get()) {
73 LOG(ERROR) << "Failed to deserialize preference value: "
74 << reader.GetErrorMessage();
75 return false;
76 }
77
78 // Merge the server value of this preference with the local value.
79 scoped_ptr<Value> new_value(MergePreference(*pref, *value));
80
81 // Update the local preference based on what we got from the
82 // sync server.
83 if (new_value->IsType(Value::TYPE_NULL)) {
84 pref_service_->ClearPref(pref_name.c_str());
85 } else if (!new_value->IsType(pref->GetType())) {
86 LOG(WARNING) << "Synced value for " << preference.name()
87 << " is of type " << new_value->GetType()
88 << " which doesn't match pref type " << pref->GetType();
89 } else if (!pref->GetValue()->Equals(new_value.get())) {
90 pref_service_->Set(pref_name.c_str(), *new_value);
91 }
92
93 SendUpdateNotificationsIfNecessary(pref_name);
94
95 // If the merge resulted in an updated value, write it back to
96 // the sync node.
97 if (!value->Equals(new_value.get()) &&
98 !WritePreferenceToNode(pref->name(), *new_value, &node)) {
99 return false;
100 }
101 Associate(pref, node.GetId());
102 } else if (pref->IsUserControlled()) {
103 // The server doesn't have a value, but we have a user-controlled value,
104 // so we push it to the server.
105 sync_api::WriteNode write_node(trans);
106 if (!write_node.InitUniqueByCreation(syncable::PREFERENCES, root, tag)) {
107 LOG(ERROR) << "Failed to create preference sync node.";
108 return false;
109 }
110
111 // Update the sync node with the local value for this preference.
112 if (!WritePreferenceToNode(pref->name(), *pref->GetValue(), &write_node))
113 return false;
114
115 Associate(pref, write_node.GetId());
116 } else {
117 // This preference is handled by policy, not the user, and therefore
118 // we do not associate it.
119 }
120
121 return true;
122 }
123
124 bool PrefModelAssociator::AssociateModels() {
125 DCHECK(CalledOnValidThread());
126
127 int64 root_id;
128 if (!GetSyncIdForTaggedNode(kPreferencesTag, &root_id)) {
129 LOG(ERROR) << "Server did not create the top-level preferences node. We "
130 << "might be running against an out-of-date server.";
131 return false;
132 }
133
134 sync_api::WriteTransaction trans(sync_service_->GetUserShare());
135 sync_api::ReadNode root(&trans);
136 if (!root.InitByIdLookup(root_id)) {
137 LOG(ERROR) << "Server did not create the top-level preferences node. We "
138 << "might be running against an out-of-date server.";
139 return false;
140 }
141
142 for (std::set<std::string>::iterator it = synced_preferences_.begin();
143 it != synced_preferences_.end(); ++it) {
144 std::string name = *it;
145 const PrefService::Preference* pref =
146 pref_service_->FindPreference(name.c_str());
147 VLOG(1) << "Associating preference " << name;
148 DCHECK(pref);
149 if (!pref->IsUserModifiable())
150 continue; // We don't sync preferences the user cannot change.
151 InitPrefNodeAndAssociate(&trans, root, pref);
152 }
153 models_associated_ = true;
154 return true;
155 }
156
157 bool PrefModelAssociator::DisassociateModels() {
158 id_map_.clear();
159 id_map_inverse_.clear();
160 models_associated_ = false;
161 return true;
162 }
163
164 bool PrefModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
165 DCHECK(has_nodes);
166 *has_nodes = false;
167 int64 preferences_sync_id;
168 if (!GetSyncIdForTaggedNode(kPreferencesTag, &preferences_sync_id)) {
169 LOG(ERROR) << "Server did not create the top-level preferences node. We "
170 << "might be running against an out-of-date server.";
171 return false;
172 }
173 sync_api::ReadTransaction trans(sync_service_->GetUserShare());
174
175 sync_api::ReadNode preferences_node(&trans);
176 if (!preferences_node.InitByIdLookup(preferences_sync_id)) {
177 LOG(ERROR) << "Server did not create the top-level preferences node. We "
178 << "might be running against an out-of-date server.";
179 return false;
180 }
181
182 // The sync model has user created nodes if the preferences folder has any
183 // children.
184 *has_nodes = sync_api::kInvalidId != preferences_node.GetFirstChildId();
185 return true;
186 }
187
188 int64 PrefModelAssociator::GetSyncIdFromChromeId(
189 const std::string& preference_name) {
190 PreferenceNameToSyncIdMap::const_iterator iter =
191 id_map_.find(preference_name);
192 return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
193 }
194
195 void PrefModelAssociator::Associate(
196 const PrefService::Preference* preference, int64 sync_id) {
197 DCHECK(CalledOnValidThread());
198
199 std::string name = preference->name();
200 DCHECK_NE(sync_api::kInvalidId, sync_id);
201 DCHECK_EQ(0U, id_map_.count(name));
202 DCHECK_EQ(0U, id_map_inverse_.count(sync_id));
203 id_map_[name] = sync_id;
204 id_map_inverse_[sync_id] = name;
205 }
206
207 void PrefModelAssociator::Disassociate(int64 sync_id) {
208 DCHECK(CalledOnValidThread());
209 SyncIdToPreferenceNameMap::iterator iter = id_map_inverse_.find(sync_id);
210 if (iter == id_map_inverse_.end())
211 return;
212 id_map_.erase(iter->second);
213 id_map_inverse_.erase(iter);
214 }
215
216 bool PrefModelAssociator::GetSyncIdForTaggedNode(const std::string& tag,
217 int64* sync_id) {
218 sync_api::ReadTransaction trans(sync_service_->GetUserShare());
219 sync_api::ReadNode sync_node(&trans);
220 if (!sync_node.InitByTagLookup(tag.c_str()))
221 return false;
222 *sync_id = sync_node.GetId();
223 return true;
224 }
225
226 Value* PrefModelAssociator::MergePreference(
227 const PrefService::Preference& local_pref,
228 const Value& server_value) {
229 const std::string& name(local_pref.name());
230 if (name == prefs::kURLsToRestoreOnStartup ||
231 name == prefs::kDesktopNotificationAllowedOrigins ||
232 name == prefs::kDesktopNotificationDeniedOrigins) {
233 return MergeListValues(*local_pref.GetValue(), server_value);
234 }
235
236 if (name == prefs::kContentSettingsPatterns ||
237 name == prefs::kGeolocationContentSettings) {
238 return MergeDictionaryValues(*local_pref.GetValue(), server_value);
239 }
240
241 // If this is not a specially handled preference, server wins.
242 return server_value.DeepCopy();
243 }
244
245 bool PrefModelAssociator::WritePreferenceToNode(
246 const std::string& name,
247 const Value& value,
248 sync_api::WriteNode* node) {
249 std::string serialized;
250 JSONStringValueSerializer json(&serialized);
251 if (!json.Serialize(value)) {
252 LOG(ERROR) << "Failed to serialize preference value.";
253 return false;
254 }
255
256 sync_pb::PreferenceSpecifics preference;
257 preference.set_name(name);
258 preference.set_value(serialized);
259 node->SetPreferenceSpecifics(preference);
260 // TODO(viettrungluu): eliminate conversion (it's temporary)
261 node->SetTitle(UTF8ToWide(name));
262 return true;
263 }
264
265 Value* PrefModelAssociator::MergeListValues(const Value& from_value,
266 const Value& to_value) {
267 if (from_value.GetType() == Value::TYPE_NULL)
268 return to_value.DeepCopy();
269 if (to_value.GetType() == Value::TYPE_NULL)
270 return from_value.DeepCopy();
271
272 DCHECK(from_value.GetType() == Value::TYPE_LIST);
273 DCHECK(to_value.GetType() == Value::TYPE_LIST);
274 const ListValue& from_list_value = static_cast<const ListValue&>(from_value);
275 const ListValue& to_list_value = static_cast<const ListValue&>(to_value);
276 ListValue* result = to_list_value.DeepCopy();
277
278 for (ListValue::const_iterator i = from_list_value.begin();
279 i != from_list_value.end(); ++i) {
280 Value* value = (*i)->DeepCopy();
281 result->AppendIfNotPresent(value);
282 }
283 return result;
284 }
285
286 Value* PrefModelAssociator::MergeDictionaryValues(
287 const Value& from_value,
288 const Value& to_value) {
289 if (from_value.GetType() == Value::TYPE_NULL)
290 return to_value.DeepCopy();
291 if (to_value.GetType() == Value::TYPE_NULL)
292 return from_value.DeepCopy();
293
294 DCHECK_EQ(from_value.GetType(), Value::TYPE_DICTIONARY);
295 DCHECK_EQ(to_value.GetType(), Value::TYPE_DICTIONARY);
296 const DictionaryValue& from_dict_value =
297 static_cast<const DictionaryValue&>(from_value);
298 const DictionaryValue& to_dict_value =
299 static_cast<const DictionaryValue&>(to_value);
300 DictionaryValue* result = to_dict_value.DeepCopy();
301
302 for (DictionaryValue::key_iterator key = from_dict_value.begin_keys();
303 key != from_dict_value.end_keys(); ++key) {
304 Value* from_value;
305 bool success = from_dict_value.GetWithoutPathExpansion(*key, &from_value);
306 DCHECK(success);
307
308 Value* to_key_value;
309 if (result->GetWithoutPathExpansion(*key, &to_key_value)) {
310 if (to_key_value->GetType() == Value::TYPE_DICTIONARY) {
311 Value* merged_value = MergeDictionaryValues(*from_value, *to_key_value);
312 result->SetWithoutPathExpansion(*key, merged_value);
313 }
314 // Note that for all other types we want to preserve the "to"
315 // values so we do nothing here.
316 } else {
317 result->SetWithoutPathExpansion(*key, from_value->DeepCopy());
318 }
319 }
320 return result;
321 }
322
323 void PrefModelAssociator::SendUpdateNotificationsIfNecessary(
324 const std::string& pref_name) {
325 // The bookmark bar visibility preference requires a special
326 // notification to update the UI.
327 if (0 == pref_name.compare(prefs::kShowBookmarkBar)) {
328 NotificationService::current()->Notify(
329 NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
330 Source<PrefModelAssociator>(this),
331 NotificationService::NoDetails());
332 }
333 }
334
335 // Not implemented.
336 void PrefModelAssociator::AbortAssociation() {}
337
338 bool PrefModelAssociator::CryptoReadyIfNecessary() {
339 // We only access the cryptographer while holding a transaction.
340 sync_api::ReadTransaction trans(sync_service_->GetUserShare());
341 syncable::ModelTypeSet encrypted_types;
342 sync_service_->GetEncryptedDataTypes(&encrypted_types);
343 return encrypted_types.count(syncable::PREFERENCES) == 0 ||
344 sync_service_->IsCryptographerReady(&trans);
345 }
346
347 void PrefModelAssociator::SetupSync(
348 ProfileSyncService* sync_service,
349 browser_sync::GenericChangeProcessor* change_processor) {
350 sync_service_ = sync_service;
351 change_processor_ = change_processor;
352 }
353
354 void PrefModelAssociator::ApplyChangesFromSync(
355 const sync_api::BaseTransaction* trans,
356 const sync_api::SyncManager::ChangeRecord* changes,
357 int change_count) {
358 if (!models_associated_)
359 return;
360 AutoReset<bool> processing_changes(&processing_syncer_changes_, true);
361 for (int i = 0; i < change_count; ++i) {
362 if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE ==
363 changes[i].action) {
364 // We never delete preferences.
365 NOTREACHED();
366 }
367
368 sync_api::ReadNode node(trans);
369 if (!node.InitByIdLookup(changes[i].id)) {
370 LOG(ERROR) << "Preference node lookup failed.";
371 return;
372 }
373 DCHECK(syncable::PREFERENCES == node.GetModelType());
374 std::string name;
375 sync_pb::PreferenceSpecifics pref_specifics =
376 node.GetPreferenceSpecifics();
377 scoped_ptr<Value> value(ReadPreferenceSpecifics(pref_specifics,
378 &name));
379 // Skip values we can't deserialize.
380 if (!value.get())
381 continue;
382
383 // It is possible that we may receive a change to a preference we do not
384 // want to sync. For example if the user is syncing a Mac client and a
385 // Windows client, the Windows client does not support
386 // kConfirmToQuitEnabled. Ignore updates from these preferences.
387 const char* pref_name = name.c_str();
388 if (!IsPrefRegistered(pref_name))
389 continue;
390
391 const PrefService::Preference* pref =
392 pref_service_->FindPreference(pref_name);
393 DCHECK(pref);
394 if (!pref->IsUserModifiable()) {
395 continue;
396 }
397
398 if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE ==
399 changes[i].action) {
400 pref_service_->ClearPref(pref_name);
401 } else {
402 pref_service_->Set(pref_name, *value);
403
404 // If this is a newly added node, associate.
405 if (sync_api::SyncManager::ChangeRecord::ACTION_ADD ==
406 changes[i].action) {
407 Associate(pref, changes[i].id);
408 }
409
410 SendUpdateNotificationsIfNecessary(name);
411 }
412 }
413 }
414
415 Value* PrefModelAssociator::ReadPreferenceSpecifics(
416 const sync_pb::PreferenceSpecifics& preference,
417 std::string* name) {
418 base::JSONReader reader;
419 scoped_ptr<Value> value(reader.JsonToValue(preference.value(), false, false));
420 if (!value.get()) {
421 std::string err = "Failed to deserialize preference value: " +
422 reader.GetErrorMessage();
423 LOG(ERROR) << err;
424 return NULL;
425 }
426 *name = preference.name();
427 return value.release();
428 }
429
430
431 std::set<std::string> PrefModelAssociator::synced_preferences() const {
432 return synced_preferences_;
433 }
434
435 void PrefModelAssociator::RegisterPref(const char* name) {
436 DCHECK(!models_associated_ && synced_preferences_.count(name) == 0);
437 synced_preferences_.insert(name);
438 }
439
440 bool PrefModelAssociator::IsPrefRegistered(const char* name) {
441 return synced_preferences_.count(name) > 0;
442 }
443
444 void PrefModelAssociator::ProcessPrefChange(const std::string& name) {
445 if (processing_syncer_changes_ || processing_syncapi_changes_)
446 return; // These are changes originating from us, ignore.
447
448 // We only process changes if we've already associated models.
449 if (!sync_service_ || !models_associated_)
450 return;
451
452 const PrefService::Preference* preference =
453 pref_service_->FindPreference(name.c_str());
454 if (!IsPrefRegistered(name.c_str()))
455 return; // We are not syncing this preference.
456
457 // The preference does not have a node. This can happen if the preference
458 // held the default value at association time. Create one and associate.
459 int64 root_id;
460 if (!GetSyncIdForTaggedNode(kPreferencesTag, &root_id)) {
461 LOG(ERROR) << "Server did not create the top-level preferences node. We "
462 << "might be running against an out-of-date server.";
463 return;
464 }
465
466 int64 sync_id = GetSyncIdFromChromeId(name);
467 if (!preference->IsUserModifiable()) {
468 // If the preference is not currently user modifiable, disassociate, so that
469 // if it becomes user modifiable me pick up the server value.
470 Disassociate(sync_id);
471 return;
472 }
473
474 AutoReset<bool> processing_changes(&processing_syncapi_changes_, true);
475 sync_api::WriteTransaction trans(sync_service_->GetUserShare());
476
477 // Since we don't create sync nodes for preferences that are not under control
478 // of the user or still have their default value, this changed preference may
479 // not have a sync node yet. If so, we create a node. Similarly, a preference
480 // may become user-modifiable (e.g. due to laxer policy configuration), in
481 // which case we also need to create a sync node and associate it.
482 if (sync_id == sync_api::kInvalidId) {
483 sync_api::ReadNode root(&trans);
484 if (!root.InitByIdLookup(root_id)) {
485 LOG(ERROR) << "Server did not create the top-level preferences node. We "
486 << "might be running against an out-of-date server.";
487 return;
488 }
489 InitPrefNodeAndAssociate(&trans, root, preference);
490 } else {
491 sync_api::WriteNode node(&trans);
492 if (!node.InitByIdLookup(sync_id)) {
493 LOG(ERROR) << "Preference node lookup failed.";
494 return;
495 }
496
497 if (!WritePreferenceToNode(name, *preference->GetValue(), &node)) {
498 LOG(ERROR) << "Failed to update preference node.";
499 return;
500 }
501 }
502 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698