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

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

Powered by Google App Engine
This is Rietveld 408576698