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

Side by Side Diff: chrome/browser/download_file.cc

Issue 2826: Move the download code to new directories: (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 12 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/download_file.h ('k') | chrome/browser/download_item_model.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « chrome/browser/download_file.h ('k') | chrome/browser/download_item_model.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698