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