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<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                                         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       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<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 
    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 
    435 SaveFilePickerSession::SaveFilePickerSession(OPENFILENAME* open_file_name)
    436     : FilePickerSessionBase(open_file_name) {
    437 }
    438 
    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 
    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 
    609 BOOL MetroGetOpenFileName(OPENFILENAME* open_file_name) {
    610   OpenFilePickerSession session(open_file_name);
    611 
    612   return session.Run();
    613 }
    614 
    615 BOOL MetroGetSaveFileName(OPENFILENAME* open_file_name) {
    616   SaveFilePickerSession session(open_file_name);
    617 
    618   return session.Run();
    619 }
    620