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 typedef winfoundtn::Collections::IVector
<HSTRING
> StringVectorItf
;
23 // TODO(siggi): Complete this implementation and move it to a common place.
24 class StringVectorImpl
: public mswr::RuntimeClass
<StringVectorItf
> {
26 ~StringVectorImpl() override
{
27 std::for_each(strings_
.begin(), strings_
.end(), ::WindowsDeleteString
);
30 HRESULT
RuntimeClassInitialize(const std::vector
<base::string16
>& list
) {
31 for (size_t i
= 0; i
< list
.size(); ++i
)
32 strings_
.push_back(MakeHString(list
[i
]));
37 // IVector<HSTRING> implementation.
38 STDMETHOD(GetAt
)(unsigned index
, HSTRING
* item
) override
{
39 if (index
>= strings_
.size())
42 return ::WindowsDuplicateString(strings_
[index
], item
);
44 STDMETHOD(get_Size
)(unsigned* size
) override
{
45 if (strings_
.size() > UINT_MAX
)
47 *size
= static_cast<unsigned>(strings_
.size());
51 winfoundtn::Collections::IVectorView
<HSTRING
>** view
) override
{
54 STDMETHOD(IndexOf
)(HSTRING value
, unsigned* index
, boolean
* found
) override
{
59 STDMETHOD(SetAt
)(unsigned index
, HSTRING item
) override
{ return E_NOTIMPL
; }
60 STDMETHOD(InsertAt
)(unsigned index
, HSTRING item
) override
{
63 STDMETHOD(RemoveAt
)(unsigned index
) override
{ return E_NOTIMPL
; }
64 STDMETHOD(Append
)(HSTRING item
) override
{ return E_NOTIMPL
; }
65 STDMETHOD(RemoveAtEnd
)() override
{ return E_NOTIMPL
; }
66 STDMETHOD(Clear
)() override
{ return E_NOTIMPL
; }
69 std::vector
<HSTRING
> strings_
;
74 FilePickerSessionBase::~FilePickerSessionBase() {
77 bool FilePickerSessionBase::Run() {
83 FilePickerSessionBase::FilePickerSessionBase(ChromeAppViewAsh
* app_view
,
84 const base::string16
& title
,
85 const base::string16
& filter
,
86 const base::FilePath
& default_path
)
87 : app_view_(app_view
),
90 default_path_(default_path
),
94 bool FilePickerSessionBase::DoFilePicker() {
95 // The file picker will fail if spawned from a snapped application,
96 // so let's attempt to unsnap first if we're in that state.
97 HRESULT hr
= ChromeAppViewAsh::Unsnap();
99 LOG(ERROR
) << "Failed to unsnap for file picker, error 0x" << hr
;
102 hr
= StartFilePicker();
104 LOG(ERROR
) << "Failed to start file picker, error 0x"
111 OpenFilePickerSession::OpenFilePickerSession(
112 ChromeAppViewAsh
* app_view
,
113 const base::string16
& title
,
114 const base::string16
& filter
,
115 const base::FilePath
& default_path
,
116 bool allow_multi_select
)
117 : FilePickerSessionBase(app_view
, title
, filter
, default_path
),
118 allow_multi_select_(allow_multi_select
) {
121 OpenFilePickerSession::~OpenFilePickerSession() {
124 HRESULT
OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp
* async
,
125 AsyncStatus status
) {
126 if (status
== Completed
) {
127 mswr::ComPtr
<winstorage::IStorageFile
> file
;
128 HRESULT hr
= async
->GetResults(file
.GetAddressOf());
131 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
133 hr
= file
.As(&storage_item
);
135 mswrw::HString file_path
;
137 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
141 const wchar_t* path_str
=
142 ::WindowsGetStringRawBuffer(file_path
.Get(), &path_len
);
148 LOG(ERROR
) << "NULL IStorageItem";
151 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
153 app_view_
->OnOpenFileCompleted(this, success_
);
157 HRESULT
OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp
* async
,
158 AsyncStatus status
) {
159 if (status
== Completed
) {
160 mswr::ComPtr
<StorageFileVectorCollection
> files
;
161 HRESULT hr
= async
->GetResults(files
.GetAddressOf());
164 base::string16 result
;
166 hr
= ComposeMultiFileResult(files
.Get(), &result
);
170 // The code below has been copied from the
171 // SelectFileDialogImpl::RunOpenMultiFileDialog function in
172 // select_file_dialog_win.cc.
174 // Consolidate this into a common place.
175 const wchar_t* selection
= result
.c_str();
176 std::vector
<base::FilePath
> files
;
178 while (*selection
) { // Empty string indicates end of list.
179 files
.push_back(base::FilePath(selection
));
180 // Skip over filename and null-terminator.
181 selection
+= files
.back().value().length() + 1;
185 } else if (files
.size() == 1) {
186 // When there is one file, it contains the path and filename.
188 } else if (files
.size() > 1) {
189 // Otherwise, the first string is the path, and the remainder are
191 std::vector
<base::FilePath
>::iterator path
= files
.begin();
192 for (std::vector
<base::FilePath
>::iterator file
= path
+ 1;
193 file
!= files
.end(); ++file
) {
194 filenames_
.push_back(path
->Append(*file
));
199 LOG(ERROR
) << "NULL StorageFileVectorCollection";
202 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
204 app_view_
->OnOpenFileCompleted(this, success_
);
208 HRESULT
OpenFilePickerSession::StartFilePicker() {
209 mswrw::HStringReference
class_name(
210 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker
);
212 // Create the file picker.
213 mswr::ComPtr
<winstorage::Pickers::IFileOpenPicker
> picker
;
214 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
215 class_name
.Get(), picker
.GetAddressOf());
218 // Set the file type filter
219 mswr::ComPtr
<winfoundtn::Collections::IVector
<HSTRING
>> filter
;
220 hr
= picker
->get_FileTypeFilter(filter
.GetAddressOf());
224 if (filter_
.empty()) {
225 hr
= filter
->Append(mswrw::HStringReference(L
"*").Get());
229 // The filter is a concatenation of zero terminated string pairs,
230 // where each pair is {description, extension}. The concatenation ends
231 // with a zero length string - e.g. a double zero terminator.
232 const wchar_t* walk
= filter_
.c_str();
233 while (*walk
!= L
'\0') {
234 // Walk past the description.
235 walk
+= wcslen(walk
) + 1;
237 // We should have an extension, but bail on malformed filters.
241 // There can be a single extension, or a list of semicolon-separated ones.
242 std::vector
<base::string16
> extensions_win32_style
;
243 size_t extension_count
= Tokenize(walk
, L
";", &extensions_win32_style
);
244 DCHECK_EQ(extension_count
, extensions_win32_style
.size());
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
;
425 size_t extension_count
= Tokenize(walk
, L
";", &extensions_win32_style
);
426 DCHECK_EQ(extension_count
, extensions_win32_style
.size());
428 // Metro wants suffixes only, not patterns. Also, metro does not support
429 // the all files ("*") pattern in the save picker.
430 std::vector
<base::string16
> extensions
;
431 for (size_t i
= 0; i
< extensions_win32_style
.size(); ++i
) {
433 base::FilePath(extensions_win32_style
[i
]).Extension();
434 if ((ext
.size() < 2) ||
435 (ext
.find_first_of(L
"*?") != base::string16::npos
))
437 extensions
.push_back(ext
);
440 if (!extensions
.empty()) {
441 // Convert to a Metro collection class.
442 mswr::ComPtr
<StringVectorItf
> list
;
443 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
444 list
.GetAddressOf(), extensions
);
448 // Finally set the filter.
449 boolean replaced
= FALSE
;
450 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
453 DCHECK_EQ(FALSE
, replaced
);
456 // Walk past the extension(s).
457 walk
+= wcslen(walk
) + 1;
461 // The save picker requires at least one choice. Callers are strongly advised
462 // to provide sensible choices. If none were given, fallback to .dat.
463 uint32 num_choices
= 0;
464 hr
= choices
->get_Size(&num_choices
);
468 if (num_choices
== 0) {
469 mswrw::HString description
;
470 // TODO(grt): Get a properly translated string. This can't be done from
471 // within metro_driver. Consider preprocessing the filter list in Chrome
472 // land to ensure it has this entry if all others are patterns. In that
473 // case, this whole block of code can be removed.
474 hr
= description
.Set(L
"Data File");
478 mswr::ComPtr
<StringVectorItf
> list
;
479 hr
= mswr::MakeAndInitialize
<StringVectorImpl
>(
480 list
.GetAddressOf(), std::vector
<base::string16
>(1, L
".dat"));
484 boolean replaced
= FALSE
;
485 hr
= choices
->Insert(description
.Get(), list
.Get(), &replaced
);
488 DCHECK_EQ(FALSE
, replaced
);
491 if (!default_path_
.empty()) {
492 base::string16 file_part
= default_path_
.BaseName().value();
493 // If the suggested_name is a root directory, then don't set it as the
495 if (file_part
.size() == 1 && file_part
[0] == L
'\\')
497 hr
= picker
->put_SuggestedFileName(
498 mswrw::HStringReference(file_part
.c_str()).Get());
503 mswr::ComPtr
<SaveFileAsyncOp
> completion
;
504 hr
= picker
->PickSaveFileAsync(&completion
);
508 // Create the callback method.
509 typedef winfoundtn::IAsyncOperationCompletedHandler
<
510 winstorage::StorageFile
*> HandlerDoneType
;
511 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
512 this, &SaveFilePickerSession::FilePickerDone
));
513 DCHECK(handler
.Get() != NULL
);
514 hr
= completion
->put_Completed(handler
.Get());
519 HRESULT
SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp
* async
,
520 AsyncStatus status
) {
521 if (status
== Completed
) {
522 mswr::ComPtr
<winstorage::IStorageFile
> file
;
523 HRESULT hr
= async
->GetResults(file
.GetAddressOf());
526 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
528 hr
= file
.As(&storage_item
);
530 mswrw::HString file_path
;
532 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
535 base::string16 path_str
= MakeStdWString(file_path
.Get());
540 LOG(ERROR
) << "NULL IStorageItem";
543 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
545 app_view_
->OnSaveFileCompleted(this, success_
);
549 FolderPickerSession::FolderPickerSession(ChromeAppViewAsh
* app_view
,
550 const base::string16
& title
)
551 : FilePickerSessionBase(app_view
, title
, L
"", base::FilePath()) {
554 HRESULT
FolderPickerSession::StartFilePicker() {
555 mswrw::HStringReference
class_name(
556 RuntimeClass_Windows_Storage_Pickers_FolderPicker
);
558 // Create the folder picker.
559 mswr::ComPtr
<winstorage::Pickers::IFolderPicker
> picker
;
560 HRESULT hr
= ::Windows::Foundation::ActivateInstance(
561 class_name
.Get(), picker
.GetAddressOf());
564 // Set the file type filter
565 mswr::ComPtr
<winfoundtn::Collections::IVector
<HSTRING
>> filter
;
566 hr
= picker
->get_FileTypeFilter(filter
.GetAddressOf());
570 hr
= filter
->Append(mswrw::HStringReference(L
"*").Get());
574 mswr::ComPtr
<FolderPickerAsyncOp
> completion
;
575 hr
= picker
->PickSingleFolderAsync(&completion
);
579 // Create the callback method.
580 typedef winfoundtn::IAsyncOperationCompletedHandler
<
581 winstorage::StorageFolder
*> HandlerDoneType
;
582 mswr::ComPtr
<HandlerDoneType
> handler(mswr::Callback
<HandlerDoneType
>(
583 this, &FolderPickerSession::FolderPickerDone
));
584 DCHECK(handler
.Get() != NULL
);
585 hr
= completion
->put_Completed(handler
.Get());
589 HRESULT
FolderPickerSession::FolderPickerDone(FolderPickerAsyncOp
* async
,
590 AsyncStatus status
) {
591 if (status
== Completed
) {
592 mswr::ComPtr
<winstorage::IStorageFolder
> folder
;
593 HRESULT hr
= async
->GetResults(folder
.GetAddressOf());
596 mswr::ComPtr
<winstorage::IStorageItem
> storage_item
;
598 hr
= folder
.As(&storage_item
);
600 mswrw::HString file_path
;
602 hr
= storage_item
->get_Path(file_path
.GetAddressOf());
605 base::string16 path_str
= MakeStdWString(file_path
.Get());
610 LOG(ERROR
) << "NULL IStorageItem";
613 LOG(ERROR
) << "Unexpected async status " << static_cast<int>(status
);
615 app_view_
->OnFolderPickerCompleted(this, success_
);