OLD | NEW |
| (Empty) |
1 // Copyright (c) 2006-2008 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 <Windows.h> | |
6 #include <objbase.h> | |
7 | |
8 #include "chrome/browser/download_file.h" | |
9 | |
10 #include "base/file_util.h" | |
11 #include "base/path_service.h" | |
12 #include "base/scoped_ptr.h" | |
13 #include "base/string_util.h" | |
14 #include "base/task.h" | |
15 #include "chrome/browser/browser_process.h" | |
16 #include "chrome/browser/download_manager.h" | |
17 #include "chrome/browser/profile.h" | |
18 #include "chrome/browser/resource_dispatcher_host.h" | |
19 #include "chrome/browser/tab_contents.h" | |
20 #include "chrome/browser/tab_util.h" | |
21 #include "chrome/common/chrome_paths.h" | |
22 #include "chrome/common/stl_util-inl.h" | |
23 #include "chrome/common/win_util.h" | |
24 #include "chrome/common/win_safe_util.h" | |
25 #include "googleurl/src/gurl.h" | |
26 #include "net/base/net_util.h" | |
27 #include "net/url_request/url_request_context.h" | |
28 | |
29 // Throttle updates to the UI thread so that a fast moving download doesn't | |
30 // cause it to become unresponsive (ins milliseconds). | |
31 static const int kUpdatePeriodMs = 500; | |
32 | |
33 // Timer task for posting UI updates. This task is created and maintained by | |
34 // the DownloadFileManager long as there is an in progress download. The task | |
35 // is cancelled when all active downloads have completed, or in the destructor | |
36 // of the DownloadFileManager. | |
37 class DownloadFileUpdateTask : public Task { | |
38 public: | |
39 explicit DownloadFileUpdateTask(DownloadFileManager* manager) | |
40 : manager_(manager) {} | |
41 virtual void Run() { | |
42 manager_->UpdateInProgressDownloads(); | |
43 } | |
44 | |
45 private: | |
46 DownloadFileManager* manager_; | |
47 | |
48 DISALLOW_EVIL_CONSTRUCTORS(DownloadFileUpdateTask); | |
49 }; | |
50 | |
51 // DownloadFile implementation ------------------------------------------------- | |
52 | |
53 DownloadFile::DownloadFile(const DownloadCreateInfo* info) | |
54 : file_(NULL), | |
55 id_(info->download_id), | |
56 render_process_id_(info->render_process_id), | |
57 render_view_id_(info->render_view_id), | |
58 request_id_(info->request_id), | |
59 bytes_so_far_(0), | |
60 path_renamed_(false), | |
61 in_progress_(true) { | |
62 } | |
63 | |
64 DownloadFile::~DownloadFile() { | |
65 Close(); | |
66 } | |
67 | |
68 bool DownloadFile::Initialize() { | |
69 if (file_util::CreateTemporaryFileName(&full_path_)) | |
70 return Open(L"wb"); | |
71 return false; | |
72 } | |
73 | |
74 // FIXME bug 595247: handle errors on file writes. | |
75 bool DownloadFile::AppendDataToFile(const char* data, int data_len) { | |
76 if (file_) { | |
77 fwrite(data, 1, data_len, file_); | |
78 bytes_so_far_ += data_len; | |
79 return true; | |
80 } | |
81 return false; | |
82 } | |
83 | |
84 void DownloadFile::Cancel() { | |
85 Close(); | |
86 DeleteFile(full_path_.c_str()); | |
87 } | |
88 | |
89 // The UI has provided us with our finalized name. | |
90 bool DownloadFile::Rename(const std::wstring& new_path) { | |
91 Close(); | |
92 | |
93 // We cannot rename because rename will keep the same security descriptor | |
94 // on the destination file. We want to recreate the security descriptor | |
95 // with the security that makes sense in the new path. | |
96 if (!file_util::RenameFileAndResetSecurityDescriptor(full_path_.c_str(), | |
97 new_path.c_str())) { | |
98 return false; | |
99 } | |
100 | |
101 DeleteFile(full_path_.c_str()); | |
102 | |
103 full_path_ = new_path; | |
104 path_renamed_ = true; | |
105 | |
106 // We don't need to re-open the file if we're done (finished or canceled). | |
107 if (!in_progress_) | |
108 return true; | |
109 | |
110 if (!Open(L"a+b")) | |
111 return false; | |
112 return true; | |
113 } | |
114 | |
115 void DownloadFile::Close() { | |
116 if (file_) { | |
117 fclose(file_); | |
118 file_ = NULL; | |
119 } | |
120 } | |
121 | |
122 bool DownloadFile::Open(const wchar_t* open_mode) { | |
123 DCHECK(!full_path_.empty()); | |
124 if (_wfopen_s(&file_, full_path_.c_str(), open_mode)) { | |
125 file_ = NULL; | |
126 return false; | |
127 } | |
128 // Sets the Zone to tell Windows that this file comes from the internet. | |
129 // We ignore the return value because a failure is not fatal. | |
130 win_util::SetInternetZoneIdentifier(full_path_); | |
131 return true; | |
132 } | |
133 | |
134 // DownloadFileManager implementation ------------------------------------------ | |
135 | |
136 DownloadFileManager::DownloadFileManager(MessageLoop* ui_loop, | |
137 ResourceDispatcherHost* rdh) | |
138 : next_id_(0), | |
139 ui_loop_(ui_loop), | |
140 resource_dispatcher_host_(rdh) { | |
141 } | |
142 | |
143 DownloadFileManager::~DownloadFileManager() { | |
144 // Check for clean shutdown. | |
145 DCHECK(downloads_.empty()); | |
146 ui_progress_.clear(); | |
147 } | |
148 | |
149 void DownloadFileManager::Initialize() { | |
150 io_loop_ = g_browser_process->io_thread()->message_loop(); | |
151 file_loop_ = g_browser_process->file_thread()->message_loop(); | |
152 } | |
153 | |
154 // Called during the browser shutdown process to clean up any state (open files, | |
155 // timers) that live on the download_thread_. | |
156 void DownloadFileManager::Shutdown() { | |
157 DCHECK(MessageLoop::current() == ui_loop_); | |
158 StopUpdateTimer(); | |
159 file_loop_->PostTask(FROM_HERE, | |
160 NewRunnableMethod(this, | |
161 &DownloadFileManager::OnShutdown)); | |
162 } | |
163 | |
164 // Cease download thread operations. | |
165 void DownloadFileManager::OnShutdown() { | |
166 DCHECK(MessageLoop::current() == file_loop_); | |
167 // Delete any partial downloads during shutdown. | |
168 for (DownloadFileMap::iterator it = downloads_.begin(); | |
169 it != downloads_.end(); ++it) { | |
170 DownloadFile* download = it->second; | |
171 if (download->in_progress()) | |
172 download->Cancel(); | |
173 delete download; | |
174 } | |
175 downloads_.clear(); | |
176 } | |
177 | |
178 // Lookup one in-progress download. | |
179 DownloadFile* DownloadFileManager::LookupDownload(int id) { | |
180 DownloadFileMap::iterator it = downloads_.find(id); | |
181 return it == downloads_.end() ? NULL : it->second; | |
182 } | |
183 | |
184 // The UI progress is updated on the file thread and removed on the UI thread. | |
185 void DownloadFileManager::RemoveDownloadFromUIProgress(int id) { | |
186 DCHECK(MessageLoop::current() == ui_loop_); | |
187 AutoLock lock(progress_lock_); | |
188 if (ui_progress_.find(id) != ui_progress_.end()) | |
189 ui_progress_.erase(id); | |
190 } | |
191 | |
192 // Throttle updates to the UI thread by only posting update notifications at a | |
193 // regularly controlled interval. | |
194 void DownloadFileManager::StartUpdateTimer() { | |
195 DCHECK(MessageLoop::current() == ui_loop_); | |
196 if (!update_timer_.IsRunning()) { | |
197 update_timer_.Start(TimeDelta::FromMilliseconds(kUpdatePeriodMs), this, | |
198 &DownloadFileManager::UpdateInProgressDownloads); | |
199 } | |
200 } | |
201 | |
202 void DownloadFileManager::StopUpdateTimer() { | |
203 DCHECK(MessageLoop::current() == ui_loop_); | |
204 update_timer_.Stop(); | |
205 } | |
206 | |
207 // Called on the IO thread once the ResourceDispatcherHost has decided that a | |
208 // request is a download. | |
209 int DownloadFileManager::GetNextId() { | |
210 DCHECK(MessageLoop::current() == io_loop_); | |
211 return next_id_++; | |
212 } | |
213 | |
214 // Notifications sent from the IO thread and run on the download thread: | |
215 | |
216 // The IO thread created 'info', but the download thread (this method) uses it | |
217 // to create a DownloadFile, then passes 'info' to the UI thread where it is | |
218 // finally consumed and deleted. | |
219 void DownloadFileManager::StartDownload(DownloadCreateInfo* info) { | |
220 DCHECK(MessageLoop::current() == file_loop_); | |
221 DCHECK(info); | |
222 | |
223 DownloadFile* download = new DownloadFile(info); | |
224 if (!download->Initialize()) { | |
225 // Couldn't open, cancel the operation. The UI thread does not yet know | |
226 // about this download so we have to clean up 'info'. We need to get back | |
227 // to the IO thread to cancel the network request and CancelDownloadRequest | |
228 // on the UI thread is the safe way to do that. | |
229 ui_loop_->PostTask(FROM_HERE, | |
230 NewRunnableFunction(&DownloadManager::CancelDownloadRequest, | |
231 info->render_process_id, | |
232 info->request_id)); | |
233 delete info; | |
234 delete download; | |
235 return; | |
236 } | |
237 | |
238 DCHECK(LookupDownload(info->download_id) == NULL); | |
239 downloads_[info->download_id] = download; | |
240 info->path = download->full_path(); | |
241 { | |
242 AutoLock lock(progress_lock_); | |
243 ui_progress_[info->download_id] = info->received_bytes; | |
244 } | |
245 | |
246 ui_loop_->PostTask(FROM_HERE, | |
247 NewRunnableMethod(this, | |
248 &DownloadFileManager::OnStartDownload, | |
249 info)); | |
250 } | |
251 | |
252 // We don't forward an update to the UI thread here, since we want to throttle | |
253 // the UI update rate via a periodic timer. If the user has cancelled the | |
254 // download (in the UI thread), we may receive a few more updates before the IO | |
255 // thread gets the cancel message: we just delete the data since the | |
256 // DownloadFile has been deleted. | |
257 void DownloadFileManager::UpdateDownload(int id, DownloadBuffer* buffer) { | |
258 DCHECK(MessageLoop::current() == file_loop_); | |
259 std::vector<DownloadBuffer::Contents> contents; | |
260 { | |
261 AutoLock auto_lock(buffer->lock); | |
262 contents.swap(buffer->contents); | |
263 } | |
264 | |
265 DownloadFile* download = LookupDownload(id); | |
266 for (size_t i = 0; i < contents.size(); ++i) { | |
267 char* data = contents[i].first; | |
268 const int data_len = contents[i].second; | |
269 if (download) | |
270 download->AppendDataToFile(data, data_len); | |
271 delete [] data; | |
272 } | |
273 | |
274 if (download) { | |
275 AutoLock lock(progress_lock_); | |
276 ui_progress_[download->id()] = download->bytes_so_far(); | |
277 } | |
278 } | |
279 | |
280 void DownloadFileManager::DownloadFinished(int id, DownloadBuffer* buffer) { | |
281 DCHECK(MessageLoop::current() == file_loop_); | |
282 delete buffer; | |
283 DownloadFileMap::iterator it = downloads_.find(id); | |
284 if (it != downloads_.end()) { | |
285 DownloadFile* download = it->second; | |
286 download->set_in_progress(false); | |
287 | |
288 ui_loop_->PostTask(FROM_HERE, | |
289 NewRunnableMethod(this, | |
290 &DownloadFileManager::OnDownloadFinished, | |
291 id, | |
292 download->bytes_so_far())); | |
293 | |
294 // We need to keep the download around until the UI thread has finalized | |
295 // the name. | |
296 if (download->path_renamed()) { | |
297 downloads_.erase(it); | |
298 delete download; | |
299 } | |
300 } | |
301 | |
302 if (downloads_.empty()) | |
303 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( | |
304 this, &DownloadFileManager::StopUpdateTimer)); | |
305 } | |
306 | |
307 // This method will be sent via a user action, or shutdown on the UI thread, and | |
308 // run on the download thread. Since this message has been sent from the UI | |
309 // thread, the download may have already completed and won't exist in our map. | |
310 void DownloadFileManager::CancelDownload(int id) { | |
311 DCHECK(MessageLoop::current() == file_loop_); | |
312 DownloadFileMap::iterator it = downloads_.find(id); | |
313 if (it != downloads_.end()) { | |
314 DownloadFile* download = it->second; | |
315 download->set_in_progress(false); | |
316 | |
317 download->Cancel(); | |
318 | |
319 ui_loop_->PostTask(FROM_HERE, | |
320 NewRunnableMethod(this, | |
321 &DownloadFileManager::RemoveDownloadFromUIProgress, | |
322 download->id())); | |
323 | |
324 if (download->path_renamed()) { | |
325 downloads_.erase(it); | |
326 delete download; | |
327 } | |
328 } | |
329 | |
330 if (downloads_.empty()) | |
331 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( | |
332 this, &DownloadFileManager::StopUpdateTimer)); | |
333 } | |
334 | |
335 // Our periodic timer has fired so send the UI thread updates on all in progress | |
336 // downloads. | |
337 void DownloadFileManager::UpdateInProgressDownloads() { | |
338 DCHECK(MessageLoop::current() == ui_loop_); | |
339 AutoLock lock(progress_lock_); | |
340 ProgressMap::iterator it = ui_progress_.begin(); | |
341 for (; it != ui_progress_.end(); ++it) { | |
342 const int id = it->first; | |
343 DownloadManager* manager = LookupManager(id); | |
344 if (manager) | |
345 manager->UpdateDownload(id, it->second); | |
346 } | |
347 } | |
348 | |
349 // Notifications sent from the download thread and run on the UI thread. | |
350 | |
351 // Lookup the DownloadManager for this WebContents' profile and inform it of | |
352 // a new download. | |
353 // TODO(paulg): When implementing download restart via the Downloads tab, | |
354 // there will be no 'render_process_id' or 'render_view_id'. | |
355 void DownloadFileManager::OnStartDownload(DownloadCreateInfo* info) { | |
356 DCHECK(MessageLoop::current() == ui_loop_); | |
357 DownloadManager* manager = | |
358 DownloadManagerFromRenderIds(info->render_process_id, | |
359 info->render_view_id); | |
360 if (!manager) { | |
361 DownloadManager::CancelDownloadRequest(info->render_process_id, | |
362 info->request_id); | |
363 delete info; | |
364 return; | |
365 } | |
366 | |
367 StartUpdateTimer(); | |
368 | |
369 // Add the download manager to our request maps for future updates. We want to | |
370 // be able to cancel all in progress downloads when a DownloadManager is | |
371 // deleted, such as when a profile is closed. We also want to be able to look | |
372 // up the DownloadManager associated with a given request without having to | |
373 // rely on using tab information, since a tab may be closed while a download | |
374 // initiated from that tab is still in progress. | |
375 DownloadRequests& downloads = requests_[manager]; | |
376 downloads.insert(info->download_id); | |
377 | |
378 // TODO(paulg): The manager will exist when restarts are implemented. | |
379 DownloadManagerMap::iterator dit = managers_.find(info->download_id); | |
380 if (dit == managers_.end()) | |
381 managers_[info->download_id] = manager; | |
382 else | |
383 NOTREACHED(); | |
384 | |
385 // StartDownload will clean up |info|. | |
386 manager->StartDownload(info); | |
387 } | |
388 | |
389 // Update the Download Manager with the finish state, and remove the request | |
390 // tracking entries. | |
391 void DownloadFileManager::OnDownloadFinished(int id, | |
392 int64 bytes_so_far) { | |
393 DCHECK(MessageLoop::current() == ui_loop_); | |
394 DownloadManager* manager = LookupManager(id); | |
395 if (manager) | |
396 manager->DownloadFinished(id, bytes_so_far); | |
397 RemoveDownload(id, manager); | |
398 RemoveDownloadFromUIProgress(id); | |
399 } | |
400 | |
401 void DownloadFileManager::DownloadUrl(const GURL& url, | |
402 const GURL& referrer, | |
403 int render_process_host_id, | |
404 int render_view_id, | |
405 URLRequestContext* request_context) { | |
406 DCHECK(MessageLoop::current() == ui_loop_); | |
407 base::Thread* thread = g_browser_process->io_thread(); | |
408 if (thread) { | |
409 thread->message_loop()->PostTask(FROM_HERE, | |
410 NewRunnableMethod(this, | |
411 &DownloadFileManager::OnDownloadUrl, | |
412 url, | |
413 referrer, | |
414 render_process_host_id, | |
415 render_view_id, | |
416 request_context)); | |
417 } | |
418 } | |
419 | |
420 // Relate a download ID to its owning DownloadManager. | |
421 DownloadManager* DownloadFileManager::LookupManager(int download_id) { | |
422 DCHECK(MessageLoop::current() == ui_loop_); | |
423 DownloadManagerMap::iterator it = managers_.find(download_id); | |
424 if (it != managers_.end()) | |
425 return it->second; | |
426 return NULL; | |
427 } | |
428 | |
429 // Utility function for look up table maintenance, called on the UI thread. | |
430 // A manager may have multiple downloads in progress, so we just look up the | |
431 // one download (id) and remove it from the set, and remove the set if it | |
432 // becomes empty. | |
433 void DownloadFileManager::RemoveDownload(int id, DownloadManager* manager) { | |
434 DCHECK(MessageLoop::current() == ui_loop_); | |
435 if (manager) { | |
436 RequestMap::iterator it = requests_.find(manager); | |
437 if (it != requests_.end()) { | |
438 DownloadRequests& downloads = it->second; | |
439 DownloadRequests::iterator rit = downloads.find(id); | |
440 if (rit != downloads.end()) | |
441 downloads.erase(rit); | |
442 if (downloads.empty()) | |
443 requests_.erase(it); | |
444 } | |
445 } | |
446 | |
447 // A download can only have one manager, so remove it if it exists. | |
448 DownloadManagerMap::iterator dit = managers_.find(id); | |
449 if (dit != managers_.end()) | |
450 managers_.erase(dit); | |
451 } | |
452 | |
453 // Utility function for converting request IDs to a TabContents. Must be called | |
454 // only on the UI thread since Profile operations may create UI objects, such as | |
455 // the first call to profile->GetDownloadManager(). | |
456 // static | |
457 DownloadManager* DownloadFileManager::DownloadManagerFromRenderIds( | |
458 int render_process_id, int render_view_id) { | |
459 TabContents* contents = tab_util::GetTabContentsByID(render_process_id, | |
460 render_view_id); | |
461 if (contents && contents->type() == TAB_CONTENTS_WEB) { | |
462 Profile* profile = contents->profile(); | |
463 if (profile) | |
464 return profile->GetDownloadManager(); | |
465 } | |
466 | |
467 return NULL; | |
468 } | |
469 | |
470 // Called by DownloadManagers in their destructor, and only on the UI thread. | |
471 void DownloadFileManager::RemoveDownloadManager(DownloadManager* manager) { | |
472 DCHECK(MessageLoop::current() == ui_loop_); | |
473 DCHECK(manager); | |
474 RequestMap::iterator it = requests_.find(manager); | |
475 if (it == requests_.end()) | |
476 return; | |
477 | |
478 const DownloadRequests& requests = it->second; | |
479 DownloadRequests::const_iterator i = requests.begin(); | |
480 for (; i != requests.end(); ++i) { | |
481 DownloadManagerMap::iterator dit = managers_.find(*i); | |
482 if (dit != managers_.end()) { | |
483 DCHECK(dit->second == manager); | |
484 managers_.erase(dit); | |
485 } | |
486 } | |
487 | |
488 requests_.erase(it); | |
489 } | |
490 | |
491 | |
492 // Notifications from the UI thread and run on the IO thread | |
493 | |
494 // Initiate a request for URL to be downloaded. | |
495 void DownloadFileManager::OnDownloadUrl(const GURL& url, | |
496 const GURL& referrer, | |
497 int render_process_host_id, | |
498 int render_view_id, | |
499 URLRequestContext* request_context) { | |
500 DCHECK(MessageLoop::current() == io_loop_); | |
501 resource_dispatcher_host_->BeginDownload(url, | |
502 referrer, | |
503 render_process_host_id, | |
504 render_view_id, | |
505 request_context); | |
506 } | |
507 | |
508 // Actions from the UI thread and run on the download thread | |
509 | |
510 // Open a download, or show it in a Windows Explorer window. We run on this | |
511 // thread to avoid blocking the UI with (potentially) slow Shell operations. | |
512 // TODO(paulg): File 'stat' operations. | |
513 void DownloadFileManager::OnShowDownloadInShell(const std::wstring full_path) { | |
514 DCHECK(MessageLoop::current() == file_loop_); | |
515 win_util::ShowItemInFolder(full_path); | |
516 } | |
517 | |
518 // Launches the selected download using ShellExecute 'open' verb. If there is | |
519 // a valid parent window, the 'safer' version will be used which can | |
520 // display a modal dialog asking for user consent on dangerous files. | |
521 void DownloadFileManager::OnOpenDownloadInShell(const std::wstring full_path, | |
522 const std::wstring& url, | |
523 HWND parent_window) { | |
524 DCHECK(MessageLoop::current() == file_loop_); | |
525 if (NULL != parent_window) { | |
526 win_util::SaferOpenItemViaShell(parent_window, L"", full_path, url, true); | |
527 } else { | |
528 win_util::OpenItemViaShell(full_path, true); | |
529 } | |
530 } | |
531 | |
532 // The DownloadManager in the UI thread has provided a final name for the | |
533 // download specified by 'id'. Rename the in progress download, and remove it | |
534 // from our table if it has been completed or cancelled already. | |
535 void DownloadFileManager::OnFinalDownloadName(int id, | |
536 const std::wstring& full_path) { | |
537 DCHECK(MessageLoop::current() == file_loop_); | |
538 DownloadFileMap::iterator it = downloads_.find(id); | |
539 if (it == downloads_.end()) | |
540 return; | |
541 | |
542 std::wstring download_dir = file_util::GetDirectoryFromPath(full_path); | |
543 if (!file_util::PathExists(download_dir)) | |
544 file_util::CreateDirectory(download_dir); | |
545 | |
546 DownloadFile* download = it->second; | |
547 if (!download->Rename(full_path)) { | |
548 // Error. Between the time the UI thread generated 'full_path' to the time | |
549 // this code runs, something happened that prevents us from renaming. | |
550 DownloadManagerMap::iterator dmit = managers_.find(download->id()); | |
551 if (dmit != managers_.end()) { | |
552 DownloadManager* dlm = dmit->second; | |
553 ui_loop_->PostTask(FROM_HERE, | |
554 NewRunnableMethod(dlm, | |
555 &DownloadManager::DownloadCancelled, | |
556 id)); | |
557 } else { | |
558 ui_loop_->PostTask(FROM_HERE, | |
559 NewRunnableFunction(&DownloadManager::CancelDownloadRequest, | |
560 download->render_process_id(), | |
561 download->request_id())); | |
562 } | |
563 } | |
564 | |
565 // If the download has completed before we got this final name, we remove it | |
566 // from our in progress map. | |
567 if (!download->in_progress()) { | |
568 downloads_.erase(it); | |
569 delete download; | |
570 } | |
571 | |
572 if (downloads_.empty()) | |
573 ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( | |
574 this, &DownloadFileManager::StopUpdateTimer)); | |
575 } | |
576 | |
577 void DownloadFileManager::CreateDirectory(const std::wstring& directory) { | |
578 if (!file_util::PathExists(directory)) | |
579 file_util::CreateDirectory(directory); | |
580 } | |
OLD | NEW |