summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/glue
diff options
context:
space:
mode:
authortim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-07 00:26:46 +0000
committertim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-04-07 00:26:46 +0000
commit1a5db8cc22d8a87d230f5a239bf25fcd7bec7846 (patch)
treea4ceaf13b7035833fab91247d08bb312c1c413c0 /chrome/browser/sync/glue
parentee9080648d4f6ecceab3f0c1ba7048474893ce44 (diff)
downloadchromium_src-1a5db8cc22d8a87d230f5a239bf25fcd7bec7846.zip
chromium_src-1a5db8cc22d8a87d230f5a239bf25fcd7bec7846.tar.gz
chromium_src-1a5db8cc22d8a87d230f5a239bf25fcd7bec7846.tar.bz2
Make the Autofill glue components capable of handling both types based on the NotificationType (for chrome -> sync) and the existence of certain AutofillSpecifics sub messages (for sync -> chrome).
Handle duplicate chrome profile labels by generating a unique label using current time (happens during association as well as change processing). Handle relabelling by Remove/Add of the sync node. Review URL: http://codereview.chromium.org/1541005 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43785 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/sync/glue')
-rw-r--r--chrome/browser/sync/glue/autofill_change_processor.cc420
-rw-r--r--chrome/browser/sync/glue/autofill_change_processor.h75
-rw-r--r--chrome/browser/sync/glue/autofill_data_type_controller.cc24
-rw-r--r--chrome/browser/sync/glue/autofill_data_type_controller.h12
-rw-r--r--chrome/browser/sync/glue/autofill_model_associator.cc399
-rw-r--r--chrome/browser/sync/glue/autofill_model_associator.h114
-rw-r--r--chrome/browser/sync/glue/autofill_model_associator_unittest.cc20
7 files changed, 872 insertions, 192 deletions
diff --git a/chrome/browser/sync/glue/autofill_change_processor.cc b/chrome/browser/sync/glue/autofill_change_processor.cc
index d8e9345..56b6e98 100644
--- a/chrome/browser/sync/glue/autofill_change_processor.cc
+++ b/chrome/browser/sync/glue/autofill_change_processor.cc
@@ -10,27 +10,33 @@
#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/sync/protocol/autofill_specifics.pb.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();
}
@@ -40,30 +46,145 @@ void AutofillChangeProcessor::Observe(NotificationType type,
const NotificationDetails& details) {
LOG(INFO) << "Observed autofill change.";
DCHECK(running());
- DCHECK(NotificationType::AUTOFILL_ENTRIES_CHANGED == type);
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
if (!observing_) {
return;
}
- AutofillChangeList* changes = Details<AutofillChangeList>(details).ptr();
-
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::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;
- }
-
- sync_api::WriteNode sync_node(&trans);
+ sync_api::WriteNode sync_node(trans);
std::string tag =
AutofillModelAssociator::KeyToTag(change->key().name(),
change->key().value());
@@ -87,16 +208,18 @@ void AutofillChangeProcessor::Observe(NotificationType type,
sync_node.SetTitle(UTF16ToWide(change->key().name() +
change->key().value()));
- WriteAutofill(&sync_node, AutofillEntry(change->key(), timestamps));
- model_associator_->Associate(&(change->key()), sync_node.GetId());
+ WriteAutofillEntry(AutofillEntry(change->key(), timestamps),
+ &sync_node);
+ model_associator_->Associate(&tag, sync_node.GetId());
}
break;
case AutofillChange::UPDATE:
{
- sync_api::WriteNode sync_node(&trans);
- int64 sync_id =
- model_associator_->GetSyncIdFromChromeId(change->key());
+ 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();
@@ -120,35 +243,39 @@ void AutofillChangeProcessor::Observe(NotificationType type,
return;
}
- WriteAutofill(&sync_node, AutofillEntry(change->key(), timestamps));
+ WriteAutofillEntry(AutofillEntry(change->key(), timestamps),
+ &sync_node);
}
break;
-
- case AutofillChange::REMOVE:
- {
- sync_api::WriteNode sync_node(&trans);
- int64 sync_id =
- model_associator_->GetSyncIdFromChromeId(change->key());
- 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;
- }
- model_associator_->Disassociate(sync_node.GetId());
- sync_node.Remove();
- }
+ 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,
@@ -167,56 +294,149 @@ void AutofillChangeProcessor::ApplyChangesFromSyncModel(
std::vector<AutofillEntry> new_entries;
for (int i = 0; i < change_count; ++i) {
- if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE ==
- changes[i].action) {
- const AutofillKey* key =
- model_associator_->GetChromeNodeFromSyncId(changes[i].id);
- if (!key || !web_database_->RemoveFormElement(key->name(),
- key->value())) {
- LOG(ERROR) << "Could not remove autofill node.";
- error_handler()->OnUnrecoverableError();
- return;
- }
- model_associator_->Disassociate(changes[i].id);
- } else {
- sync_api::ReadNode sync_node(trans);
- if (!sync_node.InitByIdLookup(changes[i].id)) {
- LOG(ERROR) << "Autofill node lookup failed.";
- error_handler()->OnUnrecoverableError();
- return;
- }
+ 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;
+ }
- // 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());
- 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 key(UTF8ToUTF16(autofill.name()),
- UTF8ToUTF16(autofill.value()));
+ // 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;
+ }
- new_entries.push_back(AutofillEntry(key, timestamps));
+ // 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());
- if (sync_api::SyncManager::ChangeRecord::ACTION_ADD ==
- changes[i].action) {
- model_associator_->Associate(&key, sync_node.GetId());
- }
- }
+ 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()));
+ 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;
@@ -232,19 +452,19 @@ 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_.Remove(this,
- NotificationType::AUTOFILL_ENTRIES_CHANGED,
- NotificationService::AllSources());
+ notification_registrar_.RemoveAll();
}
// static
-void AutofillChangeProcessor::WriteAutofill(
- sync_api::WriteNode* node,
- const AutofillEntry& entry) {
+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()));
@@ -256,4 +476,38 @@ void AutofillChangeProcessor::WriteAutofill(
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
diff --git a/chrome/browser/sync/glue/autofill_change_processor.h b/chrome/browser/sync/glue/autofill_change_processor.h
index ccd266d..4304f7e 100644
--- a/chrome/browser/sync/glue/autofill_change_processor.h
+++ b/chrome/browser/sync/glue/autofill_change_processor.h
@@ -6,13 +6,21 @@
#define CHROME_BROWSER_SYNC_GLUE_AUTOFILL_CHANGE_PROCESSOR_H_
#include "base/scoped_ptr.h"
+#include "chrome/browser/autofill/autofill_profile.h"
+#include "chrome/browser/autofill/credit_card.h"
+#include "chrome/browser/autofill/personal_data_manager.h"
#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/change_processor.h"
#include "chrome/browser/sync/glue/sync_backend_host.h"
+#include "chrome/browser/sync/protocol/autofill_specifics.pb.h"
+#include "chrome/browser/webdata/web_data_service.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_registrar.h"
+class AutofillCreditCardChange;
class AutofillEntry;
+class AutofillProfileChange;
+class PersonalDataManager;
class WebDatabase;
namespace browser_sync {
@@ -22,12 +30,13 @@ class UnrecoverableErrorHandler;
// This class is responsible for taking changes from the web data service and
// applying them to the sync_api 'syncable' model, and vice versa. All
-// operations and use of this class are from the UI thread.
+// operations and use of this class are from the DB thread.
class AutofillChangeProcessor : public ChangeProcessor,
public NotificationObserver {
public:
AutofillChangeProcessor(AutofillModelAssociator* model_associator,
WebDatabase* web_database,
+ PersonalDataManager* personal_data,
UnrecoverableErrorHandler* error_handler);
virtual ~AutofillChangeProcessor() {}
@@ -45,8 +54,11 @@ class AutofillChangeProcessor : public ChangeProcessor,
// Copy the properties of the given Autofill entry into the sync
// node.
- static void WriteAutofill(sync_api::WriteNode* node,
- const AutofillEntry& entry);
+ static void WriteAutofillEntry(const AutofillEntry& entry,
+ sync_api::WriteNode* node);
+ // As above, for autofill profiles.
+ static void WriteAutofillProfile(const AutoFillProfile& profile,
+ sync_api::WriteNode* node);
protected:
virtual void StartImpl(Profile* profile);
@@ -56,6 +68,59 @@ class AutofillChangeProcessor : public ChangeProcessor,
void StartObserving();
void StopObserving();
+ // A function to remove the sync node for an autofill entry or profile.
+ void RemoveSyncNode(const std::string& tag,
+ sync_api::WriteTransaction* trans);
+
+ // These two methods are dispatched to by Observe depending on the type.
+ void ObserveAutofillEntriesChanged(AutofillChangeList* changes,
+ sync_api::WriteTransaction* trans,
+ const sync_api::ReadNode& autofill_root);
+ void ObserveAutofillProfileChanged(AutofillProfileChange* change,
+ sync_api::WriteTransaction* trans,
+ const sync_api::ReadNode& autofill_root);
+
+ // The following methods are the implementation of ApplyChangeFromSyncModel
+ // for the respective autofill subtypes.
+ void ApplySyncAutofillEntryChange(
+ sync_api::SyncManager::ChangeRecord::Action action,
+ const sync_pb::AutofillSpecifics& autofill,
+ std::vector<AutofillEntry>* new_entries,
+ int64 sync_id);
+ void ApplySyncAutofillProfileChange(
+ sync_api::SyncManager::ChangeRecord::Action action,
+ const sync_pb::AutofillProfileSpecifics& profile,
+ int64 sync_id);
+
+ // Delete is a special case of change application.
+ void ApplySyncAutofillEntryDelete(
+ const sync_pb::AutofillSpecifics& autofill);
+ void ApplySyncAutofillProfileDelete(
+ const sync_pb::AutofillProfileSpecifics& profile,
+ int64 sync_id);
+
+ // If the chrome model tries to add an AutoFillProfile with a label that
+ // is already in use, we perform a move-aside by calling-back into the chrome
+ // model and overwriting the label with a unique value we can apply for sync.
+ // This method should be called on an ADD notification from the chrome model.
+ // |tag| contains the unique sync client tag identifier for |profile|, which
+ // is derived from the profile label using ProfileLabelToTag.
+ void HandleMoveAsideIfNeeded(
+ sync_api::BaseTransaction* trans, AutoFillProfile* profile,
+ std::string* tag);
+
+ // Helper to create a sync node with tag |tag|, storing |profile| as
+ // the node's AutofillSpecifics.
+ void AddAutofillProfileSyncNode(
+ sync_api::WriteTransaction* trans,
+ const sync_api::BaseNode& autofill,
+ const std::string& tag,
+ const AutoFillProfile* profile);
+
+ // Helper to post a task to the UI loop to inform the PersonalDataManager
+ // it needs to refresh itself.
+ void PostOptimisticRefreshTask();
+
// The two models should be associated according to this ModelAssociator.
AutofillModelAssociator* model_associator_;
@@ -64,6 +129,10 @@ class AutofillChangeProcessor : public ChangeProcessor,
// holding a reference.
WebDatabase* web_database_;
+ // We periodically tell the PersonalDataManager to refresh as we make
+ // changes to the autofill data in the WebDatabase.
+ PersonalDataManager* personal_data_;
+
NotificationRegistrar notification_registrar_;
bool observing_;
diff --git a/chrome/browser/sync/glue/autofill_data_type_controller.cc b/chrome/browser/sync/glue/autofill_data_type_controller.cc
index 5e7bca68..cdb31ce 100644
--- a/chrome/browser/sync/glue/autofill_data_type_controller.cc
+++ b/chrome/browser/sync/glue/autofill_data_type_controller.cc
@@ -25,7 +25,8 @@ AutofillDataTypeController::AutofillDataTypeController(
: profile_sync_factory_(profile_sync_factory),
profile_(profile),
sync_service_(sync_service),
- state_(NOT_RUNNING) {
+ state_(NOT_RUNNING),
+ personal_data_(NULL) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
DCHECK(profile_sync_factory);
DCHECK(profile);
@@ -48,6 +49,20 @@ void AutofillDataTypeController::Start(StartCallback* start_callback) {
start_callback_.reset(start_callback);
+ // Waiting for the personal data is subtle: we do this as the PDM resets
+ // its cache of unique IDs once it gets loaded. If we were to proceed with
+ // association, the local ids in the mappings would wind up colliding.
+ personal_data_ = profile_->GetPersonalDataManager();
+ if (!personal_data_->IsDataLoaded()) {
+ set_state(MODEL_STARTING);
+ personal_data_->SetObserver(this);
+ return;
+ }
+
+ ContinueStartAfterPersonalDataLoaded();
+}
+
+void AutofillDataTypeController::ContinueStartAfterPersonalDataLoaded() {
web_data_service_ = profile_->GetWebDataService(Profile::IMPLICIT_ACCESS);
if (web_data_service_.get() && web_data_service_->IsDatabaseLoaded()) {
set_state(ASSOCIATING);
@@ -62,6 +77,12 @@ void AutofillDataTypeController::Start(StartCallback* start_callback) {
}
}
+void AutofillDataTypeController::OnPersonalDataLoaded() {
+ DCHECK_EQ(state_, MODEL_STARTING);
+ personal_data_->RemoveObserver(this);
+ ContinueStartAfterPersonalDataLoaded();
+}
+
void AutofillDataTypeController::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
@@ -102,6 +123,7 @@ void AutofillDataTypeController::StartImpl() {
profile_sync_factory_->CreateAutofillSyncComponents(
sync_service_,
web_data_service_->GetDatabase(),
+ profile_->GetPersonalDataManager(),
this);
model_associator_.reset(sync_components.model_associator);
change_processor_.reset(sync_components.change_processor);
diff --git a/chrome/browser/sync/glue/autofill_data_type_controller.h b/chrome/browser/sync/glue/autofill_data_type_controller.h
index 5ae23ec..b9b5983 100644
--- a/chrome/browser/sync/glue/autofill_data_type_controller.h
+++ b/chrome/browser/sync/glue/autofill_data_type_controller.h
@@ -7,6 +7,7 @@
#include "base/basictypes.h"
#include "base/scoped_ptr.h"
+#include "chrome/browser/autofill/personal_data_manager.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/glue/data_type_controller.h"
@@ -21,7 +22,8 @@ class ChangeProcessor;
// A class that manages the startup and shutdown of autofill sync.
class AutofillDataTypeController : public DataTypeController,
- public NotificationObserver {
+ public NotificationObserver,
+ public PersonalDataManager::Observer {
public:
AutofillDataTypeController(
ProfileSyncFactory* profile_sync_factory,
@@ -64,6 +66,9 @@ class AutofillDataTypeController : public DataTypeController,
const NotificationSource& source,
const NotificationDetails& details);
+ // PersonalDataManager::Observer implementation:
+ virtual void OnPersonalDataLoaded();
+
private:
void StartImpl();
void StartDone(StartResult result, State state);
@@ -72,6 +77,10 @@ class AutofillDataTypeController : public DataTypeController,
void StartFailed(StartResult result);
void OnUnrecoverableErrorImpl();
+ // Second-half of "Start" implementation, called once personal data has
+ // loaded.
+ void ContinueStartAfterPersonalDataLoaded();
+
void set_state(State state) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
state_ = state;
@@ -82,6 +91,7 @@ class AutofillDataTypeController : public DataTypeController,
ProfileSyncService* sync_service_;
State state_;
+ PersonalDataManager* personal_data_;
scoped_refptr<WebDataService> web_data_service_;
scoped_ptr<AssociatorInterface> model_associator_;
scoped_ptr<ChangeProcessor> change_processor_;
diff --git a/chrome/browser/sync/glue/autofill_model_associator.cc b/chrome/browser/sync/glue/autofill_model_associator.cc
index 54f254b..019c7b0 100644
--- a/chrome/browser/sync/glue/autofill_model_associator.cc
+++ b/chrome/browser/sync/glue/autofill_model_associator.cc
@@ -6,7 +6,10 @@
#include <vector>
+#include "base/task.h"
+#include "base/time.h"
#include "base/utf_string_conversions.h"
+#include "chrome/browser/autofill/autofill_profile.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/sync/engine/syncapi.h"
@@ -16,45 +19,206 @@
#include "chrome/browser/webdata/web_database.h"
#include "net/base/escape.h"
+using base::TimeTicks;
+
namespace browser_sync {
const char kAutofillTag[] = "google_chrome_autofill";
+const char kAutofillEntryNamespaceTag[] = "autofill_entry|";
+const char kAutofillProfileNamespaceTag[] = "autofill_profile|";
+
+static const int kMaxNumAttemptsToFindUniqueLabel = 100;
AutofillModelAssociator::AutofillModelAssociator(
ProfileSyncService* sync_service,
WebDatabase* web_database,
+ PersonalDataManager* personal_data,
UnrecoverableErrorHandler* error_handler)
: sync_service_(sync_service),
web_database_(web_database),
+ personal_data_(personal_data),
error_handler_(error_handler),
autofill_node_id_(sync_api::kInvalidId) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
DCHECK(sync_service_);
DCHECK(web_database_);
DCHECK(error_handler_);
+ DCHECK(personal_data_);
}
AutofillModelAssociator::~AutofillModelAssociator() {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
}
+bool AutofillModelAssociator::TraverseAndAssociateChromeAutofillEntries(
+ sync_api::WriteTransaction* write_trans,
+ const sync_api::ReadNode& autofill_root,
+ const std::vector<AutofillEntry>& all_entries_from_db,
+ std::set<AutofillKey>* current_entries,
+ std::vector<AutofillEntry>* new_entries) {
+
+ const std::vector<AutofillEntry>& entries = all_entries_from_db;
+ for (std::vector<AutofillEntry>::const_iterator ix = entries.begin();
+ ix != entries.end(); ++ix) {
+ std::string tag = KeyToTag(ix->key().name(), ix->key().value());
+ if (id_map_.find(tag) != id_map_.end()) {
+ // It seems that name/value pairs are not unique in the web database.
+ // As a result, we have to filter out duplicates here. This is probably
+ // a bug in the database.
+ continue;
+ }
+
+ sync_api::ReadNode node(write_trans);
+ if (node.InitByClientTagLookup(syncable::AUTOFILL, tag)) {
+ const sync_pb::AutofillSpecifics& autofill(node.GetAutofillSpecifics());
+ DCHECK_EQ(tag, KeyToTag(UTF8ToUTF16(autofill.name()),
+ UTF8ToUTF16(autofill.value())));
+
+ std::vector<base::Time> timestamps;
+ if (MergeTimestamps(autofill, ix->timestamps(), &timestamps)) {
+ AutofillEntry new_entry(ix->key(), timestamps);
+ new_entries->push_back(new_entry);
+
+ sync_api::WriteNode write_node(write_trans);
+ if (!write_node.InitByClientTagLookup(syncable::AUTOFILL, tag)) {
+ LOG(ERROR) << "Failed to write autofill sync node.";
+ return false;
+ }
+ AutofillChangeProcessor::WriteAutofillEntry(new_entry, &write_node);
+ }
+
+ Associate(&tag, node.GetId());
+ } else {
+ sync_api::WriteNode node(write_trans);
+ if (!node.InitUniqueByCreation(syncable::AUTOFILL,
+ autofill_root, tag)) {
+ LOG(ERROR) << "Failed to create autofill sync node.";
+ error_handler_->OnUnrecoverableError();
+ return false;
+ }
+ node.SetTitle(UTF16ToWide(ix->key().name() + ix->key().value()));
+ AutofillChangeProcessor::WriteAutofillEntry(*ix, &node);
+ Associate(&tag, node.GetId());
+ }
+
+ current_entries->insert(ix->key());
+ }
+ return true;
+}
+
+bool AutofillModelAssociator::TraverseAndAssociateChromeAutoFillProfiles(
+ sync_api::WriteTransaction* write_trans,
+ const sync_api::ReadNode& autofill_root,
+ const std::vector<AutoFillProfile*>& all_profiles_from_db,
+ std::set<string16>* current_profiles,
+ std::vector<AutoFillProfile*>* updated_profiles) {
+ const std::vector<AutoFillProfile*>& profiles = all_profiles_from_db;
+ for (std::vector<AutoFillProfile*>::const_iterator ix = profiles.begin();
+ ix != profiles.end(); ++ix) {
+ string16 label((*ix)->Label());
+ std::string tag(ProfileLabelToTag(label));
+
+ sync_api::ReadNode node(write_trans);
+ if (node.InitByClientTagLookup(syncable::AUTOFILL, tag)) {
+ const sync_pb::AutofillSpecifics& autofill(node.GetAutofillSpecifics());
+ DCHECK(autofill.has_profile());
+ DCHECK_EQ(ProfileLabelToTag(UTF8ToUTF16(autofill.profile().label())),
+ tag);
+ int64 sync_id = node.GetId();
+ if (id_map_.find(tag) != id_map_.end()) {
+ // We just looked up something we already associated. Move aside.
+ label = MakeUniqueLabel(label, write_trans);
+ if (label.empty()) {
+ error_handler_->OnUnrecoverableError();
+ return false;
+ }
+ tag = ProfileLabelToTag(label);
+ (*ix)->set_label(label);
+ sync_id = MakeNewAutofillProfileSyncNode(write_trans, autofill_root,
+ tag, **ix);
+ updated_profiles->push_back(*ix);
+ } else {
+ // Overwrite local with cloud state.
+ if (OverwriteProfileWithServerData(*ix, autofill.profile()))
+ updated_profiles->push_back(*ix);
+ sync_id = node.GetId();
+ }
+
+ Associate(&tag, sync_id);
+ } else {
+ int64 id = MakeNewAutofillProfileSyncNode(write_trans, autofill_root,
+ tag, **ix);
+ Associate(&tag, id);
+ }
+ current_profiles->insert(label);
+ }
+ return true;
+}
+
+// static
+string16 AutofillModelAssociator::MakeUniqueLabel(
+ const string16& non_unique_label, sync_api::BaseTransaction* trans) {
+ int unique_id = 1; // Priming so we start by appending "2".
+ while (unique_id++ < kMaxNumAttemptsToFindUniqueLabel) {
+ string16 suffix(UTF8ToUTF16(IntToString(unique_id)));
+ string16 unique_label = non_unique_label + suffix;
+ sync_api::ReadNode node(trans);
+ if (node.InitByClientTagLookup(syncable::AUTOFILL,
+ ProfileLabelToTag(unique_label))) {
+ continue;
+ }
+ return unique_label;
+ }
+
+ LOG(ERROR) << "Couldn't create unique tag for autofill node. Srsly?!";
+ return string16();
+}
+
+int64 AutofillModelAssociator::MakeNewAutofillProfileSyncNode(
+ sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill_root,
+ const std::string& tag, const AutoFillProfile& profile) {
+ sync_api::WriteNode node(trans);
+ if (!node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)) {
+ LOG(ERROR) << "Failed to create autofill sync node.";
+ error_handler_->OnUnrecoverableError();
+ return false;
+ }
+ node.SetTitle(UTF8ToWide(tag));
+ AutofillChangeProcessor::WriteAutofillProfile(profile, &node);
+ return node.GetId();
+}
+
+
+bool AutofillModelAssociator::LoadAutofillData(
+ std::vector<AutofillEntry>* entries,
+ std::vector<AutoFillProfile*>* profiles) {
+ if (!web_database_->GetAllAutofillEntries(entries))
+ return false;
+
+ if (!web_database_->GetAutoFillProfiles(profiles))
+ return false;
+
+ return true;
+}
+
bool AutofillModelAssociator::AssociateModels() {
LOG(INFO) << "Associating Autofill Models";
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
// TODO(zork): Attempt to load the model association from storage.
std::vector<AutofillEntry> entries;
- if (!web_database_->GetAllAutofillEntries(&entries)) {
- LOG(ERROR) << "Could not get the autofill entries.";
+ ScopedVector<AutoFillProfile> profiles;
+
+ if (!LoadAutofillData(&entries, &profiles.get())) {
+ LOG(ERROR) << "Could not get the autofill data from WebDatabase.";
return false;
}
- std::set<AutofillKey> current_entries;
- std::vector<AutofillEntry> new_entries;
-
+ DataBundle bundle;
{
sync_api::WriteTransaction trans(
sync_service_->backend()->GetUserShareHandle());
+
sync_api::ReadNode autofill_root(&trans);
if (!autofill_root.InitByTagLookup(kAutofillTag)) {
error_handler_->OnUnrecoverableError();
@@ -63,78 +227,13 @@ bool AutofillModelAssociator::AssociateModels() {
return false;
}
- for (std::vector<AutofillEntry>::iterator ix = entries.begin();
- ix != entries.end(); ++ix) {
- if (id_map_.find(ix->key()) != id_map_.end()) {
- // It seems that name/value pairs are not unique in the web database.
- // As a result, we have to filter out duplicates here. This is probably
- // a bug in the database.
- continue;
- }
-
- std::string tag = KeyToTag(ix->key().name(), ix->key().value());
-
- sync_api::ReadNode node(&trans);
- if (node.InitByClientTagLookup(syncable::AUTOFILL, tag)) {
- const sync_pb::AutofillSpecifics& autofill(node.GetAutofillSpecifics());
- DCHECK_EQ(tag, KeyToTag(UTF8ToUTF16(autofill.name()),
- UTF8ToUTF16(autofill.value())));
-
- std::vector<base::Time> timestamps;
- if (MergeTimestamps(autofill, ix->timestamps(), &timestamps)) {
- AutofillEntry new_entry(ix->key(), timestamps);
- new_entries.push_back(new_entry);
-
- sync_api::WriteNode write_node(&trans);
- if (!write_node.InitByClientTagLookup(syncable::AUTOFILL, tag)) {
- LOG(ERROR) << "Failed to write autofill sync node.";
- return false;
- }
- AutofillChangeProcessor::WriteAutofill(&write_node, new_entry);
- }
-
- Associate(&(ix->key()), node.GetId());
- } else {
- sync_api::WriteNode node(&trans);
- if (!node.InitUniqueByCreation(syncable::AUTOFILL,
- autofill_root, tag)) {
- LOG(ERROR) << "Failed to create autofill sync node.";
- error_handler_->OnUnrecoverableError();
- return false;
- }
- node.SetTitle(UTF16ToWide(ix->key().name() + ix->key().value()));
- AutofillChangeProcessor::WriteAutofill(&node, *ix);
- Associate(&(ix->key()), node.GetId());
- }
-
- current_entries.insert(ix->key());
- }
-
- int64 sync_child_id = autofill_root.GetFirstChildId();
- while (sync_child_id != sync_api::kInvalidId) {
- sync_api::ReadNode sync_child_node(&trans);
- if (!sync_child_node.InitByIdLookup(sync_child_id)) {
- LOG(ERROR) << "Failed to fetch child node.";
- error_handler_->OnUnrecoverableError();
- return false;
- }
- const sync_pb::AutofillSpecifics& autofill(
- sync_child_node.GetAutofillSpecifics());
- AutofillKey key(UTF8ToUTF16(autofill.name()),
- UTF8ToUTF16(autofill.value()));
-
- if (current_entries.find(key) == current_entries.end()) {
- std::vector<base::Time> timestamps;
- int timestamps_count = autofill.usage_timestamp_size();
- for (int c = 0; c < timestamps_count; ++c) {
- timestamps.push_back(base::Time::FromInternalValue(
- autofill.usage_timestamp(c)));
- }
- Associate(&key, sync_child_node.GetId());
- new_entries.push_back(AutofillEntry(key, timestamps));
- }
-
- sync_child_id = sync_child_node.GetSuccessorId();
+ if (!TraverseAndAssociateChromeAutofillEntries(&trans, autofill_root,
+ entries, &bundle.current_entries, &bundle.new_entries) ||
+ !TraverseAndAssociateChromeAutoFillProfiles(&trans, autofill_root,
+ profiles.get(), &bundle.current_profiles,
+ &bundle.updated_profiles) ||
+ !TraverseAndAssociateAllSyncNodes(&trans, autofill_root, &bundle)) {
+ return false;
}
}
@@ -143,16 +242,99 @@ bool AutofillModelAssociator::AssociateModels() {
// this is the only thread that writes to the database. We also don't have
// to worry about the sync model getting out of sync, because changes are
// propogated to the ChangeProcessor on this thread.
- if (new_entries.size() &&
- !web_database_->UpdateAutofillEntries(new_entries)) {
+ if (!SaveChangesToWebData(bundle)) {
LOG(ERROR) << "Failed to update autofill entries.";
error_handler_->OnUnrecoverableError();
return false;
}
+ ChromeThread::PostTask(ChromeThread::UI, FROM_HERE,
+ new DoOptimisticRefreshTask(personal_data_));
+ return true;
+}
+
+bool AutofillModelAssociator::SaveChangesToWebData(const DataBundle& bundle) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
+ if (bundle.new_entries.size() &&
+ !web_database_->UpdateAutofillEntries(bundle.new_entries)) {
+ return false;
+ }
+
+ for (size_t i = 0; i < bundle.new_profiles.size(); i++) {
+ if (!web_database_->AddAutoFillProfile(*bundle.new_profiles[i]))
+ return false;
+ }
+
+ for (size_t i = 0; i < bundle.updated_profiles.size(); i++) {
+ if (!web_database_->UpdateAutoFillProfile(*bundle.updated_profiles[i]))
+ return false;
+ }
return true;
}
+bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes(
+ sync_api::WriteTransaction* write_trans,
+ const sync_api::ReadNode& autofill_root,
+ DataBundle* bundle) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
+
+ int64 sync_child_id = autofill_root.GetFirstChildId();
+ while (sync_child_id != sync_api::kInvalidId) {
+ sync_api::ReadNode sync_child(write_trans);
+ if (!sync_child.InitByIdLookup(sync_child_id)) {
+ LOG(ERROR) << "Failed to fetch child node.";
+ error_handler_->OnUnrecoverableError();
+ return false;
+ }
+ const sync_pb::AutofillSpecifics& autofill(
+ sync_child.GetAutofillSpecifics());
+
+ if (autofill.has_value())
+ AddNativeEntryIfNeeded(autofill, bundle, sync_child);
+ else if (autofill.has_profile())
+ AddNativeProfileIfNeeded(autofill.profile(), bundle, sync_child);
+ else
+ NOTREACHED() << "AutofillSpecifics has no autofill data!";
+
+ sync_child_id = sync_child.GetSuccessorId();
+ }
+ return true;
+}
+
+void AutofillModelAssociator::AddNativeEntryIfNeeded(
+ const sync_pb::AutofillSpecifics& autofill, DataBundle* bundle,
+ const sync_api::ReadNode& node) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
+ AutofillKey key(UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()));
+
+ if (bundle->current_entries.find(key) == bundle->current_entries.end()) {
+ std::vector<base::Time> timestamps;
+ int timestamps_count = autofill.usage_timestamp_size();
+ for (int c = 0; c < timestamps_count; ++c) {
+ timestamps.push_back(base::Time::FromInternalValue(
+ autofill.usage_timestamp(c)));
+ }
+ std::string tag(KeyToTag(key.name(), key.value()));
+ Associate(&tag, node.GetId());
+ bundle->new_entries.push_back(AutofillEntry(key, timestamps));
+ }
+}
+
+void AutofillModelAssociator::AddNativeProfileIfNeeded(
+ const sync_pb::AutofillProfileSpecifics& profile, DataBundle* bundle,
+ const sync_api::ReadNode& node) {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
+ if (bundle->current_profiles.find(UTF8ToUTF16(profile.label())) ==
+ bundle->current_profiles.end()) {
+ std::string tag(ProfileLabelToTag(UTF8ToUTF16(profile.label())));
+ Associate(&tag, node.GetId());
+ AutoFillProfile* p = personal_data_->
+ CreateNewEmptyAutoFillProfileForDBThread(UTF8ToUTF16(profile.label()));
+ OverwriteProfileWithServerData(p, profile);
+ bundle->new_profiles.push_back(p);
+ }
+}
+
bool AutofillModelAssociator::DisassociateModels() {
id_map_.clear();
id_map_inverse_.clear();
@@ -192,13 +374,13 @@ bool AutofillModelAssociator::ChromeModelHasUserCreatedNodes(bool* has_nodes) {
}
int64 AutofillModelAssociator::GetSyncIdFromChromeId(
- const AutofillKey autofill) {
+ const std::string autofill) {
AutofillToSyncIdMap::const_iterator iter = id_map_.find(autofill);
return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
}
void AutofillModelAssociator::Associate(
- const AutofillKey* autofill, int64 sync_id) {
+ const std::string* autofill, int64 sync_id) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
DCHECK_NE(sync_api::kInvalidId, sync_id);
DCHECK(id_map_.find(*autofill) == id_map_.end());
@@ -207,16 +389,6 @@ void AutofillModelAssociator::Associate(
id_map_inverse_[sync_id] = *autofill;
}
-const AutofillKey* AutofillModelAssociator::GetChromeNodeFromSyncId(
- int64 sync_id) {
- DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
- SyncIdToAutofillMap::iterator iter = id_map_inverse_.find(sync_id);
- if (iter == id_map_inverse_.end())
- return NULL;
-
- return &(iter->second);
-}
-
void AutofillModelAssociator::Disassociate(int64 sync_id) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB));
SyncIdToAutofillMap::iterator iter = id_map_inverse_.find(sync_id);
@@ -240,7 +412,15 @@ bool AutofillModelAssociator::GetSyncIdForTaggedNode(const std::string& tag,
// static
std::string AutofillModelAssociator::KeyToTag(const string16& name,
const string16& value) {
- return EscapePath(UTF16ToUTF8(name)) + "|" + EscapePath(UTF16ToUTF8(value));
+ std::string ns(kAutofillEntryNamespaceTag);
+ return ns + EscapePath(UTF16ToUTF8(name)) + "|" +
+ EscapePath(UTF16ToUTF8(value));
+}
+
+// static
+std::string AutofillModelAssociator::ProfileLabelToTag(const string16& label) {
+ std::string ns(kAutofillProfileNamespaceTag);
+ return ns + EscapePath(UTF16ToUTF8(label));
}
// static
@@ -270,4 +450,39 @@ bool AutofillModelAssociator::MergeTimestamps(
return different;
}
+// Helper to compare the local value and cloud value of a field, merge into
+// the local value if they differ, and return whether the merge happened.
+bool MergeField(FormGroup* f, AutoFillFieldType t,
+ const std::string& specifics_field) {
+ if (UTF16ToUTF8(f->GetFieldText(AutoFillType(t))) == specifics_field)
+ return false;
+ f->SetInfo(AutoFillType(t), UTF8ToUTF16(specifics_field));
+ return true;
+}
+
+// static
+bool AutofillModelAssociator::OverwriteProfileWithServerData(
+ AutoFillProfile* merge_into,
+ const sync_pb::AutofillProfileSpecifics& specifics) {
+ bool diff = false;
+ AutoFillProfile* p = merge_into;
+ const sync_pb::AutofillProfileSpecifics& s(specifics);
+ diff = MergeField(p, NAME_FIRST, s.name_first()) || diff;
+ diff = MergeField(p, NAME_LAST, s.name_last()) || diff;
+ diff = MergeField(p, NAME_MIDDLE, s.name_middle()) || diff;
+ diff = MergeField(p, ADDRESS_HOME_LINE1, s.address_home_line1()) || diff;
+ diff = MergeField(p, ADDRESS_HOME_LINE2, s.address_home_line2()) || diff;
+ diff = MergeField(p, ADDRESS_HOME_CITY, s.address_home_city()) || diff;
+ diff = MergeField(p, ADDRESS_HOME_STATE, s.address_home_state()) || diff;
+ diff = MergeField(p, ADDRESS_HOME_COUNTRY, s.address_home_country()) || diff;
+ diff = MergeField(p, ADDRESS_HOME_ZIP, s.address_home_zip()) || diff;
+ diff = MergeField(p, EMAIL_ADDRESS, s.email_address()) || diff;
+ diff = MergeField(p, COMPANY_NAME, s.company_name()) || diff;
+ diff = MergeField(p, PHONE_FAX_WHOLE_NUMBER, s.phone_fax_whole_number())
+ || diff;
+ diff = MergeField(p, PHONE_HOME_WHOLE_NUMBER, s.phone_home_whole_number())
+ || diff;
+ return diff;
+}
+
} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/autofill_model_associator.h b/chrome/browser/sync/glue/autofill_model_associator.h
index eb279ed..3ae9703 100644
--- a/chrome/browser/sync/glue/autofill_model_associator.h
+++ b/chrome/browser/sync/glue/autofill_model_associator.h
@@ -12,34 +12,58 @@
#include "base/basictypes.h"
#include "base/scoped_ptr.h"
+#include "chrome/browser/autofill/personal_data_manager.h"
#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/sync/engine/syncapi.h"
#include "chrome/browser/sync/glue/model_associator.h"
#include "chrome/browser/sync/protocol/autofill_specifics.pb.h"
#include "chrome/browser/webdata/autofill_entry.h"
+class AutoFillProfile;
+
class ProfileSyncService;
class WebDatabase;
+namespace sync_api {
+class WriteTransaction;
+}
+
namespace browser_sync {
class AutofillChangeProcessor;
class UnrecoverableErrorHandler;
extern const char kAutofillTag[];
+extern const char kAutofillProfileNamespaceTag[];
+extern const char kAutofillEntryNamespaceTag[];
// Contains all model association related logic:
// * Algorithm to associate autofill model and sync model.
// We do not check if we have local data before this run; we always
// merge and sync.
class AutofillModelAssociator
- : public PerDataTypeAssociatorInterface<AutofillKey, AutofillKey> {
+ : public PerDataTypeAssociatorInterface<std::string, std::string> {
public:
static syncable::ModelType model_type() { return syncable::AUTOFILL; }
AutofillModelAssociator(ProfileSyncService* sync_service,
WebDatabase* web_database,
+ PersonalDataManager* data_manager,
UnrecoverableErrorHandler* error_handler);
virtual ~AutofillModelAssociator();
+ // A task used by this class and the change processor to inform the
+ // PersonalDataManager living on the UI thread that it needs to refresh.
+ class DoOptimisticRefreshTask : public Task {
+ public:
+ explicit DoOptimisticRefreshTask(PersonalDataManager* pdm) : pdm_(pdm) {}
+ virtual void Run() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ pdm_->Refresh();
+ }
+ private:
+ PersonalDataManager* pdm_;
+ };
+
// PerDataTypeAssociatorInterface implementation.
//
// Iterates through the sync model looking for matched pairs of items.
@@ -56,20 +80,23 @@ class AutofillModelAssociator
// user-defined autofill entries.
virtual bool ChromeModelHasUserCreatedNodes(bool* has_nodes);
- virtual const AutofillKey* GetChromeNodeFromSyncId(int64 sync_id);
+ // Not implemented.
+ virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id) {
+ return NULL;
+ }
// Not implemented.
- virtual bool InitSyncNodeFromChromeId(AutofillKey node_id,
+ virtual bool InitSyncNodeFromChromeId(std::string node_id,
sync_api::BaseNode* sync_node) {
return false;
}
// Returns the sync id for the given autofill name, or sync_api::kInvalidId
// if the autofill name is not associated to any sync id.
- virtual int64 GetSyncIdFromChromeId(AutofillKey node_id);
+ virtual int64 GetSyncIdFromChromeId(std::string node_id);
// Associates the given autofill name with the given sync id.
- virtual void Associate(const AutofillKey* node, int64 sync_id);
+ virtual void Associate(const std::string* node, int64 sync_id);
// Remove the association that corresponds to the given sync id.
virtual void Disassociate(int64 sync_id);
@@ -79,20 +106,91 @@ class AutofillModelAssociator
virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id);
static std::string KeyToTag(const string16& name, const string16& value);
+ static std::string ProfileLabelToTag(const string16& label);
+
static bool MergeTimestamps(const sync_pb::AutofillSpecifics& autofill,
const std::vector<base::Time>& timestamps,
std::vector<base::Time>* new_timestamps);
+ static bool OverwriteProfileWithServerData(
+ AutoFillProfile* merge_into,
+ const sync_pb::AutofillProfileSpecifics& specifics);
- protected:
// Returns sync service instance.
ProfileSyncService* sync_service() { return sync_service_; }
+ static string16 MakeUniqueLabel(const string16& non_unique_label,
+ sync_api::BaseTransaction* trans);
+
private:
- typedef std::map<AutofillKey, int64> AutofillToSyncIdMap;
- typedef std::map<int64, AutofillKey> SyncIdToAutofillMap;
+ typedef std::map<std::string, int64> AutofillToSyncIdMap;
+ typedef std::map<int64, std::string> SyncIdToAutofillMap;
+
+ // A convenience wrapper of a bunch of state we pass around while associating
+ // models, and send to the WebDatabase for persistence.
+ struct DataBundle {
+ std::set<AutofillKey> current_entries;
+ std::vector<AutofillEntry> new_entries;
+ std::set<string16> current_profiles;
+ std::vector<AutoFillProfile*> updated_profiles;
+ std::vector<AutoFillProfile*> new_profiles; // We own these pointers.
+ ~DataBundle() { STLDeleteElements(&new_profiles); }
+ };
+
+ // Helper to query WebDatabase for the current autofill state.
+ bool LoadAutofillData(std::vector<AutofillEntry>* entries,
+ std::vector<AutoFillProfile*>* profiles);
+
+ // We split up model association first by autofill sub-type (entries, and
+ // profiles. There is a Traverse* method for each of these.
+ bool TraverseAndAssociateChromeAutofillEntries(
+ sync_api::WriteTransaction* write_trans,
+ const sync_api::ReadNode& autofill_root,
+ const std::vector<AutofillEntry>& all_entries_from_db,
+ std::set<AutofillKey>* current_entries,
+ std::vector<AutofillEntry>* new_entries);
+ bool TraverseAndAssociateChromeAutoFillProfiles(
+ sync_api::WriteTransaction* write_trans,
+ const sync_api::ReadNode& autofill_root,
+ const std::vector<AutoFillProfile*>& all_profiles_from_db,
+ std::set<string16>* current_profiles,
+ std::vector<AutoFillProfile*>* updated_profiles);
+
+ // Once the above traversals are complete, we traverse the sync model to
+ // associate all remaining nodes.
+ bool TraverseAndAssociateAllSyncNodes(
+ sync_api::WriteTransaction* write_trans,
+ const sync_api::ReadNode& autofill_root,
+ DataBundle* bundle);
+
+ // Helper to persist any changes that occured during model association to
+ // the WebDatabase.
+ bool SaveChangesToWebData(const DataBundle& bundle);
+
+ // Helper to insert an AutofillEntry into the WebDatabase (e.g. in response
+ // to encountering a sync node that doesn't exist yet locally).
+ void AddNativeEntryIfNeeded(const sync_pb::AutofillSpecifics& autofill,
+ DataBundle* bundle,
+ const sync_api::ReadNode& node);
+
+ // Helper to insert an AutoFillProfile into the WebDatabase (e.g. in response
+ // to encountering a sync node that doesn't exist yet locally).
+ void AddNativeProfileIfNeeded(
+ const sync_pb::AutofillProfileSpecifics& profile,
+ DataBundle* bundle,
+ const sync_api::ReadNode& node);
+
+ // Helper to insert a sync node for the given AutoFillProfile (e.g. in
+ // response to encountering a native profile that doesn't exist yet in the
+ // cloud).
+ int64 MakeNewAutofillProfileSyncNode(
+ sync_api::WriteTransaction* trans,
+ const sync_api::BaseNode& autofill_root,
+ const std::string& tag,
+ const AutoFillProfile& profile);
ProfileSyncService* sync_service_;
WebDatabase* web_database_;
+ PersonalDataManager* personal_data_;
UnrecoverableErrorHandler* error_handler_;
int64 autofill_node_id_;
diff --git a/chrome/browser/sync/glue/autofill_model_associator_unittest.cc b/chrome/browser/sync/glue/autofill_model_associator_unittest.cc
index 240962a..cfd7e8d 100644
--- a/chrome/browser/sync/glue/autofill_model_associator_unittest.cc
+++ b/chrome/browser/sync/glue/autofill_model_associator_unittest.cc
@@ -13,16 +13,28 @@ class AutofillModelAssociatorTest : public testing::Test {
};
TEST_F(AutofillModelAssociatorTest, KeyToTag) {
- EXPECT_EQ("foo|bar",
+ EXPECT_EQ("autofill_entry|foo|bar",
AutofillModelAssociator::KeyToTag(UTF8ToUTF16("foo"),
UTF8ToUTF16("bar")));
- EXPECT_EQ("%7C|%7C",
+ EXPECT_EQ("autofill_entry|%7C|%7C",
AutofillModelAssociator::KeyToTag(UTF8ToUTF16("|"),
UTF8ToUTF16("|")));
- EXPECT_EQ("%7C|",
+ EXPECT_EQ("autofill_entry|%7C|",
AutofillModelAssociator::KeyToTag(UTF8ToUTF16("|"),
UTF8ToUTF16("")));
- EXPECT_EQ("|%7C",
+ EXPECT_EQ("autofill_entry||%7C",
AutofillModelAssociator::KeyToTag(UTF8ToUTF16(""),
UTF8ToUTF16("|")));
}
+
+TEST_F(AutofillModelAssociatorTest, ProfileLabelToTag) {
+ string16 label(ASCIIToUTF16("awesome_address"));
+ EXPECT_EQ("autofill_profile|awesome_address",
+ AutofillModelAssociator::ProfileLabelToTag(label));
+
+ EXPECT_EQ("autofill_profile|%7C%7C",
+ AutofillModelAssociator::ProfileLabelToTag(ASCIIToUTF16("||")));
+ EXPECT_NE(AutofillModelAssociator::KeyToTag(ASCIIToUTF16("autofill_profile"),
+ ASCIIToUTF16("home")),
+ AutofillModelAssociator::ProfileLabelToTag(ASCIIToUTF16("home")));
+}