1 // Copyright (c) 2012 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 "stdafx.h"
6 #include "win8/metro_driver/file_picker.h"
7
8 #include <windows.storage.pickers.h>
9
10 #include "base/bind.h"
11 #include "base/files/file_path.h"
12 #include "base/logging.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/strings/string_util.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "base/win/metro.h"
17 #include "base/win/scoped_comptr.h"
18 #include "win8/metro_driver/chrome_app_view.h"
19 #include "win8/metro_driver/winrt_utils.h"
20
21 namespace {
22
23 namespace winstorage = ABI::Windows::Storage;
24 typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf;
25
26 // TODO(siggi): Complete this implementation and move it to a common place.
27 class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> {
28 public:
~StringVectorImpl()29 ~StringVectorImpl() {
30 std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString);
31 }
32
RuntimeClassInitialize(const std::vector<base::string16> & list)33 HRESULT RuntimeClassInitialize(const std::vector<base::string16>& list) {
34 for (size_t i = 0; i < list.size(); ++i)
35 strings_.push_back(MakeHString(list[i]));
36
37 return S_OK;
38 }
39
40 // IVector<HSTRING> implementation.
STDMETHOD(GetAt)41 STDMETHOD(GetAt)(unsigned index, HSTRING* item) {
42 if (index >= strings_.size())
43 return E_INVALIDARG;
44
45 return ::WindowsDuplicateString(strings_[index], item);
46 }
STDMETHOD(get_Size)47 STDMETHOD(get_Size)(unsigned *size) {
48 if (strings_.size() > UINT_MAX)
49 return E_UNEXPECTED;
50 *size = static_cast<unsigned>(strings_.size());
51 return S_OK;
52 }
STDMETHOD(GetView)53 STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) {
54 return E_NOTIMPL;
55 }
STDMETHOD(IndexOf)56 STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) {
57 return E_NOTIMPL;
58 }
59
60 // write methods
STDMETHOD(SetAt)61 STDMETHOD(SetAt)(unsigned index, HSTRING item) {
62 return E_NOTIMPL;
63 }
STDMETHOD(InsertAt)64 STDMETHOD(InsertAt)(unsigned index, HSTRING item) {
65 return E_NOTIMPL;
66 }
STDMETHOD(RemoveAt)67 STDMETHOD(RemoveAt)(unsigned index) {
68 return E_NOTIMPL;
69 }
STDMETHOD(Append)70 STDMETHOD(Append)(HSTRING item) {
71 return E_NOTIMPL;
72 }
STDMETHOD(RemoveAtEnd)73 STDMETHOD(RemoveAtEnd)() {
74 return E_NOTIMPL;
75 }
STDMETHOD(Clear)76 STDMETHOD(Clear)() {
77 return E_NOTIMPL;
78 }
79
80 private:
81 std::vector<HSTRING> strings_;
82 };
83
84 class FilePickerSessionBase {
85 public:
86 // Creates a file picker for open_file_name.
87 explicit FilePickerSessionBase(OPENFILENAME* open_file_name);
88
89 // Runs the picker, returns true on success.
90 bool Run();
91
92 protected:
93 // Creates, configures and starts a file picker.
94 // If the HRESULT returned is a failure code the file picker has not started,
95 // so no callbacks should be expected.
96 virtual HRESULT StartFilePicker() = 0;
97
98 // The parameters to our picker.
99 OPENFILENAME* open_file_name_;
100 // The event Run waits on.
101 base::WaitableEvent event_;
102 // True iff a file picker has successfully finished.
103 bool success_;
104
105 private:
106 // Initiate a file picker, must be called on the metro dispatcher's thread.
107 void DoFilePicker();
108
109 DISALLOW_COPY_AND_ASSIGN(FilePickerSessionBase);
110 };
111
112 class OpenFilePickerSession : public FilePickerSessionBase {
113 public:
114 explicit OpenFilePickerSession(OPENFILENAME* open_file_name);
115
116 private:
117 virtual HRESULT StartFilePicker() OVERRIDE;
118
119 typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*>
120 SingleFileAsyncOp;
121 typedef winfoundtn::Collections::IVectorView<
122 winstorage::StorageFile*> StorageFileVectorCollection;
123 typedef winfoundtn::IAsyncOperation<StorageFileVectorCollection*>
124 MultiFileAsyncOp;
125
126 // Called asynchronously when a single file picker is done.
127 HRESULT SinglePickerDone(SingleFileAsyncOp* async, AsyncStatus status);
128
129 // Called asynchronously when a multi file picker is done.
130 HRESULT MultiPickerDone(MultiFileAsyncOp* async, AsyncStatus status);
131
132 // Composes a multi-file result string suitable for returning to a
133 // from a storage file collection.
134 static HRESULT ComposeMultiFileResult(StorageFileVectorCollection* files,
135 base::string16* result);
136 private:
137 DISALLOW_COPY_AND_ASSIGN(OpenFilePickerSession);
138 };
139
140 class SaveFilePickerSession : public FilePickerSessionBase {
141 public:
142 explicit SaveFilePickerSession(OPENFILENAME* open_file_name);
143
144 private:
145 virtual HRESULT StartFilePicker() OVERRIDE;
146
147 typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*>
148 SaveFileAsyncOp;
149
150 // Called asynchronously when the save file picker is done.
151 HRESULT FilePickerDone(SaveFileAsyncOp* async, AsyncStatus status);
152 };
153
FilePickerSessionBase(OPENFILENAME * open_file_name)154 FilePickerSessionBase::FilePickerSessionBase(OPENFILENAME* open_file_name)
155 : open_file_name_(open_file_name),
156 event_(true, false),
157 success_(false) {
158 }
159
Run()160 bool FilePickerSessionBase::Run() {
161 DCHECK(globals.appview_msg_loop != NULL);
162
163 // Post the picker request over to the metro thread.
164 bool posted = globals.appview_msg_loop->PostTask(FROM_HERE,
165 base::Bind(&FilePickerSessionBase::DoFilePicker, base::Unretained(this)));
166 if (!posted)
167 return false;
168
169 // Wait for the file picker to complete.
170 event_.Wait();
171
172 return success_;
173 }
174
DoFilePicker()175 void FilePickerSessionBase::DoFilePicker() {
176 // The file picker will fail if spawned from a snapped application,
177 // so let's attempt to unsnap first if we're in that state.
178 HRESULT hr = ChromeAppView::Unsnap();
179 if (FAILED(hr)) {
180 LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr;
181 }
182
183 if (SUCCEEDED(hr))
184 hr = StartFilePicker();
185
186 if (FAILED(hr)) {
187 LOG(ERROR) << "Failed to start file picker, error 0x"
188 << std::hex << hr;
189
190 event_.Signal();
191 }
192 }
193
OpenFilePickerSession(OPENFILENAME * open_file_name)194 OpenFilePickerSession::OpenFilePickerSession(OPENFILENAME* open_file_name)
195 : FilePickerSessionBase(open_file_name) {
196 }
197
SinglePickerDone(SingleFileAsyncOp * async,AsyncStatus status)198 HRESULT OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp* async,
199 AsyncStatus status) {
200 if (status == Completed) {
201 mswr::ComPtr<winstorage::IStorageFile> file;
202 HRESULT hr = async->GetResults(file.GetAddressOf());
203
204 if (file) {
205 mswr::ComPtr<winstorage::IStorageItem> storage_item;
206 if (SUCCEEDED(hr))
207 hr = file.As(&storage_item);
208
209 mswrw::HString file_path;
210 if (SUCCEEDED(hr))
211 hr = storage_item->get_Path(file_path.GetAddressOf());
212
213 if (SUCCEEDED(hr)) {
214 UINT32 path_len = 0;
215 const wchar_t* path_str =
216 ::WindowsGetStringRawBuffer(file_path.Get(), &path_len);
217
218 // If the selected file name is longer than the supplied buffer,
219 // we return false as per GetOpenFileName documentation.
220 if (path_len < open_file_name_->nMaxFile) {
221 base::wcslcpy(open_file_name_->lpstrFile,
222 path_str,
223 open_file_name_->nMaxFile);
224 success_ = true;
225 }
226 }
227 } else {
228 LOG(ERROR) << "NULL IStorageItem";
229 }
230 } else {
231 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
232 }
233
234 event_.Signal();
235
236 return S_OK;
237 }
238
MultiPickerDone(MultiFileAsyncOp * async,AsyncStatus status)239 HRESULT OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp* async,
240 AsyncStatus status) {
241 if (status == Completed) {
242 mswr::ComPtr<StorageFileVectorCollection> files;
243 HRESULT hr = async->GetResults(files.GetAddressOf());
244
245 if (files) {
246 base::string16 result;
247 if (SUCCEEDED(hr))
248 hr = ComposeMultiFileResult(files.Get(), &result);
249
250 if (SUCCEEDED(hr)) {
251 if (result.size() + 1 < open_file_name_->nMaxFile) {
252 // Because the result has embedded nulls, we must memcpy.
253 memcpy(open_file_name_->lpstrFile,
254 result.c_str(),
255 (result.size() + 1) * sizeof(result[0]));
256 success_ = true;
257 }
258 }
259 } else {
260 LOG(ERROR) << "NULL StorageFileVectorCollection";
261 }
262 } else {
263 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
264 }
265
266 event_.Signal();
267
268 return S_OK;
269 }
270
StartFilePicker()271 HRESULT OpenFilePickerSession::StartFilePicker() {
272 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread());
273 DCHECK(open_file_name_ != NULL);
274
275 mswrw::HStringReference class_name(
276 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker);
277
278 // Create the file picker.
279 mswr::ComPtr<winstorage::Pickers::IFileOpenPicker> picker;
280 HRESULT hr = ::Windows::Foundation::ActivateInstance(
281 class_name.Get(), picker.GetAddressOf());
282 CheckHR(hr);
283
284 // Set the file type filter
285 mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter;
286 hr = picker->get_FileTypeFilter(filter.GetAddressOf());
287 if (FAILED(hr))
288 return hr;
289
290 if (open_file_name_->lpstrFilter == NULL) {
291 hr = filter->Append(mswrw::HStringReference(L"*").Get());
292 if (FAILED(hr))
293 return hr;
294 } else {
295 // The filter is a concatenation of zero terminated string pairs,
296 // where each pair is {description, extension}. The concatenation ends
297 // with a zero length string - e.g. a double zero terminator.
298 const wchar_t* walk = open_file_name_->lpstrFilter;
299 while (*walk != L'\0') {
300 // Walk past the description.
301 walk += wcslen(walk) + 1;
302
303 // We should have an extension, but bail on malformed filters.
304 if (*walk == L'\0')
305 break;
306
307 // There can be a single extension, or a list of semicolon-separated ones.
308 std::vector<base::string16> extensions_win32_style;
309 size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
310 DCHECK_EQ(extension_count, extensions_win32_style.size());
311
312 // Metro wants suffixes only, not patterns.
313 mswrw::HString extension;
314 for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
315 if (extensions_win32_style[i] == L"*.*") {
316 // The wildcard filter is "*" for Metro. The string "*.*" produces
317 // an "invalid parameter" error.
318 hr = extension.Set(L"*");
319 } else {
320 // Metro wants suffixes only, not patterns.
321 base::string16 ext =
322 base::FilePath(extensions_win32_style[i]).Extension();
323 if ((ext.size() < 2) ||
324 (ext.find_first_of(L"*?") != base::string16::npos)) {
325 continue;
326 }
327 hr = extension.Set(ext.c_str());
328 }
329 if (SUCCEEDED(hr))
330 hr = filter->Append(extension.Get());
331 if (FAILED(hr))
332 return hr;
333 }
334
335 // Walk past the extension.
336 walk += wcslen(walk) + 1;
337 }
338 }
339
340 // Spin up a single or multi picker as appropriate.
341 if (open_file_name_->Flags & OFN_ALLOWMULTISELECT) {
342 mswr::ComPtr<MultiFileAsyncOp> completion;
343 hr = picker->PickMultipleFilesAsync(&completion);
344 if (FAILED(hr))
345 return hr;
346
347 // Create the callback method.
348 typedef winfoundtn::IAsyncOperationCompletedHandler<
349 StorageFileVectorCollection*> HandlerDoneType;
350 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
351 this, &OpenFilePickerSession::MultiPickerDone));
352 DCHECK(handler.Get() != NULL);
353 hr = completion->put_Completed(handler.Get());
354
355 return hr;
356 } else {
357 mswr::ComPtr<SingleFileAsyncOp> completion;
358 hr = picker->PickSingleFileAsync(&completion);
359 if (FAILED(hr))
360 return hr;
361
362 // Create the callback method.
363 typedef winfoundtn::IAsyncOperationCompletedHandler<
364 winstorage::StorageFile*> HandlerDoneType;
365 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
366 this, &OpenFilePickerSession::SinglePickerDone));
367 DCHECK(handler.Get() != NULL);
368 hr = completion->put_Completed(handler.Get());
369
370 return hr;
371 }
372 }
373
ComposeMultiFileResult(StorageFileVectorCollection * files,base::string16 * result)374 HRESULT OpenFilePickerSession::ComposeMultiFileResult(
375 StorageFileVectorCollection* files, base::string16* result) {
376 DCHECK(files != NULL);
377 DCHECK(result != NULL);
378
379 // Empty the output string.
380 result->clear();
381
382 unsigned int num_files = 0;
383 HRESULT hr = files->get_Size(&num_files);
384 if (FAILED(hr))
385 return hr;
386
387 // Make sure we return an error on an empty collection.
388 if (num_files == 0) {
389 DLOG(ERROR) << "Empty collection on input.";
390 return E_UNEXPECTED;
391 }
392
393 // This stores the base path that should be the parent of all the files.
394 base::FilePath base_path;
395
396 // Iterate through the collection and append the file paths to the result.
397 for (unsigned int i = 0; i < num_files; ++i) {
398 mswr::ComPtr<winstorage::IStorageFile> file;
399 hr = files->GetAt(i, file.GetAddressOf());
400 if (FAILED(hr))
401 return hr;
402
403 mswr::ComPtr<winstorage::IStorageItem> storage_item;
404 hr = file.As(&storage_item);
405 if (FAILED(hr))
406 return hr;
407
408 mswrw::HString file_path_str;
409 hr = storage_item->get_Path(file_path_str.GetAddressOf());
410 if (FAILED(hr))
411 return hr;
412
413 base::FilePath file_path(MakeStdWString(file_path_str.Get()));
414 if (base_path.empty()) {
415 DCHECK(result->empty());
416 base_path = file_path.DirName();
417
418 // Append the path, including the terminating zero.
419 // We do this only for the first file.
420 result->append(base_path.value().c_str(), base_path.value().size() + 1);
421 }
422 DCHECK(!result->empty());
423 DCHECK(!base_path.empty());
424 DCHECK(base_path == file_path.DirName());
425
426 // Append the base name, including the terminating zero.
427 base::FilePath base_name = file_path.BaseName();
428 result->append(base_name.value().c_str(), base_name.value().size() + 1);
429 }
430
431 DCHECK(!result->empty());
432
433 return S_OK;
434 }
435
SaveFilePickerSession(OPENFILENAME * open_file_name)436 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name)
437 : FilePickerSessionBase(open_file_name) {
438 }
439
StartFilePicker()440 HRESULT SaveFilePickerSession::StartFilePicker() {
441 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread());
442 DCHECK(open_file_name_ != NULL);
443
444 mswrw::HStringReference class_name(
445 RuntimeClass_Windows_Storage_Pickers_FileSavePicker);
446
447 // Create the file picker.
448 mswr::ComPtr<winstorage::Pickers::IFileSavePicker> picker;
449 HRESULT hr = ::Windows::Foundation::ActivateInstance(
450 class_name.Get(), picker.GetAddressOf());
451 CheckHR(hr);
452
453 typedef winfoundtn::Collections::IMap<HSTRING, StringVectorItf*>
454 StringVectorMap;
455 mswr::ComPtr<StringVectorMap> choices;
456 hr = picker->get_FileTypeChoices(choices.GetAddressOf());
457 if (FAILED(hr))
458 return hr;
459
460 if (open_file_name_->lpstrFilter) {
461 // The filter is a concatenation of zero terminated string pairs,
462 // where each pair is {description, extension list}. The concatenation ends
463 // with a zero length string - e.g. a double zero terminator.
464 const wchar_t* walk = open_file_name_->lpstrFilter;
465 while (*walk != L'\0') {
466 mswrw::HString description;
467 hr = description.Set(walk);
468 if (FAILED(hr))
469 return hr;
470
471 // Walk past the description.
472 walk += wcslen(walk) + 1;
473
474 // We should have an extension, but bail on malformed filters.
475 if (*walk == L'\0')
476 break;
477
478 // There can be a single extension, or a list of semicolon-separated ones.
479 std::vector<base::string16> extensions_win32_style;
480 size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
481 DCHECK_EQ(extension_count, extensions_win32_style.size());
482
483 // Metro wants suffixes only, not patterns. Also, metro does not support
484 // the all files ("*") pattern in the save picker.
485 std::vector<base::string16> extensions;
486 for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
487 base::string16 ext =
488 base::FilePath(extensions_win32_style[i]).Extension();
489 if ((ext.size() < 2) ||
490 (ext.find_first_of(L"*?") != base::string16::npos))
491 continue;
492 extensions.push_back(ext);
493 }
494
495 if (!extensions.empty()) {
496 // Convert to a Metro collection class.
497 mswr::ComPtr<StringVectorItf> list;
498 hr = mswr::MakeAndInitialize<StringVectorImpl>(
499 list.GetAddressOf(), extensions);
500 if (FAILED(hr))
501 return hr;
502
503 // Finally set the filter.
504 boolean replaced = FALSE;
505 hr = choices->Insert(description.Get(), list.Get(), &replaced);
506 if (FAILED(hr))
507 return hr;
508 DCHECK_EQ(FALSE, replaced);
509 }
510
511 // Walk past the extension(s).
512 walk += wcslen(walk) + 1;
513 }
514 }
515
516 // The save picker requires at least one choice. Callers are strongly advised
517 // to provide sensible choices. If none were given, fallback to .dat.
518 uint32 num_choices = 0;
519 hr = choices->get_Size(&num_choices);
520 if (FAILED(hr))
521 return hr;
522
523 if (num_choices == 0) {
524 mswrw::HString description;
525 // TODO(grt): Get a properly translated string. This can't be done from
526 // within metro_driver. Consider preprocessing the filter list in Chrome
527 // land to ensure it has this entry if all others are patterns. In that
528 // case, this whole block of code can be removed.
529 hr = description.Set(L"Data File");
530 if (FAILED(hr))
531 return hr;
532
533 mswr::ComPtr<StringVectorItf> list;
534 hr = mswr::MakeAndInitialize<StringVectorImpl>(
535 list.GetAddressOf(), std::vector<base::string16>(1, L".dat"));
536 if (FAILED(hr))
537 return hr;
538
539 boolean replaced = FALSE;
540 hr = choices->Insert(description.Get(), list.Get(), &replaced);
541 if (FAILED(hr))
542 return hr;
543 DCHECK_EQ(FALSE, replaced);
544 }
545
546 if (open_file_name_->lpstrFile != NULL) {
547 hr = picker->put_SuggestedFileName(
548 mswrw::HStringReference(
549 const_cast<const wchar_t*>(open_file_name_->lpstrFile)).Get());
550 if (FAILED(hr))
551 return hr;
552 }
553
554 mswr::ComPtr<SaveFileAsyncOp> completion;
555 hr = picker->PickSaveFileAsync(&completion);
556 if (FAILED(hr))
557 return hr;
558
559 // Create the callback method.
560 typedef winfoundtn::IAsyncOperationCompletedHandler<
561 winstorage::StorageFile*> HandlerDoneType;
562 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
563 this, &SaveFilePickerSession::FilePickerDone));
564 DCHECK(handler.Get() != NULL);
565 hr = completion->put_Completed(handler.Get());
566
567 return hr;
568 }
569
FilePickerDone(SaveFileAsyncOp * async,AsyncStatus status)570 HRESULT SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp* async,
571 AsyncStatus status) {
572 if (status == Completed) {
573 mswr::ComPtr<winstorage::IStorageFile> file;
574 HRESULT hr = async->GetResults(file.GetAddressOf());
575
576 if (file) {
577 mswr::ComPtr<winstorage::IStorageItem> storage_item;
578 if (SUCCEEDED(hr))
579 hr = file.As(&storage_item);
580
581 mswrw::HString file_path;
582 if (SUCCEEDED(hr))
583 hr = storage_item->get_Path(file_path.GetAddressOf());
584
585 if (SUCCEEDED(hr)) {
586 base::string16 path_str = MakeStdWString(file_path.Get());
587
588 // If the selected file name is longer than the supplied buffer,
589 // we return false as per GetOpenFileName documentation.
590 if (path_str.size() < open_file_name_->nMaxFile) {
591 base::wcslcpy(open_file_name_->lpstrFile,
592 path_str.c_str(),
593 open_file_name_->nMaxFile);
594 success_ = true;
595 }
596 }
597 } else {
598 LOG(ERROR) << "NULL IStorageItem";
599 }
600 } else {
601 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
602 }
603
604 event_.Signal();
605
606 return S_OK;
607 }
608
609 } // namespace
610
MetroGetOpenFileName(OPENFILENAME * open_file_name)611 BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) {
612 OpenFilePickerSession session(open_file_name);
613
614 return session.Run();
615 }
616
MetroGetSaveFileName(OPENFILENAME * open_file_name)617 BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) {
618 SaveFilePickerSession session(open_file_name);
619
620 return session.Run();
621 }
622