bookmarks: Move BookmarkModelObserver into 'bookmarks' namespace.
[chromium-blink-merge.git] / win8 / metro_driver / file_picker_ash.cc
bloba270cb915a5d5fa6602bdefc7724b68113295184
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.
5 #include "stdafx.h"
6 #include "win8/metro_driver/file_picker_ash.h"
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"
19 namespace {
21 typedef winfoundtn::Collections::IVector<HSTRING> StringVectorItf;
23 // TODO(siggi): Complete this implementation and move it to a common place.
24 class StringVectorImpl : public mswr::RuntimeClass<StringVectorItf> {
25 public:
26 ~StringVectorImpl() {
27 std::for_each(strings_.begin(), strings_.end(), ::WindowsDeleteString);
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]));
34 return S_OK;
37 // IVector<HSTRING> implementation.
38 STDMETHOD(GetAt)(unsigned index, HSTRING* item) {
39 if (index >= strings_.size())
40 return E_INVALIDARG;
42 return ::WindowsDuplicateString(strings_[index], item);
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;
50 STDMETHOD(GetView)(winfoundtn::Collections::IVectorView<HSTRING> **view) {
51 return E_NOTIMPL;
53 STDMETHOD(IndexOf)(HSTRING value, unsigned *index, boolean *found) {
54 return E_NOTIMPL;
57 // write methods
58 STDMETHOD(SetAt)(unsigned index, HSTRING item) {
59 return E_NOTIMPL;
61 STDMETHOD(InsertAt)(unsigned index, HSTRING item) {
62 return E_NOTIMPL;
64 STDMETHOD(RemoveAt)(unsigned index) {
65 return E_NOTIMPL;
67 STDMETHOD(Append)(HSTRING item) {
68 return E_NOTIMPL;
70 STDMETHOD(RemoveAtEnd)() {
71 return E_NOTIMPL;
73 STDMETHOD(Clear)() {
74 return E_NOTIMPL;
77 private:
78 std::vector<HSTRING> strings_;
81 } // namespace
83 FilePickerSessionBase::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) {
94 bool FilePickerSessionBase::Run() {
95 if (!DoFilePicker())
96 return false;
97 return success_;
100 bool 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;
108 hr = StartFilePicker();
109 if (FAILED(hr)) {
110 LOG(ERROR) << "Failed to start file picker, error 0x"
111 << std::hex << hr;
112 return false;
114 return true;
117 OpenFilePickerSession::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) {
127 HRESULT OpenFilePickerSession::SinglePickerDone(SingleFileAsyncOp* async,
128 AsyncStatus status) {
129 if (status == Completed) {
130 mswr::ComPtr<winstorage::IStorageFile> file;
131 HRESULT hr = async->GetResults(file.GetAddressOf());
133 if (file) {
134 mswr::ComPtr<winstorage::IStorageItem> storage_item;
135 if (SUCCEEDED(hr))
136 hr = file.As(&storage_item);
138 mswrw::HString file_path;
139 if (SUCCEEDED(hr))
140 hr = storage_item->get_Path(file_path.GetAddressOf());
142 if (SUCCEEDED(hr)) {
143 UINT32 path_len = 0;
144 const wchar_t* path_str =
145 ::WindowsGetStringRawBuffer(file_path.Get(), &path_len);
147 result_ = path_str;
148 success_ = true;
150 } else {
151 LOG(ERROR) << "NULL IStorageItem";
153 } else {
154 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
156 app_view_->OnOpenFileCompleted(this, success_);
157 return S_OK;
160 HRESULT OpenFilePickerSession::MultiPickerDone(MultiFileAsyncOp* async,
161 AsyncStatus status) {
162 if (status == Completed) {
163 mswr::ComPtr<StorageFileVectorCollection> files;
164 HRESULT hr = async->GetResults(files.GetAddressOf());
166 if (files) {
167 base::string16 result;
168 if (SUCCEEDED(hr))
169 hr = ComposeMultiFileResult(files.Get(), &result);
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;
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;
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));
201 } else {
202 LOG(ERROR) << "NULL StorageFileVectorCollection";
204 } else {
205 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
207 app_view_->OnOpenFileCompleted(this, success_);
208 return S_OK;
211 HRESULT OpenFilePickerSession::StartFilePicker() {
212 mswrw::HStringReference class_name(
213 RuntimeClass_Windows_Storage_Pickers_FileOpenPicker);
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);
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;
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;
240 // We should have an extension, but bail on malformed filters.
241 if (*walk == L'\0')
242 break;
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());
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;
264 hr = extension.Set(ext.c_str());
266 if (SUCCEEDED(hr))
267 hr = filter->Append(extension.Get());
268 if (FAILED(hr))
269 return hr;
272 // Walk past the extension.
273 walk += wcslen(walk) + 1;
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;
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());
292 return hr;
293 } else {
294 mswr::ComPtr<SingleFileAsyncOp> completion;
295 hr = picker->PickSingleFileAsync(&completion);
296 if (FAILED(hr))
297 return hr;
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());
307 return hr;
311 HRESULT OpenFilePickerSession::ComposeMultiFileResult(
312 StorageFileVectorCollection* files, base::string16* result) {
313 DCHECK(files != NULL);
314 DCHECK(result != NULL);
316 // Empty the output string.
317 result->clear();
319 unsigned int num_files = 0;
320 HRESULT hr = files->get_Size(&num_files);
321 if (FAILED(hr))
322 return hr;
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;
330 // This stores the base path that should be the parent of all the files.
331 base::FilePath base_path;
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;
340 mswr::ComPtr<winstorage::IStorageItem> storage_item;
341 hr = file.As(&storage_item);
342 if (FAILED(hr))
343 return hr;
345 mswrw::HString file_path_str;
346 hr = storage_item->get_Path(file_path_str.GetAddressOf());
347 if (FAILED(hr))
348 return hr;
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();
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);
359 DCHECK(!result->empty());
360 DCHECK(!base_path.empty());
361 DCHECK(base_path == file_path.DirName());
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);
368 DCHECK(!result->empty());
370 return S_OK;
373 SaveFilePickerSession::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) {
383 int 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_;
391 HRESULT SaveFilePickerSession::StartFilePicker() {
392 mswrw::HStringReference class_name(
393 RuntimeClass_Windows_Storage_Pickers_FileSavePicker);
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);
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;
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;
419 // Walk past the description.
420 walk += wcslen(walk) + 1;
422 // We should have an extension, but bail on malformed filters.
423 if (*walk == L'\0')
424 break;
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());
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);
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;
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);
459 // Walk past the extension(s).
460 walk += wcslen(walk) + 1;
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;
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;
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;
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);
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;
506 mswr::ComPtr<SaveFileAsyncOp> completion;
507 hr = picker->PickSaveFileAsync(&completion);
508 if (FAILED(hr))
509 return hr;
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());
519 return hr;
522 HRESULT SaveFilePickerSession::FilePickerDone(SaveFileAsyncOp* async,
523 AsyncStatus status) {
524 if (status == Completed) {
525 mswr::ComPtr<winstorage::IStorageFile> file;
526 HRESULT hr = async->GetResults(file.GetAddressOf());
528 if (file) {
529 mswr::ComPtr<winstorage::IStorageItem> storage_item;
530 if (SUCCEEDED(hr))
531 hr = file.As(&storage_item);
533 mswrw::HString file_path;
534 if (SUCCEEDED(hr))
535 hr = storage_item->get_Path(file_path.GetAddressOf());
537 if (SUCCEEDED(hr)) {
538 base::string16 path_str = MakeStdWString(file_path.Get());
539 result_ = path_str;
540 success_ = true;
542 } else {
543 LOG(ERROR) << "NULL IStorageItem";
545 } else {
546 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
548 app_view_->OnSaveFileCompleted(this, success_);
549 return S_OK;
552 FolderPickerSession::FolderPickerSession(ChromeAppViewAsh* app_view,
553 const base::string16& title)
554 : FilePickerSessionBase(app_view, title, L"", base::FilePath()) {}
556 HRESULT FolderPickerSession::StartFilePicker() {
557 mswrw::HStringReference class_name(
558 RuntimeClass_Windows_Storage_Pickers_FolderPicker);
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);
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;
572 hr = filter->Append(mswrw::HStringReference(L"*").Get());
573 if (FAILED(hr))
574 return hr;
576 mswr::ComPtr<FolderPickerAsyncOp> completion;
577 hr = picker->PickSingleFolderAsync(&completion);
578 if (FAILED(hr))
579 return hr;
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;
591 HRESULT FolderPickerSession::FolderPickerDone(FolderPickerAsyncOp* async,
592 AsyncStatus status) {
593 if (status == Completed) {
594 mswr::ComPtr<winstorage::IStorageFolder> folder;
595 HRESULT hr = async->GetResults(folder.GetAddressOf());
597 if (folder) {
598 mswr::ComPtr<winstorage::IStorageItem> storage_item;
599 if (SUCCEEDED(hr))
600 hr = folder.As(&storage_item);
602 mswrw::HString file_path;
603 if (SUCCEEDED(hr))
604 hr = storage_item->get_Path(file_path.GetAddressOf());
606 if (SUCCEEDED(hr)) {
607 base::string16 path_str = MakeStdWString(file_path.Get());
608 result_ = path_str;
609 success_ = true;
611 } else {
612 LOG(ERROR) << "NULL IStorageItem";
614 } else {
615 LOG(ERROR) << "Unexpected async status " << static_cast<int>(status);
617 app_view_->OnFolderPickerCompleted(this, success_);
618 return S_OK;