Home | History | Annotate | Download | only in metro_driver
      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:
     29   ~StringVectorImpl() {
     30     std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString);
     31   }
     32 
     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.
     41   STDMETHOD(GetAt)(unsigned index, HSTRING* item) {
     42     if (index >= strings_.size())
     43       return E_INVALIDARG;
     44 
     45     return ::WindowsDuplicateString(strings_[index], item);
     46   }
     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   }
     53   STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) {
     54     return E_NOTIMPL;
     55   }
     56   STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) {
     57     return E_NOTIMPL;
     58   }
     59 
     60   // write methods
     61   STDMETHOD(SetAt)(unsigned index, HSTRING item) {
     62     return E_NOTIMPL;
     63   }
     64   STDMETHOD(InsertAt)(unsigned index, HSTRING item) {
     65     return E_NOTIMPL;
     66   }
     67   STDMETHOD(RemoveAt)(unsigned index) {
     68     return E_NOTIMPL;
     69   }
     70   STDMETHOD(Append)(HSTRING item) {
     71     return E_NOTIMPL;
     72   }
     73   STDMETHOD(RemoveAtEnd)() {
     74     return E_NOTIMPL;
     75   }
     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 
    154 FilePickerSessionBase::FilePickerSessionBase(OPENFILENAME* open_file_name)
    155     : open_file_name_(open_file_name),
    156       event_(true, false),
    157       success_(false) {
    158 }
    159 
    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 
    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 
    194 OpenFilePickerSession::OpenFilePickerSession(OPENFILENAME* open_file_name)
    195     : FilePickerSessionBase(open_file_name) {
    196 }
    197 
    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 
    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 
    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 
    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 
    436 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name)
    437     : FilePickerSessionBase(open_file_name) {
    438 }
    439 
    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 
    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 
    611 BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) {
    612   OpenFilePickerSession session(open_file_name);
    613 
    614   return session.Run();
    615 }
    616 
    617 BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) {
    618   SaveFilePickerSession session(open_file_name);
    619 
    620   return session.Run();
    621 }
    622