Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(8972)

Unified Diff: chrome/browser/extensions/api/file_system/file_system_api.cc

Issue 18331017: Support choosing multiple files with fileSystem.chooseEntry. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/extensions/api/file_system/file_system_api.cc
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 c02d5e011a633df8a2da4569854f4b0246ebfbcc..0b8bf72ceab657280ba710be6458373a80c30dd0 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,125 @@ 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
+ // calls the success or failure callback.
+ void TaskDone() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (--outstanding_tasks_ == 0) {
+ 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::UI));
Matt Giuca 2013/08/01 00:02:24 Nit: Remove this DCHECK. There is nothing in this
Sam McNally 2013/08/01 00:08:39 Done.
+ 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)) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WritableFileChecker::Error, this, error));
+ return;
+ }
+ }
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WritableFileChecker::TaskDone, this));
+ }
+
+#if defined(OS_CHROMEOS)
+ void CheckRemoteWritableFile(drive::FileError error,
+ const base::FilePath& path) {
+ if (error == drive::FILE_ERROR_OK) {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&WritableFileChecker::TaskDone, this));
+ } else {
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(
+ &WritableFileChecker::Error,
+ this,
+ 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.
@@ -396,38 +504,67 @@ bool FileSystemEntryFunction::HasFileSystemWritePermission() {
}
void FileSystemEntryFunction::CheckWritableFile(const base::FilePath& path) {
+ std::vector<base::FilePath> paths;
+ paths.push_back(path);
+ CheckWritableFiles(paths, false);
+}
+
+void FileSystemEntryFunction::CheckWritableFiles(
+ const std::vector<base::FilePath>& paths, bool multiple) {
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, WRITABLE, multiple),
+ 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,
+ EntryType entry_type,
+ bool multiple) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ std::vector<std::pair<base::FilePath, std::string> > paths_and_overrides;
+ for (std::vector<base::FilePath>::const_iterator it = paths.begin();
+ it != paths.end(); ++it) {
+ paths_and_overrides.push_back(std::make_pair(*it, ""));
}
-#endif
- content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
- base::Bind(&CheckLocalWritableFile, path, extension_->path(), on_success,
- on_failure));
+ RegisterFileSystemsAndSendResponseWithIdOverrides(
+ paths_and_overrides, entry_type, multiple);
}
-void FileSystemEntryFunction::RegisterFileSystemAndSendResponse(
- const base::FilePath& path, EntryType entry_type) {
- RegisterFileSystemAndSendResponseWithIdOverride(path, entry_type, "");
+void FileSystemEntryFunction::RegisterFileSystemsAndSendResponseWithIdOverrides(
Matt Giuca 2013/08/01 00:02:24 Nit: Move this function below RegisterFileSystemAn
Sam McNally 2013/08/01 00:08:39 Done.
+ const std::vector<std::pair<base::FilePath, std::string> >&
+ paths_and_overrides,
+ EntryType entry_type,
+ bool multiple) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+
+ base::DictionaryValue* response = new base::DictionaryValue();
+ base::ListValue* list = new base::ListValue();
+ response->Set("entries", list);
+ response->SetBoolean("multiple", multiple);
+ SetResult(response);
+ for (std::vector<std::pair<base::FilePath, std::string> >::const_iterator it =
+ paths_and_overrides.begin();
+ it != paths_and_overrides.end(); ++it) {
+ list->Append(BuildEntryDict(it->first, entry_type, it->second));
+ }
+
+ SendResponse(true);
}
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);
+ std::vector<std::pair<base::FilePath, std::string> > paths_and_overrides;
+ paths_and_overrides.push_back(std::make_pair(path, id));
+ RegisterFileSystemsAndSendResponseWithIdOverrides(
+ paths_and_overrides, entry_type, false);
+}
+base::DictionaryValue* FileSystemEntryFunction::BuildEntryDict(
+ const base::FilePath& path, EntryType entry_type, const std::string& id) {
bool writable = entry_type == WRITABLE;
extensions::app_file_handler_util::GrantedFileEntry file_entry =
extensions::app_file_handler_util::CreateFileEntry(profile(),
@@ -435,20 +572,19 @@ void FileSystemEntryFunction::RegisterFileSystemAndSendResponseWithIdOverride(
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);
else
dict->SetString("id", id);
-
- SendResponse(true);
+ return dict;
}
-void FileSystemEntryFunction::HandleWritableFileError() {
+void FileSystemEntryFunction::HandleWritableFileError(
+ const std::string& error) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
- error_ = kWritableFileError;
+ error_ = error;
SendResponse(false);
}
@@ -504,8 +640,10 @@ class FileSystemChooseEntryFunction::FilePicker
const base::FilePath& suggested_name,
const ui::SelectFileDialog::FileTypeInfo& file_type_info,
ui::SelectFileDialog::Type picker_type,
- EntryType entry_type)
+ EntryType entry_type,
+ bool multiple)
: entry_type_(entry_type),
+ multiple_(multiple),
function_(function) {
select_file_dialog_ = ui::SelectFileDialog::Create(
this, new ChromeSelectFilePolicy(web_contents));
@@ -521,11 +659,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 +701,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,16 +717,33 @@ 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, entry_type_, multiple_);
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_;
+ bool multiple_;
scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
scoped_refptr<FileSystemChooseEntryFunction> function_;
@@ -588,7 +754,8 @@ class FileSystemChooseEntryFunction::FilePicker
void FileSystemChooseEntryFunction::ShowPicker(
const ui::SelectFileDialog::FileTypeInfo& file_type_info,
ui::SelectFileDialog::Type picker_type,
- EntryType entry_type) {
+ EntryType entry_type,
+ bool multiple) {
// 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
@@ -614,7 +781,7 @@ void FileSystemChooseEntryFunction::ShowPicker(
// 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);
+ picker_type, entry_type, multiple);
}
// static
@@ -623,6 +790,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 +805,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 +813,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 +847,19 @@ void FileSystemChooseEntryFunction::SetInitialPathOnFileThread(
}
}
-void FileSystemChooseEntryFunction::FileSelected(const base::FilePath& path,
- EntryType entry_type) {
+void FileSystemChooseEntryFunction::FilesSelected(
+ const std::vector<base::FilePath>& paths,
+ EntryType entry_type,
+ bool multiple) {
file_system_api::SetLastChooseEntryDirectory(
- ExtensionPrefs::Get(profile()),
- GetExtension()->id(),
- path.DirName());
+ ExtensionPrefs::Get(profile()), GetExtension()->id(), paths[0].DirName());
if (entry_type == WRITABLE) {
- CheckWritableFile(path);
+ CheckWritableFiles(paths, multiple);
return;
}
// Don't need to check the file, it's for reading.
- RegisterFileSystemAndSendResponse(path, READ_ONLY);
+ RegisterFileSystemsAndSendResponse(paths, READ_ONLY, multiple);
}
void FileSystemChooseEntryFunction::FileSelectionCanceled() {
@@ -761,10 +938,18 @@ bool FileSystemChooseEntryFunction::RunImpl() {
ui::SelectFileDialog::SELECT_OPEN_FILE;
file_system::ChooseEntryOptions* options = params->options.get();
+ bool multiple = false;
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;
} else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) {
+ if (multiple) {
+ error_ = kMultipleUnsupportedError;
+ return false;
+ }
entry_type = WRITABLE;
picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
}
@@ -798,7 +983,7 @@ bool FileSystemChooseEntryFunction::RunImpl() {
suggested_name, previous_path),
base::Bind(
&FileSystemChooseEntryFunction::ShowPicker, this, file_type_info,
- picker_type, entry_type));
+ picker_type, entry_type, multiple));
return true;
}

Powered by Google App Engine
This is Rietveld 408576698