Upstream version 10.39.225.0
[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 }
62
63 void TypedUrlChangeProcessor::Observe(
64     int type,
65     const content::NotificationSource& source,
66     const content::NotificationDetails& details) {
67   DCHECK(backend_loop_ == base::MessageLoop::current());
68
69   base::AutoLock al(disconnect_lock_);
70   if (disconnected_)
71     return;
72
73   DVLOG(1) << "Observed typed_url change.";
74   if (type == chrome::NOTIFICATION_HISTORY_URLS_MODIFIED) {
75     HandleURLsModified(
76         content::Details<history::URLsModifiedDetails>(details).ptr());
77   } else if (type == chrome::NOTIFICATION_HISTORY_URLS_DELETED) {
78     HandleURLsDeleted(
79         content::Details<history::URLsDeletedDetails>(details).ptr());
80   } else {
81     DCHECK_EQ(chrome::NOTIFICATION_HISTORY_URL_VISITED, type);
82     HandleURLsVisited(
83         content::Details<history::URLVisitedDetails>(details).ptr());
84   }
85   UMA_HISTOGRAM_PERCENTAGE("Sync.TypedUrlChangeProcessorErrors",
86                            model_associator_->GetErrorPercentage());
87 }
88
89 void TypedUrlChangeProcessor::HandleURLsModified(
90     history::URLsModifiedDetails* details) {
91
92   syncer::WriteTransaction trans(FROM_HERE, share_handle());
93   for (history::URLRows::iterator url = details->changed_urls.begin();
94        url != details->changed_urls.end(); ++url) {
95     if (url->typed_count() > 0) {
96       // If there were any errors updating the sync node, just ignore them and
97       // continue on to process the next URL.
98       CreateOrUpdateSyncNode(*url, &trans);
99     }
100   }
101 }
102
103 bool TypedUrlChangeProcessor::CreateOrUpdateSyncNode(
104     history::URLRow url, syncer::WriteTransaction* trans) {
105   DCHECK_GT(url.typed_count(), 0);
106   // Get the visits for this node.
107   history::VisitVector visit_vector;
108   if (!model_associator_->FixupURLAndGetVisits(&url, &visit_vector)) {
109     DLOG(ERROR) << "Could not load visits for url: " << url.url();
110     return false;
111   }
112
113   syncer::ReadNode typed_url_root(trans);
114   if (typed_url_root.InitTypeRoot(syncer::TYPED_URLS) !=
115           syncer::BaseNode::INIT_OK) {
116     syncer::SyncError error(FROM_HERE,
117                             syncer::SyncError::DATATYPE_ERROR,
118                             "No top level folder",
119                             syncer::TYPED_URLS);
120     error_handler()->OnSingleDataTypeUnrecoverableError(error);
121     return false;
122   }
123
124   if (model_associator_->ShouldIgnoreUrl(url.url()))
125     return true;
126
127   DCHECK(!visit_vector.empty());
128   std::string tag = url.url().spec();
129   syncer::WriteNode update_node(trans);
130   syncer::BaseNode::InitByLookupResult result =
131       update_node.InitByClientTagLookup(syncer::TYPED_URLS, tag);
132   if (result == syncer::BaseNode::INIT_OK) {
133     model_associator_->WriteToSyncNode(url, visit_vector, &update_node);
134   } else if (result == syncer::BaseNode::INIT_FAILED_DECRYPT_IF_NECESSARY) {
135     syncer::SyncError error(FROM_HERE,
136                             syncer::SyncError::DATATYPE_ERROR,
137                             "Failed to decrypt.",
138                             syncer::TYPED_URLS);
139     error_handler()->OnSingleDataTypeUnrecoverableError(error);
140     return false;
141   } else {
142     syncer::WriteNode create_node(trans);
143     syncer::WriteNode::InitUniqueByCreationResult result =
144         create_node.InitUniqueByCreation(syncer::TYPED_URLS,
145                                          typed_url_root, tag);
146     if (result != syncer::WriteNode::INIT_SUCCESS) {
147
148       syncer::SyncError error(FROM_HERE,
149                               syncer::SyncError::DATATYPE_ERROR,
150                               "Failed to create sync node",
151                               syncer::TYPED_URLS);
152       error_handler()->OnSingleDataTypeUnrecoverableError(error);
153       return false;
154     }
155
156     create_node.SetTitle(tag);
157     model_associator_->WriteToSyncNode(url, visit_vector, &create_node);
158   }
159   return true;
160 }
161
162 void TypedUrlChangeProcessor::HandleURLsDeleted(
163     history::URLsDeletedDetails* details) {
164   syncer::WriteTransaction trans(FROM_HERE, share_handle());
165
166   // Ignore archivals (we don't want to sync them as deletions, to avoid
167   // extra traffic up to the server, and also to make sure that a client with
168   // a bad clock setting won't go on an archival rampage and delete all
169   // history from every client). The server will gracefully age out the sync DB
170   // entries when they've been idle for long enough.
171   if (details->expired)
172     return;
173
174   if (details->all_history) {
175     if (!model_associator_->DeleteAllNodes(&trans)) {
176       syncer::SyncError error(FROM_HERE,
177                               syncer::SyncError::DATATYPE_ERROR,
178                               "Failed to delete local nodes.",
179                               syncer::TYPED_URLS);
180       error_handler()->OnSingleDataTypeUnrecoverableError(error);
181       return;
182     }
183   } else {
184     for (history::URLRows::const_iterator row = details->rows.begin();
185          row != details->rows.end(); ++row) {
186       syncer::WriteNode sync_node(&trans);
187       // The deleted URL could have been non-typed, so it might not be found
188       // in the sync DB.
189       if (sync_node.InitByClientTagLookup(syncer::TYPED_URLS,
190                                           row->url().spec()) ==
191               syncer::BaseNode::INIT_OK) {
192         sync_node.Tombstone();
193       }
194     }
195   }
196 }
197
198 void TypedUrlChangeProcessor::HandleURLsVisited(
199     history::URLVisitedDetails* details) {
200   if (!ShouldSyncVisit(details))
201     return;
202
203   syncer::WriteTransaction trans(FROM_HERE, share_handle());
204   CreateOrUpdateSyncNode(details->row, &trans);
205 }
206
207 bool TypedUrlChangeProcessor::ShouldSyncVisit(
208     history::URLVisitedDetails* details) {
209   int typed_count = details->row.typed_count();
210   ui::PageTransition transition =
211       ui::PageTransitionStripQualifier(details->transition);
212
213   // Just use an ad-hoc criteria to determine whether to ignore this
214   // notification. For most users, the distribution of visits is roughly a bell
215   // curve with a long tail - there are lots of URLs with < 5 visits so we want
216   // to make sure we sync up every visit to ensure the proper ordering of
217   // suggestions. But there are relatively few URLs with > 10 visits, and those
218   // tend to be more broadly distributed such that there's no need to sync up
219   // every visit to preserve their relative ordering.
220   return (transition == ui::PAGE_TRANSITION_TYPED &&
221           typed_count > 0 &&
222           (typed_count < kTypedUrlVisitThrottleThreshold ||
223            (typed_count % kTypedUrlVisitThrottleMultiple) == 0));
224 }
225
226 void TypedUrlChangeProcessor::ApplyChangesFromSyncModel(
227     const syncer::BaseTransaction* trans,
228     int64 model_version,
229     const syncer::ImmutableChangeRecordList& changes) {
230   DCHECK(backend_loop_ == base::MessageLoop::current());
231
232   base::AutoLock al(disconnect_lock_);
233   if (disconnected_)
234     return;
235
236   syncer::ReadNode typed_url_root(trans);
237   if (typed_url_root.InitTypeRoot(syncer::TYPED_URLS) !=
238           syncer::BaseNode::INIT_OK) {
239     syncer::SyncError error(FROM_HERE,
240                             syncer::SyncError::DATATYPE_ERROR,
241                             "Failed to init type root.",
242                             syncer::TYPED_URLS);
243     error_handler()->OnSingleDataTypeUnrecoverableError(error);
244     return;
245   }
246
247   DCHECK(pending_new_urls_.empty() && pending_new_visits_.empty() &&
248          pending_deleted_visits_.empty() && pending_updated_urls_.empty() &&
249          pending_deleted_urls_.empty());
250
251   for (syncer::ChangeRecordList::const_iterator it =
252            changes.Get().begin(); it != changes.Get().end(); ++it) {
253     if (syncer::ChangeRecord::ACTION_DELETE ==
254         it->action) {
255       DCHECK(it->specifics.has_typed_url()) <<
256           "Typed URL delete change does not have necessary specifics.";
257       GURL url(it->specifics.typed_url().url());
258       pending_deleted_urls_.push_back(url);
259       continue;
260     }
261
262     syncer::ReadNode sync_node(trans);
263     if (sync_node.InitByIdLookup(it->id) != syncer::BaseNode::INIT_OK) {
264       syncer::SyncError error(FROM_HERE,
265                               syncer::SyncError::DATATYPE_ERROR,
266                               "Failed to init sync node.",
267                               syncer::TYPED_URLS);
268       error_handler()->OnSingleDataTypeUnrecoverableError(error);
269       return;
270     }
271
272     // Check that the changed node is a child of the typed_urls folder.
273     DCHECK(typed_url_root.GetId() == sync_node.GetParentId());
274     DCHECK(syncer::TYPED_URLS == sync_node.GetModelType());
275
276     const sync_pb::TypedUrlSpecifics& typed_url(
277         sync_node.GetTypedUrlSpecifics());
278     DCHECK(typed_url.visits_size());
279
280     if (model_associator_->ShouldIgnoreUrl(GURL(typed_url.url())))
281       continue;
282
283     sync_pb::TypedUrlSpecifics filtered_url =
284         model_associator_->FilterExpiredVisits(typed_url);
285     if (!filtered_url.visits_size()) {
286       continue;
287     }
288
289     model_associator_->UpdateFromSyncDB(
290         filtered_url, &pending_new_visits_, &pending_deleted_visits_,
291         &pending_updated_urls_, &pending_new_urls_);
292   }
293 }
294
295 void TypedUrlChangeProcessor::CommitChangesFromSyncModel() {
296   DCHECK(backend_loop_ == base::MessageLoop::current());
297
298   base::AutoLock al(disconnect_lock_);
299   if (disconnected_)
300     return;
301
302   // Make sure we stop listening for changes while we're modifying the backend,
303   // so we don't try to re-apply these changes to the sync DB.
304   ScopedStopObserving<TypedUrlChangeProcessor> stop_observing(this);
305   if (!pending_deleted_urls_.empty())
306     history_backend_->DeleteURLs(pending_deleted_urls_);
307
308   model_associator_->WriteToHistoryBackend(&pending_new_urls_,
309                                            &pending_updated_urls_,
310                                            &pending_new_visits_,
311                                            &pending_deleted_visits_);
312
313   pending_new_urls_.clear();
314   pending_updated_urls_.clear();
315   pending_new_visits_.clear();
316   pending_deleted_visits_.clear();
317   pending_deleted_urls_.clear();
318   UMA_HISTOGRAM_PERCENTAGE("Sync.TypedUrlChangeProcessorErrors",
319                            model_associator_->GetErrorPercentage());
320 }
321
322 void TypedUrlChangeProcessor::Disconnect() {
323   base::AutoLock al(disconnect_lock_);
324   disconnected_ = true;
325 }
326
327 void TypedUrlChangeProcessor::StartImpl() {
328   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
329   DCHECK(history_backend_);
330   DCHECK(backend_loop_);
331   backend_loop_->PostTask(FROM_HERE,
332                           base::Bind(&TypedUrlChangeProcessor::StartObserving,
333                                      base::Unretained(this)));
334 }
335
336 void TypedUrlChangeProcessor::StartObserving() {
337   DCHECK(backend_loop_ == base::MessageLoop::current());
338   DCHECK(profile_);
339   notification_registrar_.Add(
340       this, chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
341       content::Source<Profile>(profile_));
342   notification_registrar_.Add(
343       this, chrome::NOTIFICATION_HISTORY_URLS_DELETED,
344       content::Source<Profile>(profile_));
345   notification_registrar_.Add(
346       this, chrome::NOTIFICATION_HISTORY_URL_VISITED,
347       content::Source<Profile>(profile_));
348 }
349
350 void TypedUrlChangeProcessor::StopObserving() {
351   DCHECK(backend_loop_ == base::MessageLoop::current());
352   DCHECK(profile_);
353   notification_registrar_.Remove(
354       this, chrome::NOTIFICATION_HISTORY_URLS_MODIFIED,
355       content::Source<Profile>(profile_));
356   notification_registrar_.Remove(
357       this, chrome::NOTIFICATION_HISTORY_URLS_DELETED,
358       content::Source<Profile>(profile_));
359   notification_registrar_.Remove(
360       this, chrome::NOTIFICATION_HISTORY_URL_VISITED,
361       content::Source<Profile>(profile_));
362 }
363
364 }  // namespace browser_sync