1 // Copyright (c) 2013 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.
6 #include "win8/metro_driver/file_picker_ash.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/strings/string_util.h"
12 #include "base/synchronization/waitable_event.h"
13 #include "base/win/metro.h"
14 #include "base/win/scoped_comptr.h"
15 #include "ui/metro_viewer/metro_viewer_messages.h"
16 #include "win8/metro_driver/chrome_app_view_ash.h"
17 #include "win8/metro_driver/winrt_utils.h"
21 namespace winstorage
= ABI::Windows::Storage
;
22 typedef winfoundtn::Collections::IVector
<HSTRING
> StringVectorItf
;
24 // TODO(siggi): Complete this implementation and move it to a common place.
25 class StringVectorImpl
: public mswr::RuntimeClass
<StringVectorItf
> {
28 std::for_each(strings_
.begin(), strings_
.end(), ::WindowsDeleteString
);
31 HRESULT
RuntimeClassInitialize(const std::vector
<base::string16
>& list
) {
32 for (size_t i
= 0; i
< list
.size(); ++i
)
33 strings_
.push_back(MakeHString(list
[i
]));
38 // IVector<HSTRING> implementation.
39 STDMETHOD(GetAt
)(unsigned index
, HSTRING
* item
) {
40 if (index
>= strings_
.size())
43 return ::WindowsDuplicateString(strings_
[index
], item
);
45 STDMETHOD(get_Size
)(unsigned *size
) {
46 if (strings_
.size() > UINT_MAX
)
48 *size
= static_cast<unsigned>(strings_
.size());
51 STDMETHOD(GetView
)(winfoundtn::Collections::IVectorView
<HSTRING
> **view
) {
54 STDMETHOD(IndexOf
)(HSTRING value
, unsigned *index
, boolean
*found
) {
59 STDMETHOD(SetAt
)(unsigned index
, HSTRING item
) {
62 STDMETHOD(InsertAt
)(unsigned index
, HSTRING item
) {
65 STDMETHOD(RemoveAt
)(unsigned index
) {
68 STDMETHOD(Append
)(HSTRING item
) {
71 STDMETHOD(RemoveAtEnd
)() {
79 std::vector
<HSTRING
> strings_
;
84 FilePickerSessionBase::FilePickerSessionBase(ChromeAppViewAsh
* app_view
,
85 const base::string16
& title
,
86 const base::string16
& filter
,
87 const base::FilePath
& default_path
)
88 : app_view_(app_view
),
91 default_path_(default_path
),
95 bool FilePickerSessionBase::Run() {
101 bool FilePickerSessionBase::DoFilePicker() {
102 // The file picker will fail if spawned from a snapped application,
103 // so let's attempt to unsnap first if we're in that state.
104 HRESULT hr
= ChromeAppViewAsh::Unsnap();
106 LOG(ERROR
) << "Failed to unsnap for file picker, error 0x" << hr
;
109 hr
= StartFilePicker();
111 LOG(ERROR
) << "Failed to start file picker, error 0x"
118 OpenFilePickerSession::OpenFilePickerSession(
119 ChromeAppViewAsh
* app_view
,
120 const base::string16
& title
,
121 const base::string16
& filter
,
122 const base::FilePath
& default_path
,
123 bool allow_multi_select
)
124 : FilePickerSessionBase(app_view
, title
, filter
, default_path
),
125 allow_multi_select_(allow_multi_select
) {
128 HRESULT
OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp
* async
,
129 AsyncStatus status
) {
130 if (status
== Completed
) {
131 mswr::ComPtr
<winstorage::IStorageFile
> file
;
132 HRESULT hr
= async
->GetResults(file
.GetAddressOf());
135 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
137 hr
= file
.As(&storage_item
);
139 mswrw::HString file_path
;
141 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
145 const wchar_t* path_str
=
146 ::WindowsGetStringRawBuffer(file_path
.Get(), &path_len
);
152 LOG(ERROR
) << "NULL IStorageItem";
155 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
157 app_view_
->OnOpenFileCompleted(this, success_
);
161 HRESULT
OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp
* async
,
162 AsyncStatus status
) {
163 if (status
== Completed
) {
164 mswr::ComPtr
<StorageFileVectorCollection
> files
;
165 HRESULT hr
= async
->GetResults(files
.GetAddressOf());
168 base::string16 result
;
170 hr
= ComposeMultiFileResult(files
.Get(), &result
);
174 // The code below has been copied from the
175 // SelectFileDialogImpl::RunOpenMultiFileDialog function in
176 // select_file_dialog_win.cc.
178 // Consolidate this into a common place.
179 const wchar_t* selection
= result
.c_str();
180 std::vector
<base::FilePath
> files
;
182 while (*selection
) { // Empty string indicates end of list.
183 files
.push_back(base::FilePath(selection
));
184 // Skip over filename and null-terminator.
185 selection
+= files
.back().value().length() + 1;
189 } else if (files
.size() == 1) {
190 // When there is one file, it contains the path and filename.
192 } else if (files
.size() > 1) {
193 // Otherwise, the first string is the path, and the remainder are
195 std::vector
<base::FilePath
>::iterator path
= files
.begin();
196 for (std::vector
<base::FilePath
>::iterator file
= path
+ 1;
197 file
!= files
.end(); ++file
) {
198 filenames_
.push_back(path
->Append(*file
));
203 LOG(ERROR
) << "NULL StorageFileVectorCollection";
206 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
208 app_view_
->OnOpenFileCompleted(this, success_
);
212 HRESULT
OpenFilePickerSession::StartFilePicker() {
213 mswrw::HStringReference
class_name(
214 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker
);
216 // Create the file picker.
217 mswr::ComPtr
<winstorage::Pickers::IFileOpenPicker
> picker
;
218 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
219 class_name
.Get(), picker
.GetAddressOf());
222 // Set the file type filter
223 mswr::ComPtr
<winfoundtn::Collections::IVector
<HSTRING
>> filter
;
224 hr
= picker
->get_FileTypeFilter(filter
.GetAddressOf());
228 if (filter_
.empty()) {
229 hr
= filter
->Append(mswrw::HStringReference(L
"*").Get());
233 // The filter is a concatenation of zero terminated string pairs,
234 // where each pair is {description, extension}. The concatenation ends
235 // with a zero length string - e.g. a double zero terminator.
236 const wchar_t* walk
= filter_
.c_str();
237 while (*walk
!= L
'\0') {
238 // Walk past the description.
239 walk
+= wcslen(walk
) + 1;
241 // We should have an extension, but bail on malformed filters.
245 // There can be a single extension, or a list of semicolon-separated ones.
246 std::vector
<base::string16
> extensions_win32_style
;
247 size_t extension_count
= Tokenize(walk
, L
";", &extensions_win32_style
);
248 DCHECK_EQ(extension_count
, extensions_win32_style
.size());
250 // Metro wants suffixes only, not patterns.
251 mswrw::HString extension
;
252 for (size_t i
= 0; i
< extensions_win32_style
.size(); ++i
) {
253 if (extensions_win32_style
[i
] == L
"*.*") {
254 // The wildcard filter is "*" for Metro. The string "*.*" produces
255 // an "invalid parameter" error.
256 hr
= extension
.Set(L
"*");
258 // Metro wants suffixes only, not patterns.
260 base::FilePath(extensions_win32_style
[i
]).Extension();
261 if ((ext
.size() < 2) ||
262 (ext
.find_first_of(L
"*?") != base::string16::npos
)) {
265 hr
= extension
.Set(ext
.c_str());
268 hr
= filter
->Append(extension
.Get());
273 // Walk past the extension.
274 walk
+= wcslen(walk
) + 1;
278 // Spin up a single or multi picker as appropriate.
279 if (allow_multi_select_
) {
280 mswr::ComPtr
<MultiFileAsyncOp
> completion
;
281 hr
= picker
->PickMultipleFilesAsync(&completion
);
285 // Create the callback method.
286 typedef winfoundtn::IAsyncOperationCompletedHandler
<
287 StorageFileVectorCollection
*> HandlerDoneType
;
288 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
289 this, &OpenFilePickerSession::MultiPickerDone
));
290 DCHECK(handler
.Get() != NULL
);
291 hr
= completion
->put_Completed(handler
.Get());
295 mswr::ComPtr
<SingleFileAsyncOp
> completion
;
296 hr
= picker
->PickSingleFileAsync(&completion
);
300 // Create the callback method.
301 typedef winfoundtn::IAsyncOperationCompletedHandler
<
302 winstorage::StorageFile
*> HandlerDoneType
;
303 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
304 this, &OpenFilePickerSession::SinglePickerDone
));
305 DCHECK(handler
.Get() != NULL
);
306 hr
= completion
->put_Completed(handler
.Get());
312 HRESULT
OpenFilePickerSession::ComposeMultiFileResult(
313 StorageFileVectorCollection
* files
, base::string16
* result
) {
314 DCHECK(files
!= NULL
);
315 DCHECK(result
!= NULL
);
317 // Empty the output string.
320 unsigned int num_files
= 0;
321 HRESULT hr
= files
->get_Size(&num_files
);
325 // Make sure we return an error on an empty collection.
326 if (num_files
== 0) {
327 DLOG(ERROR
) << "Empty collection on input.";
331 // This stores the base path that should be the parent of all the files.
332 base::FilePath base_path
;
334 // Iterate through the collection and append the file paths to the result.
335 for (unsigned int i
= 0; i
< num_files
; ++i
) {
336 mswr::ComPtr
<winstorage::IStorageFile
> file
;
337 hr
= files
->GetAt(i
, file
.GetAddressOf());
341 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
342 hr
= file
.As(&storage_item
);
346 mswrw::HString file_path_str
;
347 hr
= storage_item
->get_Path(file_path_str
.GetAddressOf());
351 base::FilePath
file_path(MakeStdWString(file_path_str
.Get()));
352 if (base_path
.empty()) {
353 DCHECK(result
->empty());
354 base_path
= file_path
.DirName();
356 // Append the path, including the terminating zero.
357 // We do this only for the first file.
358 result
->append(base_path
.value().c_str(), base_path
.value().size() + 1);
360 DCHECK(!result
->empty());
361 DCHECK(!base_path
.empty());
362 DCHECK(base_path
== file_path
.DirName());
364 // Append the base name, including the terminating zero.
365 base::FilePath base_name
= file_path
.BaseName();
366 result
->append(base_name
.value().c_str(), base_name
.value().size() + 1);
369 DCHECK(!result
->empty());
374 SaveFilePickerSession::SaveFilePickerSession(
375 ChromeAppViewAsh
* app_view
,
376 const MetroViewerHostMsg_SaveAsDialogParams
& params
)
377 : FilePickerSessionBase(app_view
,
380 params
.suggested_name
),
381 filter_index_(params
.filter_index
) {
384 int SaveFilePickerSession::filter_index() const {
386 // Add support for returning the correct filter index. This does not work in
387 // regular Chrome metro on trunk as well.
388 // BUG: https://code.google.com/p/chromium/issues/detail?id=172704
389 return filter_index_
;
392 HRESULT
SaveFilePickerSession::StartFilePicker() {
393 mswrw::HStringReference
class_name(
394 RuntimeClass_Windows_Storage_Pickers_FileSavePicker
);
396 // Create the file picker.
397 mswr::ComPtr
<winstorage::Pickers::IFileSavePicker
> picker
;
398 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
399 class_name
.Get(), picker
.GetAddressOf());
402 typedef winfoundtn::Collections::IMap
<HSTRING
, StringVectorItf
*>
404 mswr::ComPtr
<StringVectorMap
> choices
;
405 hr
= picker
->get_FileTypeChoices(choices
.GetAddressOf());
409 if (!filter_
.empty()) {
410 // The filter is a concatenation of zero terminated string pairs,
411 // where each pair is {description, extension list}. The concatenation ends
412 // with a zero length string - e.g. a double zero terminator.
413 const wchar_t* walk
= filter_
.c_str();
414 while (*walk
!= L
'\0') {
415 mswrw::HString description
;
416 hr
= description
.Set(walk
);
420 // Walk past the description.
421 walk
+= wcslen(walk
) + 1;
423 // We should have an extension, but bail on malformed filters.
427 // There can be a single extension, or a list of semicolon-separated ones.
428 std::vector
<base::string16
> extensions_win32_style
;
429 size_t extension_count
= Tokenize(walk
, L
";", &extensions_win32_style
);
430 DCHECK_EQ(extension_count
, extensions_win32_style
.size());
432 // Metro wants suffixes only, not patterns. Also, metro does not support
433 // the all files ("*") pattern in the save picker.
434 std::vector
<base::string16
> extensions
;
435 for (size_t i
= 0; i
< extensions_win32_style
.size(); ++i
) {
437 base::FilePath(extensions_win32_style
[i
]).Extension();
438 if ((ext
.size() < 2) ||
439 (ext
.find_first_of(L
"*?") != base::string16::npos
))
441 extensions
.push_back(ext
);
444 if (!extensions
.empty()) {
445 // Convert to a Metro collection class.
446 mswr::ComPtr
<StringVectorItf
> list
;
447 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
448 list
.GetAddressOf(), extensions
);
452 // Finally set the filter.
453 boolean replaced
= FALSE
;
454 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
457 DCHECK_EQ(FALSE
, replaced
);
460 // Walk past the extension(s).
461 walk
+= wcslen(walk
) + 1;
465 // The save picker requires at least one choice. Callers are strongly advised
466 // to provide sensible choices. If none were given, fallback to .dat.
467 uint32 num_choices
= 0;
468 hr
= choices
->get_Size(&num_choices
);
472 if (num_choices
== 0) {
473 mswrw::HString description
;
474 // TODO(grt): Get a properly translated string. This can't be done from
475 // within metro_driver. Consider preprocessing the filter list in Chrome
476 // land to ensure it has this entry if all others are patterns. In that
477 // case, this whole block of code can be removed.
478 hr
= description
.Set(L
"Data File");
482 mswr::ComPtr
<StringVectorItf
> list
;
483 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
484 list
.GetAddressOf(), std::vector
<base::string16
>(1, L
".dat"));
488 boolean replaced
= FALSE
;
489 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
492 DCHECK_EQ(FALSE
, replaced
);
495 if (!default_path_
.empty()) {
496 base::string16 file_part
= default_path_
.BaseName().value();
497 // If the suggested_name is a root directory, then don't set it as the
499 if (file_part
.size() == 1 && file_part
[0] == L
'\\')
501 hr
= picker
->put_SuggestedFileName(
502 mswrw::HStringReference(file_part
.c_str()).Get());
507 mswr::ComPtr
<SaveFileAsyncOp
> completion
;
508 hr
= picker
->PickSaveFileAsync(&completion
);
512 // Create the callback method.
513 typedef winfoundtn::IAsyncOperationCompletedHandler
<
514 winstorage::StorageFile
*> HandlerDoneType
;
515 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
516 this, &SaveFilePickerSession::FilePickerDone
));
517 DCHECK(handler
.Get() != NULL
);
518 hr
= completion
->put_Completed(handler
.Get());
523 HRESULT
SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp
* async
,
524 AsyncStatus status
) {
525 if (status
== Completed
) {
526 mswr::ComPtr
<winstorage::IStorageFile
> file
;
527 HRESULT hr
= async
->GetResults(file
.GetAddressOf());
530 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
532 hr
= file
.As(&storage_item
);
534 mswrw::HString file_path
;
536 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
539 base::string16 path_str
= MakeStdWString(file_path
.Get());
544 LOG(ERROR
) << "NULL IStorageItem";
547 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
549 app_view_
->OnSaveFileCompleted(this, success_
);
553 FolderPickerSession::FolderPickerSession(ChromeAppViewAsh
* app_view
,
554 const base::string16
& title
)
555 : FilePickerSessionBase(app_view
, title
, L
"", base::FilePath()) {}
557 HRESULT
FolderPickerSession::StartFilePicker() {
558 mswrw::HStringReference
class_name(
559 RuntimeClass_Windows_Storage_Pickers_FolderPicker
);
561 // Create the folder picker.
562 mswr::ComPtr
<winstorage::Pickers::IFolderPicker
> picker
;
563 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
564 class_name
.Get(), picker
.GetAddressOf());
567 // Set the file type filter
568 mswr::ComPtr
<winfoundtn::Collections::IVector
<HSTRING
>> filter
;
569 hr
= picker
->get_FileTypeFilter(filter
.GetAddressOf());
573 hr
= filter
->Append(mswrw::HStringReference(L
"*").Get());
577 mswr::ComPtr
<FolderPickerAsyncOp
> completion
;
578 hr
= picker
->PickSingleFolderAsync(&completion
);
582 // Create the callback method.
583 typedef winfoundtn::IAsyncOperationCompletedHandler
<
584 winstorage::StorageFolder
*> HandlerDoneType
;
585 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
586 this, &FolderPickerSession::FolderPickerDone
));
587 DCHECK(handler
.Get() != NULL
);
588 hr
= completion
->put_Completed(handler
.Get());
592 HRESULT
FolderPickerSession::FolderPickerDone(FolderPickerAsyncOp
* async
,
593 AsyncStatus status
) {
594 if (status
== Completed
) {
595 mswr::ComPtr
<winstorage::IStorageFolder
> folder
;
596 HRESULT hr
= async
->GetResults(folder
.GetAddressOf());
599 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
601 hr
= folder
.As(&storage_item
);
603 mswrw::HString file_path
;
605 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
608 base::string16 path_str
= MakeStdWString(file_path
.Get());
613 LOG(ERROR
) << "NULL IStorageItem";
616 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
618 app_view_
->OnFolderPickerCompleted(this, success_
);