• 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<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