diff options
Diffstat (limited to 'chrome/browser/sync')
23 files changed, 2705 insertions, 358 deletions
diff --git a/chrome/browser/sync/engine/read_node_mock.h b/chrome/browser/sync/engine/read_node_mock.h new file mode 100644 index 0000000..f13eddd --- /dev/null +++ b/chrome/browser/sync/engine/read_node_mock.h @@ -0,0 +1,29 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SYNC_ENGINE_READ_NODE_MOCK_H_
+#define CHROME_BROWSER_SYNC_ENGINE_READ_NODE_MOCK_H_
+#pragma once
+
+#include <string>
+
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+class ReadNodeMock : public sync_api::ReadNode {
+ public:
+ ReadNodeMock() { }
+ virtual ~ReadNodeMock() {}
+ MOCK_METHOD2(InitByClientTagLookup,
+ bool(syncable::ModelType model_type, const std::string& tag));
+ MOCK_CONST_METHOD0(GetAutofillProfileSpecifics,
+ const sync_pb::AutofillProfileSpecifics&());
+ MOCK_CONST_METHOD0(GetId, int64());
+ MOCK_CONST_METHOD0(GetFirstChildId, int64());
+ MOCK_CONST_METHOD0(GetFirstChild, int64());
+ MOCK_CONST_METHOD0(GetSuccessorId, int64());
+ MOCK_METHOD1(InitByIdLookup, bool(int64 id));
+};
+#endif // CHROME_BROWSER_SYNC_ENGINE_READ_NODE_MOCK_H_
+
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc index 5a71eb8..bc34982 100644 --- a/chrome/browser/sync/engine/syncapi.cc +++ b/chrome/browser/sync/engine/syncapi.cc @@ -83,6 +83,7 @@ using syncable::Directory; using syncable::DirectoryManager; using syncable::Entry; using syncable::SPECIFICS; +using sync_pb::AutofillProfileSpecifics; typedef GoogleServiceAuthError AuthError; @@ -281,6 +282,11 @@ const sync_pb::AutofillSpecifics& BaseNode::GetAutofillSpecifics() const { return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::autofill); } +const AutofillProfileSpecifics& BaseNode::GetAutofillProfileSpecifics() const { + DCHECK_EQ(GetModelType(), syncable::AUTOFILL_PROFILE); + return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::autofill_profile); +} + const sync_pb::BookmarkSpecifics& BaseNode::GetBookmarkSpecifics() const { DCHECK(GetModelType() == syncable::BOOKMARKS); return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::bookmark); @@ -757,6 +763,11 @@ ReadNode::ReadNode(const BaseTransaction* transaction) DCHECK(transaction); } +ReadNode::ReadNode() { + entry_ = NULL; + transaction_ = NULL; +} + ReadNode::~ReadNode() { delete entry_; } diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h index 95facaf..2019f41 100644 --- a/chrome/browser/sync/engine/syncapi.h +++ b/chrome/browser/sync/engine/syncapi.h @@ -81,6 +81,7 @@ class WriteTransaction; namespace sync_pb { class AppSpecifics; class AutofillSpecifics; +class AutofillProfileSpecifics; class BookmarkSpecifics; class EntitySpecifics; class ExtensionSpecifics; @@ -149,7 +150,7 @@ class BaseNode { // metahandle). These ids are strictly local handles. They will persist // on this client, but the same object on a different client may have a // different ID value. - int64 GetId() const; + virtual int64 GetId() const; // Nodes are hierarchically arranged into a single-rooted tree. // InitByRootLookup on ReadNode allows access to the root. GetParentId is @@ -195,6 +196,9 @@ class BaseNode { // data. Can only be called if GetModelType() == AUTOFILL. const sync_pb::AutofillSpecifics& GetAutofillSpecifics() const; + virtual const sync_pb::AutofillProfileSpecifics& + GetAutofillProfileSpecifics() const; + // Getter specific to the NIGORI datatype. Returns protobuf // data. Can only be called if GetModelType() == NIGORI. const sync_pb::NigoriSpecifics& GetNigoriSpecifics() const; @@ -232,11 +236,11 @@ class BaseNode { // Return the ID of the node immediately after this in the sibling order. // For the last node in the ordering, return 0. - int64 GetSuccessorId() const; + virtual int64 GetSuccessorId() const; // Return the ID of the first child of this node. If this node has no // children, return 0. - int64 GetFirstChildId() const; + virtual int64 GetFirstChildId() const; // These virtual accessors provide access to data members of derived classes. virtual const syncable::Entry* GetEntry() const = 0; @@ -256,8 +260,7 @@ class BaseNode { bool DecryptIfNecessary(syncable::Entry* entry); private: - // Node is meant for stack use only. - void* operator new(size_t size); + void* operator new(size_t size); // Node is meant for stack use only. // If this node represents a password, this field will hold the actual // decrypted password data. @@ -457,6 +460,9 @@ class ReadNode : public BaseNode { virtual const syncable::Entry* GetEntry() const; virtual const BaseTransaction* GetTransaction() const; + protected: + ReadNode(); + private: void* operator new(size_t size); // Node is meant for stack use only. @@ -492,6 +498,8 @@ class BaseTransaction { explicit BaseTransaction(UserShare* share); virtual ~BaseTransaction(); + BaseTransaction() { lookup_= NULL; } + private: // A syncable ScopedDirLookup, which is the parent of syncable transactions. syncable::ScopedDirLookup* lookup_; @@ -537,6 +545,12 @@ class WriteTransaction : public BaseTransaction { virtual syncable::BaseTransaction* GetWrappedTrans() const; syncable::WriteTransaction* GetWrappedWriteTrans() { return transaction_; } + protected: + WriteTransaction() {} + + void SetTransaction(syncable::WriteTransaction* trans) { + transaction_ = trans;} + private: void* operator new(size_t size); // Transaction is meant for stack use only. diff --git a/chrome/browser/sync/engine/syncapi_mock.h b/chrome/browser/sync/engine/syncapi_mock.h new file mode 100644 index 0000000..ada0a56 --- /dev/null +++ b/chrome/browser/sync/engine/syncapi_mock.h @@ -0,0 +1,26 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_ENGINE_SYNCAPI_MOCK_H_ +#define CHROME_BROWSER_SYNC_ENGINE_SYNCAPI_MOCK_H_ +#pragma once + +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/syncable/syncable.h" +#include "chrome/browser/sync/syncable/syncable_mock.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using sync_api::WriteTransaction; + +class MockWriteTransaction : public sync_api::WriteTransaction { + public: + explicit MockWriteTransaction(Directory* directory) + : sync_api::WriteTransaction() { + this->SetTransaction(new MockSyncableWriteTransaction(directory)); + } +}; + +#endif // CHROME_BROWSER_SYNC_ENGINE_SYNCAPI_MOCK_H_ + diff --git a/chrome/browser/sync/glue/autofill_change_processor.cc b/chrome/browser/sync/glue/autofill_change_processor.cc index d93f25d..d415950 100644 --- a/chrome/browser/sync/glue/autofill_change_processor.cc +++ b/chrome/browser/sync/glue/autofill_change_processor.cc @@ -9,8 +9,10 @@ #include "base/string_util.h" #include "base/utf_string_conversions.h" +#include "chrome/browser/guid.h" #include "chrome/browser/profile.h" #include "chrome/browser/autofill/personal_data_manager.h" +#include "chrome/browser/sync/glue/autofill_change_processor2.h" #include "chrome/browser/sync/glue/autofill_model_associator.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/webdata/autofill_change.h" @@ -73,62 +75,10 @@ void AutofillChangeProcessor::Observe(NotificationType type, 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."; - } -} + DCHECK(type.value == NotificationType::AUTOFILL_ENTRIES_CHANGED); -void AutofillChangeProcessor::ChangeProfileLabelIfAlreadyTaken( - sync_api::BaseTransaction* trans, - const string16& pre_update_label, - AutoFillProfile* profile, - std::string* tag) { - DCHECK_EQ(AutofillModelAssociator::ProfileLabelToTag(profile->Label()), - *tag); - sync_api::ReadNode read_node(trans); - if (!pre_update_label.empty() && pre_update_label == profile->Label()) - return; - if (read_node.InitByClientTagLookup(syncable::AUTOFILL, *tag)) { - // Handle the edge case of duplicate labels. - string16 new_label(AutofillModelAssociator::MakeUniqueLabel( - profile->Label(), pre_update_label, trans)); - if (new_label.empty()) { - error_handler()->OnUnrecoverableError(FROM_HERE, - "No unique label; can't move aside"); - return; - } - OverrideProfileLabel(new_label, profile, tag); - } -} - -void AutofillChangeProcessor::OverrideProfileLabel( - const string16& new_label, - AutoFillProfile* profile_to_update, - std::string* tag_to_update) { - tag_to_update->assign(AutofillModelAssociator::ProfileLabelToTag(new_label)); - - profile_to_update->set_label(new_label); - if (!web_database_->UpdateAutoFillProfile(*profile_to_update)) { - std::string err = "Failed to overwrite label for node "; - err += UTF16ToUTF8(new_label); - error_handler()->OnUnrecoverableError(FROM_HERE, err); - return; - } - - // Notify the PersonalDataManager that it's out of date. - PostOptimisticRefreshTask(); + AutofillChangeList* changes = Details<AutofillChangeList>(details).ptr(); + ObserveAutofillEntriesChanged(changes, &trans, autofill_root); } void AutofillChangeProcessor::PostOptimisticRefreshTask() { @@ -137,71 +87,6 @@ void AutofillChangeProcessor::PostOptimisticRefreshTask() { 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)) { - error_handler()->OnUnrecoverableError(FROM_HERE, - "Failed to create autofill sync node."); - 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(clone->Label(), change->key()); - ChangeProfileLabelIfAlreadyTaken(trans, string16(), clone.get(), &tag); - AddAutofillProfileSyncNode(trans, autofill_root, tag, clone.get()); - break; - } - case AutofillProfileChange::UPDATE: { - scoped_ptr<AutoFillProfile> clone( - static_cast<AutoFillProfile*>(change->profile()->Clone())); - std::string pre_update_tag = AutofillModelAssociator::ProfileLabelToTag( - change->pre_update_label()); - DCHECK_EQ(clone->Label(), change->key()); - sync_api::WriteNode sync_node(trans); - ChangeProfileLabelIfAlreadyTaken(trans, change->pre_update_label(), - clone.get(), &tag); - if (pre_update_tag != tag) { - // If the label changes, replace the node instead of updating it. - RemoveSyncNode(pre_update_tag, trans); - AddAutofillProfileSyncNode(trans, autofill_root, tag, clone.get()); - return; - } - int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); - if (sync_api::kInvalidId == sync_id) { - std::string err = "Unexpected notification for: " + tag; - error_handler()->OnUnrecoverableError(FROM_HERE, err); - return; - } else { - if (!sync_node.InitByIdLookup(sync_id)) { - error_handler()->OnUnrecoverableError(FROM_HERE, - "Autofill node lookup failed."); - return; - } - WriteAutofillProfile(*clone.get(), &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) { @@ -324,7 +209,8 @@ void AutofillChangeProcessor::ApplyChangesFromSyncModel( << "Autofill specifics data not present on delete!"; const sync_pb::AutofillSpecifics& autofill = changes[i].specifics.GetExtension(sync_pb::autofill); - if (autofill.has_value() || autofill.has_profile()) { + if (autofill.has_value() || + (HasNotMigratedYet() && autofill.has_profile())) { autofill_changes_.push_back(AutofillChangeRecord(changes[i].action, changes[i].id, autofill)); @@ -349,7 +235,8 @@ void AutofillChangeProcessor::ApplyChangesFromSyncModel( const sync_pb::AutofillSpecifics& autofill( sync_node.GetAutofillSpecifics()); int64 sync_id = sync_node.GetId(); - if (autofill.has_value() || autofill.has_profile()) { + if (autofill.has_value() || + (HasNotMigratedYet() && autofill.has_profile())) { autofill_changes_.push_back(AutofillChangeRecord(changes[i].action, sync_id, autofill)); } else { @@ -374,8 +261,8 @@ void AutofillChangeProcessor::CommitChangesFromSyncModel() { if (autofill_changes_[i].autofill_.has_value()) { ApplySyncAutofillEntryDelete(autofill_changes_[i].autofill_); } else if (autofill_changes_[i].autofill_.has_profile()) { - ApplySyncAutofillProfileDelete(autofill_changes_[i].autofill_.profile(), - autofill_changes_[i].id_); + DCHECK(HasNotMigratedYet()); + ApplySyncAutofillProfileDelete(autofill_changes_[i].id_); } else { NOTREACHED() << "Autofill's CommitChanges received change with no" " data!"; @@ -389,6 +276,7 @@ void AutofillChangeProcessor::CommitChangesFromSyncModel() { autofill_changes_[i].autofill_, &new_entries, autofill_changes_[i].id_); } else if (autofill_changes_[i].autofill_.has_profile()) { + DCHECK(HasNotMigratedYet()); ApplySyncAutofillProfileChange(autofill_changes_[i].action_, autofill_changes_[i].autofill_.profile(), autofill_changes_[i].id_); @@ -448,38 +336,42 @@ void AutofillChangeProcessor::ApplySyncAutofillProfileChange( 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(), + std::string guid(guid::GenerateGUID()); + scoped_ptr<AutoFillProfile> p(new AutoFillProfile); + p->set_guid(guid); + AutofillModelAssociator::FillProfileWithServerData(p.get(), profile); - - model_associator_->Associate(&tag, sync_id); if (!web_database_->AddAutoFillProfile(*p.get())) { - NOTREACHED() << "Couldn't add autofill profile: " << profile.label(); + NOTREACHED() << "Couldn't add autofill profile: " << guid; return; } + model_associator_->Associate(&guid, sync_id); 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; + const std::string* guid = model_associator_->GetChromeNodeFromSyncId( + sync_id); + if (guid == NULL) { + LOG(ERROR) << " Model association has not happened for " << sync_id; + error_handler()->OnUnrecoverableError(FROM_HERE, + "model association has not happened"); return; } - AutofillModelAssociator::OverwriteProfileWithServerData(p, profile); - if (!web_database_->UpdateAutoFillProfile(*p)) { - NOTREACHED() << "Couldn't update autofill profile: " << label; + AutoFillProfile *temp_ptr; + if (!web_database_->GetAutoFillProfileForGUID(*guid, &temp_ptr)) { + LOG(ERROR) << "Autofill profile not found for " << *guid; + return; + } + + scoped_ptr<AutoFillProfile> p(temp_ptr); + + AutofillModelAssociator::FillProfileWithServerData(p.get(), profile); + if (!web_database_->UpdateAutoFillProfile(*(p.get()))) { + LOG(ERROR) << "Couldn't update autofill profile: " << guid; return; } - delete p; break; } default: @@ -488,20 +380,19 @@ void AutofillChangeProcessor::ApplySyncAutofillProfileChange( } 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; + + const std::string *guid = model_associator_->GetChromeNodeFromSyncId(sync_id); + if (guid == NULL) { + LOG(ERROR)<< "The profile is not associated"; return; } - if (!web_database_->RemoveAutoFillProfile(p->guid())) { - NOTREACHED() << "Couldn't remove autofill profile: " << label; + + if (!web_database_->RemoveAutoFillProfile(*guid)) { + LOG(ERROR) << "Could not remove the profile"; return; } + model_associator_->Disassociate(sync_id); } @@ -520,8 +411,6 @@ void AutofillChangeProcessor::StartObserving() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); notification_registrar_.Add(this, NotificationType::AUTOFILL_ENTRIES_CHANGED, NotificationService::AllSources()); - notification_registrar_.Add(this, NotificationType::AUTOFILL_PROFILE_CHANGED, - NotificationService::AllSources()); } void AutofillChangeProcessor::StopObserving() { @@ -549,7 +438,6 @@ 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( @@ -577,5 +465,8 @@ void AutofillChangeProcessor::WriteAutofillProfile( AutoFillType(PHONE_HOME_WHOLE_NUMBER)))); node->SetAutofillSpecifics(autofill); } +bool AutofillChangeProcessor::HasNotMigratedYet() { + return true; +} } // namespace browser_sync diff --git a/chrome/browser/sync/glue/autofill_change_processor.h b/chrome/browser/sync/glue/autofill_change_processor.h index 7a84520..e901c8e 100644 --- a/chrome/browser/sync/glue/autofill_change_processor.h +++ b/chrome/browser/sync/glue/autofill_change_processor.h @@ -12,6 +12,7 @@ #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/autofill_change_processor2.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" @@ -104,43 +105,15 @@ class AutofillChangeProcessor : public ChangeProcessor, 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. - // |existing_unique_label| is the current label of the object, if any; this - // is an allowed value, because it's taken by the item in question. - // For new items, set |existing_unique_label| to the empty string. - void ChangeProfileLabelIfAlreadyTaken( - sync_api::BaseTransaction* trans, - const string16& existing_unique_label, - AutoFillProfile* profile, - std::string* tag); - - // Reassign the label of the profile, write this back to the web database, - // and update |tag| with the tag corresponding to the new label. - void OverrideProfileLabel( - const string16& new_label, - AutoFillProfile* profile_to_update, - std::string* tag_to_update); - - // 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(); + // Called to see if we need to upgrade to the new autofill2 profile. + bool HasNotMigratedYet(); + // The two models should be associated according to this ModelAssociator. AutofillModelAssociator* model_associator_; diff --git a/chrome/browser/sync/glue/autofill_change_processor2.cc b/chrome/browser/sync/glue/autofill_change_processor2.cc new file mode 100644 index 0000000..b2b564e --- /dev/null +++ b/chrome/browser/sync/glue/autofill_change_processor2.cc @@ -0,0 +1,584 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/glue/autofill_change_processor2.h" + +#include <string> +#include <vector> + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/autofill/personal_data_manager.h" +#include "chrome/browser/sync/glue/autofill_model_associator.h" +#include "chrome/browser/sync/glue/autofill_model_associator2.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/webdata/autofill_change.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/browser/webdata/web_database.h" +#include "chrome/common/notification_service.h" + +namespace browser_sync { + +class AutofillModelAssociator2; +struct AutofillChangeProcessor2::AutofillChangeRecord { + sync_api::SyncManager::ChangeRecord::Action action_; + int64 id_; + sync_pb::AutofillSpecifics autofill_; + AutofillChangeRecord(sync_api::SyncManager::ChangeRecord::Action action, + int64 id, const sync_pb::AutofillSpecifics& autofill) + : action_(action), + id_(id), + autofill_(autofill) { } +}; + +AutofillChangeProcessor2::AutofillChangeProcessor2( + AutofillModelAssociator2* model_associator, + WebDatabase* web_database, + PersonalDataManager* personal_data, + UnrecoverableErrorHandler* error_handler) + : ChangeProcessor(error_handler), + model_associator_(model_associator), + web_database_(web_database), + personal_data_(personal_data), + observing_(false) { + DCHECK(model_associator); + DCHECK(web_database); + DCHECK(error_handler); + DCHECK(personal_data); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + StartObserving(); +} + +AutofillChangeProcessor2::~AutofillChangeProcessor2() {} + +void AutofillChangeProcessor2::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + // Ensure this notification came from our web database. + WebDataService* wds = Source<WebDataService>(source).ptr(); + if (!wds || wds->GetDatabase() != web_database_) + return; + + DCHECK(running()); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!observing_) + return; + + sync_api::WriteTransaction trans(share_handle()); + sync_api::ReadNode autofill_root(&trans); + if (!autofill_root.InitByTagLookup(kAutofillTag)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Server did not create the top-level autofill node. " + "We might be running against an out-of-date server."); + return; + } + + 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 AutofillChangeProcessor2::ChangeProfileLabelIfAlreadyTaken( + sync_api::BaseTransaction* trans, + const string16& pre_update_label, + AutoFillProfile* profile, + std::string* tag) { + DCHECK_EQ(AutofillModelAssociator2::ProfileLabelToTag(profile->Label()), + *tag); + sync_api::ReadNode read_node(trans); + if (!pre_update_label.empty() && pre_update_label == profile->Label()) + return; + if (read_node.InitByClientTagLookup(syncable::AUTOFILL, *tag)) { + // Handle the edge case of duplicate labels. + string16 new_label(AutofillModelAssociator2::MakeUniqueLabel( + profile->Label(), pre_update_label, trans)); + if (new_label.empty()) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "No unique label; can't move aside"); + return; + } + OverrideProfileLabel(new_label, profile, tag); + } +} + +void AutofillChangeProcessor2::OverrideProfileLabel( + const string16& new_label, + AutoFillProfile* profile_to_update, + std::string* tag_to_update) { + tag_to_update->assign(AutofillModelAssociator2::ProfileLabelToTag(new_label)); + + profile_to_update->set_label(new_label); + if (!web_database_->UpdateAutoFillProfile(*profile_to_update)) { + std::string err = "Failed to overwrite label for node "; + err += UTF16ToUTF8(new_label); + error_handler()->OnUnrecoverableError(FROM_HERE, err); + return; + } + + // Notify the PersonalDataManager that it's out of date. + PostOptimisticRefreshTask(); +} + +void AutofillChangeProcessor2::PostOptimisticRefreshTask() { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + new AutofillModelAssociator2::DoOptimisticRefreshTask( + personal_data_)); +} + +void AutofillChangeProcessor2::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)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Failed to create autofill sync node."); + return; + } + sync_node.SetTitle(UTF8ToWide(tag)); + + WriteAutofillProfile(*profile, &sync_node); + model_associator_->Associate(&tag, sync_node.GetId()); +} + +void AutofillChangeProcessor2::ObserveAutofillProfileChanged( + AutofillProfileChange* change, sync_api::WriteTransaction* trans, + const sync_api::ReadNode& autofill_root) { + std::string tag(AutofillModelAssociator2::ProfileLabelToTag(change->key())); + switch (change->type()) { + case AutofillProfileChange::ADD: { + scoped_ptr<AutoFillProfile> clone( + static_cast<AutoFillProfile*>(change->profile()->Clone())); + DCHECK_EQ(clone->Label(), change->key()); + ChangeProfileLabelIfAlreadyTaken(trans, string16(), clone.get(), &tag); + AddAutofillProfileSyncNode(trans, autofill_root, tag, clone.get()); + break; + } + case AutofillProfileChange::UPDATE: { + scoped_ptr<AutoFillProfile> clone( + static_cast<AutoFillProfile*>(change->profile()->Clone())); + std::string pre_update_tag = AutofillModelAssociator2::ProfileLabelToTag( + change->pre_update_label()); + DCHECK_EQ(clone->Label(), change->key()); + sync_api::WriteNode sync_node(trans); + ChangeProfileLabelIfAlreadyTaken(trans, change->pre_update_label(), + clone.get(), &tag); + if (pre_update_tag != tag) { + // If the label changes, replace the node instead of updating it. + RemoveSyncNode(pre_update_tag, trans); + AddAutofillProfileSyncNode(trans, autofill_root, tag, clone.get()); + return; + } + int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); + if (sync_api::kInvalidId == sync_id) { + std::string err = "Unexpected notification for: " + tag; + error_handler()->OnUnrecoverableError(FROM_HERE, err); + return; + } else { + if (!sync_node.InitByIdLookup(sync_id)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Autofill node lookup failed."); + return; + } + WriteAutofillProfile(*clone.get(), &sync_node); + } + break; + } + case AutofillProfileChange::REMOVE: { + RemoveSyncNode(tag, trans); + break; + } + } +} + +void AutofillChangeProcessor2::ObserveAutofillEntriesChanged( + AutofillChangeList* changes, sync_api::WriteTransaction* trans, + const sync_api::ReadNode& autofill_root) { + for (AutofillChangeList::iterator change = changes->begin(); + change != changes->end(); ++change) { + switch (change->type()) { + case AutofillChange::ADD: + { + sync_api::WriteNode sync_node(trans); + std::string tag = + AutofillModelAssociator2::KeyToTag(change->key().name(), + change->key().value()); + if (!sync_node.InitUniqueByCreation(syncable::AUTOFILL, + autofill_root, tag)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Failed to create autofill sync node."); + return; + } + + std::vector<base::Time> timestamps; + if (!web_database_->GetAutofillTimestamps( + change->key().name(), + change->key().value(), + ×tamps)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Failed to get timestamps."); + return; + } + + sync_node.SetTitle(UTF8ToWide(tag)); + + WriteAutofillEntry(AutofillEntry(change->key(), timestamps), + &sync_node); + model_associator_->Associate(&tag, sync_node.GetId()); + } + break; + + case AutofillChange::UPDATE: + { + sync_api::WriteNode sync_node(trans); + std::string tag = AutofillModelAssociator2::KeyToTag( + change->key().name(), change->key().value()); + int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); + if (sync_api::kInvalidId == sync_id) { + std::string err = "Unexpected notification for: " + + UTF16ToUTF8(change->key().name()); + error_handler()->OnUnrecoverableError(FROM_HERE, err); + return; + } else { + if (!sync_node.InitByIdLookup(sync_id)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Autofill node lookup failed."); + return; + } + } + + std::vector<base::Time> timestamps; + if (!web_database_->GetAutofillTimestamps( + change->key().name(), + change->key().value(), + ×tamps)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Failed to get timestamps."); + return; + } + + WriteAutofillEntry(AutofillEntry(change->key(), timestamps), + &sync_node); + } + break; + case AutofillChange::REMOVE: { + std::string tag = AutofillModelAssociator2::KeyToTag( + change->key().name(), change->key().value()); + RemoveSyncNode(tag, trans); + } + break; + } + } +} + +void AutofillChangeProcessor2::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) { + std::string err = "Unexpected notification for: " + tag; + error_handler()->OnUnrecoverableError(FROM_HERE, err); + return; + } else { + if (!sync_node.InitByIdLookup(sync_id)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Autofill node lookup failed."); + return; + } + model_associator_->Disassociate(sync_node.GetId()); + sync_node.Remove(); + } +} + +void AutofillChangeProcessor2::ApplyChangesFromSyncModel( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!running()) + return; + StopObserving(); + + sync_api::ReadNode autofill_root(trans); + if (!autofill_root.InitByTagLookup(kAutofillTag)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Autofill root node lookup failed."); + return; + } + + for (int i = 0; i < change_count; ++i) { + sync_api::SyncManager::ChangeRecord::Action action(changes[i].action); + if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == action) { + DCHECK(changes[i].specifics.HasExtension(sync_pb::autofill)) + << "Autofill specifics data not present on delete!"; + const sync_pb::AutofillSpecifics& autofill = + changes[i].specifics.GetExtension(sync_pb::autofill); + if (autofill.has_value() || autofill.has_profile()) { + autofill_changes_.push_back(AutofillChangeRecord(changes[i].action, + changes[i].id, + autofill)); + } else { + NOTREACHED() << "Autofill specifics has no data!"; + } + continue; + } + + // Handle an update or add. + sync_api::ReadNode sync_node(trans); + if (!sync_node.InitByIdLookup(changes[i].id)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Autofill node lookup failed."); + return; + } + + // Check that the changed node is a child of the autofills folder. + DCHECK(autofill_root.GetId() == sync_node.GetParentId()); + DCHECK(syncable::AUTOFILL == sync_node.GetModelType()); + + const sync_pb::AutofillSpecifics& autofill( + sync_node.GetAutofillSpecifics()); + int64 sync_id = sync_node.GetId(); + if (autofill.has_value() || autofill.has_profile()) { + autofill_changes_.push_back(AutofillChangeRecord(changes[i].action, + sync_id, autofill)); + } else { + NOTREACHED() << "Autofill specifics has no data!"; + } + } + + StartObserving(); +} + +void AutofillChangeProcessor2::CommitChangesFromSyncModel() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!running()) + return; + StopObserving(); + + std::vector<AutofillEntry> new_entries; + for (unsigned int i = 0; i < autofill_changes_.size(); i++) { + // Handle deletions. + if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == + autofill_changes_[i].action_) { + if (autofill_changes_[i].autofill_.has_value()) { + ApplySyncAutofillEntryDelete(autofill_changes_[i].autofill_); + } else if (autofill_changes_[i].autofill_.has_profile()) { + ApplySyncAutofillProfileDelete(autofill_changes_[i].autofill_.profile(), + autofill_changes_[i].id_); + } else { + NOTREACHED() << "Autofill's CommitChanges received change with no" + " data!"; + } + continue; + } + + // Handle update/adds. + if (autofill_changes_[i].autofill_.has_value()) { + ApplySyncAutofillEntryChange(autofill_changes_[i].action_, + autofill_changes_[i].autofill_, &new_entries, + autofill_changes_[i].id_); + } else if (autofill_changes_[i].autofill_.has_profile()) { + ApplySyncAutofillProfileChange(autofill_changes_[i].action_, + autofill_changes_[i].autofill_.profile(), + autofill_changes_[i].id_); + } else { + NOTREACHED() << "Autofill's CommitChanges received change with no data!"; + } + } + autofill_changes_.clear(); + + // Make changes + if (!web_database_->UpdateAutofillEntries(new_entries)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Could not update autofill entries."); + return; + } + + PostOptimisticRefreshTask(); + + StartObserving(); +} + +void AutofillChangeProcessor2::ApplySyncAutofillEntryDelete( + const sync_pb::AutofillSpecifics& autofill) { + if (!web_database_->RemoveFormElement( + UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Could not remove autofill node."); + return; + } +} + +void AutofillChangeProcessor2::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(AutofillModelAssociator2::KeyToTag(k.name(), k.value())); + if (action == sync_api::SyncManager::ChangeRecord::ACTION_ADD) + model_associator_->Associate(&tag, sync_id); +} + +void AutofillChangeProcessor2::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(AutofillModelAssociator2::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()))); + AutofillModelAssociator2::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; + } + AutofillModelAssociator2::OverwriteProfileWithServerData(p, profile); + if (!web_database_->UpdateAutoFillProfile(*p)) { + NOTREACHED() << "Couldn't update autofill profile: " << label; + return; + } + delete p; + break; + } + default: + NOTREACHED(); + } +} + +void AutofillChangeProcessor2::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->guid())) { + NOTREACHED() << "Couldn't remove autofill profile: " << label; + return; + } + model_associator_->Disassociate(sync_id); +} + +void AutofillChangeProcessor2::StartImpl(Profile* profile) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + observing_ = true; +} + +void AutofillChangeProcessor2::StopImpl() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + observing_ = false; +} + + +void AutofillChangeProcessor2::StartObserving() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + notification_registrar_.Add(this, NotificationType::AUTOFILL_ENTRIES_CHANGED, + NotificationService::AllSources()); + notification_registrar_.Add(this, NotificationType::AUTOFILL_PROFILE_CHANGED, + NotificationService::AllSources()); +} + +void AutofillChangeProcessor2::StopObserving() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + notification_registrar_.RemoveAll(); +} + +// static +void AutofillChangeProcessor2::WriteAutofillEntry( + const AutofillEntry& entry, + sync_api::WriteNode* node) { + sync_pb::AutofillSpecifics autofill; + autofill.set_name(UTF16ToUTF8(entry.key().name())); + autofill.set_value(UTF16ToUTF8(entry.key().value())); + const std::vector<base::Time>& ts(entry.timestamps()); + for (std::vector<base::Time>::const_iterator timestamp = ts.begin(); + timestamp != ts.end(); ++timestamp) { + autofill.add_usage_timestamp(timestamp->ToInternalValue()); + } + node->SetAutofillSpecifics(autofill); +} + +// static +void AutofillChangeProcessor2::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_processor2.h b/chrome/browser/sync/glue/autofill_change_processor2.h new file mode 100644 index 0000000..25d7f51 --- /dev/null +++ b/chrome/browser/sync/glue/autofill_change_processor2.h @@ -0,0 +1,172 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_GLUE_AUTOFILL_CHANGE_PROCESSOR2_H_ +#define CHROME_BROWSER_SYNC_GLUE_AUTOFILL_CHANGE_PROCESSOR2_H_ +#pragma once + +#include <string> +#include <vector> + +#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 { + +class AutofillModelAssociator2; +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 DB thread. +class AutofillChangeProcessor2 : public ChangeProcessor, + public NotificationObserver { + public: + AutofillChangeProcessor2(AutofillModelAssociator2* model_associator, + WebDatabase* web_database, + PersonalDataManager* personal_data, + UnrecoverableErrorHandler* error_handler); + virtual ~AutofillChangeProcessor2(); + + // NotificationObserver implementation. + // WebDataService -> sync_api model change application. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // sync_api model -> WebDataService change application. + virtual void ApplyChangesFromSyncModel( + const sync_api::BaseTransaction* trans, + const sync_api::SyncManager::ChangeRecord* changes, + int change_count); + + // Commit any changes from ApplyChangesFromSyncModel buffered in + // autofill_changes_. + virtual void CommitChangesFromSyncModel(); + + // Copy the properties of the given Autofill entry into the sync + // node. + 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); + // TODO(georgey) : add the same processing for CC info (already in protocol + // buffers). + + protected: + virtual void StartImpl(Profile* profile); + virtual void StopImpl(); + + private: + 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. + // |existing_unique_label| is the current label of the object, if any; this + // is an allowed value, because it's taken by the item in question. + // For new items, set |existing_unique_label| to the empty string. + void ChangeProfileLabelIfAlreadyTaken( + sync_api::BaseTransaction* trans, + const string16& existing_unique_label, + AutoFillProfile* profile, + std::string* tag); + + // Reassign the label of the profile, write this back to the web database, + // and update |tag| with the tag corresponding to the new label. + void OverrideProfileLabel( + const string16& new_label, + AutoFillProfile* profile_to_update, + std::string* tag_to_update); + + // 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. + AutofillModelAssociator2* model_associator_; + + // The model we are processing changes from. This is owned by the + // WebDataService which is kept alive by our data type controller + // 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_; + + // Record of changes from ApplyChangesFromSyncModel. These are then processed + // in CommitChangesFromSyncModel. + struct AutofillChangeRecord; + std::vector<AutofillChangeRecord> autofill_changes_; + + DISALLOW_COPY_AND_ASSIGN(AutofillChangeProcessor2); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_AUTOFILL_CHANGE_PROCESSOR2_H_ + diff --git a/chrome/browser/sync/glue/autofill_model_associator.cc b/chrome/browser/sync/glue/autofill_model_associator.cc index b550ab5..6409e9b 100644 --- a/chrome/browser/sync/glue/autofill_model_associator.cc +++ b/chrome/browser/sync/glue/autofill_model_associator.cc @@ -12,6 +12,7 @@ #include "base/utf_string_conversions.h" #include "chrome/browser/autofill/autofill_profile.h" #include "chrome/browser/browser_thread.h" +#include "chrome/browser/guid.h" #include "chrome/browser/profile.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/autofill_change_processor.h" @@ -26,9 +27,6 @@ namespace browser_sync { const char kAutofillTag[] = "google_chrome_autofill"; const char kAutofillEntryNamespaceTag[] = "autofill_entry|"; -const char kAutofillProfileNamespaceTag[] = "autofill_profile|"; - -static const int kMaxNumAttemptsToFindUniqueLabel = 100; struct AutofillModelAssociator::DataBundle { std::set<AutofillKey> current_entries; @@ -123,87 +121,6 @@ bool AutofillModelAssociator::TraverseAndAssociateChromeAutofillEntries( 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, string16(), write_trans); - if (label.empty()) { - return false; - } - tag = ProfileLabelToTag(label); - // TODO(dhollowa): Replace with |AutoFillProfile::set_guid|. - // http://crbug.com/58813 - (*ix)->set_label(label); - if (!MakeNewAutofillProfileSyncNode(write_trans, autofill_root, - tag, **ix, &sync_id)) { - return false; - } - 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; - if (!MakeNewAutofillProfileSyncNode(write_trans, autofill_root, - tag, **ix, &id)) { - return false; - } - Associate(&tag, id); - } - current_profiles->insert(label); - } - return true; -} - -// static -string16 AutofillModelAssociator::MakeUniqueLabel( - const string16& non_unique_label, - const string16& existing_unique_label, - sync_api::BaseTransaction* trans) { - if (!non_unique_label.empty() && non_unique_label == existing_unique_label) { - return existing_unique_label; - } - int unique_id = 1; // Priming so we start by appending "2". - while (unique_id++ < kMaxNumAttemptsToFindUniqueLabel) { - string16 suffix(base::IntToString16(unique_id)); - string16 unique_label = non_unique_label + suffix; - if (unique_label == existing_unique_label) - return unique_label; // We'll use the one we already have. - 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(); -} - bool AutofillModelAssociator::MakeNewAutofillProfileSyncNode( sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill_root, const std::string& tag, const AutoFillProfile& profile, int64* sync_id) { @@ -265,11 +182,15 @@ bool AutofillModelAssociator::AssociateModels() { } 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)) { + entries, &bundle.current_entries, &bundle.new_entries)) { + return false; + } + + if (!TraverseAndAssociateAllSyncNodes( + &trans, + autofill_root, + &bundle, + profiles.get())) { return false; } } @@ -319,7 +240,8 @@ bool AutofillModelAssociator::SaveChangesToWebData(const DataBundle& bundle) { bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, - DataBundle* bundle) { + DataBundle* bundle, + const std::vector<AutoFillProfile*>& all_profiles_from_db) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); int64 sync_child_id = autofill_root.GetFirstChildId(); @@ -332,18 +254,57 @@ bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( const sync_pb::AutofillSpecifics& autofill( sync_child.GetAutofillSpecifics()); - if (autofill.has_value()) + if (autofill.has_value()) { AddNativeEntryIfNeeded(autofill, bundle, sync_child); - else if (autofill.has_profile()) - AddNativeProfileIfNeeded(autofill.profile(), bundle, sync_child); - else + } else if (autofill.has_profile() && HasNotMigratedYet()) { + // Ignore autofill profiles if we are not upgrading. + AddNativeProfileIfNeeded( + autofill.profile(), + bundle, + sync_child, + all_profiles_from_db); + } else { NOTREACHED() << "AutofillSpecifics has no autofill data!"; + } sync_child_id = sync_child.GetSuccessorId(); } return true; } +// Define the functor to be used as the predicate in find_if call. +struct CompareProfiles + : public std::binary_function<AutoFillProfile*, AutoFillProfile*, bool> { + bool operator() (AutoFillProfile* p1, AutoFillProfile* p2) const { + if (p1->Compare(*p2) == 0) + return true; + else + return false; + } +}; + +AutoFillProfile* AutofillModelAssociator::FindCorrespondingNodeFromWebDB( + const sync_pb::AutofillProfileSpecifics& profile, + const std::vector<AutoFillProfile*>& all_profiles_from_db) { + static std::string guid(guid::GenerateGUID()); + AutoFillProfile p; + p.set_guid(guid); + if (!FillProfileWithServerData(&p, profile)) { + // Not a big deal. We encountered an error. Just say this profile does not + // exist. + LOG(ERROR) << " Profile could not be associated"; + return NULL; + } + + // Now instantiate the functor and call find_if. + std::vector<AutoFillProfile*>::const_iterator ix = + std::find_if(all_profiles_from_db.begin(), + all_profiles_from_db.end(), + std::bind2nd(CompareProfiles(), &p)); + + return (ix == all_profiles_from_db.end()) ? NULL : *ix; +} + void AutofillModelAssociator::AddNativeEntryIfNeeded( const sync_pb::AutofillSpecifics& autofill, DataBundle* bundle, const sync_api::ReadNode& node) { @@ -364,16 +325,26 @@ void AutofillModelAssociator::AddNativeEntryIfNeeded( } void AutofillModelAssociator::AddNativeProfileIfNeeded( - const sync_pb::AutofillProfileSpecifics& profile, DataBundle* bundle, - const sync_api::ReadNode& node) { + const sync_pb::AutofillProfileSpecifics& profile, + DataBundle* bundle, + const sync_api::ReadNode& node, + const std::vector<AutoFillProfile*>& all_profiles_from_db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::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); + + scoped_ptr<AutoFillProfile> profile_in_web_db(FindCorrespondingNodeFromWebDB( + profile, all_profiles_from_db)); + + if (profile_in_web_db.get() != NULL) { + int64 sync_id = node.GetId(); + std::string guid = profile_in_web_db->guid(); + Associate(&guid, sync_id); + return; + } else { // Create a new node. + std::string guid = guid::GenerateGUID(); + Associate(&guid, node.GetId()); + AutoFillProfile* p = new AutoFillProfile(guid); + FillProfileWithServerData(p, profile); bundle->new_profiles.push_back(p); } } @@ -417,7 +388,8 @@ void AutofillModelAssociator::AbortAssociation() { const std::string* AutofillModelAssociator::GetChromeNodeFromSyncId(int64 sync_id) { - return NULL; + SyncIdToAutofillMap::const_iterator iter = id_map_inverse_.find(sync_id); + return iter == id_map_inverse_.end() ? NULL : &(iter->second); } bool AutofillModelAssociator::InitSyncNodeFromChromeId( @@ -476,12 +448,6 @@ std::string AutofillModelAssociator::KeyToTag(const string16& name, } // static -std::string AutofillModelAssociator::ProfileLabelToTag(const string16& label) { - std::string ns(kAutofillProfileNamespaceTag); - return ns + EscapePath(UTF16ToUTF8(label)); -} - -// static bool AutofillModelAssociator::MergeTimestamps( const sync_pb::AutofillSpecifics& autofill, const std::vector<base::Time>& timestamps, @@ -519,7 +485,7 @@ bool MergeField(FormGroup* f, AutoFillFieldType t, } // static -bool AutofillModelAssociator::OverwriteProfileWithServerData( +bool AutofillModelAssociator::FillProfileWithServerData( AutoFillProfile* merge_into, const sync_pb::AutofillProfileSpecifics& specifics) { bool diff = false; @@ -543,4 +509,8 @@ bool AutofillModelAssociator::OverwriteProfileWithServerData( return diff; } +bool AutofillModelAssociator::HasNotMigratedYet() { + return true; +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/autofill_model_associator.h b/chrome/browser/sync/glue/autofill_model_associator.h index d4e5363..ee81ed0 100644 --- a/chrome/browser/sync/glue/autofill_model_associator.h +++ b/chrome/browser/sync/glue/autofill_model_associator.h @@ -16,6 +16,7 @@ #include "base/ref_counted.h" #include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/autofill_model_associator2.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" @@ -99,12 +100,11 @@ 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( + static bool FillProfileWithServerData( AutoFillProfile* merge_into, const sync_pb::AutofillProfileSpecifics& specifics); @@ -114,15 +114,18 @@ class AutofillModelAssociator // Returns sync service instance. ProfileSyncService* sync_service() { return sync_service_; } - // Compute and apply suffix to a label so that the resulting label is - // unique in the sync database. - // |new_non_unique_label| is the colliding label which is to be uniquified. - // |existing_unique_label| is the current label of the object, if any; this - // is treated as a unique label even if colliding. If no such label is - // available, |existing_unique_label| may be empty. - static string16 MakeUniqueLabel(const string16& new_non_unique_label, - const string16& existing_unique_label, - sync_api::BaseTransaction* trans); + protected: + // Is called to determine if we need to upgrade to the new + // autofillprofile2 data type. If so we need to sync up autofillprofile + // first to the latest available changes on the server and then upgrade + // to autofillprofile2. + virtual bool HasNotMigratedYet(); + + // Given a profile from sync db it tries to match the profile against + // one in web db. it ignores the guid and compares the actual data. + AutoFillProfile* FindCorrespondingNodeFromWebDB( + const sync_pb::AutofillProfileSpecifics& profile, + const std::vector<AutoFillProfile*>& all_profiles_from_db); private: typedef std::map<std::string, int64> AutofillToSyncIdMap; @@ -156,7 +159,8 @@ class AutofillModelAssociator bool TraverseAndAssociateAllSyncNodes( sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, - DataBundle* bundle); + DataBundle* bundle, + const std::vector<AutoFillProfile*>& all_profiles_from_db); // Helper to persist any changes that occured during model association to // the WebDatabase. @@ -173,7 +177,8 @@ class AutofillModelAssociator void AddNativeProfileIfNeeded( const sync_pb::AutofillProfileSpecifics& profile, DataBundle* bundle, - const sync_api::ReadNode& node); + const sync_api::ReadNode& node, + const std::vector<AutoFillProfile*>& all_profiles_from_db); // 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 diff --git a/chrome/browser/sync/glue/autofill_model_associator2.cc b/chrome/browser/sync/glue/autofill_model_associator2.cc new file mode 100644 index 0000000..272c8ae --- /dev/null +++ b/chrome/browser/sync/glue/autofill_model_associator2.cc @@ -0,0 +1,547 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/glue/autofill_model_associator2.h" + +#include <vector> + +#include "base/task.h" +#include "base/time.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/autofill/autofill_profile.h" +#include "chrome/browser/browser_thread.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/autofill_change_processor.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/protocol/autofill_specifics.pb.h" +#include "chrome/browser/webdata/web_database.h" +#include "net/base/escape.h" + +using base::TimeTicks; + +namespace browser_sync { + +extern const char kAutofillTag[]; +extern const char kAutofillEntryNamespaceTag[]; +const char kAutofillProfileNamespaceTag[] = "autofill_profile|"; + +static const int kMaxNumAttemptsToFindUniqueLabel = 100; + +struct AutofillModelAssociator2::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); } +}; + +AutofillModelAssociator2::DoOptimisticRefreshTask::DoOptimisticRefreshTask( + PersonalDataManager* pdm) : pdm_(pdm) {} + +AutofillModelAssociator2::DoOptimisticRefreshTask::~DoOptimisticRefreshTask() {} + +void AutofillModelAssociator2::DoOptimisticRefreshTask::Run() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + pdm_->Refresh(); +} + +AutofillModelAssociator2::AutofillModelAssociator2( + ProfileSyncService* sync_service, + WebDatabase* web_database, + PersonalDataManager* personal_data) + : sync_service_(sync_service), + web_database_(web_database), + personal_data_(personal_data), + autofill_node_id_(sync_api::kInvalidId), + abort_association_pending_(false) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + DCHECK(sync_service_); + DCHECK(web_database_); + DCHECK(personal_data_); +} + +AutofillModelAssociator2::~AutofillModelAssociator2() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); +} + +bool AutofillModelAssociator2::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; + } + AutofillChangeProcessor2::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."; + return false; + } + node.SetTitle(UTF8ToWide(tag)); + AutofillChangeProcessor2::WriteAutofillEntry(*ix, &node); + Associate(&tag, node.GetId()); + } + + current_entries->insert(ix->key()); + } + return true; +} + +bool AutofillModelAssociator2::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, string16(), write_trans); + if (label.empty()) { + return false; + } + tag = ProfileLabelToTag(label); + // TODO(dhollowa): Replace with |AutoFillProfile::set_guid|. + // http://crbug.com/58813 + (*ix)->set_label(label); + if (!MakeNewAutofillProfileSyncNode(write_trans, autofill_root, + tag, **ix, &sync_id)) { + return false; + } + 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; + if (!MakeNewAutofillProfileSyncNode(write_trans, autofill_root, + tag, **ix, &id)) { + return false; + } + Associate(&tag, id); + } + current_profiles->insert(label); + } + return true; +} + +// static +string16 AutofillModelAssociator2::MakeUniqueLabel( + const string16& non_unique_label, + const string16& existing_unique_label, + sync_api::BaseTransaction* trans) { + if (!non_unique_label.empty() && non_unique_label == existing_unique_label) { + return existing_unique_label; + } + int unique_id = 1; // Priming so we start by appending "2". + while (unique_id++ < kMaxNumAttemptsToFindUniqueLabel) { + string16 suffix(base::IntToString16(unique_id)); + string16 unique_label = non_unique_label + suffix; + if (unique_label == existing_unique_label) + return unique_label; // We'll use the one we already have. + 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(); +} + +bool AutofillModelAssociator2::MakeNewAutofillProfileSyncNode( + sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill_root, + const std::string& tag, const AutoFillProfile& profile, int64* sync_id) { + sync_api::WriteNode node(trans); + if (!node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)) { + LOG(ERROR) << "Failed to create autofill sync node."; + return false; + } + node.SetTitle(UTF8ToWide(tag)); + AutofillChangeProcessor2::WriteAutofillProfile(profile, &node); + *sync_id = node.GetId(); + return true; +} + + +bool AutofillModelAssociator2::LoadAutofillData( + std::vector<AutofillEntry>* entries, + std::vector<AutoFillProfile*>* profiles) { + if (IsAbortPending()) + return false; + if (!web_database_->GetAllAutofillEntries(entries)) + return false; + + if (IsAbortPending()) + return false; + if (!web_database_->GetAutoFillProfiles(profiles)) + return false; + + return true; +} + +bool AutofillModelAssociator2::AssociateModels() { + VLOG(1) << "Associating Autofill Models"; + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + { + AutoLock lock(abort_association_pending_lock_); + abort_association_pending_ = false; + } + + // TODO(zork): Attempt to load the model association from storage. + std::vector<AutofillEntry> entries; + ScopedVector<AutoFillProfile> profiles; + + if (!LoadAutofillData(&entries, &profiles.get())) { + LOG(ERROR) << "Could not get the autofill data from WebDatabase."; + return false; + } + + DataBundle bundle; + { + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + + sync_api::ReadNode autofill_root(&trans); + if (!autofill_root.InitByTagLookup(kAutofillTag)) { + LOG(ERROR) << "Server did not create the top-level autofill node. We " + << "might be running against an out-of-date server."; + return false; + } + + 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; + } + } + + // Since we're on the DB thread, we don't have to worry about updating + // the autofill database after closing the write transaction, since + // 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 (!SaveChangesToWebData(bundle)) { + LOG(ERROR) << "Failed to update autofill entries."; + return false; + } + + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + new DoOptimisticRefreshTask(personal_data_)); + return true; +} + +bool AutofillModelAssociator2::SaveChangesToWebData(const DataBundle& bundle) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + if (IsAbortPending()) + return false; + + 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 (IsAbortPending()) + return false; + if (!web_database_->AddAutoFillProfile(*bundle.new_profiles[i])) + return false; + } + + for (size_t i = 0; i < bundle.updated_profiles.size(); i++) { + if (IsAbortPending()) + return false; + if (!web_database_->UpdateAutoFillProfile(*bundle.updated_profiles[i])) + return false; + } + return true; +} + +bool AutofillModelAssociator2::TraverseAndAssociateAllSyncNodes( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + DataBundle* bundle) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::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."; + 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 AutofillModelAssociator2::AddNativeEntryIfNeeded( + const sync_pb::AutofillSpecifics& autofill, DataBundle* bundle, + const sync_api::ReadNode& node) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::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 AutofillModelAssociator2::AddNativeProfileIfNeeded( + const sync_pb::AutofillProfileSpecifics& profile, DataBundle* bundle, + const sync_api::ReadNode& node) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::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 AutofillModelAssociator2::DisassociateModels() { + id_map_.clear(); + id_map_inverse_.clear(); + return true; +} + +bool AutofillModelAssociator2::SyncModelHasUserCreatedNodes(bool* has_nodes) { + DCHECK(has_nodes); + *has_nodes = false; + int64 autofill_sync_id; + if (!GetSyncIdForTaggedNode(kAutofillTag, &autofill_sync_id)) { + LOG(ERROR) << "Server did not create the top-level autofill node. We " + << "might be running against an out-of-date server."; + return false; + } + sync_api::ReadTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + + sync_api::ReadNode autofill_node(&trans); + if (!autofill_node.InitByIdLookup(autofill_sync_id)) { + LOG(ERROR) << "Server did not create the top-level autofill node. We " + << "might be running against an out-of-date server."; + return false; + } + + // The sync model has user created nodes if the autofill folder has any + // children. + *has_nodes = sync_api::kInvalidId != autofill_node.GetFirstChildId(); + return true; +} + +void AutofillModelAssociator2::AbortAssociation() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + AutoLock lock(abort_association_pending_lock_); + abort_association_pending_ = true; +} + +const std::string* +AutofillModelAssociator2::GetChromeNodeFromSyncId(int64 sync_id) { + return NULL; +} + +bool AutofillModelAssociator2::InitSyncNodeFromChromeId( + std::string node_id, + sync_api::BaseNode* sync_node) { + return false; +} + +int64 AutofillModelAssociator2::GetSyncIdFromChromeId( + const std::string autofill) { + AutofillToSyncIdMap::const_iterator iter = id_map_.find(autofill); + return iter == id_map_.end() ? sync_api::kInvalidId : iter->second; +} + +void AutofillModelAssociator2::Associate( + const std::string* autofill, int64 sync_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + DCHECK_NE(sync_api::kInvalidId, sync_id); + DCHECK(id_map_.find(*autofill) == id_map_.end()); + DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end()); + id_map_[*autofill] = sync_id; + id_map_inverse_[sync_id] = *autofill; +} + +void AutofillModelAssociator2::Disassociate(int64 sync_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + SyncIdToAutofillMap::iterator iter = id_map_inverse_.find(sync_id); + if (iter == id_map_inverse_.end()) + return; + CHECK(id_map_.erase(iter->second)); + id_map_inverse_.erase(iter); +} + +bool AutofillModelAssociator2::GetSyncIdForTaggedNode(const std::string& tag, + int64* sync_id) { + sync_api::ReadTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + sync_api::ReadNode sync_node(&trans); + if (!sync_node.InitByTagLookup(tag.c_str())) + return false; + *sync_id = sync_node.GetId(); + return true; +} + +bool AutofillModelAssociator2::IsAbortPending() { + AutoLock lock(abort_association_pending_lock_); + return abort_association_pending_; +} + +// static +std::string AutofillModelAssociator2::KeyToTag(const string16& name, + const string16& value) { + std::string ns(kAutofillEntryNamespaceTag); + return ns + EscapePath(UTF16ToUTF8(name)) + "|" + + EscapePath(UTF16ToUTF8(value)); +} + +// static +std::string AutofillModelAssociator2::ProfileLabelToTag(const string16& label) { + std::string ns(kAutofillProfileNamespaceTag); + return ns + EscapePath(UTF16ToUTF8(label)); +} + +// static +bool AutofillModelAssociator2::MergeTimestamps( + const sync_pb::AutofillSpecifics& autofill, + const std::vector<base::Time>& timestamps, + std::vector<base::Time>* new_timestamps) { + DCHECK(new_timestamps); + std::set<base::Time> timestamp_union(timestamps.begin(), + timestamps.end()); + + size_t timestamps_count = autofill.usage_timestamp_size(); + + bool different = timestamps.size() != timestamps_count; + for (size_t c = 0; c < timestamps_count; ++c) { + if (timestamp_union.insert(base::Time::FromInternalValue( + autofill.usage_timestamp(c))).second) { + different = true; + } + } + + if (different) { + new_timestamps->insert(new_timestamps->begin(), + timestamp_union.begin(), + timestamp_union.end()); + } + 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 MergeField2(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 AutofillModelAssociator2::OverwriteProfileWithServerData( + AutoFillProfile* merge_into, + const sync_pb::AutofillProfileSpecifics& specifics) { + bool diff = false; + AutoFillProfile* p = merge_into; + const sync_pb::AutofillProfileSpecifics& s(specifics); + diff = MergeField2(p, NAME_FIRST, s.name_first()) || diff; + diff = MergeField2(p, NAME_LAST, s.name_last()) || diff; + diff = MergeField2(p, NAME_MIDDLE, s.name_middle()) || diff; + diff = MergeField2(p, ADDRESS_HOME_LINE1, s.address_home_line1()) || diff; + diff = MergeField2(p, ADDRESS_HOME_LINE2, s.address_home_line2()) || diff; + diff = MergeField2(p, ADDRESS_HOME_CITY, s.address_home_city()) || diff; + diff = MergeField2(p, ADDRESS_HOME_STATE, s.address_home_state()) || diff; + diff = MergeField2(p, ADDRESS_HOME_COUNTRY, s.address_home_country()) || diff; + diff = MergeField2(p, ADDRESS_HOME_ZIP, s.address_home_zip()) || diff; + diff = MergeField2(p, EMAIL_ADDRESS, s.email_address()) || diff; + diff = MergeField2(p, COMPANY_NAME, s.company_name()) || diff; + diff = MergeField2(p, PHONE_FAX_WHOLE_NUMBER, s.phone_fax_whole_number()) + || diff; + diff = MergeField2(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_associator2.h b/chrome/browser/sync/glue/autofill_model_associator2.h new file mode 100644 index 0000000..0ad37de --- /dev/null +++ b/chrome/browser/sync/glue/autofill_model_associator2.h @@ -0,0 +1,212 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_GLUE_AUTOFILL_MODEL_ASSOCIATOR2_H_ +#define CHROME_BROWSER_SYNC_GLUE_AUTOFILL_MODEL_ASSOCIATOR2_H_ +#pragma once + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/lock.h" +#include "base/ref_counted.h" +#include "chrome/browser/autofill/personal_data_manager.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 AutofillModelAssociator2 + : public PerDataTypeAssociatorInterface<std::string, std::string> { + public: + static syncable::ModelType model_type() { return syncable::AUTOFILL; } + AutofillModelAssociator2(ProfileSyncService* sync_service, + WebDatabase* web_database, + PersonalDataManager* data_manager); + virtual ~AutofillModelAssociator2(); + + // 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); + virtual ~DoOptimisticRefreshTask(); + virtual void Run(); + private: + scoped_refptr<PersonalDataManager> pdm_; + }; + + // PerDataTypeAssociatorInterface implementation. + // + // Iterates through the sync model looking for matched pairs of items. + virtual bool AssociateModels(); + + // Clears all associations. + virtual bool DisassociateModels(); + + // The has_nodes out param is true if the sync model has nodes other + // than the permanent tagged nodes. + virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes); + + // See ModelAssociator interface. + virtual void AbortAssociation(); + + // Not implemented. + virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id); + + // Not implemented. + virtual bool InitSyncNodeFromChromeId(std::string node_id, + sync_api::BaseNode* sync_node); + + // 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(std::string node_id); + + // Associates the given autofill name with the given 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); + + // Returns whether a node with the given permanent tag was found and update + // |sync_id| with that node's id. + 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); + + // TODO(georgey) : add the same processing for CC info (already in protocol + // buffers). + + // Returns sync service instance. + ProfileSyncService* sync_service() { return sync_service_; } + + // Compute and apply suffix to a label so that the resulting label is + // unique in the sync database. + // |new_non_unique_label| is the colliding label which is to be uniquified. + // |existing_unique_label| is the current label of the object, if any; this + // is treated as a unique label even if colliding. If no such label is + // available, |existing_unique_label| may be empty. + static string16 MakeUniqueLabel(const string16& new_non_unique_label, + const string16& existing_unique_label, + sync_api::BaseTransaction* trans); + + private: + 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; + + // 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). + bool MakeNewAutofillProfileSyncNode( + sync_api::WriteTransaction* trans, + const sync_api::BaseNode& autofill_root, + const std::string& tag, + const AutoFillProfile& profile, + int64* sync_id); + + // Called at various points in model association to determine if the + // user requested an abort. + bool IsAbortPending(); + + ProfileSyncService* sync_service_; + WebDatabase* web_database_; + PersonalDataManager* personal_data_; + int64 autofill_node_id_; + + AutofillToSyncIdMap id_map_; + SyncIdToAutofillMap id_map_inverse_; + + // Abort association pending flag and lock. If this is set to true + // (via the AbortAssociation method), return from the + // AssociateModels method as soon as possible. + Lock abort_association_pending_lock_; + bool abort_association_pending_; + + DISALLOW_COPY_AND_ASSIGN(AutofillModelAssociator2); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_AUTOFILL_MODEL_ASSOCIATOR2_H_ + diff --git a/chrome/browser/sync/glue/autofill_model_associator_unittest.cc b/chrome/browser/sync/glue/autofill_model_associator_unittest.cc index cfd7e8d..a76f634 100644 --- a/chrome/browser/sync/glue/autofill_model_associator_unittest.cc +++ b/chrome/browser/sync/glue/autofill_model_associator_unittest.cc @@ -26,15 +26,3 @@ TEST_F(AutofillModelAssociatorTest, KeyToTag) { 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"))); -} diff --git a/chrome/browser/sync/glue/autofill_profile_model_associator.cc b/chrome/browser/sync/glue/autofill_profile_model_associator.cc new file mode 100644 index 0000000..0680da8 --- /dev/null +++ b/chrome/browser/sync/glue/autofill_profile_model_associator.cc @@ -0,0 +1,371 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/sync/glue/autofill_profile_model_associator.h" + +#include "base/utf_string_conversions.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/webdata/web_database.h" + +using sync_api::ReadNode; +namespace browser_sync { + +const char kAutofillProfileTag[] = "google_chrome_autofill_profile"; + +AutofillProfileModelAssociator::AutofillProfileModelAssociator( + ProfileSyncService* sync_service, + WebDatabase* web_database, + PersonalDataManager* personal_data) + : sync_service_(sync_service), + web_database_(web_database), + personal_data_(personal_data), + autofill_node_id_(sync_api::kInvalidId), + abort_association_pending_(false) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + DCHECK(sync_service_); + DCHECK(web_database_); + DCHECK(personal_data_); +} + +AutofillProfileModelAssociator::~AutofillProfileModelAssociator() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); +} + +bool AutofillProfileModelAssociator::TraverseAndAssociateChromeAutoFillProfiles( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + const std::vector<AutoFillProfile*>& all_profiles_from_db, + std::set<std::string>* current_profiles, + std::vector<AutoFillProfile*>* updated_profiles, + std::vector<AutoFillProfile*>* new_profiles, + std::vector<std::string>* profiles_to_delete) { + + // Alias the all_profiles_from_db so we fit in 80 characters + const std::vector<AutoFillProfile*>& profiles(all_profiles_from_db); + for (std::vector<AutoFillProfile*>::const_iterator ix = profiles.begin(); + ix != profiles.end(); + ++ix) { + std::string guid((*ix)->guid()); + + ReadNode node(write_trans); + if (node.InitByClientTagLookup(syncable::AUTOFILL_PROFILE, guid)) { + const sync_pb::AutofillProfileSpecifics& autofill( + node.GetAutofillProfileSpecifics()); + if (OverwriteProfileWithServerData(*ix, autofill)) { + updated_profiles->push_back(*ix); + } + Associate(&guid, node.GetId()); + current_profiles->insert(guid); + } else { + MakeNewAutofillProfileSyncNodeIfNeeded(write_trans, + autofill_root, + (**ix), + new_profiles, + current_profiles, + profiles_to_delete); + } + } + + return true; +} + +bool AutofillProfileModelAssociator::LoadAutofillData( + std::vector<AutoFillProfile*>* profiles) { + if (IsAbortPending()) + return false; + + if (!web_database_->GetAutoFillProfiles(profiles)) + return false; + + return true; +} + +bool AutofillProfileModelAssociator::AssociateModels() { + VLOG(1) << "Associating Autofill Models"; + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + { + AutoLock lock(abort_association_pending_lock_); + abort_association_pending_ = false; + } + + ScopedVector<AutoFillProfile> profiles; + + if (!LoadAutofillData(&profiles.get())) { + LOG(ERROR) << "Could not get the autofill data from WebDatabase."; + return false; + } + + DataBundle bundle; + { + // The write transaction lock is held inside this block. + // We do all the web db operations outside this block. + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + + sync_api::ReadNode autofill_root(&trans); + if (!autofill_root.InitByTagLookup(kAutofillProfileTag)) { + LOG(ERROR) << "Server did not create the top-level autofill node. We " + << "might be running against an out-of-date server."; + return false; + } + + if (!TraverseAndAssociateChromeAutoFillProfiles(&trans, autofill_root, + profiles.get(), &bundle.current_profiles, + &bundle.updated_profiles, + &bundle.new_profiles, + &bundle.profiles_to_delete) || + !TraverseAndAssociateAllSyncNodes(&trans, autofill_root, &bundle)) { + return false; + } + } + + if (!SaveChangesToWebData(bundle)) { + LOG(ERROR) << "Failed to update autofill entries."; + return false; + } + + // TODO(lipalani) Bug 64111- split out the OptimisticRefreshTask + // into its own class + // from autofill_model_associator + // Will be done as part of the autofill_model_associator work. + // BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + // new DoOptimisticRefreshTask(personal_data_)); + return true; +} + +bool AutofillProfileModelAssociator::DisassociateModels() { + id_map_.clear(); + id_map_inverse_.clear(); + return true; +} + +// 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 AutofillProfileModelAssociator::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; +} +bool AutofillProfileModelAssociator::SyncModelHasUserCreatedNodes( + bool *has_nodes) { + CHECK_NE(has_nodes, reinterpret_cast<bool*>(NULL)); + sync_api::ReadTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + + sync_api::ReadNode node(&trans); + + if (!node.InitByClientTagLookup( + syncable::AUTOFILL_PROFILE, + kAutofillProfileTag)) { + LOG(ERROR) << "Sever did not create a top level node" + << "Out of data server or autofill type not enabled"; + return false; + } + + *has_nodes = sync_api::kInvalidId != node.GetFirstChildId(); + return true; +} +// static +bool AutofillProfileModelAssociator::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; +} + + +int64 AutofillProfileModelAssociator::FindSyncNodeWithProfile( + sync_api::WriteTransaction* trans, + const sync_api::BaseNode& autofill_root, + const AutoFillProfile& profile_from_db) { + int64 sync_child_id = autofill_root.GetFirstChildId(); + while (sync_child_id != sync_api::kInvalidId) { + ReadNode read_node(trans); + AutoFillProfile p; + if (read_node.InitByIdLookup(sync_child_id)) { + LOG(ERROR) << "unable to find the id given by getfirst child " << + sync_child_id; + return sync_api::kInvalidId; + } + const sync_pb::AutofillProfileSpecifics& autofill_specifics( + read_node.GetAutofillProfileSpecifics()); + OverwriteProfileWithServerData(&p, autofill_specifics); + if (p.Compare(profile_from_db) == 0) { + return sync_child_id; + } + sync_child_id = read_node.GetSuccessorId(); + } + + return sync_api::kInvalidId; +} +bool AutofillProfileModelAssociator::MakeNewAutofillProfileSyncNodeIfNeeded( + sync_api::WriteTransaction* trans, + const sync_api::BaseNode& autofill_root, + const AutoFillProfile& profile, + std::vector<AutoFillProfile*>* new_profiles, + std::set<std::string>* current_profiles, + std::vector<std::string>* profiles_to_delete) { + + int64 sync_node_id = FindSyncNodeWithProfile(trans, autofill_root, profile); + if (sync_node_id != sync_api::kInvalidId) { + // In case of duplicates throw away the local profile and apply the + // server profile.(The only difference between the 2 profiles are the guids) + profiles_to_delete->push_back(profile.guid()); + sync_api::ReadNode read_node(trans); + if (!read_node.InitByIdLookup(sync_node_id)) { + LOG(ERROR); + return false; + } + const sync_pb::AutofillProfileSpecifics& autofill_specifics( + read_node.GetAutofillProfileSpecifics()); + AutoFillProfile* p = new AutoFillProfile(autofill_specifics.guid()); + OverwriteProfileWithServerData(p, autofill_specifics); + new_profiles->push_back(p); + std::string guid = autofill_specifics.guid(); + Associate(&guid, sync_node_id); + current_profiles->insert(autofill_specifics.guid()); + } else { + sync_api::WriteNode node(trans); + if (!node.InitUniqueByCreation( + syncable::AUTOFILL_PROFILE, autofill_root, profile.guid())) { + LOG(ERROR) << "Failed to create autofill sync node."; + return false; + } + node.SetTitle(UTF8ToWide(profile.guid())); + + // TODO(lipalani) -Bug 64111 This needs rewriting. This will be tackled + // when rewriting autofill change processor. + // AutofillChangeProcessor::WriteAutofillProfile(profile, &node); + } + return true; +} + +bool AutofillProfileModelAssociator::TraverseAndAssociateAllSyncNodes( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + DataBundle* bundle) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + int64 sync_child_id = autofill_root.GetFirstChildId(); + while (sync_child_id != sync_api::kInvalidId) { + ReadNode sync_child(write_trans); + if (!sync_child.InitByIdLookup(sync_child_id)) { + LOG(ERROR) << "Failed to fetch child node."; + return false; + } + const sync_pb::AutofillProfileSpecifics& autofill( + sync_child.GetAutofillProfileSpecifics()); + + AddNativeProfileIfNeeded(autofill, bundle, sync_child); + + sync_child_id = sync_child.GetSuccessorId(); + } + return true; +} + +void AutofillProfileModelAssociator::AddNativeProfileIfNeeded( + const sync_pb::AutofillProfileSpecifics& profile, + DataBundle* bundle, + const sync_api::ReadNode& node) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + if (bundle->current_profiles.find(profile.guid()) == + bundle->current_profiles.end()) { + std::string guid(profile.guid()); + Associate(&guid, node.GetId()); + AutoFillProfile* p = new AutoFillProfile(profile.guid()); + OverwriteProfileWithServerData(p, profile); + bundle->new_profiles.push_back(p); + } +} + +bool AutofillProfileModelAssociator::SaveChangesToWebData( + const DataBundle& bundle) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + if (IsAbortPending()) + return false; + + for (size_t i = 0; i < bundle.new_profiles.size(); i++) { + if (IsAbortPending()) + return false; + if (!web_database_->AddAutoFillProfile(*bundle.new_profiles[i])) + return false; + } + + for (size_t i = 0; i < bundle.updated_profiles.size(); i++) { + if (IsAbortPending()) + return false; + if (!web_database_->UpdateAutoFillProfile(*bundle.updated_profiles[i])) + return false; + } + + for (size_t i = 0; i< bundle.profiles_to_delete.size(); ++i) { + if (IsAbortPending()) + return false; + if (!web_database_->RemoveAutoFillProfile(bundle.profiles_to_delete[i])) + return false; + } + return true; +} + +void AutofillProfileModelAssociator::Associate( + const std::string* autofill, + int64 sync_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + DCHECK_NE(sync_api::kInvalidId, sync_id); + DCHECK(id_map_.find(*autofill) == id_map_.end()); + DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end()); + id_map_[*autofill] = sync_id; + id_map_inverse_[sync_id] = *autofill; +} + +void AutofillProfileModelAssociator::Disassociate(int64 sync_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + SyncIdToAutofillMap::iterator iter = id_map_inverse_.find(sync_id); + if (iter == id_map_inverse_.end()) + return; + CHECK(id_map_.erase(iter->second)); + id_map_inverse_.erase(iter); +} + +int64 AutofillProfileModelAssociator::GetSyncIdFromChromeId( + const std::string autofill) { + AutofillToSyncIdMap::const_iterator iter = id_map_.find(autofill); + return iter == id_map_.end() ? sync_api::kInvalidId : iter->second; +} + +void AutofillProfileModelAssociator::AbortAssociation() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + AutoLock lock(abort_association_pending_lock_); + abort_association_pending_ = true; +} + +bool AutofillProfileModelAssociator::IsAbortPending() { + AutoLock lock(abort_association_pending_lock_); + return abort_association_pending_; +} + +} // namespace browser_sync + diff --git a/chrome/browser/sync/glue/autofill_profile_model_associator.h b/chrome/browser/sync/glue/autofill_profile_model_associator.h new file mode 100644 index 0000000..75c75ef --- /dev/null +++ b/chrome/browser/sync/glue/autofill_profile_model_associator.h @@ -0,0 +1,199 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SYNC_GLUE_AUTOFILL_PROFILE_MODEL_ASSOCIATOR_H_
+#define CHROME_BROWSER_SYNC_GLUE_AUTOFILL_PROFILE_MODEL_ASSOCIATOR_H_
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/lock.h"
+#include "base/ref_counted.h"
+#include "chrome/browser/autofill/personal_data_manager.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;
+
+// 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 AutofillProfileModelAssociator
+ : public PerDataTypeAssociatorInterface<std::string, std::string> {
+ public:
+ AutofillProfileModelAssociator(ProfileSyncService* sync_service,
+ WebDatabase* web_database,
+ PersonalDataManager* data_manager);
+ virtual ~AutofillProfileModelAssociator();
+
+ // A convenience wrapper of a bunch of state we pass around while
+ // associating models, and send to the WebDatabase for persistence.
+ // We do this so we hold the write lock for only a small period.
+ // When storing the web db we are out of the write lock.
+ struct DataBundle;
+
+ static syncable::ModelType model_type() { return syncable::AUTOFILL_PROFILE; }
+
+ // PerDataTypeAssociatorInterface implementation.
+ //
+ // Iterates through the sync model looking for matched pairs of items.
+ virtual bool AssociateModels();
+
+ // Clears all associations.
+ virtual bool DisassociateModels();
+
+ // TODO(lipalani) Bug 64111.
+ // The has_nodes out param is true if the sync model has nodes other
+ // than the permanent tagged nodes.
+ virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes);
+
+ // See ModelAssociator interface.
+ virtual void AbortAssociation();
+
+ virtual const std::string* GetChromeNodeFromSyncId(
+ int64 sync_id) {
+ return NULL;
+ }
+
+ 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(std::string node_id);
+
+ // Associates the given autofill name with the given 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);
+
+ // TODO(lipalani) Bug 64111. Returns whether a node with the
+ // given permanent tag was found and update
+ // |sync_id| with that node's id. No current use. To Implement
+ // only for completeness.
+ virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id) {
+ return false;
+ }
+
+ // Returns sync service instance.
+ ProfileSyncService* sync_service() { return sync_service_; }
+
+ protected:
+ AutofillProfileModelAssociator() {}
+ bool TraverseAndAssociateChromeAutoFillProfiles(
+ sync_api::WriteTransaction* write_trans,
+ const sync_api::ReadNode& autofill_root,
+ const std::vector<AutoFillProfile*>& all_profiles_from_db,
+ std::set<std::string>* current_profiles,
+ std::vector<AutoFillProfile*>* updated_profiles,
+ std::vector<AutoFillProfile*>* new_profiles,
+ std::vector<std::string>* profiles_to_delete);
+
+ // Helper to insert an AutoFillProfile into the WebDatabase (e.g. in response
+ // to encountering a sync node that doesn't exist yet locally).
+ virtual 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).
+ virtual bool MakeNewAutofillProfileSyncNodeIfNeeded(
+ sync_api::WriteTransaction* trans,
+ const sync_api::BaseNode& autofill_root,
+ const AutoFillProfile& profile,
+ std::vector<AutoFillProfile*>* new_profiles,
+ std::set<std::string>* current_profiles,
+ std::vector<std::string>* profiles_to_delete);
+
+ // 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);
+
+ static bool OverwriteProfileWithServerData(
+ AutoFillProfile* merge_into,
+ const sync_pb::AutofillProfileSpecifics& specifics);
+
+ private:
+ 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;
+
+ // Helper to query WebDatabase for the current autofill state.
+ bool LoadAutofillData(std::vector<AutoFillProfile*>* profiles);
+
+ static bool MergeField(FormGroup* f,
+ AutoFillFieldType t,
+ const std::string& specifics_field);
+
+ // Helper to persist any changes that occured during model association to
+ // the WebDatabase.
+ bool SaveChangesToWebData(const DataBundle& bundle);
+
+ // Called at various points in model association to determine if the
+ // user requested an abort.
+ bool IsAbortPending();
+
+ int64 FindSyncNodeWithProfile(sync_api::WriteTransaction* trans,
+ const sync_api::BaseNode& autofill_root,
+ const AutoFillProfile& profile);
+
+ ProfileSyncService* sync_service_;
+ WebDatabase* web_database_;
+ PersonalDataManager* personal_data_;
+ int64 autofill_node_id_;
+
+ AutofillToSyncIdMap id_map_;
+ SyncIdToAutofillMap id_map_inverse_;
+
+ // Abort association pending flag and lock. If this is set to true
+ // (via the AbortAssociation method), return from the
+ // AssociateModels method as soon as possible.
+ Lock abort_association_pending_lock_;
+ bool abort_association_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutofillProfileModelAssociator);
+};
+
+struct AutofillProfileModelAssociator::DataBundle {
+ std::set<std::string> current_profiles;
+ std::vector<std::string> profiles_to_delete;
+ std::vector<AutoFillProfile*> updated_profiles;
+ std::vector<AutoFillProfile*> new_profiles; // We own these pointers.
+ ~DataBundle() { STLDeleteElements(&new_profiles); }
+};
+
+} // namespace browser_sync
+
+#endif // CHROME_BROWSER_SYNC_GLUE_AUTOFILL_PROFILE_MODEL_ASSOCIATOR_H_
+
diff --git a/chrome/browser/sync/glue/autofill_profile_model_associator_unittest.cc b/chrome/browser/sync/glue/autofill_profile_model_associator_unittest.cc new file mode 100644 index 0000000..ee6a9fc --- /dev/null +++ b/chrome/browser/sync/glue/autofill_profile_model_associator_unittest.cc @@ -0,0 +1,261 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/utf_string_conversions.h" +#include "chrome/browser/sync/engine/read_node_mock.h" +#include "chrome/browser/sync/engine/syncapi_mock.h" +#include "chrome/browser/sync/glue/autofill_profile_model_associator.h" +#include "chrome/browser/sync/syncable/syncable.h" +#include "chrome/browser/sync/syncable/syncable_mock.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Return; +using ::testing::DoDefault; +using ::testing::ReturnRef; +using ::testing::Pointee; +using ::testing::Ref; +using ::testing::Invoke; +class AutoFillProfile; + +using browser_sync::AutofillProfileModelAssociator; + +// Note this is not a generic mock. This is a mock used to +// test AutofillProfileModelAssociator class itself by mocking +// other functions that are called by the functions we need to test. +class MockAutofillProfileModelAssociator + : public AutofillProfileModelAssociator { + public: + MockAutofillProfileModelAssociator() { + } + virtual ~MockAutofillProfileModelAssociator() {} + bool TraverseAndAssociateChromeAutoFillProfilesWrapper( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + const std::vector<AutoFillProfile*>& all_profiles_from_db, + std::set<std::string>* current_profiles, + std::vector<AutoFillProfile*>* updated_profiles, + std::vector<AutoFillProfile*>* new_profiles, + std::vector<std::string>* profiles_to_delete) { + return TraverseAndAssociateChromeAutoFillProfiles(write_trans, + autofill_root, + all_profiles_from_db, + current_profiles, + updated_profiles, + new_profiles, + profiles_to_delete); + } + MOCK_METHOD3(AddNativeProfileIfNeeded, + void(const sync_pb::AutofillProfileSpecifics&, + DataBundle*, + const sync_api::ReadNode&)); + MOCK_METHOD2(OverwriteProfileWithServerData, + bool(AutoFillProfile*, + const sync_pb::AutofillProfileSpecifics&)); + MOCK_METHOD6(MakeNewAutofillProfileSyncNodeIfNeeded, + bool(sync_api::WriteTransaction*, + const sync_api::BaseNode&, + const AutoFillProfile&, + std::vector<AutoFillProfile*>*, + std::set<std::string>*, + std::vector<std::string>*)); + MOCK_METHOD2(Associate, void(const std::string*, int64)); + + bool TraverseAndAssociateAllSyncNodesWrapper( + sync_api::WriteTransaction *trans, + const sync_api::ReadNode &autofill_root, + browser_sync::AutofillProfileModelAssociator::DataBundle *bundle) { + return TraverseAndAssociateAllSyncNodes(trans, autofill_root, bundle); + } + + void AddNativeProfileIfNeededWrapper( + const sync_pb::AutofillProfileSpecifics& profile, + DataBundle* bundle, + const sync_api::ReadNode& node) { + AutofillProfileModelAssociator::AddNativeProfileIfNeeded( + profile, + bundle, + node); + } +}; + +class AutofillProfileModelAssociatorTest : public testing::Test { + public: + AutofillProfileModelAssociatorTest() + : db_thread_(BrowserThread::DB, &message_loop_) {} + + protected: + MessageLoop message_loop_; + BrowserThread db_thread_; + MockAutofillProfileModelAssociator associator_; +}; + +TEST_F(AutofillProfileModelAssociatorTest, + TestAssociateProfileInWebDBWithSyncDB) { + ScopedVector<AutoFillProfile> profiles_from_web_db; + std::string guid = "abc"; + + sync_pb::EntitySpecifics specifics; + MockDirectory mock_directory; + sync_pb::AutofillProfileSpecifics *profile_specifics = + specifics.MutableExtension(sync_pb::autofill_profile); + + profile_specifics->set_guid(guid); + + std::set<std::string> current_profiles; + + // This will be released inside the function + // TraverseAndAssociateChromeAutofillProfiles + AutoFillProfile *profile = new AutoFillProfile(guid); + + // Set up the entry kernel with what we want. + EntryKernel kernel; + kernel.put(syncable::SPECIFICS, specifics); + kernel.put(syncable::META_HANDLE, 1); + + MockWriteTransaction write_trans(&mock_directory); + EXPECT_CALL(mock_directory, GetEntryByClientTag(_)) + .WillOnce(Return(&kernel)); + + sync_api::ReadNode read_node(&write_trans); + + EXPECT_CALL(associator_, Associate(Pointee(guid), 1)); + + profiles_from_web_db.push_back(profile); + + associator_.TraverseAndAssociateChromeAutoFillProfilesWrapper(&write_trans, + read_node, + profiles_from_web_db.get(), + ¤t_profiles, + NULL, + NULL, + NULL); + + EXPECT_EQ((unsigned int)1, current_profiles.size()); +} + +TEST_F(AutofillProfileModelAssociatorTest, TestAssociatingMissingWebDBProfile) { + ScopedVector<AutoFillProfile> profiles_from_web_db; + MockDirectory mock_directory; + + MockWriteTransaction write_trans(&mock_directory); + EXPECT_CALL(mock_directory, + GetEntryByClientTag(_)) + .WillOnce(Return(reinterpret_cast<EntryKernel*>(NULL))); + + sync_api::ReadNode autofill_root(&write_trans); + + std::string guid = "abc"; + std::set<std::string> current_profiles; + AutoFillProfile *profile = new AutoFillProfile(guid); + + EXPECT_CALL(associator_, + MakeNewAutofillProfileSyncNodeIfNeeded(&write_trans, + Ref(autofill_root), + Ref(*profile), + _, + ¤t_profiles, + NULL)) + .WillOnce(Return(true)); + + profiles_from_web_db.push_back(profile); + + associator_.TraverseAndAssociateChromeAutoFillProfilesWrapper(&write_trans, + autofill_root, + profiles_from_web_db.get(), + ¤t_profiles, + NULL, + NULL, + NULL); +} + +TEST_F(AutofillProfileModelAssociatorTest, + TestAssociateProfileInSyncDBWithWebDB) { + ReadNodeMock autofill_root; + + // The constrcutor itself will initialize the id to root. + syncable::Id root_id; + + sync_pb::EntitySpecifics specifics; + MockDirectory mock_directory; + sync_pb::AutofillProfileSpecifics *profile_specifics = + specifics.MutableExtension(sync_pb::autofill_profile); + + profile_specifics->set_guid("abc"); + + // Set up the entry kernel with what we want. + EntryKernel kernel; + kernel.put(syncable::SPECIFICS, specifics); + kernel.put(syncable::META_HANDLE, 1); + kernel.put(syncable::ID, root_id); + + MockWriteTransaction write_trans(&mock_directory); + + browser_sync::AutofillProfileModelAssociator::DataBundle bundle; + + EXPECT_CALL(autofill_root, GetFirstChildId()) + .WillOnce(Return(1)); + + EXPECT_CALL(mock_directory, + GetEntryByHandle(_)) + .WillOnce(Return(&kernel)); + + EXPECT_CALL(associator_, + AddNativeProfileIfNeeded(_, + &bundle, + _)); + + associator_.TraverseAndAssociateAllSyncNodesWrapper(&write_trans, + autofill_root, + &bundle); +} + +TEST_F(AutofillProfileModelAssociatorTest, TestDontNeedToAddNativeProfile) { + ::testing::StrictMock<MockAutofillProfileModelAssociator> associator; + sync_pb::AutofillProfileSpecifics profile_specifics; + ReadNodeMock read_node; + std::string guid = "abc"; + std::set<std::string> current_profiles; + browser_sync::AutofillProfileModelAssociator::DataBundle bundle; + + profile_specifics.set_guid(guid); + + bundle.current_profiles.insert(guid); + + // We have no expectations to set. We have used a strict mock. + // If the profile is already present no other function + // should be called. + associator.AddNativeProfileIfNeededWrapper(profile_specifics, &bundle, + read_node); +} + +TEST_F(AutofillProfileModelAssociatorTest, TestNeedToAddNativeProfile) { + sync_pb::AutofillProfileSpecifics profile_specifics; + ReadNodeMock read_node; + std::string guid = "abc"; + std::set<std::string> current_profiles; + browser_sync::AutofillProfileModelAssociator::DataBundle bundle; + std::string first_name = "lingesh"; + + profile_specifics.set_guid(guid); + profile_specifics.set_name_first(first_name); + + EXPECT_CALL(read_node, GetId()) + .WillOnce(Return(1)); + + EXPECT_CALL(associator_, + Associate(Pointee(guid), 1)); + + associator_.AddNativeProfileIfNeededWrapper( + profile_specifics, + &bundle, + read_node); + + EXPECT_EQ(bundle.new_profiles.size(), (unsigned int)1); + EXPECT_EQ( + bundle.new_profiles.front()->GetFieldText(AutoFillType(NAME_FIRST)), + UTF8ToUTF16(first_name)); +} + diff --git a/chrome/browser/sync/profile_sync_factory_impl.cc b/chrome/browser/sync/profile_sync_factory_impl.cc index ed8f429..3e3945e5 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.cc +++ b/chrome/browser/sync/profile_sync_factory_impl.cc @@ -7,8 +7,10 @@ #include "chrome/browser/profile.h" #include "chrome/browser/sync/glue/app_data_type_controller.h" #include "chrome/browser/sync/glue/autofill_change_processor.h" +#include "chrome/browser/sync/glue/autofill_change_processor2.h" #include "chrome/browser/sync/glue/autofill_data_type_controller.h" #include "chrome/browser/sync/glue/autofill_model_associator.h" +#include "chrome/browser/sync/glue/autofill_model_associator2.h" #include "chrome/browser/sync/glue/bookmark_change_processor.h" #include "chrome/browser/sync/glue/bookmark_data_type_controller.h" #include "chrome/browser/sync/glue/bookmark_model_associator.h" @@ -40,8 +42,10 @@ using browser_sync::AppDataTypeController; using browser_sync::AutofillChangeProcessor; +using browser_sync::AutofillChangeProcessor2; using browser_sync::AutofillDataTypeController; using browser_sync::AutofillModelAssociator; +using browser_sync::AutofillModelAssociator2; using browser_sync::BookmarkChangeProcessor; using browser_sync::BookmarkDataTypeController; using browser_sync::BookmarkModelAssociator; @@ -172,16 +176,30 @@ ProfileSyncFactoryImpl::CreateAutofillSyncComponents( WebDatabase* web_database, PersonalDataManager* personal_data, browser_sync::UnrecoverableErrorHandler* error_handler) { - AutofillModelAssociator* model_associator = - new AutofillModelAssociator(profile_sync_service, - web_database, - personal_data); - AutofillChangeProcessor* change_processor = - new AutofillChangeProcessor(model_associator, - web_database, - personal_data, - error_handler); - return SyncComponents(model_associator, change_processor); + + if (command_line_->HasSwitch(switches::kEnableSyncNewAutofill)) { + AutofillModelAssociator* model_associator = + new AutofillModelAssociator(profile_sync_service, + web_database, + personal_data); + AutofillChangeProcessor* change_processor = + new AutofillChangeProcessor(model_associator, + web_database, + personal_data, + error_handler); + return SyncComponents(model_associator, change_processor); + } else { + AutofillModelAssociator2* model_associator = + new AutofillModelAssociator2(profile_sync_service, + web_database, + personal_data); + AutofillChangeProcessor2* change_processor = + new AutofillChangeProcessor2(model_associator, + web_database, + personal_data, + error_handler); + return SyncComponents(model_associator, change_processor); + } } ProfileSyncFactory::SyncComponents diff --git a/chrome/browser/sync/profile_sync_service_autofill_unittest.cc b/chrome/browser/sync/profile_sync_service_autofill_unittest.cc index 17354b0..a7b929a 100644 --- a/chrome/browser/sync/profile_sync_service_autofill_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_autofill_unittest.cc @@ -24,8 +24,10 @@ #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/engine/syncer_util.h" #include "chrome/browser/sync/glue/autofill_change_processor.h" +#include "chrome/browser/sync/glue/autofill_change_processor2.h" #include "chrome/browser/sync/glue/autofill_data_type_controller.h" #include "chrome/browser/sync/glue/autofill_model_associator.h" +#include "chrome/browser/sync/glue/autofill_model_associator2.h" #include "chrome/browser/sync/profile_sync_factory.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_test_util.h" @@ -45,9 +47,9 @@ using base::Time; using base::WaitableEvent; -using browser_sync::AutofillChangeProcessor; +using browser_sync::AutofillChangeProcessor2; using browser_sync::AutofillDataTypeController; -using browser_sync::AutofillModelAssociator; +using browser_sync::AutofillModelAssociator2; using browser_sync::GROUP_DB; using browser_sync::kAutofillTag; using browser_sync::SyncBackendHostForProfileSyncTest; @@ -128,10 +130,10 @@ ACTION_P4(MakeAutofillSyncComponents, service, wd, pdm, dtc) { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (!BrowserThread::CurrentlyOn(BrowserThread::DB)) return ProfileSyncFactory::SyncComponents(NULL, NULL); - AutofillModelAssociator* model_associator = - new AutofillModelAssociator(service, wd, pdm); - AutofillChangeProcessor* change_processor = - new AutofillChangeProcessor(model_associator, wd, pdm, dtc); + AutofillModelAssociator2* model_associator = + new AutofillModelAssociator2(service, wd, pdm); + AutofillChangeProcessor2* change_processor = + new AutofillChangeProcessor2(model_associator, wd, pdm, dtc); return ProfileSyncFactory::SyncComponents(model_associator, change_processor); } @@ -212,12 +214,12 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { return false; sync_api::WriteNode node(&trans); - std::string tag = AutofillModelAssociator::KeyToTag(entry.key().name(), + std::string tag = AutofillModelAssociator2::KeyToTag(entry.key().name(), entry.key().value()); if (!node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)) return false; - AutofillChangeProcessor::WriteAutofillEntry(entry, &node); + AutofillChangeProcessor2::WriteAutofillEntry(entry, &node); return true; } @@ -228,11 +230,11 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { if (!autofill_root.InitByTagLookup(browser_sync::kAutofillTag)) return false; sync_api::WriteNode node(&trans); - std::string tag = AutofillModelAssociator::ProfileLabelToTag( + std::string tag = browser_sync::AutofillModelAssociator2::ProfileLabelToTag( profile.Label()); if (!node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)) return false; - AutofillChangeProcessor::WriteAutofillProfile(profile, &node); + AutofillChangeProcessor2::WriteAutofillProfile(profile, &node); sync_pb::AutofillSpecifics s(node.GetAutofillSpecifics()); s.mutable_profile()->set_label(UTF16ToUTF8(profile.Label())); node.SetAutofillSpecifics(s); @@ -267,7 +269,7 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { } else if (autofill.has_profile()) { AutoFillProfile p; p.set_label(UTF8ToUTF16(autofill.profile().label())); - AutofillModelAssociator::OverwriteProfileWithServerData(&p, + AutofillModelAssociator2::OverwriteProfileWithServerData(&p, autofill.profile()); profiles->push_back(p); } @@ -390,7 +392,7 @@ class FakeServerUpdater: public base::RefCountedThreadSafe<FakeServerUpdater> { ASSERT_TRUE(dir.good()); // Create autofill protobuf - std::string tag = AutofillModelAssociator::KeyToTag(entry_.key().name(), + std::string tag = AutofillModelAssociator2::KeyToTag(entry_.key().name(), entry_.key().value()); sync_pb::AutofillSpecifics new_autofill; new_autofill.set_name(UTF16ToUTF8(entry_.key().name())); diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc index 7a7d3f1..c95daa8 100644 --- a/chrome/browser/sync/syncable/model_type.cc +++ b/chrome/browser/sync/syncable/model_type.cc @@ -37,6 +37,9 @@ void AddDefaultExtensionValue(syncable::ModelType datatype, case AUTOFILL: specifics->MutableExtension(sync_pb::autofill); break; + case AUTOFILL_PROFILE: + specifics->MutableExtension(sync_pb::autofill_profile); + break; case THEMES: specifics->MutableExtension(sync_pb::theme); break; @@ -104,6 +107,9 @@ ModelType GetModelTypeFromSpecifics(const sync_pb::EntitySpecifics& specifics) { if (specifics.HasExtension(sync_pb::autofill)) return AUTOFILL; + if (specifics.HasExtension(sync_pb::autofill_profile)) + return AUTOFILL_PROFILE; + if (specifics.HasExtension(sync_pb::theme)) return THEMES; @@ -261,6 +267,9 @@ const char kExtensionNotificationType[] = "EXTENSION"; const char kNigoriNotificationType[] = "NIGORI"; const char kAppNotificationType[] = "APP"; const char kSessionNotificationType[] = "SESSION"; +// TODO(lipalani) Bug 64111. +// talk to akalin to make sure this is what I understand this to be. +const char kAutofillProfileType[] = "AUTOFILL_PROFILE"; // TODO(akalin): This is a hack to make new sync data types work with // server-issued notifications. Remove this when it's not needed // anymore. @@ -300,6 +309,9 @@ bool RealModelTypeToNotificationType(ModelType model_type, case SESSIONS: *notification_type = kSessionNotificationType; return true; + case AUTOFILL_PROFILE: + *notification_type = kAutofillProfileType; + return true; // TODO(akalin): This is a hack to make new sync data types work with // server-issued notifications. Remove this when it's not needed // anymore. diff --git a/chrome/browser/sync/syncable/model_type.h b/chrome/browser/sync/syncable/model_type.h index 2831bd2..442e462 100644 --- a/chrome/browser/sync/syncable/model_type.h +++ b/chrome/browser/sync/syncable/model_type.h @@ -51,6 +51,8 @@ enum ModelType { PASSWORDS, // An autofill folder or an autofill object. AUTOFILL, + // An autofill Profile Object + AUTOFILL_PROFILE, // A themes folder or a themes object. THEMES, // A typed_url folder or a typed_url object. diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc index 99cc2ba..903b71d 100644 --- a/chrome/browser/sync/syncable/syncable.cc +++ b/chrome/browser/sync/syncable/syncable.cc @@ -352,12 +352,12 @@ EntryKernel* Directory::GetEntryByServerTag(const string& tag) { return NULL; } -EntryKernel* Directory::GetEntryByHandle(const int64 metahandle) { +EntryKernel* Directory::GetEntryByHandle(int64 metahandle) { ScopedKernelLock lock(this); return GetEntryByHandle(metahandle, &lock); } -EntryKernel* Directory::GetEntryByHandle(const int64 metahandle, +EntryKernel* Directory::GetEntryByHandle(int64 metahandle, ScopedKernelLock* lock) { // Look up in memory kernel_->needle.put(META_HANDLE, metahandle); @@ -998,12 +998,21 @@ void BaseTransaction::Lock() { } BaseTransaction::BaseTransaction(Directory* directory, const char* name, - const char* source_file, int line, WriterTag writer) + const char* source_file, int line, WriterTag writer) : directory_(directory), dirkernel_(directory->kernel_), name_(name), source_file_(source_file), line_(line), writer_(writer) { Lock(); } +BaseTransaction::BaseTransaction(Directory* directory) + : directory_(directory), + dirkernel_(NULL), + name_(NULL), + source_file_(NULL), + line_(NULL), + writer_(INVALID) { +} + BaseTransaction::~BaseTransaction() {} void BaseTransaction::UnlockAndLog(OriginalEntries* originals_arg) { @@ -1098,6 +1107,11 @@ WriteTransaction::WriteTransaction(const ScopedDirLookup& scoped_dir, originals_(new OriginalEntries) { } +WriteTransaction::WriteTransaction(Directory *directory) + : BaseTransaction(directory), + originals_(new OriginalEntries) { +} + void WriteTransaction::SaveOriginal(EntryKernel* entry) { if (NULL == entry) return; diff --git a/chrome/browser/sync/syncable/syncable.h b/chrome/browser/sync/syncable/syncable.h index c26232f7..19d98ee 100644 --- a/chrome/browser/sync/syncable/syncable.h +++ b/chrome/browser/sync/syncable/syncable.h @@ -726,7 +726,7 @@ class Directory { // an account. We keep this for each datatype. It doesn't actually map // to any time scale; its name is an historical artifact. int64 last_download_timestamp(ModelType type) const; - void set_last_download_timestamp(ModelType type, int64 value); + virtual void set_last_download_timestamp(ModelType type, int64 value); // Find the model type or model types which have the least timestamp, and // return them along with the types having that timestamp. This is done @@ -773,11 +773,12 @@ class Directory { browser_sync::ChannelEventHandler<DirectoryChangeEvent>* observer); protected: // for friends, mainly used by Entry constructors - EntryKernel* GetEntryByHandle(const int64 handle); - EntryKernel* GetEntryByHandle(const int64 metahandle, ScopedKernelLock* lock); - EntryKernel* GetEntryById(const Id& id); + virtual EntryKernel* GetEntryByHandle(int64 handle); + virtual EntryKernel* GetEntryByHandle(int64 metahandle, + ScopedKernelLock* lock); + virtual EntryKernel* GetEntryById(const Id& id); EntryKernel* GetEntryByServerTag(const std::string& tag); - EntryKernel* GetEntryByClientTag(const std::string& tag); + virtual EntryKernel* GetEntryByClientTag(const std::string& tag); EntryKernel* GetRootEntry(); bool ReindexId(EntryKernel* const entry, const Id& new_id); void ReindexParentId(EntryKernel* const entry, const Id& new_parent_id); @@ -823,7 +824,7 @@ class Directory { // Find the first or last child in the positional ordering under a parent, // and return its id. Returns a root Id if parent has no children. - Id GetFirstChildId(BaseTransaction* trans, const Id& parent_id); + virtual Id GetFirstChildId(BaseTransaction* trans, const Id& parent_id); Id GetLastChildId(BaseTransaction* trans, const Id& parent_id); // SaveChanges works by taking a consistent snapshot of the current Directory @@ -1058,6 +1059,9 @@ class BaseTransaction { BaseTransaction(Directory* directory, const char* name, const char* source_file, int line, WriterTag writer); + // For unit testing. Everything will be mocked out no point initializing. + explicit BaseTransaction(Directory* directory); + void UnlockAndLog(OriginalEntries* entries); bool NotifyTransactionChangingAndEnding(OriginalEntries* entries); virtual void NotifyTransactionComplete(); @@ -1112,6 +1116,8 @@ class WriteTransaction : public BaseTransaction { // is done. OriginalEntries* const originals_; + explicit WriteTransaction(Directory *directory); + DISALLOW_COPY_AND_ASSIGN(WriteTransaction); }; diff --git a/chrome/browser/sync/syncable/syncable_mock.h b/chrome/browser/sync/syncable/syncable_mock.h new file mode 100644 index 0000000..9412fe0 --- /dev/null +++ b/chrome/browser/sync/syncable/syncable_mock.h @@ -0,0 +1,40 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_SYNC_SYNCABLE_SYNCABLE_MOCK_H_ +#define CHROME_BROWSER_SYNC_SYNCABLE_SYNCABLE_MOCK_H_ +#pragma once + +#include <string> + +#include "chrome/browser/sync/syncable/syncable.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using syncable::Directory; +using syncable::EntryKernel; + +class MockDirectory : public Directory { + public: + MockDirectory() { + init_kernel("myk"); + } + MOCK_METHOD1(GetEntryByHandle, syncable::EntryKernel*(int64)); + + MOCK_METHOD2(set_last_downloadstamp, void(syncable::ModelType, int64)); + + MOCK_METHOD1(GetEntryByClientTag, + syncable::EntryKernel*(const std::string&)); +}; + +class MockSyncableWriteTransaction : public syncable::WriteTransaction { + public: + explicit MockSyncableWriteTransaction(Directory *directory) : + WriteTransaction(directory, syncable::UNITTEST, "dontcare.cpp", 25) { + } +}; + + +#endif // CHROME_BROWSER_SYNC_SYNCABLE_SYNCABLE_MOCK_H_ + |