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_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/synchronization/waitable_event.h"
14 #include "base/win/metro.h"
15 #include "base/win/scoped_comptr.h"
16 #include "ui/metro_viewer/metro_viewer_messages.h"
17 #include "win8/metro_driver/chrome_app_view_ash.h"
18 #include "win8/metro_driver/winrt_utils.h"
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
> {
27 ~StringVectorImpl() override
{
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
) override
{
40 if (index
>= strings_
.size())
43 return ::WindowsDuplicateString(strings_
[index
], item
);
45 STDMETHOD(get_Size
)(unsigned* size
) override
{
46 if (strings_
.size() > UINT_MAX
)
48 *size
= static_cast<unsigned>(strings_
.size());
52 winfoundtn::Collections::IVectorView
<HSTRING
>** view
) override
{
55 STDMETHOD(IndexOf
)(HSTRING value
, unsigned* index
, boolean
* found
) override
{
60 STDMETHOD(SetAt
)(unsigned index
, HSTRING item
) override
{ return E_NOTIMPL
; }
61 STDMETHOD(InsertAt
)(unsigned index
, HSTRING item
) override
{
64 STDMETHOD(RemoveAt
)(unsigned index
) override
{ return E_NOTIMPL
; }
65 STDMETHOD(Append
)(HSTRING item
) override
{ return E_NOTIMPL
; }
66 STDMETHOD(RemoveAtEnd
)() override
{ return E_NOTIMPL
; }
67 STDMETHOD(Clear
)() override
{ return E_NOTIMPL
; }
70 std::vector
<HSTRING
> strings_
;
75 FilePickerSessionBase::~FilePickerSessionBase() {
78 bool FilePickerSessionBase::Run() {
84 FilePickerSessionBase::FilePickerSessionBase(ChromeAppViewAsh
* app_view
,
85 const base::string16
& title
,
86 const base::string16
& filter
,
87 const base::FilePath
& default_path
)
91 default_path_(default_path
),
95 bool FilePickerSessionBase::DoFilePicker() {
96 // The file picker will fail if spawned from a snapped application,
97 // so let's attempt to unsnap first if we're in that state.
98 HRESULT hr
= ChromeAppViewAsh::Unsnap();
100 LOG(ERROR
) << "Failed to unsnap for file picker, error 0x" << hr
;
103 hr
= StartFilePicker();
105 LOG(ERROR
) << "Failed to start file picker, error 0x"
112 OpenFilePickerSession::OpenFilePickerSession(
113 ChromeAppViewAsh
* app_view
,
114 const base::string16
& title
,
115 const base::string16
& filter
,
116 const base::FilePath
& default_path
,
117 bool allow_multi_select
)
118 : FilePickerSessionBase(app_view
, title
, filter
, default_path
),
119 allow_multi_select_(allow_multi_select
) {
122 OpenFilePickerSession::~OpenFilePickerSession() {
125 HRESULT
OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp
* async
,
126 AsyncStatus status
) {
127 if (status
== Completed
) {
128 mswr::ComPtr
<winstorage::IStorageFile
> file
;
129 HRESULT hr
= async
->GetResults(file
.GetAddressOf());
132 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
134 hr
= file
.As(&storage_item
);
136 mswrw::HString file_path
;
138 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
142 const wchar_t* path_str
=
143 ::WindowsGetStringRawBuffer(file_path
.Get(), &path_len
);
149 LOG(ERROR
) << "NULL IStorageItem";
152 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
154 app_view_
->OnOpenFileCompleted(this, success_
);
158 HRESULT
OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp
* async
,
159 AsyncStatus status
) {
160 if (status
== Completed
) {
161 mswr::ComPtr
<StorageFileVectorCollection
> files
;
162 HRESULT hr
= async
->GetResults(files
.GetAddressOf());
165 base::string16 result
;
167 hr
= ComposeMultiFileResult(files
.Get(), &result
);
171 // The code below has been copied from the
172 // SelectFileDialogImpl::RunOpenMultiFileDialog function in
173 // select_file_dialog_win.cc.
175 // Consolidate this into a common place.
176 const wchar_t* selection
= result
.c_str();
177 std::vector
<base::FilePath
> files
;
179 while (*selection
) { // Empty string indicates end of list.
180 files
.push_back(base::FilePath(selection
));
181 // Skip over filename and null-terminator.
182 selection
+= files
.back().value().length() + 1;
186 } else if (files
.size() == 1) {
187 // When there is one file, it contains the path and filename.
189 } else if (files
.size() > 1) {
190 // Otherwise, the first string is the path, and the remainder are
192 std::vector
<base::FilePath
>::iterator path
= files
.begin();
193 for (std::vector
<base::FilePath
>::iterator file
= path
+ 1;
194 file
!= files
.end(); ++file
) {
195 filenames_
.push_back(path
->Append(*file
));
200 LOG(ERROR
) << "NULL StorageFileVectorCollection";
203 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
205 app_view_
->OnOpenFileCompleted(this, success_
);
209 HRESULT
OpenFilePickerSession::StartFilePicker() {
210 mswrw::HStringReference
class_name(
211 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker
);
213 // Create the file picker.
214 mswr::ComPtr
<winstorage::Pickers::IFileOpenPicker
> picker
;
215 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
216 class_name
.Get(), picker
.GetAddressOf());
219 // Set the file type filter
220 mswr::ComPtr
<winfoundtn::Collections::IVector
<HSTRING
>> filter
;
221 hr
= picker
->get_FileTypeFilter(filter
.GetAddressOf());
225 if (filter_
.empty()) {
226 hr
= filter
->Append(mswrw::HStringReference(L
"*").Get());
230 // The filter is a concatenation of zero terminated string pairs,
231 // where each pair is {description, extension}. The concatenation ends
232 // with a zero length string - e.g. a double zero terminator.
233 const wchar_t* walk
= filter_
.c_str();
234 while (*walk
!= L
'\0') {
235 // Walk past the description.
236 walk
+= wcslen(walk
) + 1;
238 // We should have an extension, but bail on malformed filters.
242 // There can be a single extension, or a list of semicolon-separated ones.
243 std::vector
<base::string16
> extensions_win32_style
= base::SplitString(
244 walk
, L
";", base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
);
246 // Metro wants suffixes only, not patterns.
247 mswrw::HString extension
;
248 for (size_t i
= 0; i
< extensions_win32_style
.size(); ++i
) {
249 if (extensions_win32_style
[i
] == L
"*.*") {
250 // The wildcard filter is "*" for Metro. The string "*.*" produces
251 // an "invalid parameter" error.
252 hr
= extension
.Set(L
"*");
254 // Metro wants suffixes only, not patterns.
256 base::FilePath(extensions_win32_style
[i
]).Extension();
257 if ((ext
.size() < 2) ||
258 (ext
.find_first_of(L
"*?") != base::string16::npos
)) {
261 hr
= extension
.Set(ext
.c_str());
264 hr
= filter
->Append(extension
.Get());
269 // Walk past the extension.
270 walk
+= wcslen(walk
) + 1;
274 // Spin up a single or multi picker as appropriate.
275 if (allow_multi_select_
) {
276 mswr::ComPtr
<MultiFileAsyncOp
> completion
;
277 hr
= picker
->PickMultipleFilesAsync(&completion
);
281 // Create the callback method.
282 typedef winfoundtn::IAsyncOperationCompletedHandler
<
283 StorageFileVectorCollection
*> HandlerDoneType
;
284 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
285 this, &OpenFilePickerSession::MultiPickerDone
));
286 DCHECK(handler
.Get() != NULL
);
287 hr
= completion
->put_Completed(handler
.Get());
291 mswr::ComPtr
<SingleFileAsyncOp
> completion
;
292 hr
= picker
->PickSingleFileAsync(&completion
);
296 // Create the callback method.
297 typedef winfoundtn::IAsyncOperationCompletedHandler
<
298 winstorage::StorageFile
*> HandlerDoneType
;
299 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
300 this, &OpenFilePickerSession::SinglePickerDone
));
301 DCHECK(handler
.Get() != NULL
);
302 hr
= completion
->put_Completed(handler
.Get());
308 HRESULT
OpenFilePickerSession::ComposeMultiFileResult(
309 StorageFileVectorCollection
* files
, base::string16
* result
) {
310 DCHECK(files
!= NULL
);
311 DCHECK(result
!= NULL
);
313 // Empty the output string.
316 unsigned int num_files
= 0;
317 HRESULT hr
= files
->get_Size(&num_files
);
321 // Make sure we return an error on an empty collection.
322 if (num_files
== 0) {
323 DLOG(ERROR
) << "Empty collection on input.";
327 // This stores the base path that should be the parent of all the files.
328 base::FilePath base_path
;
330 // Iterate through the collection and append the file paths to the result.
331 for (unsigned int i
= 0; i
< num_files
; ++i
) {
332 mswr::ComPtr
<winstorage::IStorageFile
> file
;
333 hr
= files
->GetAt(i
, file
.GetAddressOf());
337 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
338 hr
= file
.As(&storage_item
);
342 mswrw::HString file_path_str
;
343 hr
= storage_item
->get_Path(file_path_str
.GetAddressOf());
347 base::FilePath
file_path(MakeStdWString(file_path_str
.Get()));
348 if (base_path
.empty()) {
349 DCHECK(result
->empty());
350 base_path
= file_path
.DirName();
352 // Append the path, including the terminating zero.
353 // We do this only for the first file.
354 result
->append(base_path
.value().c_str(), base_path
.value().size() + 1);
356 DCHECK(!result
->empty());
357 DCHECK(!base_path
.empty());
358 DCHECK(base_path
== file_path
.DirName());
360 // Append the base name, including the terminating zero.
361 base::FilePath base_name
= file_path
.BaseName();
362 result
->append(base_name
.value().c_str(), base_name
.value().size() + 1);
365 DCHECK(!result
->empty());
370 SaveFilePickerSession::SaveFilePickerSession(
371 ChromeAppViewAsh
* app_view
,
372 const MetroViewerHostMsg_SaveAsDialogParams
& params
)
373 : FilePickerSessionBase(app_view
,
376 params
.suggested_name
),
377 filter_index_(params
.filter_index
) {
380 int SaveFilePickerSession::filter_index() const {
382 // Add support for returning the correct filter index. This does not work in
383 // regular Chrome metro on trunk as well.
384 // BUG: https://code.google.com/p/chromium/issues/detail?id=172704
385 return filter_index_
;
388 HRESULT
SaveFilePickerSession::StartFilePicker() {
389 mswrw::HStringReference
class_name(
390 RuntimeClass_Windows_Storage_Pickers_FileSavePicker
);
392 // Create the file picker.
393 mswr::ComPtr
<winstorage::Pickers::IFileSavePicker
> picker
;
394 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
395 class_name
.Get(), picker
.GetAddressOf());
398 typedef winfoundtn::Collections::IMap
<HSTRING
, StringVectorItf
*>
400 mswr::ComPtr
<StringVectorMap
> choices
;
401 hr
= picker
->get_FileTypeChoices(choices
.GetAddressOf());
405 if (!filter_
.empty()) {
406 // The filter is a concatenation of zero terminated string pairs,
407 // where each pair is {description, extension list}. The concatenation ends
408 // with a zero length string - e.g. a double zero terminator.
409 const wchar_t* walk
= filter_
.c_str();
410 while (*walk
!= L
'\0') {
411 mswrw::HString description
;
412 hr
= description
.Set(walk
);
416 // Walk past the description.
417 walk
+= wcslen(walk
) + 1;
419 // We should have an extension, but bail on malformed filters.
423 // There can be a single extension, or a list of semicolon-separated ones.
424 std::vector
<base::string16
> extensions_win32_style
= base::SplitString(
425 walk
, L
";", base::KEEP_WHITESPACE
, base::SPLIT_WANT_NONEMPTY
);
427 // Metro wants suffixes only, not patterns. Also, metro does not support
428 // the all files ("*") pattern in the save picker.
429 std::vector
<base::string16
> extensions
;
430 for (size_t i
= 0; i
< extensions_win32_style
.size(); ++i
) {
432 base::FilePath(extensions_win32_style
[i
]).Extension();
433 if ((ext
.size() < 2) ||
434 (ext
.find_first_of(L
"*?") != base::string16::npos
))
436 extensions
.push_back(ext
);
439 if (!extensions
.empty()) {
440 // Convert to a Metro collection class.
441 mswr::ComPtr
<StringVectorItf
> list
;
442 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
443 list
.GetAddressOf(), extensions
);
447 // Finally set the filter.
448 boolean replaced
= FALSE
;
449 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
452 DCHECK_EQ(FALSE
, replaced
);
455 // Walk past the extension(s).
456 walk
+= wcslen(walk
) + 1;
460 // The save picker requires at least one choice. Callers are strongly advised
461 // to provide sensible choices. If none were given, fallback to .dat.
462 uint32 num_choices
= 0;
463 hr
= choices
->get_Size(&num_choices
);
467 if (num_choices
== 0) {
468 mswrw::HString description
;
469 // TODO(grt): Get a properly translated string. This can't be done from
470 // within metro_driver. Consider preprocessing the filter list in Chrome
471 // land to ensure it has this entry if all others are patterns. In that
472 // case, this whole block of code can be removed.
473 hr
= description
.Set(L
"Data File");
477 mswr::ComPtr
<StringVectorItf
> list
;
478 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
479 list
.GetAddressOf(), std::vector
<base::string16
>(1, L
".dat"));
483 boolean replaced
= FALSE
;
484 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
487 DCHECK_EQ(FALSE
, replaced
);
490 if (!default_path_
.empty()) {
491 base::string16 file_part
= default_path_
.BaseName().value();
492 // If the suggested_name is a root directory, then don't set it as the
494 if (file_part
.size() == 1 && file_part
[0] == L
'\\')
496 hr
= picker
->put_SuggestedFileName(
497 mswrw::HStringReference(file_part
.c_str()).Get());
502 mswr::ComPtr
<SaveFileAsyncOp
> completion
;
503 hr
= picker
->PickSaveFileAsync(&completion
);
507 // Create the callback method.
508 typedef winfoundtn::IAsyncOperationCompletedHandler
<
509 winstorage::StorageFile
*> HandlerDoneType
;
510 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
511 this, &SaveFilePickerSession::FilePickerDone
));
512 DCHECK(handler
.Get() != NULL
);
513 hr
= completion
->put_Completed(handler
.Get());
518 HRESULT
SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp
* async
,
519 AsyncStatus status
) {
520 if (status
== Completed
) {
521 mswr::ComPtr
<winstorage::IStorageFile
> file
;
522 HRESULT hr
= async
->GetResults(file
.GetAddressOf());
525 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
527 hr
= file
.As(&storage_item
);
529 mswrw::HString file_path
;
531 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
534 base::string16 path_str
= MakeStdWString(file_path
.Get());
539 LOG(ERROR
) << "NULL IStorageItem";
542 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
544 app_view_
->OnSaveFileCompleted(this, success_
);
548 FolderPickerSession::FolderPickerSession(ChromeAppViewAsh
* app_view
,
549 const base::string16
& title
)
550 : FilePickerSessionBase(app_view
, title
, L
"", base::FilePath()) {
553 HRESULT
FolderPickerSession::StartFilePicker() {
554 mswrw::HStringReference
class_name(
555 RuntimeClass_Windows_Storage_Pickers_FolderPicker
);
557 // Create the folder picker.
558 mswr::ComPtr
<winstorage::Pickers::IFolderPicker
> picker
;
559 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
560 class_name
.Get(), picker
.GetAddressOf());
563 // Set the file type filter
564 mswr::ComPtr
<winfoundtn::Collections::IVector
<HSTRING
>> filter
;
565 hr
= picker
->get_FileTypeFilter(filter
.GetAddressOf());
569 hr
= filter
->Append(mswrw::HStringReference(L
"*").Get());
573 mswr::ComPtr
<FolderPickerAsyncOp
> completion
;
574 hr
= picker
->PickSingleFolderAsync(&completion
);
578 // Create the callback method.
579 typedef winfoundtn::IAsyncOperationCompletedHandler
<
580 winstorage::StorageFolder
*> HandlerDoneType
;
581 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
582 this, &FolderPickerSession::FolderPickerDone
));
583 DCHECK(handler
.Get() != NULL
);
584 hr
= completion
->put_Completed(handler
.Get());
588 HRESULT
FolderPickerSession::FolderPickerDone(FolderPickerAsyncOp
* async
,
589 AsyncStatus status
) {
590 if (status
== Completed
) {
591 mswr::ComPtr
<winstorage::IStorageFolder
> folder
;
592 HRESULT hr
= async
->GetResults(folder
.GetAddressOf());
595 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
597 hr
= folder
.As(&storage_item
);
599 mswrw::HString file_path
;
601 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
604 base::string16 path_str
= MakeStdWString(file_path
.Get());
609 LOG(ERROR
) << "NULL IStorageItem";
612 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
614 app_view_
->OnFolderPickerCompleted(this, success_
);