// 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/autofill_change_processor.h"
#include <string>
#include <vector>
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/autofill/personal_data_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/glue/autofill_model_associator.h"
#include "chrome/browser/sync/glue/autofill_profile_model_associator.h"
#include "chrome/browser/sync/glue/do_optimistic_refresh_task.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/guid.h"
#include "content/common/notification_service.h"
namespace browser_sync {
struct AutofillChangeProcessor::AutofillChangeRecord {
sync_api::SyncManager::ChangeRecord::Action action_;
int64 id_;
sync_pb::AutofillSpecifics autofill_;
AutofillChangeRecord(sync_api::SyncManager::ChangeRecord::Action action,
int64 id, const sync_pb::AutofillSpecifics& autofill)
: action_(action),
id_(id),
autofill_(autofill) { }
};
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(BrowserThread::CurrentlyOn(BrowserThread::DB));
StartObserving();
}
AutofillChangeProcessor::~AutofillChangeProcessor() {}
void AutofillChangeProcessor::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
// Ensure this notification came from our web database.
WebDataService* wds = Source<WebDataService>(source).ptr();
if (!wds || wds->GetDatabase() != web_database_)
return;
DCHECK(running());
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
if (!observing_)
return;
sync_api::WriteTransaction trans(share_handle());
sync_api::ReadNode autofill_root(&trans);
if (!autofill_root.InitByTagLookup(kAutofillTag)) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Server did not create the top-level autofill node. "
"We might be running against an out-of-date server.");
return;
}
DCHECK(type.value == NotificationType::AUTOFILL_ENTRIES_CHANGED);
AutofillChangeList* changes = Details<AutofillChangeList>(details).ptr();
ObserveAutofillEntriesChanged(changes, &trans, autofill_root);
}
void AutofillChangeProcessor::PostOptimisticRefreshTask() {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
new DoOptimisticRefreshForAutofill(
personal_data_));
}
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)) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Failed to create autofill sync node.");
return;
}
std::vector<base::Time> timestamps;
if (!web_database_->GetAutofillTable()->GetAutofillTimestamps(
change->key().name(),
change->key().value(),
×tamps)) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Failed to get timestamps.");
return;
}
sync_node.SetTitle(UTF8ToWide(tag));
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) {
std::string err = "Unexpected notification for: " +
UTF16ToUTF8(change->key().name());
error_handler()->OnUnrecoverableError(FROM_HERE, err);
return;
} else {
if (!sync_node.InitByIdLookup(sync_id)) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Autofill node lookup failed.");
return;
}
}
std::vector<base::Time> timestamps;
if (!web_database_->GetAutofillTable()->GetAutofillTimestamps(
change->key().name(),
change->key().value(),
×tamps)) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Failed to get timestamps.");
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) {
// This could happen because web db might have duplicates and when an entry
// and its duplicate is deleted.
LOG(WARNING) <<
"Bogus delete notification generate for autofill entry " + tag;
return;
} else {
if (!sync_node.InitByIdLookup(sync_id)) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Autofill node lookup failed.");
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(BrowserThread::CurrentlyOn(BrowserThread::DB));
if (!running())
return;
StopObserving();
bool autofill_profile_not_migrated = HasNotMigratedYet(trans);
sync_api::ReadNode autofill_root(trans);
if (!autofill_root.InitByTagLookup(kAutofillTag)) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Autofill root node lookup failed.");
return;
}
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) {
DCHECK(changes[i].specifics.HasExtension(sync_pb::autofill))
<< "Autofill specifics data not present on delete!";
const sync_pb::AutofillSpecifics& autofill =
changes[i].specifics.GetExtension(sync_pb::autofill);
if (autofill.has_value() ||
(autofill_profile_not_migrated && autofill.has_profile())) {
autofill_changes_.push_back(AutofillChangeRecord(changes[i].action,
changes[i].id,
autofill));
} 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)) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Autofill node lookup failed.");
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() ||
(autofill_profile_not_migrated && autofill.has_profile())) {
autofill_changes_.push_back(AutofillChangeRecord(changes[i].action,
sync_id, autofill));
} else {
NOTREACHED() << "Autofill specifics has no data!";
}
}
StartObserving();
}
void AutofillChangeProcessor::CommitChangesFromSyncModel() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
if (!running())
return;
StopObserving();
std::vector<AutofillEntry> new_entries;
for (unsigned int i = 0; i < autofill_changes_.size(); i++) {
// Handle deletions.
if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE ==
autofill_changes_[i].action_) {
if (autofill_changes_[i].autofill_.has_value()) {
ApplySyncAutofillEntryDelete(autofill_changes_[i].autofill_);
} else if (autofill_changes_[i].autofill_.has_profile()) {
ApplySyncAutofillProfileDelete(autofill_changes_[i].id_);
} else {
NOTREACHED() << "Autofill's CommitChanges received change with no"
" data!";
}
continue;
}
// Handle update/adds.
if (autofill_changes_[i].autofill_.has_value()) {
ApplySyncAutofillEntryChange(autofill_changes_[i].action_,
autofill_changes_[i].autofill_, &new_entries,
autofill_changes_[i].id_);
} else if (autofill_changes_[i].autofill_.has_profile()) {
ApplySyncAutofillProfileChange(autofill_changes_[i].action_,
autofill_changes_[i].autofill_.profile(),
autofill_changes_[i].id_);
} else {
NOTREACHED() << "Autofill's CommitChanges received change with no data!";
}
}
autofill_changes_.clear();
// Make changes
if (!web_database_->GetAutofillTable()->UpdateAutofillEntries(new_entries)) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Could not update autofill entries.");
return;
}
PostOptimisticRefreshTask();
StartObserving();
}
void AutofillChangeProcessor::ApplySyncAutofillEntryDelete(
const sync_pb::AutofillSpecifics& autofill) {
if (!web_database_->GetAutofillTable()->RemoveFormElement(
UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))) {
error_handler()->OnUnrecoverableError(FROM_HERE,
"Could not remove autofill node.");
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);
switch (action) {
case sync_api::SyncManager::ChangeRecord::ACTION_ADD: {
std::string guid(guid::GenerateGUID());
if (guid::IsValidGUID(guid) == false) {
DCHECK(false) << "Guid generated is invalid " << guid;
return;
}
scoped_ptr<AutofillProfile> p(new AutofillProfile);
p->set_guid(guid);
AutofillModelAssociator::FillProfileWithServerData(p.get(),
profile);
if (!web_database_->GetAutofillTable()->AddAutofillProfile(*p.get())) {
NOTREACHED() << "Couldn't add autofill profile: " << guid;
return;
}
model_associator_->Associate(&guid, sync_id);
break;
}
case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: {
const std::string* guid = model_associator_->GetChromeNodeFromSyncId(
sync_id);
if (guid == NULL) {
LOG(ERROR) << " Model association has not happened for " << sync_id;
error_handler()->OnUnrecoverableError(FROM_HERE,
"model association has not happened");
return;
}
AutofillProfile *temp_ptr;
if (!web_database_->GetAutofillTable()->GetAutofillProfile(
*guid, &temp_ptr)) {
LOG(ERROR) << "Autofill profile not found for " << *guid;
return;
}
scoped_ptr<AutofillProfile> p(temp_ptr);
AutofillModelAssociator::FillProfileWithServerData(p.get(), profile);
if (!web_database_->GetAutofillTable()->UpdateAutofillProfile(
*(p.get()))) {
LOG(ERROR) << "Couldn't update autofill profile: " << guid;
return;
}
break;
}
default:
NOTREACHED();
}
}
void AutofillChangeProcessor::ApplySyncAutofillProfileDelete(
int64 sync_id) {
const std::string *guid = model_associator_->GetChromeNodeFromSyncId(sync_id);
if (guid == NULL) {
LOG(ERROR)<< "The profile is not associated";
return;
}
if (!web_database_->GetAutofillTable()->RemoveAutofillProfile(*guid)) {
LOG(ERROR) << "Could not remove the profile";
return;
}
model_associator_->Disassociate(sync_id);
}
void AutofillChangeProcessor::StartImpl(Profile* profile) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
observing_ = true;
}
void AutofillChangeProcessor::StopImpl() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
observing_ = false;
}
void AutofillChangeProcessor::StartObserving() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
notification_registrar_.Add(this, NotificationType::AUTOFILL_ENTRIES_CHANGED,
NotificationService::AllSources());
}
void AutofillChangeProcessor::StopObserving() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::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);
}
bool AutofillChangeProcessor::HasNotMigratedYet(
const sync_api::BaseTransaction* trans) {
return model_associator_->HasNotMigratedYet(trans);
}
} // namespace browser_sync