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.
5 #include "chrome/browser/history/delete_directive_handler.h"
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"
21 std::string RandASCIIString(size_t length) {
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)));
30 std::string DeleteDirectiveToString(
31 const sync_pb::HistoryDeleteDirectiveSpecifics& delete_directive) {
32 scoped_ptr<base::DictionaryValue> value(
33 syncer::HistoryDeleteDirectiveSpecificsToValue(delete_directive));
35 base::JSONWriter::Write(value.get(), &str);
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())
48 if (range1.start_time_usec() > range2.start_time_usec())
50 return range1.end_time_usec() < range2.end_time_usec();
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);
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();
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) {
70 base::Time::FromInternalValue(global_id_directive.global_id(i)));
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();
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);
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());
95 } else if (delete_directive.has_time_range_directive()) {
96 const sync_pb::TimeRangeDirective& time_range_directive =
97 delete_directive.time_range_directive();
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());
107 NOTREACHED() << "Delete directive has no time range or global ID directive";
110 #endif // !defined(NDEBUG)
112 } // anonymous namespace
116 class DeleteDirectiveHandler::DeleteDirectiveTask : public HistoryDBTask {
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) {}
126 // Implements HistoryDBTask.
127 virtual bool RunOnDBThread(history::HistoryBackend* backend,
128 history::HistoryDatabase* db) OVERRIDE;
129 virtual void DoneRunOnMainThread() OVERRIDE;
132 virtual ~DeleteDirectiveTask() {}
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
137 void ProcessGlobalIdDeleteDirectives(
138 history::HistoryBackend* history_backend,
139 const syncer::SyncDataList& global_id_directives);
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);
148 base::WeakPtr<DeleteDirectiveHandler> delete_directive_handler_;
149 syncer::SyncDataList delete_directives_;
150 DeleteDirectiveHandler::PostProcessingAction post_processing_action_;
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);
166 time_range_directives.push_back(*it);
170 ProcessGlobalIdDeleteDirectives(backend, global_id_directives);
171 std::sort(time_range_directives.begin(), time_range_directives.end(),
173 ProcessTimeRangeDeleteDirectives(backend, time_range_directives);
177 void DeleteDirectiveHandler::DeleteDirectiveTask::DoneRunOnMainThread() {
178 if (delete_directive_handler_.get()) {
179 delete_directive_handler_->FinishProcessing(post_processing_action_,
185 DeleteDirectiveHandler::DeleteDirectiveTask::ProcessGlobalIdDeleteDirectives(
186 history::HistoryBackend* history_backend,
187 const syncer::SyncDataList& global_id_directives) {
188 if (global_id_directives.empty())
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
194 typedef std::map<std::pair<base::Time, base::Time>, std::set<base::Time> >
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());
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.";
212 GetTimesFromGlobalIds(
215 std::make_pair(UnixUsecToTime(id_directive.start_time_usec()),
216 UnixUsecToTime(id_directive.end_time_usec()))]);
219 if (id_times_group.empty())
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(
229 group_it->first.first,
230 group_it->first.second + base::TimeDelta::FromMicroseconds(1));
235 DeleteDirectiveHandler::DeleteDirectiveTask::ProcessTimeRangeDeleteDirectives(
236 history::HistoryBackend* history_backend,
237 const syncer::SyncDataList& time_range_directives) {
238 if (time_range_directives.empty())
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);
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.";
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));
273 current_start_time = directive_start_time;
275 if (directive_end_time > current_end_time)
276 current_end_time = directive_end_time;
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));
286 DeleteDirectiveHandler::DeleteDirectiveHandler()
287 : weak_ptr_factory_(this) {}
289 DeleteDirectiveHandler::~DeleteDirectiveHandler() {
290 weak_ptr_factory_.InvalidateWeakPtrs();
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(),
305 DROP_AFTER_PROCESSING)),
310 void DeleteDirectiveHandler::Stop() {
311 DCHECK(thread_checker_.CalledOnValidThread());
312 sync_processor_.reset();
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;
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);
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;
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);
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);
347 syncer::SyncError error = ProcessLocalDeleteDirective(delete_directive);
348 return !error.IsSet();
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(
357 syncer::SyncError::DATATYPE_ERROR,
358 "Cannot send local delete directive to sync",
359 syncer::HISTORY_DELETE_DIRECTIVES);
362 CheckDeleteDirectiveValid(delete_directive);
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(
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);
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(
387 syncer::SyncError::DATATYPE_ERROR,
389 syncer::HISTORY_DELETE_DIRECTIVES);
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());
399 case syncer::SyncChange::ACTION_DELETE:
400 // TODO(akalin): Keep track of existing delete directives.
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(),
416 KEEP_AFTER_PROCESSING)),
419 return syncer::SyncError();
422 void DeleteDirectiveHandler::FinishProcessing(
423 PostProcessingAction post_processing_action,
424 const syncer::SyncDataList& delete_directives) {
425 DCHECK(thread_checker_.CalledOnValidThread());
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]));
437 sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
441 } // namespace history