diff options
38 files changed, 2109 insertions, 23 deletions
diff --git a/chrome/browser/history/history.cc b/chrome/browser/history/history.cc index b1b9d09..fe85e5f 100644 --- a/chrome/browser/history/history.cc +++ b/chrome/browser/history/history.cc @@ -86,6 +86,13 @@ class HistoryService::BackendDelegate : public HistoryBackend::Delegate { virtual void BroadcastNotifications(NotificationType type, history::HistoryDetails* details) { + // Send the notification on the history thread. + if (NotificationService::current()) { + Details<history::HistoryDetails> det(details); + NotificationService::current()->Notify(type, + NotificationService::AllSources(), + det); + } // Send the notification to the history service on the main thread. message_loop_->PostTask(FROM_HERE, NewRunnableMethod(history_service_.get(), &HistoryService::BroadcastNotifications, type, details)); diff --git a/chrome/browser/history/history.h b/chrome/browser/history/history.h index 8f7c68c..2326f8b 100644 --- a/chrome/browser/history/history.h +++ b/chrome/browser/history/history.h @@ -41,6 +41,11 @@ class Thread; class Time; } +namespace browser_sync { +class HistoryModelWorker; +class TypedUrlDataTypeController; +} + namespace history { class InMemoryHistoryBackend; @@ -492,8 +497,8 @@ class HistoryService : public CancelableRequestProvider, // Schedules a HistoryDBTask for running on the history backend thread. See // HistoryDBTask for details on what this does. - Handle ScheduleDBTask(HistoryDBTask* task, - CancelableRequestConsumerBase* consumer); + virtual Handle ScheduleDBTask(HistoryDBTask* task, + CancelableRequestConsumerBase* consumer); // Testing ------------------------------------------------------------------- @@ -525,6 +530,17 @@ class HistoryService : public CancelableRequestProvider, // The same as AddPageWithDetails() but takes a vector. void AddPagesWithDetails(const std::vector<history::URLRow>& info); + protected: + ~HistoryService(); + + // These are not currently used, hopefully we can do something in the future + // to ensure that the most important things happen first. + enum SchedulePriority { + PRIORITY_UI, // The highest priority (must respond to UI events). + PRIORITY_NORMAL, // Normal stuff like adding a page. + PRIORITY_LOW, // Low priority things like indexing or expiration. + }; + private: class BackendDelegate; friend class base::RefCountedThreadSafe<HistoryService>; @@ -541,16 +557,6 @@ class HistoryService : public CancelableRequestProvider, friend class FavIconRequest; friend class TestingProfile; - ~HistoryService(); - - // These are not currently used, hopefully we can do something in the future - // to ensure that the most important things happen first. - enum SchedulePriority { - PRIORITY_UI, // The highest priority (must respond to UI events). - PRIORITY_NORMAL, // Normal stuff like adding a page. - PRIORITY_LOW, // Low priority things like indexing or expiration. - }; - // Implementation of NotificationObserver. virtual void Observe(NotificationType type, const NotificationSource& source, diff --git a/chrome/browser/history/history_backend.cc b/chrome/browser/history/history_backend.cc index 420095e..58fa489 100644 --- a/chrome/browser/history/history_backend.cc +++ b/chrome/browser/history/history_backend.cc @@ -689,7 +689,6 @@ std::pair<URLID, VisitID> HistoryBackend::AddPageVisit( return std::make_pair(url_id, visit_id); } -// Note: this method is only for testing purposes. void HistoryBackend::AddPagesWithDetails(const std::vector<URLRow>& urls) { if (!db_.get()) return; @@ -850,6 +849,24 @@ void HistoryBackend::IterateURLs(HistoryService::URLEnumerator* iterator) { iterator->OnComplete(false); // Failure. } +bool HistoryBackend::GetAllTypedURLs(std::vector<history::URLRow>* urls) { + if (db_.get()) + return db_->GetAllTypedUrls(urls); + return false; +} + +bool HistoryBackend::UpdateURL(const URLID id, const history::URLRow& url) { + if (db_.get()) + return db_->UpdateURLRow(id, url); + return false; +} + +bool HistoryBackend::GetURL(const GURL& url, history::URLRow* url_row) { + if (db_.get()) + return db_->GetRowForURL(url, url_row) != 0; + return false; +} + void HistoryBackend::QueryURL(scoped_refptr<QueryURLRequest> request, const GURL& url, bool want_visits) { @@ -1730,6 +1747,18 @@ void HistoryBackend::ReleaseDBTasks() { // //////////////////////////////////////////////////////////////////////////////// +void HistoryBackend::DeleteURLs(const std::vector<GURL>& urls) { + for (std::vector<GURL>::const_iterator url = urls.begin(); url != urls.end(); + ++url) { + expirer_.DeleteURL(*url); + } + + db_->GetStartDate(&first_recorded_time_); + // Force a commit, if the user is deleting something for privacy reasons, we + // want to get it on disk ASAP. + Commit(); +} + void HistoryBackend::DeleteURL(const GURL& url) { expirer_.DeleteURL(url); diff --git a/chrome/browser/history/history_backend.h b/chrome/browser/history/history_backend.h index cfd1637..7d230c6 100644 --- a/chrome/browser/history/history_backend.h +++ b/chrome/browser/history/history_backend.h @@ -112,8 +112,7 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>, // Navigation ---------------------------------------------------------------- void AddPage(scoped_refptr<HistoryAddPageArgs> request); - void SetPageTitle(const GURL& url, const std::wstring& title); - void AddPageWithDetails(const URLRow& info); + virtual void SetPageTitle(const GURL& url, const std::wstring& title); // Indexing ------------------------------------------------------------------ @@ -239,9 +238,17 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>, void ProcessDBTask(scoped_refptr<HistoryDBTaskRequest> request); + virtual bool GetAllTypedURLs(std::vector<history::URLRow>* urls); + + virtual bool UpdateURL(const URLID id, const history::URLRow& url); + + virtual bool GetURL(const GURL& url, history::URLRow* url_row); + // Deleting ------------------------------------------------------------------ - void DeleteURL(const GURL& url); + virtual void DeleteURLs(const std::vector<GURL>& urls); + + virtual void DeleteURL(const GURL& url); // Calls ExpireHistoryBackend::ExpireHistoryBetween and commits the change. void ExpireHistoryBetween(scoped_refptr<ExpireHistoryRequest> request, @@ -272,6 +279,9 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>, ExpireHistoryBackend* expire_backend() { return &expirer_; } #endif + protected: + virtual ~HistoryBackend(); + private: friend class base::RefCountedThreadSafe<HistoryBackend>; friend class CommitLaterTask; // The commit task needs to call Commit(). @@ -282,8 +292,6 @@ class HistoryBackend : public base::RefCountedThreadSafe<HistoryBackend>, FRIEND_TEST(HistoryBackendTest, StripUsernamePasswordTest); friend class ::TestingProfile; - ~HistoryBackend(); - // Computes the name of the specified database on disk. FilePath GetThumbnailFileName() const; FilePath GetArchivedFileName() const; diff --git a/chrome/browser/history/url_database.cc b/chrome/browser/history/url_database.cc index c5c6199..e1c8106 100644 --- a/chrome/browser/history/url_database.cc +++ b/chrome/browser/history/url_database.cc @@ -79,6 +79,20 @@ bool URLDatabase::GetURLRow(URLID url_id, URLRow* info) { return false; } +bool URLDatabase::GetAllTypedUrls(std::vector<history::URLRow>* urls) { + sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, + "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE typed_count > 0")); + if (!statement) + return false; + + while (statement.Step()) { + URLRow info; + FillURLRow(statement, &info); + urls->push_back(info); + } + return true; +} + URLID URLDatabase::GetRowForURL(const GURL& url, history::URLRow* info) { sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE url=?")); diff --git a/chrome/browser/history/url_database.h b/chrome/browser/history/url_database.h index 6e2eca1..9fb67b4 100644 --- a/chrome/browser/history/url_database.h +++ b/chrome/browser/history/url_database.h @@ -54,6 +54,10 @@ class URLDatabase { // success and false otherwise. bool GetURLRow(URLID url_id, URLRow* info); + // Looks up all urls that were typed in manually. Fills info with the data. + // Returns true on success and false otherwise. + bool GetAllTypedUrls(std::vector<history::URLRow>* urls); + // Looks up the given URL and if it exists, fills the given pointers with the // associated info and returns the ID of that URL. If the info pointer is // NULL, no information about the URL will be filled in, only the ID will be diff --git a/chrome/browser/sync/engine/model_safe_worker.h b/chrome/browser/sync/engine/model_safe_worker.h index 5e5b70d..df2edae 100644 --- a/chrome/browser/sync/engine/model_safe_worker.h +++ b/chrome/browser/sync/engine/model_safe_worker.h @@ -21,6 +21,8 @@ enum ModelSafeGroup { // native model. GROUP_UI, // Models that live on UI thread and are being synced. GROUP_DB, // Models that live on DB thread and are being synced. + GROUP_HISTORY, // Models that live on history thread and are being + // synced. MODEL_SAFE_GROUP_COUNT, }; diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc index 484e6c3..433475c 100644 --- a/chrome/browser/sync/engine/syncapi.cc +++ b/chrome/browser/sync/engine/syncapi.cc @@ -52,6 +52,7 @@ #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" #include "chrome/browser/sync/protocol/service_constants.h" +#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" #include "chrome/browser/sync/sessions/sync_session_context.h" #include "chrome/browser/sync/syncable/directory_manager.h" #include "chrome/browser/sync/syncable/syncable.h" @@ -482,6 +483,11 @@ const sync_pb::PreferenceSpecifics& BaseNode::GetPreferenceSpecifics() const { return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::preference); } +const sync_pb::TypedUrlSpecifics& BaseNode::GetTypedUrlSpecifics() const { + DCHECK(GetModelType() == syncable::TYPED_URLS); + return GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::typed_url); +} + syncable::ModelType BaseNode::GetModelType() const { return GetEntry()->GetModelType(); } @@ -554,6 +560,19 @@ void WriteNode::PutPreferenceSpecificsAndMarkForSyncing( PutSpecificsAndMarkForSyncing(entity_specifics); } +void WriteNode::SetTypedUrlSpecifics( + const sync_pb::TypedUrlSpecifics& new_value) { + DCHECK(GetModelType() == syncable::TYPED_URLS); + PutTypedUrlSpecificsAndMarkForSyncing(new_value); +} + +void WriteNode::PutTypedUrlSpecificsAndMarkForSyncing( + const sync_pb::TypedUrlSpecifics& new_value) { + sync_pb::EntitySpecifics entity_specifics; + entity_specifics.MutableExtension(sync_pb::typed_url)->CopyFrom(new_value); + PutSpecificsAndMarkForSyncing(entity_specifics); +} + void WriteNode::PutSpecificsAndMarkForSyncing( const sync_pb::EntitySpecifics& specifics) { // Skip redundant changes. diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h index 459ff40..ca1cc01 100644 --- a/chrome/browser/sync/engine/syncapi.h +++ b/chrome/browser/sync/engine/syncapi.h @@ -72,6 +72,7 @@ class AutofillSpecifics; class BookmarkSpecifics; class EntitySpecifics; class PreferenceSpecifics; +class TypedUrlSpecifics; } namespace sync_api { @@ -170,6 +171,10 @@ class BaseNode { // data. Can only be called if GetModelType() == PREFERENCE. const sync_pb::PreferenceSpecifics& GetPreferenceSpecifics() const; + // Getter specific to the TYPED_URLS datatype. Returns protobuf + // data. Can only be called if GetModelType() == TYPED_URLS. + const sync_pb::TypedUrlSpecifics& GetTypedUrlSpecifics() const; + // Returns the local external ID associated with the node. int64 GetExternalId() const; @@ -277,6 +282,10 @@ class WriteNode : public BaseNode { // Should only be called if GetModelType() == PREFERENCE. void SetPreferenceSpecifics(const sync_pb::PreferenceSpecifics& specifics); + // Set the typed_url specifics (url, title, typed_count, etc). + // Should only be called if GetModelType() == TYPED_URLS. + void SetTypedUrlSpecifics(const sync_pb::TypedUrlSpecifics& specifics); + // Implementation of BaseNode's abstract virtual accessors. virtual const syncable::Entry* GetEntry() const; @@ -302,6 +311,8 @@ class WriteNode : public BaseNode { const sync_pb::BookmarkSpecifics& new_value); void PutPreferenceSpecificsAndMarkForSyncing( const sync_pb::PreferenceSpecifics& new_value); + void PutTypedUrlSpecificsAndMarkForSyncing( + const sync_pb::TypedUrlSpecifics& new_value); void PutSpecificsAndMarkForSyncing( const sync_pb::EntitySpecifics& specifics); diff --git a/chrome/browser/sync/glue/autofill_model_associator.cc b/chrome/browser/sync/glue/autofill_model_associator.cc index ccda440..57d1bae 100644 --- a/chrome/browser/sync/glue/autofill_model_associator.cc +++ b/chrome/browser/sync/glue/autofill_model_associator.cc @@ -108,7 +108,7 @@ bool AutofillModelAssociator::AssociateModels() { sync_api::ReadNode sync_child_node(&trans); if (!sync_child_node.InitByIdLookup(sync_child_id)) { LOG(ERROR) << "Failed to fetch child node."; - sync_service_->OnUnrecoverableError(); + error_handler_->OnUnrecoverableError(); return false; } const sync_pb::AutofillSpecifics& autofill( diff --git a/chrome/browser/sync/glue/data_type_manager_impl.cc b/chrome/browser/sync/glue/data_type_manager_impl.cc index aba040c..12cd4d2 100644 --- a/chrome/browser/sync/glue/data_type_manager_impl.cc +++ b/chrome/browser/sync/glue/data_type_manager_impl.cc @@ -18,7 +18,8 @@ namespace { static const syncable::ModelType kStartOrder[] = { syncable::BOOKMARKS, syncable::PREFERENCES, - syncable::AUTOFILL + syncable::AUTOFILL, + syncable::TYPED_URLS, }; } // namespace diff --git a/chrome/browser/sync/glue/history_model_worker.cc b/chrome/browser/sync/glue/history_model_worker.cc new file mode 100644 index 0000000..cab697c --- /dev/null +++ b/chrome/browser/sync/glue/history_model_worker.cc @@ -0,0 +1,53 @@ +// 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/history_model_worker.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/ref_counted.h" +#include "base/task.h" +#include "base/waitable_event.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/sync/util/closure.h" + +using base::WaitableEvent; + +namespace browser_sync { + +class WorkerTask : public HistoryDBTask { + public: + WorkerTask(Closure* work, WaitableEvent* done) + : work_(work), done_(done) {} + + virtual bool RunOnDBThread(history::HistoryBackend* backend, + history::HistoryDatabase* db) { + work_->Run(); + done_->Signal(); + return true; + } + + // Since the DoWorkAndWaitUntilDone() is syncronous, we don't need to run any + // code asynchronously on the main thread after completion. + virtual void DoneRunOnMainThread() {} + + protected: + Closure* work_; + WaitableEvent* done_; +}; + + +HistoryModelWorker::HistoryModelWorker(HistoryService* history_service) + : history_service_(history_service) { +} + +void HistoryModelWorker::DoWorkAndWaitUntilDone(Closure* work) { + WaitableEvent done(false, false); + scoped_refptr<WorkerTask> task = new WorkerTask(work, &done); + history_service_->ScheduleDBTask(task.get(), this); + done.Wait(); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/history_model_worker.h b/chrome/browser/sync/glue/history_model_worker.h new file mode 100644 index 0000000..d9fa5ed --- /dev/null +++ b/chrome/browser/sync/glue/history_model_worker.h @@ -0,0 +1,47 @@ +// 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_HISTORY_MODEL_WORKER_H_ +#define CHROME_BROWSER_SYNC_GLUE_HISTORY_MODEL_WORKER_H_ + +#include "chrome/browser/sync/engine/model_safe_worker.h" + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "chrome/browser/cancelable_request.h" +#include "chrome/browser/sync/util/closure.h" + +class HistoryService; + +namespace base { +class WaitableEvent; +} + +namespace browser_sync { + +// A ModelSafeWorker for history models that accepts requests +// from the syncapi that need to be fulfilled on the history thread. +class HistoryModelWorker : public browser_sync::ModelSafeWorker, + public CancelableRequestConsumerBase { + public: + explicit HistoryModelWorker(HistoryService* history_service); + + // ModelSafeWorker implementation. Called on syncapi SyncerThread. + void DoWorkAndWaitUntilDone(Closure* work); + virtual ModelSafeGroup GetModelSafeGroup() { return GROUP_HISTORY; } + + // CancelableRequestConsumerBase implementation. + virtual void OnRequestAdded(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) {} + + virtual void OnRequestRemoved(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) {} + private: + scoped_refptr<HistoryService> history_service_; + DISALLOW_COPY_AND_ASSIGN(HistoryModelWorker); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_HISTORY_MODEL_WORKER_H_ diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc index 113a4b1..51a8f86 100644 --- a/chrome/browser/sync/glue/sync_backend_host.cc +++ b/chrome/browser/sync/glue/sync_backend_host.cc @@ -7,8 +7,10 @@ #include "base/file_version_info.h" #include "base/utf_string_conversions.h" #include "chrome/browser/chrome_thread.h" +#include "chrome/browser/profile.h" #include "chrome/browser/sync/glue/change_processor.h" #include "chrome/browser/sync/glue/database_model_worker.h" +#include "chrome/browser/sync/glue/history_model_worker.h" #include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/glue/http_bridge.h" #include "chrome/common/notification_service.h" @@ -29,10 +31,12 @@ namespace browser_sync { SyncBackendHost::SyncBackendHost( SyncFrontend* frontend, + Profile* profile, const FilePath& profile_path, const DataTypeController::TypeMap& data_type_controllers) : core_thread_("Chrome_SyncCoreThread"), frontend_loop_(MessageLoop::current()), + profile_(profile), frontend_(frontend), sync_data_folder_path_(profile_path.Append(kSyncDataFolderName)), data_type_controllers_(data_type_controllers), @@ -71,6 +75,9 @@ void SyncBackendHost::Initialize( // when a new type is synced as the worker may already exist and you just // need to update routing_info_. registrar_.workers[GROUP_DB] = new DatabaseModelWorker(); + registrar_.workers[GROUP_HISTORY] = + new HistoryModelWorker( + profile_->GetHistoryService(Profile::IMPLICIT_ACCESS)); registrar_.workers[GROUP_UI] = new UIModelWorker(frontend_loop_); registrar_.workers[GROUP_PASSIVE] = new ModelSafeWorker(); @@ -135,9 +142,11 @@ void SyncBackendHost::Shutdown(bool sync_disabled) { registrar_.routing_info.clear(); registrar_.workers[GROUP_DB] = NULL; + registrar_.workers[GROUP_HISTORY] = NULL; registrar_.workers[GROUP_UI] = NULL; registrar_.workers[GROUP_PASSIVE] = NULL; registrar_.workers.erase(GROUP_DB); + registrar_.workers.erase(GROUP_HISTORY); registrar_.workers.erase(GROUP_UI); registrar_.workers.erase(GROUP_PASSIVE); frontend_ = NULL; diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h index 405fe35..8edc7ba 100644 --- a/chrome/browser/sync/glue/sync_backend_host.h +++ b/chrome/browser/sync/glue/sync_backend_host.h @@ -25,6 +25,8 @@ #include "chrome/browser/sync/syncable/model_type.h" #include "googleurl/src/gurl.h" +class Profile; + namespace browser_sync { class ChangeProcessor; @@ -74,6 +76,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // and communicates to via the SyncFrontend interface (on the same thread // it used to call the constructor). SyncBackendHost(SyncFrontend* frontend, + Profile* profile, const FilePath& profile_path, const DataTypeController::TypeMap& data_type_controllers); // For testing. @@ -160,6 +163,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { registrar_.routing_info[syncable::BOOKMARKS] = GROUP_PASSIVE; registrar_.routing_info[syncable::PREFERENCES] = GROUP_PASSIVE; registrar_.routing_info[syncable::AUTOFILL] = GROUP_PASSIVE; + registrar_.routing_info[syncable::TYPED_URLS] = GROUP_PASSIVE; core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), @@ -356,6 +360,8 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // to safely talk back to the SyncFrontend. MessageLoop* const frontend_loop_; + Profile* profile_; + // This is state required to implement ModelSafeWorkerRegistrar. struct { // We maintain ownership of all workers. In some cases, we need to ensure diff --git a/chrome/browser/sync/glue/typed_url_change_processor.cc b/chrome/browser/sync/glue/typed_url_change_processor.cc new file mode 100644 index 0000000..709c5e6 --- /dev/null +++ b/chrome/browser/sync/glue/typed_url_change_processor.cc @@ -0,0 +1,239 @@ +// 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/typed_url_change_processor.h"
+
+#include "base/string_util.h"
+#include "chrome/browser/history/history_backend.h"
+#include "chrome/browser/history/history_notifications.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/sync/glue/typed_url_model_associator.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
+
+namespace browser_sync {
+
+TypedUrlChangeProcessor::TypedUrlChangeProcessor(
+ TypedUrlModelAssociator* model_associator,
+ history::HistoryBackend* history_backend,
+ UnrecoverableErrorHandler* error_handler)
+ : ChangeProcessor(error_handler),
+ model_associator_(model_associator),
+ history_backend_(history_backend),
+ observing_(false),
+ expected_loop_(MessageLoop::current()) {
+ DCHECK(model_associator);
+ DCHECK(history_backend);
+ DCHECK(error_handler);
+ DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::UI));
+ // When running in unit tests, there is already a NotificationService object.
+ // Since only one can exist at a time per thread, check first.
+ if (!NotificationService::current())
+ notification_service_.reset(new NotificationService);
+ StartObserving();
+}
+
+TypedUrlChangeProcessor::~TypedUrlChangeProcessor() {
+ DCHECK(expected_loop_ == MessageLoop::current());
+}
+
+void TypedUrlChangeProcessor::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ if (!observing_)
+ return;
+
+ LOG(INFO) << "Observed typed_url change.";
+ DCHECK(running());
+ DCHECK(NotificationType::HISTORY_TYPED_URLS_MODIFIED == type ||
+ NotificationType::HISTORY_URLS_DELETED == type);
+ if (type == NotificationType::HISTORY_TYPED_URLS_MODIFIED) {
+ HandleURLsModified(Details<history::URLsModifiedDetails>(details).ptr());
+ } else if (type == NotificationType::HISTORY_URLS_DELETED) {
+ HandleURLsDeleted(Details<history::URLsDeletedDetails>(details).ptr());
+ }
+}
+
+void TypedUrlChangeProcessor::HandleURLsModified(
+ history::URLsModifiedDetails* details) {
+ sync_api::WriteTransaction trans(share_handle());
+
+ sync_api::ReadNode typed_url_root(&trans);
+ if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) {
+ error_handler()->OnUnrecoverableError();
+ LOG(ERROR) << "Server did not create the top-level typed_url node. We "
+ << "might be running against an out-of-date server.";
+ return;
+ }
+
+ for (std::vector<history::URLRow>::iterator url =
+ details->changed_urls.begin(); url != details->changed_urls.end();
+ ++url) {
+ std::string tag = url->url().spec();
+
+ sync_api::WriteNode update_node(&trans);
+ if (update_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
+ model_associator_->WriteToSyncNode(*url, &update_node);
+ } else {
+ sync_api::WriteNode create_node(&trans);
+ if (!create_node.InitUniqueByCreation(syncable::TYPED_URLS,
+ typed_url_root, tag)) {
+ LOG(ERROR) << "Failed to create typed_url sync node.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+
+ create_node.SetTitle(UTF8ToWide(tag));
+ model_associator_->WriteToSyncNode(*url, &create_node);
+
+ model_associator_->Associate(&tag, create_node.GetId());
+ }
+ }
+}
+
+void TypedUrlChangeProcessor::HandleURLsDeleted(
+ history::URLsDeletedDetails* details) {
+ sync_api::WriteTransaction trans(share_handle());
+
+ if (details->all_history) {
+ if (!model_associator_->DeleteAllNodes(&trans)) {
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+ } else {
+ for (std::set<GURL>::iterator url = details->urls.begin();
+ url != details->urls.end(); ++url) {
+ sync_api::WriteNode sync_node(&trans);
+ int64 sync_id =
+ model_associator_->GetSyncIdFromChromeId(url->spec());
+ if (sync_api::kInvalidId == sync_id) {
+ LOG(ERROR) << "Unexpected notification for: " <<
+ url->spec();
+ error_handler()->OnUnrecoverableError();
+ return;
+ } else {
+ if (!sync_node.InitByIdLookup(sync_id)) {
+ LOG(ERROR) << "Typed url node lookup failed.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+ model_associator_->Disassociate(sync_node.GetId());
+ sync_node.Remove();
+ }
+ }
+ }
+}
+
+void TypedUrlChangeProcessor::ApplyChangesFromSyncModel(
+ const sync_api::BaseTransaction* trans,
+ const sync_api::SyncManager::ChangeRecord* changes,
+ int change_count) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ if (!running())
+ return;
+ StopObserving();
+
+ sync_api::ReadNode typed_url_root(trans);
+ if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) {
+ LOG(ERROR) << "TypedUrl root node lookup failed.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+
+ TypedUrlModelAssociator::TypedUrlTitleVector titles;
+ TypedUrlModelAssociator::TypedUrlVector new_urls;
+ TypedUrlModelAssociator::TypedUrlUpdateVector updated_urls;
+
+ for (int i = 0; i < change_count; ++i) {
+
+ sync_api::ReadNode sync_node(trans);
+ if (!sync_node.InitByIdLookup(changes[i].id)) {
+ LOG(ERROR) << "TypedUrl node lookup failed.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+
+ // Check that the changed node is a child of the typed_urls folder.
+ DCHECK(typed_url_root.GetId() == sync_node.GetParentId());
+ DCHECK(syncable::TYPED_URLS == sync_node.GetModelType());
+
+ const sync_pb::TypedUrlSpecifics& typed_url(
+ sync_node.GetTypedUrlSpecifics());
+ if (sync_api::SyncManager::ChangeRecord::ACTION_ADD == changes[i].action) {
+ history::URLRow new_url(GURL(typed_url.url()));
+ new_url.set_title(UTF8ToWide(typed_url.title()));
+ new_url.set_visit_count(typed_url.visit_count());
+ new_url.set_typed_count(typed_url.typed_count());
+ new_url.set_last_visit(
+ base::Time::FromInternalValue(typed_url.last_visit()));
+ new_url.set_hidden(typed_url.hidden());
+
+ new_urls.push_back(new_url);
+ } else if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE ==
+ changes[i].action) {
+ history_backend_->DeleteURL(GURL(typed_url.url()));
+ } else {
+ history::URLRow old_url;
+ if (!history_backend_->GetURL(GURL(typed_url.url()), &old_url)) {
+ LOG(ERROR) << "TypedUrl db lookup failed.";
+ error_handler()->OnUnrecoverableError();
+ return;
+ }
+
+ history::URLRow new_url(GURL(typed_url.url()));
+ new_url.set_title(UTF8ToWide(typed_url.title()));
+ new_url.set_visit_count(typed_url.visit_count());
+ new_url.set_typed_count(typed_url.typed_count());
+ new_url.set_last_visit(
+ base::Time::FromInternalValue(typed_url.last_visit()));
+ new_url.set_hidden(typed_url.hidden());
+
+ updated_urls.push_back(
+ std::pair<history::URLID, history::URLRow>(old_url.id(), new_url));
+
+ if (old_url.title().compare(new_url.title()) != 0) {
+ titles.push_back(std::pair<GURL, std::wstring>(new_url.url(),
+ new_url.title()));
+ }
+ }
+ }
+ model_associator_->WriteToHistoryBackend(&titles, &new_urls, &updated_urls);
+
+ StartObserving();
+}
+
+void TypedUrlChangeProcessor::StartImpl(Profile* profile) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ observing_ = true;
+}
+
+void TypedUrlChangeProcessor::StopImpl() {
+ DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
+ observing_ = false;
+}
+
+
+void TypedUrlChangeProcessor::StartObserving() {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ notification_registrar_.Add(this,
+ NotificationType::HISTORY_TYPED_URLS_MODIFIED,
+ NotificationService::AllSources());
+ notification_registrar_.Add(this, NotificationType::HISTORY_URLS_DELETED,
+ NotificationService::AllSources());
+}
+
+void TypedUrlChangeProcessor::StopObserving() {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ notification_registrar_.Remove(this,
+ NotificationType::HISTORY_TYPED_URLS_MODIFIED,
+ NotificationService::AllSources());
+ notification_registrar_.Remove(this,
+ NotificationType::HISTORY_URLS_DELETED,
+ NotificationService::AllSources());
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/typed_url_change_processor.h b/chrome/browser/sync/glue/typed_url_change_processor.h new file mode 100644 index 0000000..920c62e --- /dev/null +++ b/chrome/browser/sync/glue/typed_url_change_processor.h @@ -0,0 +1,95 @@ +// 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_TYPED_URL_CHANGE_PROCESSOR_H_ +#define CHROME_BROWSER_SYNC_GLUE_TYPED_URL_CHANGE_PROCESSOR_H_ + +#include "chrome/browser/sync/glue/change_processor.h" + +#include <vector> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "base/time.h" +#include "chrome/browser/sync/glue/sync_backend_host.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_type.h" + +class MessageLoop; +class NotificationService; + +namespace history { +class HistoryBackend; +struct URLsDeletedDetails; +struct URLsModifiedDetails; +class URLRow; +}; + +namespace browser_sync { + +class TypedUrlModelAssociator; +class UnrecoverableErrorHandler; + +// This class is responsible for taking changes from the history backend and +// applying them to the sync_api 'syncable' model, and vice versa. All +// operations and use of this class are from the UI thread. +class TypedUrlChangeProcessor : public ChangeProcessor, + public NotificationObserver { + public: + TypedUrlChangeProcessor(TypedUrlModelAssociator* model_associator, + history::HistoryBackend* history_backend, + UnrecoverableErrorHandler* error_handler); + virtual ~TypedUrlChangeProcessor(); + + // NotificationObserver implementation. + // History -> 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); + + protected: + virtual void StartImpl(Profile* profile); + virtual void StopImpl(); + + private: + bool WriteTypedUrl(sync_api::WriteNode* node, + const string16& name, + const string16& value, + std::vector<base::Time>& timestamps); + + void StartObserving(); + void StopObserving(); + + void HandleURLsModified(history::URLsModifiedDetails* details); + void HandleURLsDeleted(history::URLsDeletedDetails* details); + + // The two models should be associated according to this ModelAssociator. + TypedUrlModelAssociator* 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. + history::HistoryBackend* history_backend_; + + NotificationRegistrar notification_registrar_; + + bool observing_; + + MessageLoop* expected_loop_; + + scoped_ptr<NotificationService> notification_service_; + + DISALLOW_COPY_AND_ASSIGN(TypedUrlChangeProcessor); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_TYPED_URL_CHANGE_PROCESSOR_H_ diff --git a/chrome/browser/sync/glue/typed_url_data_type_controller.cc b/chrome/browser/sync/glue/typed_url_data_type_controller.cc new file mode 100644 index 0000000..831f2ae --- /dev/null +++ b/chrome/browser/sync/glue/typed_url_data_type_controller.cc @@ -0,0 +1,209 @@ +// 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/histogram.h" +#include "base/logging.h" +#include "base/task.h" +#include "base/time.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/history/history.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/sync/glue/typed_url_change_processor.h" +#include "chrome/browser/sync/glue/typed_url_data_type_controller.h" +#include "chrome/browser/sync/glue/typed_url_model_associator.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/profile_sync_factory.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/common/notification_service.h" + +namespace browser_sync { + +class ControlTask : public HistoryDBTask { + public: + ControlTask(TypedUrlDataTypeController* controller, bool start) + : controller_(controller), start_(start) {} + + virtual bool RunOnDBThread(history::HistoryBackend* backend, + history::HistoryDatabase* db) { + if (start_) { + controller_->StartImpl(backend); + } else { + controller_->StopImpl(); + } + + // Release the reference to the controller. This ensures that + // the controller isn't held past its lifetime in unit tests. + controller_ = NULL; + return true; + } + + virtual void DoneRunOnMainThread() {} + + protected: + scoped_refptr<TypedUrlDataTypeController> controller_; + bool start_; +}; + +TypedUrlDataTypeController::TypedUrlDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* sync_service) + : profile_sync_factory_(profile_sync_factory), + profile_(profile), + sync_service_(sync_service), + state_(NOT_RUNNING), + merge_allowed_(false) { + DCHECK(profile_sync_factory); + DCHECK(profile); + DCHECK(sync_service); +} + +TypedUrlDataTypeController::~TypedUrlDataTypeController() { +} + +void TypedUrlDataTypeController::Start(bool merge_allowed, + StartCallback* start_callback) { + LOG(INFO) << "Starting typed_url data controller."; + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + DCHECK(start_callback); + if (state_ != NOT_RUNNING || start_callback_.get()) { + start_callback->Run(BUSY); + delete start_callback; + return; + } + + start_callback_.reset(start_callback); + merge_allowed_ = merge_allowed; + + HistoryService* history = profile_->GetHistoryServiceWithoutCreating(); + if (history) { + set_state(ASSOCIATING); + history_service_ = history; + history_service_->ScheduleDBTask(new ControlTask(this, true), this); + } else { + set_state(MODEL_STARTING); + notification_registrar_.Add(this, NotificationType::HISTORY_LOADED, + NotificationService::AllSources()); + } +} + +void TypedUrlDataTypeController::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + LOG(INFO) << "History loaded observed."; + notification_registrar_.Remove(this, + NotificationType::HISTORY_LOADED, + NotificationService::AllSources()); + + history_service_ = profile_->GetHistoryServiceWithoutCreating(); + DCHECK(history_service_.get()); + history_service_->ScheduleDBTask(new ControlTask(this, true), this); +} + +void TypedUrlDataTypeController::Stop() { + LOG(INFO) << "Stopping typed_url data type controller."; + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + + if (change_processor_ != NULL) + sync_service_->DeactivateDataType(this, change_processor_.get()); + + if (model_associator_ != NULL) + model_associator_->DisassociateModels(); + + set_state(NOT_RUNNING); + DCHECK(history_service_.get()); + history_service_->ScheduleDBTask(new ControlTask(this, false), this); +} + +void TypedUrlDataTypeController::StartImpl(history::HistoryBackend* backend) { + LOG(INFO) << "TypedUrl data type controller StartImpl called."; + // No additional services need to be started before we can proceed + // with model association. + ProfileSyncFactory::SyncComponents sync_components = + profile_sync_factory_->CreateTypedUrlSyncComponents( + sync_service_, + backend, + this); + model_associator_.reset(sync_components.model_associator); + change_processor_.reset(sync_components.change_processor); + + bool chrome_has_nodes = false; + if (!model_associator_->ChromeModelHasUserCreatedNodes(&chrome_has_nodes)) { + StartFailed(UNRECOVERABLE_ERROR); + return; + } + bool sync_has_nodes = false; + if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { + StartFailed(UNRECOVERABLE_ERROR); + return; + } + + if (chrome_has_nodes && sync_has_nodes && !merge_allowed_) { + StartFailed(NEEDS_MERGE); + return; + } + + base::TimeTicks start_time = base::TimeTicks::Now(); + bool merge_success = model_associator_->AssociateModels(); + UMA_HISTOGRAM_TIMES("Sync.TypedUrlAssociationTime", + base::TimeTicks::Now() - start_time); + if (!merge_success) { + StartFailed(NEEDS_MERGE); + return; + } + + sync_service_->ActivateDataType(this, change_processor_.get()); + StartDone(!sync_has_nodes ? OK_FIRST_RUN : OK, RUNNING); +} + +void TypedUrlDataTypeController::StartDone( + DataTypeController::StartResult result, + DataTypeController::State new_state) { + LOG(INFO) << "TypedUrl data type controller StartDone called."; + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + NewRunnableMethod( + this, + &TypedUrlDataTypeController::StartDoneImpl, + result, + new_state)); +} + +void TypedUrlDataTypeController::StartDoneImpl( + DataTypeController::StartResult result, + DataTypeController::State new_state) { + LOG(INFO) << "TypedUrl data type controller StartDoneImpl called."; + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + set_state(new_state); + start_callback_->Run(result); + start_callback_.reset(); +} + +void TypedUrlDataTypeController::StopImpl() { + LOG(INFO) << "TypedUrl data type controller StopImpl called."; + + change_processor_.reset(); + model_associator_.reset(); + + state_ = NOT_RUNNING; +} + +void TypedUrlDataTypeController::StartFailed(StartResult result) { + change_processor_.reset(); + model_associator_.reset(); + StartDone(result, NOT_RUNNING); +} + +void TypedUrlDataTypeController::OnUnrecoverableError() { + ChromeThread::PostTask( + ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, + &TypedUrlDataTypeController::OnUnrecoverableErrorImpl)); +} + +void TypedUrlDataTypeController::OnUnrecoverableErrorImpl() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + sync_service_->OnUnrecoverableError(); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/typed_url_data_type_controller.h b/chrome/browser/sync/glue/typed_url_data_type_controller.h new file mode 100644 index 0000000..9897f71 --- /dev/null +++ b/chrome/browser/sync/glue/typed_url_data_type_controller.h @@ -0,0 +1,113 @@ +// 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_TYPED_URL_DATA_TYPE_CONTROLLER_H__ +#define CHROME_BROWSER_SYNC_GLUE_TYPED_URL_DATA_TYPE_CONTROLLER_H__ + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/cancelable_request.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/glue/data_type_controller.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_type.h" + +class NotificationSource; +class NotificationDetails; +class Profile; +class ProfileSyncFactory; +class ProfileSyncService; + +namespace browser_sync { + +class AssociatorInterface; +class ChangeProcessor; +class ControlTask; + +// A class that manages the startup and shutdown of typed_url sync. +class TypedUrlDataTypeController : public DataTypeController, + public NotificationObserver, + public CancelableRequestConsumerBase { + public: + TypedUrlDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* sync_service); + virtual ~TypedUrlDataTypeController(); + + // DataTypeController implementation + virtual void Start(bool merge_allowed, StartCallback* start_callback); + + virtual void Stop(); + + virtual bool enabled() { + return true; + } + + virtual syncable::ModelType type() { + return syncable::TYPED_URLS; + } + + virtual browser_sync::ModelSafeGroup model_safe_group() { + return browser_sync::GROUP_HISTORY; + } + + virtual const char* name() const { + // For logging only. + return "typed_url"; + } + + virtual State state() { + return state_; + } + + // UnrecoverableHandler implementation + virtual void OnUnrecoverableError(); + + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // CancelableRequestConsumerBase implementation. + virtual void OnRequestAdded(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) {} + + virtual void OnRequestRemoved(CancelableRequestProvider* provider, + CancelableRequestProvider::Handle handle) {} + private: + friend class ControlTask; + void StartImpl(history::HistoryBackend* backend); + void StartDone(StartResult result, State state); + void StartDoneImpl(StartResult result, State state); + void StopImpl(); + void StartFailed(StartResult result); + void OnUnrecoverableErrorImpl(); + + void set_state(State state) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + state_ = state; + } + + ProfileSyncFactory* profile_sync_factory_; + Profile* profile_; + ProfileSyncService* sync_service_; + State state_; + + scoped_ptr<AssociatorInterface> model_associator_; + scoped_ptr<ChangeProcessor> change_processor_; + scoped_ptr<StartCallback> start_callback_; + scoped_refptr<HistoryService> history_service_; + + NotificationRegistrar notification_registrar_; + + bool merge_allowed_; + + DISALLOW_COPY_AND_ASSIGN(TypedUrlDataTypeController); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_TYPED_URL_DATA_TYPE_CONTROLLER_H__ diff --git a/chrome/browser/sync/glue/typed_url_model_associator.cc b/chrome/browser/sync/glue/typed_url_model_associator.cc new file mode 100644 index 0000000..080a598 --- /dev/null +++ b/chrome/browser/sync/glue/typed_url_model_associator.cc @@ -0,0 +1,338 @@ +// 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/typed_url_model_associator.h"
+
+#include <set>
+
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/history/history_backend.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/sync/engine/syncapi.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
+
+namespace browser_sync {
+
+const char kTypedUrlTag[] = "google_chrome_typed_urls";
+
+TypedUrlModelAssociator::TypedUrlModelAssociator(
+ ProfileSyncService* sync_service,
+ history::HistoryBackend* history_backend,
+ UnrecoverableErrorHandler* error_handler)
+ : sync_service_(sync_service),
+ history_backend_(history_backend),
+ error_handler_(error_handler),
+ typed_url_node_id_(sync_api::kInvalidId),
+ expected_loop_(MessageLoop::current()) {
+ DCHECK(sync_service_);
+ DCHECK(history_backend_);
+ DCHECK(error_handler_);
+ DCHECK(!ChromeThread::CurrentlyOn(ChromeThread::UI));
+}
+
+bool TypedUrlModelAssociator::AssociateModels() {
+ LOG(INFO) << "Associating TypedUrl Models";
+ DCHECK(expected_loop_ == MessageLoop::current());
+
+ std::vector<history::URLRow> typed_urls;
+ if (!history_backend_->GetAllTypedURLs(&typed_urls)) {
+ LOG(ERROR) << "Could not get the typed_url entries.";
+ return false;
+ }
+
+ sync_api::WriteTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+ sync_api::ReadNode typed_url_root(&trans);
+ if (!typed_url_root.InitByTagLookup(kTypedUrlTag)) {
+ LOG(ERROR) << "Server did not create the top-level typed_url node. We "
+ << "might be running against an out-of-date server.";
+ return false;
+ }
+
+ std::set<std::string> current_urls;
+ TypedUrlTitleVector titles;
+ TypedUrlVector new_urls;
+ TypedUrlUpdateVector updated_urls;
+
+ for (std::vector<history::URLRow>::iterator ix = typed_urls.begin();
+ ix != typed_urls.end(); ++ix) {
+ std::string tag = ix->url().spec();
+
+ sync_api::ReadNode node(&trans);
+ if (node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
+ const sync_pb::TypedUrlSpecifics& typed_url(node.GetTypedUrlSpecifics());
+ DCHECK_EQ(tag, typed_url.url());
+
+ history::URLRow new_url(ix->url());
+
+ int difference = MergeUrls(typed_url, *ix, &new_url);
+ if (difference & DIFF_NODE_CHANGED) {
+ sync_api::WriteNode write_node(&trans);
+ if (!write_node.InitByClientTagLookup(syncable::TYPED_URLS, tag)) {
+ LOG(ERROR) << "Failed to edit typed_url sync node.";
+ return false;
+ }
+ WriteToSyncNode(new_url, &write_node);
+ }
+ if (difference & DIFF_TITLE_CHANGED) {
+ titles.push_back(std::pair<GURL, std::wstring>(new_url.url(),
+ new_url.title()));
+ }
+ if (difference & DIFF_ROW_CHANGED) {
+ updated_urls.push_back(
+ std::pair<history::URLID, history::URLRow>(ix->id(), new_url));
+ }
+
+ Associate(&tag, node.GetId());
+ } else {
+ sync_api::WriteNode node(&trans);
+ if (!node.InitUniqueByCreation(syncable::TYPED_URLS,
+ typed_url_root, tag)) {
+ LOG(ERROR) << "Failed to create typed_url sync node.";
+ return false;
+ }
+
+ node.SetTitle(UTF8ToWide(tag));
+ WriteToSyncNode(*ix, &node);
+
+ Associate(&tag, node.GetId());
+ }
+
+ current_urls.insert(tag);
+ }
+
+ int64 sync_child_id = typed_url_root.GetFirstChildId();
+ while (sync_child_id != sync_api::kInvalidId) {
+ sync_api::ReadNode sync_child_node(&trans);
+ if (!sync_child_node.InitByIdLookup(sync_child_id)) {
+ LOG(ERROR) << "Failed to fetch child node.";
+ return false;
+ }
+ const sync_pb::TypedUrlSpecifics& typed_url(
+ sync_child_node.GetTypedUrlSpecifics());
+
+ if (current_urls.find(typed_url.url()) == current_urls.end()) {
+ history::URLRow new_url(GURL(typed_url.url()));
+
+ new_url.set_title(UTF8ToWide(typed_url.title()));
+ new_url.set_visit_count(typed_url.visit_count());
+ new_url.set_typed_count(typed_url.typed_count());
+ new_url.set_last_visit(
+ base::Time::FromInternalValue(typed_url.last_visit()));
+ new_url.set_hidden(typed_url.hidden());
+
+ Associate(&typed_url.url(), sync_child_node.GetId());
+ new_urls.push_back(new_url);
+ }
+
+ sync_child_id = sync_child_node.GetSuccessorId();
+ }
+
+ WriteToHistoryBackend(&titles, &new_urls, &updated_urls);
+
+ return true;
+}
+
+bool TypedUrlModelAssociator::DeleteAllNodes(
+ sync_api::WriteTransaction* trans) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ for (TypedUrlToSyncIdMap::iterator node_id = id_map_.begin();
+ node_id != id_map_.end(); ++node_id) {
+ sync_api::WriteNode sync_node(trans);
+ if (!sync_node.InitByIdLookup(node_id->second)) {
+ LOG(ERROR) << "Typed url node lookup failed.";
+ return false;
+ }
+ sync_node.Remove();
+ }
+
+ id_map_.clear();
+ id_map_inverse_.clear();
+ return true;
+}
+
+bool TypedUrlModelAssociator::DisassociateModels() {
+ id_map_.clear();
+ id_map_inverse_.clear();
+ return true;
+}
+
+bool TypedUrlModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
+ DCHECK(has_nodes);
+ *has_nodes = false;
+ int64 typed_url_sync_id;
+ if (!GetSyncIdForTaggedNode(kTypedUrlTag, &typed_url_sync_id)) {
+ LOG(ERROR) << "Server did not create the top-level typed_url node. We "
+ << "might be running against an out-of-date server.";
+ return false;
+ }
+ sync_api::ReadTransaction trans(
+ sync_service_->backend()->GetUserShareHandle());
+
+ sync_api::ReadNode typed_url_node(&trans);
+ if (!typed_url_node.InitByIdLookup(typed_url_sync_id)) {
+ LOG(ERROR) << "Server did not create the top-level typed_url node. We "
+ << "might be running against an out-of-date server.";
+ return false;
+ }
+
+ // The sync model has user created nodes if the typed_url folder has any
+ // children.
+ *has_nodes = sync_api::kInvalidId != typed_url_node.GetFirstChildId();
+ return true;
+}
+
+bool TypedUrlModelAssociator::ChromeModelHasUserCreatedNodes(bool* has_nodes) {
+ DCHECK(has_nodes);
+ // Assume the typed_url model always have user-created nodes.
+ *has_nodes = true;
+ return true;
+}
+
+int64 TypedUrlModelAssociator::GetSyncIdFromChromeId(
+ const std::string typed_url) {
+ TypedUrlToSyncIdMap::const_iterator iter = id_map_.find(typed_url);
+ return iter == id_map_.end() ? sync_api::kInvalidId : iter->second;
+}
+
+void TypedUrlModelAssociator::Associate(
+ const std::string* typed_url, int64 sync_id) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ DCHECK_NE(sync_api::kInvalidId, sync_id);
+ DCHECK(id_map_.find(*typed_url) == id_map_.end());
+ DCHECK(id_map_inverse_.find(sync_id) == id_map_inverse_.end());
+ id_map_[*typed_url] = sync_id;
+ id_map_inverse_[sync_id] = *typed_url;
+}
+
+void TypedUrlModelAssociator::Disassociate(int64 sync_id) {
+ DCHECK(expected_loop_ == MessageLoop::current());
+ SyncIdToTypedUrlMap::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 TypedUrlModelAssociator::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;
+}
+
+void TypedUrlModelAssociator::WriteToHistoryBackend(
+ const TypedUrlTitleVector* titles,
+ const TypedUrlVector* new_urls,
+ const TypedUrlUpdateVector* updated_urls) {
+ if (titles) {
+ for (TypedUrlTitleVector::const_iterator title = titles->begin();
+ title != titles->end(); ++title) {
+ history_backend_->SetPageTitle(title->first, title->second);
+ }
+ }
+ if (new_urls) {
+ history_backend_->AddPagesWithDetails(*new_urls);
+ }
+ if (updated_urls) {
+ for (TypedUrlUpdateVector::const_iterator url = updated_urls->begin();
+ url != updated_urls->end(); ++url) {
+ history_backend_->UpdateURL(url->first, url->second);
+ }
+ }
+}
+
+// static
+int TypedUrlModelAssociator::MergeUrls(
+ const sync_pb::TypedUrlSpecifics& typed_url,
+ const history::URLRow& url,
+ history::URLRow* new_url) {
+ DCHECK(new_url);
+ DCHECK(!typed_url.url().compare(url.url().spec()));
+ DCHECK(!typed_url.url().compare(new_url->url().spec()));
+
+ // Convert these values only once.
+ std::wstring typed_title(UTF8ToWide(typed_url.title()));
+ base::Time typed_visit =
+ base::Time::FromInternalValue(typed_url.last_visit());
+
+ // This is a bitfield represting what we'll need to update with the output
+ // value.
+ int different = DIFF_NONE;
+
+ // Check if the non-incremented values changed.
+ if ((typed_title.compare(url.title()) != 0) ||
+ (typed_visit != url.last_visit()) ||
+ (typed_url.hidden() != url.hidden())) {
+
+ // Use the values from the most recent visit.
+ if (typed_visit >= url.last_visit()) {
+ new_url->set_title(typed_title);
+ new_url->set_last_visit(typed_visit);
+ new_url->set_hidden(typed_url.hidden());
+ different |= DIFF_ROW_CHANGED;
+
+ // If we're changing the local title, note this.
+ if (new_url->title().compare(url.title()) != 0) {
+ different |= DIFF_TITLE_CHANGED;
+ }
+ } else {
+ new_url->set_title(url.title());
+ new_url->set_last_visit(url.last_visit());
+ new_url->set_hidden(url.hidden());
+ different |= DIFF_NODE_CHANGED;
+ }
+ } else {
+ // No difference.
+ new_url->set_title(url.title());
+ new_url->set_last_visit(url.last_visit());
+ new_url->set_hidden(url.hidden());
+ }
+
+ // For visit count, we just select the maximum value.
+ if (typed_url.visit_count() > url.visit_count()) {
+ new_url->set_visit_count(typed_url.visit_count());
+ different |= DIFF_ROW_CHANGED;
+ } else if (typed_url.visit_count() < url.visit_count()) {
+ new_url->set_visit_count(url.visit_count());
+ different |= DIFF_NODE_CHANGED;
+ } else {
+ new_url->set_visit_count(typed_url.visit_count());
+ }
+
+ // For typed count, we just select the maximum value.
+ if (typed_url.typed_count() > url.typed_count()) {
+ new_url->set_typed_count(typed_url.typed_count());
+ different |= DIFF_ROW_CHANGED;
+ } else if (typed_url.typed_count() < url.typed_count()) {
+ new_url->set_typed_count(url.visit_count());
+ different |= DIFF_NODE_CHANGED;
+ } else {
+ // No difference.
+ new_url->set_typed_count(typed_url.typed_count());
+ }
+
+ return different;
+}
+
+// static
+void TypedUrlModelAssociator::WriteToSyncNode(const history::URLRow& url,
+ sync_api::WriteNode* node) {
+ sync_pb::TypedUrlSpecifics typed_url;
+ typed_url.set_url(url.url().spec());
+ typed_url.set_title(WideToUTF8(url.title()));
+ typed_url.set_visit_count(url.visit_count());
+ typed_url.set_typed_count(url.typed_count());
+ typed_url.set_last_visit(url.last_visit().ToInternalValue());
+ typed_url.set_hidden(url.hidden());
+
+ node->SetTypedUrlSpecifics(typed_url);
+}
+
+} // namespace browser_sync
diff --git a/chrome/browser/sync/glue/typed_url_model_associator.h b/chrome/browser/sync/glue/typed_url_model_associator.h new file mode 100644 index 0000000..b3087c6 --- /dev/null +++ b/chrome/browser/sync/glue/typed_url_model_associator.h @@ -0,0 +1,145 @@ +// 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_TYPED_URL_MODEL_ASSOCIATOR_H_ +#define CHROME_BROWSER_SYNC_GLUE_TYPED_URL_MODEL_ASSOCIATOR_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "base/task.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/sync/glue/model_associator.h" +#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" + +class GURL; +class MessageLoop; +class ProfileSyncService; + +namespace history { +class HistoryBackend; +class URLRow; +}; + +namespace sync_api { +class WriteNode; +class WriteTransaction; +}; + +namespace browser_sync { + +class TypedUrlChangeProcessor; +class UnrecoverableErrorHandler; + +extern const char kTypedUrlTag[]; + +// Contains all model association related logic: +// * Algorithm to associate typed_url model and sync model. +// * Persisting model associations and loading them back. +// We do not check if we have local data before this run; we always +// merge and sync. +class TypedUrlModelAssociator + : public PerDataTypeAssociatorInterface<std::string, std::string> { + public: + typedef std::vector<std::pair<GURL, std::wstring> > TypedUrlTitleVector; + typedef std::vector<history::URLRow> TypedUrlVector; + typedef std::vector<std::pair<history::URLID, history::URLRow> > + TypedUrlUpdateVector; + + static syncable::ModelType model_type() { return syncable::TYPED_URLS; } + TypedUrlModelAssociator(ProfileSyncService* sync_service, + history::HistoryBackend* history_backend, + UnrecoverableErrorHandler* error_handler); + virtual ~TypedUrlModelAssociator() { } + + // PerDataTypeAssociatorInterface implementation. + // + // Iterates through the sync model looking for matched pairs of items. + virtual bool AssociateModels(); + + // Delete all typed url nodes. + bool DeleteAllNodes(sync_api::WriteTransaction* trans); + + // 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); + + // The has_nodes out param is true if the autofill model has any + // user-defined typed_url entries. + virtual bool ChromeModelHasUserCreatedNodes(bool* has_nodes); + + // Not implemented. + virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id) { + return NULL; + } + + // Not implemented. + virtual bool InitSyncNodeFromChromeId(std::string node_id, + sync_api::BaseNode* sync_node) { + return false; + } + + // Returns the sync id for the given typed_url name, or sync_api::kInvalidId + // if the typed_url name is not associated to any sync id. + virtual int64 GetSyncIdFromChromeId(std::string node_id); + + // Associates the given typed_url 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); + + void WriteToHistoryBackend(const TypedUrlTitleVector* titles, + const TypedUrlVector* new_urls, + const TypedUrlUpdateVector* updated_urls); + + enum { + DIFF_NONE = 0x0000, + DIFF_NODE_CHANGED = 0x0001, + DIFF_TITLE_CHANGED = 0x0002, + DIFF_ROW_CHANGED = 0x0004, + }; + + static int MergeUrls(const sync_pb::TypedUrlSpecifics& typed_url, + const history::URLRow& url, + history::URLRow* new_url); + static void WriteToSyncNode(const history::URLRow& url, + sync_api::WriteNode* node); + + protected: + // Returns sync service instance. + ProfileSyncService* sync_service() { return sync_service_; } + + private: + typedef std::map<std::string, int64> TypedUrlToSyncIdMap; + typedef std::map<int64, std::string> SyncIdToTypedUrlMap; + + ProfileSyncService* sync_service_; + history::HistoryBackend* history_backend_; + UnrecoverableErrorHandler* error_handler_; + int64 typed_url_node_id_; + + MessageLoop* expected_loop_; + + TypedUrlToSyncIdMap id_map_; + SyncIdToTypedUrlMap id_map_inverse_; + + DISALLOW_COPY_AND_ASSIGN(TypedUrlModelAssociator); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_TYPED_URL_MODEL_ASSOCIATOR_H_ diff --git a/chrome/browser/sync/glue/typed_url_model_associator_unittest.cc b/chrome/browser/sync/glue/typed_url_model_associator_unittest.cc new file mode 100644 index 0000000..e45ed06 --- /dev/null +++ b/chrome/browser/sync/glue/typed_url_model_associator_unittest.cc @@ -0,0 +1,120 @@ +// 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/string_piece.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/sync/glue/typed_url_model_associator.h"
+#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using browser_sync::TypedUrlModelAssociator;
+
+class TypedUrlModelAssociatorTest : public testing::Test {
+ public:
+ static history::URLRow MakeTypedUrlRow(const char* url,
+ const char* title,
+ int visit_count,
+ int typed_count,
+ int64 last_visit,
+ bool hidden) {
+ GURL gurl(url);
+ history::URLRow history_url(gurl);
+ history_url.set_title(UTF8ToWide(title));
+ history_url.set_visit_count(visit_count);
+ history_url.set_typed_count(typed_count);
+ history_url.set_last_visit(
+ base::Time::FromInternalValue(last_visit));
+ history_url.set_hidden(hidden);
+ return history_url;
+ }
+
+ static sync_pb::TypedUrlSpecifics MakeTypedUrlSpecifics(const char* url,
+ const char* title,
+ int visit_count,
+ int typed_count,
+ int64 last_visit,
+ bool hidden) {
+ sync_pb::TypedUrlSpecifics typed_url;
+ typed_url.set_url(url);
+ typed_url.set_title(title);
+ typed_url.set_visit_count(visit_count);
+ typed_url.set_typed_count(typed_count);
+ typed_url.set_last_visit(last_visit);
+ typed_url.set_hidden(hidden);
+ return typed_url;
+ }
+
+ static bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) {
+ return (lhs.url().spec().compare(rhs.url().spec()) == 0) &&
+ (lhs.title().compare(rhs.title()) == 0) &&
+ (lhs.visit_count() == rhs.visit_count()) &&
+ (lhs.typed_count() == rhs.typed_count()) &&
+ (lhs.last_visit() == rhs.last_visit()) &&
+ (lhs.hidden() == rhs.hidden());
+ }
+};
+
+TEST_F(TypedUrlModelAssociatorTest, MergeUrls) {
+ history::URLRow row1(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 3, false));
+ sync_pb::TypedUrlSpecifics specs1(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie",
+ 1, 2, 3, false));
+ history::URLRow new_row1(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_NONE,
+ TypedUrlModelAssociator::MergeUrls(specs1, row1, &new_row1));
+
+ history::URLRow row2(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 3, false));
+ sync_pb::TypedUrlSpecifics specs2(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie",
+ 1, 2, 4, true));
+ history::URLRow expected2(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 4, true));
+ history::URLRow new_row2(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_ROW_CHANGED,
+ TypedUrlModelAssociator::MergeUrls(specs2, row2, &new_row2));
+ EXPECT_TRUE(URLsEqual(new_row2, expected2));
+
+ history::URLRow row3(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 3, false));
+ sync_pb::TypedUrlSpecifics specs3(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie2",
+ 1, 2, 4, true));
+ history::URLRow expected3(MakeTypedUrlRow("http://pie.com/", "pie2",
+ 1, 2, 4, true));
+ history::URLRow new_row3(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_ROW_CHANGED |
+ TypedUrlModelAssociator::DIFF_TITLE_CHANGED,
+ TypedUrlModelAssociator::MergeUrls(specs3, row3, &new_row3));
+ EXPECT_TRUE(URLsEqual(new_row3, expected3));
+
+ history::URLRow row4(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 4, false));
+ sync_pb::TypedUrlSpecifics specs4(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie2",
+ 1, 2, 3, true));
+ history::URLRow expected4(MakeTypedUrlRow("http://pie.com/", "pie",
+ 1, 2, 4, false));
+ history::URLRow new_row4(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_NODE_CHANGED,
+ TypedUrlModelAssociator::MergeUrls(specs4, row4, &new_row4));
+ EXPECT_TRUE(URLsEqual(new_row4, expected4));
+
+ history::URLRow row5(MakeTypedUrlRow("http://pie.com/", "pie",
+ 2, 1, 3, false));
+ sync_pb::TypedUrlSpecifics specs5(MakeTypedUrlSpecifics("http://pie.com/",
+ "pie",
+ 1, 2, 3, false));
+ history::URLRow expected5(MakeTypedUrlRow("http://pie.com/", "pie",
+ 2, 2, 3, false));
+ history::URLRow new_row5(GURL("http://pie.com/"));
+ EXPECT_EQ(TypedUrlModelAssociator::DIFF_ROW_CHANGED |
+ TypedUrlModelAssociator::DIFF_NODE_CHANGED,
+ TypedUrlModelAssociator::MergeUrls(specs5, row5, &new_row5));
+ EXPECT_TRUE(URLsEqual(new_row5, expected5));
+
+}
diff --git a/chrome/browser/sync/profile_sync_factory.h b/chrome/browser/sync/profile_sync_factory.h index ae61f05..ef813d4 100644 --- a/chrome/browser/sync/profile_sync_factory.h +++ b/chrome/browser/sync/profile_sync_factory.h @@ -21,6 +21,10 @@ class SyncBackendHost; class UnrecoverableErrorHandler; } +namespace history { +class HistoryBackend; +}; + // Factory class for all profile sync related classes. class ProfileSyncFactory { public: @@ -71,6 +75,14 @@ class ProfileSyncFactory { virtual SyncComponents CreatePreferenceSyncComponents( ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler) = 0; + + // Instantiates both a model associator and change processor for the + // typed_url data type. The pointers in the return struct are owned + // by the caller. + virtual SyncComponents CreateTypedUrlSyncComponents( + ProfileSyncService* profile_sync_service, + history::HistoryBackend* history_backend, + browser_sync::UnrecoverableErrorHandler* error_handler) = 0; }; #endif // CHROME_BROWSER_SYNC_PROFILE_SYNC_FACTORY_H__ diff --git a/chrome/browser/sync/profile_sync_factory_impl.cc b/chrome/browser/sync/profile_sync_factory_impl.cc index a687e3b..89df00ba 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.cc +++ b/chrome/browser/sync/profile_sync_factory_impl.cc @@ -16,6 +16,9 @@ #include "chrome/browser/sync/glue/preference_data_type_controller.h" #include "chrome/browser/sync/glue/preference_model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" +#include "chrome/browser/sync/glue/typed_url_change_processor.h" +#include "chrome/browser/sync/glue/typed_url_data_type_controller.h" +#include "chrome/browser/sync/glue/typed_url_model_associator.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory_impl.h" #include "chrome/browser/webdata/web_data_service.h" @@ -26,16 +29,17 @@ using browser_sync::AutofillDataTypeController; using browser_sync::AutofillModelAssociator; using browser_sync::BookmarkChangeProcessor; using browser_sync::BookmarkDataTypeController; -using browser_sync::BookmarkChangeProcessor; using browser_sync::BookmarkModelAssociator; using browser_sync::DataTypeController; using browser_sync::DataTypeManager; using browser_sync::DataTypeManagerImpl; -using browser_sync::PreferenceDataTypeController; using browser_sync::PreferenceChangeProcessor; using browser_sync::PreferenceDataTypeController; using browser_sync::PreferenceModelAssociator; using browser_sync::SyncBackendHost; +using browser_sync::TypedUrlChangeProcessor; +using browser_sync::TypedUrlDataTypeController; +using browser_sync::TypedUrlModelAssociator; using browser_sync::UnrecoverableErrorHandler; ProfileSyncFactoryImpl::ProfileSyncFactoryImpl( @@ -71,6 +75,13 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService() { new PreferenceDataTypeController(this, pss)); } + // TypedUrl sync is disabled by default. Register only if + // explicitly enabled. + if (command_line_->HasSwitch(switches::kEnableSyncTypedUrls)) { + pss->RegisterDataTypeController( + new TypedUrlDataTypeController(this, profile_, pss)); + } + return pss; } @@ -121,3 +132,19 @@ ProfileSyncFactoryImpl::CreatePreferenceSyncComponents( error_handler); return SyncComponents(model_associator, change_processor); } + +ProfileSyncFactory::SyncComponents +ProfileSyncFactoryImpl::CreateTypedUrlSyncComponents( + ProfileSyncService* profile_sync_service, + history::HistoryBackend* history_backend, + browser_sync::UnrecoverableErrorHandler* error_handler) { + TypedUrlModelAssociator* model_associator = + new TypedUrlModelAssociator(profile_sync_service, + history_backend, + error_handler); + TypedUrlChangeProcessor* change_processor = + new TypedUrlChangeProcessor(model_associator, + history_backend, + error_handler); + return SyncComponents(model_associator, change_processor); +} diff --git a/chrome/browser/sync/profile_sync_factory_impl.h b/chrome/browser/sync/profile_sync_factory_impl.h index 2989c53..effec7e 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.h +++ b/chrome/browser/sync/profile_sync_factory_impl.h @@ -36,6 +36,11 @@ class ProfileSyncFactoryImpl : public ProfileSyncFactory { ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler); + virtual SyncComponents CreateTypedUrlSyncComponents( + ProfileSyncService* profile_sync_service, + history::HistoryBackend* history_backend, + browser_sync::UnrecoverableErrorHandler* error_handler); + private: Profile* profile_; CommandLine* command_line_; diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h index d635b6e..ba4d078 100644 --- a/chrome/browser/sync/profile_sync_factory_mock.h +++ b/chrome/browser/sync/profile_sync_factory_mock.h @@ -39,6 +39,11 @@ class ProfileSyncFactoryMock : public ProfileSyncFactory { MOCK_METHOD2(CreatePreferenceSyncComponents, SyncComponents(ProfileSyncService* profile_sync_service, browser_sync::UnrecoverableErrorHandler* error_handler)); + MOCK_METHOD3(CreateTypedUrlSyncComponents, + SyncComponents( + ProfileSyncService* profile_sync_service, + history::HistoryBackend* history_backend, + browser_sync::UnrecoverableErrorHandler* error_handler)); private: SyncComponents MakeBookmarkSyncComponents(); diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc index 256f443..bb184cd 100644 --- a/chrome/browser/sync/profile_sync_service.cc +++ b/chrome/browser/sync/profile_sync_service.cc @@ -184,7 +184,8 @@ void ProfileSyncService::StartUp() { profile_->GetPrefs()->GetInt64(prefs::kSyncLastSyncedTime)); backend_.reset( - new SyncBackendHost(this, profile_->GetPath(), data_type_controllers_)); + new SyncBackendHost(this, profile_, profile_->GetPath(), + data_type_controllers_)); // Initialize the backend. Every time we start up a new SyncBackendHost, // we'll want to start from a fresh SyncDB, so delete any old one that might diff --git a/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc b/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc new file mode 100644 index 0000000..6c18b59 --- /dev/null +++ b/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc @@ -0,0 +1,498 @@ +// 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 <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +#include "base/ref_counted.h" +#include "base/string16.h" +#include "base/thread.h" +#include "base/time.h" +#include "base/waitable_event.h" +#include "chrome/browser/history/history_backend.h" +#include "chrome/browser/history/history_types.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/sync_backend_host.h" +#include "chrome/browser/sync/glue/sync_backend_host_mock.h" +#include "chrome/browser/sync/glue/typed_url_change_processor.h" +#include "chrome/browser/sync/glue/typed_url_data_type_controller.h" +#include "chrome/browser/sync/glue/typed_url_model_associator.h" +#include "chrome/browser/sync/profile_sync_factory.h" +#include "chrome/browser/sync/profile_sync_factory_mock.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/profile_sync_test_util.h" +#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" +#include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/common/notification_service.h" +#include "chrome/test/profile_mock.h" +#include "chrome/test/sync/engine/test_id_factory.h" +#include "chrome/test/testing_profile.h" +#include "googleurl/src/gurl.h" +#include "testing/gmock/include/gmock/gmock.h" + +using base::Time; +using base::Thread; +using base::WaitableEvent; +using browser_sync::SyncBackendHost; +using browser_sync::SyncBackendHostMock; +using browser_sync::TestIdFactory; +using browser_sync::TypedUrlChangeProcessor; +using browser_sync::TypedUrlDataTypeController; +using browser_sync::TypedUrlModelAssociator; +using browser_sync::UnrecoverableErrorHandler; +using history::HistoryBackend; +using history::URLID; +using history::URLRow; +using sync_api::SyncManager; +using sync_api::UserShare; +using syncable::BASE_VERSION; +using syncable::CREATE; +using syncable::DirectoryManager; +using syncable::ID; +using syncable::IS_DEL; +using syncable::IS_DIR; +using syncable::IS_UNAPPLIED_UPDATE; +using syncable::IS_UNSYNCED; +using syncable::MutableEntry; +using syncable::SERVER_IS_DIR; +using syncable::SERVER_VERSION; +using syncable::SPECIFICS; +using syncable::ScopedDirLookup; +using syncable::UNIQUE_SERVER_TAG; +using syncable::UNITTEST; +using syncable::WriteTransaction; +using testing::_; +using testing::DoAll; +using testing::DoDefault; +using testing::Invoke; +using testing::Return; +using testing::SetArgumentPointee; +using testing::WithArgs; + +class TestingProfileSyncService : public ProfileSyncService { + public: + explicit TestingProfileSyncService(ProfileSyncFactory* factory, + Profile* profile, + bool bootstrap_sync_authentication) + : ProfileSyncService(factory, profile, bootstrap_sync_authentication) { + RegisterPreferences(); + SetSyncSetupCompleted(); + } + virtual ~TestingProfileSyncService() { + } + + virtual void InitializeBackend(bool delete_sync_data_folder) { + browser_sync::TestHttpBridgeFactory* factory = + new browser_sync::TestHttpBridgeFactory(); + browser_sync::TestHttpBridgeFactory* factory2 = + new browser_sync::TestHttpBridgeFactory(); + backend()->InitializeForTestMode(L"testuser", factory, factory2, + delete_sync_data_folder, browser_sync::kDefaultNotificationMethod); + } + + private: + // When testing under ChromiumOS, this method must not return an empty + // value value in order for the profile sync service to start. + virtual std::string GetLsidForAuthBootstraping() { + return "foo"; + } +}; + +class HistoryBackendMock : public HistoryBackend { + public: + HistoryBackendMock() : HistoryBackend(FilePath(), NULL, NULL) {} + MOCK_METHOD1(GetAllTypedURLs, bool(std::vector<history::URLRow>* entries)); + MOCK_METHOD2(UpdateURL, bool(history::URLID id, history::URLRow& url)); + MOCK_METHOD2(SetPageTitle, void(const GURL& url, const std::wstring& title)); + MOCK_METHOD2(GetURL, bool(const GURL& url_id, history::URLRow* url_row)); + MOCK_METHOD1(DeleteURL, void(const GURL& url)); +}; + +class HistoryThreadNotificationService : + public base::RefCountedThreadSafe<HistoryThreadNotificationService> { + public: + explicit HistoryThreadNotificationService(Thread* history_thread) + : done_event_(false, false), + history_thread_(history_thread) {} + + void Init() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + history_thread_->message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, &HistoryThreadNotificationService::InitTask)); + done_event_.Wait(); + } + + void TearDown() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + history_thread_->message_loop()->PostTask( + FROM_HERE, + NewRunnableMethod(this, + &HistoryThreadNotificationService::TearDownTask)); + done_event_.Wait(); + } + + private: + friend class base::RefCountedThreadSafe<HistoryThreadNotificationService>; + + void InitTask() { + service_.reset(new NotificationService()); + done_event_.Signal(); + } + + void TearDownTask() { + service_.reset(NULL); + done_event_.Signal(); + } + + WaitableEvent done_event_; + Thread* history_thread_; + scoped_ptr<NotificationService> service_; +}; + +class HistoryServiceMock : public HistoryService { + public: + HistoryServiceMock() {} + MOCK_METHOD2(ScheduleDBTask, Handle(HistoryDBTask*, + CancelableRequestConsumerBase*)); +}; + +class RunOnDBThreadTask : public Task { + public: + RunOnDBThreadTask(HistoryBackend* backend, HistoryDBTask* task) + : backend_(backend), task_(task) {} + virtual void Run() { + task_->RunOnDBThread(backend_, NULL); + } + private: + HistoryBackend* backend_; + HistoryDBTask* task_; +}; + +ACTION_P2(RunTaskOnDBThread, thread, backend) { + thread->message_loop()->PostTask( + FROM_HERE, + new RunOnDBThreadTask(backend, arg0)); + return 0; +} + +ACTION(QuitUIMessageLoop) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + MessageLoop::current()->Quit(); +} + +ACTION_P3(MakeTypedUrlSyncComponents, service, hb, dtc) { + TypedUrlModelAssociator* model_associator = + new TypedUrlModelAssociator(service, hb, dtc); + TypedUrlChangeProcessor* change_processor = + new TypedUrlChangeProcessor(model_associator, hb, service); + return ProfileSyncFactory::SyncComponents(model_associator, change_processor); +} + +class ProfileSyncServiceTypedUrlTest : public testing::Test { + protected: + ProfileSyncServiceTypedUrlTest() + : ui_thread_(ChromeThread::UI, &message_loop_), + history_thread_("history"), + done_event_(false, false) { + } + + virtual void SetUp() { + history_backend_ = new HistoryBackendMock(); + history_service_ = new HistoryServiceMock(); + EXPECT_CALL((*history_service_.get()), ScheduleDBTask(_, _)) + .WillRepeatedly(RunTaskOnDBThread(&history_thread_, + history_backend_)); + history_thread_.Start(); + + notification_service_ = + new HistoryThreadNotificationService(&history_thread_); + notification_service_->Init(); + } + + virtual void TearDown() { + history_backend_ = NULL; + history_service_ = NULL; + service_.reset(); + notification_service_->TearDown(); + history_thread_.Stop(); + MessageLoop::current()->RunAllPending(); + } + + void StartSyncService(Task* task) { + if (!service_.get()) { + service_.reset( + new TestingProfileSyncService(&factory_, &profile_, false)); + service_->AddObserver(&observer_); + TypedUrlDataTypeController* data_type_controller = + new TypedUrlDataTypeController(&factory_, + &profile_, + service_.get()); + + EXPECT_CALL(factory_, CreateTypedUrlSyncComponents(_, _, _)). + WillOnce(MakeTypedUrlSyncComponents(service_.get(), + history_backend_.get(), + data_type_controller)); + EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). + WillOnce(MakeDataTypeManager(&backend_mock_)); + + EXPECT_CALL(profile_, GetHistoryServiceWithoutCreating()). + WillRepeatedly(Return(history_service_.get())); + + EXPECT_CALL(profile_, GetHistoryService(_)). + WillRepeatedly(Return(history_service_.get())); + + // State changes once for the backend init and once for startup done. + EXPECT_CALL(observer_, OnStateChanged()). + WillOnce(InvokeTask(task)). + WillOnce(QuitUIMessageLoop()); + service_->RegisterDataTypeController(data_type_controller); + service_->Initialize(); + MessageLoop::current()->Run(); + } + } + + void CreateTypedUrlRoot() { + UserShare* user_share = service_->backend()->GetUserShareHandle(); + DirectoryManager* dir_manager = user_share->dir_manager.get(); + + ScopedDirLookup dir(dir_manager, user_share->authenticated_name); + if (!dir.good()) + return; + + WriteTransaction wtrans(dir, UNITTEST, __FILE__, __LINE__); + MutableEntry node(&wtrans, + CREATE, + wtrans.root_id(), + browser_sync::kTypedUrlTag); + node.Put(UNIQUE_SERVER_TAG, browser_sync::kTypedUrlTag); + node.Put(IS_DIR, true); + node.Put(SERVER_IS_DIR, false); + node.Put(IS_UNSYNCED, false); + node.Put(IS_UNAPPLIED_UPDATE, false); + node.Put(SERVER_VERSION, 20); + node.Put(BASE_VERSION, 20); + node.Put(IS_DEL, false); + node.Put(ID, ids_.MakeServer(browser_sync::kTypedUrlTag)); + sync_pb::EntitySpecifics specifics; + specifics.MutableExtension(sync_pb::typed_url); + node.Put(SPECIFICS, specifics); + } + + void AddTypedUrlSyncNode(const history::URLRow& url) { + sync_api::WriteTransaction trans( + service_->backend()->GetUserShareHandle()); + sync_api::ReadNode typed_url_root(&trans); + ASSERT_TRUE(typed_url_root.InitByTagLookup(browser_sync::kTypedUrlTag)); + + sync_api::WriteNode node(&trans); + std::string tag = url.url().spec(); + ASSERT_TRUE(node.InitUniqueByCreation(syncable::TYPED_URLS, + typed_url_root, + tag)); + TypedUrlModelAssociator::WriteToSyncNode(url, &node); + } + + void GetTypedUrlsFromSyncDB(std::vector<history::URLRow>* urls) { + sync_api::ReadTransaction trans(service_->backend()->GetUserShareHandle()); + sync_api::ReadNode typed_url_root(&trans); + if (!typed_url_root.InitByTagLookup(browser_sync::kTypedUrlTag)) + return; + + int64 child_id = typed_url_root.GetFirstChildId(); + while (child_id != sync_api::kInvalidId) { + sync_api::ReadNode child_node(&trans); + if (!child_node.InitByIdLookup(child_id)) + return; + + const sync_pb::TypedUrlSpecifics& typed_url( + child_node.GetTypedUrlSpecifics()); + history::URLRow new_url(GURL(typed_url.url())); + + new_url.set_title(UTF8ToWide(typed_url.title())); + new_url.set_visit_count(typed_url.visit_count()); + new_url.set_typed_count(typed_url.typed_count()); + new_url.set_last_visit( + base::Time::FromInternalValue(typed_url.last_visit())); + new_url.set_hidden(typed_url.hidden()); + + urls->push_back(new_url); + child_id = child_node.GetSuccessorId(); + } + } + + void SetIdleChangeProcessorExpectations() { + EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)).Times(0); + EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)).Times(0); + EXPECT_CALL((*history_backend_.get()), GetURL(_, _)).Times(0); + EXPECT_CALL((*history_backend_.get()), DeleteURL(_)).Times(0); + } + + static bool URLsEqual(history::URLRow& lhs, history::URLRow& rhs) { + return (lhs.url().spec().compare(rhs.url().spec()) == 0) && + (lhs.title().compare(rhs.title()) == 0) && + (lhs.visit_count() == rhs.visit_count()) && + (lhs.typed_count() == rhs.typed_count()) && + (lhs.last_visit() == rhs.last_visit()) && + (lhs.hidden() == rhs.hidden()); + } + + static history::URLRow MakeTypedUrlEntry(const char* url, + const char* title, + int visit_count, + int typed_count, + int64 last_visit, + bool hidden) { + GURL gurl(url); + URLRow history_url(gurl); + history_url.set_title(UTF8ToWide(title)); + history_url.set_visit_count(visit_count); + history_url.set_typed_count(typed_count); + history_url.set_last_visit( + base::Time::FromInternalValue(last_visit)); + history_url.set_hidden(hidden); + return history_url; + } + + friend class AddTypedUrlEntriesTask; + friend class CreateTypedUrlRootTask; + + MessageLoopForUI message_loop_; + ChromeThread ui_thread_; + Thread history_thread_; + WaitableEvent done_event_; + scoped_refptr<HistoryThreadNotificationService> notification_service_; + + scoped_ptr<TestingProfileSyncService> service_; + ProfileMock profile_; + ProfileSyncFactoryMock factory_; + SyncBackendHostMock backend_mock_; + ProfileSyncServiceObserverMock observer_; + scoped_refptr<HistoryBackendMock> history_backend_; + scoped_refptr<HistoryServiceMock> history_service_; + + TestIdFactory ids_; +}; + +class CreateTypedUrlRootTask : public Task { + public: + explicit CreateTypedUrlRootTask(ProfileSyncServiceTypedUrlTest* test) + : test_(test) { + } + + virtual void Run() { + test_->CreateTypedUrlRoot(); + } + + private: + ProfileSyncServiceTypedUrlTest* test_; +}; + +class AddTypedUrlEntriesTask : public Task { + public: + AddTypedUrlEntriesTask(ProfileSyncServiceTypedUrlTest* test, + const std::vector<history::URLRow>& entries) + : test_(test), entries_(entries) { + } + + virtual void Run() { + test_->CreateTypedUrlRoot(); + for (size_t i = 0; i < entries_.size(); ++i) { + test_->AddTypedUrlSyncNode(entries_[i]); + } + } + + private: + ProfileSyncServiceTypedUrlTest* test_; + const std::vector<history::URLRow>& entries_; +}; + +TEST_F(ProfileSyncServiceTypedUrlTest, EmptyNativeEmptySync) { + EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)). + WillOnce(Return(true)); + SetIdleChangeProcessorExpectations(); + CreateTypedUrlRootTask task(this); + StartSyncService(&task); + std::vector<history::URLRow> sync_entries; + GetTypedUrlsFromSyncDB(&sync_entries); + EXPECT_EQ(0U, sync_entries.size()); +} + +TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeEmptySync) { + std::vector<history::URLRow> entries; + entries.push_back(MakeTypedUrlEntry("http://foo.com", "bar", + 1, 2, 15, false)); + EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)). + WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true))); + SetIdleChangeProcessorExpectations(); + CreateTypedUrlRootTask task(this); + StartSyncService(&task); + std::vector<history::URLRow> sync_entries; + GetTypedUrlsFromSyncDB(&sync_entries); + ASSERT_EQ(1U, entries.size()); + EXPECT_TRUE(URLsEqual(entries[0], sync_entries[0])); +} + +TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeHasSyncNoMerge) { + history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry", + 1, 2, 15, false)); + history::URLRow sync_entry(MakeTypedUrlEntry("http://sync.com", "entry", + 2, 3, 16, false)); + + std::vector<history::URLRow> native_entries; + native_entries.push_back(native_entry); + EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)). + WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true))); + + std::vector<history::URLRow> sync_entries; + sync_entries.push_back(sync_entry); + AddTypedUrlEntriesTask task(this, sync_entries); + + EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)). + WillRepeatedly(Return(true)); + StartSyncService(&task); + + std::map<std::string, history::URLRow> expected; + expected[native_entry.url().spec()] = native_entry; + expected[sync_entry.url().spec()] = sync_entry; + + std::vector<history::URLRow> new_sync_entries; + GetTypedUrlsFromSyncDB(&new_sync_entries); + + EXPECT_TRUE(new_sync_entries.size() == expected.size()); + for (std::vector<history::URLRow>::iterator entry = new_sync_entries.begin(); + entry != new_sync_entries.end(); ++entry) { + EXPECT_TRUE(URLsEqual(expected[entry->url().spec()], *entry)); + } +} + +TEST_F(ProfileSyncServiceTypedUrlTest, HasNativeHasSyncMerge) { + history::URLRow native_entry(MakeTypedUrlEntry("http://native.com", "entry", + 1, 2, 15, false)); + history::URLRow sync_entry(MakeTypedUrlEntry("http://native.com", "name", + 2, 1, 17, false)); + history::URLRow merged_entry(MakeTypedUrlEntry("http://native.com", "name", + 2, 1, 17, false)); + + std::vector<history::URLRow> native_entries; + native_entries.push_back(native_entry); + EXPECT_CALL((*history_backend_.get()), GetAllTypedURLs(_)). + WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true))); + + std::vector<history::URLRow> sync_entries; + sync_entries.push_back(sync_entry); + AddTypedUrlEntriesTask task(this, sync_entries); + + EXPECT_CALL((*history_backend_.get()), UpdateURL(_, _)). + WillRepeatedly(Return(true)); + EXPECT_CALL((*history_backend_.get()), SetPageTitle(_, _)). + WillRepeatedly(Return()); + StartSyncService(&task); + + std::vector<history::URLRow> new_sync_entries; + GetTypedUrlsFromSyncDB(&new_sync_entries); + ASSERT_EQ(1U, new_sync_entries.size()); + EXPECT_TRUE(URLsEqual(merged_entry, new_sync_entries[0])); +} diff --git a/chrome/browser/sync/protocol/typed_url_specifics.proto b/chrome/browser/sync/protocol/typed_url_specifics.proto new file mode 100644 index 0000000..a6bd309 --- /dev/null +++ b/chrome/browser/sync/protocol/typed_url_specifics.proto @@ -0,0 +1,27 @@ +// 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. +// +// Sync protocol datatype extension for typed urls. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package sync_pb; + +import "sync.proto"; + +// Properties of typed_url sync objects. +message TypedUrlSpecifics { + optional string url = 1; + optional string title = 2; + optional int32 visit_count = 3; + optional int32 typed_count = 4; + optional int64 last_visit = 5; + optional bool hidden = 6; +} + +extend EntitySpecifics { + optional TypedUrlSpecifics typed_url = 40781; +} diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc index 10ff451..63a8d65 100644 --- a/chrome/browser/sync/syncable/model_type.cc +++ b/chrome/browser/sync/syncable/model_type.cc @@ -8,6 +8,7 @@ #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" +#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" #include "chrome/browser/sync/protocol/sync.pb.h" namespace syncable { @@ -24,6 +25,9 @@ void AddDefaultExtensionValue(syncable::ModelType datatype, case AUTOFILL: specifics->MutableExtension(sync_pb::autofill); break; + case TYPED_URLS: + specifics->MutableExtension(sync_pb::typed_url); + break; default: NOTREACHED() << "No known extension for model type."; } @@ -49,6 +53,9 @@ ModelType GetModelType(const sync_pb::SyncEntity& sync_pb_entity) { if (sync_entity.specifics().HasExtension(sync_pb::autofill)) return syncable::AUTOFILL; + if (sync_entity.specifics().HasExtension(sync_pb::typed_url)) + return syncable::TYPED_URLS; + // Loose check for server-created top-level folders that aren't // bound to a particular model type. if (!sync_entity.server_defined_unique_tag().empty() && diff --git a/chrome/browser/sync/syncable/model_type.h b/chrome/browser/sync/syncable/model_type.h index e5dd648..7fc5655 100644 --- a/chrome/browser/sync/syncable/model_type.h +++ b/chrome/browser/sync/syncable/model_type.h @@ -28,6 +28,7 @@ enum ModelType { BOOKMARKS, // A bookmark folder or a bookmark URL object. PREFERENCES, // A preference folder or a preference object. AUTOFILL, // An autofill folder or an autofill object. + TYPED_URLS, // A typed_url folder or a typed_url object. MODEL_TYPE_COUNT, }; diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc index 6a8e628..880074b 100644 --- a/chrome/browser/sync/syncable/syncable.cc +++ b/chrome/browser/sync/syncable/syncable.cc @@ -39,6 +39,7 @@ #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" #include "chrome/browser/sync/protocol/service_constants.h" +#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" #include "chrome/browser/sync/syncable/directory_backing_store.h" #include "chrome/browser/sync/syncable/directory_manager.h" #include "chrome/browser/sync/syncable/syncable-inl.h" @@ -1063,6 +1064,8 @@ syncable::ModelType Entry::GetServerModelType() const { return PREFERENCES; if (Get(SERVER_SPECIFICS).HasExtension(sync_pb::autofill)) return AUTOFILL; + if (Get(SERVER_SPECIFICS).HasExtension(sync_pb::typed_url)) + return TYPED_URLS; if (IsRoot()) return TOP_LEVEL_FOLDER; // Loose check for server-created top-level folders that aren't @@ -1090,6 +1093,8 @@ syncable::ModelType Entry::GetModelType() const { return PREFERENCES; if (Get(SPECIFICS).HasExtension(sync_pb::autofill)) return AUTOFILL; + if (Get(SPECIFICS).HasExtension(sync_pb::typed_url)) + return TYPED_URLS; if (IsRoot()) return TOP_LEVEL_FOLDER; // Loose check for server-created top-level folders that aren't diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index b80b36d..124ccb5 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -700,6 +700,7 @@ 'browser/sync/protocol/autofill_specifics.proto', 'browser/sync/protocol/bookmark_specifics.proto', 'browser/sync/protocol/preference_specifics.proto', + 'browser/sync/protocol/typed_url_specifics.proto', ], 'rules': [ { @@ -849,6 +850,8 @@ '<(protoc_out_dir)/chrome/browser/sync/protocol/bookmark_specifics.pb.h', '<(protoc_out_dir)/chrome/browser/sync/protocol/preference_specifics.pb.cc', '<(protoc_out_dir)/chrome/browser/sync/protocol/preference_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/typed_url_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/typed_url_specifics.pb.h', 'browser/sync/engine/all_status.cc', 'browser/sync/engine/all_status.h', 'browser/sync/engine/apply_updates_command.cc', diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 9ce744d..6cbd8b1 100755 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1897,6 +1897,8 @@ 'browser/sync/glue/data_type_manager_impl.h', 'browser/sync/glue/database_model_worker.cc', 'browser/sync/glue/database_model_worker.h', + 'browser/sync/glue/history_model_worker.cc', + 'browser/sync/glue/history_model_worker.h', 'browser/sync/glue/http_bridge.cc', 'browser/sync/glue/http_bridge.h', 'browser/sync/glue/model_associator.h', @@ -1908,6 +1910,12 @@ 'browser/sync/glue/preference_model_associator.h', 'browser/sync/glue/sync_backend_host.cc', 'browser/sync/glue/sync_backend_host.h', + 'browser/sync/glue/typed_url_change_processor.cc', + 'browser/sync/glue/typed_url_change_processor.h', + 'browser/sync/glue/typed_url_data_type_controller.cc', + 'browser/sync/glue/typed_url_data_type_controller.h', + 'browser/sync/glue/typed_url_model_associator.cc', + 'browser/sync/glue/typed_url_model_associator.h', 'browser/sync/glue/ui_model_worker.cc', 'browser/sync/glue/ui_model_worker.h', 'browser/sync/notification_method.h', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 368cb2a..ad74905 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -867,6 +867,7 @@ 'browser/sync/glue/http_bridge_unittest.cc', 'browser/sync/glue/preference_data_type_controller_unittest.cc', 'browser/sync/glue/sync_backend_host_mock.h', + 'browser/sync/glue/typed_url_model_associator_unittest.cc', 'browser/sync/glue/ui_model_worker_unittest.cc', 'browser/sync/profile_sync_service_mock.h', 'browser/sync/profile_sync_service_startup_unittest.cc', @@ -876,6 +877,7 @@ 'browser/sync/profile_sync_factory_mock.h', 'browser/sync/profile_sync_service_autofill_unittest.cc', 'browser/sync/profile_sync_service_preference_unittest.cc', + 'browser/sync/profile_sync_service_typed_url_unittest.cc', 'browser/sync/profile_sync_test_util.h', 'browser/sync/sync_setup_wizard_unittest.cc', 'browser/sync/sync_ui_util_mac_unittest.mm', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index e3bc887..56528c6 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -174,6 +174,9 @@ const char kDisableSyncBookmarks[] = "disable-sync-bookmarks"; // Disable syncing of preferences. const char kDisableSyncPreferences[] = "disable-sync-preferences"; +// Disable syncing of typed urls. +const char kDisableSyncTypedUrls[] = "disable-sync-typed-urls"; + // Enables the backend service for web resources, used in the new tab page for // loading tips and recommendations from a JSON feed. const char kDisableWebResources[] = "disable-web-resources"; @@ -291,6 +294,9 @@ const char kEnableSyncBookmarks[] = "enable-sync-bookmarks"; // Enable syncing browser preferences. const char kEnableSyncPreferences[] = "enable-sync-preferences"; +// Enable syncing browser typed urls. +const char kEnableSyncTypedUrls[] = "enable-sync-typed-urls"; + // Enable the tabbed bookmark manager const char kEnableTabbedBookmarkManager[] = "enable-tabbed-bookmark-manager"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index eb3b06e..134df40 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -65,6 +65,7 @@ extern const char kDisableSync[]; extern const char kDisableSyncAutofill[]; extern const char kDisableSyncBookmarks[]; extern const char kDisableSyncPreferences[]; +extern const char kDisableSyncTypedUrls[]; extern const char kDisableWebResources[]; extern const char kDisableWebSecurity[]; extern const char kDisableWebSockets[]; @@ -99,6 +100,7 @@ extern const char kEnableSync[]; extern const char kEnableSyncAutofill[]; extern const char kEnableSyncBookmarks[]; extern const char kEnableSyncPreferences[]; +extern const char kEnableSyncTypedUrls[]; extern const char kEnableTabbedBookmarkManager[]; extern const char kEnableUserDataDirProfiles[]; extern const char kEnableUserStyleSheet[]; diff --git a/chrome/test/profile_mock.h b/chrome/test/profile_mock.h index c85a1ca..388d282 100644 --- a/chrome/test/profile_mock.h +++ b/chrome/test/profile_mock.h @@ -13,6 +13,8 @@ class ProfileMock : public TestingProfile { public: MOCK_METHOD0(GetBookmarkModel, BookmarkModel*()); MOCK_METHOD1(GetWebDataService, WebDataService*(ServiceAccessType access)); + MOCK_METHOD1(GetHistoryService, HistoryService*(ServiceAccessType access)); + MOCK_METHOD0(GetHistoryServiceWithoutCreating, HistoryService*()); }; #endif // CHROME_TEST_PROFILE_MOCK_H__ |