diff options
author | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-07 00:26:46 +0000 |
---|---|---|
committer | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-07 00:26:46 +0000 |
commit | 1a5db8cc22d8a87d230f5a239bf25fcd7bec7846 (patch) | |
tree | a4ceaf13b7035833fab91247d08bb312c1c413c0 /chrome/browser/sync/glue | |
parent | ee9080648d4f6ecceab3f0c1ba7048474893ce44 (diff) | |
download | chromium_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')
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(), ×tamps)) { + 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(), ×tamps)) { - 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"))); +} |