• 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(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 
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 
DownloadHistoryData(content::DownloadItem * item)66   explicit DownloadHistoryData(content::DownloadItem* item)
67     : state_(NOT_PERSISTED) {
68     item->SetUserData(kKey, this);
69   }
70 
~DownloadHistoryData()71   virtual ~DownloadHistoryData() {
72   }
73 
state() const74   PersistenceState state() const { return state_; }
SetState(PersistenceState s)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.
info()81   history::DownloadRow* info() { return info_.get(); }
set_info(const history::DownloadRow & i)82   void set_info(const history::DownloadRow& i) {
83     info_.reset(new history::DownloadRow(i));
84   }
clear_info()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 
GetDownloadRow(content::DownloadItem * item)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 
ShouldUpdateHistory(const history::DownloadRow * previous,const history::DownloadRow & current)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 
HistoryAdapter(HistoryService * history)155 DownloadHistory::HistoryAdapter::HistoryAdapter(HistoryService* history)
156   : history_(history) {
157 }
~HistoryAdapter()158 DownloadHistory::HistoryAdapter::~HistoryAdapter() {}
159 
QueryDownloads(const HistoryService::DownloadQueryCallback & callback)160 void DownloadHistory::HistoryAdapter::QueryDownloads(
161     const HistoryService::DownloadQueryCallback& callback) {
162   history_->QueryDownloads(callback);
163 }
164 
CreateDownload(const history::DownloadRow & info,const HistoryService::DownloadCreateCallback & callback)165 void DownloadHistory::HistoryAdapter::CreateDownload(
166     const history::DownloadRow& info,
167     const HistoryService::DownloadCreateCallback& callback) {
168   history_->CreateDownload(info, callback);
169 }
170 
UpdateDownload(const history::DownloadRow & data)171 void DownloadHistory::HistoryAdapter::UpdateDownload(
172     const history::DownloadRow& data) {
173   history_->UpdateDownload(data);
174 }
175 
RemoveDownloads(const std::set<uint32> & ids)176 void DownloadHistory::HistoryAdapter::RemoveDownloads(
177     const std::set<uint32>& ids) {
178   history_->RemoveDownloads(ids);
179 }
180 
181 
Observer()182 DownloadHistory::Observer::Observer() {}
~Observer()183 DownloadHistory::Observer::~Observer() {}
184 
IsPersisted(content::DownloadItem * item)185 bool DownloadHistory::IsPersisted(content::DownloadItem* item) {
186   DownloadHistoryData* data = DownloadHistoryData::Get(item);
187   return data && (data->state() == DownloadHistoryData::PERSISTED);
188 }
189 
DownloadHistory(content::DownloadManager * manager,scoped_ptr<HistoryAdapter> history)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 
~DownloadHistory()208 DownloadHistory::~DownloadHistory() {
209   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
210   FOR_EACH_OBSERVER(Observer, observers_, OnDownloadHistoryDestroyed());
211   observers_.Clear();
212 }
213 
AddObserver(DownloadHistory::Observer * observer)214 void DownloadHistory::AddObserver(DownloadHistory::Observer* observer) {
215   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
216   observers_.AddObserver(observer);
217 }
218 
RemoveObserver(DownloadHistory::Observer * observer)219 void DownloadHistory::RemoveObserver(DownloadHistory::Observer* observer) {
220   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
221   observers_.RemoveObserver(observer);
222 }
223 
QueryCallback(scoped_ptr<InfoVector> infos)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 
MaybeAddToHistory(content::DownloadItem * item)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 
ItemAdded(uint32 download_id,bool success)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 
OnDownloadCreated(content::DownloadManager * manager,content::DownloadItem * item)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 
OnDownloadUpdated(content::DownloadManager * manager,content::DownloadItem * item)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 
OnDownloadOpened(content::DownloadManager * manager,content::DownloadItem * item)385 void DownloadHistory::OnDownloadOpened(
386     content::DownloadManager* manager, content::DownloadItem* item) {
387   OnDownloadUpdated(manager, item);
388 }
389 
OnDownloadRemoved(content::DownloadManager * manager,content::DownloadItem * item)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 
ScheduleRemoveDownload(uint32 download_id)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 
RemoveDownloadsBatch()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