Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / history / delete_directive_handler.cc
1 // Copyright (c) 2013 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/history/delete_directive_handler.h"
6
7 #include "base/json/json_writer.h"
8 #include "base/rand_util.h"
9 #include "base/time/time.h"
10 #include "base/values.h"
11 #include "chrome/browser/history/history_backend.h"
12 #include "chrome/browser/history/history_db_task.h"
13 #include "chrome/browser/history/history_service.h"
14 #include "sync/api/sync_change.h"
15 #include "sync/protocol/history_delete_directive_specifics.pb.h"
16 #include "sync/protocol/proto_value_conversions.h"
17 #include "sync/protocol/sync.pb.h"
18
19 namespace {
20
21 std::string RandASCIIString(size_t length) {
22   std::string result;
23   const int kMin = static_cast<int>(' ');
24   const int kMax = static_cast<int>('~');
25   for (size_t i = 0; i < length; ++i)
26     result.push_back(static_cast<char>(base::RandInt(kMin, kMax)));
27   return result;
28 }
29
30 std::string DeleteDirectiveToString(
31     const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
32   scoped_ptr<base::DictionaryValue> value(
33       syncer::HistoryDeleteDirectiveSpecificsToValue(delete_directive));
34   std::string str;
35   base::JSONWriter::Write(value.get(), &str);
36   return str;
37 }
38
39 // Compare time range directives first by start time, then by end time.
40 bool TimeRangeLessThan(const syncer::SyncData& data1,
41                        const syncer::SyncData& data2) {
42   const sync_pb::TimeRangeDirective& range1 =
43       data1.GetSpecifics().history_delete_directive().time_range_directive();
44   const sync_pb::TimeRangeDirective& range2 =
45       data2.GetSpecifics().history_delete_directive().time_range_directive();
46   if (range1.start_time_usec() < range2.start_time_usec())
47     return true;
48   if (range1.start_time_usec() > range2.start_time_usec())
49     return false;
50   return range1.end_time_usec() < range2.end_time_usec();
51 }
52
53 // Converts a Unix timestamp in microseconds to a base::Time value.
54 base::Time UnixUsecToTime(int64 usec) {
55   return base::Time::UnixEpoch() + base::TimeDelta::FromMicroseconds(usec);
56 }
57
58 // Converts a base::Time value to a Unix timestamp in microseconds.
59 int64 TimeToUnixUsec(base::Time time) {
60   DCHECK(!time.is_null());
61   return (time - base::Time::UnixEpoch()).InMicroseconds();
62 }
63
64 // Converts global IDs in |global_id_directive| to times.
65 void GetTimesFromGlobalIds(
66     const sync_pb::GlobalIdDirective& global_id_directive,
67     std::set<base::Time> *times) {
68   for (int i = 0; i < global_id_directive.global_id_size(); ++i) {
69     times->insert(
70         base::Time::FromInternalValue(global_id_directive.global_id(i)));
71   }
72 }
73
74 #if !defined(NDEBUG)
75 // Checks that the given delete directive is properly formed.
76 void CheckDeleteDirectiveValid(
77     const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
78   if (delete_directive.has_global_id_directive()) {
79     const sync_pb::GlobalIdDirective& global_id_directive =
80         delete_directive.global_id_directive();
81
82     DCHECK(!delete_directive.has_time_range_directive());
83     DCHECK_NE(global_id_directive.global_id_size(), 0);
84     if (global_id_directive.has_start_time_usec())
85       DCHECK_GE(global_id_directive.start_time_usec(), 0);
86     if (global_id_directive.has_end_time_usec()) {
87       DCHECK_GT(global_id_directive.end_time_usec(), 0);
88
89       if (global_id_directive.has_start_time_usec()) {
90         DCHECK_LE(global_id_directive.start_time_usec(),
91                   global_id_directive.end_time_usec());
92       }
93     }
94
95   } else if (delete_directive.has_time_range_directive()) {
96     const sync_pb::TimeRangeDirective& time_range_directive =
97         delete_directive.time_range_directive();
98
99     DCHECK(!delete_directive.has_global_id_directive());
100     DCHECK(time_range_directive.has_start_time_usec());
101     DCHECK(time_range_directive.has_end_time_usec());
102     DCHECK_GE(time_range_directive.start_time_usec(), 0);
103     DCHECK_GT(time_range_directive.end_time_usec(), 0);
104     DCHECK_GT(time_range_directive.end_time_usec(),
105               time_range_directive.start_time_usec());
106   } else {
107     NOTREACHED() << "Delete directive has no time range or global ID directive";
108   }
109 }
110 #endif  // !defined(NDEBUG)
111
112 }  // anonymous namespace
113
114 namespace history {
115
116 class DeleteDirectiveHandler::DeleteDirectiveTask : public HistoryDBTask {
117  public:
118   DeleteDirectiveTask(
119       base::WeakPtr<DeleteDirectiveHandler> delete_directive_handler,
120       const syncer::SyncDataList& delete_directive,
121       DeleteDirectiveHandler::PostProcessingAction post_processing_action)
122      : delete_directive_handler_(delete_directive_handler),
123        delete_directives_(delete_directive),
124        post_processing_action_(post_processing_action) {}
125
126   // Implements HistoryDBTask.
127   virtual bool RunOnDBThread(history::HistoryBackend* backend,
128                              history::HistoryDatabase* db) OVERRIDE;
129   virtual void DoneRunOnMainThread() OVERRIDE;
130
131  private:
132   virtual ~DeleteDirectiveTask() {}
133
134   // Process a list of global Id directives. Delete all visits to a URL in
135   // time ranges of directives if the timestamp of one visit matches with one
136   // global id.
137   void ProcessGlobalIdDeleteDirectives(
138       history::HistoryBackend* history_backend,
139       const syncer::SyncDataList& global_id_directives);
140
141   // Process a list of time range directives, all history entries within the
142   // time ranges are deleted. |time_range_directives| should be sorted by
143   // |start_time_usec| and |end_time_usec| already.
144   void ProcessTimeRangeDeleteDirectives(
145       history::HistoryBackend* history_backend,
146       const syncer::SyncDataList& time_range_directives);
147
148   base::WeakPtr<DeleteDirectiveHandler> delete_directive_handler_;
149   syncer::SyncDataList delete_directives_;
150   DeleteDirectiveHandler::PostProcessingAction post_processing_action_;
151 };
152
153 bool DeleteDirectiveHandler::DeleteDirectiveTask::RunOnDBThread(
154     history::HistoryBackend* backend,
155     history::HistoryDatabase* db) {
156   syncer::SyncDataList global_id_directives;
157   syncer::SyncDataList time_range_directives;
158   for (syncer::SyncDataList::const_iterator it = delete_directives_.begin();
159        it != delete_directives_.end(); ++it) {
160     DCHECK_EQ(it->GetDataType(), syncer::HISTORY_DELETE_DIRECTIVES);
161     const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive =
162         it->GetSpecifics().history_delete_directive();
163     if (delete_directive.has_global_id_directive()) {
164       global_id_directives.push_back(*it);
165     } else {
166       time_range_directives.push_back(*it);
167     }
168   }
169
170   ProcessGlobalIdDeleteDirectives(backend, global_id_directives);
171   std::sort(time_range_directives.begin(), time_range_directives.end(),
172             TimeRangeLessThan);
173   ProcessTimeRangeDeleteDirectives(backend, time_range_directives);
174   return true;
175 }
176
177 void DeleteDirectiveHandler::DeleteDirectiveTask::DoneRunOnMainThread() {
178   if (delete_directive_handler_.get()) {
179     delete_directive_handler_->FinishProcessing(post_processing_action_,
180                                                 delete_directives_);
181   }
182 }
183
184 void
185 DeleteDirectiveHandler::DeleteDirectiveTask::ProcessGlobalIdDeleteDirectives(
186     history::HistoryBackend* history_backend,
187     const syncer::SyncDataList& global_id_directives) {
188   if (global_id_directives.empty())
189     return;
190
191   // Group times represented by global IDs by time ranges of delete directives.
192   // It's more efficient for backend to process all directives with same time
193   // range at once.
194   typedef std::map<std::pair<base::Time, base::Time>, std::set<base::Time> >
195       GlobalIdTimesGroup;
196   GlobalIdTimesGroup id_times_group;
197   for (size_t i = 0; i < global_id_directives.size(); ++i) {
198     DVLOG(1) << "Processing delete directive: "
199              << DeleteDirectiveToString(
200                     global_id_directives[i].GetSpecifics()
201                         .history_delete_directive());
202
203     const sync_pb::GlobalIdDirective& id_directive =
204         global_id_directives[i].GetSpecifics().history_delete_directive()
205             .global_id_directive();
206     if (id_directive.global_id_size() == 0 ||
207         !id_directive.has_start_time_usec() ||
208         !id_directive.has_end_time_usec()) {
209       DLOG(ERROR) << "Invalid global id directive.";
210       continue;
211     }
212     GetTimesFromGlobalIds(
213         id_directive,
214         &id_times_group[
215             std::make_pair(UnixUsecToTime(id_directive.start_time_usec()),
216                            UnixUsecToTime(id_directive.end_time_usec()))]);
217   }
218
219   if (id_times_group.empty())
220     return;
221
222   // Call backend to expire history of directives in each group.
223   for (GlobalIdTimesGroup::const_iterator group_it = id_times_group.begin();
224       group_it != id_times_group.end(); ++group_it) {
225     // Add 1us to cover history entries visited at the end time because time
226     // range in directive is inclusive.
227     history_backend->ExpireHistoryForTimes(
228         group_it->second,
229         group_it->first.first,
230         group_it->first.second + base::TimeDelta::FromMicroseconds(1));
231   }
232 }
233
234 void
235 DeleteDirectiveHandler::DeleteDirectiveTask::ProcessTimeRangeDeleteDirectives(
236     history::HistoryBackend* history_backend,
237     const syncer::SyncDataList& time_range_directives) {
238   if (time_range_directives.empty())
239     return;
240
241   // Iterate through time range directives. Expire history in combined
242   // time range for multiple directives whose time ranges overlap.
243   base::Time current_start_time;
244   base::Time current_end_time;
245   for (size_t i = 0; i < time_range_directives.size(); ++i) {
246     const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive =
247         time_range_directives[i].GetSpecifics().history_delete_directive();
248     DVLOG(1) << "Processing time range directive: "
249              << DeleteDirectiveToString(delete_directive);
250
251     const sync_pb::TimeRangeDirective& time_range_directive =
252         delete_directive.time_range_directive();
253     if (!time_range_directive.has_start_time_usec() ||
254         !time_range_directive.has_end_time_usec() ||
255         time_range_directive.start_time_usec() >=
256             time_range_directive.end_time_usec()) {
257       DLOG(ERROR) << "Invalid time range directive.";
258       continue;
259     }
260
261     base::Time directive_start_time =
262         UnixUsecToTime(time_range_directive.start_time_usec());
263     base::Time directive_end_time =
264         UnixUsecToTime(time_range_directive.end_time_usec());
265     if (directive_start_time > current_end_time) {
266       if (!current_start_time.is_null()) {
267         // Add 1us to cover history entries visited at the end time because
268         // time range in directive is inclusive.
269         history_backend->ExpireHistoryBetween(
270             std::set<GURL>(), current_start_time,
271             current_end_time + base::TimeDelta::FromMicroseconds(1));
272       }
273       current_start_time = directive_start_time;
274     }
275     if (directive_end_time > current_end_time)
276       current_end_time = directive_end_time;
277   }
278
279   if (!current_start_time.is_null()) {
280     history_backend->ExpireHistoryBetween(
281         std::set<GURL>(), current_start_time,
282         current_end_time + base::TimeDelta::FromMicroseconds(1));
283   }
284 }
285
286 DeleteDirectiveHandler::DeleteDirectiveHandler()
287     : weak_ptr_factory_(this) {}
288
289 DeleteDirectiveHandler::~DeleteDirectiveHandler() {
290   weak_ptr_factory_.InvalidateWeakPtrs();
291 }
292
293 void DeleteDirectiveHandler::Start(
294     HistoryService* history_service,
295     const syncer::SyncDataList& initial_sync_data,
296     scoped_ptr<syncer::SyncChangeProcessor> sync_processor) {
297   DCHECK(thread_checker_.CalledOnValidThread());
298   sync_processor_ = sync_processor.Pass();
299   if (!initial_sync_data.empty()) {
300     // Drop processed delete directives during startup.
301     history_service->ScheduleDBTask(
302         scoped_ptr<history::HistoryDBTask>(
303             new DeleteDirectiveTask(weak_ptr_factory_.GetWeakPtr(),
304                                     initial_sync_data,
305                                     DROP_AFTER_PROCESSING)),
306         &internal_tracker_);
307   }
308 }
309
310 void DeleteDirectiveHandler::Stop() {
311   DCHECK(thread_checker_.CalledOnValidThread());
312   sync_processor_.reset();
313 }
314
315 bool DeleteDirectiveHandler::CreateDeleteDirectives(
316     const std::set<int64>& global_ids,
317     base::Time begin_time,
318     base::Time end_time) {
319   base::Time now = base::Time::Now();
320   sync_pb::HistoryDeleteDirectiveSpecifics delete_directive;
321
322   // Delete directives require a non-null begin time, so use 1 if it's null.
323   int64 begin_time_usecs =
324       begin_time.is_null() ? 0 : TimeToUnixUsec(begin_time);
325
326   // Determine the actual end time -- it should not be null or in the future.
327   // TODO(dubroy): Use sane time (crbug.com/146090) here when it's available.
328   base::Time end = (end_time.is_null() || end_time > now) ? now : end_time;
329   // -1 because end time in delete directives is inclusive.
330   int64 end_time_usecs = TimeToUnixUsec(end) - 1;
331
332   if (global_ids.empty()) {
333     sync_pb::TimeRangeDirective* time_range_directive =
334         delete_directive.mutable_time_range_directive();
335     time_range_directive->set_start_time_usec(begin_time_usecs);
336     time_range_directive->set_end_time_usec(end_time_usecs);
337   } else {
338     for (std::set<int64>::const_iterator it = global_ids.begin();
339          it != global_ids.end(); ++it) {
340       sync_pb::GlobalIdDirective* global_id_directive =
341           delete_directive.mutable_global_id_directive();
342       global_id_directive->add_global_id(*it);
343       global_id_directive->set_start_time_usec(begin_time_usecs);
344       global_id_directive->set_end_time_usec(end_time_usecs);
345     }
346   }
347   syncer::SyncError error = ProcessLocalDeleteDirective(delete_directive);
348   return !error.IsSet();
349 }
350
351 syncer::SyncError DeleteDirectiveHandler::ProcessLocalDeleteDirective(
352     const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
353   DCHECK(thread_checker_.CalledOnValidThread());
354   if (!sync_processor_) {
355     return syncer::SyncError(
356         FROM_HERE,
357         syncer::SyncError::DATATYPE_ERROR,
358         "Cannot send local delete directive to sync",
359         syncer::HISTORY_DELETE_DIRECTIVES);
360   }
361 #if !defined(NDEBUG)
362   CheckDeleteDirectiveValid(delete_directive);
363 #endif
364
365   // Generate a random sync tag since history delete directives don't
366   // have a 'built-in' ID.  8 bytes should suffice.
367   std::string sync_tag = RandASCIIString(8);
368   sync_pb::EntitySpecifics entity_specifics;
369   entity_specifics.mutable_history_delete_directive()->CopyFrom(
370       delete_directive);
371   syncer::SyncData sync_data =
372       syncer::SyncData::CreateLocalData(
373           sync_tag, sync_tag, entity_specifics);
374   syncer::SyncChange change(
375       FROM_HERE, syncer::SyncChange::ACTION_ADD, sync_data);
376   syncer::SyncChangeList changes(1, change);
377   return sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
378 }
379
380 syncer::SyncError DeleteDirectiveHandler::ProcessSyncChanges(
381     HistoryService* history_service,
382     const syncer::SyncChangeList& change_list) {
383   DCHECK(thread_checker_.CalledOnValidThread());
384   if (!sync_processor_) {
385     return syncer::SyncError(
386         FROM_HERE,
387         syncer::SyncError::DATATYPE_ERROR,
388         "Sync is disabled.",
389         syncer::HISTORY_DELETE_DIRECTIVES);
390   }
391
392   syncer::SyncDataList delete_directives;
393   for (syncer::SyncChangeList::const_iterator it = change_list.begin();
394        it != change_list.end(); ++it) {
395     switch (it->change_type()) {
396       case syncer::SyncChange::ACTION_ADD:
397         delete_directives.push_back(it->sync_data());
398         break;
399       case syncer::SyncChange::ACTION_DELETE:
400         // TODO(akalin): Keep track of existing delete directives.
401         break;
402       default:
403         NOTREACHED();
404         break;
405     }
406   }
407
408   if (!delete_directives.empty()) {
409     // Don't drop real-time delete directive so that sync engine can detect
410     // redelivered delete directives to avoid processing them again and again
411     // in one chrome session.
412     history_service->ScheduleDBTask(
413         scoped_ptr<history::HistoryDBTask>(
414             new DeleteDirectiveTask(weak_ptr_factory_.GetWeakPtr(),
415                                     delete_directives,
416                                     KEEP_AFTER_PROCESSING)),
417         &internal_tracker_);
418   }
419   return syncer::SyncError();
420 }
421
422 void DeleteDirectiveHandler::FinishProcessing(
423     PostProcessingAction post_processing_action,
424     const syncer::SyncDataList& delete_directives) {
425   DCHECK(thread_checker_.CalledOnValidThread());
426
427   // If specified, drop processed delete directive in sync model because they
428   // only need to be applied once.
429   if (sync_processor_.get() &&
430       post_processing_action == DROP_AFTER_PROCESSING) {
431     syncer::SyncChangeList change_list;
432     for (size_t i = 0; i < delete_directives.size(); ++i) {
433       change_list.push_back(
434           syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_DELETE,
435                              delete_directives[i]));
436     }
437     sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
438   }
439 }
440
441 }  // namespace history