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.
5 // DownloadHistory manages persisting DownloadItems to the history service by
6 // observing a single DownloadManager and all its DownloadItems using an
7 // AllDownloadItemNotifier.
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.
29 #include "chrome/browser/download/download_history.h"
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"
40 #if !defined(OS_ANDROID)
41 #include "chrome/browser/extensions/api/downloads/downloads_api.h"
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
{
54 enum PersistenceState
{
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
);
66 explicit DownloadHistoryData(content::DownloadItem
* item
)
67 : state_(NOT_PERSISTED
) {
68 item
->SetUserData(kKey
, this);
71 virtual ~DownloadHistoryData() {
74 PersistenceState
state() const { return state_
; }
75 void SetState(PersistenceState s
) { state_
= s
; }
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
));
90 static const char kKey
[];
92 PersistenceState state_
;
93 scoped_ptr
<history::DownloadRow
> info_
;
95 DISALLOW_COPY_AND_ASSIGN(DownloadHistoryData
);
98 const char DownloadHistoryData::kKey
[] =
99 "DownloadItem DownloadHistoryData";
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
);
107 by_ext_id
= by_ext
->id();
108 by_ext_name
= by_ext
->name();
112 return history::DownloadRow(
114 item
->GetTargetFilePath(),
116 item
->GetReferrerUrl(),
117 item
->GetStartTime(),
120 item
->GetLastModifiedTime(),
121 item
->GetReceivedBytes(),
122 item
->GetTotalBytes(),
124 item
->GetDangerType(),
125 item
->GetLastReason(),
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
));
151 typedef std::vector
<history::DownloadRow
> InfoVector
;
153 } // anonymous namespace
155 DownloadHistory::HistoryAdapter::HistoryAdapter(HistoryService
* history
)
156 : history_(history
) {
158 DownloadHistory::HistoryAdapter::~HistoryAdapter() {}
160 void DownloadHistory::HistoryAdapter::QueryDownloads(
161 const HistoryService::DownloadQueryCallback
& callback
) {
162 history_
->QueryDownloads(callback
);
165 void DownloadHistory::HistoryAdapter::CreateDownload(
166 const history::DownloadRow
& info
,
167 const HistoryService::DownloadCreateCallback
& callback
) {
168 history_
->CreateDownload(info
, callback
);
171 void DownloadHistory::HistoryAdapter::UpdateDownload(
172 const history::DownloadRow
& data
) {
173 history_
->UpdateDownload(data
);
176 void DownloadHistory::HistoryAdapter::RemoveDownloads(
177 const std::set
<uint32
>& ids
) {
178 history_
->RemoveDownloads(ids
);
182 DownloadHistory::Observer::Observer() {}
183 DownloadHistory::Observer::~Observer() {}
185 bool DownloadHistory::IsPersisted(content::DownloadItem
* item
) {
186 DownloadHistoryData
* data
= DownloadHistoryData::Get(item
);
187 return data
&& (data
->state() == DownloadHistoryData::PERSISTED
);
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
),
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
);
204 history_
->QueryDownloads(base::Bind(
205 &DownloadHistory::QueryCallback
, weak_ptr_factory_
.GetWeakPtr()));
208 DownloadHistory::~DownloadHistory() {
209 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
210 FOR_EACH_OBSERVER(Observer
, observers_
, OnDownloadHistoryDestroyed());
214 void DownloadHistory::AddObserver(DownloadHistory::Observer
* observer
) {
215 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
216 observers_
.AddObserver(observer
);
219 void DownloadHistory::RemoveObserver(DownloadHistory::Observer
* observer
) {
220 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
221 observers_
.RemoveObserver(observer
);
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())
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(
246 it
->interrupt_reason
,
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();
254 DCHECK_EQ(DownloadHistoryData::Get(item
)->state(),
255 DownloadHistoryData::PERSISTED
);
258 notifier_
.GetManager()->CheckForHistoryFilesRemoval();
261 void DownloadHistory::MaybeAddToHistory(content::DownloadItem
* item
) {
262 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
264 uint32 download_id
= item
->GetId();
265 DownloadHistoryData
* data
= DownloadHistoryData::Get(item
);
266 bool removing
= removing_ids_
.find(download_id
) != removing_ids_
.end();
268 // TODO(benjhayden): Remove IsTemporary().
269 if (download_crx_util::IsExtensionDownload(*item
) ||
270 item
->IsTemporary() ||
271 (data
->state() != DownloadHistoryData::NOT_PERSISTED
) ||
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
));
283 history_
->CreateDownload(*data
->info(), base::Bind(
284 &DownloadHistory::ItemAdded
, weak_ptr_factory_
.GetWeakPtr(),
286 FOR_EACH_OBSERVER(Observer
, observers_
, OnDownloadStored(
287 item
, *data
->info()));
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
);
295 ScheduleRemoveDownload(download_id
);
299 if (!notifier_
.GetManager())
302 content::DownloadItem
* item
= notifier_
.GetManager()->GetDownload(
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.
312 DownloadHistoryData
* data
= DownloadHistoryData::Get(item
);
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.
318 DVLOG(20) << __FUNCTION__
<< " INSERT failed id=" << download_id
;
319 data
->SetState(DownloadHistoryData::NOT_PERSISTED
);
322 data
->SetState(DownloadHistoryData::PERSISTED
);
324 UMA_HISTOGRAM_CUSTOM_COUNTS("Download.HistorySize2",
328 (1 << 7)/*num_buckets*/);
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
);
338 void DownloadHistory::OnDownloadCreated(
339 content::DownloadManager
* manager
, content::DownloadItem
* item
) {
340 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
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
;
349 if (item
->GetState() == content::DownloadItem::IN_PROGRESS
) {
350 data
->set_info(GetDownloadRow(item
));
352 MaybeAddToHistory(item
);
355 void DownloadHistory::OnDownloadUpdated(
356 content::DownloadManager
* manager
, content::DownloadItem
* item
) {
357 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
359 DownloadHistoryData
* data
= DownloadHistoryData::Get(item
);
360 if (data
->state() == DownloadHistoryData::NOT_PERSISTED
) {
361 MaybeAddToHistory(item
);
364 if (item
->IsTemporary()) {
365 OnDownloadRemoved(notifier_
.GetManager(), item
);
369 history::DownloadRow
current_info(GetDownloadRow(item
));
370 bool should_update
= ShouldUpdateHistory(data
->info(), current_info
);
371 UMA_HISTOGRAM_ENUMERATION("Download.HistoryPropagatedUpdate",
374 history_
->UpdateDownload(current_info
);
375 FOR_EACH_OBSERVER(Observer
, observers_
, OnDownloadStored(
376 item
, current_info
));
378 if (item
->GetState() == content::DownloadItem::IN_PROGRESS
) {
379 data
->set_info(current_info
);
385 void DownloadHistory::OnDownloadOpened(
386 content::DownloadManager
* manager
, content::DownloadItem
* item
) {
387 OnDownloadUpdated(manager
, item
);
390 void DownloadHistory::OnDownloadRemoved(
391 content::DownloadManager
* manager
, content::DownloadItem
* item
) {
392 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
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());
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().
412 void DownloadHistory::ScheduleRemoveDownload(uint32 download_id
) {
413 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
415 // For database efficiency, batch removals together if they happen all at
417 if (removing_ids_
.empty()) {
418 content::BrowserThread::PostTask(content::BrowserThread::UI
, FROM_HERE
,
419 base::Bind(&DownloadHistory::RemoveDownloadsBatch
,
420 weak_ptr_factory_
.GetWeakPtr()));
422 removing_ids_
.insert(download_id
);
425 void DownloadHistory::RemoveDownloadsBatch() {
426 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
428 removing_ids_
.swap(remove_ids
);
429 history_
->RemoveDownloads(remove_ids
);
430 FOR_EACH_OBSERVER(Observer
, observers_
, OnDownloadsRemoved(remove_ids
));