// Copyright (c) 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "stdafx.h" #include "win8/metro_driver/file_picker_ash.h" #include "base/bind.h" #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/synchronization/waitable_event.h" #include "base/win/metro.h" #include "base/win/scoped_comptr.h" #include "ui/metro_viewer/metro_viewer_messages.h" #include "win8/metro_driver/chrome_app_view_ash.h" #include "win8/metro_driver/winrt_utils.h" namespace { typedef winfoundtn::Collections::IVector StringVectorItf; // TODO(siggi): Complete this implementation and move it to a common place. class StringVectorImpl : public mswr::RuntimeClass { public: ~StringVectorImpl() override { std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString); } HRESULT RuntimeClassInitialize(const std::vector& list) { for (size_t i = 0; i < list.size(); ++i) strings_.push_back(MakeHString(list[i])); return S_OK; } // IVector implementation. STDMETHOD(GetAt)(unsigned index, HSTRING* item) override { if (index >= strings_.size()) return E_INVALIDARG; return ::WindowsDuplicateString(strings_[index], item); } STDMETHOD(get_Size)(unsigned* size) override { if (strings_.size() > UINT_MAX) return E_UNEXPECTED; *size = static_cast(strings_.size()); return S_OK; } STDMETHOD(GetView)( winfoundtn::Collections::IVectorView** view) override { return E_NOTIMPL; } STDMETHOD(IndexOf)(HSTRING value, unsigned* index, boolean* found) override { return E_NOTIMPL; } // write methods STDMETHOD(SetAt)(unsigned index, HSTRING item) override { return E_NOTIMPL; } STDMETHOD(InsertAt)(unsigned index, HSTRING item) override { return E_NOTIMPL; } STDMETHOD(RemoveAt)(unsigned index) override { return E_NOTIMPL; } STDMETHOD(Append)(HSTRING item) override { return E_NOTIMPL; } STDMETHOD(RemoveAtEnd)() override { return E_NOTIMPL; } STDMETHOD(Clear)() override { return E_NOTIMPL; } private: std::vector strings_; }; } // namespace FilePickerSessionBase::~FilePickerSessionBase() { } bool FilePickerSessionBase::Run() { if (!DoFilePicker()) return false; return success_; } FilePickerSessionBase::FilePickerSessionBase(ChromeAppViewAsh* app_view, const base::string16& title, const base::string16& filter, const base::FilePath& default_path) : success_(false), title_(title), filter_(filter), default_path_(default_path), app_view_(app_view) { } bool FilePickerSessionBase::DoFilePicker() { // The file picker will fail if spawned from a snapped application, // so let's attempt to unsnap first if we're in that state. HRESULT hr = ChromeAppViewAsh::Unsnap(); if (FAILED(hr)) { LOG(ERROR) << "Failed to unsnap for file picker, error 0x" << hr; return false; } hr = StartFilePicker(); if (FAILED(hr)) { LOG(ERROR) << "Failed to start file picker, error 0x" << std::hex << hr; return false; } return true; } OpenFilePickerSession::OpenFilePickerSession( ChromeAppViewAsh* app_view, const base::string16& title, const base::string16& filter, const base::FilePath& default_path, bool allow_multi_select) : FilePickerSessionBase(app_view, title, filter, default_path), allow_multi_select_(allow_multi_select) { } OpenFilePickerSession::~OpenFilePickerSession() { } HRESULT OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp* async, AsyncStatus status) { if (status == Completed) { mswr::ComPtr file; HRESULT hr = async->GetResults(file.GetAddressOf()); if (file) { mswr::ComPtr storage_item; if (SUCCEEDED(hr)) hr = file.As(&storage_item); mswrw::HString file_path; if (SUCCEEDED(hr)) hr = storage_item->get_Path(file_path.GetAddressOf()); if (SUCCEEDED(hr)) { UINT32 path_len = 0; const wchar_t* path_str = ::WindowsGetStringRawBuffer(file_path.Get(), &path_len); result_ = path_str; success_ = true; } } else { LOG(ERROR) << "NULL IStorageItem"; } } else { LOG(ERROR) << "Unexpected async status " << static_cast(status); } app_view_->OnOpenFileCompleted(this, success_); return S_OK; } HRESULT OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp* async, AsyncStatus status) { if (status == Completed) { mswr::ComPtr files; HRESULT hr = async->GetResults(files.GetAddressOf()); if (files) { base::string16 result; if (SUCCEEDED(hr)) hr = ComposeMultiFileResult(files.Get(), &result); if (SUCCEEDED(hr)) { success_ = true; // The code below has been copied from the // SelectFileDialogImpl::RunOpenMultiFileDialog function in // select_file_dialog_win.cc. // TODO(ananta) // Consolidate this into a common place. const wchar_t* selection = result.c_str(); std::vector files; while (*selection) { // Empty string indicates end of list. files.push_back(base::FilePath(selection)); // Skip over filename and null-terminator. selection += files.back().value().length() + 1; } if (files.empty()) { success_ = false; } else if (files.size() == 1) { // When there is one file, it contains the path and filename. filenames_ = files; } else if (files.size() > 1) { // Otherwise, the first string is the path, and the remainder are // filenames. std::vector::iterator path = files.begin(); for (std::vector::iterator file = path + 1; file != files.end(); ++file) { filenames_.push_back(path->Append(*file)); } } } } else { LOG(ERROR) << "NULL StorageFileVectorCollection"; } } else { LOG(ERROR) << "Unexpected async status " << static_cast(status); } app_view_->OnOpenFileCompleted(this, success_); return S_OK; } HRESULT OpenFilePickerSession::StartFilePicker() { mswrw::HStringReference class_name( RuntimeClass_Windows_Storage_Pickers_FileOpenPicker); // Create the file picker. mswr::ComPtr picker; HRESULT hr = ::Windows::Foundation::ActivateInstance( class_name.Get(), picker.GetAddressOf()); CheckHR(hr); // Set the file type filter mswr::ComPtr> filter; hr = picker->get_FileTypeFilter(filter.GetAddressOf()); if (FAILED(hr)) return hr; if (filter_.empty()) { hr = filter->Append(mswrw::HStringReference(L"*").Get()); if (FAILED(hr)) return hr; } else { // The filter is a concatenation of zero terminated string pairs, // where each pair is {description, extension}. The concatenation ends // with a zero length string - e.g. a double zero terminator. const wchar_t* walk = filter_.c_str(); while (*walk != L'\0') { // Walk past the description. walk += wcslen(walk) + 1; // We should have an extension, but bail on malformed filters. if (*walk == L'\0') break; // There can be a single extension, or a list of semicolon-separated ones. std::vector extensions_win32_style = base::SplitString( walk, L";", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); // Metro wants suffixes only, not patterns. mswrw::HString extension; for (size_t i = 0; i < extensions_win32_style.size(); ++i) { if (extensions_win32_style[i] == L"*.*") { // The wildcard filter is "*" for Metro. The string "*.*" produces // an "invalid parameter" error. hr = extension.Set(L"*"); } else { // Metro wants suffixes only, not patterns. base::string16 ext = base::FilePath(extensions_win32_style[i]).Extension(); if ((ext.size() < 2) || (ext.find_first_of(L"*?") != base::string16::npos)) { continue; } hr = extension.Set(ext.c_str()); } if (SUCCEEDED(hr)) hr = filter->Append(extension.Get()); if (FAILED(hr)) return hr; } // Walk past the extension. walk += wcslen(walk) + 1; } } // Spin up a single or multi picker as appropriate. if (allow_multi_select_) { mswr::ComPtr completion; hr = picker->PickMultipleFilesAsync(&completion); if (FAILED(hr)) return hr; // Create the callback method. typedef winfoundtn::IAsyncOperationCompletedHandler< StorageFileVectorCollection*> HandlerDoneType; mswr::ComPtr handler(mswr::Callback( this, &OpenFilePickerSession::MultiPickerDone)); DCHECK(handler.Get() != NULL); hr = completion->put_Completed(handler.Get()); return hr; } else { mswr::ComPtr completion; hr = picker->PickSingleFileAsync(&completion); if (FAILED(hr)) return hr; // Create the callback method. typedef winfoundtn::IAsyncOperationCompletedHandler< winstorage::StorageFile*> HandlerDoneType; mswr::ComPtr handler(mswr::Callback( this, &OpenFilePickerSession::SinglePickerDone)); DCHECK(handler.Get() != NULL); hr = completion->put_Completed(handler.Get()); return hr; } } HRESULT OpenFilePickerSession::ComposeMultiFileResult( StorageFileVectorCollection* files, base::string16* result) { DCHECK(files != NULL); DCHECK(result != NULL); // Empty the output string. result->clear(); unsigned int num_files = 0; HRESULT hr = files->get_Size(&num_files); if (FAILED(hr)) return hr; // Make sure we return an error on an empty collection. if (num_files == 0) { DLOG(ERROR) << "Empty collection on input."; return E_UNEXPECTED; } // This stores the base path that should be the parent of all the files. base::FilePath base_path; // Iterate through the collection and append the file paths to the result. for (unsigned int i = 0; i < num_files; ++i) { mswr::ComPtr file; hr = files->GetAt(i, file.GetAddressOf()); if (FAILED(hr)) return hr; mswr::ComPtr storage_item; hr = file.As(&storage_item); if (FAILED(hr)) return hr; mswrw::HString file_path_str; hr = storage_item->get_Path(file_path_str.GetAddressOf()); if (FAILED(hr)) return hr; base::FilePath file_path(MakeStdWString(file_path_str.Get())); if (base_path.empty()) { DCHECK(result->empty()); base_path = file_path.DirName(); // Append the path, including the terminating zero. // We do this only for the first file. result->append(base_path.value().c_str(), base_path.value().size() + 1); } DCHECK(!result->empty()); DCHECK(!base_path.empty()); DCHECK(base_path == file_path.DirName()); // Append the base name, including the terminating zero. base::FilePath base_name = file_path.BaseName(); result->append(base_name.value().c_str(), base_name.value().size() + 1); } DCHECK(!result->empty()); return S_OK; } SaveFilePickerSession::SaveFilePickerSession( ChromeAppViewAsh* app_view, const MetroViewerHostMsg_SaveAsDialogParams& params) : FilePickerSessionBase(app_view, params.title, params.filter, params.suggested_name), filter_index_(params.filter_index) { } int SaveFilePickerSession::filter_index() const { // TODO(ananta) // Add support for returning the correct filter index. This does not work in // regular Chrome metro on trunk as well. // BUG: https://code.google.com/p/chromium/issues/detail?id=172704 return filter_index_; } HRESULT SaveFilePickerSession::StartFilePicker() { mswrw::HStringReference class_name( RuntimeClass_Windows_Storage_Pickers_FileSavePicker); // Create the file picker. mswr::ComPtr picker; HRESULT hr = ::Windows::Foundation::ActivateInstance( class_name.Get(), picker.GetAddressOf()); CheckHR(hr); typedef winfoundtn::Collections::IMap StringVectorMap; mswr::ComPtr choices; hr = picker->get_FileTypeChoices(choices.GetAddressOf()); if (FAILED(hr)) return hr; if (!filter_.empty()) { // The filter is a concatenation of zero terminated string pairs, // where each pair is {description, extension list}. The concatenation ends // with a zero length string - e.g. a double zero terminator. const wchar_t* walk = filter_.c_str(); while (*walk != L'\0') { mswrw::HString description; hr = description.Set(walk); if (FAILED(hr)) return hr; // Walk past the description. walk += wcslen(walk) + 1; // We should have an extension, but bail on malformed filters. if (*walk == L'\0') break; // There can be a single extension, or a list of semicolon-separated ones. std::vector extensions_win32_style = base::SplitString( walk, L";", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); // Metro wants suffixes only, not patterns. Also, metro does not support // the all files ("*") pattern in the save picker. std::vector extensions; for (size_t i = 0; i < extensions_win32_style.size(); ++i) { base::string16 ext = base::FilePath(extensions_win32_style[i]).Extension(); if ((ext.size() < 2) || (ext.find_first_of(L"*?") != base::string16::npos)) continue; extensions.push_back(ext); } if (!extensions.empty()) { // Convert to a Metro collection class. mswr::ComPtr list; hr = mswr::MakeAndInitialize( list.GetAddressOf(), extensions); if (FAILED(hr)) return hr; // Finally set the filter. boolean replaced = FALSE; hr = choices->Insert(description.Get(), list.Get(), &replaced); if (FAILED(hr)) return hr; DCHECK_EQ(FALSE, replaced); } // Walk past the extension(s). walk += wcslen(walk) + 1; } } // The save picker requires at least one choice. Callers are strongly advised // to provide sensible choices. If none were given, fallback to .dat. uint32 num_choices = 0; hr = choices->get_Size(&num_choices); if (FAILED(hr)) return hr; if (num_choices == 0) { mswrw::HString description; // TODO(grt): Get a properly translated string. This can't be done from // within metro_driver. Consider preprocessing the filter list in Chrome // land to ensure it has this entry if all others are patterns. In that // case, this whole block of code can be removed. hr = description.Set(L"Data File"); if (FAILED(hr)) return hr; mswr::ComPtr list; hr = mswr::MakeAndInitialize( list.GetAddressOf(), std::vector(1, L".dat")); if (FAILED(hr)) return hr; boolean replaced = FALSE; hr = choices->Insert(description.Get(), list.Get(), &replaced); if (FAILED(hr)) return hr; DCHECK_EQ(FALSE, replaced); } if (!default_path_.empty()) { base::string16 file_part = default_path_.BaseName().value(); // If the suggested_name is a root directory, then don't set it as the // suggested name. if (file_part.size() == 1 && file_part[0] == L'\\') file_part.clear(); hr = picker->put_SuggestedFileName( mswrw::HStringReference(file_part.c_str()).Get()); if (FAILED(hr)) return hr; } mswr::ComPtr completion; hr = picker->PickSaveFileAsync(&completion); if (FAILED(hr)) return hr; // Create the callback method. typedef winfoundtn::IAsyncOperationCompletedHandler< winstorage::StorageFile*> HandlerDoneType; mswr::ComPtr handler(mswr::Callback( this, &SaveFilePickerSession::FilePickerDone)); DCHECK(handler.Get() != NULL); hr = completion->put_Completed(handler.Get()); return hr; } HRESULT SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp* async, AsyncStatus status) { if (status == Completed) { mswr::ComPtr file; HRESULT hr = async->GetResults(file.GetAddressOf()); if (file) { mswr::ComPtr storage_item; if (SUCCEEDED(hr)) hr = file.As(&storage_item); mswrw::HString file_path; if (SUCCEEDED(hr)) hr = storage_item->get_Path(file_path.GetAddressOf()); if (SUCCEEDED(hr)) { base::string16 path_str = MakeStdWString(file_path.Get()); result_ = path_str; success_ = true; } } else { LOG(ERROR) << "NULL IStorageItem"; } } else { LOG(ERROR) << "Unexpected async status " << static_cast(status); } app_view_->OnSaveFileCompleted(this, success_); return S_OK; } FolderPickerSession::FolderPickerSession(ChromeAppViewAsh* app_view, const base::string16& title) : FilePickerSessionBase(app_view, title, L"", base::FilePath()) { } HRESULT FolderPickerSession::StartFilePicker() { mswrw::HStringReference class_name( RuntimeClass_Windows_Storage_Pickers_FolderPicker); // Create the folder picker. mswr::ComPtr picker; HRESULT hr = ::Windows::Foundation::ActivateInstance( class_name.Get(), picker.GetAddressOf()); CheckHR(hr); // Set the file type filter mswr::ComPtr> filter; hr = picker->get_FileTypeFilter(filter.GetAddressOf()); if (FAILED(hr)) return hr; hr = filter->Append(mswrw::HStringReference(L"*").Get()); if (FAILED(hr)) return hr; mswr::ComPtr completion; hr = picker->PickSingleFolderAsync(&completion); if (FAILED(hr)) return hr; // Create the callback method. typedef winfoundtn::IAsyncOperationCompletedHandler< winstorage::StorageFolder*> HandlerDoneType; mswr::ComPtr handler(mswr::Callback( this, &FolderPickerSession::FolderPickerDone)); DCHECK(handler.Get() != NULL); hr = completion->put_Completed(handler.Get()); return hr; } HRESULT FolderPickerSession::FolderPickerDone(FolderPickerAsyncOp* async, AsyncStatus status) { if (status == Completed) { mswr::ComPtr folder; HRESULT hr = async->GetResults(folder.GetAddressOf()); if (folder) { mswr::ComPtr storage_item; if (SUCCEEDED(hr)) hr = folder.As(&storage_item); mswrw::HString file_path; if (SUCCEEDED(hr)) hr = storage_item->get_Path(file_path.GetAddressOf()); if (SUCCEEDED(hr)) { base::string16 path_str = MakeStdWString(file_path.Get()); result_ = path_str; success_ = true; } } else { LOG(ERROR) << "NULL IStorageItem"; } } else { LOG(ERROR) << "Unexpected async status " << static_cast(status); } app_view_->OnFolderPickerCompleted(this, success_); return S_OK; }