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