• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • 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 
Get(content::DownloadItem * item)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 
Get(const content::DownloadItem * item)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 
DownloadHistoryData(content::DownloadItem * item)72   explicit DownloadHistoryData(content::DownloadItem* item)
73       : state_(NOT_PERSISTED),
74         was_restored_from_history_(false) {
75     item->SetUserData(kKey, this);
76   }
77 
~DownloadHistoryData()78   virtual ~DownloadHistoryData() {
79   }
80 
state() const81   PersistenceState state() const { return state_; }
SetState(PersistenceState s)82   void SetState(PersistenceState s) { state_ = s; }
83 
was_restored_from_history() const84   bool was_restored_from_history() const { return was_restored_from_history_; }
set_was_restored_from_history(bool value)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.
info()93   history::DownloadRow* info() { return info_.get(); }
set_info(const history::DownloadRow & i)94   void set_info(const history::DownloadRow& i) {
95     info_.reset(new history::DownloadRow(i));
96   }
clear_info()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 
GetDownloadRow(content::DownloadItem * item)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 
ShouldUpdateHistory(const history::DownloadRow * previous,const history::DownloadRow & current)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 
HistoryAdapter(HistoryService * history)172 DownloadHistory::HistoryAdapter::HistoryAdapter(HistoryService* history)
173   : history_(history) {
174 }
~HistoryAdapter()175 DownloadHistory::HistoryAdapter::~HistoryAdapter() {}
176 
QueryDownloads(const HistoryService::DownloadQueryCallback & callback)177 void DownloadHistory::HistoryAdapter::QueryDownloads(
178     const HistoryService::DownloadQueryCallback& callback) {
179   history_->QueryDownloads(callback);
180 }
181 
CreateDownload(const history::DownloadRow & info,const HistoryService::DownloadCreateCallback & callback)182 void DownloadHistory::HistoryAdapter::CreateDownload(
183     const history::DownloadRow& info,
184     const HistoryService::DownloadCreateCallback& callback) {
185   history_->CreateDownload(info, callback);
186 }
187 
UpdateDownload(const history::DownloadRow & data)188 void DownloadHistory::HistoryAdapter::UpdateDownload(
189     const history::DownloadRow& data) {
190   history_->UpdateDownload(data);
191 }
192 
RemoveDownloads(const std::set<uint32> & ids)193 void DownloadHistory::HistoryAdapter::RemoveDownloads(
194     const std::set<uint32>& ids) {
195   history_->RemoveDownloads(ids);
196 }
197 
Observer()198 DownloadHistory::Observer::Observer() {}
~Observer()199 DownloadHistory::Observer::~Observer() {}
200 
201 // static
IsPersisted(const content::DownloadItem * item)202 bool DownloadHistory::IsPersisted(const content::DownloadItem* item) {
203   const DownloadHistoryData* data = DownloadHistoryData::Get(item);
204   return data && (data->state() == DownloadHistoryData::PERSISTED);
205 }
206 
DownloadHistory(content::DownloadManager * manager,scoped_ptr<HistoryAdapter> history)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 
~DownloadHistory()225 DownloadHistory::~DownloadHistory() {
226   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
227   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadHistoryDestroyed());
228   observers_.Clear();
229 }
230 
AddObserver(DownloadHistory::Observer * observer)231 void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) {
232   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
233   observers_.AddObserver(observer);
234 }
235 
RemoveObserver(DownloadHistory::Observer * observer)236 void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) {
237   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
238   observers_.RemoveObserver(observer);
239 }
240 
WasRestoredFromHistory(const content::DownloadItem * download) const241 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 
QueryCallback(scoped_ptr<InfoVector> infos)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 
MaybeAddToHistory(content::DownloadItem * item)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 
ItemAdded(uint32 download_id,bool success)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 
OnDownloadCreated(content::DownloadManager * manager,content::DownloadItem * item)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 
OnDownloadUpdated(content::DownloadManager * manager,content::DownloadItem * item)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 
OnDownloadOpened(content::DownloadManager * manager,content::DownloadItem * item)419 void DownloadHistory::OnDownloadOpened(
420     content::DownloadManager* manager, content::DownloadItem* item) {
421   OnDownloadUpdated(manager, item);
422 }
423 
OnDownloadRemoved(content::DownloadManager * manager,content::DownloadItem * item)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 
ScheduleRemoveDownload(uint32 download_id)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 
RemoveDownloadsBatch()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