blob: 81616d56280e357037db79394a8462a04adef5e5 [file] [log] [blame]
[email protected]b3841c502011-03-09 01:21:311// Copyright (c) 2011 The Chromium Authors. All rights reserved.
[email protected]ba70d082010-09-10 16:54:492// 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
[email protected]5ac950b2010-12-09 21:34:257#include <string>
8
[email protected]ba70d082010-09-10 16:54:499#include "base/file_util.h"
10#include "base/string_split.h"
11#include "base/string_util.h"
12#include "base/utf_string_conversions.h"
[email protected]ba70d082010-09-10 16:54:4913#include "chrome/browser/platform_util.h"
[email protected]8ecad5e2010-12-02 21:18:3314#include "chrome/browser/profiles/profile.h"
[email protected]ba70d082010-09-10 16:54:4915#include "chrome/common/render_messages_params.h"
[email protected]5de634712011-03-02 00:20:1916#include "content/browser/renderer_host/render_view_host.h"
17#include "content/browser/renderer_host/render_widget_host_view.h"
[email protected]b3841c502011-03-09 01:21:3118#include "content/common/notification_details.h"
19#include "content/common/notification_source.h"
[email protected]ba70d082010-09-10 16:54:4920#include "grit/generated_resources.h"
[email protected]b3841c502011-03-09 01:21:3121#include "net/base/mime_util.h"
[email protected]c051a1b2011-01-21 23:30:1722#include "ui/base/l10n/l10n_util.h"
[email protected]ba70d082010-09-10 16:54:4923
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) {
[email protected]9897e092011-02-04 22:09:1198 // Directory upload returns directories via a "." file, so that
99 // empty directories are included. This util call just checks
[email protected]ba70d082010-09-10 16:54:49100 // the flags in the structure; there's no file I/O going on.
101 if (file_util::FileEnumerator::IsDirectory(data.info))
[email protected]9897e092011-02-04 22:09:11102 directory_lister_results_.push_back(
103 data.path.Append(FILE_PATH_LITERAL(".")));
104 else
105 directory_lister_results_.push_back(data.path);
[email protected]ba70d082010-09-10 16:54:49106}
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
[email protected]cbcd12ed2010-12-16 23:42:57170 // If no valid extension is added, bail out.
171 if (valid_type_count == 0)
172 return NULL;
173
[email protected]ba70d082010-09-10 16:54:49174 // 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(),
[email protected]cbcd12ed2010-12-16 23:42:57235 file_types.get() ? 1 : 0, // 1-based index.
[email protected]ba70d082010-09-10 16:54:49236 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}