OLD | NEW |
| (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 } | |
OLD | NEW |