普通文本  |  229行  |  7.93 KB

// 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