Home | History | Annotate | Download | only in download
      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 // DownloadHistory manages persisting DownloadItems to the history service by
      6 // observing a single DownloadManager and all its DownloadItems using an
      7 // AllDownloadItemNotifier.
      8 //
      9 // DownloadHistory decides whether and when to add items to, remove items from,
     10 // and update items in the database. DownloadHistory uses DownloadHistoryData to
     11 // store per-DownloadItem data such as whether the item is persisted or being
     12 // persisted, and the last history::DownloadRow that was passed to the database.
     13 // When the DownloadManager and its delegate (ChromeDownloadManagerDelegate) are
     14 // initialized, DownloadHistory is created and queries the HistoryService. When
     15 // the HistoryService calls back from QueryDownloads() to QueryCallback(),
     16 // DownloadHistory uses DownloadManager::CreateDownloadItem() to inform
     17 // DownloadManager of these persisted DownloadItems. CreateDownloadItem()
     18 // internally calls OnDownloadCreated(), which normally adds items to the
     19 // database, so QueryCallback() uses |loading_id_| to disable adding these items
     20 // to the database.  If a download is removed via OnDownloadRemoved() while the
     21 // item is still being added to the database, DownloadHistory uses
     22 // |removed_while_adding_| to remember to remove the item when its ItemAdded()
     23 // callback is called.  All callbacks are bound with a weak pointer to
     24 // DownloadHistory to prevent use-after-free bugs.
     25 // ChromeDownloadManagerDelegate owns DownloadHistory, and deletes it in
     26 // Shutdown(), which is called by DownloadManagerImpl::Shutdown() after all
     27 // DownloadItems are destroyed.
     28 
     29 #include "chrome/browser/download/download_history.h"
     30 
     31 #include "base/metrics/histogram.h"
     32 #include "chrome/browser/download/download_crx_util.h"
     33 #include "chrome/browser/history/download_database.h"
     34 #include "chrome/browser/history/download_row.h"
     35 #include "chrome/browser/history/history_service.h"
     36 #include "content/public/browser/browser_thread.h"
     37 #include "content/public/browser/download_item.h"
     38 #include "content/public/browser/download_manager.h"
     39 
     40 #if defined(ENABLE_EXTENSIONS)
     41 #include "chrome/browser/extensions/api/downloads/downloads_api.h"
     42 #endif
     43 
     44 namespace {
     45 
     46 // Per-DownloadItem data. This information does not belong inside DownloadItem,
     47 // and keeping maps in DownloadHistory from DownloadItem to this information is
     48 // error-prone and complicated. Unfortunately, DownloadHistory::removing_*_ and
     49 // removed_while_adding_ cannot be moved into this class partly because
     50 // DownloadHistoryData is destroyed when DownloadItems are destroyed, and we
     51 // have no control over when DownloadItems are destroyed.
     52 class DownloadHistoryData : public base::SupportsUserData::Data {
     53  public:
     54   enum PersistenceState {
     55     NOT_PERSISTED,
     56     PERSISTING,
     57     PERSISTED,
     58   };
     59 
     60   static DownloadHistoryData* Get(content::DownloadItem* item) {
     61     base::SupportsUserData::Data* data = item->GetUserData(kKey);
     62     return (data == NULL) ? NULL :
     63       static_cast<DownloadHistoryData*>(data);
     64   }
     65 
     66   static const DownloadHistoryData* Get(const content::DownloadItem* item) {
     67     const base::SupportsUserData::Data* data = item->GetUserData(kKey);
     68     return (data == NULL) ? NULL
     69                           : static_cast<const DownloadHistoryData*>(data);
     70   }
     71 
     72   explicit DownloadHistoryData(content::DownloadItem* item)
     73       : state_(NOT_PERSISTED),
     74         was_restored_from_history_(false) {
     75     item->SetUserData(kKey, this);
     76   }
     77 
     78   virtual ~DownloadHistoryData() {
     79   }
     80 
     81   PersistenceState state() const { return state_; }
     82   void SetState(PersistenceState s) { state_ = s; }
     83 
     84   bool was_restored_from_history() const { return was_restored_from_history_; }
     85   void set_was_restored_from_history(bool value) {
     86     was_restored_from_history_ = value;
     87   }
     88 
     89   // This allows DownloadHistory::OnDownloadUpdated() to see what changed in a
     90   // DownloadItem if anything, in order to prevent writing to the database
     91   // unnecessarily.  It is nullified when the item is no longer in progress in
     92   // order to save memory.
     93   history::DownloadRow* info() { return info_.get(); }
     94   void set_info(const history::DownloadRow& i) {
     95     info_.reset(new history::DownloadRow(i));
     96   }
     97   void clear_info() {
     98     info_.reset();
     99   }
    100 
    101  private:
    102   static const char kKey[];
    103 
    104   PersistenceState state_;
    105   scoped_ptr<history::DownloadRow> info_;
    106   bool was_restored_from_history_;
    107 
    108   DISALLOW_COPY_AND_ASSIGN(DownloadHistoryData);
    109 };
    110 
    111 const char DownloadHistoryData::kKey[] =
    112   "DownloadItem DownloadHistoryData";
    113 
    114 history::DownloadRow GetDownloadRow(
    115     content::DownloadItem* item) {
    116   std::string by_ext_id, by_ext_name;
    117 #if defined(ENABLE_EXTENSIONS)
    118   extensions::DownloadedByExtension* by_ext =
    119       extensions::DownloadedByExtension::Get(item);
    120   if (by_ext) {
    121     by_ext_id = by_ext->id();
    122     by_ext_name = by_ext->name();
    123   }
    124 #endif
    125 
    126   return history::DownloadRow(
    127       item->GetFullPath(),
    128       item->GetTargetFilePath(),
    129       item->GetUrlChain(),
    130       item->GetReferrerUrl(),
    131       item->GetMimeType(),
    132       item->GetOriginalMimeType(),
    133       item->GetStartTime(),
    134       item->GetEndTime(),
    135       item->GetETag(),
    136       item->GetLastModifiedTime(),
    137       item->GetReceivedBytes(),
    138       item->GetTotalBytes(),
    139       item->GetState(),
    140       item->GetDangerType(),
    141       item->GetLastReason(),
    142       item->GetId(),
    143       item->GetOpened(),
    144       by_ext_id,
    145       by_ext_name);
    146 }
    147 
    148 bool ShouldUpdateHistory(const history::DownloadRow* previous,
    149                          const history::DownloadRow& current) {
    150   // Ignore url, referrer, mime_type, original_mime_type, start_time,
    151   // id, db_handle, which don't change.
    152   return ((previous == NULL) ||
    153           (previous->current_path != current.current_path) ||
    154           (previous->target_path != current.target_path) ||
    155           (previous->end_time != current.end_time) ||
    156           (previous->received_bytes != current.received_bytes) ||
    157           (previous->total_bytes != current.total_bytes) ||
    158           (previous->etag != current.etag) ||
    159           (previous->last_modified != current.last_modified) ||
    160           (previous->state != current.state) ||
    161           (previous->danger_type != current.danger_type) ||
    162           (previous->interrupt_reason != current.interrupt_reason) ||
    163           (previous->opened != current.opened) ||
    164           (previous->by_ext_id != current.by_ext_id) ||
    165           (previous->by_ext_name != current.by_ext_name));
    166 }
    167 
    168 typedef std::vector<history::DownloadRow> InfoVector;
    169 
    170 }  // anonymous namespace
    171 
    172 DownloadHistory::HistoryAdapter::HistoryAdapter(HistoryService* history)
    173   : history_(history) {
    174 }
    175 DownloadHistory::HistoryAdapter::~HistoryAdapter() {}
    176 
    177 void DownloadHistory::HistoryAdapter::QueryDownloads(
    178     const HistoryService::DownloadQueryCallback& callback) {
    179   history_->QueryDownloads(callback);
    180 }
    181 
    182 void DownloadHistory::HistoryAdapter::CreateDownload(
    183     const history::DownloadRow& info,
    184     const HistoryService::DownloadCreateCallback& callback) {
    185   history_->CreateDownload(info, callback);
    186 }
    187 
    188 void DownloadHistory::HistoryAdapter::UpdateDownload(
    189     const history::DownloadRow& data) {
    190   history_->UpdateDownload(data);
    191 }
    192 
    193 void DownloadHistory::HistoryAdapter::RemoveDownloads(
    194     const std::set<uint32>& ids) {
    195   history_->RemoveDownloads(ids);
    196 }
    197 
    198 DownloadHistory::Observer::Observer() {}
    199 DownloadHistory::Observer::~Observer() {}
    200 
    201 // static
    202 bool DownloadHistory::IsPersisted(const content::DownloadItem* item) {
    203   const DownloadHistoryData* data = DownloadHistoryData::Get(item);
    204   return data && (data->state() == DownloadHistoryData::PERSISTED);
    205 }
    206 
    207 DownloadHistory::DownloadHistory(content::DownloadManager* manager,
    208                                  scoped_ptr<HistoryAdapter> history)
    209   : notifier_(manager, this),
    210     history_(history.Pass()),
    211     loading_id_(content::DownloadItem::kInvalidId),
    212     history_size_(0),
    213     weak_ptr_factory_(this) {
    214   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    215   content::DownloadManager::DownloadVector items;
    216   notifier_.GetManager()->GetAllDownloads(&items);
    217   for (content::DownloadManager::DownloadVector::const_iterator
    218        it = items.begin(); it != items.end(); ++it) {
    219     OnDownloadCreated(notifier_.GetManager(), *it);
    220   }
    221   history_->QueryDownloads(base::Bind(
    222       &DownloadHistory::QueryCallback, weak_ptr_factory_.GetWeakPtr()));
    223 }
    224 
    225 DownloadHistory::~DownloadHistory() {
    226   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    227   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadHistoryDestroyed());
    228   observers_.Clear();
    229 }
    230 
    231 void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) {
    232   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    233   observers_.AddObserver(observer);
    234 }
    235 
    236 void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) {
    237   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    238   observers_.RemoveObserver(observer);
    239 }
    240 
    241 bool DownloadHistory::WasRestoredFromHistory(
    242     const content::DownloadItem* download) const {
    243   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    244   const DownloadHistoryData* data = DownloadHistoryData::Get(download);
    245 
    246   // The OnDownloadCreated handler sets the was_restored_from_history flag when
    247   // resetting the loading_id_. So one of the two conditions below will hold for
    248   // a download restored from history even if the caller of this method is
    249   // racing with our OnDownloadCreated handler.
    250   return (data && data->was_restored_from_history()) ||
    251          download->GetId() == loading_id_;
    252 }
    253 
    254 void DownloadHistory::QueryCallback(scoped_ptr<InfoVector> infos) {
    255   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    256   // ManagerGoingDown() may have happened before the history loaded.
    257   if (!notifier_.GetManager())
    258     return;
    259   for (InfoVector::const_iterator it = infos->begin();
    260        it != infos->end(); ++it) {
    261     loading_id_ = it->id;
    262     content::DownloadItem* item = notifier_.GetManager()->CreateDownloadItem(
    263         loading_id_,
    264         it->current_path,
    265         it->target_path,
    266         it->url_chain,
    267         it->referrer_url,
    268         it->mime_type,
    269         it->original_mime_type,
    270         it->start_time,
    271         it->end_time,
    272         it->etag,
    273         it->last_modified,
    274         it->received_bytes,
    275         it->total_bytes,
    276         it->state,
    277         it->danger_type,
    278         it->interrupt_reason,
    279         it->opened);
    280 #if defined(ENABLE_EXTENSIONS)
    281     if (!it->by_ext_id.empty() && !it->by_ext_name.empty()) {
    282       new extensions::DownloadedByExtension(
    283           item, it->by_ext_id, it->by_ext_name);
    284       item->UpdateObservers();
    285     }
    286 #endif
    287     DCHECK_EQ(DownloadHistoryData::PERSISTED,
    288               DownloadHistoryData::Get(item)->state());
    289     ++history_size_;
    290   }
    291   notifier_.GetManager()->CheckForHistoryFilesRemoval();
    292 }
    293 
    294 void DownloadHistory::MaybeAddToHistory(content::DownloadItem* item) {
    295   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    296 
    297   uint32 download_id = item->GetId();
    298   DownloadHistoryData* data = DownloadHistoryData::Get(item);
    299   bool removing = removing_ids_.find(download_id) != removing_ids_.end();
    300 
    301   // TODO(benjhayden): Remove IsTemporary().
    302   if (download_crx_util::IsExtensionDownload(*item) ||
    303       item->IsTemporary() ||
    304       (data->state() != DownloadHistoryData::NOT_PERSISTED) ||
    305       removing)
    306     return;
    307 
    308   data->SetState(DownloadHistoryData::PERSISTING);
    309   if (data->info() == NULL) {
    310     // Keep the info here regardless of whether the item is in progress so that,
    311     // when ItemAdded() calls OnDownloadUpdated(), it can decide whether to
    312     // Update the db and/or clear the info.
    313     data->set_info(GetDownloadRow(item));
    314   }
    315 
    316   history_->CreateDownload(*data->info(), base::Bind(
    317       &DownloadHistory::ItemAdded, weak_ptr_factory_.GetWeakPtr(),
    318       download_id));
    319   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(
    320       item, *data->info()));
    321 }
    322 
    323 void DownloadHistory::ItemAdded(uint32 download_id, bool success) {
    324   if (removed_while_adding_.find(download_id) !=
    325       removed_while_adding_.end()) {
    326     removed_while_adding_.erase(download_id);
    327     if (success)
    328       ScheduleRemoveDownload(download_id);
    329     return;
    330   }
    331 
    332   if (!notifier_.GetManager())
    333     return;
    334 
    335   content::DownloadItem* item = notifier_.GetManager()->GetDownload(
    336       download_id);
    337   if (!item) {
    338     // This item will have called OnDownloadDestroyed().  If the item should
    339     // have been removed from history, then it would have also called
    340     // OnDownloadRemoved(), which would have put |download_id| in
    341     // removed_while_adding_, handled above.
    342     return;
    343   }
    344 
    345   DownloadHistoryData* data = DownloadHistoryData::Get(item);
    346 
    347   // The sql INSERT statement failed. Avoid an infinite loop: don't
    348   // automatically retry. Retry adding the next time the item is updated by
    349   // resetting the state to NOT_PERSISTED.
    350   if (!success) {
    351     DVLOG(20) << __FUNCTION__ << " INSERT failed id=" << download_id;
    352     data->SetState(DownloadHistoryData::NOT_PERSISTED);
    353     return;
    354   }
    355   data->SetState(DownloadHistoryData::PERSISTED);
    356 
    357   UMA_HISTOGRAM_CUSTOM_COUNTS("Download.HistorySize2",
    358                               history_size_,
    359                               0/*min*/,
    360                               (1 << 23)/*max*/,
    361                               (1 << 7)/*num_buckets*/);
    362   ++history_size_;
    363 
    364   // In case the item changed or became temporary while it was being added.
    365   // Don't just update all of the item's observers because we're the only
    366   // observer that can also see data->state(), which is the only thing that
    367   // ItemAdded() changed.
    368   OnDownloadUpdated(notifier_.GetManager(), item);
    369 }
    370 
    371 void DownloadHistory::OnDownloadCreated(
    372     content::DownloadManager* manager, content::DownloadItem* item) {
    373   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    374 
    375   // All downloads should pass through OnDownloadCreated exactly once.
    376   CHECK(!DownloadHistoryData::Get(item));
    377   DownloadHistoryData* data = new DownloadHistoryData(item);
    378   if (item->GetId() == loading_id_) {
    379     data->SetState(DownloadHistoryData::PERSISTED);
    380     data->set_was_restored_from_history(true);
    381     loading_id_ = content::DownloadItem::kInvalidId;
    382   }
    383   if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
    384     data->set_info(GetDownloadRow(item));
    385   }
    386   MaybeAddToHistory(item);
    387 }
    388 
    389 void DownloadHistory::OnDownloadUpdated(
    390     content::DownloadManager* manager, content::DownloadItem* item) {
    391   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    392 
    393   DownloadHistoryData* data = DownloadHistoryData::Get(item);
    394   if (data->state() == DownloadHistoryData::NOT_PERSISTED) {
    395     MaybeAddToHistory(item);
    396     return;
    397   }
    398   if (item->IsTemporary()) {
    399     OnDownloadRemoved(notifier_.GetManager(), item);
    400     return;
    401   }
    402 
    403   history::DownloadRow current_info(GetDownloadRow(item));
    404   bool should_update = ShouldUpdateHistory(data->info(), current_info);
    405   UMA_HISTOGRAM_ENUMERATION("Download.HistoryPropagatedUpdate",
    406                             should_update, 2);
    407   if (should_update) {
    408     history_->UpdateDownload(current_info);
    409     FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(
    410         item, current_info));
    411   }
    412   if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
    413     data->set_info(current_info);
    414   } else {
    415     data->clear_info();
    416   }
    417 }
    418 
    419 void DownloadHistory::OnDownloadOpened(
    420     content::DownloadManager* manager, content::DownloadItem* item) {
    421   OnDownloadUpdated(manager, item);
    422 }
    423 
    424 void DownloadHistory::OnDownloadRemoved(
    425     content::DownloadManager* manager, content::DownloadItem* item) {
    426   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    427 
    428   DownloadHistoryData* data = DownloadHistoryData::Get(item);
    429   if (data->state() != DownloadHistoryData::PERSISTED) {
    430     if (data->state() == DownloadHistoryData::PERSISTING) {
    431       // ScheduleRemoveDownload will be called when history_ calls ItemAdded().
    432       removed_while_adding_.insert(item->GetId());
    433     }
    434     return;
    435   }
    436   ScheduleRemoveDownload(item->GetId());
    437   // This is important: another OnDownloadRemoved() handler could do something
    438   // that synchronously fires an OnDownloadUpdated().
    439   data->SetState(DownloadHistoryData::NOT_PERSISTED);
    440   // ItemAdded increments history_size_ only if the item wasn't
    441   // removed_while_adding_, so the next line does not belong in
    442   // ScheduleRemoveDownload().
    443   --history_size_;
    444 }
    445 
    446 void DownloadHistory::ScheduleRemoveDownload(uint32 download_id) {
    447   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    448 
    449   // For database efficiency, batch removals together if they happen all at
    450   // once.
    451   if (removing_ids_.empty()) {
    452     content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
    453         base::Bind(&DownloadHistory::RemoveDownloadsBatch,
    454                    weak_ptr_factory_.GetWeakPtr()));
    455   }
    456   removing_ids_.insert(download_id);
    457 }
    458 
    459 void DownloadHistory::RemoveDownloadsBatch() {
    460   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    461   IdSet remove_ids;
    462   removing_ids_.swap(remove_ids);
    463   history_->RemoveDownloads(remove_ids);
    464   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadsRemoved(remove_ids));
    465 }
    466