Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(69)

Side by Side Diff: content/browser/download/download_manager.cc

Issue 8351052: Created a DownloadManager interface, for use in unit tests.. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Merged with trunk Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 #include "content/browser/download/download_manager.h"
6
7 #include <iterator>
8
9 #include "base/bind.h"
10 #include "base/callback.h"
11 #include "base/file_util.h"
12 #include "base/i18n/case_conversion.h"
13 #include "base/logging.h"
14 #include "base/stl_util.h"
15 #include "base/stringprintf.h"
16 #include "base/synchronization/lock.h"
17 #include "base/sys_string_conversions.h"
18 #include "build/build_config.h"
19 #include "content/browser/browser_context.h"
20 #include "content/browser/download/download_create_info.h"
21 #include "content/browser/download/download_file_manager.h"
22 #include "content/browser/download/download_id_factory.h"
23 #include "content/browser/download/download_item.h"
24 #include "content/browser/download/download_persistent_store_info.h"
25 #include "content/browser/download/download_stats.h"
26 #include "content/browser/download/download_status_updater.h"
27 #include "content/browser/download/interrupt_reasons.h"
28 #include "content/browser/renderer_host/render_process_host.h"
29 #include "content/browser/renderer_host/render_view_host.h"
30 #include "content/browser/renderer_host/resource_dispatcher_host.h"
31 #include "content/browser/tab_contents/tab_contents.h"
32 #include "content/public/browser/browser_thread.h"
33 #include "content/public/browser/content_browser_client.h"
34 #include "content/public/browser/download_manager_delegate.h"
35 #include "content/public/browser/notification_service.h"
36 #include "content/public/browser/notification_types.h"
37
38 using content::BrowserThread;
39
40 namespace {
41
42 // Param structs exist because base::Bind can only handle 6 args.
43 struct URLParams {
44 URLParams(const GURL& url, const GURL& referrer)
45 : url_(url), referrer_(referrer) {}
46 GURL url_;
47 GURL referrer_;
48 };
49
50 struct RenderParams {
51 RenderParams(int rpi, int rvi)
52 : render_process_id_(rpi), render_view_id_(rvi) {}
53 int render_process_id_;
54 int render_view_id_;
55 };
56
57 void BeginDownload(const URLParams& url_params,
58 const DownloadSaveInfo& save_info,
59 ResourceDispatcherHost* resource_dispatcher_host,
60 const RenderParams& render_params,
61 const content::ResourceContext* context) {
62 net::URLRequest* request = new net::URLRequest(url_params.url_,
63 resource_dispatcher_host);
64 request->set_referrer(url_params.referrer_.spec());
65 resource_dispatcher_host->BeginDownload(
66 request, save_info, true,
67 DownloadResourceHandler::OnStartedCallback(),
68 render_params.render_process_id_,
69 render_params.render_view_id_,
70 *context);
71 }
72
73 } // namespace
74
75 DownloadManager::DownloadManager(content::DownloadManagerDelegate* delegate,
76 DownloadIdFactory* id_factory,
77 DownloadStatusUpdater* status_updater)
78 : shutdown_needed_(false),
79 browser_context_(NULL),
80 file_manager_(NULL),
81 status_updater_((status_updater != NULL)
82 ? status_updater->AsWeakPtr()
83 : base::WeakPtr<DownloadStatusUpdater>()),
84 delegate_(delegate),
85 id_factory_(id_factory),
86 largest_db_handle_in_history_(DownloadItem::kUninitializedHandle) {
87 // NOTE(benjhayden): status_updater may be NULL when using
88 // TestingBrowserProcess.
89 if (status_updater_.get() != NULL)
90 status_updater_->AddDelegate(this);
91 }
92
93 DownloadManager::~DownloadManager() {
94 DCHECK(!shutdown_needed_);
95 if (status_updater_.get() != NULL)
96 status_updater_->RemoveDelegate(this);
97 }
98
99 DownloadId DownloadManager::GetNextId() {
100 return id_factory_->GetNextId();
101 }
102
103 void DownloadManager::Shutdown() {
104 VLOG(20) << __FUNCTION__ << "()"
105 << " shutdown_needed_ = " << shutdown_needed_;
106 if (!shutdown_needed_)
107 return;
108 shutdown_needed_ = false;
109
110 FOR_EACH_OBSERVER(Observer, observers_, ManagerGoingDown());
111 // TODO(benjhayden): Consider clearing observers_.
112
113 if (file_manager_) {
114 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
115 base::Bind(&DownloadFileManager::OnDownloadManagerShutdown,
116 file_manager_, make_scoped_refptr(this)));
117 }
118
119 AssertContainersConsistent();
120
121 // Go through all downloads in downloads_. Dangerous ones we need to
122 // remove on disk, and in progress ones we need to cancel.
123 for (DownloadSet::iterator it = downloads_.begin(); it != downloads_.end();) {
124 DownloadItem* download = *it;
125
126 // Save iterator from potential erases in this set done by called code.
127 // Iterators after an erasure point are still valid for lists and
128 // associative containers such as sets.
129 it++;
130
131 if (download->safety_state() == DownloadItem::DANGEROUS &&
132 download->IsPartialDownload()) {
133 // The user hasn't accepted it, so we need to remove it
134 // from the disk. This may or may not result in it being
135 // removed from the DownloadManager queues and deleted
136 // (specifically, DownloadManager::RemoveDownload only
137 // removes and deletes it if it's known to the history service)
138 // so the only thing we know after calling this function is that
139 // the download was deleted if-and-only-if it was removed
140 // from all queues.
141 download->Delete(DownloadItem::DELETE_DUE_TO_BROWSER_SHUTDOWN);
142 } else if (download->IsPartialDownload()) {
143 download->Cancel(false);
144 delegate_->UpdateItemInPersistentStore(download);
145 }
146 }
147
148 // At this point, all dangerous downloads have had their files removed
149 // and all in progress downloads have been cancelled. We can now delete
150 // anything left.
151
152 // Copy downloads_ to separate container so as not to set off checks
153 // in DownloadItem destruction.
154 DownloadSet downloads_to_delete;
155 downloads_to_delete.swap(downloads_);
156
157 in_progress_.clear();
158 active_downloads_.clear();
159 history_downloads_.clear();
160 STLDeleteElements(&downloads_to_delete);
161
162 DCHECK(save_page_downloads_.empty());
163
164 file_manager_ = NULL;
165 delegate_->Shutdown();
166
167 shutdown_needed_ = false;
168 }
169
170 void DownloadManager::GetTemporaryDownloads(
171 const FilePath& dir_path, DownloadVector* result) {
172 DCHECK(result);
173
174 for (DownloadMap::iterator it = history_downloads_.begin();
175 it != history_downloads_.end(); ++it) {
176 if (it->second->is_temporary() &&
177 it->second->full_path().DirName() == dir_path)
178 result->push_back(it->second);
179 }
180 }
181
182 void DownloadManager::GetAllDownloads(
183 const FilePath& dir_path, DownloadVector* result) {
184 DCHECK(result);
185
186 for (DownloadMap::iterator it = history_downloads_.begin();
187 it != history_downloads_.end(); ++it) {
188 if (!it->second->is_temporary() &&
189 (dir_path.empty() || it->second->full_path().DirName() == dir_path))
190 result->push_back(it->second);
191 }
192 }
193
194 void DownloadManager::SearchDownloads(const string16& query,
195 DownloadVector* result) {
196 string16 query_lower(base::i18n::ToLower(query));
197
198 for (DownloadMap::iterator it = history_downloads_.begin();
199 it != history_downloads_.end(); ++it) {
200 DownloadItem* download_item = it->second;
201
202 if (download_item->is_temporary())
203 continue;
204
205 // Display Incognito downloads only in Incognito window, and vice versa.
206 // The Incognito Downloads page will get the list of non-Incognito downloads
207 // from its parent profile.
208 if (browser_context_->IsOffTheRecord() != download_item->is_otr())
209 continue;
210
211 if (download_item->MatchesQuery(query_lower))
212 result->push_back(download_item);
213 }
214 }
215
216 // Query the history service for information about all persisted downloads.
217 bool DownloadManager::Init(content::BrowserContext* browser_context) {
218 DCHECK(browser_context);
219 DCHECK(!shutdown_needed_) << "DownloadManager already initialized.";
220 shutdown_needed_ = true;
221
222 browser_context_ = browser_context;
223
224 // In test mode, there may be no ResourceDispatcherHost. In this case it's
225 // safe to avoid setting |file_manager_| because we only call a small set of
226 // functions, none of which need it.
227 ResourceDispatcherHost* rdh =
228 content::GetContentClient()->browser()->GetResourceDispatcherHost();
229 if (rdh) {
230 file_manager_ = rdh->download_file_manager();
231 DCHECK(file_manager_);
232 }
233
234 return true;
235 }
236
237 // We have received a message from DownloadFileManager about a new download.
238 void DownloadManager::StartDownload(int32 download_id) {
239 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
240
241 if (delegate_->ShouldStartDownload(download_id))
242 RestartDownload(download_id);
243 }
244
245 void DownloadManager::CheckForHistoryFilesRemoval() {
246 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
247 for (DownloadMap::iterator it = history_downloads_.begin();
248 it != history_downloads_.end(); ++it) {
249 CheckForFileRemoval(it->second);
250 }
251 }
252
253 void DownloadManager::CheckForFileRemoval(DownloadItem* download_item) {
254 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
255 if (download_item->IsComplete() &&
256 !download_item->file_externally_removed()) {
257 BrowserThread::PostTask(
258 BrowserThread::FILE, FROM_HERE,
259 base::Bind(&DownloadManager::CheckForFileRemovalOnFileThread,
260 this, download_item->db_handle(),
261 download_item->GetTargetFilePath()));
262 }
263 }
264
265 void DownloadManager::CheckForFileRemovalOnFileThread(
266 int64 db_handle, const FilePath& path) {
267 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
268 if (!file_util::PathExists(path)) {
269 BrowserThread::PostTask(
270 BrowserThread::UI, FROM_HERE,
271 base::Bind(&DownloadManager::OnFileRemovalDetected, this, db_handle));
272 }
273 }
274
275 void DownloadManager::OnFileRemovalDetected(int64 db_handle) {
276 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
277 DownloadMap::iterator it = history_downloads_.find(db_handle);
278 if (it != history_downloads_.end()) {
279 DownloadItem* download_item = it->second;
280 download_item->OnDownloadedFileRemoved();
281 }
282 }
283
284 void DownloadManager::RestartDownload(
285 int32 download_id) {
286 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
287
288 DownloadItem* download = GetActiveDownloadItem(download_id);
289 if (!download)
290 return;
291
292 VLOG(20) << __FUNCTION__ << "()"
293 << " download = " << download->DebugString(true);
294
295 FilePath suggested_path = download->suggested_path();
296
297 if (download->prompt_user_for_save_location()) {
298 // We must ask the user for the place to put the download.
299 TabContents* contents = download->GetTabContents();
300
301 // |id_ptr| will be deleted in either FileSelected() or
302 // FileSelectionCancelled().
303 int32* id_ptr = new int32;
304 *id_ptr = download_id;
305
306 delegate_->ChooseDownloadPath(
307 contents, suggested_path, reinterpret_cast<void*>(id_ptr));
308
309 FOR_EACH_OBSERVER(Observer, observers_,
310 SelectFileDialogDisplayed(download_id));
311 } else {
312 // No prompting for download, just continue with the suggested name.
313 ContinueDownloadWithPath(download, suggested_path);
314 }
315 }
316
317 void DownloadManager::CreateDownloadItem(
318 DownloadCreateInfo* info, const DownloadRequestHandle& request_handle) {
319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
320
321 DownloadItem* download = new DownloadItem(
322 this, *info, new DownloadRequestHandle(request_handle),
323 browser_context_->IsOffTheRecord());
324 int32 download_id = info->download_id.local();
325 DCHECK(!ContainsKey(in_progress_, download_id));
326
327 // TODO(rdsmith): Remove after http://crbug.com/85408 resolved.
328 CHECK(!ContainsKey(active_downloads_, download_id));
329 downloads_.insert(download);
330 active_downloads_[download_id] = download;
331 }
332
333 void DownloadManager::ContinueDownloadWithPath(DownloadItem* download,
334 const FilePath& chosen_file) {
335 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
336 DCHECK(download);
337
338 int32 download_id = download->id();
339
340 // NOTE(ahendrickson) Eventually |active_downloads_| will replace
341 // |in_progress_|, but we don't want to change the semantics yet.
342 DCHECK(!ContainsKey(in_progress_, download_id));
343 DCHECK(ContainsKey(downloads_, download));
344 DCHECK(ContainsKey(active_downloads_, download_id));
345
346 // Make sure the initial file name is set only once.
347 download->OnPathDetermined(chosen_file);
348
349 VLOG(20) << __FUNCTION__ << "()"
350 << " download = " << download->DebugString(true);
351
352 in_progress_[download_id] = download;
353 UpdateDownloadProgress(); // Reflect entry into in_progress_.
354
355 // Rename to intermediate name.
356 FilePath download_path;
357 if (!delegate_->OverrideIntermediatePath(download, &download_path))
358 download_path = download->full_path();
359
360 BrowserThread::PostTask(
361 BrowserThread::FILE, FROM_HERE,
362 base::Bind(&DownloadFileManager::RenameInProgressDownloadFile,
363 file_manager_, download->global_id(), download_path));
364
365 download->Rename(download_path);
366
367 delegate_->AddItemToPersistentStore(download);
368 }
369
370 void DownloadManager::UpdateDownload(int32 download_id, int64 size) {
371 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
372 DownloadMap::iterator it = active_downloads_.find(download_id);
373 if (it != active_downloads_.end()) {
374 DownloadItem* download = it->second;
375 if (download->IsInProgress()) {
376 download->Update(size);
377 UpdateDownloadProgress(); // Reflect size updates.
378 delegate_->UpdateItemInPersistentStore(download);
379 }
380 }
381 }
382
383 void DownloadManager::OnResponseCompleted(int32 download_id,
384 int64 size,
385 const std::string& hash) {
386 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
387 VLOG(20) << __FUNCTION__ << "()" << " download_id = " << download_id
388 << " size = " << size;
389 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
390
391 // If it's not in active_downloads_, that means it was cancelled; just
392 // ignore the notification.
393 if (active_downloads_.count(download_id) == 0)
394 return;
395
396 DownloadItem* download = active_downloads_[download_id];
397 download->OnAllDataSaved(size, hash);
398 delegate_->OnResponseCompleted(download);
399
400 MaybeCompleteDownload(download);
401 }
402
403 void DownloadManager::AssertQueueStateConsistent(DownloadItem* download) {
404 // TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
405 if (download->state() == DownloadItem::REMOVING) {
406 CHECK(!ContainsKey(downloads_, download));
407 CHECK(!ContainsKey(active_downloads_, download->id()));
408 CHECK(!ContainsKey(in_progress_, download->id()));
409 CHECK(!ContainsKey(history_downloads_, download->db_handle()));
410 return;
411 }
412
413 // Should be in downloads_ if we're not REMOVING.
414 CHECK(ContainsKey(downloads_, download));
415
416 // Check history_downloads_ consistency.
417 if (download->db_handle() != DownloadItem::kUninitializedHandle) {
418 CHECK(ContainsKey(history_downloads_, download->db_handle()));
419 } else {
420 // TODO(rdsmith): Somewhat painful; make sure to disable in
421 // release builds after resolution of http://crbug.com/85408.
422 for (DownloadMap::iterator it = history_downloads_.begin();
423 it != history_downloads_.end(); ++it) {
424 CHECK(it->second != download);
425 }
426 }
427
428 int64 state = download->state();
429 base::debug::Alias(&state);
430 if (ContainsKey(active_downloads_, download->id())) {
431 if (download->db_handle() != DownloadItem::kUninitializedHandle)
432 CHECK_EQ(DownloadItem::IN_PROGRESS, download->state());
433 if (DownloadItem::IN_PROGRESS != download->state())
434 CHECK_EQ(DownloadItem::kUninitializedHandle, download->db_handle());
435 }
436 if (DownloadItem::IN_PROGRESS == download->state())
437 CHECK(ContainsKey(active_downloads_, download->id()));
438 }
439
440 bool DownloadManager::IsDownloadReadyForCompletion(DownloadItem* download) {
441 // If we don't have all the data, the download is not ready for
442 // completion.
443 if (!download->all_data_saved())
444 return false;
445
446 // If the download is dangerous, but not yet validated, it's not ready for
447 // completion.
448 if (download->safety_state() == DownloadItem::DANGEROUS)
449 return false;
450
451 // If the download isn't active (e.g. has been cancelled) it's not
452 // ready for completion.
453 if (active_downloads_.count(download->id()) == 0)
454 return false;
455
456 // If the download hasn't been inserted into the history system
457 // (which occurs strictly after file name determination, intermediate
458 // file rename, and UI display) then it's not ready for completion.
459 if (download->db_handle() == DownloadItem::kUninitializedHandle)
460 return false;
461
462 return true;
463 }
464
465 void DownloadManager::MaybeCompleteDownload(DownloadItem* download) {
466 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
467 VLOG(20) << __FUNCTION__ << "()" << " download = "
468 << download->DebugString(false);
469
470 if (!IsDownloadReadyForCompletion(download))
471 return;
472
473 // TODO(rdsmith): DCHECK that we only pass through this point
474 // once per download. The natural way to do this is by a state
475 // transition on the DownloadItem.
476
477 // Confirm we're in the proper set of states to be here;
478 // in in_progress_, have all data, have a history handle, (validated or safe).
479 DCHECK_NE(DownloadItem::DANGEROUS, download->safety_state());
480 DCHECK_EQ(1u, in_progress_.count(download->id()));
481 DCHECK(download->all_data_saved());
482 DCHECK(download->db_handle() != DownloadItem::kUninitializedHandle);
483 DCHECK_EQ(1u, history_downloads_.count(download->db_handle()));
484
485 // Give the delegate a chance to override.
486 if (!delegate_->ShouldCompleteDownload(download))
487 return;
488
489 VLOG(20) << __FUNCTION__ << "()" << " executing: download = "
490 << download->DebugString(false);
491
492 // Remove the id from in_progress
493 in_progress_.erase(download->id());
494 UpdateDownloadProgress(); // Reflect removal from in_progress_.
495
496 delegate_->UpdateItemInPersistentStore(download);
497
498 // Finish the download.
499 download->OnDownloadCompleting(file_manager_);
500 }
501
502 void DownloadManager::DownloadCompleted(int32 download_id) {
503 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
504 DownloadItem* download = GetDownloadItem(download_id);
505 DCHECK(download);
506 delegate_->UpdateItemInPersistentStore(download);
507 active_downloads_.erase(download_id);
508 AssertQueueStateConsistent(download);
509 }
510
511 void DownloadManager::OnDownloadRenamedToFinalName(int download_id,
512 const FilePath& full_path,
513 int uniquifier) {
514 VLOG(20) << __FUNCTION__ << "()" << " download_id = " << download_id
515 << " full_path = \"" << full_path.value() << "\""
516 << " uniquifier = " << uniquifier;
517 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
518
519 DownloadItem* item = GetDownloadItem(download_id);
520 if (!item)
521 return;
522
523 if (item->safety_state() == DownloadItem::SAFE) {
524 DCHECK_EQ(0, uniquifier) << "We should not uniquify SAFE downloads twice";
525 }
526
527 BrowserThread::PostTask(
528 BrowserThread::FILE, FROM_HERE,
529 base::Bind(&DownloadFileManager::CompleteDownload,
530 file_manager_, item->global_id()));
531
532 if (uniquifier)
533 item->set_path_uniquifier(uniquifier);
534
535 item->OnDownloadRenamedToFinalName(full_path);
536 delegate_->UpdatePathForItemInPersistentStore(item, full_path);
537 }
538
539 void DownloadManager::CancelDownload(int32 download_id) {
540 DownloadItem* download = GetActiveDownload(download_id);
541 // A cancel at the right time could remove the download from the
542 // |active_downloads_| map before we get here.
543 if (!download)
544 return;
545
546 download->Cancel(true);
547 }
548
549 void DownloadManager::DownloadCancelledInternal(DownloadItem* download) {
550 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
551
552 VLOG(20) << __FUNCTION__ << "()"
553 << " download = " << download->DebugString(true);
554
555 RemoveFromActiveList(download);
556 // This function is called from the DownloadItem, so DI state
557 // should already have been updated.
558 AssertQueueStateConsistent(download);
559
560 if (file_manager_)
561 download->OffThreadCancel(file_manager_);
562 }
563
564 void DownloadManager::OnDownloadInterrupted(int32 download_id,
565 int64 size,
566 InterruptReason reason) {
567 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
568
569 DownloadItem* download = GetActiveDownload(download_id);
570 if (!download)
571 return;
572
573 VLOG(20) << __FUNCTION__ << "()"
574 << " reason " << InterruptReasonDebugString(reason)
575 << " at offset " << download->received_bytes()
576 << " size = " << size
577 << " download = " << download->DebugString(true);
578
579 RemoveFromActiveList(download);
580 download->Interrupted(size, reason);
581 download->OffThreadCancel(file_manager_);
582 }
583
584 DownloadItem* DownloadManager::GetActiveDownload(int32 download_id) {
585 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
586 DownloadMap::iterator it = active_downloads_.find(download_id);
587 if (it == active_downloads_.end())
588 return NULL;
589
590 DownloadItem* download = it->second;
591
592 DCHECK(download);
593 DCHECK_EQ(download_id, download->id());
594
595 return download;
596 }
597
598 void DownloadManager::RemoveFromActiveList(DownloadItem* download) {
599 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
600 DCHECK(download);
601
602 // Clean up will happen when the history system create callback runs if we
603 // don't have a valid db_handle yet.
604 if (download->db_handle() != DownloadItem::kUninitializedHandle) {
605 in_progress_.erase(download->id());
606 active_downloads_.erase(download->id());
607 UpdateDownloadProgress(); // Reflect removal from in_progress_.
608 delegate_->UpdateItemInPersistentStore(download);
609 }
610 }
611
612 void DownloadManager::SetDownloadManagerDelegate(
613 content::DownloadManagerDelegate* delegate) {
614 delegate_ = delegate;
615 }
616
617 void DownloadManager::UpdateDownloadProgress() {
618 delegate_->DownloadProgressUpdated();
619 }
620
621 int DownloadManager::RemoveDownloadItems(
622 const DownloadVector& pending_deletes) {
623 if (pending_deletes.empty())
624 return 0;
625
626 // Delete from internal maps.
627 for (DownloadVector::const_iterator it = pending_deletes.begin();
628 it != pending_deletes.end();
629 ++it) {
630 DownloadItem* download = *it;
631 DCHECK(download);
632 history_downloads_.erase(download->db_handle());
633 save_page_downloads_.erase(download->id());
634 downloads_.erase(download);
635 }
636
637 // Tell observers to refresh their views.
638 NotifyModelChanged();
639
640 // Delete the download items themselves.
641 const int num_deleted = static_cast<int>(pending_deletes.size());
642 STLDeleteContainerPointers(pending_deletes.begin(), pending_deletes.end());
643 return num_deleted;
644 }
645
646 void DownloadManager::RemoveDownload(int64 download_handle) {
647 DownloadMap::iterator it = history_downloads_.find(download_handle);
648 if (it == history_downloads_.end())
649 return;
650
651 // Make history update.
652 DownloadItem* download = it->second;
653 delegate_->RemoveItemFromPersistentStore(download);
654
655 // Remove from our tables and delete.
656 int downloads_count = RemoveDownloadItems(DownloadVector(1, download));
657 DCHECK_EQ(1, downloads_count);
658 }
659
660 int DownloadManager::RemoveDownloadsBetween(const base::Time remove_begin,
661 const base::Time remove_end) {
662 delegate_->RemoveItemsFromPersistentStoreBetween(remove_begin, remove_end);
663
664 // All downloads visible to the user will be in the history,
665 // so scan that map.
666 DownloadVector pending_deletes;
667 for (DownloadMap::const_iterator it = history_downloads_.begin();
668 it != history_downloads_.end();
669 ++it) {
670 DownloadItem* download = it->second;
671 if (download->start_time() >= remove_begin &&
672 (remove_end.is_null() || download->start_time() < remove_end) &&
673 (download->IsComplete() || download->IsCancelled())) {
674 AssertQueueStateConsistent(download);
675 pending_deletes.push_back(download);
676 }
677 }
678 return RemoveDownloadItems(pending_deletes);
679 }
680
681 int DownloadManager::RemoveDownloads(const base::Time remove_begin) {
682 return RemoveDownloadsBetween(remove_begin, base::Time());
683 }
684
685 int DownloadManager::RemoveAllDownloads() {
686 download_stats::RecordClearAllSize(history_downloads_.size());
687 // The null times make the date range unbounded.
688 return RemoveDownloadsBetween(base::Time(), base::Time());
689 }
690
691 // Initiate a download of a specific URL. We send the request to the
692 // ResourceDispatcherHost, and let it send us responses like a regular
693 // download.
694 void DownloadManager::DownloadUrl(const GURL& url,
695 const GURL& referrer,
696 const std::string& referrer_charset,
697 TabContents* tab_contents) {
698 DownloadUrlToFile(url, referrer, referrer_charset, DownloadSaveInfo(),
699 tab_contents);
700 }
701
702 void DownloadManager::DownloadUrlToFile(const GURL& url,
703 const GURL& referrer,
704 const std::string& referrer_charset,
705 const DownloadSaveInfo& save_info,
706 TabContents* tab_contents) {
707 DCHECK(tab_contents);
708 ResourceDispatcherHost* resource_dispatcher_host =
709 content::GetContentClient()->browser()->GetResourceDispatcherHost();
710 // We send a pointer to content::ResourceContext, instead of the usual
711 // reference, so that a copy of the object isn't made.
712 // base::Bind can't handle 7 args, so we use URLParams and RenderParams.
713 BrowserThread::PostTask(
714 BrowserThread::IO, FROM_HERE,
715 base::Bind(&BeginDownload,
716 URLParams(url, referrer), save_info, resource_dispatcher_host,
717 RenderParams(tab_contents->GetRenderProcessHost()->id(),
718 tab_contents->render_view_host()->routing_id()),
719 &tab_contents->browser_context()->GetResourceContext()));
720 }
721
722 void DownloadManager::AddObserver(Observer* observer) {
723 observers_.AddObserver(observer);
724 observer->ModelChanged();
725 }
726
727 void DownloadManager::RemoveObserver(Observer* observer) {
728 observers_.RemoveObserver(observer);
729 }
730
731 bool DownloadManager::IsDownloadProgressKnown() const {
732 for (DownloadMap::const_iterator i = in_progress_.begin();
733 i != in_progress_.end(); ++i) {
734 if (i->second->total_bytes() <= 0)
735 return false;
736 }
737
738 return true;
739 }
740
741 int64 DownloadManager::GetInProgressDownloadCount() const {
742 return in_progress_.size();
743 }
744
745 int64 DownloadManager::GetReceivedDownloadBytes() const {
746 DCHECK(IsDownloadProgressKnown());
747 int64 received_bytes = 0;
748 for (DownloadMap::const_iterator i = in_progress_.begin();
749 i != in_progress_.end(); ++i) {
750 received_bytes += i->second->received_bytes();
751 }
752 return received_bytes;
753 }
754
755 int64 DownloadManager::GetTotalDownloadBytes() const {
756 DCHECK(IsDownloadProgressKnown());
757 int64 total_bytes = 0;
758 for (DownloadMap::const_iterator i = in_progress_.begin();
759 i != in_progress_.end(); ++i) {
760 total_bytes += i->second->total_bytes();
761 }
762 return total_bytes;
763 }
764
765 void DownloadManager::FileSelected(const FilePath& path, void* params) {
766 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
767
768 int32* id_ptr = reinterpret_cast<int32*>(params);
769 DCHECK(id_ptr != NULL);
770 int32 download_id = *id_ptr;
771 delete id_ptr;
772
773 DownloadItem* download = GetActiveDownloadItem(download_id);
774 if (!download)
775 return;
776 VLOG(20) << __FUNCTION__ << "()" << " path = \"" << path.value() << "\""
777 << " download = " << download->DebugString(true);
778
779 if (download->prompt_user_for_save_location())
780 last_download_path_ = path.DirName();
781
782 // Make sure the initial file name is set only once.
783 ContinueDownloadWithPath(download, path);
784 }
785
786 void DownloadManager::FileSelectionCanceled(void* params) {
787 // The user didn't pick a place to save the file, so need to cancel the
788 // download that's already in progress to the temporary location.
789 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
790 int32* id_ptr = reinterpret_cast<int32*>(params);
791 DCHECK(id_ptr != NULL);
792 int32 download_id = *id_ptr;
793 delete id_ptr;
794
795 DownloadItem* download = GetActiveDownloadItem(download_id);
796 if (!download)
797 return;
798
799 VLOG(20) << __FUNCTION__ << "()"
800 << " download = " << download->DebugString(true);
801
802 // TODO(ahendrickson) -- This currently has no effect, as the download is
803 // not put on the active list until the file selection is complete. Need
804 // to put it on the active list earlier in the process.
805 RemoveFromActiveList(download);
806
807 download->OffThreadCancel(file_manager_);
808 }
809
810 // Operations posted to us from the history service ----------------------------
811
812 // The history service has retrieved all download entries. 'entries' contains
813 // 'DownloadPersistentStoreInfo's in sorted order (by ascending start_time).
814 void DownloadManager::OnPersistentStoreQueryComplete(
815 std::vector<DownloadPersistentStoreInfo>* entries) {
816 // TODO(rdsmith): Remove this and related logic when
817 // http://crbug.com/85408 is fixed.
818 largest_db_handle_in_history_ = 0;
819
820 for (size_t i = 0; i < entries->size(); ++i) {
821 DownloadItem* download = new DownloadItem(this, entries->at(i));
822 // TODO(rdsmith): Remove after http://crbug.com/85408 resolved.
823 CHECK(!ContainsKey(history_downloads_, download->db_handle()));
824 downloads_.insert(download);
825 history_downloads_[download->db_handle()] = download;
826 VLOG(20) << __FUNCTION__ << "()" << i << ">"
827 << " download = " << download->DebugString(true);
828
829 if (download->db_handle() > largest_db_handle_in_history_)
830 largest_db_handle_in_history_ = download->db_handle();
831 }
832 NotifyModelChanged();
833 CheckForHistoryFilesRemoval();
834 }
835
836 void DownloadManager::AddDownloadItemToHistory(DownloadItem* download,
837 int64 db_handle) {
838 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
839
840 // TODO(rdsmith): Convert to DCHECK() when http://crbug.com/85408
841 // is fixed.
842 CHECK_NE(DownloadItem::kUninitializedHandle, db_handle);
843
844 download_stats::RecordHistorySize(history_downloads_.size());
845
846 DCHECK(download->db_handle() == DownloadItem::kUninitializedHandle);
847 download->set_db_handle(db_handle);
848
849 // TODO(rdsmith): Convert to DCHECK() when http://crbug.com/85408
850 // is fixed.
851 CHECK(!ContainsKey(history_downloads_, download->db_handle()));
852 history_downloads_[download->db_handle()] = download;
853
854 // Show in the appropriate browser UI.
855 // This includes buttons to save or cancel, for a dangerous download.
856 ShowDownloadInBrowser(download);
857
858 // Inform interested objects about the new download.
859 NotifyModelChanged();
860 }
861
862
863 void DownloadManager::OnItemAddedToPersistentStore(int32 download_id,
864 int64 db_handle) {
865 if (save_page_downloads_.count(download_id)) {
866 OnSavePageItemAddedToPersistentStore(download_id, db_handle);
867 } else if (active_downloads_.count(download_id)) {
868 OnDownloadItemAddedToPersistentStore(download_id, db_handle);
869 }
870 // It's valid that we don't find a matching item, i.e. on shutdown.
871 }
872
873 // Once the new DownloadItem's creation info has been committed to the history
874 // service, we associate the DownloadItem with the db handle, update our
875 // 'history_downloads_' map and inform observers.
876 void DownloadManager::OnDownloadItemAddedToPersistentStore(int32 download_id,
877 int64 db_handle) {
878 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
879 DownloadItem* download = GetActiveDownloadItem(download_id);
880 if (!download)
881 return;
882
883 VLOG(20) << __FUNCTION__ << "()" << " db_handle = " << db_handle
884 << " download_id = " << download_id
885 << " download = " << download->DebugString(true);
886
887 // TODO(rdsmith): Remove after http://crbug.com/85408 resolved.
888 int64 largest_handle = largest_db_handle_in_history_;
889 base::debug::Alias(&largest_handle);
890 int32 matching_item_download_id
891 = (ContainsKey(history_downloads_, db_handle) ?
892 history_downloads_[db_handle]->id() : 0);
893 base::debug::Alias(&matching_item_download_id);
894
895 CHECK(!ContainsKey(history_downloads_, db_handle));
896
897 AddDownloadItemToHistory(download, db_handle);
898
899 // If the download is still in progress, try to complete it.
900 //
901 // Otherwise, download has been cancelled or interrupted before we've
902 // received the DB handle. We post one final message to the history
903 // service so that it can be properly in sync with the DownloadItem's
904 // completion status, and also inform any observers so that they get
905 // more than just the start notification.
906 if (download->IsInProgress()) {
907 MaybeCompleteDownload(download);
908 } else {
909 // TODO(rdsmith): Convert to DCHECK() when http://crbug.com/85408
910 // is fixed.
911 CHECK(download->IsCancelled())
912 << " download = " << download->DebugString(true);
913 in_progress_.erase(download_id);
914 active_downloads_.erase(download_id);
915 delegate_->UpdateItemInPersistentStore(download);
916 download->UpdateObservers();
917 }
918 }
919
920 void DownloadManager::ShowDownloadInBrowser(DownloadItem* download) {
921 // The 'contents' may no longer exist if the user closed the tab before we
922 // get this start completion event.
923 TabContents* content = download->GetTabContents();
924
925 // If the contents no longer exists, we ask the embedder to suggest another
926 // tab.
927 if (!content)
928 content = delegate_->GetAlternativeTabContentsToNotifyForDownload();
929
930 if (content)
931 content->OnStartDownload(download);
932 }
933
934 // Clears the last download path, used to initialize "save as" dialogs.
935 void DownloadManager::ClearLastDownloadPath() {
936 last_download_path_ = FilePath();
937 }
938
939 void DownloadManager::NotifyModelChanged() {
940 FOR_EACH_OBSERVER(Observer, observers_, ModelChanged());
941 }
942
943 DownloadItem* DownloadManager::GetDownloadItem(int download_id) {
944 // The |history_downloads_| map is indexed by the download's db_handle,
945 // not its id, so we have to iterate.
946 for (DownloadMap::iterator it = history_downloads_.begin();
947 it != history_downloads_.end(); ++it) {
948 DownloadItem* item = it->second;
949 if (item->id() == download_id)
950 return item;
951 }
952 return NULL;
953 }
954
955 DownloadItem* DownloadManager::GetActiveDownloadItem(int download_id) {
956 DCHECK(ContainsKey(active_downloads_, download_id));
957 DownloadItem* download = active_downloads_[download_id];
958 DCHECK(download != NULL);
959 return download;
960 }
961
962 // Confirm that everything in all maps is also in |downloads_|, and that
963 // everything in |downloads_| is also in some other map.
964 void DownloadManager::AssertContainersConsistent() const {
965 #if !defined(NDEBUG)
966 // Turn everything into sets.
967 const DownloadMap* input_maps[] = {&active_downloads_,
968 &history_downloads_,
969 &save_page_downloads_};
970 DownloadSet active_set, history_set, save_page_set;
971 DownloadSet* all_sets[] = {&active_set, &history_set, &save_page_set};
972 DCHECK_EQ(ARRAYSIZE_UNSAFE(input_maps), ARRAYSIZE_UNSAFE(all_sets));
973 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_maps); i++) {
974 for (DownloadMap::const_iterator it = input_maps[i]->begin();
975 it != input_maps[i]->end(); ++it) {
976 all_sets[i]->insert(&*it->second);
977 }
978 }
979
980 // Check if each set is fully present in downloads, and create a union.
981 DownloadSet downloads_union;
982 for (int i = 0; i < static_cast<int>(ARRAYSIZE_UNSAFE(all_sets)); i++) {
983 DownloadSet remainder;
984 std::insert_iterator<DownloadSet> insert_it(remainder, remainder.begin());
985 std::set_difference(all_sets[i]->begin(), all_sets[i]->end(),
986 downloads_.begin(), downloads_.end(),
987 insert_it);
988 DCHECK(remainder.empty());
989 std::insert_iterator<DownloadSet>
990 insert_union(downloads_union, downloads_union.end());
991 std::set_union(downloads_union.begin(), downloads_union.end(),
992 all_sets[i]->begin(), all_sets[i]->end(),
993 insert_union);
994 }
995
996 // Is everything in downloads_ present in one of the other sets?
997 DownloadSet remainder;
998 std::insert_iterator<DownloadSet>
999 insert_remainder(remainder, remainder.begin());
1000 std::set_difference(downloads_.begin(), downloads_.end(),
1001 downloads_union.begin(), downloads_union.end(),
1002 insert_remainder);
1003 DCHECK(remainder.empty());
1004 #endif
1005 }
1006
1007 void DownloadManager::SavePageDownloadStarted(DownloadItem* download) {
1008 DCHECK(!ContainsKey(save_page_downloads_, download->id()));
1009 downloads_.insert(download);
1010 save_page_downloads_[download->id()] = download;
1011
1012 // Add this entry to the history service.
1013 // Additionally, the UI is notified in the callback.
1014 delegate_->AddItemToPersistentStore(download);
1015 }
1016
1017 // SavePackage will call SavePageDownloadFinished upon completion/cancellation.
1018 // The history callback will call OnSavePageItemAddedToPersistentStore.
1019 // If the download finishes before the history callback,
1020 // OnSavePageItemAddedToPersistentStore calls SavePageDownloadFinished, ensuring
1021 // that the history event is update regardless of the order in which these two
1022 // events complete.
1023 // If something removes the download item from the download manager (Remove,
1024 // Shutdown) the result will be that the SavePage system will not be able to
1025 // properly update the download item (which no longer exists) or the download
1026 // history, but the action will complete properly anyway. This may lead to the
1027 // history entry being wrong on a reload of chrome (specifically in the case of
1028 // Initiation -> History Callback -> Removal -> Completion), but there's no way
1029 // to solve that without canceling on Remove (which would then update the DB).
1030
1031 void DownloadManager::OnSavePageItemAddedToPersistentStore(int32 download_id,
1032 int64 db_handle) {
1033 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1034
1035 DownloadMap::const_iterator it = save_page_downloads_.find(download_id);
1036 // This can happen if the download manager is shutting down and all maps
1037 // have been cleared.
1038 if (it == save_page_downloads_.end())
1039 return;
1040
1041 DownloadItem* download = it->second;
1042 if (!download) {
1043 NOTREACHED();
1044 return;
1045 }
1046
1047 // TODO(rdsmith): Remove after http://crbug.com/85408 resolved.
1048 int64 largest_handle = largest_db_handle_in_history_;
1049 base::debug::Alias(&largest_handle);
1050 CHECK(!ContainsKey(history_downloads_, db_handle));
1051
1052 AddDownloadItemToHistory(download, db_handle);
1053
1054 // Finalize this download if it finished before the history callback.
1055 if (!download->IsInProgress())
1056 SavePageDownloadFinished(download);
1057 }
1058
1059 void DownloadManager::SavePageDownloadFinished(DownloadItem* download) {
1060 if (download->db_handle() != DownloadItem::kUninitializedHandle) {
1061 delegate_->UpdateItemInPersistentStore(download);
1062 DCHECK(ContainsKey(save_page_downloads_, download->id()));
1063 save_page_downloads_.erase(download->id());
1064
1065 if (download->IsComplete())
1066 content::NotificationService::current()->Notify(
1067 content::NOTIFICATION_SAVE_PACKAGE_SUCCESSFULLY_FINISHED,
1068 content::Source<DownloadManager>(this),
1069 content::Details<DownloadItem>(download));
1070 }
1071 }
1072
1073 void DownloadManager::MarkDownloadOpened(DownloadItem* download) {
1074 delegate_->UpdateItemInPersistentStore(download);
1075 int num_unopened = 0;
1076 for (DownloadMap::iterator it = history_downloads_.begin();
1077 it != history_downloads_.end(); ++it) {
1078 if (it->second->IsComplete() && !it->second->opened())
1079 ++num_unopened;
1080 }
1081 download_stats::RecordOpensOutstanding(num_unopened);
1082 }
OLDNEW
« no previous file with comments | « content/browser/download/download_manager.h ('k') | content/browser/download/download_manager_impl.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698