download_util.cc revision c407dc5cd9bdc5668497f21b26b09d988ab439de
1// Copyright (c) 2010 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// Download utility implementation 6 7#include "chrome/browser/download/download_util.h" 8 9#if defined(OS_WIN) 10#include <shobjidl.h> 11#endif 12#include <string> 13 14#include "app/l10n_util.h" 15#include "app/resource_bundle.h" 16#include "base/file_util.h" 17#include "base/i18n/rtl.h" 18#include "base/i18n/time_formatting.h" 19#include "base/path_service.h" 20#include "base/singleton.h" 21#include "base/string_util.h" 22#include "base/utf_string_conversions.h" 23#include "base/values.h" 24#include "chrome/browser/browser_process.h" 25#include "chrome/browser/chrome_thread.h" 26#include "chrome/browser/download/download_item.h" 27#include "chrome/browser/download/download_item_model.h" 28#include "chrome/browser/download/download_manager.h" 29#include "chrome/browser/net/chrome_url_request_context.h" 30#include "chrome/browser/renderer_host/resource_dispatcher_host.h" 31#include "chrome/common/chrome_paths.h" 32#include "chrome/common/extensions/extension.h" 33#include "chrome/common/time_format.h" 34#include "gfx/canvas_skia.h" 35#include "gfx/rect.h" 36#include "grit/generated_resources.h" 37#include "grit/locale_settings.h" 38#include "grit/theme_resources.h" 39#include "net/base/mime_util.h" 40#include "skia/ext/image_operations.h" 41#include "third_party/skia/include/core/SkPath.h" 42#include "third_party/skia/include/core/SkShader.h" 43 44#if defined(TOOLKIT_VIEWS) 45#include "app/os_exchange_data.h" 46#include "views/drag_utils.h" 47#endif 48 49#if defined(OS_LINUX) 50#if defined(TOOLKIT_VIEWS) 51#include "app/drag_drop_types.h" 52#include "views/widget/widget_gtk.h" 53#elif defined(TOOLKIT_GTK) 54#include "chrome/browser/gtk/custom_drag.h" 55#endif // defined(TOOLKIT_GTK) 56#endif // defined(OS_LINUX) 57 58#if defined(OS_WIN) 59#include "app/os_exchange_data_provider_win.h" 60#include "base/base_drag_source.h" 61#include "base/scoped_comptr_win.h" 62#include "base/win_util.h" 63#include "chrome/browser/browser_list.h" 64#include "chrome/browser/views/frame/browser_view.h" 65#endif 66 67namespace download_util { 68 69// How many times to cycle the complete animation. This should be an odd number 70// so that the animation ends faded out. 71static const int kCompleteAnimationCycles = 5; 72 73// Download opening ------------------------------------------------------------ 74 75bool CanOpenDownload(DownloadItem* download) { 76 FilePath file_to_use = download->full_path(); 77 if (!download->original_name().value().empty()) 78 file_to_use = download->original_name(); 79 80 return !Extension::IsExtension(file_to_use) && 81 !download->manager()->IsExecutableFile(file_to_use); 82} 83 84void OpenDownload(DownloadItem* download) { 85 if (download->state() == DownloadItem::IN_PROGRESS) { 86 download->set_open_when_complete(!download->open_when_complete()); 87 } else if (download->state() == DownloadItem::COMPLETE) { 88 download->NotifyObserversDownloadOpened(); 89 download->manager()->OpenDownload(download, NULL); 90 } 91} 92 93// Download temporary file creation -------------------------------------------- 94 95class DefaultDownloadDirectory { 96 public: 97 const FilePath& path() const { return path_; } 98 private: 99 DefaultDownloadDirectory() { 100 if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS, &path_)) { 101 NOTREACHED(); 102 } 103 if (DownloadPathIsDangerous(path_)) { 104 if (!PathService::Get(chrome::DIR_DEFAULT_DOWNLOADS_SAFE, &path_)) { 105 NOTREACHED(); 106 } 107 } 108 } 109 friend struct DefaultSingletonTraits<DefaultDownloadDirectory>; 110 FilePath path_; 111}; 112 113const FilePath& GetDefaultDownloadDirectory() { 114 return Singleton<DefaultDownloadDirectory>::get()->path(); 115} 116 117bool CreateTemporaryFileForDownload(FilePath* temp_file) { 118 if (file_util::CreateTemporaryFileInDir(GetDefaultDownloadDirectory(), 119 temp_file)) 120 return true; 121 return file_util::CreateTemporaryFile(temp_file); 122} 123 124bool DownloadPathIsDangerous(const FilePath& download_path) { 125 FilePath desktop_dir; 126 if (!PathService::Get(chrome::DIR_USER_DESKTOP, &desktop_dir)) { 127 NOTREACHED(); 128 return false; 129 } 130 return (download_path == desktop_dir); 131} 132 133// Download progress painting -------------------------------------------------- 134 135// Common bitmaps used for download progress animations. We load them once the 136// first time we do a progress paint, then reuse them as they are always the 137// same. 138SkBitmap* g_foreground_16 = NULL; 139SkBitmap* g_background_16 = NULL; 140SkBitmap* g_foreground_32 = NULL; 141SkBitmap* g_background_32 = NULL; 142 143void PaintDownloadProgress(gfx::Canvas* canvas, 144#if defined(TOOLKIT_VIEWS) 145 views::View* containing_view, 146#endif 147 int origin_x, 148 int origin_y, 149 int start_angle, 150 int percent_done, 151 PaintDownloadProgressSize size) { 152 // Load up our common bitmaps 153 if (!g_background_16) { 154 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 155 g_foreground_16 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16); 156 g_background_16 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_BACKGROUND_16); 157 g_foreground_32 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32); 158 g_background_32 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_BACKGROUND_32); 159 } 160 161 SkBitmap* background = (size == BIG) ? g_background_32 : g_background_16; 162 SkBitmap* foreground = (size == BIG) ? g_foreground_32 : g_foreground_16; 163 164 const int kProgressIconSize = (size == BIG) ? kBigProgressIconSize : 165 kSmallProgressIconSize; 166 167 // We start by storing the bounds of the background and foreground bitmaps 168 // so that it is easy to mirror the bounds if the UI layout is RTL. 169 gfx::Rect background_bounds(origin_x, origin_y, 170 background->width(), background->height()); 171 gfx::Rect foreground_bounds(origin_x, origin_y, 172 foreground->width(), foreground->height()); 173 174#if defined(TOOLKIT_VIEWS) 175 // Mirror the positions if necessary. 176 int mirrored_x = containing_view->MirroredLeftPointForRect(background_bounds); 177 background_bounds.set_x(mirrored_x); 178 mirrored_x = containing_view->MirroredLeftPointForRect(foreground_bounds); 179 foreground_bounds.set_x(mirrored_x); 180#endif 181 182 // Draw the background progress image. 183 SkPaint background_paint; 184 canvas->DrawBitmapInt(*background, 185 background_bounds.x(), 186 background_bounds.y(), 187 background_paint); 188 189 // Layer the foreground progress image in an arc proportional to the download 190 // progress. The arc grows clockwise, starting in the midnight position, as 191 // the download progresses. However, if the download does not have known total 192 // size (the server didn't give us one), then we just spin an arc around until 193 // we're done. 194 float sweep_angle = 0.0; 195 float start_pos = static_cast<float>(kStartAngleDegrees); 196 if (percent_done < 0) { 197 sweep_angle = kUnknownAngleDegrees; 198 start_pos = static_cast<float>(start_angle); 199 } else if (percent_done > 0) { 200 sweep_angle = static_cast<float>(kMaxDegrees / 100.0 * percent_done); 201 } 202 203 // Set up an arc clipping region for the foreground image. Don't bother using 204 // a clipping region if it would round to 360 (really 0) degrees, since that 205 // would eliminate the foreground completely and be quite confusing (it would 206 // look like 0% complete when it should be almost 100%). 207 SkPaint foreground_paint; 208 if (sweep_angle < static_cast<float>(kMaxDegrees - 1)) { 209 SkRect oval; 210 oval.set(SkIntToScalar(foreground_bounds.x()), 211 SkIntToScalar(foreground_bounds.y()), 212 SkIntToScalar(foreground_bounds.x() + kProgressIconSize), 213 SkIntToScalar(foreground_bounds.y() + kProgressIconSize)); 214 SkPath path; 215 path.arcTo(oval, 216 SkFloatToScalar(start_pos), 217 SkFloatToScalar(sweep_angle), false); 218 path.lineTo(SkIntToScalar(foreground_bounds.x() + kProgressIconSize / 2), 219 SkIntToScalar(foreground_bounds.y() + kProgressIconSize / 2)); 220 221 SkShader* shader = 222 SkShader::CreateBitmapShader(*foreground, 223 SkShader::kClamp_TileMode, 224 SkShader::kClamp_TileMode); 225 SkMatrix shader_scale; 226 shader_scale.setTranslate(SkIntToScalar(foreground_bounds.x()), 227 SkIntToScalar(foreground_bounds.y())); 228 shader->setLocalMatrix(shader_scale); 229 foreground_paint.setShader(shader); 230 foreground_paint.setAntiAlias(true); 231 shader->unref(); 232 canvas->AsCanvasSkia()->drawPath(path, foreground_paint); 233 return; 234 } 235 236 canvas->DrawBitmapInt(*foreground, 237 foreground_bounds.x(), 238 foreground_bounds.y(), 239 foreground_paint); 240} 241 242void PaintDownloadComplete(gfx::Canvas* canvas, 243#if defined(TOOLKIT_VIEWS) 244 views::View* containing_view, 245#endif 246 int origin_x, 247 int origin_y, 248 double animation_progress, 249 PaintDownloadProgressSize size) { 250 // Load up our common bitmaps. 251 if (!g_foreground_16) { 252 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 253 g_foreground_16 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_16); 254 g_foreground_32 = rb.GetBitmapNamed(IDR_DOWNLOAD_PROGRESS_FOREGROUND_32); 255 } 256 257 SkBitmap* complete = (size == BIG) ? g_foreground_32 : g_foreground_16; 258 259 gfx::Rect complete_bounds(origin_x, origin_y, 260 complete->width(), complete->height()); 261#if defined(TOOLKIT_VIEWS) 262 // Mirror the positions if necessary. 263 complete_bounds.set_x( 264 containing_view->MirroredLeftPointForRect(complete_bounds)); 265#endif 266 267 // Start at full opacity, then loop back and forth five times before ending 268 // at zero opacity. 269 static const double PI = 3.141592653589793; 270 double opacity = sin(animation_progress * PI * kCompleteAnimationCycles + 271 PI/2) / 2 + 0.5; 272 273 canvas->SaveLayerAlpha(static_cast<int>(255.0 * opacity), complete_bounds); 274 canvas->AsCanvasSkia()->drawARGB(0, 255, 255, 255, SkXfermode::kClear_Mode); 275 canvas->DrawBitmapInt(*complete, complete_bounds.x(), complete_bounds.y()); 276 canvas->Restore(); 277} 278 279// Load a language dependent height so that the dangerous download confirmation 280// message doesn't overlap with the download link label. 281int GetBigProgressIconSize() { 282 static int big_progress_icon_size = 0; 283 if (big_progress_icon_size == 0) { 284 string16 locale_size_str = 285 WideToUTF16Hack(l10n_util::GetString(IDS_DOWNLOAD_BIG_PROGRESS_SIZE)); 286 bool rc = StringToInt(locale_size_str, &big_progress_icon_size); 287 if (!rc || big_progress_icon_size < kBigProgressIconSize) { 288 NOTREACHED(); 289 big_progress_icon_size = kBigProgressIconSize; 290 } 291 } 292 293 return big_progress_icon_size; 294} 295 296int GetBigProgressIconOffset() { 297 return (GetBigProgressIconSize() - kBigIconSize) / 2; 298} 299 300#if defined(TOOLKIT_VIEWS) 301// Download dragging 302void DragDownload(const DownloadItem* download, 303 SkBitmap* icon, 304 gfx::NativeView view) { 305 DCHECK(download); 306 307 // Set up our OLE machinery 308 OSExchangeData data; 309 310 if (icon) { 311 drag_utils::CreateDragImageForFile(download->file_name().value(), icon, 312 &data); 313 } 314 315 const FilePath full_path = download->full_path(); 316 data.SetFilename(full_path.ToWStringHack()); 317 318 std::string mime_type = download->mime_type(); 319 if (mime_type.empty()) 320 net::GetMimeTypeFromFile(full_path, &mime_type); 321 322 // Add URL so that we can load supported files when dragged to TabContents. 323 if (net::IsSupportedMimeType(mime_type)) { 324 data.SetURL(GURL(WideToUTF8(full_path.ToWStringHack())), 325 download->file_name().ToWStringHack()); 326 } 327 328#if defined(OS_WIN) 329 scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); 330 331 // Run the drag and drop loop 332 DWORD effects; 333 DoDragDrop(OSExchangeDataProviderWin::GetIDataObject(data), drag_source.get(), 334 DROPEFFECT_COPY | DROPEFFECT_LINK, &effects); 335#elif defined(OS_LINUX) 336 GtkWidget* root = gtk_widget_get_toplevel(view); 337 if (!root) 338 return; 339 views::WidgetGtk* widget = views::WidgetGtk::GetViewForNative(root); 340 if (!widget) 341 return; 342 343 widget->DoDrag(data, DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK); 344#endif // OS_WIN 345} 346#elif defined(OS_LINUX) 347void DragDownload(const DownloadItem* download, 348 SkBitmap* icon, 349 gfx::NativeView view) { 350 DownloadItemDrag::BeginDrag(download, icon); 351} 352#endif // OS_LINUX 353 354DictionaryValue* CreateDownloadItemValue(DownloadItem* download, int id) { 355 DictionaryValue* file_value = new DictionaryValue(); 356 357 file_value->SetInteger(L"started", 358 static_cast<int>(download->start_time().ToTimeT())); 359 file_value->SetString(L"since_string", 360 TimeFormat::RelativeDate(download->start_time(), NULL)); 361 file_value->SetString(L"date_string", 362 base::TimeFormatShortDate(download->start_time())); 363 file_value->SetInteger(L"id", id); 364 file_value->SetString(L"file_path", download->full_path().ToWStringHack()); 365 // Keep file names as LTR. 366 std::wstring file_name = download->GetFileName().ToWStringHack(); 367 base::i18n::GetDisplayStringInLTRDirectionality(&file_name); 368 file_value->SetString(L"file_name", file_name); 369 file_value->SetString(L"url", download->url().spec()); 370 file_value->SetBoolean(L"otr", download->is_otr()); 371 372 if (download->state() == DownloadItem::IN_PROGRESS) { 373 if (download->safety_state() == DownloadItem::DANGEROUS) { 374 file_value->SetString(L"state", L"DANGEROUS"); 375 } else if (download->is_paused()) { 376 file_value->SetString(L"state", L"PAUSED"); 377 } else { 378 file_value->SetString(L"state", L"IN_PROGRESS"); 379 } 380 381 file_value->SetString(L"progress_status_text", 382 GetProgressStatusText(download)); 383 384 file_value->SetInteger(L"percent", 385 static_cast<int>(download->PercentComplete())); 386 file_value->SetInteger(L"received", 387 static_cast<int>(download->received_bytes())); 388 } else if (download->state() == DownloadItem::CANCELLED) { 389 file_value->SetString(L"state", L"CANCELLED"); 390 } else if (download->state() == DownloadItem::COMPLETE) { 391 if (download->safety_state() == DownloadItem::DANGEROUS) { 392 file_value->SetString(L"state", L"DANGEROUS"); 393 } else { 394 file_value->SetString(L"state", L"COMPLETE"); 395 } 396 } 397 398 file_value->SetInteger(L"total", 399 static_cast<int>(download->total_bytes())); 400 401 return file_value; 402} 403 404std::wstring GetProgressStatusText(DownloadItem* download) { 405 int64 total = download->total_bytes(); 406 int64 size = download->received_bytes(); 407 DataUnits amount_units = GetByteDisplayUnits(size); 408 std::wstring received_size = FormatBytes(size, amount_units, true); 409 std::wstring amount = received_size; 410 411 // Adjust both strings for the locale direction since we don't yet know which 412 // string we'll end up using for constructing the final progress string. 413 std::wstring amount_localized; 414 if (base::i18n::AdjustStringForLocaleDirection(amount, &amount_localized)) { 415 amount.assign(amount_localized); 416 received_size.assign(amount_localized); 417 } 418 419 if (total) { 420 amount_units = GetByteDisplayUnits(total); 421 std::wstring total_text = FormatBytes(total, amount_units, true); 422 std::wstring total_text_localized; 423 if (base::i18n::AdjustStringForLocaleDirection(total_text, 424 &total_text_localized)) 425 total_text.assign(total_text_localized); 426 427 amount = l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SIZE, 428 received_size, 429 total_text); 430 } else { 431 amount.assign(received_size); 432 } 433 amount_units = GetByteDisplayUnits(download->CurrentSpeed()); 434 std::wstring speed_text = FormatSpeed(download->CurrentSpeed(), 435 amount_units, true); 436 std::wstring speed_text_localized; 437 if (base::i18n::AdjustStringForLocaleDirection(speed_text, 438 &speed_text_localized)) 439 speed_text.assign(speed_text_localized); 440 441 base::TimeDelta remaining; 442 std::wstring time_remaining; 443 if (download->is_paused()) 444 time_remaining = l10n_util::GetString(IDS_DOWNLOAD_PROGRESS_PAUSED); 445 else if (download->TimeRemaining(&remaining)) 446 time_remaining = TimeFormat::TimeRemaining(remaining); 447 448 if (time_remaining.empty()) { 449 return l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_STATUS_TIME_UNKNOWN, 450 speed_text, amount); 451 } 452 return l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_STATUS, speed_text, 453 amount, time_remaining); 454} 455 456#if !defined(OS_MACOSX) 457void UpdateAppIconDownloadProgress(int download_count, 458 bool progress_known, 459 float progress) { 460#if defined(OS_WIN) 461 // Taskbar progress bar is only supported on Win7. 462 if (win_util::GetWinVersion() < win_util::WINVERSION_WIN7) 463 return; 464 465 ScopedComPtr<ITaskbarList3> taskbar; 466 HRESULT result = taskbar.CreateInstance(CLSID_TaskbarList, NULL, 467 CLSCTX_INPROC_SERVER); 468 if (FAILED(result)) { 469 LOG(INFO) << "failed creating a TaskbarList object: " << result; 470 return; 471 } 472 473 result = taskbar->HrInit(); 474 if (FAILED(result)) { 475 LOG(ERROR) << "failed initializing an ITaskbarList3 interface."; 476 return; 477 } 478 479 // Iterate through all the browser windows, and draw the progress bar. 480 for (BrowserList::const_iterator browser_iterator = BrowserList::begin(); 481 browser_iterator != BrowserList::end(); browser_iterator++) { 482 HWND frame = (*browser_iterator)->window()->GetNativeHandle(); 483 if (download_count == 0 || progress == 1.0f) 484 taskbar->SetProgressState(frame, TBPF_NOPROGRESS); 485 else if (!progress_known) 486 taskbar->SetProgressState(frame, TBPF_INDETERMINATE); 487 else 488 taskbar->SetProgressValue(frame, (int)(progress * 100), 100); 489 } 490#endif 491} 492#endif 493 494// Appends the passed the number between parenthesis the path before the 495// extension. 496void AppendNumberToPath(FilePath* path, int number) { 497 *path = path->InsertBeforeExtensionASCII(StringPrintf(" (%d)", number)); 498} 499 500// Attempts to find a number that can be appended to that path to make it 501// unique. If |path| does not exist, 0 is returned. If it fails to find such 502// a number, -1 is returned. 503int GetUniquePathNumber(const FilePath& path) { 504 const int kMaxAttempts = 100; 505 506 if (!file_util::PathExists(path)) 507 return 0; 508 509 FilePath new_path; 510 for (int count = 1; count <= kMaxAttempts; ++count) { 511 new_path = FilePath(path); 512 AppendNumberToPath(&new_path, count); 513 514 if (!file_util::PathExists(new_path)) 515 return count; 516 } 517 518 return -1; 519} 520 521void DownloadUrl( 522 const GURL& url, 523 const GURL& referrer, 524 const std::string& referrer_charset, 525 const DownloadSaveInfo& save_info, 526 ResourceDispatcherHost* rdh, 527 int render_process_host_id, 528 int render_view_id, 529 URLRequestContextGetter* request_context_getter) { 530 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); 531 532 URLRequestContext* context = request_context_getter->GetURLRequestContext(); 533 context->set_referrer_charset(referrer_charset); 534 535 rdh->BeginDownload(url, 536 referrer, 537 save_info, 538 true, // Show "Save as" UI. 539 render_process_host_id, 540 render_view_id, 541 context); 542} 543 544void CancelDownloadRequest(ResourceDispatcherHost* rdh, 545 int render_process_id, 546 int request_id) { 547 DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO)); 548 rdh->CancelRequest(render_process_id, request_id, false); 549} 550 551int GetUniquePathNumberWithCrDownload(const FilePath& path) { 552 const int kMaxAttempts = 100; 553 554 if (!file_util::PathExists(path) && 555 !file_util::PathExists(GetCrDownloadPath(path))) 556 return 0; 557 558 FilePath new_path; 559 for (int count = 1; count <= kMaxAttempts; ++count) { 560 new_path = FilePath(path); 561 AppendNumberToPath(&new_path, count); 562 563 if (!file_util::PathExists(new_path) && 564 !file_util::PathExists(GetCrDownloadPath(new_path))) 565 return count; 566 } 567 568 return -1; 569} 570 571FilePath GetCrDownloadPath(const FilePath& suggested_path) { 572 FilePath::StringType file_name; 573 SStringPrintf(&file_name, PRFilePathLiteral FILE_PATH_LITERAL(".crdownload"), 574 suggested_path.value().c_str()); 575 return FilePath(file_name); 576} 577 578} // namespace download_util 579