51539c91586026de8c63016611ea3c485e3b110a
[platform/framework/web/crosswalk.git] / src / chrome / browser / sync / glue / typed_url_change_processor.cc
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.
4
5 #include "chrome/browser/sync/glue/typed_url_change_processor.h"
6
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.
25
26 using content::BrowserThread;
27
28 namespace browser_sync {
29
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;
33
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;
37
38 TypedUrlChangeProcessor::TypedUrlChangeProcessor(
39     Profile* profile,
40     TypedUrlModelAssociator* model_associator,
41     history::HistoryBackend* history_backend,
42     sync_driver::DataTypeErrorHandler* error_handler)
43     : sync_driver::ChangeProcessor(error_handler),
44       profile_(profile),
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());
57 }
58
59 TypedUrlChangeProcessor::~TypedUrlChangeProcessor() {
60   DCHECK(backend_loop_ == base::MessageLoop::current());
61   DCHECK(history_backend_);
62   history_backend_->RemoveObserver(this);
63 }
64
65 void TypedUrlChangeProcessor::Observe(
66     int type,
67     const content::NotificationSource& source,
68     const content::NotificationDetails& details) {
69   DCHECK(backend_loop_ == base::MessageLoop::current());
70
71   base::AutoLock al(disconnect_lock_);
72   if (disconnected_)
73     return;
74
75   DVLOG(1) << "Observed typed_url change.";
76   if (type == chrome::NOTIFICATION_HISTORY_URLS_MODIFIED) {
77     HandleURLsModified(
78         content::Details<history::URLsModifiedDetails>(details).ptr());
79   } else if (type == chrome::NOTIFICATION_HISTORY_URLS_DELETED) {
80     HandleURLsDeleted(
81         content::Details<history::URLsDeletedDetails>(details).ptr());
82   }
83   UMA_HISTOGRAM_PERCENTAGE("Sync.TypedUrlChangeProcessorErrors",
84                            model_associator_->GetErrorPercentage());
85 }
86
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());
94
95   base::AutoLock al(disconnect_lock_);
96   if (disconnected_)
97     return;
98
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);
103   }
104   UMA_HISTOGRAM_PERCENTAGE("Sync.TypedUrlChangeProcessorErrors",
105                            model_associator_->GetErrorPercentage());
106 }
107
108 void TypedUrlChangeProcessor::HandleURLsModified(
109     history::URLsModifiedDetails* details) {
110
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);
118     }
119   }
120 }
121
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();
129     return false;
130   }
131
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",
138                             syncer::TYPED_URLS);
139     error_handler()->OnSingleDataTypeUnrecoverableError(error);
140     return false;
141   }
142
143   if (model_associator_->ShouldIgnoreUrl(url.url()))
144     return true;
145
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.",
157                             syncer::TYPED_URLS);
158     error_handler()->OnSingleDataTypeUnrecoverableError(error);
159     return false;
160   } else {
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) {
166
167       syncer::SyncError error(FROM_HERE,
168                               syncer::SyncError::DATATYPE_ERROR,
169                               "Failed to create sync node",
170                               syncer::TYPED_URLS);
171       error_handler()->OnSingleDataTypeUnrecoverableError(error);
172       return false;
173     }
174
175     create_node.SetTitle(tag);
176     model_associator_->WriteToSyncNode(url, visit_vector, &create_node);
177   }
178   return true;
179 }
180
181 void TypedUrlChangeProcessor::HandleURLsDeleted(
182     history::URLsDeletedDetails* details) {
183   syncer::WriteTransaction trans(FROM_HERE, share_handle());
184
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)
191     return;
192
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.",
198                               syncer::TYPED_URLS);
199       error_handler()->OnSingleDataTypeUnrecoverableError(error);
200       return;
201     }
202   } else {
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
207       // in the sync DB.
208       if (sync_node.InitByClientTagLookup(syncer::TYPED_URLS,
209                                           row->url().spec()) ==
210               syncer::BaseNode::INIT_OK) {
211         sync_node.Tombstone();
212       }
213     }
214   }
215 }
216
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) &&
227           typed_count > 0 &&
228           (typed_count < kTypedUrlVisitThrottleThreshold ||
229            (typed_count % kTypedUrlVisitThrottleMultiple) == 0));
230 }
231
232 void TypedUrlChangeProcessor::ApplyChangesFromSyncModel(
233     const syncer::BaseTransaction* trans,
234     int64 model_version,
235     const syncer::ImmutableChangeRecordList& changes) {
236   DCHECK(backend_loop_ == base::MessageLoop::current());
237
238   base::AutoLock al(disconnect_lock_);
239   if (disconnected_)
240     return;
241
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.",
248                             syncer::TYPED_URLS);
249     error_handler()->OnSingleDataTypeUnrecoverableError(error);
250     return;
251   }
252
253   DCHECK(pending_new_urls_.empty() && pending_new_visits_.empty() &&
254          pending_deleted_visits_.empty() && pending_updated_urls_.empty() &&
255          pending_deleted_urls_.empty());
256
257   for (syncer::ChangeRecordList::const_iterator it =
258            changes.Get().begin(); it != changes.Get().end(); ++it) {
259     if (syncer::ChangeRecord::ACTION_DELETE ==
260         it->action) {
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);
265       continue;
266     }
267
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.",
273                               syncer::TYPED_URLS);
274       error_handler()->OnSingleDataTypeUnrecoverableError(error);
275       return;
276     }
277
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());
281
282     const sync_pb::TypedUrlSpecifics& typed_url(
283         sync_node.GetTypedUrlSpecifics());
284     DCHECK(typed_url.visits_size());
285
286     if (model_associator_->ShouldIgnoreUrl(GURL(typed_url.url())))
287       continue;
288
289     sync_pb::TypedUrlSpecifics filtered_url =
290         model_associator_->FilterExpiredVisits(typed_url);
291     if (!filtered_url.visits_size()) {
292       continue;
293     }
294
295     model_associator_->UpdateFromSyncDB(
296         filtered_url, &pending_new_visits_, &pending_deleted_visits_,
297         &pending_updated_urls_, &pending_new_urls_);
298   }
299 }
300
301 void TypedUrlChangeProcessor::CommitChangesFromSyncModel() {
302   DCHECK(backend_loop_ == base::MessageLoop::current());
303
304   base::AutoLock al(disconnect_lock_);
305   if (disconnected_)
306     return;
307
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_);
313
314   model_associator_->WriteToHistoryBackend(&pending_new_urls_,
315                                            &pending_updated_urls_,
316                                            &pending_new_visits_,
317                                            &pending_deleted_visits_);
318
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());
326 }
327
328 void TypedUrlChangeProcessor::Disconnect() {
329   base::AutoLock al(disconnect_lock_);
330   disconnected_ = true;
331 }
332
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)));
340 }
341
342 void TypedUrlChangeProcessor::StartObserving() {
343   DCHECK(backend_loop_ == base::MessageLoop::current());
344   DCHECK(history_backend_);
345   DCHECK(profile_);
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);
353 }
354
355 void TypedUrlChangeProcessor::StopObserving() {
356   DCHECK(backend_loop_ == base::MessageLoop::current());
357   DCHECK(history_backend_);
358   DCHECK(profile_);
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);
366 }
367
368 }  // namespace browser_sync