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 " << static_cast<int>(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 " << static_cast<int>(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       for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
    253         if (extensions_win32_style[i] == L"*.*") {
    254           // The wildcard filter is "*" for Metro. The string "*.*" produces
    255           // an "invalid parameter" error.
    256           hr = extension.Set(L"*");
    257         } else {
    258           // Metro wants suffixes only, not patterns.
    259           string16 ext = base::FilePath(extensions_win32_style[i]).Extension();
    260           if ((ext.size() < 2) ||
    261               (ext.find_first_of(L"*?") != 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 
    311 HRESULT OpenFilePickerSession::ComposeMultiFileResult(
    312     StorageFileVectorCollection* files, 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 
    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 
    383 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 
    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<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<string16> extensions;
    434       for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
    435         string16 ext = base::FilePath(extensions_win32_style[i]).Extension();
    436         if ((ext.size() < 2) ||
    437             (ext.find_first_of(L"*?") != string16::npos))
    438           continue;
    439         extensions.push_back(ext);
    440       }
    441 
    442       if (!extensions.empty()) {
    443         // Convert to a Metro collection class.
    444         mswr::ComPtr<StringVectorItf> list;
    445         hr = mswr::MakeAndInitialize<StringVectorImpl>(
    446             list.GetAddressOf(), extensions);
    447         if (FAILED(hr))
    448           return hr;
    449 
    450         // Finally set the filter.
    451         boolean replaced = FALSE;
    452         hr = choices->Insert(description.Get(), list.Get(), &replaced);
    453         if (FAILED(hr))
    454           return hr;
    455         DCHECK_EQ(FALSE, replaced);
    456       }
    457 
    458       // Walk past the extension(s).
    459       walk += wcslen(walk) + 1;
    460     }
    461   }
    462 
    463   // The save picker requires at least one choice.  Callers are strongly advised
    464   // to provide sensible choices.  If none were given, fallback to .dat.
    465   uint32 num_choices = 0;
    466   hr = choices->get_Size(&num_choices);
    467   if (FAILED(hr))
    468     return hr;
    469 
    470   if (num_choices == 0) {
    471     mswrw::HString description;
    472     // TODO(grt): Get a properly translated string.  This can't be done from
    473     // within metro_driver.  Consider preprocessing the filter list in Chrome
    474     // land to ensure it has this entry if all others are patterns.  In that
    475     // case, this whole block of code can be removed.
    476     hr = description.Set(L"Data File");
    477     if (FAILED(hr))
    478       return hr;
    479 
    480     mswr::ComPtr<StringVectorItf> list;
    481     hr = mswr::MakeAndInitialize<StringVectorImpl>(
    482         list.GetAddressOf(), std::vector<string16>(1, L".dat"));
    483     if (FAILED(hr))
    484       return hr;
    485 
    486     boolean replaced = FALSE;
    487     hr = choices->Insert(description.Get(), list.Get(), &replaced);
    488     if (FAILED(hr))
    489       return hr;
    490     DCHECK_EQ(FALSE, replaced);
    491   }
    492 
    493   if (!default_path_.empty()) {
    494     string16 file_part = default_path_.BaseName().value();
    495     // If the suggested_name is a root directory, then don't set it as the
    496     // suggested name.
    497     if (file_part.size() == 1 && file_part[0] == L'\\')
    498       file_part.clear();
    499     hr = picker->put_SuggestedFileName(
    500         mswrw::HStringReference(file_part.c_str()).Get());
    501     if (FAILED(hr))
    502       return hr;
    503   }
    504 
    505   mswr::ComPtr<SaveFileAsyncOp> completion;
    506   hr = picker->PickSaveFileAsync(&completion);
    507   if (FAILED(hr))
    508     return hr;
    509 
    510   // Create the callback method.
    511   typedef winfoundtn::IAsyncOperationCompletedHandler<
    512       winstorage::StorageFile*> HandlerDoneType;
    513   mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
    514       this, &SaveFilePickerSession::FilePickerDone));
    515   DCHECK(handler.Get() != NULL);
    516   hr = completion->put_Completed(handler.Get());
    517 
    518   return hr;
    519 }
    520 
    521 HRESULT SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp* async,
    522                                               AsyncStatus status) {
    523   if (status == Completed) {
    524     mswr::ComPtr<winstorage::IStorageFile> file;
    525     HRESULT hr = async->GetResults(file.GetAddressOf());
    526 
    527     if (file) {
    528       mswr::ComPtr<winstorage::IStorageItem> storage_item;
    529       if (SUCCEEDED(hr))
    530         hr = file.As(&storage_item);
    531 
    532       mswrw::HString file_path;
    533       if (SUCCEEDED(hr))
    534         hr = storage_item->get_Path(file_path.GetAddressOf());
    535 
    536       if (SUCCEEDED(hr)) {
    537         string16 path_str = MakeStdWString(file_path.Get());
    538         result_ = path_str;
    539         success_ = true;
    540       }
    541     } else {
    542       LOG(ERROR) << "NULL IStorageItem";
    543     }
    544   } else {
    545     LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
    546   }
    547   app_view_->OnSaveFileCompleted(this, success_);
    548   return S_OK;
    549 }
    550 
    551 FolderPickerSession::FolderPickerSession(ChromeAppViewAsh* app_view,
    552                                          const string16& title)
    553     : FilePickerSessionBase(app_view, title, L"", base::FilePath()) {}
    554 
    555 HRESULT FolderPickerSession::StartFilePicker() {
    556   mswrw::HStringReference class_name(
    557       RuntimeClass_Windows_Storage_Pickers_FolderPicker);
    558 
    559   // Create the folder picker.
    560   mswr::ComPtr<winstorage::Pickers::IFolderPicker> picker;
    561   HRESULT hr = ::Windows::Foundation::ActivateInstance(
    562       class_name.Get(), picker.GetAddressOf());
    563   CheckHR(hr);
    564 
    565   // Set the file type filter
    566   mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter;
    567   hr = picker->get_FileTypeFilter(filter.GetAddressOf());
    568   if (FAILED(hr))
    569     return hr;
    570 
    571   hr = filter->Append(mswrw::HStringReference(L"*").Get());
    572   if (FAILED(hr))
    573     return hr;
    574 
    575   mswr::ComPtr<FolderPickerAsyncOp> completion;
    576   hr = picker->PickSingleFolderAsync(&completion);
    577   if (FAILED(hr))
    578     return hr;
    579 
    580   // Create the callback method.
    581   typedef winfoundtn::IAsyncOperationCompletedHandler<
    582       winstorage::StorageFolder*> HandlerDoneType;
    583   mswr::ComPtr<HandlerDoneType> handler(mswr::Callback<HandlerDoneType>(
    584       this, &FolderPickerSession::FolderPickerDone));
    585   DCHECK(handler.Get() != NULL);
    586   hr = completion->put_Completed(handler.Get());
    587   return hr;
    588 }
    589 
    590 HRESULT FolderPickerSession::FolderPickerDone(FolderPickerAsyncOp* async,
    591                                               AsyncStatus status) {
    592   if (status == Completed) {
    593     mswr::ComPtr<winstorage::IStorageFolder> folder;
    594     HRESULT hr = async->GetResults(folder.GetAddressOf());
    595 
    596     if (folder) {
    597       mswr::ComPtr<winstorage::IStorageItem> storage_item;
    598       if (SUCCEEDED(hr))
    599         hr = folder.As(&storage_item);
    600 
    601       mswrw::HString file_path;
    602       if (SUCCEEDED(hr))
    603         hr = storage_item->get_Path(file_path.GetAddressOf());
    604 
    605       if (SUCCEEDED(hr)) {
    606         string16 path_str = MakeStdWString(file_path.Get());
    607         result_ = path_str;
    608         success_ = true;
    609       }
    610     } else {
    611       LOG(ERROR) << "NULL IStorageItem";
    612     }
    613   } else {
    614     LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
    615   }
    616   app_view_->OnFolderPickerCompleted(this, success_);
    617   return S_OK;
    618 }
    619 
    620