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