1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/sync/glue/typed_url_change_processor.h"
7 #include "base/location.h"
8 #include "base/metrics/histogram.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/history/history_backend.h"
13 #include "chrome/browser/history/history_notifications.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/sync/glue/typed_url_model_associator.h"
16 #include "chrome/browser/sync/profile_sync_service.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/notification_service.h"
19 #include "sync/internal_api/public/change_record.h"
20 #include "sync/internal_api/public/read_node.h"
21 #include "sync/internal_api/public/write_node.h"
22 #include "sync/internal_api/public/write_transaction.h"
23 #include "sync/protocol/typed_url_specifics.pb.h"
24 #include "sync/syncable/entry.h" // TODO(tim): Investigating bug 121587.
26 using content::BrowserThread;
28 namespace browser_sync {
30 // This is the threshold at which we start throttling sync updates for typed
31 // URLs - any URLs with a typed_count >= this threshold will be throttled.
32 static const int kTypedUrlVisitThrottleThreshold = 10;
34 // This is the multiple we use when throttling sync updates. If the multiple is
35 // N, we sync up every Nth update (i.e. when typed_count % N == 0).
36 static const int kTypedUrlVisitThrottleMultiple = 10;
38 TypedUrlChangeProcessor::TypedUrlChangeProcessor(
40 TypedUrlModelAssociator* model_associator,
41 history::HistoryBackend* history_backend,
42 sync_driver::DataTypeErrorHandler* error_handler)
43 : sync_driver::ChangeProcessor(error_handler),
45 model_associator_(model_associator),
46 history_backend_(history_backend),
47 backend_loop_(base::MessageLoop::current()),
48 disconnected_(false) {
49 DCHECK(model_associator);
50 DCHECK(history_backend);
51 DCHECK(error_handler);
52 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
53 // When running in unit tests, there is already a NotificationService object.
54 // Since only one can exist at a time per thread, check first.
55 if (!content::NotificationService::current())
56 notification_service_.reset(content::NotificationService::Create());
59 TypedUrlChangeProcessor::~TypedUrlChangeProcessor() {
60 DCHECK(backend_loop_ == base::MessageLoop::current());
61 DCHECK(history_backend_);
62 history_backend_->RemoveObserver(this);
65 void TypedUrlChangeProcessor::Observe(
67 const content::NotificationSource& source,
68 const content::NotificationDetails& details) {
69 DCHECK(backend_loop_ == base::MessageLoop::current());
71 base::AutoLock al(disconnect_lock_);
75 DVLOG(1) << "Observed typed_url change.";
76 if (type == chrome::NOTIFICATION_HISTORY_URLS_MODIFIED) {
78 content::Details<history::URLsModifiedDetails>(details).ptr());
79 } else if (type == chrome::NOTIFICATION_HISTORY_URLS_DELETED) {
81 content::Details<history::URLsDeletedDetails>(details).ptr());
83 UMA_HISTOGRAM_PERCENTAGE("Sync.TypedUrlChangeProcessorErrors",
84 model_associator_->GetErrorPercentage());
87 void TypedUrlChangeProcessor::OnURLVisited(
88 history::HistoryBackend* history_backend,
89 ui::PageTransition transition,
90 const history::URLRow& row,
91 const history::RedirectList& redirects,
92 base::Time visit_time) {
93 DCHECK(backend_loop_ == base::MessageLoop::current());
95 base::AutoLock al(disconnect_lock_);
99 DVLOG(1) << "Observed typed_url change.";
100 if (ShouldSyncVisit(row.typed_count(), transition)) {
101 syncer::WriteTransaction trans(FROM_HERE, share_handle());
102 CreateOrUpdateSyncNode(row, &trans);
104 UMA_HISTOGRAM_PERCENTAGE("Sync.TypedUrlChangeProcessorErrors",
105 model_associator_->GetErrorPercentage());
108 void TypedUrlChangeProcessor::HandleURLsModified(
109 history::URLsModifiedDetails* details) {
111 syncer::WriteTransaction trans(FROM_HERE, share_handle());
112 for (history::URLRows::iterator url = details->changed_urls.begin();
113 url != details->changed_urls.end(); ++url) {
114 if (url->typed_count() > 0) {
115 // If there were any errors updating the sync node, just ignore them and
116 // continue on to process the next URL.
117 CreateOrUpdateSyncNode(*url, &trans);
122 bool TypedUrlChangeProcessor::CreateOrUpdateSyncNode(
123 history::URLRow url, syncer::WriteTransaction* trans) {
124 DCHECK_GT(url.typed_count(), 0);
125 // Get the visits for this node.
126 history::VisitVector visit_vector;
127 if (!model_associator_->FixupURLAndGetVisits(&url, &visit_vector)) {
128 DLOG(ERROR) << "Could not load visits for url: " << url.url();
132 syncer::ReadNode typed_url_root(trans);
133 if (typed_url_root.InitTypeRoot(syncer::TYPED_URLS) !=
134 syncer::BaseNode::INIT_OK) {
135 syncer::SyncError error(FROM_HERE,
136 syncer::SyncError::DATATYPE_ERROR,
137 "No top level folder",
139 error_handler()->OnSingleDataTypeUnrecoverableError(error);
143 if (model_associator_->ShouldIgnoreUrl(url.url()))
146 DCHECK(!visit_vector.empty());
147 std::string tag = url.url().spec();
148 syncer::WriteNode update_node(trans);
149 syncer::BaseNode::InitByLookupResult result =
150 update_node.InitByClientTagLookup(syncer::TYPED_URLS, tag);
151 if (result == syncer::BaseNode::INIT_OK) {
152 model_associator_->WriteToSyncNode(url, visit_vector, &update_node);
153 } else if (result == syncer::BaseNode::INIT_FAILED_DECRYPT_IF_NECESSARY) {
154 syncer::SyncError error(FROM_HERE,
155 syncer::SyncError::DATATYPE_ERROR,
156 "Failed to decrypt.",
158 error_handler()->OnSingleDataTypeUnrecoverableError(error);
161 syncer::WriteNode create_node(trans);
162 syncer::WriteNode::InitUniqueByCreationResult result =
163 create_node.InitUniqueByCreation(syncer::TYPED_URLS,
164 typed_url_root, tag);
165 if (result != syncer::WriteNode::INIT_SUCCESS) {
167 syncer::SyncError error(FROM_HERE,
168 syncer::SyncError::DATATYPE_ERROR,
169 "Failed to create sync node",
171 error_handler()->OnSingleDataTypeUnrecoverableError(error);
175 create_node.SetTitle(tag);
176 model_associator_->WriteToSyncNode(url, visit_vector, &create_node);
181 void TypedUrlChangeProcessor::HandleURLsDeleted(
182 history::URLsDeletedDetails* details) {
183 syncer::WriteTransaction trans(FROM_HERE, share_handle());
185 // Ignore archivals (we don't want to sync them as deletions, to avoid
186 // extra traffic up to the server, and also to make sure that a client with
187 // a bad clock setting won't go on an archival rampage and delete all
188 // history from every client). The server will gracefully age out the sync DB
189 // entries when they've been idle for long enough.
190 if (details->expired)
193 if (details->all_history) {
194 if (!model_associator_->DeleteAllNodes(&trans)) {
195 syncer::SyncError error(FROM_HERE,
196 syncer::SyncError::DATATYPE_ERROR,
197 "Failed to delete local nodes.",
199 error_handler()->OnSingleDataTypeUnrecoverableError(error);
203 for (history::URLRows::const_iterator row = details->rows.begin();
204 row != details->rows.end(); ++row) {
205 syncer::WriteNode sync_node(&trans);
206 // The deleted URL could have been non-typed, so it might not be found
208 if (sync_node.InitByClientTagLookup(syncer::TYPED_URLS,
209 row->url().spec()) ==
210 syncer::BaseNode::INIT_OK) {
211 sync_node.Tombstone();
217 bool TypedUrlChangeProcessor::ShouldSyncVisit(int typed_count,
218 ui::PageTransition transition) {
219 // Just use an ad-hoc criteria to determine whether to ignore this
220 // notification. For most users, the distribution of visits is roughly a bell
221 // curve with a long tail - there are lots of URLs with < 5 visits so we want
222 // to make sure we sync up every visit to ensure the proper ordering of
223 // suggestions. But there are relatively few URLs with > 10 visits, and those
224 // tend to be more broadly distributed such that there's no need to sync up
225 // every visit to preserve their relative ordering.
226 return (ui::PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_TYPED) &&
228 (typed_count < kTypedUrlVisitThrottleThreshold ||
229 (typed_count % kTypedUrlVisitThrottleMultiple) == 0));
232 void TypedUrlChangeProcessor::ApplyChangesFromSyncModel(
233 const syncer::BaseTransaction* trans,
235 const syncer::ImmutableChangeRecordList& changes) {
236 DCHECK(backend_loop_ == base::MessageLoop::current());
238 base::AutoLock al(disconnect_lock_);
242 syncer::ReadNode typed_url_root(trans);
243 if (typed_url_root.InitTypeRoot(syncer::TYPED_URLS) !=
244 syncer::BaseNode::INIT_OK) {
245 syncer::SyncError error(FROM_HERE,
246 syncer::SyncError::DATATYPE_ERROR,
247 "Failed to init type root.",
249 error_handler()->OnSingleDataTypeUnrecoverableError(error);
253 DCHECK(pending_new_urls_.empty() && pending_new_visits_.empty() &&
254 pending_deleted_visits_.empty() && pending_updated_urls_.empty() &&
255 pending_deleted_urls_.empty());
257 for (syncer::ChangeRecordList::const_iterator it =
258 changes.Get().begin(); it != changes.Get().end(); ++it) {
259 if (syncer::ChangeRecord::ACTION_DELETE ==
261 DCHECK(it->specifics.has_typed_url()) <<
262 "Typed URL delete change does not have necessary specifics.";
263 GURL url(it->specifics.typed_url().url());
264 pending_deleted_urls_.push_back(url);
268 syncer::ReadNode sync_node(trans);
269 if (sync_node.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) {
270 syncer::SyncError error(FROM_HERE,
271 syncer::SyncError::DATATYPE_ERROR,
272 "Failed to init sync node.",
274 error_handler()->OnSingleDataTypeUnrecoverableError(error);
278 // Check that the changed node is a child of the typed_urls folder.
279 DCHECK(typed_url_root.GetId() == sync_node.GetParentId());
280 DCHECK(syncer::TYPED_URLS == sync_node.GetModelType());
282 const sync_pb::TypedUrlSpecifics& typed_url(
283 sync_node.GetTypedUrlSpecifics());
284 DCHECK(typed_url.visits_size());
286 if (model_associator_->ShouldIgnoreUrl(GURL(typed_url.url())))
289 sync_pb::TypedUrlSpecifics filtered_url =
290 model_associator_->FilterExpiredVisits(typed_url);
291 if (!filtered_url.visits_size()) {
295 model_associator_->UpdateFromSyncDB(
296 filtered_url, &pending_new_visits_, &pending_deleted_visits_,
297 &pending_updated_urls_, &pending_new_urls_);
301 void TypedUrlChangeProcessor::CommitChangesFromSyncModel() {
302 DCHECK(backend_loop_ == base::MessageLoop::current());
304 base::AutoLock al(disconnect_lock_);
308 // Make sure we stop listening for changes while we're modifying the backend,
309 // so we don't try to re-apply these changes to the sync DB.
310 ScopedStopObserving<TypedUrlChangeProcessor> stop_observing(this);
311 if (!pending_deleted_urls_.empty())
312 history_backend_->DeleteURLs(pending_deleted_urls_);
314 model_associator_->WriteToHistoryBackend(&pending_new_urls_,
315 &pending_updated_urls_,
316 &pending_new_visits_,
317 &pending_deleted_visits_);
319 pending_new_urls_.clear();
320 pending_updated_urls_.clear();
321 pending_new_visits_.clear();
322 pending_deleted_visits_.clear();
323 pending_deleted_urls_.clear();
324 UMA_HISTOGRAM_PERCENTAGE("Sync.TypedUrlChangeProcessorErrors",
325 model_associator_->GetErrorPercentage());
328 void TypedUrlChangeProcessor::Disconnect() {
329 base::AutoLock al(disconnect_lock_);
330 disconnected_ = true;
333 void TypedUrlChangeProcessor::StartImpl() {
334 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
335 DCHECK(history_backend_);
336 DCHECK(backend_loop_);
337 backend_loop_->PostTask(FROM_HERE,
338 base::Bind(&TypedUrlChangeProcessor::StartObserving,
339 base::Unretained(this)));
342 void TypedUrlChangeProcessor::StartObserving() {
343 DCHECK(backend_loop_ == base::MessageLoop::current());
344 DCHECK(history_backend_);
346 notification_registrar_.Add(
347 this, chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
348 content::Source<Profile>(profile_));
349 notification_registrar_.Add(
350 this, chrome::NOTIFICATION_HISTORY_URLS_DELETED,
351 content::Source<Profile>(profile_));
352 history_backend_->AddObserver(this);
355 void TypedUrlChangeProcessor::StopObserving() {
356 DCHECK(backend_loop_ == base::MessageLoop::current());
357 DCHECK(history_backend_);
359 notification_registrar_.Remove(
360 this, chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
361 content::Source<Profile>(profile_));
362 notification_registrar_.Remove(
363 this, chrome::NOTIFICATION_HISTORY_URLS_DELETED,
364 content::Source<Profile>(profile_));
365 history_backend_->RemoveObserver(this);
368 } // namespace browser_sync