summaryrefslogtreecommitdiffstats
path: root/components/history
diff options
context:
space:
mode:
authormaxbogue <maxbogue@chromium.org>2015-10-22 16:34:32 -0700
committerCommit bot <commit-bot@chromium.org>2015-10-22 23:35:14 +0000
commitae4d4cd14920a9ec246406b99d5f49c2a06c450c (patch)
tree24a38e19c29c1617d624d4972c9309a007543307 /components/history
parent67349c42be488047259ba0b919d882a234b49e64 (diff)
downloadchromium_src-ae4d4cd14920a9ec246406b99d5f49c2a06c450c.zip
chromium_src-ae4d4cd14920a9ec246406b99d5f49c2a06c450c.tar.gz
chromium_src-ae4d4cd14920a9ec246406b99d5f49c2a06c450c.tar.bz2
[Sync] Remove history and favicon deps from sync_driver.
All datatype specific code is being moved out of sync_driver. BUG=543199, 512038 Review URL: https://codereview.chromium.org/1402153003 Cr-Commit-Position: refs/heads/master@{#355672}
Diffstat (limited to 'components/history')
-rw-r--r--components/history/DEPS1
-rw-r--r--components/history/core/browser/BUILD.gn5
-rw-r--r--components/history/core/browser/history_model_worker.cc131
-rw-r--r--components/history/core/browser/history_model_worker.h62
-rw-r--r--components/history/core/browser/typed_url_model_associator.cc858
-rw-r--r--components/history/core/browser/typed_url_model_associator.h208
6 files changed, 1265 insertions, 0 deletions
diff --git a/components/history/DEPS b/components/history/DEPS
index 58b0b17..9111271 100644
--- a/components/history/DEPS
+++ b/components/history/DEPS
@@ -3,6 +3,7 @@ include_rules = [
"+components/keyed_service",
"+components/query_parser",
"+components/signin/core",
+ "+components/sync_driver",
"+google_apis/gaia",
"+net",
"+sql",
diff --git a/components/history/core/browser/BUILD.gn b/components/history/core/browser/BUILD.gn
index 4ce6421..2da4805 100644
--- a/components/history/core/browser/BUILD.gn
+++ b/components/history/core/browser/BUILD.gn
@@ -31,6 +31,8 @@ static_library("browser") {
"history_db_task.h",
"history_match.cc",
"history_match.h",
+ "history_model_worker.cc",
+ "history_model_worker.h",
"history_service.cc",
"history_service.h",
"history_service_observer.h",
@@ -58,6 +60,8 @@ static_library("browser") {
"top_sites_impl.cc",
"top_sites_impl.h",
"top_sites_observer.h",
+ "typed_url_model_associator.cc",
+ "typed_url_model_associator.h",
"typed_url_syncable_service.cc",
"typed_url_syncable_service.h",
"url_database.cc",
@@ -93,6 +97,7 @@ static_library("browser") {
"//components/keyed_service/core",
"//components/query_parser",
"//components/signin/core/browser",
+ "//components/sync_driver",
"//components/url_formatter",
"//google_apis",
"//net",
diff --git a/components/history/core/browser/history_model_worker.cc b/components/history/core/browser/history_model_worker.cc
new file mode 100644
index 0000000..76f4200
--- /dev/null
+++ b/components/history/core/browser/history_model_worker.cc
@@ -0,0 +1,131 @@
+// Copyright 2012 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 "components/history/core/browser/history_model_worker.h"
+
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+
+using base::WaitableEvent;
+
+namespace browser_sync {
+
+class WorkerTask : public history::HistoryDBTask {
+ public:
+ WorkerTask(
+ const syncer::WorkCallback& work,
+ WaitableEvent* done,
+ syncer::SyncerError* error)
+ : work_(work), done_(done), error_(error) {}
+
+ bool RunOnDBThread(history::HistoryBackend* backend,
+ history::HistoryDatabase* db) override {
+ *error_ = work_.Run();
+ done_->Signal();
+ return true;
+ }
+
+ // Since the DoWorkAndWaitUntilDone() is synchronous, we don't need to run
+ // any code asynchronously on the main thread after completion.
+ void DoneRunOnMainThread() override {}
+
+ protected:
+ ~WorkerTask() override {}
+
+ syncer::WorkCallback work_;
+ WaitableEvent* done_;
+ syncer::SyncerError* error_;
+};
+
+class AddDBThreadObserverTask : public history::HistoryDBTask {
+ public:
+ explicit AddDBThreadObserverTask(base::Closure register_callback)
+ : register_callback_(register_callback) {}
+
+ bool RunOnDBThread(history::HistoryBackend* backend,
+ history::HistoryDatabase* db) override {
+ register_callback_.Run();
+ return true;
+ }
+
+ void DoneRunOnMainThread() override {}
+
+ private:
+ ~AddDBThreadObserverTask() override {}
+
+ base::Closure register_callback_;
+};
+
+namespace {
+
+// Post the work task on |history_service|'s DB thread from the UI
+// thread.
+void PostWorkerTask(
+ const base::WeakPtr<history::HistoryService>& history_service,
+ const syncer::WorkCallback& work,
+ base::CancelableTaskTracker* cancelable_tracker,
+ WaitableEvent* done,
+ syncer::SyncerError* error) {
+ if (history_service.get()) {
+ scoped_ptr<history::HistoryDBTask> task(new WorkerTask(work, done, error));
+ history_service->ScheduleDBTask(task.Pass(), cancelable_tracker);
+ } else {
+ *error = syncer::CANNOT_DO_WORK;
+ done->Signal();
+ }
+}
+
+} // namespace
+
+HistoryModelWorker::HistoryModelWorker(
+ const base::WeakPtr<history::HistoryService>& history_service,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ui_thread,
+ syncer::WorkerLoopDestructionObserver* observer)
+ : syncer::ModelSafeWorker(observer),
+ history_service_(history_service),
+ ui_thread_(ui_thread) {
+ CHECK(history_service.get());
+ DCHECK(ui_thread_->BelongsToCurrentThread());
+ cancelable_tracker_.reset(new base::CancelableTaskTracker);
+}
+
+void HistoryModelWorker::RegisterForLoopDestruction() {
+ CHECK(history_service_.get());
+ history_service_->ScheduleDBTask(
+ scoped_ptr<history::HistoryDBTask>(new AddDBThreadObserverTask(
+ base::Bind(&HistoryModelWorker::RegisterOnDBThread, this))),
+ cancelable_tracker_.get());
+}
+
+void HistoryModelWorker::RegisterOnDBThread() {
+ SetWorkingLoopToCurrent();
+}
+
+syncer::SyncerError HistoryModelWorker::DoWorkAndWaitUntilDoneImpl(
+ const syncer::WorkCallback& work) {
+ syncer::SyncerError error = syncer::UNSET;
+ if (ui_thread_->PostTask(FROM_HERE,
+ base::Bind(&PostWorkerTask, history_service_, work,
+ cancelable_tracker_.get(),
+ work_done_or_stopped(), &error))) {
+ work_done_or_stopped()->Wait();
+ } else {
+ error = syncer::CANNOT_DO_WORK;
+ }
+ return error;
+}
+
+syncer::ModelSafeGroup HistoryModelWorker::GetModelSafeGroup() {
+ return syncer::GROUP_HISTORY;
+}
+
+HistoryModelWorker::~HistoryModelWorker() {
+ // The base::CancelableTaskTracker class is not thread-safe and must only be
+ // used from a single thread but the current object may not be destroyed from
+ // the UI thread, so delete it from the UI thread.
+ ui_thread_->DeleteSoon(FROM_HERE, cancelable_tracker_.release());
+}
+
+} // namespace browser_sync
diff --git a/components/history/core/browser/history_model_worker.h b/components/history/core/browser/history_model_worker.h
new file mode 100644
index 0000000..e16d756
--- /dev/null
+++ b/components/history/core/browser/history_model_worker.h
@@ -0,0 +1,62 @@
+// Copyright 2012 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 COMPONENTS_HISTORY_CORE_BROWSER_HISTORY_MODEL_WORKER_H_
+#define COMPONENTS_HISTORY_CORE_BROWSER_HISTORY_MODEL_WORKER_H_
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/task/cancelable_task_tracker.h"
+#include "components/history/core/browser/history_db_task.h"
+#include "components/history/core/browser/history_service.h"
+#include "sync/internal_api/public/engine/model_safe_worker.h"
+
+namespace history {
+class HistoryService;
+}
+
+namespace browser_sync {
+
+// A syncer::ModelSafeWorker for history models that accepts requests
+// from the syncapi that need to be fulfilled on the history thread.
+class HistoryModelWorker : public syncer::ModelSafeWorker {
+ public:
+ explicit HistoryModelWorker(
+ const base::WeakPtr<history::HistoryService>& history_service,
+ const scoped_refptr<base::SingleThreadTaskRunner>& ui_thread,
+ syncer::WorkerLoopDestructionObserver* observer);
+
+ // syncer::ModelSafeWorker implementation. Called on syncapi SyncerThread.
+ void RegisterForLoopDestruction() override;
+ syncer::ModelSafeGroup GetModelSafeGroup() override;
+
+ // Called on history DB thread to register HistoryModelWorker to observe
+ // destruction of history backend loop.
+ void RegisterOnDBThread();
+
+ protected:
+ syncer::SyncerError DoWorkAndWaitUntilDoneImpl(
+ const syncer::WorkCallback& work) override;
+
+ private:
+ ~HistoryModelWorker() override;
+
+ const base::WeakPtr<history::HistoryService> history_service_;
+
+ // A reference to the UI thread's task runner.
+ const scoped_refptr<base::SingleThreadTaskRunner> ui_thread_;
+
+ // Helper object to make sure we don't leave tasks running on the history
+ // thread.
+ scoped_ptr<base::CancelableTaskTracker> cancelable_tracker_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistoryModelWorker);
+};
+
+} // namespace browser_sync
+
+#endif // COMPONENTS_HISTORY_CORE_BROWSER_HISTORY_MODEL_WORKER_H_
diff --git a/components/history/core/browser/typed_url_model_associator.cc b/components/history/core/browser/typed_url_model_associator.cc
new file mode 100644
index 0000000..38be8e8
--- /dev/null
+++ b/components/history/core/browser/typed_url_model_associator.cc
@@ -0,0 +1,858 @@
+// Copyright 2012 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 "components/history/core/browser/typed_url_model_associator.h"
+
+#include <algorithm>
+#include <set>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/history/core/browser/history_backend.h"
+#include "components/sync_driver/sync_service.h"
+#include "net/base/net_util.h"
+#include "sync/api/sync_error.h"
+#include "sync/api/sync_merge_result.h"
+#include "sync/internal_api/public/read_node.h"
+#include "sync/internal_api/public/read_transaction.h"
+#include "sync/internal_api/public/write_node.h"
+#include "sync/internal_api/public/write_transaction.h"
+#include "sync/protocol/typed_url_specifics.pb.h"
+
+namespace browser_sync {
+
+// The server backend can't handle arbitrarily large node sizes, so to keep
+// the size under control we limit the visit array.
+static const int kMaxTypedUrlVisits = 100;
+
+// There's no limit on how many visits the history DB could have for a given
+// typed URL, so we limit how many we fetch from the DB to avoid crashes due to
+// running out of memory (http://crbug.com/89793). This value is different
+// from kMaxTypedUrlVisits, as some of the visits fetched from the DB may be
+// RELOAD visits, which will be stripped.
+static const int kMaxVisitsToFetch = 1000;
+
+static bool CheckVisitOrdering(const history::VisitVector& visits) {
+ int64 previous_visit_time = 0;
+ for (history::VisitVector::const_iterator visit = visits.begin();
+ visit != visits.end(); ++visit) {
+ if (visit != visits.begin()) {
+ // We allow duplicate visits here - they shouldn't really be allowed, but
+ // they still seem to show up sometimes and we haven't figured out the
+ // source, so we just log an error instead of failing an assertion.
+ // (http://crbug.com/91473).
+ if (previous_visit_time == visit->visit_time.ToInternalValue())
+ DVLOG(1) << "Duplicate visit time encountered";
+ else if (previous_visit_time > visit->visit_time.ToInternalValue())
+ return false;
+ }
+
+ previous_visit_time = visit->visit_time.ToInternalValue();
+ }
+ return true;
+}
+
+TypedUrlModelAssociator::TypedUrlModelAssociator(
+ sync_driver::SyncService* sync_service,
+ history::HistoryBackend* history_backend,
+ sync_driver::DataTypeErrorHandler* error_handler)
+ : sync_service_(sync_service),
+ history_backend_(history_backend),
+ expected_loop_(base::MessageLoop::current()),
+ abort_requested_(false),
+ error_handler_(error_handler),
+ num_db_accesses_(0),
+ num_db_errors_(0) {
+ DCHECK(sync_service_);
+ // history_backend_ may be null for unit tests (since it's not mockable).
+}
+
+TypedUrlModelAssociator::~TypedUrlModelAssociator() {}
+
+
+bool TypedUrlModelAssociator::FixupURLAndGetVisits(
+ history::URLRow* url,
+ history::VisitVector* visits) {
+ ++num_db_accesses_;
+ CHECK(history_backend_);
+ if (!history_backend_->GetMostRecentVisitsForURL(
+ url->id(), kMaxVisitsToFetch, visits)) {
+ ++num_db_errors_;
+ return false;
+ }
+
+ // Sometimes (due to a bug elsewhere in the history or sync code, or due to
+ // a crash between adding a URL to the history database and updating the
+ // visit DB) the visit vector for a URL can be empty. If this happens, just
+ // create a new visit whose timestamp is the same as the last_visit time.
+ // This is a workaround for http://crbug.com/84258.
+ if (visits->empty()) {
+ DVLOG(1) << "Found empty visits for URL: " << url->url();
+
+ if (url->last_visit().is_null()) {
+ // If modified URL is bookmarked, history backend treats it as modified
+ // even if all its visits are deleted. Return false to stop further
+ // processing because sync expects valid visit time for modified entry.
+ return false;
+ }
+
+ history::VisitRow visit(
+ url->id(), url->last_visit(), 0, ui::PAGE_TRANSITION_TYPED, 0);
+ visits->push_back(visit);
+ }
+
+ // GetMostRecentVisitsForURL() returns the data in the opposite order that
+ // we need it, so reverse it.
+ std::reverse(visits->begin(), visits->end());
+
+ // Sometimes, the last_visit field in the URL doesn't match the timestamp of
+ // the last visit in our visit array (they come from different tables, so
+ // crashes/bugs can cause them to mismatch), so just set it here.
+ url->set_last_visit(visits->back().visit_time);
+ DCHECK(CheckVisitOrdering(*visits));
+ return true;
+}
+
+bool TypedUrlModelAssociator::ShouldIgnoreUrl(const GURL& url) {
+ // Ignore empty URLs. Not sure how this can happen (maybe import from other
+ // busted browsers, or misuse of the history API, or just plain bugs) but we
+ // can't deal with them.
+ if (url.spec().empty())
+ return true;
+
+ // Ignore local file URLs.
+ if (url.SchemeIsFile())
+ return true;
+
+ // Ignore localhost URLs.
+ if (net::IsLocalhost(url.host()))
+ return true;
+
+ return false;
+}
+
+bool TypedUrlModelAssociator::ShouldIgnoreVisits(
+ const history::VisitVector& visits) {
+ // We ignore URLs that were imported, but have never been visited by
+ // chromium.
+ static const int kLastImportedSource = history::SOURCE_EXTENSION;
+ history::VisitSourceMap map;
+ if (!history_backend_->GetVisitsSource(visits, &map))
+ return false; // If we can't read the visit, assume it's not imported.
+
+ // Walk the list of visits and look for a non-imported item.
+ for (history::VisitVector::const_iterator it = visits.begin();
+ it != visits.end(); ++it) {
+ if (map.count(it->visit_id) == 0 ||
+ map[it->visit_id] <= kLastImportedSource) {
+ return false;
+ }
+ }
+ // We only saw imported visits, so tell the caller to ignore them.
+ return true;
+}
+
+syncer::SyncError TypedUrlModelAssociator::AssociateModels(
+ syncer::SyncMergeResult* local_merge_result,
+ syncer::SyncMergeResult* syncer_merge_result) {
+ ClearErrorStats();
+ syncer::SyncError error =
+ DoAssociateModels(local_merge_result, syncer_merge_result);
+ UMA_HISTOGRAM_PERCENTAGE("Sync.TypedUrlModelAssociationErrors",
+ GetErrorPercentage());
+ ClearErrorStats();
+ return error;
+}
+
+void TypedUrlModelAssociator::ClearErrorStats() {
+ num_db_accesses_ = 0;
+ num_db_errors_ = 0;
+}
+
+int TypedUrlModelAssociator::GetErrorPercentage() const {
+ return num_db_accesses_ ? (100 * num_db_errors_ / num_db_accesses_) : 0;
+}
+
+syncer::SyncError TypedUrlModelAssociator::DoAssociateModels(
+ syncer::SyncMergeResult* local_merge_result,
+ syncer::SyncMergeResult* syncer_merge_result) {
+ DVLOG(1) << "Associating TypedUrl Models";
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+
+ history::URLRows typed_urls;
+ ++num_db_accesses_;
+ bool query_succeeded =
+ history_backend_ && history_backend_->GetAllTypedURLs(&typed_urls);
+
+ history::URLRows new_urls;
+ history::URLRows updated_urls;
+ TypedUrlVisitVector new_visits;
+ {
+ base::AutoLock au(abort_lock_);
+ if (abort_requested_) {
+ return syncer::SyncError(FROM_HERE,
+ syncer::SyncError::DATATYPE_ERROR,
+ "Association was aborted.",
+ model_type());
+ }
+
+ // Must lock and check first to make sure |error_handler_| is valid.
+ if (!query_succeeded) {
+ ++num_db_errors_;
+ return error_handler_->CreateAndUploadError(
+ FROM_HERE,
+ "Could not get the typed_url entries.",
+ model_type());
+ }
+ local_merge_result->set_num_items_before_association(typed_urls.size());
+
+ // Get all the visits.
+ std::map<history::URLID, history::VisitVector> visit_vectors;
+ for (history::URLRows::iterator ix = typed_urls.begin();
+ ix != typed_urls.end();) {
+ DCHECK_EQ(0U, visit_vectors.count(ix->id()));
+ if (!FixupURLAndGetVisits(&(*ix), &(visit_vectors[ix->id()])) ||
+ ShouldIgnoreUrl(ix->url()) ||
+ ShouldIgnoreVisits(visit_vectors[ix->id()])) {
+ // Ignore this URL if we couldn't load the visits or if there's some
+ // other problem with it (it was empty, or imported and never visited).
+ ix = typed_urls.erase(ix);
+ } else {
+ ++ix;
+ }
+ }
+
+ syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
+ syncer::ReadNode typed_url_root(&trans);
+ if (typed_url_root.InitTypeRoot(syncer::TYPED_URLS) !=
+ syncer::BaseNode::INIT_OK) {
+ return error_handler_->CreateAndUploadError(
+ FROM_HERE,
+ "Server did not create the top-level typed_url node. We "
+ "might be running against an out-of-date server.",
+ model_type());
+ }
+ syncer_merge_result->set_num_items_before_association(
+ typed_url_root.GetTotalNodeCount());
+
+ std::set<std::string> current_urls;
+ for (history::URLRows::iterator ix = typed_urls.begin();
+ ix != typed_urls.end(); ++ix) {
+ std::string tag = ix->url().spec();
+ // Empty URLs should be filtered out by ShouldIgnoreUrl() previously.
+ DCHECK(!tag.empty());
+ history::VisitVector& visits = visit_vectors[ix->id()];
+
+ syncer::ReadNode node(&trans);
+ if (node.InitByClientTagLookup(syncer::TYPED_URLS, tag) ==
+ syncer::BaseNode::INIT_OK) {
+ // Same URL exists in sync data and in history data - compare the
+ // entries to see if there's any difference.
+ sync_pb::TypedUrlSpecifics typed_url(
+ FilterExpiredVisits(node.GetTypedUrlSpecifics()));
+ DCHECK_EQ(tag, typed_url.url());
+
+ // Initialize fields in |new_url| to the same values as the fields in
+ // the existing URLRow in the history DB. This is needed because we
+ // overwrite the existing value below in WriteToHistoryBackend(), but
+ // some of the values in that structure are not synced (like
+ // typed_count).
+ history::URLRow new_url(*ix);
+
+ std::vector<history::VisitInfo> added_visits;
+ MergeResult difference =
+ MergeUrls(typed_url, *ix, &visits, &new_url, &added_visits);
+ if (difference & DIFF_UPDATE_NODE) {
+ syncer::WriteNode write_node(&trans);
+ if (write_node.InitByClientTagLookup(syncer::TYPED_URLS, tag) !=
+ syncer::BaseNode::INIT_OK) {
+ return error_handler_->CreateAndUploadError(
+ FROM_HERE,
+ "Failed to edit typed_url sync node.",
+ model_type());
+ }
+ // We don't want to resurrect old visits that have been aged out by
+ // other clients, so remove all visits that are older than the
+ // earliest existing visit in the sync node.
+ if (typed_url.visits_size() > 0) {
+ base::Time earliest_visit =
+ base::Time::FromInternalValue(typed_url.visits(0));
+ for (history::VisitVector::iterator it = visits.begin();
+ it != visits.end() && it->visit_time < earliest_visit; ) {
+ it = visits.erase(it);
+ }
+ // Should never be possible to delete all the items, since the
+ // visit vector contains all the items in typed_url.visits.
+ DCHECK_GT(visits.size(), 0u);
+ }
+ DCHECK_EQ(new_url.last_visit().ToInternalValue(),
+ visits.back().visit_time.ToInternalValue());
+ WriteToSyncNode(new_url, visits, &write_node);
+ syncer_merge_result->set_num_items_modified(
+ syncer_merge_result->num_items_modified() + 1);
+ }
+ if (difference & DIFF_LOCAL_ROW_CHANGED) {
+ DCHECK_EQ(ix->id(), new_url.id());
+ updated_urls.push_back(new_url);
+ }
+ if (difference & DIFF_LOCAL_VISITS_ADDED) {
+ new_visits.push_back(
+ std::pair<GURL, std::vector<history::VisitInfo> >(ix->url(),
+ added_visits));
+ }
+ } else {
+ // Sync has never seen this URL before.
+ syncer::WriteNode node(&trans);
+ syncer::WriteNode::InitUniqueByCreationResult result =
+ node.InitUniqueByCreation(syncer::TYPED_URLS, tag);
+ if (result != syncer::WriteNode::INIT_SUCCESS) {
+ return error_handler_->CreateAndUploadError(
+ FROM_HERE,
+ "Failed to create typed_url sync node: " + tag,
+ model_type());
+ }
+
+ node.SetTitle(tag);
+ WriteToSyncNode(*ix, visits, &node);
+ syncer_merge_result->set_num_items_added(
+ syncer_merge_result->num_items_added() + 1);
+ }
+
+ current_urls.insert(tag);
+ }
+
+ // Now walk the sync nodes and detect any URLs that exist there, but not in
+ // the history DB, so we can add them to our local history DB.
+ std::vector<int64> obsolete_nodes;
+ std::vector<int64> sync_ids;
+ typed_url_root.GetChildIds(&sync_ids);
+
+ for (std::vector<int64>::const_iterator it = sync_ids.begin();
+ it != sync_ids.end(); ++it) {
+ syncer::ReadNode sync_child_node(&trans);
+ if (sync_child_node.InitByIdLookup(*it) != syncer::BaseNode::INIT_OK) {
+ return error_handler_->CreateAndUploadError(
+ FROM_HERE,
+ "Failed to fetch child node.",
+ model_type());
+ }
+ const sync_pb::TypedUrlSpecifics& typed_url(
+ sync_child_node.GetTypedUrlSpecifics());
+
+ // Ignore old sync nodes that don't have any transition data stored with
+ // them, or transition data that does not match the visit data (will be
+ // deleted below).
+ if (typed_url.visit_transitions_size() == 0 ||
+ typed_url.visit_transitions_size() != typed_url.visits_size()) {
+ // Generate a debug assertion to help track down http://crbug.com/91473,
+ // even though we gracefully handle this case by throwing away this
+ // node.
+ DCHECK_EQ(typed_url.visits_size(), typed_url.visit_transitions_size());
+ DVLOG(1) << "Deleting obsolete sync node with no visit "
+ << "transition info.";
+ obsolete_nodes.push_back(sync_child_node.GetId());
+ continue;
+ }
+
+ if (typed_url.url().empty()) {
+ DVLOG(1) << "Ignoring empty URL in sync DB";
+ continue;
+ }
+
+ // Now, get rid of the expired visits, and if there are no un-expired
+ // visits left, just ignore this node.
+ sync_pb::TypedUrlSpecifics filtered_url = FilterExpiredVisits(typed_url);
+ if (filtered_url.visits_size() == 0) {
+ DVLOG(1) << "Ignoring expired URL in sync DB: " << filtered_url.url();
+ continue;
+ }
+
+ if (current_urls.find(filtered_url.url()) == current_urls.end()) {
+ // Update the local DB from the sync DB. Since we are doing our
+ // initial model association, we don't want to remove any of the
+ // existing visits (pass NULL as |visits_to_remove|).
+ UpdateFromSyncDB(filtered_url,
+ &new_visits,
+ NULL,
+ &updated_urls,
+ &new_urls);
+ local_merge_result->set_num_items_added(
+ local_merge_result->num_items_added() + 1);
+ }
+ }
+
+ // If we encountered any obsolete nodes, remove them so they don't hang
+ // around and confuse people looking at the sync node browser.
+ if (!obsolete_nodes.empty()) {
+ for (std::vector<int64>::const_iterator it = obsolete_nodes.begin();
+ it != obsolete_nodes.end();
+ ++it) {
+ syncer::WriteNode sync_node(&trans);
+ if (sync_node.InitByIdLookup(*it) != syncer::BaseNode::INIT_OK) {
+ return error_handler_->CreateAndUploadError(
+ FROM_HERE,
+ "Failed to fetch obsolete node.",
+ model_type());
+ }
+ sync_node.Tombstone();
+ }
+ }
+ syncer_merge_result->set_num_items_after_association(
+ typed_url_root.GetTotalNodeCount());
+ }
+
+ // Since we're on the history thread, we don't have to worry about updating
+ // the history database after closing the write transaction, since
+ // this is the only thread that writes to the database. We also don't have
+ // to worry about the sync model getting out of sync, because changes are
+ // propagated to the ChangeProcessor on this thread.
+ WriteToHistoryBackend(&new_urls, &updated_urls, &new_visits, NULL);
+ local_merge_result->set_num_items_modified(updated_urls.size());
+ local_merge_result->set_num_items_after_association(
+ local_merge_result->num_items_before_association() +
+ local_merge_result->num_items_added());
+ return syncer::SyncError();
+}
+
+void TypedUrlModelAssociator::UpdateFromSyncDB(
+ const sync_pb::TypedUrlSpecifics& typed_url,
+ TypedUrlVisitVector* visits_to_add,
+ history::VisitVector* visits_to_remove,
+ history::URLRows* updated_urls,
+ history::URLRows* new_urls) {
+ history::URLRow new_url(GURL(typed_url.url()));
+ history::VisitVector existing_visits;
+ bool existing_url = history_backend_->GetURL(new_url.url(), &new_url);
+ if (existing_url) {
+ // This URL already exists locally - fetch the visits so we can
+ // merge them below.
+ if (!FixupURLAndGetVisits(&new_url, &existing_visits)) {
+ // Couldn't load the visits for this URL due to some kind of DB error.
+ // Don't bother writing this URL to the history DB (if we ignore the
+ // error and continue, we might end up duplicating existing visits).
+ DLOG(ERROR) << "Could not load visits for url: " << new_url.url();
+ return;
+ }
+ }
+ visits_to_add->push_back(std::pair<GURL, std::vector<history::VisitInfo> >(
+ new_url.url(), std::vector<history::VisitInfo>()));
+
+ // Update the URL with information from the typed URL.
+ UpdateURLRowFromTypedUrlSpecifics(typed_url, &new_url);
+
+ // Figure out which visits we need to add.
+ DiffVisits(existing_visits, typed_url, &visits_to_add->back().second,
+ visits_to_remove);
+
+ if (existing_url) {
+ updated_urls->push_back(new_url);
+ } else {
+ new_urls->push_back(new_url);
+ }
+}
+
+sync_pb::TypedUrlSpecifics TypedUrlModelAssociator::FilterExpiredVisits(
+ const sync_pb::TypedUrlSpecifics& source) {
+ // Make a copy of the source, then regenerate the visits.
+ sync_pb::TypedUrlSpecifics specifics(source);
+ specifics.clear_visits();
+ specifics.clear_visit_transitions();
+ for (int i = 0; i < source.visits_size(); ++i) {
+ base::Time time = base::Time::FromInternalValue(source.visits(i));
+ if (!history_backend_->IsExpiredVisitTime(time)) {
+ specifics.add_visits(source.visits(i));
+ specifics.add_visit_transitions(source.visit_transitions(i));
+ }
+ }
+ DCHECK(specifics.visits_size() == specifics.visit_transitions_size());
+ return specifics;
+}
+
+bool TypedUrlModelAssociator::DeleteAllNodes(
+ syncer::WriteTransaction* trans) {
+ DCHECK(expected_loop_ == base::MessageLoop::current());
+
+ // Just walk through all our child nodes and delete them.
+ syncer::ReadNode typed_url_root(trans);
+ if (typed_url_root.InitTypeRoot(syncer::TYPED_URLS) !=
+ syncer::BaseNode::INIT_OK) {
+ LOG(ERROR) << "Could not lookup root node";
+ return false;
+ }
+
+ std::vector<int64> sync_ids;
+ typed_url_root.GetChildIds(&sync_ids);
+
+ for (std::vector<int64>::const_iterator it = sync_ids.begin();
+ it != sync_ids.end(); ++it) {
+ syncer::WriteNode sync_child_node(trans);
+ if (sync_child_node.InitByIdLookup(*it) != syncer::BaseNode::INIT_OK) {
+ LOG(ERROR) << "Typed url node lookup failed.";
+ return false;
+ }
+ sync_child_node.Tombstone();
+ }
+ return true;
+}
+
+syncer::SyncError TypedUrlModelAssociator::DisassociateModels() {
+ return syncer::SyncError();
+}
+
+void TypedUrlModelAssociator::AbortAssociation() {
+ base::AutoLock lock(abort_lock_);
+ abort_requested_ = true;
+}
+
+bool TypedUrlModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
+ DCHECK(has_nodes);
+ *has_nodes = false;
+ syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
+ syncer::ReadNode sync_node(&trans);
+ if (sync_node.InitTypeRoot(syncer::TYPED_URLS) != syncer::BaseNode::INIT_OK) {
+ 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_node.HasChildren();
+ return true;
+}
+
+void TypedUrlModelAssociator::WriteToHistoryBackend(
+ const history::URLRows* new_urls,
+ const history::URLRows* updated_urls,
+ const TypedUrlVisitVector* new_visits,
+ const history::VisitVector* deleted_visits) {
+ if (new_urls) {
+ history_backend_->AddPagesWithDetails(*new_urls, history::SOURCE_SYNCED);
+ }
+ if (updated_urls) {
+ ++num_db_accesses_;
+ // These are existing entries in the URL database. We don't verify the
+ // visit_count or typed_count values here, because either one (or both)
+ // could be zero in the case of bookmarks, or in the case of a URL
+ // transitioning from non-typed to typed as a result of this sync.
+ // In the field we sometimes run into errors on specific URLs. It's OK to
+ // just continue, as we can try writing again on the next model association.
+ size_t num_successful_updates = history_backend_->UpdateURLs(*updated_urls);
+ num_db_errors_ += updated_urls->size() - num_successful_updates;
+ }
+ if (new_visits) {
+ for (TypedUrlVisitVector::const_iterator visits = new_visits->begin();
+ visits != new_visits->end(); ++visits) {
+ // If there are no visits to add, just skip this.
+ if (visits->second.empty())
+ continue;
+ ++num_db_accesses_;
+ if (!history_backend_->AddVisits(visits->first, visits->second,
+ history::SOURCE_SYNCED)) {
+ ++num_db_errors_;
+ DLOG(ERROR) << "Could not add visits.";
+ }
+ }
+ }
+ if (deleted_visits) {
+ ++num_db_accesses_;
+ if (!history_backend_->RemoveVisits(*deleted_visits)) {
+ ++num_db_errors_;
+ DLOG(ERROR) << "Could not remove visits.";
+ // This is bad news, since it means we may end up resurrecting history
+ // entries on the next reload. It's unavoidable so we'll just keep on
+ // syncing.
+ }
+ }
+}
+
+// static
+TypedUrlModelAssociator::MergeResult TypedUrlModelAssociator::MergeUrls(
+ const sync_pb::TypedUrlSpecifics& node,
+ const history::URLRow& url,
+ history::VisitVector* visits,
+ history::URLRow* new_url,
+ std::vector<history::VisitInfo>* new_visits) {
+ DCHECK(new_url);
+ DCHECK(!node.url().compare(url.url().spec()));
+ DCHECK(!node.url().compare(new_url->url().spec()));
+ DCHECK(visits->size());
+ CHECK_EQ(node.visits_size(), node.visit_transitions_size());
+
+ // If we have an old-format node (before we added the visits and
+ // visit_transitions arrays to the protobuf) or else the node only contained
+ // expired visits, so just overwrite it with our local history data.
+ if (node.visits_size() == 0)
+ return DIFF_UPDATE_NODE;
+
+ // Convert these values only once.
+ base::string16 node_title(base::UTF8ToUTF16(node.title()));
+ base::Time node_last_visit = base::Time::FromInternalValue(
+ node.visits(node.visits_size() - 1));
+
+ // This is a bitfield representing what we'll need to update with the output
+ // value.
+ MergeResult different = DIFF_NONE;
+
+ // Check if the non-incremented values changed.
+ if ((node_title.compare(url.title()) != 0) ||
+ (node.hidden() != url.hidden())) {
+ // Use the values from the most recent visit.
+ if (node_last_visit >= url.last_visit()) {
+ new_url->set_title(node_title);
+ new_url->set_hidden(node.hidden());
+ different |= DIFF_LOCAL_ROW_CHANGED;
+ } else {
+ new_url->set_title(url.title());
+ new_url->set_hidden(url.hidden());
+ different |= DIFF_UPDATE_NODE;
+ }
+ } else {
+ // No difference.
+ new_url->set_title(url.title());
+ new_url->set_hidden(url.hidden());
+ }
+
+ size_t node_num_visits = node.visits_size();
+ size_t history_num_visits = visits->size();
+ size_t node_visit_index = 0;
+ size_t history_visit_index = 0;
+ base::Time earliest_history_time = (*visits)[0].visit_time;
+ // Walk through the two sets of visits and figure out if any new visits were
+ // added on either side.
+ while (node_visit_index < node_num_visits ||
+ history_visit_index < history_num_visits) {
+ // Time objects are initialized to "earliest possible time".
+ base::Time node_time, history_time;
+ if (node_visit_index < node_num_visits)
+ node_time = base::Time::FromInternalValue(node.visits(node_visit_index));
+ if (history_visit_index < history_num_visits)
+ history_time = (*visits)[history_visit_index].visit_time;
+ if (node_visit_index >= node_num_visits ||
+ (history_visit_index < history_num_visits &&
+ node_time > history_time)) {
+ // We found a visit in the history DB that doesn't exist in the sync DB,
+ // so mark the node as modified so the caller will update the sync node.
+ different |= DIFF_UPDATE_NODE;
+ ++history_visit_index;
+ } else if (history_visit_index >= history_num_visits ||
+ node_time < history_time) {
+ // Found a visit in the sync node that doesn't exist in the history DB, so
+ // add it to our list of new visits and set the appropriate flag so the
+ // caller will update the history DB.
+ // If the node visit is older than any existing visit in the history DB,
+ // don't re-add it - this keeps us from resurrecting visits that were
+ // aged out locally.
+ if (node_time > earliest_history_time) {
+ different |= DIFF_LOCAL_VISITS_ADDED;
+ new_visits->push_back(history::VisitInfo(
+ node_time,
+ ui::PageTransitionFromInt(
+ node.visit_transitions(node_visit_index))));
+ }
+ // This visit is added to visits below.
+ ++node_visit_index;
+ } else {
+ // Same (already synced) entry found in both DBs - no need to do anything.
+ ++node_visit_index;
+ ++history_visit_index;
+ }
+ }
+
+ DCHECK(CheckVisitOrdering(*visits));
+ if (different & DIFF_LOCAL_VISITS_ADDED) {
+ // Insert new visits into the apropriate place in the visits vector.
+ history::VisitVector::iterator visit_ix = visits->begin();
+ for (std::vector<history::VisitInfo>::iterator new_visit =
+ new_visits->begin();
+ new_visit != new_visits->end(); ++new_visit) {
+ while (visit_ix != visits->end() &&
+ new_visit->first > visit_ix->visit_time) {
+ ++visit_ix;
+ }
+ visit_ix = visits->insert(visit_ix,
+ history::VisitRow(url.id(), new_visit->first,
+ 0, new_visit->second, 0));
+ ++visit_ix;
+ }
+ }
+ DCHECK(CheckVisitOrdering(*visits));
+
+ new_url->set_last_visit(visits->back().visit_time);
+ return different;
+}
+
+// static
+void TypedUrlModelAssociator::WriteToSyncNode(
+ const history::URLRow& url,
+ const history::VisitVector& visits,
+ syncer::WriteNode* node) {
+ sync_pb::TypedUrlSpecifics typed_url;
+ WriteToTypedUrlSpecifics(url, visits, &typed_url);
+ node->SetTypedUrlSpecifics(typed_url);
+}
+
+void TypedUrlModelAssociator::WriteToTypedUrlSpecifics(
+ const history::URLRow& url,
+ const history::VisitVector& visits,
+ sync_pb::TypedUrlSpecifics* typed_url) {
+
+ DCHECK(!url.last_visit().is_null());
+ DCHECK(!visits.empty());
+ DCHECK_EQ(url.last_visit().ToInternalValue(),
+ visits.back().visit_time.ToInternalValue());
+
+ typed_url->set_url(url.url().spec());
+ typed_url->set_title(base::UTF16ToUTF8(url.title()));
+ typed_url->set_hidden(url.hidden());
+
+ DCHECK(CheckVisitOrdering(visits));
+
+ bool only_typed = false;
+ int skip_count = 0;
+
+ if (visits.size() > static_cast<size_t>(kMaxTypedUrlVisits)) {
+ int typed_count = 0;
+ int total = 0;
+ // Walk the passed-in visit vector and count the # of typed visits.
+ for (history::VisitVector::const_iterator visit = visits.begin();
+ visit != visits.end(); ++visit) {
+ ui::PageTransition transition =
+ ui::PageTransitionStripQualifier(visit->transition);
+ // We ignore reload visits.
+ if (transition == ui::PAGE_TRANSITION_RELOAD)
+ continue;
+ ++total;
+ if (transition == ui::PAGE_TRANSITION_TYPED)
+ ++typed_count;
+ }
+ // We should have at least one typed visit. This can sometimes happen if
+ // the history DB has an inaccurate count for some reason (there's been
+ // bugs in the history code in the past which has left users in the wild
+ // with incorrect counts - http://crbug.com/84258).
+ DCHECK_GT(typed_count, 0);
+
+ if (typed_count > kMaxTypedUrlVisits) {
+ only_typed = true;
+ skip_count = typed_count - kMaxTypedUrlVisits;
+ } else if (total > kMaxTypedUrlVisits) {
+ skip_count = total - kMaxTypedUrlVisits;
+ }
+ }
+
+
+ for (history::VisitVector::const_iterator visit = visits.begin();
+ visit != visits.end(); ++visit) {
+ ui::PageTransition transition =
+ ui::PageTransitionStripQualifier(visit->transition);
+ // Skip reload visits.
+ if (transition == ui::PAGE_TRANSITION_RELOAD)
+ continue;
+
+ // If we only have room for typed visits, then only add typed visits.
+ if (only_typed && transition != ui::PAGE_TRANSITION_TYPED)
+ continue;
+
+ if (skip_count > 0) {
+ // We have too many entries to fit, so we need to skip the oldest ones.
+ // Only skip typed URLs if there are too many typed URLs to fit.
+ if (only_typed || transition != ui::PAGE_TRANSITION_TYPED) {
+ --skip_count;
+ continue;
+ }
+ }
+ typed_url->add_visits(visit->visit_time.ToInternalValue());
+ typed_url->add_visit_transitions(visit->transition);
+ }
+ DCHECK_EQ(skip_count, 0);
+
+ if (typed_url->visits_size() == 0) {
+ // If we get here, it's because we don't actually have any TYPED visits
+ // even though the visit's typed_count > 0 (corrupted typed_count). So
+ // let's go ahead and add a RELOAD visit at the most recent visit since
+ // it's not legal to have an empty visit array (yet another workaround
+ // for http://crbug.com/84258).
+ typed_url->add_visits(url.last_visit().ToInternalValue());
+ typed_url->add_visit_transitions(ui::PAGE_TRANSITION_RELOAD);
+ }
+ CHECK_GT(typed_url->visits_size(), 0);
+ CHECK_LE(typed_url->visits_size(), kMaxTypedUrlVisits);
+ CHECK_EQ(typed_url->visits_size(), typed_url->visit_transitions_size());
+}
+
+// static
+void TypedUrlModelAssociator::DiffVisits(
+ const history::VisitVector& old_visits,
+ const sync_pb::TypedUrlSpecifics& new_url,
+ std::vector<history::VisitInfo>* new_visits,
+ history::VisitVector* removed_visits) {
+ DCHECK(new_visits);
+ size_t old_visit_count = old_visits.size();
+ size_t new_visit_count = new_url.visits_size();
+ size_t old_index = 0;
+ size_t new_index = 0;
+ while (old_index < old_visit_count && new_index < new_visit_count) {
+ base::Time new_visit_time =
+ base::Time::FromInternalValue(new_url.visits(new_index));
+ if (old_visits[old_index].visit_time < new_visit_time) {
+ if (new_index > 0 && removed_visits) {
+ // If there are visits missing from the start of the node, that
+ // means that they were probably clipped off due to our code that
+ // limits the size of the sync nodes - don't delete them from our
+ // local history.
+ removed_visits->push_back(old_visits[old_index]);
+ }
+ ++old_index;
+ } else if (old_visits[old_index].visit_time > new_visit_time) {
+ new_visits->push_back(history::VisitInfo(
+ new_visit_time,
+ ui::PageTransitionFromInt(
+ new_url.visit_transitions(new_index))));
+ ++new_index;
+ } else {
+ ++old_index;
+ ++new_index;
+ }
+ }
+
+ if (removed_visits) {
+ for ( ; old_index < old_visit_count; ++old_index) {
+ removed_visits->push_back(old_visits[old_index]);
+ }
+ }
+
+ for ( ; new_index < new_visit_count; ++new_index) {
+ new_visits->push_back(history::VisitInfo(
+ base::Time::FromInternalValue(new_url.visits(new_index)),
+ ui::PageTransitionFromInt(new_url.visit_transitions(new_index))));
+ }
+}
+
+
+// static
+void TypedUrlModelAssociator::UpdateURLRowFromTypedUrlSpecifics(
+ const sync_pb::TypedUrlSpecifics& typed_url, history::URLRow* new_url) {
+ DCHECK_GT(typed_url.visits_size(), 0);
+ CHECK_EQ(typed_url.visit_transitions_size(), typed_url.visits_size());
+ new_url->set_title(base::UTF8ToUTF16(typed_url.title()));
+ new_url->set_hidden(typed_url.hidden());
+ // Only provide the initial value for the last_visit field - after that, let
+ // the history code update the last_visit field on its own.
+ if (new_url->last_visit().is_null()) {
+ new_url->set_last_visit(base::Time::FromInternalValue(
+ typed_url.visits(typed_url.visits_size() - 1)));
+ }
+}
+
+bool TypedUrlModelAssociator::CryptoReadyIfNecessary() {
+ // We only access the cryptographer while holding a transaction.
+ syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
+ const syncer::ModelTypeSet encrypted_types = trans.GetEncryptedTypes();
+ return !encrypted_types.Has(syncer::TYPED_URLS) ||
+ sync_service_->IsCryptographerReady(&trans);
+}
+
+} // namespace browser_sync
diff --git a/components/history/core/browser/typed_url_model_associator.h b/components/history/core/browser/typed_url_model_associator.h
new file mode 100644
index 0000000..35db2eb
--- /dev/null
+++ b/components/history/core/browser/typed_url_model_associator.h
@@ -0,0 +1,208 @@
+// Copyright 2012 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 COMPONENTS_HISTORY_CORE_BROWSER_TYPED_URL_MODEL_ASSOCIATOR_H_
+#define COMPONENTS_HISTORY_CORE_BROWSER_TYPED_URL_MODEL_ASSOCIATOR_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/strings/string16.h"
+#include "components/history/core/browser/history_types.h"
+#include "components/sync_driver/data_type_error_handler.h"
+#include "components/sync_driver/model_associator.h"
+#include "sync/protocol/typed_url_specifics.pb.h"
+
+class GURL;
+
+namespace base {
+class MessageLoop;
+}
+
+namespace history {
+class HistoryBackend;
+class URLRow;
+};
+
+namespace syncer {
+class WriteNode;
+class WriteTransaction;
+};
+
+namespace sync_driver {
+class SyncService;
+}
+
+namespace browser_sync {
+
+// 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 sync_driver::AssociatorInterface {
+ public:
+ typedef std::vector<std::pair<GURL, std::vector<history::VisitInfo> > >
+ TypedUrlVisitVector;
+
+ static syncer::ModelType model_type() { return syncer::TYPED_URLS; }
+ TypedUrlModelAssociator(sync_driver::SyncService* sync_service,
+ history::HistoryBackend* history_backend,
+ sync_driver::DataTypeErrorHandler* error_handler);
+ ~TypedUrlModelAssociator() override;
+
+ // AssociatorInterface implementation.
+ //
+ // Iterates through the sync model looking for matched pairs of items.
+ syncer::SyncError AssociateModels(
+ syncer::SyncMergeResult* local_merge_result,
+ syncer::SyncMergeResult* syncer_merge_result) override;
+
+ // Clears all associations.
+ syncer::SyncError DisassociateModels() override;
+
+ // Called from the main thread, to abort the currently active model
+ // association (for example, if we are shutting down).
+ void AbortAssociation() override;
+
+ // The has_nodes out param is true if the sync model has nodes other
+ // than the permanent tagged nodes.
+ bool SyncModelHasUserCreatedNodes(bool* has_nodes) override;
+
+ bool CryptoReadyIfNecessary() override;
+
+ // Delete all typed url nodes.
+ bool DeleteAllNodes(syncer::WriteTransaction* trans);
+
+ void WriteToHistoryBackend(const history::URLRows* new_urls,
+ const history::URLRows* updated_urls,
+ const TypedUrlVisitVector* new_visits,
+ const history::VisitVector* deleted_visits);
+
+ // Given a typed URL in the sync DB, looks for an existing entry in the
+ // local history DB and generates a list of visits to add to the
+ // history DB to bring it up to date (avoiding duplicates).
+ // Updates the passed |visits_to_add| and |visits_to_remove| vectors with the
+ // visits to add to/remove from the history DB, and adds a new entry to either
+ // |updated_urls| or |new_urls| depending on whether the URL already existed
+ // in the history DB.
+ void UpdateFromSyncDB(const sync_pb::TypedUrlSpecifics& typed_url,
+ TypedUrlVisitVector* visits_to_add,
+ history::VisitVector* visits_to_remove,
+ history::URLRows* updated_urls,
+ history::URLRows* new_urls);
+
+ // Given a TypedUrlSpecifics object, removes all visits that are older than
+ // the current expiration time. Note that this can result in having no visits
+ // at all.
+ sync_pb::TypedUrlSpecifics FilterExpiredVisits(
+ const sync_pb::TypedUrlSpecifics& specifics);
+
+ // Returns the percentage of DB accesses that have resulted in an error.
+ int GetErrorPercentage() const;
+
+ // Bitfield returned from MergeUrls to specify the result of the merge.
+ typedef uint32 MergeResult;
+ static const MergeResult DIFF_NONE = 0;
+ static const MergeResult DIFF_UPDATE_NODE = 1 << 0;
+ static const MergeResult DIFF_LOCAL_ROW_CHANGED = 1 << 1;
+ static const MergeResult DIFF_LOCAL_VISITS_ADDED = 1 << 2;
+
+ // Merges the URL information in |typed_url| with the URL information from the
+ // history database in |url| and |visits|, and returns a bitmask with the
+ // results of the merge:
+ // DIFF_UPDATE_NODE - changes have been made to |new_url| and |visits| which
+ // should be persisted to the sync node.
+ // DIFF_LOCAL_ROW_CHANGED - The history data in |new_url| should be persisted
+ // to the history DB.
+ // DIFF_LOCAL_VISITS_ADDED - |new_visits| contains a list of visits that
+ // should be written to the history DB for this URL. Deletions are not
+ // written to the DB - each client is left to age out visits on their own.
+ static MergeResult MergeUrls(const sync_pb::TypedUrlSpecifics& typed_url,
+ const history::URLRow& url,
+ history::VisitVector* visits,
+ history::URLRow* new_url,
+ std::vector<history::VisitInfo>* new_visits);
+ static void WriteToSyncNode(const history::URLRow& url,
+ const history::VisitVector& visits,
+ syncer::WriteNode* node);
+
+ // Diffs the set of visits between the history DB and the sync DB, using the
+ // sync DB as the canonical copy. Result is the set of |new_visits| and
+ // |removed_visits| that can be applied to the history DB to make it match
+ // the sync DB version. |removed_visits| can be null if the caller does not
+ // care about which visits to remove.
+ static void DiffVisits(const history::VisitVector& old_visits,
+ const sync_pb::TypedUrlSpecifics& new_url,
+ std::vector<history::VisitInfo>* new_visits,
+ history::VisitVector* removed_visits);
+
+ // Converts the passed URL information to a TypedUrlSpecifics structure for
+ // writing to the sync DB
+ static void WriteToTypedUrlSpecifics(const history::URLRow& url,
+ const history::VisitVector& visits,
+ sync_pb::TypedUrlSpecifics* specifics);
+
+ // Fetches visits from the history DB corresponding to the passed URL. This
+ // function compensates for the fact that the history DB has rather poor data
+ // integrity (duplicate visits, visit timestamps that don't match the
+ // last_visit timestamp, huge data sets that exhaust memory when fetched,
+ // etc) by modifying the passed |url| object and |visits| vector.
+ // Returns false if we could not fetch the visits for the passed URL, and
+ // tracks DB error statistics internally for reporting via UMA.
+ bool FixupURLAndGetVisits(history::URLRow* url,
+ history::VisitVector* visits);
+
+ // Updates the passed |url_row| based on the values in |specifics|. Fields
+ // that are not contained in |specifics| (such as typed_count) are left
+ // unchanged.
+ static void UpdateURLRowFromTypedUrlSpecifics(
+ const sync_pb::TypedUrlSpecifics& specifics, history::URLRow* url_row);
+
+ // Helper function that determines if we should ignore a URL for the purposes
+ // of sync, because it contains invalid data.
+ bool ShouldIgnoreUrl(const GURL& url);
+
+ protected:
+ // Helper function that clears our error counters (used to reset stats after
+ // model association so we can track model association errors separately).
+ // Overridden by tests.
+ virtual void ClearErrorStats();
+
+ private:
+ // Helper routine that actually does the work of associating models.
+ syncer::SyncError DoAssociateModels(
+ syncer::SyncMergeResult* local_merge_result,
+ syncer::SyncMergeResult* syncer_merge_result);
+
+ // Helper function that determines if we should ignore a URL for the purposes
+ // of sync, based on the visits the URL had.
+ bool ShouldIgnoreVisits(const history::VisitVector& visits);
+
+ sync_driver::SyncService* sync_service_;
+ history::HistoryBackend* history_backend_;
+
+ base::MessageLoop* expected_loop_;
+
+ bool abort_requested_;
+ base::Lock abort_lock_;
+
+ // Guaranteed to outlive datatypes.
+ sync_driver::DataTypeErrorHandler* error_handler_;
+
+ // Statistics for the purposes of tracking the percentage of DB accesses that
+ // fail for each client via UMA.
+ int num_db_accesses_;
+ int num_db_errors_;
+
+ DISALLOW_COPY_AND_ASSIGN(TypedUrlModelAssociator);
+};
+
+} // namespace browser_sync
+
+#endif // COMPONENTS_HISTORY_CORE_BROWSER_TYPED_URL_MODEL_ASSOCIATOR_H_