| Index: components/history/core/browser/typed_url_sync_bridge.cc
|
| diff --git a/components/history/core/browser/typed_url_sync_bridge.cc b/components/history/core/browser/typed_url_sync_bridge.cc
|
| index 40d57d67272767f1a37f1945c92bd6d3f478bab5..48edb4e6f63df5bfa7dc2377d31d3e10be497f39 100644
|
| --- a/components/history/core/browser/typed_url_sync_bridge.cc
|
| +++ b/components/history/core/browser/typed_url_sync_bridge.cc
|
| @@ -4,22 +4,67 @@
|
|
|
| #include "components/history/core/browser/typed_url_sync_bridge.h"
|
|
|
| +#include "base/big_endian.h"
|
| #include "base/memory/ptr_util.h"
|
| +#include "base/strings/utf_string_conversions.h"
|
| +#include "components/history/core/browser/history_backend.h"
|
| +#include "components/sync/model/mutable_data_batch.h"
|
| #include "components/sync/model_impl/sync_metadata_store_change_list.h"
|
|
|
| +using syncer::EntityData;
|
| +using sync_pb::TypedUrlSpecifics;
|
| +using syncer::MutableDataBatch;
|
| +
|
| namespace history {
|
|
|
| +namespace {
|
| +
|
| +// 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;
|
| +
|
| +// Enforce oldest to newest visit order.
|
| +static bool CheckVisitOrdering(const VisitVector& visits) {
|
| + int64_t previous_visit_time = 0;
|
| + for (VisitVector::const_iterator visit = visits.begin();
|
| + visit != visits.end(); ++visit) {
|
| + if (visit != visits.begin() &&
|
| + previous_visit_time > visit->visit_time.ToInternalValue())
|
| + return false;
|
| +
|
| + previous_visit_time = visit->visit_time.ToInternalValue();
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +std::string GetStorageKeyFromURLRow(const URLRow& row) {
|
| + std::string storage_key(sizeof(row.id()), 0);
|
| + base::WriteBigEndian<URLID>(&storage_key[0], row.id());
|
| + return storage_key;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| TypedURLSyncBridge::TypedURLSyncBridge(
|
| HistoryBackend* history_backend,
|
| - syncer::SyncMetadataStore* sync_metadata_store,
|
| + TypedURLSyncMetadataDatabase* sync_metadata_database,
|
| const ChangeProcessorFactory& change_processor_factory)
|
| : ModelTypeSyncBridge(change_processor_factory, syncer::TYPED_URLS),
|
| history_backend_(history_backend),
|
| - sync_metadata_store_(sync_metadata_store) {
|
| + sync_metadata_database_(sync_metadata_database),
|
| + num_db_accesses_(0),
|
| + num_db_errors_(0),
|
| + history_backend_observer_(this) {
|
| DCHECK(history_backend_);
|
| DCHECK(sequence_checker_.CalledOnValidSequence());
|
| - DCHECK(sync_metadata_store_);
|
| - NOTIMPLEMENTED();
|
| + DCHECK(sync_metadata_database_);
|
| }
|
|
|
| TypedURLSyncBridge::~TypedURLSyncBridge() {
|
| @@ -31,7 +76,7 @@ std::unique_ptr<syncer::MetadataChangeList>
|
| TypedURLSyncBridge::CreateMetadataChangeList() {
|
| DCHECK(sequence_checker_.CalledOnValidSequence());
|
| return base::MakeUnique<syncer::SyncMetadataStoreChangeList>(
|
| - sync_metadata_store_, syncer::TYPED_URLS);
|
| + sync_metadata_database_, syncer::TYPED_URLS);
|
| }
|
|
|
| base::Optional<syncer::ModelError> TypedURLSyncBridge::MergeSyncData(
|
| @@ -58,15 +103,31 @@ void TypedURLSyncBridge::GetData(StorageKeyList storage_keys,
|
|
|
| void TypedURLSyncBridge::GetAllData(DataCallback callback) {
|
| DCHECK(sequence_checker_.CalledOnValidSequence());
|
| - NOTIMPLEMENTED();
|
| +
|
| + history::URLRows typed_urls;
|
| + ++num_db_accesses_;
|
| + if (!history_backend_->GetAllTypedURLs(&typed_urls)) {
|
| + ++num_db_errors_;
|
| + change_processor()->ReportError(FROM_HERE,
|
| + "Could not get the typed_url entries.");
|
| + return;
|
| + }
|
| +
|
| + auto batch = base::MakeUnique<MutableDataBatch>();
|
| + for (history::URLRow& url : typed_urls) {
|
| + VisitVector visits_vector;
|
| + FixupURLAndGetVisits(&url, &visits_vector);
|
| + batch->Put(GetStorageKeyFromURLRow(url),
|
| + CreateEntityData(url, visits_vector));
|
| + }
|
| + callback.Run(std::move(batch));
|
| }
|
|
|
| // Must be exactly the value of GURL::spec() for backwards comparability with
|
| // the previous (Directory + SyncableService) iteration of sync integration.
|
| // This can be large but it is assumed that this is not held in memory at steady
|
| // state.
|
| -std::string TypedURLSyncBridge::GetClientTag(
|
| - const syncer::EntityData& entity_data) {
|
| +std::string TypedURLSyncBridge::GetClientTag(const EntityData& entity_data) {
|
| DCHECK(sequence_checker_.CalledOnValidSequence());
|
| DCHECK(entity_data.specifics.has_typed_url())
|
| << "EntityData does not have typed urls specifics.";
|
| @@ -76,11 +137,25 @@ std::string TypedURLSyncBridge::GetClientTag(
|
|
|
| // Prefer to use URLRow::id() to uniquely identify entities when coordinating
|
| // with sync because it has a significantly low memory cost than a URL.
|
| -std::string TypedURLSyncBridge::GetStorageKey(
|
| - const syncer::EntityData& entity_data) {
|
| +std::string TypedURLSyncBridge::GetStorageKey(const EntityData& entity_data) {
|
| DCHECK(sequence_checker_.CalledOnValidSequence());
|
| - NOTIMPLEMENTED();
|
| - return std::string();
|
| + DCHECK(history_backend_);
|
| + DCHECK(entity_data.specifics.has_typed_url())
|
| + << "EntityData does not have typed urls specifics.";
|
| +
|
| + const TypedUrlSpecifics& typed_url(entity_data.specifics.typed_url());
|
| + URLRow existing_url;
|
| + ++num_db_accesses_;
|
| + bool is_existing_url =
|
| + history_backend_->GetURL(GURL(typed_url.url()), &existing_url);
|
| +
|
| + if (!is_existing_url) {
|
| + // The typed url did not save to local history database yet, so return URL
|
| + // for now.
|
| + return entity_data.specifics.typed_url().url();
|
| + }
|
| +
|
| + return GetStorageKeyFromURLRow(existing_url);
|
| }
|
|
|
| void TypedURLSyncBridge::OnURLVisited(history::HistoryBackend* history_backend,
|
| @@ -108,4 +183,209 @@ void TypedURLSyncBridge::OnURLsDeleted(history::HistoryBackend* history_backend,
|
| NOTIMPLEMENTED();
|
| }
|
|
|
| +void TypedURLSyncBridge::Init() {
|
| + DCHECK(sequence_checker_.CalledOnValidSequence());
|
| +
|
| + history_backend_observer_.Add(history_backend_);
|
| + LoadMetadata();
|
| +}
|
| +
|
| +int TypedURLSyncBridge::GetErrorPercentage() const {
|
| + return num_db_accesses_ ? (100 * num_db_errors_ / num_db_accesses_) : 0;
|
| +}
|
| +
|
| +bool TypedURLSyncBridge::WriteToTypedUrlSpecifics(
|
| + const URLRow& url,
|
| + const VisitVector& visits,
|
| + 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 (std::find_if(visits.begin(), visits.end(),
|
| + [](const history::VisitRow& visit) {
|
| + return ui::PageTransitionCoreTypeIs(
|
| + visit.transition, ui::PAGE_TRANSITION_TYPED);
|
| + }) == visits.end()) {
|
| + // This URL has no TYPED visits, don't sync it
|
| + return false;
|
| + }
|
| +
|
| + 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 (VisitRow visit : visits) {
|
| + // We ignore reload visits.
|
| + if (PageTransitionCoreTypeIs(visit.transition,
|
| + ui::PAGE_TRANSITION_RELOAD)) {
|
| + continue;
|
| + }
|
| + ++total;
|
| + if (PageTransitionCoreTypeIs(visit.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(typed_count > 0);
|
| +
|
| + if (typed_count > kMaxTypedUrlVisits) {
|
| + only_typed = true;
|
| + skip_count = typed_count - kMaxTypedUrlVisits;
|
| + } else if (total > kMaxTypedUrlVisits) {
|
| + skip_count = total - kMaxTypedUrlVisits;
|
| + }
|
| + }
|
| +
|
| + for (const auto& visit : visits) {
|
| + // Skip reload visits.
|
| + if (PageTransitionCoreTypeIs(visit.transition, ui::PAGE_TRANSITION_RELOAD))
|
| + continue;
|
| +
|
| + // If we only have room for typed visits, then only add typed visits.
|
| + if (only_typed && !PageTransitionCoreTypeIs(visit.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 || !PageTransitionCoreTypeIs(visit.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);
|
| +
|
| + 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());
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void TypedURLSyncBridge::LoadMetadata() {
|
| + if (!history_backend_ || !sync_metadata_database_) {
|
| + change_processor()->ReportError(
|
| + FROM_HERE, "Failed to load TypedURLSyncMetadataDatabase.");
|
| + return;
|
| + }
|
| +
|
| + auto batch = base::MakeUnique<syncer::MetadataBatch>();
|
| + if (!sync_metadata_database_->GetAllSyncMetadata(batch.get())) {
|
| + change_processor()->ReportError(
|
| + FROM_HERE,
|
| + "Failed reading typed url metadata from TypedURLSyncMetadataDatabase.");
|
| + return;
|
| + }
|
| + change_processor()->ModelReadyToSync(std::move(batch));
|
| +}
|
| +
|
| +void TypedURLSyncBridge::ClearErrorStats() {
|
| + num_db_accesses_ = 0;
|
| + num_db_errors_ = 0;
|
| +}
|
| +
|
| +bool TypedURLSyncBridge::FixupURLAndGetVisits(URLRow* url,
|
| + VisitVector* visits) {
|
| + ++num_db_accesses_;
|
| + if (!history_backend_->GetMostRecentVisitsForURL(url->id(), kMaxVisitsToFetch,
|
| + visits)) {
|
| + ++num_db_errors_;
|
| + // 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: " << url->url();
|
| + 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;
|
| + }
|
| +
|
| + 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));
|
| +
|
| + // Removes all visits that are older than the current expiration time. Visits
|
| + // are in ascending order now, so we can check from beginning to check how
|
| + // many expired visits.
|
| + size_t num_expired_visits = 0;
|
| + for (auto& visit : *visits) {
|
| + base::Time time = visit.visit_time;
|
| + if (history_backend_->IsExpiredVisitTime(time)) {
|
| + ++num_expired_visits;
|
| + } else {
|
| + break;
|
| + }
|
| + }
|
| + if (num_expired_visits != 0) {
|
| + if (num_expired_visits == visits->size()) {
|
| + DVLOG(1) << "All visits are expired for url: " << url->url();
|
| + visits->clear();
|
| + return false;
|
| + }
|
| + visits->erase(visits->begin(), visits->begin() + num_expired_visits);
|
| + }
|
| + DCHECK(CheckVisitOrdering(*visits));
|
| +
|
| + return true;
|
| +}
|
| +
|
| +std::unique_ptr<EntityData> TypedURLSyncBridge::CreateEntityData(
|
| + const URLRow& row,
|
| + const VisitVector& visits) {
|
| + auto entity_data = base::MakeUnique<EntityData>();
|
| + TypedUrlSpecifics* specifics = entity_data->specifics.mutable_typed_url();
|
| +
|
| + if (!WriteToTypedUrlSpecifics(row, visits, specifics)) {
|
| + // Cannot write to specifics, ex. no TYPED visits.
|
| + return base::MakeUnique<EntityData>();
|
| + }
|
| + entity_data->non_unique_name = row.url().spec();
|
| +
|
| + return entity_data;
|
| +}
|
| +
|
| } // namespace history
|
|
|