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