file_select_helper.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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#include "chrome/browser/file_select_helper.h"
6
7#include <string>
8
9#include "base/file_util.h"
10#include "base/string_split.h"
11#include "base/string_util.h"
12#include "base/utf_string_conversions.h"
13#include "net/base/mime_util.h"
14#include "chrome/browser/platform_util.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/renderer_host/render_view_host.h"
17#include "chrome/browser/renderer_host/render_widget_host_view.h"
18#include "chrome/common/notification_details.h"
19#include "chrome/common/notification_source.h"
20#include "chrome/common/render_messages_params.h"
21#include "grit/generated_resources.h"
22#include "ui/base/l10n/l10n_util.h"
23
24FileSelectHelper::FileSelectHelper(Profile* profile)
25    : profile_(profile),
26      render_view_host_(NULL),
27      select_file_dialog_(),
28      dialog_type_(SelectFileDialog::SELECT_OPEN_FILE) {
29}
30
31FileSelectHelper::~FileSelectHelper() {
32  // There may be pending file dialogs, we need to tell them that we've gone
33  // away so they don't try and call back to us.
34  if (select_file_dialog_.get())
35    select_file_dialog_->ListenerDestroyed();
36
37  // Stop any pending directory enumeration and prevent a callback.
38  if (directory_lister_.get()) {
39    directory_lister_->set_delegate(NULL);
40    directory_lister_->Cancel();
41  }
42}
43
44void FileSelectHelper::FileSelected(const FilePath& path,
45                                    int index, void* params) {
46  if (!render_view_host_)
47    return;
48
49  profile_->set_last_selected_directory(path.DirName());
50
51  if (dialog_type_ == SelectFileDialog::SELECT_FOLDER) {
52    DirectorySelected(path);
53    return;
54  }
55
56  std::vector<FilePath> files;
57  files.push_back(path);
58  render_view_host_->FilesSelectedInChooser(files);
59  // We are done with this showing of the dialog.
60  render_view_host_ = NULL;
61}
62
63void FileSelectHelper::MultiFilesSelected(const std::vector<FilePath>& files,
64                                          void* params) {
65  if (!files.empty())
66    profile_->set_last_selected_directory(files[0].DirName());
67  if (!render_view_host_)
68    return;
69
70  render_view_host_->FilesSelectedInChooser(files);
71  // We are done with this showing of the dialog.
72  render_view_host_ = NULL;
73}
74
75void FileSelectHelper::FileSelectionCanceled(void* params) {
76  if (!render_view_host_)
77    return;
78
79  // If the user cancels choosing a file to upload we pass back an
80  // empty vector.
81  render_view_host_->FilesSelectedInChooser(std::vector<FilePath>());
82
83  // We are done with this showing of the dialog.
84  render_view_host_ = NULL;
85}
86
87void FileSelectHelper::DirectorySelected(const FilePath& path) {
88  directory_lister_ = new net::DirectoryLister(path,
89                                               true,
90                                               net::DirectoryLister::NO_SORT,
91                                               this);
92  if (!directory_lister_->Start())
93    FileSelectionCanceled(NULL);
94}
95
96void FileSelectHelper::OnListFile(
97    const net::DirectoryLister::DirectoryListerData& data) {
98  // Directory upload returns directories via a "." file, so that
99  // empty directories are included.  This util call just checks
100  // the flags in the structure; there's no file I/O going on.
101  if (file_util::FileEnumerator::IsDirectory(data.info))
102    directory_lister_results_.push_back(
103        data.path.Append(FILE_PATH_LITERAL(".")));
104  else
105    directory_lister_results_.push_back(data.path);
106}
107
108void FileSelectHelper::OnListDone(int error) {
109  if (!render_view_host_)
110    return;
111
112  if (error) {
113    FileSelectionCanceled(NULL);
114    return;
115  }
116
117  render_view_host_->FilesSelectedInChooser(directory_lister_results_);
118  render_view_host_ = NULL;
119  directory_lister_ = NULL;
120  directory_lister_results_.clear();
121}
122
123SelectFileDialog::FileTypeInfo* FileSelectHelper::GetFileTypesFromAcceptType(
124    const string16& accept_types) {
125  if (accept_types.empty())
126    return NULL;
127
128  // Split the accept-type string on commas.
129  std::vector<string16> mime_types;
130  base::SplitStringUsingSubstr(accept_types, ASCIIToUTF16(","), &mime_types);
131  if (mime_types.empty())
132    return NULL;
133
134  // Create FileTypeInfo and pre-allocate for the first extension list.
135  scoped_ptr<SelectFileDialog::FileTypeInfo> file_type(
136      new SelectFileDialog::FileTypeInfo());
137  file_type->include_all_files = true;
138  file_type->extensions.resize(1);
139  std::vector<FilePath::StringType>* extensions = &file_type->extensions.back();
140
141  // Find the correspondinge extensions.
142  int valid_type_count = 0;
143  int description_id = 0;
144  for (size_t i = 0; i < mime_types.size(); ++i) {
145    string16 mime_type = mime_types[i];
146    std::string ascii_mime_type = StringToLowerASCII(UTF16ToASCII(mime_type));
147
148    TrimWhitespace(ascii_mime_type, TRIM_ALL, &ascii_mime_type);
149    if (ascii_mime_type.empty())
150      continue;
151
152    size_t old_extension_size = extensions->size();
153    if (ascii_mime_type == "image/*") {
154      description_id = IDS_IMAGE_FILES;
155      net::GetImageExtensions(extensions);
156    } else if (ascii_mime_type == "audio/*") {
157      description_id = IDS_AUDIO_FILES;
158      net::GetAudioExtensions(extensions);
159    } else if (ascii_mime_type == "video/*") {
160      description_id = IDS_VIDEO_FILES;
161      net::GetVideoExtensions(extensions);
162    } else {
163      net::GetExtensionsForMimeType(ascii_mime_type, extensions);
164    }
165
166    if (extensions->size() > old_extension_size)
167      valid_type_count++;
168  }
169
170  // If no valid extension is added, bail out.
171  if (valid_type_count == 0)
172    return NULL;
173
174  // Use a generic description "Custom Files" if either of the following is
175  // true:
176  // 1) There're multiple types specified, like "audio/*,video/*"
177  // 2) There're multiple extensions for a MIME type without parameter, like
178  //    "ehtml,shtml,htm,html" for "text/html". On Windows, the select file
179  //    dialog uses the first extension in the list to form the description,
180  //    like "EHTML Files". This is not what we want.
181  if (valid_type_count > 1 ||
182      (valid_type_count == 1 && description_id == 0 && extensions->size() > 1))
183    description_id = IDS_CUSTOM_FILES;
184
185  if (description_id) {
186    file_type->extension_description_overrides.push_back(
187        l10n_util::GetStringUTF16(description_id));
188  }
189
190  return file_type.release();
191}
192
193void FileSelectHelper::RunFileChooser(
194    RenderViewHost* render_view_host,
195    const ViewHostMsg_RunFileChooser_Params &params) {
196  DCHECK(!render_view_host_);
197  render_view_host_ = render_view_host;
198  notification_registrar_.RemoveAll();
199  notification_registrar_.Add(this,
200                              NotificationType::RENDER_WIDGET_HOST_DESTROYED,
201                              Source<RenderViewHost>(render_view_host));
202
203  if (!select_file_dialog_.get())
204    select_file_dialog_ = SelectFileDialog::Create(this);
205
206  switch (params.mode) {
207    case ViewHostMsg_RunFileChooser_Params::Open:
208      dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;
209      break;
210    case ViewHostMsg_RunFileChooser_Params::OpenMultiple:
211      dialog_type_ = SelectFileDialog::SELECT_OPEN_MULTI_FILE;
212      break;
213    case ViewHostMsg_RunFileChooser_Params::OpenFolder:
214      dialog_type_ = SelectFileDialog::SELECT_FOLDER;
215      break;
216    case ViewHostMsg_RunFileChooser_Params::Save:
217      dialog_type_ = SelectFileDialog::SELECT_SAVEAS_FILE;
218      break;
219    default:
220      dialog_type_ = SelectFileDialog::SELECT_OPEN_FILE;  // Prevent warning.
221      NOTREACHED();
222  }
223  scoped_ptr<SelectFileDialog::FileTypeInfo> file_types(
224      GetFileTypesFromAcceptType(params.accept_types));
225  FilePath default_file_name = params.default_file_name;
226  if (default_file_name.empty())
227    default_file_name = profile_->last_selected_directory();
228
229  gfx::NativeWindow owning_window =
230      platform_util::GetTopLevel(render_view_host_->view()->GetNativeView());
231  select_file_dialog_->SelectFile(dialog_type_,
232                                  params.title,
233                                  default_file_name,
234                                  file_types.get(),
235                                  file_types.get() ? 1 : 0,  // 1-based index.
236                                  FILE_PATH_LITERAL(""),
237                                  owning_window,
238                                  NULL);
239}
240
241void FileSelectHelper::Observe(NotificationType type,
242                               const NotificationSource& source,
243                               const NotificationDetails& details) {
244  DCHECK(type == NotificationType::RENDER_WIDGET_HOST_DESTROYED);
245  DCHECK(Details<RenderViewHost>(details).ptr() == render_view_host_);
246  render_view_host_ = NULL;
247}
248