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