Home | History | Annotate | Download | only in history
      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