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