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