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
21typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf;
22
23// TODO(siggi): Complete this implementation and move it to a common place.
24class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> {
25 public:
26  ~StringVectorImpl() {
27    std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString);
28  }
29
30  HRESULT RuntimeClassInitialize(const std::vector<base::string16>& list) {
31    for (size_t i = 0; i < list.size(); ++i)
32      strings_.push_back(MakeHString(list[i]));
33
34    return S_OK;
35  }
36
37  // IVector<HSTRING> implementation.
38  STDMETHOD(GetAt)(unsigned index, HSTRING* item) {
39    if (index >= strings_.size())
40      return E_INVALIDARG;
41
42    return ::WindowsDuplicateString(strings_[index], item);
43  }
44  STDMETHOD(get_Size)(unsigned *size) {
45    if (strings_.size() > UINT_MAX)
46      return E_UNEXPECTED;
47    *size = static_cast<unsigned>(strings_.size());
48    return S_OK;
49  }
50  STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) {
51    return E_NOTIMPL;
52  }
53  STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) {
54    return E_NOTIMPL;
55  }
56
57  // write methods
58  STDMETHOD(SetAt)(unsigned index, HSTRING item) {
59    return E_NOTIMPL;
60  }
61  STDMETHOD(InsertAt)(unsigned index, HSTRING item) {
62    return E_NOTIMPL;
63  }
64  STDMETHOD(RemoveAt)(unsigned index) {
65    return E_NOTIMPL;
66  }
67  STDMETHOD(Append)(HSTRING item) {
68    return E_NOTIMPL;
69  }
70  STDMETHOD(RemoveAtEnd)() {
71    return E_NOTIMPL;
72  }
73  STDMETHOD(Clear)() {
74    return E_NOTIMPL;
75  }
76
77 private:
78  std::vector<HSTRING> strings_;
79};
80
81}  // namespace
82
83FilePickerSessionBase::FilePickerSessionBase(ChromeAppViewAsh* app_view,
84                                             const base::string16& title,
85                                             const base::string16& filter,
86                                             const base::FilePath& default_path)
87    : app_view_(app_view),
88      title_(title),
89      filter_(filter),
90      default_path_(default_path),
91      success_(false) {
92}
93
94bool FilePickerSessionBase::Run() {
95  if (!DoFilePicker())
96    return false;
97  return success_;
98}
99
100bool FilePickerSessionBase::DoFilePicker() {
101  // The file picker will fail if spawned from a snapped application,
102  // so let's attempt to unsnap first if we're in that state.
103  HRESULT hr = ChromeAppViewAsh::Unsnap();
104  if (FAILED(hr)) {
105    LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr;
106    return false;
107  }
108  hr = StartFilePicker();
109  if (FAILED(hr)) {
110    LOG(ERROR) << "Failed to start file picker, error 0x"
111               << std::hex << hr;
112    return false;
113  }
114  return true;
115}
116
117OpenFilePickerSession::OpenFilePickerSession(
118    ChromeAppViewAsh* app_view,
119    const base::string16& title,
120    const base::string16& filter,
121    const base::FilePath& default_path,
122    bool allow_multi_select)
123    : FilePickerSessionBase(app_view, title, filter, default_path),
124      allow_multi_select_(allow_multi_select) {
125}
126
127HRESULT OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp* async,
128                                                AsyncStatus status) {
129  if (status == Completed) {
130    mswr::ComPtr<winstorage::IStorageFile> file;
131    HRESULT hr = async->GetResults(file.GetAddressOf());
132
133    if (file) {
134      mswr::ComPtr<winstorage::IStorageItem> storage_item;
135      if (SUCCEEDED(hr))
136        hr = file.As(&storage_item);
137
138      mswrw::HString file_path;
139      if (SUCCEEDED(hr))
140        hr = storage_item->get_Path(file_path.GetAddressOf());
141
142      if (SUCCEEDED(hr)) {
143        UINT32 path_len = 0;
144        const wchar_t* path_str =
145            ::WindowsGetStringRawBuffer(file_path.Get(), &path_len);
146
147        result_ = path_str;
148        success_ = true;
149      }
150    } else {
151      LOG(ERROR) << "NULL IStorageItem";
152    }
153  } else {
154    LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
155  }
156  app_view_->OnOpenFileCompleted(this, success_);
157  return S_OK;
158}
159
160HRESULT OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp* async,
161                                               AsyncStatus status) {
162  if (status == Completed) {
163    mswr::ComPtr<StorageFileVectorCollection> files;
164    HRESULT hr = async->GetResults(files.GetAddressOf());
165
166    if (files) {
167      base::string16 result;
168      if (SUCCEEDED(hr))
169        hr = ComposeMultiFileResult(files.Get(), &result);
170
171      if (SUCCEEDED(hr)) {
172        success_ = true;
173        // The code below has been copied from the
174        // SelectFileDialogImpl::RunOpenMultiFileDialog function in
175        // select_file_dialog_win.cc.
176        // TODO(ananta)
177        // Consolidate this into a common place.
178        const wchar_t* selection = result.c_str();
179        std::vector<base::FilePath> files;
180
181        while (*selection) {  // Empty string indicates end of list.
182          files.push_back(base::FilePath(selection));
183          // Skip over filename and null-terminator.
184          selection += files.back().value().length() + 1;
185        }
186        if (files.empty()) {
187          success_ = false;
188        } else if (files.size() == 1) {
189          // When there is one file, it contains the path and filename.
190          filenames_ = files;
191        } else if (files.size() > 1) {
192          // Otherwise, the first string is the path, and the remainder are
193          // filenames.
194          std::vector<base::FilePath>::iterator path = files.begin();
195          for (std::vector<base::FilePath>::iterator file = path + 1;
196               file != files.end(); ++file) {
197            filenames_.push_back(path->Append(*file));
198          }
199        }
200      }
201    } else {
202      LOG(ERROR) << "NULL StorageFileVectorCollection";
203    }
204  } else {
205    LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
206  }
207  app_view_->OnOpenFileCompleted(this, success_);
208  return S_OK;
209}
210
211HRESULT OpenFilePickerSession::StartFilePicker() {
212  mswrw::HStringReference class_name(
213      RuntimeClass_Windows_Storage_Pickers_FileOpenPicker);
214
215  // Create the file picker.
216  mswr::ComPtr<winstorage::Pickers::IFileOpenPicker> picker;
217  HRESULT hr = ::Windows::Foundation::ActivateInstance(
218      class_name.Get(), picker.GetAddressOf());
219  CheckHR(hr);
220
221  // Set the file type filter
222  mswr::ComPtr<winfoundtn::Collections::IVector<HSTRING>> filter;
223  hr = picker->get_FileTypeFilter(filter.GetAddressOf());
224  if (FAILED(hr))
225    return hr;
226
227  if (filter_.empty()) {
228    hr = filter->Append(mswrw::HStringReference(L"*").Get());
229    if (FAILED(hr))
230      return hr;
231  } else {
232    // The filter is a concatenation of zero terminated string pairs,
233    // where each pair is {description, extension}. The concatenation ends
234    // with a zero length string - e.g. a double zero terminator.
235    const wchar_t* walk = filter_.c_str();
236    while (*walk != L'\0') {
237      // Walk past the description.
238      walk += wcslen(walk) + 1;
239
240      // We should have an extension, but bail on malformed filters.
241      if (*walk == L'\0')
242        break;
243
244      // There can be a single extension, or a list of semicolon-separated ones.
245      std::vector<base::string16> extensions_win32_style;
246      size_t extension_count = Tokenize(walk, L";", &extensions_win32_style);
247      DCHECK_EQ(extension_count, extensions_win32_style.size());
248
249      // Metro wants suffixes only, not patterns.
250      mswrw::HString extension;
251      for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
252        if (extensions_win32_style[i] == L"*.*") {
253          // The wildcard filter is "*" for Metro. The string "*.*" produces
254          // an "invalid parameter" error.
255          hr = extension.Set(L"*");
256        } else {
257          // Metro wants suffixes only, not patterns.
258          base::string16 ext =
259              base::FilePath(extensions_win32_style[i]).Extension();
260          if ((ext.size() < 2) ||
261              (ext.find_first_of(L"*?") != base::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, base::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<base::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<base::string16> extensions;
434      for (size_t i = 0; i < extensions_win32_style.size(); ++i) {
435        base::string16 ext =
436            base::FilePath(extensions_win32_style[i]).Extension();
437        if ((ext.size() < 2) ||
438            (ext.find_first_of(L"*?") != base::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<base::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    base::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
522HRESULT 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        base::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 " << static_cast<int>(status);
547  }
548  app_view_->OnSaveFileCompleted(this, success_);
549  return S_OK;
550}
551
552FolderPickerSession::FolderPickerSession(ChromeAppViewAsh* app_view,
553                                         const base::string16& title)
554    : FilePickerSessionBase(app_view, title, L"", base::FilePath()) {}
555
556HRESULT 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
591HRESULT 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        base::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 " << static_cast<int>(status);
616  }
617  app_view_->OnFolderPickerCompleted(this, success_);
618  return S_OK;
619}
620
621