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