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
19namespace {
20
21namespace winstorage = ABI::Windows::Storage;
22typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf;
23
24// TODO(siggi): Complete this implementation and move it to a common place.
25class 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
84FilePickerSessionBase::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
95bool FilePickerSessionBase::Run() {
96  if (!DoFilePicker())
97    return false;
98  return success_;
99}
100
101bool 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
118OpenFilePickerSession::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
128HRESULT 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
161HRESULT 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
212HRESULT 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
311HRESULT 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
373SaveFilePickerSession::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
383int 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
391HRESULT 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
521HRESULT 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
551FolderPickerSession::FolderPickerSession(ChromeAppViewAsh* app_view,
552                                         const string16& title)
553    : FilePickerSessionBase(app_view, title, L"", base::FilePath()) {}
554
555HRESULT 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
590HRESULT 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