Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

autofill_change_processor.cc

// Copyright (c) 2010 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/autofill_change_processor.h"

#include <string>
#include <vector>

#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/autofill/personal_data_manager.h"
#include "chrome/browser/sync/glue/autofill_model_associator.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/webdata/autofill_change.h"
#include "chrome/browser/webdata/web_data_service.h"
#include "chrome/browser/webdata/web_database.h"
#include "chrome/common/notification_service.h"

typedef sync_api::SyncManager::ExtraAutofillChangeRecordData
    ExtraAutofillChangeRecordData;

namespace browser_sync {

AutofillChangeProcessor::AutofillChangeProcessor(
    AutofillModelAssociator* model_associator,
    WebDatabase* web_database,
    PersonalDataManager* personal_data,
    UnrecoverableErrorHandler* error_handler)
    : ChangeProcessor(error_handler),
      model_associator_(model_associator),
      web_database_(web_database),
      personal_data_(personal_data),
      observing_(false) {
  DCHECK(model_associator);
  DCHECK(web_database);
  DCHECK(error_handler);
  DCHECK(personal_data);
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
  StartObserving();
}

void AutofillChangeProcessor::Observe(NotificationType type,
                                      const NotificationSource& source,
                                      const NotificationDetails& details) {
  LOG(INFO) << "Observed autofill change.";
  DCHECK(running());
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
  if (!observing_) {
    return;
  }

  sync_api::WriteTransaction trans(share_handle());
  sync_api::ReadNode autofill_root(&trans);
  if (!autofill_root.InitByTagLookup(kAutofillTag)) {
    error_handler()->OnUnrecoverableError();
    LOG(ERROR) << "Server did not create the top-level autofill node. "
        << "We might be running against an out-of-date server.";
    return;
  }

  switch (type.value) {
    case NotificationType::AUTOFILL_ENTRIES_CHANGED: {
      AutofillChangeList* changes = Details<AutofillChangeList>(details).ptr();
      ObserveAutofillEntriesChanged(changes, &trans, autofill_root);
      break;
    }
    case NotificationType::AUTOFILL_PROFILE_CHANGED: {
      AutofillProfileChange* change =
          Details<AutofillProfileChange>(details).ptr();
      ObserveAutofillProfileChanged(change, &trans, autofill_root);
      break;
    }
    default:
      NOTREACHED()  << "Invalid NotificationType.";
  }
}

void AutofillChangeProcessor::HandleMoveAsideIfNeeded(
    sync_api::BaseTransaction* trans, AutoFillProfile* profile,
    std::string* tag) {
  DCHECK_EQ(AutofillModelAssociator::ProfileLabelToTag(profile->Label()),
                                                       *tag);
  sync_api::ReadNode read_node(trans);
  if (read_node.InitByClientTagLookup(syncable::AUTOFILL, *tag)) {
    // Handle the edge case of duplicate labels.
    string16 label(AutofillModelAssociator::MakeUniqueLabel(profile->Label(),
        trans));
    if (label.empty()) {
      error_handler()->OnUnrecoverableError();
      return;
    }
    tag->assign(AutofillModelAssociator::ProfileLabelToTag(label));

    profile->set_label(label);
    if (!web_database_->UpdateAutoFillProfile(*profile)) {
      LOG(ERROR) << "Failed to overwrite label for node" << label;
      error_handler()->OnUnrecoverableError();
      return;
    }

    // Notify the PersonalDataManager that it's out of date.
    PostOptimisticRefreshTask();
  }
}

void AutofillChangeProcessor::PostOptimisticRefreshTask() {
  ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
      new AutofillModelAssociator::DoOptimisticRefreshTask(
           personal_data_));
}

void AutofillChangeProcessor::AddAutofillProfileSyncNode(
    sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill,
    const std::string& tag, const AutoFillProfile* profile) {
  sync_api::WriteNode sync_node(trans);
  if (!sync_node.InitUniqueByCreation(syncable::AUTOFILL, autofill, tag)) {
    LOG(ERROR) << "Failed to create autofill sync node.";
    error_handler()->OnUnrecoverableError();
    return;
  }
  sync_node.SetTitle(UTF8ToWide(tag));

  WriteAutofillProfile(*profile, &sync_node);
  model_associator_->Associate(&tag, sync_node.GetId());
}

void AutofillChangeProcessor::ObserveAutofillProfileChanged(
    AutofillProfileChange* change, sync_api::WriteTransaction* trans,
    const sync_api::ReadNode& autofill_root) {
  std::string tag(AutofillModelAssociator::ProfileLabelToTag(change->key()));
  switch (change->type()) {
    case AutofillProfileChange::ADD: {
      scoped_ptr<AutoFillProfile> clone(
          static_cast<AutoFillProfile*>(change->profile()->Clone()));
      DCHECK_EQ(AutofillModelAssociator::ProfileLabelToTag(clone->Label()),
                tag);
      HandleMoveAsideIfNeeded(trans, clone.get(), &tag);
      AddAutofillProfileSyncNode(trans, autofill_root, tag, clone.get());
      break;
    }
    case AutofillProfileChange::UPDATE: {
      scoped_ptr<AutoFillProfile> clone(
          static_cast<AutoFillProfile*>(change->profile()->Clone()));
      sync_api::WriteNode sync_node(trans);
      if (change->pre_update_label() != change->profile()->Label()) {
        // A re-labelling: we need to remove + add on the sync side.
        RemoveSyncNode(AutofillModelAssociator::ProfileLabelToTag(
            change->pre_update_label()), trans);
        // Watch out! Could be relabelling to an existing label!
        HandleMoveAsideIfNeeded(trans, clone.get(), &tag);
        AddAutofillProfileSyncNode(trans, autofill_root, tag,
                                   clone.get());
        return;
      }
      int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag);
      if (sync_api::kInvalidId == sync_id) {
        LOG(ERROR) << "Unexpected notification for: " << tag;
        error_handler()->OnUnrecoverableError();
        return;
      } else {
        if (!sync_node.InitByIdLookup(sync_id)) {
          LOG(ERROR) << "Autofill node lookup failed.";
          error_handler()->OnUnrecoverableError();
          return;
        }
        WriteAutofillProfile(*change->profile(), &sync_node);
      }
      break;
    }
    case AutofillProfileChange::REMOVE: {
      RemoveSyncNode(tag, trans);
      break;
    }
  }
}

void AutofillChangeProcessor::ObserveAutofillEntriesChanged(
    AutofillChangeList* changes, sync_api::WriteTransaction* trans,
    const sync_api::ReadNode& autofill_root) {
  for (AutofillChangeList::iterator change = changes->begin();
       change != changes->end(); ++change) {
    switch (change->type()) {
      case AutofillChange::ADD:
        {
          sync_api::WriteNode sync_node(trans);
          std::string tag =
              AutofillModelAssociator::KeyToTag(change->key().name(),
                                                change->key().value());
          if (!sync_node.InitUniqueByCreation(syncable::AUTOFILL,
                                              autofill_root, tag)) {
            LOG(ERROR) << "Failed to create autofill sync node.";
            error_handler()->OnUnrecoverableError();
            return;
          }

          std::vector<base::Time> timestamps;
          if (!web_database_->GetAutofillTimestamps(
                  change->key().name(),
                  change->key().value(),
                  &timestamps)) {
            LOG(ERROR) << "Failed to get timestamps.";
            error_handler()->OnUnrecoverableError();
            return;
          }

          sync_node.SetTitle(UTF16ToWide(change->key().name() +
                                         change->key().value()));

          WriteAutofillEntry(AutofillEntry(change->key(), timestamps),
                             &sync_node);
          model_associator_->Associate(&tag, sync_node.GetId());
        }
        break;

      case AutofillChange::UPDATE:
        {
          sync_api::WriteNode sync_node(trans);
          std::string tag = AutofillModelAssociator::KeyToTag(
              change->key().name(), change->key().value());
          int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag);
          if (sync_api::kInvalidId == sync_id) {
            LOG(ERROR) << "Unexpected notification for: " <<
                       change->key().name();
            error_handler()->OnUnrecoverableError();
            return;
          } else {
            if (!sync_node.InitByIdLookup(sync_id)) {
              LOG(ERROR) << "Autofill node lookup failed.";
              error_handler()->OnUnrecoverableError();
              return;
            }
          }

          std::vector<base::Time> timestamps;
          if (!web_database_->GetAutofillTimestamps(
                   change->key().name(),
                   change->key().value(),
                   &timestamps)) {
            LOG(ERROR) << "Failed to get timestamps.";
            error_handler()->OnUnrecoverableError();
            return;
          }

          WriteAutofillEntry(AutofillEntry(change->key(), timestamps),
                             &sync_node);
        }
        break;
      case AutofillChange::REMOVE: {
        std::string tag = AutofillModelAssociator::KeyToTag(
            change->key().name(), change->key().value());
        RemoveSyncNode(tag, trans);
        }
        break;
    }
  }
}

void AutofillChangeProcessor::RemoveSyncNode(const std::string& tag,
    sync_api::WriteTransaction* trans) {
  sync_api::WriteNode sync_node(trans);
  int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag);
  if (sync_api::kInvalidId == sync_id) {
    LOG(ERROR) << "Unexpected notification";
    error_handler()->OnUnrecoverableError();
    return;
  } else {
    if (!sync_node.InitByIdLookup(sync_id)) {
      LOG(ERROR) << "Autofill node lookup failed.";
      error_handler()->OnUnrecoverableError();
      return;
    }
    model_associator_->Disassociate(sync_node.GetId());
    sync_node.Remove();
  }
}

void AutofillChangeProcessor::ApplyChangesFromSyncModel(
    const sync_api::BaseTransaction* trans,
    const sync_api::SyncManager::ChangeRecord* changes,
    int change_count) {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
  if (!running())
    return;
  StopObserving();

  sync_api::ReadNode autofill_root(trans);
  if (!autofill_root.InitByTagLookup(kAutofillTag)) {
    LOG(ERROR) << "Autofill root node lookup failed.";
    error_handler()->OnUnrecoverableError();
    return;
  }

  std::vector<AutofillEntry> new_entries;
  for (int i = 0; i < change_count; ++i) {
    sync_api::SyncManager::ChangeRecord::Action action(changes[i].action);
    if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == action) {
      scoped_ptr<ExtraAutofillChangeRecordData> data(
          static_cast<ExtraAutofillChangeRecordData*>(changes[i].extra));
      DCHECK(data.get()) << "Extra autofill change record data not present!";
      const sync_pb::AutofillSpecifics* autofill(data->pre_deletion_data);
      if (autofill->has_value())
        ApplySyncAutofillEntryDelete(*autofill);
      else if (autofill->has_profile())
        ApplySyncAutofillProfileDelete(autofill->profile(), changes[i].id);
      else
        NOTREACHED() << "Autofill specifics has no data!";
      continue;
    }

    // Handle an update or add.
    sync_api::ReadNode sync_node(trans);
    if (!sync_node.InitByIdLookup(changes[i].id)) {
      LOG(ERROR) << "Autofill node lookup failed.";
      error_handler()->OnUnrecoverableError();
      return;
    }

    // Check that the changed node is a child of the autofills folder.
    DCHECK(autofill_root.GetId() == sync_node.GetParentId());
    DCHECK(syncable::AUTOFILL == sync_node.GetModelType());

    const sync_pb::AutofillSpecifics& autofill(
        sync_node.GetAutofillSpecifics());
    int64 sync_id = sync_node.GetId();
    if (autofill.has_value())
      ApplySyncAutofillEntryChange(action, autofill, &new_entries, sync_id);
    else if (autofill.has_profile())
      ApplySyncAutofillProfileChange(action, autofill.profile(), sync_id);
    else
      NOTREACHED() << "Autofill specifics has no data!";
  }

  if (!web_database_->UpdateAutofillEntries(new_entries)) {
    LOG(ERROR) << "Could not update autofill entries.";
    error_handler()->OnUnrecoverableError();
    return;
  }

  PostOptimisticRefreshTask();

  StartObserving();
}

void AutofillChangeProcessor::ApplySyncAutofillEntryDelete(
      const sync_pb::AutofillSpecifics& autofill) {
  if (!web_database_->RemoveFormElement(
      UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))) {
    LOG(ERROR) << "Could not remove autofill node.";
    error_handler()->OnUnrecoverableError();
    return;
  }
}

void AutofillChangeProcessor::ApplySyncAutofillEntryChange(
      sync_api::SyncManager::ChangeRecord::Action action,
      const sync_pb::AutofillSpecifics& autofill,
      std::vector<AutofillEntry>* new_entries,
      int64 sync_id) {
  DCHECK_NE(sync_api::SyncManager::ChangeRecord::ACTION_DELETE, action);

  std::vector<base::Time> timestamps;
  size_t timestamps_size = autofill.usage_timestamp_size();
  for (size_t c = 0; c < timestamps_size; ++c) {
    timestamps.push_back(
        base::Time::FromInternalValue(autofill.usage_timestamp(c)));
  }
  AutofillKey k(UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()));
  AutofillEntry new_entry(k, timestamps);

  new_entries->push_back(new_entry);
  std::string tag(AutofillModelAssociator::KeyToTag(k.name(), k.value()));
  if (action == sync_api::SyncManager::ChangeRecord::ACTION_ADD)
    model_associator_->Associate(&tag, sync_id);
}

void AutofillChangeProcessor::ApplySyncAutofillProfileChange(
    sync_api::SyncManager::ChangeRecord::Action action,
    const sync_pb::AutofillProfileSpecifics& profile,
    int64 sync_id) {
  DCHECK_NE(sync_api::SyncManager::ChangeRecord::ACTION_DELETE, action);

  std::string tag(AutofillModelAssociator::ProfileLabelToTag(
      UTF8ToUTF16(profile.label())));
  switch (action) {
    case sync_api::SyncManager::ChangeRecord::ACTION_ADD: {
      PersonalDataManager* pdm = model_associator_->sync_service()->
          profile()->GetPersonalDataManager();
      scoped_ptr<AutoFillProfile> p(
          pdm->CreateNewEmptyAutoFillProfileForDBThread(
              UTF8ToUTF16(profile.label())));
      AutofillModelAssociator::OverwriteProfileWithServerData(p.get(),
                                                              profile);

      model_associator_->Associate(&tag, sync_id);
      if (!web_database_->AddAutoFillProfile(*p.get())) {
        NOTREACHED() << "Couldn't add autofill profile: " << profile.label();
        return;
      }
      break;
    }
    case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: {
      AutoFillProfile* p = NULL;
      string16 label = UTF8ToUTF16(profile.label());
      if (!web_database_->GetAutoFillProfileForLabel(label, &p)) {
        NOTREACHED() << "Couldn't retrieve autofill profile: " << label;
        return;
      }
      AutofillModelAssociator::OverwriteProfileWithServerData(p, profile);
      if (!web_database_->UpdateAutoFillProfile(*p)) {
        NOTREACHED() << "Couldn't update autofill profile: " << label;
        return;
      }
      delete p;
      break;
    }
    default:
      NOTREACHED();
  }
}

void AutofillChangeProcessor::ApplySyncAutofillProfileDelete(
    const sync_pb::AutofillProfileSpecifics& profile,
    int64 sync_id) {
  string16 label(UTF8ToUTF16(profile.label()));
  AutoFillProfile* ptr = NULL;
  bool get_success = web_database_->GetAutoFillProfileForLabel(label, &ptr);
  scoped_ptr<AutoFillProfile> p(ptr);
  if (!get_success) {
    NOTREACHED() << "Couldn't retrieve autofill profile: " << label;
    return;
  }
  if (!web_database_->RemoveAutoFillProfile(p->unique_id())) {
    NOTREACHED() << "Couldn't remove autofill profile: " << label;
    return;
  }
  model_associator_->Disassociate(sync_id);
}

void AutofillChangeProcessor::StartImpl(Profile* profile) {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
  observing_ = true;
}

void AutofillChangeProcessor::StopImpl() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
  observing_ = false;
}


void AutofillChangeProcessor::StartObserving() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
  notification_registrar_.Add(this, NotificationType::AUTOFILL_ENTRIES_CHANGED,
                              NotificationService::AllSources());
  notification_registrar_.Add(this, NotificationType::AUTOFILL_PROFILE_CHANGED,
                              NotificationService::AllSources());
}

void AutofillChangeProcessor::StopObserving() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
  notification_registrar_.RemoveAll();
}

// static
void AutofillChangeProcessor::WriteAutofillEntry(
    const AutofillEntry& entry,
    sync_api::WriteNode* node) {
  sync_pb::AutofillSpecifics autofill;
  autofill.set_name(UTF16ToUTF8(entry.key().name()));
  autofill.set_value(UTF16ToUTF8(entry.key().value()));
  const std::vector<base::Time>& ts(entry.timestamps());
  for (std::vector<base::Time>::const_iterator timestamp = ts.begin();
       timestamp != ts.end(); ++timestamp) {
    autofill.add_usage_timestamp(timestamp->ToInternalValue());
  }
  node->SetAutofillSpecifics(autofill);
}

// static
void AutofillChangeProcessor::WriteAutofillProfile(
    const AutoFillProfile& profile, sync_api::WriteNode* node) {
  sync_pb::AutofillSpecifics autofill;
  sync_pb::AutofillProfileSpecifics* s(autofill.mutable_profile());
  s->set_label(UTF16ToUTF8(profile.Label()));
  s->set_name_first(UTF16ToUTF8(
      profile.GetFieldText(AutoFillType(NAME_FIRST))));
  s->set_name_middle(UTF16ToUTF8(
      profile.GetFieldText(AutoFillType(NAME_MIDDLE))));
  s->set_name_last(UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_LAST))));
  s->set_address_home_line1(
      UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_LINE1))));
  s->set_address_home_line2(
      UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_LINE2))));
  s->set_address_home_city(UTF16ToUTF8(profile.GetFieldText(
      AutoFillType(ADDRESS_HOME_CITY))));
  s->set_address_home_state(UTF16ToUTF8(profile.GetFieldText(
      AutoFillType(ADDRESS_HOME_STATE))));
  s->set_address_home_country(UTF16ToUTF8(profile.GetFieldText(
      AutoFillType(ADDRESS_HOME_COUNTRY))));
  s->set_address_home_zip(UTF16ToUTF8(profile.GetFieldText(
      AutoFillType(ADDRESS_HOME_ZIP))));
  s->set_email_address(UTF16ToUTF8(profile.GetFieldText(
      AutoFillType(EMAIL_ADDRESS))));
  s->set_company_name(UTF16ToUTF8(profile.GetFieldText(
      AutoFillType(COMPANY_NAME))));
  s->set_phone_fax_whole_number(UTF16ToUTF8(profile.GetFieldText(
      AutoFillType(PHONE_FAX_WHOLE_NUMBER))));
  s->set_phone_home_whole_number(UTF16ToUTF8(profile.GetFieldText(
      AutoFillType(PHONE_HOME_WHOLE_NUMBER))));
  node->SetAutofillSpecifics(autofill);
}

}  // namespace browser_sync

Generated by  Doxygen 1.6.0   Back to index