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(OS_ANDROID)
     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   explicit DownloadHistoryData(content::DownloadItem* item)
     67     : state_(NOT_PERSISTED) {
     68     item->SetUserData(kKey, this);
     69   }
     70 
     71   virtual ~DownloadHistoryData() {
     72   }
     73 
     74   PersistenceState state() const { return state_; }
     75   void SetState(PersistenceState s) { state_ = s; }
     76 
     77   // This allows DownloadHistory::OnDownloadUpdated() to see what changed in a
     78   // DownloadItem if anything, in order to prevent writing to the database
     79   // unnecessarily.  It is nullified when the item is no longer in progress in
     80   // order to save memory.
     81   history::DownloadRow* info() { return info_.get(); }
     82   void set_info(const history::DownloadRow& i) {
     83     info_.reset(new history::DownloadRow(i));
     84   }
     85   void clear_info() {
     86     info_.reset();
     87   }
     88 
     89  private:
     90   static const char kKey[];
     91 
     92   PersistenceState state_;
     93   scoped_ptr<history::DownloadRow> info_;
     94 
     95   DISALLOW_COPY_AND_ASSIGN(DownloadHistoryData);
     96 };
     97 
     98 const char DownloadHistoryData::kKey[] =
     99   "DownloadItem DownloadHistoryData";
    100 
    101 history::DownloadRow GetDownloadRow(
    102     content::DownloadItem* item) {
    103   std::string by_ext_id, by_ext_name;
    104 #if !defined(OS_ANDROID)
    105   DownloadedByExtension* by_ext = DownloadedByExtension::Get(item);
    106   if (by_ext) {
    107     by_ext_id = by_ext->id();
    108     by_ext_name = by_ext->name();
    109   }
    110 #endif
    111 
    112   return history::DownloadRow(
    113       item->GetFullPath(),
    114       item->GetTargetFilePath(),
    115       item->GetUrlChain(),
    116       item->GetReferrerUrl(),
    117       item->GetStartTime(),
    118       item->GetEndTime(),
    119       item->GetETag(),
    120       item->GetLastModifiedTime(),
    121       item->GetReceivedBytes(),
    122       item->GetTotalBytes(),
    123       item->GetState(),
    124       item->GetDangerType(),
    125       item->GetLastReason(),
    126       item->GetId(),
    127       item->GetOpened(),
    128       by_ext_id,
    129       by_ext_name);
    130 }
    131 
    132 bool ShouldUpdateHistory(const history::DownloadRow* previous,
    133                          const history::DownloadRow& current) {
    134   // Ignore url, referrer, start_time, id, which don't change.
    135   return ((previous == NULL) ||
    136           (previous->current_path != current.current_path) ||
    137           (previous->target_path != current.target_path) ||
    138           (previous->end_time != current.end_time) ||
    139           (previous->received_bytes != current.received_bytes) ||
    140           (previous->total_bytes != current.total_bytes) ||
    141           (previous->etag != current.etag) ||
    142           (previous->last_modified != current.last_modified) ||
    143           (previous->state != current.state) ||
    144           (previous->danger_type != current.danger_type) ||
    145           (previous->interrupt_reason != current.interrupt_reason) ||
    146           (previous->opened != current.opened) ||
    147           (previous->by_ext_id != current.by_ext_id) ||
    148           (previous->by_ext_name != current.by_ext_name));
    149 }
    150 
    151 typedef std::vector<history::DownloadRow> InfoVector;
    152 
    153 }  // anonymous namespace
    154 
    155 DownloadHistory::HistoryAdapter::HistoryAdapter(HistoryService* history)
    156   : history_(history) {
    157 }
    158 DownloadHistory::HistoryAdapter::~HistoryAdapter() {}
    159 
    160 void DownloadHistory::HistoryAdapter::QueryDownloads(
    161     const HistoryService::DownloadQueryCallback& callback) {
    162   history_->QueryDownloads(callback);
    163 }
    164 
    165 void DownloadHistory::HistoryAdapter::CreateDownload(
    166     const history::DownloadRow& info,
    167     const HistoryService::DownloadCreateCallback& callback) {
    168   history_->CreateDownload(info, callback);
    169 }
    170 
    171 void DownloadHistory::HistoryAdapter::UpdateDownload(
    172     const history::DownloadRow& data) {
    173   history_->UpdateDownload(data);
    174 }
    175 
    176 void DownloadHistory::HistoryAdapter::RemoveDownloads(
    177     const std::set<uint32>& ids) {
    178   history_->RemoveDownloads(ids);
    179 }
    180 
    181 
    182 DownloadHistory::Observer::Observer() {}
    183 DownloadHistory::Observer::~Observer() {}
    184 
    185 bool DownloadHistory::IsPersisted(content::DownloadItem* item) {
    186   DownloadHistoryData* data = DownloadHistoryData::Get(item);
    187   return data && (data->state() == DownloadHistoryData::PERSISTED);
    188 }
    189 
    190 DownloadHistory::DownloadHistory(content::DownloadManager* manager,
    191                                  scoped_ptr<HistoryAdapter> history)
    192   : notifier_(manager, this),
    193     history_(history.Pass()),
    194     loading_id_(content::DownloadItem::kInvalidId),
    195     history_size_(0),
    196     weak_ptr_factory_(this) {
    197   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    198   content::DownloadManager::DownloadVector items;
    199   notifier_.GetManager()->GetAllDownloads(&items);
    200   for (content::DownloadManager::DownloadVector::const_iterator
    201        it = items.begin(); it != items.end(); ++it) {
    202     OnDownloadCreated(notifier_.GetManager(), *it);
    203   }
    204   history_->QueryDownloads(base::Bind(
    205       &DownloadHistory::QueryCallback, weak_ptr_factory_.GetWeakPtr()));
    206 }
    207 
    208 DownloadHistory::~DownloadHistory() {
    209   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    210   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadHistoryDestroyed());
    211   observers_.Clear();
    212 }
    213 
    214 void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) {
    215   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    216   observers_.AddObserver(observer);
    217 }
    218 
    219 void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) {
    220   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    221   observers_.RemoveObserver(observer);
    222 }
    223 
    224 void DownloadHistory::QueryCallback(scoped_ptr<InfoVector> infos) {
    225   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    226   // ManagerGoingDown() may have happened before the history loaded.
    227   if (!notifier_.GetManager())
    228     return;
    229   for (InfoVector::const_iterator it = infos->begin();
    230        it != infos->end(); ++it) {
    231     loading_id_ = it->id;
    232     content::DownloadItem* item = notifier_.GetManager()->CreateDownloadItem(
    233         loading_id_,
    234         it->current_path,
    235         it->target_path,
    236         it->url_chain,
    237         it->referrer_url,
    238         it->start_time,
    239         it->end_time,
    240         it->etag,
    241         it->last_modified,
    242         it->received_bytes,
    243         it->total_bytes,
    244         it->state,
    245         it->danger_type,
    246         it->interrupt_reason,
    247         it->opened);
    248 #if !defined(OS_ANDROID)
    249     if (!it->by_ext_id.empty() && !it->by_ext_name.empty()) {
    250       new DownloadedByExtension(item, it->by_ext_id, it->by_ext_name);
    251       item->UpdateObservers();
    252     }
    253 #endif
    254     DCHECK_EQ(DownloadHistoryData::Get(item)->state(),
    255               DownloadHistoryData::PERSISTED);
    256     ++history_size_;
    257   }
    258   notifier_.GetManager()->CheckForHistoryFilesRemoval();
    259 }
    260 
    261 void DownloadHistory::MaybeAddToHistory(content::DownloadItem* item) {
    262   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    263 
    264   uint32 download_id = item->GetId();
    265   DownloadHistoryData* data = DownloadHistoryData::Get(item);
    266   bool removing = removing_ids_.find(download_id) != removing_ids_.end();
    267 
    268   // TODO(benjhayden): Remove IsTemporary().
    269   if (download_crx_util::IsExtensionDownload(*item) ||
    270       item->IsTemporary() ||
    271       (data->state() != DownloadHistoryData::NOT_PERSISTED) ||
    272       removing)
    273     return;
    274 
    275   data->SetState(DownloadHistoryData::PERSISTING);
    276   if (data->info() == NULL) {
    277     // Keep the info here regardless of whether the item is in progress so that,
    278     // when ItemAdded() calls OnDownloadUpdated(), it can decide whether to
    279     // Update the db and/or clear the info.
    280     data->set_info(GetDownloadRow(item));
    281   }
    282 
    283   history_->CreateDownload(*data->info(), base::Bind(
    284       &DownloadHistory::ItemAdded, weak_ptr_factory_.GetWeakPtr(),
    285       download_id));
    286   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(
    287       item, *data->info()));
    288 }
    289 
    290 void DownloadHistory::ItemAdded(uint32 download_id, bool success) {
    291   if (removed_while_adding_.find(download_id) !=
    292       removed_while_adding_.end()) {
    293     removed_while_adding_.erase(download_id);
    294     if (success)
    295       ScheduleRemoveDownload(download_id);
    296     return;
    297   }
    298 
    299   if (!notifier_.GetManager())
    300     return;
    301 
    302   content::DownloadItem* item = notifier_.GetManager()->GetDownload(
    303       download_id);
    304   if (!item) {
    305     // This item will have called OnDownloadDestroyed().  If the item should
    306     // have been removed from history, then it would have also called
    307     // OnDownloadRemoved(), which would have put |download_id| in
    308     // removed_while_adding_, handled above.
    309     return;
    310   }
    311 
    312   DownloadHistoryData* data = DownloadHistoryData::Get(item);
    313 
    314   // The sql INSERT statement failed. Avoid an infinite loop: don't
    315   // automatically retry. Retry adding the next time the item is updated by
    316   // resetting the state to NOT_PERSISTED.
    317   if (!success) {
    318     DVLOG(20) << __FUNCTION__ << " INSERT failed id=" << download_id;
    319     data->SetState(DownloadHistoryData::NOT_PERSISTED);
    320     return;
    321   }
    322   data->SetState(DownloadHistoryData::PERSISTED);
    323 
    324   UMA_HISTOGRAM_CUSTOM_COUNTS("Download.HistorySize2",
    325                               history_size_,
    326                               0/*min*/,
    327                               (1 << 23)/*max*/,
    328                               (1 << 7)/*num_buckets*/);
    329   ++history_size_;
    330 
    331   // In case the item changed or became temporary while it was being added.
    332   // Don't just update all of the item's observers because we're the only
    333   // observer that can also see data->state(), which is the only thing that
    334   // ItemAdded() changed.
    335   OnDownloadUpdated(notifier_.GetManager(), item);
    336 }
    337 
    338 void DownloadHistory::OnDownloadCreated(
    339     content::DownloadManager* manager, content::DownloadItem* item) {
    340   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    341 
    342   // All downloads should pass through OnDownloadCreated exactly once.
    343   CHECK(!DownloadHistoryData::Get(item));
    344   DownloadHistoryData* data = new DownloadHistoryData(item);
    345   if (item->GetId() == loading_id_) {
    346     data->SetState(DownloadHistoryData::PERSISTED);
    347     loading_id_ = content::DownloadItem::kInvalidId;
    348   }
    349   if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
    350     data->set_info(GetDownloadRow(item));
    351   }
    352   MaybeAddToHistory(item);
    353 }
    354 
    355 void DownloadHistory::OnDownloadUpdated(
    356     content::DownloadManager* manager, content::DownloadItem* item) {
    357   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    358 
    359   DownloadHistoryData* data = DownloadHistoryData::Get(item);
    360   if (data->state() == DownloadHistoryData::NOT_PERSISTED) {
    361     MaybeAddToHistory(item);
    362     return;
    363   }
    364   if (item->IsTemporary()) {
    365     OnDownloadRemoved(notifier_.GetManager(), item);
    366     return;
    367   }
    368 
    369   history::DownloadRow current_info(GetDownloadRow(item));
    370   bool should_update = ShouldUpdateHistory(data->info(), current_info);
    371   UMA_HISTOGRAM_ENUMERATION("Download.HistoryPropagatedUpdate",
    372                             should_update, 2);
    373   if (should_update) {
    374     history_->UpdateDownload(current_info);
    375     FOR_EACH_OBSERVER(Observer, observers_, OnDownloadStored(
    376         item, current_info));
    377   }
    378   if (item->GetState() == content::DownloadItem::IN_PROGRESS) {
    379     data->set_info(current_info);
    380   } else {
    381     data->clear_info();
    382   }
    383 }
    384 
    385 void DownloadHistory::OnDownloadOpened(
    386     content::DownloadManager* manager, content::DownloadItem* item) {
    387   OnDownloadUpdated(manager, item);
    388 }
    389 
    390 void DownloadHistory::OnDownloadRemoved(
    391     content::DownloadManager* manager, content::DownloadItem* item) {
    392   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    393 
    394   DownloadHistoryData* data = DownloadHistoryData::Get(item);
    395   if (data->state() != DownloadHistoryData::PERSISTED) {
    396     if (data->state() == DownloadHistoryData::PERSISTING) {
    397       // ScheduleRemoveDownload will be called when history_ calls ItemAdded().
    398       removed_while_adding_.insert(item->GetId());
    399     }
    400     return;
    401   }
    402   ScheduleRemoveDownload(item->GetId());
    403   // This is important: another OnDownloadRemoved() handler could do something
    404   // that synchronously fires an OnDownloadUpdated().
    405   data->SetState(DownloadHistoryData::NOT_PERSISTED);
    406   // ItemAdded increments history_size_ only if the item wasn't
    407   // removed_while_adding_, so the next line does not belong in
    408   // ScheduleRemoveDownload().
    409   --history_size_;
    410 }
    411 
    412 void DownloadHistory::ScheduleRemoveDownload(uint32 download_id) {
    413   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    414 
    415   // For database efficiency, batch removals together if they happen all at
    416   // once.
    417   if (removing_ids_.empty()) {
    418     content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
    419         base::Bind(&DownloadHistory::RemoveDownloadsBatch,
    420                    weak_ptr_factory_.GetWeakPtr()));
    421   }
    422   removing_ids_.insert(download_id);
    423 }
    424 
    425 void DownloadHistory::RemoveDownloadsBatch() {
    426   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    427   IdSet remove_ids;
    428   removing_ids_.swap(remove_ids);
    429   history_->RemoveDownloads(remove_ids);
    430   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadsRemoved(remove_ids));
    431 }
    432