diff options
author | sammc@chromium.org <sammc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-02 11:25:57 +0000 |
---|---|---|
committer | sammc@chromium.org <sammc@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-02 11:25:57 +0000 |
commit | fb2211c6b85f4213d31cb75600d80c4a5f11605a (patch) | |
tree | b5030b9677bd80d736d806be48f93d44ff67aff1 /chrome/browser/extensions/api/file_system | |
parent | 030562a7cec40fbd5deb76c4e4f9d37dc3e91ff9 (diff) | |
download | chromium_src-fb2211c6b85f4213d31cb75600d80c4a5f11605a.zip chromium_src-fb2211c6b85f4213d31cb75600d80c4a5f11605a.tar.gz chromium_src-fb2211c6b85f4213d31cb75600d80c4a5f11605a.tar.bz2 |
Support choosing multiple files with fileSystem.chooseEntry.
This adds acceptMultiple to ChooseEntryOptions, which when set to true,
presents the user with a file chooser dialog that allows the selection
of multiple files.
BUG=159062
Review URL: https://chromiumcodereview.appspot.com/18331017
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@215269 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/extensions/api/file_system')
3 files changed, 356 insertions, 118 deletions
diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc index c02d5e01..e347fcf 100644 --- a/chrome/browser/extensions/api/file_system/file_system_api.cc +++ b/chrome/browser/extensions/api/file_system/file_system_api.cc @@ -12,6 +12,7 @@ #include "base/logging.h" #include "base/path_service.h" #include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/value_conversions.h" @@ -60,10 +61,13 @@ const char kSecurityError[] = "Security error"; const char kInvalidCallingPage[] = "Invalid calling page. This function can't " "be called from a background page."; const char kUserCancelled[] = "User cancelled"; -const char kWritableFileError[] = +const char kWritableFileRestrictedLocationError[] = "Cannot write to file in a restricted location"; +const char kWritableFileErrorFormat[] = "Error opening %s"; const char kRequiresFileSystemWriteError[] = "Operation requires fileSystem.write permission"; +const char kMultipleUnsupportedError[] = + "acceptsMultiple: true is not supported for 'saveFile'"; const char kUnknownIdError[] = "Unknown id"; namespace file_system = extensions::api::file_system; @@ -156,6 +160,7 @@ base::FilePath PrettifyPath(const base::FilePath& source_path) { bool g_skip_picker_for_test = false; bool g_use_suggested_path_for_test = false; base::FilePath* g_path_to_be_picked_for_test; +std::vector<base::FilePath>* g_paths_to_be_picked_for_test; bool GetFileSystemAndPathOfFileEntry( const std::string& filesystem_name, @@ -210,13 +215,19 @@ bool GetFilePathOfFileEntry(const std::string& filesystem_name, } bool DoCheckWritableFile(const base::FilePath& path, - const base::FilePath& extension_directory) { + const base::FilePath& extension_directory, + std::string* error_message) { // Don't allow links. - if (base::PathExists(path) && file_util::IsLink(path)) + if (base::PathExists(path) && file_util::IsLink(path)) { + *error_message = base::StringPrintf(kWritableFileErrorFormat, + path.BaseName().AsUTF8Unsafe().c_str()); return false; + } - if (extension_directory == path || extension_directory.IsParent(path)) + if (extension_directory == path || extension_directory.IsParent(path)) { + *error_message = kWritableFileRestrictedLocationError; return false; + } bool is_whitelisted_path = false; @@ -236,6 +247,7 @@ bool DoCheckWritableFile(const base::FilePath& path, base::FilePath blacklisted_path; if (PathService::Get(kBlacklistedPaths[i], &blacklisted_path) && (blacklisted_path == path || blacklisted_path.IsParent(path))) { + *error_message = kWritableFileRestrictedLocationError; return false; } } @@ -251,29 +263,120 @@ bool DoCheckWritableFile(const base::FilePath& path, // Close the file so we don't keep a lock open. if (file != base::kInvalidPlatformFileValue) base::ClosePlatformFile(file); - return error == base::PLATFORM_FILE_OK || - error == base::PLATFORM_FILE_ERROR_EXISTS; -} + if (error != base::PLATFORM_FILE_OK && + error != base::PLATFORM_FILE_ERROR_EXISTS) { + *error_message = base::StringPrintf(kWritableFileErrorFormat, + path.BaseName().AsUTF8Unsafe().c_str()); + return false; + } -void CheckLocalWritableFile(const base::FilePath& path, - const base::FilePath& extension_directory, - const base::Closure& on_success, - const base::Closure& on_failure) { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, - DoCheckWritableFile(path, extension_directory) ? on_success : on_failure); + return true; } +// Checks whether a list of paths are all OK for writing and calls a provided +// on_success or on_failure callback when done. A file is OK for writing if it +// is not a symlink, is not in a blacklisted path and can be opened for writing; +// files are created if they do not exist. +class WritableFileChecker + : public base::RefCountedThreadSafe<WritableFileChecker> { + public: + WritableFileChecker( + const std::vector<base::FilePath>& paths, + Profile* profile, + const base::FilePath& extension_path, + const base::Closure& on_success, + const base::Callback<void(const std::string&)>& on_failure) + : outstanding_tasks_(1), + extension_path_(extension_path), + on_success_(on_success), + on_failure_(on_failure) { #if defined(OS_CHROMEOS) -void CheckRemoteWritableFile(const base::Closure& on_success, - const base::Closure& on_failure, - drive::FileError error, - const base::FilePath& path) { - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, - error == drive::FILE_ERROR_OK ? on_success : on_failure); -} + if (drive::util::IsUnderDriveMountPoint(paths[0])) { + outstanding_tasks_ = paths.size(); + for (std::vector<base::FilePath>::const_iterator it = paths.begin(); + it != paths.end(); ++it) { + DCHECK(drive::util::IsUnderDriveMountPoint(*it)); + drive::util::PrepareWritableFileAndRun( + profile, + *it, + base::Bind(&WritableFileChecker::CheckRemoteWritableFile, this)); + } + return; + } +#endif + content::BrowserThread::PostTask( + content::BrowserThread::FILE, + FROM_HERE, + base::Bind(&WritableFileChecker::CheckLocalWritableFiles, this, paths)); + } + + private: + friend class base::RefCountedThreadSafe<WritableFileChecker>; + virtual ~WritableFileChecker() {} + + // Called when a work item is completed. If all work items are done, this + // posts a task to run AllTasksDone on the UI thread. + void TaskDone() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); + if (--outstanding_tasks_ == 0) { + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind(&WritableFileChecker::AllTasksDone, this)); + } + } + + // Called on the UI thread when all tasks are done. + void AllTasksDone() { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + if (error_.empty()) + on_success_.Run(); + else + on_failure_.Run(error_); + } + + // Reports an error in completing a work item. This may be called more than + // once, but only the last message will be retained. + void Error(const std::string& message) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); + DCHECK(!message.empty()); + error_ = message; + TaskDone(); + } + + void CheckLocalWritableFiles(const std::vector<base::FilePath>& paths) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); + std::string error; + for (std::vector<base::FilePath>::const_iterator it = paths.begin(); + it != paths.end(); ++it) { + if (!DoCheckWritableFile(*it, extension_path_, &error)) { + Error(error); + return; + } + } + TaskDone(); + } + +#if defined(OS_CHROMEOS) + void CheckRemoteWritableFile(drive::FileError error, + const base::FilePath& path) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); + if (error == drive::FILE_ERROR_OK) { + TaskDone(); + } else { + Error(base::StringPrintf(kWritableFileErrorFormat, + path.BaseName().AsUTF8Unsafe().c_str())); + } + } #endif + int outstanding_tasks_; + const base::FilePath extension_path_; + std::string error_; + base::Closure on_success_; + base::Callback<void(const std::string&)> on_failure_; +}; + // Expand the mime-types and extensions provided in an AcceptOption, returning // them within the passed extension vector. Returns false if no valid types // were found. @@ -387,6 +490,11 @@ bool FileSystemGetDisplayPathFunction::RunImpl() { return true; } +FileSystemEntryFunction::FileSystemEntryFunction() + : multiple_(false), + entry_type_(READ_ONLY), + response_(NULL) {} + bool FileSystemEntryFunction::HasFileSystemWritePermission() { const extensions::Extension* extension = GetExtension(); if (!extension) @@ -395,60 +503,68 @@ bool FileSystemEntryFunction::HasFileSystemWritePermission() { return extension->HasAPIPermission(APIPermission::kFileSystemWrite); } -void FileSystemEntryFunction::CheckWritableFile(const base::FilePath& path) { +void FileSystemEntryFunction::CheckWritableFiles( + const std::vector<base::FilePath>& paths) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); - base::Closure on_success = - base::Bind(&FileSystemEntryFunction::RegisterFileSystemAndSendResponse, - this, path, WRITABLE); - base::Closure on_failure = - base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this); + scoped_refptr<WritableFileChecker> helper = new WritableFileChecker( + paths, profile_, extension_->path(), + base::Bind( + &FileSystemEntryFunction::RegisterFileSystemsAndSendResponse, + this, paths), + base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this)); +} -#if defined(OS_CHROMEOS) - if (drive::util::IsUnderDriveMountPoint(path)) { - drive::util::PrepareWritableFileAndRun(profile_, path, - base::Bind(&CheckRemoteWritableFile, on_success, on_failure)); - return; +void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse( + const std::vector<base::FilePath>& paths) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + CreateResponse(); + for (std::vector<base::FilePath>::const_iterator it = paths.begin(); + it != paths.end(); ++it) { + AddEntryToResponse(*it, ""); } -#endif - content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE, - base::Bind(&CheckLocalWritableFile, path, extension_->path(), on_success, - on_failure)); + SendResponse(true); } -void FileSystemEntryFunction::RegisterFileSystemAndSendResponse( - const base::FilePath& path, EntryType entry_type) { - RegisterFileSystemAndSendResponseWithIdOverride(path, entry_type, ""); +void FileSystemEntryFunction::CreateResponse() { + DCHECK(!response_); + response_ = new base::DictionaryValue(); + base::ListValue* list = new base::ListValue(); + response_->Set("entries", list); + response_->SetBoolean("multiple", multiple_); + SetResult(response_); } -void FileSystemEntryFunction::RegisterFileSystemAndSendResponseWithIdOverride( - const base::FilePath& path, EntryType entry_type, const std::string& id) { - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); - - fileapi::IsolatedContext* isolated_context = - fileapi::IsolatedContext::GetInstance(); - DCHECK(isolated_context); - - bool writable = entry_type == WRITABLE; +void FileSystemEntryFunction::AddEntryToResponse( + const base::FilePath& path, + const std::string& id_override) { + DCHECK(response_); + bool writable = entry_type_ == WRITABLE; extensions::app_file_handler_util::GrantedFileEntry file_entry = - extensions::app_file_handler_util::CreateFileEntry(profile(), - GetExtension()->id(), render_view_host_->GetProcess()->GetID(), path, + extensions::app_file_handler_util::CreateFileEntry( + profile(), + GetExtension()->id(), + render_view_host_->GetProcess()->GetID(), + path, writable); - - base::DictionaryValue* dict = new base::DictionaryValue(); - SetResult(dict); - dict->SetString("fileSystemId", file_entry.filesystem_id); - dict->SetString("baseName", file_entry.registered_name); - if (id.empty()) - dict->SetString("id", file_entry.id); + base::ListValue* entries; + bool success = response_->GetList("entries", &entries); + DCHECK(success); + + base::DictionaryValue* entry = new base::DictionaryValue(); + entry->SetString("fileSystemId", file_entry.filesystem_id); + entry->SetString("baseName", file_entry.registered_name); + if (id_override.empty()) + entry->SetString("id", file_entry.id); else - dict->SetString("id", id); - - SendResponse(true); + entry->SetString("id", id_override); + entries->Append(entry); } -void FileSystemEntryFunction::HandleWritableFileError() { +void FileSystemEntryFunction::HandleWritableFileError( + const std::string& error) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); - error_ = kWritableFileError; + error_ = error; SendResponse(false); } @@ -462,13 +578,16 @@ bool FileSystemGetWritableEntryFunction::RunImpl() { error_ = kRequiresFileSystemWriteError; return false; } + entry_type_ = WRITABLE; base::FilePath path; if (!GetFilePathOfFileEntry(filesystem_name, filesystem_path, render_view_host_, &path, &error_)) return false; - CheckWritableFile(path); + std::vector<base::FilePath> paths; + paths.push_back(path); + CheckWritableFiles(paths); return true; } @@ -503,10 +622,8 @@ class FileSystemChooseEntryFunction::FilePicker content::WebContents* web_contents, const base::FilePath& suggested_name, const ui::SelectFileDialog::FileTypeInfo& file_type_info, - ui::SelectFileDialog::Type picker_type, - EntryType entry_type) - : entry_type_(entry_type), - function_(function) { + ui::SelectFileDialog::Type picker_type) + : function_(function) { select_file_dialog_ = ui::SelectFileDialog::Create( this, new ChromeSelectFilePolicy(web_contents)); gfx::NativeWindow owning_window = web_contents ? @@ -521,11 +638,21 @@ class FileSystemChooseEntryFunction::FilePicker base::Unretained(this), suggested_name, 1, static_cast<void*>(NULL))); } else if (g_path_to_be_picked_for_test) { - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, base::Bind( &FileSystemChooseEntryFunction::FilePicker::FileSelected, base::Unretained(this), *g_path_to_be_picked_for_test, 1, static_cast<void*>(NULL))); + } else if (g_paths_to_be_picked_for_test) { + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind( + &FileSystemChooseEntryFunction::FilePicker::MultiFilesSelected, + base::Unretained(this), + *g_paths_to_be_picked_for_test, + static_cast<void*>(NULL))); } else { content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, base::Bind( @@ -553,8 +680,9 @@ class FileSystemChooseEntryFunction::FilePicker virtual void FileSelected(const base::FilePath& path, int index, void* params) OVERRIDE { - function_->FileSelected(path, entry_type_); - delete this; + std::vector<base::FilePath> paths; + paths.push_back(path); + MultiFilesSelected(paths, params); } virtual void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file, @@ -568,17 +696,31 @@ class FileSystemChooseEntryFunction::FilePicker // // TODO(kinaba): remove this, once after the file picker implements proper // switch of the path treatment depending on the |support_drive| flag. - function_->FileSelected(file.file_path, entry_type_); + FileSelected(file.file_path, index, params); + } + + virtual void MultiFilesSelected(const std::vector<base::FilePath>& files, + void* params) OVERRIDE { + function_->FilesSelected(files); delete this; } + virtual void MultiFilesSelectedWithExtraInfo( + const std::vector<ui::SelectedFileInfo>& files, + void* params) OVERRIDE { + std::vector<base::FilePath> paths; + for (std::vector<ui::SelectedFileInfo>::const_iterator it = files.begin(); + it != files.end(); ++it) { + paths.push_back(it->file_path); + } + MultiFilesSelected(paths, params); + } + virtual void FileSelectionCanceled(void* params) OVERRIDE { function_->FileSelectionCanceled(); delete this; } - EntryType entry_type_; - scoped_refptr<ui::SelectFileDialog> select_file_dialog_; scoped_refptr<FileSystemChooseEntryFunction> function_; @@ -587,8 +729,7 @@ class FileSystemChooseEntryFunction::FilePicker void FileSystemChooseEntryFunction::ShowPicker( const ui::SelectFileDialog::FileTypeInfo& file_type_info, - ui::SelectFileDialog::Type picker_type, - EntryType entry_type) { + ui::SelectFileDialog::Type picker_type) { // TODO(asargent/benwells) - As a short term remediation for crbug.com/179010 // we're adding the ability for a whitelisted extension to use this API since // chrome.fileBrowserHandler.selectFile is ChromeOS-only. Eventually we'd @@ -613,8 +754,8 @@ void FileSystemChooseEntryFunction::ShowPicker( // its destruction (and subsequent sending of the function response) until the // user has selected a file or cancelled the picker. At that point, the picker // will delete itself, which will also free the function instance. - new FilePicker(this, web_contents, initial_path_, file_type_info, - picker_type, entry_type); + new FilePicker( + this, web_contents, initial_path_, file_type_info, picker_type); } // static @@ -623,6 +764,14 @@ void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest( g_skip_picker_for_test = true; g_use_suggested_path_for_test = false; g_path_to_be_picked_for_test = path; + g_paths_to_be_picked_for_test = NULL; +} + +void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest( + std::vector<base::FilePath>* paths) { + g_skip_picker_for_test = true; + g_use_suggested_path_for_test = false; + g_paths_to_be_picked_for_test = paths; } // static @@ -630,6 +779,7 @@ void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() { g_skip_picker_for_test = true; g_use_suggested_path_for_test = true; g_path_to_be_picked_for_test = NULL; + g_paths_to_be_picked_for_test = NULL; } // static @@ -637,6 +787,7 @@ void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() { g_skip_picker_for_test = true; g_use_suggested_path_for_test = false; g_path_to_be_picked_for_test = NULL; + g_paths_to_be_picked_for_test = NULL; } // static @@ -670,19 +821,18 @@ void FileSystemChooseEntryFunction::SetInitialPathOnFileThread( } } -void FileSystemChooseEntryFunction::FileSelected(const base::FilePath& path, - EntryType entry_type) { +void FileSystemChooseEntryFunction::FilesSelected( + const std::vector<base::FilePath>& paths) { + DCHECK(!paths.empty()); file_system_api::SetLastChooseEntryDirectory( - ExtensionPrefs::Get(profile()), - GetExtension()->id(), - path.DirName()); - if (entry_type == WRITABLE) { - CheckWritableFile(path); + ExtensionPrefs::Get(profile()), GetExtension()->id(), paths[0].DirName()); + if (entry_type_ == WRITABLE) { + CheckWritableFiles(paths); return; } // Don't need to check the file, it's for reading. - RegisterFileSystemAndSendResponse(path, READ_ONLY); + RegisterFileSystemsAndSendResponse(paths); } void FileSystemChooseEntryFunction::FileSelectionCanceled() { @@ -756,16 +906,22 @@ bool FileSystemChooseEntryFunction::RunImpl() { base::FilePath suggested_name; ui::SelectFileDialog::FileTypeInfo file_type_info; - EntryType entry_type = READ_ONLY; ui::SelectFileDialog::Type picker_type = ui::SelectFileDialog::SELECT_OPEN_FILE; file_system::ChooseEntryOptions* options = params->options.get(); if (options) { + multiple_ = options->accepts_multiple; + if (multiple_) + picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE; if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE) { - entry_type = WRITABLE; + entry_type_ = WRITABLE; } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) { - entry_type = WRITABLE; + if (multiple_) { + error_ = kMultipleUnsupportedError; + return false; + } + entry_type_ = WRITABLE; picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE; } @@ -777,7 +933,7 @@ bool FileSystemChooseEntryFunction::RunImpl() { options->accepts.get(), options->accepts_all_types.get()); } - if (entry_type == WRITABLE && !HasFileSystemWritePermission()) { + if (entry_type_ == WRITABLE && !HasFileSystemWritePermission()) { error_ = kRequiresFileSystemWriteError; return false; } @@ -798,7 +954,7 @@ bool FileSystemChooseEntryFunction::RunImpl() { suggested_name, previous_path), base::Bind( &FileSystemChooseEntryFunction::ShowPicker, this, file_type_info, - picker_type, entry_type)); + picker_type)); return true; } @@ -868,13 +1024,11 @@ bool FileSystemRestoreEntryFunction::RunImpl() { // |needs_new_entry| will be false if the renderer already has an Entry for // |entry_id|. if (needs_new_entry) { - // Reuse the ID of the retained file entry so retainEntry returns the same - // ID that was passed to restoreEntry. - RegisterFileSystemAndSendResponseWithIdOverride( - file_entry->path, - file_entry->writable ? WRITABLE : READ_ONLY, - file_entry->id); + entry_type_ = file_entry->writable ? WRITABLE : READ_ONLY; + CreateResponse(); + AddEntryToResponse(file_entry->path, file_entry->id); } + SendResponse(true); return true; } diff --git a/chrome/browser/extensions/api/file_system/file_system_api.h b/chrome/browser/extensions/api/file_system/file_system_api.h index 97b8dd1..cf46329 100644 --- a/chrome/browser/extensions/api/file_system/file_system_api.h +++ b/chrome/browser/extensions/api/file_system/file_system_api.h @@ -50,34 +50,42 @@ class FileSystemEntryFunction : public AsyncExtensionFunction { WRITABLE }; + FileSystemEntryFunction(); + virtual ~FileSystemEntryFunction() {} bool HasFileSystemWritePermission(); - // This is called when a writable file entry is being returned. The function - // will ensure the file exists, creating it if necessary, and also check that - // the file is not a link. If it succeeds it proceeds to - // RegisterFileSystemAndSendResponse, otherwise to HandleWritableFileError. - void CheckWritableFile(const base::FilePath& path); + // This is called when writable file entries are being returned. The function + // will ensure the files exist, creating them if necessary, and also check + // that none of the files are links. If it succeeds it proceeds to + // RegisterFileSystemsAndSendResponse, otherwise to HandleWritableFileError. + void CheckWritableFiles(const std::vector<base::FilePath>& path); // This will finish the choose file process. This is either called directly - // from FileSelected, or from CreateFileIfNecessary. It is called on the UI + // from FilesSelected, or from WritableFileChecker. It is called on the UI // thread. - void RegisterFileSystemAndSendResponse(const base::FilePath& path, - EntryType entry_type); + void RegisterFileSystemsAndSendResponse( + const std::vector<base::FilePath>& path); - // This will finish the choose file process. This is either called directly - // from FileSelected, or from CreateFileIfNecessary. It is called on the UI - // thread. |id_override| specifies the id to send in the response instead of - // the generated id. This can be useful for creating a file entry with an id - // matching another file entry, e.g. for restoreEntry. - void RegisterFileSystemAndSendResponseWithIdOverride( - const base::FilePath& path, - EntryType entry_type, - const std::string& id_override); + // Creates a response dictionary and sets it as the response to be sent. + void CreateResponse(); + + // Adds an entry to the response dictionary. + void AddEntryToResponse(const base::FilePath& path, + const std::string& id_override); // called on the UI thread if there is a problem checking a writable file. - void HandleWritableFileError(); + void HandleWritableFileError(const std::string& error); + + // Whether multiple entries have been requested. + bool multiple_; + + // The type of the entry or entries to return. + EntryType entry_type_; + + // The dictionary to send as the response. + base::DictionaryValue* response_; }; class FileSystemGetWritableEntryFunction : public FileSystemEntryFunction { @@ -104,6 +112,8 @@ class FileSystemChooseEntryFunction : public FileSystemEntryFunction { public: // Allow picker UI to be skipped in testing. static void SkipPickerAndAlwaysSelectPathForTest(base::FilePath* path); + static void SkipPickerAndAlwaysSelectPathsForTest( + std::vector<base::FilePath>* paths); static void SkipPickerAndSelectSuggestedPathForTest(); static void SkipPickerAndAlwaysCancelForTest(); static void StopSkippingPickerForTest(); @@ -133,15 +143,14 @@ class FileSystemChooseEntryFunction : public FileSystemEntryFunction { virtual ~FileSystemChooseEntryFunction() {} virtual bool RunImpl() OVERRIDE; void ShowPicker(const ui::SelectFileDialog::FileTypeInfo& file_type_info, - ui::SelectFileDialog::Type picker_type, - EntryType entry_type); + ui::SelectFileDialog::Type picker_type); private: void SetInitialPathOnFileThread(const base::FilePath& suggested_name, const base::FilePath& previous_path); - // FileSelected and FileSelectionCanceled are called by the file picker. - void FileSelected(const base::FilePath& path, EntryType entry_type); + // FilesSelected and FileSelectionCanceled are called by the file picker. + void FilesSelected(const std::vector<base::FilePath>& path); void FileSelectionCanceled(); base::FilePath initial_path_; diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chrome/browser/extensions/api/file_system/file_system_apitest.cc index a08faad..9f1c709 100644 --- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc +++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc @@ -97,6 +97,30 @@ class FileSystemApiTest : public PlatformAppBrowserTest { return destination; } + std::vector<base::FilePath> TempFilePaths( + const std::vector<std::string>& destination_names, + bool copy_gold) { + if (!temp_dir_.CreateUniqueTempDir()) { + ADD_FAILURE() << "CreateUniqueTempDir failed"; + return std::vector<base::FilePath>(); + } + FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest( + "test_temp", temp_dir_.path()); + + std::vector<base::FilePath> result; + for (std::vector<std::string>::const_iterator it = + destination_names.begin(); + it != destination_names.end(); ++it) { + base::FilePath destination = temp_dir_.path().AppendASCII(*it); + if (copy_gold) { + base::FilePath source = test_root_folder_.AppendASCII("gold.txt"); + EXPECT_TRUE(base::CopyFile(source, destination)); + } + result.push_back(destination); + } + return result; + } + void CheckStoredDirectoryMatches(const base::FilePath& filename) { const Extension* extension = GetSingleLoadedExtension(); ASSERT_TRUE(extension); @@ -222,6 +246,31 @@ IN_PROC_BROWSER_TEST_F(FileSystemApiTest, CheckStoredDirectoryMatches(test_file); } +IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiOpenMultipleSuggested) { + base::FilePath test_file = TempFilePath("open_existing.txt", true); + ASSERT_FALSE(test_file.empty()); + ASSERT_TRUE(PathService::OverrideAndCreateIfNeeded( + chrome::DIR_USER_DOCUMENTS, test_file.DirName(), false)); + FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest(); + ASSERT_TRUE(RunPlatformAppTest( + "api_test/file_system/open_multiple_with_suggested_name")) + << message_; + CheckStoredDirectoryMatches(test_file); +} + +IN_PROC_BROWSER_TEST_F(FileSystemApiTest, + FileSystemApiOpenMultipleExistingFilesTest) { + std::vector<std::string> names; + names.push_back("open_existing1.txt"); + names.push_back("open_existing2.txt"); + std::vector<base::FilePath> test_files = TempFilePaths(names, true); + ASSERT_EQ(2u, test_files.size()); + FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest( + &test_files); + ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/open_multiple_existing")) + << message_; +} + IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiInvalidChooseEntryTypeTest) { base::FilePath test_file = TempFilePath("open_existing.txt", true); @@ -272,6 +321,20 @@ IN_PROC_BROWSER_TEST_F(FileSystemApiTest, CheckStoredDirectoryMatches(test_file); } +IN_PROC_BROWSER_TEST_F(FileSystemApiTest, + FileSystemApiOpenMultipleWritableExistingFilesTest) { + std::vector<std::string> names; + names.push_back("open_existing1.txt"); + names.push_back("open_existing2.txt"); + std::vector<base::FilePath> test_files = TempFilePaths(names, true); + ASSERT_EQ(2u, test_files.size()); + FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest( + &test_files); + ASSERT_TRUE(RunPlatformAppTest( + "api_test/file_system/open_multiple_writable_existing_with_write")) + << message_; +} + IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiOpenCancelTest) { FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest(); ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/open_cancel")) @@ -326,6 +389,18 @@ IN_PROC_BROWSER_TEST_F(FileSystemApiTest, CheckStoredDirectoryMatches(test_file); } +IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiSaveMultipleFilesTest) { + std::vector<std::string> names; + names.push_back("save1.txt"); + names.push_back("save2.txt"); + std::vector<base::FilePath> test_files = TempFilePaths(names, false); + ASSERT_EQ(2u, test_files.size()); + FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest( + &test_files); + ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/save_multiple")) + << message_; +} + IN_PROC_BROWSER_TEST_F(FileSystemApiTest, FileSystemApiSaveCancelTest) { FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest(); ASSERT_TRUE(RunPlatformAppTest("api_test/file_system/save_cancel")) |