// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/sync/glue/preference_change_processor.h" #include <set> #include <string> #include "base/auto_reset.h" #include "base/json/json_reader.h" #include "base/utf_string_conversions.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/glue/preference_model_associator.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" #include "chrome/common/pref_names.h" #include "content/browser/browser_thread.h" #include "content/common/json_value_serializer.h" #include "content/common/notification_details.h" #include "content/common/notification_source.h" namespace browser_sync { PreferenceChangeProcessor::PreferenceChangeProcessor( PreferenceModelAssociator* model_associator, UnrecoverableErrorHandler* error_handler) : ChangeProcessor(error_handler), pref_service_(NULL), model_associator_(model_associator), processing_pref_change_(false) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(model_associator); DCHECK(error_handler); } PreferenceChangeProcessor::~PreferenceChangeProcessor() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); } void PreferenceChangeProcessor::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(running()); DCHECK(NotificationType::PREF_CHANGED == type); DCHECK_EQ(pref_service_, Source<PrefService>(source).ptr()); // Avoid recursion. if (processing_pref_change_) return; AutoReset<bool> guard(&processing_pref_change_, true); std::string* name = Details<std::string>(details).ptr(); const PrefService::Preference* preference = pref_service_->FindPreference((*name).c_str()); DCHECK(preference); int64 sync_id = model_associator_->GetSyncIdFromChromeId(*name); bool user_modifiable = preference->IsUserModifiable(); if (!user_modifiable) { // We do not track preferences the user cannot change. model_associator_->Disassociate(sync_id); return; } sync_api::WriteTransaction trans(share_handle()); sync_api::WriteNode node(&trans); // Since we don't create sync nodes for preferences that are not under control // of the user or still have their default value, this changed preference may // not have a sync node yet. If so, we create a node. Similarly, a preference // may become user-modifiable (e.g. due to laxer policy configuration), in // which case we also need to create a sync node and associate it. if (sync_api::kInvalidId == sync_id) { sync_api::ReadNode root(&trans); if (!root.InitByTagLookup(browser_sync::kPreferencesTag)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Can't find root."); return; } // InitPrefNodeAndAssociate takes care of writing the value to the sync // node if appropriate. if (!model_associator_->InitPrefNodeAndAssociate(&trans, root, preference)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Can't create sync node."); } return; } if (!node.InitByIdLookup(sync_id)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Preference node lookup failed."); return; } if (!PreferenceModelAssociator::WritePreferenceToNode( preference->name(), *preference->GetValue(), &node)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Failed to update preference node."); return; } } void PreferenceChangeProcessor::ApplyChangesFromSyncModel( const sync_api::BaseTransaction* trans, const sync_api::SyncManager::ChangeRecord* changes, int change_count) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!running()) return; StopObserving(); for (int i = 0; i < change_count; ++i) { sync_api::ReadNode node(trans); // TODO(ncarter): Can't look up the name for deletions: lookup of // deleted items fails at the syncapi layer. However, the node should // generally still exist in the syncable database; we just need to // plumb the syncapi so that it succeeds. if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == changes[i].action) { // Until the above is fixed, we have no choice but to ignore deletions. LOG(ERROR) << "No way to handle pref deletion"; continue; } if (!node.InitByIdLookup(changes[i].id)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Preference node lookup failed."); return; } DCHECK(syncable::PREFERENCES == node.GetModelType()); std::string name; scoped_ptr<Value> value(ReadPreference(&node, &name)); // Skip values we can't deserialize. if (!value.get()) continue; // It is possible that we may receive a change to a preference we // do not want to sync. For example if the user is syncing a Mac // client and a Windows client, the Windows client does not // support kConfirmToQuitEnabled. Ignore updates from these // preferences. const char* pref_name = name.c_str(); if (model_associator_->synced_preferences().count(pref_name) == 0) continue; // Don't try to overwrite preferences not controllable by the user. const PrefService::Preference* pref = pref_service_->FindPreference(pref_name); DCHECK(pref); if (!pref->IsUserModifiable()) continue; if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == changes[i].action) { pref_service_->ClearPref(pref_name); } else { pref_service_->Set(pref_name, *value); // If this is a newly added node, associate. if (sync_api::SyncManager::ChangeRecord::ACTION_ADD == changes[i].action) { const PrefService::Preference* preference = pref_service_->FindPreference(name.c_str()); model_associator_->Associate(preference, changes[i].id); } model_associator_->AfterUpdateOperations(name); } } StartObserving(); } Value* PreferenceChangeProcessor::ReadPreference( sync_api::ReadNode* node, std::string* name) { const sync_pb::PreferenceSpecifics& preference( node->GetPreferenceSpecifics()); base::JSONReader reader; scoped_ptr<Value> value(reader.JsonToValue(preference.value(), false, false)); if (!value.get()) { std::string err = "Failed to deserialize preference value: " + reader.GetErrorMessage(); error_handler()->OnUnrecoverableError(FROM_HERE, err); return NULL; } *name = preference.name(); return value.release(); } void PreferenceChangeProcessor::StartImpl(Profile* profile) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); pref_service_ = profile->GetPrefs(); registrar_.Init(pref_service_); StartObserving(); } void PreferenceChangeProcessor::StopImpl() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); StopObserving(); pref_service_ = NULL; } void PreferenceChangeProcessor::StartObserving() { DCHECK(pref_service_); for (std::set<std::string>::const_iterator it = model_associator_->synced_preferences().begin(); it != model_associator_->synced_preferences().end(); ++it) { registrar_.Add((*it).c_str(), this); } } void PreferenceChangeProcessor::StopObserving() { DCHECK(pref_service_); for (std::set<std::string>::const_iterator it = model_associator_->synced_preferences().begin(); it != model_associator_->synced_preferences().end(); ++it) { registrar_.Remove((*it).c_str(), this); } } } // namespace browser_sync