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