Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(506)

Side by Side Diff: components/history/core/browser/typed_url_sync_bridge.cc

Issue 2901093009: [USS] Implement GetAllData and GetStorageKey. (Closed)
Patch Set: add DCHECK Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2017 The Chromium Authors. All rights reserved. 1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/history/core/browser/typed_url_sync_bridge.h" 5 #include "components/history/core/browser/typed_url_sync_bridge.h"
6 6
7 #include "base/big_endian.h"
7 #include "base/memory/ptr_util.h" 8 #include "base/memory/ptr_util.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "components/history/core/browser/history_backend.h"
11 #include "components/sync/model/mutable_data_batch.h"
8 #include "components/sync/model_impl/sync_metadata_store_change_list.h" 12 #include "components/sync/model_impl/sync_metadata_store_change_list.h"
9 13
14 using syncer::EntityData;
15 using sync_pb::TypedUrlSpecifics;
16 using syncer::MutableDataBatch;
17
10 namespace history { 18 namespace history {
11 19
20 namespace {
21
22 // The server backend can't handle arbitrarily large node sizes, so to keep
23 // the size under control we limit the visit array.
24 static const int kMaxTypedUrlVisits = 100;
25
26 // There's no limit on how many visits the history DB could have for a given
27 // typed URL, so we limit how many we fetch from the DB to avoid crashes due to
28 // running out of memory (http://crbug.com/89793). This value is different
29 // from kMaxTypedUrlVisits, as some of the visits fetched from the DB may be
30 // RELOAD visits, which will be stripped.
31 static const int kMaxVisitsToFetch = 1000;
32
33 // Enforce oldest to newest visit order.
34 static bool CheckVisitOrdering(const VisitVector& visits) {
35 int64_t previous_visit_time = 0;
36 for (VisitVector::const_iterator visit = visits.begin();
37 visit != visits.end(); ++visit) {
38 if (visit != visits.begin() &&
39 previous_visit_time > visit->visit_time.ToInternalValue())
40 return false;
41
42 previous_visit_time = visit->visit_time.ToInternalValue();
43 }
44 return true;
45 }
46
47 std::string GetStorageKeyFromURLRow(const URLRow& row) {
48 std::string storage_key(sizeof(row.id()), 0);
49 base::WriteBigEndian<URLID>(&storage_key[0], row.id());
50 return storage_key;
51 }
52
53 } // namespace
54
12 TypedURLSyncBridge::TypedURLSyncBridge( 55 TypedURLSyncBridge::TypedURLSyncBridge(
13 HistoryBackend* history_backend, 56 HistoryBackend* history_backend,
14 syncer::SyncMetadataStore* sync_metadata_store, 57 TypedURLSyncMetadataDatabase* sync_metadata_database,
15 const ChangeProcessorFactory& change_processor_factory) 58 const ChangeProcessorFactory& change_processor_factory)
16 : ModelTypeSyncBridge(change_processor_factory, syncer::TYPED_URLS), 59 : ModelTypeSyncBridge(change_processor_factory, syncer::TYPED_URLS),
17 history_backend_(history_backend), 60 history_backend_(history_backend),
18 sync_metadata_store_(sync_metadata_store) { 61 sync_metadata_database_(sync_metadata_database),
62 num_db_accesses_(0),
63 num_db_errors_(0),
64 history_backend_observer_(this) {
19 DCHECK(history_backend_); 65 DCHECK(history_backend_);
20 DCHECK(sequence_checker_.CalledOnValidSequence()); 66 DCHECK(sequence_checker_.CalledOnValidSequence());
21 DCHECK(sync_metadata_store_); 67 DCHECK(sync_metadata_database_);
22 NOTIMPLEMENTED(); 68
69 history_backend_observer_.Add(history_backend_);
70
71 LoadMetadata();
brettw 2017/06/05 17:31:15 I like to avoid database operations or other simil
Gang Wu 2017/06/05 20:04:14 Done.
23 } 72 }
24 73
25 TypedURLSyncBridge::~TypedURLSyncBridge() { 74 TypedURLSyncBridge::~TypedURLSyncBridge() {
26 // TODO(gangwu): unregister as HistoryBackendObserver, can use ScopedObserver 75 // TODO(gangwu): unregister as HistoryBackendObserver, can use ScopedObserver
27 // to do it. 76 // to do it.
28 } 77 }
29 78
30 std::unique_ptr<syncer::MetadataChangeList> 79 std::unique_ptr<syncer::MetadataChangeList>
31 TypedURLSyncBridge::CreateMetadataChangeList() { 80 TypedURLSyncBridge::CreateMetadataChangeList() {
32 DCHECK(sequence_checker_.CalledOnValidSequence()); 81 DCHECK(sequence_checker_.CalledOnValidSequence());
33 return base::MakeUnique<syncer::SyncMetadataStoreChangeList>( 82 return base::MakeUnique<syncer::SyncMetadataStoreChangeList>(
34 sync_metadata_store_, syncer::TYPED_URLS); 83 sync_metadata_database_, syncer::TYPED_URLS);
35 } 84 }
36 85
37 base::Optional<syncer::ModelError> TypedURLSyncBridge::MergeSyncData( 86 base::Optional<syncer::ModelError> TypedURLSyncBridge::MergeSyncData(
38 std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, 87 std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
39 syncer::EntityDataMap entity_data_map) { 88 syncer::EntityDataMap entity_data_map) {
40 DCHECK(sequence_checker_.CalledOnValidSequence()); 89 DCHECK(sequence_checker_.CalledOnValidSequence());
41 NOTIMPLEMENTED(); 90 NOTIMPLEMENTED();
42 return {}; 91 return {};
43 } 92 }
44 93
45 base::Optional<syncer::ModelError> TypedURLSyncBridge::ApplySyncChanges( 94 base::Optional<syncer::ModelError> TypedURLSyncBridge::ApplySyncChanges(
46 std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, 95 std::unique_ptr<syncer::MetadataChangeList> metadata_change_list,
47 syncer::EntityChangeList entity_changes) { 96 syncer::EntityChangeList entity_changes) {
48 DCHECK(sequence_checker_.CalledOnValidSequence()); 97 DCHECK(sequence_checker_.CalledOnValidSequence());
49 NOTIMPLEMENTED(); 98 NOTIMPLEMENTED();
50 return {}; 99 return {};
51 } 100 }
52 101
53 void TypedURLSyncBridge::GetData(StorageKeyList storage_keys, 102 void TypedURLSyncBridge::GetData(StorageKeyList storage_keys,
54 DataCallback callback) { 103 DataCallback callback) {
55 DCHECK(sequence_checker_.CalledOnValidSequence()); 104 DCHECK(sequence_checker_.CalledOnValidSequence());
56 NOTIMPLEMENTED(); 105 NOTIMPLEMENTED();
57 } 106 }
58 107
59 void TypedURLSyncBridge::GetAllData(DataCallback callback) { 108 void TypedURLSyncBridge::GetAllData(DataCallback callback) {
60 DCHECK(sequence_checker_.CalledOnValidSequence()); 109 DCHECK(sequence_checker_.CalledOnValidSequence());
61 NOTIMPLEMENTED(); 110
111 history::URLRows typed_urls;
112 ++num_db_accesses_;
113 if (!history_backend_->GetAllTypedURLs(&typed_urls)) {
114 ++num_db_errors_;
115 change_processor()->ReportError(FROM_HERE,
116 "Could not get the typed_url entries.");
117 return;
118 }
119
120 auto batch = base::MakeUnique<MutableDataBatch>();
121 for (history::URLRow& url : typed_urls) {
122 VisitVector visits_vector;
123 FixupURLAndGetVisits(&url, &visits_vector);
124 batch->Put(GetStorageKeyFromURLRow(url),
125 CreateEntityData(url, visits_vector));
126 }
127 callback.Run(std::move(batch));
62 } 128 }
63 129
64 // Must be exactly the value of GURL::spec() for backwards comparability with 130 // Must be exactly the value of GURL::spec() for backwards comparability with
65 // the previous (Directory + SyncableService) iteration of sync integration. 131 // the previous (Directory + SyncableService) iteration of sync integration.
66 // This can be large but it is assumed that this is not held in memory at steady 132 // This can be large but it is assumed that this is not held in memory at steady
67 // state. 133 // state.
68 std::string TypedURLSyncBridge::GetClientTag( 134 std::string TypedURLSyncBridge::GetClientTag(const EntityData& entity_data) {
69 const syncer::EntityData& entity_data) {
70 DCHECK(sequence_checker_.CalledOnValidSequence()); 135 DCHECK(sequence_checker_.CalledOnValidSequence());
71 DCHECK(entity_data.specifics.has_typed_url()) 136 DCHECK(entity_data.specifics.has_typed_url())
72 << "EntityData does not have typed urls specifics."; 137 << "EntityData does not have typed urls specifics.";
73 138
74 return entity_data.specifics.typed_url().url(); 139 return entity_data.specifics.typed_url().url();
75 } 140 }
76 141
77 // Prefer to use URLRow::id() to uniquely identify entities when coordinating 142 // Prefer to use URLRow::id() to uniquely identify entities when coordinating
78 // with sync because it has a significantly low memory cost than a URL. 143 // with sync because it has a significantly low memory cost than a URL.
79 std::string TypedURLSyncBridge::GetStorageKey( 144 std::string TypedURLSyncBridge::GetStorageKey(const EntityData& entity_data) {
80 const syncer::EntityData& entity_data) {
81 DCHECK(sequence_checker_.CalledOnValidSequence()); 145 DCHECK(sequence_checker_.CalledOnValidSequence());
82 NOTIMPLEMENTED(); 146 DCHECK(history_backend_);
83 return std::string(); 147 DCHECK(entity_data.specifics.has_typed_url())
148 << "EntityData does not have typed urls specifics.";
149
150 const TypedUrlSpecifics& typed_url(entity_data.specifics.typed_url());
151 URLRow existing_url;
152 ++num_db_accesses_;
153 bool is_existing_url =
154 history_backend_->GetURL(GURL(typed_url.url()), &existing_url);
155
156 if (!is_existing_url) {
157 // The typed url did not save to local history database yet, so return URL
158 // for now.
159 return entity_data.specifics.typed_url().url();
160 }
161
162 return GetStorageKeyFromURLRow(existing_url);
84 } 163 }
85 164
86 void TypedURLSyncBridge::OnURLVisited(history::HistoryBackend* history_backend, 165 void TypedURLSyncBridge::OnURLVisited(history::HistoryBackend* history_backend,
87 ui::PageTransition transition, 166 ui::PageTransition transition,
88 const history::URLRow& row, 167 const history::URLRow& row,
89 const history::RedirectList& redirects, 168 const history::RedirectList& redirects,
90 base::Time visit_time) { 169 base::Time visit_time) {
91 DCHECK(sequence_checker_.CalledOnValidSequence()); 170 DCHECK(sequence_checker_.CalledOnValidSequence());
92 NOTIMPLEMENTED(); 171 NOTIMPLEMENTED();
93 } 172 }
94 173
95 void TypedURLSyncBridge::OnURLsModified( 174 void TypedURLSyncBridge::OnURLsModified(
96 history::HistoryBackend* history_backend, 175 history::HistoryBackend* history_backend,
97 const history::URLRows& changed_urls) { 176 const history::URLRows& changed_urls) {
98 DCHECK(sequence_checker_.CalledOnValidSequence()); 177 DCHECK(sequence_checker_.CalledOnValidSequence());
99 NOTIMPLEMENTED(); 178 NOTIMPLEMENTED();
100 } 179 }
101 180
102 void TypedURLSyncBridge::OnURLsDeleted(history::HistoryBackend* history_backend, 181 void TypedURLSyncBridge::OnURLsDeleted(history::HistoryBackend* history_backend,
103 bool all_history, 182 bool all_history,
104 bool expired, 183 bool expired,
105 const history::URLRows& deleted_rows, 184 const history::URLRows& deleted_rows,
106 const std::set<GURL>& favicon_urls) { 185 const std::set<GURL>& favicon_urls) {
107 DCHECK(sequence_checker_.CalledOnValidSequence()); 186 DCHECK(sequence_checker_.CalledOnValidSequence());
108 NOTIMPLEMENTED(); 187 NOTIMPLEMENTED();
109 } 188 }
110 189
190 int TypedURLSyncBridge::GetErrorPercentage() const {
191 return num_db_accesses_ ? (100 * num_db_errors_ / num_db_accesses_) : 0;
192 }
193
194 bool TypedURLSyncBridge::WriteToTypedUrlSpecifics(
195 const URLRow& url,
196 const VisitVector& visits,
197 TypedUrlSpecifics* typed_url) {
198 DCHECK(!url.last_visit().is_null());
199 DCHECK(!visits.empty());
200 DCHECK_EQ(url.last_visit().ToInternalValue(),
201 visits.back().visit_time.ToInternalValue());
202
203 typed_url->set_url(url.url().spec());
204 typed_url->set_title(base::UTF16ToUTF8(url.title()));
205 typed_url->set_hidden(url.hidden());
206
207 DCHECK(CheckVisitOrdering(visits));
208
209 bool only_typed = false;
210 int skip_count = 0;
211
212 if (std::find_if(visits.begin(), visits.end(),
213 [](const history::VisitRow& visit) {
214 return ui::PageTransitionCoreTypeIs(
215 visit.transition, ui::PAGE_TRANSITION_TYPED);
216 }) == visits.end()) {
217 // This URL has no TYPED visits, don't sync it
218 return false;
219 }
220
221 if (visits.size() > static_cast<size_t>(kMaxTypedUrlVisits)) {
222 int typed_count = 0;
223 int total = 0;
224 // Walk the passed-in visit vector and count the # of typed visits.
225 for (VisitVector::const_iterator visit = visits.begin();
brettw 2017/06/05 17:31:15 This would be clearer using a range-based one: f
Gang Wu 2017/06/05 20:04:14 Done.
226 visit != visits.end(); ++visit) {
227 // We ignore reload visits.
228 if (PageTransitionCoreTypeIs(visit->transition,
229 ui::PAGE_TRANSITION_RELOAD)) {
230 continue;
231 }
232 ++total;
233 if (PageTransitionCoreTypeIs(visit->transition,
234 ui::PAGE_TRANSITION_TYPED)) {
235 ++typed_count;
236 }
237 }
238
239 // We should have at least one typed visit. This can sometimes happen if
240 // the history DB has an inaccurate count for some reason (there's been
241 // bugs in the history code in the past which has left users in the wild
242 // with incorrect counts - http://crbug.com/84258).
243 DCHECK(typed_count > 0);
244
245 if (typed_count > kMaxTypedUrlVisits) {
246 only_typed = true;
247 skip_count = typed_count - kMaxTypedUrlVisits;
248 } else if (total > kMaxTypedUrlVisits) {
249 skip_count = total - kMaxTypedUrlVisits;
250 }
251 }
252
253 for (const auto& visit : visits) {
254 // Skip reload visits.
255 if (PageTransitionCoreTypeIs(visit.transition, ui::PAGE_TRANSITION_RELOAD))
256 continue;
257
258 // If we only have room for typed visits, then only add typed visits.
259 if (only_typed && !PageTransitionCoreTypeIs(visit.transition,
260 ui::PAGE_TRANSITION_TYPED)) {
261 continue;
262 }
263
264 if (skip_count > 0) {
265 // We have too many entries to fit, so we need to skip the oldest ones.
266 // Only skip typed URLs if there are too many typed URLs to fit.
267 if (only_typed || !PageTransitionCoreTypeIs(visit.transition,
268 ui::PAGE_TRANSITION_TYPED)) {
269 --skip_count;
270 continue;
271 }
272 }
273 typed_url->add_visits(visit.visit_time.ToInternalValue());
274 typed_url->add_visit_transitions(visit.transition);
275 }
276 DCHECK_EQ(skip_count, 0);
277
278 CHECK_GT(typed_url->visits_size(), 0);
279 CHECK_LE(typed_url->visits_size(), kMaxTypedUrlVisits);
280 CHECK_EQ(typed_url->visits_size(), typed_url->visit_transitions_size());
281
282 return true;
283 }
284
285 void TypedURLSyncBridge::LoadMetadata() {
286 if (!history_backend_ || !sync_metadata_database_) {
287 change_processor()->ReportError(
288 FROM_HERE, "Failed to load TypedURLSyncMetadataDatabase.");
289 return;
290 }
291
292 auto batch = base::MakeUnique<syncer::MetadataBatch>();
293 if (!sync_metadata_database_->GetAllSyncMetadata(batch.get())) {
294 change_processor()->ReportError(
295 FROM_HERE,
296 "Failed reading typed url metadata from TypedURLSyncMetadataDatabase.");
297 return;
298 }
299 change_processor()->ModelReadyToSync(std::move(batch));
300 }
301
302 void TypedURLSyncBridge::ClearErrorStats() {
303 num_db_accesses_ = 0;
304 num_db_errors_ = 0;
305 }
306
307 bool TypedURLSyncBridge::FixupURLAndGetVisits(URLRow* url,
308 VisitVector* visits) {
309 CHECK(history_backend_);
brettw 2017/06/05 17:31:15 Is this necessary? The constructor already dchecks
Gang Wu 2017/06/05 20:04:14 Done.
310 ++num_db_accesses_;
311 if (!history_backend_->GetMostRecentVisitsForURL(url->id(), kMaxVisitsToFetch,
312 visits)) {
313 ++num_db_errors_;
314 // Couldn't load the visits for this URL due to some kind of DB error.
315 // Don't bother writing this URL to the history DB (if we ignore the
316 // error and continue, we might end up duplicating existing visits).
317 DLOG(ERROR) << "Could not load visits for url: " << url->url();
318 return false;
319 }
320
321 // Sometimes (due to a bug elsewhere in the history or sync code, or due to
322 // a crash between adding a URL to the history database and updating the
323 // visit DB) the visit vector for a URL can be empty. If this happens, just
324 // create a new visit whose timestamp is the same as the last_visit time.
325 // This is a workaround for http://crbug.com/84258.
326 if (visits->empty()) {
327 DVLOG(1) << "Found empty visits for URL: " << url->url();
328 if (url->last_visit().is_null()) {
329 // If modified URL is bookmarked, history backend treats it as modified
330 // even if all its visits are deleted. Return false to stop further
331 // processing because sync expects valid visit time for modified entry.
332 return false;
333 }
334
335 VisitRow visit(url->id(), url->last_visit(), 0, ui::PAGE_TRANSITION_TYPED,
336 0);
337 visits->push_back(visit);
338 }
339
340 // GetMostRecentVisitsForURL() returns the data in the opposite order that
341 // we need it, so reverse it.
342 std::reverse(visits->begin(), visits->end());
brettw 2017/06/05 17:31:15 I asked in the header to document this ordering. B
Gang Wu 2017/06/05 20:04:14 Visits may not need to be order in this CL, but in
343
344 // Sometimes, the last_visit field in the URL doesn't match the timestamp of
345 // the last visit in our visit array (they come from different tables, so
346 // crashes/bugs can cause them to mismatch), so just set it here.
347 url->set_last_visit(visits->back().visit_time);
348 DCHECK(CheckVisitOrdering(*visits));
349
350 // Removes all visits that are older than the current expiration time. Visits
351 // are in ascending order now, so we can check from beginning to check how
352 // many expired visits.
353 size_t num_expired_visits = 0;
354 for (auto& visit : *visits) {
355 base::Time time = visit.visit_time;
356 if (history_backend_->IsExpiredVisitTime(time)) {
357 ++num_expired_visits;
358 } else {
359 break;
360 }
361 }
362 if (num_expired_visits != 0) {
363 if (num_expired_visits == visits->size()) {
364 DVLOG(1) << "All visits are expired for url: " << url->url();
365 visits->clear();
366 return false;
367 }
368 visits->erase(visits->begin(), visits->begin() + num_expired_visits);
369 }
370 DCHECK(CheckVisitOrdering(*visits));
371
372 return true;
373 }
374
375 std::unique_ptr<EntityData> TypedURLSyncBridge::CreateEntityData(
376 const URLRow& row,
377 const VisitVector& visits) {
378 auto entity_data = base::MakeUnique<EntityData>();
379 TypedUrlSpecifics* specifics = entity_data->specifics.mutable_typed_url();
380
381 if (!WriteToTypedUrlSpecifics(row, visits, specifics)) {
382 // Cannot write to specifics, ex. no TYPED visits.
383 return base::MakeUnique<EntityData>();
384 }
385 entity_data->non_unique_name = row.url().spec();
386
387 return entity_data;
388 }
389
111 } // namespace history 390 } // namespace history
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698