OLD | NEW |
---|---|
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(8, 0); | |
pavely
2017/06/01 20:49:18
If row.id() changes type or simply has different s
Gang Wu
2017/06/02 00:00:23
Done.
| |
49 base::WriteBigEndian(&storage_key[0], row.id()); | |
pavely
2017/06/01 20:49:18
nit: You can use storage_key.data() instead of &st
Gang Wu
2017/06/02 00:00:23
it won't work, I received the error message as fol
| |
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(); | |
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(); | |
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_); | |
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()); | |
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 |
OLD | NEW |