• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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