Home | History | Annotate | Download | only in metro_driver
      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 namespace winstorage = ABI::Windows::Storage;
     22 typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf;
     23 
     24 // TODO(siggi): Complete this implementation and move it to a common place.
     25 class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> {
     26  public:
     27   ~StringVectorImpl() {
     28     std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString);
     29   }
     30 
     31   HRESULT RuntimeClassInitialize(const std::vector<string16>& list) {
     32     for (size_t i = 0; i < list.size(); ++i)
     33       strings_.push_back(MakeHString(list[i]));
     34 
     35     return S_OK;
     36   }
     37 
     38   // IVector<HSTRING> implementation.
     39   STDMETHOD(GetAt)(unsigned index, HSTRING* item) {
     40     if (index >= strings_.size())
     41       return E_INVALIDARG;
     42 
     43     return ::WindowsDuplicateString(strings_[index], item);
     44   }
     45   STDMETHOD(get_Size)(unsigned *size) {
     46     if (strings_.size() > UINT_MAX)
     47       return E_UNEXPECTED;
     48     *size = static_cast<unsigned>(strings_.size());
     49     return S_OK;
     50   }
     51   STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) {
     52     return E_NOTIMPL;
     53   }
     54   STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) {
     55     return E_NOTIMPL;
     56   }
     57 
     58   // write methods
     59   STDMETHOD(SetAt)(unsigned index, HSTRING item) {
     60     return E_NOTIMPL;
     61   }
     62   STDMETHOD(InsertAt)(unsigned index, HSTRING item) {
     63     return E_NOTIMPL;
     64   }
     65   STDMETHOD(RemoveAt)(unsigned index) {
     66     return E_NOTIMPL;
     67   }
     68   STDMETHOD(Append)(HSTRING item) {
     69     return E_NOTIMPL;
     70   }
     71   STDMETHOD(RemoveAtEnd)() {
     72     return E_NOTIMPL;
     73   }
     74   STDMETHOD(Clear)() {
     75     return E_NOTIMPL;
     76   }
     77 
     78  private:
     79   std::vector<HSTRING> strings_;
     80 };
     81 
     82 }  // namespace
     83 
     84 FilePickerSessionBase::FilePickerSessionBase(ChromeAppViewAsh* app_view,
     85                                              const string16& title,
     86                                              const string16& filter,
     87                                              const base::FilePath& default_path)
     88     : app_view_(app_view),
     89       title_(title),
     90       filter_(filter),
     91       default_path_(default_path),
     92       success_(false) {
     93 }
     94 
     95 bool FilePickerSessionBase::Run() {
     96   if (!DoFilePicker())
     97     return false;
     98   return success_;
     99 }
    100 
    101 bool FilePickerSessionBase::DoFilePicker() {
    102   // The file picker will fail if spawned from a snapped application,
    103   // so let's attempt to unsnap first if we're in that state.
    104   HRESULT hr = ChromeAppViewAsh::Unsnap();
    105   if (FAILED(hr)) {
    106     LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr;
    107     return false;
    108   }
    109   hr = StartFilePicker();
    110   if (FAILED(hr)) {
    111     LOG(ERROR) << "Failed to start file picker, error 0x"
    112                << std::hex << hr;
    113     return false;
    114   }
    115   return true;
    116 }
    117 
    118 OpenFilePickerSession::OpenFilePickerSession(
    119     ChromeAppViewAsh* app_view,
    120     const string16& title,
    121     const string16& filter,
    122     const base::FilePath& default_path,
    123     bool allow_multi_select)
    124     : FilePickerSessionBase(app_view, title, filter, default_path),
    125       allow_multi_select_(allow_multi_select) {
    126 }
    127 
    128 HRESULT OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp* async,
    129                                                 AsyncStatus status) {
    130   if (status == Completed) {
    131     mswr::ComPtr<winstorage::IStorageFile> file;
    132     HRESULT hr = async->GetResults(file.GetAddressOf());
    133 
    134     if (file) {
    135       mswr::ComPtr<winstorage::IStorageItem> storage_item;
    136       if (SUCCEEDED(hr))
    137         hr = file.As(&storage_item);
    138 
    139       mswrw::HString file_path;
    140       if (SUCCEEDED(hr))
    141         hr = storage_item->get_Path(file_path.GetAddressOf());
    142 
    143       if (SUCCEEDED(hr)) {
    144         UINT32 path_len = 0;
    145         const wchar_t* path_str =
    146             ::WindowsGetStringRawBuffer(file_path.Get(), &path_len);
    147 
    148         result_ = path_str;
    149         success_ = true;
    150       }
    151     } else {
    152       LOG(ERROR) << "NULL IStorageItem";
    153     }
    154   } else {
    155     LOG(ERROR) << "Unexpected async status " << status;
    156   }
    157   app_view_->OnOpenFileCompleted(this, success_);
    158   return S_OK;
    159 }
    160 
    161 HRESULT OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp* async,
    162                                                AsyncStatus status) {
    163   if (status == Completed) {
    164     mswr::ComPtr<StorageFileVectorCollection> files;
    165     HRESULT hr = async->GetResults(files.GetAddressOf());
    166 
    167     if (files) {
    168       string16 result;
    169       if (SUCCEEDED(hr))
    170         hr = ComposeMultiFileResult(files.Get(), &result);
    171 
    172       if (SUCCEEDED(hr)) {
    173         success_ = true;
    174         // The code below has been copied from the
    175         // SelectFileDialogImpl::RunOpenMultiFileDialog function in
    176         // select_file_dialog_win.cc.
    177         // TODO(ananta)
    178         // Consolidate this into a common place.
    179         const wchar_t* selection = result.c_str();
    180         std::vector<base::FilePath> files;
    181 
    182         while (*selection) {  // Empty string indicates end of list.
    183           files.push_back(base::FilePath(selection));
    184           // Skip over filename and null-terminator.
    185           selection += files.back().value().length() + 1;
    186         }
    187         if (files.empty()) {
    188           success_ = false;
    189         } else if (files.size() == 1) {
    190           // When there is one file, it contains the path and filename.
    191           filenames_ = files;
    192         } else if (files.size() > 1) {
    193           // Otherwise, the first string is the path, and the remainder are
    194           // filenames.
    195           std::vector<base::FilePath>::iterator path = files.begin();
    196           for (std::vector<base::FilePath>::iterator file = path + 1;
    197                file != files.end(); ++file) {
    198             filenames_.push_back(path->Append(*file));
    199           }
    200         }
    201       }
    202     } else {
    203       LOG(ERROR) << "NULL StorageFileVectorCollection";
    204     }
    205   } else {
    206     LOG(ERROR) << "Unexpected async status " << status;
    207   }
    208   app_view_->OnOpenFileCompleted(this, success_);
    209   return S_OK;
    210 }
    211 
    212 HRESULT OpenFilePickerSession::StartFilePicker() {
    213   mswrw::HStringReference class_name(
    214       RuntimeClass_Windows_Storage_Pickers_FileOpenPicker);
    215 
    216   // Create the file picker.
    217   mswr::ComPtr<winstorage::Pickers::IFileOpenPicker> picker;
    218   HRESULT hr = ::Windows::Foundation::ActivateInstance(
    219       class_name.Get(), picker.GetAddressOf());
    220   CheckHR(hr);
    221 
    222   // Set the file type filter
    223   mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter;
    224   hr = picker->get_FileTypeFilter(filter.GetAddressOf());
    225   if (FAILED(hr))
    226     return hr;
    227 
    228   if (filter_.empty()) {
    229     hr = filter->Append(mswrw::HStringReference(L"*").Get());
    230     if (FAILED(hr))
    231       return hr;
    232   } else {
    233     // The filter is a concatenation of zero terminated string pairs,
    234     // where each pair is {description, extension}. The concatenation ends
    235     // with a zero length string - e.g. a double zero terminator.
    236     const wchar_t* walk = filter_.c_str();
    237     while (*walk != L'\0') {
    238       // Walk past the description.
    239       walk += wcslen(walk) + 1;
    240 
    241       // We should have an extension, but bail on malformed filters.
    242       if (*walk == L'\0')
    243         break;
    244 
    245       // There can be a single extension, or a list of semicolon-separated ones.
    246       std::vector<string16> extensions_win32_style;
    247       size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
    248       DCHECK_EQ(extension_count, extensions_win32_style.size());
    249 
    250       // Metro wants suffixes only, not patterns.
    251       mswrw::HString extension;
    252       std::vector<string16> extensions;
    253       for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
    254         if (extensions_win32_style[i] == L"*.*") {
    255           // The wildcard filter is "*" for Metro. The string "*.*" produces
    256           // an "invalid parameter" error.
    257           hr = extension.Set(L"*");
    258         } else {
    259           // Metro wants suffixes only, not patterns.
    260           string16 ext = base::FilePath(extensions_win32_style[i]).Extension();
    261           if ((ext.size() < 2) ||
    262               (ext.find_first_of(L"*?") != string16::npos)) {
    263             continue;
    264           }
    265           hr = extension.Set(ext.c_str());
    266         }
    267         if (SUCCEEDED(hr))
    268           hr = filter->Append(extension.Get());
    269         if (FAILED(hr))
    270           return hr;
    271       }
    272 
    273       // Walk past the extension.
    274       walk += wcslen(walk) + 1;
    275     }
    276   }
    277 
    278   // Spin up a single or multi picker as appropriate.
    279   if (allow_multi_select_) {
    280     mswr::ComPtr<MultiFileAsyncOp> completion;
    281     hr = picker->PickMultipleFilesAsync(&completion);
    282     if (FAILED(hr))
    283       return hr;
    284 
    285     // Create the callback method.
    286     typedef winfoundtn::IAsyncOperationCompletedHandler<
    287         StorageFileVectorCollection*> HandlerDoneType;
    288     mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
    289         this, &OpenFilePickerSession::MultiPickerDone));
    290     DCHECK(handler.Get() != NULL);
    291     hr = completion->put_Completed(handler.Get());
    292 
    293     return hr;
    294   } else {
    295     mswr::ComPtr<SingleFileAsyncOp> completion;
    296     hr = picker->PickSingleFileAsync(&completion);
    297     if (FAILED(hr))
    298       return hr;
    299 
    300     // Create the callback method.
    301     typedef winfoundtn::IAsyncOperationCompletedHandler<
    302         winstorage::StorageFile*> HandlerDoneType;
    303     mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
    304         this, &OpenFilePickerSession::SinglePickerDone));
    305     DCHECK(handler.Get() != NULL);
    306     hr = completion->put_Completed(handler.Get());
    307 
    308     return hr;
    309   }
    310 }
    311 
    312 HRESULT OpenFilePickerSession::ComposeMultiFileResult(
    313     StorageFileVectorCollection* files, string16* result) {
    314   DCHECK(files != NULL);
    315   DCHECK(result != NULL);
    316 
    317   // Empty the output string.
    318   result->clear();
    319 
    320   unsigned int num_files = 0;
    321   HRESULT hr = files->get_Size(&num_files);
    322   if (FAILED(hr))
    323     return hr;
    324 
    325   // Make sure we return an error on an empty collection.
    326   if (num_files == 0) {
    327     DLOG(ERROR) << "Empty collection on input.";
    328     return E_UNEXPECTED;
    329   }
    330 
    331   // This stores the base path that should be the parent of all the files.
    332   base::FilePath base_path;
    333 
    334   // Iterate through the collection and append the file paths to the result.
    335   for (unsigned int i = 0; i < num_files; ++i) {
    336     mswr::ComPtr<winstorage::IStorageFile> file;
    337     hr = files->GetAt(i, file.GetAddressOf());
    338     if (FAILED(hr))
    339       return hr;
    340 
    341     mswr::ComPtr<winstorage::IStorageItem> storage_item;
    342     hr = file.As(&storage_item);
    343     if (FAILED(hr))
    344       return hr;
    345 
    346     mswrw::HString file_path_str;
    347     hr = storage_item->get_Path(file_path_str.GetAddressOf());
    348     if (FAILED(hr))
    349       return hr;
    350 
    351     base::FilePath file_path(MakeStdWString(file_path_str.Get()));
    352     if (base_path.empty()) {
    353       DCHECK(result->empty());
    354       base_path = file_path.DirName();
    355 
    356       // Append the path, including the terminating zero.
    357       // We do this only for the first file.
    358       result->append(base_path.value().c_str(), base_path.value().size() + 1);
    359     }
    360     DCHECK(!result->empty());
    361     DCHECK(!base_path.empty());
    362     DCHECK(base_path == file_path.DirName());
    363 
    364     // Append the base name, including the terminating zero.
    365     base::FilePath base_name = file_path.BaseName();
    366     result->append(base_name.value().c_str(), base_name.value().size() + 1);
    367   }
    368 
    369   DCHECK(!result->empty());
    370 
    371   return S_OK;
    372 }
    373 
    374 SaveFilePickerSession::SaveFilePickerSession(
    375     ChromeAppViewAsh* app_view,
    376     const MetroViewerHostMsg_SaveAsDialogParams& params)
    377     : FilePickerSessionBase(app_view,
    378                             params.title,
    379                             params.filter,
    380                             params.suggested_name),
    381       filter_index_(params.filter_index) {
    382 }
    383 
    384 int SaveFilePickerSession::filter_index() const {
    385   // TODO(ananta)
    386   // Add support for returning the correct filter index. This does not work in
    387   // regular Chrome metro on trunk as well.
    388   // BUG: https://code.google.com/p/chromium/issues/detail?id=172704
    389   return filter_index_;
    390 }
    391 
    392 HRESULT SaveFilePickerSession::StartFilePicker() {
    393   mswrw::HStringReference class_name(
    394       RuntimeClass_Windows_Storage_Pickers_FileSavePicker);
    395 
    396   // Create the file picker.
    397   mswr::ComPtr<winstorage::Pickers::IFileSavePicker> picker;
    398   HRESULT hr = ::Windows::Foundation::ActivateInstance(
    399       class_name.Get(), picker.GetAddressOf());
    400   CheckHR(hr);
    401 
    402   typedef winfoundtn::Collections::IMap<HSTRING, StringVectorItf*>
    403       StringVectorMap;
    404   mswr::ComPtr<StringVectorMap> choices;
    405   hr = picker->get_FileTypeChoices(choices.GetAddressOf());
    406   if (FAILED(hr))
    407     return hr;
    408 
    409   if (!filter_.empty()) {
    410     // The filter is a concatenation of zero terminated string pairs,
    411     // where each pair is {description, extension list}. The concatenation ends
    412     // with a zero length string - e.g. a double zero terminator.
    413     const wchar_t* walk = filter_.c_str();
    414     while (*walk != L'\0') {
    415       mswrw::HString description;
    416       hr = description.Set(walk);
    417       if (FAILED(hr))
    418         return hr;
    419 
    420       // Walk past the description.
    421       walk += wcslen(walk) + 1;
    422 
    423       // We should have an extension, but bail on malformed filters.
    424       if (*walk == L'\0')
    425         break;
    426 
    427       // There can be a single extension, or a list of semicolon-separated ones.
    428       std::vector<string16> extensions_win32_style;
    429       size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
    430       DCHECK_EQ(extension_count, extensions_win32_style.size());
    431 
    432       // Metro wants suffixes only, not patterns.  Also, metro does not support
    433       // the all files ("*") pattern in the save picker.
    434       std::vector<string16> extensions;
    435       for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
    436         string16 ext = base::FilePath(extensions_win32_style[i]).Extension();
    437         if ((ext.size() < 2) ||
    438             (ext.find_first_of(L"*?") != 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<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     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 
    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         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 " << status;
    547   }
    548   app_view_->OnSaveFileCompleted(this, success_);
    549   return S_OK;
    550 }
    551 
    552 FolderPickerSession::FolderPickerSession(ChromeAppViewAsh* app_view,
    553                                          const string16& title)
    554     : FilePickerSessionBase(app_view, title, L"", base::FilePath()) {}
    555 
    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 
    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         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 " << status;
    616   }
    617   app_view_->OnFolderPickerCompleted(this, success_);
    618   return S_OK;
    619 }
    620 
    621