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<string16> & list)33 HRESULT RuntimeClassInitialize(const std::vector<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 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 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<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 string16 ext = base::FilePath(extensions_win32_style[i]).Extension();
322 if ((ext.size() < 2) ||
323 (ext.find_first_of(L"*?") != string16::npos)) {
324 continue;
325 }
326 hr = extension.Set(ext.c_str());
327 }
328 if (SUCCEEDED(hr))
329 hr = filter->Append(extension.Get());
330 if (FAILED(hr))
331 return hr;
332 }
333
334 // Walk past the extension.
335 walk += wcslen(walk) + 1;
336 }
337 }
338
339 // Spin up a single or multi picker as appropriate.
340 if (open_file_name_->Flags & OFN_ALLOWMULTISELECT) {
341 mswr::ComPtr<MultiFileAsyncOp> completion;
342 hr = picker->PickMultipleFilesAsync(&completion);
343 if (FAILED(hr))
344 return hr;
345
346 // Create the callback method.
347 typedef winfoundtn::IAsyncOperationCompletedHandler<
348 StorageFileVectorCollection*> HandlerDoneType;
349 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
350 this, &OpenFilePickerSession::MultiPickerDone));
351 DCHECK(handler.Get() != NULL);
352 hr = completion->put_Completed(handler.Get());
353
354 return hr;
355 } else {
356 mswr::ComPtr<SingleFileAsyncOp> completion;
357 hr = picker->PickSingleFileAsync(&completion);
358 if (FAILED(hr))
359 return hr;
360
361 // Create the callback method.
362 typedef winfoundtn::IAsyncOperationCompletedHandler<
363 winstorage::StorageFile*> HandlerDoneType;
364 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
365 this, &OpenFilePickerSession::SinglePickerDone));
366 DCHECK(handler.Get() != NULL);
367 hr = completion->put_Completed(handler.Get());
368
369 return hr;
370 }
371 }
372
ComposeMultiFileResult(StorageFileVectorCollection * files,string16 * result)373 HRESULT OpenFilePickerSession::ComposeMultiFileResult(
374 StorageFileVectorCollection* files, string16* result) {
375 DCHECK(files != NULL);
376 DCHECK(result != NULL);
377
378 // Empty the output string.
379 result->clear();
380
381 unsigned int num_files = 0;
382 HRESULT hr = files->get_Size(&num_files);
383 if (FAILED(hr))
384 return hr;
385
386 // Make sure we return an error on an empty collection.
387 if (num_files == 0) {
388 DLOG(ERROR) << "Empty collection on input.";
389 return E_UNEXPECTED;
390 }
391
392 // This stores the base path that should be the parent of all the files.
393 base::FilePath base_path;
394
395 // Iterate through the collection and append the file paths to the result.
396 for (unsigned int i = 0; i < num_files; ++i) {
397 mswr::ComPtr<winstorage::IStorageFile> file;
398 hr = files->GetAt(i, file.GetAddressOf());
399 if (FAILED(hr))
400 return hr;
401
402 mswr::ComPtr<winstorage::IStorageItem> storage_item;
403 hr = file.As(&storage_item);
404 if (FAILED(hr))
405 return hr;
406
407 mswrw::HString file_path_str;
408 hr = storage_item->get_Path(file_path_str.GetAddressOf());
409 if (FAILED(hr))
410 return hr;
411
412 base::FilePath file_path(MakeStdWString(file_path_str.Get()));
413 if (base_path.empty()) {
414 DCHECK(result->empty());
415 base_path = file_path.DirName();
416
417 // Append the path, including the terminating zero.
418 // We do this only for the first file.
419 result->append(base_path.value().c_str(), base_path.value().size() + 1);
420 }
421 DCHECK(!result->empty());
422 DCHECK(!base_path.empty());
423 DCHECK(base_path == file_path.DirName());
424
425 // Append the base name, including the terminating zero.
426 base::FilePath base_name = file_path.BaseName();
427 result->append(base_name.value().c_str(), base_name.value().size() + 1);
428 }
429
430 DCHECK(!result->empty());
431
432 return S_OK;
433 }
434
SaveFilePickerSession(OPENFILENAME * open_file_name)435 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name)
436 : FilePickerSessionBase(open_file_name) {
437 }
438
StartFilePicker()439 HRESULT SaveFilePickerSession::StartFilePicker() {
440 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread());
441 DCHECK(open_file_name_ != NULL);
442
443 mswrw::HStringReference class_name(
444 RuntimeClass_Windows_Storage_Pickers_FileSavePicker);
445
446 // Create the file picker.
447 mswr::ComPtr<winstorage::Pickers::IFileSavePicker> picker;
448 HRESULT hr = ::Windows::Foundation::ActivateInstance(
449 class_name.Get(), picker.GetAddressOf());
450 CheckHR(hr);
451
452 typedef winfoundtn::Collections::IMap<HSTRING, StringVectorItf*>
453 StringVectorMap;
454 mswr::ComPtr<StringVectorMap> choices;
455 hr = picker->get_FileTypeChoices(choices.GetAddressOf());
456 if (FAILED(hr))
457 return hr;
458
459 if (open_file_name_->lpstrFilter) {
460 // The filter is a concatenation of zero terminated string pairs,
461 // where each pair is {description, extension list}. The concatenation ends
462 // with a zero length string - e.g. a double zero terminator.
463 const wchar_t* walk = open_file_name_->lpstrFilter;
464 while (*walk != L'\0') {
465 mswrw::HString description;
466 hr = description.Set(walk);
467 if (FAILED(hr))
468 return hr;
469
470 // Walk past the description.
471 walk += wcslen(walk) + 1;
472
473 // We should have an extension, but bail on malformed filters.
474 if (*walk == L'\0')
475 break;
476
477 // There can be a single extension, or a list of semicolon-separated ones.
478 std::vector<string16> extensions_win32_style;
479 size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
480 DCHECK_EQ(extension_count, extensions_win32_style.size());
481
482 // Metro wants suffixes only, not patterns. Also, metro does not support
483 // the all files ("*") pattern in the save picker.
484 std::vector<string16> extensions;
485 for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
486 string16 ext = base::FilePath(extensions_win32_style[i]).Extension();
487 if ((ext.size() < 2) ||
488 (ext.find_first_of(L"*?") != string16::npos))
489 continue;
490 extensions.push_back(ext);
491 }
492
493 if (!extensions.empty()) {
494 // Convert to a Metro collection class.
495 mswr::ComPtr<StringVectorItf> list;
496 hr = mswr::MakeAndInitialize<StringVectorImpl>(
497 list.GetAddressOf(), extensions);
498 if (FAILED(hr))
499 return hr;
500
501 // Finally set the filter.
502 boolean replaced = FALSE;
503 hr = choices->Insert(description.Get(), list.Get(), &replaced);
504 if (FAILED(hr))
505 return hr;
506 DCHECK_EQ(FALSE, replaced);
507 }
508
509 // Walk past the extension(s).
510 walk += wcslen(walk) + 1;
511 }
512 }
513
514 // The save picker requires at least one choice. Callers are strongly advised
515 // to provide sensible choices. If none were given, fallback to .dat.
516 uint32 num_choices = 0;
517 hr = choices->get_Size(&num_choices);
518 if (FAILED(hr))
519 return hr;
520
521 if (num_choices == 0) {
522 mswrw::HString description;
523 // TODO(grt): Get a properly translated string. This can't be done from
524 // within metro_driver. Consider preprocessing the filter list in Chrome
525 // land to ensure it has this entry if all others are patterns. In that
526 // case, this whole block of code can be removed.
527 hr = description.Set(L"Data File");
528 if (FAILED(hr))
529 return hr;
530
531 mswr::ComPtr<StringVectorItf> list;
532 hr = mswr::MakeAndInitialize<StringVectorImpl>(
533 list.GetAddressOf(), std::vector<string16>(1, L".dat"));
534 if (FAILED(hr))
535 return hr;
536
537 boolean replaced = FALSE;
538 hr = choices->Insert(description.Get(), list.Get(), &replaced);
539 if (FAILED(hr))
540 return hr;
541 DCHECK_EQ(FALSE, replaced);
542 }
543
544 if (open_file_name_->lpstrFile != NULL) {
545 hr = picker->put_SuggestedFileName(
546 mswrw::HStringReference(
547 const_cast<const wchar_t*>(open_file_name_->lpstrFile)).Get());
548 if (FAILED(hr))
549 return hr;
550 }
551
552 mswr::ComPtr<SaveFileAsyncOp> completion;
553 hr = picker->PickSaveFileAsync(&completion);
554 if (FAILED(hr))
555 return hr;
556
557 // Create the callback method.
558 typedef winfoundtn::IAsyncOperationCompletedHandler<
559 winstorage::StorageFile*> HandlerDoneType;
560 mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
561 this, &SaveFilePickerSession::FilePickerDone));
562 DCHECK(handler.Get() != NULL);
563 hr = completion->put_Completed(handler.Get());
564
565 return hr;
566 }
567
FilePickerDone(SaveFileAsyncOp * async,AsyncStatus status)568 HRESULT SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp* async,
569 AsyncStatus status) {
570 if (status == Completed) {
571 mswr::ComPtr<winstorage::IStorageFile> file;
572 HRESULT hr = async->GetResults(file.GetAddressOf());
573
574 if (file) {
575 mswr::ComPtr<winstorage::IStorageItem> storage_item;
576 if (SUCCEEDED(hr))
577 hr = file.As(&storage_item);
578
579 mswrw::HString file_path;
580 if (SUCCEEDED(hr))
581 hr = storage_item->get_Path(file_path.GetAddressOf());
582
583 if (SUCCEEDED(hr)) {
584 string16 path_str = MakeStdWString(file_path.Get());
585
586 // If the selected file name is longer than the supplied buffer,
587 // we return false as per GetOpenFileName documentation.
588 if (path_str.size() < open_file_name_->nMaxFile) {
589 base::wcslcpy(open_file_name_->lpstrFile,
590 path_str.c_str(),
591 open_file_name_->nMaxFile);
592 success_ = true;
593 }
594 }
595 } else {
596 LOG(ERROR) << "NULL IStorageItem";
597 }
598 } else {
599 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
600 }
601
602 event_.Signal();
603
604 return S_OK;
605 }
606
607 } // namespace
608
MetroGetOpenFileName(OPENFILENAME * open_file_name)609 BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) {
610 OpenFilePickerSession session(open_file_name);
611
612 return session.Run();
613 }
614
MetroGetSaveFileName(OPENFILENAME * open_file_name)615 BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) {
616 SaveFilePickerSession session(open_file_name);
617
618 return session.Run();
619 }
620