MD Downloads: show 50 downloads instead of 150
[chromium-blink-merge.git] / win8 / metro_driver / file_picker.cc
blobb41e6be01d49878748bf767dbe99ff044d81b9ca
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.
5 #include "stdafx.h"
6 #include "win8/metro_driver/file_picker.h"
8 #include <windows.storage.pickers.h>
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"
21 namespace {
23 namespace winstorage = ABI::Windows::Storage;
24 typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf;
26 // TODO(siggi): Complete this implementation and move it to a common place.
27 class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> {
28 public:
29 ~StringVectorImpl() {
30 std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString);
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]));
37 return S_OK;
40 // IVector<HSTRING> implementation.
41 STDMETHOD(GetAt)(unsigned index, HSTRING* item) {
42 if (index >= strings_.size())
43 return E_INVALIDARG;
45 return ::WindowsDuplicateString(strings_[index], item);
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;
53 STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) {
54 return E_NOTIMPL;
56 STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) {
57 return E_NOTIMPL;
60 // write methods
61 STDMETHOD(SetAt)(unsigned index, HSTRING item) {
62 return E_NOTIMPL;
64 STDMETHOD(InsertAt)(unsigned index, HSTRING item) {
65 return E_NOTIMPL;
67 STDMETHOD(RemoveAt)(unsigned index) {
68 return E_NOTIMPL;
70 STDMETHOD(Append)(HSTRING item) {
71 return E_NOTIMPL;
73 STDMETHOD(RemoveAtEnd)() {
74 return E_NOTIMPL;
76 STDMETHOD(Clear)() {
77 return E_NOTIMPL;
80 private:
81 std::vector<HSTRING> strings_;
84 class FilePickerSessionBase {
85 public:
86 // Creates a file picker for open_file_name.
87 explicit FilePickerSessionBase(OPENFILENAME* open_file_name);
89 // Runs the picker, returns true on success.
90 bool Run();
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;
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_;
105 private:
106 // Initiate a file picker, must be called on the metro dispatcher's thread.
107 void DoFilePicker();
109 DISALLOW_COPY_AND_ASSIGN(FilePickerSessionBase);
112 class OpenFilePickerSession : public FilePickerSessionBase {
113 public:
114 explicit OpenFilePickerSession(OPENFILENAME* open_file_name);
116 private:
117 HRESULT StartFilePicker() override;
119 typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*>
120 SingleFileAsyncOp;
121 typedef winfoundtn::Collections::IVectorView<
122 winstorage::StorageFile*> StorageFileVectorCollection;
123 typedef winfoundtn::IAsyncOperation<StorageFileVectorCollection*>
124 MultiFileAsyncOp;
126 // Called asynchronously when a single file picker is done.
127 HRESULT SinglePickerDone(SingleFileAsyncOp* async, AsyncStatus status);
129 // Called asynchronously when a multi file picker is done.
130 HRESULT MultiPickerDone(MultiFileAsyncOp* async, AsyncStatus status);
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);
140 class SaveFilePickerSession : public FilePickerSessionBase {
141 public:
142 explicit SaveFilePickerSession(OPENFILENAME* open_file_name);
144 private:
145 HRESULT StartFilePicker() override;
147 typedef winfoundtn::IAsyncOperation<winstorage::StorageFile*>
148 SaveFileAsyncOp;
150 // Called asynchronously when the save file picker is done.
151 HRESULT FilePickerDone(SaveFileAsyncOp* async, AsyncStatus status);
154 FilePickerSessionBase::FilePickerSessionBase(OPENFILENAME* open_file_name)
155 : open_file_name_(open_file_name),
156 event_(true, false),
157 success_(false) {
160 bool FilePickerSessionBase::Run() {
161 DCHECK(globals.appview_msg_loop != NULL);
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;
169 // Wait for the file picker to complete.
170 event_.Wait();
172 return success_;
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;
183 if (SUCCEEDED(hr))
184 hr = StartFilePicker();
186 if (FAILED(hr)) {
187 LOG(ERROR) << "Failed to start file picker, error 0x"
188 << std::hex << hr;
190 event_.Signal();
194 OpenFilePickerSession::OpenFilePickerSession(OPENFILENAME* open_file_name)
195 : FilePickerSessionBase(open_file_name) {
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());
204 if (file) {
205 mswr::ComPtr<winstorage::IStorageItem> storage_item;
206 if (SUCCEEDED(hr))
207 hr = file.As(&storage_item);
209 mswrw::HString file_path;
210 if (SUCCEEDED(hr))
211 hr = storage_item->get_Path(file_path.GetAddressOf());
213 if (SUCCEEDED(hr)) {
214 UINT32 path_len = 0;
215 const wchar_t* path_str =
216 ::WindowsGetStringRawBuffer(file_path.Get(), &path_len);
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;
227 } else {
228 LOG(ERROR) << "NULL IStorageItem";
230 } else {
231 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
234 event_.Signal();
236 return S_OK;
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());
245 if (files) {
246 base::string16 result;
247 if (SUCCEEDED(hr))
248 hr = ComposeMultiFileResult(files.Get(), &result);
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;
259 } else {
260 LOG(ERROR) << "NULL StorageFileVectorCollection";
262 } else {
263 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
266 event_.Signal();
268 return S_OK;
271 HRESULT OpenFilePickerSession::StartFilePicker() {
272 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread());
273 DCHECK(open_file_name_ != NULL);
275 mswrw::HStringReference class_name(
276 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker);
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);
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;
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;
303 // We should have an extension, but bail on malformed filters.
304 if (*walk == L'\0')
305 break;
307 // There can be a single extension, or a list of semicolon-separated ones.
308 std::vector<base::string16> extensions_win32_style = base::SplitString(
309 walk, L";", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
311 // Metro wants suffixes only, not patterns.
312 mswrw::HString extension;
313 for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
314 if (extensions_win32_style[i] == L"*.*") {
315 // The wildcard filter is "*" for Metro. The string "*.*" produces
316 // an "invalid parameter" error.
317 hr = extension.Set(L"*");
318 } else {
319 // Metro wants suffixes only, not patterns.
320 base::string16 ext =
321 base::FilePath(extensions_win32_style[i]).Extension();
322 if ((ext.size() < 2) ||
323 (ext.find_first_of(L"*?") != base::string16::npos)) {
324 continue;
326 hr = extension.Set(ext.c_str());
328 if (SUCCEEDED(hr))
329 hr = filter->Append(extension.Get());
330 if (FAILED(hr))
331 return hr;
334 // Walk past the extension.
335 walk += wcslen(walk) + 1;
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;
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());
354 return hr;
355 } else {
356 mswr::ComPtr<SingleFileAsyncOp> completion;
357 hr = picker->PickSingleFileAsync(&completion);
358 if (FAILED(hr))
359 return hr;
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());
369 return hr;
373 HRESULT OpenFilePickerSession::ComposeMultiFileResult(
374 StorageFileVectorCollection* files, base::string16* result) {
375 DCHECK(files != NULL);
376 DCHECK(result != NULL);
378 // Empty the output string.
379 result->clear();
381 unsigned int num_files = 0;
382 HRESULT hr = files->get_Size(&num_files);
383 if (FAILED(hr))
384 return hr;
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;
392 // This stores the base path that should be the parent of all the files.
393 base::FilePath base_path;
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;
402 mswr::ComPtr<winstorage::IStorageItem> storage_item;
403 hr = file.As(&storage_item);
404 if (FAILED(hr))
405 return hr;
407 mswrw::HString file_path_str;
408 hr = storage_item->get_Path(file_path_str.GetAddressOf());
409 if (FAILED(hr))
410 return hr;
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();
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);
421 DCHECK(!result->empty());
422 DCHECK(!base_path.empty());
423 DCHECK(base_path == file_path.DirName());
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);
430 DCHECK(!result->empty());
432 return S_OK;
435 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name)
436 : FilePickerSessionBase(open_file_name) {
439 HRESULT SaveFilePickerSession::StartFilePicker() {
440 DCHECK(globals.appview_msg_loop->BelongsToCurrentThread());
441 DCHECK(open_file_name_ != NULL);
443 mswrw::HStringReference class_name(
444 RuntimeClass_Windows_Storage_Pickers_FileSavePicker);
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);
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;
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;
470 // Walk past the description.
471 walk += wcslen(walk) + 1;
473 // We should have an extension, but bail on malformed filters.
474 if (*walk == L'\0')
475 break;
477 // There can be a single extension, or a list of semicolon-separated ones.
478 std::vector<base::string16> extensions_win32_style = base::SplitString(
479 walk, L";", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
481 // Metro wants suffixes only, not patterns. Also, metro does not support
482 // the all files ("*") pattern in the save picker.
483 std::vector<base::string16> extensions;
484 for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
485 base::string16 ext =
486 base::FilePath(extensions_win32_style[i]).Extension();
487 if ((ext.size() < 2) ||
488 (ext.find_first_of(L"*?") != base::string16::npos))
489 continue;
490 extensions.push_back(ext);
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;
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);
509 // Walk past the extension(s).
510 walk += wcslen(walk) + 1;
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;
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;
531 mswr::ComPtr<StringVectorItf> list;
532 hr = mswr::MakeAndInitialize<StringVectorImpl>(
533 list.GetAddressOf(), std::vector<base::string16>(1, L".dat"));
534 if (FAILED(hr))
535 return hr;
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);
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;
552 mswr::ComPtr<SaveFileAsyncOp> completion;
553 hr = picker->PickSaveFileAsync(&completion);
554 if (FAILED(hr))
555 return hr;
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());
565 return hr;
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());
574 if (file) {
575 mswr::ComPtr<winstorage::IStorageItem> storage_item;
576 if (SUCCEEDED(hr))
577 hr = file.As(&storage_item);
579 mswrw::HString file_path;
580 if (SUCCEEDED(hr))
581 hr = storage_item->get_Path(file_path.GetAddressOf());
583 if (SUCCEEDED(hr)) {
584 base::string16 path_str = MakeStdWString(file_path.Get());
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;
595 } else {
596 LOG(ERROR) << "NULL IStorageItem";
598 } else {
599 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
602 event_.Signal();
604 return S_OK;
607 } // namespace
609 BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) {
610 OpenFilePickerSession session(open_file_name);
612 return session.Run();
615 BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) {
616 SaveFilePickerSession session(open_file_name);
618 return session.Run();