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

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: Fixed typo. 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);
398
399 delegate_->OnResponseCompleted(download, hash);
400
401 MaybeCompleteDownload(download);
402 }
403
404 void DownloadManager::AssertQueueStateConsistent(DownloadItem* download) {
405 // TODO(rdsmith): Change to DCHECK after http://crbug.com/85408 resolved.
406 if (download->state() == DownloadItem::REMOVING) {
407 CHECK(!ContainsKey(downloads_, download));
408 CHECK(!ContainsKey(active_downloads_, download->id()));
409 CHECK(!ContainsKey(in_progress_, download->id()));
410 CHECK(!ContainsKey(history_downloads_, download->db_handle()));
411 return;
412 }
413
414 // Should be in downloads_ if we're not REMOVING.
415 CHECK(ContainsKey(downloads_, download));
416
417 // Check history_downloads_ consistency.
418 if (download->db_handle() != DownloadItem::kUninitializedHandle) {
419 CHECK(ContainsKey(history_downloads_, download->db_handle()));
420 } else {
421 // TODO(rdsmith): Somewhat painful; make sure to disable in
422 // release builds after resolution of http://crbug.com/85408.
423 for (DownloadMap::iterator it = history_downloads_.begin();
424 it != history_downloads_.end(); ++it) {
425 CHECK(it->second != download);
426 }
427 }
428
429 int64 state = download->state();
430 base::debug::Alias(&state);
431 if (ContainsKey(active_downloads_, download->id())) {
432 if (download->db_handle() != DownloadItem::kUninitializedHandle)
433 CHECK_EQ(DownloadItem::IN_PROGRESS, download->state());
434 if (DownloadItem::IN_PROGRESS != download->state())
435 CHECK_EQ(DownloadItem::kUninitializedHandle, download->db_handle());
436 }
437 if (DownloadItem::IN_PROGRESS == download->state())
438 CHECK(ContainsKey(active_downloads_, download->id()));
439 }
440
441 bool DownloadManager::IsDownloadReadyForCompletion(DownloadItem* download) {
442 // If we don't have all the data, the download is not ready for
443 // completion.
444 if (!download->all_data_saved())
445 return false;
446
447 // If the download is dangerous, but not yet validated, it's not ready for
448 // completion.
449 if (download->safety_state() == DownloadItem::DANGEROUS)
450 return false;
451
452 // If the download isn't active (e.g. has been cancelled) it's not
453 // ready for completion.
454 if (active_downloads_.count(download->id()) == 0)
455 return false;
456
457 // If the download hasn't been inserted into the history system
458 // (which occurs strictly after file name determination, intermediate
459 // file rename, and UI display) then it's not ready for completion.
460 if (download->db_handle() == DownloadItem::kUninitializedHandle)
461 return false;
462
463 return true;
464 }
465
466 void DownloadManager::MaybeCompleteDownload(DownloadItem* download) {
467 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
468 VLOG(20) << __FUNCTION__ << "()" << " download = "
469 << download->DebugString(false);
470
471 if (!IsDownloadReadyForCompletion(download))
472 return;
473
474 // TODO(rdsmith): DCHECK that we only pass through this point
475 // once per download. The natural way to do this is by a state
476 // transition on the DownloadItem.
477
478 // Confirm we're in the proper set of states to be here;
479 // in in_progress_, have all data, have a history handle, (validated or safe).
480 DCHECK_NE(DownloadItem::DANGEROUS, download->safety_state());
481 DCHECK_EQ(1u, in_progress_.count(download->id()));
482 DCHECK(download->all_data_saved());
483 DCHECK(download->db_handle() != DownloadItem::kUninitializedHandle);
484 DCHECK_EQ(1u, history_downloads_.count(download->db_handle()));
485
486 // Give the delegate a chance to override.
487 if (!delegate_->ShouldCompleteDownload(download))
488 return;
489
490 VLOG(20) << __FUNCTION__ << "()" << " executing: download = "
491 << download->DebugString(false);
492
493 // Remove the id from in_progress
494 in_progress_.erase(download->id());
495 UpdateDownloadProgress(); // Reflect removal from in_progress_.
496
497 delegate_->UpdateItemInPersistentStore(download);
498
499 // Finish the download.
500 download->OnDownloadCompleting(file_manager_);
501 }
502
503 void DownloadManager::DownloadCompleted(int32 download_id) {
504 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
505 DownloadItem* download = GetDownloadItem(download_id);
506 DCHECK(download);
507 delegate_->UpdateItemInPersistentStore(download);
508 active_downloads_.erase(download_id);
509 AssertQueueStateConsistent(download);
510 }
511
512 void DownloadManager::OnDownloadRenamedToFinalName(int download_id,
513 const FilePath& full_path,
514 int uniquifier) {
515 VLOG(20) << __FUNCTION__ << "()" << " download_id = " << download_id
516 << " full_path = \"" << full_path.value() << "\""
517 << " uniquifier = " << uniquifier;
518 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
519
520 DownloadItem* item = GetDownloadItem(download_id);
521 if (!item)
522 return;
523
524 if (item->safety_state() == DownloadItem::SAFE) {
525 DCHECK_EQ(0, uniquifier) << "We should not uniquify SAFE downloads twice";
526 }
527
528 BrowserThread::PostTask(
529 BrowserThread::FILE, FROM_HERE,
530 base::Bind(&DownloadFileManager::CompleteDownload,
531 file_manager_, item->global_id()));
532
533 if (uniquifier)
534 item->set_path_uniquifier(uniquifier);
535
536 item->OnDownloadRenamedToFinalName(full_path);
537 delegate_->UpdatePathForItemInPersistentStore(item, full_path);
538 }
539
540 void DownloadManager::CancelDownload(int32 download_id) {
541 DownloadItem* download = GetActiveDownload(download_id);
542 // A cancel at the right time could remove the download from the
543 // |active_downloads_| map before we get here.
544 if (!download)
545 return;
546
547 download->Cancel(true);
548 }
549
550 void DownloadManager::DownloadCancelledInternal(DownloadItem* download) {
551 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
552
553 VLOG(20) << __FUNCTION__ << "()"
554 << " download = " << download->DebugString(true);
555
556 RemoveFromActiveList(download);
557 // This function is called from the DownloadItem, so DI state
558 // should already have been updated.
559 AssertQueueStateConsistent(download);
560
561 if (file_manager_)
562 download->OffThreadCancel(file_manager_);
563 }
564
565 void DownloadManager::OnDownloadInterrupted(int32 download_id,
566 int64 size,
567 InterruptReason reason) {
568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
569
570 DownloadItem* download = GetActiveDownload(download_id);
571 if (!download)
572 return;
573
574 VLOG(20) << __FUNCTION__ << "()"
575 << " reason " << InterruptReasonDebugString(reason)
576 << " at offset " << download->received_bytes()
577 << " size = " << size
578 << " download = " << download->DebugString(true);
579
580 RemoveFromActiveList(download);
581 download->Interrupted(size, reason);
582 download->OffThreadCancel(file_manager_);
583 }
584
585 DownloadItem* DownloadManager::GetActiveDownload(int32 download_id) {
586 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
587 DownloadMap::iterator it = active_downloads_.find(download_id);
588 if (it == active_downloads_.end())
589 return NULL;
590
591 DownloadItem* download = it->second;
592
593 DCHECK(download);
594 DCHECK_EQ(download_id, download->id());
595
596 return download;
597 }
598
599 void DownloadManager::RemoveFromActiveList(DownloadItem* download) {
600 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
601 DCHECK(download);
602
603 // Clean up will happen when the history system create callback runs if we
604 // don't have a valid db_handle yet.
605 if (download->db_handle() != DownloadItem::kUninitializedHandle) {
606 in_progress_.erase(download->id());
607 active_downloads_.erase(download->id());
608 UpdateDownloadProgress(); // Reflect removal from in_progress_.
609 delegate_->UpdateItemInPersistentStore(download);
610 }
611 }
612
613 void DownloadManager::SetDownloadManagerDelegate(
614 content::DownloadManagerDelegate* delegate) {
615 delegate_ = delegate;
616 }
617
618 void DownloadManager::UpdateDownloadProgress() {
619 delegate_->DownloadProgressUpdated();
620 }
621
622 int DownloadManager::RemoveDownloadItems(
623 const DownloadVector& pending_deletes) {
624 if (pending_deletes.empty())
625 return 0;
626
627 // Delete from internal maps.
628 for (DownloadVector::const_iterator it = pending_deletes.begin();
629 it != pending_deletes.end();
630 ++it) {
631 DownloadItem* download = *it;
632 DCHECK(download);
633 history_downloads_.erase(download->db_handle());
634 save_page_downloads_.erase(download->id());
635 downloads_.erase(download);
636 }
637
638 // Tell observers to refresh their views.
639 NotifyModelChanged();
640
641 // Delete the download items themselves.
642 const int num_deleted = static_cast<int>(pending_deletes.size());
643 STLDeleteContainerPointers(pending_deletes.begin(), pending_deletes.end());
644 return num_deleted;
645 }
646
647 void DownloadManager::RemoveDownload(int64 download_handle) {
648 DownloadMap::iterator it = history_downloads_.find(download_handle);
649 if (it == history_downloads_.end())
650 return;
651
652 // Make history update.
653 DownloadItem* download = it->second;
654 delegate_->RemoveItemFromPersistentStore(download);
655
656 // Remove from our tables and delete.
657 int downloads_count = RemoveDownloadItems(DownloadVector(1, download));
658 DCHECK_EQ(1, downloads_count);
659 }
660
661 int DownloadManager::RemoveDownloadsBetween(const base::Time remove_begin,
662 const base::Time remove_end) {
663 delegate_->RemoveItemsFromPersistentStoreBetween(remove_begin, remove_end);
664
665 // All downloads visible to the user will be in the history,
666 // so scan that map.
667 DownloadVector pending_deletes;
668 for (DownloadMap::const_iterator it = history_downloads_.begin();
669 it != history_downloads_.end();
670 ++it) {
671 DownloadItem* download = it->second;
672 if (download->start_time() >= remove_begin &&
673 (remove_end.is_null() || download->start_time() < remove_end) &&
674 (download->IsComplete() || download->IsCancelled())) {
675 AssertQueueStateConsistent(download);
676 pending_deletes.push_back(download);
677 }
678 }
679 return RemoveDownloadItems(pending_deletes);
680 }
681
682 int DownloadManager::RemoveDownloads(const base::Time remove_begin) {
683 return RemoveDownloadsBetween(remove_begin, base::Time());
684 }
685
686 int DownloadManager::RemoveAllDownloads() {
687 download_stats::RecordClearAllSize(history_downloads_.size());
688 // The null times make the date range unbounded.
689 return RemoveDownloadsBetween(base::Time(), base::Time());
690 }
691
692 // Initiate a download of a specific URL. We send the request to the
693 // ResourceDispatcherHost, and let it send us responses like a regular
694 // download.
695 void DownloadManager::DownloadUrl(const GURL& url,
696 const GURL& referrer,
697 const std::string& referrer_charset,
698 TabContents* tab_contents) {
699 DownloadUrlToFile(url, referrer, referrer_charset, DownloadSaveInfo(),
700 tab_contents);
701 }
702
703 void DownloadManager::DownloadUrlToFile(const GURL& url,
704 const GURL& referrer,
705 const std::string& referrer_charset,
706 const DownloadSaveInfo& save_info,
707 TabContents* tab_contents) {
708 DCHECK(tab_contents);
709 ResourceDispatcherHost* resource_dispatcher_host =
710 content::GetContentClient()->browser()->GetResourceDispatcherHost();
711 // We send a pointer to content::ResourceContext, instead of the usual
712 // reference, so that a copy of the object isn't made.
713 // base::Bind can't handle 7 args, so we use URLParams and RenderParams.
714 BrowserThread::PostTask(
715 BrowserThread::IO, FROM_HERE,
716 base::Bind(&BeginDownload,
717 URLParams(url, referrer), save_info, resource_dispatcher_host,
718 RenderParams(tab_contents->GetRenderProcessHost()->id(),
719 tab_contents->render_view_host()->routing_id()),
720 &tab_contents->browser_context()->GetResourceContext()));
721 }
722
723 void DownloadManager::AddObserver(Observer* observer) {
724 observers_.AddObserver(observer);
725 observer->ModelChanged();
726 }
727
728 void DownloadManager::RemoveObserver(Observer* observer) {
729 observers_.RemoveObserver(observer);
730 }
731
732 bool DownloadManager::IsDownloadProgressKnown() const {
733 for (DownloadMap::const_iterator i = in_progress_.begin();
734 i != in_progress_.end(); ++i) {
735 if (i->second->total_bytes() <= 0)
736 return false;
737 }
738
739 return true;
740 }
741
742 int64 DownloadManager::GetInProgressDownloadCount() const {
743 return in_progress_.size();
744 }
745
746 int64 DownloadManager::GetReceivedDownloadBytes() const {
747 DCHECK(IsDownloadProgressKnown());
748 int64 received_bytes = 0;
749 for (DownloadMap::const_iterator i = in_progress_.begin();
750 i != in_progress_.end(); ++i) {
751 received_bytes += i->second->received_bytes();
752 }
753 return received_bytes;
754 }
755
756 int64 DownloadManager::GetTotalDownloadBytes() const {
757 DCHECK(IsDownloadProgressKnown());
758 int64 total_bytes = 0;
759 for (DownloadMap::const_iterator i = in_progress_.begin();
760 i != in_progress_.end(); ++i) {
761 total_bytes += i->second->total_bytes();
762 }
763 return total_bytes;
764 }
765
766 void DownloadManager::FileSelected(const FilePath& path, void* params) {
767 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
768
769 int32* id_ptr = reinterpret_cast<int32*>(params);
770 DCHECK(id_ptr != NULL);
771 int32 download_id = *id_ptr;
772 delete id_ptr;
773
774 DownloadItem* download = GetActiveDownloadItem(download_id);
775 if (!download)
776 return;
777 VLOG(20) << __FUNCTION__ << "()" << " path = \"" << path.value() << "\""
778 << " download = " << download->DebugString(true);
779
780 if (download->prompt_user_for_save_location())
781 last_download_path_ = path.DirName();
782
783 // Make sure the initial file name is set only once.
784 ContinueDownloadWithPath(download, path);
785 }
786
787 void DownloadManager::FileSelectionCanceled(void* params) {
788 // The user didn't pick a place to save the file, so need to cancel the
789 // download that's already in progress to the temporary location.
790 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
791 int32* id_ptr = reinterpret_cast<int32*>(params);
792 DCHECK(id_ptr != NULL);
793 int32 download_id = *id_ptr;
794 delete id_ptr;
795
796 DownloadItem* download = GetActiveDownloadItem(download_id);
797 if (!download)
798 return;
799
800 VLOG(20) << __FUNCTION__ << "()"
801 << " download = " << download->DebugString(true);
802
803 // TODO(ahendrickson) -- This currently has no effect, as the download is
804 // not put on the active list until the file selection is complete. Need
805 // to put it on the active list earlier in the process.
806 RemoveFromActiveList(download);
807
808 download->OffThreadCancel(file_manager_);
809 }
810
811 // Operations posted to us from the history service ----------------------------
812
813 // The history service has retrieved all download entries. 'entries' contains
814 // 'DownloadPersistentStoreInfo's in sorted order (by ascending start_time).
815 void DownloadManager::OnPersistentStoreQueryComplete(
816 std::vector<DownloadPersistentStoreInfo>* entries) {
817 // TODO(rdsmith): Remove this and related logic when
818 // http://crbug.com/85408 is fixed.
819 largest_db_handle_in_history_ = 0;
820
821 for (size_t i = 0; i < entries->size(); ++i) {
822 DownloadItem* download = new DownloadItem(this, entries->at(i));
823 // TODO(rdsmith): Remove after http://crbug.com/85408 resolved.
824 CHECK(!ContainsKey(history_downloads_, download->db_handle()));
825 downloads_.insert(download);
826 history_downloads_[download->db_handle()] = download;
827 VLOG(20) << __FUNCTION__ << "()" << i << ">"
828 << " download = " << download->DebugString(true);
829
830 if (download->db_handle() > largest_db_handle_in_history_)
831 largest_db_handle_in_history_ = download->db_handle();
832 }
833 NotifyModelChanged();
834 CheckForHistoryFilesRemoval();
835 }
836
837 void DownloadManager::AddDownloadItemToHistory(DownloadItem* download,
838 int64 db_handle) {
839 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
840
841 // TODO(rdsmith): Convert to DCHECK() when http://crbug.com/85408
842 // is fixed.
843 CHECK_NE(DownloadItem::kUninitializedHandle, db_handle);
844
845 download_stats::RecordHistorySize(history_downloads_.size());
846
847 DCHECK(download->db_handle() == DownloadItem::kUninitializedHandle);
848 download->set_db_handle(db_handle);
849
850 // TODO(rdsmith): Convert to DCHECK() when http://crbug.com/85408
851 // is fixed.
852 CHECK(!ContainsKey(history_downloads_, download->db_handle()));
853 history_downloads_[download->db_handle()] = download;
854
855 // Show in the appropriate browser UI.
856 // This includes buttons to save or cancel, for a dangerous download.
857 ShowDownloadInBrowser(download);
858
859 // Inform interested objects about the new download.
860 NotifyModelChanged();
861 }
862
863
864 void DownloadManager::OnItemAddedToPersistentStore(int32 download_id,
865 int64 db_handle) {
866 if (save_page_downloads_.count(download_id)) {
867 OnSavePageItemAddedToPersistentStore(download_id, db_handle);
868 } else if (active_downloads_.count(download_id)) {
869 OnDownloadItemAddedToPersistentStore(download_id, db_handle);
870 }
871 // It's valid that we don't find a matching item, i.e. on shutdown.
872 }
873
874 // Once the new DownloadItem's creation info has been committed to the history
875 // service, we associate the DownloadItem with the db handle, update our
876 // 'history_downloads_' map and inform observers.
877 void DownloadManager::OnDownloadItemAddedToPersistentStore(int32 download_id,
878 int64 db_handle) {
879 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
880 DownloadItem* download = GetActiveDownloadItem(download_id);
881 if (!download)
882 return;
883
884 VLOG(20) << __FUNCTION__ << "()" << " db_handle = " << db_handle
885 << " download_id = " << download_id
886 << " download = " << download->DebugString(true);
887
888 // TODO(rdsmith): Remove after http://crbug.com/85408 resolved.
889 int64 largest_handle = largest_db_handle_in_history_;
890 base::debug::Alias(&largest_handle);
891 int32 matching_item_download_id
892 = (ContainsKey(history_downloads_, db_handle) ?
893 history_downloads_[db_handle]->id() : 0);
894 base::debug::Alias(&matching_item_download_id);
895
896 CHECK(!ContainsKey(history_downloads_, db_handle));
897
898 AddDownloadItemToHistory(download, db_handle);
899
900 // If the download is still in progress, try to complete it.
901 //
902 // Otherwise, download has been cancelled or interrupted before we've
903 // received the DB handle. We post one final message to the history
904 // service so that it can be properly in sync with the DownloadItem's
905 // completion status, and also inform any observers so that they get
906 // more than just the start notification.
907 if (download->IsInProgress()) {
908 MaybeCompleteDownload(download);
909 } else {
910 // TODO(rdsmith): Convert to DCHECK() when http://crbug.com/85408
911 // is fixed.
912 CHECK(download->IsCancelled())
913 << " download = " << download->DebugString(true);
914 in_progress_.erase(download_id);
915 active_downloads_.erase(download_id);
916 delegate_->UpdateItemInPersistentStore(download);
917 download->UpdateObservers();
918 }
919 }
920
921 void DownloadManager::ShowDownloadInBrowser(DownloadItem* download) {
922 // The 'contents' may no longer exist if the user closed the tab before we
923 // get this start completion event.
924 TabContents* content = download->GetTabContents();
925
926 // If the contents no longer exists, we ask the embedder to suggest another
927 // tab.
928 if (!content)
929 content = delegate_->GetAlternativeTabContentsToNotifyForDownload();
930
931 if (content)
932 content->OnStartDownload(download);
933 }
934
935 // Clears the last download path, used to initialize "save as" dialogs.
936 void DownloadManager::ClearLastDownloadPath() {
937 last_download_path_ = FilePath();
938 }
939
940 void DownloadManager::NotifyModelChanged() {
941 FOR_EACH_OBSERVER(Observer, observers_, ModelChanged());
942 }
943
944 DownloadItem* DownloadManager::GetDownloadItem(int download_id) {
945 // The |history_downloads_| map is indexed by the download's db_handle,
946 // not its id, so we have to iterate.
947 for (DownloadMap::iterator it = history_downloads_.begin();
948 it != history_downloads_.end(); ++it) {
949 DownloadItem* item = it->second;
950 if (item->id() == download_id)
951 return item;
952 }
953 return NULL;
954 }
955
956 DownloadItem* DownloadManager::GetActiveDownloadItem(int download_id) {
957 DCHECK(ContainsKey(active_downloads_, download_id));
958 DownloadItem* download = active_downloads_[download_id];
959 DCHECK(download != NULL);
960 return download;
961 }
962
963 // Confirm that everything in all maps is also in |downloads_|, and that
964 // everything in |downloads_| is also in some other map.
965 void DownloadManager::AssertContainersConsistent() const {
966 #if !defined(NDEBUG)
967 // Turn everything into sets.
968 const DownloadMap* input_maps[] = {&active_downloads_,
969 &history_downloads_,
970 &save_page_downloads_};
971 DownloadSet active_set, history_set, save_page_set;
972 DownloadSet* all_sets[] = {&active_set, &history_set, &save_page_set};
973 DCHECK_EQ(ARRAYSIZE_UNSAFE(input_maps), ARRAYSIZE_UNSAFE(all_sets));
974 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(input_maps); i++) {
975 for (DownloadMap::const_iterator it = input_maps[i]->begin();
976 it != input_maps[i]->end(); ++it) {
977 all_sets[i]->insert(&*it->second);
978 }
979 }
980
981 // Check if each set is fully present in downloads, and create a union.
982 DownloadSet downloads_union;
983 for (int i = 0; i < static_cast<int>(ARRAYSIZE_UNSAFE(all_sets)); i++) {
984 DownloadSet remainder;
985 std::insert_iterator<DownloadSet> insert_it(remainder, remainder.begin());
986 std::set_difference(all_sets[i]->begin(), all_sets[i]->end(),
987 downloads_.begin(), downloads_.end(),
988 insert_it);
989 DCHECK(remainder.empty());
990 std::insert_iterator<DownloadSet>
991 insert_union(downloads_union, downloads_union.end());
992 std::set_union(downloads_union.begin(), downloads_union.end(),
993 all_sets[i]->begin(), all_sets[i]->end(),
994 insert_union);
995 }
996
997 // Is everything in downloads_ present in one of the other sets?
998 DownloadSet remainder;
999 std::insert_iterator<DownloadSet>
1000 insert_remainder(remainder, remainder.begin());
1001 std::set_difference(downloads_.begin(), downloads_.end(),
1002 downloads_union.begin(), downloads_union.end(),
1003 insert_remainder);
1004 DCHECK(remainder.empty());
1005 #endif
1006 }
1007
1008 void DownloadManager::SavePageDownloadStarted(DownloadItem* download) {
1009 DCHECK(!ContainsKey(save_page_downloads_, download->id()));
1010 downloads_.insert(download);
1011 save_page_downloads_[download->id()] = download;
1012
1013 // Add this entry to the history service.
1014 // Additionally, the UI is notified in the callback.
1015 delegate_->AddItemToPersistentStore(download);
1016 }
1017
1018 // SavePackage will call SavePageDownloadFinished upon completion/cancellation.
1019 // The history callback will call OnSavePageItemAddedToPersistentStore.
1020 // If the download finishes before the history callback,
1021 // OnSavePageItemAddedToPersistentStore calls SavePageDownloadFinished, ensuring
1022 // that the history event is update regardless of the order in which these two
1023 // events complete.
1024 // If something removes the download item from the download manager (Remove,
1025 // Shutdown) the result will be that the SavePage system will not be able to
1026 // properly update the download item (which no longer exists) or the download
1027 // history, but the action will complete properly anyway. This may lead to the
1028 // history entry being wrong on a reload of chrome (specifically in the case of
1029 // Initiation -> History Callback -> Removal -> Completion), but there's no way
1030 // to solve that without canceling on Remove (which would then update the DB).
1031
1032 void DownloadManager::OnSavePageItemAddedToPersistentStore(int32 download_id,
1033 int64 db_handle) {
1034 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1035
1036 DownloadMap::const_iterator it = save_page_downloads_.find(download_id);
1037 // This can happen if the download manager is shutting down and all maps
1038 // have been cleared.
1039 if (it == save_page_downloads_.end())
1040 return;
1041
1042 DownloadItem* download = it->second;
1043 if (!download) {
1044 NOTREACHED();
1045 return;
1046 }
1047
1048 // TODO(rdsmith): Remove after http://crbug.com/85408 resolved.
1049 int64 largest_handle = largest_db_handle_in_history_;
1050 base::debug::Alias(&largest_handle);
1051 CHECK(!ContainsKey(history_downloads_, db_handle));
1052
1053 AddDownloadItemToHistory(download, db_handle);
1054
1055 // Finalize this download if it finished before the history callback.
1056 if (!download->IsInProgress())
1057 SavePageDownloadFinished(download);
1058 }
1059
1060 void DownloadManager::SavePageDownloadFinished(DownloadItem* download) {
1061 if (download->db_handle() != DownloadItem::kUninitializedHandle) {
1062 delegate_->UpdateItemInPersistentStore(download);
1063 DCHECK(ContainsKey(save_page_downloads_, download->id()));
1064 save_page_downloads_.erase(download->id());
1065
1066 if (download->IsComplete())
1067 content::NotificationService::current()->Notify(
1068 content::NOTIFICATION_SAVE_PACKAGE_SUCCESSFULLY_FINISHED,
1069 content::Source<DownloadManager>(this),
1070 content::Details<DownloadItem>(download));
1071 }
1072 }
1073
1074 void DownloadManager::MarkDownloadOpened(DownloadItem* download) {
1075 delegate_->UpdateItemInPersistentStore(download);
1076 int num_unopened = 0;
1077 for (DownloadMap::iterator it = history_downloads_.begin();
1078 it != history_downloads_.end(); ++it) {
1079 if (it->second->IsComplete() && !it->second->opened())
1080 ++num_unopened;
1081 }
1082 download_stats::RecordOpensOutstanding(num_unopened);
1083 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698