summaryrefslogtreecommitdiff
path: root/chromium/webkit/browser/fileapi
diff options
context:
space:
mode:
authorZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
committerZeno Albisser <zeno.albisser@digia.com>2013-08-15 21:46:11 +0200
commit679147eead574d186ebf3069647b4c23e8ccace6 (patch)
treefc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/webkit/browser/fileapi
downloadqtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz
Initial import.
Diffstat (limited to 'chromium/webkit/browser/fileapi')
-rw-r--r--chromium/webkit/browser/fileapi/OWNERS2
-rw-r--r--chromium/webkit/browser/fileapi/async_file_test_helper.cc239
-rw-r--r--chromium/webkit/browser/fileapi/async_file_test_helper.h94
-rw-r--r--chromium/webkit/browser/fileapi/async_file_util.h346
-rw-r--r--chromium/webkit/browser/fileapi/async_file_util_adapter.cc346
-rw-r--r--chromium/webkit/browser/fileapi/async_file_util_adapter.h115
-rw-r--r--chromium/webkit/browser/fileapi/copy_or_move_file_validator.h54
-rw-r--r--chromium/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc326
-rw-r--r--chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.cc325
-rw-r--r--chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.h122
-rw-r--r--chromium/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc562
-rw-r--r--chromium/webkit/browser/fileapi/dump_file_system.cc205
-rw-r--r--chromium/webkit/browser/fileapi/external_mount_points.cc335
-rw-r--r--chromium/webkit/browser/fileapi/external_mount_points.h159
-rw-r--r--chromium/webkit/browser/fileapi/external_mount_points_unittest.cc462
-rw-r--r--chromium/webkit/browser/fileapi/file_observers.h83
-rw-r--r--chromium/webkit/browser/fileapi/file_permission_policy.cc34
-rw-r--r--chromium/webkit/browser/fileapi/file_permission_policy.h33
-rw-r--r--chromium/webkit/browser/fileapi/file_stream_writer.h72
-rw-r--r--chromium/webkit/browser/fileapi/file_system_backend.h155
-rw-r--r--chromium/webkit/browser/fileapi/file_system_context.cc448
-rw-r--r--chromium/webkit/browser/fileapi/file_system_context.h329
-rw-r--r--chromium/webkit/browser/fileapi/file_system_context_unittest.cc328
-rw-r--r--chromium/webkit/browser/fileapi/file_system_dir_url_request_job.cc130
-rw-r--r--chromium/webkit/browser/fileapi/file_system_dir_url_request_job.h66
-rw-r--r--chromium/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc290
-rw-r--r--chromium/webkit/browser/fileapi/file_system_file_stream_reader.cc126
-rw-r--r--chromium/webkit/browser/fileapi/file_system_file_stream_reader.h79
-rw-r--r--chromium/webkit/browser/fileapi/file_system_file_stream_reader_unittest.cc283
-rw-r--r--chromium/webkit/browser/fileapi/file_system_file_util.cc25
-rw-r--r--chromium/webkit/browser/fileapi/file_system_file_util.h189
-rw-r--r--chromium/webkit/browser/fileapi/file_system_operation.h276
-rw-r--r--chromium/webkit/browser/fileapi/file_system_operation_context.cc32
-rw-r--r--chromium/webkit/browser/fileapi/file_system_operation_context.h105
-rw-r--r--chromium/webkit/browser/fileapi/file_system_operation_impl.cc539
-rw-r--r--chromium/webkit/browser/fileapi/file_system_operation_impl.h263
-rw-r--r--chromium/webkit/browser/fileapi/file_system_operation_impl_unittest.cc1187
-rw-r--r--chromium/webkit/browser/fileapi/file_system_operation_impl_write_unittest.cc341
-rw-r--r--chromium/webkit/browser/fileapi/file_system_operation_runner.cc563
-rw-r--r--chromium/webkit/browser/fileapi/file_system_operation_runner.h297
-rw-r--r--chromium/webkit/browser/fileapi/file_system_options.cc19
-rw-r--r--chromium/webkit/browser/fileapi/file_system_options.h53
-rw-r--r--chromium/webkit/browser/fileapi/file_system_quota_client.cc204
-rw-r--r--chromium/webkit/browser/fileapi/file_system_quota_client.h72
-rw-r--r--chromium/webkit/browser/fileapi/file_system_quota_client_unittest.cc582
-rw-r--r--chromium/webkit/browser/fileapi/file_system_quota_util.h81
-rw-r--r--chromium/webkit/browser/fileapi/file_system_url.cc188
-rw-r--r--chromium/webkit/browser/fileapi/file_system_url.h174
-rw-r--r--chromium/webkit/browser/fileapi/file_system_url_request_job.cc240
-rw-r--r--chromium/webkit/browser/fileapi/file_system_url_request_job.h83
-rw-r--r--chromium/webkit/browser/fileapi/file_system_url_request_job_factory.cc68
-rw-r--r--chromium/webkit/browser/fileapi/file_system_url_request_job_factory.h28
-rw-r--r--chromium/webkit/browser/fileapi/file_system_url_request_job_unittest.cc361
-rw-r--r--chromium/webkit/browser/fileapi/file_system_url_unittest.cc194
-rw-r--r--chromium/webkit/browser/fileapi/file_system_usage_cache.cc316
-rw-r--r--chromium/webkit/browser/fileapi/file_system_usage_cache.h105
-rw-r--r--chromium/webkit/browser/fileapi/file_system_usage_cache_unittest.cc158
-rw-r--r--chromium/webkit/browser/fileapi/file_writer_delegate.cc254
-rw-r--r--chromium/webkit/browser/fileapi/file_writer_delegate.h103
-rw-r--r--chromium/webkit/browser/fileapi/file_writer_delegate_unittest.cc478
-rw-r--r--chromium/webkit/browser/fileapi/isolated_context.cc469
-rw-r--r--chromium/webkit/browser/fileapi/isolated_context.h197
-rw-r--r--chromium/webkit/browser/fileapi/isolated_context_unittest.cc336
-rw-r--r--chromium/webkit/browser/fileapi/isolated_file_system_backend.cc146
-rw-r--r--chromium/webkit/browser/fileapi/isolated_file_system_backend.h56
-rw-r--r--chromium/webkit/browser/fileapi/isolated_file_util.cc126
-rw-r--r--chromium/webkit/browser/fileapi/isolated_file_util.h54
-rw-r--r--chromium/webkit/browser/fileapi/isolated_file_util_unittest.cc547
-rw-r--r--chromium/webkit/browser/fileapi/local_file_stream_writer.cc233
-rw-r--r--chromium/webkit/browser/fileapi/local_file_stream_writer.h92
-rw-r--r--chromium/webkit/browser/fileapi/local_file_stream_writer_unittest.cc180
-rw-r--r--chromium/webkit/browser/fileapi/local_file_util.cc257
-rw-r--r--chromium/webkit/browser/fileapi/local_file_util.h99
-rw-r--r--chromium/webkit/browser/fileapi/local_file_util_unittest.cc387
-rw-r--r--chromium/webkit/browser/fileapi/mock_file_change_observer.cc51
-rw-r--r--chromium/webkit/browser/fileapi/mock_file_change_observer.h103
-rw-r--r--chromium/webkit/browser/fileapi/mock_file_system_context.cc42
-rw-r--r--chromium/webkit/browser/fileapi/mock_file_system_context.h34
-rw-r--r--chromium/webkit/browser/fileapi/mock_file_system_options.cc40
-rw-r--r--chromium/webkit/browser/fileapi/mock_file_system_options.h23
-rw-r--r--chromium/webkit/browser/fileapi/mount_points.cc15
-rw-r--r--chromium/webkit/browser/fileapi/mount_points.h105
-rw-r--r--chromium/webkit/browser/fileapi/native_file_util.cc262
-rw-r--r--chromium/webkit/browser/fileapi/native_file_util.h68
-rw-r--r--chromium/webkit/browser/fileapi/native_file_util_unittest.cc341
-rw-r--r--chromium/webkit/browser/fileapi/obfuscated_file_util.cc1444
-rw-r--r--chromium/webkit/browser/fileapi/obfuscated_file_util.h296
-rw-r--r--chromium/webkit/browser/fileapi/obfuscated_file_util_unittest.cc2375
-rw-r--r--chromium/webkit/browser/fileapi/open_file_system_mode.h22
-rw-r--r--chromium/webkit/browser/fileapi/recursive_operation_delegate.cc141
-rw-r--r--chromium/webkit/browser/fileapi/recursive_operation_delegate.h101
-rw-r--r--chromium/webkit/browser/fileapi/remove_operation_delegate.cc88
-rw-r--r--chromium/webkit/browser/fileapi/remove_operation_delegate.h48
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_database_test_helper.cc100
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_database_test_helper.h28
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_directory_database.cc928
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_directory_database.h125
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_directory_database_unittest.cc678
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_stream_writer.cc245
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_stream_writer.h95
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_system_backend.cc258
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_system_backend.h123
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.cc442
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.h194
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc82
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_system_backend_unittest.cc325
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.cc152
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.h103
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.cc105
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.h49
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_isolated_origin_database_unittest.cc87
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_origin_database.cc341
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_origin_database.h71
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_origin_database_interface.cc20
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_origin_database_interface.h55
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_origin_database_unittest.cc307
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_quota_observer.cc139
-rw-r--r--chromium/webkit/browser/fileapi/sandbox_quota_observer.h82
-rw-r--r--chromium/webkit/browser/fileapi/task_runner_bound_observer_list.h99
-rw-r--r--chromium/webkit/browser/fileapi/test_file_set.cc76
-rw-r--r--chromium/webkit/browser/fileapi/test_file_set.h41
-rw-r--r--chromium/webkit/browser/fileapi/test_file_system_backend.cc230
-rw-r--r--chromium/webkit/browser/fileapi/test_file_system_backend.h96
-rw-r--r--chromium/webkit/browser/fileapi/timed_task_helper.cc92
-rw-r--r--chromium/webkit/browser/fileapi/timed_task_helper.h59
-rw-r--r--chromium/webkit/browser/fileapi/timed_task_helper_unittest.cc83
-rw-r--r--chromium/webkit/browser/fileapi/transient_file_util.cc55
-rw-r--r--chromium/webkit/browser/fileapi/transient_file_util.h36
-rw-r--r--chromium/webkit/browser/fileapi/transient_file_util_unittest.cc122
-rw-r--r--chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.cc115
-rw-r--r--chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.h64
-rw-r--r--chromium/webkit/browser/fileapi/upload_file_system_file_element_reader_unittest.cc295
132 files changed, 29101 insertions, 0 deletions
diff --git a/chromium/webkit/browser/fileapi/OWNERS b/chromium/webkit/browser/fileapi/OWNERS
new file mode 100644
index 00000000000..6ca58a9dbe5
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/OWNERS
@@ -0,0 +1,2 @@
+ericu@chromium.org
+tzik@chromium.org
diff --git a/chromium/webkit/browser/fileapi/async_file_test_helper.cc b/chromium/webkit/browser/fileapi/async_file_test_helper.cc
new file mode 100644
index 00000000000..8a01fd2a474
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/async_file_test_helper.cc
@@ -0,0 +1,239 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/async_file_test_helper.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+namespace {
+
+typedef FileSystemOperation::FileEntryList FileEntryList;
+
+void AssignAndQuit(base::RunLoop* run_loop,
+ base::PlatformFileError* result_out,
+ base::PlatformFileError result) {
+ *result_out = result;
+ run_loop->Quit();
+}
+
+base::Callback<void(base::PlatformFileError)>
+AssignAndQuitCallback(base::RunLoop* run_loop,
+ base::PlatformFileError* result) {
+ return base::Bind(&AssignAndQuit, run_loop, base::Unretained(result));
+}
+
+void GetMetadataCallback(base::RunLoop* run_loop,
+ base::PlatformFileError* result_out,
+ base::PlatformFileInfo* file_info_out,
+ base::PlatformFileError result,
+ const base::PlatformFileInfo& file_info) {
+ *result_out = result;
+ if (file_info_out)
+ *file_info_out = file_info;
+ run_loop->Quit();
+}
+
+void CreateSnapshotFileCallback(
+ base::RunLoop* run_loop,
+ base::PlatformFileError* result_out,
+ base::FilePath* platform_path_out,
+ base::PlatformFileError result,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) {
+ DCHECK(!file_ref.get());
+ *result_out = result;
+ if (platform_path_out)
+ *platform_path_out = platform_path;
+ run_loop->Quit();
+}
+
+void ReadDirectoryCallback(base::RunLoop* run_loop,
+ base::PlatformFileError* result_out,
+ FileEntryList* entries_out,
+ base::PlatformFileError result,
+ const FileEntryList& entries,
+ bool has_more) {
+ *result_out = result;
+ *entries_out = entries;
+ if (result != base::PLATFORM_FILE_OK || !has_more)
+ run_loop->Quit();
+}
+
+void DidGetUsageAndQuota(quota::QuotaStatusCode* status_out,
+ int64* usage_out,
+ int64* quota_out,
+ quota::QuotaStatusCode status,
+ int64 usage,
+ int64 quota) {
+ if (status_out)
+ *status_out = status;
+ if (usage_out)
+ *usage_out = usage;
+ if (quota_out)
+ *quota_out = quota;
+}
+
+} // namespace
+
+const int64 AsyncFileTestHelper::kDontCheckSize = -1;
+
+base::PlatformFileError AsyncFileTestHelper::Copy(
+ FileSystemContext* context,
+ const FileSystemURL& src,
+ const FileSystemURL& dest) {
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ context->operation_runner()->Copy(
+ src, dest, AssignAndQuitCallback(&run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+base::PlatformFileError AsyncFileTestHelper::Move(
+ FileSystemContext* context,
+ const FileSystemURL& src,
+ const FileSystemURL& dest) {
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ context->operation_runner()->Move(
+ src, dest, AssignAndQuitCallback(&run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+base::PlatformFileError AsyncFileTestHelper::Remove(
+ FileSystemContext* context,
+ const FileSystemURL& url,
+ bool recursive) {
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ context->operation_runner()->Remove(
+ url, recursive, AssignAndQuitCallback(&run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+base::PlatformFileError AsyncFileTestHelper::ReadDirectory(
+ FileSystemContext* context,
+ const FileSystemURL& url,
+ FileEntryList* entries) {
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ DCHECK(entries);
+ entries->clear();
+ base::RunLoop run_loop;
+ context->operation_runner()->ReadDirectory(
+ url, base::Bind(&ReadDirectoryCallback, &run_loop, &result, entries));
+ run_loop.Run();
+ return result;
+}
+
+base::PlatformFileError AsyncFileTestHelper::CreateDirectory(
+ FileSystemContext* context,
+ const FileSystemURL& url) {
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ context->operation_runner()->CreateDirectory(
+ url,
+ false /* exclusive */,
+ false /* recursive */,
+ AssignAndQuitCallback(&run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+base::PlatformFileError AsyncFileTestHelper::CreateFile(
+ FileSystemContext* context,
+ const FileSystemURL& url) {
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ context->operation_runner()->CreateFile(
+ url, false /* exclusive */,
+ AssignAndQuitCallback(&run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+base::PlatformFileError AsyncFileTestHelper::TruncateFile(
+ FileSystemContext* context,
+ const FileSystemURL& url,
+ size_t size) {
+ base::RunLoop run_loop;
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ context->operation_runner()->Truncate(
+ url, size, AssignAndQuitCallback(&run_loop, &result));
+ run_loop.Run();
+ return result;
+}
+
+base::PlatformFileError AsyncFileTestHelper::GetMetadata(
+ FileSystemContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileInfo* file_info) {
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ context->operation_runner()->GetMetadata(
+ url, base::Bind(&GetMetadataCallback, &run_loop, &result,
+ file_info));
+ run_loop.Run();
+ return result;
+}
+
+base::PlatformFileError AsyncFileTestHelper::GetPlatformPath(
+ FileSystemContext* context,
+ const FileSystemURL& url,
+ base::FilePath* platform_path) {
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ base::RunLoop run_loop;
+ context->operation_runner()->CreateSnapshotFile(
+ url, base::Bind(&CreateSnapshotFileCallback, &run_loop, &result,
+ platform_path));
+ run_loop.Run();
+ return result;
+}
+
+bool AsyncFileTestHelper::FileExists(
+ FileSystemContext* context,
+ const FileSystemURL& url,
+ int64 expected_size) {
+ base::PlatformFileInfo file_info;
+ base::PlatformFileError result = GetMetadata(context, url, &file_info);
+ if (result != base::PLATFORM_FILE_OK || file_info.is_directory)
+ return false;
+ return expected_size == kDontCheckSize || file_info.size == expected_size;
+}
+
+bool AsyncFileTestHelper::DirectoryExists(
+ FileSystemContext* context,
+ const FileSystemURL& url) {
+ base::PlatformFileInfo file_info;
+ base::PlatformFileError result = GetMetadata(context, url, &file_info);
+ return (result == base::PLATFORM_FILE_OK) && file_info.is_directory;
+}
+
+quota::QuotaStatusCode AsyncFileTestHelper::GetUsageAndQuota(
+ quota::QuotaManager* quota_manager,
+ const GURL& origin,
+ FileSystemType type,
+ int64* usage,
+ int64* quota) {
+ quota::QuotaStatusCode status = quota::kQuotaStatusUnknown;
+ quota_manager->GetUsageAndQuota(
+ origin,
+ FileSystemTypeToQuotaStorageType(type),
+ base::Bind(&DidGetUsageAndQuota, &status, usage, quota));
+ base::MessageLoop::current()->RunUntilIdle();
+ return status;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/async_file_test_helper.h b/chromium/webkit/browser/fileapi/async_file_test_helper.h
new file mode 100644
index 00000000000..cfa7981e03e
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/async_file_test_helper.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_TEST_HELPER_H_
+#define WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_TEST_HELPER_H_
+
+#include "base/basictypes.h"
+#include "webkit/browser/fileapi/file_system_operation.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/quota/quota_status_code.h"
+
+namespace quota {
+class QuotaManager;
+}
+
+namespace fileapi {
+
+class FileSystemContext;
+class FileSystemURL;
+
+// A helper class to perform async file operations in a synchronous way.
+class AsyncFileTestHelper {
+ public:
+ typedef FileSystemOperation::FileEntryList FileEntryList;
+
+ static const int64 kDontCheckSize;
+
+ // Performs Copy from |src| to |dest| and returns the status code.
+ static base::PlatformFileError Copy(FileSystemContext* context,
+ const FileSystemURL& src,
+ const FileSystemURL& dest);
+
+ // Performs Move from |src| to |dest| and returns the status code.
+ static base::PlatformFileError Move(FileSystemContext* context,
+ const FileSystemURL& src,
+ const FileSystemURL& dest);
+
+ // Removes the given |url|.
+ static base::PlatformFileError Remove(FileSystemContext* context,
+ const FileSystemURL& url,
+ bool recursive);
+
+ // Performs ReadDirectory on |url|.
+ static base::PlatformFileError ReadDirectory(FileSystemContext* context,
+ const FileSystemURL& url,
+ FileEntryList* entries);
+
+ // Creates a directory at |url|.
+ static base::PlatformFileError CreateDirectory(FileSystemContext* context,
+ const FileSystemURL& url);
+
+ // Creates a file at |url|.
+ static base::PlatformFileError CreateFile(FileSystemContext* context,
+ const FileSystemURL& url);
+
+ // Truncates the file |url| to |size|.
+ static base::PlatformFileError TruncateFile(FileSystemContext* context,
+ const FileSystemURL& url,
+ size_t size);
+
+ // Retrieves PlatformFileInfo for |url| and populates |file_info|.
+ static base::PlatformFileError GetMetadata(FileSystemContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileInfo* file_info);
+
+ // Retrieves FilePath for |url| and populates |platform_path|.
+ static base::PlatformFileError GetPlatformPath(FileSystemContext* context,
+ const FileSystemURL& url,
+ base::FilePath* platform_path);
+
+ // Returns true if a file exists at |url| with |size|. If |size| is
+ // kDontCheckSize it doesn't check the file size (but just check its
+ // existence).
+ static bool FileExists(FileSystemContext* context,
+ const FileSystemURL& url,
+ int64 size);
+
+ // Returns true if a directory exists at |url|.
+ static bool DirectoryExists(FileSystemContext* context,
+ const FileSystemURL& url);
+
+ // Returns usage and quota. It's valid to pass NULL to |usage| and/or |quota|.
+ static quota::QuotaStatusCode GetUsageAndQuota(
+ quota::QuotaManager* quota_manager,
+ const GURL& origin,
+ FileSystemType type,
+ int64* usage,
+ int64* quota);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_TEST_HELPER_H_
diff --git a/chromium/webkit/browser/fileapi/async_file_util.h b/chromium/webkit/browser/fileapi/async_file_util.h
new file mode 100644
index 00000000000..dc8216eaac0
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/async_file_util.h
@@ -0,0 +1,346 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_
+#define WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/files/file_util_proxy.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/fileapi/directory_entry.h"
+
+namespace base {
+class Time;
+}
+
+namespace webkit_blob {
+class ShareableFileReference;
+}
+
+namespace fileapi {
+
+class FileSystemOperationContext;
+class FileSystemURL;
+
+// An interface which provides filesystem-specific file operations for
+// FileSystemOperationImpl.
+//
+// Each filesystem which needs to be dispatched from FileSystemOperationImpl
+// must implement this interface or a synchronous version of interface:
+// FileSystemFileUtil.
+//
+// As far as an instance of this class is owned by a FileSystemBackend
+// (which is owned by FileSystemContext), it's guaranteed that this instance's
+// alive while FileSystemOperationContext given to each operation is kept
+// alive. (Note that this instance might be freed on different thread
+// from the thread it is created.)
+//
+// It is NOT valid to give null callback to this class, and implementors
+// can assume that they don't get any null callbacks.
+//
+class WEBKIT_STORAGE_BROWSER_EXPORT AsyncFileUtil {
+ public:
+ typedef base::Callback<
+ void(base::PlatformFileError result)> StatusCallback;
+
+ // |on_close_callback| will be called after the |file| is closed in the
+ // child process. |on_close_callback|.is_null() can be true, if no operation
+ // is needed on closing the file.
+ typedef base::Callback<
+ void(base::PlatformFileError result,
+ base::PassPlatformFile file,
+ const base::Closure& on_close_callback)> CreateOrOpenCallback;
+
+ typedef base::Callback<
+ void(base::PlatformFileError result,
+ bool created)> EnsureFileExistsCallback;
+
+ typedef base::Callback<
+ void(base::PlatformFileError result,
+ const base::PlatformFileInfo& file_info)> GetFileInfoCallback;
+
+ typedef std::vector<DirectoryEntry> EntryList;
+ typedef base::Callback<
+ void(base::PlatformFileError result,
+ const EntryList& file_list,
+ bool has_more)> ReadDirectoryCallback;
+
+ typedef base::Callback<
+ void(base::PlatformFileError result,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref
+ )> CreateSnapshotFileCallback;
+
+ AsyncFileUtil() {}
+ virtual ~AsyncFileUtil() {}
+
+ // Creates or opens a file with the given flags.
+ // If PLATFORM_FILE_CREATE is set in |file_flags| it always tries to create
+ // a new file at the given |url| and calls back with
+ // PLATFORM_FILE_ERROR_FILE_EXISTS if the |url| already exists.
+ //
+ // FileSystemOperationImpl::OpenFile calls this.
+ // This is used only by Pepper/NaCL File API.
+ //
+ virtual void CreateOrOpen(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ int file_flags,
+ const CreateOrOpenCallback& callback) = 0;
+
+ // Ensures that the given |url| exist. This creates a empty new file
+ // at |url| if the |url| does not exist.
+ //
+ // FileSystemOperationImpl::CreateFile calls this.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_OK and created==true if a file has not existed and
+ // is created at |url|.
+ // - PLATFORM_FILE_OK and created==false if the file already exists.
+ // - Other error code (with created=false) if a file hasn't existed yet
+ // and there was an error while creating a new file.
+ //
+ virtual void EnsureFileExists(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const EnsureFileExistsCallback& callback) = 0;
+
+ // Creates directory at given url.
+ //
+ // FileSystemOperationImpl::CreateDirectory calls this.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if the |url|'s parent directory
+ // does not exist and |recursive| is false.
+ // - PLATFORM_FILE_ERROR_EXISTS if a directory already exists at |url|
+ // and |exclusive| is true.
+ // - PLATFORM_FILE_ERROR_EXISTS if a file already exists at |url|
+ // (regardless of |exclusive| value).
+ // - Other error code if it failed to create a directory.
+ //
+ virtual void CreateDirectory(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) = 0;
+
+ // Retrieves the information about a file.
+ //
+ // FileSystemOperationImpl::GetMetadata calls this.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if the file doesn't exist.
+ // - Other error code if there was an error while retrieving the file info.
+ //
+ virtual void GetFileInfo(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const GetFileInfoCallback& callback) = 0;
+
+ // Reads contents of a directory at |path|.
+ //
+ // FileSystemOperationImpl::ReadDirectory calls this.
+ //
+ // Note that the |name| field of each entry in |file_list|
+ // returned by |callback| should have a base file name
+ // of the entry relative to the directory, but not an absolute path.
+ //
+ // (E.g. if ReadDirectory is called for a directory
+ // 'path/to/dir' and the directory has entries 'a' and 'b',
+ // the returned |file_list| should include entries whose names
+ // are 'a' and 'b', but not '/path/to/dir/a' and '/path/to/dir/b'.)
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if the target directory doesn't exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_DIRECTORY if an entry exists at |url| but
+ // is a file (not a directory).
+ //
+ virtual void ReadDirectory(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const ReadDirectoryCallback& callback) = 0;
+
+ // Modifies timestamps of a file or directory at |url| with
+ // |last_access_time| and |last_modified_time|. The function DOES NOT
+ // create a file unlike 'touch' command on Linux.
+ //
+ // FileSystemOperationImpl::TouchFile calls this.
+ // This is used only by Pepper/NaCL File API.
+ //
+ virtual void Touch(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) = 0;
+
+ // Truncates a file at |path| to |length|. If |length| is larger than
+ // the original file size, the file will be extended, and the extended
+ // part is filled with null bytes.
+ //
+ // FileSystemOperationImpl::Truncate calls this.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if the file doesn't exist.
+ //
+ virtual void Truncate(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ int64 length,
+ const StatusCallback& callback) = 0;
+
+ // Copies a file from |src_url| to |dest_url|.
+ // This must be called for files that belong to the same filesystem
+ // (i.e. type() and origin() of the |src_url| and |dest_url| must match).
+ //
+ // FileSystemOperationImpl::Copy calls this for same-filesystem copy case.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ virtual void CopyFileLocal(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) = 0;
+
+ // Moves a local file from |src_url| to |dest_url|.
+ // This must be called for files that belong to the same filesystem
+ // (i.e. type() and origin() of the |src_url| and |dest_url| must match).
+ //
+ // FileSystemOperationImpl::Move calls this for same-filesystem move case.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ virtual void MoveFileLocal(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) = 0;
+
+ // Copies in a single file from a different filesystem.
+ //
+ // FileSystemOperationImpl::Copy or Move calls this for cross-filesystem
+ // cases.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_file_path|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ virtual void CopyInForeignFile(
+ scoped_ptr<FileSystemOperationContext> context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) = 0;
+
+ // Deletes a single file.
+ //
+ // FileSystemOperationImpl::RemoveFile calls this.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |url| is not a file.
+ //
+ virtual void DeleteFile(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const StatusCallback& callback) = 0;
+
+ // Removes a single empty directory.
+ //
+ // FileSystemOperationImpl::RemoveDirectory calls this.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory.
+ // - PLATFORM_FILE_ERROR_NOT_EMPTY if |url| is not empty.
+ //
+ virtual void DeleteDirectory(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const StatusCallback& callback) = 0;
+
+ // Removes a single file or a single directory with its contents
+ // (i.e. files/subdirectories under the directory).
+ //
+ // FileSystemOperationImpl::Remove calls this.
+ // On some platforms, such as Chrome OS Drive File System, recursive file
+ // deletion can be implemented more efficiently than calling DeleteFile() and
+ // DeleteDirectory() for each files/directories.
+ // This method is optional, so if not supported,
+ // PLATFORM_ERROR_INVALID_OPERATION should be returned via |callback|.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - PLATFORM_ERROR_INVALID_OPERATION if this operation is not supported.
+ virtual void DeleteRecursively(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const StatusCallback& callback) = 0;
+
+ // Creates a local snapshot file for a given |url| and returns the
+ // metadata and platform path of the snapshot file via |callback|.
+ // In regular filesystem cases the implementation may simply return
+ // the metadata of the file itself (as well as GetMetadata does),
+ // while in non-regular filesystem case the backend may create a
+ // temporary snapshot file which holds the file data and return
+ // the metadata of the temporary file.
+ //
+ // In the callback, it returns:
+ // |file_info| is the metadata of the snapshot file created.
+ // |platform_path| is the full absolute platform path to the snapshot
+ // file created. If a file is not backed by a real local file in
+ // the implementor's FileSystem, the implementor must create a
+ // local snapshot file and return the path of the created file.
+ //
+ // If implementors creates a temporary file for snapshotting and wants
+ // FileAPI backend to take care of the lifetime of the file (so that
+ // it won't get deleted while JS layer has any references to the created
+ // File/Blob object), it should return non-empty |file_ref|.
+ // Via the |file_ref| implementors can schedule a file deletion
+ // or arbitrary callbacks when the last reference of File/Blob is dropped.
+ //
+ // FileSystemOperationImpl::CreateSnapshotFile calls this.
+ //
+ // This reports following error code via |callback|:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |url| exists but is a directory.
+ //
+ // The field values of |file_info| are undefined (implementation
+ // dependent) in error cases, and the caller should always
+ // check the return code.
+ virtual void CreateSnapshotFile(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const CreateSnapshotFileCallback& callback) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AsyncFileUtil);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_
diff --git a/chromium/webkit/browser/fileapi/async_file_util_adapter.cc b/chromium/webkit/browser/fileapi/async_file_util_adapter.cc
new file mode 100644
index 00000000000..807c2e20227
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/async_file_util_adapter.cc
@@ -0,0 +1,346 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/async_file_util_adapter.h"
+
+#include "base/bind.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task_runner_util.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using base::Bind;
+using base::Callback;
+using base::Owned;
+using base::PlatformFileError;
+using base::Unretained;
+using webkit_blob::ShareableFileReference;
+
+namespace fileapi {
+
+namespace {
+
+class EnsureFileExistsHelper {
+ public:
+ EnsureFileExistsHelper() : error_(base::PLATFORM_FILE_OK), created_(false) {}
+
+ void RunWork(FileSystemFileUtil* file_util,
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ error_ = file_util->EnsureFileExists(context, url, &created_);
+ }
+
+ void Reply(const AsyncFileUtil::EnsureFileExistsCallback& callback) {
+ callback.Run(error_, created_);
+ }
+
+ private:
+ base::PlatformFileError error_;
+ bool created_;
+ DISALLOW_COPY_AND_ASSIGN(EnsureFileExistsHelper);
+};
+
+class GetFileInfoHelper {
+ public:
+ GetFileInfoHelper()
+ : error_(base::PLATFORM_FILE_OK) {}
+
+ void GetFileInfo(FileSystemFileUtil* file_util,
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ error_ = file_util->GetFileInfo(context, url, &file_info_, &platform_path_);
+ }
+
+ void CreateSnapshotFile(FileSystemFileUtil* file_util,
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ scoped_file_ = file_util->CreateSnapshotFile(
+ context, url, &error_, &file_info_, &platform_path_);
+ }
+
+ void ReplyFileInfo(const AsyncFileUtil::GetFileInfoCallback& callback) {
+ callback.Run(error_, file_info_);
+ }
+
+ void ReplySnapshotFile(
+ const AsyncFileUtil::CreateSnapshotFileCallback& callback) {
+ callback.Run(error_, file_info_, platform_path_,
+ ShareableFileReference::GetOrCreate(scoped_file_.Pass()));
+ }
+
+ private:
+ base::PlatformFileError error_;
+ base::PlatformFileInfo file_info_;
+ base::FilePath platform_path_;
+ webkit_blob::ScopedFile scoped_file_;
+ DISALLOW_COPY_AND_ASSIGN(GetFileInfoHelper);
+};
+
+class ReadDirectoryHelper {
+ public:
+ ReadDirectoryHelper() : error_(base::PLATFORM_FILE_OK) {}
+
+ void RunWork(FileSystemFileUtil* file_util,
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ base::PlatformFileInfo file_info;
+ base::FilePath platform_path;
+ PlatformFileError error = file_util->GetFileInfo(
+ context, url, &file_info, &platform_path);
+ if (error != base::PLATFORM_FILE_OK) {
+ error_ = error;
+ return;
+ }
+ if (!file_info.is_directory) {
+ error_ = base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
+ return;
+ }
+
+ scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> file_enum(
+ file_util->CreateFileEnumerator(context, url));
+
+ base::FilePath current;
+ while (!(current = file_enum->Next()).empty()) {
+ DirectoryEntry entry;
+ entry.is_directory = file_enum->IsDirectory();
+ entry.name = VirtualPath::BaseName(current).value();
+ entry.size = file_enum->Size();
+ entry.last_modified_time = file_enum->LastModifiedTime();
+ entries_.push_back(entry);
+ }
+ error_ = base::PLATFORM_FILE_OK;
+ }
+
+ void Reply(const AsyncFileUtil::ReadDirectoryCallback& callback) {
+ callback.Run(error_, entries_, false /* has_more */);
+ }
+
+ private:
+ base::PlatformFileError error_;
+ std::vector<DirectoryEntry> entries_;
+ DISALLOW_COPY_AND_ASSIGN(ReadDirectoryHelper);
+};
+
+void RunCreateOrOpenCallback(
+ const AsyncFileUtil::CreateOrOpenCallback& callback,
+ base::PlatformFileError result,
+ base::PassPlatformFile file,
+ bool created) {
+ callback.Run(result, file, base::Closure());
+}
+
+} // namespace
+
+AsyncFileUtilAdapter::AsyncFileUtilAdapter(
+ FileSystemFileUtil* sync_file_util)
+ : sync_file_util_(sync_file_util) {
+ DCHECK(sync_file_util_.get());
+}
+
+AsyncFileUtilAdapter::~AsyncFileUtilAdapter() {
+}
+
+void AsyncFileUtilAdapter::CreateOrOpen(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ int file_flags,
+ const CreateOrOpenCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = base::FileUtilProxy::RelayCreateOrOpen(
+ context_ptr->task_runner(),
+ Bind(&FileSystemFileUtil::CreateOrOpen, Unretained(sync_file_util_.get()),
+ context_ptr, url, file_flags),
+ Bind(&FileSystemFileUtil::Close, Unretained(sync_file_util_.get()),
+ base::Owned(context_ptr)),
+ Bind(&RunCreateOrOpenCallback, callback));
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::EnsureFileExists(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const EnsureFileExistsCallback& callback) {
+ EnsureFileExistsHelper* helper = new EnsureFileExistsHelper;
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = context_ptr->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&EnsureFileExistsHelper::RunWork, Unretained(helper),
+ sync_file_util_.get(), base::Owned(context_ptr), url),
+ Bind(&EnsureFileExistsHelper::Reply, Owned(helper), callback));
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::CreateDirectory(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = base::PostTaskAndReplyWithResult(
+ context_ptr->task_runner(), FROM_HERE,
+ Bind(&FileSystemFileUtil::CreateDirectory,
+ Unretained(sync_file_util_.get()),
+ base::Owned(context_ptr), url, exclusive, recursive),
+ callback);
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::GetFileInfo(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const GetFileInfoCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ GetFileInfoHelper* helper = new GetFileInfoHelper;
+ const bool success = context_ptr->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&GetFileInfoHelper::GetFileInfo, Unretained(helper),
+ sync_file_util_.get(), base::Owned(context_ptr), url),
+ Bind(&GetFileInfoHelper::ReplyFileInfo, Owned(helper), callback));
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::ReadDirectory(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const ReadDirectoryCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ ReadDirectoryHelper* helper = new ReadDirectoryHelper;
+ const bool success = context_ptr->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&ReadDirectoryHelper::RunWork, Unretained(helper),
+ sync_file_util_.get(), base::Owned(context_ptr), url),
+ Bind(&ReadDirectoryHelper::Reply, Owned(helper), callback));
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::Touch(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = base::PostTaskAndReplyWithResult(
+ context_ptr->task_runner(), FROM_HERE,
+ Bind(&FileSystemFileUtil::Touch, Unretained(sync_file_util_.get()),
+ base::Owned(context_ptr), url,
+ last_access_time, last_modified_time),
+ callback);
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::Truncate(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ int64 length,
+ const StatusCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = base::PostTaskAndReplyWithResult(
+ context_ptr->task_runner(), FROM_HERE,
+ Bind(&FileSystemFileUtil::Truncate, Unretained(sync_file_util_.get()),
+ base::Owned(context_ptr), url, length),
+ callback);
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::CopyFileLocal(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = base::PostTaskAndReplyWithResult(
+ context_ptr->task_runner(), FROM_HERE,
+ Bind(&FileSystemFileUtil::CopyOrMoveFile,
+ Unretained(sync_file_util_.get()),
+ base::Owned(context_ptr), src_url, dest_url, true /* copy */),
+ callback);
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::MoveFileLocal(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = base::PostTaskAndReplyWithResult(
+ context_ptr->task_runner(), FROM_HERE,
+ Bind(&FileSystemFileUtil::CopyOrMoveFile,
+ Unretained(sync_file_util_.get()),
+ base::Owned(context_ptr), src_url, dest_url, false /* copy */),
+ callback);
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::CopyInForeignFile(
+ scoped_ptr<FileSystemOperationContext> context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = base::PostTaskAndReplyWithResult(
+ context_ptr->task_runner(), FROM_HERE,
+ Bind(&FileSystemFileUtil::CopyInForeignFile,
+ Unretained(sync_file_util_.get()),
+ base::Owned(context_ptr), src_file_path, dest_url),
+ callback);
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::DeleteFile(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = base::PostTaskAndReplyWithResult(
+ context_ptr->task_runner(), FROM_HERE,
+ Bind(&FileSystemFileUtil::DeleteFile,
+ Unretained(sync_file_util_.get()),
+ base::Owned(context_ptr), url),
+ callback);
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::DeleteDirectory(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ const bool success = base::PostTaskAndReplyWithResult(
+ context_ptr->task_runner(), FROM_HERE,
+ Bind(&FileSystemFileUtil::DeleteDirectory,
+ Unretained(sync_file_util_.get()),
+ base::Owned(context_ptr), url),
+ callback);
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::DeleteRecursively(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
+}
+
+void AsyncFileUtilAdapter::CreateSnapshotFile(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const CreateSnapshotFileCallback& callback) {
+ FileSystemOperationContext* context_ptr = context.release();
+ GetFileInfoHelper* helper = new GetFileInfoHelper;
+ const bool success = context_ptr->task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ Bind(&GetFileInfoHelper::CreateSnapshotFile, Unretained(helper),
+ sync_file_util_.get(), base::Owned(context_ptr), url),
+ Bind(&GetFileInfoHelper::ReplySnapshotFile, Owned(helper), callback));
+ DCHECK(success);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/async_file_util_adapter.h b/chromium/webkit/browser/fileapi/async_file_util_adapter.h
new file mode 100644
index 00000000000..9da3db9eac7
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/async_file_util_adapter.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_
+#define WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "webkit/browser/fileapi/async_file_util.h"
+
+namespace fileapi {
+
+class FileSystemFileUtil;
+
+// An adapter class for FileSystemFileUtil classes to provide asynchronous
+// interface.
+//
+// A filesystem can do either:
+// - implement a synchronous version of FileUtil by extending
+// FileSystemFileUtil and atach it to this adapter, or
+// - directly implement AsyncFileUtil.
+//
+// This instance (as thus this->sync_file_util_) is guaranteed to be alive
+// as far as FileSystemOperationContext given to each operation is kept alive.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE AsyncFileUtilAdapter
+ : public AsyncFileUtil {
+ public:
+ // Creates a new AsyncFileUtil for |sync_file_util|. This takes the
+ // ownership of |sync_file_util|. (This doesn't take scoped_ptr<> just
+ // to save extra make_scoped_ptr; in all use cases a new fresh FileUtil is
+ // created only for this adapter.)
+ explicit AsyncFileUtilAdapter(FileSystemFileUtil* sync_file_util);
+
+ virtual ~AsyncFileUtilAdapter();
+
+ FileSystemFileUtil* sync_file_util() {
+ return sync_file_util_.get();
+ }
+
+ // AsyncFileUtil overrides.
+ virtual void CreateOrOpen(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ int file_flags,
+ const CreateOrOpenCallback& callback) OVERRIDE;
+ virtual void EnsureFileExists(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const EnsureFileExistsCallback& callback) OVERRIDE;
+ virtual void CreateDirectory(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void GetFileInfo(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const GetFileInfoCallback& callback) OVERRIDE;
+ virtual void ReadDirectory(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const ReadDirectoryCallback& callback) OVERRIDE;
+ virtual void Touch(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void Truncate(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ int64 length,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void CopyFileLocal(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void MoveFileLocal(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void CopyInForeignFile(
+ scoped_ptr<FileSystemOperationContext> context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void DeleteFile(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void DeleteDirectory(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void DeleteRecursively(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void CreateSnapshotFile(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& url,
+ const CreateSnapshotFileCallback& callback) OVERRIDE;
+
+ private:
+ scoped_ptr<FileSystemFileUtil> sync_file_util_;
+
+ DISALLOW_COPY_AND_ASSIGN(AsyncFileUtilAdapter);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_
diff --git a/chromium/webkit/browser/fileapi/copy_or_move_file_validator.h b/chromium/webkit/browser/fileapi/copy_or_move_file_validator.h
new file mode 100644
index 00000000000..2bd4df17b7a
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/copy_or_move_file_validator.h
@@ -0,0 +1,54 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_
+#define WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_
+
+#include "base/callback.h"
+#include "base/platform_file.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace fileapi {
+
+class FileSystemURL;
+
+class WEBKIT_STORAGE_BROWSER_EXPORT CopyOrMoveFileValidator {
+ public:
+ // Callback that is invoked when validation completes. A result of
+ // base::PLATFORM_FILE_OK means the file validated.
+ typedef base::Callback<void(base::PlatformFileError result)> ResultCallback;
+
+ virtual ~CopyOrMoveFileValidator() {}
+
+ // Called on a source file before copying or moving to the final
+ // destination.
+ virtual void StartPreWriteValidation(
+ const ResultCallback& result_callback) = 0;
+
+ // Called on a destination file after copying or moving to the final
+ // destination. Suitable for running Anti-Virus checks.
+ virtual void StartPostWriteValidation(
+ const base::FilePath& dest_platform_path,
+ const ResultCallback& result_callback) = 0;
+};
+
+class CopyOrMoveFileValidatorFactory {
+ public:
+ virtual ~CopyOrMoveFileValidatorFactory() {}
+
+ // This method must always return a non-NULL validator. |src_url| is needed
+ // in addition to |platform_path| because in the obfuscated file system
+ // case, |platform_path| will be an obfuscated filename and extension.
+ virtual CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator(
+ const FileSystemURL& src_url,
+ const base::FilePath& platform_path) = 0;
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_
diff --git a/chromium/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc b/chromium/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc
new file mode 100644
index 00000000000..caecccfddfb
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc
@@ -0,0 +1,326 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/async_file_test_helper.h"
+#include "webkit/browser/fileapi/copy_or_move_file_validator.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/test_file_system_backend.h"
+#include "webkit/browser/quota/mock_special_storage_policy.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+namespace {
+
+const FileSystemType kNoValidatorType = kFileSystemTypeTemporary;
+const FileSystemType kWithValidatorType = kFileSystemTypeTest;
+
+void ExpectOk(const GURL& origin_url,
+ const std::string& name,
+ base::PlatformFileError error) {
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+}
+
+class CopyOrMoveFileValidatorTestHelper {
+ public:
+ CopyOrMoveFileValidatorTestHelper(
+ const GURL& origin,
+ FileSystemType src_type,
+ FileSystemType dest_type)
+ : origin_(origin),
+ src_type_(src_type),
+ dest_type_(dest_type) {}
+
+ ~CopyOrMoveFileValidatorTestHelper() {
+ file_system_context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void SetUp() {
+ ASSERT_TRUE(base_.CreateUniqueTempDir());
+ base::FilePath base_dir = base_.path();
+
+ file_system_context_ = CreateFileSystemContextForTesting(NULL, base_dir);
+
+ // Set up TestFileSystemBackend to require CopyOrMoveFileValidator.
+ FileSystemBackend* test_file_system_backend =
+ file_system_context_->GetFileSystemBackend(kWithValidatorType);
+ static_cast<TestFileSystemBackend*>(test_file_system_backend)->
+ set_require_copy_or_move_validator(true);
+
+ // Sets up source.
+ FileSystemBackend* src_file_system_backend =
+ file_system_context_->GetFileSystemBackend(src_type_);
+ src_file_system_backend->OpenFileSystem(
+ origin_, src_type_,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&ExpectOk));
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_EQ(base::PLATFORM_FILE_OK, CreateDirectory(SourceURL("")));
+
+ // Sets up dest.
+ DCHECK_EQ(kWithValidatorType, dest_type_);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, CreateDirectory(DestURL("")));
+
+ copy_src_ = SourceURL("copy_src.jpg");
+ move_src_ = SourceURL("move_src.jpg");
+ copy_dest_ = DestURL("copy_dest.jpg");
+ move_dest_ = DestURL("move_dest.jpg");
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK, CreateFile(copy_src_, 10));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, CreateFile(move_src_, 10));
+
+ ASSERT_TRUE(FileExists(copy_src_, 10));
+ ASSERT_TRUE(FileExists(move_src_, 10));
+ ASSERT_FALSE(FileExists(copy_dest_, 10));
+ ASSERT_FALSE(FileExists(move_dest_, 10));
+ }
+
+ void SetMediaCopyOrMoveFileValidatorFactory(
+ scoped_ptr<CopyOrMoveFileValidatorFactory> factory) {
+ TestFileSystemBackend* backend = static_cast<TestFileSystemBackend*>(
+ file_system_context_->GetFileSystemBackend(kWithValidatorType));
+ backend->InitializeCopyOrMoveFileValidatorFactory(factory.Pass());
+ }
+
+ void CopyTest(base::PlatformFileError expected) {
+ ASSERT_TRUE(FileExists(copy_src_, 10));
+ ASSERT_FALSE(FileExists(copy_dest_, 10));
+
+ EXPECT_EQ(expected,
+ AsyncFileTestHelper::Copy(
+ file_system_context_.get(), copy_src_, copy_dest_));
+
+ EXPECT_TRUE(FileExists(copy_src_, 10));
+ if (expected == base::PLATFORM_FILE_OK)
+ EXPECT_TRUE(FileExists(copy_dest_, 10));
+ else
+ EXPECT_FALSE(FileExists(copy_dest_, 10));
+ };
+
+ void MoveTest(base::PlatformFileError expected) {
+ ASSERT_TRUE(FileExists(move_src_, 10));
+ ASSERT_FALSE(FileExists(move_dest_, 10));
+
+ EXPECT_EQ(expected,
+ AsyncFileTestHelper::Move(
+ file_system_context_.get(), move_src_, move_dest_));
+
+ if (expected == base::PLATFORM_FILE_OK) {
+ EXPECT_FALSE(FileExists(move_src_, 10));
+ EXPECT_TRUE(FileExists(move_dest_, 10));
+ } else {
+ EXPECT_TRUE(FileExists(move_src_, 10));
+ EXPECT_FALSE(FileExists(move_dest_, 10));
+ }
+ };
+
+ private:
+ FileSystemURL SourceURL(const std::string& path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ origin_, src_type_,
+ base::FilePath().AppendASCII("src").AppendASCII(path));
+ }
+
+ FileSystemURL DestURL(const std::string& path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ origin_, dest_type_,
+ base::FilePath().AppendASCII("dest").AppendASCII(path));
+ }
+
+ base::PlatformFileError CreateFile(const FileSystemURL& url, size_t size) {
+ base::PlatformFileError result =
+ AsyncFileTestHelper::CreateFile(file_system_context_.get(), url);
+ if (result != base::PLATFORM_FILE_OK)
+ return result;
+ return AsyncFileTestHelper::TruncateFile(
+ file_system_context_.get(), url, size);
+ }
+
+ base::PlatformFileError CreateDirectory(const FileSystemURL& url) {
+ return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(),
+ url);
+ }
+
+ bool FileExists(const FileSystemURL& url, int64 expected_size) {
+ return AsyncFileTestHelper::FileExists(
+ file_system_context_.get(), url, expected_size);
+ }
+
+ base::ScopedTempDir base_;
+
+ const GURL origin_;
+
+ const FileSystemType src_type_;
+ const FileSystemType dest_type_;
+ std::string src_fsid_;
+ std::string dest_fsid_;
+
+ base::MessageLoop message_loop_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+
+ FileSystemURL copy_src_;
+ FileSystemURL copy_dest_;
+ FileSystemURL move_src_;
+ FileSystemURL move_dest_;
+
+ DISALLOW_COPY_AND_ASSIGN(CopyOrMoveFileValidatorTestHelper);
+};
+
+// For TestCopyOrMoveFileValidatorFactory
+enum Validity {
+ VALID,
+ PRE_WRITE_INVALID,
+ POST_WRITE_INVALID
+};
+
+class TestCopyOrMoveFileValidatorFactory
+ : public CopyOrMoveFileValidatorFactory {
+ public:
+ // A factory that creates validators that accept everything or nothing.
+ // TODO(gbillock): switch args to enum or something
+ explicit TestCopyOrMoveFileValidatorFactory(Validity validity)
+ : validity_(validity) {}
+ virtual ~TestCopyOrMoveFileValidatorFactory() {}
+
+ virtual CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator(
+ const FileSystemURL& /*src_url*/,
+ const base::FilePath& /*platform_path*/) OVERRIDE {
+ return new TestCopyOrMoveFileValidator(validity_);
+ }
+
+ private:
+ class TestCopyOrMoveFileValidator : public CopyOrMoveFileValidator {
+ public:
+ explicit TestCopyOrMoveFileValidator(Validity validity)
+ : result_(validity == VALID || validity == POST_WRITE_INVALID
+ ? base::PLATFORM_FILE_OK
+ : base::PLATFORM_FILE_ERROR_SECURITY),
+ write_result_(validity == VALID || validity == PRE_WRITE_INVALID
+ ? base::PLATFORM_FILE_OK
+ : base::PLATFORM_FILE_ERROR_SECURITY) {
+ }
+ virtual ~TestCopyOrMoveFileValidator() {}
+
+ virtual void StartPreWriteValidation(
+ const ResultCallback& result_callback) OVERRIDE {
+ // Post the result since a real validator must do work asynchronously.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(result_callback, result_));
+ }
+
+ virtual void StartPostWriteValidation(
+ const base::FilePath& dest_platform_path,
+ const ResultCallback& result_callback) OVERRIDE {
+ // Post the result since a real validator must do work asynchronously.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(result_callback, write_result_));
+ }
+
+ private:
+ base::PlatformFileError result_;
+ base::PlatformFileError write_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidator);
+ };
+
+ Validity validity_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestCopyOrMoveFileValidatorFactory);
+};
+
+} // namespace
+
+TEST(CopyOrMoveFileValidatorTest, NoValidatorWithinSameFSType) {
+ // Within a file system type, validation is not expected, so it should
+ // work for kWithValidatorType without a validator set.
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kWithValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ helper.CopyTest(base::PLATFORM_FILE_OK);
+ helper.MoveTest(base::PLATFORM_FILE_OK);
+}
+
+TEST(CopyOrMoveFileValidatorTest, MissingValidator) {
+ // Copying or moving into a kWithValidatorType requires a file
+ // validator. An error is expected if copy is attempted without a validator.
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY);
+ helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY);
+}
+
+TEST(CopyOrMoveFileValidatorTest, AcceptAll) {
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ scoped_ptr<CopyOrMoveFileValidatorFactory> factory(
+ new TestCopyOrMoveFileValidatorFactory(VALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(factory.Pass());
+
+ helper.CopyTest(base::PLATFORM_FILE_OK);
+ helper.MoveTest(base::PLATFORM_FILE_OK);
+}
+
+TEST(CopyOrMoveFileValidatorTest, AcceptNone) {
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ scoped_ptr<CopyOrMoveFileValidatorFactory> factory(
+ new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(factory.Pass());
+
+ helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY);
+ helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY);
+}
+
+TEST(CopyOrMoveFileValidatorTest, OverrideValidator) {
+ // Once set, you can not override the validator.
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ scoped_ptr<CopyOrMoveFileValidatorFactory> reject_factory(
+ new TestCopyOrMoveFileValidatorFactory(PRE_WRITE_INVALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(reject_factory.Pass());
+
+ scoped_ptr<CopyOrMoveFileValidatorFactory> accept_factory(
+ new TestCopyOrMoveFileValidatorFactory(VALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(accept_factory.Pass());
+
+ helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY);
+ helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY);
+}
+
+TEST(CopyOrMoveFileValidatorTest, RejectPostWrite) {
+ CopyOrMoveFileValidatorTestHelper helper(GURL("http://foo"),
+ kNoValidatorType,
+ kWithValidatorType);
+ helper.SetUp();
+ scoped_ptr<CopyOrMoveFileValidatorFactory> factory(
+ new TestCopyOrMoveFileValidatorFactory(POST_WRITE_INVALID));
+ helper.SetMediaCopyOrMoveFileValidatorFactory(factory.Pass());
+
+ helper.CopyTest(base::PLATFORM_FILE_ERROR_SECURITY);
+ helper.MoveTest(base::PLATFORM_FILE_ERROR_SECURITY);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.cc b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.cc
new file mode 100644
index 00000000000..da1ef97b984
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.cc
@@ -0,0 +1,325 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/copy_or_move_operation_delegate.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "webkit/browser/fileapi/copy_or_move_file_validator.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/recursive_operation_delegate.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& src_root,
+ const FileSystemURL& dest_root,
+ OperationType operation_type,
+ const StatusCallback& callback)
+ : RecursiveOperationDelegate(file_system_context),
+ src_root_(src_root),
+ dest_root_(dest_root),
+ operation_type_(operation_type),
+ callback_(callback),
+ weak_factory_(this) {
+ same_file_system_ = src_root_.IsInSameFileSystem(dest_root_);
+}
+
+CopyOrMoveOperationDelegate::~CopyOrMoveOperationDelegate() {
+}
+
+void CopyOrMoveOperationDelegate::Run() {
+ // Not supported; this should never be called.
+ NOTREACHED();
+}
+
+void CopyOrMoveOperationDelegate::RunRecursively() {
+ // Perform light-weight checks first.
+
+ // It is an error to try to copy/move an entry into its child.
+ if (same_file_system_ && src_root_.path().IsParent(dest_root_.path())) {
+ callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
+ return;
+ }
+
+ // It is an error to copy/move an entry into the same path.
+ if (same_file_system_ && src_root_.path() == dest_root_.path()) {
+ callback_.Run(base::PLATFORM_FILE_ERROR_EXISTS);
+ return;
+ }
+
+ // First try to copy/move it as a file.
+ CopyOrMoveFile(URLPair(src_root_, dest_root_),
+ base::Bind(&CopyOrMoveOperationDelegate::DidTryCopyOrMoveFile,
+ weak_factory_.GetWeakPtr()));
+}
+
+void CopyOrMoveOperationDelegate::ProcessFile(const FileSystemURL& src_url,
+ const StatusCallback& callback) {
+ CopyOrMoveFile(URLPair(src_url, CreateDestURL(src_url)), callback);
+}
+
+void CopyOrMoveOperationDelegate::ProcessDirectory(const FileSystemURL& src_url,
+ const StatusCallback& callback) {
+ FileSystemURL dest_url = CreateDestURL(src_url);
+
+ // If operation_type == Move we may need to record directories and
+ // restore directory timestamps in the end, though it may have
+ // negative performance impact.
+ // See http://crbug.com/171284 for more details.
+ operation_runner()->CreateDirectory(
+ dest_url, false /* exclusive */, false /* recursive */, callback);
+}
+
+void CopyOrMoveOperationDelegate::DidTryCopyOrMoveFile(
+ base::PlatformFileError error) {
+ if (error == base::PLATFORM_FILE_OK ||
+ error != base::PLATFORM_FILE_ERROR_NOT_A_FILE) {
+ callback_.Run(error);
+ return;
+ }
+
+ // The src_root_ looks to be a directory.
+ // Try removing the dest_root_ to see if it exists and/or it is an
+ // empty directory.
+ operation_runner()->RemoveDirectory(
+ dest_root_,
+ base::Bind(&CopyOrMoveOperationDelegate::DidTryRemoveDestRoot,
+ weak_factory_.GetWeakPtr()));
+}
+
+void CopyOrMoveOperationDelegate::DidTryRemoveDestRoot(
+ base::PlatformFileError error) {
+ if (error == base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY) {
+ callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
+ return;
+ }
+ if (error != base::PLATFORM_FILE_OK &&
+ error != base::PLATFORM_FILE_ERROR_NOT_FOUND) {
+ callback_.Run(error);
+ return;
+ }
+
+ // Start to process the source directory recursively.
+ // TODO(kinuko): This could be too expensive for same_file_system_==true
+ // and operation==MOVE case, probably we can just rename the root directory.
+ // http://crbug.com/172187
+ StartRecursiveOperation(
+ src_root_,
+ base::Bind(&CopyOrMoveOperationDelegate::DidFinishRecursiveCopyDir,
+ weak_factory_.GetWeakPtr(), src_root_, callback_));
+}
+
+void CopyOrMoveOperationDelegate::CopyOrMoveFile(
+ const URLPair& url_pair,
+ const StatusCallback& callback) {
+ // Same filesystem case.
+ if (same_file_system_) {
+ if (operation_type_ == OPERATION_MOVE) {
+ operation_runner()->MoveFileLocal(url_pair.src, url_pair.dest, callback);
+ } else {
+ operation_runner()->CopyFileLocal(url_pair.src, url_pair.dest, callback);
+ }
+ return;
+ }
+
+ // Cross filesystem case.
+ // Perform CreateSnapshotFile, CopyInForeignFile and then calls
+ // copy_callback which removes the source file if operation_type == MOVE.
+ StatusCallback copy_callback =
+ base::Bind(&CopyOrMoveOperationDelegate::DidFinishCopy,
+ weak_factory_.GetWeakPtr(), url_pair, callback);
+ operation_runner()->CreateSnapshotFile(
+ url_pair.src,
+ base::Bind(&CopyOrMoveOperationDelegate::DidCreateSnapshot,
+ weak_factory_.GetWeakPtr(), url_pair, copy_callback));
+}
+
+void CopyOrMoveOperationDelegate::DidCreateSnapshot(
+ const URLPair& url_pair,
+ const StatusCallback& callback,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) {
+ if (error != base::PLATFORM_FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+ current_file_ref_ = file_ref;
+
+ // For now we assume CreateSnapshotFile always return a valid local file path.
+ // TODO(kinuko): Otherwise create a FileStreamReader to perform a copy/move.
+ DCHECK(!platform_path.empty());
+
+ CopyOrMoveFileValidatorFactory* factory =
+ file_system_context()->GetCopyOrMoveFileValidatorFactory(
+ dest_root_.type(), &error);
+ if (error != base::PLATFORM_FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+ if (!factory) {
+ DidValidateFile(url_pair.dest, callback, file_info, platform_path, error);
+ return;
+ }
+
+ validator_.reset(
+ factory->CreateCopyOrMoveFileValidator(url_pair.src, platform_path));
+ validator_->StartPreWriteValidation(
+ base::Bind(&CopyOrMoveOperationDelegate::DidValidateFile,
+ weak_factory_.GetWeakPtr(),
+ url_pair.dest, callback, file_info, platform_path));
+}
+
+void CopyOrMoveOperationDelegate::DidValidateFile(
+ const FileSystemURL& dest,
+ const StatusCallback& callback,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ base::PlatformFileError error) {
+ if (error != base::PLATFORM_FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ operation_runner()->CopyInForeignFile(platform_path, dest, callback);
+}
+
+void CopyOrMoveOperationDelegate::DidFinishRecursiveCopyDir(
+ const FileSystemURL& src,
+ const StatusCallback& callback,
+ base::PlatformFileError error) {
+ if (error != base::PLATFORM_FILE_OK ||
+ operation_type_ == OPERATION_COPY) {
+ callback.Run(error);
+ return;
+ }
+
+ DCHECK_EQ(OPERATION_MOVE, operation_type_);
+
+ // Remove the source for finalizing move operation.
+ operation_runner()->Remove(
+ src, true /* recursive */,
+ base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void CopyOrMoveOperationDelegate::DidFinishCopy(
+ const URLPair& url_pair,
+ const StatusCallback& callback,
+ base::PlatformFileError error) {
+ if (error != base::PLATFORM_FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ // |validator_| is NULL in the same-filesystem case or when the destination
+ // filesystem does not do validation.
+ if (!validator_.get()) {
+ scoped_refptr<webkit_blob::ShareableFileReference> file_ref;
+ DidPostWriteValidation(url_pair, callback, file_ref,
+ base::PLATFORM_FILE_OK);
+ return;
+ }
+
+ DCHECK(!same_file_system_);
+ operation_runner()->CreateSnapshotFile(
+ url_pair.dest,
+ base::Bind(&CopyOrMoveOperationDelegate::DoPostWriteValidation,
+ weak_factory_.GetWeakPtr(), url_pair, callback));
+}
+
+void CopyOrMoveOperationDelegate::DoPostWriteValidation(
+ const URLPair& url_pair,
+ const StatusCallback& callback,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) {
+ if (error != base::PLATFORM_FILE_OK) {
+ operation_runner()->Remove(
+ url_pair.dest, true,
+ base::Bind(&CopyOrMoveOperationDelegate::DidRemoveDestForError,
+ weak_factory_.GetWeakPtr(), error, callback));
+ return;
+ }
+
+ DCHECK(validator_.get());
+ // Note: file_ref passed here to keep the file alive until after
+ // the StartPostWriteValidation operation finishes.
+ validator_->StartPostWriteValidation(
+ platform_path,
+ base::Bind(&CopyOrMoveOperationDelegate::DidPostWriteValidation,
+ weak_factory_.GetWeakPtr(), url_pair, callback, file_ref));
+}
+
+// |file_ref| is unused; it is passed here to make sure the reference is
+// alive until after post-write validation is complete.
+void CopyOrMoveOperationDelegate::DidPostWriteValidation(
+ const URLPair& url_pair,
+ const StatusCallback& callback,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& /*file_ref*/,
+ base::PlatformFileError error) {
+ if (error != base::PLATFORM_FILE_OK) {
+ operation_runner()->Remove(
+ url_pair.dest, true,
+ base::Bind(&CopyOrMoveOperationDelegate::DidRemoveDestForError,
+ weak_factory_.GetWeakPtr(), error, callback));
+ return;
+ }
+
+ if (operation_type_ == OPERATION_COPY) {
+ callback.Run(error);
+ return;
+ }
+
+ DCHECK_EQ(OPERATION_MOVE, operation_type_);
+
+ // Remove the source for finalizing move operation.
+ operation_runner()->Remove(
+ url_pair.src, true /* recursive */,
+ base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void CopyOrMoveOperationDelegate::DidRemoveSourceForMove(
+ const StatusCallback& callback,
+ base::PlatformFileError error) {
+ if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND)
+ error = base::PLATFORM_FILE_OK;
+ callback.Run(error);
+}
+
+FileSystemURL CopyOrMoveOperationDelegate::CreateDestURL(
+ const FileSystemURL& src_url) const {
+ DCHECK_EQ(src_root_.type(), src_url.type());
+ DCHECK_EQ(src_root_.origin(), src_url.origin());
+
+ base::FilePath relative = dest_root_.virtual_path();
+ src_root_.virtual_path().AppendRelativePath(src_url.virtual_path(),
+ &relative);
+ return file_system_context()->CreateCrackedFileSystemURL(
+ dest_root_.origin(),
+ dest_root_.mount_type(),
+ relative);
+}
+
+void CopyOrMoveOperationDelegate::DidRemoveDestForError(
+ base::PlatformFileError prior_error,
+ const StatusCallback& callback,
+ base::PlatformFileError error) {
+ if (error != base::PLATFORM_FILE_OK) {
+ VLOG(1) << "Error removing destination file after validation error: "
+ << error;
+ }
+ callback.Run(prior_error);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.h b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.h
new file mode 100644
index 00000000000..ceceb971367
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_
+#define WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_
+
+#include <stack>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "webkit/browser/fileapi/recursive_operation_delegate.h"
+
+namespace webkit_blob {
+class ShareableFileReference;
+}
+
+namespace fileapi {
+
+class CopyOrMoveFileValidator;
+
+// A delegate class for recursive copy or move operations.
+class CopyOrMoveOperationDelegate
+ : public RecursiveOperationDelegate {
+ public:
+ enum OperationType {
+ OPERATION_COPY,
+ OPERATION_MOVE
+ };
+
+ CopyOrMoveOperationDelegate(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& src_root,
+ const FileSystemURL& dest_root,
+ OperationType operation_type,
+ const StatusCallback& callback);
+ virtual ~CopyOrMoveOperationDelegate();
+
+ // RecursiveOperationDelegate overrides:
+ virtual void Run() OVERRIDE;
+ virtual void RunRecursively() OVERRIDE;
+ virtual void ProcessFile(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void ProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+
+ private:
+ struct URLPair {
+ URLPair(const FileSystemURL& src, const FileSystemURL& dest)
+ : src(src),
+ dest(dest) {
+ }
+ FileSystemURL src;
+ FileSystemURL dest;
+ };
+
+ void DidTryCopyOrMoveFile(base::PlatformFileError error);
+ void DidTryRemoveDestRoot(base::PlatformFileError error);
+ void CopyOrMoveFile(
+ const URLPair& url_pair,
+ const StatusCallback& callback);
+ void DidCreateSnapshot(
+ const URLPair& url_pair,
+ const StatusCallback& callback,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref);
+ void DidValidateFile(
+ const FileSystemURL& dest,
+ const StatusCallback& callback,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ base::PlatformFileError error);
+ void DidFinishRecursiveCopyDir(
+ const FileSystemURL& src,
+ const StatusCallback& callback,
+ base::PlatformFileError error);
+ void DidFinishCopy(
+ const URLPair& url_pair,
+ const StatusCallback& callback,
+ base::PlatformFileError error);
+ void DoPostWriteValidation(
+ const URLPair& url_pair,
+ const StatusCallback& callback,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref);
+ void DidPostWriteValidation(
+ const URLPair& url_pair,
+ const StatusCallback& callback,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref,
+ base::PlatformFileError error);
+ void DidRemoveSourceForMove(
+ const StatusCallback& callback,
+ base::PlatformFileError error);
+ void DidRemoveDestForError(
+ base::PlatformFileError prior_error,
+ const StatusCallback& callback,
+ base::PlatformFileError error);
+
+ FileSystemURL CreateDestURL(const FileSystemURL& src_url) const;
+
+ FileSystemURL src_root_;
+ FileSystemURL dest_root_;
+ bool same_file_system_;
+ OperationType operation_type_;
+ StatusCallback callback_;
+
+ scoped_refptr<webkit_blob::ShareableFileReference> current_file_ref_;
+
+ scoped_ptr<CopyOrMoveFileValidator> validator_;
+
+ base::WeakPtrFactory<CopyOrMoveOperationDelegate> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOperationDelegate);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_
diff --git a/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
new file mode 100644
index 00000000000..183becd7dbf
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
@@ -0,0 +1,562 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <map>
+#include <queue>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/async_file_test_helper.h"
+#include "webkit/browser/fileapi/copy_or_move_file_validator.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/test_file_set.h"
+#include "webkit/browser/fileapi/test_file_system_backend.h"
+#include "webkit/browser/quota/mock_quota_manager.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+typedef FileSystemOperation::FileEntryList FileEntryList;
+
+namespace {
+
+void ExpectOk(const GURL& origin_url,
+ const std::string& name,
+ base::PlatformFileError error) {
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+}
+
+class TestValidatorFactory : public CopyOrMoveFileValidatorFactory {
+ public:
+ // A factory that creates validators that accept everything or nothing.
+ TestValidatorFactory() {}
+ virtual ~TestValidatorFactory() {}
+
+ virtual CopyOrMoveFileValidator* CreateCopyOrMoveFileValidator(
+ const FileSystemURL& /*src_url*/,
+ const base::FilePath& /*platform_path*/) OVERRIDE {
+ // Move arg management to TestValidator?
+ return new TestValidator(true, true, std::string("2"));
+ }
+
+ private:
+ class TestValidator : public CopyOrMoveFileValidator {
+ public:
+ explicit TestValidator(bool pre_copy_valid,
+ bool post_copy_valid,
+ const std::string& reject_string)
+ : result_(pre_copy_valid ? base::PLATFORM_FILE_OK
+ : base::PLATFORM_FILE_ERROR_SECURITY),
+ write_result_(post_copy_valid ? base::PLATFORM_FILE_OK
+ : base::PLATFORM_FILE_ERROR_SECURITY),
+ reject_string_(reject_string) {
+ }
+ virtual ~TestValidator() {}
+
+ virtual void StartPreWriteValidation(
+ const ResultCallback& result_callback) OVERRIDE {
+ // Post the result since a real validator must do work asynchronously.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(result_callback, result_));
+ }
+
+ virtual void StartPostWriteValidation(
+ const base::FilePath& dest_platform_path,
+ const ResultCallback& result_callback) OVERRIDE {
+ base::PlatformFileError result = write_result_;
+ std::string unsafe = dest_platform_path.BaseName().AsUTF8Unsafe();
+ if (unsafe.find(reject_string_) != std::string::npos) {
+ result = base::PLATFORM_FILE_ERROR_SECURITY;
+ }
+ // Post the result since a real validator must do work asynchronously.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(result_callback, result));
+ }
+
+ private:
+ base::PlatformFileError result_;
+ base::PlatformFileError write_result_;
+ std::string reject_string_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestValidator);
+ };
+};
+
+} // namespace
+
+class CopyOrMoveOperationTestHelper {
+ public:
+ CopyOrMoveOperationTestHelper(
+ const GURL& origin,
+ FileSystemType src_type,
+ FileSystemType dest_type)
+ : origin_(origin),
+ src_type_(src_type),
+ dest_type_(dest_type) {}
+
+ ~CopyOrMoveOperationTestHelper() {
+ file_system_context_ = NULL;
+ quota_manager_proxy_->SimulateQuotaManagerDestroyed();
+ quota_manager_ = NULL;
+ quota_manager_proxy_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void SetUp() {
+ SetUp(true, true);
+ }
+
+ void SetUpNoValidator() {
+ SetUp(true, false);
+ }
+
+ void SetUp(bool require_copy_or_move_validator,
+ bool init_copy_or_move_validator) {
+ ASSERT_TRUE(base_.CreateUniqueTempDir());
+ base::FilePath base_dir = base_.path();
+ quota_manager_ =
+ new quota::MockQuotaManager(false /* is_incognito */,
+ base_dir,
+ base::MessageLoopProxy::current().get(),
+ base::MessageLoopProxy::current().get(),
+ NULL /* special storage policy */);
+ quota_manager_proxy_ = new quota::MockQuotaManagerProxy(
+ quota_manager_.get(), base::MessageLoopProxy::current().get());
+ file_system_context_ =
+ CreateFileSystemContextForTesting(quota_manager_proxy_.get(), base_dir);
+
+ // Prepare the origin's root directory.
+ FileSystemBackend* backend =
+ file_system_context_->GetFileSystemBackend(src_type_);
+ backend->OpenFileSystem(origin_, src_type_,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&ExpectOk));
+ backend = file_system_context_->GetFileSystemBackend(dest_type_);
+ if (dest_type_ == kFileSystemTypeTest) {
+ TestFileSystemBackend* test_backend =
+ static_cast<TestFileSystemBackend*>(backend);
+ scoped_ptr<CopyOrMoveFileValidatorFactory> factory(
+ new TestValidatorFactory);
+ test_backend->set_require_copy_or_move_validator(
+ require_copy_or_move_validator);
+ if (init_copy_or_move_validator)
+ test_backend->InitializeCopyOrMoveFileValidatorFactory(factory.Pass());
+ }
+ backend->OpenFileSystem(origin_, dest_type_,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&ExpectOk));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Grant relatively big quota initially.
+ quota_manager_->SetQuota(origin_,
+ FileSystemTypeToQuotaStorageType(src_type_),
+ 1024 * 1024);
+ quota_manager_->SetQuota(origin_,
+ FileSystemTypeToQuotaStorageType(dest_type_),
+ 1024 * 1024);
+ }
+
+ int64 GetSourceUsage() {
+ int64 usage = 0;
+ GetUsageAndQuota(src_type_, &usage, NULL);
+ return usage;
+ }
+
+ int64 GetDestUsage() {
+ int64 usage = 0;
+ GetUsageAndQuota(dest_type_, &usage, NULL);
+ return usage;
+ }
+
+ FileSystemURL SourceURL(const std::string& path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ origin_, src_type_, base::FilePath::FromUTF8Unsafe(path));
+ }
+
+ FileSystemURL DestURL(const std::string& path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ origin_, dest_type_, base::FilePath::FromUTF8Unsafe(path));
+ }
+
+ base::PlatformFileError Copy(const FileSystemURL& src,
+ const FileSystemURL& dest) {
+ return AsyncFileTestHelper::Copy(file_system_context_.get(), src, dest);
+ }
+
+ base::PlatformFileError Move(const FileSystemURL& src,
+ const FileSystemURL& dest) {
+ return AsyncFileTestHelper::Move(file_system_context_.get(), src, dest);
+ }
+
+ base::PlatformFileError SetUpTestCaseFiles(
+ const FileSystemURL& root,
+ const test::TestCaseRecord* const test_cases,
+ size_t test_case_size) {
+ base::PlatformFileError result = base::PLATFORM_FILE_ERROR_FAILED;
+ for (size_t i = 0; i < test_case_size; ++i) {
+ const test::TestCaseRecord& test_case = test_cases[i];
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ root.origin(),
+ root.mount_type(),
+ root.virtual_path().Append(test_case.path));
+ if (test_case.is_directory)
+ result = CreateDirectory(url);
+ else
+ result = CreateFile(url, test_case.data_file_size);
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result) << url.DebugString();
+ if (result != base::PLATFORM_FILE_OK)
+ return result;
+ }
+ return result;
+ }
+
+ void VerifyTestCaseFiles(
+ const FileSystemURL& root,
+ const test::TestCaseRecord* const test_cases,
+ size_t test_case_size) {
+ std::map<base::FilePath, const test::TestCaseRecord*> test_case_map;
+ for (size_t i = 0; i < test_case_size; ++i) {
+ test_case_map[
+ base::FilePath(test_cases[i].path).NormalizePathSeparators()] =
+ &test_cases[i];
+ }
+
+ std::queue<FileSystemURL> directories;
+ FileEntryList entries;
+ directories.push(root);
+ while (!directories.empty()) {
+ FileSystemURL dir = directories.front();
+ directories.pop();
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ReadDirectory(dir, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ dir.origin(),
+ dir.mount_type(),
+ dir.virtual_path().Append(entries[i].name));
+ base::FilePath relative;
+ root.virtual_path().AppendRelativePath(url.virtual_path(), &relative);
+ relative = relative.NormalizePathSeparators();
+ ASSERT_TRUE(ContainsKey(test_case_map, relative));
+ if (entries[i].is_directory) {
+ EXPECT_TRUE(test_case_map[relative]->is_directory);
+ directories.push(url);
+ } else {
+ EXPECT_FALSE(test_case_map[relative]->is_directory);
+ EXPECT_TRUE(FileExists(url, test_case_map[relative]->data_file_size));
+ }
+ test_case_map.erase(relative);
+ }
+ }
+ EXPECT_TRUE(test_case_map.empty());
+ std::map<base::FilePath, const test::TestCaseRecord*>::const_iterator it;
+ for (it = test_case_map.begin(); it != test_case_map.end(); ++it) {
+ LOG(ERROR) << "Extra entry: " << it->first.LossyDisplayName();
+ }
+ }
+
+ base::PlatformFileError ReadDirectory(const FileSystemURL& url,
+ FileEntryList* entries) {
+ return AsyncFileTestHelper::ReadDirectory(
+ file_system_context_.get(), url, entries);
+ }
+
+ base::PlatformFileError CreateDirectory(const FileSystemURL& url) {
+ return AsyncFileTestHelper::CreateDirectory(file_system_context_.get(),
+ url);
+ }
+
+ base::PlatformFileError CreateFile(const FileSystemURL& url, size_t size) {
+ base::PlatformFileError result =
+ AsyncFileTestHelper::CreateFile(file_system_context_.get(), url);
+ if (result != base::PLATFORM_FILE_OK)
+ return result;
+ return AsyncFileTestHelper::TruncateFile(
+ file_system_context_.get(), url, size);
+ }
+
+ bool FileExists(const FileSystemURL& url, int64 expected_size) {
+ return AsyncFileTestHelper::FileExists(
+ file_system_context_.get(), url, expected_size);
+ }
+
+ bool DirectoryExists(const FileSystemURL& url) {
+ return AsyncFileTestHelper::DirectoryExists(file_system_context_.get(),
+ url);
+ }
+
+ private:
+ void GetUsageAndQuota(FileSystemType type, int64* usage, int64* quota) {
+ quota::QuotaStatusCode status = AsyncFileTestHelper::GetUsageAndQuota(
+ quota_manager_.get(), origin_, type, usage, quota);
+ ASSERT_EQ(quota::kQuotaStatusOk, status);
+ }
+
+ private:
+ base::ScopedTempDir base_;
+
+ const GURL origin_;
+ const FileSystemType src_type_;
+ const FileSystemType dest_type_;
+
+ base::MessageLoop message_loop_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ scoped_refptr<quota::MockQuotaManagerProxy> quota_manager_proxy_;
+ scoped_refptr<quota::MockQuotaManager> quota_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOperationTestHelper);
+};
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFile) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ kFileSystemTypeTemporary,
+ kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64 src_initial_usage = helper.GetSourceUsage();
+ int64 dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source file.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateFile(src, 10));
+ int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Copy it.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Copy(src, dest));
+
+ // Verify.
+ ASSERT_TRUE(helper.FileExists(src, 10));
+ ASSERT_TRUE(helper.FileExists(dest, 10));
+
+ int64 src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
+
+ int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleFile) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ kFileSystemTypeTemporary,
+ kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64 src_initial_usage = helper.GetSourceUsage();
+ int64 dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source file.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateFile(src, 10));
+ int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Move it.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Move(src, dest));
+
+ // Verify.
+ ASSERT_FALSE(helper.FileExists(src, AsyncFileTestHelper::kDontCheckSize));
+ ASSERT_TRUE(helper.FileExists(dest, 10));
+
+ int64 src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage, src_new_usage);
+
+ int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleDirectory) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ kFileSystemTypeTemporary,
+ kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64 src_initial_usage = helper.GetSourceUsage();
+ int64 dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source directory.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src));
+ int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Copy it.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Copy(src, dest));
+
+ // Verify.
+ ASSERT_TRUE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ int64 src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
+
+ int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, MoveSingleDirectory) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ kFileSystemTypeTemporary,
+ kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64 src_initial_usage = helper.GetSourceUsage();
+ int64 dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source directory.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src));
+ int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Move it.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Move(src, dest));
+
+ // Verify.
+ ASSERT_FALSE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ int64 src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage, src_new_usage);
+
+ int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, CopyDirectory) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ kFileSystemTypeTemporary,
+ kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64 src_initial_usage = helper.GetSourceUsage();
+ int64 dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source directory.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ helper.SetUpTestCaseFiles(src,
+ test::kRegularTestCases,
+ test::kRegularTestCaseSize));
+ int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Copy it.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Copy(src, dest));
+
+ // Verify.
+ ASSERT_TRUE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ helper.VerifyTestCaseFiles(dest,
+ test::kRegularTestCases,
+ test::kRegularTestCaseSize);
+
+ int64 src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage + src_increase, src_new_usage);
+
+ int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, MoveDirectory) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ kFileSystemTypeTemporary,
+ kFileSystemTypePersistent);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+ int64 src_initial_usage = helper.GetSourceUsage();
+ int64 dest_initial_usage = helper.GetDestUsage();
+
+ // Set up a source directory.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ helper.SetUpTestCaseFiles(src,
+ test::kRegularTestCases,
+ test::kRegularTestCaseSize));
+ int64 src_increase = helper.GetSourceUsage() - src_initial_usage;
+
+ // Move it.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.Move(src, dest));
+
+ // Verify.
+ ASSERT_FALSE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ helper.VerifyTestCaseFiles(dest,
+ test::kRegularTestCases,
+ test::kRegularTestCaseSize);
+
+ int64 src_new_usage = helper.GetSourceUsage();
+ ASSERT_EQ(src_initial_usage, src_new_usage);
+
+ int64 dest_increase = helper.GetDestUsage() - dest_initial_usage;
+ ASSERT_EQ(src_increase, dest_increase);
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest,
+ MoveDirectoryFailPostWriteValidation) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ kFileSystemTypeTemporary,
+ kFileSystemTypeTest);
+ helper.SetUp();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+
+ // Set up a source directory.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateDirectory(src));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ helper.SetUpTestCaseFiles(src,
+ test::kRegularTestCases,
+ test::kRegularTestCaseSize));
+
+ // Move it.
+ helper.Move(src, dest);
+
+ // Verify.
+ ASSERT_TRUE(helper.DirectoryExists(src));
+ ASSERT_TRUE(helper.DirectoryExists(dest));
+
+ test::TestCaseRecord kMoveDirResultCases[] = {
+ {false, FILE_PATH_LITERAL("file 0"), 38},
+ {false, FILE_PATH_LITERAL("file 3"), 0},
+ };
+
+ helper.VerifyTestCaseFiles(dest,
+ kMoveDirResultCases,
+ arraysize(kMoveDirResultCases));
+}
+
+TEST(LocalFileSystemCopyOrMoveOperationTest, CopySingleFileNoValidator) {
+ CopyOrMoveOperationTestHelper helper(GURL("http://foo"),
+ kFileSystemTypeTemporary,
+ kFileSystemTypeTest);
+ helper.SetUpNoValidator();
+
+ FileSystemURL src = helper.SourceURL("a");
+ FileSystemURL dest = helper.DestURL("b");
+
+ // Set up a source file.
+ ASSERT_EQ(base::PLATFORM_FILE_OK, helper.CreateFile(src, 10));
+
+ // The copy attempt should fail with a security error -- getting
+ // the factory returns a security error, and the copy operation must
+ // respect that.
+ ASSERT_EQ(base::PLATFORM_FILE_ERROR_SECURITY, helper.Copy(src, dest));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/dump_file_system.cc b/chromium/webkit/browser/fileapi/dump_file_system.cc
new file mode 100644
index 00000000000..4dd60f26b36
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/dump_file_system.cc
@@ -0,0 +1,205 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// A tool to dump HTML5 filesystem from CUI.
+//
+// Usage:
+//
+// ./out/Release/dump_file_system [options] <filesystem dir> [origin]...
+//
+// If no origin is specified, this dumps all origins in the profile dir.
+//
+// Available options:
+//
+// -t : dumps temporary files instead of persistent.
+// -s : dumps syncable files instead of persistent.
+// -l : more information will be displayed.
+//
+// The format of -l option is:
+//
+// === ORIGIN origin_name origin_dir ===
+// file_name file_id file_size file_content_path
+// ...
+//
+// where file_name has a trailing slash, file_size is the number of
+// children, and file_content_path is empty if the file is a directory.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <stack>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+#include "webkit/browser/fileapi/obfuscated_file_util.h"
+#include "webkit/browser/fileapi/sandbox_directory_database.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+#include "webkit/browser/fileapi/sandbox_origin_database.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace {
+
+bool g_opt_long;
+fileapi::FileSystemType g_opt_fs_type = fileapi::kFileSystemTypePersistent;
+
+void ShowMessageAndExit(const std::string& msg) {
+ fprintf(stderr, "%s\n", msg.c_str());
+ exit(EXIT_FAILURE);
+}
+
+void ShowUsageAndExit(const std::string& arg0) {
+ ShowMessageAndExit(
+ "Usage: " + arg0 +
+ " [-l] [-t] [-s] <filesystem dir> [origin]...");
+}
+
+} // namespace
+
+namespace fileapi {
+
+static void DumpDirectoryTree(const std::string& origin_name,
+ base::FilePath origin_dir) {
+ origin_dir = origin_dir.Append(
+ ObfuscatedFileUtil::GetDirectoryNameForType(g_opt_fs_type));
+
+ printf("=== ORIGIN %s %s ===\n",
+ origin_name.c_str(), FilePathToString(origin_dir).c_str());
+
+ if (!base::DirectoryExists(origin_dir))
+ return;
+
+ SandboxDirectoryDatabase directory_db(origin_dir);
+ SandboxDirectoryDatabase::FileId root_id;
+ if (!directory_db.GetFileWithPath(StringToFilePath("/"), &root_id))
+ return;
+
+ std::stack<std::pair<SandboxDirectoryDatabase::FileId,
+ std::string> > paths;
+ paths.push(std::make_pair(root_id, ""));
+ while (!paths.empty()) {
+ SandboxDirectoryDatabase::FileId id = paths.top().first;
+ const std::string dirname = paths.top().second;
+ paths.pop();
+
+ SandboxDirectoryDatabase::FileInfo info;
+ if (!directory_db.GetFileInfo(id, &info)) {
+ ShowMessageAndExit(base::StringPrintf("GetFileInfo failed for %"PRId64,
+ id));
+ }
+
+ const std::string name =
+ dirname + "/" + FilePathToString(base::FilePath(info.name));
+ std::vector<SandboxDirectoryDatabase::FileId> children;
+ if (info.is_directory()) {
+ if (!directory_db.ListChildren(id, &children)) {
+ ShowMessageAndExit(base::StringPrintf(
+ "ListChildren failed for %s (%"PRId64")",
+ info.name.c_str(), id));
+ }
+
+ for (size_t j = children.size(); j; j--)
+ paths.push(make_pair(children[j-1], name));
+ }
+
+ // +1 for the leading extra slash.
+ const char* display_name = name.c_str() + 1;
+ const char* directory_suffix = info.is_directory() ? "/" : "";
+ if (g_opt_long) {
+ int64 size;
+ if (info.is_directory()) {
+ size = static_cast<int64>(children.size());
+ } else {
+ file_util::GetFileSize(origin_dir.Append(info.data_path), &size);
+ }
+ // TODO(hamaji): Modification time?
+ printf("%s%s %"PRId64" %"PRId64" %s\n",
+ display_name,
+ directory_suffix,
+ id,
+ size,
+ FilePathToString(info.data_path).c_str());
+ } else {
+ printf("%s%s\n", display_name, directory_suffix);
+ }
+ }
+}
+
+static void DumpOrigin(const base::FilePath& file_system_dir,
+ const std::string& origin_name) {
+ SandboxOriginDatabase origin_db(file_system_dir);
+ base::FilePath origin_dir;
+ if (!origin_db.HasOriginPath(origin_name)) {
+ ShowMessageAndExit("Origin " + origin_name + " is not in " +
+ FilePathToString(file_system_dir));
+ }
+
+ if (!origin_db.GetPathForOrigin(origin_name, &origin_dir)) {
+ ShowMessageAndExit("Failed to get path of origin " + origin_name +
+ " in " + FilePathToString(file_system_dir));
+ }
+ DumpDirectoryTree(origin_name, file_system_dir.Append(origin_dir));
+}
+
+static void DumpFileSystem(const base::FilePath& file_system_dir) {
+ SandboxOriginDatabase origin_db(file_system_dir);
+ std::vector<SandboxOriginDatabase::OriginRecord> origins;
+ origin_db.ListAllOrigins(&origins);
+ for (size_t i = 0; i < origins.size(); i++) {
+ const SandboxOriginDatabase::OriginRecord& origin = origins[i];
+ DumpDirectoryTree(origin.origin, file_system_dir.Append(origin.path));
+ puts("");
+ }
+}
+
+} // namespace fileapi
+
+int main(int argc, char* argv[]) {
+ const char* arg0 = argv[0];
+ std::string username = "Default";
+ while (true) {
+ if (argc < 2)
+ ShowUsageAndExit(arg0);
+
+ if (std::string(argv[1]) == "-l") {
+ g_opt_long = true;
+ argc--;
+ argv++;
+ } else if (std::string(argv[1]) == "-t") {
+ g_opt_fs_type = fileapi::kFileSystemTypeTemporary;
+ argc--;
+ argv++;
+ } else if (std::string(argv[1]) == "-s") {
+ g_opt_fs_type = fileapi::kFileSystemTypeSyncable;
+ argc--;
+ argv++;
+ } else {
+ break;
+ }
+ }
+
+ if (argc < 2)
+ ShowUsageAndExit(arg0);
+
+ const base::FilePath file_system_dir = fileapi::StringToFilePath(argv[1]);
+ if (!base::DirectoryExists(file_system_dir)) {
+ ShowMessageAndExit(fileapi::FilePathToString(file_system_dir) +
+ " is not a filesystem directory");
+ }
+
+ if (argc == 2) {
+ fileapi::DumpFileSystem(file_system_dir);
+ } else {
+ for (int i = 2; i < argc; i++) {
+ fileapi::DumpOrigin(file_system_dir, argv[i]);
+ }
+ }
+ return 0;
+}
diff --git a/chromium/webkit/browser/fileapi/external_mount_points.cc b/chromium/webkit/browser/fileapi/external_mount_points.cc
new file mode 100644
index 00000000000..a3ecd4b6678
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/external_mount_points.cc
@@ -0,0 +1,335 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/external_mount_points.h"
+
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+namespace {
+
+// Normalizes file path so it has normalized separators and ends with exactly
+// one separator. Paths have to be normalized this way for use in
+// GetVirtualPath method. Separators cannot be completely stripped, or
+// GetVirtualPath could not working in some edge cases.
+// For example, /a/b/c(1)/d would be erroneously resolved as c/d if the
+// following mount points were registered: "/a/b/c", "/a/b/c(1)". (Note:
+// "/a/b/c" < "/a/b/c(1)" < "/a/b/c/").
+base::FilePath NormalizeFilePath(const base::FilePath& path) {
+ if (path.empty())
+ return path;
+
+ base::FilePath::StringType path_str = path.StripTrailingSeparators().value();
+ if (!base::FilePath::IsSeparator(path_str[path_str.length() - 1]))
+ path_str.append(FILE_PATH_LITERAL("/"));
+
+ return base::FilePath(path_str).NormalizePathSeparators();
+}
+
+// Wrapper around ref-counted ExternalMountPoints that will be used to lazily
+// create and initialize LazyInstance system ExternalMountPoints.
+class SystemMountPointsLazyWrapper {
+ public:
+ SystemMountPointsLazyWrapper()
+ : system_mount_points_(fileapi::ExternalMountPoints::CreateRefCounted()) {
+ }
+
+ ~SystemMountPointsLazyWrapper() {}
+
+ fileapi::ExternalMountPoints* get() {
+ return system_mount_points_.get();
+ }
+
+ private:
+ scoped_refptr<fileapi::ExternalMountPoints> system_mount_points_;
+};
+
+base::LazyInstance<SystemMountPointsLazyWrapper>::Leaky
+ g_external_mount_points = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace fileapi {
+
+class ExternalMountPoints::Instance {
+ public:
+ Instance(FileSystemType type, const base::FilePath& path)
+ : type_(type), path_(path.StripTrailingSeparators()) {}
+ ~Instance() {}
+
+ FileSystemType type() const { return type_; }
+ const base::FilePath& path() const { return path_; }
+
+ private:
+ const FileSystemType type_;
+ const base::FilePath path_;
+
+ DISALLOW_COPY_AND_ASSIGN(Instance);
+};
+
+//--------------------------------------------------------------------------
+
+// static
+ExternalMountPoints* ExternalMountPoints::GetSystemInstance() {
+ return g_external_mount_points.Pointer()->get();
+}
+
+// static
+scoped_refptr<ExternalMountPoints> ExternalMountPoints::CreateRefCounted() {
+ return new ExternalMountPoints();
+}
+
+bool ExternalMountPoints::RegisterFileSystem(
+ const std::string& mount_name,
+ FileSystemType type,
+ const base::FilePath& path_in) {
+ base::AutoLock locker(lock_);
+
+ base::FilePath path = NormalizeFilePath(path_in);
+ if (!ValidateNewMountPoint(mount_name, path))
+ return false;
+
+ instance_map_[mount_name] = new Instance(type, path);
+ if (!path.empty())
+ path_to_name_map_.insert(std::make_pair(path, mount_name));
+ return true;
+}
+
+bool ExternalMountPoints::HandlesFileSystemMountType(
+ FileSystemType type) const {
+ return type == kFileSystemTypeExternal ||
+ type == kFileSystemTypeNativeForPlatformApp;
+}
+
+bool ExternalMountPoints::RevokeFileSystem(const std::string& mount_name) {
+ base::AutoLock locker(lock_);
+ NameToInstance::iterator found = instance_map_.find(mount_name);
+ if (found == instance_map_.end())
+ return false;
+ Instance* instance = found->second;
+ path_to_name_map_.erase(NormalizeFilePath(instance->path()));
+ delete found->second;
+ instance_map_.erase(found);
+ return true;
+}
+
+bool ExternalMountPoints::GetRegisteredPath(
+ const std::string& filesystem_id, base::FilePath* path) const {
+ DCHECK(path);
+ base::AutoLock locker(lock_);
+ NameToInstance::const_iterator found = instance_map_.find(filesystem_id);
+ if (found == instance_map_.end())
+ return false;
+ *path = found->second->path();
+ return true;
+}
+
+bool ExternalMountPoints::CrackVirtualPath(const base::FilePath& virtual_path,
+ std::string* mount_name,
+ FileSystemType* type,
+ base::FilePath* path) const {
+ DCHECK(mount_name);
+ DCHECK(path);
+
+ // The path should not contain any '..' references.
+ if (virtual_path.ReferencesParent())
+ return false;
+
+ // The virtual_path should comprise of <mount_name> and <relative_path> parts.
+ std::vector<base::FilePath::StringType> components;
+ virtual_path.GetComponents(&components);
+ if (components.size() < 1)
+ return false;
+
+ std::vector<base::FilePath::StringType>::iterator component_iter =
+ components.begin();
+ std::string maybe_mount_name =
+ base::FilePath(*component_iter++).MaybeAsASCII();
+ if (maybe_mount_name.empty())
+ return false;
+
+ base::FilePath cracked_path;
+ {
+ base::AutoLock locker(lock_);
+ NameToInstance::const_iterator found_instance =
+ instance_map_.find(maybe_mount_name);
+ if (found_instance == instance_map_.end())
+ return false;
+
+ *mount_name = maybe_mount_name;
+ const Instance* instance = found_instance->second;
+ if (type)
+ *type = instance->type();
+ cracked_path = instance->path();
+ }
+
+ for (; component_iter != components.end(); ++component_iter)
+ cracked_path = cracked_path.Append(*component_iter);
+ *path = cracked_path;
+ return true;
+}
+
+FileSystemURL ExternalMountPoints::CrackURL(const GURL& url) const {
+ FileSystemURL filesystem_url = FileSystemURL(url);
+ if (!filesystem_url.is_valid())
+ return FileSystemURL();
+ return CrackFileSystemURL(filesystem_url);
+}
+
+FileSystemURL ExternalMountPoints::CreateCrackedFileSystemURL(
+ const GURL& origin,
+ FileSystemType type,
+ const base::FilePath& path) const {
+ return CrackFileSystemURL(FileSystemURL(origin, type, path));
+}
+
+void ExternalMountPoints::AddMountPointInfosTo(
+ std::vector<MountPointInfo>* mount_points) const {
+ base::AutoLock locker(lock_);
+ DCHECK(mount_points);
+ for (NameToInstance::const_iterator iter = instance_map_.begin();
+ iter != instance_map_.end(); ++iter) {
+ mount_points->push_back(MountPointInfo(iter->first, iter->second->path()));
+ }
+}
+
+bool ExternalMountPoints::GetVirtualPath(const base::FilePath& path_in,
+ base::FilePath* virtual_path) const {
+ DCHECK(virtual_path);
+
+ base::AutoLock locker(lock_);
+
+ base::FilePath path = NormalizeFilePath(path_in);
+ std::map<base::FilePath, std::string>::const_reverse_iterator iter(
+ path_to_name_map_.upper_bound(path));
+ if (iter == path_to_name_map_.rend())
+ return false;
+
+ *virtual_path = CreateVirtualRootPath(iter->second);
+ if (iter->first == path)
+ return true;
+ return iter->first.AppendRelativePath(path, virtual_path);
+}
+
+base::FilePath ExternalMountPoints::CreateVirtualRootPath(
+ const std::string& mount_name) const {
+ return base::FilePath().AppendASCII(mount_name);
+}
+
+FileSystemURL ExternalMountPoints::CreateExternalFileSystemURL(
+ const GURL& origin,
+ const std::string& mount_name,
+ const base::FilePath& path) const {
+ return CreateCrackedFileSystemURL(
+ origin,
+ fileapi::kFileSystemTypeExternal,
+ // Avoid using FilePath::Append as path may be an absolute path.
+ base::FilePath(
+ CreateVirtualRootPath(mount_name).value() +
+ base::FilePath::kSeparators[0] + path.value()));
+}
+
+ExternalMountPoints::ExternalMountPoints() {}
+
+ExternalMountPoints::~ExternalMountPoints() {
+ STLDeleteContainerPairSecondPointers(instance_map_.begin(),
+ instance_map_.end());
+}
+
+FileSystemURL ExternalMountPoints::CrackFileSystemURL(
+ const FileSystemURL& url) const {
+ if (!HandlesFileSystemMountType(url.type()))
+ return FileSystemURL();
+
+ base::FilePath virtual_path = url.path();
+ if (url.type() == kFileSystemTypeNativeForPlatformApp) {
+#if defined(OS_CHROMEOS)
+ // On Chrome OS, find a mount point and virtual path for the external fs.
+ if (!GetVirtualPath(url.path(), &virtual_path))
+ return FileSystemURL();
+#else
+ // On other OS, it is simply a native local path.
+ return FileSystemURL(
+ url.origin(), url.mount_type(), url.virtual_path(),
+ url.mount_filesystem_id(), kFileSystemTypeNativeLocal,
+ url.path(), url.filesystem_id());
+#endif
+ }
+
+ std::string mount_name;
+ FileSystemType cracked_type;
+ base::FilePath cracked_path;
+
+ if (!CrackVirtualPath(virtual_path, &mount_name, &cracked_type,
+ &cracked_path)) {
+ return FileSystemURL();
+ }
+
+ return FileSystemURL(
+ url.origin(), url.mount_type(), url.virtual_path(),
+ !url.filesystem_id().empty() ? url.filesystem_id() : mount_name,
+ cracked_type, cracked_path, mount_name);
+}
+
+bool ExternalMountPoints::ValidateNewMountPoint(const std::string& mount_name,
+ const base::FilePath& path) {
+ lock_.AssertAcquired();
+
+ // Mount name must not be empty.
+ if (mount_name.empty())
+ return false;
+
+ // Verify there is no registered mount point with the same name.
+ NameToInstance::iterator found = instance_map_.find(mount_name);
+ if (found != instance_map_.end())
+ return false;
+
+ // Allow empty paths.
+ if (path.empty())
+ return true;
+
+ // Verify path is legal.
+ if (path.ReferencesParent() || !path.IsAbsolute())
+ return false;
+
+ // Check there the new path does not overlap with one of the existing ones.
+ std::map<base::FilePath, std::string>::reverse_iterator potential_parent(
+ path_to_name_map_.upper_bound(path));
+ if (potential_parent != path_to_name_map_.rend()) {
+ if (potential_parent->first == path ||
+ potential_parent->first.IsParent(path)) {
+ return false;
+ }
+ }
+
+ std::map<base::FilePath, std::string>::iterator potential_child =
+ path_to_name_map_.upper_bound(path);
+ if (potential_child == path_to_name_map_.end())
+ return true;
+ return !(potential_child->first == path) &&
+ !path.IsParent(potential_child->first);
+}
+
+ScopedExternalFileSystem::ScopedExternalFileSystem(
+ const std::string& mount_name,
+ FileSystemType type,
+ const base::FilePath& path)
+ : mount_name_(mount_name) {
+ ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ mount_name, type, path);
+}
+
+base::FilePath ScopedExternalFileSystem::GetVirtualRootPath() const {
+ return ExternalMountPoints::GetSystemInstance()->
+ CreateVirtualRootPath(mount_name_);
+}
+
+ScopedExternalFileSystem::~ScopedExternalFileSystem() {
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(mount_name_);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/external_mount_points.h b/chromium/webkit/browser/fileapi/external_mount_points.h
new file mode 100644
index 00000000000..9118443b7b6
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/external_mount_points.h
@@ -0,0 +1,159 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_
+#define WEBKIT_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "webkit/browser/fileapi/mount_points.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace fileapi {
+class FileSystemURL;
+}
+
+namespace fileapi {
+
+// Manages external filesystem namespaces that are identified by 'mount name'
+// and are persisted until RevokeFileSystem is called.
+// Files in an external filesystem are identified by a filesystem URL like:
+//
+// filesystem:<origin>/external/<mount_name>/relative/path
+//
+class WEBKIT_STORAGE_BROWSER_EXPORT ExternalMountPoints
+ : public base::RefCountedThreadSafe<ExternalMountPoints>,
+ public MountPoints {
+ public:
+ static ExternalMountPoints* GetSystemInstance();
+ static scoped_refptr<ExternalMountPoints> CreateRefCounted();
+
+ // Registers a new named external filesystem.
+ // The |path| is registered as the root path of the mount point which
+ // is identified by a URL "filesystem:.../external/mount_name".
+ //
+ // For example, if the path "/media/removable" is registered with
+ // the mount_name "removable", a filesystem URL like
+ // "filesystem:.../external/removable/a/b" will be resolved as
+ // "/media/removable/a/b".
+ //
+ // The |mount_name| should NOT contain a path separator '/'.
+ // Returns false if the given name is already registered.
+ //
+ // Overlapping mount points in a single MountPoints instance are not allowed.
+ // Adding mount point whose path overlaps with an existing mount point will
+ // fail.
+ //
+ // If not empty, |path| must be absolute. It is allowed for the path to be
+ // empty, but |GetVirtualPath| will not work for those mount points.
+ //
+ // An external file system registered by this method can be revoked
+ // by calling RevokeFileSystem with |mount_name|.
+ bool RegisterFileSystem(const std::string& mount_name,
+ FileSystemType type,
+ const base::FilePath& path);
+
+ // MountPoints overrides.
+ virtual bool HandlesFileSystemMountType(FileSystemType type) const OVERRIDE;
+ virtual bool RevokeFileSystem(const std::string& mount_name) OVERRIDE;
+ virtual bool GetRegisteredPath(const std::string& mount_name,
+ base::FilePath* path) const OVERRIDE;
+ virtual bool CrackVirtualPath(const base::FilePath& virtual_path,
+ std::string* mount_name,
+ FileSystemType* type,
+ base::FilePath* path) const OVERRIDE;
+ virtual FileSystemURL CrackURL(const GURL& url) const OVERRIDE;
+ virtual FileSystemURL CreateCrackedFileSystemURL(
+ const GURL& origin,
+ FileSystemType type,
+ const base::FilePath& path) const OVERRIDE;
+
+ // Returns a list of registered MountPointInfos (of <mount_name, path>).
+ void AddMountPointInfosTo(std::vector<MountPointInfo>* mount_points) const;
+
+ // Converts a path on a registered file system to virtual path relative to the
+ // file system root. E.g. if 'Downloads' file system is mapped to
+ // '/usr/local/home/Downloads', and |absolute| path is set to
+ // '/usr/local/home/Downloads/foo', the method will set |virtual_path| to
+ // 'Downloads/foo'.
+ // Returns false if the path cannot be resolved (e.g. if the path is not
+ // part of any registered filesystem).
+ //
+ // Returned virtual_path will have normalized path separators.
+ bool GetVirtualPath(const base::FilePath& absolute_path,
+ base::FilePath* virtual_path) const;
+
+ // Returns the virtual root path that looks like /<mount_name>.
+ base::FilePath CreateVirtualRootPath(const std::string& mount_name) const;
+
+ FileSystemURL CreateExternalFileSystemURL(
+ const GURL& origin,
+ const std::string& mount_name,
+ const base::FilePath& path) const;
+
+ private:
+ friend class base::RefCountedThreadSafe<ExternalMountPoints>;
+
+ // Represents each file system instance (defined in the .cc).
+ class Instance;
+
+ typedef std::map<std::string, Instance*> NameToInstance;
+
+ // Reverse map from registered path to its corresponding mount name.
+ typedef std::map<base::FilePath, std::string> PathToName;
+
+ // Use |GetSystemInstance| of |CreateRefCounted| to get an instance.
+ ExternalMountPoints();
+ virtual ~ExternalMountPoints();
+
+ // MountPoint overrides.
+ virtual FileSystemURL CrackFileSystemURL(
+ const FileSystemURL& url) const OVERRIDE;
+
+ // Performs sanity checks on the new mount point.
+ // Checks the following:
+ // - there is no registered mount point with mount_name
+ // - path does not contain a reference to a parent
+ // - path is absolute
+ // - path does not overlap with an existing mount point path.
+ //
+ // |lock_| should be taken before calling this method.
+ bool ValidateNewMountPoint(const std::string& mount_name,
+ const base::FilePath& path);
+
+ // This lock needs to be obtained when accessing the instance_map_.
+ mutable base::Lock lock_;
+
+ NameToInstance instance_map_;
+ PathToName path_to_name_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExternalMountPoints);
+};
+
+// Registers a scoped external filesystem which gets revoked when it scopes out.
+class WEBKIT_STORAGE_BROWSER_EXPORT ScopedExternalFileSystem {
+ public:
+ ScopedExternalFileSystem(const std::string& mount_name,
+ FileSystemType type,
+ const base::FilePath& path);
+ ~ScopedExternalFileSystem();
+
+ base::FilePath GetVirtualRootPath() const;
+
+ private:
+ const std::string mount_name_;
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_
diff --git a/chromium/webkit/browser/fileapi/external_mount_points_unittest.cc b/chromium/webkit/browser/fileapi/external_mount_points_unittest.cc
new file mode 100644
index 00000000000..cc2acbe2a16
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/external_mount_points_unittest.cc
@@ -0,0 +1,462 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/external_mount_points.h"
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+#define FPL FILE_PATH_LITERAL
+
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+#define DRIVE FPL("C:")
+#else
+#define DRIVE
+#endif
+
+using fileapi::FileSystemURL;
+
+namespace {
+
+TEST(ExternalMountPointsTest, AddMountPoint) {
+ scoped_refptr<fileapi::ExternalMountPoints> mount_points(
+ fileapi::ExternalMountPoints::CreateRefCounted());
+
+ struct TestCase {
+ // The mount point's name.
+ const char* const name;
+ // The mount point's path.
+ const base::FilePath::CharType* const path;
+ // Whether the mount point registration should succeed.
+ bool success;
+ // Path returned by GetRegisteredPath. NULL if the method is expected to
+ // fail.
+ const base::FilePath::CharType* const registered_path;
+ };
+
+ const TestCase kTestCases[] = {
+ // Valid mount point.
+ { "test", DRIVE FPL("/foo/test"), true, DRIVE FPL("/foo/test") },
+ // Valid mount point with only one path component.
+ { "bbb", DRIVE FPL("/bbb"), true, DRIVE FPL("/bbb") },
+ // Existing mount point path is substring of the mount points path.
+ { "test11", DRIVE FPL("/foo/test11"), true, DRIVE FPL("/foo/test11") },
+ // Path substring of an existing path.
+ { "test1", DRIVE FPL("/foo/test1"), true, DRIVE FPL("/foo/test1") },
+ // Empty mount point name and path.
+ { "", DRIVE FPL(""), false, NULL },
+ // Empty mount point name.
+ { "", DRIVE FPL("/ddd"), false, NULL },
+ // Empty mount point path.
+ { "empty_path", FPL(""), true, FPL("") },
+ // Name different from path's base name.
+ { "not_base_name", DRIVE FPL("/x/y/z"), true, DRIVE FPL("/x/y/z") },
+ // References parent.
+ { "invalid", DRIVE FPL("../foo/invalid"), false, NULL },
+ // Relative path.
+ { "relative", DRIVE FPL("foo/relative"), false, NULL },
+ // Existing mount point path.
+ { "path_exists", DRIVE FPL("/foo/test"), false, NULL },
+ // Mount point with the same name exists.
+ { "test", DRIVE FPL("/foo/a/test_name_exists"), false,
+ DRIVE FPL("/foo/test") },
+ // Child of an existing mount point.
+ { "a1", DRIVE FPL("/foo/test/a"), false, NULL },
+ // Parent of an existing mount point.
+ { "foo1", DRIVE FPL("/foo"), false, NULL },
+ // Bit bigger depth.
+ { "g", DRIVE FPL("/foo/a/b/c/d/e/f/g"), true,
+ DRIVE FPL("/foo/a/b/c/d/e/f/g") },
+ // Sibling mount point (with similar name) exists.
+ { "ff", DRIVE FPL("/foo/a/b/c/d/e/ff"), true,
+ DRIVE FPL("/foo/a/b/c/d/e/ff") },
+ // Lexicographically last among existing mount points.
+ { "yyy", DRIVE FPL("/zzz/yyy"), true, DRIVE FPL("/zzz/yyy") },
+ // Parent of the lexicographically last mount point.
+ { "zzz1", DRIVE FPL("/zzz"), false, NULL },
+ // Child of the lexicographically last mount point.
+ { "xxx1", DRIVE FPL("/zzz/yyy/xxx"), false, NULL },
+ // Lexicographically first among existing mount points.
+ { "b", DRIVE FPL("/a/b"), true, DRIVE FPL("/a/b") },
+ // Parent of lexicographically first mount point.
+ { "a2", DRIVE FPL("/a"), false, NULL },
+ // Child of lexicographically last mount point.
+ { "c1", DRIVE FPL("/a/b/c"), false, NULL },
+ // Parent to all of the mount points.
+ { "root", DRIVE FPL("/"), false, NULL },
+ // Path contains .. component.
+ { "funky", DRIVE FPL("/tt/fun/../funky"), false, NULL },
+ // Windows separators.
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ { "win", DRIVE FPL("\\try\\separators\\win"), true,
+ DRIVE FPL("\\try\\separators\\win") },
+ { "win1", DRIVE FPL("\\try/separators\\win1"), true,
+ DRIVE FPL("\\try/separators\\win1") },
+ { "win2", DRIVE FPL("\\try/separators\\win"), false, NULL },
+#else
+ { "win", DRIVE FPL("\\separators\\win"), false, NULL },
+ { "win1", DRIVE FPL("\\try/separators\\win1"), false, NULL },
+#endif
+ // Win separators, but relative path.
+ { "win2", DRIVE FPL("try\\separators\\win2"), false, NULL },
+ };
+
+ // Test adding mount points.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
+ EXPECT_EQ(kTestCases[i].success,
+ mount_points->RegisterFileSystem(
+ kTestCases[i].name,
+ fileapi::kFileSystemTypeNativeLocal,
+ base::FilePath(kTestCases[i].path)))
+ << "Adding mount point: " << kTestCases[i].name << " with path "
+ << kTestCases[i].path;
+ }
+
+ // Test that final mount point presence state is as expected.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
+ base::FilePath found_path;
+ EXPECT_EQ(kTestCases[i].registered_path != NULL,
+ mount_points->GetRegisteredPath(kTestCases[i].name, &found_path))
+ << "Test case: " << i;
+
+ if (kTestCases[i].registered_path) {
+ base::FilePath expected_path(kTestCases[i].registered_path);
+ EXPECT_EQ(expected_path.NormalizePathSeparators(), found_path);
+ }
+ }
+}
+
+TEST(ExternalMountPointsTest, GetVirtualPath) {
+ scoped_refptr<fileapi::ExternalMountPoints> mount_points(
+ fileapi::ExternalMountPoints::CreateRefCounted());
+
+ mount_points->RegisterFileSystem("c",
+ fileapi::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/a/b/c")));
+ // Note that "/a/b/c" < "/a/b/c(1)" < "/a/b/c/".
+ mount_points->RegisterFileSystem("c(1)",
+ fileapi::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/a/b/c(1)")));
+ mount_points->RegisterFileSystem("x",
+ fileapi::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/z/y/x")));
+ mount_points->RegisterFileSystem("o",
+ fileapi::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/m/n/o")));
+ // A mount point whose name does not match its path base name.
+ mount_points->RegisterFileSystem("mount",
+ fileapi::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/root/foo")));
+ // A mount point with an empty path.
+ mount_points->RegisterFileSystem("empty_path",
+ fileapi::kFileSystemTypeNativeLocal,
+ base::FilePath());
+
+ struct TestCase {
+ const base::FilePath::CharType* const local_path;
+ bool success;
+ const base::FilePath::CharType* const virtual_path;
+ };
+
+ const TestCase kTestCases[] = {
+ // Empty path.
+ { FPL(""), false, FPL("") },
+ // No registered mount point (but is parent to a mount point).
+ { DRIVE FPL("/a/b"), false, FPL("") },
+ // No registered mount point (but is parent to a mount point).
+ { DRIVE FPL("/z/y"), false, FPL("") },
+ // No registered mount point (but is parent to a mount point).
+ { DRIVE FPL("/m/n"), false, FPL("") },
+ // No registered mount point.
+ { DRIVE FPL("/foo/mount"), false, FPL("") },
+ // An existing mount point path is substring.
+ { DRIVE FPL("/a/b/c1"), false, FPL("") },
+ // No leading /.
+ { DRIVE FPL("a/b/c"), false, FPL("") },
+ // Sibling to a root path.
+ { DRIVE FPL("/a/b/d/e"), false, FPL("") },
+ // Sibling to a root path.
+ { DRIVE FPL("/z/y/v/u"), false, FPL("") },
+ // Sibling to a root path.
+ { DRIVE FPL("/m/n/p/q"), false, FPL("") },
+ // Mount point root path.
+ { DRIVE FPL("/a/b/c"), true, FPL("c") },
+ // Mount point root path.
+ { DRIVE FPL("/z/y/x"), true, FPL("x") },
+ // Mount point root path.
+ { DRIVE FPL("/m/n/o"), true, FPL("o") },
+ // Mount point child path.
+ { DRIVE FPL("/a/b/c/d/e"), true, FPL("c/d/e") },
+ // Mount point child path.
+ { DRIVE FPL("/z/y/x/v/u"), true, FPL("x/v/u") },
+ // Mount point child path.
+ { DRIVE FPL("/m/n/o/p/q"), true, FPL("o/p/q") },
+ // Name doesn't match mount point path base name.
+ { DRIVE FPL("/root/foo/a/b/c"), true, FPL("mount/a/b/c") },
+ { DRIVE FPL("/root/foo"), true, FPL("mount") },
+ // Mount point contains character whose ASCII code is smaller than file path
+ // separator's.
+ { DRIVE FPL("/a/b/c(1)/d/e"), true, FPL("c(1)/d/e") },
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ // Path with win separators mixed in.
+ { DRIVE FPL("/a\\b\\c/d"), true, FPL("c/d") },
+#endif
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
+ // Initialize virtual path with a value.
+ base::FilePath virtual_path(DRIVE FPL("/mount"));
+ base::FilePath local_path(kTestCases[i].local_path);
+ EXPECT_EQ(kTestCases[i].success,
+ mount_points->GetVirtualPath(local_path, &virtual_path))
+ << "Resolving " << kTestCases[i].local_path;
+
+ // There are no guarantees for |virtual_path| value if |GetVirtualPath|
+ // fails.
+ if (!kTestCases[i].success)
+ continue;
+
+ base::FilePath expected_virtual_path(kTestCases[i].virtual_path);
+ EXPECT_EQ(expected_virtual_path.NormalizePathSeparators(), virtual_path)
+ << "Resolving " << kTestCases[i].local_path;
+ }
+}
+
+TEST(ExternalMountPointsTest, HandlesFileSystemMountType) {
+ scoped_refptr<fileapi::ExternalMountPoints> mount_points(
+ fileapi::ExternalMountPoints::CreateRefCounted());
+
+ const GURL test_origin("http://chromium.org");
+ const base::FilePath test_path(FPL("/mount"));
+
+ // Should handle External File System.
+ EXPECT_TRUE(mount_points->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeExternal));
+
+ // Shouldn't handle the rest.
+ EXPECT_FALSE(mount_points->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeIsolated));
+ EXPECT_FALSE(mount_points->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeTemporary));
+ EXPECT_FALSE(mount_points->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypePersistent));
+ EXPECT_FALSE(mount_points->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeTest));
+ // Not even if it's external subtype.
+ EXPECT_FALSE(mount_points->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeNativeLocal));
+ EXPECT_FALSE(mount_points->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeRestrictedNativeLocal));
+ EXPECT_FALSE(mount_points->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeDrive));
+ EXPECT_FALSE(mount_points->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeSyncable));
+}
+
+TEST(ExternalMountPointsTest, CreateCrackedFileSystemURL) {
+ scoped_refptr<fileapi::ExternalMountPoints> mount_points(
+ fileapi::ExternalMountPoints::CreateRefCounted());
+
+ const GURL kTestOrigin("http://chromium.org");
+
+ mount_points->RegisterFileSystem("c",
+ fileapi::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/a/b/c")));
+ mount_points->RegisterFileSystem("c(1)",
+ fileapi::kFileSystemTypeDrive,
+ base::FilePath(DRIVE FPL("/a/b/c(1)")));
+ mount_points->RegisterFileSystem("empty_path",
+ fileapi::kFileSystemTypeSyncable,
+ base::FilePath());
+ mount_points->RegisterFileSystem("mount",
+ fileapi::kFileSystemTypeDrive,
+ base::FilePath(DRIVE FPL("/root")));
+
+ // Try cracking invalid GURL.
+ FileSystemURL invalid = mount_points->CrackURL(GURL("http://chromium.og"));
+ EXPECT_FALSE(invalid.is_valid());
+
+ // Try cracking isolated path.
+ FileSystemURL isolated = mount_points->CreateCrackedFileSystemURL(
+ kTestOrigin, fileapi::kFileSystemTypeIsolated, base::FilePath(FPL("c")));
+ EXPECT_FALSE(isolated.is_valid());
+
+ // Try native local which is not cracked.
+ FileSystemURL native_local = mount_points->CreateCrackedFileSystemURL(
+ kTestOrigin, fileapi::kFileSystemTypeNativeLocal, base::FilePath(FPL("c")));
+ EXPECT_FALSE(native_local.is_valid());
+
+ struct TestCase {
+ const base::FilePath::CharType* const path;
+ bool expect_valid;
+ fileapi::FileSystemType expect_type;
+ const base::FilePath::CharType* const expect_path;
+ const char* const expect_fs_id;
+ };
+
+ const TestCase kTestCases[] = {
+ { FPL("c/d/e"),
+ true, fileapi::kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), "c" },
+ { FPL("c(1)/d/e"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)/d/e"), "c(1)" },
+ { FPL("c(1)"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)"), "c(1)" },
+ { FPL("empty_path/a"),
+ true, fileapi::kFileSystemTypeSyncable, FPL("a"), "empty_path" },
+ { FPL("empty_path"),
+ true, fileapi::kFileSystemTypeSyncable, FPL(""), "empty_path" },
+ { FPL("mount/a/b"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root/a/b"), "mount" },
+ { FPL("mount"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root"), "mount" },
+ { FPL("cc"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL(""),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL(".."),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ // Absolte paths.
+ { FPL("/c/d/e"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL("/c(1)/d/e"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL("/empty_path"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ // PAth references parent.
+ { FPL("c/d/../e"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL("/empty_path/a/../b"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ { FPL("c/d\\e"),
+ true, fileapi::kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), "c" },
+ { FPL("mount\\a\\b"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root/a/b"), "mount" },
+#endif
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
+ FileSystemURL cracked = mount_points->CreateCrackedFileSystemURL(
+ kTestOrigin,
+ fileapi::kFileSystemTypeExternal,
+ base::FilePath(kTestCases[i].path));
+
+ EXPECT_EQ(kTestCases[i].expect_valid, cracked.is_valid())
+ << "Test case index: " << i;
+
+ if (!kTestCases[i].expect_valid)
+ continue;
+
+ EXPECT_EQ(kTestOrigin, cracked.origin())
+ << "Test case index: " << i;
+ EXPECT_EQ(kTestCases[i].expect_type, cracked.type())
+ << "Test case index: " << i;
+ EXPECT_EQ(base::FilePath(kTestCases[i].expect_path).NormalizePathSeparators(),
+ cracked.path())
+ << "Test case index: " << i;
+ EXPECT_EQ(base::FilePath(kTestCases[i].path).NormalizePathSeparators(),
+ cracked.virtual_path())
+ << "Test case index: " << i;
+ EXPECT_EQ(kTestCases[i].expect_fs_id, cracked.filesystem_id())
+ << "Test case index: " << i;
+ EXPECT_EQ(fileapi::kFileSystemTypeExternal, cracked.mount_type())
+ << "Test case index: " << i;
+ }
+}
+
+TEST(ExternalMountPointsTest, CrackVirtualPath) {
+ scoped_refptr<fileapi::ExternalMountPoints> mount_points(
+ fileapi::ExternalMountPoints::CreateRefCounted());
+
+ const GURL kTestOrigin("http://chromium.org");
+
+ mount_points->RegisterFileSystem("c",
+ fileapi::kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/a/b/c")));
+ mount_points->RegisterFileSystem("c(1)",
+ fileapi::kFileSystemTypeDrive,
+ base::FilePath(DRIVE FPL("/a/b/c(1)")));
+ mount_points->RegisterFileSystem("empty_path",
+ fileapi::kFileSystemTypeSyncable,
+ base::FilePath());
+ mount_points->RegisterFileSystem("mount",
+ fileapi::kFileSystemTypeDrive,
+ base::FilePath(DRIVE FPL("/root")));
+
+ struct TestCase {
+ const base::FilePath::CharType* const path;
+ bool expect_valid;
+ fileapi::FileSystemType expect_type;
+ const base::FilePath::CharType* const expect_path;
+ const char* const expect_name;
+ };
+
+ const TestCase kTestCases[] = {
+ { FPL("c/d/e"),
+ true, fileapi::kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), "c" },
+ { FPL("c(1)/d/e"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)/d/e"), "c(1)" },
+ { FPL("c(1)"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/a/b/c(1)"), "c(1)" },
+ { FPL("empty_path/a"),
+ true, fileapi::kFileSystemTypeSyncable, FPL("a"), "empty_path" },
+ { FPL("empty_path"),
+ true, fileapi::kFileSystemTypeSyncable, FPL(""), "empty_path" },
+ { FPL("mount/a/b"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root/a/b"), "mount" },
+ { FPL("mount"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root"), "mount" },
+ { FPL("cc"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL(""),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL(".."),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ // Absolte paths.
+ { FPL("/c/d/e"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL("/c(1)/d/e"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL("/empty_path"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ // PAth references parent.
+ { FPL("c/d/../e"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+ { FPL("/empty_path/a/../b"),
+ false, fileapi::kFileSystemTypeUnknown, FPL(""), "" },
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ { FPL("c/d\\e"),
+ true, fileapi::kFileSystemTypeNativeLocal, DRIVE FPL("/a/b/c/d/e"), "c" },
+ { FPL("mount\\a\\b"),
+ true, fileapi::kFileSystemTypeDrive, DRIVE FPL("/root/a/b"), "mount" },
+#endif
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
+ std::string cracked_name;
+ fileapi::FileSystemType cracked_type;
+ base::FilePath cracked_path;
+ EXPECT_EQ(kTestCases[i].expect_valid,
+ mount_points->CrackVirtualPath(base::FilePath(kTestCases[i].path),
+ &cracked_name, &cracked_type, &cracked_path))
+ << "Test case index: " << i;
+
+ if (!kTestCases[i].expect_valid)
+ continue;
+
+ EXPECT_EQ(kTestCases[i].expect_type, cracked_type)
+ << "Test case index: " << i;
+ EXPECT_EQ(base::FilePath(kTestCases[i].expect_path).NormalizePathSeparators(),
+ cracked_path)
+ << "Test case index: " << i;
+ EXPECT_EQ(kTestCases[i].expect_name, cracked_name)
+ << "Test case index: " << i;
+ }
+}
+
+} // namespace
+
diff --git a/chromium/webkit/browser/fileapi/file_observers.h b/chromium/webkit/browser/fileapi/file_observers.h
new file mode 100644
index 00000000000..c791503d82f
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_observers.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_OBSERVERS_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_OBSERVERS_H_
+
+#include "base/basictypes.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+// TODO(kinuko): Split this file into per-observer multiple files.
+
+namespace fileapi {
+
+class FileSystemURL;
+
+// An abstract interface to observe update operations.
+//
+// OnStartUpdate and OnEndUpdate are called once for each target url
+// before and after following operations regardless of whether the operation
+// is made recursively or not (i.e. StartUpdate() will be called only once
+// for destination url regardless of whether it is recursive copy or not):
+// CreateFile(), CreateDirectory(),
+// Copy() (destination only),
+// Move() (both for source and destination),
+// Remove(), Write(), Truncate(), TouchFile()
+//
+// OnUpdate() is called each time the |url| is updated but works only for
+// sandboxed files (where usage is tracked).
+class WEBKIT_STORAGE_BROWSER_EXPORT FileUpdateObserver {
+ public:
+ FileUpdateObserver() {}
+ virtual ~FileUpdateObserver() {}
+
+ virtual void OnStartUpdate(const FileSystemURL& url) = 0;
+ virtual void OnUpdate(const FileSystemURL& url, int64 delta) = 0;
+ virtual void OnEndUpdate(const FileSystemURL& url) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileUpdateObserver);
+};
+
+// An abstract interface to observe file access.
+// OnAccess is called whenever an operation reads file contents or metadata.
+// (It is called only once per operation regardless of whether the operation
+// is recursive or not)
+class WEBKIT_STORAGE_BROWSER_EXPORT FileAccessObserver {
+ public:
+ FileAccessObserver() {}
+ virtual ~FileAccessObserver() {}
+
+ virtual void OnAccess(const FileSystemURL& url) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileAccessObserver);
+};
+
+// An abstract interface to observe file changes.
+// Each method of this class is called once per file/directory is created,
+// removed or modified. For recursive operations each method is called for
+// each subdirectory/subfile. Currently ChangeObserver is only supported
+// by the local sandbox file system.
+class WEBKIT_STORAGE_BROWSER_EXPORT FileChangeObserver {
+ public:
+ FileChangeObserver() {}
+ virtual ~FileChangeObserver() {}
+
+ virtual void OnCreateFile(const FileSystemURL& url) = 0;
+ virtual void OnCreateFileFrom(const FileSystemURL& url,
+ const FileSystemURL& src) = 0;
+ virtual void OnRemoveFile(const FileSystemURL& url) = 0;
+ virtual void OnModifyFile(const FileSystemURL& url) = 0;
+
+ virtual void OnCreateDirectory(const FileSystemURL& url) = 0;
+ virtual void OnRemoveDirectory(const FileSystemURL& url) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileChangeObserver);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_OBSERVERS_H_
diff --git a/chromium/webkit/browser/fileapi/file_permission_policy.cc b/chromium/webkit/browser/fileapi/file_permission_policy.cc
new file mode 100644
index 00000000000..164548791db
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_permission_policy.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_permission_policy.h"
+
+#include "base/platform_file.h"
+
+namespace fileapi {
+
+const int kReadFilePermissions = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_EXCLUSIVE_READ |
+ base::PLATFORM_FILE_ASYNC;
+
+const int kWriteFilePermissions = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_EXCLUSIVE_WRITE |
+ base::PLATFORM_FILE_ASYNC |
+ base::PLATFORM_FILE_WRITE_ATTRIBUTES;
+
+const int kCreateFilePermissions = base::PLATFORM_FILE_CREATE;
+
+const int kOpenFilePermissions = base::PLATFORM_FILE_CREATE |
+ base::PLATFORM_FILE_OPEN_ALWAYS |
+ base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_OPEN_TRUNCATED |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_EXCLUSIVE_WRITE |
+ base::PLATFORM_FILE_DELETE_ON_CLOSE |
+ base::PLATFORM_FILE_WRITE_ATTRIBUTES;
+
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_permission_policy.h b/chromium/webkit/browser/fileapi/file_permission_policy.h
new file mode 100644
index 00000000000..3975c3ccace
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_permission_policy.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_
+
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace fileapi {
+
+WEBKIT_STORAGE_BROWSER_EXPORT extern const int kReadFilePermissions;
+WEBKIT_STORAGE_BROWSER_EXPORT extern const int kWriteFilePermissions;
+WEBKIT_STORAGE_BROWSER_EXPORT extern const int kCreateFilePermissions;
+WEBKIT_STORAGE_BROWSER_EXPORT extern const int kOpenFilePermissions;
+
+enum FilePermissionPolicy {
+ // Any access should be always denied.
+ FILE_PERMISSION_ALWAYS_DENY = 0x0,
+
+ // Access is sandboxed, no extra permission check is necessary.
+ FILE_PERMISSION_SANDBOX = 1 << 0,
+
+ // Access should be restricted to read-only.
+ FILE_PERMISSION_READ_ONLY = 1 << 1,
+
+ // Access should be examined by per-file permission policy.
+ FILE_PERMISSION_USE_FILE_PERMISSION = 1 << 2,
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_
diff --git a/chromium/webkit/browser/fileapi/file_stream_writer.h b/chromium/webkit/browser/fileapi/file_stream_writer.h
new file mode 100644
index 00000000000..7262cdf6ccd
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_stream_writer.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_
+
+#include "base/basictypes.h"
+#include "net/base/completion_callback.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace net {
+class IOBuffer;
+}
+
+namespace fileapi {
+
+// A generic interface for writing to a file-like object.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileStreamWriter {
+ public:
+ // Closes the file. If there's an in-flight operation, it is canceled (i.e.,
+ // the callback function associated with the operation is not called).
+ virtual ~FileStreamWriter() {}
+
+ // Writes to the current cursor position asynchronously.
+ //
+ // Up to buf_len bytes will be written. (In other words, partial
+ // writes are allowed.) If the write completed synchronously, it returns
+ // the number of bytes written. If the operation could not be performed, it
+ // returns an error code. Otherwise, net::ERR_IO_PENDING is returned, and the
+ // callback will be run on the thread where Write() was called when the write
+ // has completed.
+ //
+ // This errors out (either synchronously or via callback) with:
+ // net::ERR_FILE_NOT_FOUND: When the target file is not found.
+ // net::ERR_ACCESS_DENIED: When the target file is a directory or
+ // the writer has no permission on the file.
+ // net::ERR_FILE_NO_SPACE: When the write will result in out of quota
+ // or there is not enough room left on the disk.
+ //
+ // It is invalid to call Write while there is an in-flight async operation.
+ virtual int Write(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) = 0;
+
+ // Cancels an in-flight async operation.
+ //
+ // If the cancel is finished synchronously, it returns net::OK. If the
+ // cancel could not be performed, it returns an error code. Especially when
+ // there is no in-flight operation, net::ERR_UNEXPECTED is returned.
+ // Otherwise, net::ERR_IO_PENDING is returned, and the callback will be run on
+ // the thread where Cancel() was called when the cancel has completed. It is
+ // invalid to call Cancel() more than once on the same async operation.
+ //
+ // In either case, the callback function passed to the in-flight async
+ // operation is dismissed immediately when Cancel() is called, and thus
+ // will never be called.
+ virtual int Cancel(const net::CompletionCallback& callback) = 0;
+
+ // Flushes the data written so far.
+ //
+ // If the flush finished synchronously, it return net::OK. If the flush could
+ // not be performed, it returns an error code. Otherwise, net::ERR_IO_PENDING
+ // is returned, and the callback will be run on the thread where Flush() was
+ // called when the flush has completed.
+ //
+ // It is invalid to call Flush while there is an in-flight async operation.
+ virtual int Flush(const net::CompletionCallback& callback) = 0;
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_backend.h b/chromium/webkit/browser/fileapi/file_system_backend.h
new file mode 100644
index 00000000000..b368db6c928
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_backend.h
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "webkit/browser/fileapi/file_permission_policy.h"
+#include "webkit/browser/fileapi/open_file_system_mode.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+class GURL;
+
+namespace webkit_blob {
+class FileStreamReader;
+}
+
+namespace fileapi {
+
+class AsyncFileUtil;
+class CopyOrMoveFileValidatorFactory;
+class FileSystemURL;
+class FileStreamWriter;
+class FileSystemContext;
+class FileSystemFileUtil;
+class FileSystemOperation;
+class FileSystemQuotaUtil;
+
+// An interface for defining a file system backend.
+//
+// NOTE: when you implement a new FileSystemBackend for your own
+// FileSystem module, please contact to kinuko@chromium.org.
+//
+class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemBackend {
+ public:
+ // Callback for InitializeFileSystem.
+ typedef base::Callback<void(const GURL& root_url,
+ const std::string& name,
+ base::PlatformFileError error)>
+ OpenFileSystemCallback;
+ virtual ~FileSystemBackend() {}
+
+ // Returns true if this filesystem backend can handle |type|.
+ // One filesystem backend may be able to handle multiple filesystem types.
+ virtual bool CanHandleType(FileSystemType type) const = 0;
+
+ // This method is called right after the backend is registered in the
+ // FileSystemContext and before any other methods are called. Each backend can
+ // do additional initialization which depends on FileSystemContext here.
+ virtual void Initialize(FileSystemContext* context) = 0;
+
+ // Opens the filesystem for the given |origin_url| and |type|.
+ // This verifies if it is allowed to request (or create) the filesystem
+ // and if it can access (or create) the root directory.
+ // If |mode| is CREATE_IF_NONEXISTENT calling this may also create
+ // the root directory (and/or related database entries etc) for
+ // the filesystem if it doesn't exist.
+ virtual void OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) = 0;
+
+ // Returns the specialized FileSystemFileUtil for this backend.
+ // It is ok to return NULL if the filesystem doesn't support synchronous
+ // version of FileUtil.
+ virtual FileSystemFileUtil* GetFileUtil(FileSystemType type) = 0;
+
+ // Returns the specialized AsyncFileUtil for this backend.
+ virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) = 0;
+
+ // Returns the specialized CopyOrMoveFileValidatorFactory for this backend
+ // and |type|. If |error_code| is PLATFORM_FILE_OK and the result is NULL,
+ // then no validator is required.
+ virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type, base::PlatformFileError* error_code) = 0;
+
+ // Returns a new instance of the specialized FileSystemOperation for this
+ // backend based on the given triplet of |origin_url|, |file_system_type|
+ // and |virtual_path|. On failure to create a file system operation, set
+ // |error_code| correspondingly.
+ // This method is usually dispatched by
+ // FileSystemContext::CreateFileSystemOperation.
+ virtual FileSystemOperation* CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::PlatformFileError* error_code) const = 0;
+
+ // Creates a new file stream reader for a given filesystem URL |url| with an
+ // offset |offset|. |expected_modification_time| specifies the expected last
+ // modification if the value is non-null, the reader will check the underlying
+ // file's actual modification time to see if the file has been modified, and
+ // if it does any succeeding read operations should fail with
+ // ERR_UPLOAD_FILE_CHANGED error.
+ // This method itself does *not* check if the given path exists and is a
+ // regular file.
+ virtual scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const = 0;
+
+ // Creates a new file stream writer for a given filesystem URL |url| with an
+ // offset |offset|.
+ // This method itself does *not* check if the given path exists and is a
+ // regular file.
+ virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context) const = 0;
+
+ // Returns the specialized FileSystemQuotaUtil for this backend.
+ // This could return NULL if this backend does not support quota.
+ virtual FileSystemQuotaUtil* GetQuotaUtil() = 0;
+};
+
+// An interface to control external file system access permissions.
+// TODO(satorux): Move this out of 'webkit/browser/fileapi'. crbug.com/257279
+class ExternalFileSystemBackend : public FileSystemBackend {
+ public:
+ // Returns true if |url| is allowed to be accessed.
+ // This is supposed to perform ExternalFileSystem-specific security
+ // checks.
+ virtual bool IsAccessAllowed(const fileapi::FileSystemURL& url) const = 0;
+ // Returns the list of top level directories that are exposed by this
+ // provider. This list is used to set appropriate child process file access
+ // permissions.
+ virtual std::vector<base::FilePath> GetRootDirectories() const = 0;
+ // Grants access to all external file system from extension identified with
+ // |extension_id|.
+ virtual void GrantFullAccessToExtension(const std::string& extension_id) = 0;
+ // Grants access to |virtual_path| from |origin_url|.
+ virtual void GrantFileAccessToExtension(
+ const std::string& extension_id,
+ const base::FilePath& virtual_path) = 0;
+ // Revokes file access from extension identified with |extension_id|.
+ virtual void RevokeAccessForExtension(
+ const std::string& extension_id) = 0;
+ // Gets virtual path by known filesystem path. Returns false when filesystem
+ // path is not exposed by this provider.
+ virtual bool GetVirtualPath(const base::FilePath& file_system_path,
+ base::FilePath* virtual_path) = 0;
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_context.cc b/chromium/webkit/browser/fileapi/file_system_context.cc
new file mode 100644
index 00000000000..f2973d35fa2
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_context.cc
@@ -0,0 +1,448 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_context.h"
+
+#include "base/bind.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/task_runner_util.h"
+#include "url/gurl.h"
+#include "webkit/browser/blob/file_stream_reader.h"
+#include "webkit/browser/fileapi/copy_or_move_file_validator.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/file_permission_policy.h"
+#include "webkit/browser/fileapi/file_stream_writer.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/file_system_options.h"
+#include "webkit/browser/fileapi/file_system_quota_client.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/fileapi/isolated_file_system_backend.h"
+#include "webkit/browser/fileapi/mount_points.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+#include "webkit/browser/fileapi/test_file_system_backend.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using quota::QuotaClient;
+
+namespace fileapi {
+
+namespace {
+
+QuotaClient* CreateQuotaClient(
+ FileSystemContext* context,
+ bool is_incognito) {
+ return new FileSystemQuotaClient(context, is_incognito);
+}
+
+void DidOpenFileSystem(
+ const FileSystemContext::OpenFileSystemCallback& callback,
+ const GURL& filesystem_root,
+ const std::string& filesystem_name,
+ base::PlatformFileError error) {
+ callback.Run(error, filesystem_name, filesystem_root);
+}
+
+} // namespace
+
+// static
+int FileSystemContext::GetPermissionPolicy(FileSystemType type) {
+ switch (type) {
+ case kFileSystemTypeTemporary:
+ case kFileSystemTypePersistent:
+ case kFileSystemTypeSyncable:
+ return FILE_PERMISSION_SANDBOX;
+
+ case kFileSystemTypeDrive:
+ case kFileSystemTypeNativeForPlatformApp:
+ case kFileSystemTypeNativeLocal:
+ return FILE_PERMISSION_USE_FILE_PERMISSION;
+
+ case kFileSystemTypeRestrictedNativeLocal:
+ return FILE_PERMISSION_READ_ONLY |
+ FILE_PERMISSION_USE_FILE_PERMISSION;
+
+ // Following types are only accessed via IsolatedFileSystem, and
+ // don't have their own permission policies.
+ case kFileSystemTypeDeviceMedia:
+ case kFileSystemTypeDragged:
+ case kFileSystemTypeForTransientFile:
+ case kFileSystemTypeItunes:
+ case kFileSystemTypeNativeMedia:
+ case kFileSystemTypePicasa:
+ return FILE_PERMISSION_ALWAYS_DENY;
+
+ // Following types only appear as mount_type, and will not be
+ // queried for their permission policies.
+ case kFileSystemTypeIsolated:
+ case kFileSystemTypeExternal:
+ return FILE_PERMISSION_ALWAYS_DENY;
+
+ // Following types should not be used to access files by FileAPI clients.
+ case kFileSystemTypeTest:
+ case kFileSystemTypeSyncableForInternalSync:
+ case kFileSystemInternalTypeEnumEnd:
+ case kFileSystemInternalTypeEnumStart:
+ case kFileSystemTypeUnknown:
+ return FILE_PERMISSION_ALWAYS_DENY;
+ }
+ NOTREACHED();
+ return FILE_PERMISSION_ALWAYS_DENY;
+}
+
+FileSystemContext::FileSystemContext(
+ base::SingleThreadTaskRunner* io_task_runner,
+ base::SequencedTaskRunner* file_task_runner,
+ ExternalMountPoints* external_mount_points,
+ quota::SpecialStoragePolicy* special_storage_policy,
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ ScopedVector<FileSystemBackend> additional_backends,
+ const base::FilePath& partition_path,
+ const FileSystemOptions& options)
+ : io_task_runner_(io_task_runner),
+ default_file_task_runner_(file_task_runner),
+ quota_manager_proxy_(quota_manager_proxy),
+ sandbox_delegate_(new SandboxFileSystemBackendDelegate(
+ quota_manager_proxy,
+ file_task_runner,
+ partition_path,
+ special_storage_policy,
+ options)),
+ sandbox_backend_(new SandboxFileSystemBackend(
+ sandbox_delegate_.get())),
+ isolated_backend_(new IsolatedFileSystemBackend()),
+ additional_backends_(additional_backends.Pass()),
+ external_mount_points_(external_mount_points),
+ partition_path_(partition_path),
+ operation_runner_(new FileSystemOperationRunner(this)) {
+ if (quota_manager_proxy) {
+ quota_manager_proxy->RegisterClient(CreateQuotaClient(
+ this, options.is_incognito()));
+ }
+
+ RegisterBackend(sandbox_backend_.get());
+ RegisterBackend(isolated_backend_.get());
+
+ for (ScopedVector<FileSystemBackend>::const_iterator iter =
+ additional_backends_.begin();
+ iter != additional_backends_.end(); ++iter) {
+ RegisterBackend(*iter);
+ }
+
+ sandbox_backend_->Initialize(this);
+ isolated_backend_->Initialize(this);
+ for (ScopedVector<FileSystemBackend>::const_iterator iter =
+ additional_backends_.begin();
+ iter != additional_backends_.end(); ++iter) {
+ (*iter)->Initialize(this);
+ }
+
+ // Additional mount points must be added before regular system-wide
+ // mount points.
+ if (external_mount_points)
+ url_crackers_.push_back(external_mount_points);
+ url_crackers_.push_back(ExternalMountPoints::GetSystemInstance());
+ url_crackers_.push_back(IsolatedContext::GetInstance());
+}
+
+bool FileSystemContext::DeleteDataForOriginOnFileThread(
+ const GURL& origin_url) {
+ DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
+ DCHECK(origin_url == origin_url.GetOrigin());
+
+ bool success = true;
+ for (FileSystemBackendMap::iterator iter = backend_map_.begin();
+ iter != backend_map_.end();
+ ++iter) {
+ FileSystemBackend* backend = iter->second;
+ if (!backend->GetQuotaUtil())
+ continue;
+ if (backend->GetQuotaUtil()->DeleteOriginDataOnFileThread(
+ this, quota_manager_proxy(), origin_url, iter->first)
+ != base::PLATFORM_FILE_OK) {
+ // Continue the loop, but record the failure.
+ success = false;
+ }
+ }
+
+ return success;
+}
+
+void FileSystemContext::Shutdown() {
+ if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ io_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&FileSystemContext::Shutdown,
+ make_scoped_refptr(this)));
+ return;
+ }
+ operation_runner_->Shutdown();
+}
+
+FileSystemQuotaUtil*
+FileSystemContext::GetQuotaUtil(FileSystemType type) const {
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend)
+ return NULL;
+ return backend->GetQuotaUtil();
+}
+
+AsyncFileUtil* FileSystemContext::GetAsyncFileUtil(
+ FileSystemType type) const {
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend)
+ return NULL;
+ return backend->GetAsyncFileUtil(type);
+}
+
+FileSystemFileUtil* FileSystemContext::GetFileUtil(
+ FileSystemType type) const {
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend)
+ return NULL;
+ return backend->GetFileUtil(type);
+}
+
+CopyOrMoveFileValidatorFactory*
+FileSystemContext::GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type, base::PlatformFileError* error_code) const {
+ DCHECK(error_code);
+ *error_code = base::PLATFORM_FILE_OK;
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend)
+ return NULL;
+ return backend->GetCopyOrMoveFileValidatorFactory(
+ type, error_code);
+}
+
+FileSystemBackend* FileSystemContext::GetFileSystemBackend(
+ FileSystemType type) const {
+ FileSystemBackendMap::const_iterator found = backend_map_.find(type);
+ if (found != backend_map_.end())
+ return found->second;
+ NOTREACHED() << "Unknown filesystem type: " << type;
+ return NULL;
+}
+
+bool FileSystemContext::IsSandboxFileSystem(FileSystemType type) const {
+ return GetQuotaUtil(type) != NULL;
+}
+
+const UpdateObserverList* FileSystemContext::GetUpdateObservers(
+ FileSystemType type) const {
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (backend->GetQuotaUtil())
+ return backend->GetQuotaUtil()->GetUpdateObservers(type);
+ return NULL;
+}
+
+const AccessObserverList* FileSystemContext::GetAccessObservers(
+ FileSystemType type) const {
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (backend->GetQuotaUtil())
+ return backend->GetQuotaUtil()->GetAccessObservers(type);
+ return NULL;
+}
+
+void FileSystemContext::GetFileSystemTypes(
+ std::vector<FileSystemType>* types) const {
+ types->clear();
+ for (FileSystemBackendMap::const_iterator iter = backend_map_.begin();
+ iter != backend_map_.end(); ++iter)
+ types->push_back(iter->first);
+}
+
+ExternalFileSystemBackend*
+FileSystemContext::external_backend() const {
+ return static_cast<ExternalFileSystemBackend*>(
+ GetFileSystemBackend(kFileSystemTypeExternal));
+}
+
+void FileSystemContext::OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend) {
+ callback.Run(base::PLATFORM_FILE_ERROR_SECURITY, std::string(), GURL());
+ return;
+ }
+
+ backend->OpenFileSystem(origin_url, type, mode,
+ base::Bind(&DidOpenFileSystem, callback));
+}
+
+void FileSystemContext::DeleteFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ const DeleteFileSystemCallback& callback) {
+ DCHECK(origin_url == origin_url.GetOrigin());
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend) {
+ callback.Run(base::PLATFORM_FILE_ERROR_SECURITY);
+ return;
+ }
+ if (!backend->GetQuotaUtil()) {
+ callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ default_file_task_runner(),
+ FROM_HERE,
+ // It is safe to pass Unretained(quota_util) since context owns it.
+ base::Bind(&FileSystemQuotaUtil::DeleteOriginDataOnFileThread,
+ base::Unretained(backend->GetQuotaUtil()),
+ make_scoped_refptr(this),
+ base::Unretained(quota_manager_proxy()),
+ origin_url,
+ type),
+ callback);
+}
+
+scoped_ptr<webkit_blob::FileStreamReader>
+FileSystemContext::CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time) {
+ if (!url.is_valid())
+ return scoped_ptr<webkit_blob::FileStreamReader>();
+ FileSystemBackend* backend = GetFileSystemBackend(url.type());
+ if (!backend)
+ return scoped_ptr<webkit_blob::FileStreamReader>();
+ return backend->CreateFileStreamReader(
+ url, offset, expected_modification_time, this);
+}
+
+scoped_ptr<FileStreamWriter> FileSystemContext::CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset) {
+ if (!url.is_valid())
+ return scoped_ptr<FileStreamWriter>();
+ FileSystemBackend* backend = GetFileSystemBackend(url.type());
+ if (!backend)
+ return scoped_ptr<FileStreamWriter>();
+ return backend->CreateFileStreamWriter(url, offset, this);
+}
+
+scoped_ptr<FileSystemOperationRunner>
+FileSystemContext::CreateFileSystemOperationRunner() {
+ return make_scoped_ptr(new FileSystemOperationRunner(this));
+}
+
+FileSystemURL FileSystemContext::CrackURL(const GURL& url) const {
+ return CrackFileSystemURL(FileSystemURL(url));
+}
+
+FileSystemURL FileSystemContext::CreateCrackedFileSystemURL(
+ const GURL& origin,
+ FileSystemType type,
+ const base::FilePath& path) const {
+ return CrackFileSystemURL(FileSystemURL(origin, type, path));
+}
+
+#if defined(OS_CHROMEOS) && defined(GOOGLE_CHROME_BUILD)
+void FileSystemContext::EnableTemporaryFileSystemInIncognito() {
+ sandbox_backend_->set_enable_temporary_file_system_in_incognito(true);
+}
+#endif
+
+FileSystemContext::~FileSystemContext() {
+}
+
+void FileSystemContext::DeleteOnCorrectThread() const {
+ if (!io_task_runner_->RunsTasksOnCurrentThread() &&
+ io_task_runner_->DeleteSoon(FROM_HERE, this)) {
+ return;
+ }
+ delete this;
+}
+
+FileSystemOperation* FileSystemContext::CreateFileSystemOperation(
+ const FileSystemURL& url, base::PlatformFileError* error_code) {
+ if (!url.is_valid()) {
+ if (error_code)
+ *error_code = base::PLATFORM_FILE_ERROR_INVALID_URL;
+ return NULL;
+ }
+
+ FileSystemBackend* backend = GetFileSystemBackend(url.type());
+ if (!backend) {
+ if (error_code)
+ *error_code = base::PLATFORM_FILE_ERROR_FAILED;
+ return NULL;
+ }
+
+ base::PlatformFileError fs_error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ backend->CreateFileSystemOperation(url, this, &fs_error);
+
+ if (error_code)
+ *error_code = fs_error;
+ return operation;
+}
+
+FileSystemURL FileSystemContext::CrackFileSystemURL(
+ const FileSystemURL& url) const {
+ if (!url.is_valid())
+ return FileSystemURL();
+
+ // The returned value in case there is no crackers which can crack the url.
+ // This is valid situation for non isolated/external file systems.
+ FileSystemURL current = url;
+
+ // File system may be mounted multiple times (e.g., an isolated filesystem on
+ // top of an external filesystem). Hence cracking needs to be iterated.
+ for (;;) {
+ FileSystemURL cracked = current;
+ for (size_t i = 0; i < url_crackers_.size(); ++i) {
+ if (!url_crackers_[i]->HandlesFileSystemMountType(current.type()))
+ continue;
+ cracked = url_crackers_[i]->CrackFileSystemURL(current);
+ if (cracked.is_valid())
+ break;
+ }
+ if (cracked == current)
+ break;
+ current = cracked;
+ }
+ return current;
+}
+
+void FileSystemContext::RegisterBackend(
+ FileSystemBackend* backend) {
+ const FileSystemType mount_types[] = {
+ kFileSystemTypeTemporary,
+ kFileSystemTypePersistent,
+ kFileSystemTypeIsolated,
+ kFileSystemTypeExternal,
+ };
+ // Register file system backends for public mount types.
+ for (size_t j = 0; j < ARRAYSIZE_UNSAFE(mount_types); ++j) {
+ if (backend->CanHandleType(mount_types[j])) {
+ const bool inserted = backend_map_.insert(
+ std::make_pair(mount_types[j], backend)).second;
+ DCHECK(inserted);
+ }
+ }
+ // Register file system backends for internal types.
+ for (int t = kFileSystemInternalTypeEnumStart + 1;
+ t < kFileSystemInternalTypeEnumEnd; ++t) {
+ FileSystemType type = static_cast<FileSystemType>(t);
+ if (backend->CanHandleType(type)) {
+ const bool inserted = backend_map_.insert(
+ std::make_pair(type, backend)).second;
+ DCHECK(inserted);
+ }
+ }
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_context.h b/chromium/webkit/browser/fileapi/file_system_context.h
new file mode 100644
index 00000000000..528f7a26021
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_context.h
@@ -0,0 +1,329 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/platform_file.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/open_file_system_mode.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "webkit/browser/fileapi/task_runner_bound_observer_list.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+class SingleThreadTaskRunner;
+}
+
+namespace chrome {
+class NativeMediaFileUtilTest;
+}
+
+namespace quota {
+class QuotaManagerProxy;
+class SpecialStoragePolicy;
+}
+
+namespace webkit_blob {
+class BlobURLRequestJobTest;
+class FileStreamReader;
+}
+
+namespace fileapi {
+
+class AsyncFileUtil;
+class CopyOrMoveFileValidatorFactory;
+class ExternalFileSystemBackend;
+class ExternalMountPoints;
+class FileStreamWriter;
+class FileSystemFileUtil;
+class FileSystemBackend;
+class FileSystemOperation;
+class FileSystemOperationRunner;
+class FileSystemOptions;
+class FileSystemQuotaUtil;
+class FileSystemURL;
+class IsolatedFileSystemBackend;
+class MountPoints;
+class SandboxFileSystemBackend;
+
+struct DefaultContextDeleter;
+
+// This class keeps and provides a file system context for FileSystem API.
+// An instance of this class is created and owned by profile.
+class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemContext
+ : public base::RefCountedThreadSafe<FileSystemContext,
+ DefaultContextDeleter> {
+ public:
+ // Returns file permission policy we should apply for the given |type|.
+ // The return value must be bitwise-or'd of FilePermissionPolicy.
+ //
+ // Note: if a part of a filesystem is returned via 'Isolated' mount point,
+ // its per-filesystem permission overrides the underlying filesystem's
+ // permission policy.
+ static int GetPermissionPolicy(FileSystemType type);
+
+ // file_task_runner is used as default TaskRunner.
+ // Unless a FileSystemBackend is overridden in CreateFileSystemOperation,
+ // it is used for all file operations and file related meta operations.
+ // The code assumes that file_task_runner->RunsTasksOnCurrentThread()
+ // returns false if the current task is not running on the thread that allows
+ // blocking file operations (like SequencedWorkerPool implementation does).
+ //
+ // |external_mount_points| contains non-system external mount points available
+ // in the context. If not NULL, it will be used during URL cracking.
+ // |external_mount_points| may be NULL only on platforms different from
+ // ChromeOS (i.e. platforms that don't use external_mount_point_provider).
+ //
+ // |additional_backends| are added to the internal backend map
+ // to serve filesystem requests for non-regular types.
+ // If none is given, this context only handles HTML5 Sandbox FileSystem
+ // and Drag-and-drop Isolated FileSystem requests.
+ FileSystemContext(
+ base::SingleThreadTaskRunner* io_task_runner,
+ base::SequencedTaskRunner* file_task_runner,
+ ExternalMountPoints* external_mount_points,
+ quota::SpecialStoragePolicy* special_storage_policy,
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ ScopedVector<FileSystemBackend> additional_backends,
+ const base::FilePath& partition_path,
+ const FileSystemOptions& options);
+
+ bool DeleteDataForOriginOnFileThread(const GURL& origin_url);
+
+ quota::QuotaManagerProxy* quota_manager_proxy() const {
+ return quota_manager_proxy_.get();
+ }
+
+ // Discards inflight operations in the operation runner.
+ void Shutdown();
+
+ // Returns a quota util for a given filesystem type. This may
+ // return NULL if the type does not support the usage tracking or
+ // it is not a quota-managed storage.
+ FileSystemQuotaUtil* GetQuotaUtil(FileSystemType type) const;
+
+ // Returns the appropriate AsyncFileUtil instance for the given |type|.
+ AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) const;
+
+ // Returns the appropriate FileUtil instance for the given |type|.
+ // This may return NULL if it is given an invalid type or the filesystem
+ // does not support synchronous file operations.
+ FileSystemFileUtil* GetFileUtil(FileSystemType type) const;
+
+ // Returns the appropriate CopyOrMoveFileValidatorFactory for the given
+ // |type|. If |error_code| is PLATFORM_FILE_OK and the result is NULL,
+ // then no validator is required.
+ CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type, base::PlatformFileError* error_code) const;
+
+ // Returns the file system backend instance for the given |type|.
+ // This may return NULL if it is given an invalid or unsupported filesystem
+ // type.
+ FileSystemBackend* GetFileSystemBackend(
+ FileSystemType type) const;
+
+ // Returns true for sandboxed filesystems. Currently this does
+ // the same as GetQuotaUtil(type) != NULL. (In an assumption that
+ // all sandboxed filesystems must cooperate with QuotaManager so that
+ // they can get deleted)
+ bool IsSandboxFileSystem(FileSystemType type) const;
+
+ // Returns observers for the given filesystem type.
+ const UpdateObserverList* GetUpdateObservers(FileSystemType type) const;
+ const AccessObserverList* GetAccessObservers(FileSystemType type) const;
+
+ // Returns all registered filesystem types.
+ void GetFileSystemTypes(std::vector<FileSystemType>* types) const;
+
+ // Returns a FileSystemBackend instance for external filesystem
+ // type, which is used only by chromeos for now. This is equivalent to
+ // calling GetFileSystemBackend(kFileSystemTypeExternal).
+ ExternalFileSystemBackend* external_backend() const;
+
+ // Used for OpenFileSystem.
+ typedef base::Callback<void(base::PlatformFileError result,
+ const std::string& name,
+ const GURL& root)> OpenFileSystemCallback;
+
+ // Used for DeleteFileSystem.
+ typedef base::Callback<void(base::PlatformFileError result)>
+ DeleteFileSystemCallback;
+
+ // Opens the filesystem for the given |origin_url| and |type|, and dispatches
+ // |callback| on completion.
+ // If |create| is true this may actually set up a filesystem instance
+ // (e.g. by creating the root directory or initializing the database
+ // entry etc).
+ void OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback);
+
+ // Deletes the filesystem for the given |origin_url| and |type|. This should
+ // be called on the IO Thread.
+ void DeleteFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ const DeleteFileSystemCallback& callback);
+
+ // Creates new FileStreamReader instance to read a file pointed by the given
+ // filesystem URL |url| starting from |offset|. |expected_modification_time|
+ // specifies the expected last modification if the value is non-null, the
+ // reader will check the underlying file's actual modification time to see if
+ // the file has been modified, and if it does any succeeding read operations
+ // should fail with ERR_UPLOAD_FILE_CHANGED error.
+ // This method internally cracks the |url|, get an appropriate
+ // FileSystemBackend for the URL and call the backend's CreateFileReader.
+ // The resolved FileSystemBackend could perform further specialization
+ // depending on the filesystem type pointed by the |url|.
+ scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time);
+
+ // Creates new FileStreamWriter instance to write into a file pointed by
+ // |url| from |offset|.
+ scoped_ptr<FileStreamWriter> CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset);
+
+ // Creates a new FileSystemOperationRunner.
+ scoped_ptr<FileSystemOperationRunner> CreateFileSystemOperationRunner();
+
+ base::SequencedTaskRunner* default_file_task_runner() {
+ return default_file_task_runner_.get();
+ }
+
+ FileSystemOperationRunner* operation_runner() {
+ return operation_runner_.get();
+ }
+
+ const base::FilePath& partition_path() const { return partition_path_; }
+
+ // Same as |CrackFileSystemURL|, but cracks FileSystemURL created from |url|.
+ FileSystemURL CrackURL(const GURL& url) const;
+ // Same as |CrackFileSystemURL|, but cracks FileSystemURL created from method
+ // arguments.
+ FileSystemURL CreateCrackedFileSystemURL(const GURL& origin,
+ FileSystemType type,
+ const base::FilePath& path) const;
+
+#if defined(OS_CHROMEOS) && defined(GOOGLE_CHROME_BUILD)
+ // Used only on ChromeOS for now.
+ void EnableTemporaryFileSystemInIncognito();
+#endif
+
+ SandboxFileSystemBackendDelegate* sandbox_delegate() {
+ return sandbox_delegate_.get();
+ }
+
+ private:
+ typedef std::map<FileSystemType, FileSystemBackend*>
+ FileSystemBackendMap;
+
+ // For CreateFileSystemOperation.
+ friend class FileSystemOperationRunner;
+
+ // For sandbox_backend().
+ friend class SandboxFileSystemTestHelper;
+
+ // Deleters.
+ friend struct DefaultContextDeleter;
+ friend class base::DeleteHelper<FileSystemContext>;
+ friend class base::RefCountedThreadSafe<FileSystemContext,
+ DefaultContextDeleter>;
+ ~FileSystemContext();
+
+ void DeleteOnCorrectThread() const;
+
+ // Creates a new FileSystemOperation instance by getting an appropriate
+ // FileSystemBackend for |url| and calling the backend's corresponding
+ // CreateFileSystemOperation method.
+ // The resolved FileSystemBackend could perform further specialization
+ // depending on the filesystem type pointed by the |url|.
+ //
+ // Called by FileSystemOperationRunner.
+ FileSystemOperation* CreateFileSystemOperation(
+ const FileSystemURL& url,
+ base::PlatformFileError* error_code);
+
+ // For non-cracked isolated and external mount points, returns a FileSystemURL
+ // created by cracking |url|. The url is cracked using MountPoints registered
+ // as |url_crackers_|. If the url cannot be cracked, returns invalid
+ // FileSystemURL.
+ //
+ // If the original url does not point to an isolated or external filesystem,
+ // returns the original url, without attempting to crack it.
+ FileSystemURL CrackFileSystemURL(const FileSystemURL& url) const;
+
+ // For initial backend_map construction. This must be called only from
+ // the constructor.
+ void RegisterBackend(FileSystemBackend* backend);
+
+ // Returns a FileSystemBackend, used only by test code.
+ SandboxFileSystemBackend* sandbox_backend() const {
+ return sandbox_backend_.get();
+ }
+
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> default_file_task_runner_;
+
+ scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_;
+
+ scoped_ptr<SandboxFileSystemBackendDelegate> sandbox_delegate_;
+
+ // Regular file system backends.
+ scoped_ptr<SandboxFileSystemBackend> sandbox_backend_;
+ scoped_ptr<IsolatedFileSystemBackend> isolated_backend_;
+
+ // Additional file system backends.
+ ScopedVector<FileSystemBackend> additional_backends_;
+
+ // Registered file system backends.
+ // The map must be constructed in the constructor since it can be accessed
+ // on multiple threads.
+ // This map itself doesn't retain each backend's ownership; ownerships
+ // of the backends are held by additional_backends_ or other scoped_ptr
+ // backend fields.
+ FileSystemBackendMap backend_map_;
+
+ // External mount points visible in the file system context (excluding system
+ // external mount points).
+ scoped_refptr<ExternalMountPoints> external_mount_points_;
+
+ // MountPoints used to crack FileSystemURLs. The MountPoints are ordered
+ // in order they should try to crack a FileSystemURL.
+ std::vector<MountPoints*> url_crackers_;
+
+ // The base path of the storage partition for this context.
+ const base::FilePath partition_path_;
+
+ scoped_ptr<FileSystemOperationRunner> operation_runner_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(FileSystemContext);
+};
+
+struct DefaultContextDeleter {
+ static void Destruct(const FileSystemContext* context) {
+ context->DeleteOnCorrectThread();
+ }
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_context_unittest.cc b/chromium/webkit/browser/fileapi/file_system_context_unittest.cc
new file mode 100644
index 00000000000..92256af625b
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_context_unittest.cc
@@ -0,0 +1,328 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_context.h"
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/fileapi/mock_file_system_options.h"
+#include "webkit/browser/quota/mock_quota_manager.h"
+#include "webkit/browser/quota/mock_special_storage_policy.h"
+
+#define FPL(x) FILE_PATH_LITERAL(x)
+
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+#define DRIVE FPL("C:")
+#else
+#define DRIVE
+#endif
+
+namespace fileapi {
+
+namespace {
+
+const char kTestOrigin[] = "http://chromium.org/";
+const base::FilePath::CharType kVirtualPathNoRoot[] = FPL("root/file");
+
+GURL CreateRawFileSystemURL(const std::string& type_str,
+ const std::string& fs_id) {
+ std::string url_str = base::StringPrintf(
+ "filesystem:http://chromium.org/%s/%s/root/file",
+ type_str.c_str(),
+ fs_id.c_str());
+ return GURL(url_str);
+}
+
+class FileSystemContextTest : public testing::Test {
+ public:
+ FileSystemContextTest() {}
+
+ virtual void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+
+ storage_policy_ = new quota::MockSpecialStoragePolicy();
+
+ mock_quota_manager_ =
+ new quota::MockQuotaManager(false /* is_incognito */,
+ data_dir_.path(),
+ base::MessageLoopProxy::current().get(),
+ base::MessageLoopProxy::current().get(),
+ storage_policy_.get());
+ }
+
+ protected:
+ FileSystemContext* CreateFileSystemContextForTest(
+ ExternalMountPoints* external_mount_points) {
+ return new FileSystemContext(base::MessageLoopProxy::current().get(),
+ base::MessageLoopProxy::current().get(),
+ external_mount_points,
+ storage_policy_.get(),
+ mock_quota_manager_->proxy(),
+ ScopedVector<FileSystemBackend>(),
+ data_dir_.path(),
+ CreateAllowFileAccessOptions());
+ }
+
+ // Verifies a *valid* filesystem url has expected values.
+ void ExpectFileSystemURLMatches(const FileSystemURL& url,
+ const GURL& expect_origin,
+ FileSystemType expect_mount_type,
+ FileSystemType expect_type,
+ const base::FilePath& expect_path,
+ const base::FilePath& expect_virtual_path,
+ const std::string& expect_filesystem_id) {
+ EXPECT_TRUE(url.is_valid());
+
+ EXPECT_EQ(expect_origin, url.origin());
+ EXPECT_EQ(expect_mount_type, url.mount_type());
+ EXPECT_EQ(expect_type, url.type());
+ EXPECT_EQ(expect_path, url.path());
+ EXPECT_EQ(expect_virtual_path, url.virtual_path());
+ EXPECT_EQ(expect_filesystem_id, url.filesystem_id());
+ }
+
+ private:
+ base::ScopedTempDir data_dir_;
+ base::MessageLoop message_loop_;
+ scoped_refptr<quota::SpecialStoragePolicy> storage_policy_;
+ scoped_refptr<quota::MockQuotaManager> mock_quota_manager_;
+};
+
+// It is not valid to pass NULL ExternalMountPoints to FileSystemContext on
+// ChromeOS.
+#if !defined(OS_CHROMEOS)
+TEST_F(FileSystemContextTest, NullExternalMountPoints) {
+ scoped_refptr<FileSystemContext> file_system_context(
+ CreateFileSystemContextForTest(NULL));
+
+ // Cracking system external mount and isolated mount points should work.
+ std::string isolated_name = "root";
+ std::string isolated_id =
+ IsolatedContext::GetInstance()->RegisterFileSystemForPath(
+ kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/isolated/root")),
+ &isolated_name);
+ // Register system external mount point.
+ ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ "system",
+ kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/sys/"))));
+
+ FileSystemURL cracked_isolated = file_system_context->CrackURL(
+ CreateRawFileSystemURL("isolated", isolated_id));
+
+ ExpectFileSystemURLMatches(
+ cracked_isolated,
+ GURL(kTestOrigin),
+ kFileSystemTypeIsolated,
+ kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/isolated/root/file")).NormalizePathSeparators(),
+ base::FilePath::FromUTF8Unsafe(isolated_id).Append(FPL("root/file")).
+ NormalizePathSeparators(),
+ isolated_id);
+
+ FileSystemURL cracked_external = file_system_context->CrackURL(
+ CreateRawFileSystemURL("external", "system"));
+
+ ExpectFileSystemURLMatches(
+ cracked_external,
+ GURL(kTestOrigin),
+ kFileSystemTypeExternal,
+ kFileSystemTypeNativeLocal,
+ base::FilePath(
+ DRIVE FPL("/test/sys/root/file")).NormalizePathSeparators(),
+ base::FilePath(FPL("system/root/file")).NormalizePathSeparators(),
+ "system");
+
+
+ IsolatedContext::GetInstance()->RevokeFileSystem(isolated_id);
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system");
+}
+#endif // !defiend(OS_CHROMEOS)
+
+TEST_F(FileSystemContextTest, FileSystemContextKeepsMountPointsAlive) {
+ scoped_refptr<ExternalMountPoints> mount_points =
+ ExternalMountPoints::CreateRefCounted();
+
+ // Register system external mount point.
+ ASSERT_TRUE(mount_points->RegisterFileSystem(
+ "system",
+ kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/sys/"))));
+
+ scoped_refptr<FileSystemContext> file_system_context(
+ CreateFileSystemContextForTest(mount_points.get()));
+
+ // Release a MountPoints reference created in the test.
+ mount_points = NULL;
+
+ // FileSystemContext should keep a reference to the |mount_points|, so it
+ // should be able to resolve the URL.
+ FileSystemURL cracked_external = file_system_context->CrackURL(
+ CreateRawFileSystemURL("external", "system"));
+
+ ExpectFileSystemURLMatches(
+ cracked_external,
+ GURL(kTestOrigin),
+ kFileSystemTypeExternal,
+ kFileSystemTypeNativeLocal,
+ base::FilePath(
+ DRIVE FPL("/test/sys/root/file")).NormalizePathSeparators(),
+ base::FilePath(FPL("system/root/file")).NormalizePathSeparators(),
+ "system");
+
+ // No need to revoke the registered filesystem since |mount_points| lifetime
+ // is bound to this test.
+}
+
+TEST_F(FileSystemContextTest, CrackFileSystemURL) {
+ scoped_refptr<ExternalMountPoints> external_mount_points(
+ ExternalMountPoints::CreateRefCounted());
+ scoped_refptr<FileSystemContext> file_system_context(
+ CreateFileSystemContextForTest(external_mount_points.get()));
+
+ // Register an isolated mount point.
+ std::string isolated_file_system_name = "root";
+ const std::string kIsolatedFileSystemID =
+ IsolatedContext::GetInstance()->RegisterFileSystemForPath(
+ kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/isolated/root")),
+ &isolated_file_system_name);
+ // Register system external mount point.
+ ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ "system",
+ kFileSystemTypeDrive,
+ base::FilePath(DRIVE FPL("/test/sys/"))));
+ ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ "ext",
+ kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/ext"))));
+ // Register a system external mount point with the same name/id as the
+ // registered isolated mount point.
+ ASSERT_TRUE(ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ kIsolatedFileSystemID,
+ kFileSystemTypeRestrictedNativeLocal,
+ base::FilePath(DRIVE FPL("/test/system/isolated"))));
+ // Add a mount points with the same name as a system mount point to
+ // FileSystemContext's external mount points.
+ ASSERT_TRUE(external_mount_points->RegisterFileSystem(
+ "ext",
+ kFileSystemTypeNativeLocal,
+ base::FilePath(DRIVE FPL("/test/local/ext/"))));
+
+ const GURL kTestOrigin = GURL("http://chromium.org/");
+ const base::FilePath kVirtualPathNoRoot = base::FilePath(FPL("root/file"));
+
+ struct TestCase {
+ // Test case values.
+ std::string root;
+ std::string type_str;
+
+ // Expected test results.
+ bool expect_is_valid;
+ FileSystemType expect_mount_type;
+ FileSystemType expect_type;
+ const base::FilePath::CharType* expect_path;
+ std::string expect_filesystem_id;
+ };
+
+ const TestCase kTestCases[] = {
+ // Following should not be handled by the url crackers:
+ {
+ "pers_mount", "persistent", true /* is_valid */,
+ kFileSystemTypePersistent, kFileSystemTypePersistent,
+ FPL("pers_mount/root/file"),
+ std::string() /* filesystem id */
+ },
+ {
+ "temp_mount", "temporary", true /* is_valid */,
+ kFileSystemTypeTemporary, kFileSystemTypeTemporary,
+ FPL("temp_mount/root/file"),
+ std::string() /* filesystem id */
+ },
+ // Should be cracked by isolated mount points:
+ {
+ kIsolatedFileSystemID, "isolated", true /* is_valid */,
+ kFileSystemTypeIsolated, kFileSystemTypeNativeLocal,
+ DRIVE FPL("/test/isolated/root/file"),
+ kIsolatedFileSystemID
+ },
+ // Should be cracked by system mount points:
+ {
+ "system", "external", true /* is_valid */,
+ kFileSystemTypeExternal, kFileSystemTypeDrive,
+ DRIVE FPL("/test/sys/root/file"),
+ "system"
+ },
+ {
+ kIsolatedFileSystemID, "external", true /* is_valid */,
+ kFileSystemTypeExternal, kFileSystemTypeRestrictedNativeLocal,
+ DRIVE FPL("/test/system/isolated/root/file"),
+ kIsolatedFileSystemID
+ },
+ // Should be cracked by FileSystemContext's ExternalMountPoints.
+ {
+ "ext", "external", true /* is_valid */,
+ kFileSystemTypeExternal, kFileSystemTypeNativeLocal,
+ DRIVE FPL("/test/local/ext/root/file"),
+ "ext"
+ },
+ // Test for invalid filesystem url (made invalid by adding invalid
+ // filesystem type).
+ {
+ "sytem", "external", false /* is_valid */,
+ // The rest of values will be ignored.
+ kFileSystemTypeUnknown, kFileSystemTypeUnknown, FPL(""),
+ std::string()
+ },
+ // Test for URL with non-existing filesystem id.
+ {
+ "invalid", "external", false /* is_valid */,
+ // The rest of values will be ignored.
+ kFileSystemTypeUnknown, kFileSystemTypeUnknown, FPL(""),
+ std::string()
+ },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
+ const base::FilePath virtual_path =
+ base::FilePath::FromUTF8Unsafe(kTestCases[i].root).Append(kVirtualPathNoRoot);
+
+ GURL raw_url =
+ CreateRawFileSystemURL(kTestCases[i].type_str, kTestCases[i].root);
+ FileSystemURL cracked_url = file_system_context->CrackURL(raw_url);
+
+ SCOPED_TRACE(testing::Message() << "Test case " << i << ": "
+ << "Cracking URL: " << raw_url);
+
+ EXPECT_EQ(kTestCases[i].expect_is_valid, cracked_url.is_valid());
+ if (!kTestCases[i].expect_is_valid)
+ continue;
+
+ ExpectFileSystemURLMatches(
+ cracked_url,
+ GURL(kTestOrigin),
+ kTestCases[i].expect_mount_type,
+ kTestCases[i].expect_type,
+ base::FilePath(kTestCases[i].expect_path).NormalizePathSeparators(),
+ virtual_path.NormalizePathSeparators(),
+ kTestCases[i].expect_filesystem_id);
+ }
+
+ IsolatedContext::GetInstance()->RevokeFileSystemByPath(
+ base::FilePath(DRIVE FPL("/test/isolated/root")));
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("system");
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem("ext");
+ ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ kIsolatedFileSystemID);
+}
+
+} // namespace
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.cc b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.cc
new file mode 100644
index 00000000000..b91c7d00d9d
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_dir_url_request_job.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop.h"
+#include "base/platform_file.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/common/fileapi/directory_entry.h"
+
+using net::NetworkDelegate;
+using net::URLRequest;
+using net::URLRequestJob;
+using net::URLRequestStatus;
+
+namespace fileapi {
+
+FileSystemDirURLRequestJob::FileSystemDirURLRequestJob(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ FileSystemContext* file_system_context)
+ : URLRequestJob(request, network_delegate),
+ file_system_context_(file_system_context),
+ weak_factory_(this) {
+}
+
+FileSystemDirURLRequestJob::~FileSystemDirURLRequestJob() {
+}
+
+bool FileSystemDirURLRequestJob::ReadRawData(net::IOBuffer* dest, int dest_size,
+ int *bytes_read) {
+ int count = std::min(dest_size, static_cast<int>(data_.size()));
+ if (count > 0) {
+ memcpy(dest->data(), data_.data(), count);
+ data_.erase(0, count);
+ }
+ *bytes_read = count;
+ return true;
+}
+
+void FileSystemDirURLRequestJob::Start() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FileSystemDirURLRequestJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+void FileSystemDirURLRequestJob::Kill() {
+ URLRequestJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+bool FileSystemDirURLRequestJob::GetMimeType(std::string* mime_type) const {
+ *mime_type = "text/html";
+ return true;
+}
+
+bool FileSystemDirURLRequestJob::GetCharset(std::string* charset) {
+ *charset = "utf-8";
+ return true;
+}
+
+void FileSystemDirURLRequestJob::StartAsync() {
+ if (!request_)
+ return;
+ url_ = file_system_context_->CrackURL(request_->url());
+ file_system_context_->operation_runner()->ReadDirectory(
+ url_,
+ base::Bind(&FileSystemDirURLRequestJob::DidReadDirectory, this));
+}
+
+void FileSystemDirURLRequestJob::DidReadDirectory(
+ base::PlatformFileError result,
+ const std::vector<DirectoryEntry>& entries,
+ bool has_more) {
+ if (result != base::PLATFORM_FILE_OK) {
+ int rv = net::ERR_FILE_NOT_FOUND;
+ if (result == base::PLATFORM_FILE_ERROR_INVALID_URL)
+ rv = net::ERR_INVALID_URL;
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+ return;
+ }
+
+ if (!request_)
+ return;
+
+ if (data_.empty()) {
+ base::FilePath relative_path = url_.path();
+#if defined(OS_POSIX)
+ relative_path = base::FilePath(FILE_PATH_LITERAL("/") + relative_path.value());
+#endif
+ const base::string16& title = relative_path.LossyDisplayName();
+ data_.append(net::GetDirectoryListingHeader(title));
+ }
+
+ typedef std::vector<DirectoryEntry>::const_iterator EntryIterator;
+ for (EntryIterator it = entries.begin(); it != entries.end(); ++it) {
+ const base::string16& name = base::FilePath(it->name).LossyDisplayName();
+ data_.append(net::GetDirectoryListingEntry(
+ name, std::string(), it->is_directory, it->size,
+ it->last_modified_time));
+ }
+
+ if (has_more) {
+ file_system_context_->operation_runner()->ReadDirectory(
+ url_,
+ base::Bind(&FileSystemDirURLRequestJob::DidReadDirectory, this));
+ } else {
+ set_expected_content_size(data_.size());
+ NotifyHeadersComplete();
+ }
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.h b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.h
new file mode 100644
index 00000000000..933550ef4ac
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/platform_file.h"
+#include "net/url_request/url_request_job.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace fileapi {
+
+class FileSystemContext;
+struct DirectoryEntry;
+
+// A request job that handles reading filesystem: URLs for directories.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemDirURLRequestJob
+ : public net::URLRequestJob {
+ public:
+ FileSystemDirURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ FileSystemContext* file_system_context);
+
+ // URLRequestJob methods:
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual bool GetCharset(std::string* charset) OVERRIDE;
+
+ // FilterContext methods (via URLRequestJob):
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ // TODO(adamk): Implement GetResponseInfo and GetResponseCode to simulate
+ // an HTTP response.
+
+ private:
+ class CallbackDispatcher;
+
+ virtual ~FileSystemDirURLRequestJob();
+
+ void StartAsync();
+ void DidReadDirectory(base::PlatformFileError result,
+ const std::vector<DirectoryEntry>& entries,
+ bool has_more);
+
+ std::string data_;
+ FileSystemURL url_;
+ FileSystemContext* file_system_context_;
+ base::WeakPtrFactory<FileSystemDirURLRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemDirURLRequestJob);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc
new file mode 100644
index 00000000000..1c197cd294d
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc
@@ -0,0 +1,290 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_dir_url_request_job.h"
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/format_macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/platform_file.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/icu/source/i18n/unicode/regex.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/quota/mock_special_storage_policy.h"
+
+namespace fileapi {
+namespace {
+
+// We always use the TEMPORARY FileSystem in this test.
+static const char kFileSystemURLPrefix[] =
+ "filesystem:http://remote/temporary/";
+
+} // namespace
+
+class FileSystemDirURLRequestJobTest : public testing::Test {
+ protected:
+ FileSystemDirURLRequestJobTest()
+ : message_loop_(base::MessageLoop::TYPE_IO), // simulate an IO thread
+ weak_factory_(this) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ special_storage_policy_ = new quota::MockSpecialStoragePolicy;
+ file_system_context_ = CreateFileSystemContextForTesting(
+ NULL, temp_dir_.path());
+
+ file_system_context_->OpenFileSystem(
+ GURL("http://remote/"), kFileSystemTypeTemporary,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&FileSystemDirURLRequestJobTest::OnOpenFileSystem,
+ weak_factory_.GetWeakPtr()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ net::URLRequest::Deprecated::RegisterProtocolFactory(
+ "filesystem", &FileSystemDirURLRequestJobFactory);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // NOTE: order matters, request must die before delegate
+ request_.reset(NULL);
+ delegate_.reset(NULL);
+
+ net::URLRequest::Deprecated::RegisterProtocolFactory("filesystem", NULL);
+ ClearUnusedJob();
+ }
+
+ void OnOpenFileSystem(base::PlatformFileError result,
+ const std::string& name,
+ const GURL& root_url) {
+ ASSERT_EQ(base::PLATFORM_FILE_OK, result);
+ }
+
+ void TestRequestHelper(const GURL& url, bool run_to_completion) {
+ delegate_.reset(new net::TestDelegate());
+ delegate_->set_quit_on_redirect(true);
+ request_.reset(empty_context_.CreateRequest(url, delegate_.get()));
+ job_ = new FileSystemDirURLRequestJob(
+ request_.get(), NULL, file_system_context_.get());
+
+ request_->Start();
+ ASSERT_TRUE(request_->is_pending()); // verify that we're starting async
+ if (run_to_completion)
+ base::MessageLoop::current()->Run();
+ }
+
+ void TestRequest(const GURL& url) {
+ TestRequestHelper(url, true);
+ }
+
+ void TestRequestNoRun(const GURL& url) {
+ TestRequestHelper(url, false);
+ }
+
+ FileSystemURL CreateURL(const base::FilePath& file_path) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://remote"),
+ fileapi::kFileSystemTypeTemporary,
+ file_path);
+ }
+
+ FileSystemOperationContext* NewOperationContext() {
+ FileSystemOperationContext* context(
+ new FileSystemOperationContext(file_system_context_.get()));
+ context->set_allowed_bytes_growth(1024);
+ return context;
+ }
+
+ void CreateDirectory(const base::StringPiece& dir_name) {
+ base::FilePath path = base::FilePath().AppendASCII(dir_name);
+ scoped_ptr<FileSystemOperationContext> context(NewOperationContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_util()->CreateDirectory(
+ context.get(),
+ CreateURL(path),
+ false /* exclusive */,
+ false /* recursive */));
+ }
+
+ void EnsureFileExists(const base::StringPiece file_name) {
+ base::FilePath path = base::FilePath().AppendASCII(file_name);
+ scoped_ptr<FileSystemOperationContext> context(NewOperationContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_util()->EnsureFileExists(
+ context.get(), CreateURL(path), NULL));
+ }
+
+ void TruncateFile(const base::StringPiece file_name, int64 length) {
+ base::FilePath path = base::FilePath().AppendASCII(file_name);
+ scoped_ptr<FileSystemOperationContext> context(NewOperationContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_util()->Truncate(
+ context.get(), CreateURL(path), length));
+ }
+
+ base::PlatformFileError GetFileInfo(const base::FilePath& path,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_file_path) {
+ scoped_ptr<FileSystemOperationContext> context(NewOperationContext());
+ return file_util()->GetFileInfo(context.get(),
+ CreateURL(path),
+ file_info, platform_file_path);
+ }
+
+ void VerifyListingEntry(const std::string& entry_line,
+ const std::string& name,
+ const std::string& url,
+ bool is_directory,
+ int64 size) {
+#define STR "([^\"]*)"
+ icu::UnicodeString pattern("^<script>addRow\\(\"" STR "\",\"" STR
+ "\",(0|1),\"" STR "\",\"" STR "\"\\);</script>");
+#undef STR
+ icu::UnicodeString input(entry_line.c_str());
+
+ UErrorCode status = U_ZERO_ERROR;
+ icu::RegexMatcher match(pattern, input, 0, status);
+
+ EXPECT_TRUE(match.find());
+ EXPECT_EQ(5, match.groupCount());
+ EXPECT_EQ(icu::UnicodeString(name.c_str()), match.group(1, status));
+ EXPECT_EQ(icu::UnicodeString(url.c_str()), match.group(2, status));
+ EXPECT_EQ(icu::UnicodeString(is_directory ? "1" : "0"),
+ match.group(3, status));
+ icu::UnicodeString size_string(FormatBytesUnlocalized(size).c_str());
+ EXPECT_EQ(size_string, match.group(4, status));
+
+ base::Time date;
+ icu::UnicodeString date_ustr(match.group(5, status));
+ std::string date_str;
+ UTF16ToUTF8(date_ustr.getBuffer(), date_ustr.length(), &date_str);
+ EXPECT_TRUE(base::Time::FromString(date_str.c_str(), &date));
+ EXPECT_FALSE(date.is_null());
+ }
+
+ GURL CreateFileSystemURL(const std::string path) {
+ return GURL(kFileSystemURLPrefix + path);
+ }
+
+ static net::URLRequestJob* FileSystemDirURLRequestJobFactory(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ DCHECK(job_);
+ net::URLRequestJob* temp = job_;
+ job_ = NULL;
+ return temp;
+ }
+
+ static void ClearUnusedJob() {
+ if (job_) {
+ scoped_refptr<net::URLRequestJob> deleter = job_;
+ job_ = NULL;
+ }
+ }
+
+ FileSystemFileUtil* file_util() {
+ return file_system_context_->GetFileUtil(kFileSystemTypeTemporary);
+ }
+
+ // Put the message loop at the top, so that it's the last thing deleted.
+ // Delete all MessageLoopProxy objects before the MessageLoop, to help prevent
+ // leaks caused by tasks posted during shutdown.
+ base::MessageLoop message_loop_;
+
+ base::ScopedTempDir temp_dir_;
+ net::URLRequestContext empty_context_;
+ scoped_ptr<net::TestDelegate> delegate_;
+ scoped_ptr<net::URLRequest> request_;
+ scoped_refptr<quota::MockSpecialStoragePolicy> special_storage_policy_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ base::WeakPtrFactory<FileSystemDirURLRequestJobTest> weak_factory_;
+
+ static net::URLRequestJob* job_;
+};
+
+// static
+net::URLRequestJob* FileSystemDirURLRequestJobTest::job_ = NULL;
+
+namespace {
+
+TEST_F(FileSystemDirURLRequestJobTest, DirectoryListing) {
+ CreateDirectory("foo");
+ CreateDirectory("foo/bar");
+ CreateDirectory("foo/bar/baz");
+
+ EnsureFileExists("foo/bar/hoge");
+ TruncateFile("foo/bar/hoge", 10);
+
+ TestRequest(CreateFileSystemURL("foo/bar/"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ EXPECT_GT(delegate_->bytes_received(), 0);
+
+ std::istringstream in(delegate_->data_received());
+ std::string line;
+ EXPECT_TRUE(std::getline(in, line));
+
+#if defined(OS_WIN)
+ EXPECT_EQ("<script>start(\"foo\\\\bar\");</script>", line);
+#elif defined(OS_POSIX)
+ EXPECT_EQ("<script>start(\"/foo/bar\");</script>", line);
+#endif
+
+ EXPECT_TRUE(std::getline(in, line));
+ VerifyListingEntry(line, "hoge", "hoge", false, 10);
+
+ EXPECT_TRUE(std::getline(in, line));
+ VerifyListingEntry(line, "baz", "baz", true, 0);
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, InvalidURL) {
+ TestRequest(GURL("filesystem:/foo/bar/baz"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ ASSERT_FALSE(request_->status().is_success());
+ EXPECT_EQ(net::ERR_INVALID_URL, request_->status().error());
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, NoSuchRoot) {
+ TestRequest(GURL("filesystem:http://remote/persistent/somedir/"));
+ ASSERT_FALSE(request_->is_pending());
+ ASSERT_FALSE(request_->status().is_success());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, NoSuchDirectory) {
+ TestRequest(CreateFileSystemURL("somedir/"));
+ ASSERT_FALSE(request_->is_pending());
+ ASSERT_FALSE(request_->status().is_success());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
+}
+
+TEST_F(FileSystemDirURLRequestJobTest, Cancel) {
+ CreateDirectory("foo");
+ TestRequestNoRun(CreateFileSystemURL("foo/"));
+ // Run StartAsync() and only StartAsync().
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release());
+ base::MessageLoop::current()->RunUntilIdle();
+ // If we get here, success! we didn't crash!
+}
+
+} // namespace (anonymous)
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_file_stream_reader.cc b/chromium/webkit/browser/fileapi/file_system_file_stream_reader.cc
new file mode 100644
index 00000000000..71b38278186
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_file_stream_reader.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_file_stream_reader.h"
+
+#include "base/files/file_util_proxy.h"
+#include "base/platform_file.h"
+#include "base/single_thread_task_runner.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "webkit/browser/blob/local_file_stream_reader.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+
+using webkit_blob::LocalFileStreamReader;
+
+namespace fileapi {
+
+namespace {
+
+void ReadAdapter(base::WeakPtr<FileSystemFileStreamReader> reader,
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ if (!reader.get())
+ return;
+ int rv = reader->Read(buf, buf_len, callback);
+ if (rv != net::ERR_IO_PENDING)
+ callback.Run(rv);
+}
+
+void GetLengthAdapter(base::WeakPtr<FileSystemFileStreamReader> reader,
+ const net::Int64CompletionCallback& callback) {
+ if (!reader.get())
+ return;
+ int rv = reader->GetLength(callback);
+ if (rv != net::ERR_IO_PENDING)
+ callback.Run(rv);
+}
+
+void Int64CallbackAdapter(const net::Int64CompletionCallback& callback,
+ int value) {
+ callback.Run(value);
+}
+
+} // namespace
+
+FileSystemFileStreamReader::FileSystemFileStreamReader(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ int64 initial_offset,
+ const base::Time& expected_modification_time)
+ : file_system_context_(file_system_context),
+ url_(url),
+ initial_offset_(initial_offset),
+ expected_modification_time_(expected_modification_time),
+ has_pending_create_snapshot_(false),
+ weak_factory_(this) {
+}
+
+FileSystemFileStreamReader::~FileSystemFileStreamReader() {
+}
+
+int FileSystemFileStreamReader::Read(
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ if (local_file_reader_)
+ return local_file_reader_->Read(buf, buf_len, callback);
+ return CreateSnapshot(
+ base::Bind(&ReadAdapter, weak_factory_.GetWeakPtr(),
+ make_scoped_refptr(buf), buf_len, callback),
+ callback);
+}
+
+int64 FileSystemFileStreamReader::GetLength(
+ const net::Int64CompletionCallback& callback) {
+ if (local_file_reader_)
+ return local_file_reader_->GetLength(callback);
+ return CreateSnapshot(
+ base::Bind(&GetLengthAdapter, weak_factory_.GetWeakPtr(), callback),
+ base::Bind(&Int64CallbackAdapter, callback));
+}
+
+int FileSystemFileStreamReader::CreateSnapshot(
+ const base::Closure& callback,
+ const net::CompletionCallback& error_callback) {
+ DCHECK(!has_pending_create_snapshot_);
+ has_pending_create_snapshot_ = true;
+ file_system_context_->operation_runner()->CreateSnapshotFile(
+ url_,
+ base::Bind(&FileSystemFileStreamReader::DidCreateSnapshot,
+ weak_factory_.GetWeakPtr(),
+ callback,
+ error_callback));
+ return net::ERR_IO_PENDING;
+}
+
+void FileSystemFileStreamReader::DidCreateSnapshot(
+ const base::Closure& callback,
+ const net::CompletionCallback& error_callback,
+ base::PlatformFileError file_error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) {
+ DCHECK(has_pending_create_snapshot_);
+ DCHECK(!local_file_reader_.get());
+ has_pending_create_snapshot_ = false;
+
+ if (file_error != base::PLATFORM_FILE_OK) {
+ error_callback.Run(net::PlatformFileErrorToNetError(file_error));
+ return;
+ }
+
+ // Keep the reference (if it's non-null) so that the file won't go away.
+ snapshot_ref_ = file_ref;
+
+ local_file_reader_.reset(
+ new LocalFileStreamReader(
+ file_system_context_->default_file_task_runner(),
+ platform_path, initial_offset_, expected_modification_time_));
+
+ callback.Run();
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_file_stream_reader.h b/chromium/webkit/browser/fileapi/file_system_file_stream_reader.h
new file mode 100644
index 00000000000..634f5684c42
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_file_stream_reader.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_
+
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/platform_file.h"
+#include "base/time/time.h"
+#include "webkit/browser/blob/file_stream_reader.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}
+
+namespace webkit_blob {
+class LocalFileStreamReader;
+}
+
+namespace fileapi {
+
+class FileSystemContext;
+
+// TODO(kinaba,satorux): This generic implementation would work for any
+// filesystems but remote filesystem should implement its own reader
+// rather than relying on FileSystemOperation::GetSnapshotFile() which
+// may force downloading the entire contents for remote files.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemFileStreamReader
+ : public webkit_blob::FileStreamReader {
+ public:
+ // Creates a new FileReader for a filesystem URL |url| form |initial_offset|.
+ // |expected_modification_time| specifies the expected last modification if
+ // the value is non-null, the reader will check the underlying file's actual
+ // modification time to see if the file has been modified, and if it does any
+ // succeeding read operations should fail with ERR_UPLOAD_FILE_CHANGED error.
+ FileSystemFileStreamReader(FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ int64 initial_offset,
+ const base::Time& expected_modification_time);
+ virtual ~FileSystemFileStreamReader();
+
+ // FileStreamReader overrides.
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int64 GetLength(
+ const net::Int64CompletionCallback& callback) OVERRIDE;
+
+ private:
+ int CreateSnapshot(const base::Closure& callback,
+ const net::CompletionCallback& error_callback);
+ void DidCreateSnapshot(
+ const base::Closure& callback,
+ const net::CompletionCallback& error_callback,
+ base::PlatformFileError file_error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref);
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+ FileSystemURL url_;
+ const int64 initial_offset_;
+ const base::Time expected_modification_time_;
+ scoped_ptr<webkit_blob::LocalFileStreamReader> local_file_reader_;
+ scoped_refptr<webkit_blob::ShareableFileReference> snapshot_ref_;
+ bool has_pending_create_snapshot_;
+ base::WeakPtrFactory<FileSystemFileStreamReader> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemFileStreamReader);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_file_stream_reader_unittest.cc b/chromium/webkit/browser/fileapi/file_system_file_stream_reader_unittest.cc
new file mode 100644
index 00000000000..ca82338a5cc
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_file_stream_reader_unittest.cc
@@ -0,0 +1,283 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_file_stream_reader.h"
+
+#include <limits>
+#include <string>
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/platform_file.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+
+namespace fileapi {
+
+namespace {
+
+const char kURLOrigin[] = "http://remote/";
+const char kTestFileName[] = "test.dat";
+const char kTestData[] = "0123456789";
+const int kTestDataSize = arraysize(kTestData) - 1;
+
+void ReadFromReader(FileSystemFileStreamReader* reader,
+ std::string* data,
+ size_t size,
+ int* result) {
+ ASSERT_TRUE(reader != NULL);
+ ASSERT_TRUE(result != NULL);
+ *result = net::OK;
+ net::TestCompletionCallback callback;
+ size_t total_bytes_read = 0;
+ while (total_bytes_read < size) {
+ scoped_refptr<net::IOBufferWithSize> buf(
+ new net::IOBufferWithSize(size - total_bytes_read));
+ int rv = reader->Read(buf.get(), buf->size(), callback.callback());
+ if (rv == net::ERR_IO_PENDING)
+ rv = callback.WaitForResult();
+ if (rv < 0)
+ *result = rv;
+ if (rv <= 0)
+ break;
+ total_bytes_read += rv;
+ data->append(buf->data(), rv);
+ }
+}
+
+void NeverCalled(int unused) { ADD_FAILURE(); }
+
+} // namespace
+
+class FileSystemFileStreamReaderTest : public testing::Test {
+ public:
+ FileSystemFileStreamReaderTest()
+ : message_loop_(base::MessageLoop::TYPE_IO) {}
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ file_system_context_ = CreateFileSystemContextForTesting(
+ NULL, temp_dir_.path());
+
+ file_system_context_->OpenFileSystem(
+ GURL(kURLOrigin), kFileSystemTypeTemporary,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&OnOpenFileSystem));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ WriteFile(kTestFileName, kTestData, kTestDataSize,
+ &test_file_modification_time_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ protected:
+ FileSystemFileStreamReader* CreateFileReader(
+ const std::string& file_name,
+ int64 initial_offset,
+ const base::Time& expected_modification_time) {
+ return new FileSystemFileStreamReader(file_system_context_.get(),
+ GetFileSystemURL(file_name),
+ initial_offset,
+ expected_modification_time);
+ }
+
+ base::Time test_file_modification_time() const {
+ return test_file_modification_time_;
+ }
+
+ void WriteFile(const std::string& file_name,
+ const char* buf,
+ int buf_size,
+ base::Time* modification_time) {
+ FileSystemFileUtil* file_util = file_system_context_->GetFileUtil(
+ kFileSystemTypeTemporary);
+ FileSystemURL url = GetFileSystemURL(file_name);
+
+ FileSystemOperationContext context(file_system_context_.get());
+ context.set_allowed_bytes_growth(1024);
+
+ base::PlatformFile handle = base::kInvalidPlatformFileValue;
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateOrOpen(
+ &context,
+ url,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &handle,
+ &created));
+ EXPECT_TRUE(created);
+ ASSERT_NE(base::kInvalidPlatformFileValue, handle);
+ ASSERT_EQ(buf_size,
+ base::WritePlatformFile(handle, 0 /* offset */, buf, buf_size));
+ base::ClosePlatformFile(handle);
+
+ base::PlatformFileInfo file_info;
+ base::FilePath platform_path;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util->GetFileInfo(&context, url, &file_info,
+ &platform_path));
+ if (modification_time)
+ *modification_time = file_info.last_modified;
+ }
+
+ private:
+ static void OnOpenFileSystem(base::PlatformFileError result,
+ const std::string& name,
+ const GURL& root_url) {
+ ASSERT_EQ(base::PLATFORM_FILE_OK, result);
+ }
+
+ FileSystemURL GetFileSystemURL(const std::string& file_name) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ GURL(kURLOrigin),
+ kFileSystemTypeTemporary,
+ base::FilePath().AppendASCII(file_name));
+ }
+
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir temp_dir_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ base::Time test_file_modification_time_;
+};
+
+TEST_F(FileSystemFileStreamReaderTest, NonExistent) {
+ const char kFileName[] = "nonexistent";
+ scoped_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kFileName, 0, base::Time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, 10, &result);
+ ASSERT_EQ(net::ERR_FILE_NOT_FOUND, result);
+ ASSERT_EQ(0U, data.size());
+}
+
+TEST_F(FileSystemFileStreamReaderTest, Empty) {
+ const char kFileName[] = "empty";
+ WriteFile(kFileName, NULL, 0, NULL);
+
+ scoped_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kFileName, 0, base::Time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, 10, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(0U, data.size());
+
+ net::TestInt64CompletionCallback callback;
+ int64 length_result = reader->GetLength(callback.callback());
+ if (length_result == net::ERR_IO_PENDING)
+ length_result = callback.WaitForResult();
+ ASSERT_EQ(0, length_result);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, GetLengthNormal) {
+ scoped_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, test_file_modification_time()));
+ net::TestInt64CompletionCallback callback;
+ int64 result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ ASSERT_EQ(kTestDataSize, result);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, GetLengthAfterModified) {
+ // Pass a fake expected modifictaion time so that the expectation fails.
+ base::Time fake_expected_modification_time =
+ test_file_modification_time() - base::TimeDelta::FromSeconds(10);
+
+ scoped_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, fake_expected_modification_time));
+ net::TestInt64CompletionCallback callback;
+ int64 result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
+
+ // With NULL expected modification time this should work.
+ reader.reset(CreateFileReader(kTestFileName, 0, base::Time()));
+ result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ ASSERT_EQ(kTestDataSize, result);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, GetLengthWithOffset) {
+ scoped_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 3, base::Time()));
+ net::TestInt64CompletionCallback callback;
+ int64 result = reader->GetLength(callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ // Initial offset does not affect the result of GetLength.
+ ASSERT_EQ(kTestDataSize, result);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, ReadNormal) {
+ scoped_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, test_file_modification_time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(kTestData, data);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, ReadAfterModified) {
+ // Pass a fake expected modifictaion time so that the expectation fails.
+ base::Time fake_expected_modification_time =
+ test_file_modification_time() - base::TimeDelta::FromSeconds(10);
+
+ scoped_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, fake_expected_modification_time));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::ERR_UPLOAD_FILE_CHANGED, result);
+ ASSERT_EQ(0U, data.size());
+
+ // With NULL expected modification time this should work.
+ data.clear();
+ reader.reset(CreateFileReader(kTestFileName, 0, base::Time()));
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(kTestData, data);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, ReadWithOffset) {
+ scoped_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 3, base::Time()));
+ int result = 0;
+ std::string data;
+ ReadFromReader(reader.get(), &data, kTestDataSize, &result);
+ ASSERT_EQ(net::OK, result);
+ ASSERT_EQ(&kTestData[3], data);
+}
+
+TEST_F(FileSystemFileStreamReaderTest, DeleteWithUnfinishedRead) {
+ scoped_ptr<FileSystemFileStreamReader> reader(
+ CreateFileReader(kTestFileName, 0, base::Time()));
+
+ net::TestCompletionCallback callback;
+ scoped_refptr<net::IOBufferWithSize> buf(
+ new net::IOBufferWithSize(kTestDataSize));
+ int rv = reader->Read(buf.get(), buf->size(), base::Bind(&NeverCalled));
+ ASSERT_TRUE(rv == net::ERR_IO_PENDING || rv >= 0);
+
+ // Delete immediately.
+ // Should not crash; nor should NeverCalled be callback.
+ reader.reset();
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_file_util.cc b/chromium/webkit/browser/fileapi/file_system_file_util.cc
new file mode 100644
index 00000000000..7317b8b2693
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_file_util.cc
@@ -0,0 +1,25 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_file_util.h"
+
+namespace fileapi {
+
+base::FilePath FileSystemFileUtil::EmptyFileEnumerator::Next() {
+ return base::FilePath();
+}
+
+int64 FileSystemFileUtil::EmptyFileEnumerator::Size() {
+ return 0;
+}
+
+base::Time FileSystemFileUtil::EmptyFileEnumerator::LastModifiedTime() {
+ return base::Time();
+}
+
+bool FileSystemFileUtil::EmptyFileEnumerator::IsDirectory() {
+ return false;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_file_util.h b/chromium/webkit/browser/fileapi/file_system_file_util.h
new file mode 100644
index 00000000000..13ea0e40a4d
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_file_util.h
@@ -0,0 +1,189 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/blob/scoped_file.h"
+
+namespace base {
+class Time;
+}
+
+namespace fileapi {
+
+class FileSystemOperationContext;
+class FileSystemURL;
+
+// A file utility interface that provides basic file utility methods for
+// FileSystem API.
+//
+// Layering structure of the FileSystemFileUtil was split out.
+// See http://crbug.com/128136 if you need it.
+class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemFileUtil {
+ public:
+ // It will be implemented by each subclass such as FileSystemFileEnumerator.
+ class WEBKIT_STORAGE_BROWSER_EXPORT AbstractFileEnumerator {
+ public:
+ virtual ~AbstractFileEnumerator() {}
+
+ // Returns an empty string if there are no more results.
+ virtual base::FilePath Next() = 0;
+
+ // These methods return metadata for the file most recently returned by
+ // Next(). If Next() has never been called, or if Next() most recently
+ // returned an empty string, then return the default values of 0,
+ // "null time", and false, respectively.
+ virtual int64 Size() = 0;
+ virtual base::Time LastModifiedTime() = 0;
+ virtual bool IsDirectory() = 0;
+ };
+
+ class WEBKIT_STORAGE_BROWSER_EXPORT EmptyFileEnumerator
+ : public AbstractFileEnumerator {
+ virtual base::FilePath Next() OVERRIDE;
+ virtual int64 Size() OVERRIDE;
+ virtual base::Time LastModifiedTime() OVERRIDE;
+ virtual bool IsDirectory() OVERRIDE;
+ };
+
+ virtual ~FileSystemFileUtil() {}
+
+ // Creates or opens a file with the given flags.
+ // See header comments for AsyncFileUtil::CreateOrOpen() for more details.
+ // This is used only by Pepper/NaCL File API.
+ virtual base::PlatformFileError CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int file_flags,
+ base::PlatformFile* file_handle,
+ bool* created) = 0;
+
+ // Closes the given file handle.
+ // This is used only for Pepper/NaCL File API.
+ virtual base::PlatformFileError Close(
+ FileSystemOperationContext* context,
+ base::PlatformFile file) = 0;
+
+ // Ensures that the given |url| exist. This creates a empty new file
+ // at |url| if the |url| does not exist.
+ // See header comments for AsyncFileUtil::EnsureFileExists() for more details.
+ virtual base::PlatformFileError EnsureFileExists(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, bool* created) = 0;
+
+ // Creates directory at given url.
+ // See header comments for AsyncFileUtil::CreateDirectory() for more details.
+ virtual base::PlatformFileError CreateDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) = 0;
+
+ // Retrieves the information about a file.
+ // See header comments for AsyncFileUtil::GetFileInfo() for more details.
+ virtual base::PlatformFileError GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_path) = 0;
+
+ // Returns a pointer to a new instance of AbstractFileEnumerator which is
+ // implemented for each FileSystemFileUtil subclass. The instance needs to be
+ // freed by the caller, and its lifetime should not extend past when the
+ // current call returns to the main FILE message loop.
+ //
+ // The supplied context must remain valid at least lifetime of the enumerator
+ // instance.
+ virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url) = 0;
+
+ // Maps |file_system_url| given |context| into |local_file_path|
+ // which represents physical file location on the host OS.
+ // This may not always make sense for all subclasses.
+ virtual base::PlatformFileError GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& file_system_url,
+ base::FilePath* local_file_path) = 0;
+
+ // Updates the file metadata information.
+ // See header comments for AsyncFileUtil::Touch() for more details.
+ virtual base::PlatformFileError Touch(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) = 0;
+
+ // Truncates a file to the given length.
+ // See header comments for AsyncFileUtil::Truncate() for more details.
+ virtual base::PlatformFileError Truncate(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 length) = 0;
+
+ // Copies or moves a single file from |src_url| to |dest_url|.
+ // The filesystem type of |src_url| and |dest_url| MUST be same.
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ virtual base::PlatformFileError CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ bool copy) = 0;
+
+ // Copies in a single file from a different filesystem.
+ // See header comments for AsyncFileUtil::CopyInForeignFile() for
+ // more details.
+ virtual base::PlatformFileError CopyInForeignFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url) = 0;
+
+ // Deletes a single file.
+ // See header comments for AsyncFileUtil::DeleteFile() for more details.
+ virtual base::PlatformFileError DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) = 0;
+
+ // Deletes a single empty directory.
+ // See header comments for AsyncFileUtil::DeleteDirectory() for more details.
+ virtual base::PlatformFileError DeleteDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) = 0;
+
+ // Creates a local snapshot file for a given |url| and returns the
+ // metadata and platform path of the snapshot file via |callback|.
+ //
+ // See header comments for AsyncFileUtil::CreateSnapshotFile() for
+ // more details.
+ virtual webkit_blob::ScopedFile CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileError* error,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_path) = 0;
+
+ protected:
+ FileSystemFileUtil() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileSystemFileUtil);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_operation.h b/chromium/webkit/browser/fileapi/file_system_operation.h
new file mode 100644
index 00000000000..f1adc191a7e
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_operation.h
@@ -0,0 +1,276 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/platform_file.h"
+#include "base/process/process.h"
+#include "webkit/common/fileapi/directory_entry.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+class URLRequest;
+}
+
+namespace webkit_blob {
+class ShareableFileReference;
+}
+
+class GURL;
+
+namespace fileapi {
+
+class FileSystemURL;
+class FileWriterDelegate;
+class FileSystemOperationImpl;
+
+// The interface class for FileSystemOperation implementations.
+//
+// This interface defines file system operations required to implement
+// "File API: Directories and System"
+// http://www.w3.org/TR/file-system-api/
+//
+// DESIGN NOTES
+//
+// This class is designed to
+//
+// 1) Serve one-time file system operation per instance. Only one
+// method(CreateFile, CreateDirectory, Copy, Move, DirectoryExists,
+// GetMetadata, ReadDirectory and Remove) may be called during the
+// lifetime of this object and it should be called no more than once.
+//
+// 2) Deliver the results of operations to the client via the callback function
+// passed as the last parameter of the method.
+//
+// Note that it is valid to delete an operation while it is running.
+// The callback will NOT be fired if the operation is deleted before
+// it gets called.
+class FileSystemOperation {
+ public:
+ virtual ~FileSystemOperation() {}
+
+ // Used for CreateFile(), etc. |result| is the return code of the operation.
+ typedef base::Callback<void(base::PlatformFileError result)> StatusCallback;
+
+ // Used for GetMetadata(). |result| is the return code of the operation,
+ // |file_info| is the obtained file info.
+ typedef base::Callback<
+ void(base::PlatformFileError result,
+ const base::PlatformFileInfo& file_info)> GetMetadataCallback;
+
+ // Used for OpenFile(). |result| is the return code of the operation.
+ // |on_close_callback| will be called after the file is closed in the child
+ // process. It can be null, if no operation is needed on closing a file.
+ typedef base::Callback<
+ void(base::PlatformFileError result,
+ base::PlatformFile file,
+ const base::Closure& on_close_callback,
+ base::ProcessHandle peer_handle)> OpenFileCallback;
+
+ // Used for ReadDirectoryCallback.
+ typedef std::vector<DirectoryEntry> FileEntryList;
+
+ // Used for ReadDirectory(). |result| is the return code of the operation,
+ // |file_list| is the list of files read, and |has_more| is true if some files
+ // are yet to be read.
+ typedef base::Callback<
+ void(base::PlatformFileError result,
+ const FileEntryList& file_list,
+ bool has_more)> ReadDirectoryCallback;
+
+ // Used for CreateSnapshotFile(). (Please see the comment at
+ // CreateSnapshotFile() below for how the method is called)
+ // |result| is the return code of the operation.
+ // |file_info| is the metadata of the snapshot file created.
+ // |platform_path| is the path to the snapshot file created.
+ //
+ // The snapshot file could simply be of the local file pointed by the given
+ // filesystem URL in local filesystem cases; remote filesystems
+ // may want to download the file into a temporary snapshot file and then
+ // return the metadata of the temporary file.
+ //
+ // |file_ref| is used to manage the lifetime of the returned
+ // snapshot file. It can be set to let the chromium backend take
+ // care of the life time of the snapshot file. Otherwise (if the returned
+ // file does not require any handling) the implementation can just
+ // return NULL. In a more complex case, the implementaiton can manage
+ // the lifetime of the snapshot file on its own (e.g. by its cache system)
+ // but also can be notified via the reference when the file becomes no
+ // longer necessary in the javascript world.
+ // Please see the comment for ShareableFileReference for details.
+ //
+ typedef base::Callback<
+ void(base::PlatformFileError result,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref)>
+ SnapshotFileCallback;
+
+ // Used for Write().
+ typedef base::Callback<void(base::PlatformFileError result,
+ int64 bytes,
+ bool complete)> WriteCallback;
+
+ // Creates a file at |path|. If |exclusive| is true, an error is raised
+ // in case a file is already present at the URL.
+ virtual void CreateFile(const FileSystemURL& path,
+ bool exclusive,
+ const StatusCallback& callback) = 0;
+
+ // Creates a directory at |path|. If |exclusive| is true, an error is
+ // raised in case a directory is already present at the URL. If
+ // |recursive| is true, create parent directories as needed just like
+ // mkdir -p does.
+ virtual void CreateDirectory(const FileSystemURL& path,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) = 0;
+
+ // Copies a file or directory from |src_path| to |dest_path|. If
+ // |src_path| is a directory, the contents of |src_path| are copied to
+ // |dest_path| recursively. A new file or directory is created at
+ // |dest_path| as needed.
+ virtual void Copy(const FileSystemURL& src_path,
+ const FileSystemURL& dest_path,
+ const StatusCallback& callback) = 0;
+
+ // Moves a file or directory from |src_path| to |dest_path|. A new file
+ // or directory is created at |dest_path| as needed.
+ virtual void Move(const FileSystemURL& src_path,
+ const FileSystemURL& dest_path,
+ const StatusCallback& callback) = 0;
+
+ // Checks if a directory is present at |path|.
+ virtual void DirectoryExists(const FileSystemURL& path,
+ const StatusCallback& callback) = 0;
+
+ // Checks if a file is present at |path|.
+ virtual void FileExists(const FileSystemURL& path,
+ const StatusCallback& callback) = 0;
+
+ // Gets the metadata of a file or directory at |path|.
+ virtual void GetMetadata(const FileSystemURL& path,
+ const GetMetadataCallback& callback) = 0;
+
+ // Reads contents of a directory at |path|.
+ virtual void ReadDirectory(const FileSystemURL& path,
+ const ReadDirectoryCallback& callback) = 0;
+
+ // Removes a file or directory at |path|. If |recursive| is true, remove
+ // all files and directories under the directory at |path| recursively.
+ virtual void Remove(const FileSystemURL& path, bool recursive,
+ const StatusCallback& callback) = 0;
+
+ // Writes the data read from |blob_request| using |writer_delegate|.
+ virtual void Write(
+ const FileSystemURL& url,
+ scoped_ptr<FileWriterDelegate> writer_delegate,
+ scoped_ptr<net::URLRequest> blob_request,
+ const WriteCallback& callback) = 0;
+
+ // Truncates a file at |path| to |length|. If |length| is larger than
+ // the original file size, the file will be extended, and the extended
+ // part is filled with null bytes.
+ virtual void Truncate(const FileSystemURL& path, int64 length,
+ const StatusCallback& callback) = 0;
+
+ // Tries to cancel the current operation [we support cancelling write or
+ // truncate only]. Reports failure for the current operation, then reports
+ // success for the cancel operation itself via the |cancel_dispatcher|.
+ //
+ // E.g. a typical cancel implementation would look like:
+ //
+ // virtual void SomeOperationImpl::Cancel(
+ // const StatusCallback& cancel_callback) {
+ // // Abort the current inflight operation first.
+ // ...
+ //
+ // // Dispatch ABORT error for the current operation by invoking
+ // // the callback function for the ongoing operation,
+ // operation_callback.Run(base::PLATFORM_FILE_ERROR_ABORT, ...);
+ //
+ // // Dispatch 'success' for the cancel (or dispatch appropriate
+ // // error code with DidFail() if the cancel has somehow failed).
+ // cancel_callback.Run(base::PLATFORM_FILE_OK);
+ // }
+ //
+ // Note that, for reporting failure, the callback function passed to a
+ // cancellable operations are kept around with the operation instance
+ // (as |operation_callback_| in the code example).
+ virtual void Cancel(const StatusCallback& cancel_callback) = 0;
+
+ // Modifies timestamps of a file or directory at |path| with
+ // |last_access_time| and |last_modified_time|. The function DOES NOT
+ // create a file unlike 'touch' command on Linux.
+ //
+ // This function is used only by Pepper as of writing.
+ virtual void TouchFile(const FileSystemURL& path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) = 0;
+
+ // Opens a file at |path| with |file_flags|, where flags are OR'ed
+ // values of base::PlatformFileFlags.
+ //
+ // |peer_handle| is the process handle of a pepper plugin process, which
+ // is necessary for underlying IPC calls with Pepper plugins.
+ //
+ // This function is used only by Pepper as of writing.
+ virtual void OpenFile(const FileSystemURL& path,
+ int file_flags,
+ base::ProcessHandle peer_handle,
+ const OpenFileCallback& callback) = 0;
+
+ // For downcasting to FileSystemOperationImpl.
+ // TODO(kinuko): this hack should go away once appropriate upload-stream
+ // handling based on element types is supported.
+ virtual FileSystemOperationImpl* AsFileSystemOperationImpl() = 0;
+
+ // Creates a local snapshot file for a given |path| and returns the
+ // metadata and platform path of the snapshot file via |callback|.
+ // In local filesystem cases the implementation may simply return
+ // the metadata of the file itself (as well as GetMetadata does),
+ // while in remote filesystem case the backend may want to download the file
+ // into a temporary snapshot file and return the metadata of the
+ // temporary file. Or if the implementaiton already has the local cache
+ // data for |path| it can simply return the path to the cache.
+ virtual void CreateSnapshotFile(const FileSystemURL& path,
+ const SnapshotFileCallback& callback) = 0;
+
+ protected:
+ // Used only for internal assertions.
+ enum OperationType {
+ kOperationNone,
+ kOperationCreateFile,
+ kOperationCreateDirectory,
+ kOperationCreateSnapshotFile,
+ kOperationCopy,
+ kOperationCopyInForeignFile,
+ kOperationMove,
+ kOperationDirectoryExists,
+ kOperationFileExists,
+ kOperationGetMetadata,
+ kOperationReadDirectory,
+ kOperationRemove,
+ kOperationWrite,
+ kOperationTruncate,
+ kOperationTouchFile,
+ kOperationOpenFile,
+ kOperationCloseFile,
+ kOperationGetLocalPath,
+ kOperationCancel,
+ };
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_operation_context.cc b/chromium/webkit/browser/fileapi/file_system_operation_context.cc
new file mode 100644
index 00000000000..dd333ba6773
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_operation_context.cc
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+
+#include "base/sequenced_task_runner.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+
+namespace fileapi {
+
+FileSystemOperationContext::FileSystemOperationContext(
+ FileSystemContext* context)
+ : file_system_context_(context),
+ task_runner_(file_system_context_->default_file_task_runner()),
+ allowed_bytes_growth_(0),
+ quota_limit_type_(quota::kQuotaLimitTypeUnknown) {}
+
+FileSystemOperationContext::FileSystemOperationContext(
+ FileSystemContext* context,
+ base::SequencedTaskRunner* task_runner)
+ : file_system_context_(context),
+ task_runner_(task_runner),
+ allowed_bytes_growth_(0),
+ quota_limit_type_(quota::kQuotaLimitTypeUnknown) {}
+
+FileSystemOperationContext::~FileSystemOperationContext() {
+ DetachUserDataThread();
+ setter_thread_checker_.DetachFromThread();
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_operation_context.h b/chromium/webkit/browser/fileapi/file_system_operation_context.h
new file mode 100644
index 00000000000..4d175108482
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_operation_context.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_
+
+#include "base/files/file_path.h"
+#include "base/supports_user_data.h"
+#include "base/threading/thread_checker.h"
+#include "webkit/browser/fileapi/task_runner_bound_observer_list.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/quota/quota_types.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace fileapi {
+
+class FileSystemContext;
+
+// A context class which is carried around by FileSystemOperation and
+// its delegated tasks. It is valid to reuse one context instance across
+// multiple operations as far as those operations are supposed to share
+// the same context (e.g. use the same task runner, share the quota etc).
+// Note that the remaining quota bytes (allowed_bytes_growth) may be
+// updated during the execution of write operations.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemOperationContext
+ : public base::SupportsUserData {
+ public:
+ explicit FileSystemOperationContext(FileSystemContext* context);
+
+ // Specifies |task_runner| which the operation is performed on.
+ // The backend of |task_runner| must outlive the IO thread.
+ FileSystemOperationContext(FileSystemContext* context,
+ base::SequencedTaskRunner* task_runner);
+
+ virtual ~FileSystemOperationContext();
+
+ FileSystemContext* file_system_context() const {
+ return file_system_context_.get();
+ }
+
+ // Updates the current remaining quota.
+ // This can be called to update the remaining quota during the operation.
+ void set_allowed_bytes_growth(const int64& allowed_bytes_growth) {
+ allowed_bytes_growth_ = allowed_bytes_growth;
+ }
+
+ // Returns the current remaining quota.
+ int64 allowed_bytes_growth() const { return allowed_bytes_growth_; }
+ quota::QuotaLimitType quota_limit_type() const { return quota_limit_type_; }
+ base::SequencedTaskRunner* task_runner() const { return task_runner_.get(); }
+ const base::FilePath& root_path() const { return root_path_; }
+
+ ChangeObserverList* change_observers() { return &change_observers_; }
+ UpdateObserverList* update_observers() { return &update_observers_; }
+
+ // Following setters should be called only on the same thread as the
+ // FileSystemOperationContext is created (i.e. are not supposed be updated
+ // after the context's passed onto other task runners).
+ void set_change_observers(const ChangeObserverList& list) {
+ DCHECK(setter_thread_checker_.CalledOnValidThread());
+ change_observers_ = list;
+ }
+ void set_update_observers(const UpdateObserverList& list) {
+ DCHECK(setter_thread_checker_.CalledOnValidThread());
+ update_observers_ = list;
+ }
+ void set_quota_limit_type(quota::QuotaLimitType limit_type) {
+ DCHECK(setter_thread_checker_.CalledOnValidThread());
+ quota_limit_type_ = limit_type;
+ }
+ void set_root_path(const base::FilePath& root_path) {
+ DCHECK(setter_thread_checker_.CalledOnValidThread());
+ root_path_ = root_path;
+ }
+
+ private:
+ scoped_refptr<FileSystemContext> file_system_context_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ // The current remaining quota, used by ObfuscatedFileUtil.
+ int64 allowed_bytes_growth_;
+
+ // The current quota limit type, used by ObfuscatedFileUtil.
+ quota::QuotaLimitType quota_limit_type_;
+
+ // Observers attached to this context.
+ ChangeObserverList change_observers_;
+ UpdateObserverList update_observers_;
+
+ // Root path for the operation, used by LocalFileUtil.
+ base::FilePath root_path_;
+
+ // Used to check its setters are not called on arbitrary thread.
+ base::ThreadChecker setter_thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationContext);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_operation_impl.cc b/chromium/webkit/browser/fileapi/file_system_operation_impl.cc
new file mode 100644
index 00000000000..60f79901923
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_operation_impl.cc
@@ -0,0 +1,539 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_operation_impl.h"
+
+#include "base/bind.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "net/base/escape.h"
+#include "net/url_request/url_request.h"
+#include "webkit/browser/fileapi/async_file_util.h"
+#include "webkit/browser/fileapi/copy_or_move_operation_delegate.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/file_writer_delegate.h"
+#include "webkit/browser/fileapi/remove_operation_delegate.h"
+#include "webkit/browser/fileapi/sandbox_file_stream_writer.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+#include "webkit/common/quota/quota_types.h"
+
+using webkit_blob::ScopedFile;
+
+namespace fileapi {
+
+FileSystemOperationImpl::FileSystemOperationImpl(
+ const FileSystemURL& url,
+ FileSystemContext* file_system_context,
+ scoped_ptr<FileSystemOperationContext> operation_context)
+ : file_system_context_(file_system_context),
+ operation_context_(operation_context.Pass()),
+ async_file_util_(NULL),
+ peer_handle_(base::kNullProcessHandle),
+ pending_operation_(kOperationNone) {
+ DCHECK(operation_context_.get());
+ operation_context_->DetachUserDataThread();
+ async_file_util_ = file_system_context_->GetAsyncFileUtil(url.type());
+ DCHECK(async_file_util_);
+}
+
+FileSystemOperationImpl::~FileSystemOperationImpl() {
+}
+
+void FileSystemOperationImpl::CreateFile(const FileSystemURL& url,
+ bool exclusive,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationCreateFile));
+ GetUsageAndQuotaThenRunTask(
+ url,
+ base::Bind(&FileSystemOperationImpl::DoCreateFile,
+ AsWeakPtr(), url, callback, exclusive),
+ base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
+}
+
+void FileSystemOperationImpl::CreateDirectory(const FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationCreateDirectory));
+ GetUsageAndQuotaThenRunTask(
+ url,
+ base::Bind(&FileSystemOperationImpl::DoCreateDirectory,
+ AsWeakPtr(), url, callback, exclusive, recursive),
+ base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
+}
+
+void FileSystemOperationImpl::Copy(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationCopy));
+ DCHECK(!recursive_operation_delegate_);
+ recursive_operation_delegate_.reset(
+ new CopyOrMoveOperationDelegate(
+ file_system_context(),
+ src_url, dest_url,
+ CopyOrMoveOperationDelegate::OPERATION_COPY,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback)));
+ recursive_operation_delegate_->RunRecursively();
+}
+
+void FileSystemOperationImpl::Move(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationMove));
+ DCHECK(!recursive_operation_delegate_);
+ recursive_operation_delegate_.reset(
+ new CopyOrMoveOperationDelegate(
+ file_system_context(),
+ src_url, dest_url,
+ CopyOrMoveOperationDelegate::OPERATION_MOVE,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback)));
+ recursive_operation_delegate_->RunRecursively();
+}
+
+void FileSystemOperationImpl::DirectoryExists(const FileSystemURL& url,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationDirectoryExists));
+ async_file_util_->GetFileInfo(
+ operation_context_.Pass(), url,
+ base::Bind(&FileSystemOperationImpl::DidDirectoryExists,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::FileExists(const FileSystemURL& url,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationFileExists));
+ async_file_util_->GetFileInfo(
+ operation_context_.Pass(), url,
+ base::Bind(&FileSystemOperationImpl::DidFileExists,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::GetMetadata(
+ const FileSystemURL& url, const GetMetadataCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationGetMetadata));
+ async_file_util_->GetFileInfo(operation_context_.Pass(), url, callback);
+}
+
+void FileSystemOperationImpl::ReadDirectory(
+ const FileSystemURL& url, const ReadDirectoryCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationReadDirectory));
+ async_file_util_->ReadDirectory(
+ operation_context_.Pass(), url, callback);
+}
+
+void FileSystemOperationImpl::Remove(const FileSystemURL& url,
+ bool recursive,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationRemove));
+ DCHECK(!recursive_operation_delegate_);
+
+ if (recursive) {
+ // For recursive removal, try to delegate the operation to AsyncFileUtil
+ // first. If not supported, it is delegated to RemoveOperationDelegate
+ // in DidDeleteRecursively.
+ async_file_util_->DeleteRecursively(
+ operation_context_.Pass(), url,
+ base::Bind(&FileSystemOperationImpl::DidDeleteRecursively,
+ AsWeakPtr(), url, callback));
+ return;
+ }
+
+ recursive_operation_delegate_.reset(
+ new RemoveOperationDelegate(
+ file_system_context(), url,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback)));
+ recursive_operation_delegate_->Run();
+}
+
+void FileSystemOperationImpl::Write(
+ const FileSystemURL& url,
+ scoped_ptr<FileWriterDelegate> writer_delegate,
+ scoped_ptr<net::URLRequest> blob_request,
+ const WriteCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationWrite));
+ file_writer_delegate_ = writer_delegate.Pass();
+ file_writer_delegate_->Start(
+ blob_request.Pass(),
+ base::Bind(&FileSystemOperationImpl::DidWrite, AsWeakPtr(),
+ url, callback));
+}
+
+void FileSystemOperationImpl::Truncate(const FileSystemURL& url, int64 length,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationTruncate));
+ GetUsageAndQuotaThenRunTask(
+ url,
+ base::Bind(&FileSystemOperationImpl::DoTruncate,
+ AsWeakPtr(), url, callback, length),
+ base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
+}
+
+void FileSystemOperationImpl::TouchFile(const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationTouchFile));
+ async_file_util_->Touch(
+ operation_context_.Pass(), url,
+ last_access_time, last_modified_time,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::OpenFile(const FileSystemURL& url,
+ int file_flags,
+ base::ProcessHandle peer_handle,
+ const OpenFileCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationOpenFile));
+ peer_handle_ = peer_handle;
+
+ if (file_flags & (
+ (base::PLATFORM_FILE_ENUMERATE | base::PLATFORM_FILE_TEMPORARY |
+ base::PLATFORM_FILE_HIDDEN))) {
+ callback.Run(base::PLATFORM_FILE_ERROR_FAILED,
+ base::kInvalidPlatformFileValue,
+ base::Closure(),
+ base::kNullProcessHandle);
+ return;
+ }
+ GetUsageAndQuotaThenRunTask(
+ url,
+ base::Bind(&FileSystemOperationImpl::DoOpenFile,
+ AsWeakPtr(),
+ url, callback, file_flags),
+ base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED,
+ base::kInvalidPlatformFileValue,
+ base::Closure(),
+ base::kNullProcessHandle));
+}
+
+// We can only get here on a write or truncate that's not yet completed.
+// We don't support cancelling any other operation at this time.
+void FileSystemOperationImpl::Cancel(const StatusCallback& cancel_callback) {
+ DCHECK(cancel_callback_.is_null());
+ cancel_callback_ = cancel_callback;
+
+ if (file_writer_delegate_.get()) {
+ DCHECK_EQ(kOperationWrite, pending_operation_);
+ // This will call DidWrite() with ABORT status code.
+ file_writer_delegate_->Cancel();
+ } else {
+ // For truncate we have no way to cancel the inflight operation (for now).
+ // Let it just run and dispatch cancel callback later.
+ DCHECK_EQ(kOperationTruncate, pending_operation_);
+ }
+}
+
+FileSystemOperationImpl* FileSystemOperationImpl::AsFileSystemOperationImpl() {
+ return this;
+}
+
+base::PlatformFileError FileSystemOperationImpl::SyncGetPlatformPath(
+ const FileSystemURL& url,
+ base::FilePath* platform_path) {
+ DCHECK(SetPendingOperationType(kOperationGetLocalPath));
+ FileSystemFileUtil* file_util = file_system_context()->GetFileUtil(
+ url.type());
+ if (!file_util)
+ return base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
+ file_util->GetLocalFilePath(operation_context_.get(), url, platform_path);
+ return base::PLATFORM_FILE_OK;
+}
+
+void FileSystemOperationImpl::CreateSnapshotFile(
+ const FileSystemURL& url,
+ const SnapshotFileCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationCreateSnapshotFile));
+ async_file_util_->CreateSnapshotFile(
+ operation_context_.Pass(), url, callback);
+}
+
+void FileSystemOperationImpl::CopyInForeignFile(
+ const base::FilePath& src_local_disk_file_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationCopyInForeignFile));
+ GetUsageAndQuotaThenRunTask(
+ dest_url,
+ base::Bind(&FileSystemOperationImpl::DoCopyInForeignFile,
+ AsWeakPtr(), src_local_disk_file_path, dest_url,
+ callback),
+ base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
+}
+
+void FileSystemOperationImpl::RemoveFile(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationRemove));
+ async_file_util_->DeleteFile(
+ operation_context_.Pass(), url,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::RemoveDirectory(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationRemove));
+ async_file_util_->DeleteDirectory(
+ operation_context_.Pass(), url,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::CopyFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationCopy));
+ DCHECK(src_url.IsInSameFileSystem(dest_url));
+ GetUsageAndQuotaThenRunTask(
+ dest_url,
+ base::Bind(&FileSystemOperationImpl::DoCopyFileLocal,
+ AsWeakPtr(), src_url, dest_url, callback),
+ base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
+}
+
+void FileSystemOperationImpl::MoveFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationMove));
+ DCHECK(src_url.IsInSameFileSystem(dest_url));
+ GetUsageAndQuotaThenRunTask(
+ dest_url,
+ base::Bind(&FileSystemOperationImpl::DoMoveFileLocal,
+ AsWeakPtr(), src_url, dest_url, callback),
+ base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
+}
+
+void FileSystemOperationImpl::GetUsageAndQuotaThenRunTask(
+ const FileSystemURL& url,
+ const base::Closure& task,
+ const base::Closure& error_callback) {
+ quota::QuotaManagerProxy* quota_manager_proxy =
+ file_system_context()->quota_manager_proxy();
+ if (!quota_manager_proxy ||
+ !file_system_context()->GetQuotaUtil(url.type())) {
+ // If we don't have the quota manager or the requested filesystem type
+ // does not support quota, we should be able to let it go.
+ operation_context_->set_allowed_bytes_growth(kint64max);
+ task.Run();
+ return;
+ }
+
+ DCHECK(quota_manager_proxy);
+ DCHECK(quota_manager_proxy->quota_manager());
+ quota_manager_proxy->quota_manager()->GetUsageAndQuota(
+ url.origin(),
+ FileSystemTypeToQuotaStorageType(url.type()),
+ base::Bind(&FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask,
+ AsWeakPtr(), task, error_callback));
+}
+
+void FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask(
+ const base::Closure& task,
+ const base::Closure& error_callback,
+ quota::QuotaStatusCode status,
+ int64 usage, int64 quota) {
+ if (status != quota::kQuotaStatusOk) {
+ LOG(WARNING) << "Got unexpected quota error : " << status;
+ error_callback.Run();
+ return;
+ }
+
+ operation_context_->set_allowed_bytes_growth(quota - usage);
+ task.Run();
+}
+
+void FileSystemOperationImpl::DoCreateFile(
+ const FileSystemURL& url,
+ const StatusCallback& callback,
+ bool exclusive) {
+ async_file_util_->EnsureFileExists(
+ operation_context_.Pass(), url,
+ base::Bind(
+ exclusive ?
+ &FileSystemOperationImpl::DidEnsureFileExistsExclusive :
+ &FileSystemOperationImpl::DidEnsureFileExistsNonExclusive,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DoCreateDirectory(
+ const FileSystemURL& url,
+ const StatusCallback& callback,
+ bool exclusive, bool recursive) {
+ async_file_util_->CreateDirectory(
+ operation_context_.Pass(),
+ url, exclusive, recursive,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DoCopyFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ async_file_util_->CopyFileLocal(
+ operation_context_.Pass(), src_url, dest_url,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DoMoveFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ async_file_util_->MoveFileLocal(
+ operation_context_.Pass(), src_url, dest_url,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DoCopyInForeignFile(
+ const base::FilePath& src_local_disk_file_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ async_file_util_->CopyInForeignFile(
+ operation_context_.Pass(),
+ src_local_disk_file_path, dest_url,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DoTruncate(const FileSystemURL& url,
+ const StatusCallback& callback,
+ int64 length) {
+ async_file_util_->Truncate(
+ operation_context_.Pass(), url, length,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DoOpenFile(const FileSystemURL& url,
+ const OpenFileCallback& callback,
+ int file_flags) {
+ async_file_util_->CreateOrOpen(
+ operation_context_.Pass(), url, file_flags,
+ base::Bind(&FileSystemOperationImpl::DidOpenFile,
+ AsWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DidEnsureFileExistsExclusive(
+ const StatusCallback& callback,
+ base::PlatformFileError rv, bool created) {
+ if (rv == base::PLATFORM_FILE_OK && !created) {
+ callback.Run(base::PLATFORM_FILE_ERROR_EXISTS);
+ } else {
+ DidFinishOperation(callback, rv);
+ }
+}
+
+void FileSystemOperationImpl::DidEnsureFileExistsNonExclusive(
+ const StatusCallback& callback,
+ base::PlatformFileError rv, bool /* created */) {
+ DidFinishOperation(callback, rv);
+}
+
+void FileSystemOperationImpl::DidFinishOperation(
+ const StatusCallback& callback,
+ base::PlatformFileError rv) {
+ if (!cancel_callback_.is_null()) {
+ DCHECK_EQ(kOperationTruncate, pending_operation_);
+
+ StatusCallback cancel_callback = cancel_callback_;
+ callback.Run(base::PLATFORM_FILE_ERROR_ABORT);
+ cancel_callback.Run(base::PLATFORM_FILE_OK);
+ } else {
+ callback.Run(rv);
+ }
+}
+
+void FileSystemOperationImpl::DidDirectoryExists(
+ const StatusCallback& callback,
+ base::PlatformFileError rv,
+ const base::PlatformFileInfo& file_info) {
+ if (rv == base::PLATFORM_FILE_OK && !file_info.is_directory)
+ rv = base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
+ callback.Run(rv);
+}
+
+void FileSystemOperationImpl::DidFileExists(
+ const StatusCallback& callback,
+ base::PlatformFileError rv,
+ const base::PlatformFileInfo& file_info) {
+ if (rv == base::PLATFORM_FILE_OK && file_info.is_directory)
+ rv = base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+ callback.Run(rv);
+}
+
+void FileSystemOperationImpl::DidDeleteRecursively(
+ const FileSystemURL& url,
+ const StatusCallback& callback,
+ base::PlatformFileError rv) {
+ if (rv == base::PLATFORM_FILE_ERROR_INVALID_OPERATION) {
+ // Recursive removal is not supported on this platform.
+ DCHECK(!recursive_operation_delegate_);
+ recursive_operation_delegate_.reset(
+ new RemoveOperationDelegate(
+ file_system_context(), url,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ AsWeakPtr(), callback)));
+ recursive_operation_delegate_->RunRecursively();
+ return;
+ }
+
+ callback.Run(rv);
+}
+
+void FileSystemOperationImpl::DidWrite(
+ const FileSystemURL& url,
+ const WriteCallback& write_callback,
+ base::PlatformFileError rv,
+ int64 bytes,
+ FileWriterDelegate::WriteProgressStatus write_status) {
+ const bool complete = (
+ write_status != FileWriterDelegate::SUCCESS_IO_PENDING);
+ if (complete && write_status != FileWriterDelegate::ERROR_WRITE_NOT_STARTED) {
+ DCHECK(operation_context_);
+ operation_context_->change_observers()->Notify(
+ &FileChangeObserver::OnModifyFile, MakeTuple(url));
+ }
+
+ StatusCallback cancel_callback = cancel_callback_;
+ write_callback.Run(rv, bytes, complete);
+ if (!cancel_callback.is_null())
+ cancel_callback.Run(base::PLATFORM_FILE_OK);
+}
+
+void FileSystemOperationImpl::DidOpenFile(
+ const OpenFileCallback& callback,
+ base::PlatformFileError rv,
+ base::PassPlatformFile file,
+ const base::Closure& on_close_callback) {
+ if (rv == base::PLATFORM_FILE_OK)
+ CHECK_NE(base::kNullProcessHandle, peer_handle_);
+ callback.Run(rv, file.ReleaseValue(), on_close_callback, peer_handle_);
+}
+
+bool FileSystemOperationImpl::SetPendingOperationType(OperationType type) {
+ if (pending_operation_ != kOperationNone)
+ return false;
+ pending_operation_ = type;
+ return true;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_operation_impl.h b/chromium/webkit/browser/fileapi/file_system_operation_impl.h
new file mode 100644
index 00000000000..293ac5178d8
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_operation_impl.h
@@ -0,0 +1,263 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_
+
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "webkit/browser/fileapi/file_system_operation.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/file_writer_delegate.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/blob/scoped_file.h"
+#include "webkit/common/quota/quota_types.h"
+
+namespace fileapi {
+
+class AsyncFileUtil;
+class FileSystemContext;
+class RecursiveOperationDelegate;
+
+// The default implementation of FileSystemOperation for file systems.
+class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemOperationImpl
+ : public NON_EXPORTED_BASE(FileSystemOperation),
+ public base::SupportsWeakPtr<FileSystemOperationImpl> {
+ public:
+ // NOTE: This constructor should not be called outside FileSystemBackends;
+ // instead please consider using
+ // file_system_context->CreateFileSystemOperation() to instantiate
+ // an appropriate FileSystemOperation.
+ FileSystemOperationImpl(
+ const FileSystemURL& url,
+ FileSystemContext* file_system_context,
+ scoped_ptr<FileSystemOperationContext> operation_context);
+
+ virtual ~FileSystemOperationImpl();
+
+ // FileSystemOperation overrides.
+ virtual void CreateFile(const FileSystemURL& url,
+ bool exclusive,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void CreateDirectory(const FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void Copy(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void Move(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void DirectoryExists(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void FileExists(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void GetMetadata(const FileSystemURL& url,
+ const GetMetadataCallback& callback) OVERRIDE;
+ virtual void ReadDirectory(const FileSystemURL& url,
+ const ReadDirectoryCallback& callback) OVERRIDE;
+ virtual void Remove(const FileSystemURL& url, bool recursive,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void Write(const FileSystemURL& url,
+ scoped_ptr<FileWriterDelegate> writer_delegate,
+ scoped_ptr<net::URLRequest> blob_request,
+ const WriteCallback& callback) OVERRIDE;
+ virtual void Truncate(const FileSystemURL& url, int64 length,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void TouchFile(const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void OpenFile(const FileSystemURL& url,
+ int file_flags,
+ base::ProcessHandle peer_handle,
+ const OpenFileCallback& callback) OVERRIDE;
+ virtual void Cancel(const StatusCallback& cancel_callback) OVERRIDE;
+ virtual FileSystemOperationImpl* AsFileSystemOperationImpl() OVERRIDE;
+ virtual void CreateSnapshotFile(
+ const FileSystemURL& path,
+ const SnapshotFileCallback& callback) OVERRIDE;
+
+ // Copies in a single file from a different filesystem.
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_file_path|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ virtual void CopyInForeignFile(const base::FilePath& src_local_disk_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback);
+
+ // Removes a single file.
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |url| is not a file.
+ //
+ void RemoveFile(const FileSystemURL& url,
+ const StatusCallback& callback);
+
+ // Removes a single empty directory.
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory.
+ // - PLATFORM_FILE_ERROR_NOT_EMPTY if |url| is not empty.
+ //
+ void RemoveDirectory(const FileSystemURL& url,
+ const StatusCallback& callback);
+
+ // Copies a file from |src_url| to |dest_url|.
+ // This must be called for files that belong to the same filesystem
+ // (i.e. type() and origin() of the |src_url| and |dest_url| must match).
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ void CopyFileLocal(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback);
+
+ // Moves a local file from |src_url| to |dest_url|.
+ // This must be called for files that belong to the same filesystem
+ // (i.e. type() and origin() of the |src_url| and |dest_url| must match).
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ void MoveFileLocal(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback);
+
+ // Synchronously gets the platform path for the given |url|.
+ // This may fail if |file_system_context| returns NULL on GetFileUtil().
+ // In such a case, base::PLATFORM_FILE_ERROR_INVALID_OPERATION will be
+ // returned.
+ base::PlatformFileError SyncGetPlatformPath(const FileSystemURL& url,
+ base::FilePath* platform_path);
+
+ FileSystemContext* file_system_context() const {
+ return file_system_context_.get();
+ }
+
+ protected:
+ // Queries the quota and usage and then runs the given |task|.
+ // If an error occurs during the quota query it runs |error_callback| instead.
+ void GetUsageAndQuotaThenRunTask(
+ const FileSystemURL& url,
+ const base::Closure& task,
+ const base::Closure& error_callback);
+
+ // Called after the quota info is obtained from the quota manager
+ // (which is triggered by GetUsageAndQuotaThenRunTask).
+ // Sets the quota info in the operation_context_ and then runs the given
+ // |task| if the returned quota status is successful, otherwise runs
+ // |error_callback|.
+ void DidGetUsageAndQuotaAndRunTask(
+ const base::Closure& task,
+ const base::Closure& error_callback,
+ quota::QuotaStatusCode status,
+ int64 usage, int64 quota);
+
+ // The 'body' methods that perform the actual work (i.e. posting the
+ // file task on proxy_) after the quota check.
+ void DoCreateFile(const FileSystemURL& url,
+ const StatusCallback& callback, bool exclusive);
+ void DoCreateDirectory(const FileSystemURL& url,
+ const StatusCallback& callback,
+ bool exclusive,
+ bool recursive);
+ void DoCopyFileLocal(const FileSystemURL& src,
+ const FileSystemURL& dest,
+ const StatusCallback& callback);
+ void DoMoveFileLocal(const FileSystemURL& src,
+ const FileSystemURL& dest,
+ const StatusCallback& callback);
+ void DoCopyInForeignFile(const base::FilePath& src_local_disk_file_path,
+ const FileSystemURL& dest,
+ const StatusCallback& callback);
+ void DoTruncate(const FileSystemURL& url,
+ const StatusCallback& callback, int64 length);
+ void DoOpenFile(const FileSystemURL& url,
+ const OpenFileCallback& callback, int file_flags);
+
+ // Callback for CreateFile for |exclusive|=true cases.
+ void DidEnsureFileExistsExclusive(const StatusCallback& callback,
+ base::PlatformFileError rv,
+ bool created);
+
+ // Callback for CreateFile for |exclusive|=false cases.
+ void DidEnsureFileExistsNonExclusive(const StatusCallback& callback,
+ base::PlatformFileError rv,
+ bool created);
+
+ void DidFinishOperation(const StatusCallback& callback,
+ base::PlatformFileError rv);
+ void DidDirectoryExists(const StatusCallback& callback,
+ base::PlatformFileError rv,
+ const base::PlatformFileInfo& file_info);
+ void DidFileExists(const StatusCallback& callback,
+ base::PlatformFileError rv,
+ const base::PlatformFileInfo& file_info);
+ void DidDeleteRecursively(const FileSystemURL& url,
+ const StatusCallback& callback,
+ base::PlatformFileError rv);
+ void DidWrite(const FileSystemURL& url,
+ const WriteCallback& callback,
+ base::PlatformFileError rv,
+ int64 bytes,
+ FileWriterDelegate::WriteProgressStatus write_status);
+ void DidOpenFile(const OpenFileCallback& callback,
+ base::PlatformFileError rv,
+ base::PassPlatformFile file,
+ const base::Closure& on_close_callback);
+
+ // Used only for internal assertions.
+ // Returns false if there's another inflight pending operation.
+ bool SetPendingOperationType(OperationType type);
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+
+ scoped_ptr<FileSystemOperationContext> operation_context_;
+ AsyncFileUtil* async_file_util_; // Not owned.
+
+ scoped_ptr<FileWriterDelegate> file_writer_delegate_;
+ scoped_ptr<RecursiveOperationDelegate> recursive_operation_delegate_;
+
+ StatusCallback cancel_callback_;
+
+ // Used only by OpenFile, in order to clone the file handle back to the
+ // requesting process.
+ base::ProcessHandle peer_handle_;
+
+ // A flag to make sure we call operation only once per instance.
+ OperationType pending_operation_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImpl);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_operation_impl_unittest.cc b/chromium/webkit/browser/fileapi/file_system_operation_impl_unittest.cc
new file mode 100644
index 00000000000..083fdeb7dbd
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_operation_impl_unittest.cc
@@ -0,0 +1,1187 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_operation_impl.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/async_file_test_helper.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/mock_file_change_observer.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+#include "webkit/browser/fileapi/sandbox_file_system_test_helper.h"
+#include "webkit/browser/quota/mock_quota_manager.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using quota::QuotaManager;
+using quota::QuotaManagerProxy;
+using webkit_blob::ShareableFileReference;
+
+namespace fileapi {
+
+namespace {
+
+const int kFileOperationStatusNotSet = 1;
+
+void AssertFileErrorEq(const tracked_objects::Location& from_here,
+ base::PlatformFileError expected,
+ base::PlatformFileError actual) {
+ ASSERT_EQ(expected, actual) << from_here.ToString();
+}
+
+} // namespace (anonymous)
+
+// Test class for FileSystemOperationImpl.
+class FileSystemOperationImplTest
+ : public testing::Test {
+ public:
+ FileSystemOperationImplTest()
+ : status_(kFileOperationStatusNotSet),
+ weak_factory_(this) {}
+
+ protected:
+ virtual void SetUp() OVERRIDE {
+ EXPECT_TRUE(base_.CreateUniqueTempDir());
+ change_observers_ = MockFileChangeObserver::CreateList(&change_observer_);
+
+ base::FilePath base_dir = base_.path().AppendASCII("filesystem");
+ quota_manager_ =
+ new quota::MockQuotaManager(false /* is_incognito */,
+ base_dir,
+ base::MessageLoopProxy::current().get(),
+ base::MessageLoopProxy::current().get(),
+ NULL /* special storage policy */);
+ quota_manager_proxy_ = new quota::MockQuotaManagerProxy(
+ quota_manager(), base::MessageLoopProxy::current().get());
+ sandbox_file_system_.SetUp(base_dir, quota_manager_proxy_.get());
+ sandbox_file_system_.AddFileChangeObserver(&change_observer_);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Let the client go away before dropping a ref of the quota manager proxy.
+ quota_manager_proxy()->SimulateQuotaManagerDestroyed();
+ quota_manager_ = NULL;
+ quota_manager_proxy_ = NULL;
+ sandbox_file_system_.TearDown();
+ }
+
+ FileSystemOperationRunner* operation_runner() {
+ return sandbox_file_system_.operation_runner();
+ }
+
+ int status() const { return status_; }
+ const base::PlatformFileInfo& info() const { return info_; }
+ const base::FilePath& path() const { return path_; }
+ const std::vector<DirectoryEntry>& entries() const {
+ return entries_;
+ }
+
+ const ShareableFileReference* shareable_file_ref() const {
+ return shareable_file_ref_.get();
+ }
+
+ quota::MockQuotaManager* quota_manager() {
+ return static_cast<quota::MockQuotaManager*>(quota_manager_.get());
+ }
+
+ quota::MockQuotaManagerProxy* quota_manager_proxy() {
+ return static_cast<quota::MockQuotaManagerProxy*>(
+ quota_manager_proxy_.get());
+ }
+
+ FileSystemFileUtil* file_util() {
+ return sandbox_file_system_.file_util();
+ }
+
+ MockFileChangeObserver* change_observer() {
+ return &change_observer_;
+ }
+
+ scoped_ptr<FileSystemOperationContext> NewContext() {
+ FileSystemOperationContext* context =
+ sandbox_file_system_.NewOperationContext();
+ // Grant enough quota for all test cases.
+ context->set_allowed_bytes_growth(1000000);
+ return make_scoped_ptr(context);
+ }
+
+ FileSystemURL URLForPath(const std::string& path) const {
+ return sandbox_file_system_.CreateURLFromUTF8(path);
+ }
+
+ base::FilePath PlatformPath(const std::string& path) {
+ return sandbox_file_system_.GetLocalPath(
+ base::FilePath::FromUTF8Unsafe(path));
+ }
+
+ bool FileExists(const std::string& path) {
+ return AsyncFileTestHelper::FileExists(
+ sandbox_file_system_.file_system_context(), URLForPath(path),
+ AsyncFileTestHelper::kDontCheckSize);
+ }
+
+ bool DirectoryExists(const std::string& path) {
+ return AsyncFileTestHelper::DirectoryExists(
+ sandbox_file_system_.file_system_context(), URLForPath(path));
+ }
+
+ FileSystemURL CreateFile(const std::string& path) {
+ FileSystemURL url = URLForPath(path);
+ bool created = false;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->EnsureFileExists(NewContext().get(),
+ url, &created));
+ EXPECT_TRUE(created);
+ return url;
+ }
+
+ FileSystemURL CreateDirectory(const std::string& path) {
+ FileSystemURL url = URLForPath(path);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->CreateDirectory(NewContext().get(), url,
+ false /* exclusive */, true));
+ return url;
+ }
+
+ int64 GetFileSize(const std::string& path) {
+ base::PlatformFileInfo info;
+ EXPECT_TRUE(file_util::GetFileInfo(PlatformPath(path), &info));
+ return info.size;
+ }
+
+ // Callbacks for recording test results.
+ FileSystemOperation::StatusCallback RecordStatusCallback() {
+ return base::Bind(&FileSystemOperationImplTest::DidFinish,
+ weak_factory_.GetWeakPtr());
+ }
+
+ FileSystemOperation::ReadDirectoryCallback
+ RecordReadDirectoryCallback() {
+ return base::Bind(&FileSystemOperationImplTest::DidReadDirectory,
+ weak_factory_.GetWeakPtr());
+ }
+
+ FileSystemOperation::GetMetadataCallback RecordMetadataCallback() {
+ return base::Bind(&FileSystemOperationImplTest::DidGetMetadata,
+ weak_factory_.GetWeakPtr());
+ }
+
+ FileSystemOperation::SnapshotFileCallback RecordSnapshotFileCallback() {
+ return base::Bind(&FileSystemOperationImplTest::DidCreateSnapshotFile,
+ weak_factory_.GetWeakPtr());
+ }
+
+ void DidFinish(base::PlatformFileError status) {
+ status_ = status;
+ }
+
+ void DidReadDirectory(
+ base::PlatformFileError status,
+ const std::vector<DirectoryEntry>& entries,
+ bool /* has_more */) {
+ entries_ = entries;
+ status_ = status;
+ }
+
+ void DidGetMetadata(base::PlatformFileError status,
+ const base::PlatformFileInfo& info) {
+ info_ = info;
+ status_ = status;
+ }
+
+ void DidCreateSnapshotFile(
+ base::PlatformFileError status,
+ const base::PlatformFileInfo& info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<ShareableFileReference>& shareable_file_ref) {
+ info_ = info;
+ path_ = platform_path;
+ status_ = status;
+ shareable_file_ref_ = shareable_file_ref;
+ }
+
+ int64 GetDataSizeOnDisk() {
+ return sandbox_file_system_.ComputeCurrentOriginUsage() -
+ sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage();
+ }
+
+ void GetUsageAndQuota(int64* usage, int64* quota) {
+ quota::QuotaStatusCode status =
+ AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(),
+ sandbox_file_system_.origin(),
+ sandbox_file_system_.type(),
+ usage,
+ quota);
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_EQ(quota::kQuotaStatusOk, status);
+ }
+
+ int64 ComputePathCost(const FileSystemURL& url) {
+ int64 base_usage;
+ GetUsageAndQuota(&base_usage, NULL);
+
+ AsyncFileTestHelper::CreateFile(
+ sandbox_file_system_.file_system_context(), url);
+ operation_runner()->Remove(url, false /* recursive */,
+ base::Bind(&AssertFileErrorEq, FROM_HERE,
+ base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ change_observer()->ResetCount();
+
+ int64 total_usage;
+ GetUsageAndQuota(&total_usage, NULL);
+ return total_usage - base_usage;
+ }
+
+ void GrantQuotaForCurrentUsage() {
+ int64 usage;
+ GetUsageAndQuota(&usage, NULL);
+ quota_manager()->SetQuota(sandbox_file_system_.origin(),
+ sandbox_file_system_.storage_type(),
+ usage);
+ }
+
+ int64 GetUsage() {
+ int64 usage = 0;
+ GetUsageAndQuota(&usage, NULL);
+ return usage;
+ }
+
+ void AddQuota(int64 quota_delta) {
+ int64 quota;
+ GetUsageAndQuota(NULL, &quota);
+ quota_manager()->SetQuota(sandbox_file_system_.origin(),
+ sandbox_file_system_.storage_type(),
+ quota + quota_delta);
+ }
+
+ base::MessageLoop message_loop_;
+ scoped_refptr<QuotaManager> quota_manager_;
+ scoped_refptr<QuotaManagerProxy> quota_manager_proxy_;
+
+ // Common temp base for nondestructive uses.
+ base::ScopedTempDir base_;
+
+ SandboxFileSystemTestHelper sandbox_file_system_;
+
+ // For post-operation status.
+ int status_;
+ base::PlatformFileInfo info_;
+ base::FilePath path_;
+ std::vector<DirectoryEntry> entries_;
+ scoped_refptr<ShareableFileReference> shareable_file_ref_;
+
+ MockFileChangeObserver change_observer_;
+ ChangeObserverList change_observers_;
+
+ base::WeakPtrFactory<FileSystemOperationImplTest> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImplTest);
+};
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcDoesntExist) {
+ change_observer()->ResetCount();
+ operation_runner()->Move(URLForPath("a"), URLForPath("b"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureContainsPath) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("src/dest"));
+
+ operation_runner()->Move(src_dir, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcDirExistsDestFile) {
+ // Src exists and is dir. Dest is a file.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ FileSystemURL dest_file(CreateFile("dest/file"));
+
+ operation_runner()->Move(src_dir, dest_file, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestMoveFailureSrcFileExistsDestNonEmptyDir) {
+ // Src exists and is a directory. Dest is a non-empty directory.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ FileSystemURL dest_file(CreateFile("dest/file"));
+
+ operation_runner()->Move(src_dir, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureSrcFileExistsDestDir) {
+ // Src exists and is a file. Dest is a directory.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL src_file(CreateFile("src/file"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ operation_runner()->Move(src_file, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveFailureDestParentDoesntExist) {
+ // Dest. parent path does not exist.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ operation_runner()->Move(src_dir, URLForPath("nonexistent/deset"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcFileAndOverwrite) {
+ FileSystemURL src_file(CreateFile("src"));
+ FileSystemURL dest_file(CreateFile("dest"));
+
+ operation_runner()->Move(src_file, dest_file, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(FileExists("dest"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcFileAndNew) {
+ FileSystemURL src_file(CreateFile("src"));
+
+ operation_runner()->Move(src_file, URLForPath("new"), RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(FileExists("new"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirAndOverwrite) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ operation_runner()->Move(src_dir, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_FALSE(DirectoryExists("src"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_EQ(2, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Make sure we've overwritten but not moved the source under the |dest_dir|.
+ EXPECT_TRUE(DirectoryExists("dest"));
+ EXPECT_FALSE(DirectoryExists("dest/src"));
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirAndNew) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ operation_runner()->Move(src_dir, URLForPath("dest/new"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_FALSE(DirectoryExists("src"));
+ EXPECT_TRUE(DirectoryExists("dest/new"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestMoveSuccessSrcDirRecursive) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ CreateDirectory("src/dir");
+ CreateFile("src/dir/sub");
+
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ operation_runner()->Move(src_dir, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(DirectoryExists("dest/dir"));
+ EXPECT_TRUE(FileExists("dest/dir/sub"));
+
+ EXPECT_EQ(3, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcDoesntExist) {
+ operation_runner()->Copy(URLForPath("a"), URLForPath("b"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureContainsPath) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("src/dir"));
+
+ operation_runner()->Copy(src_dir, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcDirExistsDestFile) {
+ // Src exists and is dir. Dest is a file.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ FileSystemURL dest_file(CreateFile("dest/file"));
+
+ operation_runner()->Copy(src_dir, dest_file, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestCopyFailureSrcFileExistsDestNonEmptyDir) {
+ // Src exists and is a directory. Dest is a non-empty directory.
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ FileSystemURL dest_file(CreateFile("dest/file"));
+
+ operation_runner()->Copy(src_dir, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureSrcFileExistsDestDir) {
+ // Src exists and is a file. Dest is a directory.
+ FileSystemURL src_file(CreateFile("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ operation_runner()->Copy(src_file, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureDestParentDoesntExist) {
+ // Dest. parent path does not exist.
+ FileSystemURL src_dir(CreateDirectory("src"));
+
+ operation_runner()->Copy(src_dir, URLForPath("nonexistent/dest"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyFailureByQuota) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL src_file(CreateFile("src/file"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+ operation_runner()->Truncate(src_file, 6, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_EQ(6, GetFileSize("src/file"));
+
+ FileSystemURL dest_file(URLForPath("dest/file"));
+ int64 dest_path_cost = ComputePathCost(dest_file);
+ GrantQuotaForCurrentUsage();
+ AddQuota(6 + dest_path_cost - 1);
+
+ operation_runner()->Copy(src_file, dest_file, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status());
+ EXPECT_FALSE(FileExists("dest/file"));
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcFileAndOverwrite) {
+ FileSystemURL src_file(CreateFile("src"));
+ FileSystemURL dest_file(CreateFile("dest"));
+
+ operation_runner()->Copy(src_file, dest_file, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(FileExists("dest"));
+ EXPECT_EQ(2, quota_manager_proxy()->notify_storage_accessed_count());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcFileAndNew) {
+ FileSystemURL src_file(CreateFile("src"));
+
+ operation_runner()->Copy(src_file, URLForPath("new"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(FileExists("new"));
+ EXPECT_EQ(2, quota_manager_proxy()->notify_storage_accessed_count());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirAndOverwrite) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ operation_runner()->Copy(src_dir, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+
+ // Make sure we've overwritten but not copied the source under the |dest_dir|.
+ EXPECT_TRUE(DirectoryExists("dest"));
+ EXPECT_FALSE(DirectoryExists("dest/src"));
+ EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 3);
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirAndNew) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ FileSystemURL dest_dir_new(URLForPath("dest"));
+
+ operation_runner()->Copy(src_dir, dest_dir_new, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(DirectoryExists("dest"));
+ EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 2);
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopySuccessSrcDirRecursive) {
+ FileSystemURL src_dir(CreateDirectory("src"));
+ CreateDirectory("src/dir");
+ CreateFile("src/dir/sub");
+
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ operation_runner()->Copy(src_dir, dest_dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(DirectoryExists("dest/dir"));
+ EXPECT_TRUE(FileExists("dest/dir/sub"));
+
+ // For recursive copy we may record multiple read access.
+ EXPECT_GE(quota_manager_proxy()->notify_storage_accessed_count(), 1);
+
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_from_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileSuccess) {
+ base::FilePath src_local_disk_file_path;
+ file_util::CreateTemporaryFile(&src_local_disk_file_path);
+ const char test_data[] = "foo";
+ int data_size = ARRAYSIZE_UNSAFE(test_data);
+ file_util::WriteFile(src_local_disk_file_path, test_data, data_size);
+
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ int64 before_usage;
+ GetUsageAndQuota(&before_usage, NULL);
+
+ // Check that the file copied and corresponding usage increased.
+ operation_runner()->CopyInForeignFile(src_local_disk_file_path,
+ URLForPath("dest/file"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(1, change_observer()->create_file_count());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(FileExists("dest/file"));
+ int64 after_usage;
+ GetUsageAndQuota(&after_usage, NULL);
+ EXPECT_GT(after_usage, before_usage);
+
+ // Compare contents of src and copied file.
+ char buffer[100];
+ EXPECT_EQ(data_size, file_util::ReadFile(PlatformPath("dest/file"),
+ buffer, data_size));
+ for (int i = 0; i < data_size; ++i)
+ EXPECT_EQ(test_data[i], buffer[i]);
+}
+
+TEST_F(FileSystemOperationImplTest, TestCopyInForeignFileFailureByQuota) {
+ base::FilePath src_local_disk_file_path;
+ file_util::CreateTemporaryFile(&src_local_disk_file_path);
+ const char test_data[] = "foo";
+ file_util::WriteFile(src_local_disk_file_path, test_data,
+ ARRAYSIZE_UNSAFE(test_data));
+
+ FileSystemURL dest_dir(CreateDirectory("dest"));
+
+ GrantQuotaForCurrentUsage();
+ operation_runner()->CopyInForeignFile(src_local_disk_file_path,
+ URLForPath("dest/file"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(FileExists("dest/file"));
+ EXPECT_EQ(0, change_observer()->create_file_count());
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateFileFailure) {
+ // Already existing file and exclusive true.
+ FileSystemURL file(CreateFile("file"));
+ operation_runner()->CreateFile(file, true, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessFileExists) {
+ // Already existing file and exclusive false.
+ FileSystemURL file(CreateFile("file"));
+ operation_runner()->CreateFile(file, false, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(FileExists("file"));
+
+ // The file was already there; did nothing.
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessExclusive) {
+ // File doesn't exist but exclusive is true.
+ operation_runner()->CreateFile(URLForPath("new"), true,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(FileExists("new"));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateFileSuccessFileDoesntExist) {
+ // Non existing file.
+ operation_runner()->CreateFile(URLForPath("nonexistent"), false,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestCreateDirFailureDestParentDoesntExist) {
+ // Dest. parent path does not exist.
+ operation_runner()->CreateDirectory(
+ URLForPath("nonexistent/dir"), false, false,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateDirFailureDirExists) {
+ // Exclusive and dir existing at path.
+ FileSystemURL dir(CreateDirectory("dir"));
+ operation_runner()->CreateDirectory(dir, true, false,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateDirFailureFileExists) {
+ // Exclusive true and file existing at path.
+ FileSystemURL file(CreateFile("file"));
+ operation_runner()->CreateDirectory(file, true, false,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateDirSuccess) {
+ // Dir exists and exclusive is false.
+ FileSystemURL dir(CreateDirectory("dir"));
+ operation_runner()->CreateDirectory(dir, false, false,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Dir doesn't exist.
+ operation_runner()->CreateDirectory(URLForPath("new"), false, false,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(DirectoryExists("new"));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateDirSuccessExclusive) {
+ // Dir doesn't exist.
+ operation_runner()->CreateDirectory(URLForPath("new"), true, false,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(DirectoryExists("new"));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestExistsAndMetadataFailure) {
+ operation_runner()->GetMetadata(URLForPath("nonexistent"),
+ RecordMetadataCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+
+ operation_runner()->FileExists(URLForPath("nonexistent"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+
+ operation_runner()->DirectoryExists(URLForPath("nonexistent"),
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestExistsAndMetadataSuccess) {
+ FileSystemURL dir(CreateDirectory("dir"));
+ FileSystemURL file(CreateFile("dir/file"));
+ int read_access = 0;
+
+ operation_runner()->DirectoryExists(dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ ++read_access;
+
+ operation_runner()->GetMetadata(dir, RecordMetadataCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(info().is_directory);
+ ++read_access;
+
+ operation_runner()->FileExists(file, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ ++read_access;
+
+ operation_runner()->GetMetadata(file, RecordMetadataCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_FALSE(info().is_directory);
+ ++read_access;
+
+ EXPECT_EQ(read_access,
+ quota_manager_proxy()->notify_storage_accessed_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestTypeMismatchErrors) {
+ FileSystemURL dir(CreateDirectory("dir"));
+ operation_runner()->FileExists(dir, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE, status());
+
+ FileSystemURL file(CreateFile("file"));
+ operation_runner()->DirectoryExists(file, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, status());
+}
+
+TEST_F(FileSystemOperationImplTest, TestReadDirFailure) {
+ // Path doesn't exist
+ operation_runner()->ReadDirectory(URLForPath("nonexistent"),
+ RecordReadDirectoryCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+
+ // File exists.
+ FileSystemURL file(CreateFile("file"));
+ operation_runner()->ReadDirectory(file, RecordReadDirectoryCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestReadDirSuccess) {
+ // parent_dir
+ // | |
+ // child_dir child_file
+ // Verify reading parent_dir.
+ FileSystemURL parent_dir(CreateDirectory("dir"));
+ FileSystemURL child_dir(CreateDirectory("dir/child_dir"));
+ FileSystemURL child_file(CreateFile("dir/child_file"));
+
+ operation_runner()->ReadDirectory(parent_dir, RecordReadDirectoryCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_EQ(2u, entries().size());
+
+ for (size_t i = 0; i < entries().size(); ++i) {
+ if (entries()[i].is_directory)
+ EXPECT_EQ(FILE_PATH_LITERAL("child_dir"), entries()[i].name);
+ else
+ EXPECT_EQ(FILE_PATH_LITERAL("child_file"), entries()[i].name);
+ }
+ EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestRemoveFailure) {
+ // Path doesn't exist.
+ operation_runner()->Remove(URLForPath("nonexistent"), false /* recursive */,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+
+ // It's an error to try to remove a non-empty directory if recursive flag
+ // is false.
+ // parent_dir
+ // | |
+ // child_dir child_file
+ // Verify deleting parent_dir.
+ FileSystemURL parent_dir(CreateDirectory("dir"));
+ FileSystemURL child_dir(CreateDirectory("dir/child_dir"));
+ FileSystemURL child_file(CreateFile("dir/child_file"));
+
+ operation_runner()->Remove(parent_dir, false /* recursive */,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestRemoveSuccess) {
+ FileSystemURL empty_dir(CreateDirectory("empty_dir"));
+ EXPECT_TRUE(DirectoryExists("empty_dir"));
+ operation_runner()->Remove(empty_dir, false /* recursive */,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_FALSE(DirectoryExists("empty_dir"));
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestRemoveSuccessRecursive) {
+ // Removing a non-empty directory with recursive flag == true should be ok.
+ // parent_dir
+ // | |
+ // child_dir child_files
+ // |
+ // child_files
+ //
+ // Verify deleting parent_dir.
+ FileSystemURL parent_dir(CreateDirectory("dir"));
+ for (int i = 0; i < 8; ++i)
+ CreateFile(base::StringPrintf("dir/file-%d", i));
+ FileSystemURL child_dir(CreateDirectory("dir/child_dir"));
+ for (int i = 0; i < 8; ++i)
+ CreateFile(base::StringPrintf("dir/child_dir/file-%d", i));
+
+ operation_runner()->Remove(parent_dir, true /* recursive */,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_FALSE(DirectoryExists("parent_dir"));
+
+ EXPECT_EQ(2, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(16, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(FileSystemOperationImplTest, TestTruncate) {
+ FileSystemURL file(CreateFile("file"));
+ base::FilePath platform_path = PlatformPath("file");
+
+ char test_data[] = "test data";
+ int data_size = static_cast<int>(sizeof(test_data));
+ EXPECT_EQ(data_size,
+ file_util::WriteFile(platform_path, test_data, data_size));
+
+ // Check that its length is the size of the data written.
+ operation_runner()->GetMetadata(file, RecordMetadataCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_FALSE(info().is_directory);
+ EXPECT_EQ(data_size, info().size);
+
+ // Extend the file by truncating it.
+ int length = 17;
+ operation_runner()->Truncate(file, length, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Check that its length is now 17 and that it's all zeroes after the test
+ // data.
+ EXPECT_EQ(length, GetFileSize("file"));
+ char data[100];
+ EXPECT_EQ(length, file_util::ReadFile(platform_path, data, length));
+ for (int i = 0; i < length; ++i) {
+ if (i < static_cast<int>(sizeof(test_data)))
+ EXPECT_EQ(test_data[i], data[i]);
+ else
+ EXPECT_EQ(0, data[i]);
+ }
+
+ // Shorten the file by truncating it.
+ length = 3;
+ operation_runner()->Truncate(file, length, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Check that its length is now 3 and that it contains only bits of test data.
+ EXPECT_EQ(length, GetFileSize("file"));
+ EXPECT_EQ(length, file_util::ReadFile(platform_path, data, length));
+ for (int i = 0; i < length; ++i)
+ EXPECT_EQ(test_data[i], data[i]);
+
+ // Truncate is not a 'read' access. (Here expected access count is 1
+ // since we made 1 read access for GetMetadata.)
+ EXPECT_EQ(1, quota_manager_proxy()->notify_storage_accessed_count());
+}
+
+TEST_F(FileSystemOperationImplTest, TestTruncateFailureByQuota) {
+ FileSystemURL dir(CreateDirectory("dir"));
+ FileSystemURL file(CreateFile("dir/file"));
+
+ GrantQuotaForCurrentUsage();
+ AddQuota(10);
+
+ operation_runner()->Truncate(file, 10, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ EXPECT_EQ(10, GetFileSize("dir/file"));
+
+ operation_runner()->Truncate(file, 11, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ EXPECT_EQ(10, GetFileSize("dir/file"));
+}
+
+TEST_F(FileSystemOperationImplTest, TestTouchFile) {
+ FileSystemURL file(CreateFile("file"));
+ base::FilePath platform_path = PlatformPath("file");
+
+ base::PlatformFileInfo info;
+ EXPECT_TRUE(file_util::GetFileInfo(platform_path, &info));
+ EXPECT_FALSE(info.is_directory);
+ EXPECT_EQ(0, info.size);
+ const base::Time last_modified = info.last_modified;
+ const base::Time last_accessed = info.last_accessed;
+
+ const base::Time new_modified_time = base::Time::UnixEpoch();
+ const base::Time new_accessed_time = new_modified_time +
+ base::TimeDelta::FromHours(77);
+ ASSERT_NE(last_modified, new_modified_time);
+ ASSERT_NE(last_accessed, new_accessed_time);
+
+ operation_runner()->TouchFile(file, new_accessed_time, new_modified_time,
+ RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ EXPECT_TRUE(file_util::GetFileInfo(platform_path, &info));
+ // We compare as time_t here to lower our resolution, to avoid false
+ // negatives caused by conversion to the local filesystem's native
+ // representation and back.
+ EXPECT_EQ(new_modified_time.ToTimeT(), info.last_modified.ToTimeT());
+ EXPECT_EQ(new_accessed_time.ToTimeT(), info.last_accessed.ToTimeT());
+}
+
+TEST_F(FileSystemOperationImplTest, TestCreateSnapshotFile) {
+ FileSystemURL dir(CreateDirectory("dir"));
+
+ // Create a file for the testing.
+ operation_runner()->DirectoryExists(dir, RecordStatusCallback());
+ FileSystemURL file(CreateFile("dir/file"));
+ operation_runner()->FileExists(file, RecordStatusCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+
+ // See if we can get a 'snapshot' file info for the file.
+ // Since FileSystemOperationImpl assumes the file exists in the local
+ // directory it should just returns the same metadata and platform_path
+ // as the file itself.
+ operation_runner()->CreateSnapshotFile(file, RecordSnapshotFileCallback());
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_FALSE(info().is_directory);
+ EXPECT_EQ(PlatformPath("dir/file"), path());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // The FileSystemOpration implementation does not create a
+ // shareable file reference.
+ EXPECT_EQ(NULL, shareable_file_ref());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestMoveSuccessSrcDirRecursiveWithQuota) {
+ FileSystemURL src(CreateDirectory("src"));
+ int src_path_cost = GetUsage();
+
+ FileSystemURL dest(CreateDirectory("dest"));
+ FileSystemURL child_file1(CreateFile("src/file1"));
+ FileSystemURL child_file2(CreateFile("src/file2"));
+ FileSystemURL child_dir(CreateDirectory("src/dir"));
+ FileSystemURL grandchild_file1(CreateFile("src/dir/file1"));
+ FileSystemURL grandchild_file2(CreateFile("src/dir/file2"));
+
+ int total_path_cost = GetUsage();
+ EXPECT_EQ(0, GetDataSizeOnDisk());
+
+ operation_runner()->Truncate(
+ child_file1, 5000,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ operation_runner()->Truncate(
+ child_file2, 400,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ operation_runner()->Truncate(
+ grandchild_file1, 30,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ operation_runner()->Truncate(
+ grandchild_file2, 2,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ const int64 all_file_size = 5000 + 400 + 30 + 2;
+ EXPECT_EQ(all_file_size, GetDataSizeOnDisk());
+ EXPECT_EQ(all_file_size + total_path_cost, GetUsage());
+
+ operation_runner()->Move(
+ src, dest,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_FALSE(DirectoryExists("src/dir"));
+ EXPECT_FALSE(FileExists("src/dir/file2"));
+ EXPECT_TRUE(DirectoryExists("dest/dir"));
+ EXPECT_TRUE(FileExists("dest/dir/file2"));
+
+ EXPECT_EQ(all_file_size, GetDataSizeOnDisk());
+ EXPECT_EQ(all_file_size + total_path_cost - src_path_cost,
+ GetUsage());
+}
+
+TEST_F(FileSystemOperationImplTest,
+ TestCopySuccessSrcDirRecursiveWithQuota) {
+ FileSystemURL src(CreateDirectory("src"));
+ FileSystemURL dest1(CreateDirectory("dest1"));
+ FileSystemURL dest2(CreateDirectory("dest2"));
+
+ int64 usage = GetUsage();
+ FileSystemURL child_file1(CreateFile("src/file1"));
+ FileSystemURL child_file2(CreateFile("src/file2"));
+ FileSystemURL child_dir(CreateDirectory("src/dir"));
+ int64 child_path_cost = GetUsage() - usage;
+ usage += child_path_cost;
+
+ FileSystemURL grandchild_file1(CreateFile("src/dir/file1"));
+ FileSystemURL grandchild_file2(CreateFile("src/dir/file2"));
+ int64 total_path_cost = GetUsage();
+ int64 grandchild_path_cost = total_path_cost - usage;
+
+ EXPECT_EQ(0, GetDataSizeOnDisk());
+
+ operation_runner()->Truncate(
+ child_file1, 8000,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ operation_runner()->Truncate(
+ child_file2, 700,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ operation_runner()->Truncate(
+ grandchild_file1, 60,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ operation_runner()->Truncate(
+ grandchild_file2, 5,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ const int64 child_file_size = 8000 + 700;
+ const int64 grandchild_file_size = 60 + 5;
+ const int64 all_file_size = child_file_size + grandchild_file_size;
+ int64 expected_usage = all_file_size + total_path_cost;
+
+ usage = GetUsage();
+ EXPECT_EQ(all_file_size, GetDataSizeOnDisk());
+ EXPECT_EQ(expected_usage, usage);
+
+ // Copy src to dest1.
+ operation_runner()->Copy(
+ src, dest1,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ expected_usage += all_file_size + child_path_cost + grandchild_path_cost;
+ EXPECT_TRUE(DirectoryExists("src/dir"));
+ EXPECT_TRUE(FileExists("src/dir/file2"));
+ EXPECT_TRUE(DirectoryExists("dest1/dir"));
+ EXPECT_TRUE(FileExists("dest1/dir/file2"));
+
+ EXPECT_EQ(2 * all_file_size, GetDataSizeOnDisk());
+ EXPECT_EQ(expected_usage, GetUsage());
+
+ // Copy src/dir to dest2.
+ operation_runner()->Copy(
+ child_dir, dest2,
+ base::Bind(&AssertFileErrorEq, FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ expected_usage += grandchild_file_size + grandchild_path_cost;
+ usage = GetUsage();
+ EXPECT_EQ(2 * child_file_size + 3 * grandchild_file_size,
+ GetDataSizeOnDisk());
+ EXPECT_EQ(expected_usage, usage);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_operation_impl_write_unittest.cc b/chromium/webkit/browser/fileapi/file_system_operation_impl_write_unittest.cc
new file mode 100644
index 00000000000..d8ecc280fc8
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_operation_impl_write_unittest.cc
@@ -0,0 +1,341 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "webkit/browser/blob/blob_storage_controller.h"
+#include "webkit/browser/blob/blob_url_request_job.h"
+#include "webkit/browser/blob/mock_blob_url_request_context.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/local_file_util.h"
+#include "webkit/browser/fileapi/mock_file_change_observer.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/test_file_system_backend.h"
+#include "webkit/browser/quota/mock_quota_manager.h"
+#include "webkit/common/blob/blob_data.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using webkit_blob::MockBlobURLRequestContext;
+using webkit_blob::ScopedTextBlob;
+
+namespace fileapi {
+
+namespace {
+
+const GURL kOrigin("http://example.com");
+const FileSystemType kFileSystemType = kFileSystemTypeTest;
+
+void AssertStatusEq(base::PlatformFileError expected,
+ base::PlatformFileError actual) {
+ ASSERT_EQ(expected, actual);
+}
+
+} // namespace
+
+class FileSystemOperationImplWriteTest
+ : public testing::Test {
+ public:
+ FileSystemOperationImplWriteTest()
+ : loop_(base::MessageLoop::TYPE_IO),
+ status_(base::PLATFORM_FILE_OK),
+ cancel_status_(base::PLATFORM_FILE_ERROR_FAILED),
+ bytes_written_(0),
+ complete_(false),
+ weak_factory_(this) {
+ change_observers_ = MockFileChangeObserver::CreateList(&change_observer_);
+ }
+
+ virtual void SetUp() {
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+
+ quota_manager_ =
+ new quota::MockQuotaManager(false /* is_incognito */,
+ dir_.path(),
+ base::MessageLoopProxy::current().get(),
+ base::MessageLoopProxy::current().get(),
+ NULL /* special storage policy */);
+ virtual_path_ = base::FilePath(FILE_PATH_LITERAL("temporary file"));
+
+ file_system_context_ = CreateFileSystemContextForTesting(
+ quota_manager_->proxy(), dir_.path());
+ url_request_context_.reset(
+ new MockBlobURLRequestContext(file_system_context_.get()));
+
+ file_system_context_->operation_runner()->CreateFile(
+ URLForPath(virtual_path_), true /* exclusive */,
+ base::Bind(&AssertStatusEq, base::PLATFORM_FILE_OK));
+
+ static_cast<TestFileSystemBackend*>(
+ file_system_context_->GetFileSystemBackend(kFileSystemType))
+ ->AddFileChangeObserver(change_observer());
+ }
+
+ virtual void TearDown() {
+ quota_manager_ = NULL;
+ file_system_context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ base::PlatformFileError status() const { return status_; }
+ base::PlatformFileError cancel_status() const { return cancel_status_; }
+ void add_bytes_written(int64 bytes, bool complete) {
+ bytes_written_ += bytes;
+ EXPECT_FALSE(complete_);
+ complete_ = complete;
+ }
+ int64 bytes_written() const { return bytes_written_; }
+ bool complete() const { return complete_; }
+
+ protected:
+ const ChangeObserverList& change_observers() const {
+ return change_observers_;
+ }
+
+ MockFileChangeObserver* change_observer() {
+ return &change_observer_;
+ }
+
+ FileSystemURL URLForPath(const base::FilePath& path) const {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ kOrigin, kFileSystemType, path);
+ }
+
+ // Callback function for recording test results.
+ FileSystemOperation::WriteCallback RecordWriteCallback() {
+ return base::Bind(&FileSystemOperationImplWriteTest::DidWrite,
+ weak_factory_.GetWeakPtr());
+ }
+
+ FileSystemOperation::StatusCallback RecordCancelCallback() {
+ return base::Bind(&FileSystemOperationImplWriteTest::DidCancel,
+ weak_factory_.GetWeakPtr());
+ }
+
+ void DidWrite(base::PlatformFileError status, int64 bytes, bool complete) {
+ if (status == base::PLATFORM_FILE_OK) {
+ add_bytes_written(bytes, complete);
+ if (complete)
+ base::MessageLoop::current()->Quit();
+ } else {
+ EXPECT_FALSE(complete_);
+ EXPECT_EQ(status_, base::PLATFORM_FILE_OK);
+ complete_ = true;
+ status_ = status;
+ if (base::MessageLoop::current()->is_running())
+ base::MessageLoop::current()->Quit();
+ }
+ }
+
+ void DidCancel(base::PlatformFileError status) {
+ cancel_status_ = status;
+ }
+
+ const MockBlobURLRequestContext& url_request_context() const {
+ return *url_request_context_;
+ }
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+ scoped_refptr<quota::MockQuotaManager> quota_manager_;
+
+ base::MessageLoop loop_;
+
+ base::ScopedTempDir dir_;
+ base::FilePath virtual_path_;
+
+ // For post-operation status.
+ base::PlatformFileError status_;
+ base::PlatformFileError cancel_status_;
+ int64 bytes_written_;
+ bool complete_;
+
+ scoped_ptr<MockBlobURLRequestContext> url_request_context_;
+
+ MockFileChangeObserver change_observer_;
+ ChangeObserverList change_observers_;
+
+ base::WeakPtrFactory<FileSystemOperationImplWriteTest> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImplWriteTest);
+};
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteSuccess) {
+ const GURL blob_url("blob:success");
+ ScopedTextBlob blob(url_request_context(), blob_url, "Hello, world!\n");
+
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_), blob_url,
+ 0, RecordWriteCallback());
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(14, bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteZero) {
+ GURL blob_url("blob:zero");
+ scoped_refptr<webkit_blob::BlobData> blob_data(new webkit_blob::BlobData());
+
+ url_request_context().blob_storage_controller()
+ ->AddFinishedBlob(blob_url, blob_data.get());
+
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_),
+ blob_url, 0, RecordWriteCallback());
+ base::MessageLoop::current()->Run();
+
+ url_request_context().blob_storage_controller()->RemoveBlob(blob_url);
+
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidBlobUrl) {
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_),
+ GURL("blob:invalid"), 0, RecordWriteCallback());
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_FAILED, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteInvalidFile) {
+ GURL blob_url("blob:writeinvalidfile");
+ ScopedTextBlob blob(url_request_context(), blob_url,
+ "It\'ll not be written.");
+
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(),
+ URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))),
+ blob_url, 0, RecordWriteCallback());
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteDir) {
+ base::FilePath virtual_dir_path(FILE_PATH_LITERAL("d"));
+ file_system_context_->operation_runner()->CreateDirectory(
+ URLForPath(virtual_dir_path),
+ true /* exclusive */, false /* recursive */,
+ base::Bind(&AssertStatusEq, base::PLATFORM_FILE_OK));
+
+ GURL blob_url("blob:writedir");
+ ScopedTextBlob blob(url_request_context(), blob_url,
+ "It\'ll not be written, too.");
+
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_dir_path),
+ blob_url, 0, RecordWriteCallback());
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, bytes_written());
+ // TODO(kinuko): This error code is platform- or fileutil- dependent
+ // right now. Make it return PLATFORM_FILE_ERROR_NOT_A_FILE in every case.
+ EXPECT_TRUE(status() == base::PLATFORM_FILE_ERROR_NOT_A_FILE ||
+ status() == base::PLATFORM_FILE_ERROR_ACCESS_DENIED ||
+ status() == base::PLATFORM_FILE_ERROR_FAILED);
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestWriteFailureByQuota) {
+ GURL blob_url("blob:success");
+ ScopedTextBlob blob(url_request_context(), blob_url, "Hello, world!\n");
+
+ quota_manager_->SetQuota(
+ kOrigin, FileSystemTypeToQuotaStorageType(kFileSystemType), 10);
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_), blob_url,
+ 0, RecordWriteCallback());
+ base::MessageLoop::current()->Run();
+
+ EXPECT_EQ(10, bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelSuccessfulWrite) {
+ GURL blob_url("blob:success");
+ ScopedTextBlob blob(url_request_context(), blob_url, "Hello, world!\n");
+
+ FileSystemOperationRunner::OperationID id =
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(), URLForPath(virtual_path_),
+ blob_url, 0, RecordWriteCallback());
+ file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback());
+ // We use RunAllPendings() instead of Run() here, because we won't dispatch
+ // callbacks after Cancel() is issued (so no chance to Quit) nor do we need
+ // to run another write cycle.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Issued Cancel() before receiving any response from Write(),
+ // so nothing should have happen.
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_ABORT, status());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, cancel_status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count());
+}
+
+TEST_F(FileSystemOperationImplWriteTest, TestImmediateCancelFailingWrite) {
+ GURL blob_url("blob:writeinvalidfile");
+ ScopedTextBlob blob(url_request_context(), blob_url,
+ "It\'ll not be written.");
+
+ FileSystemOperationRunner::OperationID id =
+ file_system_context_->operation_runner()->Write(
+ &url_request_context(),
+ URLForPath(base::FilePath(FILE_PATH_LITERAL("nonexist"))),
+ blob_url, 0, RecordWriteCallback());
+ file_system_context_->operation_runner()->Cancel(id, RecordCancelCallback());
+ // We use RunAllPendings() instead of Run() here, because we won't dispatch
+ // callbacks after Cancel() is issued (so no chance to Quit) nor do we need
+ // to run another write cycle.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Issued Cancel() before receiving any response from Write(),
+ // so nothing should have happen.
+ EXPECT_EQ(0, bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_ABORT, status());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, cancel_status());
+ EXPECT_TRUE(complete());
+
+ EXPECT_EQ(0, change_observer()->get_and_reset_modify_file_count());
+}
+
+// TODO(ericu,dmikurube,kinuko): Add more tests for cancel cases.
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_operation_runner.cc b/chromium/webkit/browser/fileapi/file_system_operation_runner.cc
new file mode 100644
index 00000000000..351536b6226
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_operation_runner.cc
@@ -0,0 +1,563 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+
+#include "base/bind.h"
+#include "net/url_request/url_request_context.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_stream_writer.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_impl.h"
+#include "webkit/browser/fileapi/file_writer_delegate.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+
+namespace fileapi {
+
+typedef FileSystemOperationRunner::OperationID OperationID;
+
+const OperationID FileSystemOperationRunner::kErrorOperationID = -1;
+
+FileSystemOperationRunner::~FileSystemOperationRunner() {
+}
+
+void FileSystemOperationRunner::Shutdown() {
+ operations_.Clear();
+}
+
+OperationID FileSystemOperationRunner::CreateFile(
+ const FileSystemURL& url,
+ bool exclusive,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForWrite(id, url);
+ operation->CreateFile(
+ url, exclusive,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::CreateDirectory(
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForWrite(id, url);
+ operation->CreateDirectory(
+ url, exclusive, recursive,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::Copy(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(dest_url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForWrite(id, dest_url);
+ PrepareForRead(id, src_url);
+ operation->Copy(
+ src_url, dest_url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::Move(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(dest_url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForWrite(id, dest_url);
+ PrepareForWrite(id, src_url);
+ operation->Move(
+ src_url, dest_url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::DirectoryExists(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForRead(id, url);
+ operation->DirectoryExists(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::FileExists(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForRead(id, url);
+ operation->FileExists(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::GetMetadata(
+ const FileSystemURL& url,
+ const GetMetadataCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error, base::PlatformFileInfo());
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForRead(id, url);
+ operation->GetMetadata(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidGetMetadata, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::ReadDirectory(
+ const FileSystemURL& url,
+ const ReadDirectoryCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error, std::vector<DirectoryEntry>(), false);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForRead(id, url);
+ operation->ReadDirectory(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidReadDirectory, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::Remove(
+ const FileSystemURL& url, bool recursive,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForWrite(id, url);
+ operation->Remove(
+ url, recursive,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::Write(
+ const net::URLRequestContext* url_request_context,
+ const FileSystemURL& url,
+ const GURL& blob_url,
+ int64 offset,
+ const WriteCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error, 0, true);
+ return kErrorOperationID;
+ }
+
+ scoped_ptr<FileStreamWriter> writer(
+ file_system_context_->CreateFileStreamWriter(url, offset));
+ if (!writer) {
+ // Write is not supported.
+ callback.Run(base::PLATFORM_FILE_ERROR_SECURITY, 0, true);
+ return kErrorOperationID;
+ }
+
+ DCHECK(blob_url.is_valid());
+ scoped_ptr<FileWriterDelegate> writer_delegate(
+ new FileWriterDelegate(writer.Pass()));
+ scoped_ptr<net::URLRequest> blob_request(url_request_context->CreateRequest(
+ blob_url, writer_delegate.get()));
+
+ OperationID id = operations_.Add(operation);
+ PrepareForWrite(id, url);
+ operation->Write(
+ url, writer_delegate.Pass(), blob_request.Pass(),
+ base::Bind(&FileSystemOperationRunner::DidWrite, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::Truncate(
+ const FileSystemURL& url, int64 length,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForWrite(id, url);
+ operation->Truncate(
+ url, length,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+void FileSystemOperationRunner::Cancel(
+ OperationID id,
+ const StatusCallback& callback) {
+ FileSystemOperation* operation = operations_.Lookup(id);
+ if (!operation) {
+ // The operation is already finished; report that we failed to stop it.
+ callback.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
+ return;
+ }
+ operation->Cancel(callback);
+}
+
+OperationID FileSystemOperationRunner::TouchFile(
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForWrite(id, url);
+ operation->TouchFile(
+ url, last_access_time, last_modified_time,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::OpenFile(
+ const FileSystemURL& url,
+ int file_flags,
+ base::ProcessHandle peer_handle,
+ const OpenFileCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error, base::kInvalidPlatformFileValue,
+ base::Closure(), base::ProcessHandle());
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ if (file_flags &
+ (base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_OPEN_ALWAYS |
+ base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_OPEN_TRUNCATED |
+ base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_WRITE |
+ base::PLATFORM_FILE_DELETE_ON_CLOSE |
+ base::PLATFORM_FILE_WRITE_ATTRIBUTES)) {
+ PrepareForWrite(id, url);
+ } else {
+ PrepareForRead(id, url);
+ }
+ operation->OpenFile(
+ url, file_flags, peer_handle,
+ base::Bind(&FileSystemOperationRunner::DidOpenFile, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::CreateSnapshotFile(
+ const FileSystemURL& url,
+ const SnapshotFileCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ if (!operation) {
+ callback.Run(error, base::PlatformFileInfo(), base::FilePath(), NULL);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ PrepareForRead(id, url);
+ operation->CreateSnapshotFile(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidCreateSnapshot, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::CopyInForeignFile(
+ const base::FilePath& src_local_disk_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation = CreateFileSystemOperationImpl(
+ dest_url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ operation->AsFileSystemOperationImpl()->CopyInForeignFile(
+ src_local_disk_path, dest_url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::RemoveFile(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation = CreateFileSystemOperationImpl(url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ operation->AsFileSystemOperationImpl()->RemoveFile(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::RemoveDirectory(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation = CreateFileSystemOperationImpl(url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ operation->AsFileSystemOperationImpl()->RemoveDirectory(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::CopyFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation = CreateFileSystemOperationImpl(
+ src_url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ operation->AsFileSystemOperationImpl()->CopyFileLocal(
+ src_url, dest_url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+OperationID FileSystemOperationRunner::MoveFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation = CreateFileSystemOperationImpl(
+ src_url, &error);
+ if (!operation) {
+ callback.Run(error);
+ return kErrorOperationID;
+ }
+ OperationID id = operations_.Add(operation);
+ operation->AsFileSystemOperationImpl()->MoveFileLocal(
+ src_url, dest_url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ id, callback));
+ return id;
+}
+
+base::PlatformFileError FileSystemOperationRunner::SyncGetPlatformPath(
+ const FileSystemURL& url,
+ base::FilePath* platform_path) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ FileSystemOperation* operation = CreateFileSystemOperationImpl(url, &error);
+ if (!operation)
+ return error;
+
+ return operation->AsFileSystemOperationImpl()->SyncGetPlatformPath(
+ url, platform_path);
+}
+
+FileSystemOperationRunner::FileSystemOperationRunner(
+ FileSystemContext* file_system_context)
+ : file_system_context_(file_system_context) {}
+
+void FileSystemOperationRunner::DidFinish(
+ OperationID id,
+ const StatusCallback& callback,
+ base::PlatformFileError rv) {
+ callback.Run(rv);
+ FinishOperation(id);
+}
+
+void FileSystemOperationRunner::DidGetMetadata(
+ OperationID id,
+ const GetMetadataCallback& callback,
+ base::PlatformFileError rv,
+ const base::PlatformFileInfo& file_info) {
+ callback.Run(rv, file_info);
+ FinishOperation(id);
+}
+
+void FileSystemOperationRunner::DidReadDirectory(
+ OperationID id,
+ const ReadDirectoryCallback& callback,
+ base::PlatformFileError rv,
+ const std::vector<DirectoryEntry>& entries,
+ bool has_more) {
+ callback.Run(rv, entries, has_more);
+ if (rv != base::PLATFORM_FILE_OK || !has_more)
+ FinishOperation(id);
+}
+
+void FileSystemOperationRunner::DidWrite(
+ OperationID id,
+ const WriteCallback& callback,
+ base::PlatformFileError rv,
+ int64 bytes,
+ bool complete) {
+ callback.Run(rv, bytes, complete);
+ if (rv != base::PLATFORM_FILE_OK || complete)
+ FinishOperation(id);
+}
+
+void FileSystemOperationRunner::DidOpenFile(
+ OperationID id,
+ const OpenFileCallback& callback,
+ base::PlatformFileError rv,
+ base::PlatformFile file,
+ const base::Closure& on_close_callback,
+ base::ProcessHandle peer_handle) {
+ callback.Run(rv, file, on_close_callback, peer_handle);
+ FinishOperation(id);
+}
+
+void FileSystemOperationRunner::DidCreateSnapshot(
+ OperationID id,
+ const SnapshotFileCallback& callback,
+ base::PlatformFileError rv,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) {
+ callback.Run(rv, file_info, platform_path, file_ref);
+ FinishOperation(id);
+}
+
+FileSystemOperation*
+FileSystemOperationRunner::CreateFileSystemOperationImpl(
+ const FileSystemURL& url, base::PlatformFileError* error) {
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, error);
+ if (!operation)
+ return NULL;
+ if (!operation->AsFileSystemOperationImpl()) {
+ *error = base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
+ delete operation;
+ return NULL;
+ }
+ return operation;
+}
+
+void FileSystemOperationRunner::PrepareForWrite(OperationID id,
+ const FileSystemURL& url) {
+ if (file_system_context_->GetUpdateObservers(url.type())) {
+ file_system_context_->GetUpdateObservers(url.type())->Notify(
+ &FileUpdateObserver::OnStartUpdate, MakeTuple(url));
+ }
+ write_target_urls_[id].insert(url);
+}
+
+void FileSystemOperationRunner::PrepareForRead(OperationID id,
+ const FileSystemURL& url) {
+ if (file_system_context_->GetAccessObservers(url.type())) {
+ file_system_context_->GetAccessObservers(url.type())->Notify(
+ &FileAccessObserver::OnAccess, MakeTuple(url));
+ }
+}
+
+void FileSystemOperationRunner::FinishOperation(OperationID id) {
+ OperationToURLSet::iterator found = write_target_urls_.find(id);
+ if (found != write_target_urls_.end()) {
+ const FileSystemURLSet& urls = found->second;
+ for (FileSystemURLSet::const_iterator iter = urls.begin();
+ iter != urls.end(); ++iter) {
+ if (file_system_context_->GetUpdateObservers(iter->type())) {
+ file_system_context_->GetUpdateObservers(iter->type())->Notify(
+ &FileUpdateObserver::OnEndUpdate, MakeTuple(*iter));
+ }
+ }
+ write_target_urls_.erase(found);
+ }
+ DCHECK(operations_.Lookup(id));
+ operations_.Remove(id);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_operation_runner.h b/chromium/webkit/browser/fileapi/file_system_operation_runner.h
new file mode 100644
index 00000000000..67f30b89b17
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_operation_runner.h
@@ -0,0 +1,297 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/id_map.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "webkit/browser/fileapi/file_system_operation.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace net {
+class URLRequestContext;
+}
+
+namespace fileapi {
+
+class FileSystemURL;
+class FileSystemContext;
+
+// A central interface for running FileSystem API operations.
+// All operation methods take callback and returns OperationID, which is
+// an integer value which can be used for cancelling an operation.
+// All operation methods return kErrorOperationID if running (posting) an
+// operation fails, in addition to dispatching the callback with an error
+// code (therefore in most cases the caller does not need to check the
+// returned operation ID).
+//
+// Some operations (e.g. CopyInForeignFile, RemoveFile, RemoveDirectory,
+// CopyFileLocal, MoveFileLocal and SyncGetPlatformPath) are only supported
+// by filesystems which implement FileSystemOperationImpl.
+// If they are called on other filesystems
+// base::PLATFORM_FILE_ERROR_INVALID_OPERATION is returned via callback.
+class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemOperationRunner
+ : public base::SupportsWeakPtr<FileSystemOperationRunner> {
+ public:
+ typedef FileSystemOperation::GetMetadataCallback GetMetadataCallback;
+ typedef FileSystemOperation::ReadDirectoryCallback ReadDirectoryCallback;
+ typedef FileSystemOperation::SnapshotFileCallback SnapshotFileCallback;
+ typedef FileSystemOperation::StatusCallback StatusCallback;
+ typedef FileSystemOperation::WriteCallback WriteCallback;
+ typedef FileSystemOperation::OpenFileCallback OpenFileCallback;
+
+ typedef int OperationID;
+
+ static const OperationID kErrorOperationID;
+
+ virtual ~FileSystemOperationRunner();
+
+ // Cancels all inflight operations.
+ void Shutdown();
+
+ // Creates a file at |url|. If |exclusive| is true, an error is raised
+ // in case a file is already present at the URL.
+ OperationID CreateFile(const FileSystemURL& url,
+ bool exclusive,
+ const StatusCallback& callback);
+
+ OperationID CreateDirectory(const FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback);
+
+ // Copies a file or directory from |src_url| to |dest_url|. If
+ // |src_url| is a directory, the contents of |src_url| are copied to
+ // |dest_url| recursively. A new file or directory is created at
+ // |dest_url| as needed.
+ OperationID Copy(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback);
+
+ // Moves a file or directory from |src_url| to |dest_url|. A new file
+ // or directory is created at |dest_url| as needed.
+ OperationID Move(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback);
+
+ // Checks if a directory is present at |url|.
+ OperationID DirectoryExists(const FileSystemURL& url,
+ const StatusCallback& callback);
+
+ // Checks if a file is present at |url|.
+ OperationID FileExists(const FileSystemURL& url,
+ const StatusCallback& callback);
+
+ // Gets the metadata of a file or directory at |url|.
+ OperationID GetMetadata(const FileSystemURL& url,
+ const GetMetadataCallback& callback);
+
+ // Reads contents of a directory at |url|.
+ OperationID ReadDirectory(const FileSystemURL& url,
+ const ReadDirectoryCallback& callback);
+
+ // Removes a file or directory at |url|. If |recursive| is true, remove
+ // all files and directories under the directory at |url| recursively.
+ OperationID Remove(const FileSystemURL& url, bool recursive,
+ const StatusCallback& callback);
+
+ // Writes contents of |blob_url| to |url| at |offset|.
+ // |url_request_context| is used to read contents in |blob_url|.
+ OperationID Write(const net::URLRequestContext* url_request_context,
+ const FileSystemURL& url,
+ const GURL& blob_url,
+ int64 offset,
+ const WriteCallback& callback);
+
+ // Truncates a file at |url| to |length|. If |length| is larger than
+ // the original file size, the file will be extended, and the extended
+ // part is filled with null bytes.
+ OperationID Truncate(const FileSystemURL& url, int64 length,
+ const StatusCallback& callback);
+
+ // Tries to cancel the operation |id| [we support cancelling write or
+ // truncate only]. Reports failure for the current operation, then reports
+ // success for the cancel operation itself via the |callback|.
+ void Cancel(OperationID id, const StatusCallback& callback);
+
+ // Modifies timestamps of a file or directory at |url| with
+ // |last_access_time| and |last_modified_time|. The function DOES NOT
+ // create a file unlike 'touch' command on Linux.
+ //
+ // This function is used only by Pepper as of writing.
+ OperationID TouchFile(const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback);
+
+ // Opens a file at |url| with |file_flags|, where flags are OR'ed
+ // values of base::PlatformFileFlags.
+ //
+ // |peer_handle| is the process handle of a pepper plugin process, which
+ // is necessary for underlying IPC calls with Pepper plugins.
+ //
+ // This function is used only by Pepper as of writing.
+ OperationID OpenFile(const FileSystemURL& url,
+ int file_flags,
+ base::ProcessHandle peer_handle,
+ const OpenFileCallback& callback);
+
+ // Creates a local snapshot file for a given |url| and returns the
+ // metadata and platform url of the snapshot file via |callback|.
+ // In local filesystem cases the implementation may simply return
+ // the metadata of the file itself (as well as GetMetadata does),
+ // while in remote filesystem case the backend may want to download the file
+ // into a temporary snapshot file and return the metadata of the
+ // temporary file. Or if the implementaiton already has the local cache
+ // data for |url| it can simply return the url to the cache.
+ OperationID CreateSnapshotFile(const FileSystemURL& url,
+ const SnapshotFileCallback& callback);
+
+ // Copies in a single file from a different filesystem.
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_file_path|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ OperationID CopyInForeignFile(const base::FilePath& src_local_disk_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback);
+
+ // Removes a single file.
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |url| is not a file.
+ //
+ OperationID RemoveFile(const FileSystemURL& url,
+ const StatusCallback& callback);
+
+ // Removes a single empty directory.
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory.
+ // - PLATFORM_FILE_ERROR_NOT_EMPTY if |url| is not empty.
+ //
+ OperationID RemoveDirectory(const FileSystemURL& url,
+ const StatusCallback& callback);
+
+ // Copies a file from |src_url| to |dest_url|.
+ // This must be called for files that belong to the same filesystem
+ // (i.e. type() and origin() of the |src_url| and |dest_url| must match).
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ OperationID CopyFileLocal(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback);
+
+ // Moves a local file from |src_url| to |dest_url|.
+ // This must be called for files that belong to the same filesystem
+ // (i.e. type() and origin() of the |src_url| and |dest_url| must match).
+ //
+ // This returns:
+ // - PLATFORM_FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - PLATFORM_FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - PLATFORM_FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - PLATFORM_FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ OperationID MoveFileLocal(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback);
+
+ // This is called only by pepper plugin as of writing to synchronously get
+ // the underlying platform path to upload a file in the sandboxed filesystem
+ // (e.g. TEMPORARY or PERSISTENT).
+ base::PlatformFileError SyncGetPlatformPath(const FileSystemURL& url,
+ base::FilePath* platform_path);
+
+ private:
+ friend class FileSystemContext;
+ explicit FileSystemOperationRunner(FileSystemContext* file_system_context);
+
+ void DidFinish(OperationID id,
+ const StatusCallback& callback,
+ base::PlatformFileError rv);
+ void DidGetMetadata(OperationID id,
+ const GetMetadataCallback& callback,
+ base::PlatformFileError rv,
+ const base::PlatformFileInfo& file_info);
+ void DidReadDirectory(OperationID id,
+ const ReadDirectoryCallback& callback,
+ base::PlatformFileError rv,
+ const std::vector<DirectoryEntry>& entries,
+ bool has_more);
+ void DidWrite(OperationID id,
+ const WriteCallback& callback,
+ base::PlatformFileError rv,
+ int64 bytes,
+ bool complete);
+ void DidOpenFile(
+ OperationID id,
+ const OpenFileCallback& callback,
+ base::PlatformFileError rv,
+ base::PlatformFile file,
+ const base::Closure& on_close_callback,
+ base::ProcessHandle peer_handle);
+ void DidCreateSnapshot(
+ OperationID id,
+ const SnapshotFileCallback& callback,
+ base::PlatformFileError rv,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref);
+
+ // A helper method for creating FileSystemOperationImpl for operations
+ // that are supported only in FileSystemOperationImpl.
+ // Note that this returns FileSystemOperation, so the caller needs to
+ // call AsFileSystemOperationImpl() (which is guaranteed to be non-null
+ // if this method returns without error).
+ FileSystemOperation* CreateFileSystemOperationImpl(
+ const FileSystemURL& url,
+ base::PlatformFileError* error);
+
+ void PrepareForWrite(OperationID id, const FileSystemURL& url);
+ void PrepareForRead(OperationID id, const FileSystemURL& url);
+
+ // This must be called at the end of any async operations.
+ void FinishOperation(OperationID id);
+
+ // Not owned; file_system_context owns this.
+ FileSystemContext* file_system_context_;
+
+ // IDMap<FileSystemOperation, IDMapOwnPointer> operations_;
+ IDMap<FileSystemOperation, IDMapOwnPointer> operations_;
+
+ // We keep track of the file to be modified by each operation so that
+ // we can notify observers when we're done.
+ typedef std::map<OperationID, FileSystemURLSet> OperationToURLSet;
+ OperationToURLSet write_target_urls_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationRunner);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_options.cc b/chromium/webkit/browser/fileapi/file_system_options.cc
new file mode 100644
index 00000000000..4100b5d1e0e
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_options.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_options.h"
+
+namespace fileapi {
+
+FileSystemOptions::FileSystemOptions(
+ ProfileMode profile_mode,
+ const std::vector<std::string>& additional_allowed_schemes)
+ : profile_mode_(profile_mode),
+ additional_allowed_schemes_(additional_allowed_schemes) {
+}
+
+FileSystemOptions::~FileSystemOptions() {
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_options.h b/chromium/webkit/browser/fileapi/file_system_options.h
new file mode 100644
index 00000000000..b6e78efce61
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_options.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_
+
+#include <string>
+#include <vector>
+
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace fileapi {
+
+// Provides runtime options that may change FileSystem API behavior.
+// This object is copyable.
+class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemOptions {
+ public:
+ enum ProfileMode {
+ PROFILE_MODE_NORMAL = 0,
+ PROFILE_MODE_INCOGNITO
+ };
+
+ // |profile_mode| specifies if the profile (for this filesystem)
+ // is running in incognito mode (PROFILE_MODE_INCOGNITO) or no
+ // (PROFILE_MODE_NORMAL).
+ // |additional_allowed_schemes| specifies schemes that are allowed
+ // to access FileSystem API in addition to "http" and "https".
+ FileSystemOptions(
+ ProfileMode profile_mode,
+ const std::vector<std::string>& additional_allowed_schemes);
+
+ ~FileSystemOptions();
+
+ // Returns true if it is running in the incognito mode.
+ bool is_incognito() const { return profile_mode_ == PROFILE_MODE_INCOGNITO; }
+
+ // Returns the schemes that must be allowed to access FileSystem API
+ // in addition to standard "http" and "https".
+ // (e.g. If the --allow-file-access-from-files option is given in chrome
+ // "file" scheme will also need to be allowed).
+ const std::vector<std::string>& additional_allowed_schemes() const {
+ return additional_allowed_schemes_;
+ }
+
+ private:
+ const ProfileMode profile_mode_;
+ const std::vector<std::string> additional_allowed_schemes_;
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_quota_client.cc b/chromium/webkit/browser/fileapi/file_system_quota_client.cc
new file mode 100644
index 00000000000..343a6e6ee5c
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_quota_client.cc
@@ -0,0 +1,204 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_quota_client.h"
+
+#include <algorithm>
+#include <set>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+#include "net/base/net_util.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_quota_util.h"
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using quota::StorageType;
+
+namespace fileapi {
+
+namespace {
+
+void GetOriginsForTypeOnFileThread(
+ FileSystemContext* context,
+ StorageType storage_type,
+ std::set<GURL>* origins_ptr) {
+ FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type);
+ DCHECK(type != kFileSystemTypeUnknown);
+
+ FileSystemQuotaUtil* quota_util = context->GetQuotaUtil(type);
+ if (!quota_util)
+ return;
+ quota_util->GetOriginsForTypeOnFileThread(type, origins_ptr);
+}
+
+void GetOriginsForHostOnFileThread(
+ FileSystemContext* context,
+ StorageType storage_type,
+ const std::string& host,
+ std::set<GURL>* origins_ptr) {
+ FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type);
+ DCHECK(type != kFileSystemTypeUnknown);
+
+ FileSystemQuotaUtil* quota_util = context->GetQuotaUtil(type);
+ if (!quota_util)
+ return;
+ quota_util->GetOriginsForHostOnFileThread(type, host, origins_ptr);
+}
+
+void DidGetOrigins(
+ const quota::QuotaClient::GetOriginsCallback& callback,
+ std::set<GURL>* origins_ptr) {
+ callback.Run(*origins_ptr);
+}
+
+quota::QuotaStatusCode DeleteOriginOnFileThread(
+ FileSystemContext* context,
+ const GURL& origin,
+ FileSystemType type) {
+ FileSystemBackend* provider = context->GetFileSystemBackend(type);
+ if (!provider || !provider->GetQuotaUtil())
+ return quota::kQuotaErrorNotSupported;
+ base::PlatformFileError result =
+ provider->GetQuotaUtil()->DeleteOriginDataOnFileThread(
+ context, context->quota_manager_proxy(), origin, type);
+ if (result == base::PLATFORM_FILE_OK)
+ return quota::kQuotaStatusOk;
+ return quota::kQuotaErrorInvalidModification;
+}
+
+} // namespace
+
+FileSystemQuotaClient::FileSystemQuotaClient(
+ FileSystemContext* file_system_context,
+ bool is_incognito)
+ : file_system_context_(file_system_context),
+ is_incognito_(is_incognito) {
+}
+
+FileSystemQuotaClient::~FileSystemQuotaClient() {}
+
+quota::QuotaClient::ID FileSystemQuotaClient::id() const {
+ return quota::QuotaClient::kFileSystem;
+}
+
+void FileSystemQuotaClient::OnQuotaManagerDestroyed() {
+ delete this;
+}
+
+void FileSystemQuotaClient::GetOriginUsage(
+ const GURL& origin_url,
+ StorageType storage_type,
+ const GetUsageCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (is_incognito_) {
+ // We don't support FileSystem in incognito mode yet.
+ callback.Run(0);
+ return;
+ }
+
+ FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type);
+ DCHECK(type != kFileSystemTypeUnknown);
+
+ FileSystemQuotaUtil* quota_util = file_system_context_->GetQuotaUtil(type);
+ if (!quota_util) {
+ callback.Run(0);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ file_task_runner(),
+ FROM_HERE,
+ // It is safe to pass Unretained(quota_util) since context owns it.
+ base::Bind(&FileSystemQuotaUtil::GetOriginUsageOnFileThread,
+ base::Unretained(quota_util),
+ file_system_context_,
+ origin_url,
+ type),
+ callback);
+}
+
+void FileSystemQuotaClient::GetOriginsForType(
+ StorageType storage_type,
+ const GetOriginsCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (is_incognito_) {
+ // We don't support FileSystem in incognito mode yet.
+ std::set<GURL> origins;
+ callback.Run(origins);
+ return;
+ }
+
+ std::set<GURL>* origins_ptr = new std::set<GURL>();
+ file_task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&GetOriginsForTypeOnFileThread,
+ file_system_context_,
+ storage_type,
+ base::Unretained(origins_ptr)),
+ base::Bind(&DidGetOrigins,
+ callback,
+ base::Owned(origins_ptr)));
+}
+
+void FileSystemQuotaClient::GetOriginsForHost(
+ StorageType storage_type,
+ const std::string& host,
+ const GetOriginsCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ if (is_incognito_) {
+ // We don't support FileSystem in incognito mode yet.
+ std::set<GURL> origins;
+ callback.Run(origins);
+ return;
+ }
+
+ std::set<GURL>* origins_ptr = new std::set<GURL>();
+ file_task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&GetOriginsForHostOnFileThread,
+ file_system_context_,
+ storage_type,
+ host,
+ base::Unretained(origins_ptr)),
+ base::Bind(&DidGetOrigins,
+ callback,
+ base::Owned(origins_ptr)));
+}
+
+void FileSystemQuotaClient::DeleteOriginData(
+ const GURL& origin,
+ StorageType type,
+ const DeletionCallback& callback) {
+ FileSystemType fs_type = QuotaStorageTypeToFileSystemType(type);
+ DCHECK(fs_type != kFileSystemTypeUnknown);
+
+ base::PostTaskAndReplyWithResult(
+ file_task_runner(),
+ FROM_HERE,
+ base::Bind(&DeleteOriginOnFileThread,
+ file_system_context_,
+ origin,
+ fs_type),
+ callback);
+}
+
+base::SequencedTaskRunner* FileSystemQuotaClient::file_task_runner() const {
+ return file_system_context_->default_file_task_runner();
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_quota_client.h b/chromium/webkit/browser/fileapi/file_system_quota_client.h
new file mode 100644
index 00000000000..f2168043f1b
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_quota_client.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "webkit/browser/fileapi/file_system_quota_util.h"
+#include "webkit/browser/quota/quota_client.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace fileapi {
+
+class FileSystemContext;
+
+// An instance of this class is created per-profile. This class
+// is self-destructed and will delete itself when OnQuotaManagerDestroyed
+// is called.
+// All of the public methods of this class are called by the quota manager
+// (except for the constructor/destructor).
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemQuotaClient
+ : public NON_EXPORTED_BASE(quota::QuotaClient) {
+ public:
+ FileSystemQuotaClient(
+ FileSystemContext* file_system_context,
+ bool is_incognito);
+ virtual ~FileSystemQuotaClient();
+
+ // QuotaClient methods.
+ virtual quota::QuotaClient::ID id() const OVERRIDE;
+ virtual void OnQuotaManagerDestroyed() OVERRIDE;
+ virtual void GetOriginUsage(const GURL& origin_url,
+ quota::StorageType type,
+ const GetUsageCallback& callback) OVERRIDE;
+ virtual void GetOriginsForType(
+ quota::StorageType type,
+ const GetOriginsCallback& callback) OVERRIDE;
+ virtual void GetOriginsForHost(
+ quota::StorageType type,
+ const std::string& host,
+ const GetOriginsCallback& callback) OVERRIDE;
+ virtual void DeleteOriginData(
+ const GURL& origin,
+ quota::StorageType type,
+ const DeletionCallback& callback) OVERRIDE;
+
+ private:
+ base::SequencedTaskRunner* file_task_runner() const;
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+
+ bool is_incognito_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemQuotaClient);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_quota_client_unittest.cc b/chromium/webkit/browser/fileapi/file_system_quota_client_unittest.cc
new file mode 100644
index 00000000000..477bb77e95e
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_quota_client_unittest.cc
@@ -0,0 +1,582 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/platform_file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_quota_client.h"
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/obfuscated_file_util.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+#include "webkit/common/quota/quota_types.h"
+
+namespace fileapi {
+namespace {
+
+const char kDummyURL1[] = "http://www.dummy.org";
+const char kDummyURL2[] = "http://www.example.com";
+const char kDummyURL3[] = "http://www.bleh";
+
+// Declared to shorten the variable names.
+const quota::StorageType kTemporary = quota::kStorageTypeTemporary;
+const quota::StorageType kPersistent = quota::kStorageTypePersistent;
+
+} // namespace
+
+class FileSystemQuotaClientTest : public testing::Test {
+ public:
+ FileSystemQuotaClientTest()
+ : weak_factory_(this),
+ additional_callback_count_(0),
+ deletion_status_(quota::kQuotaStatusUnknown) {
+ }
+
+ virtual void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ file_system_context_ = CreateFileSystemContextForTesting(
+ NULL, data_dir_.path());
+ }
+
+ struct TestFile {
+ bool isDirectory;
+ const char* name;
+ int64 size;
+ const char* origin_url;
+ quota::StorageType type;
+ };
+
+ protected:
+ FileSystemQuotaClient* NewQuotaClient(bool is_incognito) {
+ return new FileSystemQuotaClient(file_system_context_.get(), is_incognito);
+ }
+
+ void GetOriginUsageAsync(FileSystemQuotaClient* quota_client,
+ const std::string& origin_url,
+ quota::StorageType type) {
+ quota_client->GetOriginUsage(
+ GURL(origin_url), type,
+ base::Bind(&FileSystemQuotaClientTest::OnGetUsage,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ int64 GetOriginUsage(FileSystemQuotaClient* quota_client,
+ const std::string& origin_url,
+ quota::StorageType type) {
+ GetOriginUsageAsync(quota_client, origin_url, type);
+ base::MessageLoop::current()->RunUntilIdle();
+ return usage_;
+ }
+
+ const std::set<GURL>& GetOriginsForType(FileSystemQuotaClient* quota_client,
+ quota::StorageType type) {
+ origins_.clear();
+ quota_client->GetOriginsForType(
+ type,
+ base::Bind(&FileSystemQuotaClientTest::OnGetOrigins,
+ weak_factory_.GetWeakPtr()));
+ base::MessageLoop::current()->RunUntilIdle();
+ return origins_;
+ }
+
+ const std::set<GURL>& GetOriginsForHost(FileSystemQuotaClient* quota_client,
+ quota::StorageType type,
+ const std::string& host) {
+ origins_.clear();
+ quota_client->GetOriginsForHost(
+ type, host,
+ base::Bind(&FileSystemQuotaClientTest::OnGetOrigins,
+ weak_factory_.GetWeakPtr()));
+ base::MessageLoop::current()->RunUntilIdle();
+ return origins_;
+ }
+
+ void RunAdditionalOriginUsageTask(FileSystemQuotaClient* quota_client,
+ const std::string& origin_url,
+ quota::StorageType type) {
+ quota_client->GetOriginUsage(
+ GURL(origin_url), type,
+ base::Bind(&FileSystemQuotaClientTest::OnGetAdditionalUsage,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ FileSystemOperationContext* CreateFileSystemOperationContext(
+ FileSystemType type) {
+ FileSystemOperationContext* context =
+ new FileSystemOperationContext(file_system_context_.get());
+ context->set_allowed_bytes_growth(100000000);
+ context->set_update_observers(
+ *file_system_context_->GetUpdateObservers(type));
+ return context;
+ }
+
+ bool CreateFileSystemDirectory(const base::FilePath& file_path,
+ const std::string& origin_url,
+ quota::StorageType storage_type) {
+ FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type);
+ FileSystemFileUtil* file_util = file_system_context_->GetFileUtil(type);
+
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL(origin_url), type, file_path);
+ scoped_ptr<FileSystemOperationContext> context(
+ CreateFileSystemOperationContext(type));
+
+ base::PlatformFileError result =
+ file_util->CreateDirectory(context.get(), url, false, false);
+ if (result != base::PLATFORM_FILE_OK)
+ return false;
+ return true;
+ }
+
+ bool CreateFileSystemFile(const base::FilePath& file_path,
+ int64 file_size,
+ const std::string& origin_url,
+ quota::StorageType storage_type) {
+ if (file_path.empty())
+ return false;
+
+ FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type);
+ FileSystemFileUtil* file_util = file_system_context_->GetFileUtil(type);
+
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL(origin_url), type, file_path);
+ scoped_ptr<FileSystemOperationContext> context(
+ CreateFileSystemOperationContext(type));
+
+ bool created = false;
+ if (base::PLATFORM_FILE_OK !=
+ file_util->EnsureFileExists(context.get(), url, &created))
+ return false;
+ EXPECT_TRUE(created);
+ if (base::PLATFORM_FILE_OK !=
+ file_util->Truncate(context.get(), url, file_size))
+ return false;
+ return true;
+ }
+
+ void InitializeOriginFiles(FileSystemQuotaClient* quota_client,
+ const TestFile* files,
+ int num_files) {
+ for (int i = 0; i < num_files; i++) {
+ base::FilePath path = base::FilePath().AppendASCII(files[i].name);
+ if (files[i].isDirectory) {
+ ASSERT_TRUE(CreateFileSystemDirectory(
+ path, files[i].origin_url, files[i].type));
+ if (path.empty()) {
+ // Create the usage cache.
+ // HACK--we always create the root [an empty path] first. If we
+ // create it later, this will fail due to a quota mismatch. If we
+ // call this before we create the root, it succeeds, but hasn't
+ // actually created the cache.
+ ASSERT_EQ(0, GetOriginUsage(
+ quota_client, files[i].origin_url, files[i].type));
+ }
+ } else {
+ ASSERT_TRUE(CreateFileSystemFile(
+ path, files[i].size, files[i].origin_url, files[i].type));
+ }
+ }
+ }
+
+ // This is a bit fragile--it depends on the test data always creating a
+ // directory before adding a file or directory to it, so that we can just
+ // count the basename of each addition. A recursive creation of a path, which
+ // created more than one directory in a single shot, would break this.
+ int64 ComputeFilePathsCostForOriginAndType(const TestFile* files,
+ int num_files,
+ const std::string& origin_url,
+ quota::StorageType type) {
+ int64 file_paths_cost = 0;
+ for (int i = 0; i < num_files; i++) {
+ if (files[i].type == type &&
+ GURL(files[i].origin_url) == GURL(origin_url)) {
+ base::FilePath path = base::FilePath().AppendASCII(files[i].name);
+ if (!path.empty()) {
+ file_paths_cost += ObfuscatedFileUtil::ComputeFilePathCost(path);
+ }
+ }
+ }
+ return file_paths_cost;
+ }
+
+ void DeleteOriginData(FileSystemQuotaClient* quota_client,
+ const std::string& origin,
+ quota::StorageType type) {
+ deletion_status_ = quota::kQuotaStatusUnknown;
+ quota_client->DeleteOriginData(
+ GURL(origin), type,
+ base::Bind(&FileSystemQuotaClientTest::OnDeleteOrigin,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ int64 usage() const { return usage_; }
+ quota::QuotaStatusCode status() { return deletion_status_; }
+ int additional_callback_count() const { return additional_callback_count_; }
+ void set_additional_callback_count(int count) {
+ additional_callback_count_ = count;
+ }
+
+ private:
+ void OnGetUsage(int64 usage) {
+ usage_ = usage;
+ }
+
+ void OnGetOrigins(const std::set<GURL>& origins) {
+ origins_ = origins;
+ }
+
+ void OnGetAdditionalUsage(int64 usage_unused) {
+ ++additional_callback_count_;
+ }
+
+ void OnDeleteOrigin(quota::QuotaStatusCode status) {
+ deletion_status_ = status;
+ }
+
+ base::ScopedTempDir data_dir_;
+ base::MessageLoop message_loop_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ base::WeakPtrFactory<FileSystemQuotaClientTest> weak_factory_;
+ int64 usage_;
+ int additional_callback_count_;
+ std::set<GURL> origins_;
+ quota::QuotaStatusCode deletion_status_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemQuotaClientTest);
+};
+
+TEST_F(FileSystemQuotaClientTest, NoFileSystemTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+
+ EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+}
+
+TEST_F(FileSystemQuotaClientTest, NoFileTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, OneFileTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 4921, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+ const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(4921 + file_paths_cost,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, TwoFilesTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 10310, kDummyURL1, kTemporary},
+ {false, "bar", 41, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+ const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(10310 + 41 + file_paths_cost,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, EmptyFilesTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 0, kDummyURL1, kTemporary},
+ {false, "bar", 0, kDummyURL1, kTemporary},
+ {false, "baz", 0, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+ const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(file_paths_cost,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, SubDirectoryTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {true, "dirtest", 0, kDummyURL1, kTemporary},
+ {false, "dirtest/foo", 11921, kDummyURL1, kTemporary},
+ {false, "bar", 4814, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+ const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(11921 + 4814 + file_paths_cost,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, MultiTypeTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {true, "dirtest", 0, kDummyURL1, kTemporary},
+ {false, "dirtest/foo", 133, kDummyURL1, kTemporary},
+ {false, "bar", 14, kDummyURL1, kTemporary},
+ {true, NULL, 0, kDummyURL1, kPersistent},
+ {true, "dirtest", 0, kDummyURL1, kPersistent},
+ {false, "dirtest/foo", 193, kDummyURL1, kPersistent},
+ {false, "bar", 9, kDummyURL1, kPersistent},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+ const int64 file_paths_cost_temporary = ComputeFilePathsCostForOriginAndType(
+ kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary);
+ const int64 file_paths_cost_persistent = ComputeFilePathsCostForOriginAndType(
+ kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(133 + 14 + file_paths_cost_temporary,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ EXPECT_EQ(193 + 9 + file_paths_cost_persistent,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, MultiDomainTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {true, "dir1", 0, kDummyURL1, kTemporary},
+ {false, "dir1/foo", 1331, kDummyURL1, kTemporary},
+ {false, "bar", 134, kDummyURL1, kTemporary},
+ {true, NULL, 0, kDummyURL1, kPersistent},
+ {true, "dir2", 0, kDummyURL1, kPersistent},
+ {false, "dir2/foo", 1903, kDummyURL1, kPersistent},
+ {false, "bar", 19, kDummyURL1, kPersistent},
+ {true, NULL, 0, kDummyURL2, kTemporary},
+ {true, "dom", 0, kDummyURL2, kTemporary},
+ {false, "dom/fan", 1319, kDummyURL2, kTemporary},
+ {false, "bar", 113, kDummyURL2, kTemporary},
+ {true, NULL, 0, kDummyURL2, kPersistent},
+ {true, "dom", 0, kDummyURL2, kPersistent},
+ {false, "dom/fan", 2013, kDummyURL2, kPersistent},
+ {false, "baz", 18, kDummyURL2, kPersistent},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+ const int64 file_paths_cost_temporary1 = ComputeFilePathsCostForOriginAndType(
+ kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary);
+ const int64 file_paths_cost_persistent1 =
+ ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles),
+ kDummyURL1, kPersistent);
+ const int64 file_paths_cost_temporary2 = ComputeFilePathsCostForOriginAndType(
+ kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL2, kTemporary);
+ const int64 file_paths_cost_persistent2 =
+ ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles),
+ kDummyURL2, kPersistent);
+
+ for (int i = 0; i < 2; i++) {
+ EXPECT_EQ(1331 + 134 + file_paths_cost_temporary1,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ EXPECT_EQ(1903 + 19 + file_paths_cost_persistent1,
+ GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent));
+ EXPECT_EQ(1319 + 113 + file_paths_cost_temporary2,
+ GetOriginUsage(quota_client.get(), kDummyURL2, kTemporary));
+ EXPECT_EQ(2013 + 18 + file_paths_cost_persistent2,
+ GetOriginUsage(quota_client.get(), kDummyURL2, kPersistent));
+ }
+}
+
+TEST_F(FileSystemQuotaClientTest, GetUsage_MultipleTasks) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 11, kDummyURL1, kTemporary},
+ {false, "bar", 22, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+ const int64 file_paths_cost = ComputeFilePathsCostForOriginAndType(
+ kFiles, ARRAYSIZE_UNSAFE(kFiles), kDummyURL1, kTemporary);
+
+ // Dispatching three GetUsage tasks.
+ set_additional_callback_count(0);
+ GetOriginUsageAsync(quota_client.get(), kDummyURL1, kTemporary);
+ RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary);
+ RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(11 + 22 + file_paths_cost, usage());
+ EXPECT_EQ(2, additional_callback_count());
+
+ // Once more, in a different order.
+ set_additional_callback_count(0);
+ RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary);
+ GetOriginUsageAsync(quota_client.get(), kDummyURL1, kTemporary);
+ RunAdditionalOriginUsageTask(quota_client.get(), kDummyURL1, kTemporary);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(11 + 22 + file_paths_cost, usage());
+ EXPECT_EQ(2, additional_callback_count());
+}
+
+TEST_F(FileSystemQuotaClientTest, GetOriginsForType) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {true, NULL, 0, kDummyURL2, kTemporary},
+ {true, NULL, 0, kDummyURL3, kPersistent},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+
+ std::set<GURL> origins = GetOriginsForType(quota_client.get(), kTemporary);
+ EXPECT_EQ(2U, origins.size());
+ EXPECT_TRUE(origins.find(GURL(kDummyURL1)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kDummyURL2)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kDummyURL3)) == origins.end());
+}
+
+TEST_F(FileSystemQuotaClientTest, GetOriginsForHost) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const char* kURL1 = "http://foo.com/";
+ const char* kURL2 = "https://foo.com/";
+ const char* kURL3 = "http://foo.com:1/";
+ const char* kURL4 = "http://foo2.com/";
+ const char* kURL5 = "http://foo.com:2/";
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kURL1, kTemporary},
+ {true, NULL, 0, kURL2, kTemporary},
+ {true, NULL, 0, kURL3, kTemporary},
+ {true, NULL, 0, kURL4, kTemporary},
+ {true, NULL, 0, kURL5, kPersistent},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+
+ std::set<GURL> origins = GetOriginsForHost(
+ quota_client.get(), kTemporary, "foo.com");
+ EXPECT_EQ(3U, origins.size());
+ EXPECT_TRUE(origins.find(GURL(kURL1)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kURL2)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kURL3)) != origins.end());
+ EXPECT_TRUE(origins.find(GURL(kURL4)) == origins.end()); // Different host.
+ EXPECT_TRUE(origins.find(GURL(kURL5)) == origins.end()); // Different type.
+}
+
+TEST_F(FileSystemQuotaClientTest, IncognitoTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(true));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, kDummyURL1, kTemporary},
+ {false, "foo", 10, kDummyURL1, kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+
+ // Having files in the usual directory wouldn't affect the result
+ // queried in incognito mode.
+ EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kTemporary));
+ EXPECT_EQ(0, GetOriginUsage(quota_client.get(), kDummyURL1, kPersistent));
+
+ std::set<GURL> origins = GetOriginsForType(quota_client.get(), kTemporary);
+ EXPECT_EQ(0U, origins.size());
+ origins = GetOriginsForHost(quota_client.get(), kTemporary, "www.dummy.org");
+ EXPECT_EQ(0U, origins.size());
+}
+
+TEST_F(FileSystemQuotaClientTest, DeleteOriginTest) {
+ scoped_ptr<FileSystemQuotaClient> quota_client(NewQuotaClient(false));
+ const TestFile kFiles[] = {
+ {true, NULL, 0, "http://foo.com/", kTemporary},
+ {false, "a", 1, "http://foo.com/", kTemporary},
+ {true, NULL, 0, "https://foo.com/", kTemporary},
+ {false, "b", 2, "https://foo.com/", kTemporary},
+ {true, NULL, 0, "http://foo.com/", kPersistent},
+ {false, "c", 4, "http://foo.com/", kPersistent},
+ {true, NULL, 0, "http://bar.com/", kTemporary},
+ {false, "d", 8, "http://bar.com/", kTemporary},
+ {true, NULL, 0, "http://bar.com/", kPersistent},
+ {false, "e", 16, "http://bar.com/", kPersistent},
+ {true, NULL, 0, "https://bar.com/", kPersistent},
+ {false, "f", 32, "https://bar.com/", kPersistent},
+ {true, NULL, 0, "https://bar.com/", kTemporary},
+ {false, "g", 64, "https://bar.com/", kTemporary},
+ };
+ InitializeOriginFiles(quota_client.get(), kFiles, ARRAYSIZE_UNSAFE(kFiles));
+ const int64 file_paths_cost_temporary_foo_https =
+ ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles),
+ "https://foo.com/", kTemporary);
+ const int64 file_paths_cost_persistent_foo =
+ ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles),
+ "http://foo.com/", kPersistent);
+ const int64 file_paths_cost_temporary_bar =
+ ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles),
+ "http://bar.com/", kTemporary);
+ const int64 file_paths_cost_temporary_bar_https =
+ ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles),
+ "https://bar.com/", kTemporary);
+ const int64 file_paths_cost_persistent_bar_https =
+ ComputeFilePathsCostForOriginAndType(kFiles, ARRAYSIZE_UNSAFE(kFiles),
+ "https://bar.com/", kPersistent);
+
+ DeleteOriginData(quota_client.get(), "http://foo.com/", kTemporary);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(quota::kQuotaStatusOk, status());
+
+ DeleteOriginData(quota_client.get(), "http://bar.com/", kPersistent);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(quota::kQuotaStatusOk, status());
+
+ DeleteOriginData(quota_client.get(), "http://buz.com/", kTemporary);
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(quota::kQuotaStatusOk, status());
+
+ EXPECT_EQ(0, GetOriginUsage(
+ quota_client.get(), "http://foo.com/", kTemporary));
+ EXPECT_EQ(0, GetOriginUsage(
+ quota_client.get(), "http://bar.com/", kPersistent));
+ EXPECT_EQ(0, GetOriginUsage(
+ quota_client.get(), "http://buz.com/", kTemporary));
+
+ EXPECT_EQ(2 + file_paths_cost_temporary_foo_https,
+ GetOriginUsage(quota_client.get(),
+ "https://foo.com/",
+ kTemporary));
+ EXPECT_EQ(4 + file_paths_cost_persistent_foo,
+ GetOriginUsage(quota_client.get(),
+ "http://foo.com/",
+ kPersistent));
+ EXPECT_EQ(8 + file_paths_cost_temporary_bar,
+ GetOriginUsage(quota_client.get(),
+ "http://bar.com/",
+ kTemporary));
+ EXPECT_EQ(32 + file_paths_cost_persistent_bar_https,
+ GetOriginUsage(quota_client.get(),
+ "https://bar.com/",
+ kPersistent));
+ EXPECT_EQ(64 + file_paths_cost_temporary_bar_https,
+ GetOriginUsage(quota_client.get(),
+ "https://bar.com/",
+ kTemporary));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_quota_util.h b/chromium/webkit/browser/fileapi/file_system_quota_util.h
new file mode 100644
index 00000000000..5655c990d4d
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_quota_util.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/platform_file.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/task_runner_bound_observer_list.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace quota {
+class QuotaManagerProxy;
+}
+
+namespace fileapi {
+
+class FileSystemContext;
+
+// An abstract interface that provides common quota-related utility functions
+// for file_system_quota_client.
+// All the methods of this class are synchronous and need to be called on
+// the thread that the method name implies.
+class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemQuotaUtil {
+ public:
+ virtual ~FileSystemQuotaUtil() {}
+
+ // Deletes the data on the origin and reports the amount of deleted data
+ // to the quota manager via |proxy|.
+ virtual base::PlatformFileError DeleteOriginDataOnFileThread(
+ FileSystemContext* context,
+ quota::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ FileSystemType type) = 0;
+
+ virtual void GetOriginsForTypeOnFileThread(fileapi::FileSystemType type,
+ std::set<GURL>* origins) = 0;
+
+ virtual void GetOriginsForHostOnFileThread(fileapi::FileSystemType type,
+ const std::string& host,
+ std::set<GURL>* origins) = 0;
+
+ // Returns the amount of data used for the origin for usage tracking.
+ virtual int64 GetOriginUsageOnFileThread(
+ fileapi::FileSystemContext* file_system_context,
+ const GURL& origin_url,
+ fileapi::FileSystemType type) = 0;
+
+ virtual void AddFileUpdateObserver(
+ FileSystemType type,
+ FileUpdateObserver* observer,
+ base::SequencedTaskRunner* task_runner) = 0;
+ virtual void AddFileChangeObserver(
+ FileSystemType type,
+ FileChangeObserver* observer,
+ base::SequencedTaskRunner* task_runner) = 0;
+ virtual void AddFileAccessObserver(
+ FileSystemType type,
+ FileAccessObserver* observer,
+ base::SequencedTaskRunner* task_runner) = 0;
+ virtual const UpdateObserverList* GetUpdateObservers(
+ FileSystemType type) const = 0;
+ virtual const ChangeObserverList* GetChangeObservers(
+ FileSystemType type) const = 0;
+ virtual const AccessObserverList* GetAccessObservers(
+ FileSystemType type) const = 0;
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_url.cc b/chromium/webkit/browser/fileapi/file_system_url.cc
new file mode 100644
index 00000000000..6269296a684
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_url.cc
@@ -0,0 +1,188 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_url.h"
+
+#include <sstream>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "net/base/escape.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+namespace {
+
+} // namespace
+
+FileSystemURL::FileSystemURL()
+ : is_valid_(false),
+ mount_type_(kFileSystemTypeUnknown),
+ type_(kFileSystemTypeUnknown) {
+}
+
+// static
+FileSystemURL FileSystemURL::CreateForTest(const GURL& url) {
+ return FileSystemURL(url);
+}
+
+FileSystemURL FileSystemURL::CreateForTest(const GURL& origin,
+ FileSystemType mount_type,
+ const base::FilePath& virtual_path) {
+ return FileSystemURL(origin, mount_type, virtual_path);
+}
+
+// static
+bool FileSystemURL::ParseFileSystemSchemeURL(
+ const GURL& url,
+ GURL* origin_url,
+ FileSystemType* mount_type,
+ base::FilePath* virtual_path) {
+ GURL origin;
+ FileSystemType file_system_type = kFileSystemTypeUnknown;
+
+ if (!url.is_valid() || !url.SchemeIsFileSystem())
+ return false;
+ DCHECK(url.inner_url());
+
+ std::string inner_path = url.inner_url()->path();
+
+ const struct {
+ FileSystemType type;
+ const char* dir;
+ } kValidTypes[] = {
+ { kFileSystemTypePersistent, kPersistentDir },
+ { kFileSystemTypeTemporary, kTemporaryDir },
+ { kFileSystemTypeIsolated, kIsolatedDir },
+ { kFileSystemTypeExternal, kExternalDir },
+ { kFileSystemTypeTest, kTestDir },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValidTypes); ++i) {
+ if (StartsWithASCII(inner_path, kValidTypes[i].dir, true)) {
+ file_system_type = kValidTypes[i].type;
+ break;
+ }
+ }
+
+ if (file_system_type == kFileSystemTypeUnknown)
+ return false;
+
+ std::string path = net::UnescapeURLComponent(url.path(),
+ net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS |
+ net::UnescapeRule::CONTROL_CHARS);
+
+ // Ensure the path is relative.
+ while (!path.empty() && path[0] == '/')
+ path.erase(0, 1);
+
+ base::FilePath converted_path = base::FilePath::FromUTF8Unsafe(path);
+
+ // All parent references should have been resolved in the renderer.
+ if (converted_path.ReferencesParent())
+ return false;
+
+ if (origin_url)
+ *origin_url = url.GetOrigin();
+ if (mount_type)
+ *mount_type = file_system_type;
+ if (virtual_path)
+ *virtual_path = converted_path.NormalizePathSeparators().
+ StripTrailingSeparators();
+
+ return true;
+}
+
+FileSystemURL::FileSystemURL(const GURL& url)
+ : mount_type_(kFileSystemTypeUnknown),
+ type_(kFileSystemTypeUnknown) {
+ is_valid_ = ParseFileSystemSchemeURL(url, &origin_, &mount_type_,
+ &virtual_path_);
+ path_ = virtual_path_;
+ type_ = mount_type_;
+}
+
+FileSystemURL::FileSystemURL(const GURL& origin,
+ FileSystemType mount_type,
+ const base::FilePath& virtual_path)
+ : is_valid_(true),
+ origin_(origin),
+ mount_type_(mount_type),
+ virtual_path_(virtual_path.NormalizePathSeparators()),
+ type_(mount_type),
+ path_(virtual_path.NormalizePathSeparators()) {
+}
+
+FileSystemURL::FileSystemURL(const GURL& origin,
+ FileSystemType mount_type,
+ const base::FilePath& virtual_path,
+ const std::string& mount_filesystem_id,
+ FileSystemType cracked_type,
+ const base::FilePath& cracked_path,
+ const std::string& filesystem_id)
+ : is_valid_(true),
+ origin_(origin),
+ mount_type_(mount_type),
+ virtual_path_(virtual_path.NormalizePathSeparators()),
+ mount_filesystem_id_(mount_filesystem_id),
+ type_(cracked_type),
+ path_(cracked_path.NormalizePathSeparators()),
+ filesystem_id_(filesystem_id) {
+}
+
+FileSystemURL::~FileSystemURL() {}
+
+std::string FileSystemURL::DebugString() const {
+ if (!is_valid_)
+ return "invalid filesystem: URL";
+ std::ostringstream ss;
+ ss << GetFileSystemRootURI(origin_, mount_type_);
+
+ // filesystem_id_ will be non empty for (and only for) cracked URLs.
+ if (!filesystem_id_.empty()) {
+ ss << virtual_path_.value();
+ ss << " (";
+ ss << GetFileSystemTypeString(type_) << "@" << filesystem_id_ << ":";
+ ss << path_.value();
+ ss << ")";
+ } else {
+ ss << path_.value();
+ }
+ return ss.str();
+}
+
+bool FileSystemURL::IsParent(const FileSystemURL& child) const {
+ return IsInSameFileSystem(child) &&
+ path().IsParent(child.path());
+}
+
+bool FileSystemURL::IsInSameFileSystem(const FileSystemURL& other) const {
+ return origin() == other.origin() &&
+ type() == other.type() &&
+ filesystem_id() == other.filesystem_id();
+}
+
+bool FileSystemURL::operator==(const FileSystemURL& that) const {
+ return origin_ == that.origin_ &&
+ type_ == that.type_ &&
+ path_ == that.path_ &&
+ filesystem_id_ == that.filesystem_id_ &&
+ is_valid_ == that.is_valid_;
+}
+
+bool FileSystemURL::Comparator::operator()(const FileSystemURL& lhs,
+ const FileSystemURL& rhs) const {
+ DCHECK(lhs.is_valid_ && rhs.is_valid_);
+ if (lhs.origin_ != rhs.origin_)
+ return lhs.origin_ < rhs.origin_;
+ if (lhs.type_ != rhs.type_)
+ return lhs.type_ < rhs.type_;
+ if (lhs.filesystem_id_ != rhs.filesystem_id_)
+ return lhs.filesystem_id_ < rhs.filesystem_id_;
+ return lhs.path_ < rhs.path_;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_url.h b/chromium/webkit/browser/fileapi/file_system_url.h
new file mode 100644
index 00000000000..6ac875d6310
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_url.h
@@ -0,0 +1,174 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_
+
+#include <set>
+#include <string>
+
+#include "base/platform_file.h"
+#include "url/gurl.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace fileapi {
+
+// A class representing a filesystem URL which consists of origin URL,
+// type and an internal path used inside the filesystem.
+//
+// When a FileSystemURL instance is created for a GURL (for filesystem: scheme),
+// each accessor method would return following values:
+//
+// Example: For a URL 'filesystem:http://foo.com/temporary/foo/bar':
+// origin() returns 'http://foo.com',
+// mount_type() returns kFileSystemTypeTemporary,
+// virtual_path() returns 'foo/bar',
+// type() returns the same value as mount_type(),
+// path() returns the same value as virtual_path(),
+//
+// All other accessors return empty or invalid value.
+//
+// FileSystemURL can also be created to represent a 'cracked' filesystem URL if
+// the original URL's type/path is pointing to a mount point which can be
+// further resolved to a lower filesystem type/path.
+//
+// Example: Assume a path '/media/removable' is mounted at mount name
+// 'mount_name' with type kFileSystemTypeFoo as an external file system.
+//
+// The original URL would look like:
+// 'filesystem:http://bar.com/external/mount_name/foo/bar':
+//
+// FileSystemURL('http://bar.com',
+// kFileSystemTypeExternal,
+// 'mount_name/foo/bar'
+// 'mount_name',
+// kFileSystemTypeFoo,
+// '/media/removable/foo/bar');
+// would create a FileSystemURL whose accessors return:
+//
+// origin() returns 'http://bar.com',
+// mount_type() returns kFileSystemTypeExternal,
+// virtual_path() returns 'mount_name/foo/bar',
+// type() returns the kFileSystemTypeFoo,
+// path() returns '/media/removable/foo/bar',
+//
+// Note that in either case virtual_path() always returns the path part after
+// 'type' part in the original URL, and mount_type() always returns the 'type'
+// part in the original URL.
+//
+// Additionally, following accessors would return valid values:
+// filesystem_id() returns 'mount_name'.
+//
+// It is impossible to directly create a valid FileSystemURL instance (except by
+// using CreatedForTest methods, which should not be used in production code).
+// To get a valid FileSystemURL, one of the following methods can be used:
+// <Friend>::CrackURL, <Friend>::CreateCrackedFileSystemURL, where <Friend> is
+// one of the friended classes.
+//
+// TODO(ericu): Look into making virtual_path() [and all FileSystem API virtual
+// paths] just an std::string, to prevent platform-specific base::FilePath
+// behavior from getting invoked by accident. Currently the base::FilePath
+// returned here needs special treatment, as it may contain paths that are
+// illegal on the current platform.
+// To avoid problems, use VirtualPath::BaseName and
+// VirtualPath::GetComponents instead of the base::FilePath methods.
+class WEBKIT_STORAGE_BROWSER_EXPORT FileSystemURL {
+ public:
+ FileSystemURL();
+ ~FileSystemURL();
+
+ // Methods for creating FileSystemURL without attempting to crack them.
+ // Should be used only in tests.
+ static FileSystemURL CreateForTest(const GURL& url);
+ static FileSystemURL CreateForTest(const GURL& origin,
+ FileSystemType mount_type,
+ const base::FilePath& virtual_path);
+
+ // Parses filesystem scheme |url| into uncracked FileSystemURL components.
+ static bool ParseFileSystemSchemeURL(const GURL& url,
+ GURL* origin,
+ FileSystemType* mount_type,
+ base::FilePath* virtual_path);
+
+ // Returns true if this instance represents a valid FileSystem URL.
+ bool is_valid() const { return is_valid_; }
+
+ // Returns the origin part of this URL. See the class comment for details.
+ const GURL& origin() const { return origin_; }
+
+ // Returns the type part of this URL. See the class comment for details.
+ FileSystemType type() const { return type_; }
+
+ // Returns the cracked path of this URL. See the class comment for details.
+ const base::FilePath& path() const { return path_; }
+
+ // Returns the original path part of this URL.
+ // See the class comment for details.
+ // TODO(kinuko): this must return std::string.
+ const base::FilePath& virtual_path() const { return virtual_path_; }
+
+ // Returns the filesystem ID/mount name for isolated/external filesystem URLs.
+ // See the class comment for details.
+ const std::string& filesystem_id() const { return filesystem_id_; }
+ const std::string& mount_filesystem_id() const {
+ return mount_filesystem_id_;
+ }
+
+ FileSystemType mount_type() const { return mount_type_; }
+
+ std::string DebugString() const;
+
+ // Returns true if this URL is a strict parent of the |child|.
+ bool IsParent(const FileSystemURL& child) const;
+
+ bool IsInSameFileSystem(const FileSystemURL& other) const;
+
+ bool operator==(const FileSystemURL& that) const;
+
+ struct WEBKIT_STORAGE_BROWSER_EXPORT Comparator {
+ bool operator() (const FileSystemURL& lhs, const FileSystemURL& rhs) const;
+ };
+
+ private:
+ friend class FileSystemContext;
+ friend class ExternalMountPoints;
+ friend class IsolatedContext;
+
+ explicit FileSystemURL(const GURL& filesystem_url);
+ FileSystemURL(const GURL& origin,
+ FileSystemType mount_type,
+ const base::FilePath& virtual_path);
+ // Creates a cracked FileSystemURL.
+ FileSystemURL(const GURL& origin,
+ FileSystemType mount_type,
+ const base::FilePath& virtual_path,
+ const std::string& mount_filesystem_id,
+ FileSystemType cracked_type,
+ const base::FilePath& cracked_path,
+ const std::string& filesystem_id);
+
+ bool is_valid_;
+
+ // Values parsed from the original URL.
+ GURL origin_;
+ FileSystemType mount_type_;
+ base::FilePath virtual_path_;
+
+ // Values obtained by cracking URLs.
+ // |mount_filesystem_id_| is retrieved from the first round of cracking,
+ // and the rest of the fields are from recursive cracking. Permission
+ // checking on the top-level mount information should be done with the former,
+ // and the low-level file operation should be implemented with the latter.
+ std::string mount_filesystem_id_;
+ FileSystemType type_;
+ base::FilePath path_;
+ std::string filesystem_id_;
+};
+
+typedef std::set<FileSystemURL, FileSystemURL::Comparator> FileSystemURLSet;
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_url_request_job.cc b/chromium/webkit/browser/fileapi/file_system_url_request_job.cc
new file mode 100644
index 00000000000..d93c14089f2
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_url_request_job.cc
@@ -0,0 +1,240 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_url_request_job.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util_proxy.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/platform_file.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request.h"
+#include "url/gurl.h"
+#include "webkit/browser/blob/file_stream_reader.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using net::NetworkDelegate;
+using net::URLRequest;
+using net::URLRequestJob;
+using net::URLRequestStatus;
+
+namespace fileapi {
+
+static net::HttpResponseHeaders* CreateHttpResponseHeaders() {
+ // HttpResponseHeaders expects its input string to be terminated by two NULs.
+ static const char kStatus[] = "HTTP/1.1 200 OK\0";
+ static const size_t kStatusLen = arraysize(kStatus);
+
+ net::HttpResponseHeaders* headers =
+ new net::HttpResponseHeaders(std::string(kStatus, kStatusLen));
+
+ // Tell WebKit never to cache this content.
+ std::string cache_control(net::HttpRequestHeaders::kCacheControl);
+ cache_control.append(": no-cache");
+ headers->AddHeader(cache_control);
+
+ return headers;
+}
+
+FileSystemURLRequestJob::FileSystemURLRequestJob(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ FileSystemContext* file_system_context)
+ : URLRequestJob(request, network_delegate),
+ file_system_context_(file_system_context),
+ weak_factory_(this),
+ is_directory_(false),
+ remaining_bytes_(0) {
+}
+
+FileSystemURLRequestJob::~FileSystemURLRequestJob() {}
+
+void FileSystemURLRequestJob::Start() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FileSystemURLRequestJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+void FileSystemURLRequestJob::Kill() {
+ reader_.reset();
+ URLRequestJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+bool FileSystemURLRequestJob::ReadRawData(net::IOBuffer* dest, int dest_size,
+ int *bytes_read) {
+ DCHECK_NE(dest_size, 0);
+ DCHECK(bytes_read);
+ DCHECK_GE(remaining_bytes_, 0);
+
+ if (reader_.get() == NULL)
+ return false;
+
+ if (remaining_bytes_ < dest_size)
+ dest_size = static_cast<int>(remaining_bytes_);
+
+ if (!dest_size) {
+ *bytes_read = 0;
+ return true;
+ }
+
+ const int rv = reader_->Read(dest, dest_size,
+ base::Bind(&FileSystemURLRequestJob::DidRead,
+ weak_factory_.GetWeakPtr()));
+ if (rv >= 0) {
+ // Data is immediately available.
+ *bytes_read = rv;
+ remaining_bytes_ -= rv;
+ DCHECK_GE(remaining_bytes_, 0);
+ return true;
+ }
+ if (rv == net::ERR_IO_PENDING)
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ else
+ NotifyFailed(rv);
+ return false;
+}
+
+bool FileSystemURLRequestJob::GetMimeType(std::string* mime_type) const {
+ DCHECK(request_);
+ DCHECK(url_.is_valid());
+ base::FilePath::StringType extension = url_.path().Extension();
+ if (!extension.empty())
+ extension = extension.substr(1);
+ return net::GetWellKnownMimeTypeFromExtension(extension, mime_type);
+}
+
+void FileSystemURLRequestJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
+ std::vector<net::HttpByteRange> ranges;
+ if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ byte_range_ = ranges[0];
+ } else {
+ // We don't support multiple range requests in one single URL request.
+ // TODO(adamk): decide whether we want to support multiple range
+ // requests.
+ NotifyFailed(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+ }
+ }
+ }
+}
+
+void FileSystemURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
+ if (response_info_)
+ *info = *response_info_;
+}
+
+int FileSystemURLRequestJob::GetResponseCode() const {
+ if (response_info_)
+ return 200;
+ return URLRequestJob::GetResponseCode();
+}
+
+void FileSystemURLRequestJob::StartAsync() {
+ if (!request_)
+ return;
+ DCHECK(!reader_.get());
+ url_ = file_system_context_->CrackURL(request_->url());
+ file_system_context_->operation_runner()->GetMetadata(
+ url_,
+ base::Bind(&FileSystemURLRequestJob::DidGetMetadata,
+ weak_factory_.GetWeakPtr()));
+}
+
+void FileSystemURLRequestJob::DidGetMetadata(
+ base::PlatformFileError error_code,
+ const base::PlatformFileInfo& file_info) {
+ if (error_code != base::PLATFORM_FILE_OK) {
+ NotifyFailed(error_code == base::PLATFORM_FILE_ERROR_INVALID_URL ?
+ net::ERR_INVALID_URL : net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ // We may have been orphaned...
+ if (!request_)
+ return;
+
+ is_directory_ = file_info.is_directory;
+
+ if (!byte_range_.ComputeBounds(file_info.size)) {
+ NotifyFailed(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+ return;
+ }
+
+ if (is_directory_) {
+ NotifyHeadersComplete();
+ return;
+ }
+
+ remaining_bytes_ = byte_range_.last_byte_position() -
+ byte_range_.first_byte_position() + 1;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ DCHECK(!reader_.get());
+ reader_ = file_system_context_->CreateFileStreamReader(
+ url_, byte_range_.first_byte_position(), base::Time());
+
+ set_expected_content_size(remaining_bytes_);
+ response_info_.reset(new net::HttpResponseInfo());
+ response_info_->headers = CreateHttpResponseHeaders();
+ NotifyHeadersComplete();
+}
+
+void FileSystemURLRequestJob::DidRead(int result) {
+ if (result > 0)
+ SetStatus(URLRequestStatus()); // Clear the IO_PENDING status
+ else if (result == 0)
+ NotifyDone(URLRequestStatus());
+ else
+ NotifyFailed(result);
+
+ remaining_bytes_ -= result;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ NotifyReadComplete(result);
+}
+
+bool FileSystemURLRequestJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (is_directory_) {
+ // This happens when we discovered the file is a directory, so needs a
+ // slash at the end of the path.
+ std::string new_path = request_->url().path();
+ new_path.push_back('/');
+ GURL::Replacements replacements;
+ replacements.SetPathStr(new_path);
+ *location = request_->url().ReplaceComponents(replacements);
+ *http_status_code = 301; // simulate a permanent redirect
+ return true;
+ }
+
+ return false;
+}
+
+void FileSystemURLRequestJob::NotifyFailed(int rv) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_url_request_job.h b/chromium/webkit/browser/fileapi/file_system_url_request_job.h
new file mode 100644
index 00000000000..6b829e6e861
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_url_request_job.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/platform_file.h"
+#include "net/http/http_byte_range.h"
+#include "net/url_request/url_request_job.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace webkit_blob {
+class FileStreamReader;
+}
+
+namespace fileapi {
+class FileSystemContext;
+
+// A request job that handles reading filesystem: URLs
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemURLRequestJob
+ : public net::URLRequestJob {
+ public:
+ FileSystemURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ FileSystemContext* file_system_context);
+
+ // URLRequestJob methods:
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) OVERRIDE;
+ virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+
+ // FilterContext methods (via URLRequestJob):
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+
+ private:
+ class CallbackDispatcher;
+
+ virtual ~FileSystemURLRequestJob();
+
+ void StartAsync();
+ void DidGetMetadata(
+ base::PlatformFileError error_code,
+ const base::PlatformFileInfo& file_info);
+ void DidRead(int result);
+ void NotifyFailed(int rv);
+
+ FileSystemContext* file_system_context_;
+ base::WeakPtrFactory<FileSystemURLRequestJob> weak_factory_;
+ scoped_ptr<webkit_blob::FileStreamReader> reader_;
+ FileSystemURL url_;
+ bool is_directory_;
+ scoped_ptr<net::HttpResponseInfo> response_info_;
+ int64 remaining_bytes_;
+ net::HttpByteRange byte_range_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemURLRequestJob);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.cc b/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.cc
new file mode 100644
index 00000000000..76ae7c9ac05
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_url_request_job_factory.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/url_request/url_request.h"
+#include "webkit/browser/fileapi/file_system_dir_url_request_job.h"
+#include "webkit/browser/fileapi/file_system_url_request_job.h"
+
+namespace fileapi {
+
+namespace {
+
+class FileSystemProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ explicit FileSystemProtocolHandler(FileSystemContext* context);
+ virtual ~FileSystemProtocolHandler();
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ // No scoped_refptr because |file_system_context_| is owned by the
+ // ProfileIOData, which also owns this ProtocolHandler.
+ FileSystemContext* const file_system_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemProtocolHandler);
+};
+
+FileSystemProtocolHandler::FileSystemProtocolHandler(
+ FileSystemContext* context)
+ : file_system_context_(context) {
+ DCHECK(file_system_context_);
+}
+
+FileSystemProtocolHandler::~FileSystemProtocolHandler() {}
+
+net::URLRequestJob* FileSystemProtocolHandler::MaybeCreateJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
+ const std::string path = request->url().path();
+
+ // If the path ends with a /, we know it's a directory. If the path refers
+ // to a directory and gets dispatched to FileSystemURLRequestJob, that class
+ // redirects back here, by adding a / to the URL.
+ if (!path.empty() && path[path.size() - 1] == '/') {
+ return new FileSystemDirURLRequestJob(
+ request, network_delegate, file_system_context_);
+ }
+ return new FileSystemURLRequestJob(
+ request, network_delegate, file_system_context_);
+}
+
+} // anonymous namespace
+
+net::URLRequestJobFactory::ProtocolHandler*
+CreateFileSystemProtocolHandler(FileSystemContext* context) {
+ DCHECK(context);
+ return new FileSystemProtocolHandler(context);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.h b/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.h
new file mode 100644
index 00000000000..5bd3c0f91a6
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_url_request_job_factory.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_
+
+#include "net/url_request/url_request_job_factory.h"
+
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class MessageLoopProxy;
+} // namespace base
+
+namespace fileapi {
+
+class FileSystemContext;
+
+// |context|'s lifetime should exceed the lifetime of the ProtocolHandler.
+// Currently, this is only used by ProfileIOData which owns |context| and the
+// ProtocolHandler.
+WEBKIT_STORAGE_BROWSER_EXPORT net::URLRequestJobFactory::ProtocolHandler*
+ CreateFileSystemProtocolHandler(FileSystemContext* context);
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_url_request_job_unittest.cc b/chromium/webkit/browser/fileapi/file_system_url_request_job_unittest.cc
new file mode 100644
index 00000000000..a8f6cabbffe
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_url_request_job_unittest.cc
@@ -0,0 +1,361 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_url_request_job.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/format_macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/platform_file.h"
+#include "base/rand_util.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+
+namespace fileapi {
+namespace {
+
+// We always use the TEMPORARY FileSystem in this test.
+const char kFileSystemURLPrefix[] = "filesystem:http://remote/temporary/";
+const char kTestFileData[] = "0123456789";
+
+void FillBuffer(char* buffer, size_t len) {
+ base::RandBytes(buffer, len);
+}
+
+} // namespace
+
+class FileSystemURLRequestJobTest : public testing::Test {
+ protected:
+ FileSystemURLRequestJobTest()
+ : message_loop_(base::MessageLoop::TYPE_IO), // simulate an IO thread
+ weak_factory_(this) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ // We use the main thread so that we can get the root path synchronously.
+ // TODO(adamk): Run this on the FILE thread we've created as well.
+ file_system_context_ =
+ CreateFileSystemContextForTesting(NULL, temp_dir_.path());
+
+ file_system_context_->OpenFileSystem(
+ GURL("http://remote/"), kFileSystemTypeTemporary,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&FileSystemURLRequestJobTest::OnOpenFileSystem,
+ weak_factory_.GetWeakPtr()));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ net::URLRequest::Deprecated::RegisterProtocolFactory(
+ "filesystem", &FileSystemURLRequestJobFactory);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ net::URLRequest::Deprecated::RegisterProtocolFactory("filesystem", NULL);
+ ClearUnusedJob();
+ if (pending_job_.get()) {
+ pending_job_->Kill();
+ pending_job_ = NULL;
+ }
+ // FileReader posts a task to close the file in destructor.
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void OnOpenFileSystem(base::PlatformFileError result,
+ const std::string& name,
+ const GURL& root_url) {
+ ASSERT_EQ(base::PLATFORM_FILE_OK, result);
+ }
+
+ void TestRequestHelper(const GURL& url,
+ const net::HttpRequestHeaders* headers,
+ bool run_to_completion) {
+ delegate_.reset(new net::TestDelegate());
+ // Make delegate_ exit the MessageLoop when the request is done.
+ delegate_->set_quit_on_complete(true);
+ delegate_->set_quit_on_redirect(true);
+ request_.reset(empty_context_.CreateRequest(url, delegate_.get()));
+ if (headers)
+ request_->SetExtraRequestHeaders(*headers);
+ ASSERT_TRUE(!job_);
+ job_ = new FileSystemURLRequestJob(
+ request_.get(), NULL, file_system_context_.get());
+ pending_job_ = job_;
+
+ request_->Start();
+ ASSERT_TRUE(request_->is_pending()); // verify that we're starting async
+ if (run_to_completion)
+ base::MessageLoop::current()->Run();
+ }
+
+ void TestRequest(const GURL& url) {
+ TestRequestHelper(url, NULL, true);
+ }
+
+ void TestRequestWithHeaders(const GURL& url,
+ const net::HttpRequestHeaders* headers) {
+ TestRequestHelper(url, headers, true);
+ }
+
+ void TestRequestNoRun(const GURL& url) {
+ TestRequestHelper(url, NULL, false);
+ }
+
+ void CreateDirectory(const base::StringPiece& dir_name) {
+ FileSystemFileUtil* file_util = file_system_context_->GetFileUtil(
+ kFileSystemTypeTemporary);
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://remote"),
+ kFileSystemTypeTemporary,
+ base::FilePath().AppendASCII(dir_name));
+
+ FileSystemOperationContext context(file_system_context_.get());
+ context.set_allowed_bytes_growth(1024);
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateDirectory(
+ &context,
+ url,
+ false /* exclusive */,
+ false /* recursive */));
+ }
+
+ void WriteFile(const base::StringPiece& file_name,
+ const char* buf, int buf_size) {
+ FileSystemFileUtil* file_util = file_system_context_->GetFileUtil(
+ kFileSystemTypeTemporary);
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://remote"),
+ kFileSystemTypeTemporary,
+ base::FilePath().AppendASCII(file_name));
+
+ FileSystemOperationContext context(file_system_context_.get());
+ context.set_allowed_bytes_growth(1024);
+
+ base::PlatformFile handle = base::kInvalidPlatformFileValue;
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateOrOpen(
+ &context,
+ url,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &handle,
+ &created));
+ EXPECT_TRUE(created);
+ ASSERT_NE(base::kInvalidPlatformFileValue, handle);
+ ASSERT_EQ(buf_size,
+ base::WritePlatformFile(handle, 0 /* offset */, buf, buf_size));
+ base::ClosePlatformFile(handle);
+ }
+
+ GURL CreateFileSystemURL(const std::string& path) {
+ return GURL(kFileSystemURLPrefix + path);
+ }
+
+ static net::URLRequestJob* FileSystemURLRequestJobFactory(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ DCHECK(job_);
+ net::URLRequestJob* temp = job_;
+ job_ = NULL;
+ return temp;
+ }
+
+ static void ClearUnusedJob() {
+ if (job_) {
+ scoped_refptr<net::URLRequestJob> deleter = job_;
+ job_ = NULL;
+ }
+ }
+
+ // Put the message loop at the top, so that it's the last thing deleted.
+ base::MessageLoop message_loop_;
+
+ base::ScopedTempDir temp_dir_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ base::WeakPtrFactory<FileSystemURLRequestJobTest> weak_factory_;
+
+ net::URLRequestContext empty_context_;
+
+ // NOTE: order matters, request must die before delegate
+ scoped_ptr<net::TestDelegate> delegate_;
+ scoped_ptr<net::URLRequest> request_;
+
+ scoped_refptr<net::URLRequestJob> pending_job_;
+ static net::URLRequestJob* job_;
+};
+
+// static
+net::URLRequestJob* FileSystemURLRequestJobTest::job_ = NULL;
+
+namespace {
+
+TEST_F(FileSystemURLRequestJobTest, FileTest) {
+ WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
+ TestRequest(CreateFileSystemURL("file1.dat"));
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ EXPECT_EQ(kTestFileData, delegate_->data_received());
+ EXPECT_EQ(200, request_->GetResponseCode());
+ std::string cache_control;
+ request_->GetResponseHeaderByName("cache-control", &cache_control);
+ EXPECT_EQ("no-cache", cache_control);
+}
+
+TEST_F(FileSystemURLRequestJobTest, FileTestFullSpecifiedRange) {
+ const size_t buffer_size = 4000;
+ scoped_ptr<char[]> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+ WriteFile("bigfile", buffer.get(), buffer_size);
+
+ const size_t first_byte_position = 500;
+ const size_t last_byte_position = buffer_size - first_byte_position;
+ std::string partial_buffer_string(buffer.get() + first_byte_position,
+ buffer.get() + last_byte_position + 1);
+
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kRange,
+ base::StringPrintf(
+ "bytes=%" PRIuS "-%" PRIuS,
+ first_byte_position, last_byte_position));
+ TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers);
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ EXPECT_TRUE(partial_buffer_string == delegate_->data_received());
+}
+
+TEST_F(FileSystemURLRequestJobTest, FileTestHalfSpecifiedRange) {
+ const size_t buffer_size = 4000;
+ scoped_ptr<char[]> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+ WriteFile("bigfile", buffer.get(), buffer_size);
+
+ const size_t first_byte_position = 500;
+ std::string partial_buffer_string(buffer.get() + first_byte_position,
+ buffer.get() + buffer_size);
+
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kRange,
+ base::StringPrintf("bytes=%" PRIuS "-",
+ first_byte_position));
+ TestRequestWithHeaders(CreateFileSystemURL("bigfile"), &headers);
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_EQ(1, delegate_->response_started_count());
+ EXPECT_FALSE(delegate_->received_data_before_response());
+ // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
+ EXPECT_TRUE(partial_buffer_string == delegate_->data_received());
+}
+
+
+TEST_F(FileSystemURLRequestJobTest, FileTestMultipleRangesNotSupported) {
+ WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kRange,
+ "bytes=0-5,10-200,200-300");
+ TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers);
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
+ request_->status().error());
+}
+
+TEST_F(FileSystemURLRequestJobTest, RangeOutOfBounds) {
+ WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
+ net::HttpRequestHeaders headers;
+ headers.SetHeader(net::HttpRequestHeaders::kRange, "bytes=500-1000");
+ TestRequestWithHeaders(CreateFileSystemURL("file1.dat"), &headers);
+
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE,
+ request_->status().error());
+}
+
+TEST_F(FileSystemURLRequestJobTest, FileDirRedirect) {
+ CreateDirectory("dir");
+ TestRequest(CreateFileSystemURL("dir"));
+
+ EXPECT_EQ(1, delegate_->received_redirect_count());
+ EXPECT_TRUE(request_->status().is_success());
+ EXPECT_FALSE(delegate_->request_failed());
+
+ // We've deferred the redirect; now cancel the request to avoid following it.
+ request_->Cancel();
+ base::MessageLoop::current()->Run();
+}
+
+TEST_F(FileSystemURLRequestJobTest, InvalidURL) {
+ TestRequest(GURL("filesystem:/foo/bar/baz"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_INVALID_URL, request_->status().error());
+}
+
+TEST_F(FileSystemURLRequestJobTest, NoSuchRoot) {
+ TestRequest(GURL("filesystem:http://remote/persistent/somefile"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
+}
+
+TEST_F(FileSystemURLRequestJobTest, NoSuchFile) {
+ TestRequest(CreateFileSystemURL("somefile"));
+ ASSERT_FALSE(request_->is_pending());
+ EXPECT_TRUE(delegate_->request_failed());
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request_->status().error());
+}
+
+TEST_F(FileSystemURLRequestJobTest, Cancel) {
+ WriteFile("file1.dat", kTestFileData, arraysize(kTestFileData) - 1);
+ TestRequestNoRun(CreateFileSystemURL("file1.dat"));
+
+ // Run StartAsync() and only StartAsync().
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, request_.release());
+ base::MessageLoop::current()->RunUntilIdle();
+ // If we get here, success! we didn't crash!
+}
+
+TEST_F(FileSystemURLRequestJobTest, GetMimeType) {
+ const char kFilename[] = "hoge.html";
+
+ std::string mime_type_direct;
+ base::FilePath::StringType extension =
+ base::FilePath().AppendASCII(kFilename).Extension();
+ if (!extension.empty())
+ extension = extension.substr(1);
+ EXPECT_TRUE(net::GetWellKnownMimeTypeFromExtension(
+ extension, &mime_type_direct));
+
+ TestRequest(CreateFileSystemURL(kFilename));
+
+ std::string mime_type_from_job;
+ request_->GetMimeType(&mime_type_from_job);
+ EXPECT_EQ(mime_type_direct, mime_type_from_job);
+}
+
+} // namespace
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_url_unittest.cc b/chromium/webkit/browser/fileapi/file_system_url_unittest.cc
new file mode 100644
index 00000000000..ace67007cb3
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_url_unittest.cc
@@ -0,0 +1,194 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_url.h"
+
+#include "base/files/file_path.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+#define FPL FILE_PATH_LITERAL
+
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+#define DRIVE FPL("C:")
+#else
+#define DRIVE FPL("/a/")
+#endif
+
+namespace fileapi {
+
+namespace {
+
+FileSystemURL CreateFileSystemURL(const std::string& url_string) {
+ FileSystemURL url = FileSystemURL::CreateForTest(GURL(url_string));
+ EXPECT_TRUE(url.type() != kFileSystemTypeExternal &&
+ url.type() != kFileSystemTypeIsolated);
+ return url;
+}
+
+std::string NormalizedUTF8Path(const base::FilePath& path) {
+ return path.NormalizePathSeparators().AsUTF8Unsafe();
+}
+
+} // namespace
+
+TEST(FileSystemURLTest, ParsePersistent) {
+ FileSystemURL url = CreateFileSystemURL(
+ "filesystem:http://chromium.org/persistent/directory/file");
+ ASSERT_TRUE(url.is_valid());
+ EXPECT_EQ("http://chromium.org/", url.origin().spec());
+ EXPECT_EQ(kFileSystemTypePersistent, url.type());
+ EXPECT_EQ(FPL("file"), VirtualPath::BaseName(url.path()).value());
+ EXPECT_EQ(FPL("directory"), url.path().DirName().value());
+}
+
+TEST(FileSystemURLTest, ParseTemporary) {
+ FileSystemURL url = CreateFileSystemURL(
+ "filesystem:http://chromium.org/temporary/directory/file");
+ ASSERT_TRUE(url.is_valid());
+ EXPECT_EQ("http://chromium.org/", url.origin().spec());
+ EXPECT_EQ(kFileSystemTypeTemporary, url.type());
+ EXPECT_EQ(FPL("file"), VirtualPath::BaseName(url.path()).value());
+ EXPECT_EQ(FPL("directory"), url.path().DirName().value());
+}
+
+TEST(FileSystemURLTest, EnsureFilePathIsRelative) {
+ FileSystemURL url = CreateFileSystemURL(
+ "filesystem:http://chromium.org/temporary/////directory/file");
+ ASSERT_TRUE(url.is_valid());
+ EXPECT_EQ("http://chromium.org/", url.origin().spec());
+ EXPECT_EQ(kFileSystemTypeTemporary, url.type());
+ EXPECT_EQ(FPL("file"), VirtualPath::BaseName(url.path()).value());
+ EXPECT_EQ(FPL("directory"), url.path().DirName().value());
+ EXPECT_FALSE(url.path().IsAbsolute());
+}
+
+TEST(FileSystemURLTest, RejectBadSchemes) {
+ EXPECT_FALSE(CreateFileSystemURL("http://chromium.org/").is_valid());
+ EXPECT_FALSE(CreateFileSystemURL("https://chromium.org/").is_valid());
+ EXPECT_FALSE(CreateFileSystemURL("file:///foo/bar").is_valid());
+ EXPECT_FALSE(CreateFileSystemURL("foobar:///foo/bar").is_valid());
+}
+
+TEST(FileSystemURLTest, UnescapePath) {
+ FileSystemURL url = CreateFileSystemURL(
+ "filesystem:http://chromium.org/persistent/%7Echromium/space%20bar");
+ ASSERT_TRUE(url.is_valid());
+ EXPECT_EQ(FPL("space bar"), VirtualPath::BaseName(url.path()).value());
+ EXPECT_EQ(FPL("~chromium"), url.path().DirName().value());
+}
+
+TEST(FileSystemURLTest, RejectBadType) {
+ EXPECT_FALSE(CreateFileSystemURL(
+ "filesystem:http://c.org/foobar/file").is_valid());
+}
+
+TEST(FileSystemURLTest, RejectMalformedURL) {
+ EXPECT_FALSE(CreateFileSystemURL("filesystem:///foobar/file").is_valid());
+ EXPECT_FALSE(CreateFileSystemURL("filesystem:foobar/file").is_valid());
+}
+
+TEST(FileSystemURLTest, CompareURLs) {
+ const GURL urls[] = {
+ GURL("filesystem:http://chromium.org/temporary/dir a/file a"),
+ GURL("filesystem:http://chromium.org/temporary/dir a/file a"),
+ GURL("filesystem:http://chromium.org/temporary/dir a/file b"),
+ GURL("filesystem:http://chromium.org/temporary/dir a/file aa"),
+ GURL("filesystem:http://chromium.org/temporary/dir b/file a"),
+ GURL("filesystem:http://chromium.org/temporary/dir aa/file b"),
+ GURL("filesystem:http://chromium.com/temporary/dir a/file a"),
+ GURL("filesystem:https://chromium.org/temporary/dir a/file a")
+ };
+
+ FileSystemURL::Comparator compare;
+ for (size_t i = 0; i < arraysize(urls); ++i) {
+ for (size_t j = 0; j < arraysize(urls); ++j) {
+ SCOPED_TRACE(testing::Message() << i << " < " << j);
+ EXPECT_EQ(urls[i] < urls[j],
+ compare(FileSystemURL::CreateForTest(urls[i]),
+ FileSystemURL::CreateForTest(urls[j])));
+ }
+ }
+
+ const FileSystemURL a = CreateFileSystemURL(
+ "filesystem:http://chromium.org/temporary/dir a/file a");
+ const FileSystemURL b = CreateFileSystemURL(
+ "filesystem:http://chromium.org/persistent/dir a/file a");
+ EXPECT_EQ(a.type() < b.type(), compare(a, b));
+ EXPECT_EQ(b.type() < a.type(), compare(b, a));
+}
+
+TEST(FileSystemURLTest, IsParent) {
+ const std::string root1 = GetFileSystemRootURI(
+ GURL("http://example.com"), kFileSystemTypeTemporary).spec();
+ const std::string root2 = GetFileSystemRootURI(
+ GURL("http://example.com"), kFileSystemTypePersistent).spec();
+ const std::string root3 = GetFileSystemRootURI(
+ GURL("http://chromium.org"), kFileSystemTypeTemporary).spec();
+
+ const std::string parent("dir");
+ const std::string child("dir/child");
+ const std::string other("other");
+
+ // True cases.
+ EXPECT_TRUE(CreateFileSystemURL(root1 + parent).IsParent(
+ CreateFileSystemURL(root1 + child)));
+ EXPECT_TRUE(CreateFileSystemURL(root2 + parent).IsParent(
+ CreateFileSystemURL(root2 + child)));
+
+ // False cases: the path is not a child.
+ EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent(
+ CreateFileSystemURL(root1 + other)));
+ EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent(
+ CreateFileSystemURL(root1 + parent)));
+ EXPECT_FALSE(CreateFileSystemURL(root1 + child).IsParent(
+ CreateFileSystemURL(root1 + parent)));
+
+ // False case: different types.
+ EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent(
+ CreateFileSystemURL(root2 + child)));
+
+ // False case: different origins.
+ EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent(
+ CreateFileSystemURL(root3 + child)));
+}
+
+TEST(FileSystemURLTest, DebugString) {
+ const GURL kOrigin("http://example.com");
+ const base::FilePath kPath(FPL("dir/file"));
+
+ const FileSystemURL kURL1 = FileSystemURL::CreateForTest(
+ kOrigin, kFileSystemTypeTemporary, kPath);
+ EXPECT_EQ("filesystem:http://example.com/temporary/" +
+ NormalizedUTF8Path(kPath),
+ kURL1.DebugString());
+}
+
+TEST(FileSystemURLTest, IsInSameFileSystem) {
+ FileSystemURL url_foo_temp_a = FileSystemURL::CreateForTest(
+ GURL("http://foo"), kFileSystemTypeTemporary,
+ base::FilePath::FromUTF8Unsafe("a"));
+ FileSystemURL url_foo_temp_b = FileSystemURL::CreateForTest(
+ GURL("http://foo"), kFileSystemTypeTemporary,
+ base::FilePath::FromUTF8Unsafe("b"));
+ FileSystemURL url_foo_perm_a = FileSystemURL::CreateForTest(
+ GURL("http://foo"), kFileSystemTypePersistent,
+ base::FilePath::FromUTF8Unsafe("a"));
+ FileSystemURL url_bar_temp_a = FileSystemURL::CreateForTest(
+ GURL("http://bar"), kFileSystemTypeTemporary,
+ base::FilePath::FromUTF8Unsafe("a"));
+ FileSystemURL url_bar_perm_a = FileSystemURL::CreateForTest(
+ GURL("http://bar"), kFileSystemTypePersistent,
+ base::FilePath::FromUTF8Unsafe("a"));
+
+ EXPECT_TRUE(url_foo_temp_a.IsInSameFileSystem(url_foo_temp_a));
+ EXPECT_TRUE(url_foo_temp_a.IsInSameFileSystem(url_foo_temp_b));
+ EXPECT_FALSE(url_foo_temp_a.IsInSameFileSystem(url_foo_perm_a));
+ EXPECT_FALSE(url_foo_temp_a.IsInSameFileSystem(url_bar_temp_a));
+ EXPECT_FALSE(url_foo_temp_a.IsInSameFileSystem(url_bar_perm_a));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_usage_cache.cc b/chromium/webkit/browser/fileapi/file_system_usage_cache.cc
new file mode 100644
index 00000000000..c1896fb1f95
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_usage_cache.cc
@@ -0,0 +1,316 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/pickle.h"
+#include "base/stl_util.h"
+#include "webkit/browser/fileapi/timed_task_helper.h"
+
+namespace fileapi {
+
+namespace {
+const int64 kCloseDelaySeconds = 5;
+const size_t kMaxHandleCacheSize = 2;
+} // namespace
+
+FileSystemUsageCache::FileSystemUsageCache(
+ base::SequencedTaskRunner* task_runner)
+ : weak_factory_(this),
+ task_runner_(task_runner) {
+}
+
+FileSystemUsageCache::~FileSystemUsageCache() {
+ task_runner_ = NULL;
+ CloseCacheFiles();
+}
+
+const base::FilePath::CharType FileSystemUsageCache::kUsageFileName[] =
+ FILE_PATH_LITERAL(".usage");
+const char FileSystemUsageCache::kUsageFileHeader[] = "FSU5";
+const int FileSystemUsageCache::kUsageFileHeaderSize = 4;
+
+// Pickle::{Read,Write}Bool treat bool as int
+const int FileSystemUsageCache::kUsageFileSize =
+ sizeof(Pickle::Header) +
+ FileSystemUsageCache::kUsageFileHeaderSize +
+ sizeof(int) + sizeof(int32) + sizeof(int64); // NOLINT
+
+bool FileSystemUsageCache::GetUsage(const base::FilePath& usage_file_path,
+ int64* usage_out) {
+ TRACE_EVENT0("FileSystem", "UsageCache::GetUsage");
+ DCHECK(CalledOnValidThread());
+ DCHECK(usage_out);
+ bool is_valid = true;
+ uint32 dirty = 0;
+ int64 usage = 0;
+ if (!Read(usage_file_path, &is_valid, &dirty, &usage))
+ return false;
+ *usage_out = usage;
+ return true;
+}
+
+bool FileSystemUsageCache::GetDirty(const base::FilePath& usage_file_path,
+ uint32* dirty_out) {
+ TRACE_EVENT0("FileSystem", "UsageCache::GetDirty");
+ DCHECK(CalledOnValidThread());
+ DCHECK(dirty_out);
+ bool is_valid = true;
+ uint32 dirty = 0;
+ int64 usage = 0;
+ if (!Read(usage_file_path, &is_valid, &dirty, &usage))
+ return false;
+ *dirty_out = dirty;
+ return true;
+}
+
+bool FileSystemUsageCache::IncrementDirty(
+ const base::FilePath& usage_file_path) {
+ TRACE_EVENT0("FileSystem", "UsageCache::IncrementDirty");
+ DCHECK(CalledOnValidThread());
+ bool is_valid = true;
+ uint32 dirty = 0;
+ int64 usage = 0;
+ bool new_handle = !HasCacheFileHandle(usage_file_path);
+ if (!Read(usage_file_path, &is_valid, &dirty, &usage))
+ return false;
+
+ bool success = Write(usage_file_path, is_valid, dirty + 1, usage);
+ if (success && dirty == 0 && new_handle)
+ FlushFile(usage_file_path);
+ return success;
+}
+
+bool FileSystemUsageCache::DecrementDirty(
+ const base::FilePath& usage_file_path) {
+ TRACE_EVENT0("FileSystem", "UsageCache::DecrementDirty");
+ DCHECK(CalledOnValidThread());
+ bool is_valid = true;
+ uint32 dirty = 0;
+ int64 usage = 0;;
+ if (!Read(usage_file_path, &is_valid, &dirty, &usage) || dirty <= 0)
+ return false;
+
+ if (dirty <= 0)
+ return false;
+
+ return Write(usage_file_path, is_valid, dirty - 1, usage);
+}
+
+bool FileSystemUsageCache::Invalidate(const base::FilePath& usage_file_path) {
+ TRACE_EVENT0("FileSystem", "UsageCache::Invalidate");
+ DCHECK(CalledOnValidThread());
+ bool is_valid = true;
+ uint32 dirty = 0;
+ int64 usage = 0;
+ if (!Read(usage_file_path, &is_valid, &dirty, &usage))
+ return false;
+
+ return Write(usage_file_path, false, dirty, usage);
+}
+
+bool FileSystemUsageCache::IsValid(const base::FilePath& usage_file_path) {
+ TRACE_EVENT0("FileSystem", "UsageCache::IsValid");
+ DCHECK(CalledOnValidThread());
+ bool is_valid = true;
+ uint32 dirty = 0;
+ int64 usage = 0;
+ if (!Read(usage_file_path, &is_valid, &dirty, &usage))
+ return false;
+ return is_valid;
+}
+
+bool FileSystemUsageCache::AtomicUpdateUsageByDelta(
+ const base::FilePath& usage_file_path, int64 delta) {
+ TRACE_EVENT0("FileSystem", "UsageCache::AtomicUpdateUsageByDelta");
+ DCHECK(CalledOnValidThread());
+ bool is_valid = true;
+ uint32 dirty = 0;
+ int64 usage = 0;;
+ if (!Read(usage_file_path, &is_valid, &dirty, &usage))
+ return false;
+ return Write(usage_file_path, is_valid, dirty, usage + delta);
+}
+
+bool FileSystemUsageCache::UpdateUsage(const base::FilePath& usage_file_path,
+ int64 fs_usage) {
+ TRACE_EVENT0("FileSystem", "UsageCache::UpdateUsage");
+ DCHECK(CalledOnValidThread());
+ return Write(usage_file_path, true, 0, fs_usage);
+}
+
+bool FileSystemUsageCache::Exists(const base::FilePath& usage_file_path) {
+ TRACE_EVENT0("FileSystem", "UsageCache::Exists");
+ DCHECK(CalledOnValidThread());
+ return base::PathExists(usage_file_path);
+}
+
+bool FileSystemUsageCache::Delete(const base::FilePath& usage_file_path) {
+ TRACE_EVENT0("FileSystem", "UsageCache::Delete");
+ DCHECK(CalledOnValidThread());
+ CloseCacheFiles();
+ return base::DeleteFile(usage_file_path, true);
+}
+
+void FileSystemUsageCache::CloseCacheFiles() {
+ TRACE_EVENT0("FileSystem", "UsageCache::CloseCacheFiles");
+ DCHECK(CalledOnValidThread());
+ for (CacheFiles::iterator itr = cache_files_.begin();
+ itr != cache_files_.end(); ++itr) {
+ if (itr->second != base::kInvalidPlatformFileValue)
+ base::ClosePlatformFile(itr->second);
+ }
+ cache_files_.clear();
+ timer_.reset();
+}
+
+bool FileSystemUsageCache::Read(const base::FilePath& usage_file_path,
+ bool* is_valid,
+ uint32* dirty_out,
+ int64* usage_out) {
+ TRACE_EVENT0("FileSystem", "UsageCache::Read");
+ DCHECK(CalledOnValidThread());
+ DCHECK(is_valid);
+ DCHECK(dirty_out);
+ DCHECK(usage_out);
+ char buffer[kUsageFileSize];
+ const char *header;
+ if (usage_file_path.empty() ||
+ !ReadBytes(usage_file_path, buffer, kUsageFileSize))
+ return false;
+ Pickle read_pickle(buffer, kUsageFileSize);
+ PickleIterator iter(read_pickle);
+ uint32 dirty = 0;
+ int64 usage = 0;
+
+ if (!read_pickle.ReadBytes(&iter, &header, kUsageFileHeaderSize) ||
+ !read_pickle.ReadBool(&iter, is_valid) ||
+ !read_pickle.ReadUInt32(&iter, &dirty) ||
+ !read_pickle.ReadInt64(&iter, &usage))
+ return false;
+
+ if (header[0] != kUsageFileHeader[0] ||
+ header[1] != kUsageFileHeader[1] ||
+ header[2] != kUsageFileHeader[2] ||
+ header[3] != kUsageFileHeader[3])
+ return false;
+
+ *dirty_out = dirty;
+ *usage_out = usage;
+ return true;
+}
+
+bool FileSystemUsageCache::Write(const base::FilePath& usage_file_path,
+ bool is_valid,
+ int32 dirty,
+ int64 usage) {
+ TRACE_EVENT0("FileSystem", "UsageCache::Write");
+ DCHECK(CalledOnValidThread());
+ Pickle write_pickle;
+ write_pickle.WriteBytes(kUsageFileHeader, kUsageFileHeaderSize);
+ write_pickle.WriteBool(is_valid);
+ write_pickle.WriteUInt32(dirty);
+ write_pickle.WriteInt64(usage);
+
+ if (!WriteBytes(usage_file_path,
+ static_cast<const char*>(write_pickle.data()),
+ write_pickle.size())) {
+ Delete(usage_file_path);
+ return false;
+ }
+ return true;
+}
+
+bool FileSystemUsageCache::GetPlatformFile(const base::FilePath& file_path,
+ base::PlatformFile* file) {
+ DCHECK(CalledOnValidThread());
+ if (cache_files_.size() >= kMaxHandleCacheSize)
+ CloseCacheFiles();
+ ScheduleCloseTimer();
+
+ std::pair<CacheFiles::iterator, bool> inserted =
+ cache_files_.insert(
+ std::make_pair(file_path, base::kInvalidPlatformFileValue));
+ if (!inserted.second) {
+ *file = inserted.first->second;
+ return true;
+ }
+
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::PlatformFile platform_file =
+ base::CreatePlatformFile(file_path,
+ base::PLATFORM_FILE_OPEN_ALWAYS |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE,
+ NULL, &error);
+ if (error != base::PLATFORM_FILE_OK) {
+ cache_files_.erase(inserted.first);
+ return false;
+ }
+
+ inserted.first->second = platform_file;
+ *file = platform_file;
+ return true;
+}
+
+bool FileSystemUsageCache::ReadBytes(const base::FilePath& file_path,
+ char* buffer,
+ int64 buffer_size) {
+ DCHECK(CalledOnValidThread());
+ base::PlatformFile file;
+ if (!GetPlatformFile(file_path, &file))
+ return false;
+ return base::ReadPlatformFile(file, 0, buffer, buffer_size) == buffer_size;
+}
+
+bool FileSystemUsageCache::WriteBytes(const base::FilePath& file_path,
+ const char* buffer,
+ int64 buffer_size) {
+ DCHECK(CalledOnValidThread());
+ base::PlatformFile file;
+ if (!GetPlatformFile(file_path, &file))
+ return false;
+ return base::WritePlatformFile(file, 0, buffer, buffer_size) == buffer_size;
+}
+
+bool FileSystemUsageCache::FlushFile(const base::FilePath& file_path) {
+ TRACE_EVENT0("FileSystem", "UsageCache::FlushFile");
+ DCHECK(CalledOnValidThread());
+ base::PlatformFile file = base::kInvalidPlatformFileValue;
+ return GetPlatformFile(file_path, &file) && base::FlushPlatformFile(file);
+}
+
+void FileSystemUsageCache::ScheduleCloseTimer() {
+ DCHECK(CalledOnValidThread());
+ if (!timer_)
+ timer_.reset(new TimedTaskHelper(task_runner_.get()));
+
+ if (timer_->IsRunning()) {
+ timer_->Reset();
+ return;
+ }
+
+ timer_->Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(kCloseDelaySeconds),
+ base::Bind(&FileSystemUsageCache::CloseCacheFiles,
+ weak_factory_.GetWeakPtr()));
+}
+
+bool FileSystemUsageCache::CalledOnValidThread() {
+ return !task_runner_.get() || task_runner_->RunsTasksOnCurrentThread();
+}
+
+bool FileSystemUsageCache::HasCacheFileHandle(const base::FilePath& file_path) {
+ DCHECK(CalledOnValidThread());
+ DCHECK_LE(cache_files_.size(), kMaxHandleCacheSize);
+ return ContainsKey(cache_files_, file_path);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_system_usage_cache.h b/chromium/webkit/browser/fileapi/file_system_usage_cache.h
new file mode 100644
index 00000000000..b4d8464884b
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_usage_cache.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/platform_file.h"
+#include "base/sequenced_task_runner.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace fileapi {
+
+class TimedTaskHelper;
+
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileSystemUsageCache {
+ public:
+ explicit FileSystemUsageCache(base::SequencedTaskRunner* task_runner);
+ ~FileSystemUsageCache();
+
+ // Gets the size described in the .usage file even if dirty > 0 or
+ // is_valid == false. Returns true if the .usage file is available.
+ bool GetUsage(const base::FilePath& usage_file_path, int64* usage);
+
+ // Gets the dirty count in the .usage file.
+ // Returns true if the .usage file is available.
+ bool GetDirty(const base::FilePath& usage_file_path, uint32* dirty);
+
+ // Increments or decrements the "dirty" entry in the .usage file.
+ // Returns false if no .usage is available.
+ bool IncrementDirty(const base::FilePath& usage_file_path);
+ bool DecrementDirty(const base::FilePath& usage_file_path);
+
+ // Notifies quota system that it needs to recalculate the usage cache of the
+ // origin. Returns false if no .usage is available.
+ bool Invalidate(const base::FilePath& usage_file_path);
+ bool IsValid(const base::FilePath& usage_file_path);
+
+ // Updates the size described in the .usage file.
+ bool UpdateUsage(const base::FilePath& usage_file_path, int64 fs_usage);
+
+ // Updates the size described in the .usage file by delta with keeping dirty
+ // even if dirty > 0.
+ bool AtomicUpdateUsageByDelta(const base::FilePath& usage_file_path,
+ int64 delta);
+
+ bool Exists(const base::FilePath& usage_file_path);
+ bool Delete(const base::FilePath& usage_file_path);
+
+ void CloseCacheFiles();
+
+ static const base::FilePath::CharType kUsageFileName[];
+ static const char kUsageFileHeader[];
+ static const int kUsageFileSize;
+ static const int kUsageFileHeaderSize;
+
+ private:
+ typedef std::map<base::FilePath, base::PlatformFile> CacheFiles;
+
+ // Read the size, validity and the "dirty" entry described in the .usage file.
+ // Returns less than zero if no .usage file is available.
+ bool Read(const base::FilePath& usage_file_path,
+ bool* is_valid,
+ uint32* dirty,
+ int64* usage);
+
+ bool Write(const base::FilePath& usage_file_path,
+ bool is_valid,
+ int32 dirty,
+ int64 fs_usage);
+
+ bool GetPlatformFile(const base::FilePath& file_path,
+ base::PlatformFile* file);
+
+ bool ReadBytes(const base::FilePath& file_path,
+ char* buffer,
+ int64 buffer_size);
+ bool WriteBytes(const base::FilePath& file_path,
+ const char* buffer,
+ int64 buffer_size);
+ bool FlushFile(const base::FilePath& file_path);
+ void ScheduleCloseTimer();
+
+ bool HasCacheFileHandle(const base::FilePath& file_path);
+
+ bool CalledOnValidThread();
+
+ scoped_ptr<TimedTaskHelper> timer_;
+ std::map<base::FilePath, base::PlatformFile> cache_files_;
+ base::WeakPtrFactory<FileSystemUsageCache> weak_factory_;
+
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemUsageCache);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_
diff --git a/chromium/webkit/browser/fileapi/file_system_usage_cache_unittest.cc b/chromium/webkit/browser/fileapi/file_system_usage_cache_unittest.cc
new file mode 100644
index 00000000000..1b99d9c10d2
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_system_usage_cache_unittest.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace fileapi {
+
+class FileSystemUsageCacheTest : public testing::Test {
+ public:
+ FileSystemUsageCacheTest()
+ : usage_cache_(base::MessageLoopProxy::current().get()) {}
+
+ virtual void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ }
+
+ protected:
+ base::FilePath GetUsageFilePath() {
+ return data_dir_.path().Append(FileSystemUsageCache::kUsageFileName);
+ }
+
+ FileSystemUsageCache* usage_cache() {
+ return &usage_cache_;
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir data_dir_;
+ FileSystemUsageCache usage_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemUsageCacheTest);
+};
+
+TEST_F(FileSystemUsageCacheTest, CreateTest) {
+ base::FilePath usage_file_path = GetUsageFilePath();
+ EXPECT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 0));
+}
+
+TEST_F(FileSystemUsageCacheTest, SetSizeTest) {
+ static const int64 size = 240122;
+ base::FilePath usage_file_path = GetUsageFilePath();
+ int64 usage = 0;
+ ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size));
+ EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage));
+ EXPECT_EQ(size, usage);
+}
+
+TEST_F(FileSystemUsageCacheTest, SetLargeSizeTest) {
+ static const int64 size = kint64max;
+ base::FilePath usage_file_path = GetUsageFilePath();
+ int64 usage = 0;
+ ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size));
+ EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage));
+ EXPECT_EQ(size, usage);
+}
+
+TEST_F(FileSystemUsageCacheTest, IncAndGetSizeTest) {
+ base::FilePath usage_file_path = GetUsageFilePath();
+ uint32 dirty = 0;
+ int64 usage = 0;
+ ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 98214));
+ ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path));
+ EXPECT_TRUE(usage_cache()->GetDirty(usage_file_path, &dirty));
+ EXPECT_EQ(1u, dirty);
+ EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage));
+ EXPECT_EQ(98214, usage);
+}
+
+TEST_F(FileSystemUsageCacheTest, DecAndGetSizeTest) {
+ static const int64 size = 71839;
+ base::FilePath usage_file_path = GetUsageFilePath();
+ int64 usage = 0;
+ ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size));
+ // DecrementDirty for dirty = 0 is invalid. It returns false.
+ ASSERT_FALSE(usage_cache()->DecrementDirty(usage_file_path));
+ EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage));
+ EXPECT_EQ(size, usage);
+}
+
+TEST_F(FileSystemUsageCacheTest, IncDecAndGetSizeTest) {
+ static const int64 size = 198491;
+ base::FilePath usage_file_path = GetUsageFilePath();
+ int64 usage = 0;
+ ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size));
+ ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path));
+ ASSERT_TRUE(usage_cache()->DecrementDirty(usage_file_path));
+ EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage));
+ EXPECT_EQ(size, usage);
+}
+
+TEST_F(FileSystemUsageCacheTest, DecIncAndGetSizeTest) {
+ base::FilePath usage_file_path = GetUsageFilePath();
+ uint32 dirty = 0;
+ int64 usage = 0;
+ ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 854238));
+ // DecrementDirty for dirty = 0 is invalid. It returns false.
+ ASSERT_FALSE(usage_cache()->DecrementDirty(usage_file_path));
+ ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path));
+ // It tests DecrementDirty (which returns false) has no effect, i.e
+ // does not make dirty = -1 after DecrementDirty.
+ EXPECT_TRUE(usage_cache()->GetDirty(usage_file_path, &dirty));
+ EXPECT_EQ(1u, dirty);
+ EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage));
+ EXPECT_EQ(854238, usage);
+}
+
+TEST_F(FileSystemUsageCacheTest, ManyIncsSameDecsAndGetSizeTest) {
+ static const int64 size = 82412;
+ base::FilePath usage_file_path = GetUsageFilePath();
+ int64 usage = 0;
+ ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, size));
+ for (int i = 0; i < 20; i++)
+ ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path));
+ for (int i = 0; i < 20; i++)
+ ASSERT_TRUE(usage_cache()->DecrementDirty(usage_file_path));
+ EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage));
+ EXPECT_EQ(size, usage);
+}
+
+TEST_F(FileSystemUsageCacheTest, ManyIncsLessDecsAndGetSizeTest) {
+ uint32 dirty = 0;
+ int64 usage = 0;
+ base::FilePath usage_file_path = GetUsageFilePath();
+ ASSERT_TRUE(usage_cache()->UpdateUsage(usage_file_path, 19319));
+ for (int i = 0; i < 20; i++)
+ ASSERT_TRUE(usage_cache()->IncrementDirty(usage_file_path));
+ for (int i = 0; i < 19; i++)
+ ASSERT_TRUE(usage_cache()->DecrementDirty(usage_file_path));
+ EXPECT_TRUE(usage_cache()->GetDirty(usage_file_path, &dirty));
+ EXPECT_EQ(1u, dirty);
+ EXPECT_TRUE(usage_cache()->GetUsage(usage_file_path, &usage));
+ EXPECT_EQ(19319, usage);
+}
+
+TEST_F(FileSystemUsageCacheTest, GetSizeWithoutCacheFileTest) {
+ int64 usage = 0;
+ base::FilePath usage_file_path = GetUsageFilePath();
+ EXPECT_FALSE(usage_cache()->GetUsage(usage_file_path, &usage));
+}
+
+TEST_F(FileSystemUsageCacheTest, IncrementDirtyWithoutCacheFileTest) {
+ base::FilePath usage_file_path = GetUsageFilePath();
+ EXPECT_FALSE(usage_cache()->IncrementDirty(usage_file_path));
+}
+
+TEST_F(FileSystemUsageCacheTest, DecrementDirtyWithoutCacheFileTest) {
+ base::FilePath usage_file_path = GetUsageFilePath();
+ EXPECT_FALSE(usage_cache()->IncrementDirty(usage_file_path));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_writer_delegate.cc b/chromium/webkit/browser/fileapi/file_writer_delegate.cc
new file mode 100644
index 00000000000..7a0094ee4d5
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_writer_delegate.cc
@@ -0,0 +1,254 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/file_writer_delegate.h"
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_util_proxy.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread_restrictions.h"
+#include "net/base/net_errors.h"
+#include "webkit/browser/fileapi/file_stream_writer.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+
+namespace fileapi {
+
+static const int kReadBufSize = 32768;
+
+namespace {
+
+base::PlatformFileError NetErrorToPlatformFileError(int error) {
+// TODO(kinuko): Move this static method to more convenient place.
+ switch (error) {
+ case net::OK:
+ return base::PLATFORM_FILE_OK;
+ case net::ERR_FILE_NO_SPACE:
+ return base::PLATFORM_FILE_ERROR_NO_SPACE;
+ case net::ERR_FILE_NOT_FOUND:
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ case net::ERR_ACCESS_DENIED:
+ return base::PLATFORM_FILE_ERROR_ACCESS_DENIED;
+ default:
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+}
+
+} // namespace
+
+FileWriterDelegate::FileWriterDelegate(
+ scoped_ptr<FileStreamWriter> file_stream_writer)
+ : file_stream_writer_(file_stream_writer.Pass()),
+ writing_started_(false),
+ bytes_written_backlog_(0),
+ bytes_written_(0),
+ bytes_read_(0),
+ io_buffer_(new net::IOBufferWithSize(kReadBufSize)),
+ weak_factory_(this) {
+}
+
+FileWriterDelegate::~FileWriterDelegate() {
+}
+
+void FileWriterDelegate::Start(scoped_ptr<net::URLRequest> request,
+ const DelegateWriteCallback& write_callback) {
+ write_callback_ = write_callback;
+ request_ = request.Pass();
+ request_->Start();
+}
+
+void FileWriterDelegate::Cancel() {
+ if (request_) {
+ // This halts any callbacks on this delegate.
+ request_->set_delegate(NULL);
+ request_->Cancel();
+ }
+
+ const int status = file_stream_writer_->Cancel(
+ base::Bind(&FileWriterDelegate::OnWriteCancelled,
+ weak_factory_.GetWeakPtr()));
+ // Return true to finish immediately if we have no pending writes.
+ // Otherwise we'll do the final cleanup in the Cancel callback.
+ if (status != net::ERR_IO_PENDING) {
+ write_callback_.Run(base::PLATFORM_FILE_ERROR_ABORT, 0,
+ GetCompletionStatusOnError());
+ }
+}
+
+void FileWriterDelegate::OnReceivedRedirect(net::URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) {
+ NOTREACHED();
+ OnError(base::PLATFORM_FILE_ERROR_SECURITY);
+}
+
+void FileWriterDelegate::OnAuthRequired(net::URLRequest* request,
+ net::AuthChallengeInfo* auth_info) {
+ NOTREACHED();
+ OnError(base::PLATFORM_FILE_ERROR_SECURITY);
+}
+
+void FileWriterDelegate::OnCertificateRequested(
+ net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_info) {
+ NOTREACHED();
+ OnError(base::PLATFORM_FILE_ERROR_SECURITY);
+}
+
+void FileWriterDelegate::OnSSLCertificateError(net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) {
+ NOTREACHED();
+ OnError(base::PLATFORM_FILE_ERROR_SECURITY);
+}
+
+void FileWriterDelegate::OnResponseStarted(net::URLRequest* request) {
+ DCHECK_EQ(request_.get(), request);
+ if (!request->status().is_success() || request->GetResponseCode() != 200) {
+ OnError(base::PLATFORM_FILE_ERROR_FAILED);
+ return;
+ }
+ Read();
+}
+
+void FileWriterDelegate::OnReadCompleted(net::URLRequest* request,
+ int bytes_read) {
+ DCHECK_EQ(request_.get(), request);
+ if (!request->status().is_success()) {
+ OnError(base::PLATFORM_FILE_ERROR_FAILED);
+ return;
+ }
+ OnDataReceived(bytes_read);
+}
+
+void FileWriterDelegate::Read() {
+ bytes_written_ = 0;
+ bytes_read_ = 0;
+ if (request_->Read(io_buffer_.get(), io_buffer_->size(), &bytes_read_)) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FileWriterDelegate::OnDataReceived,
+ weak_factory_.GetWeakPtr(), bytes_read_));
+ } else if (!request_->status().is_io_pending()) {
+ OnError(base::PLATFORM_FILE_ERROR_FAILED);
+ }
+}
+
+void FileWriterDelegate::OnDataReceived(int bytes_read) {
+ bytes_read_ = bytes_read;
+ if (!bytes_read_) { // We're done.
+ OnProgress(0, true);
+ } else {
+ // This could easily be optimized to rotate between a pool of buffers, so
+ // that we could read and write at the same time. It's not yet clear that
+ // it's necessary.
+ cursor_ = new net::DrainableIOBuffer(io_buffer_.get(), bytes_read_);
+ Write();
+ }
+}
+
+void FileWriterDelegate::Write() {
+ writing_started_ = true;
+ int64 bytes_to_write = bytes_read_ - bytes_written_;
+ int write_response =
+ file_stream_writer_->Write(cursor_.get(),
+ static_cast<int>(bytes_to_write),
+ base::Bind(&FileWriterDelegate::OnDataWritten,
+ weak_factory_.GetWeakPtr()));
+ if (write_response > 0) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FileWriterDelegate::OnDataWritten,
+ weak_factory_.GetWeakPtr(), write_response));
+ } else if (net::ERR_IO_PENDING != write_response) {
+ OnError(NetErrorToPlatformFileError(write_response));
+ }
+}
+
+void FileWriterDelegate::OnDataWritten(int write_response) {
+ if (write_response > 0) {
+ OnProgress(write_response, false);
+ cursor_->DidConsume(write_response);
+ bytes_written_ += write_response;
+ if (bytes_written_ == bytes_read_)
+ Read();
+ else
+ Write();
+ } else {
+ OnError(NetErrorToPlatformFileError(write_response));
+ }
+}
+
+FileWriterDelegate::WriteProgressStatus
+FileWriterDelegate::GetCompletionStatusOnError() const {
+ return writing_started_ ? ERROR_WRITE_STARTED : ERROR_WRITE_NOT_STARTED;
+}
+
+void FileWriterDelegate::OnError(base::PlatformFileError error) {
+ if (request_) {
+ request_->set_delegate(NULL);
+ request_->Cancel();
+ }
+
+ if (writing_started_)
+ FlushForCompletion(error, 0, ERROR_WRITE_STARTED);
+ else
+ write_callback_.Run(error, 0, ERROR_WRITE_NOT_STARTED);
+}
+
+void FileWriterDelegate::OnProgress(int bytes_written, bool done) {
+ DCHECK(bytes_written + bytes_written_backlog_ >= bytes_written_backlog_);
+ static const int kMinProgressDelayMS = 200;
+ base::Time currentTime = base::Time::Now();
+ if (done || last_progress_event_time_.is_null() ||
+ (currentTime - last_progress_event_time_).InMilliseconds() >
+ kMinProgressDelayMS) {
+ bytes_written += bytes_written_backlog_;
+ last_progress_event_time_ = currentTime;
+ bytes_written_backlog_ = 0;
+
+ if (done) {
+ FlushForCompletion(base::PLATFORM_FILE_OK, bytes_written,
+ SUCCESS_COMPLETED);
+ } else {
+ write_callback_.Run(base::PLATFORM_FILE_OK, bytes_written,
+ SUCCESS_IO_PENDING);
+ }
+ return;
+ }
+ bytes_written_backlog_ += bytes_written;
+}
+
+void FileWriterDelegate::OnWriteCancelled(int status) {
+ write_callback_.Run(base::PLATFORM_FILE_ERROR_ABORT, 0,
+ GetCompletionStatusOnError());
+}
+
+void FileWriterDelegate::FlushForCompletion(
+ base::PlatformFileError error,
+ int bytes_written,
+ WriteProgressStatus progress_status) {
+ int flush_error = file_stream_writer_->Flush(
+ base::Bind(&FileWriterDelegate::OnFlushed, weak_factory_.GetWeakPtr(),
+ error, bytes_written, progress_status));
+ if (flush_error != net::ERR_IO_PENDING)
+ OnFlushed(error, bytes_written, progress_status, flush_error);
+}
+
+void FileWriterDelegate::OnFlushed(base::PlatformFileError error,
+ int bytes_written,
+ WriteProgressStatus progress_status,
+ int flush_error) {
+ if (error == base::PLATFORM_FILE_OK && flush_error != net::OK) {
+ // If the Flush introduced an error, overwrite the status.
+ // Otherwise, keep the original error status.
+ error = NetErrorToPlatformFileError(flush_error);
+ progress_status = GetCompletionStatusOnError();
+ }
+ write_callback_.Run(error, bytes_written, progress_status);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/file_writer_delegate.h b/chromium/webkit/browser/fileapi/file_writer_delegate.h
new file mode 100644
index 00000000000..ae63e7f01de
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_writer_delegate.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_
+#define WEBKIT_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/platform_file.h"
+#include "base/time/time.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/url_request/url_request.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace fileapi {
+
+class FileStreamWriter;
+
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileWriterDelegate
+ : public net::URLRequest::Delegate {
+ public:
+ enum WriteProgressStatus {
+ SUCCESS_IO_PENDING,
+ SUCCESS_COMPLETED,
+ ERROR_WRITE_STARTED,
+ ERROR_WRITE_NOT_STARTED,
+ };
+
+ typedef base::Callback<void(base::PlatformFileError result,
+ int64 bytes,
+ WriteProgressStatus write_status)>
+ DelegateWriteCallback;
+
+ FileWriterDelegate(scoped_ptr<FileStreamWriter> file_writer);
+ virtual ~FileWriterDelegate();
+
+ void Start(scoped_ptr<net::URLRequest> request,
+ const DelegateWriteCallback& write_callback);
+
+ // Cancels the current write operation. This will synchronously or
+ // asynchronously call the given write callback (which may result in
+ // deleting this).
+ void Cancel();
+
+ virtual void OnReceivedRedirect(net::URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) OVERRIDE;
+ virtual void OnAuthRequired(net::URLRequest* request,
+ net::AuthChallengeInfo* auth_info) OVERRIDE;
+ virtual void OnCertificateRequested(
+ net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_info) OVERRIDE;
+ virtual void OnSSLCertificateError(net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) OVERRIDE;
+ virtual void OnResponseStarted(net::URLRequest* request) OVERRIDE;
+ virtual void OnReadCompleted(net::URLRequest* request,
+ int bytes_read) OVERRIDE;
+
+ private:
+ void OnGetFileInfoAndStartRequest(
+ scoped_ptr<net::URLRequest> request,
+ base::PlatformFileError error,
+ const base::PlatformFileInfo& file_info);
+ void Read();
+ void OnDataReceived(int bytes_read);
+ void Write();
+ void OnDataWritten(int write_response);
+ void OnError(base::PlatformFileError error);
+ void OnProgress(int bytes_read, bool done);
+ void OnWriteCancelled(int status);
+ void FlushForCompletion(base::PlatformFileError error,
+ int bytes_written,
+ WriteProgressStatus progress_status);
+ void OnFlushed(base::PlatformFileError error,
+ int bytes_written,
+ WriteProgressStatus progress_status,
+ int flush_error);
+
+ WriteProgressStatus GetCompletionStatusOnError() const;
+
+ DelegateWriteCallback write_callback_;
+ scoped_ptr<FileStreamWriter> file_stream_writer_;
+ base::Time last_progress_event_time_;
+ bool writing_started_;
+ int bytes_written_backlog_;
+ int bytes_written_;
+ int bytes_read_;
+ scoped_refptr<net::IOBufferWithSize> io_buffer_;
+ scoped_refptr<net::DrainableIOBuffer> cursor_;
+ scoped_ptr<net::URLRequest> request_;
+
+ base::WeakPtrFactory<FileWriterDelegate> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileWriterDelegate);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_
diff --git a/chromium/webkit/browser/fileapi/file_writer_delegate_unittest.cc b/chromium/webkit/browser/fileapi/file_writer_delegate_unittest.cc
new file mode 100644
index 00000000000..340fcb0120e
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/file_writer_delegate_unittest.cc
@@ -0,0 +1,478 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/io_buffer.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/platform_test.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_quota_util.h"
+#include "webkit/browser/fileapi/file_writer_delegate.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/sandbox_file_stream_writer.h"
+
+namespace fileapi {
+
+namespace {
+
+const GURL kOrigin("http://example.com");
+const FileSystemType kFileSystemType = kFileSystemTypeTest;
+
+const char kData[] = "The quick brown fox jumps over the lazy dog.\n";
+const int kDataSize = ARRAYSIZE_UNSAFE(kData) - 1;
+
+class Result {
+ public:
+ Result()
+ : status_(base::PLATFORM_FILE_OK),
+ bytes_written_(0),
+ write_status_(FileWriterDelegate::SUCCESS_IO_PENDING) {}
+
+ base::PlatformFileError status() const { return status_; }
+ int64 bytes_written() const { return bytes_written_; }
+ FileWriterDelegate::WriteProgressStatus write_status() const {
+ return write_status_;
+ }
+
+ void DidWrite(base::PlatformFileError status, int64 bytes,
+ FileWriterDelegate::WriteProgressStatus write_status) {
+ write_status_ = write_status;
+ if (status == base::PLATFORM_FILE_OK) {
+ bytes_written_ += bytes;
+ if (write_status_ != FileWriterDelegate::SUCCESS_IO_PENDING)
+ base::MessageLoop::current()->Quit();
+ } else {
+ EXPECT_EQ(base::PLATFORM_FILE_OK, status_);
+ status_ = status;
+ base::MessageLoop::current()->Quit();
+ }
+ }
+
+ private:
+ // For post-operation status.
+ base::PlatformFileError status_;
+ int64 bytes_written_;
+ FileWriterDelegate::WriteProgressStatus write_status_;
+};
+
+} // namespace (anonymous)
+
+class FileWriterDelegateTest : public PlatformTest {
+ public:
+ FileWriterDelegateTest()
+ : loop_(base::MessageLoop::TYPE_IO) {}
+
+ protected:
+ virtual void SetUp() OVERRIDE;
+ virtual void TearDown() OVERRIDE;
+
+ FileSystemFileUtil* file_util() {
+ return file_system_context_->GetFileUtil(kFileSystemType);
+ }
+
+ int64 usage() {
+ return file_system_context_->GetQuotaUtil(kFileSystemType)
+ ->GetOriginUsageOnFileThread(
+ file_system_context_.get(), kOrigin, kFileSystemType);
+ }
+
+ int64 GetFileSizeOnDisk(const char* test_file_path) {
+ // There might be in-flight flush/write.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&base::DoNothing));
+ base::MessageLoop::current()->RunUntilIdle();
+
+ FileSystemURL url = GetFileSystemURL(test_file_path);
+ base::PlatformFileInfo file_info;
+ base::FilePath platform_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->GetFileInfo(NewOperationContext().get(), url,
+ &file_info, &platform_path));
+ return file_info.size;
+ }
+
+ FileSystemURL GetFileSystemURL(const char* file_name) const {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name));
+ }
+
+ scoped_ptr<FileSystemOperationContext> NewOperationContext() {
+ FileSystemOperationContext* context =
+ new FileSystemOperationContext(file_system_context_.get());
+ context->set_update_observers(
+ *file_system_context_->GetUpdateObservers(kFileSystemType));
+ context->set_root_path(dir_.path());
+ return make_scoped_ptr(context);
+ }
+
+ FileWriterDelegate* CreateWriterDelegate(
+ const char* test_file_path,
+ int64 offset,
+ int64 allowed_growth) {
+ SandboxFileStreamWriter* writer = new SandboxFileStreamWriter(
+ file_system_context_.get(),
+ GetFileSystemURL(test_file_path),
+ offset,
+ *file_system_context_->GetUpdateObservers(kFileSystemType));
+ writer->set_default_quota(allowed_growth);
+ return new FileWriterDelegate(scoped_ptr<FileStreamWriter>(writer));
+ }
+
+ FileWriterDelegate::DelegateWriteCallback GetWriteCallback(Result* result) {
+ return base::Bind(&Result::DidWrite, base::Unretained(result));
+ }
+
+ // Creates and sets up a FileWriterDelegate for writing the given |blob_url|,
+ // and creates a new FileWriterDelegate for the file.
+ void PrepareForWrite(const char* test_file_path,
+ const GURL& blob_url,
+ int64 offset,
+ int64 allowed_growth) {
+ file_writer_delegate_.reset(
+ CreateWriterDelegate(test_file_path, offset, allowed_growth));
+ request_.reset(empty_context_.CreateRequest(
+ blob_url, file_writer_delegate_.get()));
+ }
+
+ static net::URLRequest::ProtocolFactory Factory;
+
+ // This should be alive until the very end of this instance.
+ base::MessageLoop loop_;
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+
+ net::URLRequestContext empty_context_;
+ scoped_ptr<FileWriterDelegate> file_writer_delegate_;
+ scoped_ptr<net::URLRequest> request_;
+
+ base::ScopedTempDir dir_;
+
+ static const char* content_;
+};
+
+const char* FileWriterDelegateTest::content_ = NULL;
+
+namespace {
+
+static std::string g_content;
+
+class FileWriterDelegateTestJob : public net::URLRequestJob {
+ public:
+ FileWriterDelegateTestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& content)
+ : net::URLRequestJob(request, network_delegate),
+ content_(content),
+ remaining_bytes_(content.length()),
+ cursor_(0) {
+ }
+
+ virtual void Start() OVERRIDE {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FileWriterDelegateTestJob::NotifyHeadersComplete, this));
+ }
+
+ virtual bool ReadRawData(net::IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) OVERRIDE {
+ if (remaining_bytes_ < buf_size)
+ buf_size = static_cast<int>(remaining_bytes_);
+
+ for (int i = 0; i < buf_size; ++i)
+ buf->data()[i] = content_[cursor_++];
+ remaining_bytes_ -= buf_size;
+
+ SetStatus(net::URLRequestStatus());
+ *bytes_read = buf_size;
+ return true;
+ }
+
+ virtual int GetResponseCode() const OVERRIDE {
+ return 200;
+ }
+
+ protected:
+ virtual ~FileWriterDelegateTestJob() {}
+
+ private:
+ std::string content_;
+ int remaining_bytes_;
+ int cursor_;
+};
+
+} // namespace (anonymous)
+
+// static
+net::URLRequestJob* FileWriterDelegateTest::Factory(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ return new FileWriterDelegateTestJob(
+ request, network_delegate, FileWriterDelegateTest::content_);
+}
+
+void FileWriterDelegateTest::SetUp() {
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+
+ file_system_context_ = CreateFileSystemContextForTesting(
+ NULL, dir_.path());
+
+ bool created = false;
+ scoped_ptr<FileSystemOperationContext> context = NewOperationContext();
+ context->set_allowed_bytes_growth(kint64max);
+ base::PlatformFileError error = file_util()->EnsureFileExists(
+ context.get(),
+ GetFileSystemURL("test"),
+ &created);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+ ASSERT_TRUE(created);
+ net::URLRequest::Deprecated::RegisterProtocolFactory("blob", &Factory);
+}
+
+void FileWriterDelegateTest::TearDown() {
+ net::URLRequest::Deprecated::RegisterProtocolFactory("blob", NULL);
+ file_system_context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimit) {
+ const GURL kBlobURL("blob:nolimit");
+ content_ = kData;
+
+ PrepareForWrite("test", kBlobURL, 0, kint64max);
+
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kDataSize, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result.status());
+}
+
+TEST_F(FileWriterDelegateTest, WriteSuccessWithJustQuota) {
+ const GURL kBlobURL("blob:just");
+ content_ = kData;
+ const int64 kAllowedGrowth = kDataSize;
+ PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth);
+
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ base::MessageLoop::current()->Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kAllowedGrowth, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+
+ EXPECT_EQ(kAllowedGrowth, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result.status());
+}
+
+TEST_F(FileWriterDelegateTest, DISABLED_WriteFailureByQuota) {
+ const GURL kBlobURL("blob:failure");
+ content_ = kData;
+ const int64 kAllowedGrowth = kDataSize - 1;
+ PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth);
+
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ base::MessageLoop::current()->Run();
+ ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kAllowedGrowth, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+
+ EXPECT_EQ(kAllowedGrowth, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, result.status());
+ ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status());
+}
+
+TEST_F(FileWriterDelegateTest, WriteZeroBytesSuccessfullyWithZeroQuota) {
+ const GURL kBlobURL("blob:zero");
+ content_ = "";
+ int64 kAllowedGrowth = 0;
+ PrepareForWrite("test", kBlobURL, 0, kAllowedGrowth);
+
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ base::MessageLoop::current()->Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kAllowedGrowth, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+
+ EXPECT_EQ(kAllowedGrowth, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result.status());
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+}
+
+TEST_F(FileWriterDelegateTest, WriteSuccessWithoutQuotaLimitConcurrent) {
+ scoped_ptr<FileWriterDelegate> file_writer_delegate2;
+ scoped_ptr<net::URLRequest> request2;
+
+ bool created = false;
+ file_util()->EnsureFileExists(NewOperationContext().get(),
+ GetFileSystemURL("test2"),
+ &created);
+ ASSERT_TRUE(created);
+
+ const GURL kBlobURL("blob:nolimitconcurrent");
+ const GURL kBlobURL2("blob:nolimitconcurrent2");
+ content_ = kData;
+
+ PrepareForWrite("test", kBlobURL, 0, kint64max);
+
+ // Credate another FileWriterDelegate for concurrent write.
+ file_writer_delegate2.reset(CreateWriterDelegate("test2", 0, kint64max));
+ request2.reset(empty_context_.CreateRequest(
+ kBlobURL2, file_writer_delegate2.get()));
+
+ Result result, result2;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ file_writer_delegate2->Start(request2.Pass(), GetWriteCallback(&result2));
+ base::MessageLoop::current()->Run();
+ if (result.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING ||
+ result2.write_status() == FileWriterDelegate::SUCCESS_IO_PENDING)
+ base::MessageLoop::current()->Run();
+
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result2.write_status());
+ file_writer_delegate_.reset();
+ file_writer_delegate2.reset();
+
+ ASSERT_EQ(kDataSize * 2, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test") + GetFileSizeOnDisk("test2"), usage());
+
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result.status());
+ EXPECT_EQ(kDataSize, result2.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result2.status());
+}
+
+TEST_F(FileWriterDelegateTest, WritesWithQuotaAndOffset) {
+ const GURL kBlobURL("blob:failure-with-updated-quota");
+ content_ = kData;
+
+ // Writing kDataSize (=45) bytes data while allowed_growth is 100.
+ int64 offset = 0;
+ int64 allowed_growth = 100;
+ ASSERT_LT(kDataSize, allowed_growth);
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+
+ {
+ Result result;
+ ASSERT_EQ(0, usage());
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ base::MessageLoop::current()->Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ ASSERT_EQ(kDataSize, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result.status());
+ }
+
+ // Trying to overwrite kDataSize bytes data while allowed_growth is 20.
+ offset = 0;
+ allowed_growth = 20;
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+
+ {
+ Result result;
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ base::MessageLoop::current()->Run();
+ EXPECT_EQ(kDataSize, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result.status());
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ }
+
+ // Trying to write kDataSize bytes data from offset 25 while
+ // allowed_growth is 55.
+ offset = 25;
+ allowed_growth = 55;
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+
+ {
+ Result result;
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ base::MessageLoop::current()->Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ EXPECT_EQ(offset + kDataSize, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result.status());
+ }
+
+ // Trying to overwrite 45 bytes data while allowed_growth is -20.
+ offset = 0;
+ allowed_growth = -20;
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+ int64 pre_write_usage = GetFileSizeOnDisk("test");
+
+ {
+ Result result;
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ base::MessageLoop::current()->Run();
+ ASSERT_EQ(FileWriterDelegate::SUCCESS_COMPLETED, result.write_status());
+ file_writer_delegate_.reset();
+
+ EXPECT_EQ(pre_write_usage, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kDataSize, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, result.status());
+ }
+
+ // Trying to overwrite 45 bytes data with offset pre_write_usage - 20,
+ // while allowed_growth is 10.
+ const int kOverlap = 20;
+ offset = pre_write_usage - kOverlap;
+ allowed_growth = 10;
+ PrepareForWrite("test", kBlobURL, offset, allowed_growth);
+
+ {
+ Result result;
+ file_writer_delegate_->Start(request_.Pass(), GetWriteCallback(&result));
+ base::MessageLoop::current()->Run();
+ ASSERT_EQ(FileWriterDelegate::ERROR_WRITE_STARTED, result.write_status());
+ file_writer_delegate_.reset();
+
+ EXPECT_EQ(pre_write_usage + allowed_growth, usage());
+ EXPECT_EQ(GetFileSizeOnDisk("test"), usage());
+ EXPECT_EQ(kOverlap + allowed_growth, result.bytes_written());
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, result.status());
+ }
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/isolated_context.cc b/chromium/webkit/browser/fileapi/isolated_context.cc
new file mode 100644
index 00000000000..d10438aa046
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/isolated_context.cc
@@ -0,0 +1,469 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/isolated_context.h"
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+namespace fileapi {
+
+namespace {
+
+base::FilePath::StringType GetRegisterNameForPath(const base::FilePath& path) {
+ // If it's not a root path simply return a base name.
+ if (path.DirName() != path)
+ return path.BaseName().value();
+
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+ base::FilePath::StringType name;
+ for (size_t i = 0;
+ i < path.value().size() && !base::FilePath::IsSeparator(path.value()[i]);
+ ++i) {
+ if (path.value()[i] == L':') {
+ name.append(L"_drive");
+ break;
+ }
+ name.append(1, path.value()[i]);
+ }
+ return name;
+#else
+ return FILE_PATH_LITERAL("<root>");
+#endif
+}
+
+bool IsSinglePathIsolatedFileSystem(FileSystemType type) {
+ switch (type) {
+ // As of writing dragged file system is the only filesystem
+ // which could have multiple top-level paths.
+ case kFileSystemTypeDragged:
+ return false;
+
+ case kFileSystemTypeUnknown:
+ NOTREACHED();
+ return true;
+
+ default:
+ return true;
+ }
+ NOTREACHED();
+ return true;
+}
+
+static base::LazyInstance<IsolatedContext>::Leaky g_isolated_context =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+IsolatedContext::FileInfoSet::FileInfoSet() {}
+IsolatedContext::FileInfoSet::~FileInfoSet() {}
+
+bool IsolatedContext::FileInfoSet::AddPath(
+ const base::FilePath& path, std::string* registered_name) {
+ // The given path should not contain any '..' and should be absolute.
+ if (path.ReferencesParent() || !path.IsAbsolute())
+ return false;
+ base::FilePath::StringType name = GetRegisterNameForPath(path);
+ std::string utf8name = base::FilePath(name).AsUTF8Unsafe();
+ base::FilePath normalized_path = path.NormalizePathSeparators();
+ bool inserted =
+ fileset_.insert(MountPointInfo(utf8name, normalized_path)).second;
+ if (!inserted) {
+ int suffix = 1;
+ std::string basepart = base::FilePath(name).RemoveExtension().AsUTF8Unsafe();
+ std::string ext = base::FilePath(base::FilePath(name).Extension()).AsUTF8Unsafe();
+ while (!inserted) {
+ utf8name = base::StringPrintf("%s (%d)", basepart.c_str(), suffix++);
+ if (!ext.empty())
+ utf8name.append(ext);
+ inserted =
+ fileset_.insert(MountPointInfo(utf8name, normalized_path)).second;
+ }
+ }
+ if (registered_name)
+ *registered_name = utf8name;
+ return true;
+}
+
+bool IsolatedContext::FileInfoSet::AddPathWithName(
+ const base::FilePath& path, const std::string& name) {
+ // The given path should not contain any '..' and should be absolute.
+ if (path.ReferencesParent() || !path.IsAbsolute())
+ return false;
+ return fileset_.insert(
+ MountPointInfo(name, path.NormalizePathSeparators())).second;
+}
+
+//--------------------------------------------------------------------------
+
+class IsolatedContext::Instance {
+ public:
+ enum PathType {
+ PLATFORM_PATH,
+ VIRTUAL_PATH
+ };
+
+ // For a single-path isolated file system, which could be registered by
+ // IsolatedContext::RegisterFileSystemForPath() or
+ // IsolatedContext::RegisterFileSystemForVirtualPath().
+ // Most of isolated file system contexts should be of this type.
+ Instance(FileSystemType type, const MountPointInfo& file_info,
+ PathType path_type);
+
+ // For a multi-paths isolated file system. As of writing only file system
+ // type which could have multi-paths is Dragged file system, and
+ // could be registered by IsolatedContext::RegisterDraggedFileSystem().
+ Instance(FileSystemType type, const std::set<MountPointInfo>& files);
+
+ ~Instance();
+
+ FileSystemType type() const { return type_; }
+ const MountPointInfo& file_info() const { return file_info_; }
+ const std::set<MountPointInfo>& files() const { return files_; }
+ int ref_counts() const { return ref_counts_; }
+
+ void AddRef() { ++ref_counts_; }
+ void RemoveRef() { --ref_counts_; }
+
+ bool ResolvePathForName(const std::string& name, base::FilePath* path) const;
+
+ // Returns true if the instance is a single-path instance.
+ bool IsSinglePathInstance() const;
+
+ private:
+ const FileSystemType type_;
+
+ // For single-path instance.
+ const MountPointInfo file_info_;
+ const PathType path_type_;
+
+ // For multiple-path instance (e.g. dragged file system).
+ const std::set<MountPointInfo> files_;
+
+ // Reference counts. Note that an isolated filesystem is created with ref==0
+ // and will get deleted when the ref count reaches <=0.
+ int ref_counts_;
+
+ DISALLOW_COPY_AND_ASSIGN(Instance);
+};
+
+IsolatedContext::Instance::Instance(FileSystemType type,
+ const MountPointInfo& file_info,
+ Instance::PathType path_type)
+ : type_(type),
+ file_info_(file_info),
+ path_type_(path_type),
+ ref_counts_(0) {
+ DCHECK(IsSinglePathIsolatedFileSystem(type_));
+}
+
+IsolatedContext::Instance::Instance(FileSystemType type,
+ const std::set<MountPointInfo>& files)
+ : type_(type),
+ path_type_(PLATFORM_PATH),
+ files_(files),
+ ref_counts_(0) {
+ DCHECK(!IsSinglePathIsolatedFileSystem(type_));
+}
+
+IsolatedContext::Instance::~Instance() {}
+
+bool IsolatedContext::Instance::ResolvePathForName(const std::string& name,
+ base::FilePath* path) const {
+ if (IsSinglePathIsolatedFileSystem(type_)) {
+ switch (path_type_) {
+ case PLATFORM_PATH:
+ *path = file_info_.path;
+ break;
+ case VIRTUAL_PATH:
+ *path = base::FilePath();
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ return file_info_.name == name;
+ }
+ std::set<MountPointInfo>::const_iterator found = files_.find(
+ MountPointInfo(name, base::FilePath()));
+ if (found == files_.end())
+ return false;
+ *path = found->path;
+ return true;
+}
+
+bool IsolatedContext::Instance::IsSinglePathInstance() const {
+ return IsSinglePathIsolatedFileSystem(type_);
+}
+
+//--------------------------------------------------------------------------
+
+// static
+IsolatedContext* IsolatedContext::GetInstance() {
+ return g_isolated_context.Pointer();
+}
+
+// static
+bool IsolatedContext::IsIsolatedType(FileSystemType type) {
+ return type == kFileSystemTypeIsolated || type == kFileSystemTypeExternal;
+}
+
+std::string IsolatedContext::RegisterDraggedFileSystem(
+ const FileInfoSet& files) {
+ base::AutoLock locker(lock_);
+ std::string filesystem_id = GetNewFileSystemId();
+ instance_map_[filesystem_id] = new Instance(
+ kFileSystemTypeDragged, files.fileset());
+ return filesystem_id;
+}
+
+std::string IsolatedContext::RegisterFileSystemForPath(
+ FileSystemType type,
+ const base::FilePath& path_in,
+ std::string* register_name) {
+ base::FilePath path(path_in.NormalizePathSeparators());
+ if (path.ReferencesParent() || !path.IsAbsolute())
+ return std::string();
+ std::string name;
+ if (register_name && !register_name->empty()) {
+ name = *register_name;
+ } else {
+ name = base::FilePath(GetRegisterNameForPath(path)).AsUTF8Unsafe();
+ if (register_name)
+ register_name->assign(name);
+ }
+
+ base::AutoLock locker(lock_);
+ std::string filesystem_id = GetNewFileSystemId();
+ instance_map_[filesystem_id] = new Instance(type, MountPointInfo(name, path),
+ Instance::PLATFORM_PATH);
+ path_to_id_map_[path].insert(filesystem_id);
+ return filesystem_id;
+}
+
+std::string IsolatedContext::RegisterFileSystemForVirtualPath(
+ FileSystemType type,
+ const std::string& register_name,
+ const base::FilePath& cracked_path_prefix) {
+ base::AutoLock locker(lock_);
+ base::FilePath path(cracked_path_prefix.NormalizePathSeparators());
+ if (path.ReferencesParent())
+ return std::string();
+ std::string filesystem_id = GetNewFileSystemId();
+ instance_map_[filesystem_id] = new Instance(
+ type,
+ MountPointInfo(register_name, cracked_path_prefix),
+ Instance::VIRTUAL_PATH);
+ path_to_id_map_[path].insert(filesystem_id);
+ return filesystem_id;
+}
+
+bool IsolatedContext::HandlesFileSystemMountType(FileSystemType type) const {
+ return type == kFileSystemTypeIsolated;
+}
+
+bool IsolatedContext::RevokeFileSystem(const std::string& filesystem_id) {
+ base::AutoLock locker(lock_);
+ return UnregisterFileSystem(filesystem_id);
+}
+
+bool IsolatedContext::GetRegisteredPath(
+ const std::string& filesystem_id, base::FilePath* path) const {
+ DCHECK(path);
+ base::AutoLock locker(lock_);
+ IDToInstance::const_iterator found = instance_map_.find(filesystem_id);
+ if (found == instance_map_.end() || !found->second->IsSinglePathInstance())
+ return false;
+ *path = found->second->file_info().path;
+ return true;
+}
+
+bool IsolatedContext::CrackVirtualPath(const base::FilePath& virtual_path,
+ std::string* id_or_name,
+ FileSystemType* type,
+ base::FilePath* path) const {
+ DCHECK(id_or_name);
+ DCHECK(path);
+
+ // This should not contain any '..' references.
+ if (virtual_path.ReferencesParent())
+ return false;
+
+ // The virtual_path should comprise <id_or_name> and <relative_path> parts.
+ std::vector<base::FilePath::StringType> components;
+ virtual_path.GetComponents(&components);
+ if (components.size() < 1)
+ return false;
+ std::vector<base::FilePath::StringType>::iterator component_iter =
+ components.begin();
+ std::string fsid = base::FilePath(*component_iter++).MaybeAsASCII();
+ if (fsid.empty())
+ return false;
+
+ base::FilePath cracked_path;
+ {
+ base::AutoLock locker(lock_);
+ IDToInstance::const_iterator found_instance = instance_map_.find(fsid);
+ if (found_instance == instance_map_.end())
+ return false;
+ *id_or_name = fsid;
+ const Instance* instance = found_instance->second;
+ if (type)
+ *type = instance->type();
+
+ if (component_iter == components.end()) {
+ // The virtual root case.
+ path->clear();
+ return true;
+ }
+
+ // *component_iter should be a name of the registered path.
+ std::string name = base::FilePath(*component_iter++).AsUTF8Unsafe();
+ if (!instance->ResolvePathForName(name, &cracked_path))
+ return false;
+ }
+
+ for (; component_iter != components.end(); ++component_iter)
+ cracked_path = cracked_path.Append(*component_iter);
+ *path = cracked_path;
+ return true;
+}
+
+FileSystemURL IsolatedContext::CrackURL(const GURL& url) const {
+ FileSystemURL filesystem_url = FileSystemURL(url);
+ if (!filesystem_url.is_valid())
+ return FileSystemURL();
+ return CrackFileSystemURL(filesystem_url);
+}
+
+FileSystemURL IsolatedContext::CreateCrackedFileSystemURL(
+ const GURL& origin,
+ FileSystemType type,
+ const base::FilePath& path) const {
+ return CrackFileSystemURL(FileSystemURL(origin, type, path));
+}
+
+void IsolatedContext::RevokeFileSystemByPath(const base::FilePath& path_in) {
+ base::AutoLock locker(lock_);
+ base::FilePath path(path_in.NormalizePathSeparators());
+ PathToID::iterator ids_iter = path_to_id_map_.find(path);
+ if (ids_iter == path_to_id_map_.end())
+ return;
+ std::set<std::string>& ids = ids_iter->second;
+ for (std::set<std::string>::iterator iter = ids.begin();
+ iter != ids.end(); ++iter) {
+ IDToInstance::iterator found = instance_map_.find(*iter);
+ if (found != instance_map_.end()) {
+ delete found->second;
+ instance_map_.erase(found);
+ }
+ }
+ path_to_id_map_.erase(ids_iter);
+}
+
+void IsolatedContext::AddReference(const std::string& filesystem_id) {
+ base::AutoLock locker(lock_);
+ DCHECK(instance_map_.find(filesystem_id) != instance_map_.end());
+ instance_map_[filesystem_id]->AddRef();
+}
+
+void IsolatedContext::RemoveReference(const std::string& filesystem_id) {
+ base::AutoLock locker(lock_);
+ // This could get called for non-existent filesystem if it has been
+ // already deleted by RevokeFileSystemByPath.
+ IDToInstance::iterator found = instance_map_.find(filesystem_id);
+ if (found == instance_map_.end())
+ return;
+ Instance* instance = found->second;
+ DCHECK_GT(instance->ref_counts(), 0);
+ instance->RemoveRef();
+ if (instance->ref_counts() == 0) {
+ bool deleted = UnregisterFileSystem(filesystem_id);
+ DCHECK(deleted);
+ }
+}
+
+bool IsolatedContext::GetDraggedFileInfo(
+ const std::string& filesystem_id,
+ std::vector<MountPointInfo>* files) const {
+ DCHECK(files);
+ base::AutoLock locker(lock_);
+ IDToInstance::const_iterator found = instance_map_.find(filesystem_id);
+ if (found == instance_map_.end() ||
+ found->second->type() != kFileSystemTypeDragged)
+ return false;
+ files->assign(found->second->files().begin(),
+ found->second->files().end());
+ return true;
+}
+
+base::FilePath IsolatedContext::CreateVirtualRootPath(
+ const std::string& filesystem_id) const {
+ return base::FilePath().AppendASCII(filesystem_id);
+}
+
+IsolatedContext::IsolatedContext() {
+}
+
+IsolatedContext::~IsolatedContext() {
+ STLDeleteContainerPairSecondPointers(instance_map_.begin(),
+ instance_map_.end());
+}
+
+FileSystemURL IsolatedContext::CrackFileSystemURL(
+ const FileSystemURL& url) const {
+ if (!HandlesFileSystemMountType(url.type()))
+ return FileSystemURL();
+
+ std::string mount_name;
+ FileSystemType cracked_type;
+ base::FilePath cracked_path;
+ if (!CrackVirtualPath(url.path(), &mount_name, &cracked_type, &cracked_path))
+ return FileSystemURL();
+
+ return FileSystemURL(
+ url.origin(), url.mount_type(), url.virtual_path(),
+ !url.filesystem_id().empty() ? url.filesystem_id() : mount_name,
+ cracked_type, cracked_path, mount_name);
+}
+
+bool IsolatedContext::UnregisterFileSystem(const std::string& filesystem_id) {
+ lock_.AssertAcquired();
+ IDToInstance::iterator found = instance_map_.find(filesystem_id);
+ if (found == instance_map_.end())
+ return false;
+ Instance* instance = found->second;
+ if (instance->IsSinglePathInstance()) {
+ PathToID::iterator ids_iter = path_to_id_map_.find(
+ instance->file_info().path);
+ DCHECK(ids_iter != path_to_id_map_.end());
+ ids_iter->second.erase(filesystem_id);
+ if (ids_iter->second.empty())
+ path_to_id_map_.erase(ids_iter);
+ }
+ delete found->second;
+ instance_map_.erase(found);
+ return true;
+}
+
+std::string IsolatedContext::GetNewFileSystemId() const {
+ // Returns an arbitrary random string which must be unique in the map.
+ lock_.AssertAcquired();
+ uint32 random_data[4];
+ std::string id;
+ do {
+ base::RandBytes(random_data, sizeof(random_data));
+ id = base::HexEncode(random_data, sizeof(random_data));
+ } while (instance_map_.find(id) != instance_map_.end());
+ return id;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/isolated_context.h b/chromium/webkit/browser/fileapi/isolated_context.h
new file mode 100644
index 00000000000..6b3f85ad98e
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/isolated_context.h
@@ -0,0 +1,197 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_
+#define WEBKIT_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/memory/singleton.h"
+#include "base/synchronization/lock.h"
+#include "webkit/browser/fileapi/mount_points.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace fileapi {
+class FileSystemURL;
+}
+
+namespace fileapi {
+
+// Manages isolated filesystem mount points which have no well-known names
+// and are identified by a string 'filesystem ID', which usually just looks
+// like random value.
+// This type of filesystem can be created on the fly and may go away when it has
+// no references from renderers.
+// Files in an isolated filesystem are registered with corresponding names and
+// identified by a filesystem URL like:
+//
+// filesystem:<origin>/isolated/<filesystem_id>/<name>/relative/path
+//
+// Some methods of this class are virtual just for mocking.
+//
+class WEBKIT_STORAGE_BROWSER_EXPORT IsolatedContext : public MountPoints {
+ public:
+ class WEBKIT_STORAGE_BROWSER_EXPORT FileInfoSet {
+ public:
+ FileInfoSet();
+ ~FileInfoSet();
+
+ // Add the given |path| to the set and populates |registered_name| with
+ // the registered name assigned for the path. |path| needs to be
+ // absolute and should not contain parent references.
+ // Return false if the |path| is not valid and could not be added.
+ bool AddPath(const base::FilePath& path, std::string* registered_name);
+
+ // Add the given |path| with the |name|.
+ // Return false if the |name| is already registered in the set or
+ // is not valid and could not be added.
+ bool AddPathWithName(const base::FilePath& path, const std::string& name);
+
+ const std::set<MountPointInfo>& fileset() const { return fileset_; }
+
+ private:
+ std::set<MountPointInfo> fileset_;
+ };
+
+ // The instance is lazily created per browser process.
+ static IsolatedContext* GetInstance();
+
+ // Returns true if the given filesystem type is managed by IsolatedContext
+ // (i.e. if the given |type| is Isolated or External).
+ // TODO(kinuko): needs a better function name.
+ static bool IsIsolatedType(FileSystemType type);
+
+ // Registers a new isolated filesystem with the given FileInfoSet |files|
+ // and returns the new filesystem_id. The files are registered with their
+ // register_name as their keys so that later we can resolve the full paths
+ // for the given name. We only expose the name and the ID for the
+ // newly created filesystem to the renderer for the sake of security.
+ //
+ // The renderer will be sending filesystem requests with a virtual path like
+ // '/<filesystem_id>/<registered_name>/<relative_path_from_the_dropped_path>'
+ // for which we could crack in the browser process by calling
+ // CrackIsolatedPath to get the full path.
+ //
+ // For example: if a dropped file has a path like '/a/b/foo' and we register
+ // the path with the name 'foo' in the newly created filesystem.
+ // Later if the context is asked to crack a virtual path like '/<fsid>/foo'
+ // it can properly return the original path '/a/b/foo' by looking up the
+ // internal mapping. Similarly if a dropped entry is a directory and its
+ // path is like '/a/b/dir' a virtual path like '/<fsid>/dir/foo' can be
+ // cracked into '/a/b/dir/foo'.
+ //
+ // Note that the path in |fileset| that contains '..' or is not an
+ // absolute path is skipped and is not registered.
+ std::string RegisterDraggedFileSystem(const FileInfoSet& files);
+
+ // Registers a new isolated filesystem for a given |path| of filesystem
+ // |type| filesystem and returns a new filesystem ID.
+ // |path| must be an absolute path which has no parent references ('..').
+ // If |register_name| is non-null and has non-empty string the path is
+ // registered as the given |register_name|, otherwise it is populated
+ // with the name internally assigned to the path.
+ std::string RegisterFileSystemForPath(FileSystemType type,
+ const base::FilePath& path,
+ std::string* register_name);
+
+ // Registers a virtual filesystem. This is different from
+ // RegisterFileSystemForPath because register_name is required, and
+ // cracked_path_prefix is allowed to be non-absolute.
+ // |register_name| is required, since we cannot infer one from the path.
+ // |cracked_path_prefix| has no parent references, but can be relative.
+ std::string RegisterFileSystemForVirtualPath(
+ FileSystemType type,
+ const std::string& register_name,
+ const base::FilePath& cracked_path_prefix);
+
+ // Revokes all filesystem(s) registered for the given path.
+ // This is assumed to be called when the registered path becomes
+ // globally invalid, e.g. when a device for the path is detached.
+ //
+ // Note that this revokes the filesystem no matter how many references it has.
+ // It is ok to call this for the path that has no associated filesystems.
+ // Note that this only works for the filesystems registered by
+ // |RegisterFileSystemForPath|.
+ void RevokeFileSystemByPath(const base::FilePath& path);
+
+ // Adds a reference to a filesystem specified by the given filesystem_id.
+ void AddReference(const std::string& filesystem_id);
+
+ // Removes a reference to a filesystem specified by the given filesystem_id.
+ // If the reference count reaches 0 the isolated context gets destroyed.
+ // It is OK to call this on the filesystem that has been already deleted
+ // (e.g. by RevokeFileSystemByPath).
+ void RemoveReference(const std::string& filesystem_id);
+
+ // Returns a set of dragged MountPointInfos registered for the
+ // |filesystem_id|.
+ // The filesystem_id must be pointing to a dragged file system
+ // (i.e. must be the one registered by RegisterDraggedFileSystem).
+ // Returns false if the |filesystem_id| is not valid.
+ bool GetDraggedFileInfo(const std::string& filesystem_id,
+ std::vector<MountPointInfo>* files) const;
+
+ // MountPoints overrides.
+ virtual bool HandlesFileSystemMountType(FileSystemType type) const OVERRIDE;
+ virtual bool RevokeFileSystem(const std::string& filesystem_id) OVERRIDE;
+ virtual bool GetRegisteredPath(const std::string& filesystem_id,
+ base::FilePath* path) const OVERRIDE;
+ virtual bool CrackVirtualPath(const base::FilePath& virtual_path,
+ std::string* filesystem_id,
+ FileSystemType* type,
+ base::FilePath* path) const OVERRIDE;
+ virtual FileSystemURL CrackURL(const GURL& url) const OVERRIDE;
+ virtual FileSystemURL CreateCrackedFileSystemURL(
+ const GURL& origin,
+ FileSystemType type,
+ const base::FilePath& path) const OVERRIDE;
+
+ // Returns the virtual root path that looks like /<filesystem_id>.
+ base::FilePath CreateVirtualRootPath(const std::string& filesystem_id) const;
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<IsolatedContext>;
+
+ // Represents each file system instance (defined in the .cc).
+ class Instance;
+
+ typedef std::map<std::string, Instance*> IDToInstance;
+
+ // Reverse map from registered path to IDs.
+ typedef std::map<base::FilePath, std::set<std::string> > PathToID;
+
+ // Obtain an instance of this class via GetInstance().
+ IsolatedContext();
+ virtual ~IsolatedContext();
+
+ // MountPoints overrides.
+ virtual FileSystemURL CrackFileSystemURL(
+ const FileSystemURL& url) const OVERRIDE;
+
+ // Unregisters a file system of given |filesystem_id|. Must be called with
+ // lock_ held. Returns true if the file system is unregistered.
+ bool UnregisterFileSystem(const std::string& filesystem_id);
+
+ // Returns a new filesystem_id. Called with lock.
+ std::string GetNewFileSystemId() const;
+
+ // This lock needs to be obtained when accessing the instance_map_.
+ mutable base::Lock lock_;
+
+ IDToInstance instance_map_;
+ PathToID path_to_id_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(IsolatedContext);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_
diff --git a/chromium/webkit/browser/fileapi/isolated_context_unittest.cc b/chromium/webkit/browser/fileapi/isolated_context_unittest.cc
new file mode 100644
index 00000000000..36073ca5f81
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/isolated_context_unittest.cc
@@ -0,0 +1,336 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+
+#define FPL(x) FILE_PATH_LITERAL(x)
+
+#if defined(FILE_PATH_USES_DRIVE_LETTERS)
+#define DRIVE FPL("C:")
+#else
+#define DRIVE
+#endif
+
+namespace fileapi {
+
+typedef IsolatedContext::MountPointInfo FileInfo;
+
+namespace {
+
+const base::FilePath kTestPaths[] = {
+ base::FilePath(DRIVE FPL("/a/b.txt")),
+ base::FilePath(DRIVE FPL("/c/d/e")),
+ base::FilePath(DRIVE FPL("/h/")),
+ base::FilePath(DRIVE FPL("/")),
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+ base::FilePath(DRIVE FPL("\\foo\\bar")),
+ base::FilePath(DRIVE FPL("\\")),
+#endif
+ // For duplicated base name test.
+ base::FilePath(DRIVE FPL("/")),
+ base::FilePath(DRIVE FPL("/f/e")),
+ base::FilePath(DRIVE FPL("/f/b.txt")),
+};
+
+} // namespace
+
+class IsolatedContextTest : public testing::Test {
+ public:
+ IsolatedContextTest() {
+ for (size_t i = 0; i < arraysize(kTestPaths); ++i)
+ fileset_.insert(kTestPaths[i].NormalizePathSeparators());
+ }
+
+ virtual void SetUp() {
+ IsolatedContext::FileInfoSet files;
+ for (size_t i = 0; i < arraysize(kTestPaths); ++i) {
+ std::string name;
+ ASSERT_TRUE(
+ files.AddPath(kTestPaths[i].NormalizePathSeparators(), &name));
+ names_.push_back(name);
+ }
+ id_ = IsolatedContext::GetInstance()->RegisterDraggedFileSystem(files);
+ IsolatedContext::GetInstance()->AddReference(id_);
+ ASSERT_FALSE(id_.empty());
+ }
+
+ virtual void TearDown() {
+ IsolatedContext::GetInstance()->RemoveReference(id_);
+ }
+
+ IsolatedContext* isolated_context() const {
+ return IsolatedContext::GetInstance();
+ }
+
+ protected:
+ std::string id_;
+ std::multiset<base::FilePath> fileset_;
+ std::vector<std::string> names_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(IsolatedContextTest);
+};
+
+TEST_F(IsolatedContextTest, RegisterAndRevokeTest) {
+ // See if the returned top-level entries match with what we registered.
+ std::vector<FileInfo> toplevels;
+ ASSERT_TRUE(isolated_context()->GetDraggedFileInfo(id_, &toplevels));
+ ASSERT_EQ(fileset_.size(), toplevels.size());
+ for (size_t i = 0; i < toplevels.size(); ++i) {
+ ASSERT_TRUE(fileset_.find(toplevels[i].path) != fileset_.end());
+ }
+
+ // See if the name of each registered kTestPaths (that is what we
+ // register in SetUp() by RegisterDraggedFileSystem) is properly cracked as
+ // a valid virtual path in the isolated filesystem.
+ for (size_t i = 0; i < arraysize(kTestPaths); ++i) {
+ base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_)
+ .AppendASCII(names_[i]);
+ std::string cracked_id;
+ base::FilePath cracked_path;
+ FileSystemType cracked_type;
+ ASSERT_TRUE(isolated_context()->CrackVirtualPath(
+ virtual_path, &cracked_id, &cracked_type, &cracked_path));
+ ASSERT_EQ(kTestPaths[i].NormalizePathSeparators().value(),
+ cracked_path.value());
+ ASSERT_EQ(id_, cracked_id);
+ ASSERT_EQ(kFileSystemTypeDragged, cracked_type);
+ }
+
+ // Make sure GetRegisteredPath returns false for id_ since it is
+ // registered for dragged files.
+ base::FilePath path;
+ ASSERT_FALSE(isolated_context()->GetRegisteredPath(id_, &path));
+
+ // Deref the current one and registering a new one.
+ isolated_context()->RemoveReference(id_);
+
+ std::string id2 = isolated_context()->RegisterFileSystemForPath(
+ kFileSystemTypeNativeLocal, base::FilePath(DRIVE FPL("/foo")), NULL);
+
+ // Make sure the GetDraggedFileInfo returns false for both ones.
+ ASSERT_FALSE(isolated_context()->GetDraggedFileInfo(id2, &toplevels));
+ ASSERT_FALSE(isolated_context()->GetDraggedFileInfo(id_, &toplevels));
+
+ // Make sure the GetRegisteredPath returns true only for the new one.
+ ASSERT_FALSE(isolated_context()->GetRegisteredPath(id_, &path));
+ ASSERT_TRUE(isolated_context()->GetRegisteredPath(id2, &path));
+
+ // Try registering three more file systems for the same path as id2.
+ std::string id3 = isolated_context()->RegisterFileSystemForPath(
+ kFileSystemTypeNativeLocal, path, NULL);
+ std::string id4 = isolated_context()->RegisterFileSystemForPath(
+ kFileSystemTypeNativeLocal, path, NULL);
+ std::string id5 = isolated_context()->RegisterFileSystemForPath(
+ kFileSystemTypeNativeLocal, path, NULL);
+
+ // Remove file system for id4.
+ isolated_context()->AddReference(id4);
+ isolated_context()->RemoveReference(id4);
+
+ // Only id4 should become invalid now.
+ ASSERT_TRUE(isolated_context()->GetRegisteredPath(id2, &path));
+ ASSERT_TRUE(isolated_context()->GetRegisteredPath(id3, &path));
+ ASSERT_FALSE(isolated_context()->GetRegisteredPath(id4, &path));
+ ASSERT_TRUE(isolated_context()->GetRegisteredPath(id5, &path));
+
+ // Revoke file system id5, after adding multiple references.
+ isolated_context()->AddReference(id5);
+ isolated_context()->AddReference(id5);
+ isolated_context()->AddReference(id5);
+ isolated_context()->RevokeFileSystem(id5);
+
+ // No matter how many references we add id5 must be invalid now.
+ ASSERT_TRUE(isolated_context()->GetRegisteredPath(id2, &path));
+ ASSERT_TRUE(isolated_context()->GetRegisteredPath(id3, &path));
+ ASSERT_FALSE(isolated_context()->GetRegisteredPath(id4, &path));
+ ASSERT_FALSE(isolated_context()->GetRegisteredPath(id5, &path));
+
+ // Revoke the file systems by path.
+ isolated_context()->RevokeFileSystemByPath(path);
+
+ // Now all the file systems associated to the path must be invalid.
+ ASSERT_FALSE(isolated_context()->GetRegisteredPath(id2, &path));
+ ASSERT_FALSE(isolated_context()->GetRegisteredPath(id3, &path));
+ ASSERT_FALSE(isolated_context()->GetRegisteredPath(id4, &path));
+ ASSERT_FALSE(isolated_context()->GetRegisteredPath(id5, &path));
+}
+
+TEST_F(IsolatedContextTest, CrackWithRelativePaths) {
+ const struct {
+ base::FilePath::StringType path;
+ bool valid;
+ } relatives[] = {
+ { FPL("foo"), true },
+ { FPL("foo/bar"), true },
+ { FPL(".."), false },
+ { FPL("foo/.."), false },
+ { FPL("foo/../bar"), false },
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+# define SHOULD_FAIL_WITH_WIN_SEPARATORS false
+#else
+# define SHOULD_FAIL_WITH_WIN_SEPARATORS true
+#endif
+ { FPL("foo\\..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS },
+ { FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS },
+ };
+
+ for (size_t i = 0; i < arraysize(kTestPaths); ++i) {
+ for (size_t j = 0; j < ARRAYSIZE_UNSAFE(relatives); ++j) {
+ SCOPED_TRACE(testing::Message() << "Testing "
+ << kTestPaths[i].value() << " " << relatives[j].path);
+ base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_)
+ .AppendASCII(names_[i]).Append(relatives[j].path);
+ std::string cracked_id;
+ base::FilePath cracked_path;
+ FileSystemType cracked_type;
+ if (!relatives[j].valid) {
+ ASSERT_FALSE(isolated_context()->CrackVirtualPath(
+ virtual_path, &cracked_id, &cracked_type, &cracked_path));
+ continue;
+ }
+ ASSERT_TRUE(isolated_context()->CrackVirtualPath(
+ virtual_path, &cracked_id, &cracked_type, &cracked_path));
+ ASSERT_EQ(kTestPaths[i].Append(relatives[j].path)
+ .NormalizePathSeparators().value(),
+ cracked_path.value());
+ ASSERT_EQ(id_, cracked_id);
+ ASSERT_EQ(kFileSystemTypeDragged, cracked_type);
+ }
+ }
+}
+
+TEST_F(IsolatedContextTest, CrackURLWithRelativePaths) {
+ const struct {
+ base::FilePath::StringType path;
+ bool valid;
+ } relatives[] = {
+ { FPL("foo"), true },
+ { FPL("foo/bar"), true },
+ { FPL(".."), false },
+ { FPL("foo/.."), false },
+ { FPL("foo/../bar"), false },
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+# define SHOULD_FAIL_WITH_WIN_SEPARATORS false
+#else
+# define SHOULD_FAIL_WITH_WIN_SEPARATORS true
+#endif
+ { FPL("foo\\..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS },
+ { FPL("foo/..\\baz"), SHOULD_FAIL_WITH_WIN_SEPARATORS },
+ };
+
+ for (size_t i = 0; i < arraysize(kTestPaths); ++i) {
+ for (size_t j = 0; j < ARRAYSIZE_UNSAFE(relatives); ++j) {
+ SCOPED_TRACE(testing::Message() << "Testing "
+ << kTestPaths[i].value() << " " << relatives[j].path);
+ base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_)
+ .AppendASCII(names_[i]).Append(relatives[j].path);
+
+ FileSystemURL cracked = isolated_context()->CreateCrackedFileSystemURL(
+ GURL("http://chromium.org"), kFileSystemTypeIsolated, virtual_path);
+
+ ASSERT_EQ(relatives[j].valid, cracked.is_valid());
+
+ if (!relatives[j].valid)
+ continue;
+ ASSERT_EQ(GURL("http://chromium.org"), cracked.origin());
+ ASSERT_EQ(kTestPaths[i].Append(relatives[j].path)
+ .NormalizePathSeparators().value(),
+ cracked.path().value());
+ ASSERT_EQ(virtual_path.NormalizePathSeparators(), cracked.virtual_path());
+ ASSERT_EQ(id_, cracked.filesystem_id());
+ ASSERT_EQ(kFileSystemTypeDragged, cracked.type());
+ ASSERT_EQ(kFileSystemTypeIsolated, cracked.mount_type());
+ }
+ }
+}
+
+TEST_F(IsolatedContextTest, TestWithVirtualRoot) {
+ std::string cracked_id;
+ base::FilePath cracked_path;
+
+ // Trying to crack virtual root "/" returns true but with empty cracked path
+ // as "/" of the isolated filesystem is a pure virtual directory
+ // that has no corresponding platform directory.
+ base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(id_);
+ ASSERT_TRUE(isolated_context()->CrackVirtualPath(
+ virtual_path, &cracked_id, NULL, &cracked_path));
+ ASSERT_EQ(FPL(""), cracked_path.value());
+ ASSERT_EQ(id_, cracked_id);
+
+ // Trying to crack "/foo" should fail (because "foo" is not the one
+ // included in the kTestPaths).
+ virtual_path = isolated_context()->CreateVirtualRootPath(
+ id_).AppendASCII("foo");
+ ASSERT_FALSE(isolated_context()->CrackVirtualPath(
+ virtual_path, &cracked_id, NULL, &cracked_path));
+}
+
+TEST_F(IsolatedContextTest, CanHandleURL) {
+ const GURL test_origin("http://chromium.org");
+ const base::FilePath test_path(FPL("/mount"));
+
+ // Should handle isolated file system.
+ EXPECT_TRUE(isolated_context()->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeIsolated));
+
+ // Shouldn't handle the rest.
+ EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeExternal));
+ EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeTemporary));
+ EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypePersistent));
+ EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeTest));
+ // Not even if it's isolated subtype.
+ EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeNativeLocal));
+ EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeDragged));
+ EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeNativeMedia));
+ EXPECT_FALSE(isolated_context()->HandlesFileSystemMountType(
+ fileapi::kFileSystemTypeDeviceMedia));
+}
+
+TEST_F(IsolatedContextTest, VirtualFileSystemTests) {
+ // Should be able to register empty and non-absolute paths
+ std::string empty_fsid = isolated_context()->RegisterFileSystemForVirtualPath(
+ fileapi::kFileSystemTypeIsolated, "_", base::FilePath());
+ std::string relative_fsid =
+ isolated_context()->RegisterFileSystemForVirtualPath(
+ fileapi::kFileSystemTypeIsolated, "_",
+ base::FilePath(FPL("relpath")));
+ ASSERT_FALSE(empty_fsid.empty());
+ ASSERT_FALSE(relative_fsid.empty());
+
+ // Make sure that filesystem root is not prepended to cracked virtual paths.
+ base::FilePath database_root = base::FilePath(DRIVE FPL("/database_path"));
+ std::string database_fsid =
+ isolated_context()->RegisterFileSystemForVirtualPath(
+ fileapi::kFileSystemTypeIsolated, "_", database_root);
+
+ base::FilePath test_virtual_path =
+ base::FilePath().AppendASCII("virtualdir").AppendASCII("virtualfile.txt");
+
+ base::FilePath whole_virtual_path =
+ isolated_context()->CreateVirtualRootPath(database_fsid)
+ .AppendASCII("_").Append(test_virtual_path);
+
+ std::string cracked_id;
+ base::FilePath cracked_path;
+ ASSERT_TRUE(isolated_context()->CrackVirtualPath(
+ whole_virtual_path, &cracked_id, NULL, &cracked_path));
+ ASSERT_EQ(database_fsid, cracked_id);
+ ASSERT_EQ(test_virtual_path, cracked_path);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/isolated_file_system_backend.cc b/chromium/webkit/browser/fileapi/isolated_file_system_backend.cc
new file mode 100644
index 00000000000..db59e35c857
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/isolated_file_system_backend.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/isolated_file_system_backend.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util_proxy.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/platform_file.h"
+#include "base/sequenced_task_runner.h"
+#include "webkit/browser/blob/local_file_stream_reader.h"
+#include "webkit/browser/fileapi/async_file_util_adapter.h"
+#include "webkit/browser/fileapi/copy_or_move_file_validator.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_stream_reader.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_operation_impl.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/fileapi/isolated_file_util.h"
+#include "webkit/browser/fileapi/local_file_stream_writer.h"
+#include "webkit/browser/fileapi/native_file_util.h"
+#include "webkit/browser/fileapi/transient_file_util.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+IsolatedFileSystemBackend::IsolatedFileSystemBackend()
+ : isolated_file_util_(new AsyncFileUtilAdapter(new IsolatedFileUtil())),
+ dragged_file_util_(new AsyncFileUtilAdapter(new DraggedFileUtil())),
+ transient_file_util_(new AsyncFileUtilAdapter(new TransientFileUtil())) {
+}
+
+IsolatedFileSystemBackend::~IsolatedFileSystemBackend() {
+}
+
+bool IsolatedFileSystemBackend::CanHandleType(FileSystemType type) const {
+ switch (type) {
+ case kFileSystemTypeIsolated:
+ case kFileSystemTypeDragged:
+ case kFileSystemTypeForTransientFile:
+ return true;
+#if !defined(OS_CHROMEOS)
+ case kFileSystemTypeNativeLocal:
+ case kFileSystemTypeNativeForPlatformApp:
+ return true;
+#endif
+ default:
+ return false;
+ }
+}
+
+void IsolatedFileSystemBackend::Initialize(FileSystemContext* context) {
+}
+
+void IsolatedFileSystemBackend::OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) {
+ // We never allow opening a new isolated FileSystem via usual OpenFileSystem.
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ GetFileSystemRootURI(origin_url, type),
+ GetFileSystemName(origin_url, type),
+ base::PLATFORM_FILE_ERROR_SECURITY));
+}
+
+FileSystemFileUtil* IsolatedFileSystemBackend::GetFileUtil(
+ FileSystemType type) {
+ switch (type) {
+ case kFileSystemTypeNativeLocal:
+ return isolated_file_util_->sync_file_util();
+ case kFileSystemTypeDragged:
+ return dragged_file_util_->sync_file_util();
+ case kFileSystemTypeForTransientFile:
+ return transient_file_util_->sync_file_util();
+ default:
+ NOTREACHED();
+ }
+ return NULL;
+}
+
+AsyncFileUtil* IsolatedFileSystemBackend::GetAsyncFileUtil(
+ FileSystemType type) {
+ switch (type) {
+ case kFileSystemTypeNativeLocal:
+ return isolated_file_util_.get();
+ case kFileSystemTypeDragged:
+ return dragged_file_util_.get();
+ case kFileSystemTypeForTransientFile:
+ return transient_file_util_.get();
+ default:
+ NOTREACHED();
+ }
+ return NULL;
+}
+
+CopyOrMoveFileValidatorFactory*
+IsolatedFileSystemBackend::GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type, base::PlatformFileError* error_code) {
+ DCHECK(error_code);
+ *error_code = base::PLATFORM_FILE_OK;
+ return NULL;
+}
+
+FileSystemOperation* IsolatedFileSystemBackend::CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::PlatformFileError* error_code) const {
+ return new FileSystemOperationImpl(
+ url, context, make_scoped_ptr(new FileSystemOperationContext(context)));
+}
+
+scoped_ptr<webkit_blob::FileStreamReader>
+IsolatedFileSystemBackend::CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const {
+ return scoped_ptr<webkit_blob::FileStreamReader>(
+ new webkit_blob::LocalFileStreamReader(
+ context->default_file_task_runner(),
+ url.path(), offset, expected_modification_time));
+}
+
+scoped_ptr<FileStreamWriter> IsolatedFileSystemBackend::CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context) const {
+ return scoped_ptr<FileStreamWriter>(new LocalFileStreamWriter(
+ context->default_file_task_runner(), url.path(), offset));
+}
+
+FileSystemQuotaUtil* IsolatedFileSystemBackend::GetQuotaUtil() {
+ // No quota support.
+ return NULL;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/isolated_file_system_backend.h b/chromium/webkit/browser/fileapi/isolated_file_system_backend.h
new file mode 100644
index 00000000000..cdc70969214
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/isolated_file_system_backend.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_
+#define WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+
+namespace fileapi {
+
+class AsyncFileUtilAdapter;
+
+class IsolatedFileSystemBackend : public FileSystemBackend {
+ public:
+ IsolatedFileSystemBackend();
+ virtual ~IsolatedFileSystemBackend();
+
+ // FileSystemBackend implementation.
+ virtual bool CanHandleType(FileSystemType type) const OVERRIDE;
+ virtual void Initialize(FileSystemContext* context) OVERRIDE;
+ virtual void OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) OVERRIDE;
+ virtual FileSystemFileUtil* GetFileUtil(FileSystemType type) OVERRIDE;
+ virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE;
+ virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type,
+ base::PlatformFileError* error_code) OVERRIDE;
+ virtual FileSystemOperation* CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::PlatformFileError* error_code) const OVERRIDE;
+ virtual scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const OVERRIDE;
+ virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context) const OVERRIDE;
+ virtual FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE;
+
+ private:
+ scoped_ptr<AsyncFileUtilAdapter> isolated_file_util_;
+ scoped_ptr<AsyncFileUtilAdapter> dragged_file_util_;
+ scoped_ptr<AsyncFileUtilAdapter> transient_file_util_;
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_
diff --git a/chromium/webkit/browser/fileapi/isolated_file_util.cc b/chromium/webkit/browser/fileapi/isolated_file_util.cc
new file mode 100644
index 00000000000..bd2dffc009e
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/isolated_file_util.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/isolated_file_util.h"
+
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/fileapi/native_file_util.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+
+using base::PlatformFileError;
+using base::PlatformFileInfo;
+
+namespace fileapi {
+
+typedef IsolatedContext::MountPointInfo FileInfo;
+
+namespace {
+
+// Simply enumerate each path from a given fileinfo set.
+// Used to enumerate top-level paths of an isolated filesystem.
+class SetFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator {
+ public:
+ explicit SetFileEnumerator(const std::vector<FileInfo>& files)
+ : files_(files) {
+ file_iter_ = files_.begin();
+ }
+ virtual ~SetFileEnumerator() {}
+
+ // AbstractFileEnumerator overrides.
+ virtual base::FilePath Next() OVERRIDE {
+ if (file_iter_ == files_.end())
+ return base::FilePath();
+ base::FilePath platform_file = (file_iter_++)->path;
+ NativeFileUtil::GetFileInfo(platform_file, &file_info_);
+ return platform_file;
+ }
+ virtual int64 Size() OVERRIDE { return file_info_.size; }
+ virtual bool IsDirectory() OVERRIDE { return file_info_.is_directory; }
+ virtual base::Time LastModifiedTime() OVERRIDE {
+ return file_info_.last_modified;
+ }
+
+ private:
+ std::vector<FileInfo> files_;
+ std::vector<FileInfo>::const_iterator file_iter_;
+ base::PlatformFileInfo file_info_;
+};
+
+} // namespace
+
+//-------------------------------------------------------------------------
+
+IsolatedFileUtil::IsolatedFileUtil() {}
+
+PlatformFileError IsolatedFileUtil::GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::FilePath* local_file_path) {
+ DCHECK(local_file_path);
+ DCHECK(url.is_valid());
+ if (url.path().empty()) {
+ // Root direcory case, which should not be accessed.
+ return base::PLATFORM_FILE_ERROR_ACCESS_DENIED;
+ }
+ *local_file_path = url.path();
+ return base::PLATFORM_FILE_OK;
+}
+
+//-------------------------------------------------------------------------
+
+DraggedFileUtil::DraggedFileUtil() {}
+
+PlatformFileError DraggedFileUtil::GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ PlatformFileInfo* file_info,
+ base::FilePath* platform_path) {
+ DCHECK(file_info);
+ std::string filesystem_id;
+ DCHECK(url.is_valid());
+ if (url.path().empty()) {
+ // The root directory case.
+ // For now we leave three time fields (modified/accessed/creation time)
+ // NULL as it is not really clear what to be set for this virtual directory.
+ // TODO(kinuko): Maybe we want to set the time when this filesystem is
+ // created (i.e. when the files/directories are dropped).
+ file_info->is_directory = true;
+ file_info->is_symbolic_link = false;
+ file_info->size = 0;
+ return base::PLATFORM_FILE_OK;
+ }
+ base::PlatformFileError error =
+ NativeFileUtil::GetFileInfo(url.path(), file_info);
+ if (file_util::IsLink(url.path()) && !base::FilePath().IsParent(url.path())) {
+ // Don't follow symlinks unless it's the one that are selected by the user.
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ }
+ if (error == base::PLATFORM_FILE_OK)
+ *platform_path = url.path();
+ return error;
+}
+
+scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator>
+ DraggedFileUtil::CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root) {
+ DCHECK(root.is_valid());
+ if (!root.path().empty())
+ return LocalFileUtil::CreateFileEnumerator(context, root);
+
+ // Root path case.
+ std::vector<FileInfo> toplevels;
+ IsolatedContext::GetInstance()->GetDraggedFileInfo(
+ root.filesystem_id(), &toplevels);
+ return scoped_ptr<AbstractFileEnumerator>(new SetFileEnumerator(toplevels));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/isolated_file_util.h b/chromium/webkit/browser/fileapi/isolated_file_util.h
new file mode 100644
index 00000000000..e6f595572c4
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/isolated_file_util.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_UTIL_H_
+#define WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_UTIL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "webkit/browser/fileapi/local_file_util.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace fileapi {
+
+class FileSystemOperationContext;
+
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE IsolatedFileUtil
+ : public LocalFileUtil {
+ public:
+ IsolatedFileUtil();
+ virtual ~IsolatedFileUtil() {}
+
+ // LocalFileUtil overrides.
+ virtual base::PlatformFileError GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& file_system_url,
+ base::FilePath* local_file_path) OVERRIDE;
+};
+
+// Dragged file system is a specialized IsolatedFileUtil where read access to
+// the virtual root directory (i.e. empty cracked path case) is allowed
+// and single isolated context may be associated with multiple file paths.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE DraggedFileUtil
+ : public IsolatedFileUtil {
+ public:
+ DraggedFileUtil();
+ virtual ~DraggedFileUtil() {}
+
+ // FileSystemFileUtil overrides.
+ virtual base::PlatformFileError GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_path) OVERRIDE;
+ virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DraggedFileUtil);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_ISOLATED_FILE_UTIL_H_
diff --git a/chromium/webkit/browser/fileapi/isolated_file_util_unittest.cc b/chromium/webkit/browser/fileapi/isolated_file_util_unittest.cc
new file mode 100644
index 00000000000..fee8ed1497d
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/isolated_file_util_unittest.cc
@@ -0,0 +1,547 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <map>
+#include <queue>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/async_file_test_helper.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/fileapi/isolated_file_util.h"
+#include "webkit/browser/fileapi/local_file_util.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/native_file_util.h"
+#include "webkit/browser/fileapi/test_file_set.h"
+
+namespace fileapi {
+
+namespace {
+
+typedef AsyncFileTestHelper::FileEntryList FileEntryList;
+
+// Used in IsolatedFileUtilTest::SimulateDropFiles().
+// Random root paths in which we create each file/directory of the
+// RegularTestCases (so that we can simulate a drop with files/directories
+// from multiple directories).
+static const base::FilePath::CharType* kRootPaths[] = {
+ FILE_PATH_LITERAL("a"),
+ FILE_PATH_LITERAL("b/c"),
+ FILE_PATH_LITERAL("etc"),
+};
+
+base::FilePath GetTopLevelPath(const base::FilePath& path) {
+ std::vector<base::FilePath::StringType> components;
+ path.GetComponents(&components);
+ return base::FilePath(components[0]);
+}
+
+bool IsDirectoryEmpty(FileSystemContext* context, const FileSystemURL& url) {
+ FileEntryList entries;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(context, url, &entries));
+ return entries.empty();
+}
+
+FileSystemURL GetEntryURL(FileSystemContext* file_system_context,
+ const FileSystemURL& dir,
+ const base::FilePath::StringType& name) {
+ return file_system_context->CreateCrackedFileSystemURL(
+ dir.origin(),
+ dir.mount_type(),
+ dir.virtual_path().Append(name));
+}
+
+base::FilePath GetRelativeVirtualPath(const FileSystemURL& root,
+ const FileSystemURL& url) {
+ if (root.virtual_path().empty())
+ return url.virtual_path();
+ base::FilePath relative;
+ const bool success = root.virtual_path().AppendRelativePath(
+ url.virtual_path(), &relative);
+ DCHECK(success);
+ return relative;
+}
+
+FileSystemURL GetOtherURL(FileSystemContext* file_system_context,
+ const FileSystemURL& root,
+ const FileSystemURL& other_root,
+ const FileSystemURL& url) {
+ return file_system_context->CreateCrackedFileSystemURL(
+ other_root.origin(),
+ other_root.mount_type(),
+ other_root.virtual_path().Append(GetRelativeVirtualPath(root, url)));
+}
+
+} // namespace
+
+// TODO(kinuko): we should have separate tests for DraggedFileUtil and
+// IsolatedFileUtil.
+class IsolatedFileUtilTest : public testing::Test {
+ public:
+ IsolatedFileUtilTest() {}
+
+ virtual void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(partition_dir_.CreateUniqueTempDir());
+ file_util_.reset(new DraggedFileUtil());
+
+ // Register the files/directories of RegularTestCases (with random
+ // root paths) as dropped files.
+ SimulateDropFiles();
+
+ file_system_context_ = CreateFileSystemContextForTesting(
+ NULL /* quota_manager */,
+ partition_dir_.path());
+
+ isolated_context()->AddReference(filesystem_id_);
+ }
+
+ virtual void TearDown() {
+ isolated_context()->RemoveReference(filesystem_id_);
+ }
+
+ protected:
+ IsolatedContext* isolated_context() const {
+ return IsolatedContext::GetInstance();
+ }
+ const base::FilePath& root_path() const {
+ return data_dir_.path();
+ }
+ FileSystemContext* file_system_context() const {
+ return file_system_context_.get();
+ }
+ FileSystemFileUtil* file_util() const { return file_util_.get(); }
+ std::string filesystem_id() const { return filesystem_id_; }
+
+ base::FilePath GetTestCasePlatformPath(
+ const base::FilePath::StringType& path) {
+ return toplevel_root_map_[GetTopLevelPath(base::FilePath(path))]
+ .Append(path).NormalizePathSeparators();
+ }
+
+ base::FilePath GetTestCaseLocalPath(const base::FilePath& path) {
+ base::FilePath relative;
+ if (data_dir_.path().AppendRelativePath(path, &relative))
+ return relative;
+ return path;
+ }
+
+ FileSystemURL GetFileSystemURL(const base::FilePath& path) const {
+ base::FilePath virtual_path = isolated_context()->CreateVirtualRootPath(
+ filesystem_id()).Append(path);
+ return file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://example.com"),
+ kFileSystemTypeIsolated,
+ virtual_path);
+ }
+
+ FileSystemURL GetOtherFileSystemURL(const base::FilePath& path) const {
+ return file_system_context()->CreateCrackedFileSystemURL(
+ GURL("http://example.com"),
+ kFileSystemTypeTemporary,
+ base::FilePath().AppendASCII("dest").Append(path));
+ }
+
+ void VerifyFilesHaveSameContent(const FileSystemURL& url1,
+ const FileSystemURL& url2) {
+ // Get the file info and the platform path for url1.
+ base::PlatformFileInfo info1;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::GetMetadata(
+ file_system_context(), url1, &info1));
+ base::FilePath platform_path1;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::GetPlatformPath(
+ file_system_context(), url1, &platform_path1));
+
+ // Get the file info and the platform path for url2.
+ base::PlatformFileInfo info2;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::GetMetadata(
+ file_system_context(), url2, &info2));
+ base::FilePath platform_path2;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::GetPlatformPath(
+ file_system_context(), url2, &platform_path2));
+
+ // See if file info matches with the other one.
+ EXPECT_EQ(info1.is_directory, info2.is_directory);
+ EXPECT_EQ(info1.size, info2.size);
+ EXPECT_EQ(info1.is_symbolic_link, info2.is_symbolic_link);
+ EXPECT_NE(platform_path1, platform_path2);
+
+ std::string content1, content2;
+ EXPECT_TRUE(file_util::ReadFileToString(platform_path1, &content1));
+ EXPECT_TRUE(file_util::ReadFileToString(platform_path2, &content2));
+ EXPECT_EQ(content1, content2);
+ }
+
+ void VerifyDirectoriesHaveSameContent(const FileSystemURL& root1,
+ const FileSystemURL& root2) {
+ base::FilePath root_path1 = root1.path();
+ base::FilePath root_path2 = root2.path();
+
+ FileEntryList entries;
+ std::queue<FileSystemURL> directories;
+
+ directories.push(root1);
+ std::set<base::FilePath> file_set1;
+ while (!directories.empty()) {
+ FileSystemURL dir = directories.front();
+ directories.pop();
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), dir, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ FileSystemURL url = GetEntryURL(file_system_context(),
+ dir, entries[i].name);
+ if (entries[i].is_directory) {
+ directories.push(url);
+ continue;
+ }
+ file_set1.insert(GetRelativeVirtualPath(root1, url));
+ }
+ }
+
+ directories.push(root2);
+ while (!directories.empty()) {
+ FileSystemURL dir = directories.front();
+ directories.pop();
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), dir, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ FileSystemURL url2 = GetEntryURL(file_system_context(),
+ dir, entries[i].name);
+ FileSystemURL url1 = GetOtherURL(file_system_context(),
+ root2, root1, url2);
+ if (entries[i].is_directory) {
+ directories.push(url2);
+ EXPECT_EQ(IsDirectoryEmpty(file_system_context(), url1),
+ IsDirectoryEmpty(file_system_context(), url2));
+ continue;
+ }
+ base::FilePath relative = GetRelativeVirtualPath(root2, url2);
+ EXPECT_TRUE(file_set1.find(relative) != file_set1.end());
+ VerifyFilesHaveSameContent(url1, url2);
+ }
+ }
+ }
+
+ scoped_ptr<FileSystemOperationContext> GetOperationContext() {
+ return make_scoped_ptr(
+ new FileSystemOperationContext(file_system_context())).Pass();
+ }
+
+
+ private:
+ void SimulateDropFiles() {
+ size_t root_path_index = 0;
+
+ IsolatedContext::FileInfoSet toplevels;
+ for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) {
+ const test::TestCaseRecord& test_case = test::kRegularTestCases[i];
+ base::FilePath path(test_case.path);
+ base::FilePath toplevel = GetTopLevelPath(path);
+
+ // We create the test case files under one of the kRootPaths
+ // to simulate a drop with multiple directories.
+ if (toplevel_root_map_.find(toplevel) == toplevel_root_map_.end()) {
+ base::FilePath root = root_path().Append(
+ kRootPaths[(root_path_index++) % arraysize(kRootPaths)]);
+ toplevel_root_map_[toplevel] = root;
+ toplevels.AddPath(root.Append(path), NULL);
+ }
+
+ test::SetUpOneTestCase(toplevel_root_map_[toplevel], test_case);
+ }
+
+ // Register the toplevel entries.
+ filesystem_id_ = isolated_context()->RegisterDraggedFileSystem(toplevels);
+ }
+
+ base::ScopedTempDir data_dir_;
+ base::ScopedTempDir partition_dir_;
+ base::MessageLoop message_loop_;
+ std::string filesystem_id_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ std::map<base::FilePath, base::FilePath> toplevel_root_map_;
+ scoped_ptr<IsolatedFileUtil> file_util_;
+ DISALLOW_COPY_AND_ASSIGN(IsolatedFileUtilTest);
+};
+
+TEST_F(IsolatedFileUtilTest, BasicTest) {
+ for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) {
+ SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i);
+ const test::TestCaseRecord& test_case = test::kRegularTestCases[i];
+
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ // See if we can query the file info via the isolated FileUtil.
+ // (This should succeed since we have registered all the top-level
+ // entries of the test cases in SetUp())
+ base::PlatformFileInfo info;
+ base::FilePath platform_path;
+ FileSystemOperationContext context(file_system_context());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->GetFileInfo(&context, url, &info, &platform_path));
+
+ // See if the obtained file info is correct.
+ if (!test_case.is_directory)
+ ASSERT_EQ(test_case.data_file_size, info.size);
+ ASSERT_EQ(test_case.is_directory, info.is_directory);
+ ASSERT_EQ(GetTestCasePlatformPath(test_case.path),
+ platform_path.NormalizePathSeparators());
+ }
+}
+
+TEST_F(IsolatedFileUtilTest, UnregisteredPathsTest) {
+ static const fileapi::test::TestCaseRecord kUnregisteredCases[] = {
+ {true, FILE_PATH_LITERAL("nonexistent"), 0},
+ {true, FILE_PATH_LITERAL("nonexistent/dir foo"), 0},
+ {false, FILE_PATH_LITERAL("nonexistent/false"), 0},
+ {false, FILE_PATH_LITERAL("foo"), 30},
+ {false, FILE_PATH_LITERAL("bar"), 20},
+ };
+
+ for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i);
+ const test::TestCaseRecord& test_case = kUnregisteredCases[i];
+
+ // Prepare the test file/directory.
+ SetUpOneTestCase(root_path(), test_case);
+
+ // Make sure regular GetFileInfo succeeds.
+ base::PlatformFileInfo info;
+ ASSERT_TRUE(file_util::GetFileInfo(
+ root_path().Append(test_case.path), &info));
+ if (!test_case.is_directory)
+ ASSERT_EQ(test_case.data_file_size, info.size);
+ ASSERT_EQ(test_case.is_directory, info.is_directory);
+ }
+
+ for (size_t i = 0; i < arraysize(kUnregisteredCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Creating kUnregisteredCases " << i);
+ const test::TestCaseRecord& test_case = kUnregisteredCases[i];
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ // We should not be able to get the valid URL for unregistered files.
+ ASSERT_FALSE(url.is_valid());
+ }
+}
+
+TEST_F(IsolatedFileUtilTest, ReadDirectoryTest) {
+ for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) {
+ const test::TestCaseRecord& test_case = test::kRegularTestCases[i];
+ if (!test_case.is_directory)
+ continue;
+
+ SCOPED_TRACE(testing::Message() << "Testing RegularTestCases " << i
+ << ": " << test_case.path);
+
+ // Read entries in the directory to construct the expected results map.
+ typedef std::map<base::FilePath::StringType, DirectoryEntry> EntryMap;
+ EntryMap expected_entry_map;
+
+ base::FilePath dir_path = GetTestCasePlatformPath(test_case.path);
+ base::FileEnumerator file_enum(
+ dir_path, false /* not recursive */,
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+ base::FilePath current;
+ while (!(current = file_enum.Next()).empty()) {
+ base::FileEnumerator::FileInfo file_info = file_enum.GetInfo();
+ DirectoryEntry entry;
+ entry.is_directory = file_info.IsDirectory();
+ entry.name = current.BaseName().value();
+ entry.size = file_info.GetSize();
+ entry.last_modified_time = file_info.GetLastModifiedTime();
+ expected_entry_map[entry.name] = entry;
+
+#if defined(OS_POSIX)
+ // Creates a symlink for each file/directory.
+ // They should be ignored by ReadDirectory, so we don't add them
+ // to expected_entry_map.
+ file_util::CreateSymbolicLink(
+ current,
+ dir_path.Append(current.BaseName().AddExtension(
+ FILE_PATH_LITERAL("link"))));
+#endif
+ }
+
+ // Perform ReadDirectory in the isolated filesystem.
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+ FileEntryList entries;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), url, &entries));
+
+ EXPECT_EQ(expected_entry_map.size(), entries.size());
+ for (size_t i = 0; i < entries.size(); ++i) {
+ const DirectoryEntry& entry = entries[i];
+ EntryMap::iterator found = expected_entry_map.find(entry.name);
+ EXPECT_TRUE(found != expected_entry_map.end());
+ EXPECT_EQ(found->second.name, entry.name);
+ EXPECT_EQ(found->second.is_directory, entry.is_directory);
+ EXPECT_EQ(found->second.size, entry.size);
+ EXPECT_EQ(found->second.last_modified_time.ToDoubleT(),
+ entry.last_modified_time.ToDoubleT());
+ }
+ }
+}
+
+TEST_F(IsolatedFileUtilTest, GetLocalFilePathTest) {
+ for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) {
+ const test::TestCaseRecord& test_case = test::kRegularTestCases[i];
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ FileSystemOperationContext context(file_system_context());
+
+ base::FilePath local_file_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->GetLocalFilePath(&context, url, &local_file_path));
+ EXPECT_EQ(GetTestCasePlatformPath(test_case.path).value(),
+ local_file_path.value());
+ }
+}
+
+TEST_F(IsolatedFileUtilTest, CopyOutFileTest) {
+ FileSystemURL src_root = GetFileSystemURL(base::FilePath());
+ FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath());
+
+ FileEntryList entries;
+ std::queue<FileSystemURL> directories;
+ directories.push(src_root);
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::CreateDirectory(file_system_context(),
+ dest_root));
+
+ while (!directories.empty()) {
+ FileSystemURL dir = directories.front();
+ directories.pop();
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(file_system_context(),
+ dir, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ FileSystemURL src_url = GetEntryURL(file_system_context(),
+ dir, entries[i].name);
+ FileSystemURL dest_url = GetOtherURL(file_system_context(),
+ src_root, dest_root, src_url);
+
+ if (entries[i].is_directory) {
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::CreateDirectory(file_system_context(),
+ dest_url));
+ directories.push(src_url);
+ continue;
+ }
+ SCOPED_TRACE(testing::Message() << "Testing file copy "
+ << src_url.path().value());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ src_url, dest_url));
+ VerifyFilesHaveSameContent(src_url, dest_url);
+ }
+ }
+}
+
+TEST_F(IsolatedFileUtilTest, CopyOutDirectoryTest) {
+ FileSystemURL src_root = GetFileSystemURL(base::FilePath());
+ FileSystemURL dest_root = GetOtherFileSystemURL(base::FilePath());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::CreateDirectory(file_system_context(),
+ dest_root));
+
+ FileEntryList entries;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(file_system_context(),
+ src_root, &entries));
+ for (size_t i = 0; i < entries.size(); ++i) {
+ if (!entries[i].is_directory)
+ continue;
+ FileSystemURL src_url = GetEntryURL(file_system_context(),
+ src_root, entries[i].name);
+ FileSystemURL dest_url = GetOtherURL(file_system_context(),
+ src_root, dest_root, src_url);
+ SCOPED_TRACE(testing::Message() << "Testing file copy "
+ << src_url.path().value());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ src_url, dest_url));
+ VerifyDirectoriesHaveSameContent(src_url, dest_url);
+ }
+}
+
+TEST_F(IsolatedFileUtilTest, TouchTest) {
+ for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) {
+ const test::TestCaseRecord& test_case = test::kRegularTestCases[i];
+ if (test_case.is_directory)
+ continue;
+ SCOPED_TRACE(testing::Message() << test_case.path);
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ base::Time last_access_time = base::Time::FromTimeT(1000);
+ base::Time last_modified_time = base::Time::FromTimeT(2000);
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Touch(GetOperationContext().get(), url,
+ last_access_time,
+ last_modified_time));
+
+ // Verification.
+ base::PlatformFileInfo info;
+ base::FilePath platform_path;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->GetFileInfo(GetOperationContext().get(), url,
+ &info, &platform_path));
+ EXPECT_EQ(last_access_time.ToTimeT(), info.last_accessed.ToTimeT());
+ EXPECT_EQ(last_modified_time.ToTimeT(), info.last_modified.ToTimeT());
+ }
+}
+
+TEST_F(IsolatedFileUtilTest, TruncateTest) {
+ for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) {
+ const test::TestCaseRecord& test_case = test::kRegularTestCases[i];
+ if (test_case.is_directory)
+ continue;
+
+ SCOPED_TRACE(testing::Message() << test_case.path);
+ FileSystemURL url = GetFileSystemURL(base::FilePath(test_case.path));
+
+ // Truncate to 0.
+ base::PlatformFileInfo info;
+ base::FilePath platform_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Truncate(GetOperationContext().get(), url, 0));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->GetFileInfo(GetOperationContext().get(), url,
+ &info, &platform_path));
+ EXPECT_EQ(0, info.size);
+
+ // Truncate (extend) to 999.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Truncate(GetOperationContext().get(), url, 999));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->GetFileInfo(GetOperationContext().get(), url,
+ &info, &platform_path));
+ EXPECT_EQ(999, info.size);
+ }
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/local_file_stream_writer.cc b/chromium/webkit/browser/fileapi/local_file_stream_writer.cc
new file mode 100644
index 00000000000..3f779c969ed
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/local_file_stream_writer.cc
@@ -0,0 +1,233 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/local_file_stream_writer.h"
+
+#include "base/callback.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace fileapi {
+
+namespace {
+
+const int kOpenFlagsForWrite = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_WRITE |
+ base::PLATFORM_FILE_ASYNC;
+
+} // namespace
+
+LocalFileStreamWriter::LocalFileStreamWriter(base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset)
+ : file_path_(file_path),
+ initial_offset_(initial_offset),
+ has_pending_operation_(false),
+ weak_factory_(this),
+ task_runner_(task_runner) {}
+
+LocalFileStreamWriter::~LocalFileStreamWriter() {
+ // Invalidate weak pointers so that we won't receive any callbacks from
+ // in-flight stream operations, which might be triggered during the file close
+ // in the FileStream destructor.
+ weak_factory_.InvalidateWeakPtrs();
+
+ // FileStream's destructor closes the file safely, since we opened the file
+ // by its Open() method.
+}
+
+int LocalFileStreamWriter::Write(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(!has_pending_operation_);
+ DCHECK(cancel_callback_.is_null());
+
+ has_pending_operation_ = true;
+ if (stream_impl_) {
+ int result = InitiateWrite(buf, buf_len, callback);
+ if (result != net::ERR_IO_PENDING)
+ has_pending_operation_ = false;
+ return result;
+ }
+ return InitiateOpen(callback,
+ base::Bind(&LocalFileStreamWriter::ReadyToWrite,
+ weak_factory_.GetWeakPtr(),
+ make_scoped_refptr(buf), buf_len, callback));
+}
+
+int LocalFileStreamWriter::Cancel(const net::CompletionCallback& callback) {
+ if (!has_pending_operation_)
+ return net::ERR_UNEXPECTED;
+
+ DCHECK(!callback.is_null());
+ cancel_callback_ = callback;
+ return net::ERR_IO_PENDING;
+}
+
+int LocalFileStreamWriter::Flush(const net::CompletionCallback& callback) {
+ DCHECK(!has_pending_operation_);
+ DCHECK(cancel_callback_.is_null());
+
+ // Write() is not called yet, so there's nothing to flush.
+ if (!stream_impl_)
+ return net::OK;
+
+ has_pending_operation_ = true;
+ int result = InitiateFlush(callback);
+ if (result != net::ERR_IO_PENDING)
+ has_pending_operation_ = false;
+ return result;
+}
+
+int LocalFileStreamWriter::InitiateOpen(
+ const net::CompletionCallback& error_callback,
+ const base::Closure& main_operation) {
+ DCHECK(has_pending_operation_);
+ DCHECK(!stream_impl_.get());
+
+ stream_impl_.reset(new net::FileStream(NULL, task_runner_));
+
+ return stream_impl_->Open(file_path_,
+ kOpenFlagsForWrite,
+ base::Bind(&LocalFileStreamWriter::DidOpen,
+ weak_factory_.GetWeakPtr(),
+ error_callback,
+ main_operation));
+}
+
+void LocalFileStreamWriter::DidOpen(
+ const net::CompletionCallback& error_callback,
+ const base::Closure& main_operation,
+ int result) {
+ DCHECK(has_pending_operation_);
+ DCHECK(stream_impl_.get());
+
+ if (CancelIfRequested())
+ return;
+
+ if (result != net::OK) {
+ has_pending_operation_ = false;
+ stream_impl_.reset(NULL);
+ error_callback.Run(result);
+ return;
+ }
+
+ InitiateSeek(error_callback, main_operation);
+}
+
+void LocalFileStreamWriter::InitiateSeek(
+ const net::CompletionCallback& error_callback,
+ const base::Closure& main_operation) {
+ DCHECK(has_pending_operation_);
+ DCHECK(stream_impl_.get());
+
+ if (initial_offset_ == 0) {
+ // No need to seek.
+ main_operation.Run();
+ return;
+ }
+
+ int result = stream_impl_->Seek(net::FROM_BEGIN, initial_offset_,
+ base::Bind(&LocalFileStreamWriter::DidSeek,
+ weak_factory_.GetWeakPtr(),
+ error_callback,
+ main_operation));
+ if (result != net::ERR_IO_PENDING) {
+ has_pending_operation_ = false;
+ error_callback.Run(result);
+ }
+}
+
+void LocalFileStreamWriter::DidSeek(
+ const net::CompletionCallback& error_callback,
+ const base::Closure& main_operation,
+ int64 result) {
+ DCHECK(has_pending_operation_);
+
+ if (CancelIfRequested())
+ return;
+
+ if (result != initial_offset_) {
+ // TODO(kinaba) add a more specific error code.
+ result = net::ERR_FAILED;
+ }
+
+ if (result < 0) {
+ has_pending_operation_ = false;
+ error_callback.Run(static_cast<int>(result));
+ return;
+ }
+
+ main_operation.Run();
+}
+
+void LocalFileStreamWriter::ReadyToWrite(
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(has_pending_operation_);
+
+ int result = InitiateWrite(buf, buf_len, callback);
+ if (result != net::ERR_IO_PENDING) {
+ has_pending_operation_ = false;
+ callback.Run(result);
+ }
+}
+
+int LocalFileStreamWriter::InitiateWrite(
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(has_pending_operation_);
+ DCHECK(stream_impl_.get());
+
+ return stream_impl_->Write(buf, buf_len,
+ base::Bind(&LocalFileStreamWriter::DidWrite,
+ weak_factory_.GetWeakPtr(),
+ callback));
+}
+
+void LocalFileStreamWriter::DidWrite(const net::CompletionCallback& callback,
+ int result) {
+ DCHECK(has_pending_operation_);
+
+ if (CancelIfRequested())
+ return;
+ has_pending_operation_ = false;
+ callback.Run(result);
+}
+
+int LocalFileStreamWriter::InitiateFlush(
+ const net::CompletionCallback& callback) {
+ DCHECK(has_pending_operation_);
+ DCHECK(stream_impl_.get());
+
+ return stream_impl_->Flush(base::Bind(&LocalFileStreamWriter::DidFlush,
+ weak_factory_.GetWeakPtr(),
+ callback));
+}
+
+void LocalFileStreamWriter::DidFlush(const net::CompletionCallback& callback,
+ int result) {
+ DCHECK(has_pending_operation_);
+
+ if (CancelIfRequested())
+ return;
+ has_pending_operation_ = false;
+ callback.Run(result);
+}
+
+bool LocalFileStreamWriter::CancelIfRequested() {
+ DCHECK(has_pending_operation_);
+
+ if (cancel_callback_.is_null())
+ return false;
+
+ net::CompletionCallback pending_cancel = cancel_callback_;
+ has_pending_operation_ = false;
+ cancel_callback_.Reset();
+ pending_cancel.Run(net::OK);
+ return true;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/local_file_stream_writer.h b/chromium/webkit/browser/fileapi/local_file_stream_writer.h
new file mode 100644
index 00000000000..08e12e80e41
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/local_file_stream_writer.h
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_
+#define WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_
+
+#include <utility>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/platform_file.h"
+#include "base/task_runner.h"
+#include "webkit/browser/fileapi/file_stream_writer.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace net {
+class FileStream;
+}
+
+namespace fileapi {
+
+// This class is a thin wrapper around net::FileStream for writing local files.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE LocalFileStreamWriter
+ : public FileStreamWriter {
+ public:
+ // Creates a writer for the existing file in the path |file_path| starting
+ // from |initial_offset|. Uses |task_runner| for async file operations.
+ LocalFileStreamWriter(base::TaskRunner* task_runner,
+ const base::FilePath& file_path, int64 initial_offset);
+ virtual ~LocalFileStreamWriter();
+
+ // FileStreamWriter overrides.
+ virtual int Write(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int Cancel(const net::CompletionCallback& callback) OVERRIDE;
+ virtual int Flush(const net::CompletionCallback& callback) OVERRIDE;
+
+ private:
+ // Opens |file_path_| and if it succeeds, proceeds to InitiateSeek().
+ // If failed, the error code is returned by calling |error_callback|.
+ int InitiateOpen(const net::CompletionCallback& error_callback,
+ const base::Closure& main_operation);
+ void DidOpen(const net::CompletionCallback& error_callback,
+ const base::Closure& main_operation,
+ int result);
+
+ // Seeks to |initial_offset_| and proceeds to |main_operation| if it succeeds.
+ // If failed, the error code is returned by calling |error_callback|.
+ void InitiateSeek(const net::CompletionCallback& error_callback,
+ const base::Closure& main_operation);
+ void DidSeek(const net::CompletionCallback& error_callback,
+ const base::Closure& main_operation,
+ int64 result);
+
+ // Passed as the |main_operation| of InitiateOpen() function.
+ void ReadyToWrite(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback);
+
+ // Writes asynchronously to the file.
+ int InitiateWrite(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback);
+ void DidWrite(const net::CompletionCallback& callback, int result);
+
+ // Flushes asynchronously to the file.
+ int InitiateFlush(const net::CompletionCallback& callback);
+ void DidFlush(const net::CompletionCallback& callback, int result);
+
+ // Stops the in-flight operation and calls |cancel_callback_| if it has been
+ // set by Cancel() for the current operation.
+ bool CancelIfRequested();
+
+ // Initialization parameters.
+ const base::FilePath file_path_;
+ const int64 initial_offset_;
+ scoped_refptr<base::TaskRunner> task_runner_;
+
+ // Current states of the operation.
+ bool has_pending_operation_;
+ scoped_ptr<net::FileStream> stream_impl_;
+ net::CompletionCallback cancel_callback_;
+
+ base::WeakPtrFactory<LocalFileStreamWriter> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(LocalFileStreamWriter);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_
diff --git a/chromium/webkit/browser/fileapi/local_file_stream_writer_unittest.cc b/chromium/webkit/browser/fileapi/local_file_stream_writer_unittest.cc
new file mode 100644
index 00000000000..bed037d7b09
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/local_file_stream_writer_unittest.cc
@@ -0,0 +1,180 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/local_file_stream_writer.h"
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/threading/thread.h"
+#include "net/base/io_buffer.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using fileapi::LocalFileStreamWriter;
+
+class LocalFileStreamWriterTest : public testing::Test {
+ public:
+ LocalFileStreamWriterTest()
+ : message_loop_(base::MessageLoop::TYPE_IO),
+ file_thread_("FileUtilProxyTestFileThread") {}
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(file_thread_.Start());
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ // Give another chance for deleted streams to perform Close.
+ base::MessageLoop::current()->RunUntilIdle();
+ file_thread_.Stop();
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ protected:
+ base::FilePath Path(const std::string& name) {
+ return temp_dir_.path().AppendASCII(name);
+ }
+
+ int WriteStringToWriter(LocalFileStreamWriter* writer,
+ const std::string& data) {
+ scoped_refptr<net::StringIOBuffer> buffer(new net::StringIOBuffer(data));
+ scoped_refptr<net::DrainableIOBuffer> drainable(
+ new net::DrainableIOBuffer(buffer.get(), buffer->size()));
+
+ while (drainable->BytesRemaining() > 0) {
+ net::TestCompletionCallback callback;
+ int result = writer->Write(
+ drainable.get(), drainable->BytesRemaining(), callback.callback());
+ if (result == net::ERR_IO_PENDING)
+ result = callback.WaitForResult();
+ if (result <= 0)
+ return result;
+ drainable->DidConsume(result);
+ }
+ return net::OK;
+ }
+
+ std::string GetFileContent(const base::FilePath& path) {
+ std::string content;
+ file_util::ReadFileToString(path, &content);
+ return content;
+ }
+
+ base::FilePath CreateFileWithContent(const std::string& name,
+ const std::string& data) {
+ base::FilePath path = Path(name);
+ file_util::WriteFile(path, data.c_str(), data.size());
+ return path;
+ }
+
+ base::MessageLoopProxy* file_task_runner() const {
+ return file_thread_.message_loop_proxy().get();
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ base::Thread file_thread_;
+ base::ScopedTempDir temp_dir_;
+};
+
+void NeverCalled(int unused) {
+ ADD_FAILURE();
+}
+
+} // namespace
+
+TEST_F(LocalFileStreamWriterTest, Write) {
+ base::FilePath path = CreateFileWithContent("file_a", std::string());
+ scoped_ptr<LocalFileStreamWriter> writer(
+ new LocalFileStreamWriter(file_task_runner(), path, 0));
+ EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "foo"));
+ EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "bar"));
+ writer.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(path));
+ EXPECT_EQ("foobar", GetFileContent(path));
+}
+
+TEST_F(LocalFileStreamWriterTest, WriteMiddle) {
+ base::FilePath path = CreateFileWithContent("file_a", "foobar");
+ scoped_ptr<LocalFileStreamWriter> writer(
+ new LocalFileStreamWriter(file_task_runner(), path, 2));
+ EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "xxx"));
+ writer.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(path));
+ EXPECT_EQ("foxxxr", GetFileContent(path));
+}
+
+TEST_F(LocalFileStreamWriterTest, WriteEnd) {
+ base::FilePath path = CreateFileWithContent("file_a", "foobar");
+ scoped_ptr<LocalFileStreamWriter> writer(
+ new LocalFileStreamWriter(file_task_runner(), path, 6));
+ EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "xxx"));
+ writer.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(path));
+ EXPECT_EQ("foobarxxx", GetFileContent(path));
+}
+
+TEST_F(LocalFileStreamWriterTest, WriteFailForNonexistingFile) {
+ base::FilePath path = Path("file_a");
+ ASSERT_FALSE(base::PathExists(path));
+ scoped_ptr<LocalFileStreamWriter> writer(
+ new LocalFileStreamWriter(file_task_runner(), path, 0));
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, WriteStringToWriter(writer.get(), "foo"));
+ writer.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(base::PathExists(path));
+}
+
+TEST_F(LocalFileStreamWriterTest, CancelBeforeOperation) {
+ base::FilePath path = Path("file_a");
+ scoped_ptr<LocalFileStreamWriter> writer(
+ new LocalFileStreamWriter(file_task_runner(), path, 0));
+ // Cancel immediately fails when there's no in-flight operation.
+ int cancel_result = writer->Cancel(base::Bind(&NeverCalled));
+ EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result);
+}
+
+TEST_F(LocalFileStreamWriterTest, CancelAfterFinishedOperation) {
+ base::FilePath path = CreateFileWithContent("file_a", std::string());
+ scoped_ptr<LocalFileStreamWriter> writer(
+ new LocalFileStreamWriter(file_task_runner(), path, 0));
+ EXPECT_EQ(net::OK, WriteStringToWriter(writer.get(), "foo"));
+
+ // Cancel immediately fails when there's no in-flight operation.
+ int cancel_result = writer->Cancel(base::Bind(&NeverCalled));
+ EXPECT_EQ(net::ERR_UNEXPECTED, cancel_result);
+
+ writer.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ // Write operation is already completed.
+ EXPECT_TRUE(base::PathExists(path));
+ EXPECT_EQ("foo", GetFileContent(path));
+}
+
+TEST_F(LocalFileStreamWriterTest, CancelWrite) {
+ base::FilePath path = CreateFileWithContent("file_a", "foobar");
+ scoped_ptr<LocalFileStreamWriter> writer(
+ new LocalFileStreamWriter(file_task_runner(), path, 0));
+
+ scoped_refptr<net::StringIOBuffer> buffer(new net::StringIOBuffer("xxx"));
+ int result =
+ writer->Write(buffer.get(), buffer->size(), base::Bind(&NeverCalled));
+ ASSERT_EQ(net::ERR_IO_PENDING, result);
+
+ net::TestCompletionCallback callback;
+ writer->Cancel(callback.callback());
+ int cancel_result = callback.WaitForResult();
+ EXPECT_EQ(net::OK, cancel_result);
+}
diff --git a/chromium/webkit/browser/fileapi/local_file_util.cc b/chromium/webkit/browser/fileapi/local_file_util.cc
new file mode 100644
index 00000000000..f13af8d207f
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/local_file_util.cc
@@ -0,0 +1,257 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/local_file_util.h"
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util_proxy.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/native_file_util.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+using base::PlatformFileError;
+
+class LocalFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator {
+ public:
+ LocalFileEnumerator(const base::FilePath& platform_root_path,
+ const base::FilePath& virtual_root_path,
+ int file_type)
+ : file_enum_(platform_root_path, false /* recursive */, file_type),
+ platform_root_path_(platform_root_path),
+ virtual_root_path_(virtual_root_path) {
+ }
+
+ virtual ~LocalFileEnumerator() {}
+
+ virtual base::FilePath Next() OVERRIDE;
+ virtual int64 Size() OVERRIDE;
+ virtual base::Time LastModifiedTime() OVERRIDE;
+ virtual bool IsDirectory() OVERRIDE;
+
+ private:
+ base::FileEnumerator file_enum_;
+ base::FileEnumerator::FileInfo file_util_info_;
+ base::FilePath platform_root_path_;
+ base::FilePath virtual_root_path_;
+};
+
+base::FilePath LocalFileEnumerator::Next() {
+ base::FilePath next = file_enum_.Next();
+ // Don't return symlinks.
+ while (!next.empty() && file_util::IsLink(next))
+ next = file_enum_.Next();
+ if (next.empty())
+ return next;
+ file_util_info_ = file_enum_.GetInfo();
+
+ base::FilePath path;
+ platform_root_path_.AppendRelativePath(next, &path);
+ return virtual_root_path_.Append(path);
+}
+
+int64 LocalFileEnumerator::Size() {
+ return file_util_info_.GetSize();
+}
+
+base::Time LocalFileEnumerator::LastModifiedTime() {
+ return file_util_info_.GetLastModifiedTime();
+}
+
+bool LocalFileEnumerator::IsDirectory() {
+ return file_util_info_.IsDirectory();
+}
+
+LocalFileUtil::LocalFileUtil() {
+}
+
+LocalFileUtil::~LocalFileUtil() {
+}
+
+PlatformFileError LocalFileUtil::CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, int file_flags,
+ base::PlatformFile* file_handle, bool* created) {
+ *created = false;
+ base::FilePath file_path;
+ PlatformFileError error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ // Disallow opening files in symlinked paths.
+ if (file_util::IsLink(file_path))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ return NativeFileUtil::CreateOrOpen(
+ file_path, file_flags, file_handle, created);
+}
+
+PlatformFileError LocalFileUtil::Close(FileSystemOperationContext* context,
+ base::PlatformFile file) {
+ return NativeFileUtil::Close(file);
+}
+
+PlatformFileError LocalFileUtil::EnsureFileExists(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool* created) {
+ base::FilePath file_path;
+ PlatformFileError error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ return NativeFileUtil::EnsureFileExists(file_path, created);
+}
+
+PlatformFileError LocalFileUtil::CreateDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) {
+ base::FilePath file_path;
+ PlatformFileError error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ return NativeFileUtil::CreateDirectory(file_path, exclusive, recursive);
+}
+
+PlatformFileError LocalFileUtil::GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_file_path) {
+ base::FilePath file_path;
+ PlatformFileError error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ // We should not follow symbolic links in sandboxed file system.
+ if (file_util::IsLink(file_path))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ error = NativeFileUtil::GetFileInfo(file_path, file_info);
+ if (error == base::PLATFORM_FILE_OK)
+ *platform_file_path = file_path;
+ return error;
+}
+
+scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> LocalFileUtil::
+ CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url) {
+ base::FilePath file_path;
+ if (GetLocalFilePath(context, root_url, &file_path) !=
+ base::PLATFORM_FILE_OK) {
+ return make_scoped_ptr(new EmptyFileEnumerator)
+ .PassAs<FileSystemFileUtil::AbstractFileEnumerator>();
+ }
+ return make_scoped_ptr(new LocalFileEnumerator(
+ file_path, root_url.path(),
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES))
+ .PassAs<FileSystemFileUtil::AbstractFileEnumerator>();
+}
+
+PlatformFileError LocalFileUtil::GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::FilePath* local_file_path) {
+ base::FilePath root = context->root_path();
+ if (root.empty())
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ *local_file_path = root.Append(url.path());
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError LocalFileUtil::Touch(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) {
+ base::FilePath file_path;
+ PlatformFileError error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ return NativeFileUtil::Touch(file_path, last_access_time, last_modified_time);
+}
+
+PlatformFileError LocalFileUtil::Truncate(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 length) {
+ base::FilePath file_path;
+ PlatformFileError error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ return NativeFileUtil::Truncate(file_path, length);
+}
+
+PlatformFileError LocalFileUtil::CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ bool copy) {
+ base::FilePath src_file_path;
+ PlatformFileError error = GetLocalFilePath(context, src_url, &src_file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+
+ base::FilePath dest_file_path;
+ error = GetLocalFilePath(context, dest_url, &dest_file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+
+ return NativeFileUtil::CopyOrMoveFile(src_file_path, dest_file_path, copy);
+}
+
+PlatformFileError LocalFileUtil::CopyInForeignFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url) {
+ if (src_file_path.empty())
+ return base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
+
+ base::FilePath dest_file_path;
+ PlatformFileError error =
+ GetLocalFilePath(context, dest_url, &dest_file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ return NativeFileUtil::CopyOrMoveFile(src_file_path, dest_file_path, true);
+}
+
+PlatformFileError LocalFileUtil::DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ base::FilePath file_path;
+ PlatformFileError error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ return NativeFileUtil::DeleteFile(file_path);
+}
+
+PlatformFileError LocalFileUtil::DeleteDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ base::FilePath file_path;
+ PlatformFileError error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ return NativeFileUtil::DeleteDirectory(file_path);
+}
+
+webkit_blob::ScopedFile LocalFileUtil::CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileError* error,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_path) {
+ DCHECK(file_info);
+ // We're just returning the local file information.
+ *error = GetFileInfo(context, url, file_info, platform_path);
+ if (*error == base::PLATFORM_FILE_OK && file_info->is_directory)
+ *error = base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+ return webkit_blob::ScopedFile();
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/local_file_util.h b/chromium/webkit/browser/fileapi/local_file_util.h
new file mode 100644
index 00000000000..abd6b54e1c3
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/local_file_util.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_
+#define WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class FilePath;
+class Time;
+}
+
+class GURL;
+
+namespace fileapi {
+
+class FileSystemOperationContext;
+class FileSystemURL;
+
+// An instance of this class is created and owned by *FileSystemBackend.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE LocalFileUtil
+ : public FileSystemFileUtil {
+ public:
+ LocalFileUtil();
+ virtual ~LocalFileUtil();
+
+ virtual base::PlatformFileError CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int file_flags,
+ base::PlatformFile* file_handle,
+ bool* created) OVERRIDE;
+ virtual base::PlatformFileError Close(
+ FileSystemOperationContext* context,
+ base::PlatformFile file) OVERRIDE;
+ virtual base::PlatformFileError EnsureFileExists(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, bool* created) OVERRIDE;
+ virtual base::PlatformFileError CreateDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) OVERRIDE;
+ virtual base::PlatformFileError GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_file) OVERRIDE;
+ virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url) OVERRIDE;
+ virtual base::PlatformFileError GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& file_system_url,
+ base::FilePath* local_file_path) OVERRIDE;
+ virtual base::PlatformFileError Touch(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) OVERRIDE;
+ virtual base::PlatformFileError Truncate(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 length) OVERRIDE;
+ virtual base::PlatformFileError CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ bool copy) OVERRIDE;
+ virtual base::PlatformFileError CopyInForeignFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url) OVERRIDE;
+ virtual base::PlatformFileError DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) OVERRIDE;
+ virtual base::PlatformFileError DeleteDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) OVERRIDE;
+ virtual webkit_blob::ScopedFile CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileError* error,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_path) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LocalFileUtil);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_
diff --git a/chromium/webkit/browser/fileapi/local_file_util_unittest.cc b/chromium/webkit/browser/fileapi/local_file_util_unittest.cc
new file mode 100644
index 00000000000..b0b52e718db
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/local_file_util_unittest.cc
@@ -0,0 +1,387 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/platform_file.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/async_file_test_helper.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/local_file_util.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/native_file_util.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace fileapi {
+
+namespace {
+
+const GURL kOrigin("http://foo/");
+const FileSystemType kFileSystemType = kFileSystemTypeTest;
+
+} // namespace
+
+class LocalFileUtilTest : public testing::Test {
+ public:
+ LocalFileUtilTest() {}
+
+ virtual void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ file_system_context_ = CreateFileSystemContextForTesting(
+ NULL, data_dir_.path());
+ }
+
+ virtual void TearDown() {
+ file_system_context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ protected:
+ FileSystemOperationContext* NewContext() {
+ FileSystemOperationContext* context =
+ new FileSystemOperationContext(file_system_context_.get());
+ context->set_update_observers(
+ *file_system_context_->GetUpdateObservers(kFileSystemType));
+ context->set_root_path(data_dir_.path());
+ return context;
+ }
+
+ LocalFileUtil* file_util() {
+ return static_cast<LocalFileUtil*>(
+ file_system_context_->GetFileUtil(kFileSystemType));
+ }
+
+ FileSystemURL CreateURL(const std::string& file_name) {
+ return file_system_context_->CreateCrackedFileSystemURL(
+ kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name));
+ }
+
+ base::FilePath LocalPath(const char *file_name) {
+ base::FilePath path;
+ scoped_ptr<FileSystemOperationContext> context(NewContext());
+ file_util()->GetLocalFilePath(context.get(), CreateURL(file_name), &path);
+ return path;
+ }
+
+ bool FileExists(const char *file_name) {
+ return base::PathExists(LocalPath(file_name)) &&
+ !base::DirectoryExists(LocalPath(file_name));
+ }
+
+ bool DirectoryExists(const char *file_name) {
+ return base::DirectoryExists(LocalPath(file_name));
+ }
+
+ int64 GetSize(const char *file_name) {
+ base::PlatformFileInfo info;
+ file_util::GetFileInfo(LocalPath(file_name), &info);
+ return info.size;
+ }
+
+ base::PlatformFileError CreateFile(const char* file_name,
+ base::PlatformFile* file_handle,
+ bool* created) {
+ int file_flags = base::PLATFORM_FILE_CREATE |
+ base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC;
+
+ scoped_ptr<FileSystemOperationContext> context(NewContext());
+ return file_util()->CreateOrOpen(
+ context.get(),
+ CreateURL(file_name),
+ file_flags, file_handle, created);
+ }
+
+ base::PlatformFileError EnsureFileExists(const char* file_name,
+ bool* created) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext());
+ return file_util()->EnsureFileExists(
+ context.get(),
+ CreateURL(file_name), created);
+ }
+
+ FileSystemContext* file_system_context() {
+ return file_system_context_.get();
+ }
+
+ private:
+ base::MessageLoop message_loop_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ base::ScopedTempDir data_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalFileUtilTest);
+};
+
+TEST_F(LocalFileUtilTest, CreateAndClose) {
+ const char *file_name = "test_file";
+ base::PlatformFile file_handle;
+ bool created;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ CreateFile(file_name, &file_handle, &created));
+ ASSERT_TRUE(created);
+
+ EXPECT_TRUE(FileExists(file_name));
+ EXPECT_EQ(0, GetSize(file_name));
+
+ scoped_ptr<FileSystemOperationContext> context(NewContext());
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Close(context.get(), file_handle));
+}
+
+// file_util::CreateSymbolicLink is only supported on POSIX.
+#if defined(OS_POSIX)
+TEST_F(LocalFileUtilTest, CreateFailForSymlink) {
+ // Create symlink target file.
+ const char *target_name = "symlink_target";
+ base::PlatformFile target_handle;
+ bool symlink_target_created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ CreateFile(target_name, &target_handle, &symlink_target_created));
+ ASSERT_TRUE(symlink_target_created);
+ base::FilePath target_path = LocalPath(target_name);
+
+ // Create symlink where target must be real file.
+ const char *symlink_name = "symlink_file";
+ base::FilePath symlink_path = LocalPath(symlink_name);
+ ASSERT_TRUE(file_util::CreateSymbolicLink(target_path, symlink_path));
+ ASSERT_TRUE(FileExists(symlink_name));
+
+ // Try to open the symlink file which should fail.
+ scoped_ptr<FileSystemOperationContext> context(NewContext());
+ FileSystemURL url = CreateURL(symlink_name);
+ int file_flags = base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ;
+ base::PlatformFile file_handle;
+ bool created = false;
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, file_util()->CreateOrOpen(
+ context.get(), url, file_flags, &file_handle, &created));
+ EXPECT_FALSE(created);
+}
+#endif
+
+TEST_F(LocalFileUtilTest, EnsureFileExists) {
+ const char *file_name = "foobar";
+ bool created;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created));
+ ASSERT_TRUE(created);
+
+ EXPECT_TRUE(FileExists(file_name));
+ EXPECT_EQ(0, GetSize(file_name));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created));
+ EXPECT_FALSE(created);
+}
+
+TEST_F(LocalFileUtilTest, TouchFile) {
+ const char *file_name = "test_file";
+ base::PlatformFile file_handle;
+ bool created;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ CreateFile(file_name, &file_handle, &created));
+ ASSERT_TRUE(created);
+
+ scoped_ptr<FileSystemOperationContext> context(NewContext());
+
+ base::PlatformFileInfo info;
+ ASSERT_TRUE(file_util::GetFileInfo(LocalPath(file_name), &info));
+ const base::Time new_accessed =
+ info.last_accessed + base::TimeDelta::FromHours(10);
+ const base::Time new_modified =
+ info.last_modified + base::TimeDelta::FromHours(5);
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Touch(context.get(), CreateURL(file_name),
+ new_accessed, new_modified));
+
+ ASSERT_TRUE(file_util::GetFileInfo(LocalPath(file_name), &info));
+ EXPECT_EQ(new_accessed, info.last_accessed);
+ EXPECT_EQ(new_modified, info.last_modified);
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Close(context.get(), file_handle));
+}
+
+TEST_F(LocalFileUtilTest, TouchDirectory) {
+ const char *dir_name = "test_dir";
+ scoped_ptr<FileSystemOperationContext> context(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->CreateDirectory(context.get(),
+ CreateURL(dir_name),
+ false /* exclusive */,
+ false /* recursive */));
+
+ base::PlatformFileInfo info;
+ ASSERT_TRUE(file_util::GetFileInfo(LocalPath(dir_name), &info));
+ const base::Time new_accessed =
+ info.last_accessed + base::TimeDelta::FromHours(10);
+ const base::Time new_modified =
+ info.last_modified + base::TimeDelta::FromHours(5);
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Touch(context.get(), CreateURL(dir_name),
+ new_accessed, new_modified));
+
+ ASSERT_TRUE(file_util::GetFileInfo(LocalPath(dir_name), &info));
+ EXPECT_EQ(new_accessed, info.last_accessed);
+ EXPECT_EQ(new_modified, info.last_modified);
+}
+
+TEST_F(LocalFileUtilTest, Truncate) {
+ const char *file_name = "truncated";
+ bool created;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(file_name, &created));
+ ASSERT_TRUE(created);
+
+ scoped_ptr<FileSystemOperationContext> context;
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(file_name), 1020));
+
+ EXPECT_TRUE(FileExists(file_name));
+ EXPECT_EQ(1020, GetSize(file_name));
+}
+
+TEST_F(LocalFileUtilTest, CopyFile) {
+ const char *from_file = "fromfile";
+ const char *to_file1 = "tofile1";
+ const char *to_file2 = "tofile2";
+ bool created;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+
+ scoped_ptr<FileSystemOperationContext> context;
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(from_file), 1020));
+
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ CreateURL(from_file),
+ CreateURL(to_file1)));
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ CreateURL(from_file),
+ CreateURL(to_file2)));
+
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+ EXPECT_TRUE(FileExists(to_file1));
+ EXPECT_EQ(1020, GetSize(to_file1));
+ EXPECT_TRUE(FileExists(to_file2));
+ EXPECT_EQ(1020, GetSize(to_file2));
+}
+
+TEST_F(LocalFileUtilTest, CopyDirectory) {
+ const char *from_dir = "fromdir";
+ const char *from_file = "fromdir/fromfile";
+ const char *to_dir = "todir";
+ const char *to_file = "todir/fromfile";
+ bool created;
+ scoped_ptr<FileSystemOperationContext> context;
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->CreateDirectory(context.get(), CreateURL(from_dir),
+ false, false));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(from_file), 1020));
+
+ EXPECT_TRUE(DirectoryExists(from_dir));
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+ EXPECT_FALSE(DirectoryExists(to_dir));
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Copy(file_system_context(),
+ CreateURL(from_dir), CreateURL(to_dir)));
+
+ EXPECT_TRUE(DirectoryExists(from_dir));
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+ EXPECT_TRUE(DirectoryExists(to_dir));
+ EXPECT_TRUE(FileExists(to_file));
+ EXPECT_EQ(1020, GetSize(to_file));
+}
+
+TEST_F(LocalFileUtilTest, MoveFile) {
+ const char *from_file = "fromfile";
+ const char *to_file = "tofile";
+ bool created;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+ scoped_ptr<FileSystemOperationContext> context;
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(from_file), 1020));
+
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Move(file_system_context(),
+ CreateURL(from_file),
+ CreateURL(to_file)));
+
+ EXPECT_FALSE(FileExists(from_file));
+ EXPECT_TRUE(FileExists(to_file));
+ EXPECT_EQ(1020, GetSize(to_file));
+}
+
+TEST_F(LocalFileUtilTest, MoveDirectory) {
+ const char *from_dir = "fromdir";
+ const char *from_file = "fromdir/fromfile";
+ const char *to_dir = "todir";
+ const char *to_file = "todir/fromfile";
+ bool created;
+ scoped_ptr<FileSystemOperationContext> context;
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->CreateDirectory(context.get(), CreateURL(from_dir),
+ false, false));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->Truncate(context.get(), CreateURL(from_file), 1020));
+
+ EXPECT_TRUE(DirectoryExists(from_dir));
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+ EXPECT_FALSE(DirectoryExists(to_dir));
+
+ context.reset(NewContext());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Move(file_system_context(),
+ CreateURL(from_dir),
+ CreateURL(to_dir)));
+
+ EXPECT_FALSE(DirectoryExists(from_dir));
+ EXPECT_TRUE(DirectoryExists(to_dir));
+ EXPECT_TRUE(FileExists(to_file));
+ EXPECT_EQ(1020, GetSize(to_file));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/mock_file_change_observer.cc b/chromium/webkit/browser/fileapi/mock_file_change_observer.cc
new file mode 100644
index 00000000000..74791f5ebd2
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/mock_file_change_observer.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/mock_file_change_observer.h"
+
+namespace fileapi {
+
+MockFileChangeObserver::MockFileChangeObserver()
+ : create_file_count_(0),
+ create_file_from_count_(0),
+ remove_file_count_(0),
+ modify_file_count_(0),
+ create_directory_count_(0),
+ remove_directory_count_(0) {}
+
+MockFileChangeObserver::~MockFileChangeObserver() {}
+
+// static
+ChangeObserverList MockFileChangeObserver::CreateList(
+ MockFileChangeObserver* observer) {
+ ChangeObserverList list;
+ return list.AddObserver(observer, base::MessageLoopProxy::current().get());
+}
+
+void MockFileChangeObserver::OnCreateFile(const FileSystemURL& url) {
+ create_file_count_++;
+}
+
+void MockFileChangeObserver::OnCreateFileFrom(const FileSystemURL& url,
+ const FileSystemURL& src) {
+ create_file_from_count_++;
+}
+
+void MockFileChangeObserver::OnRemoveFile(const FileSystemURL& url) {
+ remove_file_count_++;
+}
+
+void MockFileChangeObserver::OnModifyFile(const FileSystemURL& url) {
+ modify_file_count_++;
+}
+
+void MockFileChangeObserver::OnCreateDirectory(const FileSystemURL& url) {
+ create_directory_count_++;
+}
+
+void MockFileChangeObserver::OnRemoveDirectory(const FileSystemURL& url) {
+ remove_directory_count_++;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/mock_file_change_observer.h b/chromium/webkit/browser/fileapi/mock_file_change_observer.h
new file mode 100644
index 00000000000..3835323d2a5
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/mock_file_change_observer.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_MOCK_FILE_CHANGE_OBSERVER_H_
+#define WEBKIT_BROWSER_FILEAPI_MOCK_FILE_CHANGE_OBSERVER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/task_runner_bound_observer_list.h"
+
+namespace fileapi {
+
+// Mock file change observer.
+class MockFileChangeObserver : public FileChangeObserver {
+ public:
+ MockFileChangeObserver();
+ virtual ~MockFileChangeObserver();
+
+ // Creates a ChangeObserverList which only contains given |observer|.
+ static ChangeObserverList CreateList(MockFileChangeObserver* observer);
+
+ // FileChangeObserver overrides.
+ virtual void OnCreateFile(const FileSystemURL& url) OVERRIDE;
+ virtual void OnCreateFileFrom(const FileSystemURL& url,
+ const FileSystemURL& src) OVERRIDE;
+ virtual void OnRemoveFile(const FileSystemURL& url) OVERRIDE;
+ virtual void OnModifyFile(const FileSystemURL& url) OVERRIDE;
+ virtual void OnCreateDirectory(const FileSystemURL& url) OVERRIDE;
+ virtual void OnRemoveDirectory(const FileSystemURL& url) OVERRIDE;
+
+ void ResetCount() {
+ create_file_count_ = 0;
+ create_file_from_count_ = 0;
+ remove_file_count_ = 0;
+ modify_file_count_ = 0;
+ create_directory_count_ = 0;
+ remove_directory_count_ = 0;
+ }
+
+ bool HasNoChange() const {
+ return create_file_count_ == 0 &&
+ create_file_from_count_ == 0 &&
+ remove_file_count_ == 0 &&
+ modify_file_count_ == 0 &&
+ create_directory_count_ == 0 &&
+ remove_directory_count_ == 0;
+ }
+
+ int create_file_count() const { return create_file_count_; }
+ int create_file_from_count() const { return create_file_from_count_; }
+ int remove_file_count() const { return remove_file_count_; }
+ int modify_file_count() const { return modify_file_count_; }
+ int create_directory_count() const { return create_directory_count_; }
+ int remove_directory_count() const { return remove_directory_count_; }
+
+ int get_and_reset_create_file_count() {
+ int tmp = create_file_count_;
+ create_file_count_ = 0;
+ return tmp;
+ }
+ int get_and_reset_create_file_from_count() {
+ int tmp = create_file_from_count_;
+ create_file_from_count_ = 0;
+ return tmp;
+ }
+ int get_and_reset_remove_file_count() {
+ int tmp = remove_file_count_;
+ remove_file_count_ = 0;
+ return tmp;
+ }
+ int get_and_reset_modify_file_count() {
+ int tmp = modify_file_count_;
+ modify_file_count_ = 0;
+ return tmp;
+ }
+ int get_and_reset_create_directory_count() {
+ int tmp = create_directory_count_;
+ create_directory_count_ = 0;
+ return tmp;
+ }
+ int get_and_reset_remove_directory_count() {
+ int tmp = remove_directory_count_;
+ remove_directory_count_ = 0;
+ return tmp;
+ }
+
+ private:
+ int create_file_count_;
+ int create_file_from_count_;
+ int remove_file_count_;
+ int modify_file_count_;
+ int create_directory_count_;
+ int remove_directory_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockFileChangeObserver);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_MOCK_FILE_CHANGE_OBSERVER_H_
diff --git a/chromium/webkit/browser/fileapi/mock_file_system_context.cc b/chromium/webkit/browser/fileapi/mock_file_system_context.cc
new file mode 100644
index 00000000000..e2556d5da13
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/mock_file_system_context.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+
+#include "base/memory/scoped_vector.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/mock_file_system_options.h"
+#include "webkit/browser/fileapi/test_file_system_backend.h"
+#include "webkit/browser/quota/mock_special_storage_policy.h"
+
+namespace fileapi {
+
+FileSystemContext* CreateFileSystemContextForTesting(
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ const base::FilePath& base_path) {
+ ScopedVector<FileSystemBackend> additional_providers;
+ additional_providers.push_back(new TestFileSystemBackend(
+ base::MessageLoopProxy::current().get(), base_path));
+ return CreateFileSystemContextWithAdditionalProvidersForTesting(
+ quota_manager_proxy, additional_providers.Pass(), base_path);
+}
+
+FileSystemContext* CreateFileSystemContextWithAdditionalProvidersForTesting(
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ ScopedVector<FileSystemBackend> additional_providers,
+ const base::FilePath& base_path) {
+ return new FileSystemContext(
+ base::MessageLoopProxy::current().get(),
+ base::MessageLoopProxy::current().get(),
+ ExternalMountPoints::CreateRefCounted().get(),
+ make_scoped_refptr(new quota::MockSpecialStoragePolicy()).get(),
+ quota_manager_proxy,
+ additional_providers.Pass(),
+ base_path,
+ CreateAllowFileAccessOptions());
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/mock_file_system_context.h b/chromium/webkit/browser/fileapi/mock_file_system_context.h
new file mode 100644
index 00000000000..bf01ba1594d
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/mock_file_system_context.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_CONTEXT_H_
+#define WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_CONTEXT_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_vector.h"
+
+namespace quota {
+class QuotaManagerProxy;
+class SpecialStoragePolicy;
+}
+
+namespace fileapi {
+
+class FileSystemContext;
+class FileSystemBackend;
+
+FileSystemContext* CreateFileSystemContextForTesting(
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ const base::FilePath& base_path);
+
+// The caller is responsible for including TestFileSystemBackend in
+// |additional_providers| if needed.
+FileSystemContext* CreateFileSystemContextWithAdditionalProvidersForTesting(
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ ScopedVector<FileSystemBackend> additional_providers,
+ const base::FilePath& base_path);
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_CONTEXT_H_
diff --git a/chromium/webkit/browser/fileapi/mock_file_system_options.cc b/chromium/webkit/browser/fileapi/mock_file_system_options.cc
new file mode 100644
index 00000000000..06fbc27c20d
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/mock_file_system_options.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/mock_file_system_options.h"
+
+#include <string>
+#include <vector>
+
+namespace fileapi {
+
+FileSystemOptions CreateIncognitoFileSystemOptions() {
+ std::vector<std::string> additional_allowed_schemes;
+#if defined(OS_CHROMEOS)
+ additional_allowed_schemes.push_back("chrome-extension");
+#endif
+ return FileSystemOptions(FileSystemOptions::PROFILE_MODE_INCOGNITO,
+ additional_allowed_schemes);
+};
+
+FileSystemOptions CreateAllowFileAccessOptions() {
+ std::vector<std::string> additional_allowed_schemes;
+ additional_allowed_schemes.push_back("file");
+#if defined(OS_CHROMEOS)
+ additional_allowed_schemes.push_back("chrome-extension");
+#endif
+ return FileSystemOptions(FileSystemOptions::PROFILE_MODE_NORMAL,
+ additional_allowed_schemes);
+};
+
+FileSystemOptions CreateDisallowFileAccessOptions() {
+ std::vector<std::string> additional_allowed_schemes;
+#if defined(OS_CHROMEOS)
+ additional_allowed_schemes.push_back("chrome-extension");
+#endif
+ return FileSystemOptions(FileSystemOptions::PROFILE_MODE_NORMAL,
+ additional_allowed_schemes);
+};
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/mock_file_system_options.h b/chromium/webkit/browser/fileapi/mock_file_system_options.h
new file mode 100644
index 00000000000..f46f6354611
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/mock_file_system_options.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_OPTIONS_H_
+#define WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_OPTIONS_H_
+
+#include "webkit/browser/fileapi/file_system_options.h"
+
+namespace fileapi {
+
+// Returns Filesystem options for incognito mode.
+FileSystemOptions CreateIncognitoFileSystemOptions();
+
+// Returns Filesystem options that allow file access.
+FileSystemOptions CreateAllowFileAccessOptions();
+
+// Returns Filesystem options that disallow file access.
+FileSystemOptions CreateDisallowFileAccessOptions();
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_MOCK_FILE_SYSTEM_OPTIONS_H_
diff --git a/chromium/webkit/browser/fileapi/mount_points.cc b/chromium/webkit/browser/fileapi/mount_points.cc
new file mode 100644
index 00000000000..cd7c7b23c76
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/mount_points.cc
@@ -0,0 +1,15 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/mount_points.h"
+
+namespace fileapi {
+
+MountPoints::MountPointInfo::MountPointInfo() {}
+MountPoints::MountPointInfo::MountPointInfo(
+ const std::string& name, const base::FilePath& path)
+ : name(name), path(path) {}
+
+} // namespace fileapi
+
diff --git a/chromium/webkit/browser/fileapi/mount_points.h b/chromium/webkit/browser/fileapi/mount_points.h
new file mode 100644
index 00000000000..5ec67b95a9c
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/mount_points.h
@@ -0,0 +1,105 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_MOUNT_POINTS_H_
+#define WEBKIT_BROWSER_FILEAPI_MOUNT_POINTS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+class GURL;
+
+namespace fileapi {
+class FileSystemURL;
+}
+
+namespace fileapi {
+
+// Represents a set of mount points for File API.
+class WEBKIT_STORAGE_BROWSER_EXPORT MountPoints {
+ public:
+ struct WEBKIT_STORAGE_BROWSER_EXPORT MountPointInfo {
+ MountPointInfo();
+ MountPointInfo(const std::string& name, const base::FilePath& path);
+
+ // The name to be used to register the path. The registered file can
+ // be referred by a virtual path /<filesystem_id>/<name>.
+ // The name should NOT contain a path separator '/'.
+ std::string name;
+
+ // The path of the file.
+ base::FilePath path;
+
+ // For STL operation.
+ bool operator<(const MountPointInfo& that) const {
+ return name < that.name;
+ }
+ };
+
+ MountPoints() {}
+ virtual ~MountPoints() {}
+
+ // Revokes a mount point identified by |mount_name|.
+ // Returns false if the |mount_name| is not (no longer) registered.
+ // TODO(kinuko): Probably this should be rather named RevokeMountPoint.
+ virtual bool RevokeFileSystem(const std::string& mount_name) = 0;
+
+ // Returns true if the MountPoints implementation handles filesystems with
+ // the given mount type.
+ virtual bool HandlesFileSystemMountType(FileSystemType type) const = 0;
+
+ // Same as CreateCrackedFileSystemURL, but cracks FileSystemURL created
+ // from |url|.
+ virtual FileSystemURL CrackURL(const GURL& url) const = 0;
+
+ // Creates a FileSystemURL with the given origin, type and path and tries to
+ // crack it as a part of one of the registered mount points.
+ // If the the URL is not valid or does not belong to any of the mount points
+ // registered in this context, returns empty, invalid FileSystemURL.
+ virtual FileSystemURL CreateCrackedFileSystemURL(
+ const GURL& origin,
+ fileapi::FileSystemType type,
+ const base::FilePath& path) const = 0;
+
+ // Returns the mount point root path registered for a given |mount_name|.
+ // Returns false if the given |mount_name| is not valid.
+ virtual bool GetRegisteredPath(const std::string& mount_name,
+ base::FilePath* path) const = 0;
+
+ // Cracks the given |virtual_path| (which is the path part of a filesystem URL
+ // without '/external' or '/isolated' prefix part) and populates the
+ // |mount_name|, |type|, and |path| if the <mount_name> part embedded in
+ // the |virtual_path| (i.e. the first component of the |virtual_path|) is a
+ // valid registered filesystem ID or mount name for an existing mount point.
+ //
+ // Returns false if the given virtual_path cannot be cracked.
+ //
+ // Note that |path| is set to empty paths if the filesystem type is isolated
+ // and |virtual_path| has no <relative_path> part (i.e. pointing to the
+ // virtual root).
+ virtual bool CrackVirtualPath(const base::FilePath& virtual_path,
+ std::string* mount_name,
+ FileSystemType* type,
+ base::FilePath* path) const = 0;
+
+ protected:
+ friend class FileSystemContext;
+
+ // Same as CrackURL and CreateCrackedFileSystemURL, but cracks the url already
+ // instantiated as the FileSystemURL class. This is internally used for nested
+ // URL cracking in FileSystemContext.
+ virtual FileSystemURL CrackFileSystemURL(const FileSystemURL& url) const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MountPoints);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_MOUNT_POINTS_H_
diff --git a/chromium/webkit/browser/fileapi/native_file_util.cc b/chromium/webkit/browser/fileapi/native_file_util.cc
new file mode 100644
index 00000000000..32e9467091c
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/native_file_util.cc
@@ -0,0 +1,262 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/native_file_util.h"
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/memory/scoped_ptr.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+
+namespace fileapi {
+
+namespace {
+
+// Sets permissions on directory at |dir_path| based on the target platform.
+// Returns true on success, or false otherwise.
+//
+// TODO(benchan): Find a better place outside webkit to host this function.
+bool SetPlatformSpecificDirectoryPermissions(const base::FilePath& dir_path) {
+#if defined(OS_CHROMEOS)
+ // System daemons on Chrome OS may run as a user different than the Chrome
+ // process but need to access files under the directories created here.
+ // Because of that, grant the execute permission on the created directory
+ // to group and other users.
+ if (HANDLE_EINTR(chmod(dir_path.value().c_str(),
+ S_IRWXU | S_IXGRP | S_IXOTH)) != 0) {
+ return false;
+ }
+#endif
+ // Keep the directory permissions unchanged on non-Chrome OS platforms.
+ return true;
+}
+
+} // namespace
+
+using base::PlatformFile;
+using base::PlatformFileError;
+
+class NativeFileEnumerator : public FileSystemFileUtil::AbstractFileEnumerator {
+ public:
+ NativeFileEnumerator(const base::FilePath& root_path,
+ bool recursive,
+ int file_type)
+ : file_enum_(root_path, recursive, file_type) {
+ }
+
+ virtual ~NativeFileEnumerator() {}
+
+ virtual base::FilePath Next() OVERRIDE;
+ virtual int64 Size() OVERRIDE;
+ virtual base::Time LastModifiedTime() OVERRIDE;
+ virtual bool IsDirectory() OVERRIDE;
+
+ private:
+ base::FileEnumerator file_enum_;
+ base::FileEnumerator::FileInfo file_util_info_;
+};
+
+base::FilePath NativeFileEnumerator::Next() {
+ base::FilePath rv = file_enum_.Next();
+ if (!rv.empty())
+ file_util_info_ = file_enum_.GetInfo();
+ return rv;
+}
+
+int64 NativeFileEnumerator::Size() {
+ return file_util_info_.GetSize();
+}
+
+base::Time NativeFileEnumerator::LastModifiedTime() {
+ return file_util_info_.GetLastModifiedTime();
+}
+
+bool NativeFileEnumerator::IsDirectory() {
+ return file_util_info_.IsDirectory();
+}
+
+PlatformFileError NativeFileUtil::CreateOrOpen(
+ const base::FilePath& path, int file_flags,
+ PlatformFile* file_handle, bool* created) {
+ if (!base::DirectoryExists(path.DirName())) {
+ // If its parent does not exist, should return NOT_FOUND error.
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ }
+ if (base::DirectoryExists(path))
+ return base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+ PlatformFileError error_code = base::PLATFORM_FILE_OK;
+ *file_handle = base::CreatePlatformFile(path, file_flags,
+ created, &error_code);
+ return error_code;
+}
+
+PlatformFileError NativeFileUtil::Close(PlatformFile file_handle) {
+ if (!base::ClosePlatformFile(file_handle))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError NativeFileUtil::EnsureFileExists(
+ const base::FilePath& path,
+ bool* created) {
+ if (!base::DirectoryExists(path.DirName()))
+ // If its parent does not exist, should return NOT_FOUND error.
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ PlatformFileError error_code = base::PLATFORM_FILE_OK;
+ // Tries to create the |path| exclusively. This should fail
+ // with base::PLATFORM_FILE_ERROR_EXISTS if the path already exists.
+ PlatformFile handle = base::CreatePlatformFile(
+ path,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_READ,
+ created, &error_code);
+ if (error_code == base::PLATFORM_FILE_ERROR_EXISTS) {
+ // Make sure created_ is false.
+ if (created)
+ *created = false;
+ error_code = base::PLATFORM_FILE_OK;
+ }
+ if (handle != base::kInvalidPlatformFileValue)
+ base::ClosePlatformFile(handle);
+ return error_code;
+}
+
+PlatformFileError NativeFileUtil::CreateDirectory(
+ const base::FilePath& path,
+ bool exclusive,
+ bool recursive) {
+ // If parent dir of file doesn't exist.
+ if (!recursive && !base::PathExists(path.DirName()))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+
+ bool path_exists = base::PathExists(path);
+ if (exclusive && path_exists)
+ return base::PLATFORM_FILE_ERROR_EXISTS;
+
+ // If file exists at the path.
+ if (path_exists && !base::DirectoryExists(path))
+ return base::PLATFORM_FILE_ERROR_EXISTS;
+
+ if (!file_util::CreateDirectory(path))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+
+ if (!SetPlatformSpecificDirectoryPermissions(path))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError NativeFileUtil::GetFileInfo(
+ const base::FilePath& path,
+ base::PlatformFileInfo* file_info) {
+ if (!base::PathExists(path))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ if (!file_util::GetFileInfo(path, file_info))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ return base::PLATFORM_FILE_OK;
+}
+
+scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator>
+ NativeFileUtil::CreateFileEnumerator(const base::FilePath& root_path,
+ bool recursive) {
+ return make_scoped_ptr(new NativeFileEnumerator(
+ root_path, recursive,
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES))
+ .PassAs<FileSystemFileUtil::AbstractFileEnumerator>();
+}
+
+PlatformFileError NativeFileUtil::Touch(
+ const base::FilePath& path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) {
+ if (!file_util::TouchFile(
+ path, last_access_time, last_modified_time))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError NativeFileUtil::Truncate(
+ const base::FilePath& path, int64 length) {
+ PlatformFileError error_code(base::PLATFORM_FILE_ERROR_FAILED);
+ PlatformFile file =
+ base::CreatePlatformFile(
+ path,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE,
+ NULL,
+ &error_code);
+ if (error_code != base::PLATFORM_FILE_OK) {
+ return error_code;
+ }
+ DCHECK_NE(base::kInvalidPlatformFileValue, file);
+ if (!base::TruncatePlatformFile(file, length))
+ error_code = base::PLATFORM_FILE_ERROR_FAILED;
+ base::ClosePlatformFile(file);
+ return error_code;
+}
+
+bool NativeFileUtil::PathExists(const base::FilePath& path) {
+ return base::PathExists(path);
+}
+
+bool NativeFileUtil::DirectoryExists(const base::FilePath& path) {
+ return base::DirectoryExists(path);
+}
+
+PlatformFileError NativeFileUtil::CopyOrMoveFile(
+ const base::FilePath& src_path,
+ const base::FilePath& dest_path,
+ bool copy) {
+ base::PlatformFileInfo info;
+ base::PlatformFileError error = NativeFileUtil::GetFileInfo(src_path, &info);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ if (info.is_directory)
+ return base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+
+ error = NativeFileUtil::GetFileInfo(dest_path, &info);
+ if (error != base::PLATFORM_FILE_OK &&
+ error != base::PLATFORM_FILE_ERROR_NOT_FOUND)
+ return error;
+ if (info.is_directory)
+ return base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
+ if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) {
+ error = NativeFileUtil::GetFileInfo(dest_path.DirName(), &info);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ if (!info.is_directory)
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ }
+
+ if (copy) {
+ if (base::CopyFile(src_path, dest_path))
+ return base::PLATFORM_FILE_OK;
+ } else {
+ if (base::Move(src_path, dest_path))
+ return base::PLATFORM_FILE_OK;
+ }
+ return base::PLATFORM_FILE_ERROR_FAILED;
+}
+
+PlatformFileError NativeFileUtil::DeleteFile(const base::FilePath& path) {
+ if (!base::PathExists(path))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ if (base::DirectoryExists(path))
+ return base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+ if (!base::DeleteFile(path, false))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError NativeFileUtil::DeleteDirectory(const base::FilePath& path) {
+ if (!base::PathExists(path))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ if (!base::DirectoryExists(path))
+ return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
+ if (!file_util::IsDirectoryEmpty(path))
+ return base::PLATFORM_FILE_ERROR_NOT_EMPTY;
+ if (!base::DeleteFile(path, false))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ return base::PLATFORM_FILE_OK;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/native_file_util.h b/chromium/webkit/browser/fileapi/native_file_util.h
new file mode 100644
index 00000000000..425e7421597
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/native_file_util.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_
+#define WEBKIT_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_
+
+#include "base/files/file_path.h"
+#include "base/files/file_util_proxy.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class Time;
+}
+
+namespace fileapi {
+
+// A thin wrapper class for accessing the OS native filesystem.
+// This performs common error checks necessary to implement FileUtil family
+// in addition to perform native filesystem operations.
+//
+// For the error checks it performs please see the comment for
+// FileSystemFileUtil interface
+// (webkit/browser/fileapi/file_system_file_util.h).
+//
+// Note that all the methods of this class are static and this does NOT
+// inherit from FileSystemFileUtil.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE NativeFileUtil {
+ public:
+ static base::PlatformFileError CreateOrOpen(
+ const base::FilePath& path,
+ int file_flags,
+ base::PlatformFile* file_handle,
+ bool* created);
+ static base::PlatformFileError Close(base::PlatformFile file);
+ static base::PlatformFileError EnsureFileExists(const base::FilePath& path,
+ bool* created);
+ static base::PlatformFileError CreateDirectory(const base::FilePath& path,
+ bool exclusive,
+ bool recursive);
+ static base::PlatformFileError GetFileInfo(const base::FilePath& path,
+ base::PlatformFileInfo* file_info);
+ static scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator>
+ CreateFileEnumerator(const base::FilePath& root_path,
+ bool recursive);
+ static base::PlatformFileError Touch(const base::FilePath& path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time);
+ static base::PlatformFileError Truncate(const base::FilePath& path,
+ int64 length);
+ static bool PathExists(const base::FilePath& path);
+ static bool DirectoryExists(const base::FilePath& path);
+ static base::PlatformFileError CopyOrMoveFile(const base::FilePath& src_path,
+ const base::FilePath& dest_path,
+ bool copy);
+ static base::PlatformFileError DeleteFile(const base::FilePath& path);
+ static base::PlatformFileError DeleteDirectory(const base::FilePath& path);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(NativeFileUtil);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_
diff --git a/chromium/webkit/browser/fileapi/native_file_util_unittest.cc b/chromium/webkit/browser/fileapi/native_file_util_unittest.cc
new file mode 100644
index 00000000000..1ed699e2e44
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/native_file_util_unittest.cc
@@ -0,0 +1,341 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/platform_file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/native_file_util.h"
+
+namespace fileapi {
+
+class NativeFileUtilTest : public testing::Test {
+ public:
+ NativeFileUtilTest() {}
+
+ virtual void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ }
+
+ protected:
+ base::FilePath Path() {
+ return data_dir_.path();
+ }
+
+ base::FilePath Path(const char* file_name) {
+ return data_dir_.path().AppendASCII(file_name);
+ }
+
+ bool FileExists(const base::FilePath& path) {
+ return base::PathExists(path) &&
+ !base::DirectoryExists(path);
+ }
+
+ int64 GetSize(const base::FilePath& path) {
+ base::PlatformFileInfo info;
+ file_util::GetFileInfo(path, &info);
+ return info.size;
+ }
+
+ private:
+ base::ScopedTempDir data_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeFileUtilTest);
+};
+
+TEST_F(NativeFileUtilTest, CreateCloseAndDeleteFile) {
+ base::FilePath file_name = Path("test_file");
+ base::PlatformFile file_handle;
+ bool created = false;
+ int flags = base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_ASYNC;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CreateOrOpen(file_name,
+ base::PLATFORM_FILE_CREATE | flags,
+ &file_handle, &created));
+ ASSERT_TRUE(created);
+
+ EXPECT_TRUE(base::PathExists(file_name));
+ EXPECT_TRUE(NativeFileUtil::PathExists(file_name));
+ EXPECT_EQ(0, GetSize(file_name));
+ EXPECT_NE(base::kInvalidPlatformFileValue, file_handle);
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK, NativeFileUtil::Close(file_handle));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CreateOrOpen(file_name,
+ base::PLATFORM_FILE_OPEN | flags,
+ &file_handle, &created));
+ ASSERT_FALSE(created);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, NativeFileUtil::Close(file_handle));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::DeleteFile(file_name));
+ EXPECT_FALSE(base::PathExists(file_name));
+ EXPECT_FALSE(NativeFileUtil::PathExists(file_name));
+}
+
+TEST_F(NativeFileUtilTest, EnsureFileExists) {
+ base::FilePath file_name = Path("foobar");
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(file_name, &created));
+ ASSERT_TRUE(created);
+
+ EXPECT_TRUE(FileExists(file_name));
+ EXPECT_EQ(0, GetSize(file_name));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(file_name, &created));
+ EXPECT_FALSE(created);
+}
+
+TEST_F(NativeFileUtilTest, CreateAndDeleteDirectory) {
+ base::FilePath dir_name = Path("test_dir");
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CreateDirectory(dir_name,
+ false /* exclusive */,
+ false /* recursive */));
+
+ EXPECT_TRUE(NativeFileUtil::DirectoryExists(dir_name));
+ EXPECT_TRUE(base::DirectoryExists(dir_name));
+
+ ASSERT_EQ(base::PLATFORM_FILE_ERROR_EXISTS,
+ NativeFileUtil::CreateDirectory(dir_name,
+ true /* exclusive */,
+ false /* recursive */));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::DeleteDirectory(dir_name));
+ EXPECT_FALSE(base::DirectoryExists(dir_name));
+ EXPECT_FALSE(NativeFileUtil::DirectoryExists(dir_name));
+}
+
+TEST_F(NativeFileUtilTest, TouchFileAndGetFileInfo) {
+ base::FilePath file_name = Path("test_file");
+ base::PlatformFileInfo native_info;
+ ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ NativeFileUtil::GetFileInfo(file_name, &native_info));
+
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(file_name, &created));
+ ASSERT_TRUE(created);
+
+ base::PlatformFileInfo info;
+ ASSERT_TRUE(file_util::GetFileInfo(file_name, &info));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::GetFileInfo(file_name, &native_info));
+ ASSERT_EQ(info.size, native_info.size);
+ ASSERT_EQ(info.is_directory, native_info.is_directory);
+ ASSERT_EQ(info.is_symbolic_link, native_info.is_symbolic_link);
+ ASSERT_EQ(info.last_modified, native_info.last_modified);
+ ASSERT_EQ(info.last_accessed, native_info.last_accessed);
+ ASSERT_EQ(info.creation_time, native_info.creation_time);
+
+ const base::Time new_accessed =
+ info.last_accessed + base::TimeDelta::FromHours(10);
+ const base::Time new_modified =
+ info.last_modified + base::TimeDelta::FromHours(5);
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::Touch(file_name,
+ new_accessed, new_modified));
+
+ ASSERT_TRUE(file_util::GetFileInfo(file_name, &info));
+ EXPECT_EQ(new_accessed, info.last_accessed);
+ EXPECT_EQ(new_modified, info.last_modified);
+}
+
+TEST_F(NativeFileUtilTest, CreateFileEnumerator) {
+ base::FilePath path_1 = Path("dir1");
+ base::FilePath path_2 = Path("file1");
+ base::FilePath path_11 = Path("dir1").AppendASCII("file11");
+ base::FilePath path_12 = Path("dir1").AppendASCII("dir12");
+ base::FilePath path_121 =
+ Path("dir1").AppendASCII("dir12").AppendASCII("file121");
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CreateDirectory(path_1, false, false));
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(path_2, &created));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(path_11, &created));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CreateDirectory(path_12, false, false));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(path_121, &created));
+
+ {
+ scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator =
+ NativeFileUtil::CreateFileEnumerator(Path(), false);
+ std::set<base::FilePath> set;
+ set.insert(path_1);
+ set.insert(path_2);
+ for (base::FilePath path = enumerator->Next(); !path.empty();
+ path = enumerator->Next())
+ EXPECT_EQ(1U, set.erase(path));
+ EXPECT_TRUE(set.empty());
+ }
+
+ {
+ scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator =
+ NativeFileUtil::CreateFileEnumerator(Path(), true);
+ std::set<base::FilePath> set;
+ set.insert(path_1);
+ set.insert(path_2);
+ set.insert(path_11);
+ set.insert(path_12);
+ set.insert(path_121);
+ for (base::FilePath path = enumerator->Next(); !path.empty();
+ path = enumerator->Next())
+ EXPECT_EQ(1U, set.erase(path));
+ EXPECT_TRUE(set.empty());
+ }
+}
+
+TEST_F(NativeFileUtilTest, Truncate) {
+ base::FilePath file_name = Path("truncated");
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(file_name, &created));
+ ASSERT_TRUE(created);
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::Truncate(file_name, 1020));
+
+ EXPECT_TRUE(FileExists(file_name));
+ EXPECT_EQ(1020, GetSize(file_name));
+}
+
+TEST_F(NativeFileUtilTest, CopyFile) {
+ base::FilePath from_file = Path("fromfile");
+ base::FilePath to_file1 = Path("tofile1");
+ base::FilePath to_file2 = Path("tofile2");
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::Truncate(from_file, 1020));
+
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CopyOrMoveFile(from_file, to_file1, true));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CopyOrMoveFile(from_file, to_file2, true));
+
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+ EXPECT_TRUE(FileExists(to_file1));
+ EXPECT_EQ(1020, GetSize(to_file1));
+ EXPECT_TRUE(FileExists(to_file2));
+ EXPECT_EQ(1020, GetSize(to_file2));
+
+ base::FilePath dir = Path("dir");
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CreateDirectory(dir, false, false));
+ ASSERT_TRUE(base::DirectoryExists(dir));
+ base::FilePath to_dir_file = dir.AppendASCII("file");
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CopyOrMoveFile(from_file, to_dir_file, true));
+ EXPECT_TRUE(FileExists(to_dir_file));
+ EXPECT_EQ(1020, GetSize(to_dir_file));
+
+ // Following tests are error checking.
+ // Source doesn't exist.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ NativeFileUtil::CopyOrMoveFile(Path("nonexists"), Path("file"),
+ true));
+
+ // Source is not a file.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE,
+ NativeFileUtil::CopyOrMoveFile(dir, Path("file"), true));
+ // Destination is not a file.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION,
+ NativeFileUtil::CopyOrMoveFile(from_file, dir, true));
+ // Destination's parent doesn't exist.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ NativeFileUtil::CopyOrMoveFile(from_file,
+ Path("nodir").AppendASCII("file"),
+ true));
+ // Destination's parent is a file.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ NativeFileUtil::CopyOrMoveFile(from_file,
+ Path("tofile1").AppendASCII("file"),
+ true));
+}
+
+TEST_F(NativeFileUtilTest, MoveFile) {
+ base::FilePath from_file = Path("fromfile");
+ base::FilePath to_file = Path("tofile");
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(created);
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK, NativeFileUtil::Truncate(from_file, 1020));
+
+ EXPECT_TRUE(FileExists(from_file));
+ EXPECT_EQ(1020, GetSize(from_file));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CopyOrMoveFile(from_file, to_file, false));
+
+ EXPECT_FALSE(FileExists(from_file));
+ EXPECT_TRUE(FileExists(to_file));
+ EXPECT_EQ(1020, GetSize(to_file));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(FileExists(from_file));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, NativeFileUtil::Truncate(from_file, 1020));
+
+ base::FilePath dir = Path("dir");
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CreateDirectory(dir, false, false));
+ ASSERT_TRUE(base::DirectoryExists(dir));
+ base::FilePath to_dir_file = dir.AppendASCII("file");
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::CopyOrMoveFile(from_file, to_dir_file, false));
+ EXPECT_FALSE(FileExists(from_file));
+ EXPECT_TRUE(FileExists(to_dir_file));
+ EXPECT_EQ(1020, GetSize(to_dir_file));
+
+ // Following is error checking.
+ // Source doesn't exist.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ NativeFileUtil::CopyOrMoveFile(Path("nonexists"), Path("file"),
+ false));
+
+ // Source is not a file.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE,
+ NativeFileUtil::CopyOrMoveFile(dir, Path("file"), false));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(FileExists(from_file));
+ // Destination is not a file.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION,
+ NativeFileUtil::CopyOrMoveFile(from_file, dir, false));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ NativeFileUtil::EnsureFileExists(from_file, &created));
+ ASSERT_TRUE(FileExists(from_file));
+ // Destination's parent doesn't exist.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ NativeFileUtil::CopyOrMoveFile(from_file,
+ Path("nodir").AppendASCII("file"),
+ false));
+ // Destination's parent is a file.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ NativeFileUtil::CopyOrMoveFile(from_file,
+ Path("tofile1").AppendASCII("file"),
+ false));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/obfuscated_file_util.cc b/chromium/webkit/browser/fileapi/obfuscated_file_util.cc
new file mode 100644
index 00000000000..7e2f7df12a8
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/obfuscated_file_util.cc
@@ -0,0 +1,1444 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/obfuscated_file_util.h"
+
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/native_file_util.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+#include "webkit/browser/fileapi/sandbox_isolated_origin_database.h"
+#include "webkit/browser/fileapi/sandbox_origin_database.h"
+#include "webkit/browser/fileapi/timed_task_helper.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/database/database_identifier.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+// Example of various paths:
+// void ObfuscatedFileUtil::DoSomething(const FileSystemURL& url) {
+// base::FilePath virtual_path = url.path();
+// base::FilePath local_path = GetLocalFilePath(url);
+//
+// NativeFileUtil::DoSomething(local_path);
+// file_util::DoAnother(local_path);
+// }
+
+namespace fileapi {
+
+namespace {
+
+typedef SandboxDirectoryDatabase::FileId FileId;
+typedef SandboxDirectoryDatabase::FileInfo FileInfo;
+
+void InitFileInfo(
+ SandboxDirectoryDatabase::FileInfo* file_info,
+ SandboxDirectoryDatabase::FileId parent_id,
+ const base::FilePath::StringType& file_name) {
+ DCHECK(file_info);
+ file_info->parent_id = parent_id;
+ file_info->name = file_name;
+}
+
+// Costs computed as per crbug.com/86114, based on the LevelDB implementation of
+// path storage under Linux. It's not clear if that will differ on Windows, on
+// which base::FilePath uses wide chars [since they're converted to UTF-8 for storage
+// anyway], but as long as the cost is high enough that one can't cheat on quota
+// by storing data in paths, it doesn't need to be all that accurate.
+const int64 kPathCreationQuotaCost = 146; // Bytes per inode, basically.
+const int64 kPathByteQuotaCost = 2; // Bytes per byte of path length in UTF-8.
+
+int64 UsageForPath(size_t length) {
+ return kPathCreationQuotaCost +
+ static_cast<int64>(length) * kPathByteQuotaCost;
+}
+
+bool AllocateQuota(FileSystemOperationContext* context, int64 growth) {
+ if (context->allowed_bytes_growth() == quota::QuotaManager::kNoLimit)
+ return true;
+
+ int64 new_quota = context->allowed_bytes_growth() - growth;
+ if (growth > 0 && new_quota < 0)
+ return false;
+ context->set_allowed_bytes_growth(new_quota);
+ return true;
+}
+
+void UpdateUsage(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 growth) {
+ context->update_observers()->Notify(
+ &FileUpdateObserver::OnUpdate, MakeTuple(url, growth));
+}
+
+void TouchDirectory(SandboxDirectoryDatabase* db, FileId dir_id) {
+ DCHECK(db);
+ if (!db->UpdateModificationTime(dir_id, base::Time::Now()))
+ NOTREACHED();
+}
+
+const base::FilePath::CharType kTemporaryDirectoryName[] = FILE_PATH_LITERAL("t");
+const base::FilePath::CharType kPersistentDirectoryName[] = FILE_PATH_LITERAL("p");
+const base::FilePath::CharType kSyncableDirectoryName[] = FILE_PATH_LITERAL("s");
+
+} // namespace
+
+using base::PlatformFile;
+using base::PlatformFileError;
+
+class ObfuscatedFileEnumerator
+ : public FileSystemFileUtil::AbstractFileEnumerator {
+ public:
+ ObfuscatedFileEnumerator(
+ SandboxDirectoryDatabase* db,
+ FileSystemOperationContext* context,
+ ObfuscatedFileUtil* obfuscated_file_util,
+ const FileSystemURL& root_url,
+ bool recursive)
+ : db_(db),
+ context_(context),
+ obfuscated_file_util_(obfuscated_file_util),
+ origin_(root_url.origin()),
+ type_(root_url.type()),
+ recursive_(recursive),
+ current_file_id_(0) {
+ base::FilePath root_virtual_path = root_url.path();
+ FileId file_id;
+
+ if (!db_->GetFileWithPath(root_virtual_path, &file_id))
+ return;
+
+ FileRecord record = { file_id, root_virtual_path };
+ recurse_queue_.push(record);
+ }
+
+ virtual ~ObfuscatedFileEnumerator() {}
+
+ virtual base::FilePath Next() OVERRIDE {
+ ProcessRecurseQueue();
+ if (display_stack_.empty())
+ return base::FilePath();
+
+ current_file_id_ = display_stack_.back();
+ display_stack_.pop_back();
+
+ FileInfo file_info;
+ base::FilePath platform_file_path;
+ base::PlatformFileError error =
+ obfuscated_file_util_->GetFileInfoInternal(
+ db_, context_, origin_, type_, current_file_id_,
+ &file_info, &current_platform_file_info_, &platform_file_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return Next();
+
+ base::FilePath virtual_path =
+ current_parent_virtual_path_.Append(file_info.name);
+ if (recursive_ && file_info.is_directory()) {
+ FileRecord record = { current_file_id_, virtual_path };
+ recurse_queue_.push(record);
+ }
+ return virtual_path;
+ }
+
+ virtual int64 Size() OVERRIDE {
+ return current_platform_file_info_.size;
+ }
+
+ virtual base::Time LastModifiedTime() OVERRIDE {
+ return current_platform_file_info_.last_modified;
+ }
+
+ virtual bool IsDirectory() OVERRIDE {
+ return current_platform_file_info_.is_directory;
+ }
+
+ private:
+ typedef SandboxDirectoryDatabase::FileId FileId;
+ typedef SandboxDirectoryDatabase::FileInfo FileInfo;
+
+ struct FileRecord {
+ FileId file_id;
+ base::FilePath virtual_path;
+ };
+
+ void ProcessRecurseQueue() {
+ while (display_stack_.empty() && !recurse_queue_.empty()) {
+ FileRecord entry = recurse_queue_.front();
+ recurse_queue_.pop();
+ if (!db_->ListChildren(entry.file_id, &display_stack_)) {
+ display_stack_.clear();
+ return;
+ }
+ current_parent_virtual_path_ = entry.virtual_path;
+ }
+ }
+
+ SandboxDirectoryDatabase* db_;
+ FileSystemOperationContext* context_;
+ ObfuscatedFileUtil* obfuscated_file_util_;
+ GURL origin_;
+ FileSystemType type_;
+ bool recursive_;
+
+ std::queue<FileRecord> recurse_queue_;
+ std::vector<FileId> display_stack_;
+ base::FilePath current_parent_virtual_path_;
+
+ FileId current_file_id_;
+ base::PlatformFileInfo current_platform_file_info_;
+};
+
+class ObfuscatedOriginEnumerator
+ : public ObfuscatedFileUtil::AbstractOriginEnumerator {
+ public:
+ typedef SandboxOriginDatabase::OriginRecord OriginRecord;
+ ObfuscatedOriginEnumerator(
+ SandboxOriginDatabaseInterface* origin_database,
+ const base::FilePath& base_file_path)
+ : base_file_path_(base_file_path) {
+ if (origin_database)
+ origin_database->ListAllOrigins(&origins_);
+ }
+
+ virtual ~ObfuscatedOriginEnumerator() {}
+
+ // Returns the next origin. Returns empty if there are no more origins.
+ virtual GURL Next() OVERRIDE {
+ OriginRecord record;
+ if (!origins_.empty()) {
+ record = origins_.back();
+ origins_.pop_back();
+ }
+ current_ = record;
+ return webkit_database::GetOriginFromIdentifier(record.origin);
+ }
+
+ // Returns the current origin's information.
+ virtual bool HasFileSystemType(FileSystemType type) const OVERRIDE {
+ if (current_.path.empty())
+ return false;
+ base::FilePath::StringType type_string =
+ ObfuscatedFileUtil::GetDirectoryNameForType(type);
+ if (type_string.empty()) {
+ NOTREACHED();
+ return false;
+ }
+ base::FilePath path =
+ base_file_path_.Append(current_.path).Append(type_string);
+ return base::DirectoryExists(path);
+ }
+
+ private:
+ std::vector<OriginRecord> origins_;
+ OriginRecord current_;
+ base::FilePath base_file_path_;
+};
+
+ObfuscatedFileUtil::ObfuscatedFileUtil(
+ quota::SpecialStoragePolicy* special_storage_policy,
+ const base::FilePath& file_system_directory,
+ base::SequencedTaskRunner* file_task_runner)
+ : special_storage_policy_(special_storage_policy),
+ file_system_directory_(file_system_directory),
+ db_flush_delay_seconds_(10 * 60), // 10 mins.
+ file_task_runner_(file_task_runner) {
+}
+
+ObfuscatedFileUtil::~ObfuscatedFileUtil() {
+ DropDatabases();
+}
+
+PlatformFileError ObfuscatedFileUtil::CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, int file_flags,
+ PlatformFile* file_handle, bool* created) {
+ PlatformFileError error = CreateOrOpenInternal(context, url, file_flags,
+ file_handle, created);
+ if (*file_handle != base::kInvalidPlatformFileValue &&
+ file_flags & base::PLATFORM_FILE_WRITE &&
+ context->quota_limit_type() == quota::kQuotaLimitTypeUnlimited) {
+ DCHECK_EQ(base::PLATFORM_FILE_OK, error);
+ context->file_system_context()->sandbox_delegate()->
+ StickyInvalidateUsageCache(url.origin(), url.type());
+ }
+ return error;
+}
+
+PlatformFileError ObfuscatedFileUtil::Close(
+ FileSystemOperationContext* context,
+ base::PlatformFile file) {
+ return NativeFileUtil::Close(file);
+}
+
+PlatformFileError ObfuscatedFileUtil::EnsureFileExists(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool* created) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ url.origin(), url.type(), true);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_FAILED;
+
+ FileId file_id;
+ if (db->GetFileWithPath(url.path(), &file_id)) {
+ FileInfo file_info;
+ if (!db->GetFileInfo(file_id, &file_info)) {
+ NOTREACHED();
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ if (file_info.is_directory())
+ return base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+ if (created)
+ *created = false;
+ return base::PLATFORM_FILE_OK;
+ }
+ FileId parent_id;
+ if (!db->GetFileWithPath(VirtualPath::DirName(url.path()), &parent_id))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+
+ FileInfo file_info;
+ InitFileInfo(&file_info, parent_id,
+ VirtualPath::BaseName(url.path()).value());
+
+ int64 growth = UsageForPath(file_info.name.size());
+ if (!AllocateQuota(context, growth))
+ return base::PLATFORM_FILE_ERROR_NO_SPACE;
+ PlatformFileError error = CreateFile(
+ context, base::FilePath(), url.origin(), url.type(), &file_info, 0, NULL);
+ if (created && base::PLATFORM_FILE_OK == error) {
+ *created = true;
+ UpdateUsage(context, url, growth);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnCreateFile, MakeTuple(url));
+ }
+ return error;
+}
+
+PlatformFileError ObfuscatedFileUtil::CreateDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ url.origin(), url.type(), true);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_FAILED;
+
+ FileId file_id;
+ if (db->GetFileWithPath(url.path(), &file_id)) {
+ FileInfo file_info;
+ if (exclusive)
+ return base::PLATFORM_FILE_ERROR_EXISTS;
+ if (!db->GetFileInfo(file_id, &file_info)) {
+ NOTREACHED();
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ if (!file_info.is_directory())
+ return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
+ return base::PLATFORM_FILE_OK;
+ }
+
+ std::vector<base::FilePath::StringType> components;
+ VirtualPath::GetComponents(url.path(), &components);
+ FileId parent_id = 0;
+ size_t index;
+ for (index = 0; index < components.size(); ++index) {
+ base::FilePath::StringType name = components[index];
+ if (name == FILE_PATH_LITERAL("/"))
+ continue;
+ if (!db->GetChildWithName(parent_id, name, &parent_id))
+ break;
+ }
+ if (!recursive && components.size() - index > 1)
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ bool first = true;
+ for (; index < components.size(); ++index) {
+ FileInfo file_info;
+ file_info.name = components[index];
+ if (file_info.name == FILE_PATH_LITERAL("/"))
+ continue;
+ file_info.modification_time = base::Time::Now();
+ file_info.parent_id = parent_id;
+ int64 growth = UsageForPath(file_info.name.size());
+ if (!AllocateQuota(context, growth))
+ return base::PLATFORM_FILE_ERROR_NO_SPACE;
+ if (!db->AddFileInfo(file_info, &parent_id)) {
+ NOTREACHED();
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ UpdateUsage(context, url, growth);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnCreateDirectory, MakeTuple(url));
+ if (first) {
+ first = false;
+ TouchDirectory(db, file_info.parent_id);
+ }
+ }
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError ObfuscatedFileUtil::GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_file_path) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ url.origin(), url.type(), false);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ FileInfo local_info;
+ return GetFileInfoInternal(db, context,
+ url.origin(), url.type(),
+ file_id, &local_info,
+ file_info, platform_file_path);
+}
+
+scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator>
+ ObfuscatedFileUtil::CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url) {
+ return CreateFileEnumerator(context, root_url, false /* recursive */);
+}
+
+PlatformFileError ObfuscatedFileUtil::GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::FilePath* local_path) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ url.origin(), url.type(), false);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ FileInfo file_info;
+ if (!db->GetFileInfo(file_id, &file_info) || file_info.is_directory()) {
+ NOTREACHED();
+ // Directories have no local file path.
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ }
+ *local_path = DataPathToLocalPath(
+ url.origin(), url.type(), file_info.data_path);
+
+ if (local_path->empty())
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError ObfuscatedFileUtil::Touch(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ url.origin(), url.type(), false);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+
+ FileInfo file_info;
+ if (!db->GetFileInfo(file_id, &file_info)) {
+ NOTREACHED();
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ if (file_info.is_directory()) {
+ if (!db->UpdateModificationTime(file_id, last_modified_time))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ return base::PLATFORM_FILE_OK;
+ }
+ base::FilePath local_path = DataPathToLocalPath(
+ url.origin(), url.type(), file_info.data_path);
+ return NativeFileUtil::Touch(
+ local_path, last_access_time, last_modified_time);
+}
+
+PlatformFileError ObfuscatedFileUtil::Truncate(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 length) {
+ base::PlatformFileInfo file_info;
+ base::FilePath local_path;
+ base::PlatformFileError error =
+ GetFileInfo(context, url, &file_info, &local_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+
+ int64 growth = length - file_info.size;
+ if (!AllocateQuota(context, growth))
+ return base::PLATFORM_FILE_ERROR_NO_SPACE;
+ error = NativeFileUtil::Truncate(local_path, length);
+ if (error == base::PLATFORM_FILE_OK) {
+ UpdateUsage(context, url, growth);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnModifyFile, MakeTuple(url));
+ }
+ return error;
+}
+
+PlatformFileError ObfuscatedFileUtil::CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ bool copy) {
+ // Cross-filesystem copies and moves should be handled via CopyInForeignFile.
+ DCHECK(src_url.origin() == dest_url.origin());
+ DCHECK(src_url.type() == dest_url.type());
+
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ src_url.origin(), src_url.type(), true);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_FAILED;
+
+ FileId src_file_id;
+ if (!db->GetFileWithPath(src_url.path(), &src_file_id))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+
+ FileId dest_file_id;
+ bool overwrite = db->GetFileWithPath(dest_url.path(),
+ &dest_file_id);
+
+ FileInfo src_file_info;
+ base::PlatformFileInfo src_platform_file_info;
+ base::FilePath src_local_path;
+ base::PlatformFileError error = GetFileInfoInternal(
+ db, context, src_url.origin(), src_url.type(), src_file_id,
+ &src_file_info, &src_platform_file_info, &src_local_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ if (src_file_info.is_directory())
+ return base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+
+ FileInfo dest_file_info;
+ base::PlatformFileInfo dest_platform_file_info; // overwrite case only
+ base::FilePath dest_local_path; // overwrite case only
+ if (overwrite) {
+ base::PlatformFileError error = GetFileInfoInternal(
+ db, context, dest_url.origin(), dest_url.type(), dest_file_id,
+ &dest_file_info, &dest_platform_file_info, &dest_local_path);
+ if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND)
+ overwrite = false; // fallback to non-overwrite case
+ else if (error != base::PLATFORM_FILE_OK)
+ return error;
+ else if (dest_file_info.is_directory())
+ return base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
+ }
+ if (!overwrite) {
+ FileId dest_parent_id;
+ if (!db->GetFileWithPath(VirtualPath::DirName(dest_url.path()),
+ &dest_parent_id)) {
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ }
+
+ dest_file_info = src_file_info;
+ dest_file_info.parent_id = dest_parent_id;
+ dest_file_info.name =
+ VirtualPath::BaseName(dest_url.path()).value();
+ }
+
+ int64 growth = 0;
+ if (copy)
+ growth += src_platform_file_info.size;
+ else
+ growth -= UsageForPath(src_file_info.name.size());
+ if (overwrite)
+ growth -= dest_platform_file_info.size;
+ else
+ growth += UsageForPath(dest_file_info.name.size());
+ if (!AllocateQuota(context, growth))
+ return base::PLATFORM_FILE_ERROR_NO_SPACE;
+
+ /*
+ * Copy-with-overwrite
+ * Just overwrite data file
+ * Copy-without-overwrite
+ * Copy backing file
+ * Create new metadata pointing to new backing file.
+ * Move-with-overwrite
+ * transaction:
+ * Remove source entry.
+ * Point target entry to source entry's backing file.
+ * Delete target entry's old backing file
+ * Move-without-overwrite
+ * Just update metadata
+ */
+ error = base::PLATFORM_FILE_ERROR_FAILED;
+ if (copy) {
+ if (overwrite) {
+ error = NativeFileUtil::CopyOrMoveFile(
+ src_local_path,
+ dest_local_path,
+ true /* copy */);
+ } else { // non-overwrite
+ error = CreateFile(context, src_local_path,
+ dest_url.origin(), dest_url.type(),
+ &dest_file_info, 0, NULL);
+ }
+ } else {
+ if (overwrite) {
+ if (db->OverwritingMoveFile(src_file_id, dest_file_id)) {
+ if (base::PLATFORM_FILE_OK !=
+ NativeFileUtil::DeleteFile(dest_local_path))
+ LOG(WARNING) << "Leaked a backing file.";
+ error = base::PLATFORM_FILE_OK;
+ } else {
+ error = base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ } else { // non-overwrite
+ if (db->UpdateFileInfo(src_file_id, dest_file_info))
+ error = base::PLATFORM_FILE_OK;
+ else
+ error = base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ }
+
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+
+ if (overwrite) {
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnModifyFile,
+ MakeTuple(dest_url));
+ } else {
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnCreateFileFrom,
+ MakeTuple(dest_url, src_url));
+ }
+
+ if (!copy) {
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnRemoveFile, MakeTuple(src_url));
+ TouchDirectory(db, src_file_info.parent_id);
+ }
+
+ TouchDirectory(db, dest_file_info.parent_id);
+
+ UpdateUsage(context, dest_url, growth);
+ return error;
+}
+
+PlatformFileError ObfuscatedFileUtil::CopyInForeignFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ dest_url.origin(), dest_url.type(), true);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_FAILED;
+
+ base::PlatformFileInfo src_platform_file_info;
+ if (!file_util::GetFileInfo(src_file_path, &src_platform_file_info))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+
+ FileId dest_file_id;
+ bool overwrite = db->GetFileWithPath(dest_url.path(),
+ &dest_file_id);
+
+ FileInfo dest_file_info;
+ base::PlatformFileInfo dest_platform_file_info; // overwrite case only
+ if (overwrite) {
+ base::FilePath dest_local_path;
+ base::PlatformFileError error = GetFileInfoInternal(
+ db, context, dest_url.origin(), dest_url.type(), dest_file_id,
+ &dest_file_info, &dest_platform_file_info, &dest_local_path);
+ if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND)
+ overwrite = false; // fallback to non-overwrite case
+ else if (error != base::PLATFORM_FILE_OK)
+ return error;
+ else if (dest_file_info.is_directory())
+ return base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
+ }
+ if (!overwrite) {
+ FileId dest_parent_id;
+ if (!db->GetFileWithPath(VirtualPath::DirName(dest_url.path()),
+ &dest_parent_id)) {
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ }
+ if (!dest_file_info.is_directory())
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ InitFileInfo(&dest_file_info, dest_parent_id,
+ VirtualPath::BaseName(dest_url.path()).value());
+ }
+
+ int64 growth = src_platform_file_info.size;
+ if (overwrite)
+ growth -= dest_platform_file_info.size;
+ else
+ growth += UsageForPath(dest_file_info.name.size());
+ if (!AllocateQuota(context, growth))
+ return base::PLATFORM_FILE_ERROR_NO_SPACE;
+
+ base::PlatformFileError error;
+ if (overwrite) {
+ base::FilePath dest_local_path = DataPathToLocalPath(
+ dest_url.origin(), dest_url.type(), dest_file_info.data_path);
+ error = NativeFileUtil::CopyOrMoveFile(
+ src_file_path, dest_local_path, true);
+ } else {
+ error = CreateFile(context, src_file_path,
+ dest_url.origin(), dest_url.type(),
+ &dest_file_info, 0, NULL);
+ }
+
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+
+ if (overwrite) {
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnModifyFile, MakeTuple(dest_url));
+ } else {
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnCreateFile, MakeTuple(dest_url));
+ }
+
+ UpdateUsage(context, dest_url, growth);
+ TouchDirectory(db, dest_file_info.parent_id);
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError ObfuscatedFileUtil::DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ url.origin(), url.type(), true);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+
+ FileInfo file_info;
+ base::PlatformFileInfo platform_file_info;
+ base::FilePath local_path;
+ base::PlatformFileError error = GetFileInfoInternal(
+ db, context, url.origin(), url.type(), file_id,
+ &file_info, &platform_file_info, &local_path);
+ if (error != base::PLATFORM_FILE_ERROR_NOT_FOUND &&
+ error != base::PLATFORM_FILE_OK)
+ return error;
+
+ if (file_info.is_directory())
+ return base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+
+ int64 growth = -UsageForPath(file_info.name.size()) - platform_file_info.size;
+ AllocateQuota(context, growth);
+ if (!db->RemoveFileInfo(file_id)) {
+ NOTREACHED();
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ UpdateUsage(context, url, growth);
+ TouchDirectory(db, file_info.parent_id);
+
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnRemoveFile, MakeTuple(url));
+
+ if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND)
+ return base::PLATFORM_FILE_OK;
+
+ error = NativeFileUtil::DeleteFile(local_path);
+ if (base::PLATFORM_FILE_OK != error)
+ LOG(WARNING) << "Leaked a backing file.";
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError ObfuscatedFileUtil::DeleteDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ url.origin(), url.type(), true);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_FAILED;
+
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ FileInfo file_info;
+ if (!db->GetFileInfo(file_id, &file_info)) {
+ NOTREACHED();
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ if (!file_info.is_directory())
+ return base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
+ if (!db->RemoveFileInfo(file_id))
+ return base::PLATFORM_FILE_ERROR_NOT_EMPTY;
+ int64 growth = -UsageForPath(file_info.name.size());
+ AllocateQuota(context, growth);
+ UpdateUsage(context, url, growth);
+ TouchDirectory(db, file_info.parent_id);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnRemoveDirectory, MakeTuple(url));
+ return base::PLATFORM_FILE_OK;
+}
+
+webkit_blob::ScopedFile ObfuscatedFileUtil::CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileError* error,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_path) {
+ // We're just returning the local file information.
+ *error = GetFileInfo(context, url, file_info, platform_path);
+ if (*error == base::PLATFORM_FILE_OK && file_info->is_directory) {
+ *file_info = base::PlatformFileInfo();
+ *error = base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+ }
+ return webkit_blob::ScopedFile();
+}
+
+scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator>
+ ObfuscatedFileUtil::CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url,
+ bool recursive) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ root_url.origin(), root_url.type(), false);
+ if (!db) {
+ return scoped_ptr<AbstractFileEnumerator>(new EmptyFileEnumerator());
+ }
+ return scoped_ptr<AbstractFileEnumerator>(
+ new ObfuscatedFileEnumerator(db, context, this, root_url, recursive));
+}
+
+bool ObfuscatedFileUtil::IsDirectoryEmpty(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ url.origin(), url.type(), false);
+ if (!db)
+ return true; // Not a great answer, but it's what others do.
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return true; // Ditto.
+ FileInfo file_info;
+ if (!db->GetFileInfo(file_id, &file_info)) {
+ DCHECK(!file_id);
+ // It's the root directory and the database hasn't been initialized yet.
+ return true;
+ }
+ if (!file_info.is_directory())
+ return true;
+ std::vector<FileId> children;
+ // TODO(ericu): This could easily be made faster with help from the database.
+ if (!db->ListChildren(file_id, &children))
+ return true;
+ return children.empty();
+}
+
+base::FilePath ObfuscatedFileUtil::GetDirectoryForOriginAndType(
+ const GURL& origin,
+ FileSystemType type,
+ bool create,
+ base::PlatformFileError* error_code) {
+ base::FilePath origin_dir = GetDirectoryForOrigin(origin, create, error_code);
+ if (origin_dir.empty())
+ return base::FilePath();
+ base::FilePath::StringType type_string = GetDirectoryNameForType(type);
+ if (type_string.empty()) {
+ LOG(WARNING) << "Unknown filesystem type requested:" << type;
+
+ if (error_code)
+ *error_code = base::PLATFORM_FILE_ERROR_INVALID_URL;
+ return base::FilePath();
+ }
+ base::FilePath path = origin_dir.Append(type_string);
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ if (!base::DirectoryExists(path) &&
+ (!create || !file_util::CreateDirectory(path))) {
+ error = create ?
+ base::PLATFORM_FILE_ERROR_FAILED :
+ base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ }
+
+ if (error_code)
+ *error_code = error;
+ return path;
+}
+
+bool ObfuscatedFileUtil::DeleteDirectoryForOriginAndType(
+ const GURL& origin, FileSystemType type) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::FilePath origin_type_path = GetDirectoryForOriginAndType(
+ origin, type, false, &error);
+ if (origin_type_path.empty())
+ return true;
+
+ if (error != base::PLATFORM_FILE_ERROR_NOT_FOUND) {
+ // TODO(dmikurube): Consider the return value of DestroyDirectoryDatabase.
+ // We ignore its error now since 1) it doesn't matter the final result, and
+ // 2) it always returns false in Windows because of LevelDB's
+ // implementation.
+ // Information about failure would be useful for debugging.
+ DestroyDirectoryDatabase(origin, type);
+ if (!base::DeleteFile(origin_type_path, true /* recursive */))
+ return false;
+ }
+
+ base::FilePath origin_path = VirtualPath::DirName(origin_type_path);
+ DCHECK_EQ(origin_path.value(),
+ GetDirectoryForOrigin(origin, false, NULL).value());
+
+ // At this point we are sure we had successfully deleted the origin/type
+ // directory (i.e. we're ready to just return true).
+ // See if we have other directories in this origin directory.
+ std::vector<FileSystemType> other_types;
+ if (type != kFileSystemTypeTemporary)
+ other_types.push_back(kFileSystemTypeTemporary);
+ if (type != kFileSystemTypePersistent)
+ other_types.push_back(kFileSystemTypePersistent);
+ if (type != kFileSystemTypeSyncable)
+ other_types.push_back(kFileSystemTypeSyncable);
+ DCHECK(type != kFileSystemTypeSyncableForInternalSync);
+
+ for (size_t i = 0; i < other_types.size(); ++i) {
+ if (base::DirectoryExists(
+ origin_path.Append(GetDirectoryNameForType(other_types[i])))) {
+ // Other type's directory exists; just return true here.
+ return true;
+ }
+ }
+
+ // No other directories seem exist. Try deleting the entire origin directory.
+ InitOriginDatabase(false);
+ if (origin_database_) {
+ origin_database_->RemovePathForOrigin(
+ webkit_database::GetIdentifierFromOrigin(origin));
+ }
+ if (!base::DeleteFile(origin_path, true /* recursive */))
+ return false;
+
+ return true;
+}
+
+// static
+base::FilePath::StringType ObfuscatedFileUtil::GetDirectoryNameForType(
+ FileSystemType type) {
+ switch (type) {
+ case kFileSystemTypeTemporary:
+ return kTemporaryDirectoryName;
+ case kFileSystemTypePersistent:
+ return kPersistentDirectoryName;
+ case kFileSystemTypeSyncable:
+ case kFileSystemTypeSyncableForInternalSync:
+ return kSyncableDirectoryName;
+ case kFileSystemTypeUnknown:
+ default:
+ return base::FilePath::StringType();
+ }
+}
+
+ObfuscatedFileUtil::AbstractOriginEnumerator*
+ObfuscatedFileUtil::CreateOriginEnumerator() {
+ std::vector<SandboxOriginDatabase::OriginRecord> origins;
+
+ InitOriginDatabase(false);
+ return new ObfuscatedOriginEnumerator(
+ origin_database_.get(), file_system_directory_);
+}
+
+bool ObfuscatedFileUtil::DestroyDirectoryDatabase(
+ const GURL& origin, FileSystemType type) {
+ std::string key = GetDirectoryDatabaseKey(origin, type);
+ if (key.empty())
+ return true;
+ DirectoryMap::iterator iter = directories_.find(key);
+ if (iter != directories_.end()) {
+ SandboxDirectoryDatabase* database = iter->second;
+ directories_.erase(iter);
+ delete database;
+ }
+
+ PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::FilePath path = GetDirectoryForOriginAndType(
+ origin, type, false, &error);
+ if (path.empty() || error == base::PLATFORM_FILE_ERROR_NOT_FOUND)
+ return true;
+ return SandboxDirectoryDatabase::DestroyDatabase(path);
+}
+
+// static
+int64 ObfuscatedFileUtil::ComputeFilePathCost(const base::FilePath& path) {
+ return UsageForPath(VirtualPath::BaseName(path).value().size());
+}
+
+void ObfuscatedFileUtil::MaybePrepopulateDatabase() {
+ // Always disable this for now. crbug.com/264429
+ return;
+
+ base::FilePath isolated_origin_dir = file_system_directory_.Append(
+ SandboxIsolatedOriginDatabase::kOriginDirectory);
+ if (!base::DirectoryExists(isolated_origin_dir))
+ return;
+
+ const FileSystemType kPrepopulateTypes[] = {
+ kFileSystemTypePersistent, kFileSystemTypeTemporary
+ };
+
+ // Prepulate the directory database(s) if and only if this instance is
+ // initialized for isolated storage dedicated for a single origin.
+ for (size_t i = 0; i < arraysize(kPrepopulateTypes); ++i) {
+ const FileSystemType type = kPrepopulateTypes[i];
+ base::FilePath::StringType type_string = GetDirectoryNameForType(type);
+ DCHECK(!type_string.empty());
+ base::FilePath path = isolated_origin_dir.Append(type_string);
+ if (!base::DirectoryExists(path))
+ continue;
+ scoped_ptr<SandboxDirectoryDatabase> db(new SandboxDirectoryDatabase(path));
+ if (db->Init(SandboxDirectoryDatabase::FAIL_ON_CORRUPTION)) {
+ directories_[GetFileSystemTypeString(type)] = db.release();
+ MarkUsed();
+ // Don't populate more than one database, as it may rather hurt
+ // performance.
+ break;
+ }
+ }
+}
+
+PlatformFileError ObfuscatedFileUtil::GetFileInfoInternal(
+ SandboxDirectoryDatabase* db,
+ FileSystemOperationContext* context,
+ const GURL& origin,
+ FileSystemType type,
+ FileId file_id,
+ FileInfo* local_info,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_file_path) {
+ DCHECK(db);
+ DCHECK(context);
+ DCHECK(file_info);
+ DCHECK(platform_file_path);
+
+ if (!db->GetFileInfo(file_id, local_info)) {
+ NOTREACHED();
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+
+ if (local_info->is_directory()) {
+ file_info->size = 0;
+ file_info->is_directory = true;
+ file_info->is_symbolic_link = false;
+ file_info->last_modified = local_info->modification_time;
+ *platform_file_path = base::FilePath();
+ // We don't fill in ctime or atime.
+ return base::PLATFORM_FILE_OK;
+ }
+ if (local_info->data_path.empty())
+ return base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
+ base::FilePath local_path = DataPathToLocalPath(
+ origin, type, local_info->data_path);
+ base::PlatformFileError error = NativeFileUtil::GetFileInfo(
+ local_path, file_info);
+ // We should not follow symbolic links in sandboxed file system.
+ if (file_util::IsLink(local_path)) {
+ LOG(WARNING) << "Found a symbolic file.";
+ error = base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ }
+ if (error == base::PLATFORM_FILE_OK) {
+ *platform_file_path = local_path;
+ } else if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) {
+ LOG(WARNING) << "Lost a backing file.";
+ InvalidateUsageCache(context, origin, type);
+ if (!db->RemoveFileInfo(file_id))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ return error;
+}
+
+PlatformFileError ObfuscatedFileUtil::CreateFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const GURL& dest_origin,
+ FileSystemType dest_type,
+ FileInfo* dest_file_info, int file_flags, PlatformFile* handle) {
+ if (handle)
+ *handle = base::kInvalidPlatformFileValue;
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ dest_origin, dest_type, true);
+
+ PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::FilePath root = GetDirectoryForOriginAndType(
+ dest_origin, dest_type, false, &error);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+
+ base::FilePath dest_local_path;
+ error = GenerateNewLocalPath(db, context, dest_origin, dest_type,
+ &dest_local_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+
+ bool created = false;
+ if (!src_file_path.empty()) {
+ DCHECK(!file_flags);
+ DCHECK(!handle);
+ error = NativeFileUtil::CopyOrMoveFile(
+ src_file_path, dest_local_path, true /* copy */);
+ created = true;
+ } else {
+ if (base::PathExists(dest_local_path)) {
+ if (!base::DeleteFile(dest_local_path, true /* recursive */)) {
+ NOTREACHED();
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ LOG(WARNING) << "A stray file detected";
+ InvalidateUsageCache(context, dest_origin, dest_type);
+ }
+
+ if (handle) {
+ error = NativeFileUtil::CreateOrOpen(
+ dest_local_path, file_flags, handle, &created);
+ // If this succeeds, we must close handle on any subsequent error.
+ } else {
+ DCHECK(!file_flags); // file_flags is only used by CreateOrOpen.
+ error = NativeFileUtil::EnsureFileExists(dest_local_path, &created);
+ }
+ }
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+
+ if (!created) {
+ NOTREACHED();
+ if (handle) {
+ DCHECK_NE(base::kInvalidPlatformFileValue, *handle);
+ base::ClosePlatformFile(*handle);
+ base::DeleteFile(dest_local_path, false /* recursive */);
+ }
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+
+ // This removes the root, including the trailing slash, leaving a relative
+ // path.
+ dest_file_info->data_path = base::FilePath(
+ dest_local_path.value().substr(root.value().length() + 1));
+
+ FileId file_id;
+ if (!db->AddFileInfo(*dest_file_info, &file_id)) {
+ if (handle) {
+ DCHECK_NE(base::kInvalidPlatformFileValue, *handle);
+ base::ClosePlatformFile(*handle);
+ }
+ base::DeleteFile(dest_local_path, false /* recursive */);
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ }
+ TouchDirectory(db, dest_file_info->parent_id);
+
+ return base::PLATFORM_FILE_OK;
+}
+
+base::FilePath ObfuscatedFileUtil::DataPathToLocalPath(
+ const GURL& origin, FileSystemType type, const base::FilePath& data_path) {
+ PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::FilePath root = GetDirectoryForOriginAndType(
+ origin, type, false, &error);
+ if (error != base::PLATFORM_FILE_OK)
+ return base::FilePath();
+ return root.Append(data_path);
+}
+
+std::string ObfuscatedFileUtil::GetDirectoryDatabaseKey(
+ const GURL& origin, FileSystemType type) {
+ std::string type_string = GetFileSystemTypeString(type);
+ if (type_string.empty()) {
+ LOG(WARNING) << "Unknown filesystem type requested:" << type;
+ return std::string();
+ }
+ // For isolated origin we just use a type string as a key.
+ if (HasIsolatedStorage(origin)) {
+ CHECK_EQ(isolated_origin_.spec(), origin.spec());
+ return type_string;
+ }
+ return webkit_database::GetIdentifierFromOrigin(origin) +
+ type_string;
+}
+
+// TODO(ericu): How to do the whole validation-without-creation thing?
+// We may not have quota even to create the database.
+// Ah, in that case don't even get here?
+// Still doesn't answer the quota issue, though.
+SandboxDirectoryDatabase* ObfuscatedFileUtil::GetDirectoryDatabase(
+ const GURL& origin, FileSystemType type, bool create) {
+ std::string key = GetDirectoryDatabaseKey(origin, type);
+ if (key.empty())
+ return NULL;
+
+ DirectoryMap::iterator iter = directories_.find(key);
+ if (iter != directories_.end()) {
+ MarkUsed();
+ return iter->second;
+ }
+
+ PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::FilePath path = GetDirectoryForOriginAndType(
+ origin, type, create, &error);
+ if (error != base::PLATFORM_FILE_OK) {
+ LOG(WARNING) << "Failed to get origin+type directory: " << path.value();
+ return NULL;
+ }
+ MarkUsed();
+ SandboxDirectoryDatabase* database = new SandboxDirectoryDatabase(path);
+ directories_[key] = database;
+ return database;
+}
+
+base::FilePath ObfuscatedFileUtil::GetDirectoryForOrigin(
+ const GURL& origin, bool create, base::PlatformFileError* error_code) {
+ if (HasIsolatedStorage(origin)) {
+ CHECK_EQ(isolated_origin_.spec(), origin.spec());
+ }
+
+ if (!InitOriginDatabase(create)) {
+ if (error_code) {
+ *error_code = create ?
+ base::PLATFORM_FILE_ERROR_FAILED :
+ base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ }
+ return base::FilePath();
+ }
+ base::FilePath directory_name;
+ std::string id = webkit_database::GetIdentifierFromOrigin(origin);
+
+ bool exists_in_db = origin_database_->HasOriginPath(id);
+ if (!exists_in_db && !create) {
+ if (error_code)
+ *error_code = base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ return base::FilePath();
+ }
+ if (!origin_database_->GetPathForOrigin(id, &directory_name)) {
+ if (error_code)
+ *error_code = base::PLATFORM_FILE_ERROR_FAILED;
+ return base::FilePath();
+ }
+
+ base::FilePath path = file_system_directory_.Append(directory_name);
+ bool exists_in_fs = base::DirectoryExists(path);
+ if (!exists_in_db && exists_in_fs) {
+ if (!base::DeleteFile(path, true)) {
+ if (error_code)
+ *error_code = base::PLATFORM_FILE_ERROR_FAILED;
+ return base::FilePath();
+ }
+ exists_in_fs = false;
+ }
+
+ if (!exists_in_fs) {
+ if (!create || !file_util::CreateDirectory(path)) {
+ if (error_code)
+ *error_code = create ?
+ base::PLATFORM_FILE_ERROR_FAILED :
+ base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ return base::FilePath();
+ }
+ }
+
+ if (error_code)
+ *error_code = base::PLATFORM_FILE_OK;
+
+ return path;
+}
+
+void ObfuscatedFileUtil::InvalidateUsageCache(
+ FileSystemOperationContext* context,
+ const GURL& origin,
+ FileSystemType type) {
+ context->file_system_context()->sandbox_delegate()->
+ InvalidateUsageCache(origin, type);
+}
+
+void ObfuscatedFileUtil::MarkUsed() {
+ if (!timer_)
+ timer_.reset(new TimedTaskHelper(file_task_runner_.get()));
+
+ if (timer_->IsRunning()) {
+ timer_->Reset();
+ } else {
+ timer_->Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(db_flush_delay_seconds_),
+ base::Bind(&ObfuscatedFileUtil::DropDatabases,
+ base::Unretained(this)));
+ }
+}
+
+void ObfuscatedFileUtil::DropDatabases() {
+ origin_database_.reset();
+ STLDeleteContainerPairSecondPointers(
+ directories_.begin(), directories_.end());
+ directories_.clear();
+ timer_.reset();
+}
+
+bool ObfuscatedFileUtil::InitOriginDatabase(bool create) {
+ if (origin_database_)
+ return true;
+
+ if (!create && !base::DirectoryExists(file_system_directory_))
+ return false;
+ if (!file_util::CreateDirectory(file_system_directory_)) {
+ LOG(WARNING) << "Failed to create FileSystem directory: " <<
+ file_system_directory_.value();
+ return false;
+ }
+
+ origin_database_.reset(
+ new SandboxOriginDatabase(file_system_directory_));
+
+ base::FilePath isolated_origin_dir = file_system_directory_.Append(
+ SandboxIsolatedOriginDatabase::kOriginDirectory);
+ if (base::DirectoryExists(isolated_origin_dir) &&
+ !isolated_origin_.is_empty()) {
+ SandboxIsolatedOriginDatabase::MigrateBackDatabase(
+ webkit_database::GetIdentifierFromOrigin(isolated_origin_),
+ file_system_directory_,
+ static_cast<SandboxOriginDatabase*>(origin_database_.get()));
+ }
+
+ return true;
+}
+
+PlatformFileError ObfuscatedFileUtil::GenerateNewLocalPath(
+ SandboxDirectoryDatabase* db,
+ FileSystemOperationContext* context,
+ const GURL& origin,
+ FileSystemType type,
+ base::FilePath* local_path) {
+ DCHECK(local_path);
+ int64 number;
+ if (!db || !db->GetNextInteger(&number))
+ return base::PLATFORM_FILE_ERROR_FAILED;
+
+ PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::FilePath new_local_path = GetDirectoryForOriginAndType(
+ origin, type, false, &error);
+ if (error != base::PLATFORM_FILE_OK)
+ return base::PLATFORM_FILE_ERROR_FAILED;
+
+ // We use the third- and fourth-to-last digits as the directory.
+ int64 directory_number = number % 10000 / 100;
+ new_local_path = new_local_path.AppendASCII(
+ base::StringPrintf("%02" PRId64, directory_number));
+
+ error = NativeFileUtil::CreateDirectory(
+ new_local_path, false /* exclusive */, false /* recursive */);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+
+ *local_path =
+ new_local_path.AppendASCII(base::StringPrintf("%08" PRId64, number));
+ return base::PLATFORM_FILE_OK;
+}
+
+PlatformFileError ObfuscatedFileUtil::CreateOrOpenInternal(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, int file_flags,
+ PlatformFile* file_handle, bool* created) {
+ DCHECK(!(file_flags & (base::PLATFORM_FILE_DELETE_ON_CLOSE |
+ base::PLATFORM_FILE_HIDDEN | base::PLATFORM_FILE_EXCLUSIVE_READ |
+ base::PLATFORM_FILE_EXCLUSIVE_WRITE)));
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(
+ url.origin(), url.type(), true);
+ if (!db)
+ return base::PLATFORM_FILE_ERROR_FAILED;
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id)) {
+ // The file doesn't exist.
+ if (!(file_flags & (base::PLATFORM_FILE_CREATE |
+ base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_OPEN_ALWAYS)))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ FileId parent_id;
+ if (!db->GetFileWithPath(VirtualPath::DirName(url.path()),
+ &parent_id))
+ return base::PLATFORM_FILE_ERROR_NOT_FOUND;
+ FileInfo file_info;
+ InitFileInfo(&file_info, parent_id,
+ VirtualPath::BaseName(url.path()).value());
+
+ int64 growth = UsageForPath(file_info.name.size());
+ if (!AllocateQuota(context, growth))
+ return base::PLATFORM_FILE_ERROR_NO_SPACE;
+ PlatformFileError error = CreateFile(
+ context, base::FilePath(),
+ url.origin(), url.type(), &file_info,
+ file_flags, file_handle);
+ if (created && base::PLATFORM_FILE_OK == error) {
+ *created = true;
+ UpdateUsage(context, url, growth);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnCreateFile, MakeTuple(url));
+ }
+ return error;
+ }
+
+ if (file_flags & base::PLATFORM_FILE_CREATE)
+ return base::PLATFORM_FILE_ERROR_EXISTS;
+
+ base::PlatformFileInfo platform_file_info;
+ base::FilePath local_path;
+ FileInfo file_info;
+ base::PlatformFileError error = GetFileInfoInternal(
+ db, context, url.origin(), url.type(), file_id,
+ &file_info, &platform_file_info, &local_path);
+ if (error != base::PLATFORM_FILE_OK)
+ return error;
+ if (file_info.is_directory())
+ return base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+
+ int64 delta = 0;
+ if (file_flags & (base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_OPEN_TRUNCATED)) {
+ // The file exists and we're truncating.
+ delta = -platform_file_info.size;
+ AllocateQuota(context, delta);
+ }
+
+ error = NativeFileUtil::CreateOrOpen(
+ local_path, file_flags, file_handle, created);
+ if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) {
+ // TODO(tzik): Also invalidate on-memory usage cache in UsageTracker.
+ // TODO(tzik): Delete database entry after ensuring the file lost.
+ InvalidateUsageCache(context, url.origin(), url.type());
+ LOG(WARNING) << "Lost a backing file.";
+ error = base::PLATFORM_FILE_ERROR_FAILED;
+ }
+
+ // If truncating we need to update the usage.
+ if (error == base::PLATFORM_FILE_OK && delta) {
+ UpdateUsage(context, url, delta);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnModifyFile, MakeTuple(url));
+ }
+ return error;
+}
+
+bool ObfuscatedFileUtil::HasIsolatedStorage(const GURL& origin) {
+ if (special_storage_policy_.get() &&
+ special_storage_policy_->HasIsolatedStorage(origin)) {
+ if (isolated_origin_.is_empty())
+ isolated_origin_ = origin;
+ // Record isolated_origin_, but always disable for now.
+ // crbug.com/264429
+ return false;
+ }
+ return false;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/obfuscated_file_util.h b/chromium/webkit/browser/fileapi/obfuscated_file_util.h
new file mode 100644
index 00000000000..0d9750d1fb5
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/obfuscated_file_util.h
@@ -0,0 +1,296 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_
+#define WEBKIT_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_
+
+#include <map>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util_proxy.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/sandbox_directory_database.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+namespace base {
+class SequencedTaskRunner;
+class TimeTicks;
+}
+
+namespace quota {
+class SpecialStoragePolicy;
+}
+
+class GURL;
+
+namespace fileapi {
+
+class FileSystemOperationContext;
+class SandboxOriginDatabaseInterface;
+class TimedTaskHelper;
+
+// The overall implementation philosophy of this class is that partial failures
+// should leave us with an intact database; we'd prefer to leak the occasional
+// backing file than have a database entry whose backing file is missing. When
+// doing FSCK operations, if you find a loose backing file with no reference,
+// you may safely delete it.
+//
+// This class must be deleted on the FILE thread, because that's where
+// DropDatabases needs to be called.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE ObfuscatedFileUtil
+ : public FileSystemFileUtil {
+ public:
+ // Origin enumerator interface.
+ // An instance of this interface is assumed to be called on the file thread.
+ class AbstractOriginEnumerator {
+ public:
+ virtual ~AbstractOriginEnumerator() {}
+
+ // Returns the next origin. Returns empty if there are no more origins.
+ virtual GURL Next() = 0;
+
+ // Returns the current origin's information.
+ virtual bool HasFileSystemType(FileSystemType type) const = 0;
+ };
+
+ ObfuscatedFileUtil(
+ quota::SpecialStoragePolicy* special_storage_policy,
+ const base::FilePath& file_system_directory,
+ base::SequencedTaskRunner* file_task_runner);
+ virtual ~ObfuscatedFileUtil();
+
+ // FileSystemFileUtil overrides.
+ virtual base::PlatformFileError CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int file_flags,
+ base::PlatformFile* file_handle,
+ bool* created) OVERRIDE;
+ virtual base::PlatformFileError Close(
+ FileSystemOperationContext* context,
+ base::PlatformFile file) OVERRIDE;
+ virtual base::PlatformFileError EnsureFileExists(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, bool* created) OVERRIDE;
+ virtual base::PlatformFileError CreateDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) OVERRIDE;
+ virtual base::PlatformFileError GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_file) OVERRIDE;
+ virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url) OVERRIDE;
+ virtual base::PlatformFileError GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& file_system_url,
+ base::FilePath* local_path) OVERRIDE;
+ virtual base::PlatformFileError Touch(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) OVERRIDE;
+ virtual base::PlatformFileError Truncate(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 length) OVERRIDE;
+ virtual base::PlatformFileError CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ bool copy) OVERRIDE;
+ virtual base::PlatformFileError CopyInForeignFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url) OVERRIDE;
+ virtual base::PlatformFileError DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) OVERRIDE;
+ virtual base::PlatformFileError DeleteDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) OVERRIDE;
+ virtual webkit_blob::ScopedFile CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileError* error,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_path) OVERRIDE;
+
+ // Same as the other CreateFileEnumerator, but with recursive support.
+ scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url,
+ bool recursive);
+
+ // Returns true if the directory |url| is empty.
+ bool IsDirectoryEmpty(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url);
+
+ // Gets the topmost directory specific to this origin and type. This will
+ // contain both the directory database's files and all the backing file
+ // subdirectories.
+ // Returns an empty path if the directory is undefined (e.g. because |type|
+ // is invalid). If the directory is defined, it will be returned, even if
+ // there is a file system error (e.g. the directory doesn't exist on disk and
+ // |create| is false). Callers should always check |error_code| to make sure
+ // the returned path is usable.
+ base::FilePath GetDirectoryForOriginAndType(
+ const GURL& origin,
+ FileSystemType type,
+ bool create,
+ base::PlatformFileError* error_code);
+
+ // Deletes the topmost directory specific to this origin and type. This will
+ // delete its directory database.
+ bool DeleteDirectoryForOriginAndType(const GURL& origin, FileSystemType type);
+
+ // TODO(ericu): This doesn't really feel like it belongs in this class.
+ // The previous version lives in FileSystemPathManager, but perhaps
+ // SandboxFileSystemBackend would be better?
+ static base::FilePath::StringType GetDirectoryNameForType(
+ FileSystemType type);
+
+ // This method and all methods of its returned class must be called only on
+ // the FILE thread. The caller is responsible for deleting the returned
+ // object.
+ AbstractOriginEnumerator* CreateOriginEnumerator();
+
+ // Deletes a directory database from the database list in the ObfuscatedFSFU
+ // and destroys the database on the disk.
+ bool DestroyDirectoryDatabase(const GURL& origin, FileSystemType type);
+
+ // Computes a cost for storing a given file in the obfuscated FSFU.
+ // As the cost of a file is independent of the cost of its parent directories,
+ // this ignores all but the BaseName of the supplied path. In order to
+ // compute the cost of adding a multi-segment directory recursively, call this
+ // on each path segment and add the results.
+ static int64 ComputeFilePathCost(const base::FilePath& path);
+
+ void MaybePrepopulateDatabase();
+
+ private:
+ typedef SandboxDirectoryDatabase::FileId FileId;
+ typedef SandboxDirectoryDatabase::FileInfo FileInfo;
+
+ friend class ObfuscatedFileEnumerator;
+ FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest, MaybeDropDatabasesAliveCase);
+ FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest,
+ MaybeDropDatabasesAlreadyDeletedCase);
+ FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest,
+ DestroyDirectoryDatabase_Isolated);
+ FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest,
+ GetDirectoryDatabase_Isolated);
+ FRIEND_TEST_ALL_PREFIXES(ObfuscatedFileUtilTest,
+ MigrationBackFromIsolated);
+
+ base::PlatformFileError GetFileInfoInternal(
+ SandboxDirectoryDatabase* db,
+ FileSystemOperationContext* context,
+ const GURL& origin,
+ FileSystemType type,
+ FileId file_id,
+ FileInfo* local_info,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_file_path);
+
+ // Creates a new file, both the underlying backing file and the entry in the
+ // database. |dest_file_info| is an in-out parameter. Supply the name and
+ // parent_id; data_path is ignored. On success, data_path will
+ // always be set to the relative path [from the root of the type-specific
+ // filesystem directory] of a NEW backing file, and handle, if supplied, will
+ // hold open PlatformFile for the backing file, which the caller is
+ // responsible for closing. If you supply a path in |source_path|, it will be
+ // used as a source from which to COPY data.
+ // Caveat: do not supply handle if you're also supplying a data path. It was
+ // easier not to support this, and no code has needed it so far, so it will
+ // DCHECK and handle will hold base::kInvalidPlatformFileValue.
+ base::PlatformFileError CreateFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& source_file_path,
+ const GURL& dest_origin,
+ FileSystemType dest_type,
+ FileInfo* dest_file_info,
+ int file_flags,
+ base::PlatformFile* handle);
+
+ // This converts from a relative path [as is stored in the FileInfo.data_path
+ // field] to an absolute platform path that can be given to the native
+ // filesystem.
+ base::FilePath DataPathToLocalPath(
+ const GURL& origin,
+ FileSystemType type,
+ const base::FilePath& data_file_path);
+
+ std::string GetDirectoryDatabaseKey(const GURL& origin, FileSystemType type);
+
+ // This returns NULL if |create| flag is false and a filesystem does not
+ // exist for the given |origin_url| and |type|.
+ // For read operations |create| should be false.
+ SandboxDirectoryDatabase* GetDirectoryDatabase(
+ const GURL& origin_url, FileSystemType type, bool create);
+
+ // Gets the topmost directory specific to this origin. This will
+ // contain both the filesystem type subdirectories.
+ base::FilePath GetDirectoryForOrigin(const GURL& origin,
+ bool create,
+ base::PlatformFileError* error_code);
+
+ void InvalidateUsageCache(FileSystemOperationContext* context,
+ const GURL& origin,
+ FileSystemType type);
+
+ void MarkUsed();
+ void DropDatabases();
+ bool InitOriginDatabase(bool create);
+
+ base::PlatformFileError GenerateNewLocalPath(
+ SandboxDirectoryDatabase* db,
+ FileSystemOperationContext* context,
+ const GURL& origin,
+ FileSystemType type,
+ base::FilePath* local_path);
+
+ base::PlatformFileError CreateOrOpenInternal(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int file_flags,
+ base::PlatformFile* file_handle,
+ bool* created);
+
+ bool HasIsolatedStorage(const GURL& origin);
+
+ typedef std::map<std::string, SandboxDirectoryDatabase*> DirectoryMap;
+ DirectoryMap directories_;
+ scoped_ptr<SandboxOriginDatabaseInterface> origin_database_;
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
+ base::FilePath file_system_directory_;
+
+ // Used to delete database after a certain period of inactivity.
+ int64 db_flush_delay_seconds_;
+
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+ scoped_ptr<TimedTaskHelper> timer_;
+
+ // If this instance is initialized for an isolated partition, this should
+ // only see a single origin.
+ GURL isolated_origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtil);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_
diff --git a/chromium/webkit/browser/fileapi/obfuscated_file_util_unittest.cc b/chromium/webkit/browser/fileapi/obfuscated_file_util_unittest.cc
new file mode 100644
index 00000000000..c4f409f76ed
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/obfuscated_file_util_unittest.cc
@@ -0,0 +1,2375 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/platform_file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/async_file_test_helper.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+#include "webkit/browser/fileapi/mock_file_change_observer.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/obfuscated_file_util.h"
+#include "webkit/browser/fileapi/sandbox_directory_database.h"
+#include "webkit/browser/fileapi/sandbox_file_system_test_helper.h"
+#include "webkit/browser/fileapi/sandbox_isolated_origin_database.h"
+#include "webkit/browser/fileapi/sandbox_origin_database.h"
+#include "webkit/browser/fileapi/test_file_set.h"
+#include "webkit/browser/quota/mock_special_storage_policy.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/database/database_identifier.h"
+#include "webkit/common/quota/quota_types.h"
+
+namespace fileapi {
+
+namespace {
+
+bool FileExists(const base::FilePath& path) {
+ return base::PathExists(path) && !base::DirectoryExists(path);
+}
+
+int64 GetSize(const base::FilePath& path) {
+ int64 size;
+ EXPECT_TRUE(file_util::GetFileSize(path, &size));
+ return size;
+}
+
+// After a move, the dest exists and the source doesn't.
+// After a copy, both source and dest exist.
+struct CopyMoveTestCaseRecord {
+ bool is_copy_not_move;
+ const char source_path[64];
+ const char dest_path[64];
+ bool cause_overwrite;
+};
+
+const CopyMoveTestCaseRecord kCopyMoveTestCases[] = {
+ // This is the combinatoric set of:
+ // rename vs. same-name
+ // different directory vs. same directory
+ // overwrite vs. no-overwrite
+ // copy vs. move
+ // We can never be called with source and destination paths identical, so
+ // those cases are omitted.
+ {true, "dir0/file0", "dir0/file1", false},
+ {false, "dir0/file0", "dir0/file1", false},
+ {true, "dir0/file0", "dir0/file1", true},
+ {false, "dir0/file0", "dir0/file1", true},
+
+ {true, "dir0/file0", "dir1/file0", false},
+ {false, "dir0/file0", "dir1/file0", false},
+ {true, "dir0/file0", "dir1/file0", true},
+ {false, "dir0/file0", "dir1/file0", true},
+ {true, "dir0/file0", "dir1/file1", false},
+ {false, "dir0/file0", "dir1/file1", false},
+ {true, "dir0/file0", "dir1/file1", true},
+ {false, "dir0/file0", "dir1/file1", true},
+};
+
+struct OriginEnumerationTestRecord {
+ std::string origin_url;
+ bool has_temporary;
+ bool has_persistent;
+};
+
+const OriginEnumerationTestRecord kOriginEnumerationTestRecords[] = {
+ {"http://example.com", false, true},
+ {"http://example1.com", true, false},
+ {"https://example1.com", true, true},
+ {"file://", false, true},
+ {"http://example.com:8000", false, true},
+};
+
+FileSystemURL FileSystemURLAppend(
+ const FileSystemURL& url, const base::FilePath::StringType& child) {
+ return FileSystemURL::CreateForTest(
+ url.origin(), url.mount_type(), url.virtual_path().Append(child));
+}
+
+FileSystemURL FileSystemURLAppendUTF8(
+ const FileSystemURL& url, const std::string& child) {
+ return FileSystemURL::CreateForTest(
+ url.origin(),
+ url.mount_type(),
+ url.virtual_path().Append(base::FilePath::FromUTF8Unsafe(child)));
+}
+
+FileSystemURL FileSystemURLDirName(const FileSystemURL& url) {
+ return FileSystemURL::CreateForTest(
+ url.origin(), url.mount_type(), VirtualPath::DirName(url.virtual_path()));
+}
+
+} // namespace (anonymous)
+
+// TODO(ericu): The vast majority of this and the other FSFU subclass tests
+// could theoretically be shared. It would basically be a FSFU interface
+// compliance test, and only the subclass-specific bits that look into the
+// implementation would need to be written per-subclass.
+class ObfuscatedFileUtilTest : public testing::Test {
+ public:
+ ObfuscatedFileUtilTest()
+ : origin_(GURL("http://www.example.com")),
+ type_(kFileSystemTypeTemporary),
+ weak_factory_(this),
+ sandbox_file_system_(origin_, type_),
+ quota_status_(quota::kQuotaStatusUnknown),
+ usage_(-1) {
+ }
+
+ virtual void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+
+ storage_policy_ = new quota::MockSpecialStoragePolicy();
+
+ quota_manager_ =
+ new quota::QuotaManager(false /* is_incognito */,
+ data_dir_.path(),
+ base::MessageLoopProxy::current().get(),
+ base::MessageLoopProxy::current().get(),
+ storage_policy_.get());
+
+ // Every time we create a new sandbox_file_system helper,
+ // it creates another context, which creates another path manager,
+ // another sandbox_backend, and another OFU.
+ // We need to pass in the context to skip all that.
+ file_system_context_ = CreateFileSystemContextForTesting(
+ quota_manager_->proxy(),
+ data_dir_.path());
+
+ sandbox_file_system_.SetUp(file_system_context_.get());
+
+ change_observers_ = MockFileChangeObserver::CreateList(&change_observer_);
+ }
+
+ virtual void TearDown() {
+ quota_manager_ = NULL;
+ sandbox_file_system_.TearDown();
+ }
+
+ scoped_ptr<FileSystemOperationContext> LimitedContext(
+ int64 allowed_bytes_growth) {
+ scoped_ptr<FileSystemOperationContext> context(
+ sandbox_file_system_.NewOperationContext());
+ context->set_allowed_bytes_growth(allowed_bytes_growth);
+ return context.Pass();
+ }
+
+ scoped_ptr<FileSystemOperationContext> UnlimitedContext() {
+ return LimitedContext(kint64max);
+ }
+
+ FileSystemOperationContext* NewContext(
+ SandboxFileSystemTestHelper* file_system) {
+ change_observer()->ResetCount();
+ FileSystemOperationContext* context;
+ if (file_system)
+ context = file_system->NewOperationContext();
+ else
+ context = sandbox_file_system_.NewOperationContext();
+ // Setting allowed_bytes_growth big enough for all tests.
+ context->set_allowed_bytes_growth(1024 * 1024);
+ context->set_change_observers(change_observers());
+ return context;
+ }
+
+ const ChangeObserverList& change_observers() const {
+ return change_observers_;
+ }
+
+ MockFileChangeObserver* change_observer() {
+ return &change_observer_;
+ }
+
+ // This can only be used after SetUp has run and created file_system_context_
+ // and obfuscated_file_util_.
+ // Use this for tests which need to run in multiple origins; we need a test
+ // helper per origin.
+ SandboxFileSystemTestHelper* NewFileSystem(
+ const GURL& origin, fileapi::FileSystemType type) {
+ SandboxFileSystemTestHelper* file_system =
+ new SandboxFileSystemTestHelper(origin, type);
+
+ file_system->SetUp(file_system_context_.get());
+ return file_system;
+ }
+
+ ObfuscatedFileUtil* ofu() {
+ return static_cast<ObfuscatedFileUtil*>(sandbox_file_system_.file_util());
+ }
+
+ const base::FilePath& test_directory() const {
+ return data_dir_.path();
+ }
+
+ const GURL& origin() const {
+ return origin_;
+ }
+
+ fileapi::FileSystemType type() const {
+ return type_;
+ }
+
+ int64 ComputeTotalFileSize() {
+ return sandbox_file_system_.ComputeCurrentOriginUsage() -
+ sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage();
+ }
+
+ void GetUsageFromQuotaManager() {
+ int64 quota = -1;
+ quota_status_ =
+ AsyncFileTestHelper::GetUsageAndQuota(quota_manager_.get(),
+ origin(),
+ sandbox_file_system_.type(),
+ &usage_,
+ &quota);
+ EXPECT_EQ(quota::kQuotaStatusOk, quota_status_);
+ }
+
+ void RevokeUsageCache() {
+ quota_manager_->ResetUsageTracker(sandbox_file_system_.storage_type());
+ usage_cache()->Delete(sandbox_file_system_.GetUsageCachePath());
+ }
+
+ int64 SizeByQuotaUtil() {
+ return sandbox_file_system_.GetCachedOriginUsage();
+ }
+
+ int64 SizeInUsageFile() {
+ base::MessageLoop::current()->RunUntilIdle();
+ int64 usage = 0;
+ return usage_cache()->GetUsage(
+ sandbox_file_system_.GetUsageCachePath(), &usage) ? usage : -1;
+ }
+
+ bool PathExists(const FileSystemURL& url) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ base::PlatformFileInfo file_info;
+ base::FilePath platform_path;
+ base::PlatformFileError error = ofu()->GetFileInfo(
+ context.get(), url, &file_info, &platform_path);
+ return error == base::PLATFORM_FILE_OK;
+ }
+
+ bool DirectoryExists(const FileSystemURL& url) {
+ return AsyncFileTestHelper::DirectoryExists(file_system_context(), url);
+ }
+
+ int64 usage() const { return usage_; }
+ FileSystemUsageCache* usage_cache() {
+ return sandbox_file_system_.usage_cache();
+ }
+
+ FileSystemURL CreateURLFromUTF8(const std::string& path) {
+ return sandbox_file_system_.CreateURLFromUTF8(path);
+ }
+
+ int64 PathCost(const FileSystemURL& url) {
+ return ObfuscatedFileUtil::ComputeFilePathCost(url.path());
+ }
+
+ FileSystemURL CreateURL(const base::FilePath& path) {
+ return sandbox_file_system_.CreateURL(path);
+ }
+
+ void CheckFileAndCloseHandle(
+ const FileSystemURL& url, base::PlatformFile file_handle) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ base::FilePath local_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath(
+ context.get(), url, &local_path));
+
+ base::PlatformFileInfo file_info0;
+ base::FilePath data_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo(
+ context.get(), url, &file_info0, &data_path));
+ EXPECT_EQ(data_path, local_path);
+ EXPECT_TRUE(FileExists(data_path));
+ EXPECT_EQ(0, GetSize(data_path));
+
+ const char data[] = "test data";
+ const int length = arraysize(data) - 1;
+
+ if (base::kInvalidPlatformFileValue == file_handle) {
+ bool created = true;
+ base::PlatformFileError error;
+ file_handle = base::CreatePlatformFile(
+ data_path,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE,
+ &created,
+ &error);
+ ASSERT_NE(base::kInvalidPlatformFileValue, file_handle);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+ EXPECT_FALSE(created);
+ }
+ ASSERT_EQ(length, base::WritePlatformFile(file_handle, 0, data, length));
+ EXPECT_TRUE(base::ClosePlatformFile(file_handle));
+
+ base::PlatformFileInfo file_info1;
+ EXPECT_EQ(length, GetSize(data_path));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo(
+ context.get(), url, &file_info1, &data_path));
+ EXPECT_EQ(data_path, local_path);
+
+ EXPECT_FALSE(file_info0.is_directory);
+ EXPECT_FALSE(file_info1.is_directory);
+ EXPECT_FALSE(file_info0.is_symbolic_link);
+ EXPECT_FALSE(file_info1.is_symbolic_link);
+ EXPECT_EQ(0, file_info0.size);
+ EXPECT_EQ(length, file_info1.size);
+ EXPECT_LE(file_info0.last_modified, file_info1.last_modified);
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate(
+ context.get(), url, length * 2));
+ EXPECT_EQ(length * 2, GetSize(data_path));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate(
+ context.get(), url, 0));
+ EXPECT_EQ(0, GetSize(data_path));
+ }
+
+ void ValidateTestDirectory(
+ const FileSystemURL& root_url,
+ const std::set<base::FilePath::StringType>& files,
+ const std::set<base::FilePath::StringType>& directories) {
+ scoped_ptr<FileSystemOperationContext> context;
+ std::set<base::FilePath::StringType>::const_iterator iter;
+ for (iter = files.begin(); iter != files.end(); ++iter) {
+ bool created = true;
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ context.get(), FileSystemURLAppend(root_url, *iter),
+ &created));
+ ASSERT_FALSE(created);
+ }
+ for (iter = directories.begin(); iter != directories.end(); ++iter) {
+ context.reset(NewContext(NULL));
+ EXPECT_TRUE(DirectoryExists(
+ FileSystemURLAppend(root_url, *iter)));
+ }
+ }
+
+ class UsageVerifyHelper {
+ public:
+ UsageVerifyHelper(scoped_ptr<FileSystemOperationContext> context,
+ SandboxFileSystemTestHelper* file_system,
+ int64 expected_usage)
+ : context_(context.Pass()),
+ sandbox_file_system_(file_system),
+ expected_usage_(expected_usage) {}
+
+ ~UsageVerifyHelper() {
+ base::MessageLoop::current()->RunUntilIdle();
+ Check();
+ }
+
+ FileSystemOperationContext* context() {
+ return context_.get();
+ }
+
+ private:
+ void Check() {
+ ASSERT_EQ(expected_usage_,
+ sandbox_file_system_->GetCachedOriginUsage());
+ }
+
+ scoped_ptr<FileSystemOperationContext> context_;
+ SandboxFileSystemTestHelper* sandbox_file_system_;
+ int64 expected_usage_;
+ };
+
+ scoped_ptr<UsageVerifyHelper> AllowUsageIncrease(int64 requested_growth) {
+ int64 usage = sandbox_file_system_.GetCachedOriginUsage();
+ return scoped_ptr<UsageVerifyHelper>(new UsageVerifyHelper(
+ LimitedContext(requested_growth),
+ &sandbox_file_system_, usage + requested_growth));
+ }
+
+ scoped_ptr<UsageVerifyHelper> DisallowUsageIncrease(int64 requested_growth) {
+ int64 usage = sandbox_file_system_.GetCachedOriginUsage();
+ return scoped_ptr<UsageVerifyHelper>(new UsageVerifyHelper(
+ LimitedContext(requested_growth - 1), &sandbox_file_system_, usage));
+ }
+
+ void FillTestDirectory(
+ const FileSystemURL& root_url,
+ std::set<base::FilePath::StringType>* files,
+ std::set<base::FilePath::StringType>* directories) {
+ scoped_ptr<FileSystemOperationContext> context;
+ std::vector<DirectoryEntry> entries;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), root_url, &entries));
+ EXPECT_EQ(0UL, entries.size());
+
+ files->clear();
+ files->insert(FILE_PATH_LITERAL("first"));
+ files->insert(FILE_PATH_LITERAL("second"));
+ files->insert(FILE_PATH_LITERAL("third"));
+ directories->clear();
+ directories->insert(FILE_PATH_LITERAL("fourth"));
+ directories->insert(FILE_PATH_LITERAL("fifth"));
+ directories->insert(FILE_PATH_LITERAL("sixth"));
+ std::set<base::FilePath::StringType>::iterator iter;
+ for (iter = files->begin(); iter != files->end(); ++iter) {
+ bool created = false;
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ context.get(),
+ FileSystemURLAppend(root_url, *iter),
+ &created));
+ ASSERT_TRUE(created);
+ }
+ for (iter = directories->begin(); iter != directories->end(); ++iter) {
+ bool exclusive = true;
+ bool recursive = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(
+ context.get(),
+ FileSystemURLAppend(root_url, *iter),
+ exclusive, recursive));
+ }
+ ValidateTestDirectory(root_url, *files, *directories);
+ }
+
+ void TestReadDirectoryHelper(const FileSystemURL& root_url) {
+ std::set<base::FilePath::StringType> files;
+ std::set<base::FilePath::StringType> directories;
+ FillTestDirectory(root_url, &files, &directories);
+
+ scoped_ptr<FileSystemOperationContext> context;
+ std::vector<DirectoryEntry> entries;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), root_url, &entries));
+ std::vector<DirectoryEntry>::iterator entry_iter;
+ EXPECT_EQ(files.size() + directories.size(), entries.size());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ for (entry_iter = entries.begin(); entry_iter != entries.end();
+ ++entry_iter) {
+ const DirectoryEntry& entry = *entry_iter;
+ std::set<base::FilePath::StringType>::iterator iter =
+ files.find(entry.name);
+ if (iter != files.end()) {
+ EXPECT_FALSE(entry.is_directory);
+ files.erase(iter);
+ continue;
+ }
+ iter = directories.find(entry.name);
+ EXPECT_FALSE(directories.end() == iter);
+ EXPECT_TRUE(entry.is_directory);
+ directories.erase(iter);
+ }
+ }
+
+ void TestTouchHelper(const FileSystemURL& url, bool is_file) {
+ base::Time last_access_time = base::Time::Now();
+ base::Time last_modified_time = base::Time::Now();
+
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Touch(
+ context.get(), url, last_access_time, last_modified_time));
+ // Currently we fire no change notifications for Touch.
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ base::FilePath local_path;
+ base::PlatformFileInfo file_info;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo(
+ context.get(), url, &file_info, &local_path));
+ // We compare as time_t here to lower our resolution, to avoid false
+ // negatives caused by conversion to the local filesystem's native
+ // representation and back.
+ EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT());
+
+ context.reset(NewContext(NULL));
+ last_modified_time += base::TimeDelta::FromHours(1);
+ last_access_time += base::TimeDelta::FromHours(14);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Touch(
+ context.get(), url, last_access_time, last_modified_time));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo(
+ context.get(), url, &file_info, &local_path));
+ EXPECT_EQ(file_info.last_modified.ToTimeT(), last_modified_time.ToTimeT());
+ if (is_file) // Directories in OFU don't support atime.
+ EXPECT_EQ(file_info.last_accessed.ToTimeT(), last_access_time.ToTimeT());
+ }
+
+ void TestCopyInForeignFileHelper(bool overwrite) {
+ base::ScopedTempDir source_dir;
+ ASSERT_TRUE(source_dir.CreateUniqueTempDir());
+ base::FilePath root_file_path = source_dir.path();
+ base::FilePath src_file_path = root_file_path.AppendASCII("file_name");
+ FileSystemURL dest_url = CreateURLFromUTF8("new file");
+ int64 src_file_length = 87;
+
+ base::PlatformFileError error_code;
+ bool created = false;
+ int file_flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE;
+ base::PlatformFile file_handle =
+ base::CreatePlatformFile(
+ src_file_path, file_flags, &created, &error_code);
+ EXPECT_TRUE(created);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error_code);
+ ASSERT_NE(base::kInvalidPlatformFileValue, file_handle);
+ ASSERT_TRUE(base::TruncatePlatformFile(file_handle, src_file_length));
+ EXPECT_TRUE(base::ClosePlatformFile(file_handle));
+
+ scoped_ptr<FileSystemOperationContext> context;
+
+ if (overwrite) {
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), dest_url, &created));
+ EXPECT_TRUE(created);
+
+ // We must have observed one (and only one) create_file_count.
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ }
+
+ const int64 path_cost =
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path());
+ if (!overwrite) {
+ // Verify that file creation requires sufficient quota for the path.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(path_cost + src_file_length - 1);
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE,
+ ofu()->CopyInForeignFile(context.get(),
+ src_file_path, dest_url));
+ }
+
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(path_cost + src_file_length);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyInForeignFile(context.get(),
+ src_file_path, dest_url));
+
+ EXPECT_TRUE(PathExists(dest_url));
+ EXPECT_FALSE(DirectoryExists(dest_url));
+
+ context.reset(NewContext(NULL));
+ base::PlatformFileInfo file_info;
+ base::FilePath data_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo(
+ context.get(), dest_url, &file_info, &data_path));
+ EXPECT_NE(data_path, src_file_path);
+ EXPECT_TRUE(FileExists(data_path));
+ EXPECT_EQ(src_file_length, GetSize(data_path));
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->DeleteFile(context.get(), dest_url));
+ }
+
+ void ClearTimestamp(const FileSystemURL& url) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Touch(context.get(), url, base::Time(), base::Time()));
+ EXPECT_EQ(base::Time(), GetModifiedTime(url));
+ }
+
+ base::Time GetModifiedTime(const FileSystemURL& url) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ base::FilePath data_path;
+ base::PlatformFileInfo file_info;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->GetFileInfo(context.get(), url, &file_info, &data_path));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ return file_info.last_modified;
+ }
+
+ void TestDirectoryTimestampHelper(const FileSystemURL& base_dir,
+ bool copy,
+ bool overwrite) {
+ scoped_ptr<FileSystemOperationContext> context;
+ const FileSystemURL src_dir_url(
+ FileSystemURLAppendUTF8(base_dir, "foo_dir"));
+ const FileSystemURL dest_dir_url(
+ FileSystemURLAppendUTF8(base_dir, "bar_dir"));
+
+ const FileSystemURL src_file_url(
+ FileSystemURLAppendUTF8(src_dir_url, "hoge"));
+ const FileSystemURL dest_file_url(
+ FileSystemURLAppendUTF8(dest_dir_url, "fuga"));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), src_dir_url, true, true));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), dest_dir_url, true, true));
+
+ bool created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), src_file_url, &created));
+ if (overwrite) {
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(),
+ dest_file_url, &created));
+ }
+
+ ClearTimestamp(src_dir_url);
+ ClearTimestamp(dest_dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(),
+ src_file_url, dest_file_url,
+ copy));
+ if (copy)
+ EXPECT_EQ(base::Time(), GetModifiedTime(src_dir_url));
+ else
+ EXPECT_NE(base::Time(), GetModifiedTime(src_dir_url));
+ EXPECT_NE(base::Time(), GetModifiedTime(dest_dir_url));
+ }
+
+ int64 ComputeCurrentUsage() {
+ return sandbox_file_system_.ComputeCurrentOriginUsage() -
+ sandbox_file_system_.ComputeCurrentDirectoryDatabaseUsage();
+ }
+
+ FileSystemContext* file_system_context() {
+ return sandbox_file_system_.file_system_context();
+ }
+
+ const base::FilePath& data_dir_path() const {
+ return data_dir_.path();
+ }
+
+ protected:
+ base::ScopedTempDir data_dir_;
+ base::MessageLoop message_loop_;
+ scoped_refptr<quota::MockSpecialStoragePolicy> storage_policy_;
+ scoped_refptr<quota::QuotaManager> quota_manager_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ GURL origin_;
+ fileapi::FileSystemType type_;
+ base::WeakPtrFactory<ObfuscatedFileUtilTest> weak_factory_;
+ SandboxFileSystemTestHelper sandbox_file_system_;
+ quota::QuotaStatusCode quota_status_;
+ int64 usage_;
+ MockFileChangeObserver change_observer_;
+ ChangeObserverList change_observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtilTest);
+};
+
+TEST_F(ObfuscatedFileUtilTest, TestCreateAndDeleteFile) {
+ base::PlatformFile file_handle = base::kInvalidPlatformFileValue;
+ bool created;
+ FileSystemURL url = CreateURLFromUTF8("fake/file");
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ int file_flags = base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE;
+
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->CreateOrOpen(
+ context.get(), url, file_flags, &file_handle,
+ &created));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->DeleteFile(context.get(), url));
+
+ url = CreateURLFromUTF8("test file");
+
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Verify that file creation requires sufficient quota for the path.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1);
+ ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE,
+ ofu()->CreateOrOpen(
+ context.get(), url, file_flags, &file_handle, &created));
+
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateOrOpen(
+ context.get(), url, file_flags, &file_handle, &created));
+ ASSERT_TRUE(created);
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+ EXPECT_NE(base::kInvalidPlatformFileValue, file_handle);
+
+ CheckFileAndCloseHandle(url, file_handle);
+
+ context.reset(NewContext(NULL));
+ base::FilePath local_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath(
+ context.get(), url, &local_path));
+ EXPECT_TRUE(base::PathExists(local_path));
+
+ // Verify that deleting a file isn't stopped by zero quota, and that it frees
+ // up quote from its path.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(0);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->DeleteFile(context.get(), url));
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_FALSE(base::PathExists(local_path));
+ EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()),
+ context->allowed_bytes_growth());
+
+ context.reset(NewContext(NULL));
+ bool exclusive = true;
+ bool recursive = true;
+ FileSystemURL directory_url = CreateURLFromUTF8(
+ "series/of/directories");
+ url = FileSystemURLAppendUTF8(directory_url, "file name");
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(), directory_url, exclusive, recursive));
+ // The oepration created 3 directories recursively.
+ EXPECT_EQ(3, change_observer()->get_and_reset_create_directory_count());
+
+ context.reset(NewContext(NULL));
+ file_handle = base::kInvalidPlatformFileValue;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateOrOpen(
+ context.get(), url, file_flags, &file_handle, &created));
+ ASSERT_TRUE(created);
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+ EXPECT_NE(base::kInvalidPlatformFileValue, file_handle);
+
+ CheckFileAndCloseHandle(url, file_handle);
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath(
+ context.get(), url, &local_path));
+ EXPECT_TRUE(base::PathExists(local_path));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->DeleteFile(context.get(), url));
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_file_count());
+ EXPECT_FALSE(base::PathExists(local_path));
+
+ // Make sure we have no unexpected changes.
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestTruncate) {
+ bool created = false;
+ FileSystemURL url = CreateURLFromUTF8("file");
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->Truncate(context.get(), url, 4));
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+
+ context.reset(NewContext(NULL));
+ base::FilePath local_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetLocalFilePath(
+ context.get(), url, &local_path));
+ EXPECT_EQ(0, GetSize(local_path));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate(
+ context.get(), url, 10));
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+ EXPECT_EQ(10, GetSize(local_path));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->Truncate(
+ context.get(), url, 1));
+ EXPECT_EQ(1, GetSize(local_path));
+ EXPECT_EQ(1, change_observer()->get_and_reset_modify_file_count());
+
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+
+ // Make sure we have no unexpected changes.
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestQuotaOnTruncation) {
+ bool created = false;
+ FileSystemURL url = CreateURLFromUTF8("file");
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(url))->context(),
+ url, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(1020)->context(),
+ url, 1020));
+ ASSERT_EQ(1020, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(-1020)->context(),
+ url, 0));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE,
+ ofu()->Truncate(
+ DisallowUsageIncrease(1021)->context(),
+ url, 1021));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(1020)->context(),
+ url, 1020));
+ ASSERT_EQ(1020, ComputeTotalFileSize());
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(0)->context(),
+ url, 1020));
+ ASSERT_EQ(1020, ComputeTotalFileSize());
+
+ // quota exceeded
+ {
+ scoped_ptr<UsageVerifyHelper> helper = AllowUsageIncrease(-1);
+ helper->context()->set_allowed_bytes_growth(
+ helper->context()->allowed_bytes_growth() - 1);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(helper->context(), url, 1019));
+ ASSERT_EQ(1019, ComputeTotalFileSize());
+ }
+
+ // Delete backing file to make following truncation fail.
+ base::FilePath local_path;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->GetLocalFilePath(
+ UnlimitedContext().get(),
+ url, &local_path));
+ ASSERT_FALSE(local_path.empty());
+ ASSERT_TRUE(base::DeleteFile(local_path, false));
+
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->Truncate(
+ LimitedContext(1234).get(),
+ url, 1234));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestEnsureFileExists) {
+ FileSystemURL url = CreateURLFromUTF8("fake/file");
+ bool created = false;
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->EnsureFileExists(
+ context.get(), url, &created));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Verify that file creation requires sufficient quota for the path.
+ context.reset(NewContext(NULL));
+ url = CreateURLFromUTF8("test file");
+ created = false;
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1);
+ ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_FALSE(created);
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_file_count());
+
+ CheckFileAndCloseHandle(url, base::kInvalidPlatformFileValue);
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_FALSE(created);
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Also test in a subdirectory.
+ url = CreateURLFromUTF8("path/to/file.txt");
+ context.reset(NewContext(NULL));
+ bool exclusive = true;
+ bool recursive = true;
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(),
+ FileSystemURLDirName(url),
+ exclusive, recursive));
+ // 2 directories: path/ and path/to.
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestDirectoryOps) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ bool exclusive = false;
+ bool recursive = false;
+ FileSystemURL url = CreateURLFromUTF8("foo/bar");
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->DeleteDirectory(context.get(), url));
+
+ FileSystemURL root = CreateURLFromUTF8(std::string());
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_FALSE(PathExists(url));
+ context.reset(NewContext(NULL));
+ EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), root));
+
+ context.reset(NewContext(NULL));
+ exclusive = false;
+ recursive = true;
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+
+ EXPECT_TRUE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+
+ context.reset(NewContext(NULL));
+ EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(), root));
+ EXPECT_TRUE(DirectoryExists(FileSystemURLDirName(url)));
+
+ context.reset(NewContext(NULL));
+ EXPECT_FALSE(ofu()->IsDirectoryEmpty(context.get(),
+ FileSystemURLDirName(url)));
+
+ // Can't remove a non-empty directory.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY,
+ ofu()->DeleteDirectory(context.get(),
+ FileSystemURLDirName(url)));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ base::PlatformFileInfo file_info;
+ base::FilePath local_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo(
+ context.get(), url, &file_info, &local_path));
+ EXPECT_TRUE(local_path.empty());
+ EXPECT_TRUE(file_info.is_directory);
+ EXPECT_FALSE(file_info.is_symbolic_link);
+
+ // Same create again should succeed, since exclusive is false.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ exclusive = true;
+ recursive = true;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ // Verify that deleting a directory isn't stopped by zero quota, and that it
+ // frees up quota from its path.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(0);
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->DeleteDirectory(context.get(), url));
+ EXPECT_EQ(1, change_observer()->get_and_reset_remove_directory_count());
+ EXPECT_EQ(ObfuscatedFileUtil::ComputeFilePathCost(url.path()),
+ context->allowed_bytes_growth());
+
+ url = CreateURLFromUTF8("foo/bop");
+
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_FALSE(PathExists(url));
+
+ context.reset(NewContext(NULL));
+ EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, ofu()->GetFileInfo(
+ context.get(), url, &file_info, &local_path));
+
+ // Verify that file creation requires sufficient quota for the path.
+ exclusive = true;
+ recursive = false;
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()) - 1);
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(url.path()));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+
+ EXPECT_TRUE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+
+ exclusive = true;
+ recursive = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ exclusive = true;
+ recursive = false;
+ url = CreateURLFromUTF8("foo");
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+
+ url = CreateURLFromUTF8("blah");
+
+ EXPECT_FALSE(DirectoryExists(url));
+ EXPECT_FALSE(PathExists(url));
+
+ exclusive = true;
+ recursive = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_EQ(1, change_observer()->get_and_reset_create_directory_count());
+
+ EXPECT_TRUE(DirectoryExists(url));
+ EXPECT_TRUE(PathExists(url));
+
+ exclusive = true;
+ recursive = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestReadDirectory) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ bool exclusive = true;
+ bool recursive = true;
+ FileSystemURL url = CreateURLFromUTF8("directory/to/use");
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ TestReadDirectoryHelper(url);
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestReadRootWithSlash) {
+ TestReadDirectoryHelper(CreateURLFromUTF8(std::string()));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestReadRootWithEmptyString) {
+ TestReadDirectoryHelper(CreateURLFromUTF8("/"));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestReadDirectoryOnFile) {
+ FileSystemURL url = CreateURLFromUTF8("file");
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+
+ std::vector<DirectoryEntry> entries;
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), url, &entries));
+
+ EXPECT_TRUE(ofu()->IsDirectoryEmpty(context.get(), url));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestTouch) {
+ FileSystemURL url = CreateURLFromUTF8("file");
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ base::Time last_access_time = base::Time::Now();
+ base::Time last_modified_time = base::Time::Now();
+
+ // It's not there yet.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->Touch(
+ context.get(), url, last_access_time, last_modified_time));
+
+ // OK, now create it.
+ context.reset(NewContext(NULL));
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ ASSERT_TRUE(created);
+ TestTouchHelper(url, true);
+
+ // Now test a directory:
+ context.reset(NewContext(NULL));
+ bool exclusive = true;
+ bool recursive = false;
+ url = CreateURLFromUTF8("dir");
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(context.get(),
+ url, exclusive, recursive));
+ TestTouchHelper(url, false);
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestPathQuotas) {
+ FileSystemURL url = CreateURLFromUTF8("fake/file");
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ url = CreateURLFromUTF8("file name");
+ context->set_allowed_bytes_growth(5);
+ bool created = false;
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_FALSE(created);
+ context->set_allowed_bytes_growth(1024);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_TRUE(created);
+ int64 path_cost = ObfuscatedFileUtil::ComputeFilePathCost(url.path());
+ EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth());
+
+ context->set_allowed_bytes_growth(1024);
+ bool exclusive = true;
+ bool recursive = true;
+ url = CreateURLFromUTF8("directory/to/use");
+ std::vector<base::FilePath::StringType> components;
+ url.path().GetComponents(&components);
+ path_cost = 0;
+ typedef std::vector<base::FilePath::StringType>::iterator iterator;
+ for (iterator iter = components.begin();
+ iter != components.end(); ++iter) {
+ path_cost += ObfuscatedFileUtil::ComputeFilePathCost(
+ base::FilePath(*iter));
+ }
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(1024);
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(), url, exclusive, recursive));
+ EXPECT_EQ(1024 - path_cost, context->allowed_bytes_growth());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileNotFound) {
+ FileSystemURL source_url = CreateURLFromUTF8("path0.txt");
+ FileSystemURL dest_url = CreateURLFromUTF8("path1.txt");
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ bool is_copy_not_move = false;
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
+ is_copy_not_move));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ context.reset(NewContext(NULL));
+ is_copy_not_move = true;
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
+ is_copy_not_move));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ source_url = CreateURLFromUTF8("dir/dir/file");
+ bool exclusive = true;
+ bool recursive = true;
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(),
+ FileSystemURLDirName(source_url),
+ exclusive, recursive));
+ EXPECT_EQ(2, change_observer()->get_and_reset_create_directory_count());
+ is_copy_not_move = false;
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
+ is_copy_not_move));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+ context.reset(NewContext(NULL));
+ is_copy_not_move = true;
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->CopyOrMoveFile(context.get(), source_url, dest_url,
+ is_copy_not_move));
+ EXPECT_TRUE(change_observer()->HasNoChange());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestCopyOrMoveFileSuccess) {
+ const int64 kSourceLength = 5;
+ const int64 kDestLength = 50;
+
+ for (size_t i = 0; i < arraysize(kCopyMoveTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "kCopyMoveTestCase " << i);
+ const CopyMoveTestCaseRecord& test_case = kCopyMoveTestCases[i];
+ SCOPED_TRACE(testing::Message() << "\t is_copy_not_move " <<
+ test_case.is_copy_not_move);
+ SCOPED_TRACE(testing::Message() << "\t source_path " <<
+ test_case.source_path);
+ SCOPED_TRACE(testing::Message() << "\t dest_path " <<
+ test_case.dest_path);
+ SCOPED_TRACE(testing::Message() << "\t cause_overwrite " <<
+ test_case.cause_overwrite);
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ bool exclusive = false;
+ bool recursive = true;
+ FileSystemURL source_url = CreateURLFromUTF8(test_case.source_path);
+ FileSystemURL dest_url = CreateURLFromUTF8(test_case.dest_path);
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(),
+ FileSystemURLDirName(source_url),
+ exclusive, recursive));
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(),
+ FileSystemURLDirName(dest_url),
+ exclusive, recursive));
+
+ bool created = false;
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), source_url, &created));
+ ASSERT_TRUE(created);
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(context.get(), source_url, kSourceLength));
+
+ if (test_case.cause_overwrite) {
+ context.reset(NewContext(NULL));
+ created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), dest_url, &created));
+ ASSERT_TRUE(created);
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(context.get(), dest_url, kDestLength));
+ }
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->CopyOrMoveFile(context.get(),
+ source_url, dest_url, test_case.is_copy_not_move));
+
+ if (test_case.is_copy_not_move) {
+ base::PlatformFileInfo file_info;
+ base::FilePath local_path;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo(
+ context.get(), source_url, &file_info, &local_path));
+ EXPECT_EQ(kSourceLength, file_info.size);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->DeleteFile(context.get(), source_url));
+ } else {
+ base::PlatformFileInfo file_info;
+ base::FilePath local_path;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, ofu()->GetFileInfo(
+ context.get(), source_url, &file_info, &local_path));
+ }
+ base::PlatformFileInfo file_info;
+ base::FilePath local_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->GetFileInfo(
+ context.get(), dest_url, &file_info, &local_path));
+ EXPECT_EQ(kSourceLength, file_info.size);
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->DeleteFile(context.get(), dest_url));
+ }
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestCopyPathQuotas) {
+ FileSystemURL src_url = CreateURLFromUTF8("src path");
+ FileSystemURL dest_url = CreateURLFromUTF8("destination path");
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists(
+ context.get(), src_url, &created));
+
+ bool is_copy = true;
+ // Copy, no overwrite.
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) - 1);
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy));
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy));
+
+ // Copy, with overwrite.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(0);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithRename) {
+ FileSystemURL src_url = CreateURLFromUTF8("src path");
+ FileSystemURL dest_url = CreateURLFromUTF8("destination path");
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists(
+ context.get(), src_url, &created));
+
+ bool is_copy = false;
+ // Move, rename, no overwrite.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) -
+ ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()) - 1);
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy));
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(
+ ObfuscatedFileUtil::ComputeFilePathCost(dest_url.path()) -
+ ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy));
+
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists(
+ context.get(), src_url, &created));
+
+ // Move, rename, with overwrite.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(0);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestMovePathQuotasWithoutRename) {
+ FileSystemURL src_url = CreateURLFromUTF8("src path");
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists(
+ context.get(), src_url, &created));
+
+ bool exclusive = true;
+ bool recursive = false;
+ FileSystemURL dir_url = CreateURLFromUTF8("directory path");
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(), dir_url, exclusive, recursive));
+
+ FileSystemURL dest_url = FileSystemURLAppend(
+ dir_url, src_url.path().value());
+
+ bool is_copy = false;
+ int64 allowed_bytes_growth = -1000; // Over quota, this should still work.
+ // Move, no rename, no overwrite.
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(allowed_bytes_growth);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy));
+ EXPECT_EQ(allowed_bytes_growth, context->allowed_bytes_growth());
+
+ // Move, no rename, with overwrite.
+ context.reset(NewContext(NULL));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->EnsureFileExists(
+ context.get(), src_url, &created));
+ context.reset(NewContext(NULL));
+ context->set_allowed_bytes_growth(allowed_bytes_growth);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), src_url, dest_url, is_copy));
+ EXPECT_EQ(
+ allowed_bytes_growth +
+ ObfuscatedFileUtil::ComputeFilePathCost(src_url.path()),
+ context->allowed_bytes_growth());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestCopyInForeignFile) {
+ TestCopyInForeignFileHelper(false /* overwrite */);
+ TestCopyInForeignFileHelper(true /* overwrite */);
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestEnumerator) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ FileSystemURL src_url = CreateURLFromUTF8("source dir");
+ bool exclusive = true;
+ bool recursive = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, ofu()->CreateDirectory(
+ context.get(), src_url, exclusive, recursive));
+
+ std::set<base::FilePath::StringType> files;
+ std::set<base::FilePath::StringType> directories;
+ FillTestDirectory(src_url, &files, &directories);
+
+ FileSystemURL dest_url = CreateURLFromUTF8("destination dir");
+
+ EXPECT_FALSE(DirectoryExists(dest_url));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Copy(
+ file_system_context(), src_url, dest_url));
+
+ ValidateTestDirectory(dest_url, files, directories);
+ EXPECT_TRUE(DirectoryExists(src_url));
+ EXPECT_TRUE(DirectoryExists(dest_url));
+ recursive = true;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Remove(
+ file_system_context(), dest_url, recursive));
+ EXPECT_FALSE(DirectoryExists(dest_url));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestOriginEnumerator) {
+ scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator>
+ enumerator(ofu()->CreateOriginEnumerator());
+ // The test helper starts out with a single filesystem.
+ EXPECT_TRUE(enumerator.get());
+ EXPECT_EQ(origin(), enumerator->Next());
+ ASSERT_TRUE(type() == kFileSystemTypeTemporary);
+ EXPECT_TRUE(enumerator->HasFileSystemType(kFileSystemTypeTemporary));
+ EXPECT_FALSE(enumerator->HasFileSystemType(kFileSystemTypePersistent));
+ EXPECT_EQ(GURL(), enumerator->Next());
+ EXPECT_FALSE(enumerator->HasFileSystemType(kFileSystemTypeTemporary));
+ EXPECT_FALSE(enumerator->HasFileSystemType(kFileSystemTypePersistent));
+
+ std::set<GURL> origins_expected;
+ origins_expected.insert(origin());
+
+ for (size_t i = 0; i < arraysize(kOriginEnumerationTestRecords); ++i) {
+ SCOPED_TRACE(testing::Message() <<
+ "Validating kOriginEnumerationTestRecords " << i);
+ const OriginEnumerationTestRecord& record =
+ kOriginEnumerationTestRecords[i];
+ GURL origin_url(record.origin_url);
+ origins_expected.insert(origin_url);
+ if (record.has_temporary) {
+ scoped_ptr<SandboxFileSystemTestHelper> file_system(
+ NewFileSystem(origin_url, kFileSystemTypeTemporary));
+ scoped_ptr<FileSystemOperationContext> context(
+ NewContext(file_system.get()));
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ context.get(),
+ file_system->CreateURLFromUTF8("file"),
+ &created));
+ EXPECT_TRUE(created);
+ }
+ if (record.has_persistent) {
+ scoped_ptr<SandboxFileSystemTestHelper> file_system(
+ NewFileSystem(origin_url, kFileSystemTypePersistent));
+ scoped_ptr<FileSystemOperationContext> context(
+ NewContext(file_system.get()));
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ context.get(),
+ file_system->CreateURLFromUTF8("file"),
+ &created));
+ EXPECT_TRUE(created);
+ }
+ }
+ enumerator.reset(ofu()->CreateOriginEnumerator());
+ EXPECT_TRUE(enumerator.get());
+ std::set<GURL> origins_found;
+ GURL origin_url;
+ while (!(origin_url = enumerator->Next()).is_empty()) {
+ origins_found.insert(origin_url);
+ SCOPED_TRACE(testing::Message() << "Handling " << origin_url.spec());
+ bool found = false;
+ for (size_t i = 0; !found && i < arraysize(kOriginEnumerationTestRecords);
+ ++i) {
+ const OriginEnumerationTestRecord& record =
+ kOriginEnumerationTestRecords[i];
+ if (GURL(record.origin_url) != origin_url)
+ continue;
+ found = true;
+ EXPECT_EQ(record.has_temporary,
+ enumerator->HasFileSystemType(kFileSystemTypeTemporary));
+ EXPECT_EQ(record.has_persistent,
+ enumerator->HasFileSystemType(kFileSystemTypePersistent));
+ }
+ // Deal with the default filesystem created by the test helper.
+ if (!found && origin_url == origin()) {
+ ASSERT_TRUE(type() == kFileSystemTypeTemporary);
+ EXPECT_EQ(true,
+ enumerator->HasFileSystemType(kFileSystemTypeTemporary));
+ EXPECT_FALSE(enumerator->HasFileSystemType(kFileSystemTypePersistent));
+ found = true;
+ }
+ EXPECT_TRUE(found);
+ }
+
+ std::set<GURL> diff;
+ std::set_symmetric_difference(origins_expected.begin(),
+ origins_expected.end(), origins_found.begin(), origins_found.end(),
+ inserter(diff, diff.begin()));
+ EXPECT_TRUE(diff.empty());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestRevokeUsageCache) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+
+ int64 expected_quota = 0;
+
+ for (size_t i = 0; i < test::kRegularTestCaseSize; ++i) {
+ SCOPED_TRACE(testing::Message() << "Creating kRegularTestCase " << i);
+ const test::TestCaseRecord& test_case = test::kRegularTestCases[i];
+ base::FilePath file_path(test_case.path);
+ expected_quota += ObfuscatedFileUtil::ComputeFilePathCost(file_path);
+ if (test_case.is_directory) {
+ bool exclusive = true;
+ bool recursive = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), CreateURL(file_path),
+ exclusive, recursive));
+ } else {
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), CreateURL(file_path),
+ &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(context.get(),
+ CreateURL(file_path),
+ test_case.data_file_size));
+ expected_quota += test_case.data_file_size;
+ }
+ }
+
+ // Usually raw size in usage cache and the usage returned by QuotaUtil
+ // should be same.
+ EXPECT_EQ(expected_quota, SizeInUsageFile());
+ EXPECT_EQ(expected_quota, SizeByQuotaUtil());
+
+ RevokeUsageCache();
+ EXPECT_EQ(-1, SizeInUsageFile());
+ EXPECT_EQ(expected_quota, SizeByQuotaUtil());
+
+ // This should reconstruct the cache.
+ GetUsageFromQuotaManager();
+ EXPECT_EQ(expected_quota, SizeInUsageFile());
+ EXPECT_EQ(expected_quota, SizeByQuotaUtil());
+ EXPECT_EQ(expected_quota, usage());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestInconsistency) {
+ const FileSystemURL kPath1 = CreateURLFromUTF8("hoge");
+ const FileSystemURL kPath2 = CreateURLFromUTF8("fuga");
+
+ scoped_ptr<FileSystemOperationContext> context;
+ base::PlatformFile file;
+ base::PlatformFileInfo file_info;
+ base::FilePath data_path;
+ bool created = false;
+
+ // Create a non-empty file.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath1, &created));
+ EXPECT_TRUE(created);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(context.get(), kPath1, 10));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->GetFileInfo(
+ context.get(), kPath1, &file_info, &data_path));
+ EXPECT_EQ(10, file_info.size);
+
+ // Destroy database to make inconsistency between database and filesystem.
+ ofu()->DestroyDirectoryDatabase(origin(), type());
+
+ // Try to get file info of broken file.
+ EXPECT_FALSE(PathExists(kPath1));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath1, &created));
+ EXPECT_TRUE(created);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->GetFileInfo(
+ context.get(), kPath1, &file_info, &data_path));
+ EXPECT_EQ(0, file_info.size);
+
+ // Make another broken file to |kPath2|.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath2, &created));
+ EXPECT_TRUE(created);
+
+ // Destroy again.
+ ofu()->DestroyDirectoryDatabase(origin(), type());
+
+ // Repair broken |kPath1|.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->Touch(context.get(), kPath1, base::Time::Now(),
+ base::Time::Now()));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath1, &created));
+ EXPECT_TRUE(created);
+
+ // Copy from sound |kPath1| to broken |kPath2|.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(context.get(), kPath1, kPath2,
+ true /* copy */));
+
+ ofu()->DestroyDirectoryDatabase(origin(), type());
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateOrOpen(
+ context.get(), kPath1,
+ base::PLATFORM_FILE_READ | base::PLATFORM_FILE_CREATE,
+ &file, &created));
+ EXPECT_TRUE(created);
+ EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info));
+ EXPECT_EQ(0, file_info.size);
+ EXPECT_TRUE(base::ClosePlatformFile(file));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestIncompleteDirectoryReading) {
+ const FileSystemURL kPath[] = {
+ CreateURLFromUTF8("foo"),
+ CreateURLFromUTF8("bar"),
+ CreateURLFromUTF8("baz")
+ };
+ const FileSystemURL empty_path = CreateURL(base::FilePath());
+ scoped_ptr<FileSystemOperationContext> context;
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kPath); ++i) {
+ bool created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), kPath[i], &created));
+ EXPECT_TRUE(created);
+ }
+
+ std::vector<DirectoryEntry> entries;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), empty_path, &entries));
+ EXPECT_EQ(3u, entries.size());
+
+ base::FilePath local_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), kPath[0], &local_path));
+ EXPECT_TRUE(base::DeleteFile(local_path, false));
+
+ entries.clear();
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::ReadDirectory(
+ file_system_context(), empty_path, &entries));
+ EXPECT_EQ(ARRAYSIZE_UNSAFE(kPath) - 1, entries.size());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCreation) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir");
+
+ // Create working directory.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), dir_url, false, false));
+
+ // EnsureFileExists, create case.
+ FileSystemURL url(FileSystemURLAppendUTF8(
+ dir_url, "EnsureFileExists_file"));
+ bool created = false;
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_TRUE(created);
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+
+ // non create case.
+ created = true;
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_FALSE(created);
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // fail case.
+ url = FileSystemURLAppendUTF8(dir_url, "EnsureFileExists_dir");
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, false, false));
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_A_FILE,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // CreateOrOpen, create case.
+ url = FileSystemURLAppendUTF8(dir_url, "CreateOrOpen_file");
+ base::PlatformFile file_handle = base::kInvalidPlatformFileValue;
+ created = false;
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateOrOpen(
+ context.get(), url,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &file_handle, &created));
+ EXPECT_NE(base::kInvalidPlatformFileValue, file_handle);
+ EXPECT_TRUE(created);
+ EXPECT_TRUE(base::ClosePlatformFile(file_handle));
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+
+ // open case.
+ file_handle = base::kInvalidPlatformFileValue;
+ created = true;
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateOrOpen(
+ context.get(), url,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE,
+ &file_handle, &created));
+ EXPECT_NE(base::kInvalidPlatformFileValue, file_handle);
+ EXPECT_FALSE(created);
+ EXPECT_TRUE(base::ClosePlatformFile(file_handle));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // fail case
+ file_handle = base::kInvalidPlatformFileValue;
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS,
+ ofu()->CreateOrOpen(
+ context.get(), url,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &file_handle, &created));
+ EXPECT_EQ(base::kInvalidPlatformFileValue, file_handle);
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // CreateDirectory, create case.
+ // Creating CreateDirectory_dir and CreateDirectory_dir/subdir.
+ url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir");
+ FileSystemURL subdir_url(FileSystemURLAppendUTF8(url, "subdir"));
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), subdir_url,
+ true /* exclusive */, true /* recursive */));
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+
+ // create subdir case.
+ // Creating CreateDirectory_dir/subdir2.
+ subdir_url = FileSystemURLAppendUTF8(url, "subdir2");
+ ClearTimestamp(dir_url);
+ ClearTimestamp(url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), subdir_url,
+ true /* exclusive */, true /* recursive */));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+ EXPECT_NE(base::Time(), GetModifiedTime(url));
+
+ // fail case.
+ url = FileSystemURLAppendUTF8(dir_url, "CreateDirectory_dir");
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_EXISTS,
+ ofu()->CreateDirectory(context.get(), url,
+ true /* exclusive */, true /* recursive */));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // CopyInForeignFile, create case.
+ url = FileSystemURLAppendUTF8(dir_url, "CopyInForeignFile_file");
+ FileSystemURL src_path = FileSystemURLAppendUTF8(
+ dir_url, "CopyInForeignFile_src_file");
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), src_path, &created));
+ EXPECT_TRUE(created);
+ base::FilePath src_local_path;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), src_path, &src_local_path));
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyInForeignFile(context.get(),
+ src_local_path,
+ url));
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForDeletion) {
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ const FileSystemURL dir_url = CreateURLFromUTF8("foo_dir");
+
+ // Create working directory.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), dir_url, false, false));
+
+ // DeleteFile, delete case.
+ FileSystemURL url = FileSystemURLAppendUTF8(
+ dir_url, "DeleteFile_file");
+ bool created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url, &created));
+ EXPECT_TRUE(created);
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->DeleteFile(context.get(), url));
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+
+ // fail case.
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ ofu()->DeleteFile(context.get(), url));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // DeleteDirectory, fail case.
+ url = FileSystemURLAppendUTF8(dir_url, "DeleteDirectory_dir");
+ FileSystemURL file_path(FileSystemURLAppendUTF8(url, "pakeratta"));
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), url, true, true));
+ created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), file_path, &created));
+ EXPECT_TRUE(created);
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_EMPTY,
+ ofu()->DeleteDirectory(context.get(), url));
+ EXPECT_EQ(base::Time(), GetModifiedTime(dir_url));
+
+ // delete case.
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->DeleteFile(context.get(), file_path));
+
+ ClearTimestamp(dir_url);
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, ofu()->DeleteDirectory(context.get(), url));
+ EXPECT_NE(base::Time(), GetModifiedTime(dir_url));
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestDirectoryTimestampForCopyAndMove) {
+ TestDirectoryTimestampHelper(
+ CreateURLFromUTF8("copy overwrite"), true, true);
+ TestDirectoryTimestampHelper(
+ CreateURLFromUTF8("copy non-overwrite"), true, false);
+ TestDirectoryTimestampHelper(
+ CreateURLFromUTF8("move overwrite"), false, true);
+ TestDirectoryTimestampHelper(
+ CreateURLFromUTF8("move non-overwrite"), false, false);
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestFileEnumeratorTimestamp) {
+ FileSystemURL dir = CreateURLFromUTF8("foo");
+ FileSystemURL url1 = FileSystemURLAppendUTF8(dir, "bar");
+ FileSystemURL url2 = FileSystemURLAppendUTF8(dir, "baz");
+
+ scoped_ptr<FileSystemOperationContext> context(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), dir, false, false));
+
+ bool created = false;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(context.get(), url1, &created));
+ EXPECT_TRUE(created);
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(context.get(), url2, false, false));
+
+ base::FilePath file_path;
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->GetLocalFilePath(context.get(), url1, &file_path));
+ EXPECT_FALSE(file_path.empty());
+
+ context.reset(NewContext(NULL));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Touch(context.get(), url1,
+ base::Time::Now() + base::TimeDelta::FromHours(1),
+ base::Time()));
+
+ context.reset(NewContext(NULL));
+ scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> file_enum(
+ ofu()->CreateFileEnumerator(context.get(), dir, false));
+
+ int count = 0;
+ base::FilePath file_path_each;
+ while (!(file_path_each = file_enum->Next()).empty()) {
+ context.reset(NewContext(NULL));
+ base::PlatformFileInfo file_info;
+ base::FilePath file_path;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->GetFileInfo(context.get(),
+ FileSystemURL::CreateForTest(
+ dir.origin(),
+ dir.mount_type(),
+ file_path_each),
+ &file_info, &file_path));
+ EXPECT_EQ(file_info.is_directory, file_enum->IsDirectory());
+ EXPECT_EQ(file_info.last_modified, file_enum->LastModifiedTime());
+ EXPECT_EQ(file_info.size, file_enum->Size());
+ ++count;
+ }
+ EXPECT_EQ(2, count);
+}
+
+// crbug.com/176470
+#if defined(OS_WIN) || defined(OS_ANDROID)
+#define MAYBE_TestQuotaOnCopyFile DISABLED_TestQuotaOnCopyFile
+#else
+#define MAYBE_TestQuotaOnCopyFile TestQuotaOnCopyFile
+#endif
+TEST_F(ObfuscatedFileUtilTest, MAYBE_TestQuotaOnCopyFile) {
+ FileSystemURL from_file(CreateURLFromUTF8("fromfile"));
+ FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile"));
+ FileSystemURL to_file1(CreateURLFromUTF8("tofile1"));
+ FileSystemURL to_file2(CreateURLFromUTF8("tofile2"));
+ bool created;
+
+ int64 expected_total_file_size = 0;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(from_file))->context(),
+ from_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(obstacle_file))->context(),
+ obstacle_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64 from_file_size = 1020;
+ expected_total_file_size += from_file_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64 obstacle_file_size = 1;
+ expected_total_file_size += obstacle_file_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(obstacle_file_size)->context(),
+ obstacle_file, obstacle_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64 to_file1_size = from_file_size;
+ expected_total_file_size += to_file1_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(
+ AllowUsageIncrease(
+ PathCost(to_file1) + to_file1_size)->context(),
+ from_file, to_file1, true /* copy */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE,
+ ofu()->CopyOrMoveFile(
+ DisallowUsageIncrease(
+ PathCost(to_file2) + from_file_size)->context(),
+ from_file, to_file2, true /* copy */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64 old_obstacle_file_size = obstacle_file_size;
+ obstacle_file_size = from_file_size;
+ expected_total_file_size += obstacle_file_size - old_obstacle_file_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(
+ AllowUsageIncrease(
+ obstacle_file_size - old_obstacle_file_size)->context(),
+ from_file, obstacle_file, true /* copy */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64 old_from_file_size = from_file_size;
+ from_file_size = old_from_file_size - 1;
+ expected_total_file_size += from_file_size - old_from_file_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(
+ from_file_size - old_from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ // quota exceeded
+ {
+ old_obstacle_file_size = obstacle_file_size;
+ obstacle_file_size = from_file_size;
+ expected_total_file_size += obstacle_file_size - old_obstacle_file_size;
+ scoped_ptr<UsageVerifyHelper> helper = AllowUsageIncrease(
+ obstacle_file_size - old_obstacle_file_size);
+ helper->context()->set_allowed_bytes_growth(
+ helper->context()->allowed_bytes_growth() - 1);
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(
+ helper->context(),
+ from_file, obstacle_file, true /* copy */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+ }
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestQuotaOnMoveFile) {
+ FileSystemURL from_file(CreateURLFromUTF8("fromfile"));
+ FileSystemURL obstacle_file(CreateURLFromUTF8("obstaclefile"));
+ FileSystemURL to_file(CreateURLFromUTF8("tofile"));
+ bool created;
+
+ int64 expected_total_file_size = 0;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(from_file))->context(),
+ from_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64 from_file_size = 1020;
+ expected_total_file_size += from_file_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64 to_file_size ALLOW_UNUSED = from_file_size;
+ from_file_size = 0;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(
+ AllowUsageIncrease(-PathCost(from_file) +
+ PathCost(to_file))->context(),
+ from_file, to_file, false /* move */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(from_file))->context(),
+ from_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(obstacle_file))->context(),
+ obstacle_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ from_file_size = 1020;
+ expected_total_file_size += from_file_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64 obstacle_file_size = 1;
+ expected_total_file_size += obstacle_file_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(1)->context(),
+ obstacle_file, obstacle_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ int64 old_obstacle_file_size = obstacle_file_size;
+ obstacle_file_size = from_file_size;
+ from_file_size = 0;
+ expected_total_file_size -= old_obstacle_file_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(
+ AllowUsageIncrease(
+ -old_obstacle_file_size - PathCost(from_file))->context(),
+ from_file, obstacle_file,
+ false /* move */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(from_file))->context(),
+ from_file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ from_file_size = 10;
+ expected_total_file_size += from_file_size;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(from_file_size)->context(),
+ from_file, from_file_size));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+
+ // quota exceeded even after operation
+ old_obstacle_file_size = obstacle_file_size;
+ obstacle_file_size = from_file_size;
+ from_file_size = 0;
+ expected_total_file_size -= old_obstacle_file_size;
+ scoped_ptr<FileSystemOperationContext> context =
+ LimitedContext(-old_obstacle_file_size - PathCost(from_file) - 1);
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CopyOrMoveFile(
+ context.get(), from_file, obstacle_file, false /* move */));
+ ASSERT_EQ(expected_total_file_size, ComputeTotalFileSize());
+ context.reset();
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestQuotaOnRemove) {
+ FileSystemURL dir(CreateURLFromUTF8("dir"));
+ FileSystemURL file(CreateURLFromUTF8("file"));
+ FileSystemURL dfile1(CreateURLFromUTF8("dir/dfile1"));
+ FileSystemURL dfile2(CreateURLFromUTF8("dir/dfile2"));
+ bool created;
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(file))->context(),
+ file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateDirectory(
+ AllowUsageIncrease(PathCost(dir))->context(),
+ dir, false, false));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(dfile1))->context(),
+ dfile1, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(dfile2))->context(),
+ dfile2, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(340)->context(),
+ file, 340));
+ ASSERT_EQ(340, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(1020)->context(),
+ dfile1, 1020));
+ ASSERT_EQ(1360, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(120)->context(),
+ dfile2, 120));
+ ASSERT_EQ(1480, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->DeleteFile(
+ AllowUsageIncrease(-PathCost(file) - 340)->context(),
+ file));
+ ASSERT_EQ(1140, ComputeTotalFileSize());
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ AsyncFileTestHelper::Remove(
+ file_system_context(), dir, true /* recursive */));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+}
+
+TEST_F(ObfuscatedFileUtilTest, TestQuotaOnOpen) {
+ FileSystemURL file(CreateURLFromUTF8("file"));
+ base::PlatformFile file_handle;
+ bool created;
+
+ // Creating a file.
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->EnsureFileExists(
+ AllowUsageIncrease(PathCost(file))->context(),
+ file, &created));
+ ASSERT_TRUE(created);
+ ASSERT_EQ(0, ComputeTotalFileSize());
+
+ // Opening it, which shouldn't change the usage.
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateOrOpen(
+ AllowUsageIncrease(0)->context(), file,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE,
+ &file_handle, &created));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+ EXPECT_TRUE(base::ClosePlatformFile(file_handle));
+
+ const int length = 33;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(length)->context(), file, length));
+ ASSERT_EQ(length, ComputeTotalFileSize());
+
+ // Opening it with CREATE_ALWAYS flag, which should truncate the file size.
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateOrOpen(
+ AllowUsageIncrease(-length)->context(), file,
+ base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
+ &file_handle, &created));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+ EXPECT_TRUE(base::ClosePlatformFile(file_handle));
+
+ // Extending the file again.
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->Truncate(
+ AllowUsageIncrease(length)->context(), file, length));
+ ASSERT_EQ(length, ComputeTotalFileSize());
+
+ // Opening it with TRUNCATED flag, which should truncate the file size.
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ ofu()->CreateOrOpen(
+ AllowUsageIncrease(-length)->context(), file,
+ base::PLATFORM_FILE_OPEN_TRUNCATED | base::PLATFORM_FILE_WRITE,
+ &file_handle, &created));
+ ASSERT_EQ(0, ComputeTotalFileSize());
+ EXPECT_TRUE(base::ClosePlatformFile(file_handle));
+}
+
+TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAliveCase) {
+ ObfuscatedFileUtil file_util(NULL,
+ data_dir_path(),
+ base::MessageLoopProxy::current().get());
+ file_util.InitOriginDatabase(true /*create*/);
+ ASSERT_TRUE(file_util.origin_database_ != NULL);
+
+ // Callback to Drop DB is called while ObfuscatedFileUtilTest is still alive.
+ file_util.db_flush_delay_seconds_ = 0;
+ file_util.MarkUsed();
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_TRUE(file_util.origin_database_ == NULL);
+}
+
+TEST_F(ObfuscatedFileUtilTest, MaybeDropDatabasesAlreadyDeletedCase) {
+ // Run message loop after OFU is already deleted to make sure callback doesn't
+ // cause a crash for use after free.
+ {
+ ObfuscatedFileUtil file_util(NULL,
+ data_dir_path(),
+ base::MessageLoopProxy::current().get());
+ file_util.InitOriginDatabase(true /*create*/);
+ file_util.db_flush_delay_seconds_ = 0;
+ file_util.MarkUsed();
+ }
+
+ // At this point the callback is still in the message queue but OFU is gone.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(ObfuscatedFileUtilTest, DestroyDirectoryDatabase_Isolated) {
+ storage_policy_->AddIsolated(origin_);
+ ObfuscatedFileUtil file_util(
+ storage_policy_.get(), data_dir_path(),
+ base::MessageLoopProxy::current().get());
+
+ // Create DirectoryDatabase for isolated origin.
+ SandboxDirectoryDatabase* db = file_util.GetDirectoryDatabase(
+ origin_, kFileSystemTypePersistent, true /* create */);
+ ASSERT_TRUE(db != NULL);
+
+ // Destory it.
+ ASSERT_TRUE(
+ file_util.DestroyDirectoryDatabase(origin_, kFileSystemTypePersistent));
+ ASSERT_TRUE(file_util.directories_.empty());
+}
+
+TEST_F(ObfuscatedFileUtilTest, GetDirectoryDatabase_Isolated) {
+ storage_policy_->AddIsolated(origin_);
+ ObfuscatedFileUtil file_util(
+ storage_policy_.get(), data_dir_path(),
+ base::MessageLoopProxy::current().get());
+
+ // Create DirectoryDatabase for isolated origin.
+ SandboxDirectoryDatabase* db = file_util.GetDirectoryDatabase(
+ origin_, kFileSystemTypePersistent, true /* create */);
+ ASSERT_TRUE(db != NULL);
+ ASSERT_EQ(1U, file_util.directories_.size());
+
+ // Remove isolated.
+ storage_policy_->RemoveIsolated(origin_);
+
+ // This should still get the same database.
+ SandboxDirectoryDatabase* db2 = file_util.GetDirectoryDatabase(
+ origin_, kFileSystemTypePersistent, false /* create */);
+ ASSERT_EQ(db, db2);
+}
+
+TEST_F(ObfuscatedFileUtilTest, MigrationBackFromIsolated) {
+ std::string kFakeDirectoryData("0123456789");
+ base::FilePath old_directory_db_path;
+
+ // Initialize the directory with one origin using
+ // SandboxIsolatedOriginDatabase.
+ {
+ std::string origin_string =
+ webkit_database::GetIdentifierFromOrigin(origin_);
+ SandboxIsolatedOriginDatabase database_old(origin_string, data_dir_path());
+ base::FilePath path;
+ EXPECT_TRUE(database_old.GetPathForOrigin(origin_string, &path));
+ EXPECT_FALSE(path.empty());
+
+ // Populate the origin directory with some fake data.
+ old_directory_db_path = data_dir_path().Append(path);
+ ASSERT_TRUE(file_util::CreateDirectory(old_directory_db_path));
+ EXPECT_EQ(static_cast<int>(kFakeDirectoryData.size()),
+ file_util::WriteFile(old_directory_db_path.AppendASCII("dummy"),
+ kFakeDirectoryData.data(),
+ kFakeDirectoryData.size()));
+ }
+
+ storage_policy_->AddIsolated(origin_);
+ ObfuscatedFileUtil file_util(
+ storage_policy_.get(), data_dir_path(),
+ base::MessageLoopProxy::current().get());
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::FilePath origin_directory = file_util.GetDirectoryForOrigin(
+ origin_, true /* create */, &error);
+ EXPECT_EQ(base::PLATFORM_FILE_OK, error);
+
+ // The database is migrated from the old one.
+ EXPECT_TRUE(base::DirectoryExists(origin_directory));
+ EXPECT_FALSE(base::DirectoryExists(old_directory_db_path));
+
+ // Check we see the same contents in the new origin directory.
+ std::string origin_db_data;
+ EXPECT_TRUE(base::PathExists(origin_directory.AppendASCII("dummy")));
+ EXPECT_TRUE(file_util::ReadFileToString(
+ origin_directory.AppendASCII("dummy"), &origin_db_data));
+ EXPECT_EQ(kFakeDirectoryData, origin_db_data);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/open_file_system_mode.h b/chromium/webkit/browser/fileapi/open_file_system_mode.h
new file mode 100644
index 00000000000..72a2bcb2f44
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/open_file_system_mode.h
@@ -0,0 +1,22 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_
+#define WEBKIT_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_
+
+namespace fileapi {
+
+// Determines the behavior on OpenFileSystem when a specified
+// FileSystem does not exist.
+// Specifying CREATE_IF_NONEXISTENT may make actual modification on
+// disk (e.g. creating a root directory, setting up a metadata database etc)
+// if the filesystem hasn't been initialized.
+enum OpenFileSystemMode {
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_
diff --git a/chromium/webkit/browser/fileapi/recursive_operation_delegate.cc b/chromium/webkit/browser/fileapi/recursive_operation_delegate.cc
new file mode 100644
index 00000000000..c91c74cf148
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/recursive_operation_delegate.cc
@@ -0,0 +1,141 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/recursive_operation_delegate.h"
+
+#include "base/bind.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+
+namespace fileapi {
+
+namespace {
+// Don't start too many inflight operations.
+const int kMaxInflightOperations = 5;
+}
+
+RecursiveOperationDelegate::RecursiveOperationDelegate(
+ FileSystemContext* file_system_context)
+ : file_system_context_(file_system_context),
+ inflight_operations_(0) {
+}
+
+RecursiveOperationDelegate::~RecursiveOperationDelegate() {
+}
+
+void RecursiveOperationDelegate::StartRecursiveOperation(
+ const FileSystemURL& root,
+ const StatusCallback& callback) {
+ callback_ = callback;
+ pending_directories_.push(root);
+ ProcessNextDirectory();
+}
+
+FileSystemOperationRunner* RecursiveOperationDelegate::operation_runner() {
+ return file_system_context_->operation_runner();
+}
+
+void RecursiveOperationDelegate::ProcessNextDirectory() {
+ DCHECK(pending_files_.empty());
+ if (inflight_operations_ > 0)
+ return;
+ if (pending_directories_.empty()) {
+ callback_.Run(base::PLATFORM_FILE_OK);
+ return;
+ }
+ FileSystemURL url = pending_directories_.front();
+ pending_directories_.pop();
+ inflight_operations_++;
+ ProcessDirectory(
+ url, base::Bind(&RecursiveOperationDelegate::DidProcessDirectory,
+ AsWeakPtr(), url));
+}
+
+void RecursiveOperationDelegate::ProcessPendingFiles() {
+ if (pending_files_.empty()) {
+ ProcessNextDirectory();
+ return;
+ }
+ while (!pending_files_.empty() &&
+ inflight_operations_ < kMaxInflightOperations) {
+ FileSystemURL url = pending_files_.front();
+ pending_files_.pop();
+ inflight_operations_++;
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&RecursiveOperationDelegate::ProcessFile,
+ AsWeakPtr(), url,
+ base::Bind(&RecursiveOperationDelegate::DidProcessFile,
+ AsWeakPtr())));
+ }
+}
+
+void RecursiveOperationDelegate::DidProcessFile(base::PlatformFileError error) {
+ inflight_operations_--;
+ DCHECK_GE(inflight_operations_, 0);
+ if (error != base::PLATFORM_FILE_OK) {
+ callback_.Run(error);
+ return;
+ }
+ ProcessPendingFiles();
+}
+
+void RecursiveOperationDelegate::DidProcessDirectory(
+ const FileSystemURL& url,
+ base::PlatformFileError error) {
+ if (error != base::PLATFORM_FILE_OK) {
+ callback_.Run(error);
+ return;
+ }
+ operation_runner()->ReadDirectory(
+ url, base::Bind(&RecursiveOperationDelegate::DidReadDirectory,
+ AsWeakPtr(), url));
+}
+
+void RecursiveOperationDelegate::DidReadDirectory(
+ const FileSystemURL& parent,
+ base::PlatformFileError error,
+ const FileEntryList& entries,
+ bool has_more) {
+ if (error != base::PLATFORM_FILE_OK) {
+ if (error == base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY) {
+ // The given path may have been a file, so try RemoveFile now.
+ ProcessFile(parent,
+ base::Bind(&RecursiveOperationDelegate::DidTryProcessFile,
+ AsWeakPtr(), error));
+ return;
+ }
+ callback_.Run(error);
+ return;
+ }
+ for (size_t i = 0; i < entries.size(); i++) {
+ FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
+ parent.origin(),
+ parent.mount_type(),
+ parent.virtual_path().Append(entries[i].name));
+ if (entries[i].is_directory)
+ pending_directories_.push(url);
+ else
+ pending_files_.push(url);
+ }
+ if (has_more)
+ return;
+
+ inflight_operations_--;
+ DCHECK_GE(inflight_operations_, 0);
+ ProcessPendingFiles();
+}
+
+void RecursiveOperationDelegate::DidTryProcessFile(
+ base::PlatformFileError previous_error,
+ base::PlatformFileError error) {
+ if (error == base::PLATFORM_FILE_ERROR_NOT_A_FILE) {
+ // It wasn't a file either; returns with the previous error.
+ callback_.Run(previous_error);
+ return;
+ }
+ DidProcessFile(error);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/recursive_operation_delegate.h b/chromium/webkit/browser/fileapi/recursive_operation_delegate.h
new file mode 100644
index 00000000000..551d875d125
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/recursive_operation_delegate.h
@@ -0,0 +1,101 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_
+#define WEBKIT_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_
+
+#include <queue>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "webkit/browser/fileapi/file_system_operation.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+namespace fileapi {
+
+class FileSystemContext;
+class FileSystemOperationRunner;
+
+// A base class for recursive operation delegates.
+// This also provides some convenient default implementations for subclasses
+// like StartRecursiveOperation() and NewNestedOperation().
+//
+// In short, each subclass should override ProcessFile and ProcessDirectory
+// to process a directory or a file. To start the recursive operation it
+// should also call StartRecursiveOperation.
+//
+// Each subclass can call NewNestedOperation to create a new file system
+// operation to perform a sub-operations, e.g. can call RemoveFile for
+// recursive Remove.
+class RecursiveOperationDelegate
+ : public base::SupportsWeakPtr<RecursiveOperationDelegate> {
+ public:
+ typedef FileSystemOperation::StatusCallback StatusCallback;
+ typedef FileSystemOperation::FileEntryList FileEntryList;
+
+ RecursiveOperationDelegate(FileSystemContext* file_system_context);
+ virtual ~RecursiveOperationDelegate();
+
+ // This is called when the consumer of this instance starts a non-recursive
+ // operation.
+ virtual void Run() = 0;
+
+ // This is called when the consumer of this instance starts a recursive
+ // operation.
+ virtual void RunRecursively() = 0;
+
+ // This is called each time a file is found while recursively
+ // performing an operation.
+ virtual void ProcessFile(const FileSystemURL& url,
+ const StatusCallback& callback) = 0;
+
+ // This is called each time a directory is found while recursively
+ // performing an operation.
+ virtual void ProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) = 0;
+
+ protected:
+ // Starts to process files/directories recursively from the given |root|.
+ // This will call ProcessFile and ProcessDirectory on each directory or file.
+ // If the given |root| is a file this simply calls ProcessFile and exits.
+ //
+ // |callback| is fired with base::PLATFORM_FILE_OK when every file/directory
+ // under |root| is processed, or fired earlier when any suboperation fails.
+ void StartRecursiveOperation(const FileSystemURL& root,
+ const StatusCallback& callback);
+
+ FileSystemContext* file_system_context() { return file_system_context_; }
+ const FileSystemContext* file_system_context() const {
+ return file_system_context_;
+ }
+
+ FileSystemOperationRunner* operation_runner();
+
+ private:
+ void ProcessNextDirectory();
+ void ProcessPendingFiles();
+ void DidProcessFile(base::PlatformFileError error);
+ void DidProcessDirectory(const FileSystemURL& url,
+ base::PlatformFileError error);
+ void DidReadDirectory(
+ const FileSystemURL& parent,
+ base::PlatformFileError error,
+ const FileEntryList& entries,
+ bool has_more);
+ void DidTryProcessFile(base::PlatformFileError previous_error,
+ base::PlatformFileError error);
+
+ FileSystemContext* file_system_context_;
+ StatusCallback callback_;
+ std::queue<FileSystemURL> pending_directories_;
+ std::queue<FileSystemURL> pending_files_;
+ int inflight_operations_;
+
+ DISALLOW_COPY_AND_ASSIGN(RecursiveOperationDelegate);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_
diff --git a/chromium/webkit/browser/fileapi/remove_operation_delegate.cc b/chromium/webkit/browser/fileapi/remove_operation_delegate.cc
new file mode 100644
index 00000000000..fcd9ba9cee2
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/remove_operation_delegate.cc
@@ -0,0 +1,88 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/remove_operation_delegate.h"
+
+#include "base/bind.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+
+namespace fileapi {
+
+RemoveOperationDelegate::RemoveOperationDelegate(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ const StatusCallback& callback)
+ : RecursiveOperationDelegate(file_system_context),
+ url_(url),
+ callback_(callback),
+ weak_factory_(this) {
+}
+
+RemoveOperationDelegate::~RemoveOperationDelegate() {}
+
+void RemoveOperationDelegate::Run() {
+ operation_runner()->RemoveFile(url_, base::Bind(
+ &RemoveOperationDelegate::DidTryRemoveFile, weak_factory_.GetWeakPtr()));
+}
+
+void RemoveOperationDelegate::RunRecursively() {
+ StartRecursiveOperation(
+ url_,
+ base::Bind(&RemoveOperationDelegate::RemoveNextDirectory,
+ weak_factory_.GetWeakPtr()));
+}
+
+void RemoveOperationDelegate::ProcessFile(const FileSystemURL& url,
+ const StatusCallback& callback) {
+ if (to_remove_directories_.size() == 1u &&
+ to_remove_directories_.top() == url) {
+ // We seem to have been re-directed from ProcessDirectory.
+ to_remove_directories_.pop();
+ }
+ operation_runner()->RemoveFile(url, base::Bind(
+ &RemoveOperationDelegate::DidRemoveFile,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void RemoveOperationDelegate::ProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) {
+ to_remove_directories_.push(url);
+ callback.Run(base::PLATFORM_FILE_OK);
+}
+
+void RemoveOperationDelegate::DidTryRemoveFile(
+ base::PlatformFileError error) {
+ if (error == base::PLATFORM_FILE_OK ||
+ error != base::PLATFORM_FILE_ERROR_NOT_A_FILE) {
+ callback_.Run(error);
+ return;
+ }
+ operation_runner()->RemoveDirectory(url_, callback_);
+}
+
+void RemoveOperationDelegate::DidRemoveFile(const StatusCallback& callback,
+ base::PlatformFileError error) {
+ if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND) {
+ callback.Run(base::PLATFORM_FILE_OK);
+ return;
+ }
+ callback.Run(error);
+}
+
+void RemoveOperationDelegate::RemoveNextDirectory(
+ base::PlatformFileError error) {
+ if (error != base::PLATFORM_FILE_OK ||
+ to_remove_directories_.empty()) {
+ callback_.Run(error);
+ return;
+ }
+ FileSystemURL url = to_remove_directories_.top();
+ to_remove_directories_.pop();
+ operation_runner()->RemoveDirectory(url, base::Bind(
+ &RemoveOperationDelegate::RemoveNextDirectory,
+ weak_factory_.GetWeakPtr()));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/remove_operation_delegate.h b/chromium/webkit/browser/fileapi/remove_operation_delegate.h
new file mode 100644
index 00000000000..ef9e1a09409
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/remove_operation_delegate.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_
+#define WEBKIT_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_
+
+#include <stack>
+
+#include "webkit/browser/fileapi/recursive_operation_delegate.h"
+
+namespace fileapi {
+
+class RemoveOperationDelegate
+ : public RecursiveOperationDelegate {
+ public:
+ RemoveOperationDelegate(FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ const StatusCallback& callback);
+ virtual ~RemoveOperationDelegate();
+
+ // RecursiveOperationDelegate overrides:
+ virtual void Run() OVERRIDE;
+ virtual void RunRecursively() OVERRIDE;
+ virtual void ProcessFile(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void ProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+
+ private:
+ void DidTryRemoveFile(base::PlatformFileError error);
+ void DidRemoveFile(const StatusCallback& callback,
+ base::PlatformFileError error);
+ void RemoveNextDirectory(base::PlatformFileError error);
+
+ FileSystemURL url_;
+ StatusCallback callback_;
+
+ std::stack<FileSystemURL> to_remove_directories_;
+
+ base::WeakPtrFactory<RemoveOperationDelegate> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(RemoveOperationDelegate);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_database_test_helper.cc b/chromium/webkit/browser/fileapi/sandbox_database_test_helper.cc
new file mode 100644
index 00000000000..6364b2bd7f5
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_database_test_helper.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_database_test_helper.h"
+
+#include <algorithm>
+#include <functional>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+void CorruptDatabase(const base::FilePath& db_path,
+ leveldb::FileType type,
+ ptrdiff_t offset,
+ size_t size) {
+ base::FileEnumerator file_enum(db_path, false /* not recursive */,
+ base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
+ base::FilePath file_path;
+ base::FilePath picked_file_path;
+ uint64 picked_file_number = kuint64max;
+
+ while (!(file_path = file_enum.Next()).empty()) {
+ uint64 number = kuint64max;
+ leveldb::FileType file_type;
+ EXPECT_TRUE(leveldb::ParseFileName(FilePathToString(file_path.BaseName()),
+ &number, &file_type));
+ if (file_type == type &&
+ (picked_file_number == kuint64max || picked_file_number < number)) {
+ picked_file_path = file_path;
+ picked_file_number = number;
+ }
+ }
+
+ EXPECT_FALSE(picked_file_path.empty());
+ EXPECT_NE(kuint64max, picked_file_number);
+
+ bool created = true;
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::PlatformFile file =
+ CreatePlatformFile(picked_file_path,
+ base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_WRITE,
+ &created, &error);
+ EXPECT_EQ(base::PLATFORM_FILE_OK, error);
+ EXPECT_FALSE(created);
+
+ base::PlatformFileInfo file_info;
+ EXPECT_TRUE(base::GetPlatformFileInfo(file, &file_info));
+ if (offset < 0)
+ offset += file_info.size;
+ EXPECT_GE(offset, 0);
+ EXPECT_LE(offset, file_info.size);
+
+ size = std::min(size, static_cast<size_t>(file_info.size - offset));
+
+ std::vector<char> buf(size);
+ int read_size = base::ReadPlatformFile(file, offset,
+ vector_as_array(&buf), buf.size());
+ EXPECT_LT(0, read_size);
+ EXPECT_GE(buf.size(), static_cast<size_t>(read_size));
+ buf.resize(read_size);
+
+ std::transform(buf.begin(), buf.end(), buf.begin(),
+ std::logical_not<char>());
+
+ int written_size = base::WritePlatformFile(file, offset,
+ vector_as_array(&buf), buf.size());
+ EXPECT_GT(written_size, 0);
+ EXPECT_EQ(buf.size(), static_cast<size_t>(written_size));
+
+ base::ClosePlatformFile(file);
+}
+
+void DeleteDatabaseFile(const base::FilePath& db_path,
+ leveldb::FileType type) {
+ base::FileEnumerator file_enum(db_path, false /* not recursive */,
+ base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
+ base::FilePath file_path;
+ while (!(file_path = file_enum.Next()).empty()) {
+ uint64 number = kuint64max;
+ leveldb::FileType file_type;
+ EXPECT_TRUE(leveldb::ParseFileName(FilePathToString(file_path.BaseName()),
+ &number, &file_type));
+ if (file_type == type) {
+ base::DeleteFile(file_path, false);
+ // We may have multiple files for the same type, so don't break here.
+ }
+ }
+
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_database_test_helper.h b/chromium/webkit/browser/fileapi/sandbox_database_test_helper.h
new file mode 100644
index 00000000000..1635c2fd265
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_database_test_helper.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_DATABASE_TEST_HELPER_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_DATABASE_TEST_HELPER_H_
+
+#include <cstddef>
+
+#include "third_party/leveldatabase/src/db/filename.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace fileapi {
+
+void CorruptDatabase(const base::FilePath& db_path,
+ leveldb::FileType type,
+ ptrdiff_t offset,
+ size_t size);
+
+void DeleteDatabaseFile(const base::FilePath& db_path,
+ leveldb::FileType type);
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_DATABASE_TEST_HELPER_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_directory_database.cc b/chromium/webkit/browser/fileapi/sandbox_directory_database.cc
new file mode 100644
index 00000000000..24790b15e31
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_directory_database.cc
@@ -0,0 +1,928 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_directory_database.h"
+
+#include <math.h>
+#include <algorithm>
+#include <set>
+#include <stack>
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/location.h"
+#include "base/metrics/histogram.h"
+#include "base/pickle.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace {
+
+bool PickleFromFileInfo(
+ const fileapi::SandboxDirectoryDatabase::FileInfo& info,
+ Pickle* pickle) {
+ DCHECK(pickle);
+ std::string data_path;
+ // Round off here to match the behavior of the filesystem on real files.
+ base::Time time =
+ base::Time::FromDoubleT(floor(info.modification_time.ToDoubleT()));
+ std::string name;
+
+ data_path = fileapi::FilePathToString(info.data_path);
+ name = fileapi::FilePathToString(base::FilePath(info.name));
+
+ if (pickle->WriteInt64(info.parent_id) &&
+ pickle->WriteString(data_path) &&
+ pickle->WriteString(name) &&
+ pickle->WriteInt64(time.ToInternalValue()))
+ return true;
+
+ NOTREACHED();
+ return false;
+}
+
+bool FileInfoFromPickle(
+ const Pickle& pickle,
+ fileapi::SandboxDirectoryDatabase::FileInfo* info) {
+ PickleIterator iter(pickle);
+ std::string data_path;
+ std::string name;
+ int64 internal_time;
+
+ if (pickle.ReadInt64(&iter, &info->parent_id) &&
+ pickle.ReadString(&iter, &data_path) &&
+ pickle.ReadString(&iter, &name) &&
+ pickle.ReadInt64(&iter, &internal_time)) {
+ info->data_path = fileapi::StringToFilePath(data_path);
+ info->name = fileapi::StringToFilePath(name).value();
+ info->modification_time = base::Time::FromInternalValue(internal_time);
+ return true;
+ }
+ LOG(ERROR) << "Pickle could not be digested!";
+ return false;
+}
+
+const base::FilePath::CharType kDirectoryDatabaseName[] =
+ FILE_PATH_LITERAL("Paths");
+const char kChildLookupPrefix[] = "CHILD_OF:";
+const char kChildLookupSeparator[] = ":";
+const char kLastFileIdKey[] = "LAST_FILE_ID";
+const char kLastIntegerKey[] = "LAST_INTEGER";
+const int64 kMinimumReportIntervalHours = 1;
+const char kInitStatusHistogramLabel[] = "FileSystem.DirectoryDatabaseInit";
+const char kDatabaseRepairHistogramLabel[] =
+ "FileSystem.DirectoryDatabaseRepair";
+
+enum InitStatus {
+ INIT_STATUS_OK = 0,
+ INIT_STATUS_CORRUPTION,
+ INIT_STATUS_IO_ERROR,
+ INIT_STATUS_UNKNOWN_ERROR,
+ INIT_STATUS_MAX
+};
+
+enum RepairResult {
+ DB_REPAIR_SUCCEEDED = 0,
+ DB_REPAIR_FAILED,
+ DB_REPAIR_MAX
+};
+
+std::string GetChildLookupKey(
+ fileapi::SandboxDirectoryDatabase::FileId parent_id,
+ const base::FilePath::StringType& child_name) {
+ std::string name;
+ name = fileapi::FilePathToString(base::FilePath(child_name));
+ return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
+ std::string(kChildLookupSeparator) + name;
+}
+
+std::string GetChildListingKeyPrefix(
+ fileapi::SandboxDirectoryDatabase::FileId parent_id) {
+ return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
+ std::string(kChildLookupSeparator);
+}
+
+const char* LastFileIdKey() {
+ return kLastFileIdKey;
+}
+
+const char* LastIntegerKey() {
+ return kLastIntegerKey;
+}
+
+std::string GetFileLookupKey(
+ fileapi::SandboxDirectoryDatabase::FileId file_id) {
+ return base::Int64ToString(file_id);
+}
+
+// Assumptions:
+// - Any database entry is one of:
+// - ("CHILD_OF:|parent_id|:<name>", "|file_id|"),
+// - ("LAST_FILE_ID", "|last_file_id|"),
+// - ("LAST_INTEGER", "|last_integer|"),
+// - ("|file_id|", "pickled FileInfo")
+// where FileInfo has |parent_id|, |data_path|, |name| and
+// |modification_time|,
+// Constraints:
+// - Each file in the database has unique backing file.
+// - Each file in |filesystem_data_directory_| has a database entry.
+// - Directory structure is tree, i.e. connected and acyclic.
+class DatabaseCheckHelper {
+ public:
+ typedef fileapi::SandboxDirectoryDatabase::FileId FileId;
+ typedef fileapi::SandboxDirectoryDatabase::FileInfo FileInfo;
+
+ DatabaseCheckHelper(fileapi::SandboxDirectoryDatabase* dir_db,
+ leveldb::DB* db,
+ const base::FilePath& path);
+
+ bool IsFileSystemConsistent() {
+ return IsDatabaseEmpty() ||
+ (ScanDatabase() && ScanDirectory() && ScanHierarchy());
+ }
+
+ private:
+ bool IsDatabaseEmpty();
+ // These 3 methods need to be called in the order. Each method requires its
+ // previous method finished successfully. They also require the database is
+ // not empty.
+ bool ScanDatabase();
+ bool ScanDirectory();
+ bool ScanHierarchy();
+
+ fileapi::SandboxDirectoryDatabase* dir_db_;
+ leveldb::DB* db_;
+ base::FilePath path_;
+
+ std::set<base::FilePath> files_in_db_;
+
+ size_t num_directories_in_db_;
+ size_t num_files_in_db_;
+ size_t num_hierarchy_links_in_db_;
+
+ FileId last_file_id_;
+ FileId last_integer_;
+};
+
+DatabaseCheckHelper::DatabaseCheckHelper(
+ fileapi::SandboxDirectoryDatabase* dir_db,
+ leveldb::DB* db,
+ const base::FilePath& path)
+ : dir_db_(dir_db), db_(db), path_(path),
+ num_directories_in_db_(0),
+ num_files_in_db_(0),
+ num_hierarchy_links_in_db_(0),
+ last_file_id_(-1), last_integer_(-1) {
+ DCHECK(dir_db_);
+ DCHECK(db_);
+ DCHECK(!path_.empty() && base::DirectoryExists(path_));
+}
+
+bool DatabaseCheckHelper::IsDatabaseEmpty() {
+ scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
+ itr->SeekToFirst();
+ return !itr->Valid();
+}
+
+bool DatabaseCheckHelper::ScanDatabase() {
+ // Scans all database entries sequentially to verify each of them has unique
+ // backing file.
+ int64 max_file_id = -1;
+ std::set<FileId> file_ids;
+
+ scoped_ptr<leveldb::Iterator> itr(db_->NewIterator(leveldb::ReadOptions()));
+ for (itr->SeekToFirst(); itr->Valid(); itr->Next()) {
+ std::string key = itr->key().ToString();
+ if (StartsWithASCII(key, kChildLookupPrefix, true)) {
+ // key: "CHILD_OF:<parent_id>:<name>"
+ // value: "<child_id>"
+ ++num_hierarchy_links_in_db_;
+ } else if (key == kLastFileIdKey) {
+ // key: "LAST_FILE_ID"
+ // value: "<last_file_id>"
+ if (last_file_id_ >= 0 ||
+ !base::StringToInt64(itr->value().ToString(), &last_file_id_))
+ return false;
+
+ if (last_file_id_ < 0)
+ return false;
+ } else if (key == kLastIntegerKey) {
+ // key: "LAST_INTEGER"
+ // value: "<last_integer>"
+ if (last_integer_ >= 0 ||
+ !base::StringToInt64(itr->value().ToString(), &last_integer_))
+ return false;
+ } else {
+ // key: "<entry_id>"
+ // value: "<pickled FileInfo>"
+ FileInfo file_info;
+ if (!FileInfoFromPickle(
+ Pickle(itr->value().data(), itr->value().size()), &file_info))
+ return false;
+
+ FileId file_id = -1;
+ if (!base::StringToInt64(key, &file_id) || file_id < 0)
+ return false;
+
+ if (max_file_id < file_id)
+ max_file_id = file_id;
+ if (!file_ids.insert(file_id).second)
+ return false;
+
+ if (file_info.is_directory()) {
+ ++num_directories_in_db_;
+ DCHECK(file_info.data_path.empty());
+ } else {
+ // Ensure any pair of file entry don't share their data_path.
+ if (!files_in_db_.insert(file_info.data_path).second)
+ return false;
+
+ // Ensure the backing file exists as a normal file.
+ base::PlatformFileInfo platform_file_info;
+ if (!file_util::GetFileInfo(
+ path_.Append(file_info.data_path), &platform_file_info) ||
+ platform_file_info.is_directory ||
+ platform_file_info.is_symbolic_link) {
+ // leveldb::Iterator iterates a snapshot of the database.
+ // So even after RemoveFileInfo() call, we'll visit hierarchy link
+ // from |parent_id| to |file_id|.
+ if (!dir_db_->RemoveFileInfo(file_id))
+ return false;
+ --num_hierarchy_links_in_db_;
+ files_in_db_.erase(file_info.data_path);
+ } else {
+ ++num_files_in_db_;
+ }
+ }
+ }
+ }
+
+ // TODO(tzik): Add constraint for |last_integer_| to avoid possible
+ // data path confliction on ObfuscatedFileUtil.
+ return max_file_id <= last_file_id_;
+}
+
+bool DatabaseCheckHelper::ScanDirectory() {
+ // TODO(kinuko): Scans all local file system entries to verify each of them
+ // has a database entry.
+ const base::FilePath kExcludes[] = {
+ base::FilePath(kDirectoryDatabaseName),
+ base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName),
+ };
+
+ // Any path in |pending_directories| is relative to |path_|.
+ std::stack<base::FilePath> pending_directories;
+ pending_directories.push(base::FilePath());
+
+ while (!pending_directories.empty()) {
+ base::FilePath dir_path = pending_directories.top();
+ pending_directories.pop();
+
+ base::FileEnumerator file_enum(
+ dir_path.empty() ? path_ : path_.Append(dir_path),
+ false /* not recursive */,
+ base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES);
+
+ base::FilePath absolute_file_path;
+ while (!(absolute_file_path = file_enum.Next()).empty()) {
+ base::FileEnumerator::FileInfo find_info = file_enum.GetInfo();
+
+ base::FilePath relative_file_path;
+ if (!path_.AppendRelativePath(absolute_file_path, &relative_file_path))
+ return false;
+
+ if (std::find(kExcludes, kExcludes + arraysize(kExcludes),
+ relative_file_path) != kExcludes + arraysize(kExcludes))
+ continue;
+
+ if (find_info.IsDirectory()) {
+ pending_directories.push(relative_file_path);
+ continue;
+ }
+
+ // Check if the file has a database entry.
+ std::set<base::FilePath>::iterator itr =
+ files_in_db_.find(relative_file_path);
+ if (itr == files_in_db_.end()) {
+ if (!base::DeleteFile(absolute_file_path, false))
+ return false;
+ } else {
+ files_in_db_.erase(itr);
+ }
+ }
+ }
+
+ return files_in_db_.empty();
+}
+
+bool DatabaseCheckHelper::ScanHierarchy() {
+ size_t visited_directories = 0;
+ size_t visited_files = 0;
+ size_t visited_links = 0;
+
+ std::stack<FileId> directories;
+ directories.push(0);
+
+ // Check if the root directory exists as a directory.
+ FileInfo file_info;
+ if (!dir_db_->GetFileInfo(0, &file_info))
+ return false;
+ if (file_info.parent_id != 0 ||
+ !file_info.is_directory())
+ return false;
+
+ while (!directories.empty()) {
+ ++visited_directories;
+ FileId dir_id = directories.top();
+ directories.pop();
+
+ std::vector<FileId> children;
+ if (!dir_db_->ListChildren(dir_id, &children))
+ return false;
+ for (std::vector<FileId>::iterator itr = children.begin();
+ itr != children.end();
+ ++itr) {
+ // Any directory must not have root directory as child.
+ if (!*itr)
+ return false;
+
+ // Check if the child knows the parent as its parent.
+ FileInfo file_info;
+ if (!dir_db_->GetFileInfo(*itr, &file_info))
+ return false;
+ if (file_info.parent_id != dir_id)
+ return false;
+
+ // Check if the parent knows the name of its child correctly.
+ FileId file_id;
+ if (!dir_db_->GetChildWithName(dir_id, file_info.name, &file_id) ||
+ file_id != *itr)
+ return false;
+
+ if (file_info.is_directory())
+ directories.push(*itr);
+ else
+ ++visited_files;
+ ++visited_links;
+ }
+ }
+
+ // Check if we've visited all database entries.
+ return num_directories_in_db_ == visited_directories &&
+ num_files_in_db_ == visited_files &&
+ num_hierarchy_links_in_db_ == visited_links;
+}
+
+// Returns true if the given |data_path| contains no parent references ("..")
+// and does not refer to special system files.
+// This is called in GetFileInfo, AddFileInfo and UpdateFileInfo to
+// ensure we're only dealing with valid data paths.
+bool VerifyDataPath(const base::FilePath& data_path) {
+ // |data_path| should not contain any ".." and should be a relative path
+ // (to the filesystem_data_directory_).
+ if (data_path.ReferencesParent() || data_path.IsAbsolute())
+ return false;
+ // See if it's not pointing to the special system paths.
+ const base::FilePath kExcludes[] = {
+ base::FilePath(kDirectoryDatabaseName),
+ base::FilePath(fileapi::FileSystemUsageCache::kUsageFileName),
+ };
+ for (size_t i = 0; i < arraysize(kExcludes); ++i) {
+ if (data_path == kExcludes[i] || kExcludes[i].IsParent(data_path))
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+namespace fileapi {
+
+SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
+}
+
+SandboxDirectoryDatabase::FileInfo::~FileInfo() {
+}
+
+SandboxDirectoryDatabase::SandboxDirectoryDatabase(
+ const base::FilePath& filesystem_data_directory)
+ : filesystem_data_directory_(filesystem_data_directory) {
+}
+
+SandboxDirectoryDatabase::~SandboxDirectoryDatabase() {
+}
+
+bool SandboxDirectoryDatabase::GetChildWithName(
+ FileId parent_id,
+ const base::FilePath::StringType& name,
+ FileId* child_id) {
+ if (!Init(REPAIR_ON_CORRUPTION))
+ return false;
+ DCHECK(child_id);
+ std::string child_key = GetChildLookupKey(parent_id, name);
+ std::string child_id_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
+ if (status.IsNotFound())
+ return false;
+ if (status.ok()) {
+ if (!base::StringToInt64(child_id_string, child_id)) {
+ LOG(ERROR) << "Hit database corruption!";
+ return false;
+ }
+ return true;
+ }
+ HandleError(FROM_HERE, status);
+ return false;
+}
+
+bool SandboxDirectoryDatabase::GetFileWithPath(
+ const base::FilePath& path, FileId* file_id) {
+ std::vector<base::FilePath::StringType> components;
+ VirtualPath::GetComponents(path, &components);
+ FileId local_id = 0;
+ std::vector<base::FilePath::StringType>::iterator iter;
+ for (iter = components.begin(); iter != components.end(); ++iter) {
+ base::FilePath::StringType name;
+ name = *iter;
+ if (name == FILE_PATH_LITERAL("/"))
+ continue;
+ if (!GetChildWithName(local_id, name, &local_id))
+ return false;
+ }
+ *file_id = local_id;
+ return true;
+}
+
+bool SandboxDirectoryDatabase::ListChildren(
+ FileId parent_id, std::vector<FileId>* children) {
+ // Check to add later: fail if parent is a file, at least in debug builds.
+ if (!Init(REPAIR_ON_CORRUPTION))
+ return false;
+ DCHECK(children);
+ std::string child_key_prefix = GetChildListingKeyPrefix(parent_id);
+
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
+ iter->Seek(child_key_prefix);
+ children->clear();
+ while (iter->Valid() &&
+ StartsWithASCII(iter->key().ToString(), child_key_prefix, true)) {
+ std::string child_id_string = iter->value().ToString();
+ FileId child_id;
+ if (!base::StringToInt64(child_id_string, &child_id)) {
+ LOG(ERROR) << "Hit database corruption!";
+ return false;
+ }
+ children->push_back(child_id);
+ iter->Next();
+ }
+ return true;
+}
+
+bool SandboxDirectoryDatabase::GetFileInfo(FileId file_id, FileInfo* info) {
+ if (!Init(REPAIR_ON_CORRUPTION))
+ return false;
+ DCHECK(info);
+ std::string file_key = GetFileLookupKey(file_id);
+ std::string file_data_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), file_key, &file_data_string);
+ if (status.ok()) {
+ bool success = FileInfoFromPickle(
+ Pickle(file_data_string.data(), file_data_string.length()), info);
+ if (!success)
+ return false;
+ if (!VerifyDataPath(info->data_path)) {
+ LOG(ERROR) << "Resolved data path is invalid: "
+ << info->data_path.value();
+ return false;
+ }
+ return true;
+ }
+ // Special-case the root, for databases that haven't been initialized yet.
+ // Without this, a query for the root's file info, made before creating the
+ // first file in the database, will fail and confuse callers.
+ if (status.IsNotFound() && !file_id) {
+ info->name = base::FilePath::StringType();
+ info->data_path = base::FilePath();
+ info->modification_time = base::Time::Now();
+ info->parent_id = 0;
+ return true;
+ }
+ HandleError(FROM_HERE, status);
+ return false;
+}
+
+bool SandboxDirectoryDatabase::AddFileInfo(
+ const FileInfo& info, FileId* file_id) {
+ if (!Init(REPAIR_ON_CORRUPTION))
+ return false;
+ DCHECK(file_id);
+ std::string child_key = GetChildLookupKey(info.parent_id, info.name);
+ std::string child_id_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), child_key, &child_id_string);
+ if (status.ok()) {
+ LOG(ERROR) << "File exists already!";
+ return false;
+ }
+ if (!status.IsNotFound()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+
+ if (!VerifyIsDirectory(info.parent_id))
+ return false;
+
+ // This would be a fine place to limit the number of files in a directory, if
+ // we decide to add that restriction.
+
+ FileId temp_id;
+ if (!GetLastFileId(&temp_id))
+ return false;
+ ++temp_id;
+
+ leveldb::WriteBatch batch;
+ if (!AddFileInfoHelper(info, temp_id, &batch))
+ return false;
+
+ batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
+ status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ *file_id = temp_id;
+ return true;
+}
+
+bool SandboxDirectoryDatabase::RemoveFileInfo(FileId file_id) {
+ if (!Init(REPAIR_ON_CORRUPTION))
+ return false;
+ leveldb::WriteBatch batch;
+ if (!RemoveFileInfoHelper(file_id, &batch))
+ return false;
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ return true;
+}
+
+bool SandboxDirectoryDatabase::UpdateFileInfo(
+ FileId file_id, const FileInfo& new_info) {
+ // TODO(ericu): We should also check to see that this doesn't create a loop,
+ // but perhaps only in a debug build.
+ if (!Init(REPAIR_ON_CORRUPTION))
+ return false;
+ DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
+ FileInfo old_info;
+ if (!GetFileInfo(file_id, &old_info))
+ return false;
+ if (old_info.parent_id != new_info.parent_id &&
+ !VerifyIsDirectory(new_info.parent_id))
+ return false;
+ if (old_info.parent_id != new_info.parent_id ||
+ old_info.name != new_info.name) {
+ // Check for name clashes.
+ FileId temp_id;
+ if (GetChildWithName(new_info.parent_id, new_info.name, &temp_id)) {
+ LOG(ERROR) << "Name collision on move.";
+ return false;
+ }
+ }
+ leveldb::WriteBatch batch;
+ if (!RemoveFileInfoHelper(file_id, &batch) ||
+ !AddFileInfoHelper(new_info, file_id, &batch))
+ return false;
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ return true;
+}
+
+bool SandboxDirectoryDatabase::UpdateModificationTime(
+ FileId file_id, const base::Time& modification_time) {
+ FileInfo info;
+ if (!GetFileInfo(file_id, &info))
+ return false;
+ info.modification_time = modification_time;
+ Pickle pickle;
+ if (!PickleFromFileInfo(info, &pickle))
+ return false;
+ leveldb::Status status = db_->Put(
+ leveldb::WriteOptions(),
+ GetFileLookupKey(file_id),
+ leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
+ pickle.size()));
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ return true;
+}
+
+bool SandboxDirectoryDatabase::OverwritingMoveFile(
+ FileId src_file_id, FileId dest_file_id) {
+ FileInfo src_file_info;
+ FileInfo dest_file_info;
+
+ if (!GetFileInfo(src_file_id, &src_file_info))
+ return false;
+ if (!GetFileInfo(dest_file_id, &dest_file_info))
+ return false;
+ if (src_file_info.is_directory() || dest_file_info.is_directory())
+ return false;
+ leveldb::WriteBatch batch;
+ // This is the only field that really gets moved over; if you add fields to
+ // FileInfo, e.g. ctime, they might need to be copied here.
+ dest_file_info.data_path = src_file_info.data_path;
+ if (!RemoveFileInfoHelper(src_file_id, &batch))
+ return false;
+ Pickle pickle;
+ if (!PickleFromFileInfo(dest_file_info, &pickle))
+ return false;
+ batch.Put(
+ GetFileLookupKey(dest_file_id),
+ leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
+ pickle.size()));
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ return true;
+}
+
+bool SandboxDirectoryDatabase::GetNextInteger(int64* next) {
+ if (!Init(REPAIR_ON_CORRUPTION))
+ return false;
+ DCHECK(next);
+ std::string int_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), LastIntegerKey(), &int_string);
+ if (status.ok()) {
+ int64 temp;
+ if (!base::StringToInt64(int_string, &temp)) {
+ LOG(ERROR) << "Hit database corruption!";
+ return false;
+ }
+ ++temp;
+ status = db_->Put(leveldb::WriteOptions(), LastIntegerKey(),
+ base::Int64ToString(temp));
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ *next = temp;
+ return true;
+ }
+ if (!status.IsNotFound()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ // The database must not yet exist; initialize it.
+ if (!StoreDefaultValues())
+ return false;
+
+ return GetNextInteger(next);
+}
+
+// static
+bool SandboxDirectoryDatabase::DestroyDatabase(const base::FilePath& path) {
+ std::string name = FilePathToString(path.Append(kDirectoryDatabaseName));
+ leveldb::Status status = leveldb::DestroyDB(name, leveldb::Options());
+ if (status.ok())
+ return true;
+ LOG(WARNING) << "Failed to destroy a database with status " <<
+ status.ToString();
+ return false;
+}
+
+bool SandboxDirectoryDatabase::Init(RecoveryOption recovery_option) {
+ if (db_)
+ return true;
+
+ std::string path =
+ FilePathToString(filesystem_data_directory_.Append(
+ kDirectoryDatabaseName));
+ leveldb::Options options;
+ options.max_open_files = 0; // Use minimum.
+ options.create_if_missing = true;
+ leveldb::DB* db;
+ leveldb::Status status = leveldb::DB::Open(options, path, &db);
+ ReportInitStatus(status);
+ if (status.ok()) {
+ db_.reset(db);
+ return true;
+ }
+ HandleError(FROM_HERE, status);
+
+ // Corruption due to missing necessary MANIFEST-* file causes IOError instead
+ // of Corruption error.
+ // Try to repair database even when IOError case.
+ if (!status.IsCorruption() && !status.IsIOError())
+ return false;
+
+ switch (recovery_option) {
+ case FAIL_ON_CORRUPTION:
+ return false;
+ case REPAIR_ON_CORRUPTION:
+ LOG(WARNING) << "Corrupted SandboxDirectoryDatabase detected."
+ << " Attempting to repair.";
+ if (RepairDatabase(path)) {
+ UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
+ DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
+ return true;
+ }
+ UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
+ DB_REPAIR_FAILED, DB_REPAIR_MAX);
+ LOG(WARNING) << "Failed to repair SandboxDirectoryDatabase.";
+ // fall through
+ case DELETE_ON_CORRUPTION:
+ LOG(WARNING) << "Clearing SandboxDirectoryDatabase.";
+ if (!base::DeleteFile(filesystem_data_directory_, true))
+ return false;
+ if (!file_util::CreateDirectory(filesystem_data_directory_))
+ return false;
+ return Init(FAIL_ON_CORRUPTION);
+ }
+
+ NOTREACHED();
+ return false;
+}
+
+bool SandboxDirectoryDatabase::RepairDatabase(const std::string& db_path) {
+ DCHECK(!db_.get());
+ leveldb::Options options;
+ options.max_open_files = 0; // Use minimum.
+ if (!leveldb::RepairDB(db_path, options).ok())
+ return false;
+ if (!Init(FAIL_ON_CORRUPTION))
+ return false;
+ if (IsFileSystemConsistent())
+ return true;
+ db_.reset();
+ return false;
+}
+
+bool SandboxDirectoryDatabase::IsFileSystemConsistent() {
+ if (!Init(FAIL_ON_CORRUPTION))
+ return false;
+ DatabaseCheckHelper helper(this, db_.get(), filesystem_data_directory_);
+ return helper.IsFileSystemConsistent();
+}
+
+void SandboxDirectoryDatabase::ReportInitStatus(
+ const leveldb::Status& status) {
+ base::Time now = base::Time::Now();
+ const base::TimeDelta minimum_interval =
+ base::TimeDelta::FromHours(kMinimumReportIntervalHours);
+ if (last_reported_time_ + minimum_interval >= now)
+ return;
+ last_reported_time_ = now;
+
+ if (status.ok()) {
+ UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
+ INIT_STATUS_OK, INIT_STATUS_MAX);
+ } else if (status.IsCorruption()) {
+ UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
+ INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
+ } else if (status.IsIOError()) {
+ UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
+ INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
+ INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
+ }
+}
+
+bool SandboxDirectoryDatabase::StoreDefaultValues() {
+ // Verify that this is a totally new database, and initialize it.
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
+ iter->SeekToFirst();
+ if (iter->Valid()) { // DB was not empty--we shouldn't have been called.
+ LOG(ERROR) << "File system origin database is corrupt!";
+ return false;
+ }
+ // This is always the first write into the database. If we ever add a
+ // version number, it should go in this transaction too.
+ FileInfo root;
+ root.parent_id = 0;
+ root.modification_time = base::Time::Now();
+ leveldb::WriteBatch batch;
+ if (!AddFileInfoHelper(root, 0, &batch))
+ return false;
+ batch.Put(LastFileIdKey(), base::Int64ToString(0));
+ batch.Put(LastIntegerKey(), base::Int64ToString(-1));
+ leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ return true;
+}
+
+bool SandboxDirectoryDatabase::GetLastFileId(FileId* file_id) {
+ if (!Init(REPAIR_ON_CORRUPTION))
+ return false;
+ DCHECK(file_id);
+ std::string id_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), LastFileIdKey(), &id_string);
+ if (status.ok()) {
+ if (!base::StringToInt64(id_string, file_id)) {
+ LOG(ERROR) << "Hit database corruption!";
+ return false;
+ }
+ return true;
+ }
+ if (!status.IsNotFound()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ // The database must not yet exist; initialize it.
+ if (!StoreDefaultValues())
+ return false;
+ *file_id = 0;
+ return true;
+}
+
+bool SandboxDirectoryDatabase::VerifyIsDirectory(FileId file_id) {
+ FileInfo info;
+ if (!file_id)
+ return true; // The root is a directory.
+ if (!GetFileInfo(file_id, &info))
+ return false;
+ if (!info.is_directory()) {
+ LOG(ERROR) << "New parent directory is a file!";
+ return false;
+ }
+ return true;
+}
+
+// This does very few safety checks!
+bool SandboxDirectoryDatabase::AddFileInfoHelper(
+ const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch) {
+ if (!VerifyDataPath(info.data_path)) {
+ LOG(ERROR) << "Invalid data path is given: " << info.data_path.value();
+ return false;
+ }
+ std::string id_string = GetFileLookupKey(file_id);
+ if (!file_id) {
+ // The root directory doesn't need to be looked up by path from its parent.
+ DCHECK(!info.parent_id);
+ DCHECK(info.data_path.empty());
+ } else {
+ std::string child_key = GetChildLookupKey(info.parent_id, info.name);
+ batch->Put(child_key, id_string);
+ }
+ Pickle pickle;
+ if (!PickleFromFileInfo(info, &pickle))
+ return false;
+ batch->Put(
+ id_string,
+ leveldb::Slice(reinterpret_cast<const char *>(pickle.data()),
+ pickle.size()));
+ return true;
+}
+
+// This does very few safety checks!
+bool SandboxDirectoryDatabase::RemoveFileInfoHelper(
+ FileId file_id, leveldb::WriteBatch* batch) {
+ DCHECK(file_id); // You can't remove the root, ever. Just delete the DB.
+ FileInfo info;
+ if (!GetFileInfo(file_id, &info))
+ return false;
+ if (info.data_path.empty()) { // It's a directory
+ std::vector<FileId> children;
+ // TODO(ericu): Make a faster is-the-directory-empty check.
+ if (!ListChildren(file_id, &children))
+ return false;
+ if (children.size()) {
+ LOG(ERROR) << "Can't remove a directory with children.";
+ return false;
+ }
+ }
+ batch->Delete(GetChildLookupKey(info.parent_id, info.name));
+ batch->Delete(GetFileLookupKey(file_id));
+ return true;
+}
+
+void SandboxDirectoryDatabase::HandleError(
+ const tracked_objects::Location& from_here,
+ const leveldb::Status& status) {
+ LOG(ERROR) << "SandboxDirectoryDatabase failed at: "
+ << from_here.ToString() << " with error: " << status.ToString();
+ db_.reset();
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_directory_database.h b/chromium/webkit/browser/fileapi/sandbox_directory_database.h
new file mode 100644
index 00000000000..4cbd43b6cbd
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_directory_database.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace tracked_objects {
+class Location;
+}
+
+namespace leveldb {
+class DB;
+class Status;
+class WriteBatch;
+}
+
+namespace fileapi {
+
+// This class WILL NOT protect you against producing directory loops, giving an
+// empty directory a backing data file, giving two files the same backing file,
+// or pointing to a nonexistent backing file. It does no file IO other than
+// that involved with talking to its underlying database. It does not create or
+// in any way touch real files; it only creates path entries in its database.
+
+// TODO(ericu): Safe mode, which does more checks such as the above on debug
+// builds.
+// TODO(ericu): Add a method that will give a unique filename for a data file.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxDirectoryDatabase {
+ public:
+ typedef int64 FileId;
+
+ struct WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE FileInfo {
+ FileInfo();
+ ~FileInfo();
+
+ bool is_directory() const {
+ return data_path.empty();
+ }
+
+ FileId parent_id;
+ base::FilePath data_path;
+ base::FilePath::StringType name;
+ // This modification time is valid only for directories, not files, as
+ // FileWriter will get the files out of sync.
+ // For files, look at the modification time of the underlying data_path.
+ base::Time modification_time;
+ };
+
+ explicit SandboxDirectoryDatabase(
+ const base::FilePath& filesystem_data_directory);
+ ~SandboxDirectoryDatabase();
+
+ bool GetChildWithName(
+ FileId parent_id,
+ const base::FilePath::StringType& name,
+ FileId* child_id);
+ bool GetFileWithPath(const base::FilePath& path, FileId* file_id);
+ // ListChildren will succeed, returning 0 children, if parent_id doesn't
+ // exist.
+ bool ListChildren(FileId parent_id, std::vector<FileId>* children);
+ bool GetFileInfo(FileId file_id, FileInfo* info);
+ bool AddFileInfo(const FileInfo& info, FileId* file_id);
+ bool RemoveFileInfo(FileId file_id);
+ // This does a full update of the FileInfo, and is what you'd use for moves
+ // and renames. If you just want to update the modification_time, use
+ // UpdateModificationTime.
+ bool UpdateFileInfo(FileId file_id, const FileInfo& info);
+ bool UpdateModificationTime(
+ FileId file_id, const base::Time& modification_time);
+ // This is used for an overwriting move of a file [not a directory] on top of
+ // another file [also not a directory]; we need to alter two files' info in a
+ // single transaction to avoid weird backing file references in the event of a
+ // partial failure.
+ bool OverwritingMoveFile(FileId src_file_id, FileId dest_file_id);
+
+ // This produces the series 0, 1, 2..., starting at 0 when the underlying
+ // filesystem is first created, and maintaining state across
+ // creation/destruction of SandboxDirectoryDatabase objects.
+ bool GetNextInteger(int64* next);
+
+ // Returns true if the database looks consistent with local filesystem.
+ bool IsFileSystemConsistent();
+
+ static bool DestroyDatabase(const base::FilePath& path);
+
+ private:
+ enum RecoveryOption {
+ DELETE_ON_CORRUPTION,
+ REPAIR_ON_CORRUPTION,
+ FAIL_ON_CORRUPTION,
+ };
+
+ friend class ObfuscatedFileUtil;
+ friend class SandboxDirectoryDatabaseTest;
+
+ bool Init(RecoveryOption recovery_option);
+ bool RepairDatabase(const std::string& db_path);
+ void ReportInitStatus(const leveldb::Status& status);
+ bool StoreDefaultValues();
+ bool GetLastFileId(FileId* file_id);
+ bool VerifyIsDirectory(FileId file_id);
+ bool AddFileInfoHelper(
+ const FileInfo& info, FileId file_id, leveldb::WriteBatch* batch);
+ bool RemoveFileInfoHelper(FileId file_id, leveldb::WriteBatch* batch);
+ void HandleError(const tracked_objects::Location& from_here,
+ const leveldb::Status& status);
+
+ const base::FilePath filesystem_data_directory_;
+ scoped_ptr<leveldb::DB> db_;
+ base::Time last_reported_time_;
+ DISALLOW_COPY_AND_ASSIGN(SandboxDirectoryDatabase);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_directory_database_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_directory_database_unittest.cc
new file mode 100644
index 00000000000..593ead96efb
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_directory_database_unittest.cc
@@ -0,0 +1,678 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_directory_database.h"
+
+#include <math.h>
+#include <limits>
+
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "webkit/browser/fileapi/sandbox_database_test_helper.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+#define FPL(x) FILE_PATH_LITERAL(x)
+
+namespace fileapi {
+
+namespace {
+const base::FilePath::CharType kDirectoryDatabaseName[] = FPL("Paths");
+}
+
+class SandboxDirectoryDatabaseTest : public testing::Test {
+ public:
+ typedef SandboxDirectoryDatabase::FileId FileId;
+ typedef SandboxDirectoryDatabase::FileInfo FileInfo;
+
+ SandboxDirectoryDatabaseTest() {
+ EXPECT_TRUE(base_.CreateUniqueTempDir());
+ InitDatabase();
+ }
+
+ SandboxDirectoryDatabase* db() {
+ return db_.get();
+ }
+
+ void InitDatabase() {
+ // Call CloseDatabase() to avoid having multiple database instances for
+ // single directory at once.
+ CloseDatabase();
+ db_.reset(new SandboxDirectoryDatabase(path()));
+ }
+
+ void CloseDatabase() {
+ db_.reset();
+ }
+
+ bool AddFileInfo(FileId parent_id, const base::FilePath::StringType& name) {
+ FileId file_id;
+ FileInfo info;
+ info.parent_id = parent_id;
+ info.name = name;
+ return db_->AddFileInfo(info, &file_id);
+ }
+
+ void CreateDirectory(FileId parent_id,
+ const base::FilePath::StringType& name,
+ FileId* file_id_out) {
+ FileInfo info;
+ info.parent_id = parent_id;
+ info.name = name;
+ ASSERT_TRUE(db_->AddFileInfo(info, file_id_out));
+ }
+
+ void CreateFile(FileId parent_id,
+ const base::FilePath::StringType& name,
+ const base::FilePath::StringType& data_path,
+ FileId* file_id_out) {
+ FileId file_id;
+
+ FileInfo info;
+ info.parent_id = parent_id;
+ info.name = name;
+ info.data_path = base::FilePath(data_path).NormalizePathSeparators();
+ ASSERT_TRUE(db_->AddFileInfo(info, &file_id));
+
+ base::FilePath local_path = path().Append(data_path);
+ if (!base::DirectoryExists(local_path.DirName()))
+ ASSERT_TRUE(file_util::CreateDirectory(local_path.DirName()));
+
+ bool created = false;
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::PlatformFile file = base::CreatePlatformFile(
+ local_path,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &created, &error);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+ ASSERT_TRUE(created);
+ ASSERT_TRUE(base::ClosePlatformFile(file));
+
+ if (file_id_out)
+ *file_id_out = file_id;
+ }
+
+ void ClearDatabaseAndDirectory() {
+ db_.reset();
+ ASSERT_TRUE(base::DeleteFile(path(), true /* recursive */));
+ ASSERT_TRUE(file_util::CreateDirectory(path()));
+ db_.reset(new SandboxDirectoryDatabase(path()));
+ }
+
+ bool RepairDatabase() {
+ return db()->RepairDatabase(
+ FilePathToString(path().Append(kDirectoryDatabaseName)));
+ }
+
+ const base::FilePath& path() {
+ return base_.path();
+ }
+
+ // Makes link from |parent_id| to |child_id| with |name|.
+ void MakeHierarchyLink(FileId parent_id,
+ FileId child_id,
+ const base::FilePath::StringType& name) {
+ ASSERT_TRUE(db()->db_->Put(
+ leveldb::WriteOptions(),
+ "CHILD_OF:" + base::Int64ToString(parent_id) + ":" +
+ FilePathToString(base::FilePath(name)),
+ base::Int64ToString(child_id)).ok());
+ }
+
+ // Deletes link from parent of |file_id| to |file_id|.
+ void DeleteHierarchyLink(FileId file_id) {
+ FileInfo file_info;
+ ASSERT_TRUE(db()->GetFileInfo(file_id, &file_info));
+ ASSERT_TRUE(db()->db_->Delete(
+ leveldb::WriteOptions(),
+ "CHILD_OF:" + base::Int64ToString(file_info.parent_id) + ":" +
+ FilePathToString(base::FilePath(file_info.name))).ok());
+ }
+
+ protected:
+ // Common temp base for nondestructive uses.
+ base::ScopedTempDir base_;
+ scoped_ptr<SandboxDirectoryDatabase> db_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxDirectoryDatabaseTest);
+};
+
+TEST_F(SandboxDirectoryDatabaseTest, TestMissingFileGetInfo) {
+ FileId file_id = 888;
+ FileInfo info;
+ EXPECT_FALSE(db()->GetFileInfo(file_id, &info));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestGetRootFileInfoBeforeCreate) {
+ FileId file_id = 0;
+ FileInfo info;
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info));
+ EXPECT_EQ(0, info.parent_id);
+ EXPECT_TRUE(info.name.empty());
+ EXPECT_TRUE(info.data_path.empty());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestMissingParentAddFileInfo) {
+ FileId parent_id = 7;
+ EXPECT_FALSE(AddFileInfo(parent_id, FILE_PATH_LITERAL("foo")));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestAddNameClash) {
+ FileInfo info;
+ FileId file_id;
+ info.parent_id = 0;
+ info.name = FILE_PATH_LITERAL("dir 0");
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id));
+
+ // Check for name clash in the root directory.
+ base::FilePath::StringType name = info.name;
+ EXPECT_FALSE(AddFileInfo(0, name));
+ name = FILE_PATH_LITERAL("dir 1");
+ EXPECT_TRUE(AddFileInfo(0, name));
+
+ name = FILE_PATH_LITERAL("subdir 0");
+ EXPECT_TRUE(AddFileInfo(file_id, name));
+
+ // Check for name clash in a subdirectory.
+ EXPECT_FALSE(AddFileInfo(file_id, name));
+ name = FILE_PATH_LITERAL("subdir 1");
+ EXPECT_TRUE(AddFileInfo(file_id, name));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRenameNoMoveNameClash) {
+ FileInfo info;
+ FileId file_id0;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ base::FilePath::StringType name2 = FILE_PATH_LITERAL("bas");
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ EXPECT_TRUE(AddFileInfo(0, name1));
+ info.name = name1;
+ EXPECT_FALSE(db()->UpdateFileInfo(file_id0, info));
+ info.name = name2;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id0, info));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestMoveSameNameNameClash) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ info.parent_id = 0;
+ EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info));
+ info.name = name1;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestMoveRenameNameClash) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ base::FilePath::StringType name2 = FILE_PATH_LITERAL("bas");
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ info.name = name1;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info));
+ info.name = name1;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
+ // Also test a successful move+rename.
+ info.parent_id = file_id0;
+ info.name = name2;
+ EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRemoveWithChildren) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ info.parent_id = 0;
+ info.name = FILE_PATH_LITERAL("foo");
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ EXPECT_FALSE(db()->RemoveFileInfo(file_id0));
+ EXPECT_TRUE(db()->RemoveFileInfo(file_id1));
+ EXPECT_TRUE(db()->RemoveFileInfo(file_id0));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestGetChildWithName) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ info.name = name1;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ EXPECT_NE(file_id0, file_id1);
+
+ FileId check_file_id;
+ EXPECT_FALSE(db()->GetChildWithName(0, name1, &check_file_id));
+ EXPECT_TRUE(db()->GetChildWithName(0, name0, &check_file_id));
+ EXPECT_EQ(file_id0, check_file_id);
+ EXPECT_FALSE(db()->GetChildWithName(file_id0, name0, &check_file_id));
+ EXPECT_TRUE(db()->GetChildWithName(file_id0, name1, &check_file_id));
+ EXPECT_EQ(file_id1, check_file_id);
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestGetFileWithPath) {
+ FileInfo info;
+ FileId file_id0;
+ FileId file_id1;
+ FileId file_id2;
+ base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
+ base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
+ base::FilePath::StringType name2 = FILE_PATH_LITERAL("dog");
+
+ info.parent_id = 0;
+ info.name = name0;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ info.parent_id = file_id0;
+ info.name = name1;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ EXPECT_NE(file_id0, file_id1);
+ info.parent_id = file_id1;
+ info.name = name2;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id2));
+ EXPECT_NE(file_id0, file_id2);
+ EXPECT_NE(file_id1, file_id2);
+
+ FileId check_file_id;
+ base::FilePath path = base::FilePath(name0);
+ EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id));
+ EXPECT_EQ(file_id0, check_file_id);
+
+ path = path.Append(name1);
+ EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id));
+ EXPECT_EQ(file_id1, check_file_id);
+
+ path = path.Append(name2);
+ EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id));
+ EXPECT_EQ(file_id2, check_file_id);
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestListChildren) {
+ // No children in the root.
+ std::vector<FileId> children;
+ EXPECT_TRUE(db()->ListChildren(0, &children));
+ EXPECT_TRUE(children.empty());
+
+ // One child in the root.
+ FileId file_id0;
+ FileInfo info;
+ info.parent_id = 0;
+ info.name = FILE_PATH_LITERAL("foo");
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id0));
+ EXPECT_TRUE(db()->ListChildren(0, &children));
+ EXPECT_EQ(children.size(), 1UL);
+ EXPECT_EQ(children[0], file_id0);
+
+ // Two children in the root.
+ FileId file_id1;
+ info.name = FILE_PATH_LITERAL("bar");
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id1));
+ EXPECT_TRUE(db()->ListChildren(0, &children));
+ EXPECT_EQ(2UL, children.size());
+ if (children[0] == file_id0) {
+ EXPECT_EQ(children[1], file_id1);
+ } else {
+ EXPECT_EQ(children[1], file_id0);
+ EXPECT_EQ(children[0], file_id1);
+ }
+
+ // No children in a subdirectory.
+ EXPECT_TRUE(db()->ListChildren(file_id0, &children));
+ EXPECT_TRUE(children.empty());
+
+ // One child in a subdirectory.
+ info.parent_id = file_id0;
+ info.name = FILE_PATH_LITERAL("foo");
+ FileId file_id2;
+ FileId file_id3;
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id2));
+ EXPECT_TRUE(db()->ListChildren(file_id0, &children));
+ EXPECT_EQ(1UL, children.size());
+ EXPECT_EQ(children[0], file_id2);
+
+ // Two children in a subdirectory.
+ info.name = FILE_PATH_LITERAL("bar");
+ EXPECT_TRUE(db()->AddFileInfo(info, &file_id3));
+ EXPECT_TRUE(db()->ListChildren(file_id0, &children));
+ EXPECT_EQ(2UL, children.size());
+ if (children[0] == file_id2) {
+ EXPECT_EQ(children[1], file_id3);
+ } else {
+ EXPECT_EQ(children[1], file_id2);
+ EXPECT_EQ(children[0], file_id3);
+ }
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestUpdateModificationTime) {
+ FileInfo info0;
+ FileId file_id;
+ info0.parent_id = 0;
+ info0.name = FILE_PATH_LITERAL("name");
+ info0.data_path = base::FilePath(FILE_PATH_LITERAL("fake path"));
+ info0.modification_time = base::Time::Now();
+ EXPECT_TRUE(db()->AddFileInfo(info0, &file_id));
+ FileInfo info1;
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
+ EXPECT_EQ(info0.name, info1.name);
+ EXPECT_EQ(info0.parent_id, info1.parent_id);
+ EXPECT_EQ(info0.data_path, info1.data_path);
+ EXPECT_EQ(
+ floor(info0.modification_time.ToDoubleT()),
+ info1.modification_time.ToDoubleT());
+
+ EXPECT_TRUE(db()->UpdateModificationTime(file_id, base::Time::UnixEpoch()));
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
+ EXPECT_EQ(info0.name, info1.name);
+ EXPECT_EQ(info0.parent_id, info1.parent_id);
+ EXPECT_EQ(info0.data_path, info1.data_path);
+ EXPECT_NE(info0.modification_time, info1.modification_time);
+ EXPECT_EQ(
+ info1.modification_time.ToDoubleT(),
+ floor(base::Time::UnixEpoch().ToDoubleT()));
+
+ EXPECT_FALSE(db()->UpdateModificationTime(999, base::Time::UnixEpoch()));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestSimpleFileOperations) {
+ FileId file_id = 888;
+ FileInfo info0;
+ EXPECT_FALSE(db()->GetFileInfo(file_id, &info0));
+ info0.parent_id = 0;
+ info0.data_path = base::FilePath(FILE_PATH_LITERAL("foo"));
+ info0.name = FILE_PATH_LITERAL("file name");
+ info0.modification_time = base::Time::Now();
+ EXPECT_TRUE(db()->AddFileInfo(info0, &file_id));
+ FileInfo info1;
+ EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
+ EXPECT_EQ(info0.parent_id, info1.parent_id);
+ EXPECT_EQ(info0.data_path, info1.data_path);
+ EXPECT_EQ(info0.name, info1.name);
+ EXPECT_EQ(
+ floor(info0.modification_time.ToDoubleT()),
+ info1.modification_time.ToDoubleT());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileSrcDirectory) {
+ FileId directory_id;
+ FileInfo info0;
+ info0.parent_id = 0;
+ info0.name = FILE_PATH_LITERAL("directory");
+ info0.modification_time = base::Time::Now();
+ EXPECT_TRUE(db()->AddFileInfo(info0, &directory_id));
+
+ FileId file_id;
+ FileInfo info1;
+ info1.parent_id = 0;
+ info1.data_path = base::FilePath(FILE_PATH_LITERAL("bar"));
+ info1.name = FILE_PATH_LITERAL("file");
+ info1.modification_time = base::Time::UnixEpoch();
+ EXPECT_TRUE(db()->AddFileInfo(info1, &file_id));
+
+ EXPECT_FALSE(db()->OverwritingMoveFile(directory_id, file_id));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileDestDirectory) {
+ FileId file_id;
+ FileInfo info0;
+ info0.parent_id = 0;
+ info0.name = FILE_PATH_LITERAL("file");
+ info0.data_path = base::FilePath(FILE_PATH_LITERAL("bar"));
+ info0.modification_time = base::Time::Now();
+ EXPECT_TRUE(db()->AddFileInfo(info0, &file_id));
+
+ FileId directory_id;
+ FileInfo info1;
+ info1.parent_id = 0;
+ info1.name = FILE_PATH_LITERAL("directory");
+ info1.modification_time = base::Time::UnixEpoch();
+ EXPECT_TRUE(db()->AddFileInfo(info1, &directory_id));
+
+ EXPECT_FALSE(db()->OverwritingMoveFile(file_id, directory_id));
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileSuccess) {
+ FileId file_id0;
+ FileInfo info0;
+ info0.parent_id = 0;
+ info0.data_path = base::FilePath(FILE_PATH_LITERAL("foo"));
+ info0.name = FILE_PATH_LITERAL("file name 0");
+ info0.modification_time = base::Time::Now();
+ EXPECT_TRUE(db()->AddFileInfo(info0, &file_id0));
+
+ FileInfo dir_info;
+ FileId dir_id;
+ dir_info.parent_id = 0;
+ dir_info.name = FILE_PATH_LITERAL("directory name");
+ EXPECT_TRUE(db()->AddFileInfo(dir_info, &dir_id));
+
+ FileId file_id1;
+ FileInfo info1;
+ info1.parent_id = dir_id;
+ info1.data_path = base::FilePath(FILE_PATH_LITERAL("bar"));
+ info1.name = FILE_PATH_LITERAL("file name 1");
+ info1.modification_time = base::Time::UnixEpoch();
+ EXPECT_TRUE(db()->AddFileInfo(info1, &file_id1));
+
+ EXPECT_TRUE(db()->OverwritingMoveFile(file_id0, file_id1));
+
+ FileInfo check_info;
+ FileId check_id;
+
+ EXPECT_FALSE(db()->GetFileWithPath(base::FilePath(info0.name), &check_id));
+ EXPECT_TRUE(db()->GetFileWithPath(
+ base::FilePath(dir_info.name).Append(info1.name), &check_id));
+ EXPECT_TRUE(db()->GetFileInfo(check_id, &check_info));
+
+ EXPECT_EQ(info0.data_path, check_info.data_path);
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestGetNextInteger) {
+ int64 next = -1;
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(0, next);
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(1, next);
+ InitDatabase();
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(2, next);
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(3, next);
+ InitDatabase();
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(4, next);
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_Empty) {
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+
+ int64 next = -1;
+ EXPECT_TRUE(db()->GetNextInteger(&next));
+ EXPECT_EQ(0, next);
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_Consistent) {
+ FileId dir_id;
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateDirectory(0, FPL("bar"), &dir_id);
+ CreateFile(dir_id, FPL("baz"), FPL("fuga"), NULL);
+ CreateFile(dir_id, FPL("fizz"), FPL("buzz"), NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest,
+ TestConsistencyCheck_BackingMultiEntry) {
+ const base::FilePath::CharType kBackingFileName[] = FPL("the celeb");
+ CreateFile(0, FPL("foo"), kBackingFileName, NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ ASSERT_TRUE(base::DeleteFile(path().Append(kBackingFileName), false));
+ CreateFile(0, FPL("bar"), kBackingFileName, NULL);
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_FileLost) {
+ const base::FilePath::CharType kBackingFileName[] = FPL("hoge");
+ CreateFile(0, FPL("foo"), kBackingFileName, NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ ASSERT_TRUE(base::DeleteFile(path().Append(kBackingFileName), false));
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_OrphanFile) {
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+
+ bool created = false;
+ base::PlatformFileError error = base::PLATFORM_FILE_ERROR_FAILED;
+ base::PlatformFile file = base::CreatePlatformFile(
+ path().Append(FPL("Orphan File")),
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &created, &error);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+ ASSERT_TRUE(created);
+ ASSERT_TRUE(base::ClosePlatformFile(file));
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_RootLoop) {
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ MakeHierarchyLink(0, 0, base::FilePath::StringType());
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_DirectoryLoop) {
+ FileId dir1_id;
+ FileId dir2_id;
+ base::FilePath::StringType dir1_name = FPL("foo");
+ CreateDirectory(0, dir1_name, &dir1_id);
+ CreateDirectory(dir1_id, FPL("bar"), &dir2_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ MakeHierarchyLink(dir2_id, dir1_id, dir1_name);
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_NameMismatch) {
+ FileId dir_id;
+ FileId file_id;
+ CreateDirectory(0, FPL("foo"), &dir_id);
+ CreateFile(dir_id, FPL("bar"), FPL("hoge/fuga/piyo"), &file_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ DeleteHierarchyLink(file_id);
+ MakeHierarchyLink(dir_id, file_id, FPL("baz"));
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_WreckedEntries) {
+ FileId dir1_id;
+ FileId dir2_id;
+ CreateDirectory(0, FPL("foo"), &dir1_id);
+ CreateDirectory(dir1_id, FPL("bar"), &dir2_id);
+ CreateFile(dir2_id, FPL("baz"), FPL("fizz/buzz"), NULL);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+ DeleteHierarchyLink(dir2_id); // Delete link from |dir1_id| to |dir2_id|.
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_Success) {
+ base::FilePath::StringType kFileName = FPL("bar");
+
+ FileId file_id_prev;
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateFile(0, kFileName, FPL("fuga"), &file_id_prev);
+
+ const base::FilePath kDatabaseDirectory =
+ path().Append(kDirectoryDatabaseName);
+ CloseDatabase();
+ CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile,
+ 0, std::numeric_limits<size_t>::max());
+ InitDatabase();
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+
+ FileId file_id;
+ EXPECT_TRUE(db()->GetChildWithName(0, kFileName, &file_id));
+ EXPECT_EQ(file_id_prev, file_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_Failure) {
+ base::FilePath::StringType kFileName = FPL("bar");
+
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateFile(0, kFileName, FPL("fuga"), NULL);
+
+ const base::FilePath kDatabaseDirectory =
+ path().Append(kDirectoryDatabaseName);
+ CloseDatabase();
+ CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile,
+ 0, std::numeric_limits<size_t>::max());
+ CorruptDatabase(kDatabaseDirectory, leveldb::kLogFile,
+ -1, 1);
+ InitDatabase();
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+
+ FileId file_id;
+ EXPECT_FALSE(db()->GetChildWithName(0, kFileName, &file_id));
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_MissingManifest) {
+ base::FilePath::StringType kFileName = FPL("bar");
+
+ FileId file_id_prev;
+ CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
+ CreateFile(0, kFileName, FPL("fuga"), &file_id_prev);
+
+ const base::FilePath kDatabaseDirectory =
+ path().Append(kDirectoryDatabaseName);
+ CloseDatabase();
+
+ DeleteDatabaseFile(kDatabaseDirectory, leveldb::kDescriptorFile);
+
+ InitDatabase();
+ EXPECT_FALSE(db()->IsFileSystemConsistent());
+
+ FileId file_id;
+ EXPECT_TRUE(db()->GetChildWithName(0, kFileName, &file_id));
+ EXPECT_EQ(file_id_prev, file_id);
+
+ EXPECT_TRUE(db()->IsFileSystemConsistent());
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.cc b/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.cc
new file mode 100644
index 00000000000..2e075ce39fe
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.cc
@@ -0,0 +1,245 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_file_stream_writer.h"
+
+#include "base/files/file_util_proxy.h"
+#include "base/platform_file.h"
+#include "base/sequenced_task_runner.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "webkit/browser/blob/local_file_stream_reader.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/local_file_stream_writer.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+namespace {
+
+// Adjust the |quota| value in overwriting case (i.e. |file_size| > 0 and
+// |file_offset| < |file_size|) to make the remaining quota calculation easier.
+// Specifically this widens the quota for overlapping range (so that we can
+// simply compare written bytes against the adjusted quota).
+int64 AdjustQuotaForOverlap(int64 quota,
+ int64 file_offset,
+ int64 file_size) {
+ DCHECK_LE(file_offset, file_size);
+ if (quota < 0)
+ quota = 0;
+ int64 overlap = file_size - file_offset;
+ if (kint64max - overlap > quota)
+ quota += overlap;
+ return quota;
+}
+
+} // namespace
+
+SandboxFileStreamWriter::SandboxFileStreamWriter(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ int64 initial_offset,
+ const UpdateObserverList& observers)
+ : file_system_context_(file_system_context),
+ url_(url),
+ initial_offset_(initial_offset),
+ observers_(observers),
+ file_size_(0),
+ total_bytes_written_(0),
+ allowed_bytes_to_write_(0),
+ has_pending_operation_(false),
+ default_quota_(kint64max),
+ weak_factory_(this) {
+ DCHECK(url_.is_valid());
+}
+
+SandboxFileStreamWriter::~SandboxFileStreamWriter() {}
+
+int SandboxFileStreamWriter::Write(
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ has_pending_operation_ = true;
+ if (local_file_writer_)
+ return WriteInternal(buf, buf_len, callback);
+
+ net::CompletionCallback write_task =
+ base::Bind(&SandboxFileStreamWriter::DidInitializeForWrite,
+ weak_factory_.GetWeakPtr(),
+ make_scoped_refptr(buf), buf_len, callback);
+ file_system_context_->operation_runner()->CreateSnapshotFile(
+ url_, base::Bind(&SandboxFileStreamWriter::DidCreateSnapshotFile,
+ weak_factory_.GetWeakPtr(), write_task));
+ return net::ERR_IO_PENDING;
+}
+
+int SandboxFileStreamWriter::Cancel(const net::CompletionCallback& callback) {
+ if (!has_pending_operation_)
+ return net::ERR_UNEXPECTED;
+
+ DCHECK(!callback.is_null());
+ cancel_callback_ = callback;
+ return net::ERR_IO_PENDING;
+}
+
+int SandboxFileStreamWriter::WriteInternal(
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ // allowed_bytes_to_write could be negative if the file size is
+ // greater than the current (possibly new) quota.
+ DCHECK(total_bytes_written_ <= allowed_bytes_to_write_ ||
+ allowed_bytes_to_write_ < 0);
+ if (total_bytes_written_ >= allowed_bytes_to_write_) {
+ has_pending_operation_ = false;
+ return net::ERR_FILE_NO_SPACE;
+ }
+
+ if (buf_len > allowed_bytes_to_write_ - total_bytes_written_)
+ buf_len = allowed_bytes_to_write_ - total_bytes_written_;
+
+ DCHECK(local_file_writer_.get());
+ const int result = local_file_writer_->Write(
+ buf, buf_len,
+ base::Bind(&SandboxFileStreamWriter::DidWrite, weak_factory_.GetWeakPtr(),
+ callback));
+ if (result != net::ERR_IO_PENDING)
+ has_pending_operation_ = false;
+ return result;
+}
+
+void SandboxFileStreamWriter::DidCreateSnapshotFile(
+ const net::CompletionCallback& callback,
+ base::PlatformFileError file_error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) {
+ DCHECK(!file_ref.get());
+
+ if (CancelIfRequested())
+ return;
+ if (file_error != base::PLATFORM_FILE_OK) {
+ callback.Run(net::PlatformFileErrorToNetError(file_error));
+ return;
+ }
+ if (file_info.is_directory) {
+ // We should not be writing to a directory.
+ callback.Run(net::ERR_ACCESS_DENIED);
+ return;
+ }
+ file_size_ = file_info.size;
+ if (initial_offset_ > file_size_) {
+ LOG(ERROR) << initial_offset_ << ", " << file_size_;
+ // This shouldn't happen as long as we check offset in the renderer.
+ NOTREACHED();
+ initial_offset_ = file_size_;
+ }
+ DCHECK(!local_file_writer_.get());
+ local_file_writer_.reset(new LocalFileStreamWriter(
+ file_system_context_->default_file_task_runner(), platform_path,
+ initial_offset_));
+
+ quota::QuotaManagerProxy* quota_manager_proxy =
+ file_system_context_->quota_manager_proxy();
+ if (!quota_manager_proxy) {
+ // If we don't have the quota manager or the requested filesystem type
+ // does not support quota, we should be able to let it go.
+ allowed_bytes_to_write_ = default_quota_;
+ callback.Run(net::OK);
+ return;
+ }
+
+ DCHECK(quota_manager_proxy->quota_manager());
+ quota_manager_proxy->quota_manager()->GetUsageAndQuota(
+ url_.origin(),
+ FileSystemTypeToQuotaStorageType(url_.type()),
+ base::Bind(&SandboxFileStreamWriter::DidGetUsageAndQuota,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void SandboxFileStreamWriter::DidGetUsageAndQuota(
+ const net::CompletionCallback& callback,
+ quota::QuotaStatusCode status,
+ int64 usage, int64 quota) {
+ if (CancelIfRequested())
+ return;
+ if (status != quota::kQuotaStatusOk) {
+ LOG(WARNING) << "Got unexpected quota error : " << status;
+ callback.Run(net::ERR_FAILED);
+ return;
+ }
+
+ allowed_bytes_to_write_ = quota - usage;
+ callback.Run(net::OK);
+}
+
+void SandboxFileStreamWriter::DidInitializeForWrite(
+ net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback,
+ int init_status) {
+ if (CancelIfRequested())
+ return;
+ if (init_status != net::OK) {
+ has_pending_operation_ = false;
+ callback.Run(init_status);
+ return;
+ }
+ allowed_bytes_to_write_ = AdjustQuotaForOverlap(
+ allowed_bytes_to_write_, initial_offset_, file_size_);
+ const int result = WriteInternal(buf, buf_len, callback);
+ if (result != net::ERR_IO_PENDING)
+ callback.Run(result);
+}
+
+void SandboxFileStreamWriter::DidWrite(
+ const net::CompletionCallback& callback,
+ int write_response) {
+ DCHECK(has_pending_operation_);
+ has_pending_operation_ = false;
+
+ if (write_response <= 0) {
+ if (CancelIfRequested())
+ return;
+ callback.Run(write_response);
+ return;
+ }
+
+ if (total_bytes_written_ + write_response + initial_offset_ > file_size_) {
+ int overlapped = file_size_ - total_bytes_written_ - initial_offset_;
+ if (overlapped < 0)
+ overlapped = 0;
+ observers_.Notify(&FileUpdateObserver::OnUpdate,
+ MakeTuple(url_, write_response - overlapped));
+ }
+ total_bytes_written_ += write_response;
+
+ if (CancelIfRequested())
+ return;
+ callback.Run(write_response);
+}
+
+bool SandboxFileStreamWriter::CancelIfRequested() {
+ if (cancel_callback_.is_null())
+ return false;
+
+ net::CompletionCallback pending_cancel = cancel_callback_;
+ has_pending_operation_ = false;
+ cancel_callback_.Reset();
+ pending_cancel.Run(net::OK);
+ return true;
+}
+
+int SandboxFileStreamWriter::Flush(const net::CompletionCallback& callback) {
+ DCHECK(!has_pending_operation_);
+ DCHECK(cancel_callback_.is_null());
+
+ // Write() is not called yet, so there's nothing to flush.
+ if (!local_file_writer_)
+ return net::OK;
+
+ return local_file_writer_->Flush(callback);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.h b/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.h
new file mode 100644
index 00000000000..43fb0cd7743
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_stream_writer.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/platform_file.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_stream_writer.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/task_runner_bound_observer_list.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+#include "webkit/common/blob/shareable_file_reference.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/quota/quota_types.h"
+
+namespace fileapi {
+
+class FileSystemContext;
+class FileSystemQuotaUtil;
+class LocalFileStreamWriter;
+
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxFileStreamWriter
+ : public FileStreamWriter {
+ public:
+ SandboxFileStreamWriter(FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ int64 initial_offset,
+ const UpdateObserverList& observers);
+ virtual ~SandboxFileStreamWriter();
+
+ // FileStreamWriter overrides.
+ virtual int Write(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) OVERRIDE;
+ virtual int Cancel(const net::CompletionCallback& callback) OVERRIDE;
+ virtual int Flush(const net::CompletionCallback& callback) OVERRIDE;
+
+ // Used only by tests.
+ void set_default_quota(int64 quota) {
+ default_quota_ = quota;
+ }
+
+ private:
+ // Performs quota calculation and calls local_file_writer_->Write().
+ int WriteInternal(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback);
+
+ // Callbacks that are chained for the first write. This eventually calls
+ // WriteInternal.
+ void DidCreateSnapshotFile(
+ const net::CompletionCallback& callback,
+ base::PlatformFileError file_error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref);
+ void DidGetUsageAndQuota(const net::CompletionCallback& callback,
+ quota::QuotaStatusCode status,
+ int64 usage, int64 quota);
+ void DidInitializeForWrite(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback,
+ int init_status);
+
+ void DidWrite(const net::CompletionCallback& callback, int write_response);
+
+ // Stops the in-flight operation, calls |cancel_callback_| and returns true
+ // if there's a pending cancel request.
+ bool CancelIfRequested();
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+ FileSystemURL url_;
+ int64 initial_offset_;
+ scoped_ptr<LocalFileStreamWriter> local_file_writer_;
+ net::CompletionCallback cancel_callback_;
+
+ UpdateObserverList observers_;
+
+ base::FilePath file_path_;
+ int64 file_size_;
+ int64 total_bytes_written_;
+ int64 allowed_bytes_to_write_;
+ bool has_pending_operation_;
+
+ int64 default_quota_;
+
+ base::WeakPtrFactory<SandboxFileStreamWriter> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxFileStreamWriter);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_backend.cc
new file mode 100644
index 00000000000..665c0f1f798
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend.cc
@@ -0,0 +1,258 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/task_runner_util.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/async_file_util_adapter.h"
+#include "webkit/browser/fileapi/copy_or_move_file_validator.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_stream_reader.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_operation_impl.h"
+#include "webkit/browser/fileapi/file_system_options.h"
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+#include "webkit/browser/fileapi/obfuscated_file_util.h"
+#include "webkit/browser/fileapi/sandbox_file_stream_writer.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "webkit/browser/fileapi/sandbox_quota_observer.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using quota::QuotaManagerProxy;
+using quota::SpecialStoragePolicy;
+
+namespace fileapi {
+
+namespace {
+
+const char kTemporaryOriginsCountLabel[] = "FileSystem.TemporaryOriginsCount";
+const char kPersistentOriginsCountLabel[] = "FileSystem.PersistentOriginsCount";
+
+} // anonymous namespace
+
+SandboxFileSystemBackend::SandboxFileSystemBackend(
+ SandboxFileSystemBackendDelegate* delegate)
+ : delegate_(delegate),
+ enable_temporary_file_system_in_incognito_(false) {
+}
+
+SandboxFileSystemBackend::~SandboxFileSystemBackend() {
+}
+
+bool SandboxFileSystemBackend::CanHandleType(FileSystemType type) const {
+ return type == kFileSystemTypeTemporary ||
+ type == kFileSystemTypePersistent;
+}
+
+void SandboxFileSystemBackend::Initialize(FileSystemContext* context) {
+ // Set quota observers.
+ update_observers_ = update_observers_.AddObserver(
+ delegate_->quota_observer(),
+ delegate_->file_task_runner());
+ access_observers_ = access_observers_.AddObserver(
+ delegate_->quota_observer(), NULL);
+}
+
+void SandboxFileSystemBackend::OpenFileSystem(
+ const GURL& origin_url,
+ fileapi::FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) {
+ DCHECK(CanHandleType(type));
+ DCHECK(delegate_);
+ if (delegate_->file_system_options().is_incognito() &&
+ !(type == kFileSystemTypeTemporary &&
+ enable_temporary_file_system_in_incognito_)) {
+ // TODO(kinuko): return an isolated temporary directory.
+ callback.Run(GURL(), std::string(), base::PLATFORM_FILE_ERROR_SECURITY);
+ return;
+ }
+
+ delegate_->OpenFileSystem(
+ origin_url, type, mode, callback,
+ GetFileSystemRootURI(origin_url, type));
+}
+
+FileSystemFileUtil* SandboxFileSystemBackend::GetFileUtil(
+ FileSystemType type) {
+ return delegate_->sync_file_util();
+}
+
+AsyncFileUtil* SandboxFileSystemBackend::GetAsyncFileUtil(
+ FileSystemType type) {
+ DCHECK(delegate_);
+ return delegate_->file_util();
+}
+
+CopyOrMoveFileValidatorFactory*
+SandboxFileSystemBackend::GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type,
+ base::PlatformFileError* error_code) {
+ DCHECK(error_code);
+ *error_code = base::PLATFORM_FILE_OK;
+ return NULL;
+}
+
+FileSystemOperation* SandboxFileSystemBackend::CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::PlatformFileError* error_code) const {
+ DCHECK(CanHandleType(url.type()));
+ DCHECK(delegate_);
+ if (!delegate_->IsAccessValid(url)) {
+ *error_code = base::PLATFORM_FILE_ERROR_SECURITY;
+ return NULL;
+ }
+
+ scoped_ptr<FileSystemOperationContext> operation_context(
+ new FileSystemOperationContext(context));
+ operation_context->set_update_observers(update_observers_);
+ operation_context->set_change_observers(change_observers_);
+
+ SpecialStoragePolicy* policy = delegate_->special_storage_policy();
+ if (policy && policy->IsStorageUnlimited(url.origin()))
+ operation_context->set_quota_limit_type(quota::kQuotaLimitTypeUnlimited);
+ else
+ operation_context->set_quota_limit_type(quota::kQuotaLimitTypeLimited);
+
+ return new FileSystemOperationImpl(url, context, operation_context.Pass());
+}
+
+scoped_ptr<webkit_blob::FileStreamReader>
+SandboxFileSystemBackend::CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const {
+ DCHECK(CanHandleType(url.type()));
+ DCHECK(delegate_);
+ if (!delegate_->IsAccessValid(url))
+ return scoped_ptr<webkit_blob::FileStreamReader>();
+ return scoped_ptr<webkit_blob::FileStreamReader>(
+ new FileSystemFileStreamReader(
+ context, url, offset, expected_modification_time));
+}
+
+scoped_ptr<fileapi::FileStreamWriter>
+SandboxFileSystemBackend::CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context) const {
+ DCHECK(CanHandleType(url.type()));
+ DCHECK(delegate_);
+ if (!delegate_->IsAccessValid(url))
+ return scoped_ptr<fileapi::FileStreamWriter>();
+ return scoped_ptr<fileapi::FileStreamWriter>(
+ new SandboxFileStreamWriter(context, url, offset, update_observers_));
+}
+
+FileSystemQuotaUtil* SandboxFileSystemBackend::GetQuotaUtil() {
+ return this;
+}
+
+SandboxFileSystemBackendDelegate::OriginEnumerator*
+SandboxFileSystemBackend::CreateOriginEnumerator() {
+ DCHECK(delegate_);
+ return delegate_->CreateOriginEnumerator();
+}
+
+base::PlatformFileError
+SandboxFileSystemBackend::DeleteOriginDataOnFileThread(
+ FileSystemContext* file_system_context,
+ QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ fileapi::FileSystemType type) {
+ DCHECK(CanHandleType(type));
+ DCHECK(delegate_);
+ return delegate_->DeleteOriginDataOnFileThread(
+ file_system_context, proxy, origin_url, type);
+}
+
+void SandboxFileSystemBackend::GetOriginsForTypeOnFileThread(
+ fileapi::FileSystemType type, std::set<GURL>* origins) {
+ DCHECK(CanHandleType(type));
+ DCHECK(delegate_);
+ delegate_->GetOriginsForTypeOnFileThread(type, origins);
+ switch (type) {
+ case kFileSystemTypeTemporary:
+ UMA_HISTOGRAM_COUNTS(kTemporaryOriginsCountLabel, origins->size());
+ break;
+ case kFileSystemTypePersistent:
+ UMA_HISTOGRAM_COUNTS(kPersistentOriginsCountLabel, origins->size());
+ break;
+ default:
+ break;
+ }
+}
+
+void SandboxFileSystemBackend::GetOriginsForHostOnFileThread(
+ fileapi::FileSystemType type, const std::string& host,
+ std::set<GURL>* origins) {
+ DCHECK(CanHandleType(type));
+ DCHECK(delegate_);
+ delegate_->GetOriginsForHostOnFileThread(type, host, origins);
+}
+
+int64 SandboxFileSystemBackend::GetOriginUsageOnFileThread(
+ FileSystemContext* file_system_context,
+ const GURL& origin_url,
+ fileapi::FileSystemType type) {
+ DCHECK(CanHandleType(type));
+ DCHECK(delegate_);
+ return delegate_->GetOriginUsageOnFileThread(
+ file_system_context, origin_url, type);
+}
+
+void SandboxFileSystemBackend::AddFileUpdateObserver(
+ FileSystemType type,
+ FileUpdateObserver* observer,
+ base::SequencedTaskRunner* task_runner) {
+ DCHECK(CanHandleType(type));
+ UpdateObserverList* list = &update_observers_;
+ *list = list->AddObserver(observer, task_runner);
+}
+
+void SandboxFileSystemBackend::AddFileChangeObserver(
+ FileSystemType type,
+ FileChangeObserver* observer,
+ base::SequencedTaskRunner* task_runner) {
+ DCHECK(CanHandleType(type));
+ ChangeObserverList* list = &change_observers_;
+ *list = list->AddObserver(observer, task_runner);
+}
+
+void SandboxFileSystemBackend::AddFileAccessObserver(
+ FileSystemType type,
+ FileAccessObserver* observer,
+ base::SequencedTaskRunner* task_runner) {
+ DCHECK(CanHandleType(type));
+ access_observers_ = access_observers_.AddObserver(observer, task_runner);
+}
+
+const UpdateObserverList* SandboxFileSystemBackend::GetUpdateObservers(
+ FileSystemType type) const {
+ DCHECK(CanHandleType(type));
+ return &update_observers_;
+}
+
+const ChangeObserverList* SandboxFileSystemBackend::GetChangeObservers(
+ FileSystemType type) const {
+ DCHECK(CanHandleType(type));
+ return &change_observers_;
+}
+
+const AccessObserverList* SandboxFileSystemBackend::GetAccessObservers(
+ FileSystemType type) const {
+ DCHECK(CanHandleType(type));
+ return &access_observers_;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend.h b/chromium/webkit/browser/fileapi/sandbox_file_system_backend.h
new file mode 100644
index 00000000000..a1a568637a0
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend.h
@@ -0,0 +1,123 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_
+
+#include <set>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_quota_util.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "webkit/browser/fileapi/task_runner_bound_observer_list.h"
+#include "webkit/browser/quota/special_storage_policy.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace fileapi {
+
+// An interface to construct or crack sandboxed filesystem paths for
+// TEMPORARY or PERSISTENT filesystems, which are placed under the user's
+// profile directory in a sandboxed way.
+// This interface also lets one enumerate and remove storage for the origins
+// that use the filesystem.
+class WEBKIT_STORAGE_BROWSER_EXPORT SandboxFileSystemBackend
+ : public FileSystemBackend,
+ public FileSystemQuotaUtil {
+ public:
+ explicit SandboxFileSystemBackend(SandboxFileSystemBackendDelegate* delegate);
+ virtual ~SandboxFileSystemBackend();
+
+ // FileSystemBackend overrides.
+ virtual bool CanHandleType(FileSystemType type) const OVERRIDE;
+ virtual void Initialize(FileSystemContext* context) OVERRIDE;
+ virtual void OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) OVERRIDE;
+ virtual FileSystemFileUtil* GetFileUtil(FileSystemType type) OVERRIDE;
+ virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE;
+ virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type,
+ base::PlatformFileError* error_code) OVERRIDE;
+ virtual FileSystemOperation* CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::PlatformFileError* error_code) const OVERRIDE;
+ virtual scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const OVERRIDE;
+ virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context) const OVERRIDE;
+ virtual FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE;
+
+ // Returns an origin enumerator of this backend.
+ // This method can only be called on the file thread.
+ SandboxFileSystemBackendDelegate::OriginEnumerator* CreateOriginEnumerator();
+
+ // FileSystemQuotaUtil overrides.
+ virtual base::PlatformFileError DeleteOriginDataOnFileThread(
+ FileSystemContext* context,
+ quota::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ FileSystemType type) OVERRIDE;
+ virtual void GetOriginsForTypeOnFileThread(
+ FileSystemType type,
+ std::set<GURL>* origins) OVERRIDE;
+ virtual void GetOriginsForHostOnFileThread(
+ FileSystemType type,
+ const std::string& host,
+ std::set<GURL>* origins) OVERRIDE;
+ virtual int64 GetOriginUsageOnFileThread(
+ FileSystemContext* context,
+ const GURL& origin_url,
+ FileSystemType type) OVERRIDE;
+ virtual void AddFileUpdateObserver(
+ FileSystemType type,
+ FileUpdateObserver* observer,
+ base::SequencedTaskRunner* task_runner) OVERRIDE;
+ virtual void AddFileChangeObserver(
+ FileSystemType type,
+ FileChangeObserver* observer,
+ base::SequencedTaskRunner* task_runner) OVERRIDE;
+ virtual void AddFileAccessObserver(
+ FileSystemType type,
+ FileAccessObserver* observer,
+ base::SequencedTaskRunner* task_runner) OVERRIDE;
+ virtual const UpdateObserverList* GetUpdateObservers(
+ FileSystemType type) const OVERRIDE;
+ virtual const ChangeObserverList* GetChangeObservers(
+ FileSystemType type) const OVERRIDE;
+ virtual const AccessObserverList* GetAccessObservers(
+ FileSystemType type) const OVERRIDE;
+
+ void set_enable_temporary_file_system_in_incognito(bool enable) {
+ enable_temporary_file_system_in_incognito_ = enable;
+ }
+
+ private:
+ SandboxFileSystemBackendDelegate* delegate_; // Not owned.
+
+ bool enable_temporary_file_system_in_incognito_;
+
+ // Observers.
+ UpdateObserverList update_observers_;
+ ChangeObserverList change_observers_;
+ AccessObserverList access_observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxFileSystemBackend);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.cc
new file mode 100644
index 00000000000..0a6034e7d9a
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.cc
@@ -0,0 +1,442 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h"
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/task_runner_util.h"
+#include "net/base/net_util.h"
+#include "webkit/browser/fileapi/async_file_util_adapter.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+#include "webkit/browser/fileapi/obfuscated_file_util.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+#include "webkit/browser/fileapi/sandbox_quota_observer.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+namespace {
+
+const char kOpenFileSystemLabel[] = "FileSystem.OpenFileSystem";
+const char kOpenFileSystemDetailLabel[] = "FileSystem.OpenFileSystemDetail";
+const char kOpenFileSystemDetailNonThrottledLabel[] =
+ "FileSystem.OpenFileSystemDetailNonthrottled";
+int64 kMinimumStatsCollectionIntervalHours = 1;
+
+enum FileSystemError {
+ kOK = 0,
+ kIncognito,
+ kInvalidSchemeError,
+ kCreateDirectoryError,
+ kNotFound,
+ kUnknownError,
+ kFileSystemErrorMax,
+};
+
+// Restricted names.
+// http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions
+const base::FilePath::CharType* const kRestrictedNames[] = {
+ FILE_PATH_LITERAL("."), FILE_PATH_LITERAL(".."),
+};
+
+// Restricted chars.
+const base::FilePath::CharType kRestrictedChars[] = {
+ FILE_PATH_LITERAL('/'), FILE_PATH_LITERAL('\\'),
+};
+
+class ObfuscatedOriginEnumerator
+ : public SandboxFileSystemBackendDelegate::OriginEnumerator {
+ public:
+ explicit ObfuscatedOriginEnumerator(ObfuscatedFileUtil* file_util) {
+ enum_.reset(file_util->CreateOriginEnumerator());
+ }
+ virtual ~ObfuscatedOriginEnumerator() {}
+
+ virtual GURL Next() OVERRIDE {
+ return enum_->Next();
+ }
+
+ virtual bool HasFileSystemType(fileapi::FileSystemType type) const OVERRIDE {
+ return enum_->HasFileSystemType(type);
+ }
+
+ private:
+ scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enum_;
+};
+
+void OpenFileSystemOnFileThread(
+ ObfuscatedFileUtil* file_util,
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ base::PlatformFileError* error_ptr) {
+ DCHECK(error_ptr);
+ const bool create = (mode == OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT);
+ file_util->GetDirectoryForOriginAndType(origin_url, type, create, error_ptr);
+ if (*error_ptr != base::PLATFORM_FILE_OK) {
+ UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemLabel,
+ kCreateDirectoryError,
+ kFileSystemErrorMax);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemLabel, kOK, kFileSystemErrorMax);
+ }
+ // The reference of file_util will be derefed on the FILE thread
+ // when the storage of this callback gets deleted regardless of whether
+ // this method is called or not.
+}
+
+void DidOpenFileSystem(
+ base::WeakPtr<SandboxFileSystemBackendDelegate> delegate,
+ const base::Callback<void(base::PlatformFileError error)>& callback,
+ base::PlatformFileError* error) {
+ if (delegate.get())
+ delegate.get()->CollectOpenFileSystemMetrics(*error);
+ callback.Run(*error);
+}
+
+} // namespace
+
+const base::FilePath::CharType
+SandboxFileSystemBackendDelegate::kFileSystemDirectory[] =
+ FILE_PATH_LITERAL("File System");
+
+SandboxFileSystemBackendDelegate::SandboxFileSystemBackendDelegate(
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ base::SequencedTaskRunner* file_task_runner,
+ const base::FilePath& profile_path,
+ quota::SpecialStoragePolicy* special_storage_policy,
+ const FileSystemOptions& file_system_options)
+ : file_task_runner_(file_task_runner),
+ sandbox_file_util_(new AsyncFileUtilAdapter(
+ new ObfuscatedFileUtil(
+ special_storage_policy,
+ profile_path.Append(kFileSystemDirectory),
+ file_task_runner))),
+ file_system_usage_cache_(new FileSystemUsageCache(file_task_runner)),
+ quota_observer_(new SandboxQuotaObserver(
+ quota_manager_proxy,
+ file_task_runner,
+ obfuscated_file_util(),
+ usage_cache())),
+ special_storage_policy_(special_storage_policy),
+ file_system_options_(file_system_options),
+ weak_factory_(this) {
+}
+
+SandboxFileSystemBackendDelegate::~SandboxFileSystemBackendDelegate() {
+ if (!file_task_runner_->RunsTasksOnCurrentThread()) {
+ AsyncFileUtil* sandbox_file_util = sandbox_file_util_.release();
+ SandboxQuotaObserver* quota_observer = quota_observer_.release();
+ FileSystemUsageCache* file_system_usage_cache =
+ file_system_usage_cache_.release();
+ if (!file_task_runner_->DeleteSoon(FROM_HERE, sandbox_file_util))
+ delete sandbox_file_util;
+ if (!file_task_runner_->DeleteSoon(FROM_HERE, quota_observer))
+ delete quota_observer;
+ if (!file_task_runner_->DeleteSoon(FROM_HERE, file_system_usage_cache))
+ delete file_system_usage_cache;
+ }
+}
+
+bool SandboxFileSystemBackendDelegate::IsAccessValid(
+ const FileSystemURL& url) const {
+ if (!IsAllowedScheme(url.origin()))
+ return false;
+
+ if (url.path().ReferencesParent())
+ return false;
+
+ // Return earlier if the path is '/', because VirtualPath::BaseName()
+ // returns '/' for '/' and we fail the "basename != '/'" check below.
+ // (We exclude '.' because it's disallowed by spec.)
+ if (VirtualPath::IsRootPath(url.path()) &&
+ url.path() != base::FilePath(base::FilePath::kCurrentDirectory))
+ return true;
+
+ // Restricted names specified in
+ // http://dev.w3.org/2009/dap/file-system/file-dir-sys.html#naming-restrictions
+ base::FilePath filename = VirtualPath::BaseName(url.path());
+ // See if the name is allowed to create.
+ for (size_t i = 0; i < arraysize(kRestrictedNames); ++i) {
+ if (filename.value() == kRestrictedNames[i])
+ return false;
+ }
+ for (size_t i = 0; i < arraysize(kRestrictedChars); ++i) {
+ if (filename.value().find(kRestrictedChars[i]) !=
+ base::FilePath::StringType::npos)
+ return false;
+ }
+
+ return true;
+}
+
+bool SandboxFileSystemBackendDelegate::IsAllowedScheme(const GURL& url) const {
+ // Basically we only accept http or https. We allow file:// URLs
+ // only if --allow-file-access-from-files flag is given.
+ if (url.SchemeIs("http") || url.SchemeIs("https"))
+ return true;
+ if (url.SchemeIsFileSystem())
+ return url.inner_url() && IsAllowedScheme(*url.inner_url());
+
+ for (size_t i = 0;
+ i < file_system_options_.additional_allowed_schemes().size();
+ ++i) {
+ if (url.SchemeIs(
+ file_system_options_.additional_allowed_schemes()[i].c_str()))
+ return true;
+ }
+ return false;
+}
+
+SandboxFileSystemBackendDelegate::OriginEnumerator*
+SandboxFileSystemBackendDelegate::CreateOriginEnumerator() {
+ return new ObfuscatedOriginEnumerator(obfuscated_file_util());
+}
+
+base::FilePath
+SandboxFileSystemBackendDelegate::GetBaseDirectoryForOriginAndType(
+ const GURL& origin_url,
+ fileapi::FileSystemType type,
+ bool create) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::FilePath path = obfuscated_file_util()->GetDirectoryForOriginAndType(
+ origin_url, type, create, &error);
+ if (error != base::PLATFORM_FILE_OK)
+ return base::FilePath();
+ return path;
+}
+
+void SandboxFileSystemBackendDelegate::OpenFileSystem(
+ const GURL& origin_url,
+ fileapi::FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback,
+ const GURL& root_url) {
+ if (!IsAllowedScheme(origin_url)) {
+ callback.Run(GURL(), std::string(), base::PLATFORM_FILE_ERROR_SECURITY);
+ return;
+ }
+
+ std::string name = GetFileSystemName(origin_url, type);
+
+ base::PlatformFileError* error_ptr = new base::PlatformFileError;
+ file_task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&OpenFileSystemOnFileThread,
+ obfuscated_file_util(), origin_url, type, mode,
+ base::Unretained(error_ptr)),
+ base::Bind(&DidOpenFileSystem,
+ weak_factory_.GetWeakPtr(),
+ base::Bind(callback, root_url, name),
+ base::Owned(error_ptr)));
+}
+
+base::PlatformFileError
+SandboxFileSystemBackendDelegate::DeleteOriginDataOnFileThread(
+ FileSystemContext* file_system_context,
+ quota::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ fileapi::FileSystemType type) {
+ int64 usage = GetOriginUsageOnFileThread(
+ file_system_context, origin_url, type);
+ usage_cache()->CloseCacheFiles();
+ bool result = obfuscated_file_util()->DeleteDirectoryForOriginAndType(
+ origin_url, type);
+ if (result && proxy) {
+ proxy->NotifyStorageModified(
+ quota::QuotaClient::kFileSystem,
+ origin_url,
+ FileSystemTypeToQuotaStorageType(type),
+ -usage);
+ }
+
+ if (result)
+ return base::PLATFORM_FILE_OK;
+ return base::PLATFORM_FILE_ERROR_FAILED;
+}
+
+void SandboxFileSystemBackendDelegate::GetOriginsForTypeOnFileThread(
+ fileapi::FileSystemType type, std::set<GURL>* origins) {
+ DCHECK(origins);
+ scoped_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator());
+ GURL origin;
+ while (!(origin = enumerator->Next()).is_empty()) {
+ if (enumerator->HasFileSystemType(type))
+ origins->insert(origin);
+ }
+}
+
+void SandboxFileSystemBackendDelegate::GetOriginsForHostOnFileThread(
+ fileapi::FileSystemType type, const std::string& host,
+ std::set<GURL>* origins) {
+ DCHECK(origins);
+ scoped_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator());
+ GURL origin;
+ while (!(origin = enumerator->Next()).is_empty()) {
+ if (host == net::GetHostOrSpecFromURL(origin) &&
+ enumerator->HasFileSystemType(type))
+ origins->insert(origin);
+ }
+}
+
+int64 SandboxFileSystemBackendDelegate::GetOriginUsageOnFileThread(
+ FileSystemContext* file_system_context,
+ const GURL& origin_url,
+ fileapi::FileSystemType type) {
+ // Don't use usage cache and return recalculated usage for sticky invalidated
+ // origins.
+ if (ContainsKey(sticky_dirty_origins_, std::make_pair(origin_url, type)))
+ return RecalculateUsage(file_system_context, origin_url, type);
+
+ base::FilePath base_path =
+ GetBaseDirectoryForOriginAndType(origin_url, type, false);
+ if (base_path.empty() || !base::DirectoryExists(base_path))
+ return 0;
+ base::FilePath usage_file_path =
+ base_path.Append(FileSystemUsageCache::kUsageFileName);
+
+ bool is_valid = usage_cache()->IsValid(usage_file_path);
+ uint32 dirty_status = 0;
+ bool dirty_status_available =
+ usage_cache()->GetDirty(usage_file_path, &dirty_status);
+ bool visited = !visited_origins_.insert(origin_url).second;
+ if (is_valid && (dirty_status == 0 || (dirty_status_available && visited))) {
+ // The usage cache is clean (dirty == 0) or the origin is already
+ // initialized and running. Read the cache file to get the usage.
+ int64 usage = 0;
+ return usage_cache()->GetUsage(usage_file_path, &usage) ? usage : -1;
+ }
+ // The usage cache has not been initialized or the cache is dirty.
+ // Get the directory size now and update the cache.
+ usage_cache()->Delete(usage_file_path);
+
+ int64 usage = RecalculateUsage(file_system_context, origin_url, type);
+
+ // This clears the dirty flag too.
+ usage_cache()->UpdateUsage(usage_file_path, usage);
+ return usage;
+}
+
+void SandboxFileSystemBackendDelegate::InvalidateUsageCache(
+ const GURL& origin,
+ fileapi::FileSystemType type) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::FilePath usage_file_path = GetUsageCachePathForOriginAndType(
+ obfuscated_file_util(), origin, type, &error);
+ if (error != base::PLATFORM_FILE_OK)
+ return;
+ usage_cache()->IncrementDirty(usage_file_path);
+}
+
+void SandboxFileSystemBackendDelegate::StickyInvalidateUsageCache(
+ const GURL& origin,
+ fileapi::FileSystemType type) {
+ sticky_dirty_origins_.insert(std::make_pair(origin, type));
+ quota_observer()->SetUsageCacheEnabled(origin, type, false);
+ InvalidateUsageCache(origin, type);
+}
+
+FileSystemFileUtil* SandboxFileSystemBackendDelegate::sync_file_util() {
+ return static_cast<AsyncFileUtilAdapter*>(file_util())->sync_file_util();
+}
+
+base::FilePath
+SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType(
+ const GURL& origin_url,
+ FileSystemType type) {
+ base::PlatformFileError error;
+ base::FilePath path = GetUsageCachePathForOriginAndType(
+ obfuscated_file_util(), origin_url, type, &error);
+ if (error != base::PLATFORM_FILE_OK)
+ return base::FilePath();
+ return path;
+}
+
+// static
+base::FilePath
+SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType(
+ ObfuscatedFileUtil* sandbox_file_util,
+ const GURL& origin_url,
+ fileapi::FileSystemType type,
+ base::PlatformFileError* error_out) {
+ DCHECK(error_out);
+ *error_out = base::PLATFORM_FILE_OK;
+ base::FilePath base_path = sandbox_file_util->GetDirectoryForOriginAndType(
+ origin_url, type, false /* create */, error_out);
+ if (*error_out != base::PLATFORM_FILE_OK)
+ return base::FilePath();
+ return base_path.Append(FileSystemUsageCache::kUsageFileName);
+}
+
+int64 SandboxFileSystemBackendDelegate::RecalculateUsage(
+ FileSystemContext* context,
+ const GURL& origin,
+ FileSystemType type) {
+ FileSystemOperationContext operation_context(context);
+ FileSystemURL url = context->CreateCrackedFileSystemURL(
+ origin, type, base::FilePath());
+ scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator(
+ obfuscated_file_util()->CreateFileEnumerator(
+ &operation_context, url, true));
+
+ base::FilePath file_path_each;
+ int64 usage = 0;
+
+ while (!(file_path_each = enumerator->Next()).empty()) {
+ usage += enumerator->Size();
+ usage += ObfuscatedFileUtil::ComputeFilePathCost(file_path_each);
+ }
+
+ return usage;
+}
+
+void SandboxFileSystemBackendDelegate::CollectOpenFileSystemMetrics(
+ base::PlatformFileError error_code) {
+ base::Time now = base::Time::Now();
+ bool throttled = now < next_release_time_for_open_filesystem_stat_;
+ if (!throttled) {
+ next_release_time_for_open_filesystem_stat_ =
+ now + base::TimeDelta::FromHours(kMinimumStatsCollectionIntervalHours);
+ }
+
+#define REPORT(report_value) \
+ UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemDetailLabel, \
+ (report_value), \
+ kFileSystemErrorMax); \
+ if (!throttled) { \
+ UMA_HISTOGRAM_ENUMERATION(kOpenFileSystemDetailNonThrottledLabel, \
+ (report_value), \
+ kFileSystemErrorMax); \
+ }
+
+ switch (error_code) {
+ case base::PLATFORM_FILE_OK:
+ REPORT(kOK);
+ break;
+ case base::PLATFORM_FILE_ERROR_INVALID_URL:
+ REPORT(kInvalidSchemeError);
+ break;
+ case base::PLATFORM_FILE_ERROR_NOT_FOUND:
+ REPORT(kNotFound);
+ break;
+ case base::PLATFORM_FILE_ERROR_FAILED:
+ default:
+ REPORT(kUnknownError);
+ break;
+ }
+#undef REPORT
+}
+
+ObfuscatedFileUtil* SandboxFileSystemBackendDelegate::obfuscated_file_util() {
+ return static_cast<ObfuscatedFileUtil*>(sync_file_util());
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.h b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.h
new file mode 100644
index 00000000000..5fb83f082b9
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate.h
@@ -0,0 +1,194 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_
+
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_options.h"
+#include "webkit/browser/fileapi/file_system_quota_util.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace quota {
+class QuotaManagerProxy;
+class SpecialStoragePolicy;
+}
+
+namespace fileapi {
+
+class AsyncFileUtil;
+class FileSystemFileUtil;
+class FileSystemURL;
+class FileSystemUsageCache;
+class ObfuscatedFileUtil;
+class SandboxFileSystemBackend;
+class SandboxFileSystemTestHelper;
+class SandboxQuotaObserver;
+
+// Delegate implementation of the some methods in Sandbox/SyncFileSystemBackend.
+// An instance of this class is created and owned by FileSystemContext.
+class WEBKIT_STORAGE_BROWSER_EXPORT SandboxFileSystemBackendDelegate {
+ public:
+ typedef FileSystemBackend::OpenFileSystemCallback OpenFileSystemCallback;
+
+ // The FileSystem directory name.
+ static const base::FilePath::CharType kFileSystemDirectory[];
+
+ // Origin enumerator interface.
+ // An instance of this interface is assumed to be called on the file thread.
+ class OriginEnumerator {
+ public:
+ virtual ~OriginEnumerator() {}
+
+ // Returns the next origin. Returns empty if there are no more origins.
+ virtual GURL Next() = 0;
+
+ // Returns the current origin's information.
+ virtual bool HasFileSystemType(FileSystemType type) const = 0;
+ };
+
+ SandboxFileSystemBackendDelegate(
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ base::SequencedTaskRunner* file_task_runner,
+ const base::FilePath& profile_path,
+ quota::SpecialStoragePolicy* special_storage_policy,
+ const FileSystemOptions& file_system_options);
+
+ ~SandboxFileSystemBackendDelegate();
+
+ // Performs API-specific validity checks on the given path |url|.
+ // Returns true if access to |url| is valid in this filesystem.
+ bool IsAccessValid(const FileSystemURL& url) const;
+
+ // Returns true if the given |url|'s scheme is allowed to access
+ // filesystem.
+ bool IsAllowedScheme(const GURL& url) const;
+
+ // Returns an origin enumerator of sandbox filesystem.
+ // This method can only be called on the file thread.
+ OriginEnumerator* CreateOriginEnumerator();
+
+ // Gets a base directory path of the sandboxed filesystem that is
+ // specified by |origin_url| and |type|.
+ // (The path is similar to the origin's root path but doesn't contain
+ // the 'unique' part.)
+ // Returns an empty path if the given type is invalid.
+ // This method can only be called on the file thread.
+ base::FilePath GetBaseDirectoryForOriginAndType(
+ const GURL& origin_url,
+ FileSystemType type,
+ bool create);
+
+ // FileSystemBackend helpers.
+ void OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback,
+ const GURL& root_url);
+
+ // FileSystemQuotaUtil helpers.
+ base::PlatformFileError DeleteOriginDataOnFileThread(
+ FileSystemContext* context,
+ quota::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ FileSystemType type);
+ void GetOriginsForTypeOnFileThread(
+ FileSystemType type,
+ std::set<GURL>* origins);
+ void GetOriginsForHostOnFileThread(
+ FileSystemType type,
+ const std::string& host,
+ std::set<GURL>* origins);
+ int64 GetOriginUsageOnFileThread(
+ FileSystemContext* context,
+ const GURL& origin_url,
+ FileSystemType type);
+ void InvalidateUsageCache(
+ const GURL& origin_url,
+ FileSystemType type);
+ void StickyInvalidateUsageCache(
+ const GURL& origin_url,
+ FileSystemType type);
+
+ void CollectOpenFileSystemMetrics(base::PlatformFileError error_code);
+
+ base::SequencedTaskRunner* file_task_runner() {
+ return file_task_runner_.get();
+ }
+
+ AsyncFileUtil* file_util() { return sandbox_file_util_.get(); }
+ FileSystemUsageCache* usage_cache() { return file_system_usage_cache_.get(); }
+ SandboxQuotaObserver* quota_observer() { return quota_observer_.get(); };
+
+ quota::SpecialStoragePolicy* special_storage_policy() {
+ return special_storage_policy_.get();
+ }
+
+ const FileSystemOptions& file_system_options() const {
+ return file_system_options_;
+ }
+
+ FileSystemFileUtil* sync_file_util();
+
+ private:
+ friend class SandboxQuotaObserver;
+ friend class SandboxFileSystemTestHelper;
+
+ // Returns a path to the usage cache file.
+ base::FilePath GetUsageCachePathForOriginAndType(
+ const GURL& origin_url,
+ FileSystemType type);
+
+ // Returns a path to the usage cache file (static version).
+ static base::FilePath GetUsageCachePathForOriginAndType(
+ ObfuscatedFileUtil* sandbox_file_util,
+ const GURL& origin_url,
+ FileSystemType type,
+ base::PlatformFileError* error_out);
+
+ int64 RecalculateUsage(FileSystemContext* context,
+ const GURL& origin,
+ FileSystemType type);
+
+ ObfuscatedFileUtil* obfuscated_file_util();
+
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+
+ scoped_ptr<AsyncFileUtil> sandbox_file_util_;
+ scoped_ptr<FileSystemUsageCache> file_system_usage_cache_;
+ scoped_ptr<SandboxQuotaObserver> quota_observer_;
+
+ scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy_;
+
+ FileSystemOptions file_system_options_;
+
+ // Acccessed only on the file thread.
+ std::set<GURL> visited_origins_;
+
+ std::set<std::pair<GURL, FileSystemType> > sticky_dirty_origins_;
+
+ base::Time next_release_time_for_open_filesystem_stat_;
+
+ base::WeakPtrFactory<SandboxFileSystemBackendDelegate> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxFileSystemBackendDelegate);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc
new file mode 100644
index 00000000000..48c3b8c4e70
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_delegate_unittest.cc
@@ -0,0 +1,82 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h"
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/mock_file_system_options.h"
+
+namespace fileapi {
+
+namespace {
+
+FileSystemURL CreateFileSystemURL(const char* path) {
+ const GURL kOrigin("http://foo/");
+ return FileSystemURL::CreateForTest(
+ kOrigin, kFileSystemTypeTemporary, base::FilePath::FromUTF8Unsafe(path));
+}
+
+} // namespace
+
+class SandboxFileSystemBackendDelegateTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ delegate_.reset(new SandboxFileSystemBackendDelegate(
+ NULL /* quota_manager_proxy */,
+ base::MessageLoopProxy::current().get(),
+ data_dir_.path(),
+ NULL /* special_storage_policy */,
+ CreateAllowFileAccessOptions()));
+ }
+
+ base::ScopedTempDir data_dir_;
+ base::MessageLoop message_loop_;
+ scoped_ptr<SandboxFileSystemBackendDelegate> delegate_;
+};
+
+TEST_F(SandboxFileSystemBackendDelegateTest, IsAccessValid) {
+ // Normal case.
+ EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL("a")));
+
+ // Access to a path with parent references ('..') should be disallowed.
+ EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL("a/../b")));
+
+ // Access from non-allowed scheme should be disallowed.
+ EXPECT_FALSE(delegate_->IsAccessValid(
+ FileSystemURL::CreateForTest(
+ GURL("unknown://bar"), kFileSystemTypeTemporary,
+ base::FilePath::FromUTF8Unsafe("foo"))));
+
+ // Access with restricted name should be disallowed.
+ EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL(".")));
+ EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL("..")));
+
+ // This is also disallowed due to Windows XP parent path handling.
+ EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL("...")));
+
+ // These are identified as unsafe cases due to weird path handling
+ // on Windows.
+ EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL(" ..")));
+ EXPECT_FALSE(delegate_->IsAccessValid(CreateFileSystemURL(".. ")));
+
+ // Similar but safe cases.
+ EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL(" .")));
+ EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL(". ")));
+ EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL("b.")));
+ EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL(".b")));
+
+ // A path that looks like a drive letter.
+ EXPECT_TRUE(delegate_->IsAccessValid(CreateFileSystemURL("c:")));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_backend_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_unittest.cc
new file mode 100644
index 00000000000..c7f03170e42
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_system_backend_unittest.cc
@@ -0,0 +1,325 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/mock_file_system_options.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+// PS stands for path separator.
+#if defined(FILE_PATH_USES_WIN_SEPARATORS)
+#define PS "\\"
+#else
+#define PS "/"
+#endif
+
+namespace fileapi {
+
+namespace {
+
+const struct RootPathTest {
+ fileapi::FileSystemType type;
+ const char* origin_url;
+ const char* expected_path;
+} kRootPathTestCases[] = {
+ { fileapi::kFileSystemTypeTemporary, "http://foo:1/",
+ "000" PS "t" },
+ { fileapi::kFileSystemTypePersistent, "http://foo:1/",
+ "000" PS "p" },
+ { fileapi::kFileSystemTypeTemporary, "http://bar.com/",
+ "001" PS "t" },
+ { fileapi::kFileSystemTypePersistent, "http://bar.com/",
+ "001" PS "p" },
+ { fileapi::kFileSystemTypeTemporary, "https://foo:2/",
+ "002" PS "t" },
+ { fileapi::kFileSystemTypePersistent, "https://foo:2/",
+ "002" PS "p" },
+ { fileapi::kFileSystemTypeTemporary, "https://bar.com/",
+ "003" PS "t" },
+ { fileapi::kFileSystemTypePersistent, "https://bar.com/",
+ "003" PS "p" },
+};
+
+const struct RootPathFileURITest {
+ fileapi::FileSystemType type;
+ const char* origin_url;
+ const char* expected_path;
+ const char* virtual_path;
+} kRootPathFileURITestCases[] = {
+ { fileapi::kFileSystemTypeTemporary, "file:///",
+ "000" PS "t", NULL },
+ { fileapi::kFileSystemTypePersistent, "file:///",
+ "000" PS "p", NULL },
+};
+
+FileSystemURL CreateFileSystemURL(const char* path) {
+ const GURL kOrigin("http://foo/");
+ return FileSystemURL::CreateForTest(
+ kOrigin, kFileSystemTypeTemporary, base::FilePath::FromUTF8Unsafe(path));
+}
+
+void DidOpenFileSystem(base::PlatformFileError* error_out,
+ const GURL& origin_url,
+ const std::string& name,
+ base::PlatformFileError error) {
+ *error_out = error;
+}
+
+} // namespace
+
+class SandboxFileSystemBackendTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ SetUpNewDelegate(CreateAllowFileAccessOptions());
+ }
+
+ void SetUpNewDelegate(const FileSystemOptions& options) {
+ delegate_.reset(new SandboxFileSystemBackendDelegate(
+ NULL /* quota_manager_proxy */,
+ base::MessageLoopProxy::current().get(),
+ data_dir_.path(),
+ NULL /* special_storage_policy */,
+ options));
+ }
+
+ void SetUpNewBackend(const FileSystemOptions& options) {
+ SetUpNewDelegate(options);
+ backend_.reset(new SandboxFileSystemBackend(delegate_.get()));
+ }
+
+ SandboxFileSystemBackendDelegate::OriginEnumerator*
+ CreateOriginEnumerator() const {
+ return backend_->CreateOriginEnumerator();
+ }
+
+ void CreateOriginTypeDirectory(const GURL& origin,
+ fileapi::FileSystemType type) {
+ base::FilePath target = delegate_->
+ GetBaseDirectoryForOriginAndType(origin, type, true);
+ ASSERT_TRUE(!target.empty());
+ ASSERT_TRUE(base::DirectoryExists(target));
+ }
+
+ bool GetRootPath(const GURL& origin_url,
+ fileapi::FileSystemType type,
+ OpenFileSystemMode mode,
+ base::FilePath* root_path) {
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ backend_->OpenFileSystem(
+ origin_url, type, mode,
+ base::Bind(&DidOpenFileSystem, &error));
+ base::MessageLoop::current()->RunUntilIdle();
+ if (error != base::PLATFORM_FILE_OK)
+ return false;
+ base::FilePath returned_root_path =
+ delegate_->GetBaseDirectoryForOriginAndType(
+ origin_url, type, false /* create */);
+ if (root_path)
+ *root_path = returned_root_path;
+ return !returned_root_path.empty();
+ }
+
+ base::FilePath file_system_path() const {
+ return data_dir_.path().Append(
+ SandboxFileSystemBackendDelegate::kFileSystemDirectory);
+ }
+
+ base::ScopedTempDir data_dir_;
+ base::MessageLoop message_loop_;
+ scoped_ptr<SandboxFileSystemBackendDelegate> delegate_;
+ scoped_ptr<SandboxFileSystemBackend> backend_;
+};
+
+TEST_F(SandboxFileSystemBackendTest, Empty) {
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+ scoped_ptr<SandboxFileSystemBackendDelegate::OriginEnumerator> enumerator(
+ CreateOriginEnumerator());
+ ASSERT_TRUE(enumerator->Next().is_empty());
+}
+
+TEST_F(SandboxFileSystemBackendTest, EnumerateOrigins) {
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+ const char* temporary_origins[] = {
+ "http://www.bar.com/",
+ "http://www.foo.com/",
+ "http://www.foo.com:1/",
+ "http://www.example.com:8080/",
+ "http://www.google.com:80/",
+ };
+ const char* persistent_origins[] = {
+ "http://www.bar.com/",
+ "http://www.foo.com:8080/",
+ "http://www.foo.com:80/",
+ };
+ size_t temporary_size = ARRAYSIZE_UNSAFE(temporary_origins);
+ size_t persistent_size = ARRAYSIZE_UNSAFE(persistent_origins);
+ std::set<GURL> temporary_set, persistent_set;
+ for (size_t i = 0; i < temporary_size; ++i) {
+ CreateOriginTypeDirectory(GURL(temporary_origins[i]),
+ fileapi::kFileSystemTypeTemporary);
+ temporary_set.insert(GURL(temporary_origins[i]));
+ }
+ for (size_t i = 0; i < persistent_size; ++i) {
+ CreateOriginTypeDirectory(GURL(persistent_origins[i]),
+ kFileSystemTypePersistent);
+ persistent_set.insert(GURL(persistent_origins[i]));
+ }
+
+ scoped_ptr<SandboxFileSystemBackendDelegate::OriginEnumerator> enumerator(
+ CreateOriginEnumerator());
+ size_t temporary_actual_size = 0;
+ size_t persistent_actual_size = 0;
+ GURL current;
+ while (!(current = enumerator->Next()).is_empty()) {
+ SCOPED_TRACE(testing::Message() << "EnumerateOrigin " << current.spec());
+ if (enumerator->HasFileSystemType(kFileSystemTypeTemporary)) {
+ ASSERT_TRUE(temporary_set.find(current) != temporary_set.end());
+ ++temporary_actual_size;
+ }
+ if (enumerator->HasFileSystemType(kFileSystemTypePersistent)) {
+ ASSERT_TRUE(persistent_set.find(current) != persistent_set.end());
+ ++persistent_actual_size;
+ }
+ }
+
+ EXPECT_EQ(temporary_size, temporary_actual_size);
+ EXPECT_EQ(persistent_size, persistent_actual_size);
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathCreateAndExamine) {
+ std::vector<base::FilePath> returned_root_path(
+ ARRAYSIZE_UNSAFE(kRootPathTestCases));
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+
+ // Create a new root directory.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPath (create) #" << i << " "
+ << kRootPathTestCases[i].expected_path);
+
+ base::FilePath root_path;
+ EXPECT_TRUE(GetRootPath(GURL(kRootPathTestCases[i].origin_url),
+ kRootPathTestCases[i].type,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ &root_path));
+
+ base::FilePath expected = file_system_path().AppendASCII(
+ kRootPathTestCases[i].expected_path);
+ EXPECT_EQ(expected.value(), root_path.value());
+ EXPECT_TRUE(base::DirectoryExists(root_path));
+ ASSERT_TRUE(returned_root_path.size() > i);
+ returned_root_path[i] = root_path;
+ }
+
+ // Get the root directory with create=false and see if we get the
+ // same directory.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPath (get) #" << i << " "
+ << kRootPathTestCases[i].expected_path);
+
+ base::FilePath root_path;
+ EXPECT_TRUE(GetRootPath(GURL(kRootPathTestCases[i].origin_url),
+ kRootPathTestCases[i].type,
+ OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
+ &root_path));
+ ASSERT_TRUE(returned_root_path.size() > i);
+ EXPECT_EQ(returned_root_path[i].value(), root_path.value());
+ }
+}
+
+TEST_F(SandboxFileSystemBackendTest,
+ GetRootPathCreateAndExamineWithNewBackend) {
+ std::vector<base::FilePath> returned_root_path(
+ ARRAYSIZE_UNSAFE(kRootPathTestCases));
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+
+ GURL origin_url("http://foo.com:1/");
+
+ base::FilePath root_path1;
+ EXPECT_TRUE(GetRootPath(origin_url, kFileSystemTypeTemporary,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ &root_path1));
+
+ SetUpNewBackend(CreateDisallowFileAccessOptions());
+ base::FilePath root_path2;
+ EXPECT_TRUE(GetRootPath(origin_url, kFileSystemTypeTemporary,
+ OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
+ &root_path2));
+
+ EXPECT_EQ(root_path1.value(), root_path2.value());
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathGetWithoutCreate) {
+ SetUpNewBackend(CreateDisallowFileAccessOptions());
+
+ // Try to get a root directory without creating.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPath (create=false) #" << i << " "
+ << kRootPathTestCases[i].expected_path);
+ EXPECT_FALSE(GetRootPath(GURL(kRootPathTestCases[i].origin_url),
+ kRootPathTestCases[i].type,
+ OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
+ NULL));
+ }
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathInIncognito) {
+ SetUpNewBackend(CreateIncognitoFileSystemOptions());
+
+ // Try to get a root directory.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPath (incognito) #" << i << " "
+ << kRootPathTestCases[i].expected_path);
+ EXPECT_FALSE(
+ GetRootPath(GURL(kRootPathTestCases[i].origin_url),
+ kRootPathTestCases[i].type,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ NULL));
+ }
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathFileURI) {
+ SetUpNewBackend(CreateDisallowFileAccessOptions());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathFileURITestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPathFileURI (disallow) #"
+ << i << " " << kRootPathFileURITestCases[i].expected_path);
+ EXPECT_FALSE(
+ GetRootPath(GURL(kRootPathFileURITestCases[i].origin_url),
+ kRootPathFileURITestCases[i].type,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ NULL));
+ }
+}
+
+TEST_F(SandboxFileSystemBackendTest, GetRootPathFileURIWithAllowFlag) {
+ SetUpNewBackend(CreateAllowFileAccessOptions());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRootPathFileURITestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "RootPathFileURI (allow) #"
+ << i << " " << kRootPathFileURITestCases[i].expected_path);
+ base::FilePath root_path;
+ EXPECT_TRUE(GetRootPath(GURL(kRootPathFileURITestCases[i].origin_url),
+ kRootPathFileURITestCases[i].type,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ &root_path));
+ base::FilePath expected = file_system_path().AppendASCII(
+ kRootPathFileURITestCases[i].expected_path);
+ EXPECT_EQ(expected.value(), root_path.value());
+ EXPECT_TRUE(base::DirectoryExists(root_path));
+ }
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.cc b/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.cc
new file mode 100644
index 00000000000..97d7a5f2e4a
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_file_system_test_helper.h"
+
+#include "base/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_operation_runner.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend.h"
+#include "webkit/browser/quota/mock_special_storage_policy.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+SandboxFileSystemTestHelper::SandboxFileSystemTestHelper(
+ const GURL& origin, FileSystemType type)
+ : origin_(origin), type_(type), file_util_(NULL) {
+}
+
+SandboxFileSystemTestHelper::SandboxFileSystemTestHelper()
+ : origin_(GURL("http://foo.com")),
+ type_(kFileSystemTypeTemporary),
+ file_util_(NULL) {
+}
+
+SandboxFileSystemTestHelper::~SandboxFileSystemTestHelper() {
+}
+
+void SandboxFileSystemTestHelper::SetUp(const base::FilePath& base_dir) {
+ SetUp(base_dir, NULL);
+}
+
+void SandboxFileSystemTestHelper::SetUp(
+ FileSystemContext* file_system_context) {
+ file_system_context_ = file_system_context;
+
+ SetUpFileSystem();
+}
+
+void SandboxFileSystemTestHelper::SetUp(
+ const base::FilePath& base_dir,
+ quota::QuotaManagerProxy* quota_manager_proxy) {
+ file_system_context_ = CreateFileSystemContextForTesting(
+ quota_manager_proxy, base_dir);
+
+ SetUpFileSystem();
+}
+
+void SandboxFileSystemTestHelper::TearDown() {
+ file_system_context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+base::FilePath SandboxFileSystemTestHelper::GetOriginRootPath() {
+ return file_system_context_->sandbox_delegate()->
+ GetBaseDirectoryForOriginAndType(origin_, type_, false);
+}
+
+base::FilePath SandboxFileSystemTestHelper::GetLocalPath(
+ const base::FilePath& path) {
+ DCHECK(file_util_);
+ base::FilePath local_path;
+ scoped_ptr<FileSystemOperationContext> context(NewOperationContext());
+ file_util_->GetLocalFilePath(context.get(), CreateURL(path), &local_path);
+ return local_path;
+}
+
+base::FilePath SandboxFileSystemTestHelper::GetLocalPathFromASCII(
+ const std::string& path) {
+ return GetLocalPath(base::FilePath().AppendASCII(path));
+}
+
+base::FilePath SandboxFileSystemTestHelper::GetUsageCachePath() const {
+ return file_system_context_->sandbox_delegate()->
+ GetUsageCachePathForOriginAndType(origin_, type_);
+}
+
+FileSystemURL SandboxFileSystemTestHelper::CreateURL(
+ const base::FilePath& path) const {
+ return file_system_context_->CreateCrackedFileSystemURL(origin_, type_, path);
+}
+
+int64 SandboxFileSystemTestHelper::GetCachedOriginUsage() const {
+ return file_system_context_->GetQuotaUtil(type_)
+ ->GetOriginUsageOnFileThread(file_system_context_.get(), origin_, type_);
+}
+
+int64 SandboxFileSystemTestHelper::ComputeCurrentOriginUsage() {
+ usage_cache()->CloseCacheFiles();
+ int64 size = base::ComputeDirectorySize(GetOriginRootPath());
+ if (base::PathExists(GetUsageCachePath()))
+ size -= FileSystemUsageCache::kUsageFileSize;
+ return size;
+}
+
+int64
+SandboxFileSystemTestHelper::ComputeCurrentDirectoryDatabaseUsage() {
+ return base::ComputeDirectorySize(
+ GetOriginRootPath().AppendASCII("Paths"));
+}
+
+FileSystemOperationRunner* SandboxFileSystemTestHelper::operation_runner() {
+ return file_system_context_->operation_runner();
+}
+
+FileSystemOperationContext*
+SandboxFileSystemTestHelper::NewOperationContext() {
+ DCHECK(file_system_context_.get());
+ FileSystemOperationContext* context =
+ new FileSystemOperationContext(file_system_context_.get());
+ context->set_update_observers(
+ *file_system_context_->GetUpdateObservers(type_));
+ return context;
+}
+
+void SandboxFileSystemTestHelper::AddFileChangeObserver(
+ FileChangeObserver* observer) {
+ file_system_context_->sandbox_backend()->
+ AddFileChangeObserver(type_, observer, NULL);
+}
+
+FileSystemUsageCache* SandboxFileSystemTestHelper::usage_cache() {
+ return file_system_context()->sandbox_delegate()->usage_cache();
+}
+
+void SandboxFileSystemTestHelper::SetUpFileSystem() {
+ DCHECK(file_system_context_.get());
+ DCHECK(file_system_context_->sandbox_backend()->CanHandleType(type_));
+
+ file_util_ = file_system_context_->GetFileUtil(type_);
+ DCHECK(file_util_);
+
+ // Prepare the origin's root directory.
+ file_system_context_->sandbox_delegate()->
+ GetBaseDirectoryForOriginAndType(origin_, type_, true /* create */);
+
+ // Initialize the usage cache file.
+ base::FilePath usage_cache_path = GetUsageCachePath();
+ if (!usage_cache_path.empty())
+ usage_cache()->UpdateUsage(usage_cache_path, 0);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.h b/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.h
new file mode 100644
index 00000000000..5a48872326c
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_file_system_test_helper.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_TEST_HELPER_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_TEST_HELPER_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "url/gurl.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+#include "webkit/browser/fileapi/task_runner_bound_observer_list.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+#include "webkit/common/quota/quota_types.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace quota {
+class QuotaManagerProxy;
+}
+
+namespace fileapi {
+
+class FileSystemContext;
+class FileSystemFileUtil;
+class FileSystemOperationContext;
+class FileSystemOperationRunner;
+
+// Filesystem test helper class that encapsulates test environment for
+// a given {origin, type} pair. This helper only works for sandboxed
+// file systems (Temporary or Persistent).
+class SandboxFileSystemTestHelper {
+ public:
+ SandboxFileSystemTestHelper(const GURL& origin, FileSystemType type);
+ SandboxFileSystemTestHelper();
+ ~SandboxFileSystemTestHelper();
+
+ void SetUp(const base::FilePath& base_dir);
+ // If you want to use more than one SandboxFileSystemTestHelper in
+ // a single base directory, they have to share a context, so that they don't
+ // have multiple databases fighting over the lock to the origin directory
+ // [deep down inside ObfuscatedFileUtil].
+ void SetUp(FileSystemContext* file_system_context);
+ void SetUp(const base::FilePath& base_dir,
+ quota::QuotaManagerProxy* quota_manager_proxy);
+ void TearDown();
+
+ base::FilePath GetOriginRootPath();
+ base::FilePath GetLocalPath(const base::FilePath& path);
+ base::FilePath GetLocalPathFromASCII(const std::string& path);
+
+ // Returns empty path if filesystem type is neither temporary nor persistent.
+ base::FilePath GetUsageCachePath() const;
+
+ FileSystemURL CreateURL(const base::FilePath& path) const;
+ FileSystemURL CreateURLFromUTF8(const std::string& utf8) const {
+ return CreateURL(base::FilePath::FromUTF8Unsafe(utf8));
+ }
+
+ // This returns cached usage size returned by QuotaUtil.
+ int64 GetCachedOriginUsage() const;
+
+ // This doesn't work with OFSFU.
+ int64 ComputeCurrentOriginUsage();
+
+ int64 ComputeCurrentDirectoryDatabaseUsage();
+
+ FileSystemOperationRunner* operation_runner();
+ FileSystemOperationContext* NewOperationContext();
+
+ void AddFileChangeObserver(FileChangeObserver* observer);
+
+ FileSystemContext* file_system_context() const {
+ return file_system_context_.get();
+ }
+
+ const GURL& origin() const { return origin_; }
+ FileSystemType type() const { return type_; }
+ quota::StorageType storage_type() const {
+ return FileSystemTypeToQuotaStorageType(type_);
+ }
+ FileSystemFileUtil* file_util() const { return file_util_; }
+ FileSystemUsageCache* usage_cache();
+
+ private:
+ void SetUpFileSystem();
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+
+ const GURL origin_;
+ const FileSystemType type_;
+ FileSystemFileUtil* file_util_;
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_TEST_HELPER_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.cc b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.cc
new file mode 100644
index 00000000000..7ed4e8ce4af
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.cc
@@ -0,0 +1,105 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_isolated_origin_database.h"
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "webkit/browser/fileapi/sandbox_origin_database.h"
+
+namespace fileapi {
+
+// Special directory name for isolated origin.
+const base::FilePath::CharType
+SandboxIsolatedOriginDatabase::kOriginDirectory[] = FILE_PATH_LITERAL("iso");
+
+SandboxIsolatedOriginDatabase::SandboxIsolatedOriginDatabase(
+ const std::string& origin,
+ const base::FilePath& file_system_directory)
+ : migration_checked_(false),
+ origin_(origin),
+ file_system_directory_(file_system_directory) {
+}
+
+SandboxIsolatedOriginDatabase::~SandboxIsolatedOriginDatabase() {
+}
+
+bool SandboxIsolatedOriginDatabase::HasOriginPath(
+ const std::string& origin) {
+ MigrateDatabaseIfNeeded();
+ return (origin_ == origin);
+}
+
+bool SandboxIsolatedOriginDatabase::GetPathForOrigin(
+ const std::string& origin, base::FilePath* directory) {
+ MigrateDatabaseIfNeeded();
+ if (origin != origin_)
+ return false;
+ *directory = base::FilePath(kOriginDirectory);
+ return true;
+}
+
+bool SandboxIsolatedOriginDatabase::RemovePathForOrigin(
+ const std::string& origin) {
+ return true;
+}
+
+bool SandboxIsolatedOriginDatabase::ListAllOrigins(
+ std::vector<OriginRecord>* origins) {
+ MigrateDatabaseIfNeeded();
+ origins->push_back(OriginRecord(origin_, base::FilePath(kOriginDirectory)));
+ return true;
+}
+
+void SandboxIsolatedOriginDatabase::DropDatabase() {
+}
+
+void SandboxIsolatedOriginDatabase::MigrateBackDatabase(
+ const std::string& origin,
+ const base::FilePath& file_system_directory,
+ SandboxOriginDatabase* database) {
+ base::FilePath isolated_directory =
+ file_system_directory.Append(kOriginDirectory);
+
+ if (database->HasOriginPath(origin)) {
+ // Don't bother.
+ base::DeleteFile(isolated_directory, true /* recursive */);
+ return;
+ }
+
+ base::FilePath directory_name;
+ if (database->GetPathForOrigin(origin, &directory_name)) {
+ base::FilePath origin_directory =
+ file_system_directory.Append(directory_name);
+ base::DeleteFile(origin_directory, true /* recursive */);
+ base::Move(isolated_directory, origin_directory);
+ }
+}
+
+void SandboxIsolatedOriginDatabase::MigrateDatabaseIfNeeded() {
+ if (migration_checked_)
+ return;
+
+ migration_checked_ = true;
+ // See if we have non-isolated version of sandbox database.
+ scoped_ptr<SandboxOriginDatabase> database(
+ new SandboxOriginDatabase(file_system_directory_));
+ if (!database->HasOriginPath(origin_))
+ return;
+
+ base::FilePath directory_name;
+ if (database->GetPathForOrigin(origin_, &directory_name) &&
+ directory_name != base::FilePath(kOriginDirectory)) {
+ base::FilePath from_path = file_system_directory_.Append(directory_name);
+ base::FilePath to_path = file_system_directory_.Append(kOriginDirectory);
+
+ if (base::PathExists(to_path))
+ base::DeleteFile(to_path, true /* recursive */);
+ base::Move(from_path, to_path);
+ }
+
+ database->RemoveDatabase();
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.h b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.h
new file mode 100644
index 00000000000..2505659b064
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database.h
@@ -0,0 +1,49 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_
+
+#include "webkit/browser/fileapi/sandbox_origin_database_interface.h"
+
+namespace fileapi {
+
+class SandboxOriginDatabase;
+
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxIsolatedOriginDatabase
+ : public SandboxOriginDatabaseInterface {
+ public:
+ static const base::FilePath::CharType kOriginDirectory[];
+
+ explicit SandboxIsolatedOriginDatabase(
+ const std::string& origin,
+ const base::FilePath& file_system_directory);
+ virtual ~SandboxIsolatedOriginDatabase();
+
+ // SandboxOriginDatabaseInterface overrides.
+ virtual bool HasOriginPath(const std::string& origin) OVERRIDE;
+ virtual bool GetPathForOrigin(const std::string& origin,
+ base::FilePath* directory) OVERRIDE;
+ virtual bool RemovePathForOrigin(const std::string& origin) OVERRIDE;
+ virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) OVERRIDE;
+ virtual void DropDatabase() OVERRIDE;
+
+ static void MigrateBackDatabase(
+ const std::string& origin,
+ const base::FilePath& file_system_directory,
+ SandboxOriginDatabase* origin_database);
+
+ private:
+ void MigrateDatabaseIfNeeded();
+
+ bool migration_checked_;
+ const std::string origin_;
+ const base::FilePath file_system_directory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxIsolatedOriginDatabase);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database_unittest.cc
new file mode 100644
index 00000000000..7eeab2185b6
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_isolated_origin_database_unittest.cc
@@ -0,0 +1,87 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/sandbox_isolated_origin_database.h"
+#include "webkit/browser/fileapi/sandbox_origin_database.h"
+
+namespace fileapi {
+
+TEST(SandboxIsolatedOriginDatabaseTest, BasicTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+
+ std::string kOrigin("origin");
+ SandboxIsolatedOriginDatabase database(kOrigin, dir.path());
+
+ EXPECT_TRUE(database.HasOriginPath(kOrigin));
+
+ base::FilePath path1, path2;
+
+ EXPECT_FALSE(database.GetPathForOrigin(std::string(), &path1));
+ EXPECT_FALSE(database.GetPathForOrigin("foo", &path1));
+
+ EXPECT_TRUE(database.HasOriginPath(kOrigin));
+ EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path1));
+ EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path2));
+ EXPECT_FALSE(path1.empty());
+ EXPECT_FALSE(path2.empty());
+ EXPECT_EQ(path1, path2);
+}
+
+TEST(SandboxIsolatedOriginDatabaseTest, MigrationTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+
+ std::string kOrigin("origin");
+ std::string kFakeDirectoryData("0123456789");
+ base::FilePath path;
+ base::FilePath old_db_path;
+
+ // Initialize the directory with one origin using the regular
+ // SandboxOriginDatabase.
+ {
+ SandboxOriginDatabase database_old(dir.path());
+ old_db_path = database_old.GetDatabasePath();
+ EXPECT_FALSE(base::PathExists(old_db_path));
+ EXPECT_TRUE(database_old.GetPathForOrigin(kOrigin, &path));
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(base::DirectoryExists(old_db_path));
+
+ // Populate the origin directory with some fake data.
+ base::FilePath directory_db_path = dir.path().Append(path);
+ ASSERT_TRUE(file_util::CreateDirectory(directory_db_path));
+ EXPECT_EQ(static_cast<int>(kFakeDirectoryData.size()),
+ file_util::WriteFile(directory_db_path.AppendASCII("dummy"),
+ kFakeDirectoryData.data(),
+ kFakeDirectoryData.size()));
+ }
+
+ // Re-open the directory using sandboxIsolatedOriginDatabase.
+ SandboxIsolatedOriginDatabase database(kOrigin, dir.path());
+
+ // The database is migrated from the old one, so we should still
+ // see the same origin.
+ EXPECT_TRUE(database.HasOriginPath(kOrigin));
+ EXPECT_TRUE(database.GetPathForOrigin(kOrigin, &path));
+ EXPECT_FALSE(path.empty());
+
+ // The directory content must be kept (or migrated if necessary),
+ // so we should see the same fake data.
+ std::string origin_db_data;
+ base::FilePath directory_db_path = dir.path().Append(path);
+ EXPECT_TRUE(base::DirectoryExists(directory_db_path));
+ EXPECT_TRUE(base::PathExists(directory_db_path.AppendASCII("dummy")));
+ EXPECT_TRUE(file_util::ReadFileToString(
+ directory_db_path.AppendASCII("dummy"), &origin_db_data));
+ EXPECT_EQ(kFakeDirectoryData, origin_db_data);
+
+ // After the migration the database must be gone.
+ EXPECT_FALSE(base::PathExists(old_db_path));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database.cc b/chromium/webkit/browser/fileapi/sandbox_origin_database.cc
new file mode 100644
index 00000000000..4ce83017cc0
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_origin_database.cc
@@ -0,0 +1,341 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_origin_database.h"
+
+#include <set>
+#include <utility>
+
+#include "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/format_macros.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace {
+
+const base::FilePath::CharType kOriginDatabaseName[] =
+ FILE_PATH_LITERAL("Origins");
+const char kOriginKeyPrefix[] = "ORIGIN:";
+const char kLastPathKey[] = "LAST_PATH";
+const int64 kMinimumReportIntervalHours = 1;
+const char kInitStatusHistogramLabel[] = "FileSystem.OriginDatabaseInit";
+const char kDatabaseRepairHistogramLabel[] = "FileSystem.OriginDatabaseRepair";
+
+enum InitStatus {
+ INIT_STATUS_OK = 0,
+ INIT_STATUS_CORRUPTION,
+ INIT_STATUS_IO_ERROR,
+ INIT_STATUS_UNKNOWN_ERROR,
+ INIT_STATUS_MAX
+};
+
+enum RepairResult {
+ DB_REPAIR_SUCCEEDED = 0,
+ DB_REPAIR_FAILED,
+ DB_REPAIR_MAX
+};
+
+std::string OriginToOriginKey(const std::string& origin) {
+ std::string key(kOriginKeyPrefix);
+ return key + origin;
+}
+
+const char* LastPathKey() {
+ return kLastPathKey;
+}
+
+} // namespace
+
+namespace fileapi {
+
+SandboxOriginDatabase::SandboxOriginDatabase(
+ const base::FilePath& file_system_directory)
+ : file_system_directory_(file_system_directory) {
+}
+
+SandboxOriginDatabase::~SandboxOriginDatabase() {
+}
+
+bool SandboxOriginDatabase::Init(InitOption init_option,
+ RecoveryOption recovery_option) {
+ if (db_)
+ return true;
+
+ base::FilePath db_path = GetDatabasePath();
+ if (init_option == FAIL_IF_NONEXISTENT && !base::PathExists(db_path))
+ return false;
+
+ std::string path = FilePathToString(db_path);
+ leveldb::Options options;
+ options.max_open_files = 0; // Use minimum.
+ options.create_if_missing = true;
+ leveldb::DB* db;
+ leveldb::Status status = leveldb::DB::Open(options, path, &db);
+ ReportInitStatus(status);
+ if (status.ok()) {
+ db_.reset(db);
+ return true;
+ }
+ HandleError(FROM_HERE, status);
+
+ // Corruption due to missing necessary MANIFEST-* file causes IOError instead
+ // of Corruption error.
+ // Try to repair database even when IOError case.
+ if (!status.IsCorruption() && !status.IsIOError())
+ return false;
+
+ switch (recovery_option) {
+ case FAIL_ON_CORRUPTION:
+ return false;
+ case REPAIR_ON_CORRUPTION:
+ LOG(WARNING) << "Attempting to repair SandboxOriginDatabase.";
+
+ if (RepairDatabase(path)) {
+ UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
+ DB_REPAIR_SUCCEEDED, DB_REPAIR_MAX);
+ LOG(WARNING) << "Repairing SandboxOriginDatabase completed.";
+ return true;
+ }
+ UMA_HISTOGRAM_ENUMERATION(kDatabaseRepairHistogramLabel,
+ DB_REPAIR_FAILED, DB_REPAIR_MAX);
+ // fall through
+ case DELETE_ON_CORRUPTION:
+ if (!base::DeleteFile(file_system_directory_, true))
+ return false;
+ if (!file_util::CreateDirectory(file_system_directory_))
+ return false;
+ return Init(init_option, FAIL_ON_CORRUPTION);
+ }
+ NOTREACHED();
+ return false;
+}
+
+bool SandboxOriginDatabase::RepairDatabase(const std::string& db_path) {
+ DCHECK(!db_.get());
+ leveldb::Options options;
+ options.max_open_files = 0; // Use minimum.
+ if (!leveldb::RepairDB(db_path, options).ok() ||
+ !Init(FAIL_IF_NONEXISTENT, FAIL_ON_CORRUPTION)) {
+ LOG(WARNING) << "Failed to repair SandboxOriginDatabase.";
+ return false;
+ }
+
+ // See if the repaired entries match with what we have on disk.
+ std::set<base::FilePath> directories;
+ base::FileEnumerator file_enum(file_system_directory_,
+ false /* recursive */,
+ base::FileEnumerator::DIRECTORIES);
+ base::FilePath path_each;
+ while (!(path_each = file_enum.Next()).empty())
+ directories.insert(path_each.BaseName());
+ std::set<base::FilePath>::iterator db_dir_itr =
+ directories.find(base::FilePath(kOriginDatabaseName));
+ // Make sure we have the database file in its directory and therefore we are
+ // working on the correct path.
+ DCHECK(db_dir_itr != directories.end());
+ directories.erase(db_dir_itr);
+
+ std::vector<OriginRecord> origins;
+ if (!ListAllOrigins(&origins)) {
+ DropDatabase();
+ return false;
+ }
+
+ // Delete any obsolete entries from the origins database.
+ for (std::vector<OriginRecord>::iterator db_origin_itr = origins.begin();
+ db_origin_itr != origins.end();
+ ++db_origin_itr) {
+ std::set<base::FilePath>::iterator dir_itr =
+ directories.find(db_origin_itr->path);
+ if (dir_itr == directories.end()) {
+ if (!RemovePathForOrigin(db_origin_itr->origin)) {
+ DropDatabase();
+ return false;
+ }
+ } else {
+ directories.erase(dir_itr);
+ }
+ }
+
+ // Delete any directories not listed in the origins database.
+ for (std::set<base::FilePath>::iterator dir_itr = directories.begin();
+ dir_itr != directories.end();
+ ++dir_itr) {
+ if (!base::DeleteFile(file_system_directory_.Append(*dir_itr),
+ true /* recursive */)) {
+ DropDatabase();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void SandboxOriginDatabase::HandleError(
+ const tracked_objects::Location& from_here,
+ const leveldb::Status& status) {
+ db_.reset();
+ LOG(ERROR) << "SandboxOriginDatabase failed at: "
+ << from_here.ToString() << " with error: " << status.ToString();
+}
+
+void SandboxOriginDatabase::ReportInitStatus(const leveldb::Status& status) {
+ base::Time now = base::Time::Now();
+ base::TimeDelta minimum_interval =
+ base::TimeDelta::FromHours(kMinimumReportIntervalHours);
+ if (last_reported_time_ + minimum_interval >= now)
+ return;
+ last_reported_time_ = now;
+
+ if (status.ok()) {
+ UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
+ INIT_STATUS_OK, INIT_STATUS_MAX);
+ } else if (status.IsCorruption()) {
+ UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
+ INIT_STATUS_CORRUPTION, INIT_STATUS_MAX);
+ } else if (status.IsIOError()) {
+ UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
+ INIT_STATUS_IO_ERROR, INIT_STATUS_MAX);
+ } else {
+ UMA_HISTOGRAM_ENUMERATION(kInitStatusHistogramLabel,
+ INIT_STATUS_UNKNOWN_ERROR, INIT_STATUS_MAX);
+ }
+}
+
+bool SandboxOriginDatabase::HasOriginPath(const std::string& origin) {
+ if (!Init(FAIL_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
+ return false;
+ if (origin.empty())
+ return false;
+ std::string path;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), OriginToOriginKey(origin), &path);
+ if (status.ok())
+ return true;
+ if (status.IsNotFound())
+ return false;
+ HandleError(FROM_HERE, status);
+ return false;
+}
+
+bool SandboxOriginDatabase::GetPathForOrigin(
+ const std::string& origin, base::FilePath* directory) {
+ if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
+ return false;
+ DCHECK(directory);
+ if (origin.empty())
+ return false;
+ std::string path_string;
+ std::string origin_key = OriginToOriginKey(origin);
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), origin_key, &path_string);
+ if (status.IsNotFound()) {
+ int last_path_number;
+ if (!GetLastPathNumber(&last_path_number))
+ return false;
+ path_string = base::StringPrintf("%03u", last_path_number + 1);
+ // store both back as a single transaction
+ leveldb::WriteBatch batch;
+ batch.Put(LastPathKey(), path_string);
+ batch.Put(origin_key, path_string);
+ status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ }
+ if (status.ok()) {
+ *directory = StringToFilePath(path_string);
+ return true;
+ }
+ HandleError(FROM_HERE, status);
+ return false;
+}
+
+bool SandboxOriginDatabase::RemovePathForOrigin(const std::string& origin) {
+ if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION))
+ return false;
+ leveldb::Status status =
+ db_->Delete(leveldb::WriteOptions(), OriginToOriginKey(origin));
+ if (status.ok() || status.IsNotFound())
+ return true;
+ HandleError(FROM_HERE, status);
+ return false;
+}
+
+bool SandboxOriginDatabase::ListAllOrigins(
+ std::vector<OriginRecord>* origins) {
+ DCHECK(origins);
+ if (!Init(CREATE_IF_NONEXISTENT, REPAIR_ON_CORRUPTION)) {
+ origins->clear();
+ return false;
+ }
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
+ std::string origin_key_prefix = OriginToOriginKey(std::string());
+ iter->Seek(origin_key_prefix);
+ origins->clear();
+ while (iter->Valid() &&
+ StartsWithASCII(iter->key().ToString(), origin_key_prefix, true)) {
+ std::string origin =
+ iter->key().ToString().substr(origin_key_prefix.length());
+ base::FilePath path = StringToFilePath(iter->value().ToString());
+ origins->push_back(OriginRecord(origin, path));
+ iter->Next();
+ }
+ return true;
+}
+
+void SandboxOriginDatabase::DropDatabase() {
+ db_.reset();
+}
+
+base::FilePath SandboxOriginDatabase::GetDatabasePath() const {
+ return file_system_directory_.Append(kOriginDatabaseName);
+}
+
+void SandboxOriginDatabase::RemoveDatabase() {
+ DropDatabase();
+ base::DeleteFile(GetDatabasePath(), true /* recursive */);
+}
+
+bool SandboxOriginDatabase::GetLastPathNumber(int* number) {
+ DCHECK(db_);
+ DCHECK(number);
+ std::string number_string;
+ leveldb::Status status =
+ db_->Get(leveldb::ReadOptions(), LastPathKey(), &number_string);
+ if (status.ok())
+ return base::StringToInt(number_string, number);
+ if (!status.IsNotFound()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ // Verify that this is a totally new database, and initialize it.
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
+ iter->SeekToFirst();
+ if (iter->Valid()) { // DB was not empty, but had no last path number!
+ LOG(ERROR) << "File system origin database is corrupt!";
+ return false;
+ }
+ // This is always the first write into the database. If we ever add a
+ // version number, they should go in in a single transaction.
+ status =
+ db_->Put(leveldb::WriteOptions(), LastPathKey(), std::string("-1"));
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return false;
+ }
+ *number = -1;
+ return true;
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database.h b/chromium/webkit/browser/fileapi/sandbox_origin_database.h
new file mode 100644
index 00000000000..94cde472ade
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_origin_database.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "webkit/browser/fileapi/sandbox_origin_database_interface.h"
+
+namespace leveldb {
+class DB;
+class Status;
+}
+
+namespace tracked_objects {
+class Location;
+}
+
+namespace fileapi {
+
+// All methods of this class other than the constructor may be used only from
+// the browser's FILE thread. The constructor may be used on any thread.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxOriginDatabase
+ : public SandboxOriginDatabaseInterface {
+ public:
+ // Only one instance of SandboxOriginDatabase should exist for a given path
+ // at a given time.
+ explicit SandboxOriginDatabase(const base::FilePath& file_system_directory);
+ virtual ~SandboxOriginDatabase();
+
+ // SandboxOriginDatabaseInterface overrides.
+ virtual bool HasOriginPath(const std::string& origin) OVERRIDE;
+ virtual bool GetPathForOrigin(const std::string& origin,
+ base::FilePath* directory) OVERRIDE;
+ virtual bool RemovePathForOrigin(const std::string& origin) OVERRIDE;
+ virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) OVERRIDE;
+ virtual void DropDatabase() OVERRIDE;
+
+ base::FilePath GetDatabasePath() const;
+ void RemoveDatabase();
+
+ private:
+ enum RecoveryOption {
+ REPAIR_ON_CORRUPTION,
+ DELETE_ON_CORRUPTION,
+ FAIL_ON_CORRUPTION,
+ };
+
+ enum InitOption {
+ CREATE_IF_NONEXISTENT,
+ FAIL_IF_NONEXISTENT,
+ };
+
+ bool Init(InitOption init_option, RecoveryOption recovery_option);
+ bool RepairDatabase(const std::string& db_path);
+ void HandleError(const tracked_objects::Location& from_here,
+ const leveldb::Status& status);
+ void ReportInitStatus(const leveldb::Status& status);
+ bool GetLastPathNumber(int* number);
+
+ base::FilePath file_system_directory_;
+ scoped_ptr<leveldb::DB> db_;
+ base::Time last_reported_time_;
+ DISALLOW_COPY_AND_ASSIGN(SandboxOriginDatabase);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.cc b/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.cc
new file mode 100644
index 00000000000..e2f80c58b32
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.cc
@@ -0,0 +1,20 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_origin_database_interface.h"
+
+namespace fileapi {
+
+SandboxOriginDatabaseInterface::OriginRecord::OriginRecord() {
+}
+
+SandboxOriginDatabaseInterface::OriginRecord::OriginRecord(
+ const std::string& origin_in, const base::FilePath& path_in)
+ : origin(origin_in), path(path_in) {
+}
+
+SandboxOriginDatabaseInterface::OriginRecord::~OriginRecord() {
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.h b/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.h
new file mode 100644
index 00000000000..8b88f9349af
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_origin_database_interface.h
@@ -0,0 +1,55 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace fileapi {
+
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE SandboxOriginDatabaseInterface {
+ public:
+ struct WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE OriginRecord {
+ std::string origin;
+ base::FilePath path;
+
+ OriginRecord();
+ OriginRecord(const std::string& origin, const base::FilePath& path);
+ ~OriginRecord();
+ };
+
+ virtual ~SandboxOriginDatabaseInterface() {}
+
+ // Returns true if the origin's path is included in this database.
+ virtual bool HasOriginPath(const std::string& origin) = 0;
+
+ // This will produce a unique path and add it to its database, if it's not
+ // already present.
+ virtual bool GetPathForOrigin(const std::string& origin,
+ base::FilePath* directory) = 0;
+
+ // Removes the origin's path from the database.
+ // Returns success if the origin has been successfully removed, or
+ // the origin is not found.
+ // (This doesn't remove the actual path).
+ virtual bool RemovePathForOrigin(const std::string& origin) = 0;
+
+ // Lists all origins in this database.
+ virtual bool ListAllOrigins(std::vector<OriginRecord>* origins) = 0;
+
+ // This will release all database resources in use; call it to save memory.
+ virtual void DropDatabase() = 0;
+
+ protected:
+ SandboxOriginDatabaseInterface() {}
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_
diff --git a/chromium/webkit/browser/fileapi/sandbox_origin_database_unittest.cc b/chromium/webkit/browser/fileapi/sandbox_origin_database_unittest.cc
new file mode 100644
index 00000000000..867dc273aad
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_origin_database_unittest.cc
@@ -0,0 +1,307 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <functional>
+#include <limits>
+#include <string>
+
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/platform_file.h"
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/leveldatabase/src/db/filename.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "webkit/browser/fileapi/sandbox_database_test_helper.h"
+#include "webkit/browser/fileapi/sandbox_origin_database.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+namespace {
+const base::FilePath::CharType kFileSystemDirName[] =
+ FILE_PATH_LITERAL("File System");
+const base::FilePath::CharType kOriginDatabaseName[] =
+ FILE_PATH_LITERAL("Origins");
+} // namespace
+
+TEST(SandboxOriginDatabaseTest, BasicTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(file_util::CreateDirectory(kFSDir));
+
+ SandboxOriginDatabase database(kFSDir);
+ std::string origin("origin");
+
+ EXPECT_FALSE(database.HasOriginPath(origin));
+ // Double-check to make sure that had no side effects.
+ EXPECT_FALSE(database.HasOriginPath(origin));
+
+ base::FilePath path0;
+ base::FilePath path1;
+
+ // Empty strings aren't valid origins.
+ EXPECT_FALSE(database.GetPathForOrigin(std::string(), &path0));
+
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path0));
+ EXPECT_TRUE(database.HasOriginPath(origin));
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path1));
+ EXPECT_FALSE(path0.empty());
+ EXPECT_FALSE(path1.empty());
+ EXPECT_EQ(path0, path1);
+
+ EXPECT_TRUE(base::PathExists(kFSDir.Append(kOriginDatabaseName)));
+}
+
+TEST(SandboxOriginDatabaseTest, TwoPathTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(file_util::CreateDirectory(kFSDir));
+
+ SandboxOriginDatabase database(kFSDir);
+ std::string origin0("origin0");
+ std::string origin1("origin1");
+
+ EXPECT_FALSE(database.HasOriginPath(origin0));
+ EXPECT_FALSE(database.HasOriginPath(origin1));
+
+ base::FilePath path0;
+ base::FilePath path1;
+ EXPECT_TRUE(database.GetPathForOrigin(origin0, &path0));
+ EXPECT_TRUE(database.HasOriginPath(origin0));
+ EXPECT_FALSE(database.HasOriginPath(origin1));
+ EXPECT_TRUE(database.GetPathForOrigin(origin1, &path1));
+ EXPECT_TRUE(database.HasOriginPath(origin1));
+ EXPECT_FALSE(path0.empty());
+ EXPECT_FALSE(path1.empty());
+ EXPECT_NE(path0, path1);
+
+ EXPECT_TRUE(base::PathExists(kFSDir.Append(kOriginDatabaseName)));
+}
+
+TEST(SandboxOriginDatabaseTest, DropDatabaseTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(file_util::CreateDirectory(kFSDir));
+
+ SandboxOriginDatabase database(kFSDir);
+ std::string origin("origin");
+
+ EXPECT_FALSE(database.HasOriginPath(origin));
+
+ base::FilePath path0;
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path0));
+ EXPECT_TRUE(database.HasOriginPath(origin));
+ EXPECT_FALSE(path0.empty());
+
+ EXPECT_TRUE(base::PathExists(kFSDir.Append(kOriginDatabaseName)));
+
+ database.DropDatabase();
+
+ base::FilePath path1;
+ EXPECT_TRUE(database.HasOriginPath(origin));
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path1));
+ EXPECT_FALSE(path1.empty());
+ EXPECT_EQ(path0, path1);
+}
+
+TEST(SandboxOriginDatabaseTest, DeleteOriginTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(file_util::CreateDirectory(kFSDir));
+
+ SandboxOriginDatabase database(kFSDir);
+ std::string origin("origin");
+
+ EXPECT_FALSE(database.HasOriginPath(origin));
+ EXPECT_TRUE(database.RemovePathForOrigin(origin));
+
+ base::FilePath path0;
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path0));
+ EXPECT_TRUE(database.HasOriginPath(origin));
+ EXPECT_FALSE(path0.empty());
+
+ EXPECT_TRUE(database.RemovePathForOrigin(origin));
+ EXPECT_FALSE(database.HasOriginPath(origin));
+
+ base::FilePath path1;
+ EXPECT_TRUE(database.GetPathForOrigin(origin, &path1));
+ EXPECT_FALSE(path1.empty());
+ EXPECT_NE(path0, path1);
+}
+
+TEST(SandboxOriginDatabaseTest, ListOriginsTest) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(file_util::CreateDirectory(kFSDir));
+
+ std::vector<SandboxOriginDatabase::OriginRecord> origins;
+
+ SandboxOriginDatabase database(kFSDir);
+ EXPECT_TRUE(database.ListAllOrigins(&origins));
+ EXPECT_TRUE(origins.empty());
+ origins.clear();
+
+ std::string origin0("origin0");
+ std::string origin1("origin1");
+
+ EXPECT_FALSE(database.HasOriginPath(origin0));
+ EXPECT_FALSE(database.HasOriginPath(origin1));
+
+ base::FilePath path0;
+ base::FilePath path1;
+ EXPECT_TRUE(database.GetPathForOrigin(origin0, &path0));
+ EXPECT_TRUE(database.ListAllOrigins(&origins));
+ EXPECT_EQ(origins.size(), 1UL);
+ EXPECT_EQ(origins[0].origin, origin0);
+ EXPECT_EQ(origins[0].path, path0);
+ origins.clear();
+ EXPECT_TRUE(database.GetPathForOrigin(origin1, &path1));
+ EXPECT_TRUE(database.ListAllOrigins(&origins));
+ EXPECT_EQ(origins.size(), 2UL);
+ if (origins[0].origin == origin0) {
+ EXPECT_EQ(origins[0].path, path0);
+ EXPECT_EQ(origins[1].origin, origin1);
+ EXPECT_EQ(origins[1].path, path1);
+ } else {
+ EXPECT_EQ(origins[0].origin, origin1);
+ EXPECT_EQ(origins[0].path, path1);
+ EXPECT_EQ(origins[1].origin, origin0);
+ EXPECT_EQ(origins[1].path, path0);
+ }
+}
+
+TEST(SandboxOriginDatabaseTest, DatabaseRecoveryTest) {
+ // Checks if SandboxOriginDatabase properly handles database corruption.
+ // In this test, we'll register some origins to the origin database, then
+ // corrupt database and its log file.
+ // After repairing, the origin database should be consistent even when some
+ // entries lost.
+
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName);
+ const base::FilePath kDBDir = kFSDir.Append(kOriginDatabaseName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(file_util::CreateDirectory(kFSDir));
+
+ const std::string kOrigins[] = {
+ "foo.example.com",
+ "bar.example.com",
+ "baz.example.com",
+ "hoge.example.com",
+ "fuga.example.com",
+ };
+
+ scoped_ptr<SandboxOriginDatabase> database(
+ new SandboxOriginDatabase(kFSDir));
+ for (size_t i = 0; i < arraysize(kOrigins); ++i) {
+ base::FilePath path;
+ EXPECT_FALSE(database->HasOriginPath(kOrigins[i]));
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigins[i], &path));
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigins[i], &path));
+
+ if (i != 1)
+ EXPECT_TRUE(file_util::CreateDirectory(kFSDir.Append(path)));
+ }
+ database.reset();
+
+ const base::FilePath kGarbageDir = kFSDir.AppendASCII("foo");
+ const base::FilePath kGarbageFile = kGarbageDir.AppendASCII("bar");
+ EXPECT_TRUE(file_util::CreateDirectory(kGarbageDir));
+ bool created = false;
+ base::PlatformFileError error;
+ base::PlatformFile file = base::CreatePlatformFile(
+ kGarbageFile,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &created, &error);
+ EXPECT_EQ(base::PLATFORM_FILE_OK, error);
+ EXPECT_TRUE(created);
+ EXPECT_TRUE(base::ClosePlatformFile(file));
+
+ // Corrupt database itself and last log entry to drop last 1 database
+ // operation. The database should detect the corruption and should recover
+ // its consistency after recovery.
+ CorruptDatabase(kDBDir, leveldb::kDescriptorFile,
+ 0, std::numeric_limits<size_t>::max());
+ CorruptDatabase(kDBDir, leveldb::kLogFile, -1, 1);
+
+ base::FilePath path;
+ database.reset(new SandboxOriginDatabase(kFSDir));
+ std::vector<SandboxOriginDatabase::OriginRecord> origins_in_db;
+ EXPECT_TRUE(database->ListAllOrigins(&origins_in_db));
+
+ // Expect all but last added origin will be repaired back, and kOrigins[1]
+ // should be dropped due to absence of backing directory.
+ EXPECT_EQ(arraysize(kOrigins) - 2, origins_in_db.size());
+
+ const std::string kOrigin("piyo.example.org");
+ EXPECT_FALSE(database->HasOriginPath(kOrigin));
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigin, &path));
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(database->HasOriginPath(kOrigin));
+
+ EXPECT_FALSE(base::PathExists(kGarbageFile));
+ EXPECT_FALSE(base::PathExists(kGarbageDir));
+}
+
+TEST(SandboxOriginDatabaseTest, DatabaseRecoveryForMissingDBFileTest) {
+ const leveldb::FileType kLevelDBFileTypes[] = {
+ leveldb::kLogFile,
+ leveldb::kDBLockFile,
+ leveldb::kTableFile,
+ leveldb::kDescriptorFile,
+ leveldb::kCurrentFile,
+ leveldb::kTempFile,
+ leveldb::kInfoLogFile,
+ };
+
+ for (size_t i = 0; i < arraysize(kLevelDBFileTypes); ++i) {
+ base::ScopedTempDir dir;
+ ASSERT_TRUE(dir.CreateUniqueTempDir());
+ const base::FilePath kFSDir = dir.path().Append(kFileSystemDirName);
+ const base::FilePath kDBDir = kFSDir.Append(kOriginDatabaseName);
+ EXPECT_FALSE(base::PathExists(kFSDir));
+ EXPECT_TRUE(file_util::CreateDirectory(kFSDir));
+
+ const std::string kOrigin = "foo.example.com";
+ base::FilePath path;
+
+ scoped_ptr<SandboxOriginDatabase> database(
+ new SandboxOriginDatabase(kFSDir));
+ EXPECT_FALSE(database->HasOriginPath(kOrigin));
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigin, &path));
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigin, &path));
+ EXPECT_TRUE(file_util::CreateDirectory(kFSDir.Append(path)));
+ database.reset();
+
+ DeleteDatabaseFile(kDBDir, kLevelDBFileTypes[i]);
+
+ database.reset(new SandboxOriginDatabase(kFSDir));
+ std::vector<SandboxOriginDatabase::OriginRecord> origins_in_db;
+ EXPECT_TRUE(database->ListAllOrigins(&origins_in_db));
+
+ const std::string kOrigin2("piyo.example.org");
+ EXPECT_FALSE(database->HasOriginPath(kOrigin2));
+ EXPECT_TRUE(database->GetPathForOrigin(kOrigin2, &path));
+ EXPECT_FALSE(path.empty());
+ EXPECT_TRUE(database->HasOriginPath(kOrigin2));
+ }
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_quota_observer.cc b/chromium/webkit/browser/fileapi/sandbox_quota_observer.cc
new file mode 100644
index 00000000000..fe5ee3796fb
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_quota_observer.cc
@@ -0,0 +1,139 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/sandbox_quota_observer.h"
+
+#include "base/sequenced_task_runner.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/file_system_usage_cache.h"
+#include "webkit/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "webkit/browser/fileapi/timed_task_helper.h"
+#include "webkit/browser/quota/quota_client.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+SandboxQuotaObserver::SandboxQuotaObserver(
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ base::SequencedTaskRunner* update_notify_runner,
+ ObfuscatedFileUtil* sandbox_file_util,
+ FileSystemUsageCache* file_system_usage_cache)
+ : quota_manager_proxy_(quota_manager_proxy),
+ update_notify_runner_(update_notify_runner),
+ sandbox_file_util_(sandbox_file_util),
+ file_system_usage_cache_(file_system_usage_cache) {}
+
+SandboxQuotaObserver::~SandboxQuotaObserver() {}
+
+void SandboxQuotaObserver::OnStartUpdate(const FileSystemURL& url) {
+ DCHECK(update_notify_runner_->RunsTasksOnCurrentThread());
+ base::FilePath usage_file_path = GetUsageCachePath(url);
+ if (usage_file_path.empty())
+ return;
+ file_system_usage_cache_->IncrementDirty(usage_file_path);
+}
+
+void SandboxQuotaObserver::OnUpdate(const FileSystemURL& url,
+ int64 delta) {
+ DCHECK(update_notify_runner_->RunsTasksOnCurrentThread());
+
+ if (quota_manager_proxy_.get()) {
+ quota_manager_proxy_->NotifyStorageModified(
+ quota::QuotaClient::kFileSystem,
+ url.origin(),
+ FileSystemTypeToQuotaStorageType(url.type()),
+ delta);
+ }
+
+ base::FilePath usage_file_path = GetUsageCachePath(url);
+ if (usage_file_path.empty())
+ return;
+
+ pending_update_notification_[usage_file_path] += delta;
+ if (!delayed_cache_update_helper_) {
+ delayed_cache_update_helper_.reset(
+ new TimedTaskHelper(update_notify_runner_.get()));
+ delayed_cache_update_helper_->Start(
+ FROM_HERE,
+ base::TimeDelta(), // No delay.
+ base::Bind(&SandboxQuotaObserver::ApplyPendingUsageUpdate,
+ base::Unretained(this)));
+ }
+}
+
+void SandboxQuotaObserver::OnEndUpdate(const FileSystemURL& url) {
+ DCHECK(update_notify_runner_->RunsTasksOnCurrentThread());
+
+ base::FilePath usage_file_path = GetUsageCachePath(url);
+ if (usage_file_path.empty())
+ return;
+
+ PendingUpdateNotificationMap::iterator found =
+ pending_update_notification_.find(usage_file_path);
+ if (found != pending_update_notification_.end()) {
+ UpdateUsageCacheFile(found->first, found->second);
+ pending_update_notification_.erase(found);
+ }
+
+ file_system_usage_cache_->DecrementDirty(usage_file_path);
+}
+
+void SandboxQuotaObserver::OnAccess(const FileSystemURL& url) {
+ if (quota_manager_proxy_.get()) {
+ quota_manager_proxy_->NotifyStorageAccessed(
+ quota::QuotaClient::kFileSystem,
+ url.origin(),
+ FileSystemTypeToQuotaStorageType(url.type()));
+ }
+}
+
+void SandboxQuotaObserver::SetUsageCacheEnabled(
+ const GURL& origin,
+ FileSystemType type,
+ bool enabled) {
+ if (quota_manager_proxy_.get()) {
+ quota_manager_proxy_->SetUsageCacheEnabled(
+ quota::QuotaClient::kFileSystem,
+ origin,
+ FileSystemTypeToQuotaStorageType(type),
+ enabled);
+ }
+}
+
+base::FilePath SandboxQuotaObserver::GetUsageCachePath(
+ const FileSystemURL& url) {
+ DCHECK(sandbox_file_util_);
+ base::PlatformFileError error = base::PLATFORM_FILE_OK;
+ base::FilePath path =
+ SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType(
+ sandbox_file_util_, url.origin(), url.type(), &error);
+ if (error != base::PLATFORM_FILE_OK) {
+ LOG(WARNING) << "Could not get usage cache path for: "
+ << url.DebugString();
+ return base::FilePath();
+ }
+ return path;
+}
+
+void SandboxQuotaObserver::ApplyPendingUsageUpdate() {
+ delayed_cache_update_helper_.reset();
+ for (PendingUpdateNotificationMap::iterator itr =
+ pending_update_notification_.begin();
+ itr != pending_update_notification_.end();
+ ++itr) {
+ UpdateUsageCacheFile(itr->first, itr->second);
+ }
+ pending_update_notification_.clear();
+}
+
+void SandboxQuotaObserver::UpdateUsageCacheFile(
+ const base::FilePath& usage_file_path,
+ int64 delta) {
+ DCHECK(!usage_file_path.empty());
+ if (!usage_file_path.empty() && delta != 0)
+ file_system_usage_cache_->AtomicUpdateUsageByDelta(usage_file_path, delta);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/sandbox_quota_observer.h b/chromium/webkit/browser/fileapi/sandbox_quota_observer.h
new file mode 100644
index 00000000000..2c16860149c
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/sandbox_quota_observer.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_
+#define WEBKIT_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace quota {
+class QuotaManagerProxy;
+}
+
+namespace fileapi {
+
+class FileSystemUsageCache;
+class FileSystemURL;
+class TimedTaskHelper;
+class ObfuscatedFileUtil;
+
+class SandboxQuotaObserver
+ : public FileUpdateObserver,
+ public FileAccessObserver {
+ public:
+ typedef std::map<base::FilePath, int64> PendingUpdateNotificationMap;
+
+ SandboxQuotaObserver(
+ quota::QuotaManagerProxy* quota_manager_proxy,
+ base::SequencedTaskRunner* update_notify_runner,
+ ObfuscatedFileUtil* sandbox_file_util,
+ FileSystemUsageCache* file_system_usage_cache_);
+ virtual ~SandboxQuotaObserver();
+
+ // FileUpdateObserver overrides.
+ virtual void OnStartUpdate(const FileSystemURL& url) OVERRIDE;
+ virtual void OnUpdate(const FileSystemURL& url, int64 delta) OVERRIDE;
+ virtual void OnEndUpdate(const FileSystemURL& url) OVERRIDE;
+
+ // FileAccessObserver overrides.
+ virtual void OnAccess(const FileSystemURL& url) OVERRIDE;
+
+ void SetUsageCacheEnabled(const GURL& origin,
+ FileSystemType type,
+ bool enabled);
+
+ private:
+ void ApplyPendingUsageUpdate();
+ void UpdateUsageCacheFile(const base::FilePath& usage_file_path, int64 delta);
+
+ base::FilePath GetUsageCachePath(const FileSystemURL& url);
+
+ scoped_refptr<quota::QuotaManagerProxy> quota_manager_proxy_;
+ scoped_refptr<base::SequencedTaskRunner> update_notify_runner_;
+
+ // Not owned; sandbox_file_util_ should have identical lifetime with this.
+ ObfuscatedFileUtil* sandbox_file_util_;
+
+ // Not owned; file_system_usage_cache_ should have longer lifetime than this.
+ FileSystemUsageCache* file_system_usage_cache_;
+
+ PendingUpdateNotificationMap pending_update_notification_;
+ scoped_ptr<TimedTaskHelper> delayed_cache_update_helper_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxQuotaObserver);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_
diff --git a/chromium/webkit/browser/fileapi/task_runner_bound_observer_list.h b/chromium/webkit/browser/fileapi/task_runner_bound_observer_list.h
new file mode 100644
index 00000000000..36a3393fe1d
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/task_runner_bound_observer_list.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_
+#define WEBKIT_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread.h"
+
+namespace fileapi {
+
+// A wrapper for dispatching method.
+template <class T, class Method, class Params>
+void NotifyWrapper(T obj, Method m, const Params& p) {
+ DispatchToMethod(base::internal::UnwrapTraits<T>::Unwrap(obj), m, p);
+}
+
+// An observer list helper to notify on a given task runner.
+// Observer pointers (stored as ObserverStoreType) must be kept alive
+// until this list dispatches all the notifications.
+//
+// Unlike regular ObserverList or ObserverListThreadSafe internal observer
+// list is immutable (though not declared const) and cannot be modified after
+// constructed.
+//
+// It is ok to specify scoped_refptr<Observer> as ObserverStoreType to
+// explicitly keep references if necessary.
+template <class Observer, class ObserverStoreType = Observer*>
+class TaskRunnerBoundObserverList {
+ public:
+ typedef scoped_refptr<base::SequencedTaskRunner> TaskRunnerPtr;
+ typedef std::map<ObserverStoreType, TaskRunnerPtr> ObserversListMap;
+
+ // Creates an empty list.
+ TaskRunnerBoundObserverList<Observer, ObserverStoreType>() {}
+
+ // Creates a new list with given |observers|.
+ explicit TaskRunnerBoundObserverList<Observer, ObserverStoreType>(
+ const ObserversListMap& observers)
+ : observers_(observers) {}
+
+ virtual ~TaskRunnerBoundObserverList<Observer, ObserverStoreType>() {}
+
+ // Returns a new observer list with given observer.
+ // It is valid to give NULL as |runner_to_notify|, and in that case
+ // notifications are dispatched on the current runner.
+ // Note that this is a const method and does NOT change 'this' observer
+ // list but returns a new list.
+ TaskRunnerBoundObserverList<Observer, ObserverStoreType> AddObserver(
+ Observer* observer,
+ base::SequencedTaskRunner* runner_to_notify) const {
+ ObserversListMap observers = observers_;
+ observers.insert(std::make_pair(observer, runner_to_notify));
+ return TaskRunnerBoundObserverList<Observer, ObserverStoreType>(observers);
+ }
+
+ // Notify on the task runner that is given to AddObserver.
+ // If we're already on the runner this just dispatches the method.
+ template <class Method, class Params>
+ void Notify(Method method, const Params& params) const {
+ COMPILE_ASSERT(
+ (base::internal::ParamsUseScopedRefptrCorrectly<Params>::value),
+ badunboundmethodparams);
+ for (typename ObserversListMap::const_iterator it = observers_.begin();
+ it != observers_.end(); ++it) {
+ if (!it->second.get() || it->second->RunsTasksOnCurrentThread()) {
+ DispatchToMethod(UnwrapTraits::Unwrap(it->first), method, params);
+ continue;
+ }
+ it->second->PostTask(
+ FROM_HERE,
+ base::Bind(&NotifyWrapper<ObserverStoreType, Method, Params>,
+ it->first, method, params));
+ }
+ }
+
+ private:
+ typedef base::internal::UnwrapTraits<ObserverStoreType> UnwrapTraits;
+
+ ObserversListMap observers_;
+};
+
+class FileAccessObserver;
+class FileChangeObserver;
+class FileUpdateObserver;
+
+typedef TaskRunnerBoundObserverList<FileAccessObserver> AccessObserverList;
+typedef TaskRunnerBoundObserverList<FileChangeObserver> ChangeObserverList;
+typedef TaskRunnerBoundObserverList<FileUpdateObserver> UpdateObserverList;
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_
diff --git a/chromium/webkit/browser/fileapi/test_file_set.cc b/chromium/webkit/browser/fileapi/test_file_set.cc
new file mode 100644
index 00000000000..7b0b0f0a7b7
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/test_file_set.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/test_file_set.h"
+
+#include <string>
+
+#include "base/file_util.h"
+#include "base/logging.h"
+#include "base/platform_file.h"
+#include "base/rand_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace fileapi {
+
+namespace test {
+
+const TestCaseRecord kRegularTestCases[] = {
+ {true, FILE_PATH_LITERAL("dir a"), 0},
+ {true, FILE_PATH_LITERAL("dir a/dir A"), 0},
+ {true, FILE_PATH_LITERAL("dir a/dir d"), 0},
+ {true, FILE_PATH_LITERAL("dir a/dir d/dir e"), 0},
+ {true, FILE_PATH_LITERAL("dir a/dir d/dir e/dir f"), 0},
+ {true, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g"), 0},
+ {true, FILE_PATH_LITERAL("dir a/dir d/dir e/dir h"), 0},
+ {true, FILE_PATH_LITERAL("dir b"), 0},
+ {true, FILE_PATH_LITERAL("dir b/dir a"), 0},
+ {true, FILE_PATH_LITERAL("dir c"), 0},
+ {false, FILE_PATH_LITERAL("file 0"), 38},
+ {false, FILE_PATH_LITERAL("file 2"), 60},
+ {false, FILE_PATH_LITERAL("file 3"), 0},
+ {false, FILE_PATH_LITERAL("dir a/file 0"), 39},
+ {false, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g/file 0"), 40},
+ {false, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g/file 1"), 41},
+ {false, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g/file 2"), 42},
+ {false, FILE_PATH_LITERAL("dir a/dir d/dir e/dir g/file 3"), 50},
+};
+
+const size_t kRegularTestCaseSize = arraysize(kRegularTestCases);
+
+void SetUpOneTestCase(const base::FilePath& root_path,
+ const TestCaseRecord& test_case) {
+ base::FilePath path = root_path.Append(test_case.path);
+ if (test_case.is_directory) {
+ ASSERT_TRUE(file_util::CreateDirectory(path));
+ return;
+ }
+ base::PlatformFileError error_code;
+ bool created = false;
+ int file_flags = base::PLATFORM_FILE_CREATE_ALWAYS |
+ base::PLATFORM_FILE_WRITE;
+ base::PlatformFile file_handle =
+ base::CreatePlatformFile(path, file_flags, &created, &error_code);
+ EXPECT_TRUE(created);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error_code);
+ ASSERT_NE(base::kInvalidPlatformFileValue, file_handle);
+ EXPECT_TRUE(base::ClosePlatformFile(file_handle));
+ if (test_case.data_file_size > 0U) {
+ std::string content = base::RandBytesAsString(test_case.data_file_size);
+ ASSERT_EQ(static_cast<int>(content.size()),
+ file_util::WriteFile(path, content.data(), content.size()));
+ }
+}
+
+
+void SetUpRegularTestCases(const base::FilePath& root_path) {
+ for (size_t i = 0; i < arraysize(kRegularTestCases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Creating kRegularTestCases " << i);
+ SetUpOneTestCase(root_path, kRegularTestCases[i]);
+ }
+}
+
+} // namespace test
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/test_file_set.h b/chromium/webkit/browser/fileapi/test_file_set.h
new file mode 100644
index 00000000000..59e90ab6e0a
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/test_file_set.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_TEST_FILE_SET_H_
+#define WEBKIT_BROWSER_FILEAPI_TEST_FILE_SET_H_
+
+#include <set>
+
+#include "base/files/file_path.h"
+
+// Common test data structures and test cases.
+
+namespace fileapi {
+
+class FileSystemFileUtil;
+
+namespace test {
+
+struct TestCaseRecord {
+ bool is_directory;
+ const base::FilePath::CharType path[64];
+ int64 data_file_size;
+};
+
+extern const TestCaseRecord kRegularTestCases[];
+extern const size_t kRegularTestCaseSize;
+
+size_t GetRegularTestCaseSize();
+
+// Creates one file or directory specified by |record|.
+void SetUpOneTestCase(const base::FilePath& root_path, const TestCaseRecord& record);
+
+// Creates the files and directories specified in kRegularTestCases.
+void SetUpRegularTestCases(const base::FilePath& root_path);
+
+} // namespace test
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_TEST_FILE_SET_H_
diff --git a/chromium/webkit/browser/fileapi/test_file_system_backend.cc b/chromium/webkit/browser/fileapi/test_file_system_backend.cc
new file mode 100644
index 00000000000..1cff5c2fb14
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/test_file_system_backend.cc
@@ -0,0 +1,230 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/test_file_system_backend.h"
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/sequenced_task_runner.h"
+#include "webkit/browser/fileapi/copy_or_move_file_validator.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_system_file_stream_reader.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_operation_impl.h"
+#include "webkit/browser/fileapi/file_system_quota_util.h"
+#include "webkit/browser/fileapi/local_file_util.h"
+#include "webkit/browser/fileapi/native_file_util.h"
+#include "webkit/browser/fileapi/sandbox_file_stream_writer.h"
+#include "webkit/browser/quota/quota_manager.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+namespace fileapi {
+
+// This only supports single origin.
+class TestFileSystemBackend::QuotaUtil
+ : public FileSystemQuotaUtil,
+ public FileUpdateObserver {
+ public:
+ QuotaUtil(base::SequencedTaskRunner* task_runner)
+ : usage_(0),
+ task_runner_(task_runner) {
+ update_observers_ = update_observers_.AddObserver(this, task_runner_.get());
+ }
+ virtual ~QuotaUtil() {}
+
+ // FileSystemQuotaUtil overrides.
+ virtual base::PlatformFileError DeleteOriginDataOnFileThread(
+ FileSystemContext* context,
+ quota::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ FileSystemType type) OVERRIDE {
+ NOTREACHED();
+ return base::PLATFORM_FILE_OK;
+ }
+
+ virtual void GetOriginsForTypeOnFileThread(
+ FileSystemType type,
+ std::set<GURL>* origins) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual void GetOriginsForHostOnFileThread(
+ FileSystemType type,
+ const std::string& host,
+ std::set<GURL>* origins) OVERRIDE {
+ NOTREACHED();
+ }
+
+ virtual int64 GetOriginUsageOnFileThread(
+ FileSystemContext* context,
+ const GURL& origin_url,
+ FileSystemType type) OVERRIDE {
+ return usage_;
+ }
+
+ virtual void AddFileUpdateObserver(
+ FileSystemType type,
+ FileUpdateObserver* observer,
+ base::SequencedTaskRunner* task_runner) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ virtual void AddFileChangeObserver(
+ FileSystemType type,
+ FileChangeObserver* observer,
+ base::SequencedTaskRunner* task_runner) OVERRIDE {
+ change_observers_ = change_observers_.AddObserver(observer, task_runner);
+ }
+
+ virtual void AddFileAccessObserver(
+ FileSystemType type,
+ FileAccessObserver* observer,
+ base::SequencedTaskRunner* task_runner) OVERRIDE {
+ NOTIMPLEMENTED();
+ }
+
+ virtual const UpdateObserverList* GetUpdateObservers(
+ FileSystemType type) const OVERRIDE {
+ return &update_observers_;
+ }
+
+ virtual const ChangeObserverList* GetChangeObservers(
+ FileSystemType type) const OVERRIDE {
+ return &change_observers_;
+ }
+
+ virtual const AccessObserverList* GetAccessObservers(
+ FileSystemType type) const OVERRIDE {
+ NOTIMPLEMENTED();
+ return NULL;
+ }
+
+ // FileUpdateObserver overrides.
+ virtual void OnStartUpdate(const FileSystemURL& url) OVERRIDE {}
+ virtual void OnUpdate(const FileSystemURL& url, int64 delta) OVERRIDE {
+ usage_ += delta;
+ }
+ virtual void OnEndUpdate(const FileSystemURL& url) OVERRIDE {}
+
+ base::SequencedTaskRunner* task_runner() { return task_runner_.get(); }
+
+ private:
+ int64 usage_;
+
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ UpdateObserverList update_observers_;
+ ChangeObserverList change_observers_;
+};
+
+TestFileSystemBackend::TestFileSystemBackend(
+ base::SequencedTaskRunner* task_runner,
+ const base::FilePath& base_path)
+ : base_path_(base_path),
+ local_file_util_(new AsyncFileUtilAdapter(new LocalFileUtil())),
+ quota_util_(new QuotaUtil(task_runner)),
+ require_copy_or_move_validator_(false) {
+}
+
+TestFileSystemBackend::~TestFileSystemBackend() {
+}
+
+bool TestFileSystemBackend::CanHandleType(FileSystemType type) const {
+ return (type == kFileSystemTypeTest);
+}
+
+void TestFileSystemBackend::Initialize(FileSystemContext* context) {
+}
+
+void TestFileSystemBackend::OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) {
+ callback.Run(GetFileSystemRootURI(origin_url, type),
+ GetFileSystemName(origin_url, type),
+ base::PLATFORM_FILE_OK);
+}
+
+FileSystemFileUtil* TestFileSystemBackend::GetFileUtil(FileSystemType type) {
+ DCHECK(local_file_util_.get());
+ return local_file_util_->sync_file_util();
+}
+
+AsyncFileUtil* TestFileSystemBackend::GetAsyncFileUtil(FileSystemType type) {
+ return local_file_util_.get();
+}
+
+CopyOrMoveFileValidatorFactory*
+TestFileSystemBackend::GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type, base::PlatformFileError* error_code) {
+ DCHECK(error_code);
+ *error_code = base::PLATFORM_FILE_OK;
+ if (require_copy_or_move_validator_) {
+ if (!copy_or_move_file_validator_factory_)
+ *error_code = base::PLATFORM_FILE_ERROR_SECURITY;
+ return copy_or_move_file_validator_factory_.get();
+ }
+ return NULL;
+}
+
+void TestFileSystemBackend::InitializeCopyOrMoveFileValidatorFactory(
+ scoped_ptr<CopyOrMoveFileValidatorFactory> factory) {
+ if (!copy_or_move_file_validator_factory_)
+ copy_or_move_file_validator_factory_ = factory.Pass();
+}
+
+FileSystemOperation* TestFileSystemBackend::CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::PlatformFileError* error_code) const {
+ scoped_ptr<FileSystemOperationContext> operation_context(
+ new FileSystemOperationContext(context));
+ operation_context->set_update_observers(*GetUpdateObservers(url.type()));
+ operation_context->set_change_observers(
+ *quota_util_->GetChangeObservers(url.type()));
+ operation_context->set_root_path(base_path_);
+ return new FileSystemOperationImpl(url, context, operation_context.Pass());
+}
+
+scoped_ptr<webkit_blob::FileStreamReader>
+TestFileSystemBackend::CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const {
+ return scoped_ptr<webkit_blob::FileStreamReader>(
+ new FileSystemFileStreamReader(
+ context, url, offset, expected_modification_time));
+}
+
+scoped_ptr<fileapi::FileStreamWriter>
+TestFileSystemBackend::CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context) const {
+ return scoped_ptr<fileapi::FileStreamWriter>(
+ new SandboxFileStreamWriter(context, url, offset,
+ *GetUpdateObservers(url.type())));
+}
+
+FileSystemQuotaUtil* TestFileSystemBackend::GetQuotaUtil() {
+ return quota_util_.get();
+}
+
+const UpdateObserverList* TestFileSystemBackend::GetUpdateObservers(
+ FileSystemType type) const {
+ return quota_util_->GetUpdateObservers(type);
+}
+
+void TestFileSystemBackend::AddFileChangeObserver(
+ FileChangeObserver* observer) {
+ quota_util_->AddFileChangeObserver(
+ kFileSystemTypeTest, observer, quota_util_->task_runner());
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/test_file_system_backend.h b/chromium/webkit/browser/fileapi/test_file_system_backend.h
new file mode 100644
index 00000000000..ef252c13118
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/test_file_system_backend.h
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_TEST_FILE_SYSTEM_BACKEND_H_
+#define WEBKIT_BROWSER_FILEAPI_TEST_FILE_SYSTEM_BACKEND_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "webkit/browser/fileapi/async_file_util_adapter.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/task_runner_bound_observer_list.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace fileapi {
+
+class AsyncFileUtilAdapter;
+class FileSystemQuotaUtil;
+
+// This should be only used for testing.
+// This file system backend uses LocalFileUtil and stores data file
+// under the given directory.
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE TestFileSystemBackend
+ : public FileSystemBackend {
+ public:
+ TestFileSystemBackend(
+ base::SequencedTaskRunner* task_runner,
+ const base::FilePath& base_path);
+ virtual ~TestFileSystemBackend();
+
+ // FileSystemBackend implementation.
+ virtual bool CanHandleType(FileSystemType type) const OVERRIDE;
+ virtual void Initialize(FileSystemContext* context) OVERRIDE;
+ virtual void OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) OVERRIDE;
+ virtual FileSystemFileUtil* GetFileUtil(FileSystemType type) OVERRIDE;
+ virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE;
+ virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type,
+ base::PlatformFileError* error_code) OVERRIDE;
+ virtual FileSystemOperation* CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::PlatformFileError* error_code) const OVERRIDE;
+ virtual scoped_ptr<webkit_blob::FileStreamReader> CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const OVERRIDE;
+ virtual scoped_ptr<FileStreamWriter> CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context) const OVERRIDE;
+ virtual FileSystemQuotaUtil* GetQuotaUtil() OVERRIDE;
+
+ // Initialize the CopyOrMoveFileValidatorFactory. Invalid to call more than
+ // once.
+ void InitializeCopyOrMoveFileValidatorFactory(
+ scoped_ptr<CopyOrMoveFileValidatorFactory> factory);
+
+ const UpdateObserverList* GetUpdateObservers(FileSystemType type) const;
+ void AddFileChangeObserver(FileChangeObserver* observer);
+
+ // For CopyOrMoveFileValidatorFactory testing. Once it's set to true
+ // GetCopyOrMoveFileValidatorFactory will start returning security
+ // error if validator is not initialized.
+ void set_require_copy_or_move_validator(bool flag) {
+ require_copy_or_move_validator_ = flag;
+ }
+
+ private:
+ class QuotaUtil;
+
+ base::FilePath base_path_;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ scoped_ptr<AsyncFileUtilAdapter> local_file_util_;
+ scoped_ptr<QuotaUtil> quota_util_;
+
+ bool require_copy_or_move_validator_;
+ scoped_ptr<CopyOrMoveFileValidatorFactory>
+ copy_or_move_file_validator_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestFileSystemBackend);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_TEST_FILE_SYSTEM_BACKEND_H_
diff --git a/chromium/webkit/browser/fileapi/timed_task_helper.cc b/chromium/webkit/browser/fileapi/timed_task_helper.cc
new file mode 100644
index 00000000000..50d1dd00d9b
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/timed_task_helper.cc
@@ -0,0 +1,92 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/timed_task_helper.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+
+namespace fileapi {
+
+struct TimedTaskHelper::Tracker {
+ explicit Tracker(TimedTaskHelper* timer) : timer(timer) {}
+
+ ~Tracker() {
+ if (timer)
+ timer->tracker_ = NULL;
+ }
+
+ TimedTaskHelper* timer;
+};
+
+TimedTaskHelper::TimedTaskHelper(base::SequencedTaskRunner* task_runner)
+ : task_runner_(task_runner),
+ tracker_(NULL) {
+}
+
+TimedTaskHelper::~TimedTaskHelper() {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ if (tracker_)
+ tracker_->timer = NULL;
+}
+
+bool TimedTaskHelper::IsRunning() const {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ return tracker_ != NULL;
+}
+
+void TimedTaskHelper::Start(
+ const tracked_objects::Location& posted_from,
+ base::TimeDelta delay,
+ const base::Closure& user_task) {
+ posted_from_ = posted_from;
+ delay_ = delay;
+ user_task_ = user_task;
+ Reset();
+}
+
+void TimedTaskHelper::Reset() {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(!user_task_.is_null());
+ desired_run_time_ = base::TimeTicks::Now() + delay_;
+
+ if (tracker_)
+ return;
+
+ // Initialize the tracker for the first time.
+ tracker_ = new Tracker(this);
+ PostDelayedTask(make_scoped_ptr(tracker_), delay_);
+}
+
+// static
+void TimedTaskHelper::Fired(scoped_ptr<Tracker> tracker) {
+ if (!tracker->timer)
+ return;
+ TimedTaskHelper* timer = tracker->timer;
+ timer->OnFired(tracker.Pass());
+}
+
+void TimedTaskHelper::OnFired(scoped_ptr<Tracker> tracker) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ base::TimeTicks now = base::TimeTicks::Now();
+ if (desired_run_time_ > now) {
+ PostDelayedTask(tracker.Pass(), desired_run_time_ - now);
+ return;
+ }
+ tracker.reset();
+ base::Closure task = user_task_;
+ user_task_.Reset();
+ task.Run();
+}
+
+void TimedTaskHelper::PostDelayedTask(scoped_ptr<Tracker> tracker,
+ base::TimeDelta delay) {
+ task_runner_->PostDelayedTask(
+ posted_from_,
+ base::Bind(&TimedTaskHelper::Fired, base::Passed(&tracker)),
+ delay);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/timed_task_helper.h b/chromium/webkit/browser/fileapi/timed_task_helper.h
new file mode 100644
index 00000000000..fcfb80ab78b
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/timed_task_helper.h
@@ -0,0 +1,59 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_
+#define WEBKIT_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace fileapi {
+
+// Works similarly as base::Timer, but takes SequencedTaskRunner and
+// runs tasks on it (instead of implicitly bound to a thread).
+// TODO(kinuko): This has nothing to do with fileapi. Move somewhere
+// more common place.
+class WEBKIT_STORAGE_BROWSER_EXPORT TimedTaskHelper {
+ public:
+ explicit TimedTaskHelper(base::SequencedTaskRunner* task_runner);
+ ~TimedTaskHelper();
+
+ bool IsRunning() const;
+ void Start(const tracked_objects::Location& posted_from,
+ base::TimeDelta delay,
+ const base::Closure& user_task);
+ void Reset();
+
+ private:
+ struct Tracker;
+ static void Fired(scoped_ptr<Tracker> tracker);
+
+ void OnFired(scoped_ptr<Tracker> tracker);
+ void PostDelayedTask(scoped_ptr<Tracker> tracker, base::TimeDelta delay);
+
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ tracked_objects::Location posted_from_;
+ base::TimeDelta delay_;
+ base::Closure user_task_;
+
+ base::TimeTicks desired_run_time_;
+
+ // This is set to non-null and owned by a timer task while timer is running.
+ Tracker* tracker_;
+
+ DISALLOW_COPY_AND_ASSIGN(TimedTaskHelper);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_
diff --git a/chromium/webkit/browser/fileapi/timed_task_helper_unittest.cc b/chromium/webkit/browser/fileapi/timed_task_helper_unittest.cc
new file mode 100644
index 00000000000..6966c614c3d
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/timed_task_helper_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/timed_task_helper.h"
+
+namespace fileapi {
+
+namespace {
+
+class Embedder {
+ public:
+ Embedder()
+ : timer_(base::MessageLoopProxy::current().get()), timer_fired_(false) {}
+
+ void OnTimerFired() {
+ timer_fired_ = true;
+ }
+
+ TimedTaskHelper* timer() { return &timer_; }
+ bool timer_fired() const { return timer_fired_; }
+
+ private:
+ TimedTaskHelper timer_;
+ bool timer_fired_;
+};
+
+} // namespace
+
+TEST(TimedTaskHelper, FireTimerWhenAlive) {
+ base::MessageLoop message_loop;
+ Embedder embedder;
+
+ ASSERT_FALSE(embedder.timer_fired());
+ ASSERT_FALSE(embedder.timer()->IsRunning());
+
+ embedder.timer()->Start(
+ FROM_HERE,
+ base::TimeDelta::FromSeconds(0),
+ base::Bind(&Embedder::OnTimerFired, base::Unretained(&embedder)));
+
+ ASSERT_TRUE(embedder.timer()->IsRunning());
+ embedder.timer()->Reset();
+ ASSERT_TRUE(embedder.timer()->IsRunning());
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ ASSERT_TRUE(embedder.timer_fired());
+}
+
+TEST(TimedTaskHelper, FireTimerWhenAlreadyDeleted) {
+ base::MessageLoop message_loop;
+
+ // Run message loop after embedder is already deleted to make sure callback
+ // doesn't cause a crash for use after free.
+ {
+ Embedder embedder;
+
+ ASSERT_FALSE(embedder.timer_fired());
+ ASSERT_FALSE(embedder.timer()->IsRunning());
+
+ embedder.timer()->Start(
+ FROM_HERE,
+ base::TimeDelta::FromSeconds(0),
+ base::Bind(&Embedder::OnTimerFired, base::Unretained(&embedder)));
+
+ ASSERT_TRUE(embedder.timer()->IsRunning());
+ }
+
+ // At this point the callback is still in the message queue but
+ // embedder is gone.
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/transient_file_util.cc b/chromium/webkit/browser/fileapi/transient_file_util.cc
new file mode 100644
index 00000000000..74c2dbea8c3
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/transient_file_util.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/transient_file_util.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+
+using webkit_blob::ScopedFile;
+
+namespace fileapi {
+
+namespace {
+
+void RevokeFileSystem(const std::string& filesystem_id,
+ const base::FilePath& /*path*/) {
+ IsolatedContext::GetInstance()->RevokeFileSystem(filesystem_id);
+}
+
+} // namespace
+
+ScopedFile TransientFileUtil::CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileError* error,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_path) {
+ DCHECK(file_info);
+ *error = GetFileInfo(context, url, file_info, platform_path);
+ if (*error == base::PLATFORM_FILE_OK && file_info->is_directory)
+ *error = base::PLATFORM_FILE_ERROR_NOT_A_FILE;
+ if (*error != base::PLATFORM_FILE_OK)
+ return ScopedFile();
+
+ // Sets-up a transient filesystem.
+ DCHECK(!platform_path->empty());
+ DCHECK(!url.filesystem_id().empty());
+
+ ScopedFile scoped_file(
+ *platform_path,
+ ScopedFile::DELETE_ON_SCOPE_OUT,
+ context->task_runner());
+ scoped_file.AddScopeOutCallback(
+ base::Bind(&RevokeFileSystem, url.filesystem_id()), NULL);
+
+ return scoped_file.Pass();
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/transient_file_util.h b/chromium/webkit/browser/fileapi/transient_file_util.h
new file mode 100644
index 00000000000..d3d56c1c173
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/transient_file_util.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_
+#define WEBKIT_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "webkit/browser/fileapi/isolated_file_util.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace fileapi {
+
+class FileSystemOperationContext;
+
+class WEBKIT_STORAGE_BROWSER_EXPORT_PRIVATE TransientFileUtil
+ : public IsolatedFileUtil {
+ public:
+ TransientFileUtil() {}
+ virtual ~TransientFileUtil() {}
+
+ // IsolatedFileUtil overrides.
+ virtual webkit_blob::ScopedFile CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::PlatformFileError* error,
+ base::PlatformFileInfo* file_info,
+ base::FilePath* platform_path) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TransientFileUtil);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_
diff --git a/chromium/webkit/browser/fileapi/transient_file_util_unittest.cc b/chromium/webkit/browser/fileapi/transient_file_util_unittest.cc
new file mode 100644
index 00000000000..2a809a30779
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/transient_file_util_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/platform_file.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+#include "webkit/browser/fileapi/transient_file_util.h"
+#include "webkit/common/blob/scoped_file.h"
+
+namespace fileapi {
+
+class TransientFileUtilTest : public testing::Test {
+ public:
+ TransientFileUtilTest() {}
+ virtual ~TransientFileUtilTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ file_system_context_ = CreateFileSystemContextForTesting(
+ NULL, base::FilePath(FILE_PATH_LITERAL("dummy")));
+ transient_file_util_.reset(new TransientFileUtil);
+
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ file_system_context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ void CreateAndRegisterTemporaryFile(
+ FileSystemURL* file_url,
+ base::FilePath* file_path) {
+ EXPECT_TRUE(
+ file_util::CreateTemporaryFileInDir(data_dir_.path(), file_path));
+ IsolatedContext* isolated_context = IsolatedContext::GetInstance();
+ std::string name = "tmp";
+ std::string fsid = isolated_context->RegisterFileSystemForPath(
+ kFileSystemTypeForTransientFile,
+ *file_path,
+ &name);
+ ASSERT_TRUE(!fsid.empty());
+ base::FilePath virtual_path = isolated_context->CreateVirtualRootPath(
+ fsid).AppendASCII(name);
+ *file_url = file_system_context_->CreateCrackedFileSystemURL(
+ GURL("http://foo"),
+ kFileSystemTypeIsolated,
+ virtual_path);
+ }
+
+ scoped_ptr<FileSystemOperationContext> NewOperationContext() {
+ return make_scoped_ptr(
+ new FileSystemOperationContext(file_system_context_.get()));
+ }
+
+ FileSystemFileUtil* file_util() { return transient_file_util_.get(); }
+
+ private:
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir data_dir_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ scoped_ptr<TransientFileUtil> transient_file_util_;
+
+ DISALLOW_COPY_AND_ASSIGN(TransientFileUtilTest);
+};
+
+TEST_F(TransientFileUtilTest, TransientFile) {
+ FileSystemURL temp_url;
+ base::FilePath temp_path;
+
+ CreateAndRegisterTemporaryFile(&temp_url, &temp_path);
+
+ base::PlatformFileError error;
+ base::PlatformFileInfo file_info;
+ base::FilePath path;
+
+ // Make sure the file is there.
+ ASSERT_TRUE(temp_url.is_valid());
+ ASSERT_TRUE(base::PathExists(temp_path));
+ ASSERT_FALSE(base::DirectoryExists(temp_path));
+
+ // Create a snapshot file.
+ {
+ webkit_blob::ScopedFile scoped_file =
+ file_util()->CreateSnapshotFile(NewOperationContext().get(),
+ temp_url,
+ &error,
+ &file_info,
+ &path);
+ ASSERT_EQ(base::PLATFORM_FILE_OK, error);
+ ASSERT_EQ(temp_path, path);
+ ASSERT_FALSE(file_info.is_directory);
+
+ // The file should be still there.
+ ASSERT_TRUE(base::PathExists(temp_path));
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util()->GetFileInfo(NewOperationContext().get(),
+ temp_url, &file_info, &path));
+ ASSERT_EQ(temp_path, path);
+ ASSERT_FALSE(file_info.is_directory);
+ }
+
+ // The file's now scoped out.
+ base::MessageLoop::current()->RunUntilIdle();
+
+ // Now the temporary file and the transient filesystem must be gone too.
+ ASSERT_FALSE(base::PathExists(temp_path));
+ ASSERT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ file_util()->GetFileInfo(NewOperationContext().get(),
+ temp_url, &file_info, &path));
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.cc b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.cc
new file mode 100644
index 00000000000..3fb5ab4d31e
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/upload_file_system_file_element_reader.h"
+
+#include "base/bind.h"
+#include "net/base/net_errors.h"
+#include "webkit/browser/blob/file_stream_reader.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+namespace fileapi {
+
+UploadFileSystemFileElementReader::UploadFileSystemFileElementReader(
+ FileSystemContext* file_system_context,
+ const GURL& url,
+ uint64 range_offset,
+ uint64 range_length,
+ const base::Time& expected_modification_time)
+ : file_system_context_(file_system_context),
+ url_(url),
+ range_offset_(range_offset),
+ range_length_(range_length),
+ expected_modification_time_(expected_modification_time),
+ stream_length_(0),
+ position_(0),
+ weak_ptr_factory_(this) {
+}
+
+UploadFileSystemFileElementReader::~UploadFileSystemFileElementReader() {
+}
+
+int UploadFileSystemFileElementReader::Init(
+ const net::CompletionCallback& callback) {
+ // Reset states.
+ weak_ptr_factory_.InvalidateWeakPtrs();
+ stream_length_ = 0;
+ position_ = 0;
+
+ // Initialize the stream reader and the length.
+ stream_reader_ =
+ file_system_context_->CreateFileStreamReader(
+ file_system_context_->CrackURL(url_),
+ range_offset_,
+ expected_modification_time_);
+ DCHECK(stream_reader_);
+
+ const int64 result = stream_reader_->GetLength(
+ base::Bind(&UploadFileSystemFileElementReader::OnGetLength,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback));
+ if (result >= 0) {
+ stream_length_ = result;
+ return net::OK;
+ }
+
+ // The error code can be casted to int.
+ return static_cast<int>(result);
+}
+
+uint64 UploadFileSystemFileElementReader::GetContentLength() const {
+ return std::min(stream_length_, range_length_);
+}
+
+uint64 UploadFileSystemFileElementReader::BytesRemaining() const {
+ return GetContentLength() - position_;
+}
+
+int UploadFileSystemFileElementReader::Read(
+ net::IOBuffer* buf,
+ int buf_length,
+ const net::CompletionCallback& callback) {
+ DCHECK_LT(0, buf_length);
+ DCHECK(stream_reader_);
+
+ const uint64 num_bytes_to_read =
+ std::min(BytesRemaining(), static_cast<uint64>(buf_length));
+
+ if (num_bytes_to_read == 0)
+ return 0;
+
+ const int result = stream_reader_->Read(
+ buf, num_bytes_to_read,
+ base::Bind(&UploadFileSystemFileElementReader::OnRead,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback));
+ if (result >= 0)
+ OnRead(net::CompletionCallback(), result);
+ return result;
+}
+
+void UploadFileSystemFileElementReader::OnGetLength(
+ const net::CompletionCallback& callback,
+ int64 result) {
+ if (result >= 0) {
+ stream_length_ = result;
+ callback.Run(net::OK);
+ return;
+ }
+ callback.Run(result);
+}
+
+void UploadFileSystemFileElementReader::OnRead(
+ const net::CompletionCallback& callback,
+ int result) {
+ if (result > 0) {
+ position_ += result;
+ DCHECK_LE(position_, GetContentLength());
+ }
+ if (!callback.is_null())
+ callback.Run(result);
+}
+
+} // namespace fileapi
diff --git a/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.h b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.h
new file mode 100644
index 00000000000..dba30868b93
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_UPLOAD_FILE_SYSTEM_FILE_ELEMENT_READER_H_
+#define WEBKIT_BROWSER_FILEAPI_UPLOAD_FILE_SYSTEM_FILE_ELEMENT_READER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/upload_element_reader.h"
+#include "url/gurl.h"
+#include "webkit/browser/webkit_storage_browser_export.h"
+
+namespace webkit_blob {
+class FileStreamReader;
+}
+
+namespace fileapi {
+
+class FileSystemContext;
+
+// An UploadElementReader implementation for filesystem file.
+class WEBKIT_STORAGE_BROWSER_EXPORT UploadFileSystemFileElementReader
+ : public net::UploadElementReader {
+ public:
+ UploadFileSystemFileElementReader(
+ FileSystemContext* file_system_context,
+ const GURL& url,
+ uint64 range_offset,
+ uint64 range_length,
+ const base::Time& expected_modification_time);
+ virtual ~UploadFileSystemFileElementReader();
+
+ // UploadElementReader overrides:
+ virtual int Init(const net::CompletionCallback& callback) OVERRIDE;
+ virtual uint64 GetContentLength() const OVERRIDE;
+ virtual uint64 BytesRemaining() const OVERRIDE;
+ virtual int Read(net::IOBuffer* buf,
+ int buf_length,
+ const net::CompletionCallback& callback) OVERRIDE;
+
+ private:
+ void OnGetLength(const net::CompletionCallback& callback, int64 result);
+ void OnRead(const net::CompletionCallback& callback, int result);
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+ const GURL url_;
+ const uint64 range_offset_;
+ const uint64 range_length_;
+ const base::Time expected_modification_time_;
+
+ scoped_ptr<webkit_blob::FileStreamReader> stream_reader_;
+
+ uint64 stream_length_;
+ uint64 position_;
+
+ base::WeakPtrFactory<UploadFileSystemFileElementReader> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UploadFileSystemFileElementReader);
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_UPLOAD_FILE_SYSTEM_FILE_ELEMENT_READER_H_
diff --git a/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader_unittest.cc b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader_unittest.cc
new file mode 100644
index 00000000000..e6e0d4cd2b4
--- /dev/null
+++ b/chromium/webkit/browser/fileapi/upload_file_system_file_element_reader_unittest.cc
@@ -0,0 +1,295 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/upload_file_system_file_element_reader.h"
+
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop/message_loop.h"
+#include "net/base/io_buffer.h"
+#include "net/base/test_completion_callback.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/file_system_backend.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/mock_file_system_context.h"
+
+namespace fileapi {
+
+namespace {
+
+const char kFileSystemURLOrigin[] = "http://remote";
+const fileapi::FileSystemType kFileSystemType =
+ fileapi::kFileSystemTypeTemporary;
+
+} // namespace
+
+class UploadFileSystemFileElementReaderTest : public testing::Test {
+ public:
+ UploadFileSystemFileElementReaderTest()
+ : message_loop_(base::MessageLoop::TYPE_IO) {}
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ file_system_context_ = fileapi::CreateFileSystemContextForTesting(
+ NULL, temp_dir_.path());
+
+ file_system_context_->OpenFileSystem(
+ GURL(kFileSystemURLOrigin),
+ kFileSystemType,
+ OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
+ base::Bind(&UploadFileSystemFileElementReaderTest::OnOpenFileSystem,
+ base::Unretained(this)));
+ base::MessageLoop::current()->RunUntilIdle();
+ ASSERT_TRUE(file_system_root_url_.is_valid());
+
+ // Prepare a file on file system.
+ const char kTestData[] = "abcdefghijklmnop0123456789";
+ file_data_.assign(kTestData, kTestData + arraysize(kTestData) - 1);
+ const char kFilename[] = "File.dat";
+ file_url_ = GetFileSystemURL(kFilename);
+ WriteFileSystemFile(kFilename, &file_data_[0], file_data_.size(),
+ &file_modification_time_);
+
+ // Create and initialize a reader.
+ reader_.reset(
+ new UploadFileSystemFileElementReader(file_system_context_.get(),
+ file_url_,
+ 0,
+ kuint64max,
+ file_modification_time_));
+ net::TestCompletionCallback callback;
+ ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(callback.callback()));
+ EXPECT_EQ(net::OK, callback.WaitForResult());
+ EXPECT_EQ(file_data_.size(), reader_->GetContentLength());
+ EXPECT_EQ(file_data_.size(), reader_->BytesRemaining());
+ EXPECT_FALSE(reader_->IsInMemory());
+ }
+
+ virtual void TearDown() OVERRIDE {
+ reader_.reset();
+ base::MessageLoop::current()->RunUntilIdle();
+ }
+
+ protected:
+ GURL GetFileSystemURL(const std::string& filename) {
+ return GURL(file_system_root_url_.spec() + filename);
+ }
+
+ void WriteFileSystemFile(const std::string& filename,
+ const char* buf,
+ int buf_size,
+ base::Time* modification_time) {
+ fileapi::FileSystemURL url =
+ file_system_context_->CreateCrackedFileSystemURL(
+ GURL(kFileSystemURLOrigin),
+ kFileSystemType,
+ base::FilePath().AppendASCII(filename));
+
+ fileapi::FileSystemFileUtil* file_util =
+ file_system_context_->GetFileUtil(kFileSystemType);
+
+ fileapi::FileSystemOperationContext context(file_system_context_.get());
+ context.set_allowed_bytes_growth(1024);
+
+ base::PlatformFile handle = base::kInvalidPlatformFileValue;
+ bool created = false;
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_util->CreateOrOpen(
+ &context,
+ url,
+ base::PLATFORM_FILE_CREATE | base::PLATFORM_FILE_WRITE,
+ &handle,
+ &created));
+ EXPECT_TRUE(created);
+ ASSERT_NE(base::kInvalidPlatformFileValue, handle);
+ ASSERT_EQ(buf_size,
+ base::WritePlatformFile(handle, 0 /* offset */, buf, buf_size));
+ base::ClosePlatformFile(handle);
+
+ base::PlatformFileInfo file_info;
+ base::FilePath platform_path;
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_util->GetFileInfo(&context, url, &file_info,
+ &platform_path));
+ *modification_time = file_info.last_modified;
+ }
+
+ void OnOpenFileSystem(base::PlatformFileError result,
+ const std::string& name,
+ const GURL& root) {
+ ASSERT_EQ(base::PLATFORM_FILE_OK, result);
+ ASSERT_TRUE(root.is_valid());
+ file_system_root_url_ = root;
+ }
+
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir temp_dir_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ GURL file_system_root_url_;
+ std::vector<char> file_data_;
+ GURL file_url_;
+ base::Time file_modification_time_;
+ scoped_ptr<UploadFileSystemFileElementReader> reader_;
+};
+
+TEST_F(UploadFileSystemFileElementReaderTest, ReadAll) {
+ scoped_refptr<net::IOBufferWithSize> buf =
+ new net::IOBufferWithSize(file_data_.size());
+ net::TestCompletionCallback read_callback;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ reader_->Read(buf.get(), buf->size(), read_callback.callback()));
+ EXPECT_EQ(buf->size(), read_callback.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data()));
+ // Try to read again.
+ EXPECT_EQ(0, reader_->Read(buf.get(), buf->size(), read_callback.callback()));
+}
+
+TEST_F(UploadFileSystemFileElementReaderTest, ReadPartially) {
+ const size_t kHalfSize = file_data_.size() / 2;
+ ASSERT_EQ(file_data_.size(), kHalfSize * 2);
+
+ scoped_refptr<net::IOBufferWithSize> buf =
+ new net::IOBufferWithSize(kHalfSize);
+
+ net::TestCompletionCallback read_callback1;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ reader_->Read(buf.get(), buf->size(), read_callback1.callback()));
+ EXPECT_EQ(buf->size(), read_callback1.WaitForResult());
+ EXPECT_EQ(file_data_.size() - buf->size(), reader_->BytesRemaining());
+ EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.begin() + kHalfSize,
+ buf->data()));
+
+ net::TestCompletionCallback read_callback2;
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ reader_->Read(buf.get(), buf->size(), read_callback2.callback()));
+ EXPECT_EQ(buf->size(), read_callback2.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_TRUE(std::equal(file_data_.begin() + kHalfSize, file_data_.end(),
+ buf->data()));
+}
+
+TEST_F(UploadFileSystemFileElementReaderTest, ReadTooMuch) {
+ const size_t kTooLargeSize = file_data_.size() * 2;
+ scoped_refptr<net::IOBufferWithSize> buf =
+ new net::IOBufferWithSize(kTooLargeSize);
+ net::TestCompletionCallback read_callback;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ reader_->Read(buf.get(), buf->size(), read_callback.callback()));
+ EXPECT_EQ(static_cast<int>(file_data_.size()), read_callback.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data()));
+}
+
+TEST_F(UploadFileSystemFileElementReaderTest, MultipleInit) {
+ scoped_refptr<net::IOBufferWithSize> buf =
+ new net::IOBufferWithSize(file_data_.size());
+
+ // Read all.
+ net::TestCompletionCallback read_callback1;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ reader_->Read(buf.get(), buf->size(), read_callback1.callback()));
+ EXPECT_EQ(buf->size(), read_callback1.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data()));
+
+ // Call Init() again to reset the state.
+ net::TestCompletionCallback init_callback;
+ ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback()));
+ EXPECT_EQ(net::OK, init_callback.WaitForResult());
+ EXPECT_EQ(file_data_.size(), reader_->GetContentLength());
+ EXPECT_EQ(file_data_.size(), reader_->BytesRemaining());
+
+ // Read again.
+ net::TestCompletionCallback read_callback2;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ reader_->Read(buf.get(), buf->size(), read_callback2.callback()));
+ EXPECT_EQ(buf->size(), read_callback2.WaitForResult());
+ EXPECT_EQ(0U, reader_->BytesRemaining());
+ EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.end(), buf->data()));
+}
+
+TEST_F(UploadFileSystemFileElementReaderTest, InitDuringAsyncOperation) {
+ scoped_refptr<net::IOBufferWithSize> buf =
+ new net::IOBufferWithSize(file_data_.size());
+
+ // Start reading all.
+ net::TestCompletionCallback read_callback1;
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ reader_->Read(buf.get(), buf->size(), read_callback1.callback()));
+
+ // Call Init to cancel the previous read.
+ net::TestCompletionCallback init_callback1;
+ EXPECT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback1.callback()));
+
+ // Call Init again to cancel the previous init.
+ net::TestCompletionCallback init_callback2;
+ EXPECT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback2.callback()));
+ EXPECT_EQ(net::OK, init_callback2.WaitForResult());
+ EXPECT_EQ(file_data_.size(), reader_->GetContentLength());
+ EXPECT_EQ(file_data_.size(), reader_->BytesRemaining());
+
+ // Read half.
+ scoped_refptr<net::IOBufferWithSize> buf2 =
+ new net::IOBufferWithSize(file_data_.size() / 2);
+ net::TestCompletionCallback read_callback2;
+ EXPECT_EQ(net::ERR_IO_PENDING,
+ reader_->Read(buf2.get(), buf2->size(), read_callback2.callback()));
+ EXPECT_EQ(buf2->size(), read_callback2.WaitForResult());
+ EXPECT_EQ(file_data_.size() - buf2->size(), reader_->BytesRemaining());
+ EXPECT_TRUE(std::equal(file_data_.begin(), file_data_.begin() + buf2->size(),
+ buf2->data()));
+
+ // Make sure callbacks are not called for cancelled operations.
+ EXPECT_FALSE(read_callback1.have_result());
+ EXPECT_FALSE(init_callback1.have_result());
+}
+
+TEST_F(UploadFileSystemFileElementReaderTest, Range) {
+ const int kOffset = 2;
+ const int kLength = file_data_.size() - kOffset * 3;
+ reader_.reset(new UploadFileSystemFileElementReader(
+ file_system_context_.get(), file_url_, kOffset, kLength, base::Time()));
+ net::TestCompletionCallback init_callback;
+ ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback()));
+ EXPECT_EQ(net::OK, init_callback.WaitForResult());
+ EXPECT_EQ(static_cast<uint64>(kLength), reader_->GetContentLength());
+ EXPECT_EQ(static_cast<uint64>(kLength), reader_->BytesRemaining());
+ scoped_refptr<net::IOBufferWithSize> buf = new net::IOBufferWithSize(kLength);
+ net::TestCompletionCallback read_callback;
+ ASSERT_EQ(net::ERR_IO_PENDING,
+ reader_->Read(buf.get(), buf->size(), read_callback.callback()));
+ EXPECT_EQ(kLength, read_callback.WaitForResult());
+ EXPECT_TRUE(std::equal(file_data_.begin() + kOffset,
+ file_data_.begin() + kOffset + kLength,
+ buf->data()));
+}
+
+TEST_F(UploadFileSystemFileElementReaderTest, FileChanged) {
+ // Expect one second before the actual modification time to simulate change.
+ const base::Time expected_modification_time =
+ file_modification_time_ - base::TimeDelta::FromSeconds(1);
+ reader_.reset(
+ new UploadFileSystemFileElementReader(file_system_context_.get(),
+ file_url_,
+ 0,
+ kuint64max,
+ expected_modification_time));
+ net::TestCompletionCallback init_callback;
+ ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback()));
+ EXPECT_EQ(net::ERR_UPLOAD_FILE_CHANGED, init_callback.WaitForResult());
+}
+
+TEST_F(UploadFileSystemFileElementReaderTest, WrongURL) {
+ const GURL wrong_url = GetFileSystemURL("wrong_file_name.dat");
+ reader_.reset(new UploadFileSystemFileElementReader(
+ file_system_context_.get(), wrong_url, 0, kuint64max, base::Time()));
+ net::TestCompletionCallback init_callback;
+ ASSERT_EQ(net::ERR_IO_PENDING, reader_->Init(init_callback.callback()));
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, init_callback.WaitForResult());
+}
+
+} // namespace fileapi