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

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..e347fcf7675118211deb07f259fc51e4d284c104 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;
}

Powered by Google App Engine
This is Rietveld 408576698