summaryrefslogtreecommitdiffstats
path: root/storage
diff options
context:
space:
mode:
authorpilgrim <pilgrim@chromium.org>2014-09-05 10:30:15 -0700
committerCommit bot <commit-bot@chromium.org>2014-09-05 17:32:24 +0000
commit4af8c2120e23d17c1cac3f929c3a1d901c5701ab (patch)
tree40e4ca0d45ec498ea74b9b4b232764abc1ec6ab1 /storage
parenta602902631a762be0b49ac0af09de0c9c840e183 (diff)
downloadchromium_src-4af8c2120e23d17c1cac3f929c3a1d901c5701ab.zip
chromium_src-4af8c2120e23d17c1cac3f929c3a1d901c5701ab.tar.gz
chromium_src-4af8c2120e23d17c1cac3f929c3a1d901c5701ab.tar.bz2
Migrate webkit/browser/ to storage/browser/
using TBR because Darin told me he defers to James' judgement. Using NOTRY because there is one presubmit error (dump_file_system.cc was moved and it spews printf). BUG=338338 TBR=darin@chromium.org NOTRY=true Review URL: https://codereview.chromium.org/539143002 Cr-Commit-Position: refs/heads/master@{#293547}
Diffstat (limited to 'storage')
-rw-r--r--storage/DEPS2
-rw-r--r--storage/browser/BUILD.gn192
-rw-r--r--storage/browser/DEPS3
-rw-r--r--storage/browser/blob/OWNERS1
-rw-r--r--storage/browser/blob/blob_data_handle.cc68
-rw-r--r--storage/browser/blob/blob_data_handle.h71
-rw-r--r--storage/browser/blob/blob_storage_context.cc325
-rw-r--r--storage/browser/blob/blob_storage_context.h118
-rw-r--r--storage/browser/blob/blob_url_request_job.cc590
-rw-r--r--storage/browser/blob/blob_url_request_job.h129
-rw-r--r--storage/browser/blob/blob_url_request_job_factory.cc90
-rw-r--r--storage/browser/blob/blob_url_request_job_factory.h71
-rw-r--r--storage/browser/blob/file_stream_reader.cc20
-rw-r--r--storage/browser/blob/file_stream_reader.h99
-rw-r--r--storage/browser/blob/local_file_stream_reader.cc169
-rw-r--r--storage/browser/blob/local_file_stream_reader.h81
-rw-r--r--storage/browser/blob/view_blob_internals_job.cc242
-rw-r--r--storage/browser/blob/view_blob_internals_job.h57
-rw-r--r--storage/browser/database/database_quota_client.cc220
-rw-r--r--storage/browser/database/database_quota_client.h57
-rw-r--r--storage/browser/database/database_tracker.cc869
-rw-r--r--storage/browser/database/database_tracker.h319
-rw-r--r--storage/browser/database/database_util.cc108
-rw-r--r--storage/browser/database/database_util.h39
-rw-r--r--storage/browser/database/databases_table.cc150
-rw-r--r--storage/browser/database/databases_table.h54
-rw-r--r--storage/browser/database/vfs_backend.cc158
-rw-r--r--storage/browser/database/vfs_backend.h42
-rw-r--r--storage/browser/fileapi/OWNERS2
-rw-r--r--storage/browser/fileapi/async_file_util.h366
-rw-r--r--storage/browser/fileapi/async_file_util_adapter.cc352
-rw-r--r--storage/browser/fileapi/async_file_util_adapter.h118
-rw-r--r--storage/browser/fileapi/copy_or_move_file_validator.h54
-rw-r--r--storage/browser/fileapi/copy_or_move_operation_delegate.cc1034
-rw-r--r--storage/browser/fileapi/copy_or_move_operation_delegate.h162
-rw-r--r--storage/browser/fileapi/dragged_file_util.cc105
-rw-r--r--storage/browser/fileapi/dragged_file_util.h41
-rw-r--r--storage/browser/fileapi/dump_file_system.cc204
-rw-r--r--storage/browser/fileapi/external_mount_points.cc357
-rw-r--r--storage/browser/fileapi/external_mount_points.h159
-rw-r--r--storage/browser/fileapi/file_observers.h83
-rw-r--r--storage/browser/fileapi/file_permission_policy.h28
-rw-r--r--storage/browser/fileapi/file_stream_writer.h87
-rw-r--r--storage/browser/fileapi/file_system_backend.h169
-rw-r--r--storage/browser/fileapi/file_system_context.cc644
-rw-r--r--storage/browser/fileapi/file_system_context.h428
-rw-r--r--storage/browser/fileapi/file_system_dir_url_request_job.cc160
-rw-r--r--storage/browser/fileapi/file_system_dir_url_request_job.h69
-rw-r--r--storage/browser/fileapi/file_system_file_stream_reader.cc140
-rw-r--r--storage/browser/fileapi/file_system_file_stream_reader.h79
-rw-r--r--storage/browser/fileapi/file_system_file_util.cc25
-rw-r--r--storage/browser/fileapi/file_system_file_util.h186
-rw-r--r--storage/browser/fileapi/file_system_operation.h485
-rw-r--r--storage/browser/fileapi/file_system_operation_context.cc34
-rw-r--r--storage/browser/fileapi/file_system_operation_context.h97
-rw-r--r--storage/browser/fileapi/file_system_operation_impl.cc559
-rw-r--r--storage/browser/fileapi/file_system_operation_impl.h206
-rw-r--r--storage/browser/fileapi/file_system_operation_runner.cc687
-rw-r--r--storage/browser/fileapi/file_system_operation_runner.h322
-rw-r--r--storage/browser/fileapi/file_system_options.cc21
-rw-r--r--storage/browser/fileapi/file_system_options.h62
-rw-r--r--storage/browser/fileapi/file_system_quota_client.cc210
-rw-r--r--storage/browser/fileapi/file_system_quota_client.h70
-rw-r--r--storage/browser/fileapi/file_system_quota_util.h91
-rw-r--r--storage/browser/fileapi/file_system_url.cc153
-rw-r--r--storage/browser/fileapi/file_system_url.h180
-rw-r--r--storage/browser/fileapi/file_system_url_request_job.cc263
-rw-r--r--storage/browser/fileapi/file_system_url_request_job.h85
-rw-r--r--storage/browser/fileapi/file_system_url_request_job_factory.cc73
-rw-r--r--storage/browser/fileapi/file_system_url_request_job_factory.h31
-rw-r--r--storage/browser/fileapi/file_system_usage_cache.cc307
-rw-r--r--storage/browser/fileapi/file_system_usage_cache.h105
-rw-r--r--storage/browser/fileapi/file_writer_delegate.cc244
-rw-r--r--storage/browser/fileapi/file_writer_delegate.h110
-rw-r--r--storage/browser/fileapi/isolated_context.cc484
-rw-r--r--storage/browser/fileapi/isolated_context.h201
-rw-r--r--storage/browser/fileapi/isolated_file_system_backend.cc152
-rw-r--r--storage/browser/fileapi/isolated_file_system_backend.h57
-rw-r--r--storage/browser/fileapi/local_file_stream_writer.cc257
-rw-r--r--storage/browser/fileapi/local_file_stream_writer.h100
-rw-r--r--storage/browser/fileapi/local_file_util.cc265
-rw-r--r--storage/browser/fileapi/local_file_util.h94
-rw-r--r--storage/browser/fileapi/mount_points.cc14
-rw-r--r--storage/browser/fileapi/mount_points.h108
-rw-r--r--storage/browser/fileapi/native_file_util.cc315
-rw-r--r--storage/browser/fileapi/native_file_util.h73
-rw-r--r--storage/browser/fileapi/obfuscated_file_util.cc1424
-rw-r--r--storage/browser/fileapi/obfuscated_file_util.h359
-rw-r--r--storage/browser/fileapi/open_file_system_mode.h22
-rw-r--r--storage/browser/fileapi/plugin_private_file_system_backend.cc311
-rw-r--r--storage/browser/fileapi/plugin_private_file_system_backend.h149
-rw-r--r--storage/browser/fileapi/quota/open_file_handle.cc55
-rw-r--r--storage/browser/fileapi/quota/open_file_handle.h70
-rw-r--r--storage/browser/fileapi/quota/open_file_handle_context.cc72
-rw-r--r--storage/browser/fileapi/quota/open_file_handle_context.h61
-rw-r--r--storage/browser/fileapi/quota/quota_backend_impl.cc172
-rw-r--r--storage/browser/fileapi/quota/quota_backend_impl.h106
-rw-r--r--storage/browser/fileapi/quota/quota_reservation.cc127
-rw-r--r--storage/browser/fileapi/quota/quota_reservation.h95
-rw-r--r--storage/browser/fileapi/quota/quota_reservation_buffer.cc105
-rw-r--r--storage/browser/fileapi/quota/quota_reservation_buffer.h87
-rw-r--r--storage/browser/fileapi/quota/quota_reservation_manager.cc91
-rw-r--r--storage/browser/fileapi/quota/quota_reservation_manager.h127
-rw-r--r--storage/browser/fileapi/recursive_operation_delegate.cc239
-rw-r--r--storage/browser/fileapi/recursive_operation_delegate.h152
-rw-r--r--storage/browser/fileapi/remove_operation_delegate.cc82
-rw-r--r--storage/browser/fileapi/remove_operation_delegate.h46
-rw-r--r--storage/browser/fileapi/sandbox_directory_database.cc939
-rw-r--r--storage/browser/fileapi/sandbox_directory_database.h135
-rw-r--r--storage/browser/fileapi/sandbox_file_stream_writer.cc247
-rw-r--r--storage/browser/fileapi/sandbox_file_stream_writer.h96
-rw-r--r--storage/browser/fileapi/sandbox_file_system_backend.cc166
-rw-r--r--storage/browser/fileapi/sandbox_file_system_backend.h85
-rw-r--r--storage/browser/fileapi/sandbox_file_system_backend_delegate.cc673
-rw-r--r--storage/browser/fileapi/sandbox_file_system_backend_delegate.h260
-rw-r--r--storage/browser/fileapi/sandbox_isolated_origin_database.cc80
-rw-r--r--storage/browser/fileapi/sandbox_isolated_origin_database.h61
-rw-r--r--storage/browser/fileapi/sandbox_origin_database.cc347
-rw-r--r--storage/browser/fileapi/sandbox_origin_database.h77
-rw-r--r--storage/browser/fileapi/sandbox_origin_database_interface.cc20
-rw-r--r--storage/browser/fileapi/sandbox_origin_database_interface.h55
-rw-r--r--storage/browser/fileapi/sandbox_prioritized_origin_database.cc223
-rw-r--r--storage/browser/fileapi/sandbox_prioritized_origin_database.h72
-rw-r--r--storage/browser/fileapi/sandbox_quota_observer.cc140
-rw-r--r--storage/browser/fileapi/sandbox_quota_observer.h81
-rw-r--r--storage/browser/fileapi/task_runner_bound_observer_list.h99
-rw-r--r--storage/browser/fileapi/timed_task_helper.cc92
-rw-r--r--storage/browser/fileapi/timed_task_helper.h59
-rw-r--r--storage/browser/fileapi/transient_file_util.cc55
-rw-r--r--storage/browser/fileapi/transient_file_util.h36
-rw-r--r--storage/browser/fileapi/watcher_manager.h67
-rw-r--r--storage/browser/quota/OWNERS1
-rw-r--r--storage/browser/quota/quota_callbacks.h129
-rw-r--r--storage/browser/quota/quota_client.h81
-rw-r--r--storage/browser/quota/quota_database.cc657
-rw-r--r--storage/browser/quota/quota_database.h190
-rw-r--r--storage/browser/quota/quota_manager.cc1631
-rw-r--r--storage/browser/quota/quota_manager.h460
-rw-r--r--storage/browser/quota/quota_manager_proxy.cc161
-rw-r--r--storage/browser/quota/quota_manager_proxy.h79
-rw-r--r--storage/browser/quota/quota_task.cc79
-rw-r--r--storage/browser/quota/quota_task.h79
-rw-r--r--storage/browser/quota/quota_temporary_storage_evictor.cc261
-rw-r--r--storage/browser/quota/quota_temporary_storage_evictor.h134
-rw-r--r--storage/browser/quota/special_storage_policy.cc38
-rw-r--r--storage/browser/quota/special_storage_policy.h86
-rw-r--r--storage/browser/quota/storage_monitor.cc379
-rw-r--r--storage/browser/quota/storage_monitor.h180
-rw-r--r--storage/browser/quota/storage_observer.cc65
-rw-r--r--storage/browser/quota/storage_observer.h79
-rw-r--r--storage/browser/quota/usage_tracker.cc694
-rw-r--r--storage/browser/quota/usage_tracker.h202
-rw-r--r--storage/browser/storage_browser_export.h34
-rw-r--r--storage/storage_browser.gyp199
154 files changed, 29926 insertions, 0 deletions
diff --git a/storage/DEPS b/storage/DEPS
index 8e3d4fd..c311233 100644
--- a/storage/DEPS
+++ b/storage/DEPS
@@ -1,5 +1,7 @@
include_rules = [
"+net",
"+sql",
+ "+third_party/leveldatabase",
+ "+third_party/sqlite",
"+third_party/WebKit/public/platform",
]
diff --git a/storage/browser/BUILD.gn b/storage/browser/BUILD.gn
new file mode 100644
index 0000000..7b0b461
--- /dev/null
+++ b/storage/browser/BUILD.gn
@@ -0,0 +1,192 @@
+# Copyright 2014 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.
+
+# GYP version: storage/storage_browser.gyp:storage
+component("browser") {
+ output_name = "storage_browser"
+ sources = [
+ "storage_browser_export.h",
+ "blob/blob_data_handle.cc",
+ "blob/blob_data_handle.h",
+ "blob/blob_storage_context.cc",
+ "blob/blob_storage_context.h",
+ "blob/blob_url_request_job.cc",
+ "blob/blob_url_request_job.h",
+ "blob/blob_url_request_job_factory.cc",
+ "blob/blob_url_request_job_factory.h",
+ "blob/file_stream_reader.cc",
+ "blob/file_stream_reader.h",
+ "blob/local_file_stream_reader.cc",
+ "blob/local_file_stream_reader.h",
+ "blob/view_blob_internals_job.cc",
+ "blob/view_blob_internals_job.h",
+ "database/database_quota_client.cc",
+ "database/database_quota_client.h",
+ "database/database_tracker.cc",
+ "database/database_tracker.h",
+ "database/database_util.cc",
+ "database/database_util.h",
+ "database/databases_table.cc",
+ "database/databases_table.h",
+ "database/vfs_backend.cc",
+ "database/vfs_backend.h",
+ "fileapi/async_file_util.h",
+ "fileapi/async_file_util_adapter.cc",
+ "fileapi/async_file_util_adapter.h",
+ "fileapi/copy_or_move_file_validator.h",
+ "fileapi/copy_or_move_operation_delegate.cc",
+ "fileapi/copy_or_move_operation_delegate.h",
+ "fileapi/dragged_file_util.cc",
+ "fileapi/dragged_file_util.h",
+ "fileapi/external_mount_points.cc",
+ "fileapi/external_mount_points.h",
+ "fileapi/file_observers.h",
+ "fileapi/file_permission_policy.h",
+ "fileapi/file_stream_writer.h",
+ "fileapi/file_system_backend.h",
+ "fileapi/file_system_context.cc",
+ "fileapi/file_system_context.h",
+ "fileapi/file_system_dir_url_request_job.cc",
+ "fileapi/file_system_dir_url_request_job.h",
+ "fileapi/file_system_file_stream_reader.cc",
+ "fileapi/file_system_file_stream_reader.h",
+ "fileapi/file_system_file_util.cc",
+ "fileapi/file_system_file_util.h",
+ "fileapi/file_system_operation.h",
+ "fileapi/file_system_operation_context.cc",
+ "fileapi/file_system_operation_context.h",
+ "fileapi/file_system_operation_impl.cc",
+ "fileapi/file_system_operation_impl.h",
+ "fileapi/file_system_operation_runner.cc",
+ "fileapi/file_system_operation_runner.h",
+ "fileapi/file_system_options.cc",
+ "fileapi/file_system_options.h",
+ "fileapi/file_system_quota_client.cc",
+ "fileapi/file_system_quota_client.h",
+ "fileapi/file_system_quota_util.h",
+ "fileapi/file_system_url.cc",
+ "fileapi/file_system_url.h",
+ "fileapi/file_system_url_request_job.cc",
+ "fileapi/file_system_url_request_job.h",
+ "fileapi/file_system_url_request_job_factory.cc",
+ "fileapi/file_system_url_request_job_factory.h",
+ "fileapi/file_system_usage_cache.cc",
+ "fileapi/file_system_usage_cache.h",
+ "fileapi/file_writer_delegate.cc",
+ "fileapi/file_writer_delegate.h",
+ "fileapi/isolated_context.cc",
+ "fileapi/isolated_context.h",
+ "fileapi/isolated_file_system_backend.cc",
+ "fileapi/isolated_file_system_backend.h",
+ "fileapi/local_file_stream_writer.cc",
+ "fileapi/local_file_stream_writer.h",
+ "fileapi/local_file_util.cc",
+ "fileapi/local_file_util.h",
+ "fileapi/mount_points.cc",
+ "fileapi/mount_points.h",
+ "fileapi/native_file_util.cc",
+ "fileapi/native_file_util.h",
+ "fileapi/obfuscated_file_util.cc",
+ "fileapi/obfuscated_file_util.h",
+ "fileapi/open_file_system_mode.h",
+ "fileapi/plugin_private_file_system_backend.cc",
+ "fileapi/plugin_private_file_system_backend.h",
+ "fileapi/quota/open_file_handle.cc",
+ "fileapi/quota/open_file_handle.h",
+ "fileapi/quota/open_file_handle_context.cc",
+ "fileapi/quota/open_file_handle_context.h",
+ "fileapi/quota/quota_backend_impl.cc",
+ "fileapi/quota/quota_backend_impl.h",
+ "fileapi/quota/quota_reservation.cc",
+ "fileapi/quota/quota_reservation.h",
+ "fileapi/quota/quota_reservation_buffer.cc",
+ "fileapi/quota/quota_reservation_buffer.h",
+ "fileapi/quota/quota_reservation_manager.cc",
+ "fileapi/quota/quota_reservation_manager.h",
+ "fileapi/recursive_operation_delegate.cc",
+ "fileapi/recursive_operation_delegate.h",
+ "fileapi/remove_operation_delegate.cc",
+ "fileapi/remove_operation_delegate.h",
+ "fileapi/sandbox_directory_database.cc",
+ "fileapi/sandbox_directory_database.h",
+ "fileapi/sandbox_file_stream_writer.cc",
+ "fileapi/sandbox_file_stream_writer.h",
+ "fileapi/sandbox_file_system_backend.cc",
+ "fileapi/sandbox_file_system_backend.h",
+ "fileapi/sandbox_file_system_backend_delegate.cc",
+ "fileapi/sandbox_file_system_backend_delegate.h",
+ "fileapi/sandbox_isolated_origin_database.cc",
+ "fileapi/sandbox_isolated_origin_database.h",
+ "fileapi/sandbox_origin_database.cc",
+ "fileapi/sandbox_origin_database.h",
+ "fileapi/sandbox_origin_database_interface.cc",
+ "fileapi/sandbox_origin_database_interface.h",
+ "fileapi/sandbox_prioritized_origin_database.cc",
+ "fileapi/sandbox_prioritized_origin_database.h",
+ "fileapi/sandbox_quota_observer.cc",
+ "fileapi/sandbox_quota_observer.h",
+ "fileapi/task_runner_bound_observer_list.h",
+ "fileapi/timed_task_helper.cc",
+ "fileapi/timed_task_helper.h",
+ "fileapi/transient_file_util.cc",
+ "fileapi/transient_file_util.h",
+ "quota/quota_callbacks.h",
+ "quota/quota_client.h",
+ "quota/quota_database.cc",
+ "quota/quota_database.h",
+ "quota/quota_manager.cc",
+ "quota/quota_manager.h",
+ "quota/quota_manager_proxy.cc",
+ "quota/quota_manager_proxy.h",
+ "quota/quota_task.cc",
+ "quota/quota_task.h",
+ "quota/quota_temporary_storage_evictor.cc",
+ "quota/quota_temporary_storage_evictor.h",
+ "quota/special_storage_policy.cc",
+ "quota/special_storage_policy.h",
+ "quota/storage_monitor.cc",
+ "quota/storage_monitor.h",
+ "quota/storage_observer.cc",
+ "quota/storage_observer.h",
+ "quota/usage_tracker.cc",
+ "quota/usage_tracker.h",
+ ]
+
+ defines = [ "STORAGE_BROWSER_IMPLEMENTATION" ]
+ configs += [ "//build/config/compiler:wexit_time_destructors" ]
+ if (is_win) {
+ cflags = [ "/wd4267" ] # TODO(jschuh): fix size_t to int truncations.
+ }
+
+ deps = [
+ "//base",
+ "//base:i18n",
+ "//base/third_party/dynamic_annotations",
+ "//net",
+ "//sql",
+ "//storage/common",
+ "//third_party/leveldatabase",
+ "//third_party/sqlite",
+ "//url",
+ ]
+
+ # TODO(GYP) support chrome_multiple_dll
+ #['chrome_multiple_dll!=1', {
+ # 'dependencies': [
+ # '<(DEPTH)/third_party/WebKit/public/blink.gyp:blink',
+ # ],
+ #}],
+}
+
+executable("dump_file_system") {
+ sources = [
+ "fileapi/dump_file_system.cc",
+ ]
+
+ deps = [
+ ":browser",
+ "//base",
+ "//storage/common",
+ ]
+}
diff --git a/storage/browser/DEPS b/storage/browser/DEPS
new file mode 100644
index 0000000..62bbdd0
--- /dev/null
+++ b/storage/browser/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "-webkit/renderer",
+]
diff --git a/storage/browser/blob/OWNERS b/storage/browser/blob/OWNERS
new file mode 100644
index 0000000..c7e8dcb
--- /dev/null
+++ b/storage/browser/blob/OWNERS
@@ -0,0 +1 @@
+jianli@chromium.org
diff --git a/storage/browser/blob/blob_data_handle.cc b/storage/browser/blob/blob_data_handle.cc
new file mode 100644
index 0000000..cf647b4
--- /dev/null
+++ b/storage/browser/blob/blob_data_handle.cc
@@ -0,0 +1,68 @@
+// 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 "storage/browser/blob/blob_data_handle.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "storage/browser/blob/blob_storage_context.h"
+#include "storage/common/blob/blob_data.h"
+
+namespace storage {
+
+BlobDataHandle::BlobDataHandleShared::BlobDataHandleShared(
+ BlobData* blob_data,
+ BlobStorageContext* context,
+ base::SequencedTaskRunner* task_runner)
+ : blob_data_(blob_data),
+ context_(context->AsWeakPtr()) {
+ context_->IncrementBlobRefCount(blob_data->uuid());
+}
+
+BlobData* BlobDataHandle::BlobDataHandleShared::data() const {
+ return blob_data_.get();
+}
+
+const std::string& BlobDataHandle::BlobDataHandleShared::uuid() const {
+ return blob_data_->uuid();
+}
+
+BlobDataHandle::BlobDataHandleShared::~BlobDataHandleShared() {
+ if (context_.get())
+ context_->DecrementBlobRefCount(blob_data_->uuid());
+}
+
+BlobDataHandle::BlobDataHandle(BlobData* blob_data,
+ BlobStorageContext* context,
+ base::SequencedTaskRunner* task_runner)
+ : io_task_runner_(task_runner),
+ shared_(new BlobDataHandleShared(blob_data, context, task_runner)) {
+ DCHECK(io_task_runner_.get());
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+}
+
+BlobDataHandle::BlobDataHandle(const BlobDataHandle& other) {
+ io_task_runner_ = other.io_task_runner_;
+ shared_ = other.shared_;
+}
+
+BlobDataHandle::~BlobDataHandle() {
+ BlobDataHandleShared* raw = shared_.get();
+ raw->AddRef();
+ shared_ = 0;
+ io_task_runner_->ReleaseSoon(FROM_HERE, raw);
+}
+
+BlobData* BlobDataHandle::data() const {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ return shared_->data();
+}
+
+std::string BlobDataHandle::uuid() const {
+ return shared_->uuid();
+}
+
+} // namespace storage
diff --git a/storage/browser/blob/blob_data_handle.h b/storage/browser/blob/blob_data_handle.h
new file mode 100644
index 0000000..1120bdf
--- /dev/null
+++ b/storage/browser/blob/blob_data_handle.h
@@ -0,0 +1,71 @@
+// 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 STORAGE_BROWSER_BLOB_BLOB_DATA_HANDLE_H_
+#define STORAGE_BROWSER_BLOB_BLOB_DATA_HANDLE_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/supports_user_data.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace storage {
+
+class BlobData;
+class BlobStorageContext;
+
+// A scoper object for use in chrome's main browser process, ensures
+// the underlying BlobData and its uuid remain in BlobStorageContext's
+// collection for the duration. This object has delete semantics and
+// maybe deleted on any thread.
+class STORAGE_EXPORT BlobDataHandle
+ : public base::SupportsUserData::Data {
+ public:
+ BlobDataHandle(const BlobDataHandle& other); // May be copied on any thread.
+ virtual ~BlobDataHandle(); // Maybe be deleted on any thread.
+ BlobData* data() const; // May only be accessed on the IO thread.
+
+ std::string uuid() const; // May be accessed on any thread.
+
+ private:
+ class BlobDataHandleShared
+ : public base::RefCountedThreadSafe<BlobDataHandleShared> {
+ public:
+ BlobDataHandleShared(BlobData* blob_data,
+ BlobStorageContext* context,
+ base::SequencedTaskRunner* task_runner);
+
+ BlobData* data() const;
+ const std::string& uuid() const;
+
+ private:
+ friend class base::DeleteHelper<BlobDataHandleShared>;
+ friend class base::RefCountedThreadSafe<BlobDataHandleShared>;
+ friend class BlobDataHandle;
+
+ virtual ~BlobDataHandleShared();
+
+ scoped_refptr<BlobData> blob_data_;
+ base::WeakPtr<BlobStorageContext> context_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobDataHandleShared);
+ };
+
+ friend class BlobStorageContext;
+ BlobDataHandle(BlobData* blob_data, BlobStorageContext* context,
+ base::SequencedTaskRunner* task_runner);
+
+ scoped_refptr<base::SequencedTaskRunner> io_task_runner_;
+ scoped_refptr<BlobDataHandleShared> shared_;
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_BLOB_BLOB_DATA_HANDLE_H_
diff --git a/storage/browser/blob/blob_storage_context.cc b/storage/browser/blob/blob_storage_context.cc
new file mode 100644
index 0000000..554c84e
--- /dev/null
+++ b/storage/browser/blob/blob_storage_context.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 "storage/browser/blob/blob_storage_context.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/common/blob/blob_data.h"
+#include "url/gurl.h"
+
+namespace storage {
+
+namespace {
+
+// We can't use GURL directly for these hash fragment manipulations
+// since it doesn't have specific knowlege of the BlobURL format. GURL
+// treats BlobURLs as if they were PathURLs which don't support hash
+// fragments.
+
+bool BlobUrlHasRef(const GURL& url) {
+ return url.spec().find('#') != std::string::npos;
+}
+
+GURL ClearBlobUrlRef(const GURL& url) {
+ size_t hash_pos = url.spec().find('#');
+ if (hash_pos == std::string::npos)
+ return url;
+ return GURL(url.spec().substr(0, hash_pos));
+}
+
+// TODO(michaeln): use base::SysInfo::AmountOfPhysicalMemoryMB() in some
+// way to come up with a better limit.
+static const int64 kMaxMemoryUsage = 500 * 1024 * 1024; // Half a gig.
+
+} // namespace
+
+BlobStorageContext::BlobMapEntry::BlobMapEntry()
+ : refcount(0), flags(0) {
+}
+
+BlobStorageContext::BlobMapEntry::BlobMapEntry(
+ int refcount, int flags, BlobData* data)
+ : refcount(refcount), flags(flags), data(data) {
+}
+
+BlobStorageContext::BlobMapEntry::~BlobMapEntry() {
+}
+
+BlobStorageContext::BlobStorageContext()
+ : memory_usage_(0) {
+}
+
+BlobStorageContext::~BlobStorageContext() {
+}
+
+scoped_ptr<BlobDataHandle> BlobStorageContext::GetBlobDataFromUUID(
+ const std::string& uuid) {
+ scoped_ptr<BlobDataHandle> result;
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return result.Pass();
+ if (found->second.flags & EXCEEDED_MEMORY)
+ return result.Pass();
+ DCHECK(!(found->second.flags & BEING_BUILT));
+ result.reset(new BlobDataHandle(
+ found->second.data.get(), this, base::MessageLoopProxy::current().get()));
+ return result.Pass();
+}
+
+scoped_ptr<BlobDataHandle> BlobStorageContext::GetBlobDataFromPublicURL(
+ const GURL& url) {
+ BlobURLMap::iterator found = public_blob_urls_.find(
+ BlobUrlHasRef(url) ? ClearBlobUrlRef(url) : url);
+ if (found == public_blob_urls_.end())
+ return scoped_ptr<BlobDataHandle>();
+ return GetBlobDataFromUUID(found->second);
+}
+
+scoped_ptr<BlobDataHandle> BlobStorageContext::AddFinishedBlob(
+ const BlobData* data) {
+ StartBuildingBlob(data->uuid());
+ for (std::vector<BlobData::Item>::const_iterator iter =
+ data->items().begin();
+ iter != data->items().end(); ++iter) {
+ AppendBlobDataItem(data->uuid(), *iter);
+ }
+ FinishBuildingBlob(data->uuid(), data->content_type());
+ scoped_ptr<BlobDataHandle> handle = GetBlobDataFromUUID(data->uuid());
+ DecrementBlobRefCount(data->uuid());
+ return handle.Pass();
+}
+
+bool BlobStorageContext::RegisterPublicBlobURL(
+ const GURL& blob_url, const std::string& uuid) {
+ DCHECK(!BlobUrlHasRef(blob_url));
+ DCHECK(IsInUse(uuid));
+ DCHECK(!IsUrlRegistered(blob_url));
+ if (!IsInUse(uuid) || IsUrlRegistered(blob_url))
+ return false;
+ IncrementBlobRefCount(uuid);
+ public_blob_urls_[blob_url] = uuid;
+ return true;
+}
+
+void BlobStorageContext::RevokePublicBlobURL(const GURL& blob_url) {
+ DCHECK(!BlobUrlHasRef(blob_url));
+ if (!IsUrlRegistered(blob_url))
+ return;
+ DecrementBlobRefCount(public_blob_urls_[blob_url]);
+ public_blob_urls_.erase(blob_url);
+}
+
+void BlobStorageContext::StartBuildingBlob(const std::string& uuid) {
+ DCHECK(!IsInUse(uuid) && !uuid.empty());
+ blob_map_[uuid] = BlobMapEntry(1, BEING_BUILT, new BlobData(uuid));
+}
+
+void BlobStorageContext::AppendBlobDataItem(
+ const std::string& uuid, const BlobData::Item& item) {
+ DCHECK(IsBeingBuilt(uuid));
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return;
+ if (found->second.flags & EXCEEDED_MEMORY)
+ return;
+ BlobData* target_blob_data = found->second.data.get();
+ DCHECK(target_blob_data);
+
+ bool exceeded_memory = false;
+
+ // The blob data is stored in the canonical way which only contains a
+ // list of Data, File, and FileSystem items. Aggregated TYPE_BLOB items
+ // are expanded into the primitive constituent types.
+ // 1) The Data item is denoted by the raw data and length.
+ // 2) The File item is denoted by the file path, the range and the expected
+ // modification time.
+ // 3) The FileSystem File item is denoted by the FileSystem URL, the range
+ // and the expected modification time.
+ // 4) The Blob items are expanded.
+ // TODO(michaeln): Would be nice to avoid copying Data items when expanding.
+
+ DCHECK(item.length() > 0);
+ switch (item.type()) {
+ case BlobData::Item::TYPE_BYTES:
+ DCHECK(!item.offset());
+ exceeded_memory = !AppendBytesItem(target_blob_data,
+ item.bytes(),
+ static_cast<int64>(item.length()));
+ break;
+ case BlobData::Item::TYPE_FILE:
+ AppendFileItem(target_blob_data,
+ item.path(),
+ item.offset(),
+ item.length(),
+ item.expected_modification_time());
+ break;
+ case BlobData::Item::TYPE_FILE_FILESYSTEM:
+ AppendFileSystemFileItem(target_blob_data,
+ item.filesystem_url(),
+ item.offset(),
+ item.length(),
+ item.expected_modification_time());
+ break;
+ case BlobData::Item::TYPE_BLOB: {
+ scoped_ptr<BlobDataHandle> src = GetBlobDataFromUUID(item.blob_uuid());
+ if (src)
+ exceeded_memory = !ExpandStorageItems(target_blob_data,
+ src->data(),
+ item.offset(),
+ item.length());
+ break;
+ }
+ default:
+ NOTREACHED();
+ break;
+ }
+
+ // If we're using too much memory, drop this blob's data.
+ // TODO(michaeln): Blob memory storage does not yet spill over to disk,
+ // as a stop gap, we'll prevent memory usage over a max amount.
+ if (exceeded_memory) {
+ memory_usage_ -= target_blob_data->GetMemoryUsage();
+ found->second.flags |= EXCEEDED_MEMORY;
+ found->second.data = new BlobData(uuid);
+ return;
+ }
+}
+
+void BlobStorageContext::FinishBuildingBlob(
+ const std::string& uuid, const std::string& content_type) {
+ DCHECK(IsBeingBuilt(uuid));
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return;
+ found->second.data->set_content_type(content_type);
+ found->second.flags &= ~BEING_BUILT;
+}
+
+void BlobStorageContext::CancelBuildingBlob(const std::string& uuid) {
+ DCHECK(IsBeingBuilt(uuid));
+ DecrementBlobRefCount(uuid);
+}
+
+void BlobStorageContext::IncrementBlobRefCount(const std::string& uuid) {
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end()) {
+ DCHECK(false);
+ return;
+ }
+ ++(found->second.refcount);
+}
+
+void BlobStorageContext::DecrementBlobRefCount(const std::string& uuid) {
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return;
+ DCHECK_EQ(found->second.data->uuid(), uuid);
+ if (--(found->second.refcount) == 0) {
+ memory_usage_ -= found->second.data->GetMemoryUsage();
+ blob_map_.erase(found);
+ }
+}
+
+bool BlobStorageContext::ExpandStorageItems(
+ BlobData* target_blob_data, BlobData* src_blob_data,
+ uint64 offset, uint64 length) {
+ DCHECK(target_blob_data && src_blob_data &&
+ length != static_cast<uint64>(-1));
+
+ std::vector<BlobData::Item>::const_iterator iter =
+ src_blob_data->items().begin();
+ if (offset) {
+ for (; iter != src_blob_data->items().end(); ++iter) {
+ if (offset >= iter->length())
+ offset -= iter->length();
+ else
+ break;
+ }
+ }
+
+ for (; iter != src_blob_data->items().end() && length > 0; ++iter) {
+ uint64 current_length = iter->length() - offset;
+ uint64 new_length = current_length > length ? length : current_length;
+ if (iter->type() == BlobData::Item::TYPE_BYTES) {
+ if (!AppendBytesItem(
+ target_blob_data,
+ iter->bytes() + static_cast<size_t>(iter->offset() + offset),
+ static_cast<int64>(new_length))) {
+ return false; // exceeded memory
+ }
+ } else if (iter->type() == BlobData::Item::TYPE_FILE) {
+ AppendFileItem(target_blob_data,
+ iter->path(),
+ iter->offset() + offset,
+ new_length,
+ iter->expected_modification_time());
+ } else {
+ DCHECK(iter->type() == BlobData::Item::TYPE_FILE_FILESYSTEM);
+ AppendFileSystemFileItem(target_blob_data,
+ iter->filesystem_url(),
+ iter->offset() + offset,
+ new_length,
+ iter->expected_modification_time());
+ }
+ length -= new_length;
+ offset = 0;
+ }
+ return true;
+}
+
+bool BlobStorageContext::AppendBytesItem(
+ BlobData* target_blob_data, const char* bytes, int64 length) {
+ if (length < 0) {
+ DCHECK(false);
+ return false;
+ }
+ if (memory_usage_ + length > kMaxMemoryUsage)
+ return false;
+ target_blob_data->AppendData(bytes, static_cast<size_t>(length));
+ memory_usage_ += length;
+ return true;
+}
+
+void BlobStorageContext::AppendFileItem(
+ BlobData* target_blob_data,
+ const base::FilePath& file_path, uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
+ target_blob_data->AppendFile(file_path, offset, length,
+ expected_modification_time);
+
+ // It may be a temporary file that should be deleted when no longer needed.
+ scoped_refptr<ShareableFileReference> shareable_file =
+ ShareableFileReference::Get(file_path);
+ if (shareable_file.get())
+ target_blob_data->AttachShareableFileReference(shareable_file.get());
+}
+
+void BlobStorageContext::AppendFileSystemFileItem(
+ BlobData* target_blob_data,
+ const GURL& filesystem_url, uint64 offset, uint64 length,
+ const base::Time& expected_modification_time) {
+ target_blob_data->AppendFileSystemFile(filesystem_url, offset, length,
+ expected_modification_time);
+}
+
+bool BlobStorageContext::IsInUse(const std::string& uuid) {
+ return blob_map_.find(uuid) != blob_map_.end();
+}
+
+bool BlobStorageContext::IsBeingBuilt(const std::string& uuid) {
+ BlobMap::iterator found = blob_map_.find(uuid);
+ if (found == blob_map_.end())
+ return false;
+ return found->second.flags & BEING_BUILT;
+}
+
+bool BlobStorageContext::IsUrlRegistered(const GURL& blob_url) {
+ return public_blob_urls_.find(blob_url) != public_blob_urls_.end();
+}
+
+} // namespace storage
diff --git a/storage/browser/blob/blob_storage_context.h b/storage/browser/blob/blob_storage_context.h
new file mode 100644
index 0000000..7ccc133
--- /dev/null
+++ b/storage/browser/blob/blob_storage_context.h
@@ -0,0 +1,118 @@
+// 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 STORAGE_BROWSER_BLOB_BLOB_STORAGE_CONTEXT_H_
+#define STORAGE_BROWSER_BLOB_BLOB_STORAGE_CONTEXT_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/blob/blob_data.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+class Time;
+}
+
+namespace content {
+class BlobStorageHost;
+}
+
+namespace storage {
+
+class BlobDataHandle;
+
+// This class handles the logistics of blob Storage within the browser process,
+// and maintains a mapping from blob uuid to the data. The class is single
+// threaded and should only be used on the IO thread.
+// In chromium, there is one instance per profile.
+class STORAGE_EXPORT BlobStorageContext
+ : public base::SupportsWeakPtr<BlobStorageContext> {
+ public:
+ BlobStorageContext();
+ ~BlobStorageContext();
+
+ scoped_ptr<BlobDataHandle> GetBlobDataFromUUID(const std::string& uuid);
+ scoped_ptr<BlobDataHandle> GetBlobDataFromPublicURL(const GURL& url);
+
+ // Useful for coining blobs from within the browser process. If the
+ // blob cannot be added due to memory consumption, returns NULL.
+ scoped_ptr<BlobDataHandle> AddFinishedBlob(const BlobData* blob_data);
+
+ // Useful for coining blob urls from within the browser process.
+ bool RegisterPublicBlobURL(const GURL& url, const std::string& uuid);
+ void RevokePublicBlobURL(const GURL& url);
+
+ private:
+ friend class content::BlobStorageHost;
+ friend class BlobDataHandle::BlobDataHandleShared;
+ friend class ViewBlobInternalsJob;
+
+ enum EntryFlags {
+ BEING_BUILT = 1 << 0,
+ EXCEEDED_MEMORY = 1 << 1,
+ };
+
+ struct BlobMapEntry {
+ int refcount;
+ int flags;
+ scoped_refptr<BlobData> data;
+
+ BlobMapEntry();
+ BlobMapEntry(int refcount, int flags, BlobData* data);
+ ~BlobMapEntry();
+ };
+
+ typedef std::map<std::string, BlobMapEntry>
+ BlobMap;
+ typedef std::map<GURL, std::string> BlobURLMap;
+
+ void StartBuildingBlob(const std::string& uuid);
+ void AppendBlobDataItem(const std::string& uuid,
+ const BlobData::Item& data_item);
+ void FinishBuildingBlob(const std::string& uuid, const std::string& type);
+ void CancelBuildingBlob(const std::string& uuid);
+ void IncrementBlobRefCount(const std::string& uuid);
+ void DecrementBlobRefCount(const std::string& uuid);
+
+ bool ExpandStorageItems(BlobData* target_blob_data,
+ BlobData* src_blob_data,
+ uint64 offset,
+ uint64 length);
+ bool AppendBytesItem(BlobData* target_blob_data,
+ const char* data, int64 length);
+ void AppendFileItem(BlobData* target_blob_data,
+ const base::FilePath& file_path,
+ uint64 offset, uint64 length,
+ const base::Time& expected_modification_time);
+ void AppendFileSystemFileItem(
+ BlobData* target_blob_data,
+ const GURL& url, uint64 offset, uint64 length,
+ const base::Time& expected_modification_time);
+
+ bool IsInUse(const std::string& uuid);
+ bool IsBeingBuilt(const std::string& uuid);
+ bool IsUrlRegistered(const GURL& blob_url);
+
+ BlobMap blob_map_;
+ BlobURLMap public_blob_urls_;
+
+ // Used to keep track of how much memory is being utilized for blob data,
+ // we count only the items of TYPE_DATA which are held in memory and not
+ // items of TYPE_FILE.
+ int64 memory_usage_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobStorageContext);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_BLOB_BLOB_STORAGE_CONTEXT_H_
diff --git a/storage/browser/blob/blob_url_request_job.cc b/storage/browser/blob/blob_url_request_job.cc
new file mode 100644
index 0000000..0084277
--- /dev/null
+++ b/storage/browser/blob/blob_url_request_job.cc
@@ -0,0 +1,590 @@
+// 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 "storage/browser/blob/blob_url_request_job.h"
+
+#include <limits>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/files/file_util_proxy.h"
+#include "base/format_macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.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 "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_status.h"
+#include "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+
+namespace storage {
+
+namespace {
+
+bool IsFileType(BlobData::Item::Type type) {
+ switch (type) {
+ case BlobData::Item::TYPE_FILE:
+ case BlobData::Item::TYPE_FILE_FILESYSTEM:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
+BlobURLRequestJob::BlobURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const scoped_refptr<BlobData>& blob_data,
+ storage::FileSystemContext* file_system_context,
+ base::MessageLoopProxy* file_thread_proxy)
+ : net::URLRequestJob(request, network_delegate),
+ blob_data_(blob_data),
+ file_system_context_(file_system_context),
+ file_thread_proxy_(file_thread_proxy),
+ total_size_(0),
+ remaining_bytes_(0),
+ pending_get_file_info_count_(0),
+ current_item_index_(0),
+ current_item_offset_(0),
+ error_(false),
+ byte_range_set_(false),
+ weak_factory_(this) {
+ DCHECK(file_thread_proxy_.get());
+}
+
+void BlobURLRequestJob::Start() {
+ // Continue asynchronously.
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BlobURLRequestJob::DidStart, weak_factory_.GetWeakPtr()));
+}
+
+void BlobURLRequestJob::Kill() {
+ DeleteCurrentFileReader();
+
+ net::URLRequestJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+bool BlobURLRequestJob::ReadRawData(net::IOBuffer* dest,
+ int dest_size,
+ int* bytes_read) {
+ DCHECK_NE(dest_size, 0);
+ DCHECK(bytes_read);
+ DCHECK_GE(remaining_bytes_, 0);
+
+ // Bail out immediately if we encounter an error.
+ if (error_) {
+ *bytes_read = 0;
+ return true;
+ }
+
+ if (remaining_bytes_ < dest_size)
+ dest_size = static_cast<int>(remaining_bytes_);
+
+ // If we should copy zero bytes because |remaining_bytes_| is zero, short
+ // circuit here.
+ if (!dest_size) {
+ *bytes_read = 0;
+ return true;
+ }
+
+ // Keep track of the buffer.
+ DCHECK(!read_buf_.get());
+ read_buf_ = new net::DrainableIOBuffer(dest, dest_size);
+
+ return ReadLoop(bytes_read);
+}
+
+bool BlobURLRequestJob::GetMimeType(std::string* mime_type) const {
+ if (!response_info_)
+ return false;
+
+ return response_info_->headers->GetMimeType(mime_type);
+}
+
+void BlobURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) {
+ if (response_info_)
+ *info = *response_info_;
+}
+
+int BlobURLRequestJob::GetResponseCode() const {
+ if (!response_info_)
+ return -1;
+
+ return response_info_->headers->response_code();
+}
+
+void BlobURLRequestJob::SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) {
+ // We only care about "Range" header here.
+ std::vector<net::HttpByteRange> ranges;
+ if (net::HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ byte_range_set_ = true;
+ byte_range_ = ranges[0];
+ } else {
+ // We don't support multiple range requests in one single URL request,
+ // because we need to do multipart encoding here.
+ // TODO(jianli): Support multipart byte range requests.
+ NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+ }
+ }
+ }
+}
+
+BlobURLRequestJob::~BlobURLRequestJob() {
+ STLDeleteValues(&index_to_reader_);
+}
+
+void BlobURLRequestJob::DidStart() {
+ error_ = false;
+
+ // We only support GET request per the spec.
+ if (request()->method() != "GET") {
+ NotifyFailure(net::ERR_METHOD_NOT_SUPPORTED);
+ return;
+ }
+
+ // If the blob data is not present, bail out.
+ if (!blob_data_.get()) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ CountSize();
+}
+
+bool BlobURLRequestJob::AddItemLength(size_t index, int64 item_length) {
+ if (item_length > kint64max - total_size_) {
+ NotifyFailure(net::ERR_FAILED);
+ return false;
+ }
+
+ // Cache the size and add it to the total size.
+ DCHECK_LT(index, item_length_list_.size());
+ item_length_list_[index] = item_length;
+ total_size_ += item_length;
+ return true;
+}
+
+void BlobURLRequestJob::CountSize() {
+ pending_get_file_info_count_ = 0;
+ total_size_ = 0;
+ item_length_list_.resize(blob_data_->items().size());
+
+ for (size_t i = 0; i < blob_data_->items().size(); ++i) {
+ const BlobData::Item& item = blob_data_->items().at(i);
+ if (IsFileType(item.type())) {
+ ++pending_get_file_info_count_;
+ GetFileStreamReader(i)->GetLength(
+ base::Bind(&BlobURLRequestJob::DidGetFileItemLength,
+ weak_factory_.GetWeakPtr(), i));
+ continue;
+ }
+
+ if (!AddItemLength(i, item.length()))
+ return;
+ }
+
+ if (pending_get_file_info_count_ == 0)
+ DidCountSize(net::OK);
+}
+
+void BlobURLRequestJob::DidCountSize(int error) {
+ DCHECK(!error_);
+
+ // If an error occured, bail out.
+ if (error != net::OK) {
+ NotifyFailure(error);
+ return;
+ }
+
+ // Apply the range requirement.
+ if (!byte_range_.ComputeBounds(total_size_)) {
+ NotifyFailure(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+ return;
+ }
+
+ remaining_bytes_ = byte_range_.last_byte_position() -
+ byte_range_.first_byte_position() + 1;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ // Do the seek at the beginning of the request.
+ if (byte_range_.first_byte_position())
+ Seek(byte_range_.first_byte_position());
+
+ NotifySuccess();
+}
+
+void BlobURLRequestJob::DidGetFileItemLength(size_t index, int64 result) {
+ // Do nothing if we have encountered an error.
+ if (error_)
+ return;
+
+ if (result == net::ERR_UPLOAD_FILE_CHANGED) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ } else if (result < 0) {
+ NotifyFailure(result);
+ return;
+ }
+
+ DCHECK_LT(index, blob_data_->items().size());
+ const BlobData::Item& item = blob_data_->items().at(index);
+ DCHECK(IsFileType(item.type()));
+
+ uint64 file_length = result;
+ uint64 item_offset = item.offset();
+ uint64 item_length = item.length();
+
+ if (item_offset > file_length) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ uint64 max_length = file_length - item_offset;
+
+ // If item length is -1, we need to use the file size being resolved
+ // in the real time.
+ if (item_length == static_cast<uint64>(-1)) {
+ item_length = max_length;
+ } else if (item_length > max_length) {
+ NotifyFailure(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+
+ if (!AddItemLength(index, item_length))
+ return;
+
+ if (--pending_get_file_info_count_ == 0)
+ DidCountSize(net::OK);
+}
+
+void BlobURLRequestJob::Seek(int64 offset) {
+ // Skip the initial items that are not in the range.
+ for (current_item_index_ = 0;
+ current_item_index_ < blob_data_->items().size() &&
+ offset >= item_length_list_[current_item_index_];
+ ++current_item_index_) {
+ offset -= item_length_list_[current_item_index_];
+ }
+
+ // Set the offset that need to jump to for the first item in the range.
+ current_item_offset_ = offset;
+
+ if (offset == 0)
+ return;
+
+ // Adjust the offset of the first stream if it is of file type.
+ const BlobData::Item& item = blob_data_->items().at(current_item_index_);
+ if (IsFileType(item.type())) {
+ DeleteCurrentFileReader();
+ CreateFileStreamReader(current_item_index_, offset);
+ }
+}
+
+bool BlobURLRequestJob::ReadItem() {
+ // Are we done with reading all the blob data?
+ if (remaining_bytes_ == 0)
+ return true;
+
+ // If we get to the last item but still expect something to read, bail out
+ // since something is wrong.
+ if (current_item_index_ >= blob_data_->items().size()) {
+ NotifyFailure(net::ERR_FAILED);
+ return false;
+ }
+
+ // Compute the bytes to read for current item.
+ int bytes_to_read = ComputeBytesToRead();
+
+ // If nothing to read for current item, advance to next item.
+ if (bytes_to_read == 0) {
+ AdvanceItem();
+ return ReadItem();
+ }
+
+ // Do the reading.
+ const BlobData::Item& item = blob_data_->items().at(current_item_index_);
+ if (item.type() == BlobData::Item::TYPE_BYTES)
+ return ReadBytesItem(item, bytes_to_read);
+ if (IsFileType(item.type())) {
+ return ReadFileItem(GetFileStreamReader(current_item_index_),
+ bytes_to_read);
+ }
+ NOTREACHED();
+ return false;
+}
+
+void BlobURLRequestJob::AdvanceItem() {
+ // Close the file if the current item is a file.
+ DeleteCurrentFileReader();
+
+ // Advance to the next item.
+ current_item_index_++;
+ current_item_offset_ = 0;
+}
+
+void BlobURLRequestJob::AdvanceBytesRead(int result) {
+ DCHECK_GT(result, 0);
+
+ // Do we finish reading the current item?
+ current_item_offset_ += result;
+ if (current_item_offset_ == item_length_list_[current_item_index_])
+ AdvanceItem();
+
+ // Subtract the remaining bytes.
+ remaining_bytes_ -= result;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ // Adjust the read buffer.
+ read_buf_->DidConsume(result);
+ DCHECK_GE(read_buf_->BytesRemaining(), 0);
+}
+
+bool BlobURLRequestJob::ReadBytesItem(const BlobData::Item& item,
+ int bytes_to_read) {
+ DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read);
+
+ memcpy(read_buf_->data(),
+ item.bytes() + item.offset() + current_item_offset_,
+ bytes_to_read);
+
+ AdvanceBytesRead(bytes_to_read);
+ return true;
+}
+
+bool BlobURLRequestJob::ReadFileItem(FileStreamReader* reader,
+ int bytes_to_read) {
+ DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read);
+ DCHECK(reader);
+ const int result = reader->Read(
+ read_buf_.get(),
+ bytes_to_read,
+ base::Bind(&BlobURLRequestJob::DidReadFile, base::Unretained(this)));
+ if (result >= 0) {
+ // Data is immediately available.
+ if (GetStatus().is_io_pending())
+ DidReadFile(result);
+ else
+ AdvanceBytesRead(result);
+ return true;
+ }
+ if (result == net::ERR_IO_PENDING)
+ SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0));
+ else
+ NotifyFailure(result);
+ return false;
+}
+
+void BlobURLRequestJob::DidReadFile(int result) {
+ if (result <= 0) {
+ NotifyFailure(net::ERR_FAILED);
+ return;
+ }
+ SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status
+
+ AdvanceBytesRead(result);
+
+ // If the read buffer is completely filled, we're done.
+ if (!read_buf_->BytesRemaining()) {
+ int bytes_read = BytesReadCompleted();
+ NotifyReadComplete(bytes_read);
+ return;
+ }
+
+ // Otherwise, continue the reading.
+ int bytes_read = 0;
+ if (ReadLoop(&bytes_read))
+ NotifyReadComplete(bytes_read);
+}
+
+void BlobURLRequestJob::DeleteCurrentFileReader() {
+ IndexToReaderMap::iterator found = index_to_reader_.find(current_item_index_);
+ if (found != index_to_reader_.end() && found->second) {
+ delete found->second;
+ index_to_reader_.erase(found);
+ }
+}
+
+int BlobURLRequestJob::BytesReadCompleted() {
+ int bytes_read = read_buf_->BytesConsumed();
+ read_buf_ = NULL;
+ return bytes_read;
+}
+
+int BlobURLRequestJob::ComputeBytesToRead() const {
+ int64 current_item_length = item_length_list_[current_item_index_];
+
+ int64 item_remaining = current_item_length - current_item_offset_;
+ int64 buf_remaining = read_buf_->BytesRemaining();
+ int64 max_remaining = std::numeric_limits<int>::max();
+
+ int64 min = std::min(std::min(std::min(item_remaining,
+ buf_remaining),
+ remaining_bytes_),
+ max_remaining);
+
+ return static_cast<int>(min);
+}
+
+bool BlobURLRequestJob::ReadLoop(int* bytes_read) {
+ // Read until we encounter an error or could not get the data immediately.
+ while (remaining_bytes_ > 0 && read_buf_->BytesRemaining() > 0) {
+ if (!ReadItem())
+ return false;
+ }
+
+ *bytes_read = BytesReadCompleted();
+ return true;
+}
+
+void BlobURLRequestJob::NotifySuccess() {
+ net::HttpStatusCode status_code = net::HTTP_OK;
+ if (byte_range_set_ && byte_range_.IsValid())
+ status_code = net::HTTP_PARTIAL_CONTENT;
+ HeadersCompleted(status_code);
+}
+
+void BlobURLRequestJob::NotifyFailure(int error_code) {
+ error_ = true;
+
+ // If we already return the headers on success, we can't change the headers
+ // now. Instead, we just error out.
+ if (response_info_) {
+ NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED,
+ error_code));
+ return;
+ }
+
+ net::HttpStatusCode status_code = net::HTTP_INTERNAL_SERVER_ERROR;
+ switch (error_code) {
+ case net::ERR_ACCESS_DENIED:
+ status_code = net::HTTP_FORBIDDEN;
+ break;
+ case net::ERR_FILE_NOT_FOUND:
+ status_code = net::HTTP_NOT_FOUND;
+ break;
+ case net::ERR_METHOD_NOT_SUPPORTED:
+ status_code = net::HTTP_METHOD_NOT_ALLOWED;
+ break;
+ case net::ERR_REQUEST_RANGE_NOT_SATISFIABLE:
+ status_code = net::HTTP_REQUESTED_RANGE_NOT_SATISFIABLE;
+ break;
+ case net::ERR_FAILED:
+ break;
+ default:
+ DCHECK(false);
+ break;
+ }
+ HeadersCompleted(status_code);
+}
+
+void BlobURLRequestJob::HeadersCompleted(net::HttpStatusCode status_code) {
+ std::string status("HTTP/1.1 ");
+ status.append(base::IntToString(status_code));
+ status.append(" ");
+ status.append(net::GetHttpReasonPhrase(status_code));
+ status.append("\0\0", 2);
+ net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status);
+
+ if (status_code == net::HTTP_OK || status_code == net::HTTP_PARTIAL_CONTENT) {
+ std::string content_length_header(net::HttpRequestHeaders::kContentLength);
+ content_length_header.append(": ");
+ content_length_header.append(base::Int64ToString(remaining_bytes_));
+ headers->AddHeader(content_length_header);
+ if (status_code == net::HTTP_PARTIAL_CONTENT) {
+ DCHECK(byte_range_set_);
+ DCHECK(byte_range_.IsValid());
+ std::string content_range_header(net::HttpResponseHeaders::kContentRange);
+ content_range_header.append(": bytes ");
+ content_range_header.append(base::StringPrintf(
+ "%" PRId64 "-%" PRId64,
+ byte_range_.first_byte_position(), byte_range_.last_byte_position()));
+ content_range_header.append("/");
+ content_range_header.append(base::StringPrintf("%" PRId64, total_size_));
+ headers->AddHeader(content_range_header);
+ }
+ if (!blob_data_->content_type().empty()) {
+ std::string content_type_header(net::HttpRequestHeaders::kContentType);
+ content_type_header.append(": ");
+ content_type_header.append(blob_data_->content_type());
+ headers->AddHeader(content_type_header);
+ }
+ if (!blob_data_->content_disposition().empty()) {
+ std::string content_disposition_header("Content-Disposition: ");
+ content_disposition_header.append(blob_data_->content_disposition());
+ headers->AddHeader(content_disposition_header);
+ }
+ }
+
+ response_info_.reset(new net::HttpResponseInfo());
+ response_info_->headers = headers;
+
+ set_expected_content_size(remaining_bytes_);
+
+ NotifyHeadersComplete();
+}
+
+FileStreamReader* BlobURLRequestJob::GetFileStreamReader(size_t index) {
+ DCHECK_LT(index, blob_data_->items().size());
+ const BlobData::Item& item = blob_data_->items().at(index);
+ if (!IsFileType(item.type()))
+ return NULL;
+ if (index_to_reader_.find(index) == index_to_reader_.end())
+ CreateFileStreamReader(index, 0);
+ DCHECK(index_to_reader_[index]);
+ return index_to_reader_[index];
+}
+
+void BlobURLRequestJob::CreateFileStreamReader(size_t index,
+ int64 additional_offset) {
+ DCHECK_LT(index, blob_data_->items().size());
+ const BlobData::Item& item = blob_data_->items().at(index);
+ DCHECK(IsFileType(item.type()));
+ DCHECK_EQ(0U, index_to_reader_.count(index));
+
+ FileStreamReader* reader = NULL;
+ switch (item.type()) {
+ case BlobData::Item::TYPE_FILE:
+ reader = FileStreamReader::CreateForLocalFile(
+ file_thread_proxy_.get(),
+ item.path(),
+ item.offset() + additional_offset,
+ item.expected_modification_time());
+ break;
+ case BlobData::Item::TYPE_FILE_FILESYSTEM:
+ reader = file_system_context_->CreateFileStreamReader(
+ storage::FileSystemURL(
+ file_system_context_->CrackURL(
+ item.filesystem_url())),
+ item.offset() + additional_offset,
+ item.expected_modification_time())
+ .release();
+ break;
+ default:
+ NOTREACHED();
+ }
+ DCHECK(reader);
+ index_to_reader_[index] = reader;
+}
+
+} // namespace storage
diff --git a/storage/browser/blob/blob_url_request_job.h b/storage/browser/blob/blob_url_request_job.h
new file mode 100644
index 0000000..58b6620
--- /dev/null
+++ b/storage/browser/blob/blob_url_request_job.h
@@ -0,0 +1,129 @@
+// 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 STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_H_
+#define STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_H_
+
+#include <map>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/http/http_byte_range.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_request_job.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/blob/blob_data.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace storage {
+class FileSystemContext;
+}
+
+namespace net {
+class DrainableIOBuffer;
+class IOBuffer;
+}
+
+namespace storage {
+
+class FileStreamReader;
+
+// A request job that handles reading blob URLs.
+class STORAGE_EXPORT BlobURLRequestJob
+ : public net::URLRequestJob {
+ public:
+ BlobURLRequestJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const scoped_refptr<BlobData>& blob_data,
+ storage::FileSystemContext* file_system_context,
+ base::MessageLoopProxy* resolving_message_loop_proxy);
+
+ // net::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 GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const net::HttpRequestHeaders& headers) OVERRIDE;
+
+ protected:
+ virtual ~BlobURLRequestJob();
+
+ private:
+ typedef std::map<size_t, FileStreamReader*> IndexToReaderMap;
+
+ // For preparing for read: get the size, apply the range and perform seek.
+ void DidStart();
+ bool AddItemLength(size_t index, int64 item_length);
+ void CountSize();
+ void DidCountSize(int error);
+ void DidGetFileItemLength(size_t index, int64 result);
+ void Seek(int64 offset);
+
+ // For reading the blob.
+ bool ReadLoop(int* bytes_read);
+ bool ReadItem();
+ void AdvanceItem();
+ void AdvanceBytesRead(int result);
+ bool ReadBytesItem(const BlobData::Item& item, int bytes_to_read);
+ bool ReadFileItem(FileStreamReader* reader, int bytes_to_read);
+
+ void DidReadFile(int result);
+ void DeleteCurrentFileReader();
+
+ int ComputeBytesToRead() const;
+ int BytesReadCompleted();
+
+ // These methods convert the result of blob data reading into response headers
+ // and pass it to URLRequestJob's NotifyDone() or NotifyHeadersComplete().
+ void NotifySuccess();
+ void NotifyFailure(int);
+ void HeadersCompleted(net::HttpStatusCode status_code);
+
+ // Returns a FileStreamReader for a blob item at |index|.
+ // If the item at |index| is not of file this returns NULL.
+ FileStreamReader* GetFileStreamReader(size_t index);
+
+ // Creates a FileStreamReader for the item at |index| with additional_offset.
+ void CreateFileStreamReader(size_t index, int64 additional_offset);
+
+ scoped_refptr<BlobData> blob_data_;
+
+ // Variables for controlling read from |blob_data_|.
+ scoped_refptr<storage::FileSystemContext> file_system_context_;
+ scoped_refptr<base::MessageLoopProxy> file_thread_proxy_;
+ std::vector<int64> item_length_list_;
+ int64 total_size_;
+ int64 remaining_bytes_;
+ int pending_get_file_info_count_;
+ IndexToReaderMap index_to_reader_;
+ size_t current_item_index_;
+ int64 current_item_offset_;
+
+ // Holds the buffer for read data with the IOBuffer interface.
+ scoped_refptr<net::DrainableIOBuffer> read_buf_;
+
+ // Is set when NotifyFailure() is called and reset when DidStart is called.
+ bool error_;
+
+ bool byte_range_set_;
+ net::HttpByteRange byte_range_;
+
+ scoped_ptr<net::HttpResponseInfo> response_info_;
+
+ base::WeakPtrFactory<BlobURLRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobURLRequestJob);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_H_
diff --git a/storage/browser/blob/blob_url_request_job_factory.cc b/storage/browser/blob/blob_url_request_job_factory.cc
new file mode 100644
index 0000000..60f9c73
--- /dev/null
+++ b/storage/browser/blob/blob_url_request_job_factory.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2011 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 "storage/browser/blob/blob_url_request_job_factory.h"
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/string_util.h"
+#include "net/base/request_priority.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/browser/blob/blob_storage_context.h"
+#include "storage/browser/blob/blob_url_request_job.h"
+#include "storage/browser/fileapi/file_system_context.h"
+
+namespace storage {
+
+namespace {
+
+int kUserDataKey; // The value is not important, the addr is a key.
+
+BlobDataHandle* GetRequestedBlobDataHandle(net::URLRequest* request) {
+ return static_cast<BlobDataHandle*>(request->GetUserData(&kUserDataKey));
+}
+
+} // namespace
+
+// static
+scoped_ptr<net::URLRequest> BlobProtocolHandler::CreateBlobRequest(
+ scoped_ptr<BlobDataHandle> blob_data_handle,
+ const net::URLRequestContext* request_context,
+ net::URLRequest::Delegate* request_delegate) {
+ const GURL kBlobUrl("blob://see_user_data/");
+ scoped_ptr<net::URLRequest> request = request_context->CreateRequest(
+ kBlobUrl, net::DEFAULT_PRIORITY, request_delegate, NULL);
+ SetRequestedBlobDataHandle(request.get(), blob_data_handle.Pass());
+ return request.Pass();
+}
+
+// static
+void BlobProtocolHandler::SetRequestedBlobDataHandle(
+ net::URLRequest* request,
+ scoped_ptr<BlobDataHandle> blob_data_handle) {
+ request->SetUserData(&kUserDataKey, blob_data_handle.release());
+}
+
+BlobProtocolHandler::BlobProtocolHandler(
+ BlobStorageContext* context,
+ storage::FileSystemContext* file_system_context,
+ const scoped_refptr<base::MessageLoopProxy>& loop_proxy)
+ : file_system_context_(file_system_context), file_loop_proxy_(loop_proxy) {
+ if (context)
+ context_ = context->AsWeakPtr();
+}
+
+BlobProtocolHandler::~BlobProtocolHandler() {
+}
+
+net::URLRequestJob* BlobProtocolHandler::MaybeCreateJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
+ return new storage::BlobURLRequestJob(request,
+ network_delegate,
+ LookupBlobData(request),
+ file_system_context_.get(),
+ file_loop_proxy_.get());
+}
+
+scoped_refptr<storage::BlobData> BlobProtocolHandler::LookupBlobData(
+ net::URLRequest* request) const {
+ BlobDataHandle* blob_data_handle = GetRequestedBlobDataHandle(request);
+ if (blob_data_handle)
+ return blob_data_handle->data();
+ if (!context_.get())
+ return NULL;
+
+ // Support looking up based on uuid, the FeedbackExtensionAPI relies on this.
+ // TODO(michaeln): Replace this use case and others like it with a BlobReader
+ // impl that does not depend on urlfetching to perform this function.
+ const std::string kPrefix("blob:uuid/");
+ if (!StartsWithASCII(request->url().spec(), kPrefix, true))
+ return NULL;
+ std::string uuid = request->url().spec().substr(kPrefix.length());
+ scoped_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(uuid);
+ return handle.get() ? handle->data() : NULL;
+}
+
+} // namespace storage
diff --git a/storage/browser/blob/blob_url_request_job_factory.h b/storage/browser/blob/blob_url_request_job_factory.h
new file mode 100644
index 0000000..e1769eb
--- /dev/null
+++ b/storage/browser/blob/blob_url_request_job_factory.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2011 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 STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_FACTORY_H_
+#define STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_FACTORY_H_
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class MessageLoopProxy;
+} // namespace base
+
+namespace storage {
+class FileSystemContext;
+} // namespace storage
+
+namespace net {
+class URLRequestContext;
+} // namespace net
+
+namespace storage {
+
+class BlobData;
+class BlobDataHandle;
+class BlobStorageContext;
+
+class STORAGE_EXPORT BlobProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ // A helper to manufacture an URLRequest to retrieve the given blob.
+ static scoped_ptr<net::URLRequest> CreateBlobRequest(
+ scoped_ptr<BlobDataHandle> blob_data_handle,
+ const net::URLRequestContext* request_context,
+ net::URLRequest::Delegate* request_delegate);
+
+ // This class ignores the request's URL and uses the value given
+ // to SetRequestedBlobDataHandle instead.
+ static void SetRequestedBlobDataHandle(
+ net::URLRequest* request,
+ scoped_ptr<BlobDataHandle> blob_data_handle);
+
+ BlobProtocolHandler(
+ BlobStorageContext* context,
+ storage::FileSystemContext* file_system_context,
+ const scoped_refptr<base::MessageLoopProxy>& file_loop_proxy);
+ virtual ~BlobProtocolHandler();
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ scoped_refptr<BlobData> LookupBlobData(
+ net::URLRequest* request) const;
+
+ base::WeakPtr<BlobStorageContext> context_;
+ const scoped_refptr<storage::FileSystemContext> file_system_context_;
+ const scoped_refptr<base::MessageLoopProxy> file_loop_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobProtocolHandler);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_BLOB_BLOB_URL_REQUEST_JOB_FACTORY_H_
diff --git a/storage/browser/blob/file_stream_reader.cc b/storage/browser/blob/file_stream_reader.cc
new file mode 100644
index 0000000..5c4aaa2
--- /dev/null
+++ b/storage/browser/blob/file_stream_reader.cc
@@ -0,0 +1,20 @@
+// Copyright 2014 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 "storage/browser/blob/file_stream_reader.h"
+
+#include "base/time/time.h"
+
+namespace storage {
+
+// Verify if the underlying file has not been modified.
+bool FileStreamReader::VerifySnapshotTime(
+ const base::Time& expected_modification_time,
+ const base::File::Info& file_info) {
+ return expected_modification_time.is_null() ||
+ expected_modification_time.ToTimeT() ==
+ file_info.last_modified.ToTimeT();
+}
+
+} // namespace storage
diff --git a/storage/browser/blob/file_stream_reader.h b/storage/browser/blob/file_stream_reader.h
new file mode 100644
index 0000000..9b2ce02
--- /dev/null
+++ b/storage/browser/blob/file_stream_reader.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 STORAGE_BLOB_FILE_STREAM_READER_H_
+#define STORAGE_BLOB_FILE_STREAM_READER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file.h"
+#include "net/base/completion_callback.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class FilePath;
+class TaskRunner;
+class Time;
+}
+
+namespace net {
+class IOBuffer;
+}
+
+namespace storage {
+class FileSystemContext;
+class FileSystemURL;
+}
+
+namespace storage {
+
+// A generic interface for reading a file-like object.
+class FileStreamReader {
+ public:
+ // Creates a new FileReader for a local file |file_path|.
+ // |initial_offset| specifies the offset in the file where the first read
+ // should start. If the given offset is out of the file range any
+ // read operation may error out with net::ERR_REQUEST_RANGE_NOT_SATISFIABLE.
+ // |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.
+ STORAGE_EXPORT static FileStreamReader*
+ CreateForLocalFile(base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ const base::Time& expected_modification_time);
+
+ // Creates a new reader 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.
+ STORAGE_EXPORT static FileStreamReader*
+ CreateForFileSystemFile(storage::FileSystemContext* context,
+ const storage::FileSystemURL& url,
+ int64 initial_offset,
+ const base::Time& expected_modification_time);
+
+ // Verify if the underlying file has not been modified.
+ STORAGE_EXPORT static bool VerifySnapshotTime(
+ const base::Time& expected_modification_time,
+ const base::File::Info& file_info);
+
+ // It is valid to delete the reader at any time. If the stream is deleted
+ // while it has a pending read, its callback will not be called.
+ virtual ~FileStreamReader() {}
+
+ // Reads from the current cursor position asynchronously.
+ //
+ // Up to buf_len bytes will be copied into buf. (In other words, partial
+ // reads are allowed.) Returns the number of bytes copied, 0 if at
+ // end-of-file, or an error code if the operation could not be performed.
+ // If the read could not complete synchronously, then ERR_IO_PENDING is
+ // returned, and the callback will be run on the thread where Read()
+ // was called, when the read has completed.
+ //
+ // It is invalid to call Read while there is an in-flight Read operation.
+ //
+ // If the stream is deleted while it has an in-flight Read operation
+ // |callback| will not be called.
+ virtual int Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) = 0;
+
+ // Returns the length of the file if it could successfully retrieve the
+ // file info *and* its last modification time equals to
+ // expected modification time (rv >= 0 cases).
+ // Otherwise, a negative error code is returned (rv < 0 cases).
+ // If the stream is deleted while it has an in-flight GetLength operation
+ // |callback| will not be called.
+ // Note that the return type is int64 to return a larger file's size (a file
+ // larger than 2G) but an error code should fit in the int range (may be
+ // smaller than int64 range).
+ virtual int64 GetLength(const net::Int64CompletionCallback& callback) = 0;
+};
+
+} // namespace storage
+
+#endif // STORAGE_BLOB_FILE_STREAM_READER_H_
diff --git a/storage/browser/blob/local_file_stream_reader.cc b/storage/browser/blob/local_file_stream_reader.cc
new file mode 100644
index 0000000..e6403b7
--- /dev/null
+++ b/storage/browser/blob/local_file_stream_reader.cc
@@ -0,0 +1,169 @@
+// 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 "storage/browser/blob/local_file_stream_reader.h"
+
+#include "base/files/file_util.h"
+#include "base/files/file_util_proxy.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task_runner.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+
+namespace storage {
+
+namespace {
+
+const int kOpenFlagsForRead = base::File::FLAG_OPEN |
+ base::File::FLAG_READ |
+ base::File::FLAG_ASYNC;
+
+} // namespace
+
+FileStreamReader* FileStreamReader::CreateForLocalFile(
+ base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ const base::Time& expected_modification_time) {
+ return new LocalFileStreamReader(task_runner, file_path, initial_offset,
+ expected_modification_time);
+}
+
+LocalFileStreamReader::~LocalFileStreamReader() {
+}
+
+int LocalFileStreamReader::Read(net::IOBuffer* buf, int buf_len,
+ const net::CompletionCallback& callback) {
+ DCHECK(!has_pending_open_);
+ if (stream_impl_)
+ return stream_impl_->Read(buf, buf_len, callback);
+ return Open(base::Bind(&LocalFileStreamReader::DidOpenForRead,
+ weak_factory_.GetWeakPtr(),
+ make_scoped_refptr(buf), buf_len, callback));
+}
+
+int64 LocalFileStreamReader::GetLength(
+ const net::Int64CompletionCallback& callback) {
+ const bool posted = base::FileUtilProxy::GetFileInfo(
+ task_runner_.get(),
+ file_path_,
+ base::Bind(&LocalFileStreamReader::DidGetFileInfoForGetLength,
+ weak_factory_.GetWeakPtr(),
+ callback));
+ DCHECK(posted);
+ return net::ERR_IO_PENDING;
+}
+
+LocalFileStreamReader::LocalFileStreamReader(
+ base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ const base::Time& expected_modification_time)
+ : task_runner_(task_runner),
+ file_path_(file_path),
+ initial_offset_(initial_offset),
+ expected_modification_time_(expected_modification_time),
+ has_pending_open_(false),
+ weak_factory_(this) {}
+
+int LocalFileStreamReader::Open(const net::CompletionCallback& callback) {
+ DCHECK(!has_pending_open_);
+ DCHECK(!stream_impl_.get());
+ has_pending_open_ = true;
+
+ // Call GetLength first to make it perform last-modified-time verification,
+ // and then call DidVerifyForOpen for do the rest.
+ return GetLength(base::Bind(&LocalFileStreamReader::DidVerifyForOpen,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void LocalFileStreamReader::DidVerifyForOpen(
+ const net::CompletionCallback& callback,
+ int64 get_length_result) {
+ if (get_length_result < 0) {
+ callback.Run(static_cast<int>(get_length_result));
+ return;
+ }
+
+ stream_impl_.reset(new net::FileStream(task_runner_));
+ const int result = stream_impl_->Open(
+ file_path_, kOpenFlagsForRead,
+ base::Bind(&LocalFileStreamReader::DidOpenFileStream,
+ weak_factory_.GetWeakPtr(),
+ callback));
+ if (result != net::ERR_IO_PENDING)
+ callback.Run(result);
+}
+
+void LocalFileStreamReader::DidOpenFileStream(
+ const net::CompletionCallback& callback,
+ int result) {
+ if (result != net::OK) {
+ callback.Run(result);
+ return;
+ }
+ result = stream_impl_->Seek(
+ base::File::FROM_BEGIN, initial_offset_,
+ base::Bind(&LocalFileStreamReader::DidSeekFileStream,
+ weak_factory_.GetWeakPtr(),
+ callback));
+ if (result != net::ERR_IO_PENDING) {
+ callback.Run(result);
+ }
+}
+
+void LocalFileStreamReader::DidSeekFileStream(
+ const net::CompletionCallback& callback,
+ int64 seek_result) {
+ if (seek_result < 0) {
+ callback.Run(static_cast<int>(seek_result));
+ return;
+ }
+ if (seek_result != initial_offset_) {
+ callback.Run(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE);
+ return;
+ }
+ callback.Run(net::OK);
+}
+
+void LocalFileStreamReader::DidOpenForRead(
+ net::IOBuffer* buf,
+ int buf_len,
+ const net::CompletionCallback& callback,
+ int open_result) {
+ DCHECK(has_pending_open_);
+ has_pending_open_ = false;
+ if (open_result != net::OK) {
+ stream_impl_.reset();
+ callback.Run(open_result);
+ return;
+ }
+ DCHECK(stream_impl_.get());
+ const int read_result = stream_impl_->Read(buf, buf_len, callback);
+ if (read_result != net::ERR_IO_PENDING)
+ callback.Run(read_result);
+}
+
+void LocalFileStreamReader::DidGetFileInfoForGetLength(
+ const net::Int64CompletionCallback& callback,
+ base::File::Error error,
+ const base::File::Info& file_info) {
+ if (file_info.is_directory) {
+ callback.Run(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+ if (error != base::File::FILE_OK) {
+ callback.Run(net::FileErrorToNetError(error));
+ return;
+ }
+ if (!VerifySnapshotTime(expected_modification_time_, file_info)) {
+ callback.Run(net::ERR_UPLOAD_FILE_CHANGED);
+ return;
+ }
+ callback.Run(file_info.size);
+}
+
+} // namespace storage
diff --git a/storage/browser/blob/local_file_stream_reader.h b/storage/browser/blob/local_file_stream_reader.h
new file mode 100644
index 0000000..b9e3328
--- /dev/null
+++ b/storage/browser/blob/local_file_stream_reader.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 STORAGE_BROWSER_BLOB_LOCAL_FILE_STREAM_READER_H_
+#define STORAGE_BROWSER_BLOB_LOCAL_FILE_STREAM_READER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class TaskRunner;
+}
+
+namespace content {
+class LocalFileStreamReaderTest;
+}
+
+namespace net {
+class FileStream;
+}
+
+namespace storage {
+
+// A thin wrapper of net::FileStream with range support for sliced file
+// handling.
+class STORAGE_EXPORT LocalFileStreamReader
+ : public NON_EXPORTED_BASE(FileStreamReader) {
+ public:
+ virtual ~LocalFileStreamReader();
+
+ // 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:
+ friend class FileStreamReader;
+ friend class content::LocalFileStreamReaderTest;
+
+ LocalFileStreamReader(base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ const base::Time& expected_modification_time);
+ int Open(const net::CompletionCallback& callback);
+
+ // Callbacks that are chained from Open for Read.
+ void DidVerifyForOpen(const net::CompletionCallback& callback,
+ int64 get_length_result);
+ void DidOpenFileStream(const net::CompletionCallback& callback,
+ int result);
+ void DidSeekFileStream(const net::CompletionCallback& callback,
+ int64 seek_result);
+ void DidOpenForRead(net::IOBuffer* buf,
+ int buf_len,
+ const net::CompletionCallback& callback,
+ int open_result);
+
+ void DidGetFileInfoForGetLength(const net::Int64CompletionCallback& callback,
+ base::File::Error error,
+ const base::File::Info& file_info);
+
+ scoped_refptr<base::TaskRunner> task_runner_;
+ scoped_ptr<net::FileStream> stream_impl_;
+ const base::FilePath file_path_;
+ const int64 initial_offset_;
+ const base::Time expected_modification_time_;
+ bool has_pending_open_;
+ base::WeakPtrFactory<LocalFileStreamReader> weak_factory_;
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_BLOB_LOCAL_FILE_STREAM_READER_H_
diff --git a/storage/browser/blob/view_blob_internals_job.cc b/storage/browser/blob/view_blob_internals_job.cc
new file mode 100644
index 0000000..2065a31
--- /dev/null
+++ b/storage/browser/blob/view_blob_internals_job.cc
@@ -0,0 +1,242 @@
+// 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 "storage/browser/blob/view_blob_internals_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/format_macros.h"
+#include "base/i18n/number_formatting.h"
+#include "base/i18n/time_formatting.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/escape.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request.h"
+#include "storage/browser/blob/blob_storage_context.h"
+#include "storage/common/blob/blob_data.h"
+
+namespace {
+
+const char kEmptyBlobStorageMessage[] = "No available blob data.";
+const char kContentType[] = "Content Type: ";
+const char kContentDisposition[] = "Content Disposition: ";
+const char kCount[] = "Count: ";
+const char kIndex[] = "Index: ";
+const char kType[] = "Type: ";
+const char kPath[] = "Path: ";
+const char kURL[] = "URL: ";
+const char kModificationTime[] = "Modification Time: ";
+const char kOffset[] = "Offset: ";
+const char kLength[] = "Length: ";
+const char kUUID[] = "Uuid: ";
+const char kRefcount[] = "Refcount: ";
+
+void StartHTML(std::string* out) {
+ out->append(
+ "<!DOCTYPE HTML>"
+ "<html><title>Blob Storage Internals</title>"
+ "<meta http-equiv=\"Content-Security-Policy\""
+ " content=\"object-src 'none'; script-src 'none'\">\n"
+ "<style>\n"
+ "body { font-family: sans-serif; font-size: 0.8em; }\n"
+ "tt, code, pre { font-family: WebKitHack, monospace; }\n"
+ "form { display: inline }\n"
+ ".subsection_body { margin: 10px 0 10px 2em; }\n"
+ ".subsection_title { font-weight: bold; }\n"
+ "</style>\n"
+ "</head><body>\n\n");
+}
+
+void EndHTML(std::string* out) {
+ out->append("\n</body></html>");
+}
+
+void AddHTMLBoldText(const std::string& text, std::string* out) {
+ out->append("<b>");
+ out->append(net::EscapeForHTML(text));
+ out->append("</b>");
+}
+
+void StartHTMLList(std::string* out) {
+ out->append("\n<ul>");
+}
+
+void EndHTMLList(std::string* out) {
+ out->append("</ul>\n");
+}
+
+void AddHTMLListItem(const std::string& element_title,
+ const std::string& element_data,
+ std::string* out) {
+ out->append("<li>");
+ // No need to escape element_title since constant string is passed.
+ out->append(element_title);
+ out->append(net::EscapeForHTML(element_data));
+ out->append("</li>\n");
+}
+
+void AddHorizontalRule(std::string* out) {
+ out->append("\n<hr>\n");
+}
+
+} // namespace
+
+namespace storage {
+
+ViewBlobInternalsJob::ViewBlobInternalsJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ BlobStorageContext* blob_storage_context)
+ : net::URLRequestSimpleJob(request, network_delegate),
+ blob_storage_context_(blob_storage_context),
+ weak_factory_(this) {
+}
+
+ViewBlobInternalsJob::~ViewBlobInternalsJob() {
+}
+
+void ViewBlobInternalsJob::Start() {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&ViewBlobInternalsJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+bool ViewBlobInternalsJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (request_->url().has_query()) {
+ // Strip the query parameters.
+ GURL::Replacements replacements;
+ replacements.ClearQuery();
+ *location = request_->url().ReplaceComponents(replacements);
+ *http_status_code = 307;
+ return true;
+ }
+ return false;
+}
+
+void ViewBlobInternalsJob::Kill() {
+ net::URLRequestSimpleJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+int ViewBlobInternalsJob::GetData(
+ std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const {
+ mime_type->assign("text/html");
+ charset->assign("UTF-8");
+
+ data->clear();
+ StartHTML(data);
+ if (blob_storage_context_->blob_map_.empty())
+ data->append(kEmptyBlobStorageMessage);
+ else
+ GenerateHTML(data);
+ EndHTML(data);
+ return net::OK;
+}
+
+void ViewBlobInternalsJob::GenerateHTML(std::string* out) const {
+ for (BlobStorageContext::BlobMap::const_iterator iter =
+ blob_storage_context_->blob_map_.begin();
+ iter != blob_storage_context_->blob_map_.end();
+ ++iter) {
+ AddHTMLBoldText(iter->first, out);
+ GenerateHTMLForBlobData(*(iter->second.data.get()),
+ iter->second.refcount,
+ out);
+ }
+ if (!blob_storage_context_->public_blob_urls_.empty()) {
+ AddHorizontalRule(out);
+ for (BlobStorageContext::BlobURLMap::const_iterator iter =
+ blob_storage_context_->public_blob_urls_.begin();
+ iter != blob_storage_context_->public_blob_urls_.end();
+ ++iter) {
+ AddHTMLBoldText(iter->first.spec(), out);
+ StartHTMLList(out);
+ AddHTMLListItem(kUUID, iter->second, out);
+ EndHTMLList(out);
+ }
+ }
+}
+
+void ViewBlobInternalsJob::GenerateHTMLForBlobData(const BlobData& blob_data,
+ int refcount,
+ std::string* out) {
+ StartHTMLList(out);
+
+ AddHTMLListItem(kRefcount, base::IntToString(refcount), out);
+ if (!blob_data.content_type().empty())
+ AddHTMLListItem(kContentType, blob_data.content_type(), out);
+ if (!blob_data.content_disposition().empty())
+ AddHTMLListItem(kContentDisposition, blob_data.content_disposition(), out);
+
+ bool has_multi_items = blob_data.items().size() > 1;
+ if (has_multi_items) {
+ AddHTMLListItem(kCount,
+ base::UTF16ToUTF8(base::FormatNumber(blob_data.items().size())), out);
+ }
+
+ for (size_t i = 0; i < blob_data.items().size(); ++i) {
+ if (has_multi_items) {
+ AddHTMLListItem(kIndex, base::UTF16ToUTF8(base::FormatNumber(i)), out);
+ StartHTMLList(out);
+ }
+ const BlobData::Item& item = blob_data.items().at(i);
+
+ switch (item.type()) {
+ case BlobData::Item::TYPE_BYTES:
+ AddHTMLListItem(kType, "data", out);
+ break;
+ case BlobData::Item::TYPE_FILE:
+ AddHTMLListItem(kType, "file", out);
+ AddHTMLListItem(kPath,
+ net::EscapeForHTML(item.path().AsUTF8Unsafe()),
+ out);
+ if (!item.expected_modification_time().is_null()) {
+ AddHTMLListItem(kModificationTime, base::UTF16ToUTF8(
+ TimeFormatFriendlyDateAndTime(item.expected_modification_time())),
+ out);
+ }
+ break;
+ case BlobData::Item::TYPE_BLOB:
+ NOTREACHED(); // Should be flattened in the storage context.
+ break;
+ case BlobData::Item::TYPE_FILE_FILESYSTEM:
+ AddHTMLListItem(kType, "filesystem", out);
+ AddHTMLListItem(kURL, item.filesystem_url().spec(), out);
+ if (!item.expected_modification_time().is_null()) {
+ AddHTMLListItem(kModificationTime, base::UTF16ToUTF8(
+ TimeFormatFriendlyDateAndTime(item.expected_modification_time())),
+ out);
+ }
+ break;
+ case BlobData::Item::TYPE_UNKNOWN:
+ NOTREACHED();
+ break;
+ }
+ if (item.offset()) {
+ AddHTMLListItem(kOffset, base::UTF16ToUTF8(base::FormatNumber(
+ static_cast<int64>(item.offset()))), out);
+ }
+ if (static_cast<int64>(item.length()) != -1) {
+ AddHTMLListItem(kLength, base::UTF16ToUTF8(base::FormatNumber(
+ static_cast<int64>(item.length()))), out);
+ }
+
+ if (has_multi_items)
+ EndHTMLList(out);
+ }
+
+ EndHTMLList(out);
+}
+
+} // namespace storage
diff --git a/storage/browser/blob/view_blob_internals_job.h b/storage/browser/blob/view_blob_internals_job.h
new file mode 100644
index 0000000..3fc2f08
--- /dev/null
+++ b/storage/browser/blob/view_blob_internals_job.h
@@ -0,0 +1,57 @@
+// 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 STORAGE_BROWSER_BLOB_VIEW_BLOB_INTERNALS_JOB_H_
+#define STORAGE_BROWSER_BLOB_VIEW_BLOB_INTERNALS_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/url_request/url_request_simple_job.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace net {
+class URLRequest;
+} // namespace net
+
+namespace storage {
+
+class BlobData;
+class BlobStorageContext;
+
+// A job subclass that implements a protocol to inspect the internal
+// state of blob registry.
+class STORAGE_EXPORT ViewBlobInternalsJob
+ : public net::URLRequestSimpleJob {
+ public:
+ ViewBlobInternalsJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ BlobStorageContext* blob_storage_context);
+
+ virtual void Start() OVERRIDE;
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const OVERRIDE;
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) OVERRIDE;
+ virtual void Kill() OVERRIDE;
+
+ private:
+ virtual ~ViewBlobInternalsJob();
+
+ void GenerateHTML(std::string* out) const;
+ static void GenerateHTMLForBlobData(const BlobData& blob_data,
+ int refcount,
+ std::string* out);
+
+ BlobStorageContext* blob_storage_context_;
+ base::WeakPtrFactory<ViewBlobInternalsJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewBlobInternalsJob);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_BLOB_VIEW_BLOB_INTERNALS_JOB_H_
diff --git a/storage/browser/database/database_quota_client.cc b/storage/browser/database/database_quota_client.cc
new file mode 100644
index 0000000..5119a1c
--- /dev/null
+++ b/storage/browser/database/database_quota_client.cc
@@ -0,0 +1,220 @@
+// 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 "storage/browser/database/database_quota_client.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/task_runner_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "storage/browser/database/database_tracker.h"
+#include "storage/browser/database/database_util.h"
+#include "storage/common/database/database_identifier.h"
+
+using storage::QuotaClient;
+
+namespace storage {
+
+namespace {
+
+int64 GetOriginUsageOnDBThread(
+ DatabaseTracker* db_tracker,
+ const GURL& origin_url) {
+ OriginInfo info;
+ if (db_tracker->GetOriginInfo(storage::GetIdentifierFromOrigin(origin_url),
+ &info))
+ return info.TotalSize();
+ return 0;
+}
+
+void GetOriginsOnDBThread(
+ DatabaseTracker* db_tracker,
+ std::set<GURL>* origins_ptr) {
+ std::vector<std::string> origin_identifiers;
+ if (db_tracker->GetAllOriginIdentifiers(&origin_identifiers)) {
+ for (std::vector<std::string>::const_iterator iter =
+ origin_identifiers.begin();
+ iter != origin_identifiers.end(); ++iter) {
+ GURL origin = storage::GetOriginFromIdentifier(*iter);
+ origins_ptr->insert(origin);
+ }
+ }
+}
+
+void GetOriginsForHostOnDBThread(
+ DatabaseTracker* db_tracker,
+ std::set<GURL>* origins_ptr,
+ const std::string& host) {
+ std::vector<std::string> origin_identifiers;
+ if (db_tracker->GetAllOriginIdentifiers(&origin_identifiers)) {
+ for (std::vector<std::string>::const_iterator iter =
+ origin_identifiers.begin();
+ iter != origin_identifiers.end(); ++iter) {
+ GURL origin = storage::GetOriginFromIdentifier(*iter);
+ if (host == net::GetHostOrSpecFromURL(origin))
+ origins_ptr->insert(origin);
+ }
+ }
+}
+
+void DidGetOrigins(
+ const QuotaClient::GetOriginsCallback& callback,
+ std::set<GURL>* origins_ptr) {
+ callback.Run(*origins_ptr);
+}
+
+void DidDeleteOriginData(
+ base::SingleThreadTaskRunner* original_task_runner,
+ const QuotaClient::DeletionCallback& callback,
+ int result) {
+ if (result == net::ERR_IO_PENDING) {
+ // The callback will be invoked via
+ // DatabaseTracker::ScheduleDatabasesForDeletion.
+ return;
+ }
+
+ storage::QuotaStatusCode status;
+ if (result == net::OK)
+ status = storage::kQuotaStatusOk;
+ else
+ status = storage::kQuotaStatusUnknown;
+
+ if (original_task_runner->BelongsToCurrentThread())
+ callback.Run(status);
+ else
+ original_task_runner->PostTask(FROM_HERE, base::Bind(callback, status));
+}
+
+} // namespace
+
+DatabaseQuotaClient::DatabaseQuotaClient(
+ base::MessageLoopProxy* db_tracker_thread,
+ DatabaseTracker* db_tracker)
+ : db_tracker_thread_(db_tracker_thread), db_tracker_(db_tracker) {
+}
+
+DatabaseQuotaClient::~DatabaseQuotaClient() {
+ if (db_tracker_thread_.get() &&
+ !db_tracker_thread_->RunsTasksOnCurrentThread() && db_tracker_.get()) {
+ DatabaseTracker* tracker = db_tracker_.get();
+ tracker->AddRef();
+ db_tracker_ = NULL;
+ if (!db_tracker_thread_->ReleaseSoon(FROM_HERE, tracker))
+ tracker->Release();
+ }
+}
+
+QuotaClient::ID DatabaseQuotaClient::id() const {
+ return kDatabase;
+}
+
+void DatabaseQuotaClient::OnQuotaManagerDestroyed() {
+ delete this;
+}
+
+void DatabaseQuotaClient::GetOriginUsage(const GURL& origin_url,
+ storage::StorageType type,
+ const GetUsageCallback& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(db_tracker_.get());
+
+ // All databases are in the temp namespace for now.
+ if (type != storage::kStorageTypeTemporary) {
+ callback.Run(0);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ db_tracker_thread_.get(),
+ FROM_HERE,
+ base::Bind(&GetOriginUsageOnDBThread, db_tracker_, origin_url),
+ callback);
+}
+
+void DatabaseQuotaClient::GetOriginsForType(
+ storage::StorageType type,
+ const GetOriginsCallback& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(db_tracker_.get());
+
+ // All databases are in the temp namespace for now.
+ if (type != storage::kStorageTypeTemporary) {
+ callback.Run(std::set<GURL>());
+ return;
+ }
+
+ std::set<GURL>* origins_ptr = new std::set<GURL>();
+ db_tracker_thread_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&GetOriginsOnDBThread,
+ db_tracker_,
+ base::Unretained(origins_ptr)),
+ base::Bind(&DidGetOrigins,
+ callback,
+ base::Owned(origins_ptr)));
+}
+
+void DatabaseQuotaClient::GetOriginsForHost(
+ storage::StorageType type,
+ const std::string& host,
+ const GetOriginsCallback& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(db_tracker_.get());
+
+ // All databases are in the temp namespace for now.
+ if (type != storage::kStorageTypeTemporary) {
+ callback.Run(std::set<GURL>());
+ return;
+ }
+
+ std::set<GURL>* origins_ptr = new std::set<GURL>();
+ db_tracker_thread_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&GetOriginsForHostOnDBThread,
+ db_tracker_,
+ base::Unretained(origins_ptr),
+ host),
+ base::Bind(&DidGetOrigins,
+ callback,
+ base::Owned(origins_ptr)));
+}
+
+void DatabaseQuotaClient::DeleteOriginData(const GURL& origin,
+ storage::StorageType type,
+ const DeletionCallback& callback) {
+ DCHECK(!callback.is_null());
+ DCHECK(db_tracker_.get());
+
+ // All databases are in the temp namespace for now, so nothing to delete.
+ if (type != storage::kStorageTypeTemporary) {
+ callback.Run(storage::kQuotaStatusOk);
+ return;
+ }
+
+ base::Callback<void(int)> delete_callback =
+ base::Bind(&DidDeleteOriginData,
+ base::MessageLoopProxy::current(),
+ callback);
+
+ PostTaskAndReplyWithResult(
+ db_tracker_thread_.get(),
+ FROM_HERE,
+ base::Bind(&DatabaseTracker::DeleteDataForOrigin,
+ db_tracker_,
+ storage::GetIdentifierFromOrigin(origin),
+ delete_callback),
+ delete_callback);
+}
+
+bool DatabaseQuotaClient::DoesSupport(storage::StorageType type) const {
+ return type == storage::kStorageTypeTemporary;
+}
+
+} // namespace storage
diff --git a/storage/browser/database/database_quota_client.h b/storage/browser/database/database_quota_client.h
new file mode 100644
index 0000000..ae276c7
--- /dev/null
+++ b/storage/browser/database/database_quota_client.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011 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 STORAGE_BROWSER_DATABASE_DATABASE_QUOTA_CLIENT_H_
+#define STORAGE_BROWSER_DATABASE_DATABASE_QUOTA_CLIENT_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "storage/browser/quota/quota_client.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/quota/quota_types.h"
+
+namespace storage {
+
+class DatabaseTracker;
+
+// A QuotaClient implementation to integrate WebSQLDatabases
+// with the quota management system. This interface is used
+// on the IO thread by the quota manager.
+class STORAGE_EXPORT_PRIVATE DatabaseQuotaClient
+ : public storage::QuotaClient {
+ public:
+ DatabaseQuotaClient(
+ base::MessageLoopProxy* tracker_thread,
+ DatabaseTracker* tracker);
+ virtual ~DatabaseQuotaClient();
+
+ // QuotaClient method overrides
+ virtual ID id() const OVERRIDE;
+ virtual void OnQuotaManagerDestroyed() OVERRIDE;
+ virtual void GetOriginUsage(const GURL& origin_url,
+ storage::StorageType type,
+ const GetUsageCallback& callback) OVERRIDE;
+ virtual void GetOriginsForType(storage::StorageType type,
+ const GetOriginsCallback& callback) OVERRIDE;
+ virtual void GetOriginsForHost(storage::StorageType type,
+ const std::string& host,
+ const GetOriginsCallback& callback) OVERRIDE;
+ virtual void DeleteOriginData(const GURL& origin,
+ storage::StorageType type,
+ const DeletionCallback& callback) OVERRIDE;
+ virtual bool DoesSupport(storage::StorageType type) const OVERRIDE;
+
+ private:
+ scoped_refptr<base::MessageLoopProxy> db_tracker_thread_;
+ scoped_refptr<DatabaseTracker> db_tracker_; // only used on its thread
+
+ DISALLOW_COPY_AND_ASSIGN(DatabaseQuotaClient);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_DATABASE_DATABASE_QUOTA_CLIENT_H_
diff --git a/storage/browser/database/database_tracker.cc b/storage/browser/database/database_tracker.cc
new file mode 100644
index 0000000..b7e4623
--- /dev/null
+++ b/storage/browser/database/database_tracker.cc
@@ -0,0 +1,869 @@
+// 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 "storage/browser/database/database_tracker.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "net/base/net_errors.h"
+#include "sql/connection.h"
+#include "sql/meta_table.h"
+#include "sql/transaction.h"
+#include "storage/browser/database/database_quota_client.h"
+#include "storage/browser/database/database_util.h"
+#include "storage/browser/database/databases_table.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/browser/quota/special_storage_policy.h"
+#include "storage/common/database/database_identifier.h"
+#include "third_party/sqlite/sqlite3.h"
+
+namespace storage {
+
+const base::FilePath::CharType kDatabaseDirectoryName[] =
+ FILE_PATH_LITERAL("databases");
+const base::FilePath::CharType kIncognitoDatabaseDirectoryName[] =
+ FILE_PATH_LITERAL("databases-incognito");
+const base::FilePath::CharType kTrackerDatabaseFileName[] =
+ FILE_PATH_LITERAL("Databases.db");
+static const int kCurrentVersion = 2;
+static const int kCompatibleVersion = 1;
+
+const base::FilePath::CharType kTemporaryDirectoryPrefix[] =
+ FILE_PATH_LITERAL("DeleteMe");
+const base::FilePath::CharType kTemporaryDirectoryPattern[] =
+ FILE_PATH_LITERAL("DeleteMe*");
+
+OriginInfo::OriginInfo()
+ : total_size_(0) {}
+
+OriginInfo::OriginInfo(const OriginInfo& origin_info)
+ : origin_identifier_(origin_info.origin_identifier_),
+ total_size_(origin_info.total_size_),
+ database_info_(origin_info.database_info_) {}
+
+OriginInfo::~OriginInfo() {}
+
+void OriginInfo::GetAllDatabaseNames(
+ std::vector<base::string16>* databases) const {
+ for (DatabaseInfoMap::const_iterator it = database_info_.begin();
+ it != database_info_.end(); it++) {
+ databases->push_back(it->first);
+ }
+}
+
+int64 OriginInfo::GetDatabaseSize(const base::string16& database_name) const {
+ DatabaseInfoMap::const_iterator it = database_info_.find(database_name);
+ if (it != database_info_.end())
+ return it->second.first;
+ return 0;
+}
+
+base::string16 OriginInfo::GetDatabaseDescription(
+ const base::string16& database_name) const {
+ DatabaseInfoMap::const_iterator it = database_info_.find(database_name);
+ if (it != database_info_.end())
+ return it->second.second;
+ return base::string16();
+}
+
+OriginInfo::OriginInfo(const std::string& origin_identifier, int64 total_size)
+ : origin_identifier_(origin_identifier), total_size_(total_size) {}
+
+DatabaseTracker::DatabaseTracker(
+ const base::FilePath& profile_path,
+ bool is_incognito,
+ storage::SpecialStoragePolicy* special_storage_policy,
+ storage::QuotaManagerProxy* quota_manager_proxy,
+ base::MessageLoopProxy* db_tracker_thread)
+ : is_initialized_(false),
+ is_incognito_(is_incognito),
+ force_keep_session_state_(false),
+ shutting_down_(false),
+ profile_path_(profile_path),
+ db_dir_(is_incognito_
+ ? profile_path_.Append(kIncognitoDatabaseDirectoryName)
+ : profile_path_.Append(kDatabaseDirectoryName)),
+ db_(new sql::Connection()),
+ special_storage_policy_(special_storage_policy),
+ quota_manager_proxy_(quota_manager_proxy),
+ db_tracker_thread_(db_tracker_thread),
+ incognito_origin_directories_generator_(0) {
+ if (quota_manager_proxy) {
+ quota_manager_proxy->RegisterClient(
+ new DatabaseQuotaClient(db_tracker_thread, this));
+ }
+}
+
+DatabaseTracker::~DatabaseTracker() {
+ DCHECK(dbs_to_be_deleted_.empty());
+ DCHECK(deletion_callbacks_.empty());
+}
+
+void DatabaseTracker::DatabaseOpened(const std::string& origin_identifier,
+ const base::string16& database_name,
+ const base::string16& database_description,
+ int64 estimated_size,
+ int64* database_size) {
+ if (shutting_down_ || !LazyInit()) {
+ *database_size = 0;
+ return;
+ }
+
+ if (quota_manager_proxy_.get())
+ quota_manager_proxy_->NotifyStorageAccessed(
+ storage::QuotaClient::kDatabase,
+ storage::GetOriginFromIdentifier(origin_identifier),
+ storage::kStorageTypeTemporary);
+
+ InsertOrUpdateDatabaseDetails(origin_identifier, database_name,
+ database_description, estimated_size);
+ if (database_connections_.AddConnection(origin_identifier, database_name)) {
+ *database_size = SeedOpenDatabaseInfo(origin_identifier,
+ database_name,
+ database_description);
+ return;
+ }
+ *database_size = UpdateOpenDatabaseInfoAndNotify(origin_identifier,
+ database_name,
+ &database_description);
+}
+
+void DatabaseTracker::DatabaseModified(const std::string& origin_identifier,
+ const base::string16& database_name) {
+ if (!LazyInit())
+ return;
+ UpdateOpenDatabaseSizeAndNotify(origin_identifier, database_name);
+}
+
+void DatabaseTracker::DatabaseClosed(const std::string& origin_identifier,
+ const base::string16& database_name) {
+ if (database_connections_.IsEmpty()) {
+ DCHECK(!is_initialized_);
+ return;
+ }
+
+ // We call NotifiyStorageAccessed when a db is opened and also when
+ // closed because we don't call it for read while open.
+ if (quota_manager_proxy_.get())
+ quota_manager_proxy_->NotifyStorageAccessed(
+ storage::QuotaClient::kDatabase,
+ storage::GetOriginFromIdentifier(origin_identifier),
+ storage::kStorageTypeTemporary);
+
+ UpdateOpenDatabaseSizeAndNotify(origin_identifier, database_name);
+ if (database_connections_.RemoveConnection(origin_identifier, database_name))
+ DeleteDatabaseIfNeeded(origin_identifier, database_name);
+}
+
+void DatabaseTracker::HandleSqliteError(
+ const std::string& origin_identifier,
+ const base::string16& database_name,
+ int error) {
+ // We only handle errors that indicate corruption and we
+ // do so with a heavy hand, we delete it. Any renderers/workers
+ // with this database open will receive a message to close it
+ // immediately, once all have closed, the files will be deleted.
+ // In the interim, all attempts to open a new connection to that
+ // database will fail.
+ // Note: the client-side filters out all but these two errors as
+ // a small optimization, see WebDatabaseObserverImpl::HandleSqliteError.
+ if (error == SQLITE_CORRUPT || error == SQLITE_NOTADB) {
+ DeleteDatabase(origin_identifier, database_name,
+ net::CompletionCallback());
+ }
+}
+
+void DatabaseTracker::CloseDatabases(const DatabaseConnections& connections) {
+ if (database_connections_.IsEmpty()) {
+ DCHECK(!is_initialized_ || connections.IsEmpty());
+ return;
+ }
+
+ // When being closed by this route, there's a chance that
+ // the tracker missed some DatabseModified calls. This method is used
+ // when a renderer crashes to cleanup its open resources.
+ // We need to examine what we have in connections for the
+ // size of each open databases and notify any differences between the
+ // actual file sizes now.
+ std::vector<std::pair<std::string, base::string16> > open_dbs;
+ connections.ListConnections(&open_dbs);
+ for (std::vector<std::pair<std::string, base::string16> >::iterator it =
+ open_dbs.begin(); it != open_dbs.end(); ++it)
+ UpdateOpenDatabaseSizeAndNotify(it->first, it->second);
+
+ std::vector<std::pair<std::string, base::string16> > closed_dbs;
+ database_connections_.RemoveConnections(connections, &closed_dbs);
+ for (std::vector<std::pair<std::string, base::string16> >::iterator it =
+ closed_dbs.begin(); it != closed_dbs.end(); ++it) {
+ DeleteDatabaseIfNeeded(it->first, it->second);
+ }
+}
+
+void DatabaseTracker::DeleteDatabaseIfNeeded(
+ const std::string& origin_identifier,
+ const base::string16& database_name) {
+ DCHECK(!database_connections_.IsDatabaseOpened(origin_identifier,
+ database_name));
+ if (IsDatabaseScheduledForDeletion(origin_identifier, database_name)) {
+ DeleteClosedDatabase(origin_identifier, database_name);
+ dbs_to_be_deleted_[origin_identifier].erase(database_name);
+ if (dbs_to_be_deleted_[origin_identifier].empty())
+ dbs_to_be_deleted_.erase(origin_identifier);
+
+ PendingDeletionCallbacks::iterator callback = deletion_callbacks_.begin();
+ while (callback != deletion_callbacks_.end()) {
+ DatabaseSet::iterator found_origin =
+ callback->second.find(origin_identifier);
+ if (found_origin != callback->second.end()) {
+ std::set<base::string16>& databases = found_origin->second;
+ databases.erase(database_name);
+ if (databases.empty()) {
+ callback->second.erase(found_origin);
+ if (callback->second.empty()) {
+ net::CompletionCallback cb = callback->first;
+ cb.Run(net::OK);
+ callback = deletion_callbacks_.erase(callback);
+ continue;
+ }
+ }
+ }
+
+ ++callback;
+ }
+ }
+}
+
+void DatabaseTracker::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void DatabaseTracker::RemoveObserver(Observer* observer) {
+ // When we remove a listener, we do not know which cached information
+ // is still needed and which information can be discarded. So we just
+ // clear all caches and re-populate them as needed.
+ observers_.RemoveObserver(observer);
+ ClearAllCachedOriginInfo();
+}
+
+void DatabaseTracker::CloseTrackerDatabaseAndClearCaches() {
+ ClearAllCachedOriginInfo();
+
+ if (!is_incognito_) {
+ meta_table_.reset(NULL);
+ databases_table_.reset(NULL);
+ db_->Close();
+ is_initialized_ = false;
+ }
+}
+
+base::string16 DatabaseTracker::GetOriginDirectory(
+ const std::string& origin_identifier) {
+ if (!is_incognito_)
+ return base::UTF8ToUTF16(origin_identifier);
+
+ OriginDirectoriesMap::const_iterator it =
+ incognito_origin_directories_.find(origin_identifier);
+ if (it != incognito_origin_directories_.end())
+ return it->second;
+
+ base::string16 origin_directory =
+ base::IntToString16(incognito_origin_directories_generator_++);
+ incognito_origin_directories_[origin_identifier] = origin_directory;
+ return origin_directory;
+}
+
+base::FilePath DatabaseTracker::GetFullDBFilePath(
+ const std::string& origin_identifier,
+ const base::string16& database_name) {
+ DCHECK(!origin_identifier.empty());
+ if (!LazyInit())
+ return base::FilePath();
+
+ int64 id = databases_table_->GetDatabaseID(origin_identifier, database_name);
+ if (id < 0)
+ return base::FilePath();
+
+ return db_dir_.Append(base::FilePath::FromUTF16Unsafe(
+ GetOriginDirectory(origin_identifier))).AppendASCII(
+ base::Int64ToString(id));
+}
+
+bool DatabaseTracker::GetOriginInfo(const std::string& origin_identifier,
+ OriginInfo* info) {
+ DCHECK(info);
+ CachedOriginInfo* cached_info = GetCachedOriginInfo(origin_identifier);
+ if (!cached_info)
+ return false;
+ *info = OriginInfo(*cached_info);
+ return true;
+}
+
+bool DatabaseTracker::GetAllOriginIdentifiers(
+ std::vector<std::string>* origin_identifiers) {
+ DCHECK(origin_identifiers);
+ DCHECK(origin_identifiers->empty());
+ if (!LazyInit())
+ return false;
+ return databases_table_->GetAllOriginIdentifiers(origin_identifiers);
+}
+
+bool DatabaseTracker::GetAllOriginsInfo(
+ std::vector<OriginInfo>* origins_info) {
+ DCHECK(origins_info);
+ DCHECK(origins_info->empty());
+
+ std::vector<std::string> origins;
+ if (!GetAllOriginIdentifiers(&origins))
+ return false;
+
+ for (std::vector<std::string>::const_iterator it = origins.begin();
+ it != origins.end(); it++) {
+ CachedOriginInfo* origin_info = GetCachedOriginInfo(*it);
+ if (!origin_info) {
+ // Restore 'origins_info' to its initial state.
+ origins_info->clear();
+ return false;
+ }
+ origins_info->push_back(OriginInfo(*origin_info));
+ }
+
+ return true;
+}
+
+bool DatabaseTracker::DeleteClosedDatabase(
+ const std::string& origin_identifier,
+ const base::string16& database_name) {
+ if (!LazyInit())
+ return false;
+
+ // Check if the database is opened by any renderer.
+ if (database_connections_.IsDatabaseOpened(origin_identifier, database_name))
+ return false;
+
+ int64 db_file_size = quota_manager_proxy_.get()
+ ? GetDBFileSize(origin_identifier, database_name)
+ : 0;
+
+ // Try to delete the file on the hard drive.
+ base::FilePath db_file = GetFullDBFilePath(origin_identifier, database_name);
+ if (!sql::Connection::Delete(db_file))
+ return false;
+
+ if (quota_manager_proxy_.get() && db_file_size)
+ quota_manager_proxy_->NotifyStorageModified(
+ storage::QuotaClient::kDatabase,
+ storage::GetOriginFromIdentifier(origin_identifier),
+ storage::kStorageTypeTemporary,
+ -db_file_size);
+
+ // Clean up the main database and invalidate the cached record.
+ databases_table_->DeleteDatabaseDetails(origin_identifier, database_name);
+ origins_info_map_.erase(origin_identifier);
+
+ std::vector<DatabaseDetails> details;
+ if (databases_table_->GetAllDatabaseDetailsForOriginIdentifier(
+ origin_identifier, &details) && details.empty()) {
+ // Try to delete the origin in case this was the last database.
+ DeleteOrigin(origin_identifier, false);
+ }
+ return true;
+}
+
+bool DatabaseTracker::DeleteOrigin(const std::string& origin_identifier,
+ bool force) {
+ if (!LazyInit())
+ return false;
+
+ // Check if any database in this origin is opened by any renderer.
+ if (database_connections_.IsOriginUsed(origin_identifier) && !force)
+ return false;
+
+ int64 deleted_size = 0;
+ if (quota_manager_proxy_.get()) {
+ CachedOriginInfo* origin_info = GetCachedOriginInfo(origin_identifier);
+ if (origin_info)
+ deleted_size = origin_info->TotalSize();
+ }
+
+ origins_info_map_.erase(origin_identifier);
+ base::FilePath origin_dir = db_dir_.AppendASCII(origin_identifier);
+
+ // Create a temporary directory to move possibly still existing databases to,
+ // as we can't delete the origin directory on windows if it contains opened
+ // files.
+ base::FilePath new_origin_dir;
+ base::CreateTemporaryDirInDir(db_dir_,
+ kTemporaryDirectoryPrefix,
+ &new_origin_dir);
+ base::FileEnumerator databases(
+ origin_dir,
+ false,
+ base::FileEnumerator::FILES);
+ for (base::FilePath database = databases.Next(); !database.empty();
+ database = databases.Next()) {
+ base::FilePath new_file = new_origin_dir.Append(database.BaseName());
+ base::Move(database, new_file);
+ }
+ base::DeleteFile(origin_dir, true);
+ base::DeleteFile(new_origin_dir, true); // might fail on windows.
+
+ databases_table_->DeleteOriginIdentifier(origin_identifier);
+
+ if (quota_manager_proxy_.get() && deleted_size) {
+ quota_manager_proxy_->NotifyStorageModified(
+ storage::QuotaClient::kDatabase,
+ storage::GetOriginFromIdentifier(origin_identifier),
+ storage::kStorageTypeTemporary,
+ -deleted_size);
+ }
+
+ return true;
+}
+
+bool DatabaseTracker::IsDatabaseScheduledForDeletion(
+ const std::string& origin_identifier,
+ const base::string16& database_name) {
+ DatabaseSet::iterator it = dbs_to_be_deleted_.find(origin_identifier);
+ if (it == dbs_to_be_deleted_.end())
+ return false;
+
+ std::set<base::string16>& databases = it->second;
+ return (databases.find(database_name) != databases.end());
+}
+
+bool DatabaseTracker::LazyInit() {
+ if (!is_initialized_ && !shutting_down_) {
+ DCHECK(!db_->is_open());
+ DCHECK(!databases_table_.get());
+ DCHECK(!meta_table_.get());
+
+ // If there are left-over directories from failed deletion attempts, clean
+ // them up.
+ if (base::DirectoryExists(db_dir_)) {
+ base::FileEnumerator directories(
+ db_dir_,
+ false,
+ base::FileEnumerator::DIRECTORIES,
+ kTemporaryDirectoryPattern);
+ for (base::FilePath directory = directories.Next(); !directory.empty();
+ directory = directories.Next()) {
+ base::DeleteFile(directory, true);
+ }
+ }
+
+ // If the tracker database exists, but it's corrupt or doesn't
+ // have a meta table, delete the database directory.
+ const base::FilePath kTrackerDatabaseFullPath =
+ db_dir_.Append(base::FilePath(kTrackerDatabaseFileName));
+ if (base::DirectoryExists(db_dir_) &&
+ base::PathExists(kTrackerDatabaseFullPath) &&
+ (!db_->Open(kTrackerDatabaseFullPath) ||
+ !sql::MetaTable::DoesTableExist(db_.get()))) {
+ db_->Close();
+ if (!base::DeleteFile(db_dir_, true))
+ return false;
+ }
+
+ db_->set_histogram_tag("DatabaseTracker");
+
+ databases_table_.reset(new DatabasesTable(db_.get()));
+ meta_table_.reset(new sql::MetaTable());
+
+ is_initialized_ =
+ base::CreateDirectory(db_dir_) &&
+ (db_->is_open() ||
+ (is_incognito_ ? db_->OpenInMemory() :
+ db_->Open(kTrackerDatabaseFullPath))) &&
+ UpgradeToCurrentVersion();
+ if (!is_initialized_) {
+ databases_table_.reset(NULL);
+ meta_table_.reset(NULL);
+ db_->Close();
+ }
+ }
+ return is_initialized_;
+}
+
+bool DatabaseTracker::UpgradeToCurrentVersion() {
+ sql::Transaction transaction(db_.get());
+ if (!transaction.Begin() ||
+ !meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion) ||
+ (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) ||
+ !databases_table_->Init())
+ return false;
+
+ if (meta_table_->GetVersionNumber() < kCurrentVersion)
+ meta_table_->SetVersionNumber(kCurrentVersion);
+
+ return transaction.Commit();
+}
+
+void DatabaseTracker::InsertOrUpdateDatabaseDetails(
+ const std::string& origin_identifier,
+ const base::string16& database_name,
+ const base::string16& database_description,
+ int64 estimated_size) {
+ DatabaseDetails details;
+ if (!databases_table_->GetDatabaseDetails(
+ origin_identifier, database_name, &details)) {
+ details.origin_identifier = origin_identifier;
+ details.database_name = database_name;
+ details.description = database_description;
+ details.estimated_size = estimated_size;
+ databases_table_->InsertDatabaseDetails(details);
+ } else if ((details.description != database_description) ||
+ (details.estimated_size != estimated_size)) {
+ details.description = database_description;
+ details.estimated_size = estimated_size;
+ databases_table_->UpdateDatabaseDetails(details);
+ }
+}
+
+void DatabaseTracker::ClearAllCachedOriginInfo() {
+ origins_info_map_.clear();
+}
+
+DatabaseTracker::CachedOriginInfo* DatabaseTracker::MaybeGetCachedOriginInfo(
+ const std::string& origin_identifier, bool create_if_needed) {
+ if (!LazyInit())
+ return NULL;
+
+ // Populate the cache with data for this origin if needed.
+ if (origins_info_map_.find(origin_identifier) == origins_info_map_.end()) {
+ if (!create_if_needed)
+ return NULL;
+
+ std::vector<DatabaseDetails> details;
+ if (!databases_table_->GetAllDatabaseDetailsForOriginIdentifier(
+ origin_identifier, &details)) {
+ return NULL;
+ }
+
+ CachedOriginInfo& origin_info = origins_info_map_[origin_identifier];
+ origin_info.SetOriginIdentifier(origin_identifier);
+ for (std::vector<DatabaseDetails>::const_iterator it = details.begin();
+ it != details.end(); it++) {
+ int64 db_file_size;
+ if (database_connections_.IsDatabaseOpened(
+ origin_identifier, it->database_name)) {
+ db_file_size = database_connections_.GetOpenDatabaseSize(
+ origin_identifier, it->database_name);
+ } else {
+ db_file_size = GetDBFileSize(origin_identifier, it->database_name);
+ }
+ origin_info.SetDatabaseSize(it->database_name, db_file_size);
+ origin_info.SetDatabaseDescription(it->database_name, it->description);
+ }
+ }
+
+ return &origins_info_map_[origin_identifier];
+}
+
+int64 DatabaseTracker::GetDBFileSize(const std::string& origin_identifier,
+ const base::string16& database_name) {
+ base::FilePath db_file_name = GetFullDBFilePath(origin_identifier,
+ database_name);
+ int64 db_file_size = 0;
+ if (!base::GetFileSize(db_file_name, &db_file_size))
+ db_file_size = 0;
+ return db_file_size;
+}
+
+int64 DatabaseTracker::SeedOpenDatabaseInfo(
+ const std::string& origin_id, const base::string16& name,
+ const base::string16& description) {
+ DCHECK(database_connections_.IsDatabaseOpened(origin_id, name));
+ int64 size = GetDBFileSize(origin_id, name);
+ database_connections_.SetOpenDatabaseSize(origin_id, name, size);
+ CachedOriginInfo* info = MaybeGetCachedOriginInfo(origin_id, false);
+ if (info) {
+ info->SetDatabaseSize(name, size);
+ info->SetDatabaseDescription(name, description);
+ }
+ return size;
+}
+
+int64 DatabaseTracker::UpdateOpenDatabaseInfoAndNotify(
+ const std::string& origin_id, const base::string16& name,
+ const base::string16* opt_description) {
+ DCHECK(database_connections_.IsDatabaseOpened(origin_id, name));
+ int64 new_size = GetDBFileSize(origin_id, name);
+ int64 old_size = database_connections_.GetOpenDatabaseSize(origin_id, name);
+ CachedOriginInfo* info = MaybeGetCachedOriginInfo(origin_id, false);
+ if (info && opt_description)
+ info->SetDatabaseDescription(name, *opt_description);
+ if (old_size != new_size) {
+ database_connections_.SetOpenDatabaseSize(origin_id, name, new_size);
+ if (info)
+ info->SetDatabaseSize(name, new_size);
+ if (quota_manager_proxy_.get())
+ quota_manager_proxy_->NotifyStorageModified(
+ storage::QuotaClient::kDatabase,
+ storage::GetOriginFromIdentifier(origin_id),
+ storage::kStorageTypeTemporary,
+ new_size - old_size);
+ FOR_EACH_OBSERVER(Observer, observers_, OnDatabaseSizeChanged(
+ origin_id, name, new_size));
+ }
+ return new_size;
+}
+
+void DatabaseTracker::ScheduleDatabaseForDeletion(
+ const std::string& origin_identifier,
+ const base::string16& database_name) {
+ DCHECK(database_connections_.IsDatabaseOpened(origin_identifier,
+ database_name));
+ dbs_to_be_deleted_[origin_identifier].insert(database_name);
+ FOR_EACH_OBSERVER(Observer, observers_, OnDatabaseScheduledForDeletion(
+ origin_identifier, database_name));
+}
+
+void DatabaseTracker::ScheduleDatabasesForDeletion(
+ const DatabaseSet& databases,
+ const net::CompletionCallback& callback) {
+ DCHECK(!databases.empty());
+
+ if (!callback.is_null())
+ deletion_callbacks_.push_back(std::make_pair(callback, databases));
+ for (DatabaseSet::const_iterator ori = databases.begin();
+ ori != databases.end(); ++ori) {
+ for (std::set<base::string16>::const_iterator db = ori->second.begin();
+ db != ori->second.end(); ++db)
+ ScheduleDatabaseForDeletion(ori->first, *db);
+ }
+}
+
+int DatabaseTracker::DeleteDatabase(const std::string& origin_identifier,
+ const base::string16& database_name,
+ const net::CompletionCallback& callback) {
+ if (!LazyInit())
+ return net::ERR_FAILED;
+
+ if (database_connections_.IsDatabaseOpened(origin_identifier,
+ database_name)) {
+ if (!callback.is_null()) {
+ DatabaseSet set;
+ set[origin_identifier].insert(database_name);
+ deletion_callbacks_.push_back(std::make_pair(callback, set));
+ }
+ ScheduleDatabaseForDeletion(origin_identifier, database_name);
+ return net::ERR_IO_PENDING;
+ }
+ DeleteClosedDatabase(origin_identifier, database_name);
+ return net::OK;
+}
+
+int DatabaseTracker::DeleteDataModifiedSince(
+ const base::Time& cutoff,
+ const net::CompletionCallback& callback) {
+ if (!LazyInit())
+ return net::ERR_FAILED;
+
+ DatabaseSet to_be_deleted;
+
+ std::vector<std::string> origins_identifiers;
+ if (!databases_table_->GetAllOriginIdentifiers(&origins_identifiers))
+ return net::ERR_FAILED;
+ int rv = net::OK;
+ for (std::vector<std::string>::const_iterator ori =
+ origins_identifiers.begin();
+ ori != origins_identifiers.end(); ++ori) {
+ if (special_storage_policy_.get() &&
+ special_storage_policy_->IsStorageProtected(
+ storage::GetOriginFromIdentifier(*ori))) {
+ continue;
+ }
+
+ std::vector<DatabaseDetails> details;
+ if (!databases_table_->
+ GetAllDatabaseDetailsForOriginIdentifier(*ori, &details))
+ rv = net::ERR_FAILED;
+ for (std::vector<DatabaseDetails>::const_iterator db = details.begin();
+ db != details.end(); ++db) {
+ base::FilePath db_file = GetFullDBFilePath(*ori, db->database_name);
+ base::File::Info file_info;
+ base::GetFileInfo(db_file, &file_info);
+ if (file_info.last_modified < cutoff)
+ continue;
+
+ // Check if the database is opened by any renderer.
+ if (database_connections_.IsDatabaseOpened(*ori, db->database_name))
+ to_be_deleted[*ori].insert(db->database_name);
+ else
+ DeleteClosedDatabase(*ori, db->database_name);
+ }
+ }
+
+ if (rv != net::OK)
+ return rv;
+
+ if (!to_be_deleted.empty()) {
+ ScheduleDatabasesForDeletion(to_be_deleted, callback);
+ return net::ERR_IO_PENDING;
+ }
+ return net::OK;
+}
+
+int DatabaseTracker::DeleteDataForOrigin(
+ const std::string& origin, const net::CompletionCallback& callback) {
+ if (!LazyInit())
+ return net::ERR_FAILED;
+
+ DatabaseSet to_be_deleted;
+
+ std::vector<DatabaseDetails> details;
+ if (!databases_table_->
+ GetAllDatabaseDetailsForOriginIdentifier(origin, &details))
+ return net::ERR_FAILED;
+ for (std::vector<DatabaseDetails>::const_iterator db = details.begin();
+ db != details.end(); ++db) {
+ // Check if the database is opened by any renderer.
+ if (database_connections_.IsDatabaseOpened(origin, db->database_name))
+ to_be_deleted[origin].insert(db->database_name);
+ else
+ DeleteClosedDatabase(origin, db->database_name);
+ }
+
+ if (!to_be_deleted.empty()) {
+ ScheduleDatabasesForDeletion(to_be_deleted, callback);
+ return net::ERR_IO_PENDING;
+ }
+ return net::OK;
+}
+
+const base::File* DatabaseTracker::GetIncognitoFile(
+ const base::string16& vfs_file_name) const {
+ DCHECK(is_incognito_);
+ FileHandlesMap::const_iterator it =
+ incognito_file_handles_.find(vfs_file_name);
+ if (it != incognito_file_handles_.end())
+ return it->second;
+
+ return NULL;
+}
+
+const base::File* DatabaseTracker::SaveIncognitoFile(
+ const base::string16& vfs_file_name,
+ base::File file) {
+ DCHECK(is_incognito_);
+ if (!file.IsValid())
+ return NULL;
+
+ base::File* to_insert = new base::File(file.Pass());
+ std::pair<FileHandlesMap::iterator, bool> rv =
+ incognito_file_handles_.insert(std::make_pair(vfs_file_name, to_insert));
+ DCHECK(rv.second);
+ return rv.first->second;
+}
+
+void DatabaseTracker::CloseIncognitoFileHandle(
+ const base::string16& vfs_file_name) {
+ DCHECK(is_incognito_);
+ DCHECK(incognito_file_handles_.find(vfs_file_name) !=
+ incognito_file_handles_.end());
+
+ FileHandlesMap::iterator it = incognito_file_handles_.find(vfs_file_name);
+ if (it != incognito_file_handles_.end()) {
+ delete it->second;
+ incognito_file_handles_.erase(it);
+ }
+}
+
+bool DatabaseTracker::HasSavedIncognitoFileHandle(
+ const base::string16& vfs_file_name) const {
+ return (incognito_file_handles_.find(vfs_file_name) !=
+ incognito_file_handles_.end());
+}
+
+void DatabaseTracker::DeleteIncognitoDBDirectory() {
+ is_initialized_ = false;
+
+ for (FileHandlesMap::iterator it = incognito_file_handles_.begin();
+ it != incognito_file_handles_.end(); it++) {
+ delete it->second;
+ }
+
+ base::FilePath incognito_db_dir =
+ profile_path_.Append(kIncognitoDatabaseDirectoryName);
+ if (base::DirectoryExists(incognito_db_dir))
+ base::DeleteFile(incognito_db_dir, true);
+}
+
+void DatabaseTracker::ClearSessionOnlyOrigins() {
+ bool has_session_only_databases =
+ special_storage_policy_.get() &&
+ special_storage_policy_->HasSessionOnlyOrigins();
+
+ // Clearing only session-only databases, and there are none.
+ if (!has_session_only_databases)
+ return;
+
+ if (!LazyInit())
+ return;
+
+ std::vector<std::string> origin_identifiers;
+ GetAllOriginIdentifiers(&origin_identifiers);
+
+ for (std::vector<std::string>::iterator origin =
+ origin_identifiers.begin();
+ origin != origin_identifiers.end(); ++origin) {
+ GURL origin_url = storage::GetOriginFromIdentifier(*origin);
+ if (!special_storage_policy_->IsStorageSessionOnly(origin_url))
+ continue;
+ if (special_storage_policy_->IsStorageProtected(origin_url))
+ continue;
+ storage::OriginInfo origin_info;
+ std::vector<base::string16> databases;
+ GetOriginInfo(*origin, &origin_info);
+ origin_info.GetAllDatabaseNames(&databases);
+
+ for (std::vector<base::string16>::iterator database = databases.begin();
+ database != databases.end(); ++database) {
+ base::File file(GetFullDBFilePath(*origin, *database),
+ base::File::FLAG_OPEN_ALWAYS |
+ base::File::FLAG_SHARE_DELETE |
+ base::File::FLAG_DELETE_ON_CLOSE |
+ base::File::FLAG_READ);
+ }
+ DeleteOrigin(*origin, true);
+ }
+}
+
+
+void DatabaseTracker::Shutdown() {
+ DCHECK(db_tracker_thread_.get());
+ DCHECK(db_tracker_thread_->BelongsToCurrentThread());
+ if (shutting_down_) {
+ NOTREACHED();
+ return;
+ }
+ shutting_down_ = true;
+ if (is_incognito_)
+ DeleteIncognitoDBDirectory();
+ else if (!force_keep_session_state_)
+ ClearSessionOnlyOrigins();
+ CloseTrackerDatabaseAndClearCaches();
+}
+
+void DatabaseTracker::SetForceKeepSessionState() {
+ DCHECK(db_tracker_thread_.get());
+ if (!db_tracker_thread_->BelongsToCurrentThread()) {
+ db_tracker_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&DatabaseTracker::SetForceKeepSessionState, this));
+ return;
+ }
+ force_keep_session_state_ = true;
+}
+
+} // namespace storage
diff --git a/storage/browser/database/database_tracker.h b/storage/browser/database/database_tracker.h
new file mode 100644
index 0000000..f998b4b
--- /dev/null
+++ b/storage/browser/database/database_tracker.h
@@ -0,0 +1,319 @@
+// 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 STORAGE_BROWSER_DATABASE_DATABASE_TRACKER_H_
+#define STORAGE_BROWSER_DATABASE_DATABASE_TRACKER_H_
+
+#include <map>
+#include <set>
+#include <utility>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_util.h"
+#include "base/time/time.h"
+#include "net/base/completion_callback.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/database/database_connections.h"
+
+namespace base {
+class MessageLoopProxy;
+}
+
+namespace content {
+class DatabaseTracker_TestHelper_Test;
+class MockDatabaseTracker;
+}
+
+namespace sql {
+class Connection;
+class MetaTable;
+}
+
+namespace storage {
+class QuotaManagerProxy;
+class SpecialStoragePolicy;
+}
+
+namespace storage {
+
+STORAGE_EXPORT extern const base::FilePath::CharType
+ kDatabaseDirectoryName[];
+STORAGE_EXPORT extern const base::FilePath::CharType
+ kTrackerDatabaseFileName[];
+
+class DatabasesTable;
+
+// This class is used to store information about all databases in an origin.
+class STORAGE_EXPORT OriginInfo {
+ public:
+ OriginInfo();
+ OriginInfo(const OriginInfo& origin_info);
+ ~OriginInfo();
+
+ const std::string& GetOriginIdentifier() const { return origin_identifier_; }
+ int64 TotalSize() const { return total_size_; }
+ void GetAllDatabaseNames(std::vector<base::string16>* databases) const;
+ int64 GetDatabaseSize(const base::string16& database_name) const;
+ base::string16 GetDatabaseDescription(
+ const base::string16& database_name) const;
+
+ protected:
+ typedef std::map<base::string16, std::pair<int64, base::string16> >
+ DatabaseInfoMap;
+
+ OriginInfo(const std::string& origin_identifier, int64 total_size);
+
+ std::string origin_identifier_;
+ int64 total_size_;
+ DatabaseInfoMap database_info_;
+};
+
+// This class manages the main database and keeps track of open databases.
+//
+// The data in this class is not thread-safe, so all methods of this class
+// should be called on the same thread. The only exceptions are the ctor(),
+// the dtor() and the database_directory() and quota_manager_proxy() getters.
+//
+// Furthermore, some methods of this class have to read/write data from/to
+// the disk. Therefore, in a multi-threaded application, all methods of this
+// class should be called on the thread dedicated to file operations (file
+// thread in the browser process, for example), if such a thread exists.
+class STORAGE_EXPORT DatabaseTracker
+ : public base::RefCountedThreadSafe<DatabaseTracker> {
+ public:
+ class Observer {
+ public:
+ virtual void OnDatabaseSizeChanged(const std::string& origin_identifier,
+ const base::string16& database_name,
+ int64 database_size) = 0;
+ virtual void OnDatabaseScheduledForDeletion(
+ const std::string& origin_identifier,
+ const base::string16& database_name) = 0;
+
+ protected:
+ virtual ~Observer() {}
+ };
+
+ DatabaseTracker(const base::FilePath& profile_path,
+ bool is_incognito,
+ storage::SpecialStoragePolicy* special_storage_policy,
+ storage::QuotaManagerProxy* quota_manager_proxy,
+ base::MessageLoopProxy* db_tracker_thread);
+
+ void DatabaseOpened(const std::string& origin_identifier,
+ const base::string16& database_name,
+ const base::string16& database_details,
+ int64 estimated_size,
+ int64* database_size);
+ void DatabaseModified(const std::string& origin_identifier,
+ const base::string16& database_name);
+ void DatabaseClosed(const std::string& origin_identifier,
+ const base::string16& database_name);
+ void HandleSqliteError(const std::string& origin_identifier,
+ const base::string16& database_name,
+ int error);
+
+ void CloseDatabases(const DatabaseConnections& connections);
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ void CloseTrackerDatabaseAndClearCaches();
+
+ const base::FilePath& DatabaseDirectory() const { return db_dir_; }
+ base::FilePath GetFullDBFilePath(const std::string& origin_identifier,
+ const base::string16& database_name);
+
+ // virtual for unit-testing only
+ virtual bool GetOriginInfo(const std::string& origin_id, OriginInfo* info);
+ virtual bool GetAllOriginIdentifiers(std::vector<std::string>* origin_ids);
+ virtual bool GetAllOriginsInfo(std::vector<OriginInfo>* origins_info);
+
+ // Safe to call on any thread.
+ storage::QuotaManagerProxy* quota_manager_proxy() const {
+ return quota_manager_proxy_.get();
+ }
+
+ bool IsDatabaseScheduledForDeletion(const std::string& origin_identifier,
+ const base::string16& database_name);
+
+ // Deletes a single database. Returns net::OK on success, net::FAILED on
+ // failure, or net::ERR_IO_PENDING and |callback| is invoked upon completion,
+ // if non-NULL.
+ int DeleteDatabase(const std::string& origin_identifier,
+ const base::string16& database_name,
+ const net::CompletionCallback& callback);
+
+ // Delete any databases that have been touched since the cutoff date that's
+ // supplied, omitting any that match IDs within |protected_origins|.
+ // Returns net::OK on success, net::FAILED if not all databases could be
+ // deleted, and net::ERR_IO_PENDING and |callback| is invoked upon completion,
+ // if non-NULL. Protected origins, according the the SpecialStoragePolicy,
+ // are not deleted by this method.
+ int DeleteDataModifiedSince(const base::Time& cutoff,
+ const net::CompletionCallback& callback);
+
+ // Delete all databases that belong to the given origin. Returns net::OK on
+ // success, net::FAILED if not all databases could be deleted, and
+ // net::ERR_IO_PENDING and |callback| is invoked upon completion, if non-NULL.
+ // virtual for unit testing only
+ virtual int DeleteDataForOrigin(const std::string& origin_identifier,
+ const net::CompletionCallback& callback);
+
+ bool IsIncognitoProfile() const { return is_incognito_; }
+
+ const base::File* GetIncognitoFile(const base::string16& vfs_file_path) const;
+ const base::File* SaveIncognitoFile(const base::string16& vfs_file_path,
+ base::File file);
+ void CloseIncognitoFileHandle(const base::string16& vfs_file_path);
+ bool HasSavedIncognitoFileHandle(const base::string16& vfs_file_path) const;
+
+ // Shutdown the database tracker, deleting database files if the tracker is
+ // used for an incognito profile.
+ void Shutdown();
+ // Disables the exit-time deletion of session-only data.
+ void SetForceKeepSessionState();
+
+ private:
+ friend class base::RefCountedThreadSafe<DatabaseTracker>;
+ friend class content::DatabaseTracker_TestHelper_Test;
+ friend class content::MockDatabaseTracker; // for testing
+
+ typedef std::map<std::string, std::set<base::string16> > DatabaseSet;
+ typedef std::vector<std::pair<net::CompletionCallback, DatabaseSet> >
+ PendingDeletionCallbacks;
+ typedef std::map<base::string16, base::File*> FileHandlesMap;
+ typedef std::map<std::string, base::string16> OriginDirectoriesMap;
+
+ class CachedOriginInfo : public OriginInfo {
+ public:
+ CachedOriginInfo() : OriginInfo(std::string(), 0) {}
+ void SetOriginIdentifier(const std::string& origin_identifier) {
+ origin_identifier_ = origin_identifier;
+ }
+ void SetDatabaseSize(const base::string16& database_name, int64 new_size) {
+ int64 old_size = 0;
+ if (database_info_.find(database_name) != database_info_.end())
+ old_size = database_info_[database_name].first;
+ database_info_[database_name].first = new_size;
+ if (new_size != old_size)
+ total_size_ += new_size - old_size;
+ }
+ void SetDatabaseDescription(const base::string16& database_name,
+ const base::string16& description) {
+ database_info_[database_name].second = description;
+ }
+ };
+
+ // virtual for unit-testing only.
+ virtual ~DatabaseTracker();
+
+ // Deletes the directory that stores all DBs in incognito mode, if it exists.
+ void DeleteIncognitoDBDirectory();
+
+ // Deletes session-only databases. Blocks databases from being created/opened.
+ void ClearSessionOnlyOrigins();
+
+ bool DeleteClosedDatabase(const std::string& origin_identifier,
+ const base::string16& database_name);
+
+ // Delete all files belonging to the given origin given that no database
+ // connections within this origin are open, or if |force| is true, delete
+ // the meta data and rename the associated directory.
+ bool DeleteOrigin(const std::string& origin_identifier, bool force);
+ void DeleteDatabaseIfNeeded(const std::string& origin_identifier,
+ const base::string16& database_name);
+
+ bool LazyInit();
+ bool UpgradeToCurrentVersion();
+ void InsertOrUpdateDatabaseDetails(const std::string& origin_identifier,
+ const base::string16& database_name,
+ const base::string16& database_details,
+ int64 estimated_size);
+
+ void ClearAllCachedOriginInfo();
+ CachedOriginInfo* MaybeGetCachedOriginInfo(
+ const std::string& origin_identifier,
+ bool create_if_needed);
+ CachedOriginInfo* GetCachedOriginInfo(
+ const std::string& origin_identifier) {
+ return MaybeGetCachedOriginInfo(origin_identifier, true);
+ }
+
+ int64 GetDBFileSize(const std::string& origin_identifier,
+ const base::string16& database_name);
+ int64 SeedOpenDatabaseInfo(const std::string& origin_identifier,
+ const base::string16& database_name,
+ const base::string16& description);
+ int64 UpdateOpenDatabaseInfoAndNotify(const std::string& origin_identifier,
+ const base::string16& database_name,
+ const base::string16* opt_description);
+ int64 UpdateOpenDatabaseSizeAndNotify(const std::string& origin_identifier,
+ const base::string16& database_name) {
+ return UpdateOpenDatabaseInfoAndNotify(
+ origin_identifier, database_name, NULL);
+ }
+
+
+ void ScheduleDatabaseForDeletion(const std::string& origin_identifier,
+ const base::string16& database_name);
+ // Schedule a set of open databases for deletion. If non-null, callback is
+ // invoked upon completion.
+ void ScheduleDatabasesForDeletion(const DatabaseSet& databases,
+ const net::CompletionCallback& callback);
+
+ // Returns the directory where all DB files for the given origin are stored.
+ base::string16 GetOriginDirectory(const std::string& origin_identifier);
+
+ bool is_initialized_;
+ const bool is_incognito_;
+ bool force_keep_session_state_;
+ bool shutting_down_;
+ const base::FilePath profile_path_;
+ const base::FilePath db_dir_;
+ scoped_ptr<sql::Connection> db_;
+ scoped_ptr<DatabasesTable> databases_table_;
+ scoped_ptr<sql::MetaTable> meta_table_;
+ ObserverList<Observer, true> observers_;
+ std::map<std::string, CachedOriginInfo> origins_info_map_;
+ DatabaseConnections database_connections_;
+
+ // The set of databases that should be deleted but are still opened
+ DatabaseSet dbs_to_be_deleted_;
+ PendingDeletionCallbacks deletion_callbacks_;
+
+ // Apps and Extensions can have special rights.
+ scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_;
+
+ scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
+
+ // The database tracker thread we're supposed to run file IO on.
+ scoped_refptr<base::MessageLoopProxy> db_tracker_thread_;
+
+ // When in incognito mode, store a DELETE_ON_CLOSE handle to each
+ // main DB and journal file that was accessed. When the incognito profile
+ // goes away (or when the browser crashes), all these handles will be
+ // closed, and the files will be deleted.
+ FileHandlesMap incognito_file_handles_;
+
+ // In a non-incognito profile, all DBs in an origin are stored in a directory
+ // named after the origin. In an incognito profile though, we do not want the
+ // directory structure to reveal the origins visited by the user (in case the
+ // browser process crashes and those directories are not deleted). So we use
+ // this map to assign directory names that do not reveal this information.
+ OriginDirectoriesMap incognito_origin_directories_;
+ int incognito_origin_directories_generator_;
+
+ FRIEND_TEST_ALL_PREFIXES(DatabaseTracker, TestHelper);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_DATABASE_DATABASE_TRACKER_H_
diff --git a/storage/browser/database/database_util.cc b/storage/browser/database/database_util.cc
new file mode 100644
index 0000000..113f533
--- /dev/null
+++ b/storage/browser/database/database_util.cc
@@ -0,0 +1,108 @@
+// 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 "storage/browser/database/database_util.h"
+
+#include "base/basictypes.h"
+#include "base/strings/utf_string_conversions.h"
+#include "storage/browser/database/database_tracker.h"
+#include "storage/browser/database/vfs_backend.h"
+#include "storage/common/database/database_identifier.h"
+
+namespace storage {
+
+namespace {
+
+bool IsSafeSuffix(const base::string16& suffix) {
+ base::char16 prev_c = 0;
+ for (base::string16::const_iterator it = suffix.begin();
+ it < suffix.end(); ++it) {
+ base::char16 c = *it;
+ if (!(IsAsciiAlpha(c) || IsAsciiDigit(c) ||
+ c == '-' || c == '.' || c == '_')) {
+ return false;
+ }
+ if (c == '.' && prev_c == '.')
+ return false;
+ prev_c = c;
+ }
+ return true;
+}
+
+}
+
+const char DatabaseUtil::kJournalFileSuffix[] = "-journal";
+
+bool DatabaseUtil::CrackVfsFileName(const base::string16& vfs_file_name,
+ std::string* origin_identifier,
+ base::string16* database_name,
+ base::string16* sqlite_suffix) {
+ // 'vfs_file_name' is of the form <origin_identifier>/<db_name>#<suffix>.
+ // <suffix> is optional.
+ DCHECK(!vfs_file_name.empty());
+ size_t first_slash_index = vfs_file_name.find('/');
+ size_t last_pound_index = vfs_file_name.rfind('#');
+ // '/' and '#' must be present in the string. Also, the string cannot start
+ // with a '/' (origin_identifier cannot be empty) and '/' must come before '#'
+ if ((first_slash_index == base::string16::npos) ||
+ (last_pound_index == base::string16::npos) ||
+ (first_slash_index == 0) ||
+ (first_slash_index > last_pound_index)) {
+ return false;
+ }
+
+ std::string origin_id = base::UTF16ToASCII(
+ vfs_file_name.substr(0, first_slash_index));
+ if (!IsValidOriginIdentifier(origin_id))
+ return false;
+
+ base::string16 suffix = vfs_file_name.substr(
+ last_pound_index + 1, vfs_file_name.length() - last_pound_index - 1);
+ if (!IsSafeSuffix(suffix))
+ return false;
+
+ if (origin_identifier)
+ *origin_identifier = origin_id;
+
+ if (database_name) {
+ *database_name = vfs_file_name.substr(
+ first_slash_index + 1, last_pound_index - first_slash_index - 1);
+ }
+
+ if (sqlite_suffix)
+ *sqlite_suffix = suffix;
+
+ return true;
+}
+
+base::FilePath DatabaseUtil::GetFullFilePathForVfsFile(
+ DatabaseTracker* db_tracker, const base::string16& vfs_file_name) {
+ std::string origin_identifier;
+ base::string16 database_name;
+ base::string16 sqlite_suffix;
+ if (!CrackVfsFileName(vfs_file_name, &origin_identifier,
+ &database_name, &sqlite_suffix)) {
+ return base::FilePath(); // invalid vfs_file_name
+ }
+
+ base::FilePath full_path = db_tracker->GetFullDBFilePath(
+ origin_identifier, database_name);
+ if (!full_path.empty() && !sqlite_suffix.empty()) {
+ DCHECK(full_path.Extension().empty());
+ full_path = full_path.InsertBeforeExtensionASCII(
+ base::UTF16ToASCII(sqlite_suffix));
+ }
+ // Watch out for directory traversal attempts from a compromised renderer.
+ if (full_path.value().find(FILE_PATH_LITERAL("..")) !=
+ base::FilePath::StringType::npos)
+ return base::FilePath();
+ return full_path;
+}
+
+bool DatabaseUtil::IsValidOriginIdentifier(
+ const std::string& origin_identifier) {
+ return GetOriginFromIdentifier(origin_identifier).is_valid();
+}
+
+} // namespace storage
diff --git a/storage/browser/database/database_util.h b/storage/browser/database/database_util.h
new file mode 100644
index 0000000..6c67fac
--- /dev/null
+++ b/storage/browser/database/database_util.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2010 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 STORAGE_BROWSER_DATABASE_DATABASE_UTIL_H_
+#define STORAGE_BROWSER_DATABASE_DATABASE_UTIL_H_
+
+#include <string>
+#include "base/strings/string16.h"
+#include "storage/browser/storage_browser_export.h"
+#include "url/gurl.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace storage {
+
+class DatabaseTracker;
+
+class STORAGE_EXPORT DatabaseUtil {
+ public:
+ static const char kJournalFileSuffix[];
+
+ // Extract various information from a database vfs_file_name. All return
+ // parameters are optional.
+ static bool CrackVfsFileName(const base::string16& vfs_file_name,
+ std::string* origin_identifier,
+ base::string16* database_name,
+ base::string16* sqlite_suffix);
+ static base::FilePath GetFullFilePathForVfsFile(
+ DatabaseTracker* db_tracker,
+ const base::string16& vfs_file_name);
+ static bool IsValidOriginIdentifier(const std::string& origin_identifier);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_DATABASE_DATABASE_UTIL_H_
diff --git a/storage/browser/database/databases_table.cc b/storage/browser/database/databases_table.cc
new file mode 100644
index 0000000..6d84ae8
--- /dev/null
+++ b/storage/browser/database/databases_table.cc
@@ -0,0 +1,150 @@
+// 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 "storage/browser/database/databases_table.h"
+
+#include "base/logging.h"
+#include "base/strings/utf_string_conversions.h"
+#include "sql/statement.h"
+
+namespace storage {
+
+DatabaseDetails::DatabaseDetails() : estimated_size(0) { }
+
+DatabaseDetails::~DatabaseDetails() {}
+
+bool DatabasesTable::Init() {
+ // 'Databases' schema:
+ // id A unique ID assigned to each database
+ // origin The originto which the database belongs. This is a
+ // string that can be used as part of a file name
+ // (http_webkit.org_0, for example).
+ // name The database name.
+ // description A short description of the database.
+ // estimated_size The estimated size of the database.
+ return db_->DoesTableExist("Databases") ||
+ (db_->Execute(
+ "CREATE TABLE Databases ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "origin TEXT NOT NULL, "
+ "name TEXT NOT NULL, "
+ "description TEXT NOT NULL, "
+ "estimated_size INTEGER NOT NULL)") &&
+ db_->Execute(
+ "CREATE INDEX origin_index ON Databases (origin)") &&
+ db_->Execute(
+ "CREATE UNIQUE INDEX unique_index ON Databases (origin, name)"));
+}
+
+int64 DatabasesTable::GetDatabaseID(const std::string& origin_identifier,
+ const base::string16& database_name) {
+ sql::Statement select_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "SELECT id FROM Databases WHERE origin = ? AND name = ?"));
+ select_statement.BindString(0, origin_identifier);
+ select_statement.BindString16(1, database_name);
+
+ if (select_statement.Step()) {
+ return select_statement.ColumnInt64(0);
+ }
+
+ return -1;
+}
+
+bool DatabasesTable::GetDatabaseDetails(const std::string& origin_identifier,
+ const base::string16& database_name,
+ DatabaseDetails* details) {
+ DCHECK(details);
+ sql::Statement select_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "SELECT description, estimated_size FROM Databases "
+ "WHERE origin = ? AND name = ?"));
+ select_statement.BindString(0, origin_identifier);
+ select_statement.BindString16(1, database_name);
+
+ if (select_statement.Step()) {
+ details->origin_identifier = origin_identifier;
+ details->database_name = database_name;
+ details->description = select_statement.ColumnString16(0);
+ details->estimated_size = select_statement.ColumnInt64(1);
+ return true;
+ }
+
+ return false;
+}
+
+bool DatabasesTable::InsertDatabaseDetails(const DatabaseDetails& details) {
+ sql::Statement insert_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "INSERT INTO Databases (origin, name, description, "
+ "estimated_size) VALUES (?, ?, ?, ?)"));
+ insert_statement.BindString(0, details.origin_identifier);
+ insert_statement.BindString16(1, details.database_name);
+ insert_statement.BindString16(2, details.description);
+ insert_statement.BindInt64(3, details.estimated_size);
+
+ return insert_statement.Run();
+}
+
+bool DatabasesTable::UpdateDatabaseDetails(const DatabaseDetails& details) {
+ sql::Statement update_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "UPDATE Databases SET description = ?, "
+ "estimated_size = ? WHERE origin = ? AND name = ?"));
+ update_statement.BindString16(0, details.description);
+ update_statement.BindInt64(1, details.estimated_size);
+ update_statement.BindString(2, details.origin_identifier);
+ update_statement.BindString16(3, details.database_name);
+
+ return (update_statement.Run() && db_->GetLastChangeCount());
+}
+
+bool DatabasesTable::DeleteDatabaseDetails(
+ const std::string& origin_identifier,
+ const base::string16& database_name) {
+ sql::Statement delete_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "DELETE FROM Databases WHERE origin = ? AND name = ?"));
+ delete_statement.BindString(0, origin_identifier);
+ delete_statement.BindString16(1, database_name);
+
+ return (delete_statement.Run() && db_->GetLastChangeCount());
+}
+
+bool DatabasesTable::GetAllOriginIdentifiers(
+ std::vector<std::string>* origin_identifiers) {
+ sql::Statement statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "SELECT DISTINCT origin FROM Databases ORDER BY origin"));
+
+ while (statement.Step())
+ origin_identifiers->push_back(statement.ColumnString(0));
+
+ return statement.Succeeded();
+}
+
+bool DatabasesTable::GetAllDatabaseDetailsForOriginIdentifier(
+ const std::string& origin_identifier,
+ std::vector<DatabaseDetails>* details_vector) {
+ sql::Statement statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "SELECT name, description, estimated_size "
+ "FROM Databases WHERE origin = ? ORDER BY name"));
+ statement.BindString(0, origin_identifier);
+
+ while (statement.Step()) {
+ DatabaseDetails details;
+ details.origin_identifier = origin_identifier;
+ details.database_name = statement.ColumnString16(0);
+ details.description = statement.ColumnString16(1);
+ details.estimated_size = statement.ColumnInt64(2);
+ details_vector->push_back(details);
+ }
+
+ return statement.Succeeded();
+}
+
+bool DatabasesTable::DeleteOriginIdentifier(
+ const std::string& origin_identifier) {
+ sql::Statement delete_statement(db_->GetCachedStatement(
+ SQL_FROM_HERE, "DELETE FROM Databases WHERE origin = ?"));
+ delete_statement.BindString(0, origin_identifier);
+
+ return (delete_statement.Run() && db_->GetLastChangeCount());
+}
+
+} // namespace storage
diff --git a/storage/browser/database/databases_table.h b/storage/browser/database/databases_table.h
new file mode 100644
index 0000000..2ade812
--- /dev/null
+++ b/storage/browser/database/databases_table.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2009 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 STORAGE_BROWSER_DATABASE_DATABASES_TABLE_H_
+#define STORAGE_BROWSER_DATABASE_DATABASES_TABLE_H_
+
+#include <vector>
+
+#include "base/strings/string16.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace sql {
+class Connection;
+}
+
+namespace storage {
+
+struct STORAGE_EXPORT_PRIVATE DatabaseDetails {
+ DatabaseDetails();
+ ~DatabaseDetails();
+
+ std::string origin_identifier;
+ base::string16 database_name;
+ base::string16 description;
+ int64 estimated_size;
+};
+
+class STORAGE_EXPORT_PRIVATE DatabasesTable {
+ public:
+ explicit DatabasesTable(sql::Connection* db) : db_(db) { }
+
+ bool Init();
+ int64 GetDatabaseID(const std::string& origin_identifier,
+ const base::string16& database_name);
+ bool GetDatabaseDetails(const std::string& origin_identifier,
+ const base::string16& database_name,
+ DatabaseDetails* details);
+ bool InsertDatabaseDetails(const DatabaseDetails& details);
+ bool UpdateDatabaseDetails(const DatabaseDetails& details);
+ bool DeleteDatabaseDetails(const std::string& origin_identifier,
+ const base::string16& database_name);
+ bool GetAllOriginIdentifiers(std::vector<std::string>* origin_identifiers);
+ bool GetAllDatabaseDetailsForOriginIdentifier(
+ const std::string& origin_identifier,
+ std::vector<DatabaseDetails>* details);
+ bool DeleteOriginIdentifier(const std::string& origin_identifier);
+ private:
+ sql::Connection* db_;
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_DATABASE_DATABASES_TABLE_H_
diff --git a/storage/browser/database/vfs_backend.cc b/storage/browser/database/vfs_backend.cc
new file mode 100644
index 0000000..1f8c97b
--- /dev/null
+++ b/storage/browser/database/vfs_backend.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2011 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 "storage/browser/database/vfs_backend.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "third_party/sqlite/sqlite3.h"
+
+namespace storage {
+
+static const int kFileTypeMask = 0x00007F00;
+
+// static
+bool VfsBackend::OpenTypeIsReadWrite(int desired_flags) {
+ return (desired_flags & SQLITE_OPEN_READWRITE) != 0;
+}
+
+// static
+bool VfsBackend::OpenFileFlagsAreConsistent(int desired_flags) {
+ const int file_type = desired_flags & kFileTypeMask;
+ const bool is_exclusive = (desired_flags & SQLITE_OPEN_EXCLUSIVE) != 0;
+ const bool is_delete = (desired_flags & SQLITE_OPEN_DELETEONCLOSE) != 0;
+ const bool is_create = (desired_flags & SQLITE_OPEN_CREATE) != 0;
+ const bool is_read_only = (desired_flags & SQLITE_OPEN_READONLY) != 0;
+ const bool is_read_write = (desired_flags & SQLITE_OPEN_READWRITE) != 0;
+
+ // All files should be opened either read-write or read-only, but not both.
+ if (is_read_only == is_read_write)
+ return false;
+
+ // If a new file is created, it must also be writable.
+ if (is_create && !is_read_write)
+ return false;
+
+ // If we're accessing an existing file, we cannot give exclusive access, and
+ // we can't delete it.
+ // Normally, we'd also check that 'is_delete' is false for a main DB, main
+ // journal or master journal file; however, when in incognito mode, we use
+ // the SQLITE_OPEN_DELETEONCLOSE flag when opening those files too and keep
+ // an open handle to them for as long as the incognito profile is around.
+ if ((is_exclusive || is_delete) && !is_create)
+ return false;
+
+ // Make sure we're opening the DB directory or that a file type is set.
+ return (file_type == SQLITE_OPEN_MAIN_DB) ||
+ (file_type == SQLITE_OPEN_TEMP_DB) ||
+ (file_type == SQLITE_OPEN_MAIN_JOURNAL) ||
+ (file_type == SQLITE_OPEN_TEMP_JOURNAL) ||
+ (file_type == SQLITE_OPEN_SUBJOURNAL) ||
+ (file_type == SQLITE_OPEN_MASTER_JOURNAL) ||
+ (file_type == SQLITE_OPEN_TRANSIENT_DB);
+}
+
+// static
+base::File VfsBackend::OpenFile(const base::FilePath& file_path,
+ int desired_flags) {
+ DCHECK(!file_path.empty());
+
+ // Verify the flags for consistency and create the database
+ // directory if it doesn't exist.
+ if (!OpenFileFlagsAreConsistent(desired_flags) ||
+ !base::CreateDirectory(file_path.DirName())) {
+ return base::File();
+ }
+
+ int flags = 0;
+ flags |= base::File::FLAG_READ;
+ if (desired_flags & SQLITE_OPEN_READWRITE)
+ flags |= base::File::FLAG_WRITE;
+
+ if (!(desired_flags & SQLITE_OPEN_MAIN_DB))
+ flags |= base::File::FLAG_EXCLUSIVE_READ | base::File::FLAG_EXCLUSIVE_WRITE;
+
+ flags |= ((desired_flags & SQLITE_OPEN_CREATE) ?
+ base::File::FLAG_OPEN_ALWAYS : base::File::FLAG_OPEN);
+
+ if (desired_flags & SQLITE_OPEN_EXCLUSIVE)
+ flags |= base::File::FLAG_EXCLUSIVE_READ | base::File::FLAG_EXCLUSIVE_WRITE;
+
+ if (desired_flags & SQLITE_OPEN_DELETEONCLOSE) {
+ flags |= base::File::FLAG_TEMPORARY | base::File::FLAG_HIDDEN |
+ base::File::FLAG_DELETE_ON_CLOSE;
+ }
+
+ // This flag will allow us to delete the file later on from the browser
+ // process.
+ flags |= base::File::FLAG_SHARE_DELETE;
+
+ // Try to open/create the DB file.
+ return base::File(file_path, flags);
+}
+
+// static
+base::File VfsBackend::OpenTempFileInDirectory(const base::FilePath& dir_path,
+ int desired_flags) {
+ // We should be able to delete temp files when they're closed
+ // and create them as needed
+ if (!(desired_flags & SQLITE_OPEN_DELETEONCLOSE) ||
+ !(desired_flags & SQLITE_OPEN_CREATE)) {
+ return base::File();
+ }
+
+ // Get a unique temp file name in the database directory.
+ base::FilePath temp_file_path;
+ if (!base::CreateTemporaryFileInDir(dir_path, &temp_file_path))
+ return base::File();
+
+ return OpenFile(temp_file_path, desired_flags);
+}
+
+// static
+int VfsBackend::DeleteFile(const base::FilePath& file_path, bool sync_dir) {
+ if (!base::PathExists(file_path))
+ return SQLITE_OK;
+ if (!base::DeleteFile(file_path, false))
+ return SQLITE_IOERR_DELETE;
+
+ int error_code = SQLITE_OK;
+#if defined(OS_POSIX)
+ if (sync_dir) {
+ base::File dir(file_path.DirName(), base::File::FLAG_READ);
+ if (dir.IsValid()) {
+ if (!dir.Flush())
+ error_code = SQLITE_IOERR_DIR_FSYNC;
+ } else {
+ error_code = SQLITE_CANTOPEN;
+ }
+ }
+#endif
+ return error_code;
+}
+
+// static
+uint32 VfsBackend::GetFileAttributes(const base::FilePath& file_path) {
+#if defined(OS_WIN)
+ uint32 attributes = ::GetFileAttributes(file_path.value().c_str());
+#elif defined(OS_POSIX)
+ uint32 attributes = 0;
+ if (!access(file_path.value().c_str(), R_OK))
+ attributes |= static_cast<uint32>(R_OK);
+ if (!access(file_path.value().c_str(), W_OK))
+ attributes |= static_cast<uint32>(W_OK);
+ if (!attributes)
+ attributes = -1;
+#endif
+ return attributes;
+}
+
+// static
+int64 VfsBackend::GetFileSize(const base::FilePath& file_path) {
+ int64 size = 0;
+ return (base::GetFileSize(file_path, &size) ? size : 0);
+}
+
+} // namespace storage
diff --git a/storage/browser/database/vfs_backend.h b/storage/browser/database/vfs_backend.h
new file mode 100644
index 0000000..0ee1dd3
--- /dev/null
+++ b/storage/browser/database/vfs_backend.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2011 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 STORAGE_BROWSER_DATABASE_VFS_BACKEND_H_
+#define STORAGE_BROWSER_DATABASE_VFS_BACKEND_H_
+
+#include "base/files/file.h"
+#include "base/process/process.h"
+#include "base/strings/string16.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace storage {
+
+class STORAGE_EXPORT VfsBackend {
+ public:
+ static base::File OpenFile(const base::FilePath& file_path,
+ int desired_flags);
+
+ static base::File OpenTempFileInDirectory(const base::FilePath& dir_path,
+ int desired_flags);
+
+ static int DeleteFile(const base::FilePath& file_path, bool sync_dir);
+
+ static uint32 GetFileAttributes(const base::FilePath& file_path);
+
+ static int64 GetFileSize(const base::FilePath& file_path);
+
+ // Used to make decisions in the DatabaseDispatcherHost.
+ static bool OpenTypeIsReadWrite(int desired_flags);
+
+ private:
+ static bool OpenFileFlagsAreConsistent(int desired_flags);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_DATABASE_VFS_BACKEND_H_
diff --git a/storage/browser/fileapi/OWNERS b/storage/browser/fileapi/OWNERS
new file mode 100644
index 0000000..232db84
--- /dev/null
+++ b/storage/browser/fileapi/OWNERS
@@ -0,0 +1,2 @@
+tzik@chromium.org
+nhiroki@chromium.org
diff --git a/storage/browser/fileapi/async_file_util.h b/storage/browser/fileapi/async_file_util.h
new file mode 100644
index 0000000..eab6af7
--- /dev/null
+++ b/storage/browser/fileapi/async_file_util.h
@@ -0,0 +1,366 @@
+// 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 STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_
+#define STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+#include "base/files/file_util_proxy.h"
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/directory_entry.h"
+
+namespace base {
+class Time;
+}
+
+namespace storage {
+class ShareableFileReference;
+}
+
+namespace storage {
+
+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 AsyncFileUtil {
+ public:
+ typedef base::Callback<void(base::File::Error 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::File file,
+ const base::Closure& on_close_callback)> CreateOrOpenCallback;
+
+ typedef base::Callback<
+ void(base::File::Error result,
+ bool created)> EnsureFileExistsCallback;
+
+ typedef base::Callback<
+ void(base::File::Error result,
+ const base::File::Info& file_info)> GetFileInfoCallback;
+
+ typedef std::vector<DirectoryEntry> EntryList;
+ typedef base::Callback<
+ void(base::File::Error result,
+ const EntryList& file_list,
+ bool has_more)> ReadDirectoryCallback;
+
+ typedef base::Callback<
+ void(base::File::Error result,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref)>
+ CreateSnapshotFileCallback;
+
+ typedef base::Callback<void(int64 size)> CopyFileProgressCallback;
+
+ typedef FileSystemOperation::CopyOrMoveOption CopyOrMoveOption;
+
+ // Creates an AsyncFileUtil instance which performs file operations on
+ // local native file system. The created instance assumes
+ // FileSystemURL::path() has the target platform path.
+ STORAGE_EXPORT static AsyncFileUtil*
+ CreateForLocalFileSystem();
+
+ AsyncFileUtil() {}
+ virtual ~AsyncFileUtil() {}
+
+ // Creates or opens a file with the given flags.
+ // If File::FLAG_CREATE is set in |file_flags| it always tries to create
+ // a new file at the given |url| and calls back with
+ // File::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|:
+ // - File::FILE_OK and created==true if a file has not existed and
+ // is created at |url|.
+ // - File::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|:
+ // - File::FILE_ERROR_NOT_FOUND if the |url|'s parent directory
+ // does not exist and |recursive| is false.
+ // - File::FILE_ERROR_EXISTS if a directory already exists at |url|
+ // and |exclusive| is true.
+ // - File::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|:
+ // - File::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|:
+ // - File::FILE_ERROR_NOT_FOUND if the target directory doesn't exist.
+ // - File::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|:
+ // - File::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).
+ // |progress_callback| is a callback to report the progress update.
+ // See file_system_operations.h for details. This should be called on the
+ // same thread as where the method's called (IO thread). Calling this
+ // is optional. It is recommended to use this callback for heavier operations
+ // (such as file network downloading), so that, e.g., clients (UIs) can
+ // update its state to show progress to users. This may be a null callback.
+ //
+ // FileSystemOperationImpl::Copy calls this for same-filesystem copy case.
+ //
+ // This reports following error code via |callback|:
+ // - File::FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::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,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ 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|:
+ // - File::FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::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,
+ CopyOrMoveOption option,
+ 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|:
+ // - File::FILE_ERROR_NOT_FOUND if |src_file_path|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::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|:
+ // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - File::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|:
+ // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - File::FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory.
+ // - File::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,
+ // File::FILE_ERROR_INVALID_OPERATION should be returned via |callback|.
+ //
+ // This reports following error code via |callback|:
+ // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - File::FILE_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|:
+ // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - File::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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_H_
diff --git a/storage/browser/fileapi/async_file_util_adapter.cc b/storage/browser/fileapi/async_file_util_adapter.cc
new file mode 100644
index 0000000..3bb20f9
--- /dev/null
+++ b/storage/browser/fileapi/async_file_util_adapter.cc
@@ -0,0 +1,352 @@
+// 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 "storage/browser/fileapi/async_file_util_adapter.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task_runner_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/common/blob/shareable_file_reference.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+using base::Bind;
+using base::Callback;
+using base::Owned;
+using base::Unretained;
+using storage::ShareableFileReference;
+
+namespace storage {
+
+namespace {
+
+class EnsureFileExistsHelper {
+ public:
+ EnsureFileExistsHelper() : error_(base::File::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::File::Error error_;
+ bool created_;
+ DISALLOW_COPY_AND_ASSIGN(EnsureFileExistsHelper);
+};
+
+class GetFileInfoHelper {
+ public:
+ GetFileInfoHelper()
+ : error_(base::File::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::File::Error error_;
+ base::File::Info file_info_;
+ base::FilePath platform_path_;
+ storage::ScopedFile scoped_file_;
+ DISALLOW_COPY_AND_ASSIGN(GetFileInfoHelper);
+};
+
+void ReadDirectoryHelper(FileSystemFileUtil* file_util,
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::SingleThreadTaskRunner* origin_loop,
+ const AsyncFileUtil::ReadDirectoryCallback& callback) {
+ base::File::Info file_info;
+ base::FilePath platform_path;
+ base::File::Error error = file_util->GetFileInfo(
+ context, url, &file_info, &platform_path);
+
+ if (error == base::File::FILE_OK && !file_info.is_directory)
+ error = base::File::FILE_ERROR_NOT_A_DIRECTORY;
+
+ std::vector<DirectoryEntry> entries;
+ if (error != base::File::FILE_OK) {
+ origin_loop->PostTask(
+ FROM_HERE, base::Bind(callback, error, entries, false /* has_more */));
+ return;
+ }
+
+ // Note: Increasing this value may make some tests in LayoutTests meaningless.
+ // (Namely, read-directory-many.html and read-directory-sync-many.html are
+ // assuming that they are reading much more entries than this constant.)
+ const size_t kResultChunkSize = 100;
+
+ 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);
+
+ if (entries.size() == kResultChunkSize) {
+ origin_loop->PostTask(
+ FROM_HERE, base::Bind(callback, base::File::FILE_OK, entries,
+ true /* has_more */));
+ entries.clear();
+ }
+ }
+ origin_loop->PostTask(
+ FROM_HERE, base::Bind(callback, base::File::FILE_OK, entries,
+ false /* has_more */));
+}
+
+void RunCreateOrOpenCallback(
+ FileSystemOperationContext* context,
+ const AsyncFileUtil::CreateOrOpenCallback& callback,
+ base::File file) {
+ callback.Run(file.Pass(), 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();
+ base::PostTaskAndReplyWithResult(
+ context_ptr->task_runner(),
+ FROM_HERE,
+ Bind(&FileSystemFileUtil::CreateOrOpen, Unretained(sync_file_util_.get()),
+ context_ptr, url, file_flags),
+ Bind(&RunCreateOrOpenCallback, base::Owned(context_ptr), callback));
+}
+
+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();
+ const bool success = context_ptr->task_runner()->PostTask(
+ FROM_HERE,
+ Bind(&ReadDirectoryHelper,
+ sync_file_util_.get(), base::Owned(context_ptr), url,
+ base::ThreadTaskRunnerHandle::Get(), 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,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ const StatusCallback& callback) {
+ // TODO(hidehiko): Support progress_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, option, true /* copy */),
+ callback);
+ DCHECK(success);
+}
+
+void AsyncFileUtilAdapter::MoveFileLocal(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ 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, option, 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::File::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 storage
diff --git a/storage/browser/fileapi/async_file_util_adapter.h b/storage/browser/fileapi/async_file_util_adapter.h
new file mode 100644
index 0000000..4ba4542
--- /dev/null
+++ b/storage/browser/fileapi/async_file_util_adapter.h
@@ -0,0 +1,118 @@
+// 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 STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_
+#define STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/async_file_util.h"
+
+namespace storage {
+
+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 STORAGE_EXPORT AsyncFileUtilAdapter
+ : public NON_EXPORTED_BASE(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,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void MoveFileLocal(
+ scoped_ptr<FileSystemOperationContext> context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_ASYNC_FILE_UTIL_ADAPTER_H_
diff --git a/storage/browser/fileapi/copy_or_move_file_validator.h b/storage/browser/fileapi/copy_or_move_file_validator.h
new file mode 100644
index 0000000..f79c473
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_
+#define STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_
+
+#include "base/callback.h"
+#include "base/files/file.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace storage {
+
+class FileSystemURL;
+
+class STORAGE_EXPORT CopyOrMoveFileValidator {
+ public:
+ // Callback that is invoked when validation completes. A result of
+ // base::File::FILE_OK means the file validated.
+ typedef base::Callback<void(base::File::Error 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_FILE_VALIDATOR_H_
diff --git a/storage/browser/fileapi/copy_or_move_operation_delegate.cc b/storage/browser/fileapi/copy_or_move_operation_delegate.cc
new file mode 100644
index 0000000..84244df
--- /dev/null
+++ b/storage/browser/fileapi/copy_or_move_operation_delegate.cc
@@ -0,0 +1,1034 @@
+// 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 "storage/browser/fileapi/copy_or_move_operation_delegate.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/copy_or_move_file_validator.h"
+#include "storage/browser/fileapi/file_observers.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/recursive_operation_delegate.h"
+#include "storage/common/blob/shareable_file_reference.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace storage {
+
+const int64 kFlushIntervalInBytes = 10 << 20; // 10MB.
+
+class CopyOrMoveOperationDelegate::CopyOrMoveImpl {
+ public:
+ virtual ~CopyOrMoveImpl() {}
+ virtual void Run(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback) = 0;
+ virtual void Cancel() = 0;
+
+ protected:
+ CopyOrMoveImpl() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CopyOrMoveImpl);
+};
+
+namespace {
+
+// Copies a file on a (same) file system. Just delegate the operation to
+// |operation_runner|.
+class CopyOrMoveOnSameFileSystemImpl
+ : public CopyOrMoveOperationDelegate::CopyOrMoveImpl {
+ public:
+ CopyOrMoveOnSameFileSystemImpl(
+ FileSystemOperationRunner* operation_runner,
+ CopyOrMoveOperationDelegate::OperationType operation_type,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOperationDelegate::CopyOrMoveOption option,
+ const FileSystemOperation::CopyFileProgressCallback&
+ file_progress_callback)
+ : operation_runner_(operation_runner),
+ operation_type_(operation_type),
+ src_url_(src_url),
+ dest_url_(dest_url),
+ option_(option),
+ file_progress_callback_(file_progress_callback) {
+ }
+
+ virtual void Run(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback) OVERRIDE {
+ if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_MOVE) {
+ operation_runner_->MoveFileLocal(src_url_, dest_url_, option_, callback);
+ } else {
+ operation_runner_->CopyFileLocal(
+ src_url_, dest_url_, option_, file_progress_callback_, callback);
+ }
+ }
+
+ virtual void Cancel() OVERRIDE {
+ // We can do nothing for the copy/move operation on a local file system.
+ // Assuming the operation is quickly done, it should be ok to just wait
+ // for the completion.
+ }
+
+ private:
+ FileSystemOperationRunner* operation_runner_;
+ CopyOrMoveOperationDelegate::OperationType operation_type_;
+ FileSystemURL src_url_;
+ FileSystemURL dest_url_;
+ CopyOrMoveOperationDelegate::CopyOrMoveOption option_;
+ FileSystemOperation::CopyFileProgressCallback file_progress_callback_;
+ DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOnSameFileSystemImpl);
+};
+
+// Specifically for cross file system copy/move operation, this class creates
+// a snapshot file, validates it if necessary, runs copying process,
+// validates the created file, and removes source file for move (noop for
+// copy).
+class SnapshotCopyOrMoveImpl
+ : public CopyOrMoveOperationDelegate::CopyOrMoveImpl {
+ public:
+ SnapshotCopyOrMoveImpl(
+ FileSystemOperationRunner* operation_runner,
+ CopyOrMoveOperationDelegate::OperationType operation_type,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOperationDelegate::CopyOrMoveOption option,
+ CopyOrMoveFileValidatorFactory* validator_factory,
+ const FileSystemOperation::CopyFileProgressCallback&
+ file_progress_callback)
+ : operation_runner_(operation_runner),
+ operation_type_(operation_type),
+ src_url_(src_url),
+ dest_url_(dest_url),
+ option_(option),
+ validator_factory_(validator_factory),
+ file_progress_callback_(file_progress_callback),
+ cancel_requested_(false),
+ weak_factory_(this) {
+ }
+
+ virtual void Run(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback) OVERRIDE {
+ file_progress_callback_.Run(0);
+ operation_runner_->CreateSnapshotFile(
+ src_url_,
+ base::Bind(&SnapshotCopyOrMoveImpl::RunAfterCreateSnapshot,
+ weak_factory_.GetWeakPtr(), callback));
+ }
+
+ virtual void Cancel() OVERRIDE {
+ cancel_requested_ = true;
+ }
+
+ private:
+ void RunAfterCreateSnapshot(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref) {
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+
+ if (error != base::File::FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ // For now we assume CreateSnapshotFile always return a valid local file
+ // path.
+ DCHECK(!platform_path.empty());
+
+ if (!validator_factory_) {
+ // No validation is needed.
+ RunAfterPreWriteValidation(platform_path, file_info, file_ref, callback,
+ base::File::FILE_OK);
+ return;
+ }
+
+ // Run pre write validation.
+ PreWriteValidation(
+ platform_path,
+ base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPreWriteValidation,
+ weak_factory_.GetWeakPtr(),
+ platform_path, file_info, file_ref, callback));
+ }
+
+ void RunAfterPreWriteValidation(
+ const base::FilePath& platform_path,
+ const base::File::Info& file_info,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref,
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error) {
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+
+ if (error != base::File::FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ // |file_ref| is unused but necessary to keep the file alive until
+ // CopyInForeignFile() is completed.
+ operation_runner_->CopyInForeignFile(
+ platform_path, dest_url_,
+ base::Bind(&SnapshotCopyOrMoveImpl::RunAfterCopyInForeignFile,
+ weak_factory_.GetWeakPtr(), file_info, file_ref, callback));
+ }
+
+ void RunAfterCopyInForeignFile(
+ const base::File::Info& file_info,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref,
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error) {
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+
+ if (error != base::File::FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ file_progress_callback_.Run(file_info.size);
+
+ if (option_ == FileSystemOperation::OPTION_NONE) {
+ RunAfterTouchFile(callback, base::File::FILE_OK);
+ return;
+ }
+
+ operation_runner_->TouchFile(
+ dest_url_, base::Time::Now() /* last_access */,
+ file_info.last_modified,
+ base::Bind(&SnapshotCopyOrMoveImpl::RunAfterTouchFile,
+ weak_factory_.GetWeakPtr(), callback));
+ }
+
+ void RunAfterTouchFile(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error) {
+ // Even if TouchFile is failed, just ignore it.
+
+ if (cancel_requested_) {
+ callback.Run(base::File::FILE_ERROR_ABORT);
+ return;
+ }
+
+ // |validator_| is NULL when the destination filesystem does not do
+ // validation.
+ if (!validator_) {
+ // No validation is needed.
+ RunAfterPostWriteValidation(callback, base::File::FILE_OK);
+ return;
+ }
+
+ PostWriteValidation(
+ base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPostWriteValidation,
+ weak_factory_.GetWeakPtr(), callback));
+ }
+
+ void RunAfterPostWriteValidation(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error) {
+ if (cancel_requested_) {
+ callback.Run(base::File::FILE_ERROR_ABORT);
+ return;
+ }
+
+ if (error != base::File::FILE_OK) {
+ // Failed to validate. Remove the destination file.
+ operation_runner_->Remove(
+ dest_url_, true /* recursive */,
+ base::Bind(&SnapshotCopyOrMoveImpl::DidRemoveDestForError,
+ weak_factory_.GetWeakPtr(), error, callback));
+ return;
+ }
+
+ if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_COPY) {
+ callback.Run(base::File::FILE_OK);
+ return;
+ }
+
+ DCHECK_EQ(CopyOrMoveOperationDelegate::OPERATION_MOVE, operation_type_);
+
+ // Remove the source for finalizing move operation.
+ operation_runner_->Remove(
+ src_url_, true /* recursive */,
+ base::Bind(&SnapshotCopyOrMoveImpl::RunAfterRemoveSourceForMove,
+ weak_factory_.GetWeakPtr(), callback));
+ }
+
+ void RunAfterRemoveSourceForMove(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error) {
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+
+ if (error == base::File::FILE_ERROR_NOT_FOUND)
+ error = base::File::FILE_OK;
+ callback.Run(error);
+ }
+
+ void DidRemoveDestForError(
+ base::File::Error prior_error,
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error) {
+ if (error != base::File::FILE_OK) {
+ VLOG(1) << "Error removing destination file after validation error: "
+ << error;
+ }
+ callback.Run(prior_error);
+ }
+
+ // Runs pre-write validation.
+ void PreWriteValidation(
+ const base::FilePath& platform_path,
+ const CopyOrMoveOperationDelegate::StatusCallback& callback) {
+ DCHECK(validator_factory_);
+ validator_.reset(
+ validator_factory_->CreateCopyOrMoveFileValidator(
+ src_url_, platform_path));
+ validator_->StartPreWriteValidation(callback);
+ }
+
+ // Runs post-write validation.
+ void PostWriteValidation(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback) {
+ operation_runner_->CreateSnapshotFile(
+ dest_url_,
+ base::Bind(
+ &SnapshotCopyOrMoveImpl::PostWriteValidationAfterCreateSnapshotFile,
+ weak_factory_.GetWeakPtr(), callback));
+ }
+
+ void PostWriteValidationAfterCreateSnapshotFile(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref) {
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+
+ if (error != base::File::FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ DCHECK(validator_);
+ // Note: file_ref passed here to keep the file alive until after
+ // the StartPostWriteValidation operation finishes.
+ validator_->StartPostWriteValidation(
+ platform_path,
+ base::Bind(&SnapshotCopyOrMoveImpl::DidPostWriteValidation,
+ weak_factory_.GetWeakPtr(), file_ref, callback));
+ }
+
+ // |file_ref| is unused; it is passed here to make sure the reference is
+ // alive until after post-write validation is complete.
+ void DidPostWriteValidation(
+ const scoped_refptr<storage::ShareableFileReference>& file_ref,
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error) {
+ callback.Run(error);
+ }
+
+ FileSystemOperationRunner* operation_runner_;
+ CopyOrMoveOperationDelegate::OperationType operation_type_;
+ FileSystemURL src_url_;
+ FileSystemURL dest_url_;
+
+ CopyOrMoveOperationDelegate::CopyOrMoveOption option_;
+ CopyOrMoveFileValidatorFactory* validator_factory_;
+ scoped_ptr<CopyOrMoveFileValidator> validator_;
+ FileSystemOperation::CopyFileProgressCallback file_progress_callback_;
+ bool cancel_requested_;
+ base::WeakPtrFactory<SnapshotCopyOrMoveImpl> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(SnapshotCopyOrMoveImpl);
+};
+
+// The size of buffer for StreamCopyHelper.
+const int kReadBufferSize = 32768;
+
+// To avoid too many progress callbacks, it should be called less
+// frequently than 50ms.
+const int kMinProgressCallbackInvocationSpanInMilliseconds = 50;
+
+// Specifically for cross file system copy/move operation, this class uses
+// stream reader and writer for copying. Validator is not supported, so if
+// necessary SnapshotCopyOrMoveImpl should be used.
+class StreamCopyOrMoveImpl
+ : public CopyOrMoveOperationDelegate::CopyOrMoveImpl {
+ public:
+ StreamCopyOrMoveImpl(
+ FileSystemOperationRunner* operation_runner,
+ FileSystemContext* file_system_context,
+ CopyOrMoveOperationDelegate::OperationType operation_type,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOperationDelegate::CopyOrMoveOption option,
+ scoped_ptr<storage::FileStreamReader> reader,
+ scoped_ptr<FileStreamWriter> writer,
+ const FileSystemOperation::CopyFileProgressCallback&
+ file_progress_callback)
+ : operation_runner_(operation_runner),
+ file_system_context_(file_system_context),
+ operation_type_(operation_type),
+ src_url_(src_url),
+ dest_url_(dest_url),
+ option_(option),
+ reader_(reader.Pass()),
+ writer_(writer.Pass()),
+ file_progress_callback_(file_progress_callback),
+ cancel_requested_(false),
+ weak_factory_(this) {}
+
+ virtual void Run(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback) OVERRIDE {
+ // Reader can be created even if the entry does not exist or the entry is
+ // a directory. To check errors before destination file creation,
+ // check metadata first.
+ operation_runner_->GetMetadata(
+ src_url_,
+ base::Bind(&StreamCopyOrMoveImpl::RunAfterGetMetadataForSource,
+ weak_factory_.GetWeakPtr(), callback));
+ }
+
+ virtual void Cancel() OVERRIDE {
+ cancel_requested_ = true;
+ if (copy_helper_)
+ copy_helper_->Cancel();
+ }
+
+ private:
+ void NotifyOnStartUpdate(const FileSystemURL& url) {
+ if (file_system_context_->GetUpdateObservers(url.type())) {
+ file_system_context_->GetUpdateObservers(url.type())
+ ->Notify(&FileUpdateObserver::OnStartUpdate, MakeTuple(url));
+ }
+ }
+
+ void NotifyOnModifyFile(const FileSystemURL& url) {
+ if (file_system_context_->GetChangeObservers(url.type())) {
+ file_system_context_->GetChangeObservers(url.type())
+ ->Notify(&FileChangeObserver::OnModifyFile, MakeTuple(url));
+ }
+ }
+
+ void NotifyOnEndUpdate(const FileSystemURL& url) {
+ if (file_system_context_->GetUpdateObservers(url.type())) {
+ file_system_context_->GetUpdateObservers(url.type())
+ ->Notify(&FileUpdateObserver::OnEndUpdate, MakeTuple(url));
+ }
+ }
+
+ void RunAfterGetMetadataForSource(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error,
+ const base::File::Info& file_info) {
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+
+ if (error != base::File::FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ if (file_info.is_directory) {
+ // If not a directory, failed with appropriate error code.
+ callback.Run(base::File::FILE_ERROR_NOT_A_FILE);
+ return;
+ }
+
+ // To use FileStreamWriter, we need to ensure the destination file exists.
+ operation_runner_->CreateFile(
+ dest_url_,
+ true /* exclusive */,
+ base::Bind(&StreamCopyOrMoveImpl::RunAfterCreateFileForDestination,
+ weak_factory_.GetWeakPtr(),
+ callback,
+ file_info.last_modified));
+ }
+
+ void RunAfterCreateFileForDestination(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ const base::Time& last_modified,
+ base::File::Error error) {
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+ // This conversion is to return the consistent status code with
+ // FileSystemFileUtil::Copy.
+ if (error == base::File::FILE_ERROR_NOT_A_FILE)
+ error = base::File::FILE_ERROR_INVALID_OPERATION;
+
+ if (error != base::File::FILE_OK &&
+ error != base::File::FILE_ERROR_EXISTS) {
+ callback.Run(error);
+ return;
+ }
+
+ if (error == base::File::FILE_ERROR_EXISTS) {
+ operation_runner_->Truncate(
+ dest_url_,
+ 0 /* length */,
+ base::Bind(&StreamCopyOrMoveImpl::RunAfterTruncateForDestination,
+ weak_factory_.GetWeakPtr(),
+ callback,
+ last_modified));
+ return;
+ }
+ RunAfterTruncateForDestination(
+ callback, last_modified, base::File::FILE_OK);
+ }
+
+ void RunAfterTruncateForDestination(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ const base::Time& last_modified,
+ base::File::Error error) {
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+
+ if (error != base::File::FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ const bool need_flush = dest_url_.mount_option().copy_sync_option() ==
+ storage::COPY_SYNC_OPTION_SYNC;
+
+ NotifyOnStartUpdate(dest_url_);
+ DCHECK(!copy_helper_);
+ copy_helper_.reset(
+ new CopyOrMoveOperationDelegate::StreamCopyHelper(
+ reader_.Pass(), writer_.Pass(),
+ need_flush,
+ kReadBufferSize,
+ file_progress_callback_,
+ base::TimeDelta::FromMilliseconds(
+ kMinProgressCallbackInvocationSpanInMilliseconds)));
+ copy_helper_->Run(
+ base::Bind(&StreamCopyOrMoveImpl::RunAfterStreamCopy,
+ weak_factory_.GetWeakPtr(), callback, last_modified));
+ }
+
+ void RunAfterStreamCopy(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ const base::Time& last_modified,
+ base::File::Error error) {
+ NotifyOnModifyFile(dest_url_);
+ NotifyOnEndUpdate(dest_url_);
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+
+ if (error != base::File::FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ if (option_ == FileSystemOperation::OPTION_NONE) {
+ RunAfterTouchFile(callback, base::File::FILE_OK);
+ return;
+ }
+
+ operation_runner_->TouchFile(
+ dest_url_, base::Time::Now() /* last_access */, last_modified,
+ base::Bind(&StreamCopyOrMoveImpl::RunAfterTouchFile,
+ weak_factory_.GetWeakPtr(), callback));
+ }
+
+ void RunAfterTouchFile(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error) {
+ // Even if TouchFile is failed, just ignore it.
+ if (cancel_requested_) {
+ callback.Run(base::File::FILE_ERROR_ABORT);
+ return;
+ }
+
+ if (operation_type_ == CopyOrMoveOperationDelegate::OPERATION_COPY) {
+ callback.Run(base::File::FILE_OK);
+ return;
+ }
+
+ DCHECK_EQ(CopyOrMoveOperationDelegate::OPERATION_MOVE, operation_type_);
+
+ // Remove the source for finalizing move operation.
+ operation_runner_->Remove(
+ src_url_, false /* recursive */,
+ base::Bind(&StreamCopyOrMoveImpl::RunAfterRemoveForMove,
+ weak_factory_.GetWeakPtr(), callback));
+ }
+
+ void RunAfterRemoveForMove(
+ const CopyOrMoveOperationDelegate::StatusCallback& callback,
+ base::File::Error error) {
+ if (cancel_requested_)
+ error = base::File::FILE_ERROR_ABORT;
+ if (error == base::File::FILE_ERROR_NOT_FOUND)
+ error = base::File::FILE_OK;
+ callback.Run(error);
+ }
+
+ FileSystemOperationRunner* operation_runner_;
+ scoped_refptr<FileSystemContext> file_system_context_;
+ CopyOrMoveOperationDelegate::OperationType operation_type_;
+ FileSystemURL src_url_;
+ FileSystemURL dest_url_;
+ CopyOrMoveOperationDelegate::CopyOrMoveOption option_;
+ scoped_ptr<storage::FileStreamReader> reader_;
+ scoped_ptr<FileStreamWriter> writer_;
+ FileSystemOperation::CopyFileProgressCallback file_progress_callback_;
+ scoped_ptr<CopyOrMoveOperationDelegate::StreamCopyHelper> copy_helper_;
+ bool cancel_requested_;
+ base::WeakPtrFactory<StreamCopyOrMoveImpl> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(StreamCopyOrMoveImpl);
+};
+
+} // namespace
+
+CopyOrMoveOperationDelegate::StreamCopyHelper::StreamCopyHelper(
+ scoped_ptr<storage::FileStreamReader> reader,
+ scoped_ptr<FileStreamWriter> writer,
+ bool need_flush,
+ int buffer_size,
+ const FileSystemOperation::CopyFileProgressCallback& file_progress_callback,
+ const base::TimeDelta& min_progress_callback_invocation_span)
+ : reader_(reader.Pass()),
+ writer_(writer.Pass()),
+ need_flush_(need_flush),
+ file_progress_callback_(file_progress_callback),
+ io_buffer_(new net::IOBufferWithSize(buffer_size)),
+ num_copied_bytes_(0),
+ previous_flush_offset_(0),
+ min_progress_callback_invocation_span_(
+ min_progress_callback_invocation_span),
+ cancel_requested_(false),
+ weak_factory_(this) {
+}
+
+CopyOrMoveOperationDelegate::StreamCopyHelper::~StreamCopyHelper() {
+}
+
+void CopyOrMoveOperationDelegate::StreamCopyHelper::Run(
+ const StatusCallback& callback) {
+ file_progress_callback_.Run(0);
+ last_progress_callback_invocation_time_ = base::Time::Now();
+ Read(callback);
+}
+
+void CopyOrMoveOperationDelegate::StreamCopyHelper::Cancel() {
+ cancel_requested_ = true;
+}
+
+void CopyOrMoveOperationDelegate::StreamCopyHelper::Read(
+ const StatusCallback& callback) {
+ int result = reader_->Read(
+ io_buffer_.get(), io_buffer_->size(),
+ base::Bind(&StreamCopyHelper::DidRead,
+ weak_factory_.GetWeakPtr(), callback));
+ if (result != net::ERR_IO_PENDING)
+ DidRead(callback, result);
+}
+
+void CopyOrMoveOperationDelegate::StreamCopyHelper::DidRead(
+ const StatusCallback& callback, int result) {
+ if (cancel_requested_) {
+ callback.Run(base::File::FILE_ERROR_ABORT);
+ return;
+ }
+
+ if (result < 0) {
+ callback.Run(NetErrorToFileError(result));
+ return;
+ }
+
+ if (result == 0) {
+ // Here is the EOF.
+ if (need_flush_)
+ Flush(callback, true /* is_eof */);
+ else
+ callback.Run(base::File::FILE_OK);
+ return;
+ }
+
+ Write(callback, new net::DrainableIOBuffer(io_buffer_.get(), result));
+}
+
+void CopyOrMoveOperationDelegate::StreamCopyHelper::Write(
+ const StatusCallback& callback,
+ scoped_refptr<net::DrainableIOBuffer> buffer) {
+ DCHECK_GT(buffer->BytesRemaining(), 0);
+
+ int result = writer_->Write(
+ buffer.get(), buffer->BytesRemaining(),
+ base::Bind(&StreamCopyHelper::DidWrite,
+ weak_factory_.GetWeakPtr(), callback, buffer));
+ if (result != net::ERR_IO_PENDING)
+ DidWrite(callback, buffer, result);
+}
+
+void CopyOrMoveOperationDelegate::StreamCopyHelper::DidWrite(
+ const StatusCallback& callback,
+ scoped_refptr<net::DrainableIOBuffer> buffer,
+ int result) {
+ if (cancel_requested_) {
+ callback.Run(base::File::FILE_ERROR_ABORT);
+ return;
+ }
+
+ if (result < 0) {
+ callback.Run(NetErrorToFileError(result));
+ return;
+ }
+
+ buffer->DidConsume(result);
+ num_copied_bytes_ += result;
+
+ // Check the elapsed time since last |file_progress_callback_| invocation.
+ base::Time now = base::Time::Now();
+ if (now - last_progress_callback_invocation_time_ >=
+ min_progress_callback_invocation_span_) {
+ file_progress_callback_.Run(num_copied_bytes_);
+ last_progress_callback_invocation_time_ = now;
+ }
+
+ if (buffer->BytesRemaining() > 0) {
+ Write(callback, buffer);
+ return;
+ }
+
+ if (need_flush_ &&
+ (num_copied_bytes_ - previous_flush_offset_) > kFlushIntervalInBytes) {
+ Flush(callback, false /* not is_eof */);
+ } else {
+ Read(callback);
+ }
+}
+
+void CopyOrMoveOperationDelegate::StreamCopyHelper::Flush(
+ const StatusCallback& callback, bool is_eof) {
+ int result = writer_->Flush(
+ base::Bind(&StreamCopyHelper::DidFlush,
+ weak_factory_.GetWeakPtr(), callback, is_eof));
+ if (result != net::ERR_IO_PENDING)
+ DidFlush(callback, is_eof, result);
+}
+
+void CopyOrMoveOperationDelegate::StreamCopyHelper::DidFlush(
+ const StatusCallback& callback, bool is_eof, int result) {
+ if (cancel_requested_) {
+ callback.Run(base::File::FILE_ERROR_ABORT);
+ return;
+ }
+
+ previous_flush_offset_ = num_copied_bytes_;
+ if (is_eof)
+ callback.Run(NetErrorToFileError(result));
+ else
+ Read(callback);
+}
+
+CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& src_root,
+ const FileSystemURL& dest_root,
+ OperationType operation_type,
+ CopyOrMoveOption option,
+ const CopyProgressCallback& progress_callback,
+ const StatusCallback& callback)
+ : RecursiveOperationDelegate(file_system_context),
+ src_root_(src_root),
+ dest_root_(dest_root),
+ operation_type_(operation_type),
+ option_(option),
+ progress_callback_(progress_callback),
+ callback_(callback),
+ weak_factory_(this) {
+ same_file_system_ = src_root_.IsInSameFileSystem(dest_root_);
+}
+
+CopyOrMoveOperationDelegate::~CopyOrMoveOperationDelegate() {
+ STLDeleteElements(&running_copy_set_);
+}
+
+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::File::FILE_ERROR_INVALID_OPERATION);
+ return;
+ }
+
+ if (same_file_system_ && src_root_.path() == dest_root_.path()) {
+ // In JS API this should return error, but we return success because Pepper
+ // wants to return success and we have a code path that returns error in
+ // Blink for JS (http://crbug.com/329517).
+ callback_.Run(base::File::FILE_OK);
+ 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_, callback_);
+}
+
+void CopyOrMoveOperationDelegate::ProcessFile(
+ const FileSystemURL& src_url,
+ const StatusCallback& callback) {
+ if (!progress_callback_.is_null()) {
+ progress_callback_.Run(
+ FileSystemOperation::BEGIN_COPY_ENTRY, src_url, FileSystemURL(), 0);
+ }
+
+ FileSystemURL dest_url = CreateDestURL(src_url);
+ CopyOrMoveImpl* impl = NULL;
+ if (same_file_system_ &&
+ (file_system_context()
+ ->GetFileSystemBackend(src_url.type())
+ ->HasInplaceCopyImplementation(src_url.type()) ||
+ operation_type_ == OperationType::OPERATION_MOVE)) {
+ impl = new CopyOrMoveOnSameFileSystemImpl(
+ operation_runner(), operation_type_, src_url, dest_url, option_,
+ base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress,
+ weak_factory_.GetWeakPtr(), src_url));
+ } else {
+ // Cross filesystem case.
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ CopyOrMoveFileValidatorFactory* validator_factory =
+ file_system_context()->GetCopyOrMoveFileValidatorFactory(
+ dest_root_.type(), &error);
+ if (error != base::File::FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ if (!validator_factory) {
+ scoped_ptr<storage::FileStreamReader> reader =
+ file_system_context()->CreateFileStreamReader(
+ src_url, 0, base::Time());
+ scoped_ptr<FileStreamWriter> writer =
+ file_system_context()->CreateFileStreamWriter(dest_url, 0);
+ if (reader && writer) {
+ impl = new StreamCopyOrMoveImpl(
+ operation_runner(),
+ file_system_context(),
+ operation_type_,
+ src_url,
+ dest_url,
+ option_,
+ reader.Pass(),
+ writer.Pass(),
+ base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress,
+ weak_factory_.GetWeakPtr(),
+ src_url));
+ }
+ }
+
+ if (!impl) {
+ impl = new SnapshotCopyOrMoveImpl(
+ operation_runner(), operation_type_, src_url, dest_url, option_,
+ validator_factory,
+ base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress,
+ weak_factory_.GetWeakPtr(), src_url));
+ }
+ }
+
+ // Register the running task.
+ running_copy_set_.insert(impl);
+ impl->Run(base::Bind(
+ &CopyOrMoveOperationDelegate::DidCopyOrMoveFile,
+ weak_factory_.GetWeakPtr(), src_url, dest_url, callback, impl));
+}
+
+void CopyOrMoveOperationDelegate::ProcessDirectory(
+ const FileSystemURL& src_url,
+ const StatusCallback& callback) {
+ if (src_url == src_root_) {
+ // 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.
+ // We do not invoke |progress_callback_| for source root, because it is
+ // already called in ProcessFile().
+ operation_runner()->RemoveDirectory(
+ dest_root_,
+ base::Bind(&CopyOrMoveOperationDelegate::DidTryRemoveDestRoot,
+ weak_factory_.GetWeakPtr(), callback));
+ return;
+ }
+
+ if (!progress_callback_.is_null()) {
+ progress_callback_.Run(
+ FileSystemOperation::BEGIN_COPY_ENTRY, src_url, FileSystemURL(), 0);
+ }
+
+ ProcessDirectoryInternal(src_url, CreateDestURL(src_url), callback);
+}
+
+void CopyOrMoveOperationDelegate::PostProcessDirectory(
+ const FileSystemURL& src_url,
+ const StatusCallback& callback) {
+ if (option_ == FileSystemOperation::OPTION_NONE) {
+ PostProcessDirectoryAfterTouchFile(
+ src_url, callback, base::File::FILE_OK);
+ return;
+ }
+
+ operation_runner()->GetMetadata(
+ src_url,
+ base::Bind(
+ &CopyOrMoveOperationDelegate::PostProcessDirectoryAfterGetMetadata,
+ weak_factory_.GetWeakPtr(), src_url, callback));
+}
+
+void CopyOrMoveOperationDelegate::OnCancel() {
+ // Request to cancel all running Copy/Move file.
+ for (std::set<CopyOrMoveImpl*>::iterator iter = running_copy_set_.begin();
+ iter != running_copy_set_.end(); ++iter)
+ (*iter)->Cancel();
+}
+
+void CopyOrMoveOperationDelegate::DidCopyOrMoveFile(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback,
+ CopyOrMoveImpl* impl,
+ base::File::Error error) {
+ running_copy_set_.erase(impl);
+ delete impl;
+
+ if (!progress_callback_.is_null() && error == base::File::FILE_OK) {
+ progress_callback_.Run(
+ FileSystemOperation::END_COPY_ENTRY, src_url, dest_url, 0);
+ }
+
+ callback.Run(error);
+}
+
+void CopyOrMoveOperationDelegate::DidTryRemoveDestRoot(
+ const StatusCallback& callback,
+ base::File::Error error) {
+ if (error == base::File::FILE_ERROR_NOT_A_DIRECTORY) {
+ callback_.Run(base::File::FILE_ERROR_INVALID_OPERATION);
+ return;
+ }
+ if (error != base::File::FILE_OK &&
+ error != base::File::FILE_ERROR_NOT_FOUND) {
+ callback_.Run(error);
+ return;
+ }
+
+ ProcessDirectoryInternal(src_root_, dest_root_, callback);
+}
+
+void CopyOrMoveOperationDelegate::ProcessDirectoryInternal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ // 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 */,
+ base::Bind(&CopyOrMoveOperationDelegate::DidCreateDirectory,
+ weak_factory_.GetWeakPtr(), src_url, dest_url, callback));
+}
+
+void CopyOrMoveOperationDelegate::DidCreateDirectory(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback,
+ base::File::Error error) {
+ if (!progress_callback_.is_null() && error == base::File::FILE_OK) {
+ progress_callback_.Run(
+ FileSystemOperation::END_COPY_ENTRY, src_url, dest_url, 0);
+ }
+
+ callback.Run(error);
+}
+
+void CopyOrMoveOperationDelegate::PostProcessDirectoryAfterGetMetadata(
+ const FileSystemURL& src_url,
+ const StatusCallback& callback,
+ base::File::Error error,
+ const base::File::Info& file_info) {
+ if (error != base::File::FILE_OK) {
+ // Ignore the error, and run post process which should run after TouchFile.
+ PostProcessDirectoryAfterTouchFile(
+ src_url, callback, base::File::FILE_OK);
+ return;
+ }
+
+ operation_runner()->TouchFile(
+ CreateDestURL(src_url), base::Time::Now() /* last access */,
+ file_info.last_modified,
+ base::Bind(
+ &CopyOrMoveOperationDelegate::PostProcessDirectoryAfterTouchFile,
+ weak_factory_.GetWeakPtr(), src_url, callback));
+}
+
+void CopyOrMoveOperationDelegate::PostProcessDirectoryAfterTouchFile(
+ const FileSystemURL& src_url,
+ const StatusCallback& callback,
+ base::File::Error error) {
+ // Even if the TouchFile is failed, just ignore it.
+
+ if (operation_type_ == OPERATION_COPY) {
+ callback.Run(base::File::FILE_OK);
+ return;
+ }
+
+ DCHECK_EQ(OPERATION_MOVE, operation_type_);
+
+ // All files and subdirectories in the directory should be moved here,
+ // so remove the source directory for finalizing move operation.
+ operation_runner()->Remove(
+ src_url, false /* recursive */,
+ base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void CopyOrMoveOperationDelegate::DidRemoveSourceForMove(
+ const StatusCallback& callback,
+ base::File::Error error) {
+ if (error == base::File::FILE_ERROR_NOT_FOUND)
+ error = base::File::FILE_OK;
+ callback.Run(error);
+}
+
+void CopyOrMoveOperationDelegate::OnCopyFileProgress(
+ const FileSystemURL& src_url, int64 size) {
+ if (!progress_callback_.is_null()) {
+ progress_callback_.Run(
+ FileSystemOperation::PROGRESS, src_url, FileSystemURL(), size);
+ }
+}
+
+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);
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/copy_or_move_operation_delegate.h b/storage/browser/fileapi/copy_or_move_operation_delegate.h
new file mode 100644
index 0000000..0b97d1c
--- /dev/null
+++ b/storage/browser/fileapi/copy_or_move_operation_delegate.h
@@ -0,0 +1,162 @@
+// 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 STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_
+#define STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_
+
+#include <set>
+#include <stack>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "storage/browser/fileapi/recursive_operation_delegate.h"
+
+namespace net {
+class DrainableIOBuffer;
+class IOBufferWithSize;
+}
+
+namespace storage {
+class FileStreamReader;
+class ShareableFileReference;
+}
+
+namespace storage {
+
+class CopyOrMoveFileValidator;
+class FileStreamWriter;
+
+// A delegate class for recursive copy or move operations.
+class CopyOrMoveOperationDelegate
+ : public RecursiveOperationDelegate {
+ public:
+ class CopyOrMoveImpl;
+ typedef FileSystemOperation::CopyProgressCallback CopyProgressCallback;
+ typedef FileSystemOperation::CopyOrMoveOption CopyOrMoveOption;
+
+ enum OperationType {
+ OPERATION_COPY,
+ OPERATION_MOVE
+ };
+
+ // Helper to copy a file by reader and writer streams.
+ // Export for testing.
+ class STORAGE_EXPORT StreamCopyHelper {
+ public:
+ StreamCopyHelper(
+ scoped_ptr<storage::FileStreamReader> reader,
+ scoped_ptr<FileStreamWriter> writer,
+ bool need_flush,
+ int buffer_size,
+ const FileSystemOperation::CopyFileProgressCallback&
+ file_progress_callback,
+ const base::TimeDelta& min_progress_callback_invocation_span);
+ ~StreamCopyHelper();
+
+ void Run(const StatusCallback& callback);
+
+ // Requests cancelling. After the cancelling is done, |callback| passed to
+ // Run will be called.
+ void Cancel();
+
+ private:
+ // Reads the content from the |reader_|.
+ void Read(const StatusCallback& callback);
+ void DidRead(const StatusCallback& callback, int result);
+
+ // Writes the content in |buffer| to |writer_|.
+ void Write(const StatusCallback& callback,
+ scoped_refptr<net::DrainableIOBuffer> buffer);
+ void DidWrite(const StatusCallback& callback,
+ scoped_refptr<net::DrainableIOBuffer> buffer, int result);
+
+ // Flushes the written content in |writer_|.
+ void Flush(const StatusCallback& callback, bool is_eof);
+ void DidFlush(const StatusCallback& callback, bool is_eof, int result);
+
+ scoped_ptr<storage::FileStreamReader> reader_;
+ scoped_ptr<FileStreamWriter> writer_;
+ const bool need_flush_;
+ FileSystemOperation::CopyFileProgressCallback file_progress_callback_;
+ scoped_refptr<net::IOBufferWithSize> io_buffer_;
+ int64 num_copied_bytes_;
+ int64 previous_flush_offset_;
+ base::Time last_progress_callback_invocation_time_;
+ base::TimeDelta min_progress_callback_invocation_span_;
+ bool cancel_requested_;
+ base::WeakPtrFactory<StreamCopyHelper> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(StreamCopyHelper);
+ };
+
+ CopyOrMoveOperationDelegate(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& src_root,
+ const FileSystemURL& dest_root,
+ OperationType operation_type,
+ CopyOrMoveOption option,
+ const CopyProgressCallback& progress_callback,
+ 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;
+ virtual void PostProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+
+
+ protected:
+ virtual void OnCancel() OVERRIDE;
+
+ private:
+ void DidCopyOrMoveFile(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback,
+ CopyOrMoveImpl* impl,
+ base::File::Error error);
+ void DidTryRemoveDestRoot(const StatusCallback& callback,
+ base::File::Error error);
+ void ProcessDirectoryInternal(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback);
+ void DidCreateDirectory(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback,
+ base::File::Error error);
+ void PostProcessDirectoryAfterGetMetadata(
+ const FileSystemURL& src_url,
+ const StatusCallback& callback,
+ base::File::Error error,
+ const base::File::Info& file_info);
+ void PostProcessDirectoryAfterTouchFile(const FileSystemURL& src_url,
+ const StatusCallback& callback,
+ base::File::Error error);
+ void DidRemoveSourceForMove(const StatusCallback& callback,
+ base::File::Error error);
+
+ void OnCopyFileProgress(const FileSystemURL& src_url, int64 size);
+ FileSystemURL CreateDestURL(const FileSystemURL& src_url) const;
+
+ FileSystemURL src_root_;
+ FileSystemURL dest_root_;
+ bool same_file_system_;
+ OperationType operation_type_;
+ CopyOrMoveOption option_;
+ CopyProgressCallback progress_callback_;
+ StatusCallback callback_;
+
+ std::set<CopyOrMoveImpl*> running_copy_set_;
+ base::WeakPtrFactory<CopyOrMoveOperationDelegate> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOperationDelegate);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_COPY_OR_MOVE_OPERATION_DELEGATE_H_
diff --git a/storage/browser/fileapi/dragged_file_util.cc b/storage/browser/fileapi/dragged_file_util.cc
new file mode 100644
index 0000000..1f15188
--- /dev/null
+++ b/storage/browser/fileapi/dragged_file_util.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 "storage/browser/fileapi/dragged_file_util.h"
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/browser/fileapi/native_file_util.h"
+#include "storage/common/blob/shareable_file_reference.h"
+
+namespace storage {
+
+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::File::Info file_info_;
+};
+
+} // namespace
+
+//-------------------------------------------------------------------------
+
+DraggedFileUtil::DraggedFileUtil() {}
+
+base::File::Error DraggedFileUtil::GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Info* 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::File::FILE_OK;
+ }
+ base::File::Error error =
+ NativeFileUtil::GetFileInfo(url.path(), file_info);
+ if (base::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::File::FILE_ERROR_NOT_FOUND;
+ }
+ if (error == base::File::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 storage
diff --git a/storage/browser/fileapi/dragged_file_util.h b/storage/browser/fileapi/dragged_file_util.h
new file mode 100644
index 0000000..205e8ef
--- /dev/null
+++ b/storage/browser/fileapi/dragged_file_util.h
@@ -0,0 +1,41 @@
+// 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 STORAGE_BROWSER_FILEAPI_DRAGGED_FILE_UTIL_H_
+#define STORAGE_BROWSER_FILEAPI_DRAGGED_FILE_UTIL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/local_file_util.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace storage {
+
+class FileSystemOperationContext;
+
+// Dragged file system is a specialized LocalFileUtil 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 STORAGE_EXPORT_PRIVATE DraggedFileUtil
+ : public LocalFileUtil {
+ public:
+ DraggedFileUtil();
+ virtual ~DraggedFileUtil() {}
+
+ // FileSystemFileUtil overrides.
+ virtual base::File::Error GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Info* 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_DRAGGED_FILE_UTIL_H_
diff --git a/storage/browser/fileapi/dump_file_system.cc b/storage/browser/fileapi/dump_file_system.cc
new file mode 100644
index 0000000..49a2fa3
--- /dev/null
+++ b/storage/browser/fileapi/dump_file_system.cc
@@ -0,0 +1,204 @@
+// 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/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+#include "storage/browser/fileapi/obfuscated_file_util.h"
+#include "storage/browser/fileapi/sandbox_directory_database.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend.h"
+#include "storage/browser/fileapi/sandbox_origin_database.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace {
+
+bool g_opt_long;
+const char* g_opt_fs_type = "p";
+
+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 storage {
+
+static void DumpDirectoryTree(const std::string& origin_name,
+ base::FilePath origin_dir) {
+ origin_dir = origin_dir.Append(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, NULL);
+ 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 {
+ base::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, NULL);
+ 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, NULL);
+ 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 storage
+
+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 = "t";
+ argc--;
+ argv++;
+ } else if (std::string(argv[1]) == "-s") {
+ g_opt_fs_type = "s";
+ argc--;
+ argv++;
+ } else {
+ break;
+ }
+ }
+
+ if (argc < 2)
+ ShowUsageAndExit(arg0);
+
+ const base::FilePath file_system_dir = storage::StringToFilePath(argv[1]);
+ if (!base::DirectoryExists(file_system_dir)) {
+ ShowMessageAndExit(storage::FilePathToString(file_system_dir) +
+ " is not a filesystem directory");
+ }
+
+ if (argc == 2) {
+ storage::DumpFileSystem(file_system_dir);
+ } else {
+ for (int i = 2; i < argc; i++) {
+ storage::DumpOrigin(file_system_dir, argv[i]);
+ }
+ }
+ return 0;
+}
diff --git a/storage/browser/fileapi/external_mount_points.cc b/storage/browser/fileapi/external_mount_points.cc
new file mode 100644
index 0000000..baa0c49
--- /dev/null
+++ b/storage/browser/fileapi/external_mount_points.cc
@@ -0,0 +1,357 @@
+// 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 "storage/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 "storage/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();
+}
+
+bool IsOverlappingMountPathForbidden(storage::FileSystemType type) {
+ return type != storage::kFileSystemTypeNativeMedia &&
+ type != storage::kFileSystemTypeDeviceMedia;
+}
+
+// Wrapper around ref-counted ExternalMountPoints that will be used to lazily
+// create and initialize LazyInstance system ExternalMountPoints.
+class SystemMountPointsLazyWrapper {
+ public:
+ SystemMountPointsLazyWrapper()
+ : system_mount_points_(storage::ExternalMountPoints::CreateRefCounted()) {
+ }
+
+ ~SystemMountPointsLazyWrapper() {}
+
+ storage::ExternalMountPoints* get() { return system_mount_points_.get(); }
+
+ private:
+ scoped_refptr<storage::ExternalMountPoints> system_mount_points_;
+};
+
+base::LazyInstance<SystemMountPointsLazyWrapper>::Leaky
+ g_external_mount_points = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+namespace storage {
+
+class ExternalMountPoints::Instance {
+ public:
+ Instance(FileSystemType type,
+ const base::FilePath& path,
+ const FileSystemMountOption& mount_option)
+ : type_(type),
+ path_(path.StripTrailingSeparators()),
+ mount_option_(mount_option) {}
+ ~Instance() {}
+
+ FileSystemType type() const { return type_; }
+ const base::FilePath& path() const { return path_; }
+ const FileSystemMountOption& mount_option() const { return mount_option_; }
+
+ private:
+ const FileSystemType type_;
+ const base::FilePath path_;
+ const FileSystemMountOption mount_option_;
+
+ 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 FileSystemMountOption& mount_option,
+ const base::FilePath& path_in) {
+ // COPY_SYNC_OPTION_SYNC is only applicable to native local file system.
+ DCHECK(type == kFileSystemTypeNativeLocal ||
+ mount_option.copy_sync_option() != COPY_SYNC_OPTION_SYNC);
+
+ base::AutoLock locker(lock_);
+
+ base::FilePath path = NormalizeFilePath(path_in);
+ if (!ValidateNewMountPoint(mount_name, type, path))
+ return false;
+
+ instance_map_[mount_name] = new Instance(type, path, mount_option);
+ if (!path.empty() && IsOverlappingMountPathForbidden(type))
+ 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;
+ if (IsOverlappingMountPathForbidden(instance->type()))
+ 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,
+ std::string* cracked_id,
+ base::FilePath* path,
+ FileSystemMountOption* mount_option) 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();
+ *mount_option = instance->mount_option();
+ }
+
+ 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,
+ storage::kFileSystemTypeExternal,
+ // Avoid using FilePath::Append as path may be an absolute path.
+ base::FilePath(CreateVirtualRootPath(mount_name).value() +
+ base::FilePath::kSeparators[0] + path.value()));
+}
+
+void ExternalMountPoints::RevokeAllFileSystems() {
+ NameToInstance instance_map_copy;
+ {
+ base::AutoLock locker(lock_);
+ instance_map_copy = instance_map_;
+ instance_map_.clear();
+ path_to_name_map_.clear();
+ }
+ STLDeleteContainerPairSecondPointers(instance_map_copy.begin(),
+ instance_map_copy.end());
+}
+
+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(), url.mount_option());
+#endif
+ }
+
+ std::string mount_name;
+ FileSystemType cracked_type;
+ std::string cracked_id;
+ base::FilePath cracked_path;
+ FileSystemMountOption cracked_mount_option;
+
+ if (!CrackVirtualPath(virtual_path, &mount_name, &cracked_type,
+ &cracked_id, &cracked_path, &cracked_mount_option)) {
+ 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,
+ cracked_id.empty() ? mount_name : cracked_id, cracked_mount_option);
+}
+
+bool ExternalMountPoints::ValidateNewMountPoint(const std::string& mount_name,
+ FileSystemType type,
+ 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;
+
+ if (IsOverlappingMountPathForbidden(type)) {
+ // 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()) {
+ if (potential_child->first == path ||
+ path.IsParent(potential_child->first)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/external_mount_points.h b/storage/browser/fileapi/external_mount_points.h
new file mode 100644
index 0000000..26468c5
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_
+#define STORAGE_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/synchronization/lock.h"
+#include "storage/browser/fileapi/mount_points.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_mount_option.h"
+#include "storage/common/fileapi/file_system_types.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace storage {
+
+class FileSystemURL;
+
+// 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 STORAGE_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 except for media galleries, which do not count toward registered
+ // paths for overlap calculation.
+ //
+ // 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 FileSystemMountOption& mount_option,
+ 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,
+ std::string* cracked_id,
+ base::FilePath* path,
+ FileSystemMountOption* mount_option) 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).
+ //
+ // Media gallery type file systems do not count for this calculation. i.e.
+ // if only a media gallery is registered for the path, false will be returned.
+ // If a media gallery and another file system are registered for related
+ // paths, only the other registration is taken into account.
+ //
+ // 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;
+
+ // Revoke all registered filesystems. Used only by testing (for clean-ups).
+ void RevokeAllFileSystems();
+
+ 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 unless it is a
+ // media gallery type.
+ //
+ // |lock_| should be taken before calling this method.
+ bool ValidateNewMountPoint(const std::string& mount_name,
+ FileSystemType type,
+ 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);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_EXTERNAL_MOUNT_POINTS_H_
diff --git a/storage/browser/fileapi/file_observers.h b/storage/browser/fileapi/file_observers.h
new file mode 100644
index 0000000..7a07193
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_FILE_OBSERVERS_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_OBSERVERS_H_
+
+#include "base/basictypes.h"
+#include "storage/browser/storage_browser_export.h"
+
+// TODO(kinuko): Split this file into per-observer multiple files.
+
+namespace storage {
+
+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 STORAGE_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 STORAGE_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 STORAGE_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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_OBSERVERS_H_
diff --git a/storage/browser/fileapi/file_permission_policy.h b/storage/browser/fileapi/file_permission_policy.h
new file mode 100644
index 0000000..8580954
--- /dev/null
+++ b/storage/browser/fileapi/file_permission_policy.h
@@ -0,0 +1,28 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_
+
+#include "storage/browser/storage_browser_export.h"
+
+namespace storage {
+
+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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_PERMISSION_POLICY_H_
diff --git a/storage/browser/fileapi/file_stream_writer.h b/storage/browser/fileapi/file_stream_writer.h
new file mode 100644
index 0000000..ec59ec0
--- /dev/null
+++ b/storage/browser/fileapi/file_stream_writer.h
@@ -0,0 +1,87 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_
+
+#include "base/basictypes.h"
+#include "net/base/completion_callback.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class FilePath;
+class TaskRunner;
+}
+
+namespace net {
+class IOBuffer;
+}
+
+namespace storage {
+
+// A generic interface for writing to a file-like object.
+class FileStreamWriter {
+ public:
+ enum OpenOrCreate { OPEN_EXISTING_FILE, CREATE_NEW_FILE };
+
+ // Creates a writer for the existing file in the path |file_path| starting
+ // from |initial_offset|. Uses |task_runner| for async file operations.
+ STORAGE_EXPORT static FileStreamWriter* CreateForLocalFile(
+ base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ OpenOrCreate open_or_create);
+
+ // 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_STREAM_WRITER_H_
diff --git a/storage/browser/fileapi/file_system_backend.h b/storage/browser/fileapi/file_system_backend.h
new file mode 100644
index 0000000..8f455d9
--- /dev/null
+++ b/storage/browser/fileapi/file_system_backend.h
@@ -0,0 +1,169 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/file_permission_policy.h"
+#include "storage/browser/fileapi/open_file_system_mode.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_types.h"
+
+class GURL;
+
+namespace storage {
+
+class AsyncFileUtil;
+class CopyOrMoveFileValidatorFactory;
+class FileSystemURL;
+class FileStreamReader;
+class FileStreamWriter;
+class FileSystemContext;
+class FileSystemFileUtil;
+class FileSystemOperation;
+class FileSystemQuotaUtil;
+class WatcherManager;
+
+// Callback to take GURL.
+typedef base::Callback<void(const GURL& url)> URLCallback;
+
+// 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 STORAGE_EXPORT FileSystemBackend {
+ public:
+ // Callback for InitializeFileSystem.
+ typedef base::Callback<void(const GURL& root_url,
+ const std::string& name,
+ base::File::Error 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;
+
+ // Resolves the filesystem root URL and the name for the given |url|.
+ // 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 ResolveURL(const FileSystemURL& url,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) = 0;
+
+ // Returns the specialized AsyncFileUtil for this backend.
+ virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) = 0;
+
+ // Returns the specialized WatcherManager for this backend.
+ virtual WatcherManager* GetWatcherManager(FileSystemType type) = 0;
+
+ // Returns the specialized CopyOrMoveFileValidatorFactory for this backend
+ // and |type|. If |error_code| is File::FILE_OK and the result is NULL,
+ // then no validator is required.
+ virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type, base::File::Error* 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::File::Error* error_code) const = 0;
+
+ // Returns true if Blobs accessing |url| should use FileStreamReader.
+ // If false, Blobs are accessed using a snapshot file by calling
+ // AsyncFileUtil::CreateSnapshotFile.
+ virtual bool SupportsStreaming(const FileSystemURL& url) const = 0;
+
+ // Returns true if specified |type| of filesystem can handle Copy()
+ // of the files in the same file system instead of streaming
+ // read/write implementation.
+ virtual bool HasInplaceCopyImplementation(FileSystemType type) 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.
+ // The |length| argument says how many bytes are going to be read using the
+ // instance of the file stream reader. If unknown, then equal to -1.
+ virtual scoped_ptr<storage::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 'storage/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 storage::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;
+ // Gets a redirect URL for contents. e.g. Google Drive URL for hosted
+ // documents. Returns empty URL if the entry does not have the redirect URL.
+ virtual void GetRedirectURLForContents(
+ const storage::FileSystemURL& url,
+ const storage::URLCallback& callback) = 0;
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_BACKEND_H_
diff --git a/storage/browser/fileapi/file_system_context.cc b/storage/browser/fileapi/file_system_context.cc
new file mode 100644
index 0000000..f42e11a
--- /dev/null
+++ b/storage/browser/fileapi/file_system_context.cc
@@ -0,0 +1,644 @@
+// 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 "storage/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 "net/url_request/url_request.h"
+#include "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/copy_or_move_file_validator.h"
+#include "storage/browser/fileapi/external_mount_points.h"
+#include "storage/browser/fileapi/file_permission_policy.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/browser/fileapi/file_system_options.h"
+#include "storage/browser/fileapi/file_system_quota_client.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/browser/fileapi/isolated_file_system_backend.h"
+#include "storage/browser/fileapi/mount_points.h"
+#include "storage/browser/fileapi/quota/quota_reservation.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/browser/quota/special_storage_policy.h"
+#include "storage/common/fileapi/file_system_info.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "url/gurl.h"
+
+using storage::QuotaClient;
+
+namespace storage {
+
+namespace {
+
+QuotaClient* CreateQuotaClient(
+ FileSystemContext* context,
+ bool is_incognito) {
+ return new FileSystemQuotaClient(context, is_incognito);
+}
+
+
+void DidGetMetadataForResolveURL(
+ const base::FilePath& path,
+ const FileSystemContext::ResolveURLCallback& callback,
+ const FileSystemInfo& info,
+ base::File::Error error,
+ const base::File::Info& file_info) {
+ if (error != base::File::FILE_OK) {
+ if (error == base::File::FILE_ERROR_NOT_FOUND) {
+ callback.Run(base::File::FILE_OK, info, path,
+ FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
+ } else {
+ callback.Run(error, FileSystemInfo(), base::FilePath(),
+ FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
+ }
+ return;
+ }
+ callback.Run(error, info, path, file_info.is_directory ?
+ FileSystemContext::RESOLVED_ENTRY_DIRECTORY :
+ FileSystemContext::RESOLVED_ENTRY_FILE);
+}
+
+void RelayResolveURLCallback(
+ scoped_refptr<base::MessageLoopProxy> message_loop,
+ const FileSystemContext::ResolveURLCallback& callback,
+ base::File::Error result,
+ const FileSystemInfo& info,
+ const base::FilePath& file_path,
+ FileSystemContext::ResolvedEntryType type) {
+ message_loop->PostTask(
+ FROM_HERE, base::Bind(callback, result, info, file_path, type));
+}
+
+} // 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:
+ case kFileSystemTypeCloudDevice:
+ case kFileSystemTypeProvided:
+ case kFileSystemTypeDeviceMediaAsFileStorage:
+ return FILE_PERMISSION_USE_FILE_PERMISSION;
+
+ case kFileSystemTypeRestrictedNativeLocal:
+ return FILE_PERMISSION_READ_ONLY |
+ FILE_PERMISSION_USE_FILE_PERMISSION;
+
+ case kFileSystemTypeDeviceMedia:
+ case kFileSystemTypeIphoto:
+ case kFileSystemTypeItunes:
+ case kFileSystemTypeNativeMedia:
+ case kFileSystemTypePicasa:
+ return FILE_PERMISSION_USE_FILE_PERMISSION;
+
+ // Following types are only accessed via IsolatedFileSystem, and
+ // don't have their own permission policies.
+ case kFileSystemTypeDragged:
+ case kFileSystemTypeForTransientFile:
+ case kFileSystemTypePluginPrivate:
+ 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,
+ storage::SpecialStoragePolicy* special_storage_policy,
+ storage::QuotaManagerProxy* quota_manager_proxy,
+ ScopedVector<FileSystemBackend> additional_backends,
+ const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers,
+ 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()),
+ plugin_private_backend_(
+ new PluginPrivateFileSystemBackend(file_task_runner,
+ partition_path,
+ special_storage_policy,
+ options)),
+ additional_backends_(additional_backends.Pass()),
+ auto_mount_handlers_(auto_mount_handlers),
+ external_mount_points_(external_mount_points),
+ partition_path_(partition_path),
+ is_incognito_(options.is_incognito()),
+ operation_runner_(new FileSystemOperationRunner(this)) {
+ RegisterBackend(sandbox_backend_.get());
+ RegisterBackend(isolated_backend_.get());
+ RegisterBackend(plugin_private_backend_.get());
+
+ for (ScopedVector<FileSystemBackend>::const_iterator iter =
+ additional_backends_.begin();
+ iter != additional_backends_.end(); ++iter) {
+ RegisterBackend(*iter);
+ }
+
+ if (quota_manager_proxy) {
+ // Quota client assumes all backends have registered.
+ quota_manager_proxy->RegisterClient(CreateQuotaClient(
+ this, options.is_incognito()));
+ }
+
+ sandbox_backend_->Initialize(this);
+ isolated_backend_->Initialize(this);
+ plugin_private_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::DeleteDataForOriginOnFileTaskRunner(
+ 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()->DeleteOriginDataOnFileTaskRunner(
+ this, quota_manager_proxy(), origin_url, iter->first)
+ != base::File::FILE_OK) {
+ // Continue the loop, but record the failure.
+ success = false;
+ }
+ }
+
+ return success;
+}
+
+scoped_refptr<QuotaReservation>
+FileSystemContext::CreateQuotaReservationOnFileTaskRunner(
+ const GURL& origin_url,
+ FileSystemType type) {
+ DCHECK(default_file_task_runner()->RunsTasksOnCurrentThread());
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend || !backend->GetQuotaUtil())
+ return scoped_refptr<QuotaReservation>();
+ return backend->GetQuotaUtil()->CreateQuotaReservationOnFileTaskRunner(
+ origin_url, type);
+}
+
+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);
+}
+
+CopyOrMoveFileValidatorFactory*
+FileSystemContext::GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type, base::File::Error* error_code) const {
+ DCHECK(error_code);
+ *error_code = base::File::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;
+}
+
+WatcherManager* FileSystemContext::GetWatcherManager(
+ FileSystemType type) const {
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend)
+ return NULL;
+ return backend->GetWatcherManager(type);
+}
+
+bool FileSystemContext::IsSandboxFileSystem(FileSystemType type) const {
+ FileSystemBackendMap::const_iterator found = backend_map_.find(type);
+ return found != backend_map_.end() && found->second->GetQuotaUtil();
+}
+
+const UpdateObserverList* FileSystemContext::GetUpdateObservers(
+ FileSystemType type) const {
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (backend->GetQuotaUtil())
+ return backend->GetQuotaUtil()->GetUpdateObservers(type);
+ return NULL;
+}
+
+const ChangeObserverList* FileSystemContext::GetChangeObservers(
+ FileSystemType type) const {
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (backend->GetQuotaUtil())
+ return backend->GetQuotaUtil()->GetChangeObservers(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(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(!callback.is_null());
+
+ if (!FileSystemContext::IsSandboxFileSystem(type)) {
+ // Disallow opening a non-sandboxed filesystem.
+ callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY);
+ return;
+ }
+
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend) {
+ callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY);
+ return;
+ }
+
+ backend->ResolveURL(
+ CreateCrackedFileSystemURL(origin_url, type, base::FilePath()),
+ mode,
+ callback);
+}
+
+void FileSystemContext::ResolveURL(
+ const FileSystemURL& url,
+ const ResolveURLCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ // If not on IO thread, forward before passing the task to the backend.
+ if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ ResolveURLCallback relay_callback =
+ base::Bind(&RelayResolveURLCallback,
+ base::MessageLoopProxy::current(), callback);
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&FileSystemContext::ResolveURL, this, url, relay_callback));
+ return;
+ }
+
+ FileSystemBackend* backend = GetFileSystemBackend(url.type());
+ if (!backend) {
+ callback.Run(base::File::FILE_ERROR_SECURITY,
+ FileSystemInfo(), base::FilePath(),
+ FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
+ return;
+ }
+
+ backend->ResolveURL(
+ url,
+ OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
+ base::Bind(&FileSystemContext::DidOpenFileSystemForResolveURL,
+ this,
+ url,
+ callback));
+}
+
+void FileSystemContext::AttemptAutoMountForURLRequest(
+ const net::URLRequest* url_request,
+ const std::string& storage_domain,
+ const StatusCallback& callback) {
+ FileSystemURL filesystem_url(url_request->url());
+ if (filesystem_url.type() == kFileSystemTypeExternal) {
+ for (size_t i = 0; i < auto_mount_handlers_.size(); i++) {
+ if (auto_mount_handlers_[i].Run(url_request, filesystem_url,
+ storage_domain, callback)) {
+ return;
+ }
+ }
+ }
+ callback.Run(base::File::FILE_ERROR_NOT_FOUND);
+}
+
+void FileSystemContext::DeleteFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ const StatusCallback& callback) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(origin_url == origin_url.GetOrigin());
+ DCHECK(!callback.is_null());
+
+ FileSystemBackend* backend = GetFileSystemBackend(type);
+ if (!backend) {
+ callback.Run(base::File::FILE_ERROR_SECURITY);
+ return;
+ }
+ if (!backend->GetQuotaUtil()) {
+ callback.Run(base::File::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::DeleteOriginDataOnFileTaskRunner,
+ base::Unretained(backend->GetQuotaUtil()),
+ make_scoped_refptr(this),
+ base::Unretained(quota_manager_proxy()),
+ origin_url,
+ type),
+ callback);
+}
+
+scoped_ptr<storage::FileStreamReader> FileSystemContext::CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time) {
+ if (!url.is_valid())
+ return scoped_ptr<storage::FileStreamReader>();
+ FileSystemBackend* backend = GetFileSystemBackend(url.type());
+ if (!backend)
+ return scoped_ptr<storage::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)
+void FileSystemContext::EnableTemporaryFileSystemInIncognito() {
+ sandbox_backend_->set_enable_temporary_file_system_in_incognito(true);
+}
+#endif
+
+bool FileSystemContext::CanServeURLRequest(const FileSystemURL& url) const {
+ // We never support accessing files in isolated filesystems via an URL.
+ if (url.mount_type() == kFileSystemTypeIsolated)
+ return false;
+#if defined(OS_CHROMEOS)
+ if (url.type() == kFileSystemTypeTemporary &&
+ sandbox_backend_->enable_temporary_file_system_in_incognito()) {
+ return true;
+ }
+#endif
+ return !is_incognito_ || !FileSystemContext::IsSandboxFileSystem(url.type());
+}
+
+bool FileSystemContext::ShouldFlushOnWriteCompletion(
+ FileSystemType type) const {
+ if (IsSandboxFileSystem(type)) {
+ // Disable Flush() for each write operation on SandboxFileSystems since it
+ // hurts the performance, assuming the FileSystems are stored in a local
+ // disk, we don't need to keep calling fsync() for it.
+ // On the other hand, other FileSystems that may stored on a removable media
+ // should be Flush()ed as soon as a write operation is completed, so that
+ // written data is saved over sudden media removal.
+ return false;
+ }
+ return true;
+}
+
+void FileSystemContext::OpenPluginPrivateFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ const std::string& filesystem_id,
+ const std::string& plugin_id,
+ OpenFileSystemMode mode,
+ const StatusCallback& callback) {
+ DCHECK(plugin_private_backend_);
+ plugin_private_backend_->OpenPrivateFileSystem(
+ origin_url, type, filesystem_id, plugin_id, mode, callback);
+}
+
+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::File::Error* error_code) {
+ if (!url.is_valid()) {
+ if (error_code)
+ *error_code = base::File::FILE_ERROR_INVALID_URL;
+ return NULL;
+ }
+
+ FileSystemBackend* backend = GetFileSystemBackend(url.type());
+ if (!backend) {
+ if (error_code)
+ *error_code = base::File::FILE_ERROR_FAILED;
+ return NULL;
+ }
+
+ base::File::Error fs_error = base::File::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);
+ }
+ }
+}
+
+void FileSystemContext::DidOpenFileSystemForResolveURL(
+ const FileSystemURL& url,
+ const FileSystemContext::ResolveURLCallback& callback,
+ const GURL& filesystem_root,
+ const std::string& filesystem_name,
+ base::File::Error error) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+
+ if (error != base::File::FILE_OK) {
+ callback.Run(error, FileSystemInfo(), base::FilePath(),
+ FileSystemContext::RESOLVED_ENTRY_NOT_FOUND);
+ return;
+ }
+
+ storage::FileSystemInfo info(
+ filesystem_name, filesystem_root, url.mount_type());
+
+ // Extract the virtual path not containing a filesystem type part from |url|.
+ base::FilePath parent = CrackURL(filesystem_root).virtual_path();
+ base::FilePath child = url.virtual_path();
+ base::FilePath path;
+
+ if (parent.empty()) {
+ path = child;
+ } else if (parent != child) {
+ bool result = parent.AppendRelativePath(child, &path);
+ DCHECK(result);
+ }
+
+ operation_runner()->GetMetadata(
+ url, base::Bind(&DidGetMetadataForResolveURL, path, callback, info));
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_system_context.h b/storage/browser/fileapi/file_system_context.h
new file mode 100644
index 0000000..09b2409
--- /dev/null
+++ b/storage/browser/fileapi/file_system_context.h
@@ -0,0 +1,428 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/open_file_system_mode.h"
+#include "storage/browser/fileapi/plugin_private_file_system_backend.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "storage/browser/fileapi/task_runner_bound_observer_list.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_types.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+class SingleThreadTaskRunner;
+}
+
+namespace chrome {
+class NativeMediaFileUtilTest;
+}
+
+namespace storage {
+class QuotaManagerProxy;
+class SpecialStoragePolicy;
+}
+
+namespace net {
+class URLRequest;
+}
+
+namespace storage {
+class BlobURLRequestJobTest;
+class FileStreamReader;
+}
+
+namespace storage {
+
+class AsyncFileUtil;
+class CopyOrMoveFileValidatorFactory;
+class ExternalFileSystemBackend;
+class ExternalMountPoints;
+class FileStreamWriter;
+class FileSystemBackend;
+class FileSystemFileUtil;
+class FileSystemOperation;
+class FileSystemOperationRunner;
+class FileSystemOptions;
+class FileSystemQuotaUtil;
+class FileSystemURL;
+class IsolatedFileSystemBackend;
+class MountPoints;
+class QuotaReservation;
+class SandboxFileSystemBackend;
+class WatchManager;
+
+struct DefaultContextDeleter;
+struct FileSystemInfo;
+
+// An auto mount handler will attempt to mount the file system requested in
+// |url_request|. If the URL is for this auto mount handler, it returns true
+// and calls |callback| when the attempt is complete. If the auto mounter
+// does not recognize the URL, it returns false and does not call |callback|.
+// Called on the IO thread.
+typedef base::Callback<bool(
+ const net::URLRequest* url_request,
+ const FileSystemURL& filesystem_url,
+ const std::string& storage_domain,
+ const base::Callback<void(base::File::Error result)>& callback)>
+ URLRequestAutoMountHandler;
+
+// This class keeps and provides a file system context for FileSystem API.
+// An instance of this class is created and owned by profile.
+class STORAGE_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.
+ //
+ // |auto_mount_handlers| are used to resolve calls to
+ // AttemptAutoMountForURLRequest. Only external filesystems are auto mounted
+ // when a filesystem: URL request is made.
+ FileSystemContext(
+ base::SingleThreadTaskRunner* io_task_runner,
+ base::SequencedTaskRunner* file_task_runner,
+ ExternalMountPoints* external_mount_points,
+ storage::SpecialStoragePolicy* special_storage_policy,
+ storage::QuotaManagerProxy* quota_manager_proxy,
+ ScopedVector<FileSystemBackend> additional_backends,
+ const std::vector<URLRequestAutoMountHandler>& auto_mount_handlers,
+ const base::FilePath& partition_path,
+ const FileSystemOptions& options);
+
+ bool DeleteDataForOriginOnFileTaskRunner(const GURL& origin_url);
+
+ // Creates a new QuotaReservation for the given |origin_url| and |type|.
+ // Returns NULL if |type| does not support quota or reservation fails.
+ // This should be run on |default_file_task_runner_| and the returned value
+ // should be destroyed on the runner.
+ scoped_refptr<QuotaReservation> CreateQuotaReservationOnFileTaskRunner(
+ const GURL& origin_url,
+ FileSystemType type);
+
+ storage::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 CopyOrMoveFileValidatorFactory for the given
+ // |type|. If |error_code| is File::FILE_OK and the result is NULL,
+ // then no validator is required.
+ CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type, base::File::Error* 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 the watcher manager for the given |type|.
+ // This may return NULL if the type does not support watching.
+ WatcherManager* GetWatcherManager(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 ChangeObserverList* GetChangeObservers(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(const GURL& root,
+ const std::string& name,
+ base::File::Error result)>
+ OpenFileSystemCallback;
+
+ // Used for ResolveURL.
+ enum ResolvedEntryType {
+ RESOLVED_ENTRY_FILE,
+ RESOLVED_ENTRY_DIRECTORY,
+ RESOLVED_ENTRY_NOT_FOUND,
+ };
+ typedef base::Callback<void(base::File::Error result,
+ const FileSystemInfo& info,
+ const base::FilePath& file_path,
+ ResolvedEntryType type)> ResolveURLCallback;
+
+ // Used for DeleteFileSystem and OpenPluginPrivateFileSystem.
+ typedef base::Callback<void(base::File::Error result)> StatusCallback;
+
+ // 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);
+
+ // Opens the filesystem for the given |url| as read-only, if the filesystem
+ // backend referred by the URL allows opening by resolveURL. Otherwise it
+ // fails with FILE_ERROR_SECURITY. The entry pointed by the URL can be
+ // absent; in that case RESOLVED_ENTRY_NOT_FOUND type is returned to the
+ // callback for indicating the absence. Can be called from any thread with
+ // a message loop. |callback| is invoked on the caller thread.
+ void ResolveURL(
+ const FileSystemURL& url,
+ const ResolveURLCallback& callback);
+
+ // Attempts to mount the filesystem needed to satisfy |url_request| made
+ // from |storage_domain|. If an appropriate file system is not found,
+ // callback will return an error.
+ void AttemptAutoMountForURLRequest(const net::URLRequest* url_request,
+ const std::string& storage_domain,
+ const StatusCallback& 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 StatusCallback& 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<storage::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)
+ // Used only on ChromeOS for now.
+ void EnableTemporaryFileSystemInIncognito();
+#endif
+
+ SandboxFileSystemBackendDelegate* sandbox_delegate() {
+ return sandbox_delegate_.get();
+ }
+
+ // Returns true if the requested url is ok to be served.
+ // (E.g. this returns false if the context is created for incognito mode)
+ bool CanServeURLRequest(const FileSystemURL& url) const;
+
+ // Returns true if a file in the file system should be flushed for each write
+ // completion.
+ bool ShouldFlushOnWriteCompletion(FileSystemType type) const;
+
+ // This must be used to open 'plugin private' filesystem.
+ // See "plugin_private_file_system_backend.h" for more details.
+ void OpenPluginPrivateFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ const std::string& filesystem_id,
+ const std::string& plugin_id,
+ OpenFileSystemMode mode,
+ const StatusCallback& callback);
+
+ private:
+ typedef std::map<FileSystemType, FileSystemBackend*>
+ FileSystemBackendMap;
+
+ // For CreateFileSystemOperation.
+ friend class FileSystemOperationRunner;
+
+ // For sandbox_backend().
+ friend class content::SandboxFileSystemTestHelper;
+
+ // For plugin_private_backend().
+ friend class content::PluginPrivateFileSystemBackendTest;
+
+ // 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::File::Error* 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);
+
+ void DidOpenFileSystemForResolveURL(
+ const FileSystemURL& url,
+ const ResolveURLCallback& callback,
+ const GURL& filesystem_root,
+ const std::string& filesystem_name,
+ base::File::Error error);
+
+ // Returns a FileSystemBackend, used only by test code.
+ SandboxFileSystemBackend* sandbox_backend() const {
+ return sandbox_backend_.get();
+ }
+
+ // Used only by test code.
+ PluginPrivateFileSystemBackend* plugin_private_backend() const {
+ return plugin_private_backend_.get();
+ }
+
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+ scoped_refptr<base::SequencedTaskRunner> default_file_task_runner_;
+
+ scoped_refptr<storage::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.
+ scoped_ptr<PluginPrivateFileSystemBackend> plugin_private_backend_;
+ ScopedVector<FileSystemBackend> additional_backends_;
+
+ std::vector<URLRequestAutoMountHandler> auto_mount_handlers_;
+
+ // 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_;
+
+ bool is_incognito_;
+
+ scoped_ptr<FileSystemOperationRunner> operation_runner_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(FileSystemContext);
+};
+
+struct DefaultContextDeleter {
+ static void Destruct(const FileSystemContext* context) {
+ context->DeleteOnCorrectThread();
+ }
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_CONTEXT_H_
diff --git a/storage/browser/fileapi/file_system_dir_url_request_job.cc b/storage/browser/fileapi/file_system_dir_url_request_job.cc
new file mode 100644
index 0000000..cce9c56
--- /dev/null
+++ b/storage/browser/fileapi/file_system_dir_url_request_job.cc
@@ -0,0 +1,160 @@
+// 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 "storage/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/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 "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/common/fileapi/directory_entry.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "url/gurl.h"
+
+using net::NetworkDelegate;
+using net::URLRequest;
+using net::URLRequestJob;
+using net::URLRequestStatus;
+
+namespace storage {
+
+FileSystemDirURLRequestJob::FileSystemDirURLRequestJob(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& storage_domain,
+ FileSystemContext* file_system_context)
+ : URLRequestJob(request, network_delegate),
+ storage_domain_(storage_domain),
+ 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());
+ if (!url_.is_valid()) {
+ file_system_context_->AttemptAutoMountForURLRequest(
+ request_,
+ storage_domain_,
+ base::Bind(&FileSystemDirURLRequestJob::DidAttemptAutoMount,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+ if (!file_system_context_->CanServeURLRequest(url_)) {
+ // In incognito mode the API is not usable and there should be no data.
+ if (url_.is_valid() && VirtualPath::IsRootPath(url_.virtual_path())) {
+ // Return an empty directory if the filesystem root is queried.
+ DidReadDirectory(base::File::FILE_OK,
+ std::vector<DirectoryEntry>(),
+ false);
+ return;
+ }
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ net::ERR_FILE_NOT_FOUND));
+ return;
+ }
+ file_system_context_->operation_runner()->ReadDirectory(
+ url_,
+ base::Bind(&FileSystemDirURLRequestJob::DidReadDirectory, this));
+}
+
+void FileSystemDirURLRequestJob::DidAttemptAutoMount(base::File::Error result) {
+ if (result >= 0 &&
+ file_system_context_->CrackURL(request_->url()).is_valid()) {
+ StartAsync();
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ net::ERR_FILE_NOT_FOUND));
+ }
+}
+
+void FileSystemDirURLRequestJob::DidReadDirectory(
+ base::File::Error result,
+ const std::vector<DirectoryEntry>& entries,
+ bool has_more) {
+ if (result != base::File::FILE_OK) {
+ int rv = net::ERR_FILE_NOT_FOUND;
+ if (result == base::File::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) {
+ set_expected_content_size(data_.size());
+ NotifyHeadersComplete();
+ }
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_system_dir_url_request_job.h b/storage/browser/fileapi/file_system_dir_url_request_job.h
new file mode 100644
index 0000000..8c71cdf
--- /dev/null
+++ b/storage/browser/fileapi/file_system_dir_url_request_job.h
@@ -0,0 +1,69 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "net/url_request/url_request_job.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace storage {
+
+class FileSystemContext;
+struct DirectoryEntry;
+
+// A request job that handles reading filesystem: URLs for directories.
+class STORAGE_EXPORT_PRIVATE FileSystemDirURLRequestJob
+ : public net::URLRequestJob {
+ public:
+ FileSystemDirURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& storage_domain,
+ 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 DidAttemptAutoMount(base::File::Error result);
+ void DidReadDirectory(base::File::Error result,
+ const std::vector<DirectoryEntry>& entries,
+ bool has_more);
+
+ std::string data_;
+ FileSystemURL url_;
+ const std::string storage_domain_;
+ FileSystemContext* file_system_context_;
+ base::WeakPtrFactory<FileSystemDirURLRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemDirURLRequestJob);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_DIR_URL_REQUEST_JOB_H_
diff --git a/storage/browser/fileapi/file_system_file_stream_reader.cc b/storage/browser/fileapi/file_system_file_stream_reader.cc
new file mode 100644
index 0000000..60a806a
--- /dev/null
+++ b/storage/browser/fileapi/file_system_file_stream_reader.cc
@@ -0,0 +1,140 @@
+// 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 "storage/browser/fileapi/file_system_file_stream_reader.h"
+
+#include "base/files/file_util_proxy.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 "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+
+using storage::FileStreamReader;
+
+// TODO(kinuko): Remove this temporary namespace hack after we move both
+// blob and fileapi into content namespace.
+namespace storage {
+
+FileStreamReader* FileStreamReader::CreateForFileSystemFile(
+ storage::FileSystemContext* file_system_context,
+ const storage::FileSystemURL& url,
+ int64 initial_offset,
+ const base::Time& expected_modification_time) {
+ return new storage::FileSystemFileStreamReader(
+ file_system_context, url, initial_offset, expected_modification_time);
+}
+
+} // namespace storage
+
+namespace storage {
+
+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() {
+}
+
+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));
+}
+
+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) {
+}
+
+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::File::Error file_error,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref) {
+ DCHECK(has_pending_create_snapshot_);
+ DCHECK(!local_file_reader_.get());
+ has_pending_create_snapshot_ = false;
+
+ if (file_error != base::File::FILE_OK) {
+ error_callback.Run(net::FileErrorToNetError(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(
+ FileStreamReader::CreateForLocalFile(
+ file_system_context_->default_file_task_runner(),
+ platform_path, initial_offset_, expected_modification_time_));
+
+ callback.Run();
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_system_file_stream_reader.h b/storage/browser/fileapi/file_system_file_stream_reader.h
new file mode 100644
index 0000000..3fe075b6
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_
+
+#include "base/bind.h"
+#include "base/files/file.h"
+#include "base/memory/ref_counted.h"
+#include "base/time/time.h"
+#include "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/blob/shareable_file_reference.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+}
+
+namespace content {
+class FileSystemFileStreamReaderTest;
+}
+
+namespace storage {
+
+class FileSystemContext;
+
+// Generic FileStreamReader implementation for FileSystem files.
+// Note: 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 STORAGE_EXPORT_PRIVATE FileSystemFileStreamReader
+ : public NON_EXPORTED_BASE(storage::FileStreamReader) {
+ public:
+ 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:
+ friend class storage::FileStreamReader;
+ friend class content::FileSystemFileStreamReaderTest;
+
+ FileSystemFileStreamReader(FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ int64 initial_offset,
+ const base::Time& expected_modification_time);
+
+ int CreateSnapshot(const base::Closure& callback,
+ const net::CompletionCallback& error_callback);
+ void DidCreateSnapshot(
+ const base::Closure& callback,
+ const net::CompletionCallback& error_callback,
+ base::File::Error file_error,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref);
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+ FileSystemURL url_;
+ const int64 initial_offset_;
+ const base::Time expected_modification_time_;
+ scoped_ptr<storage::FileStreamReader> local_file_reader_;
+ scoped_refptr<storage::ShareableFileReference> snapshot_ref_;
+ bool has_pending_create_snapshot_;
+ base::WeakPtrFactory<FileSystemFileStreamReader> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemFileStreamReader);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_STREAM_READER_H_
diff --git a/storage/browser/fileapi/file_system_file_util.cc b/storage/browser/fileapi/file_system_file_util.cc
new file mode 100644
index 0000000..f356109
--- /dev/null
+++ b/storage/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 "storage/browser/fileapi/file_system_file_util.h"
+
+namespace storage {
+
+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 storage
diff --git a/storage/browser/fileapi/file_system_file_util.h b/storage/browser/fileapi/file_system_file_util.h
new file mode 100644
index 0000000..868d091
--- /dev/null
+++ b/storage/browser/fileapi/file_system_file_util.h
@@ -0,0 +1,186 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/blob/scoped_file.h"
+
+namespace base {
+class Time;
+}
+
+namespace storage {
+
+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 STORAGE_EXPORT FileSystemFileUtil {
+ public:
+ typedef FileSystemOperation::CopyOrMoveOption CopyOrMoveOption;
+
+ // It will be implemented by each subclass such as FileSystemFileEnumerator.
+ class STORAGE_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 STORAGE_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::File CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int file_flags) = 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::File::Error 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::File::Error 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::File::Error GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Info* 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::File::Error 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::File::Error 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::File::Error 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.
+ // For |option|, please see file_system_operation.h
+ //
+ // This returns:
+ // - File::FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ virtual base::File::Error CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ bool copy) = 0;
+
+ // Copies in a single file from a different filesystem.
+ // See header comments for AsyncFileUtil::CopyInForeignFile() for
+ // more details.
+ virtual base::File::Error 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::File::Error DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) = 0;
+
+ // Deletes a single empty directory.
+ // See header comments for AsyncFileUtil::DeleteDirectory() for more details.
+ virtual base::File::Error 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 storage::ScopedFile CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Error* error,
+ base::File::Info* file_info,
+ base::FilePath* platform_path) = 0;
+
+ protected:
+ FileSystemFileUtil() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileSystemFileUtil);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_FILE_UTIL_H_
diff --git a/storage/browser/fileapi/file_system_operation.h b/storage/browser/fileapi/file_system_operation.h
new file mode 100644
index 0000000..4b38da0
--- /dev/null
+++ b/storage/browser/fileapi/file_system_operation.h
@@ -0,0 +1,485 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/process/process.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/directory_entry.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+class URLRequest;
+}
+
+namespace storage {
+class ShareableFileReference;
+}
+
+class GURL;
+
+namespace storage {
+
+class FileSystemContext;
+class FileSystemURL;
+class FileWriterDelegate;
+
+// 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:
+ STORAGE_EXPORT static FileSystemOperation* Create(
+ const FileSystemURL& url,
+ FileSystemContext* file_system_context,
+ scoped_ptr<FileSystemOperationContext> operation_context);
+
+ virtual ~FileSystemOperation() {}
+
+ // Used for CreateFile(), etc. |result| is the return code of the operation.
+ typedef base::Callback<void(base::File::Error 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::File::Error result,
+ const base::File::Info& file_info)> GetMetadataCallback;
+
+ // Used for OpenFile(). |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::File file,
+ const base::Closure& on_close_callback)> 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::File::Error 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::File::Error result,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref)>
+ SnapshotFileCallback;
+
+ // Used for progress update callback for Copy().
+ //
+ // BEGIN_COPY_ENTRY is fired for each copy creation beginning (for both
+ // file and directory).
+ // The |source_url| is the URL of the source entry. |size| should not be
+ // used.
+ //
+ // END_COPY_ENTRY is fired for each copy creation finishing (for both
+ // file and directory).
+ // The |source_url| is the URL of the source entry. The |destination_url| is
+ // the URL of the destination entry. |size| should not be used.
+ //
+ // PROGRESS is fired periodically during file copying (not fired for
+ // directory copy).
+ // The |source_url| is the URL of the source file. |size| is the number
+ // of cumulative copied bytes for the currently copied file.
+ // Both at beginning and ending of file copying, PROGRESS event should be
+ // called. At beginning, |size| should be 0. At ending, |size| should be
+ // the size of the file.
+ //
+ // Here is an example callback sequence of recursive copy. Suppose
+ // there are a/b/c.txt (100 bytes) and a/b/d.txt (200 bytes), and trying to
+ // copy a to x recursively, then the progress update sequence will be:
+ //
+ // BEGIN_COPY_ENTRY a (starting create "a" directory in x/).
+ // END_COPY_ENTRY a x/a (creating "a" directory in x/ is finished).
+ //
+ // BEGIN_COPY_ENTRY a/b (starting create "b" directory in x/a).
+ // END_COPY_ENTRY a/b x/a/b (creating "b" directory in x/a/ is finished).
+ //
+ // BEGIN_COPY_ENTRY a/b/c.txt (starting to copy "c.txt" in x/a/b/).
+ // PROGRESS a/b/c.txt 0 (The first PROGRESS's |size| should be 0).
+ // PROGRESS a/b/c.txt 10
+ // :
+ // PROGRESS a/b/c.txt 90
+ // PROGRESS a/b/c.txt 100 (The last PROGRESS's |size| should be the size of
+ // the file).
+ // END_COPY_ENTRY a/b/c.txt x/a/b/c.txt (copying "c.txt" is finished).
+ //
+ // BEGIN_COPY_ENTRY a/b/d.txt (starting to copy "d.txt" in x/a/b).
+ // PROGRESS a/b/d.txt 0 (The first PROGRESS's |size| should be 0).
+ // PROGRESS a/b/d.txt 10
+ // :
+ // PROGRESS a/b/d.txt 190
+ // PROGRESS a/b/d.txt 200 (The last PROGRESS's |size| should be the size of
+ // the file).
+ // END_COPY_ENTRY a/b/d.txt x/a/b/d.txt (copy "d.txt" is finished).
+ //
+ // Note that event sequence of a/b/c.txt and a/b/d.txt can be interlaced,
+ // because they can be done in parallel. Also PROGRESS events are optional,
+ // so they may not be appeared.
+ // All the progress callback invocation should be done before StatusCallback
+ // given to the Copy is called. Especially if an error is found before first
+ // progres callback invocation, the progress callback may NOT invoked for the
+ // copy.
+ //
+ // Note for future extension. Currently this callback is only supported on
+ // Copy(). We can extend this to Move(), because Move() is sometimes
+ // implemented as "copy then delete."
+ // In more precise, Move() usually can be implemented either 1) by updating
+ // the metadata of resource (e.g. root of moving directory tree), or 2) by
+ // copying directory tree and them removing the source tree.
+ // For 1)'s case, we can simply add BEGIN_MOVE_ENTRY and END_MOVE_ENTRY
+ // for root directory.
+ // For 2)'s case, we can add BEGIN_DELETE_ENTRY and END_DELETE_ENTRY for each
+ // entry.
+ // For both cases, we probably won't need to use PROGRESS event because
+ // these operations should be done quickly (at least much faster than copying
+ // usually).
+ enum CopyProgressType {
+ BEGIN_COPY_ENTRY,
+ END_COPY_ENTRY,
+ PROGRESS,
+ };
+ typedef base::Callback<void(CopyProgressType type,
+ const FileSystemURL& source_url,
+ const FileSystemURL& destination_url,
+ int64 size)>
+ CopyProgressCallback;
+
+ // Used for CopyFileLocal() to report progress update.
+ // |size| is the cumulative copied bytes for the copy.
+ // At the beginning the progress callback should be called with |size| = 0,
+ // and also at the ending the progress callback should be called with |size|
+ // set to the copied file size.
+ typedef base::Callback<void(int64 size)> CopyFileProgressCallback;
+
+ // The option for copy or move operation.
+ enum CopyOrMoveOption {
+ // No additional operation.
+ OPTION_NONE,
+
+ // Preserves last modified time if possible. If the operation to update
+ // last modified time is not supported on the file system for the
+ // destination file, this option would be simply ignored (i.e. Copy would
+ // be successfully done without preserving last modified time).
+ OPTION_PRESERVE_LAST_MODIFIED,
+ };
+
+ // Used for Write().
+ typedef base::Callback<void(base::File::Error 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.
+ // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's
+ // comment for details.
+ // |progress_callback| is periodically called to report the progress
+ // update. See also the comment of CopyProgressCallback. This callback is
+ // optional.
+ //
+ // For recursive case this internally creates new FileSystemOperations and
+ // calls:
+ // - ReadDirectory, CopyFileLocal and CreateDirectory
+ // for same-filesystem case, or
+ // - ReadDirectory and CreateSnapshotFile on source filesystem and
+ // CopyInForeignFile and CreateDirectory on dest filesystem
+ // for cross-filesystem case.
+ //
+ virtual void Copy(const FileSystemURL& src_path,
+ const FileSystemURL& dest_path,
+ CopyOrMoveOption option,
+ const CopyProgressCallback& progress_callback,
+ 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.
+ // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's
+ // comment for details.
+ //
+ // For recursive case this internally creates new FileSystemOperations and
+ // calls:
+ // - ReadDirectory, MoveFileLocal, CreateDirectory and Remove
+ // for same-filesystem case, or
+ // - ReadDirectory, CreateSnapshotFile and Remove on source filesystem and
+ // CopyInForeignFile and CreateDirectory on dest filesystem
+ // for cross-filesystem case.
+ //
+ virtual void Move(const FileSystemURL& src_path,
+ const FileSystemURL& dest_path,
+ CopyOrMoveOption option,
+ 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::File::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::File::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::File::Flags.
+ //
+ // This function is used only by Pepper as of writing.
+ virtual void OpenFile(const FileSystemURL& path,
+ int file_flags,
+ const OpenFileCallback& callback) = 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;
+
+ // Copies in a single file from a different filesystem.
+ //
+ // This returns:
+ // - File::FILE_ERROR_NOT_FOUND if |src_file_path|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::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) = 0;
+
+ // Removes a single file.
+ //
+ // This returns:
+ // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - File::FILE_ERROR_NOT_A_FILE if |url| is not a file.
+ //
+ virtual void RemoveFile(const FileSystemURL& url,
+ const StatusCallback& callback) = 0;
+
+ // Removes a single empty directory.
+ //
+ // This returns:
+ // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - File::FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory.
+ // - File::FILE_ERROR_NOT_EMPTY if |url| is not empty.
+ //
+ virtual void RemoveDirectory(const FileSystemURL& url,
+ 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).
+ // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's
+ // comment for details.
+ // |progress_callback| is periodically called to report the progress
+ // update. See also the comment of CopyFileProgressCallback. This callback is
+ // optional.
+ //
+ // This returns:
+ // - File::FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ virtual void CopyFileLocal(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ 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).
+ // |option| specifies the minor behavior of Copy(). See CopyOrMoveOption's
+ // comment for details.
+ //
+ // This returns:
+ // - File::FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::FILE_ERROR_FAILED if |dest_url| does not exist and
+ // its parent path is a file.
+ //
+ virtual void MoveFileLocal(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const StatusCallback& callback) = 0;
+
+ // Synchronously gets the platform path for the given |url|.
+ // This may fail if the given |url|'s filesystem type is neither
+ // temporary nor persistent.
+ // In such a case, base::File::FILE_ERROR_INVALID_OPERATION will be
+ // returned.
+ virtual base::File::Error SyncGetPlatformPath(
+ const FileSystemURL& url,
+ base::FilePath* platform_path) = 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_H_
diff --git a/storage/browser/fileapi/file_system_operation_context.cc b/storage/browser/fileapi/file_system_operation_context.cc
new file mode 100644
index 0000000..b392d7b
--- /dev/null
+++ b/storage/browser/fileapi/file_system_operation_context.cc
@@ -0,0 +1,34 @@
+// 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 "storage/browser/fileapi/file_system_operation_context.h"
+
+#include "base/sequenced_task_runner.h"
+#include "storage/browser/fileapi/file_system_context.h"
+
+namespace storage {
+
+FileSystemOperationContext::FileSystemOperationContext(
+ FileSystemContext* context)
+ : file_system_context_(context),
+ task_runner_(file_system_context_->default_file_task_runner()),
+ allowed_bytes_growth_(0),
+ quota_limit_type_(storage::kQuotaLimitTypeUnknown) {
+}
+
+FileSystemOperationContext::FileSystemOperationContext(
+ FileSystemContext* context,
+ base::SequencedTaskRunner* task_runner)
+ : file_system_context_(context),
+ task_runner_(task_runner),
+ allowed_bytes_growth_(0),
+ quota_limit_type_(storage::kQuotaLimitTypeUnknown) {
+}
+
+FileSystemOperationContext::~FileSystemOperationContext() {
+ DetachUserDataThread();
+ setter_thread_checker_.DetachFromThread();
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_system_operation_context.h b/storage/browser/fileapi/file_system_operation_context.h
new file mode 100644
index 0000000..ceecec1
--- /dev/null
+++ b/storage/browser/fileapi/file_system_operation_context.h
@@ -0,0 +1,97 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_
+#define STORAGE_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 "storage/browser/fileapi/task_runner_bound_observer_list.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/quota/quota_types.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace storage {
+
+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 STORAGE_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_; }
+ storage::QuotaLimitType quota_limit_type() const { return quota_limit_type_; }
+ base::SequencedTaskRunner* task_runner() const { return task_runner_.get(); }
+
+ 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(storage::QuotaLimitType limit_type) {
+ DCHECK(setter_thread_checker_.CalledOnValidThread());
+ quota_limit_type_ = limit_type;
+ }
+
+ 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.
+ storage::QuotaLimitType quota_limit_type_;
+
+ // Observers attached to this context.
+ ChangeObserverList change_observers_;
+ UpdateObserverList update_observers_;
+
+ // Used to check its setters are not called on arbitrary thread.
+ base::ThreadChecker setter_thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationContext);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_CONTEXT_H_
diff --git a/storage/browser/fileapi/file_system_operation_impl.cc b/storage/browser/fileapi/file_system_operation_impl.cc
new file mode 100644
index 0000000..75985f4
--- /dev/null
+++ b/storage/browser/fileapi/file_system_operation_impl.cc
@@ -0,0 +1,559 @@
+// 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 "storage/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 "storage/browser/fileapi/async_file_util.h"
+#include "storage/browser/fileapi/copy_or_move_operation_delegate.h"
+#include "storage/browser/fileapi/file_observers.h"
+#include "storage/browser/fileapi/file_system_backend.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/file_writer_delegate.h"
+#include "storage/browser/fileapi/remove_operation_delegate.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/common/blob/shareable_file_reference.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "storage/common/quota/quota_types.h"
+
+using storage::ScopedFile;
+
+namespace storage {
+
+FileSystemOperation* FileSystemOperation::Create(
+ const FileSystemURL& url,
+ FileSystemContext* file_system_context,
+ scoped_ptr<FileSystemOperationContext> operation_context) {
+ return new FileSystemOperationImpl(url, file_system_context,
+ operation_context.Pass());
+}
+
+FileSystemOperationImpl::~FileSystemOperationImpl() {
+}
+
+void FileSystemOperationImpl::CreateFile(const FileSystemURL& url,
+ bool exclusive,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationCreateFile));
+ GetUsageAndQuotaThenRunTask(
+ url,
+ base::Bind(&FileSystemOperationImpl::DoCreateFile,
+ weak_factory_.GetWeakPtr(), url, callback, exclusive),
+ base::Bind(callback, base::File::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,
+ weak_factory_.GetWeakPtr(), url, callback,
+ exclusive, recursive),
+ base::Bind(callback, base::File::FILE_ERROR_FAILED));
+}
+
+void FileSystemOperationImpl::Copy(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const CopyProgressCallback& progress_callback,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationCopy));
+ DCHECK(!recursive_operation_delegate_);
+
+ // TODO(hidehiko): Support |progress_callback|. (crbug.com/278038).
+ recursive_operation_delegate_.reset(
+ new CopyOrMoveOperationDelegate(
+ file_system_context(),
+ src_url, dest_url,
+ CopyOrMoveOperationDelegate::OPERATION_COPY,
+ option,
+ progress_callback,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ weak_factory_.GetWeakPtr(), callback)));
+ recursive_operation_delegate_->RunRecursively();
+}
+
+void FileSystemOperationImpl::Move(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ 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,
+ option,
+ FileSystemOperation::CopyProgressCallback(),
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ weak_factory_.GetWeakPtr(), 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,
+ weak_factory_.GetWeakPtr(), 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,
+ weak_factory_.GetWeakPtr(), 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,
+ weak_factory_.GetWeakPtr(), url, callback));
+ return;
+ }
+
+ recursive_operation_delegate_.reset(
+ new RemoveOperationDelegate(
+ file_system_context(), url,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ weak_factory_.GetWeakPtr(), 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,
+ weak_factory_.GetWeakPtr(), url, callback));
+}
+
+void FileSystemOperationImpl::Truncate(const FileSystemURL& url, int64 length,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationTruncate));
+ GetUsageAndQuotaThenRunTask(
+ url,
+ base::Bind(&FileSystemOperationImpl::DoTruncate,
+ weak_factory_.GetWeakPtr(), url, callback, length),
+ base::Bind(callback, base::File::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,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::OpenFile(const FileSystemURL& url,
+ int file_flags,
+ const OpenFileCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationOpenFile));
+
+ if (file_flags &
+ (base::File::FLAG_TEMPORARY | base::File::FLAG_HIDDEN)) {
+ callback.Run(base::File(base::File::FILE_ERROR_FAILED),
+ base::Closure());
+ return;
+ }
+ GetUsageAndQuotaThenRunTask(
+ url,
+ base::Bind(&FileSystemOperationImpl::DoOpenFile,
+ weak_factory_.GetWeakPtr(),
+ url, callback, file_flags),
+ base::Bind(callback, Passed(base::File(base::File::FILE_ERROR_FAILED)),
+ base::Closure()));
+}
+
+// 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 if (recursive_operation_delegate_) {
+ // This will call DidFinishOperation() with ABORT status code.
+ recursive_operation_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_);
+ }
+}
+
+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,
+ weak_factory_.GetWeakPtr(), src_local_disk_file_path, dest_url,
+ callback),
+ base::Bind(callback, base::File::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,
+ weak_factory_.GetWeakPtr(), 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,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::CopyFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationCopy));
+ DCHECK(src_url.IsInSameFileSystem(dest_url));
+
+ GetUsageAndQuotaThenRunTask(
+ dest_url,
+ base::Bind(&FileSystemOperationImpl::DoCopyFileLocal,
+ weak_factory_.GetWeakPtr(), src_url, dest_url, option,
+ progress_callback, callback),
+ base::Bind(callback, base::File::FILE_ERROR_FAILED));
+}
+
+void FileSystemOperationImpl::MoveFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const StatusCallback& callback) {
+ DCHECK(SetPendingOperationType(kOperationMove));
+ DCHECK(src_url.IsInSameFileSystem(dest_url));
+ GetUsageAndQuotaThenRunTask(
+ dest_url,
+ base::Bind(&FileSystemOperationImpl::DoMoveFileLocal,
+ weak_factory_.GetWeakPtr(),
+ src_url, dest_url, option, callback),
+ base::Bind(callback, base::File::FILE_ERROR_FAILED));
+}
+
+base::File::Error FileSystemOperationImpl::SyncGetPlatformPath(
+ const FileSystemURL& url,
+ base::FilePath* platform_path) {
+ DCHECK(SetPendingOperationType(kOperationGetLocalPath));
+ if (!file_system_context()->IsSandboxFileSystem(url.type()))
+ return base::File::FILE_ERROR_INVALID_OPERATION;
+ FileSystemFileUtil* file_util =
+ file_system_context()->sandbox_delegate()->sync_file_util();
+ file_util->GetLocalFilePath(operation_context_.get(), url, platform_path);
+ return base::File::FILE_OK;
+}
+
+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),
+ pending_operation_(kOperationNone),
+ weak_factory_(this) {
+ DCHECK(operation_context_.get());
+ operation_context_->DetachUserDataThread();
+ async_file_util_ = file_system_context_->GetAsyncFileUtil(url.type());
+ DCHECK(async_file_util_);
+}
+
+void FileSystemOperationImpl::GetUsageAndQuotaThenRunTask(
+ const FileSystemURL& url,
+ const base::Closure& task,
+ const base::Closure& error_callback) {
+ storage::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,
+ weak_factory_.GetWeakPtr(), task, error_callback));
+}
+
+void FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask(
+ const base::Closure& task,
+ const base::Closure& error_callback,
+ storage::QuotaStatusCode status,
+ int64 usage,
+ int64 quota) {
+ if (status != storage::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,
+ weak_factory_.GetWeakPtr(), 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,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DoCopyFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ const StatusCallback& callback) {
+ async_file_util_->CopyFileLocal(
+ operation_context_.Pass(), src_url, dest_url, option, progress_callback,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DoMoveFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const StatusCallback& callback) {
+ async_file_util_->MoveFileLocal(
+ operation_context_.Pass(), src_url, dest_url, option,
+ base::Bind(&FileSystemOperationImpl::DidFinishOperation,
+ weak_factory_.GetWeakPtr(), 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,
+ weak_factory_.GetWeakPtr(), 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,
+ weak_factory_.GetWeakPtr(), 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,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void FileSystemOperationImpl::DidEnsureFileExistsExclusive(
+ const StatusCallback& callback,
+ base::File::Error rv, bool created) {
+ if (rv == base::File::FILE_OK && !created) {
+ callback.Run(base::File::FILE_ERROR_EXISTS);
+ } else {
+ DidFinishOperation(callback, rv);
+ }
+}
+
+void FileSystemOperationImpl::DidEnsureFileExistsNonExclusive(
+ const StatusCallback& callback,
+ base::File::Error rv, bool /* created */) {
+ DidFinishOperation(callback, rv);
+}
+
+void FileSystemOperationImpl::DidFinishOperation(
+ const StatusCallback& callback,
+ base::File::Error rv) {
+ if (!cancel_callback_.is_null()) {
+ StatusCallback cancel_callback = cancel_callback_;
+ callback.Run(rv);
+
+ // Return OK only if we succeeded to stop the operation.
+ cancel_callback.Run(rv == base::File::FILE_ERROR_ABORT ?
+ base::File::FILE_OK :
+ base::File::FILE_ERROR_INVALID_OPERATION);
+ } else {
+ callback.Run(rv);
+ }
+}
+
+void FileSystemOperationImpl::DidDirectoryExists(
+ const StatusCallback& callback,
+ base::File::Error rv,
+ const base::File::Info& file_info) {
+ if (rv == base::File::FILE_OK && !file_info.is_directory)
+ rv = base::File::FILE_ERROR_NOT_A_DIRECTORY;
+ callback.Run(rv);
+}
+
+void FileSystemOperationImpl::DidFileExists(
+ const StatusCallback& callback,
+ base::File::Error rv,
+ const base::File::Info& file_info) {
+ if (rv == base::File::FILE_OK && file_info.is_directory)
+ rv = base::File::FILE_ERROR_NOT_A_FILE;
+ callback.Run(rv);
+}
+
+void FileSystemOperationImpl::DidDeleteRecursively(
+ const FileSystemURL& url,
+ const StatusCallback& callback,
+ base::File::Error rv) {
+ if (rv == base::File::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,
+ weak_factory_.GetWeakPtr(), callback)));
+ recursive_operation_delegate_->RunRecursively();
+ return;
+ }
+
+ callback.Run(rv);
+}
+
+void FileSystemOperationImpl::DidWrite(
+ const FileSystemURL& url,
+ const WriteCallback& write_callback,
+ base::File::Error 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::File::FILE_OK);
+}
+
+void FileSystemOperationImpl::DidOpenFile(
+ const OpenFileCallback& callback,
+ base::File file,
+ const base::Closure& on_close_callback) {
+ callback.Run(file.Pass(), on_close_callback);
+}
+
+bool FileSystemOperationImpl::SetPendingOperationType(OperationType type) {
+ if (pending_operation_ != kOperationNone)
+ return false;
+ pending_operation_ = type;
+ return true;
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_system_operation_impl.h b/storage/browser/fileapi/file_system_operation_impl.h
new file mode 100644
index 0000000..76cf436
--- /dev/null
+++ b/storage/browser/fileapi/file_system_operation_impl.h
@@ -0,0 +1,206 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_
+#define STORAGE_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 "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/file_writer_delegate.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/blob/scoped_file.h"
+#include "storage/common/quota/quota_types.h"
+
+namespace storage {
+
+class AsyncFileUtil;
+class FileSystemContext;
+class RecursiveOperationDelegate;
+
+// The default implementation of FileSystemOperation for file systems.
+class STORAGE_EXPORT FileSystemOperationImpl
+ : public NON_EXPORTED_BASE(FileSystemOperation) {
+ public:
+ 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,
+ CopyOrMoveOption option,
+ const CopyProgressCallback& progress_callback,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void Move(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ 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,
+ const OpenFileCallback& callback) OVERRIDE;
+ virtual void Cancel(const StatusCallback& cancel_callback) OVERRIDE;
+ virtual void CreateSnapshotFile(
+ const FileSystemURL& path,
+ const SnapshotFileCallback& callback) OVERRIDE;
+ virtual void CopyInForeignFile(const base::FilePath& src_local_disk_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void RemoveFile(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void RemoveDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void CopyFileLocal(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void MoveFileLocal(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const StatusCallback& callback) OVERRIDE;
+ virtual base::File::Error SyncGetPlatformPath(
+ const FileSystemURL& url,
+ base::FilePath* platform_path) OVERRIDE;
+
+ FileSystemContext* file_system_context() const {
+ return file_system_context_.get();
+ }
+
+ private:
+ friend class FileSystemOperation;
+
+ FileSystemOperationImpl(
+ const FileSystemURL& url,
+ FileSystemContext* file_system_context,
+ scoped_ptr<FileSystemOperationContext> operation_context);
+
+ // 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,
+ storage::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,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ const StatusCallback& callback);
+ void DoMoveFileLocal(const FileSystemURL& src,
+ const FileSystemURL& dest,
+ CopyOrMoveOption option,
+ 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::File::Error rv,
+ bool created);
+
+ // Callback for CreateFile for |exclusive|=false cases.
+ void DidEnsureFileExistsNonExclusive(const StatusCallback& callback,
+ base::File::Error rv,
+ bool created);
+
+ void DidFinishOperation(const StatusCallback& callback,
+ base::File::Error rv);
+ void DidDirectoryExists(const StatusCallback& callback,
+ base::File::Error rv,
+ const base::File::Info& file_info);
+ void DidFileExists(const StatusCallback& callback,
+ base::File::Error rv,
+ const base::File::Info& file_info);
+ void DidDeleteRecursively(const FileSystemURL& url,
+ const StatusCallback& callback,
+ base::File::Error rv);
+ void DidWrite(const FileSystemURL& url,
+ const WriteCallback& callback,
+ base::File::Error rv,
+ int64 bytes,
+ FileWriterDelegate::WriteProgressStatus write_status);
+ void DidOpenFile(const OpenFileCallback& callback,
+ base::File 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_;
+
+ // A flag to make sure we call operation only once per instance.
+ OperationType pending_operation_;
+
+ base::WeakPtrFactory<FileSystemOperationImpl> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationImpl);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_IMPL_H_
diff --git a/storage/browser/fileapi/file_system_operation_runner.cc b/storage/browser/fileapi/file_system_operation_runner.cc
new file mode 100644
index 0000000..58ccb94
--- /dev/null
+++ b/storage/browser/fileapi/file_system_operation_runner.cc
@@ -0,0 +1,687 @@
+// 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 "storage/browser/fileapi/file_system_operation_runner.h"
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/stl_util.h"
+#include "net/url_request/url_request_context.h"
+#include "storage/browser/blob/blob_url_request_job_factory.h"
+#include "storage/browser/fileapi/file_observers.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_writer_delegate.h"
+#include "storage/common/blob/shareable_file_reference.h"
+
+namespace storage {
+
+typedef FileSystemOperationRunner::OperationID OperationID;
+
+class FileSystemOperationRunner::BeginOperationScoper
+ : public base::SupportsWeakPtr<
+ FileSystemOperationRunner::BeginOperationScoper> {
+ public:
+ BeginOperationScoper() {}
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BeginOperationScoper);
+};
+
+FileSystemOperationRunner::OperationHandle::OperationHandle() {}
+FileSystemOperationRunner::OperationHandle::~OperationHandle() {}
+
+FileSystemOperationRunner::~FileSystemOperationRunner() {
+}
+
+void FileSystemOperationRunner::Shutdown() {
+ operations_.Clear();
+}
+
+OperationID FileSystemOperationRunner::CreateFile(
+ const FileSystemURL& url,
+ bool exclusive,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ PrepareForWrite(handle.id, url);
+ operation->CreateFile(
+ url, exclusive,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::CreateDirectory(
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ PrepareForWrite(handle.id, url);
+ operation->CreateDirectory(
+ url, exclusive, recursive,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::Copy(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const CopyProgressCallback& progress_callback,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(dest_url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ PrepareForWrite(handle.id, dest_url);
+ PrepareForRead(handle.id, src_url);
+ operation->Copy(
+ src_url, dest_url, option,
+ progress_callback.is_null() ?
+ CopyProgressCallback() :
+ base::Bind(&FileSystemOperationRunner::OnCopyProgress, AsWeakPtr(),
+ handle, progress_callback),
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::Move(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(dest_url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ PrepareForWrite(handle.id, dest_url);
+ PrepareForWrite(handle.id, src_url);
+ operation->Move(
+ src_url, dest_url, option,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::DirectoryExists(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ PrepareForRead(handle.id, url);
+ operation->DirectoryExists(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::FileExists(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ PrepareForRead(handle.id, url);
+ operation->FileExists(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::GetMetadata(
+ const FileSystemURL& url,
+ const GetMetadataCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidGetMetadata(handle, callback, error, base::File::Info());
+ return handle.id;
+ }
+ PrepareForRead(handle.id, url);
+ operation->GetMetadata(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidGetMetadata, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::ReadDirectory(
+ const FileSystemURL& url,
+ const ReadDirectoryCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidReadDirectory(handle, callback, error, std::vector<DirectoryEntry>(),
+ false);
+ return handle.id;
+ }
+ PrepareForRead(handle.id, url);
+ operation->ReadDirectory(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidReadDirectory, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::Remove(
+ const FileSystemURL& url, bool recursive,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ PrepareForWrite(handle.id, url);
+ operation->Remove(
+ url, recursive,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::Write(
+ const net::URLRequestContext* url_request_context,
+ const FileSystemURL& url,
+ scoped_ptr<storage::BlobDataHandle> blob,
+ int64 offset,
+ const WriteCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidWrite(handle, callback, error, 0, true);
+ return handle.id;
+ }
+
+ scoped_ptr<FileStreamWriter> writer(
+ file_system_context_->CreateFileStreamWriter(url, offset));
+ if (!writer) {
+ // Write is not supported.
+ DidWrite(handle, callback, base::File::FILE_ERROR_SECURITY, 0, true);
+ return handle.id;
+ }
+
+ FileWriterDelegate::FlushPolicy flush_policy =
+ file_system_context_->ShouldFlushOnWriteCompletion(url.type())
+ ? FileWriterDelegate::FLUSH_ON_COMPLETION
+ : FileWriterDelegate::NO_FLUSH_ON_COMPLETION;
+ scoped_ptr<FileWriterDelegate> writer_delegate(
+ new FileWriterDelegate(writer.Pass(), flush_policy));
+
+ scoped_ptr<net::URLRequest> blob_request(
+ storage::BlobProtocolHandler::CreateBlobRequest(
+ blob.Pass(), url_request_context, writer_delegate.get()));
+
+ PrepareForWrite(handle.id, url);
+ operation->Write(
+ url, writer_delegate.Pass(), blob_request.Pass(),
+ base::Bind(&FileSystemOperationRunner::DidWrite, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::Truncate(
+ const FileSystemURL& url, int64 length,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ PrepareForWrite(handle.id, url);
+ operation->Truncate(
+ url, length,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+void FileSystemOperationRunner::Cancel(
+ OperationID id,
+ const StatusCallback& callback) {
+ if (ContainsKey(finished_operations_, id)) {
+ DCHECK(!ContainsKey(stray_cancel_callbacks_, id));
+ stray_cancel_callbacks_[id] = callback;
+ return;
+ }
+ FileSystemOperation* operation = operations_.Lookup(id);
+ if (!operation) {
+ // There is no operation with |id|.
+ callback.Run(base::File::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::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ PrepareForWrite(handle.id, url);
+ operation->TouchFile(
+ url, last_access_time, last_modified_time,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::OpenFile(
+ const FileSystemURL& url,
+ int file_flags,
+ const OpenFileCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidOpenFile(handle, callback, base::File(error), base::Closure());
+ return handle.id;
+ }
+ if (file_flags &
+ (base::File::FLAG_CREATE | base::File::FLAG_OPEN_ALWAYS |
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_OPEN_TRUNCATED |
+ base::File::FLAG_WRITE | base::File::FLAG_EXCLUSIVE_WRITE |
+ base::File::FLAG_DELETE_ON_CLOSE |
+ base::File::FLAG_WRITE_ATTRIBUTES)) {
+ PrepareForWrite(handle.id, url);
+ } else {
+ PrepareForRead(handle.id, url);
+ }
+ operation->OpenFile(
+ url, file_flags,
+ base::Bind(&FileSystemOperationRunner::DidOpenFile, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::CreateSnapshotFile(
+ const FileSystemURL& url,
+ const SnapshotFileCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidCreateSnapshot(handle, callback, error, base::File::Info(),
+ base::FilePath(), NULL);
+ return handle.id;
+ }
+ PrepareForRead(handle.id, url);
+ operation->CreateSnapshotFile(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidCreateSnapshot, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::CopyInForeignFile(
+ const base::FilePath& src_local_disk_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(dest_url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ operation->CopyInForeignFile(
+ src_local_disk_path, dest_url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::RemoveFile(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ operation->RemoveFile(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::RemoveDirectory(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ operation->RemoveDirectory(
+ url,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::CopyFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(src_url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ operation->CopyFileLocal(
+ src_url, dest_url, option, progress_callback,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+OperationID FileSystemOperationRunner::MoveFileLocal(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const StatusCallback& callback) {
+ base::File::Error error = base::File::FILE_OK;
+ FileSystemOperation* operation =
+ file_system_context_->CreateFileSystemOperation(src_url, &error);
+ BeginOperationScoper scope;
+ OperationHandle handle = BeginOperation(operation, scope.AsWeakPtr());
+ if (!operation) {
+ DidFinish(handle, callback, error);
+ return handle.id;
+ }
+ operation->MoveFileLocal(
+ src_url, dest_url, option,
+ base::Bind(&FileSystemOperationRunner::DidFinish, AsWeakPtr(),
+ handle, callback));
+ return handle.id;
+}
+
+base::File::Error FileSystemOperationRunner::SyncGetPlatformPath(
+ const FileSystemURL& url,
+ base::FilePath* platform_path) {
+ base::File::Error error = base::File::FILE_OK;
+ scoped_ptr<FileSystemOperation> operation(
+ file_system_context_->CreateFileSystemOperation(url, &error));
+ if (!operation.get())
+ return error;
+ return operation->SyncGetPlatformPath(url, platform_path);
+}
+
+FileSystemOperationRunner::FileSystemOperationRunner(
+ FileSystemContext* file_system_context)
+ : file_system_context_(file_system_context) {}
+
+void FileSystemOperationRunner::DidFinish(
+ const OperationHandle& handle,
+ const StatusCallback& callback,
+ base::File::Error rv) {
+ if (handle.scope) {
+ finished_operations_.insert(handle.id);
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE, base::Bind(&FileSystemOperationRunner::DidFinish,
+ AsWeakPtr(), handle, callback, rv));
+ return;
+ }
+ callback.Run(rv);
+ FinishOperation(handle.id);
+}
+
+void FileSystemOperationRunner::DidGetMetadata(
+ const OperationHandle& handle,
+ const GetMetadataCallback& callback,
+ base::File::Error rv,
+ const base::File::Info& file_info) {
+ if (handle.scope) {
+ finished_operations_.insert(handle.id);
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE, base::Bind(&FileSystemOperationRunner::DidGetMetadata,
+ AsWeakPtr(), handle, callback, rv, file_info));
+ return;
+ }
+ callback.Run(rv, file_info);
+ FinishOperation(handle.id);
+}
+
+void FileSystemOperationRunner::DidReadDirectory(
+ const OperationHandle& handle,
+ const ReadDirectoryCallback& callback,
+ base::File::Error rv,
+ const std::vector<DirectoryEntry>& entries,
+ bool has_more) {
+ if (handle.scope) {
+ finished_operations_.insert(handle.id);
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE, base::Bind(&FileSystemOperationRunner::DidReadDirectory,
+ AsWeakPtr(), handle, callback, rv,
+ entries, has_more));
+ return;
+ }
+ callback.Run(rv, entries, has_more);
+ if (rv != base::File::FILE_OK || !has_more)
+ FinishOperation(handle.id);
+}
+
+void FileSystemOperationRunner::DidWrite(
+ const OperationHandle& handle,
+ const WriteCallback& callback,
+ base::File::Error rv,
+ int64 bytes,
+ bool complete) {
+ if (handle.scope) {
+ finished_operations_.insert(handle.id);
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE, base::Bind(&FileSystemOperationRunner::DidWrite, AsWeakPtr(),
+ handle, callback, rv, bytes, complete));
+ return;
+ }
+ callback.Run(rv, bytes, complete);
+ if (rv != base::File::FILE_OK || complete)
+ FinishOperation(handle.id);
+}
+
+void FileSystemOperationRunner::DidOpenFile(
+ const OperationHandle& handle,
+ const OpenFileCallback& callback,
+ base::File file,
+ const base::Closure& on_close_callback) {
+ if (handle.scope) {
+ finished_operations_.insert(handle.id);
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE, base::Bind(&FileSystemOperationRunner::DidOpenFile,
+ AsWeakPtr(), handle, callback, Passed(&file),
+ on_close_callback));
+ return;
+ }
+ callback.Run(file.Pass(), on_close_callback);
+ FinishOperation(handle.id);
+}
+
+void FileSystemOperationRunner::DidCreateSnapshot(
+ const OperationHandle& handle,
+ const SnapshotFileCallback& callback,
+ base::File::Error rv,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref) {
+ if (handle.scope) {
+ finished_operations_.insert(handle.id);
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE, base::Bind(&FileSystemOperationRunner::DidCreateSnapshot,
+ AsWeakPtr(), handle, callback, rv, file_info,
+ platform_path, file_ref));
+ return;
+ }
+ callback.Run(rv, file_info, platform_path, file_ref);
+ FinishOperation(handle.id);
+}
+
+void FileSystemOperationRunner::OnCopyProgress(
+ const OperationHandle& handle,
+ const CopyProgressCallback& callback,
+ FileSystemOperation::CopyProgressType type,
+ const FileSystemURL& source_url,
+ const FileSystemURL& dest_url,
+ int64 size) {
+ if (handle.scope) {
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE, base::Bind(
+ &FileSystemOperationRunner::OnCopyProgress,
+ AsWeakPtr(), handle, callback, type, source_url, dest_url, size));
+ return;
+ }
+ callback.Run(type, source_url, dest_url, size);
+}
+
+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));
+ }
+}
+
+FileSystemOperationRunner::OperationHandle
+FileSystemOperationRunner::BeginOperation(
+ FileSystemOperation* operation,
+ base::WeakPtr<BeginOperationScoper> scope) {
+ OperationHandle handle;
+ handle.id = operations_.Add(operation);
+ handle.scope = scope;
+ return handle;
+}
+
+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);
+ }
+
+ // IDMap::Lookup fails if the operation is NULL, so we don't check
+ // operations_.Lookup(id) here.
+
+ operations_.Remove(id);
+ finished_operations_.erase(id);
+
+ // Dispatch stray cancel callback if exists.
+ std::map<OperationID, StatusCallback>::iterator found_cancel =
+ stray_cancel_callbacks_.find(id);
+ if (found_cancel != stray_cancel_callbacks_.end()) {
+ // This cancel has been requested after the operation has finished,
+ // so report that we failed to stop it.
+ found_cancel->second.Run(base::File::FILE_ERROR_INVALID_OPERATION);
+ stray_cancel_callbacks_.erase(found_cancel);
+ }
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_system_operation_runner.h b/storage/browser/fileapi/file_system_operation_runner.h
new file mode 100644
index 0000000..deca413
--- /dev/null
+++ b/storage/browser/fileapi/file_system_operation_runner.h
@@ -0,0 +1,322 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/id_map.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/blob/blob_data_handle.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace net {
+class URLRequestContext;
+}
+
+namespace storage {
+
+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).
+class STORAGE_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 FileSystemOperation::CopyProgressCallback CopyProgressCallback;
+ typedef FileSystemOperation::CopyFileProgressCallback
+ CopyFileProgressCallback;
+ typedef FileSystemOperation::CopyOrMoveOption CopyOrMoveOption;
+
+ typedef int OperationID;
+
+ 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.
+ // For |option| and |progress_callback|, see file_system_operation.h for
+ // details.
+ OperationID Copy(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ const CopyProgressCallback& progress_callback,
+ 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.
+ // For |option|, see file_system_operation.h for details.
+ OperationID Move(const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ 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|.
+ OperationID Write(const net::URLRequestContext* url_request_context,
+ const FileSystemURL& url,
+ scoped_ptr<storage::BlobDataHandle> blob,
+ 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,
+ 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:
+ // - File::FILE_ERROR_NOT_FOUND if |src_file_path|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::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:
+ // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - File::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:
+ // - File::FILE_ERROR_NOT_FOUND if |url| does not exist.
+ // - File::FILE_ERROR_NOT_A_DIRECTORY if |url| is not a directory.
+ // - File::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).
+ // For |option| and |progress_callback|, see file_system_operation.h for
+ // details.
+ //
+ // This returns:
+ // - File::FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::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,
+ CopyOrMoveOption option,
+ const CopyFileProgressCallback& progress_callback,
+ 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).
+ // For |option|, see file_system_operation.h for details.
+ //
+ // This returns:
+ // - File::FILE_ERROR_NOT_FOUND if |src_url|
+ // or the parent directory of |dest_url| does not exist.
+ // - File::FILE_ERROR_NOT_A_FILE if |src_url| exists but is not a file.
+ // - File::FILE_ERROR_INVALID_OPERATION if |dest_url| exists and
+ // is not a file.
+ // - File::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,
+ CopyOrMoveOption option,
+ 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::File::Error SyncGetPlatformPath(const FileSystemURL& url,
+ base::FilePath* platform_path);
+
+ private:
+ class BeginOperationScoper;
+
+ struct OperationHandle {
+ OperationID id;
+ base::WeakPtr<BeginOperationScoper> scope;
+
+ OperationHandle();
+ ~OperationHandle();
+ };
+
+ friend class FileSystemContext;
+ explicit FileSystemOperationRunner(FileSystemContext* file_system_context);
+
+ void DidFinish(const OperationHandle& handle,
+ const StatusCallback& callback,
+ base::File::Error rv);
+ void DidGetMetadata(const OperationHandle& handle,
+ const GetMetadataCallback& callback,
+ base::File::Error rv,
+ const base::File::Info& file_info);
+ void DidReadDirectory(const OperationHandle& handle,
+ const ReadDirectoryCallback& callback,
+ base::File::Error rv,
+ const std::vector<DirectoryEntry>& entries,
+ bool has_more);
+ void DidWrite(const OperationHandle& handle,
+ const WriteCallback& callback,
+ base::File::Error rv,
+ int64 bytes,
+ bool complete);
+ void DidOpenFile(
+ const OperationHandle& handle,
+ const OpenFileCallback& callback,
+ base::File file,
+ const base::Closure& on_close_callback);
+ void DidCreateSnapshot(
+ const OperationHandle& handle,
+ const SnapshotFileCallback& callback,
+ base::File::Error rv,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref);
+
+ void OnCopyProgress(
+ const OperationHandle& handle,
+ const CopyProgressCallback& callback,
+ FileSystemOperation::CopyProgressType type,
+ const FileSystemURL& source_url,
+ const FileSystemURL& dest_url,
+ int64 size);
+
+ void PrepareForWrite(OperationID id, const FileSystemURL& url);
+ void PrepareForRead(OperationID id, const FileSystemURL& url);
+
+ // These must be called at the beginning and end of any async operations.
+ OperationHandle BeginOperation(FileSystemOperation* operation,
+ base::WeakPtr<BeginOperationScoper> scope);
+ 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_;
+
+ // Operations that are finished but not yet fire their callbacks.
+ std::set<OperationID> finished_operations_;
+
+ // Callbacks for stray cancels whose target operation is already finished.
+ std::map<OperationID, StatusCallback> stray_cancel_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemOperationRunner);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPERATION_RUNNER_H_
diff --git a/storage/browser/fileapi/file_system_options.cc b/storage/browser/fileapi/file_system_options.cc
new file mode 100644
index 0000000..47fc3d0
--- /dev/null
+++ b/storage/browser/fileapi/file_system_options.cc
@@ -0,0 +1,21 @@
+// 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 "storage/browser/fileapi/file_system_options.h"
+
+namespace storage {
+
+FileSystemOptions::FileSystemOptions(
+ ProfileMode profile_mode,
+ const std::vector<std::string>& additional_allowed_schemes,
+ leveldb::Env* env_override)
+ : profile_mode_(profile_mode),
+ additional_allowed_schemes_(additional_allowed_schemes),
+ env_override_(env_override) {
+}
+
+FileSystemOptions::~FileSystemOptions() {
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_system_options.h b/storage/browser/fileapi/file_system_options.h
new file mode 100644
index 0000000..6e15baf
--- /dev/null
+++ b/storage/browser/fileapi/file_system_options.h
@@ -0,0 +1,62 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_
+
+#include <string>
+#include <vector>
+
+#include "storage/browser/storage_browser_export.h"
+
+namespace leveldb {
+class Env;
+}
+
+namespace storage {
+
+// Provides runtime options that may change FileSystem API behavior.
+// This object is copyable.
+class STORAGE_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".
+ // Non-NULL |env_override| overrides internal LevelDB environment.
+ FileSystemOptions(
+ ProfileMode profile_mode,
+ const std::vector<std::string>& additional_allowed_schemes,
+ leveldb::Env* env_override);
+
+ ~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_;
+ }
+
+ leveldb::Env* env_override() const { return env_override_; }
+
+ private:
+ const ProfileMode profile_mode_;
+ const std::vector<std::string> additional_allowed_schemes_;
+ leveldb::Env* env_override_;
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_OPTIONS_H_
diff --git a/storage/browser/fileapi/file_system_quota_client.cc b/storage/browser/fileapi/file_system_quota_client.cc
new file mode 100644
index 0000000..c8c79b3
--- /dev/null
+++ b/storage/browser/fileapi/file_system_quota_client.cc
@@ -0,0 +1,210 @@
+// 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 "storage/browser/fileapi/file_system_quota_client.h"
+
+#include <algorithm>
+#include <set>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.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 "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_quota_util.h"
+#include "storage/browser/fileapi/file_system_usage_cache.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "url/gurl.h"
+
+using storage::StorageType;
+
+namespace storage {
+
+namespace {
+
+void GetOriginsForTypeOnFileTaskRunner(
+ 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->GetOriginsForTypeOnFileTaskRunner(type, origins_ptr);
+}
+
+void GetOriginsForHostOnFileTaskRunner(
+ 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->GetOriginsForHostOnFileTaskRunner(type, host, origins_ptr);
+}
+
+void DidGetOrigins(const storage::QuotaClient::GetOriginsCallback& callback,
+ std::set<GURL>* origins_ptr) {
+ callback.Run(*origins_ptr);
+}
+
+storage::QuotaStatusCode DeleteOriginOnFileTaskRunner(
+ FileSystemContext* context,
+ const GURL& origin,
+ FileSystemType type) {
+ FileSystemBackend* provider = context->GetFileSystemBackend(type);
+ if (!provider || !provider->GetQuotaUtil())
+ return storage::kQuotaErrorNotSupported;
+ base::File::Error result =
+ provider->GetQuotaUtil()->DeleteOriginDataOnFileTaskRunner(
+ context, context->quota_manager_proxy(), origin, type);
+ if (result == base::File::FILE_OK)
+ return storage::kQuotaStatusOk;
+ return storage::kQuotaErrorInvalidModification;
+}
+
+} // namespace
+
+FileSystemQuotaClient::FileSystemQuotaClient(
+ FileSystemContext* file_system_context,
+ bool is_incognito)
+ : file_system_context_(file_system_context),
+ is_incognito_(is_incognito) {
+}
+
+FileSystemQuotaClient::~FileSystemQuotaClient() {}
+
+storage::QuotaClient::ID FileSystemQuotaClient::id() const {
+ return storage::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::GetOriginUsageOnFileTaskRunner,
+ 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(&GetOriginsForTypeOnFileTaskRunner,
+ 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(&GetOriginsForHostOnFileTaskRunner,
+ 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(&DeleteOriginOnFileTaskRunner,
+ file_system_context_,
+ origin,
+ fs_type),
+ callback);
+}
+
+bool FileSystemQuotaClient::DoesSupport(
+ storage::StorageType storage_type) const {
+ FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type);
+ DCHECK(type != kFileSystemTypeUnknown);
+ return file_system_context_->IsSandboxFileSystem(type);
+}
+
+base::SequencedTaskRunner* FileSystemQuotaClient::file_task_runner() const {
+ return file_system_context_->default_file_task_runner();
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_system_quota_client.h b/storage/browser/fileapi/file_system_quota_client.h
new file mode 100644
index 0000000..e82a0ed
--- /dev/null
+++ b/storage/browser/fileapi/file_system_quota_client.h
@@ -0,0 +1,70 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_
+#define STORAGE_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 "storage/browser/fileapi/file_system_quota_util.h"
+#include "storage/browser/quota/quota_client.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_types.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace storage {
+
+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 STORAGE_EXPORT_PRIVATE FileSystemQuotaClient
+ : public NON_EXPORTED_BASE(storage::QuotaClient) {
+ public:
+ FileSystemQuotaClient(
+ FileSystemContext* file_system_context,
+ bool is_incognito);
+ virtual ~FileSystemQuotaClient();
+
+ // QuotaClient methods.
+ virtual storage::QuotaClient::ID id() const OVERRIDE;
+ virtual void OnQuotaManagerDestroyed() OVERRIDE;
+ virtual void GetOriginUsage(const GURL& origin_url,
+ storage::StorageType type,
+ const GetUsageCallback& callback) OVERRIDE;
+ virtual void GetOriginsForType(storage::StorageType type,
+ const GetOriginsCallback& callback) OVERRIDE;
+ virtual void GetOriginsForHost(storage::StorageType type,
+ const std::string& host,
+ const GetOriginsCallback& callback) OVERRIDE;
+ virtual void DeleteOriginData(const GURL& origin,
+ storage::StorageType type,
+ const DeletionCallback& callback) OVERRIDE;
+ virtual bool DoesSupport(storage::StorageType type) const OVERRIDE;
+
+ private:
+ base::SequencedTaskRunner* file_task_runner() const;
+
+ scoped_refptr<FileSystemContext> file_system_context_;
+
+ bool is_incognito_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemQuotaClient);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_CLIENT_H_
diff --git a/storage/browser/fileapi/file_system_quota_util.h b/storage/browser/fileapi/file_system_quota_util.h
new file mode 100644
index 0000000..b5288ff
--- /dev/null
+++ b/storage/browser/fileapi/file_system_quota_util.h
@@ -0,0 +1,91 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/files/file.h"
+#include "storage/browser/fileapi/task_runner_bound_observer_list.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "url/gurl.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace storage {
+class QuotaManagerProxy;
+}
+
+namespace storage {
+
+class FileSystemContext;
+class QuotaReservation;
+
+// 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 STORAGE_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::File::Error DeleteOriginDataOnFileTaskRunner(
+ FileSystemContext* context,
+ storage::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ FileSystemType type) = 0;
+
+ virtual void GetOriginsForTypeOnFileTaskRunner(storage::FileSystemType type,
+ std::set<GURL>* origins) = 0;
+
+ virtual void GetOriginsForHostOnFileTaskRunner(storage::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 GetOriginUsageOnFileTaskRunner(
+ storage::FileSystemContext* file_system_context,
+ const GURL& origin_url,
+ storage::FileSystemType type) = 0;
+
+ // Creates new reservation object for the origin and the type.
+ virtual scoped_refptr<QuotaReservation>
+ CreateQuotaReservationOnFileTaskRunner(
+ const GURL& origin_url,
+ 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;
+
+ // Returns the observer list for |type|, or returns NULL if any observers
+ // have not been registered on |type|.
+ 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_QUOTA_UTIL_H_
diff --git a/storage/browser/fileapi/file_system_url.cc b/storage/browser/fileapi/file_system_url.cc
new file mode 100644
index 0000000..26d0a97c
--- /dev/null
+++ b/storage/browser/fileapi/file_system_url.cc
@@ -0,0 +1,153 @@
+// 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 "storage/browser/fileapi/file_system_url.h"
+
+#include <sstream>
+
+#include "base/logging.h"
+#include "base/strings/string_util.h"
+#include "net/base/escape.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace storage {
+
+namespace {
+
+} // namespace
+
+FileSystemURL::FileSystemURL()
+ : is_valid_(false),
+ mount_type_(kFileSystemTypeUnknown),
+ type_(kFileSystemTypeUnknown),
+ mount_option_(COPY_SYNC_OPTION_NO_SYNC) {
+}
+
+// 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);
+}
+
+FileSystemURL::FileSystemURL(const GURL& url)
+ : mount_type_(kFileSystemTypeUnknown),
+ type_(kFileSystemTypeUnknown),
+ mount_option_(COPY_SYNC_OPTION_NO_SYNC) {
+ 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()),
+ mount_option_(COPY_SYNC_OPTION_NO_SYNC) {
+}
+
+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,
+ const FileSystemMountOption& mount_option)
+ : 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),
+ mount_option_(mount_option) {
+}
+
+FileSystemURL::~FileSystemURL() {}
+
+GURL FileSystemURL::ToGURL() const {
+ if (!is_valid_)
+ return GURL();
+
+ std::string url = GetFileSystemRootURI(origin_, mount_type_).spec();
+ if (url.empty())
+ return GURL();
+
+ // Exactly match with DOMFileSystemBase::createFileSystemURL()'s encoding
+ // behavior, where the path is escaped by KURL::encodeWithURLEscapeSequences
+ // which is essentially encodeURIComponent except '/'.
+ std::string escaped = net::EscapeQueryParamValue(
+ virtual_path_.NormalizePathSeparatorsTo('/').AsUTF8Unsafe(),
+ false /* use_plus */);
+ ReplaceSubstringsAfterOffset(&escaped, 0, "%2F", "/");
+ url.append(escaped);
+
+ // Build nested GURL.
+ return GURL(url);
+}
+
+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 storage
diff --git a/storage/browser/fileapi/file_system_url.h b/storage/browser/fileapi/file_system_url.h
new file mode 100644
index 0000000..7b0e97d
--- /dev/null
+++ b/storage/browser/fileapi/file_system_url.h
@@ -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.
+
+#ifndef STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_
+
+#include <set>
+#include <string>
+
+#include "base/files/file_path.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_mount_option.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "url/gurl.h"
+
+namespace storage {
+
+// 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 STORAGE_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);
+
+ // 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_; }
+
+ const FileSystemMountOption& mount_option() const { return mount_option_; }
+
+ // Returns the formatted URL of this instance.
+ GURL ToGURL() const;
+
+ 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;
+
+ bool operator!=(const FileSystemURL& that) const {
+ return !(*this == that);
+ }
+
+ struct STORAGE_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,
+ const FileSystemMountOption& mount_option);
+
+ 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_;
+ FileSystemMountOption mount_option_;
+};
+
+typedef std::set<FileSystemURL, FileSystemURL::Comparator> FileSystemURLSet;
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_H_
diff --git a/storage/browser/fileapi/file_system_url_request_job.cc b/storage/browser/fileapi/file_system_url_request_job.cc
new file mode 100644
index 0000000..8c3e832
--- /dev/null
+++ b/storage/browser/fileapi/file_system_url_request_job.cc
@@ -0,0 +1,263 @@
+// 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 "storage/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/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 "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "url/gurl.h"
+
+using net::NetworkDelegate;
+using net::URLRequest;
+using net::URLRequestJob;
+using net::URLRequestStatus;
+
+namespace storage {
+
+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,
+ const std::string& storage_domain,
+ FileSystemContext* file_system_context)
+ : URLRequestJob(request, network_delegate),
+ storage_domain_(storage_domain),
+ file_system_context_(file_system_context),
+ is_directory_(false),
+ remaining_bytes_(0),
+ weak_factory_(this) {
+}
+
+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());
+ if (!url_.is_valid()) {
+ file_system_context_->AttemptAutoMountForURLRequest(
+ request_,
+ storage_domain_,
+ base::Bind(&FileSystemURLRequestJob::DidAttemptAutoMount,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+ if (!file_system_context_->CanServeURLRequest(url_)) {
+ // In incognito mode the API is not usable and there should be no data.
+ NotifyFailed(net::ERR_FILE_NOT_FOUND);
+ return;
+ }
+ file_system_context_->operation_runner()->GetMetadata(
+ url_,
+ base::Bind(&FileSystemURLRequestJob::DidGetMetadata,
+ weak_factory_.GetWeakPtr()));
+}
+
+void FileSystemURLRequestJob::DidAttemptAutoMount(base::File::Error result) {
+ if (result >= 0 &&
+ file_system_context_->CrackURL(request_->url()).is_valid()) {
+ StartAsync();
+ } else {
+ NotifyFailed(net::ERR_FILE_NOT_FOUND);
+ }
+}
+
+void FileSystemURLRequestJob::DidGetMetadata(
+ base::File::Error error_code,
+ const base::File::Info& file_info) {
+ if (error_code != base::File::FILE_OK) {
+ NotifyFailed(error_code == base::File::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 storage
diff --git a/storage/browser/fileapi/file_system_url_request_job.h b/storage/browser/fileapi/file_system_url_request_job.h
new file mode 100644
index 0000000..9910e5d
--- /dev/null
+++ b/storage/browser/fileapi/file_system_url_request_job.h
@@ -0,0 +1,85 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_
+
+#include <string>
+
+#include "base/files/file.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "net/http/http_byte_range.h"
+#include "net/url_request/url_request_job.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/storage_browser_export.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+}
+
+namespace storage {
+class FileStreamReader;
+}
+
+namespace storage {
+class FileSystemContext;
+
+// A request job that handles reading filesystem: URLs
+class STORAGE_EXPORT_PRIVATE FileSystemURLRequestJob
+ : public net::URLRequestJob {
+ public:
+ FileSystemURLRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& storage_domain,
+ 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 DidAttemptAutoMount(base::File::Error result);
+ void DidGetMetadata(base::File::Error error_code,
+ const base::File::Info& file_info);
+ void DidRead(int result);
+ void NotifyFailed(int rv);
+
+ const std::string storage_domain_;
+ FileSystemContext* file_system_context_;
+ scoped_ptr<storage::FileStreamReader> reader_;
+ FileSystemURL url_;
+ bool is_directory_;
+ scoped_ptr<net::HttpResponseInfo> response_info_;
+ int64 remaining_bytes_;
+ net::HttpByteRange byte_range_;
+ base::WeakPtrFactory<FileSystemURLRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemURLRequestJob);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_H_
diff --git a/storage/browser/fileapi/file_system_url_request_job_factory.cc b/storage/browser/fileapi/file_system_url_request_job_factory.cc
new file mode 100644
index 0000000..68150e4
--- /dev/null
+++ b/storage/browser/fileapi/file_system_url_request_job_factory.cc
@@ -0,0 +1,73 @@
+// 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 "storage/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 "storage/browser/fileapi/file_system_dir_url_request_job.h"
+#include "storage/browser/fileapi/file_system_url_request_job.h"
+
+namespace storage {
+
+namespace {
+
+class FileSystemProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ FileSystemProtocolHandler(const std::string& storage_domain,
+ FileSystemContext* context);
+ virtual ~FileSystemProtocolHandler();
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ const std::string storage_domain_;
+
+ // 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(
+ const std::string& storage_domain,
+ FileSystemContext* context)
+ : storage_domain_(storage_domain),
+ 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, storage_domain_, file_system_context_);
+ }
+ return new FileSystemURLRequestJob(
+ request, network_delegate, storage_domain_, file_system_context_);
+}
+
+} // anonymous namespace
+
+net::URLRequestJobFactory::ProtocolHandler* CreateFileSystemProtocolHandler(
+ const std::string& storage_domain, FileSystemContext* context) {
+ DCHECK(context);
+ return new FileSystemProtocolHandler(storage_domain, context);
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_system_url_request_job_factory.h b/storage/browser/fileapi/file_system_url_request_job_factory.h
new file mode 100644
index 0000000..921b5e3
--- /dev/null
+++ b/storage/browser/fileapi/file_system_url_request_job_factory.h
@@ -0,0 +1,31 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_
+
+#include <string>
+
+#include "net/url_request/url_request_job_factory.h"
+
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class MessageLoopProxy;
+} // namespace base
+
+namespace storage {
+
+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.
+STORAGE_EXPORT net::URLRequestJobFactory::ProtocolHandler*
+ CreateFileSystemProtocolHandler(const std::string& storage_domain,
+ FileSystemContext* context);
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_URL_REQUEST_JOB_FACTORY_H_
diff --git a/storage/browser/fileapi/file_system_usage_cache.cc b/storage/browser/fileapi/file_system_usage_cache.cc
new file mode 100644
index 0000000..0cfbfd8
--- /dev/null
+++ b/storage/browser/fileapi/file_system_usage_cache.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 "storage/browser/fileapi/file_system_usage_cache.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/pickle.h"
+#include "base/stl_util.h"
+#include "storage/browser/fileapi/timed_task_helper.h"
+
+namespace storage {
+
+namespace {
+const int64 kCloseDelaySeconds = 5;
+const size_t kMaxHandleCacheSize = 2;
+} // namespace
+
+FileSystemUsageCache::FileSystemUsageCache(
+ base::SequencedTaskRunner* task_runner)
+ : task_runner_(task_runner),
+ weak_factory_(this) {
+}
+
+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());
+ STLDeleteValues(&cache_files_);
+ 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;
+}
+
+base::File* FileSystemUsageCache::GetFile(const base::FilePath& file_path) {
+ DCHECK(CalledOnValidThread());
+ if (cache_files_.size() >= kMaxHandleCacheSize)
+ CloseCacheFiles();
+ ScheduleCloseTimer();
+
+ base::File* new_file = NULL;
+ std::pair<CacheFiles::iterator, bool> inserted =
+ cache_files_.insert(std::make_pair(file_path, new_file));
+ if (!inserted.second)
+ return inserted.first->second;
+
+ new_file = new base::File(file_path,
+ base::File::FLAG_OPEN_ALWAYS |
+ base::File::FLAG_READ |
+ base::File::FLAG_WRITE);
+ if (!new_file->IsValid()) {
+ cache_files_.erase(inserted.first);
+ delete new_file;
+ return NULL;
+ }
+
+ inserted.first->second = new_file;
+ return new_file;
+}
+
+bool FileSystemUsageCache::ReadBytes(const base::FilePath& file_path,
+ char* buffer,
+ int64 buffer_size) {
+ DCHECK(CalledOnValidThread());
+ base::File* file = GetFile(file_path);
+ if (!file)
+ return false;
+ return file->Read(0, buffer, buffer_size) == buffer_size;
+}
+
+bool FileSystemUsageCache::WriteBytes(const base::FilePath& file_path,
+ const char* buffer,
+ int64 buffer_size) {
+ DCHECK(CalledOnValidThread());
+ base::File* file = GetFile(file_path);
+ if (!file)
+ return false;
+ return file->Write(0, buffer, buffer_size) == buffer_size;
+}
+
+bool FileSystemUsageCache::FlushFile(const base::FilePath& file_path) {
+ TRACE_EVENT0("FileSystem", "UsageCache::FlushFile");
+ DCHECK(CalledOnValidThread());
+ base::File* file = GetFile(file_path);
+ if (!file)
+ return false;
+ return file->Flush();
+}
+
+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 storage
diff --git a/storage/browser/fileapi/file_system_usage_cache.h b/storage/browser/fileapi/file_system_usage_cache.h
new file mode 100644
index 0000000..c2b2aef
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequenced_task_runner.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace storage {
+
+class TimedTaskHelper;
+
+class STORAGE_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::File*> 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);
+
+ base::File* GetFile(const base::FilePath& file_path);
+
+ 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_;
+ CacheFiles cache_files_;
+
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+
+ base::WeakPtrFactory<FileSystemUsageCache> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystemUsageCache);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_SYSTEM_USAGE_CACHE_H_
diff --git a/storage/browser/fileapi/file_writer_delegate.cc b/storage/browser/fileapi/file_writer_delegate.cc
new file mode 100644
index 0000000..fc533eb
--- /dev/null
+++ b/storage/browser/fileapi/file_writer_delegate.cc
@@ -0,0 +1,244 @@
+// 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 "storage/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 "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace storage {
+
+static const int kReadBufSize = 32768;
+
+FileWriterDelegate::FileWriterDelegate(
+ scoped_ptr<FileStreamWriter> file_stream_writer,
+ FlushPolicy flush_policy)
+ : file_stream_writer_(file_stream_writer.Pass()),
+ writing_started_(false),
+ flush_policy_(flush_policy),
+ 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::File::FILE_ERROR_ABORT, 0,
+ GetCompletionStatusOnError());
+ }
+}
+
+void FileWriterDelegate::OnReceivedRedirect(
+ net::URLRequest* request,
+ const net::RedirectInfo& redirect_info,
+ bool* defer_redirect) {
+ NOTREACHED();
+ OnError(base::File::FILE_ERROR_SECURITY);
+}
+
+void FileWriterDelegate::OnAuthRequired(net::URLRequest* request,
+ net::AuthChallengeInfo* auth_info) {
+ NOTREACHED();
+ OnError(base::File::FILE_ERROR_SECURITY);
+}
+
+void FileWriterDelegate::OnCertificateRequested(
+ net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_info) {
+ NOTREACHED();
+ OnError(base::File::FILE_ERROR_SECURITY);
+}
+
+void FileWriterDelegate::OnSSLCertificateError(net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) {
+ NOTREACHED();
+ OnError(base::File::FILE_ERROR_SECURITY);
+}
+
+void FileWriterDelegate::OnResponseStarted(net::URLRequest* request) {
+ DCHECK_EQ(request_.get(), request);
+ if (!request->status().is_success() || request->GetResponseCode() != 200) {
+ OnError(base::File::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::File::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::File::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(NetErrorToFileError(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(NetErrorToFileError(write_response));
+ }
+}
+
+FileWriterDelegate::WriteProgressStatus
+FileWriterDelegate::GetCompletionStatusOnError() const {
+ return writing_started_ ? ERROR_WRITE_STARTED : ERROR_WRITE_NOT_STARTED;
+}
+
+void FileWriterDelegate::OnError(base::File::Error error) {
+ if (request_) {
+ request_->set_delegate(NULL);
+ request_->Cancel();
+ }
+
+ if (writing_started_)
+ MaybeFlushForCompletion(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) {
+ MaybeFlushForCompletion(base::File::FILE_OK, bytes_written,
+ SUCCESS_COMPLETED);
+ } else {
+ write_callback_.Run(base::File::FILE_OK, bytes_written,
+ SUCCESS_IO_PENDING);
+ }
+ return;
+ }
+ bytes_written_backlog_ += bytes_written;
+}
+
+void FileWriterDelegate::OnWriteCancelled(int status) {
+ write_callback_.Run(base::File::FILE_ERROR_ABORT, 0,
+ GetCompletionStatusOnError());
+}
+
+void FileWriterDelegate::MaybeFlushForCompletion(
+ base::File::Error error,
+ int bytes_written,
+ WriteProgressStatus progress_status) {
+ if (flush_policy_ == NO_FLUSH_ON_COMPLETION) {
+ write_callback_.Run(error, bytes_written, progress_status);
+ return;
+ }
+ DCHECK_EQ(FLUSH_ON_COMPLETION, flush_policy_);
+
+ 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::File::Error error,
+ int bytes_written,
+ WriteProgressStatus progress_status,
+ int flush_error) {
+ if (error == base::File::FILE_OK && flush_error != net::OK) {
+ // If the Flush introduced an error, overwrite the status.
+ // Otherwise, keep the original error status.
+ error = NetErrorToFileError(flush_error);
+ progress_status = GetCompletionStatusOnError();
+ }
+ write_callback_.Run(error, bytes_written, progress_status);
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/file_writer_delegate.h b/storage/browser/fileapi/file_writer_delegate.h
new file mode 100644
index 0000000..eefad4c
--- /dev/null
+++ b/storage/browser/fileapi/file_writer_delegate.h
@@ -0,0 +1,110 @@
+// 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 STORAGE_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_
+#define STORAGE_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.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 "storage/browser/storage_browser_export.h"
+
+namespace storage {
+
+class FileStreamWriter;
+
+class STORAGE_EXPORT_PRIVATE FileWriterDelegate
+ : public net::URLRequest::Delegate {
+ public:
+ enum FlushPolicy {
+ FLUSH_ON_COMPLETION,
+ NO_FLUSH_ON_COMPLETION,
+ };
+
+ enum WriteProgressStatus {
+ SUCCESS_IO_PENDING,
+ SUCCESS_COMPLETED,
+ ERROR_WRITE_STARTED,
+ ERROR_WRITE_NOT_STARTED,
+ };
+
+ typedef base::Callback<void(base::File::Error result,
+ int64 bytes,
+ WriteProgressStatus write_status)>
+ DelegateWriteCallback;
+
+ FileWriterDelegate(scoped_ptr<FileStreamWriter> file_writer,
+ FlushPolicy flush_policy);
+ 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 net::RedirectInfo& redirect_info,
+ 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::File::Error error,
+ const base::File::Info& file_info);
+ void Read();
+ void OnDataReceived(int bytes_read);
+ void Write();
+ void OnDataWritten(int write_response);
+ void OnError(base::File::Error error);
+ void OnProgress(int bytes_read, bool done);
+ void OnWriteCancelled(int status);
+ void MaybeFlushForCompletion(base::File::Error error,
+ int bytes_written,
+ WriteProgressStatus progress_status);
+ void OnFlushed(base::File::Error 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_;
+ FlushPolicy flush_policy_;
+ 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_FILE_WRITER_DELEGATE_H_
diff --git a/storage/browser/fileapi/isolated_context.cc b/storage/browser/fileapi/isolated_context.cc
new file mode 100644
index 0000000..20e28c1
--- /dev/null
+++ b/storage/browser/fileapi/isolated_context.cc
@@ -0,0 +1,484 @@
+// 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 "storage/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 "storage/browser/fileapi/file_system_url.h"
+
+namespace storage {
+
+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) {
+ DCHECK_NE(kFileSystemTypeUnknown, type);
+ // As of writing dragged file system is the only filesystem which could have
+ // multiple top-level paths.
+ return type != kFileSystemTypeDragged;
+}
+
+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 std::string& filesystem_id,
+ 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 std::string& filesystem_id() const { return filesystem_id_; }
+ 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_;
+ const std::string filesystem_id_;
+
+ // 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 std::string& filesystem_id,
+ const MountPointInfo& file_info,
+ Instance::PathType path_type)
+ : type_(type),
+ filesystem_id_(filesystem_id),
+ 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 std::string& filesystem_id,
+ 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 new_id = GetNewFileSystemId();
+ instance_map_[new_id] = new Instance(type, filesystem_id,
+ MountPointInfo(name, path),
+ Instance::PLATFORM_PATH);
+ path_to_id_map_[path].insert(new_id);
+ return new_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,
+ std::string(), // filesystem_id
+ 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,
+ std::string* cracked_id,
+ base::FilePath* path,
+ FileSystemMountOption* mount_option) const {
+ DCHECK(id_or_name);
+ DCHECK(path);
+
+ // This should not contain any '..' references.
+ if (virtual_path.ReferencesParent())
+ return false;
+
+ // Set the default mount option.
+ *mount_option = FileSystemMountOption();
+
+ // 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 (cracked_id)
+ *cracked_id = instance->filesystem_id();
+
+ 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;
+ std::string cracked_mount_name;
+ FileSystemType cracked_type;
+ base::FilePath cracked_path;
+ FileSystemMountOption cracked_mount_option;
+ if (!CrackVirtualPath(url.path(), &mount_name, &cracked_type,
+ &cracked_mount_name, &cracked_path,
+ &cracked_mount_option)) {
+ 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,
+ cracked_mount_name.empty() ? mount_name : cracked_mount_name,
+ cracked_mount_option);
+}
+
+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 storage
diff --git a/storage/browser/fileapi/isolated_context.h b/storage/browser/fileapi/isolated_context.h
new file mode 100644
index 0000000..aa18e9b
--- /dev/null
+++ b/storage/browser/fileapi/isolated_context.h
@@ -0,0 +1,201 @@
+// 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 STORAGE_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_
+#define STORAGE_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 "storage/browser/fileapi/mount_points.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_types.h"
+
+namespace storage {
+class FileSystemURL;
+}
+
+namespace storage {
+
+// 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 STORAGE_EXPORT IsolatedContext : public MountPoints {
+ public:
+ class STORAGE_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 with |filesystem_id| 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 std::string& filesystem_id,
+ 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,
+ std::string* cracked_id,
+ base::FilePath* path,
+ FileSystemMountOption* mount_option) 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_ISOLATED_CONTEXT_H_
diff --git a/storage/browser/fileapi/isolated_file_system_backend.cc b/storage/browser/fileapi/isolated_file_system_backend.cc
new file mode 100644
index 0000000..90a0ce8
--- /dev/null
+++ b/storage/browser/fileapi/isolated_file_system_backend.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 "storage/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/sequenced_task_runner.h"
+#include "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/async_file_util_adapter.h"
+#include "storage/browser/fileapi/copy_or_move_file_validator.h"
+#include "storage/browser/fileapi/dragged_file_util.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/browser/fileapi/native_file_util.h"
+#include "storage/browser/fileapi/transient_file_util.h"
+#include "storage/browser/fileapi/watcher_manager.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace storage {
+
+IsolatedFileSystemBackend::IsolatedFileSystemBackend()
+ : isolated_file_util_(new AsyncFileUtilAdapter(new LocalFileUtil())),
+ 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::ResolveURL(
+ const FileSystemURL& url,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) {
+ // We never allow opening a new isolated FileSystem via usual ResolveURL.
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ GURL(),
+ std::string(),
+ base::File::FILE_ERROR_SECURITY));
+}
+
+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;
+}
+
+WatcherManager* IsolatedFileSystemBackend::GetWatcherManager(
+ FileSystemType type) {
+ return NULL;
+}
+
+CopyOrMoveFileValidatorFactory*
+IsolatedFileSystemBackend::GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type, base::File::Error* error_code) {
+ DCHECK(error_code);
+ *error_code = base::File::FILE_OK;
+ return NULL;
+}
+
+FileSystemOperation* IsolatedFileSystemBackend::CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::File::Error* error_code) const {
+ return FileSystemOperation::Create(
+ url, context, make_scoped_ptr(new FileSystemOperationContext(context)));
+}
+
+bool IsolatedFileSystemBackend::SupportsStreaming(
+ const storage::FileSystemURL& url) const {
+ return false;
+}
+
+bool IsolatedFileSystemBackend::HasInplaceCopyImplementation(
+ storage::FileSystemType type) const {
+ DCHECK(type == kFileSystemTypeNativeLocal || type == kFileSystemTypeDragged ||
+ type == kFileSystemTypeForTransientFile);
+ return false;
+}
+
+scoped_ptr<storage::FileStreamReader>
+IsolatedFileSystemBackend::CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const {
+ return scoped_ptr<storage::FileStreamReader>(
+ storage::FileStreamReader::CreateForLocalFile(
+ 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>(
+ FileStreamWriter::CreateForLocalFile(
+ context->default_file_task_runner(),
+ url.path(),
+ offset,
+ FileStreamWriter::OPEN_EXISTING_FILE));
+}
+
+FileSystemQuotaUtil* IsolatedFileSystemBackend::GetQuotaUtil() {
+ // No quota support.
+ return NULL;
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/isolated_file_system_backend.h b/storage/browser/fileapi/isolated_file_system_backend.h
new file mode 100644
index 0000000..3894ff4
--- /dev/null
+++ b/storage/browser/fileapi/isolated_file_system_backend.h
@@ -0,0 +1,57 @@
+// 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 STORAGE_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_
+#define STORAGE_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/file_system_backend.h"
+
+namespace storage {
+
+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 ResolveURL(const FileSystemURL& url,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) OVERRIDE;
+ virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE;
+ virtual WatcherManager* GetWatcherManager(FileSystemType type) OVERRIDE;
+ virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type,
+ base::File::Error* error_code) OVERRIDE;
+ virtual FileSystemOperation* CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::File::Error* error_code) const OVERRIDE;
+ virtual bool SupportsStreaming(const FileSystemURL& url) const OVERRIDE;
+ virtual bool HasInplaceCopyImplementation(
+ storage::FileSystemType type) const OVERRIDE;
+ virtual scoped_ptr<storage::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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_ISOLATED_FILE_SYSTEM_BACKEND_H_
diff --git a/storage/browser/fileapi/local_file_stream_writer.cc b/storage/browser/fileapi/local_file_stream_writer.cc
new file mode 100644
index 0000000..0eb555f
--- /dev/null
+++ b/storage/browser/fileapi/local_file_stream_writer.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 "storage/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 storage {
+
+namespace {
+
+const int kOpenFlagsForWrite = base::File::FLAG_OPEN |
+ base::File::FLAG_WRITE |
+ base::File::FLAG_ASYNC;
+const int kCreateFlagsForWrite = base::File::FLAG_CREATE |
+ base::File::FLAG_WRITE |
+ base::File::FLAG_ASYNC;
+
+} // namespace
+
+FileStreamWriter* FileStreamWriter::CreateForLocalFile(
+ base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ OpenOrCreate open_or_create) {
+ return new LocalFileStreamWriter(
+ task_runner, file_path, initial_offset, open_or_create);
+}
+
+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;
+}
+
+LocalFileStreamWriter::LocalFileStreamWriter(base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ OpenOrCreate open_or_create)
+ : file_path_(file_path),
+ open_or_create_(open_or_create),
+ initial_offset_(initial_offset),
+ task_runner_(task_runner),
+ has_pending_operation_(false),
+ weak_factory_(this) {}
+
+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(task_runner_));
+
+ int open_flags = 0;
+ switch (open_or_create_) {
+ case OPEN_EXISTING_FILE:
+ open_flags = kOpenFlagsForWrite;
+ break;
+ case CREATE_NEW_FILE:
+ open_flags = kCreateFlagsForWrite;
+ break;
+ }
+
+ return stream_impl_->Open(file_path_,
+ open_flags,
+ 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(base::File::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 storage
diff --git a/storage/browser/fileapi/local_file_stream_writer.h b/storage/browser/fileapi/local_file_stream_writer.h
new file mode 100644
index 0000000..17417d0
--- /dev/null
+++ b/storage/browser/fileapi/local_file_stream_writer.h
@@ -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.
+
+#ifndef STORAGE_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_
+#define STORAGE_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/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/task_runner.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace content {
+class LocalFileStreamWriterTest;
+}
+
+namespace net {
+class FileStream;
+}
+
+namespace storage {
+
+// This class is a thin wrapper around net::FileStream for writing local files.
+class STORAGE_EXPORT LocalFileStreamWriter
+ : public NON_EXPORTED_BASE(FileStreamWriter) {
+ public:
+ 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:
+ friend class content::LocalFileStreamWriterTest;
+ friend class FileStreamWriter;
+ LocalFileStreamWriter(base::TaskRunner* task_runner,
+ const base::FilePath& file_path,
+ int64 initial_offset,
+ OpenOrCreate open_or_create);
+
+ // 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_;
+ OpenOrCreate open_or_create_;
+ 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_LOCAL_FILE_STREAM_WRITER_H_
diff --git a/storage/browser/fileapi/local_file_util.cc b/storage/browser/fileapi/local_file_util.cc
new file mode 100644
index 0000000..19ce59e
--- /dev/null
+++ b/storage/browser/fileapi/local_file_util.cc
@@ -0,0 +1,265 @@
+// 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 "storage/browser/fileapi/local_file_util.h"
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/files/file_util_proxy.h"
+#include "storage/browser/fileapi/async_file_util_adapter.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/native_file_util.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "url/gurl.h"
+
+namespace storage {
+
+AsyncFileUtil* AsyncFileUtil::CreateForLocalFileSystem() {
+ return new AsyncFileUtilAdapter(new LocalFileUtil());
+}
+
+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() && base::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() {}
+
+base::File LocalFileUtil::CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, int file_flags) {
+ base::FilePath file_path;
+ base::File::Error error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::File::FILE_OK)
+ return base::File(error);
+ // Disallow opening files in symlinked paths.
+ if (base::IsLink(file_path))
+ return base::File(base::File::FILE_ERROR_NOT_FOUND);
+
+ return NativeFileUtil::CreateOrOpen(file_path, file_flags);
+}
+
+base::File::Error LocalFileUtil::EnsureFileExists(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool* created) {
+ base::FilePath file_path;
+ base::File::Error error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+ return NativeFileUtil::EnsureFileExists(file_path, created);
+}
+
+base::File::Error LocalFileUtil::CreateDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) {
+ base::FilePath file_path;
+ base::File::Error error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+ return NativeFileUtil::CreateDirectory(file_path, exclusive, recursive);
+}
+
+base::File::Error LocalFileUtil::GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Info* file_info,
+ base::FilePath* platform_file_path) {
+ base::FilePath file_path;
+ base::File::Error error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+ // We should not follow symbolic links in sandboxed file system.
+ if (base::IsLink(file_path))
+ return base::File::FILE_ERROR_NOT_FOUND;
+
+ error = NativeFileUtil::GetFileInfo(file_path, file_info);
+ if (error == base::File::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::File::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>();
+}
+
+base::File::Error LocalFileUtil::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::File::FILE_ERROR_ACCESS_DENIED;
+ }
+ *local_file_path = url.path();
+ return base::File::FILE_OK;
+}
+
+base::File::Error LocalFileUtil::Touch(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) {
+ base::FilePath file_path;
+ base::File::Error error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+ return NativeFileUtil::Touch(file_path, last_access_time, last_modified_time);
+}
+
+base::File::Error LocalFileUtil::Truncate(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 length) {
+ base::FilePath file_path;
+ base::File::Error error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+ return NativeFileUtil::Truncate(file_path, length);
+}
+
+base::File::Error LocalFileUtil::CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ bool copy) {
+ base::FilePath src_file_path;
+ base::File::Error error = GetLocalFilePath(context, src_url, &src_file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+
+ base::FilePath dest_file_path;
+ error = GetLocalFilePath(context, dest_url, &dest_file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+
+ return NativeFileUtil::CopyOrMoveFile(
+ src_file_path,
+ dest_file_path,
+ option,
+ storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url, copy));
+}
+
+base::File::Error LocalFileUtil::CopyInForeignFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url) {
+ if (src_file_path.empty())
+ return base::File::FILE_ERROR_INVALID_OPERATION;
+
+ base::FilePath dest_file_path;
+ base::File::Error error =
+ GetLocalFilePath(context, dest_url, &dest_file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+ return NativeFileUtil::CopyOrMoveFile(
+ src_file_path,
+ dest_file_path,
+ FileSystemOperation::OPTION_NONE,
+ storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url,
+ true /* copy */));
+}
+
+base::File::Error LocalFileUtil::DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ base::FilePath file_path;
+ base::File::Error error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+ return NativeFileUtil::DeleteFile(file_path);
+}
+
+base::File::Error LocalFileUtil::DeleteDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ base::FilePath file_path;
+ base::File::Error error = GetLocalFilePath(context, url, &file_path);
+ if (error != base::File::FILE_OK)
+ return error;
+ return NativeFileUtil::DeleteDirectory(file_path);
+}
+
+storage::ScopedFile LocalFileUtil::CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Error* error,
+ base::File::Info* 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::File::FILE_OK && file_info->is_directory)
+ *error = base::File::FILE_ERROR_NOT_A_FILE;
+ return storage::ScopedFile();
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/local_file_util.h b/storage/browser/fileapi/local_file_util.h
new file mode 100644
index 0000000..dbbdef0
--- /dev/null
+++ b/storage/browser/fileapi/local_file_util.h
@@ -0,0 +1,94 @@
+// 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 STORAGE_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_
+#define STORAGE_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class Time;
+}
+
+class GURL;
+
+namespace storage {
+
+class FileSystemOperationContext;
+class FileSystemURL;
+
+// An instance of this class is created and owned by *FileSystemBackend.
+class STORAGE_EXPORT LocalFileUtil
+ : public FileSystemFileUtil {
+ public:
+ LocalFileUtil();
+ virtual ~LocalFileUtil();
+
+ virtual base::File CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int file_flags) OVERRIDE;
+ virtual base::File::Error EnsureFileExists(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, bool* created) OVERRIDE;
+ virtual base::File::Error CreateDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) OVERRIDE;
+ virtual base::File::Error GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Info* file_info,
+ base::FilePath* platform_file) OVERRIDE;
+ virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url) OVERRIDE;
+ virtual base::File::Error GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& file_system_url,
+ base::FilePath* local_file_path) OVERRIDE;
+ virtual base::File::Error Touch(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) OVERRIDE;
+ virtual base::File::Error Truncate(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 length) OVERRIDE;
+ virtual base::File::Error CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ bool copy) OVERRIDE;
+ virtual base::File::Error CopyInForeignFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url) OVERRIDE;
+ virtual base::File::Error DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) OVERRIDE;
+ virtual base::File::Error DeleteDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) OVERRIDE;
+ virtual storage::ScopedFile CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Error* error,
+ base::File::Info* file_info,
+ base::FilePath* platform_path) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LocalFileUtil);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_LOCAL_FILE_UTIL_H_
diff --git a/storage/browser/fileapi/mount_points.cc b/storage/browser/fileapi/mount_points.cc
new file mode 100644
index 0000000..bc0bf84
--- /dev/null
+++ b/storage/browser/fileapi/mount_points.cc
@@ -0,0 +1,14 @@
+// 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 "storage/browser/fileapi/mount_points.h"
+
+namespace storage {
+
+MountPoints::MountPointInfo::MountPointInfo() {}
+MountPoints::MountPointInfo::MountPointInfo(
+ const std::string& name, const base::FilePath& path)
+ : name(name), path(path) {}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/mount_points.h b/storage/browser/fileapi/mount_points.h
new file mode 100644
index 0000000..61da5fa
--- /dev/null
+++ b/storage/browser/fileapi/mount_points.h
@@ -0,0 +1,108 @@
+// 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 STORAGE_BROWSER_FILEAPI_MOUNT_POINTS_H_
+#define STORAGE_BROWSER_FILEAPI_MOUNT_POINTS_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+class GURL;
+
+namespace storage {
+class FileSystemMountOption;
+class FileSystemURL;
+}
+
+namespace storage {
+
+// Represents a set of mount points for File API.
+class STORAGE_EXPORT MountPoints {
+ public:
+ struct STORAGE_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,
+ storage::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,
+ std::string* cracked_id,
+ base::FilePath* path,
+ FileSystemMountOption* mount_option) 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_MOUNT_POINTS_H_
diff --git a/storage/browser/fileapi/native_file_util.cc b/storage/browser/fileapi/native_file_util.cc
new file mode 100644
index 0000000..b44179c
--- /dev/null
+++ b/storage/browser/fileapi/native_file_util.cc
@@ -0,0 +1,315 @@
+// 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 "storage/browser/fileapi/native_file_util.h"
+
+#include "base/files/file.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+
+namespace storage {
+
+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;
+}
+
+// Copies a file |from| to |to|, and ensure the written content is synced to
+// the disk. This is essentially base::CopyFile followed by fsync().
+bool CopyFileAndSync(const base::FilePath& from, const base::FilePath& to) {
+ base::File infile(from, base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!infile.IsValid()) {
+ return false;
+ }
+
+ base::File outfile(to,
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
+ if (!outfile.IsValid()) {
+ return false;
+ }
+
+ const int kBufferSize = 32768;
+ std::vector<char> buffer(kBufferSize);
+
+ for (;;) {
+ int bytes_read = infile.ReadAtCurrentPos(&buffer[0], kBufferSize);
+ if (bytes_read < 0)
+ return false;
+ if (bytes_read == 0)
+ break;
+ for (int bytes_written = 0; bytes_written < bytes_read; ) {
+ int bytes_written_partial = outfile.WriteAtCurrentPos(
+ &buffer[bytes_written], bytes_read - bytes_written);
+ if (bytes_written_partial < 0)
+ return false;
+ bytes_written += bytes_written_partial;
+ }
+ }
+
+ return outfile.Flush();
+}
+
+} // namespace
+
+using base::PlatformFile;
+
+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();
+}
+
+NativeFileUtil::CopyOrMoveMode NativeFileUtil::CopyOrMoveModeForDestination(
+ const FileSystemURL& dest_url, bool copy) {
+ if (copy) {
+ return dest_url.mount_option().copy_sync_option() == COPY_SYNC_OPTION_SYNC ?
+ COPY_SYNC : COPY_NOSYNC;
+ }
+ return MOVE;
+}
+
+base::File NativeFileUtil::CreateOrOpen(const base::FilePath& path,
+ int file_flags) {
+ if (!base::DirectoryExists(path.DirName())) {
+ // If its parent does not exist, should return NOT_FOUND error.
+ return base::File(base::File::FILE_ERROR_NOT_FOUND);
+ }
+
+ // TODO(rvargas): Check |file_flags| instead. See bug 356358.
+ if (base::DirectoryExists(path))
+ return base::File(base::File::FILE_ERROR_NOT_A_FILE);
+
+ return base::File(path, file_flags);
+}
+
+base::File::Error 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::File::FILE_ERROR_NOT_FOUND;
+
+ // Tries to create the |path| exclusively. This should fail
+ // with base::File::FILE_ERROR_EXISTS if the path already exists.
+ base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_READ);
+
+ if (file.IsValid()) {
+ if (created)
+ *created = file.created();
+ return base::File::FILE_OK;
+ }
+
+ base::File::Error error_code = file.error_details();
+ if (error_code == base::File::FILE_ERROR_EXISTS) {
+ // Make sure created_ is false.
+ if (created)
+ *created = false;
+ error_code = base::File::FILE_OK;
+ }
+ return error_code;
+}
+
+base::File::Error 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::File::FILE_ERROR_NOT_FOUND;
+
+ bool path_exists = base::PathExists(path);
+ if (exclusive && path_exists)
+ return base::File::FILE_ERROR_EXISTS;
+
+ // If file exists at the path.
+ if (path_exists && !base::DirectoryExists(path))
+ return base::File::FILE_ERROR_EXISTS;
+
+ if (!base::CreateDirectory(path))
+ return base::File::FILE_ERROR_FAILED;
+
+ if (!SetPlatformSpecificDirectoryPermissions(path)) {
+ // Since some file systems don't support permission setting, we do not treat
+ // an error from the function as the failure of copying. Just log it.
+ LOG(WARNING) << "Setting directory permission failed: "
+ << path.AsUTF8Unsafe();
+ }
+
+ return base::File::FILE_OK;
+}
+
+base::File::Error NativeFileUtil::GetFileInfo(
+ const base::FilePath& path,
+ base::File::Info* file_info) {
+ if (!base::PathExists(path))
+ return base::File::FILE_ERROR_NOT_FOUND;
+
+ if (!base::GetFileInfo(path, file_info))
+ return base::File::FILE_ERROR_FAILED;
+ return base::File::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>();
+}
+
+base::File::Error NativeFileUtil::Touch(
+ const base::FilePath& path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) {
+ if (!base::TouchFile(path, last_access_time, last_modified_time))
+ return base::File::FILE_ERROR_FAILED;
+ return base::File::FILE_OK;
+}
+
+base::File::Error NativeFileUtil::Truncate(const base::FilePath& path,
+ int64 length) {
+ base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+ if (!file.IsValid())
+ return file.error_details();
+
+ if (!file.SetLength(length))
+ return base::File::FILE_ERROR_FAILED;
+
+ return base::File::FILE_OK;
+}
+
+bool NativeFileUtil::PathExists(const base::FilePath& path) {
+ return base::PathExists(path);
+}
+
+bool NativeFileUtil::DirectoryExists(const base::FilePath& path) {
+ return base::DirectoryExists(path);
+}
+
+base::File::Error NativeFileUtil::CopyOrMoveFile(
+ const base::FilePath& src_path,
+ const base::FilePath& dest_path,
+ FileSystemOperation::CopyOrMoveOption option,
+ CopyOrMoveMode mode) {
+ base::File::Info info;
+ base::File::Error error = NativeFileUtil::GetFileInfo(src_path, &info);
+ if (error != base::File::FILE_OK)
+ return error;
+ if (info.is_directory)
+ return base::File::FILE_ERROR_NOT_A_FILE;
+ base::Time last_modified = info.last_modified;
+
+ error = NativeFileUtil::GetFileInfo(dest_path, &info);
+ if (error != base::File::FILE_OK &&
+ error != base::File::FILE_ERROR_NOT_FOUND)
+ return error;
+ if (info.is_directory)
+ return base::File::FILE_ERROR_INVALID_OPERATION;
+ if (error == base::File::FILE_ERROR_NOT_FOUND) {
+ error = NativeFileUtil::GetFileInfo(dest_path.DirName(), &info);
+ if (error != base::File::FILE_OK)
+ return error;
+ if (!info.is_directory)
+ return base::File::FILE_ERROR_NOT_FOUND;
+ }
+
+ switch (mode) {
+ case COPY_NOSYNC:
+ if (!base::CopyFile(src_path, dest_path))
+ return base::File::FILE_ERROR_FAILED;
+ break;
+ case COPY_SYNC:
+ if (!CopyFileAndSync(src_path, dest_path))
+ return base::File::FILE_ERROR_FAILED;
+ break;
+ case MOVE:
+ if (!base::Move(src_path, dest_path))
+ return base::File::FILE_ERROR_FAILED;
+ break;
+ }
+
+ // Preserve the last modified time. Do not return error here even if
+ // the setting is failed, because the copy itself is successfully done.
+ if (option == FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED)
+ base::TouchFile(dest_path, last_modified, last_modified);
+
+ return base::File::FILE_OK;
+}
+
+base::File::Error NativeFileUtil::DeleteFile(const base::FilePath& path) {
+ if (!base::PathExists(path))
+ return base::File::FILE_ERROR_NOT_FOUND;
+ if (base::DirectoryExists(path))
+ return base::File::FILE_ERROR_NOT_A_FILE;
+ if (!base::DeleteFile(path, false))
+ return base::File::FILE_ERROR_FAILED;
+ return base::File::FILE_OK;
+}
+
+base::File::Error NativeFileUtil::DeleteDirectory(const base::FilePath& path) {
+ if (!base::PathExists(path))
+ return base::File::FILE_ERROR_NOT_FOUND;
+ if (!base::DirectoryExists(path))
+ return base::File::FILE_ERROR_NOT_A_DIRECTORY;
+ if (!base::IsDirectoryEmpty(path))
+ return base::File::FILE_ERROR_NOT_EMPTY;
+ if (!base::DeleteFile(path, false))
+ return base::File::FILE_ERROR_FAILED;
+ return base::File::FILE_OK;
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/native_file_util.h b/storage/browser/fileapi/native_file_util.h
new file mode 100644
index 0000000..b8c180e
--- /dev/null
+++ b/storage/browser/fileapi/native_file_util.h
@@ -0,0 +1,73 @@
+// 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 STORAGE_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_
+#define STORAGE_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util_proxy.h"
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class Time;
+}
+
+namespace storage {
+
+// 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 STORAGE_EXPORT_PRIVATE NativeFileUtil {
+ public:
+ enum CopyOrMoveMode {
+ COPY_NOSYNC,
+ COPY_SYNC,
+ MOVE
+ };
+ static CopyOrMoveMode CopyOrMoveModeForDestination(
+ const FileSystemURL& dest_url, bool copy);
+
+ static base::File CreateOrOpen(const base::FilePath& path, int file_flags);
+ static base::File::Error EnsureFileExists(const base::FilePath& path,
+ bool* created);
+ static base::File::Error CreateDirectory(const base::FilePath& path,
+ bool exclusive,
+ bool recursive);
+ static base::File::Error GetFileInfo(const base::FilePath& path,
+ base::File::Info* file_info);
+ static scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator>
+ CreateFileEnumerator(const base::FilePath& root_path,
+ bool recursive);
+ static base::File::Error Touch(const base::FilePath& path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time);
+ static base::File::Error Truncate(const base::FilePath& path,
+ int64 length);
+ static bool PathExists(const base::FilePath& path);
+ static bool DirectoryExists(const base::FilePath& path);
+ static base::File::Error CopyOrMoveFile(
+ const base::FilePath& src_path,
+ const base::FilePath& dest_path,
+ FileSystemOperation::CopyOrMoveOption option,
+ CopyOrMoveMode mode);
+ static base::File::Error DeleteFile(const base::FilePath& path);
+ static base::File::Error DeleteDirectory(const base::FilePath& path);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(NativeFileUtil);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_NATIVE_FILE_UTIL_H_
diff --git a/storage/browser/fileapi/obfuscated_file_util.cc b/storage/browser/fileapi/obfuscated_file_util.cc
new file mode 100644
index 0000000..8c02dd1
--- /dev/null
+++ b/storage/browser/fileapi/obfuscated_file_util.cc
@@ -0,0 +1,1424 @@
+// 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 "storage/browser/fileapi/obfuscated_file_util.h"
+
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "base/files/file_util.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.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 "storage/browser/fileapi/file_observers.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/native_file_util.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend.h"
+#include "storage/browser/fileapi/sandbox_isolated_origin_database.h"
+#include "storage/browser/fileapi/sandbox_origin_database.h"
+#include "storage/browser/fileapi/sandbox_prioritized_origin_database.h"
+#include "storage/browser/fileapi/timed_task_helper.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/common/database/database_identifier.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "url/gurl.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 storage {
+
+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() == storage::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();
+}
+
+enum IsolatedOriginStatus {
+ kIsolatedOriginMatch,
+ kIsolatedOriginDontMatch,
+ kIsolatedOriginStatusMax,
+};
+
+} // namespace
+
+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),
+ root_url_(root_url),
+ 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::File::Error error =
+ obfuscated_file_util_->GetFileInfoInternal(
+ db_, context_, root_url_, current_file_id_,
+ &file_info, &current_platform_file_info_, &platform_file_path);
+ if (error != base::File::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_;
+ FileSystemURL root_url_;
+ bool recursive_;
+
+ std::queue<FileRecord> recurse_queue_;
+ std::vector<FileId> display_stack_;
+ base::FilePath current_parent_virtual_path_;
+
+ FileId current_file_id_;
+ base::File::Info 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 storage::GetOriginFromIdentifier(record.origin);
+ }
+
+ // Returns the current origin's information.
+ virtual bool HasTypeDirectory(const std::string& type_string) const OVERRIDE {
+ if (current_.path.empty())
+ return false;
+ if (type_string.empty()) {
+ NOTREACHED();
+ return false;
+ }
+ base::FilePath path =
+ base_file_path_.Append(current_.path).AppendASCII(type_string);
+ return base::DirectoryExists(path);
+ }
+
+ private:
+ std::vector<OriginRecord> origins_;
+ OriginRecord current_;
+ base::FilePath base_file_path_;
+};
+
+ObfuscatedFileUtil::ObfuscatedFileUtil(
+ storage::SpecialStoragePolicy* special_storage_policy,
+ const base::FilePath& file_system_directory,
+ leveldb::Env* env_override,
+ base::SequencedTaskRunner* file_task_runner,
+ const GetTypeStringForURLCallback& get_type_string_for_url,
+ const std::set<std::string>& known_type_strings,
+ SandboxFileSystemBackendDelegate* sandbox_delegate)
+ : special_storage_policy_(special_storage_policy),
+ file_system_directory_(file_system_directory),
+ env_override_(env_override),
+ db_flush_delay_seconds_(10 * 60), // 10 mins.
+ file_task_runner_(file_task_runner),
+ get_type_string_for_url_(get_type_string_for_url),
+ known_type_strings_(known_type_strings),
+ sandbox_delegate_(sandbox_delegate) {
+}
+
+ObfuscatedFileUtil::~ObfuscatedFileUtil() {
+ DropDatabases();
+}
+
+base::File ObfuscatedFileUtil::CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, int file_flags) {
+ base::File file = CreateOrOpenInternal(context, url, file_flags);
+ if (file.IsValid() && file_flags & base::File::FLAG_WRITE &&
+ context->quota_limit_type() == storage::kQuotaLimitTypeUnlimited &&
+ sandbox_delegate_) {
+ sandbox_delegate_->StickyInvalidateUsageCache(url.origin(), url.type());
+ }
+ return file.Pass();
+}
+
+base::File::Error ObfuscatedFileUtil::EnsureFileExists(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool* created) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true);
+ if (!db)
+ return base::File::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::File::FILE_ERROR_FAILED;
+ }
+ if (file_info.is_directory())
+ return base::File::FILE_ERROR_NOT_A_FILE;
+ if (created)
+ *created = false;
+ return base::File::FILE_OK;
+ }
+ FileId parent_id;
+ if (!db->GetFileWithPath(VirtualPath::DirName(url.path()), &parent_id))
+ return base::File::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::File::FILE_ERROR_NO_SPACE;
+ base::File::Error error = CreateFile(context, base::FilePath(), url,
+ &file_info);
+ if (created && base::File::FILE_OK == error) {
+ *created = true;
+ UpdateUsage(context, url, growth);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnCreateFile, MakeTuple(url));
+ }
+ return error;
+}
+
+base::File::Error ObfuscatedFileUtil::CreateDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true);
+ if (!db)
+ return base::File::FILE_ERROR_FAILED;
+
+ FileId file_id;
+ if (db->GetFileWithPath(url.path(), &file_id)) {
+ FileInfo file_info;
+ if (exclusive)
+ return base::File::FILE_ERROR_EXISTS;
+ if (!db->GetFileInfo(file_id, &file_info)) {
+ NOTREACHED();
+ return base::File::FILE_ERROR_FAILED;
+ }
+ if (!file_info.is_directory())
+ return base::File::FILE_ERROR_NOT_A_DIRECTORY;
+ return base::File::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 (!db->IsDirectory(parent_id))
+ return base::File::FILE_ERROR_NOT_A_DIRECTORY;
+ if (!recursive && components.size() - index > 1)
+ return base::File::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::File::FILE_ERROR_NO_SPACE;
+ base::File::Error error = db->AddFileInfo(file_info, &parent_id);
+ if (error != base::File::FILE_OK)
+ return error;
+ UpdateUsage(context, url, growth);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnCreateDirectory, MakeTuple(url));
+ if (first) {
+ first = false;
+ TouchDirectory(db, file_info.parent_id);
+ }
+ }
+ return base::File::FILE_OK;
+}
+
+base::File::Error ObfuscatedFileUtil::GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Info* file_info,
+ base::FilePath* platform_file_path) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, false);
+ if (!db)
+ return base::File::FILE_ERROR_NOT_FOUND;
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::File::FILE_ERROR_NOT_FOUND;
+ FileInfo local_info;
+ return GetFileInfoInternal(db, context, url,
+ 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 */);
+}
+
+base::File::Error ObfuscatedFileUtil::GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::FilePath* local_path) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, false);
+ if (!db)
+ return base::File::FILE_ERROR_NOT_FOUND;
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::File::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::File::FILE_ERROR_NOT_FOUND;
+ }
+ *local_path = DataPathToLocalPath(url, file_info.data_path);
+
+ if (local_path->empty())
+ return base::File::FILE_ERROR_NOT_FOUND;
+ return base::File::FILE_OK;
+}
+
+base::File::Error ObfuscatedFileUtil::Touch(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, false);
+ if (!db)
+ return base::File::FILE_ERROR_NOT_FOUND;
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::File::FILE_ERROR_NOT_FOUND;
+
+ FileInfo file_info;
+ if (!db->GetFileInfo(file_id, &file_info)) {
+ NOTREACHED();
+ return base::File::FILE_ERROR_FAILED;
+ }
+ if (file_info.is_directory()) {
+ if (!db->UpdateModificationTime(file_id, last_modified_time))
+ return base::File::FILE_ERROR_FAILED;
+ return base::File::FILE_OK;
+ }
+ return NativeFileUtil::Touch(
+ DataPathToLocalPath(url, file_info.data_path),
+ last_access_time, last_modified_time);
+}
+
+base::File::Error ObfuscatedFileUtil::Truncate(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 length) {
+ base::File::Info file_info;
+ base::FilePath local_path;
+ base::File::Error error =
+ GetFileInfo(context, url, &file_info, &local_path);
+ if (error != base::File::FILE_OK)
+ return error;
+
+ int64 growth = length - file_info.size;
+ if (!AllocateQuota(context, growth))
+ return base::File::FILE_ERROR_NO_SPACE;
+ error = NativeFileUtil::Truncate(local_path, length);
+ if (error == base::File::FILE_OK) {
+ UpdateUsage(context, url, growth);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnModifyFile, MakeTuple(url));
+ }
+ return error;
+}
+
+base::File::Error ObfuscatedFileUtil::CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ 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, true);
+ if (!db)
+ return base::File::FILE_ERROR_FAILED;
+
+ FileId src_file_id;
+ if (!db->GetFileWithPath(src_url.path(), &src_file_id))
+ return base::File::FILE_ERROR_NOT_FOUND;
+
+ FileId dest_file_id;
+ bool overwrite = db->GetFileWithPath(dest_url.path(),
+ &dest_file_id);
+
+ FileInfo src_file_info;
+ base::File::Info src_platform_file_info;
+ base::FilePath src_local_path;
+ base::File::Error error = GetFileInfoInternal(
+ db, context, src_url, src_file_id,
+ &src_file_info, &src_platform_file_info, &src_local_path);
+ if (error != base::File::FILE_OK)
+ return error;
+ if (src_file_info.is_directory())
+ return base::File::FILE_ERROR_NOT_A_FILE;
+
+ FileInfo dest_file_info;
+ base::File::Info dest_platform_file_info; // overwrite case only
+ base::FilePath dest_local_path; // overwrite case only
+ if (overwrite) {
+ base::File::Error error = GetFileInfoInternal(
+ db, context, dest_url, dest_file_id,
+ &dest_file_info, &dest_platform_file_info, &dest_local_path);
+ if (error == base::File::FILE_ERROR_NOT_FOUND)
+ overwrite = false; // fallback to non-overwrite case
+ else if (error != base::File::FILE_OK)
+ return error;
+ else if (dest_file_info.is_directory())
+ return base::File::FILE_ERROR_INVALID_OPERATION;
+ }
+ if (!overwrite) {
+ FileId dest_parent_id;
+ if (!db->GetFileWithPath(VirtualPath::DirName(dest_url.path()),
+ &dest_parent_id)) {
+ return base::File::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::File::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::File::FILE_ERROR_FAILED;
+ if (copy) {
+ if (overwrite) {
+ error = NativeFileUtil::CopyOrMoveFile(
+ src_local_path,
+ dest_local_path,
+ option,
+ storage::NativeFileUtil::CopyOrMoveModeForDestination(
+ dest_url, true /* copy */));
+ } else { // non-overwrite
+ error = CreateFile(context, src_local_path, dest_url, &dest_file_info);
+ }
+ } else {
+ if (overwrite) {
+ if (db->OverwritingMoveFile(src_file_id, dest_file_id)) {
+ if (base::File::FILE_OK !=
+ NativeFileUtil::DeleteFile(dest_local_path))
+ LOG(WARNING) << "Leaked a backing file.";
+ error = base::File::FILE_OK;
+ } else {
+ error = base::File::FILE_ERROR_FAILED;
+ }
+ } else { // non-overwrite
+ if (db->UpdateFileInfo(src_file_id, dest_file_info))
+ error = base::File::FILE_OK;
+ else
+ error = base::File::FILE_ERROR_FAILED;
+ }
+ }
+
+ if (error != base::File::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;
+}
+
+base::File::Error ObfuscatedFileUtil::CopyInForeignFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(dest_url, true);
+ if (!db)
+ return base::File::FILE_ERROR_FAILED;
+
+ base::File::Info src_platform_file_info;
+ if (!base::GetFileInfo(src_file_path, &src_platform_file_info))
+ return base::File::FILE_ERROR_NOT_FOUND;
+
+ FileId dest_file_id;
+ bool overwrite = db->GetFileWithPath(dest_url.path(),
+ &dest_file_id);
+
+ FileInfo dest_file_info;
+ base::File::Info dest_platform_file_info; // overwrite case only
+ if (overwrite) {
+ base::FilePath dest_local_path;
+ base::File::Error error = GetFileInfoInternal(
+ db, context, dest_url, dest_file_id,
+ &dest_file_info, &dest_platform_file_info, &dest_local_path);
+ if (error == base::File::FILE_ERROR_NOT_FOUND)
+ overwrite = false; // fallback to non-overwrite case
+ else if (error != base::File::FILE_OK)
+ return error;
+ else if (dest_file_info.is_directory())
+ return base::File::FILE_ERROR_INVALID_OPERATION;
+ }
+ if (!overwrite) {
+ FileId dest_parent_id;
+ if (!db->GetFileWithPath(VirtualPath::DirName(dest_url.path()),
+ &dest_parent_id)) {
+ return base::File::FILE_ERROR_NOT_FOUND;
+ }
+ if (!dest_file_info.is_directory())
+ return base::File::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::File::FILE_ERROR_NO_SPACE;
+
+ base::File::Error error;
+ if (overwrite) {
+ base::FilePath dest_local_path =
+ DataPathToLocalPath(dest_url, dest_file_info.data_path);
+ error = NativeFileUtil::CopyOrMoveFile(
+ src_file_path,
+ dest_local_path,
+ FileSystemOperation::OPTION_NONE,
+ storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url,
+ true /* copy */));
+ } else {
+ error = CreateFile(context, src_file_path, dest_url, &dest_file_info);
+ }
+
+ if (error != base::File::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::File::FILE_OK;
+}
+
+base::File::Error ObfuscatedFileUtil::DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true);
+ if (!db)
+ return base::File::FILE_ERROR_FAILED;
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::File::FILE_ERROR_NOT_FOUND;
+
+ FileInfo file_info;
+ base::File::Info platform_file_info;
+ base::FilePath local_path;
+ base::File::Error error = GetFileInfoInternal(
+ db, context, url, file_id, &file_info, &platform_file_info, &local_path);
+ if (error != base::File::FILE_ERROR_NOT_FOUND &&
+ error != base::File::FILE_OK)
+ return error;
+
+ if (file_info.is_directory())
+ return base::File::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::File::FILE_ERROR_FAILED;
+ }
+ UpdateUsage(context, url, growth);
+ TouchDirectory(db, file_info.parent_id);
+
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnRemoveFile, MakeTuple(url));
+
+ if (error == base::File::FILE_ERROR_NOT_FOUND)
+ return base::File::FILE_OK;
+
+ error = NativeFileUtil::DeleteFile(local_path);
+ if (base::File::FILE_OK != error)
+ LOG(WARNING) << "Leaked a backing file.";
+ return base::File::FILE_OK;
+}
+
+base::File::Error ObfuscatedFileUtil::DeleteDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true);
+ if (!db)
+ return base::File::FILE_ERROR_FAILED;
+
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id))
+ return base::File::FILE_ERROR_NOT_FOUND;
+ FileInfo file_info;
+ if (!db->GetFileInfo(file_id, &file_info)) {
+ NOTREACHED();
+ return base::File::FILE_ERROR_FAILED;
+ }
+ if (!file_info.is_directory())
+ return base::File::FILE_ERROR_NOT_A_DIRECTORY;
+ if (!db->RemoveFileInfo(file_id))
+ return base::File::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::File::FILE_OK;
+}
+
+storage::ScopedFile ObfuscatedFileUtil::CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Error* error,
+ base::File::Info* 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::File::FILE_OK && file_info->is_directory) {
+ *file_info = base::File::Info();
+ *error = base::File::FILE_ERROR_NOT_A_FILE;
+ }
+ return storage::ScopedFile();
+}
+
+scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator>
+ ObfuscatedFileUtil::CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url,
+ bool recursive) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(root_url, 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, 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,
+ const std::string& type_string,
+ bool create,
+ base::File::Error* error_code) {
+ base::FilePath origin_dir = GetDirectoryForOrigin(origin, create, error_code);
+ if (origin_dir.empty())
+ return base::FilePath();
+ if (type_string.empty())
+ return origin_dir;
+ base::FilePath path = origin_dir.AppendASCII(type_string);
+ base::File::Error error = base::File::FILE_OK;
+ if (!base::DirectoryExists(path) &&
+ (!create || !base::CreateDirectory(path))) {
+ error = create ?
+ base::File::FILE_ERROR_FAILED :
+ base::File::FILE_ERROR_NOT_FOUND;
+ }
+
+ if (error_code)
+ *error_code = error;
+ return path;
+}
+
+bool ObfuscatedFileUtil::DeleteDirectoryForOriginAndType(
+ const GURL& origin,
+ const std::string& type_string) {
+ base::File::Error error = base::File::FILE_OK;
+ base::FilePath origin_type_path = GetDirectoryForOriginAndType(
+ origin, type_string, false, &error);
+ if (origin_type_path.empty())
+ return true;
+ if (error != base::File::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.
+ if (!type_string.empty())
+ DestroyDirectoryDatabase(origin, type_string);
+ 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());
+
+ if (!type_string.empty()) {
+ // 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.
+ for (std::set<std::string>::iterator iter = known_type_strings_.begin();
+ iter != known_type_strings_.end();
+ ++iter) {
+ if (*iter == type_string)
+ continue;
+ if (base::DirectoryExists(origin_path.AppendASCII(*iter))) {
+ // Other type's directory exists; just return true here.
+ return true;
+ }
+ }
+ }
+
+ // No other directories seem exist. Try deleting the entire origin directory.
+ InitOriginDatabase(origin, false);
+ if (origin_database_) {
+ origin_database_->RemovePathForOrigin(
+ storage::GetIdentifierFromOrigin(origin));
+ }
+ if (!base::DeleteFile(origin_path, true /* recursive */))
+ return false;
+
+ return true;
+}
+
+ObfuscatedFileUtil::AbstractOriginEnumerator*
+ObfuscatedFileUtil::CreateOriginEnumerator() {
+ std::vector<SandboxOriginDatabase::OriginRecord> origins;
+
+ InitOriginDatabase(GURL(), false);
+ return new ObfuscatedOriginEnumerator(
+ origin_database_.get(), file_system_directory_);
+}
+
+bool ObfuscatedFileUtil::DestroyDirectoryDatabase(
+ const GURL& origin,
+ const std::string& type_string) {
+ std::string key = GetDirectoryDatabaseKey(origin, type_string);
+ if (key.empty())
+ return true;
+ DirectoryMap::iterator iter = directories_.find(key);
+ if (iter != directories_.end()) {
+ SandboxDirectoryDatabase* database = iter->second;
+ directories_.erase(iter);
+ delete database;
+ }
+
+ base::File::Error error = base::File::FILE_OK;
+ base::FilePath path = GetDirectoryForOriginAndType(
+ origin, type_string, false, &error);
+ if (path.empty() || error == base::File::FILE_ERROR_NOT_FOUND)
+ return true;
+ return SandboxDirectoryDatabase::DestroyDatabase(path, env_override_);
+}
+
+// static
+int64 ObfuscatedFileUtil::ComputeFilePathCost(const base::FilePath& path) {
+ return UsageForPath(VirtualPath::BaseName(path).value().size());
+}
+
+void ObfuscatedFileUtil::MaybePrepopulateDatabase(
+ const std::vector<std::string>& type_strings_to_prepopulate) {
+ SandboxPrioritizedOriginDatabase database(file_system_directory_,
+ env_override_);
+ std::string origin_string = database.GetPrimaryOrigin();
+ if (origin_string.empty() || !database.HasOriginPath(origin_string))
+ return;
+ const GURL origin = storage::GetOriginFromIdentifier(origin_string);
+
+ // Prepopulate the directory database(s) if and only if this instance
+ // has primary origin and the directory database is already there.
+ for (size_t i = 0; i < type_strings_to_prepopulate.size(); ++i) {
+ const std::string type_string = type_strings_to_prepopulate[i];
+ // Only handles known types.
+ if (!ContainsKey(known_type_strings_, type_string))
+ continue;
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ base::FilePath path = GetDirectoryForOriginAndType(
+ origin, type_string, false, &error);
+ if (error != base::File::FILE_OK)
+ continue;
+ scoped_ptr<SandboxDirectoryDatabase> db(
+ new SandboxDirectoryDatabase(path, env_override_));
+ if (db->Init(SandboxDirectoryDatabase::FAIL_ON_CORRUPTION)) {
+ directories_[GetDirectoryDatabaseKey(origin, type_string)] = db.release();
+ MarkUsed();
+ // Don't populate more than one database, as it may rather hurt
+ // performance.
+ break;
+ }
+ }
+}
+
+base::FilePath ObfuscatedFileUtil::GetDirectoryForURL(
+ const FileSystemURL& url,
+ bool create,
+ base::File::Error* error_code) {
+ return GetDirectoryForOriginAndType(
+ url.origin(), CallGetTypeStringForURL(url), create, error_code);
+}
+
+std::string ObfuscatedFileUtil::CallGetTypeStringForURL(
+ const FileSystemURL& url) {
+ DCHECK(!get_type_string_for_url_.is_null());
+ return get_type_string_for_url_.Run(url);
+}
+
+base::File::Error ObfuscatedFileUtil::GetFileInfoInternal(
+ SandboxDirectoryDatabase* db,
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ FileId file_id,
+ FileInfo* local_info,
+ base::File::Info* 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::File::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::File::FILE_OK;
+ }
+ if (local_info->data_path.empty())
+ return base::File::FILE_ERROR_INVALID_OPERATION;
+ base::FilePath local_path = DataPathToLocalPath(url, local_info->data_path);
+ base::File::Error error = NativeFileUtil::GetFileInfo(
+ local_path, file_info);
+ // We should not follow symbolic links in sandboxed file system.
+ if (base::IsLink(local_path)) {
+ LOG(WARNING) << "Found a symbolic file.";
+ error = base::File::FILE_ERROR_NOT_FOUND;
+ }
+ if (error == base::File::FILE_OK) {
+ *platform_file_path = local_path;
+ } else if (error == base::File::FILE_ERROR_NOT_FOUND) {
+ LOG(WARNING) << "Lost a backing file.";
+ InvalidateUsageCache(context, url.origin(), url.type());
+ if (!db->RemoveFileInfo(file_id))
+ return base::File::FILE_ERROR_FAILED;
+ }
+ return error;
+}
+
+base::File ObfuscatedFileUtil::CreateAndOpenFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& dest_url,
+ FileInfo* dest_file_info, int file_flags) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(dest_url, true);
+
+ base::FilePath root, dest_local_path;
+ base::File::Error error = GenerateNewLocalPath(db, context, dest_url, &root,
+ &dest_local_path);
+ if (error != base::File::FILE_OK)
+ return base::File(error);
+
+ if (base::PathExists(dest_local_path)) {
+ if (!base::DeleteFile(dest_local_path, true /* recursive */))
+ return base::File(base::File::FILE_ERROR_FAILED);
+ LOG(WARNING) << "A stray file detected";
+ InvalidateUsageCache(context, dest_url.origin(), dest_url.type());
+ }
+
+ base::File file = NativeFileUtil::CreateOrOpen(dest_local_path, file_flags);
+ if (!file.IsValid())
+ return file.Pass();
+
+ if (!file.created()) {
+ file.Close();
+ base::DeleteFile(dest_local_path, false /* recursive */);
+ return base::File(base::File::FILE_ERROR_FAILED);
+ }
+
+ error = CommitCreateFile(root, dest_local_path, db, dest_file_info);
+ if (error != base::File::FILE_OK) {
+ file.Close();
+ base::DeleteFile(dest_local_path, false /* recursive */);
+ return base::File(error);
+ }
+
+ return file.Pass();
+}
+
+base::File::Error ObfuscatedFileUtil::CreateFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url,
+ FileInfo* dest_file_info) {
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(dest_url, true);
+
+ base::FilePath root, dest_local_path;
+ base::File::Error error = GenerateNewLocalPath(db, context, dest_url, &root,
+ &dest_local_path);
+ if (error != base::File::FILE_OK)
+ return error;
+
+ bool created = false;
+ if (src_file_path.empty()) {
+ if (base::PathExists(dest_local_path)) {
+ if (!base::DeleteFile(dest_local_path, true /* recursive */))
+ return base::File::FILE_ERROR_FAILED;
+ LOG(WARNING) << "A stray file detected";
+ InvalidateUsageCache(context, dest_url.origin(), dest_url.type());
+ }
+
+ error = NativeFileUtil::EnsureFileExists(dest_local_path, &created);
+ } else {
+ error = NativeFileUtil::CopyOrMoveFile(
+ src_file_path,
+ dest_local_path,
+ FileSystemOperation::OPTION_NONE,
+ storage::NativeFileUtil::CopyOrMoveModeForDestination(dest_url,
+ true /* copy */));
+ created = true;
+ }
+ if (error != base::File::FILE_OK)
+ return error;
+ if (!created)
+ return base::File::FILE_ERROR_FAILED;
+
+ return CommitCreateFile(root, dest_local_path, db, dest_file_info);
+}
+
+base::File::Error ObfuscatedFileUtil::CommitCreateFile(
+ const base::FilePath& root,
+ const base::FilePath& local_path,
+ SandboxDirectoryDatabase* db,
+ FileInfo* dest_file_info) {
+ // This removes the root, including the trailing slash, leaving a relative
+ // path.
+ dest_file_info->data_path = base::FilePath(
+ local_path.value().substr(root.value().length() + 1));
+
+ FileId file_id;
+ base::File::Error error = db->AddFileInfo(*dest_file_info, &file_id);
+ if (error != base::File::FILE_OK)
+ return error;
+
+ TouchDirectory(db, dest_file_info->parent_id);
+ return base::File::FILE_OK;
+}
+
+base::FilePath ObfuscatedFileUtil::DataPathToLocalPath(
+ const FileSystemURL& url, const base::FilePath& data_path) {
+ base::File::Error error = base::File::FILE_OK;
+ base::FilePath root = GetDirectoryForURL(url, false, &error);
+ if (error != base::File::FILE_OK)
+ return base::FilePath();
+ return root.Append(data_path);
+}
+
+std::string ObfuscatedFileUtil::GetDirectoryDatabaseKey(
+ const GURL& origin, const std::string& type_string) {
+ if (type_string.empty()) {
+ LOG(WARNING) << "Unknown filesystem type requested:" << type_string;
+ return std::string();
+ }
+ // For isolated origin we just use a type string as a key.
+ return storage::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 FileSystemURL& url, bool create) {
+ std::string key = GetDirectoryDatabaseKey(
+ url.origin(), CallGetTypeStringForURL(url));
+ if (key.empty())
+ return NULL;
+
+ DirectoryMap::iterator iter = directories_.find(key);
+ if (iter != directories_.end()) {
+ MarkUsed();
+ return iter->second;
+ }
+
+ base::File::Error error = base::File::FILE_OK;
+ base::FilePath path = GetDirectoryForURL(url, create, &error);
+ if (error != base::File::FILE_OK) {
+ LOG(WARNING) << "Failed to get origin+type directory: "
+ << url.DebugString() << " error:" << error;
+ return NULL;
+ }
+ MarkUsed();
+ SandboxDirectoryDatabase* database =
+ new SandboxDirectoryDatabase(path, env_override_);
+ directories_[key] = database;
+ return database;
+}
+
+base::FilePath ObfuscatedFileUtil::GetDirectoryForOrigin(
+ const GURL& origin, bool create, base::File::Error* error_code) {
+ if (!InitOriginDatabase(origin, create)) {
+ if (error_code) {
+ *error_code = create ?
+ base::File::FILE_ERROR_FAILED :
+ base::File::FILE_ERROR_NOT_FOUND;
+ }
+ return base::FilePath();
+ }
+ base::FilePath directory_name;
+ std::string id = storage::GetIdentifierFromOrigin(origin);
+
+ bool exists_in_db = origin_database_->HasOriginPath(id);
+ if (!exists_in_db && !create) {
+ if (error_code)
+ *error_code = base::File::FILE_ERROR_NOT_FOUND;
+ return base::FilePath();
+ }
+ if (!origin_database_->GetPathForOrigin(id, &directory_name)) {
+ if (error_code)
+ *error_code = base::File::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::File::FILE_ERROR_FAILED;
+ return base::FilePath();
+ }
+ exists_in_fs = false;
+ }
+
+ if (!exists_in_fs) {
+ if (!create || !base::CreateDirectory(path)) {
+ if (error_code)
+ *error_code = create ?
+ base::File::FILE_ERROR_FAILED :
+ base::File::FILE_ERROR_NOT_FOUND;
+ return base::FilePath();
+ }
+ }
+
+ if (error_code)
+ *error_code = base::File::FILE_OK;
+
+ return path;
+}
+
+void ObfuscatedFileUtil::InvalidateUsageCache(
+ FileSystemOperationContext* context,
+ const GURL& origin,
+ FileSystemType type) {
+ if (sandbox_delegate_)
+ 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(const GURL& origin_hint,
+ bool create) {
+ if (origin_database_)
+ return true;
+
+ if (!create && !base::DirectoryExists(file_system_directory_))
+ return false;
+ if (!base::CreateDirectory(file_system_directory_)) {
+ LOG(WARNING) << "Failed to create FileSystem directory: " <<
+ file_system_directory_.value();
+ return false;
+ }
+
+ SandboxPrioritizedOriginDatabase* prioritized_origin_database =
+ new SandboxPrioritizedOriginDatabase(file_system_directory_,
+ env_override_);
+ origin_database_.reset(prioritized_origin_database);
+
+ if (origin_hint.is_empty() || !HasIsolatedStorage(origin_hint))
+ return true;
+
+ const std::string isolated_origin_string =
+ storage::GetIdentifierFromOrigin(origin_hint);
+
+ // TODO(kinuko): Deprecate this after a few release cycles, e.g. around M33.
+ base::FilePath isolated_origin_dir = file_system_directory_.Append(
+ SandboxIsolatedOriginDatabase::kObsoleteOriginDirectory);
+ if (base::DirectoryExists(isolated_origin_dir) &&
+ prioritized_origin_database->GetSandboxOriginDatabase()) {
+ SandboxIsolatedOriginDatabase::MigrateBackFromObsoleteOriginDatabase(
+ isolated_origin_string,
+ file_system_directory_,
+ prioritized_origin_database->GetSandboxOriginDatabase());
+ }
+
+ prioritized_origin_database->InitializePrimaryOrigin(
+ isolated_origin_string);
+
+ return true;
+}
+
+base::File::Error ObfuscatedFileUtil::GenerateNewLocalPath(
+ SandboxDirectoryDatabase* db,
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::FilePath* root,
+ base::FilePath* local_path) {
+ DCHECK(local_path);
+ int64 number;
+ if (!db || !db->GetNextInteger(&number))
+ return base::File::FILE_ERROR_FAILED;
+
+ base::File::Error error = base::File::FILE_OK;
+ *root = GetDirectoryForURL(url, false, &error);
+ if (error != base::File::FILE_OK)
+ return error;
+
+ // We use the third- and fourth-to-last digits as the directory.
+ int64 directory_number = number % 10000 / 100;
+ base::FilePath new_local_path = root->AppendASCII(
+ base::StringPrintf("%02" PRId64, directory_number));
+
+ error = NativeFileUtil::CreateDirectory(
+ new_local_path, false /* exclusive */, false /* recursive */);
+ if (error != base::File::FILE_OK)
+ return error;
+
+ *local_path =
+ new_local_path.AppendASCII(base::StringPrintf("%08" PRId64, number));
+ return base::File::FILE_OK;
+}
+
+base::File ObfuscatedFileUtil::CreateOrOpenInternal(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, int file_flags) {
+ DCHECK(!(file_flags & (base::File::FLAG_DELETE_ON_CLOSE |
+ base::File::FLAG_HIDDEN | base::File::FLAG_EXCLUSIVE_READ |
+ base::File::FLAG_EXCLUSIVE_WRITE)));
+ SandboxDirectoryDatabase* db = GetDirectoryDatabase(url, true);
+ if (!db)
+ return base::File(base::File::FILE_ERROR_FAILED);
+ FileId file_id;
+ if (!db->GetFileWithPath(url.path(), &file_id)) {
+ // The file doesn't exist.
+ if (!(file_flags & (base::File::FLAG_CREATE |
+ base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_OPEN_ALWAYS))) {
+ return base::File(base::File::FILE_ERROR_NOT_FOUND);
+ }
+ FileId parent_id;
+ if (!db->GetFileWithPath(VirtualPath::DirName(url.path()), &parent_id))
+ return base::File(base::File::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::File(base::File::FILE_ERROR_NO_SPACE);
+ base::File file = CreateAndOpenFile(context, url, &file_info, file_flags);
+ if (file.IsValid()) {
+ UpdateUsage(context, url, growth);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnCreateFile, MakeTuple(url));
+ }
+ return file.Pass();
+ }
+
+ if (file_flags & base::File::FLAG_CREATE)
+ return base::File(base::File::FILE_ERROR_EXISTS);
+
+ base::File::Info platform_file_info;
+ base::FilePath local_path;
+ FileInfo file_info;
+ base::File::Error error = GetFileInfoInternal(
+ db, context, url, file_id, &file_info, &platform_file_info, &local_path);
+ if (error != base::File::FILE_OK)
+ return base::File(error);
+ if (file_info.is_directory())
+ return base::File(base::File::FILE_ERROR_NOT_A_FILE);
+
+ int64 delta = 0;
+ if (file_flags & (base::File::FLAG_CREATE_ALWAYS |
+ base::File::FLAG_OPEN_TRUNCATED)) {
+ // The file exists and we're truncating.
+ delta = -platform_file_info.size;
+ AllocateQuota(context, delta);
+ }
+
+ base::File file = NativeFileUtil::CreateOrOpen(local_path, file_flags);
+ if (!file.IsValid()) {
+ error = file.error_details();
+ if (error == base::File::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.";
+ return base::File(base::File::FILE_ERROR_FAILED);
+ }
+ return file.Pass();
+ }
+
+ // If truncating we need to update the usage.
+ if (delta) {
+ UpdateUsage(context, url, delta);
+ context->change_observers()->Notify(
+ &FileChangeObserver::OnModifyFile, MakeTuple(url));
+ }
+ return file.Pass();
+}
+
+bool ObfuscatedFileUtil::HasIsolatedStorage(const GURL& origin) {
+ return special_storage_policy_.get() &&
+ special_storage_policy_->HasIsolatedStorage(origin);
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/obfuscated_file_util.h b/storage/browser/fileapi/obfuscated_file_util.h
new file mode 100644
index 0000000..822a85d
--- /dev/null
+++ b/storage/browser/fileapi/obfuscated_file_util.h
@@ -0,0 +1,359 @@
+// 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 STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_
+#define STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+#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 "storage/browser/fileapi/file_system_file_util.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/sandbox_directory_database.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/blob/shareable_file_reference.h"
+#include "storage/common/fileapi/file_system_types.h"
+
+namespace base {
+class SequencedTaskRunner;
+class TimeTicks;
+}
+
+namespace content {
+class ObfuscatedFileUtilTest;
+class QuotaBackendImplTest;
+}
+
+namespace storage {
+class SpecialStoragePolicy;
+}
+
+class GURL;
+
+namespace storage {
+
+class FileSystemOperationContext;
+class SandboxOriginDatabaseInterface;
+class TimedTaskHelper;
+
+// This file util stores directory information in LevelDB to obfuscate
+// and to neutralize virtual file paths given by arbitrary apps.
+// Files are stored with two-level isolation: per-origin and per-type.
+// The isolation is done by storing data in separate directory partitions.
+// For example, a file in Temporary file system for origin 'www.example.com'
+// is stored in a different partition for a file in Persistent file system
+// for the same origin, or for Temporary file system for another origin.
+//
+// * Per-origin directory name information is stored in a separate LevelDB,
+// which is maintained by SandboxOriginDatabase.
+// * Per-type directory name information is given by
+// GetTypeStringForURLCallback that is given in CTOR.
+// We use a small static mapping (e.g. 't' for Temporary type) for
+// regular sandbox filesystems.
+//
+// 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 STORAGE_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.
+ // |type_string| must be ascii string.
+ virtual bool HasTypeDirectory(const std::string& type_string) const = 0;
+ };
+
+ typedef base::Callback<std::string(const FileSystemURL&)>
+ GetTypeStringForURLCallback;
+
+ // |get_type_string_for_url| is user-defined callback that should return
+ // a type string for the given FileSystemURL. The type string is used
+ // to provide per-type isolation in the sandboxed filesystem directory.
+ // Note that this method is called on file_task_runner.
+ //
+ // |known_type_strings| are known type string names that this file system
+ // should care about.
+ // This info is used to determine whether we could delete the entire
+ // origin directory or not in DeleteDirectoryForOriginAndType. If no directory
+ // for any known type exists the origin directory may get deleted when
+ // one origin/type pair is deleted.
+ //
+ ObfuscatedFileUtil(storage::SpecialStoragePolicy* special_storage_policy,
+ const base::FilePath& file_system_directory,
+ leveldb::Env* env_override,
+ base::SequencedTaskRunner* file_task_runner,
+ const GetTypeStringForURLCallback& get_type_string_for_url,
+ const std::set<std::string>& known_type_strings,
+ SandboxFileSystemBackendDelegate* sandbox_delegate);
+ virtual ~ObfuscatedFileUtil();
+
+ // FileSystemFileUtil overrides.
+ virtual base::File CreateOrOpen(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int file_flags) OVERRIDE;
+ virtual base::File::Error EnsureFileExists(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url, bool* created) OVERRIDE;
+ virtual base::File::Error CreateDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive) OVERRIDE;
+ virtual base::File::Error GetFileInfo(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Info* file_info,
+ base::FilePath* platform_file) OVERRIDE;
+ virtual scoped_ptr<AbstractFileEnumerator> CreateFileEnumerator(
+ FileSystemOperationContext* context,
+ const FileSystemURL& root_url) OVERRIDE;
+ virtual base::File::Error GetLocalFilePath(
+ FileSystemOperationContext* context,
+ const FileSystemURL& file_system_url,
+ base::FilePath* local_path) OVERRIDE;
+ virtual base::File::Error Touch(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) OVERRIDE;
+ virtual base::File::Error Truncate(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int64 length) OVERRIDE;
+ virtual base::File::Error CopyOrMoveFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ CopyOrMoveOption option,
+ bool copy) OVERRIDE;
+ virtual base::File::Error CopyInForeignFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& src_file_path,
+ const FileSystemURL& dest_url) OVERRIDE;
+ virtual base::File::Error DeleteFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) OVERRIDE;
+ virtual base::File::Error DeleteDirectory(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url) OVERRIDE;
+ virtual storage::ScopedFile CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Error* error,
+ base::File::Info* 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 the topmost origin directory if |type_string| is empty.
+ // Returns an empty path if the directory is undefined.
+ // 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,
+ const std::string& type_string,
+ bool create,
+ base::File::Error* error_code);
+
+ // Deletes the topmost directory specific to this origin and type. This will
+ // delete its directory database.
+ // Deletes the topmost origin directory if |type_string| is empty.
+ bool DeleteDirectoryForOriginAndType(
+ const GURL& origin,
+ const std::string& type_string);
+
+ // 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,
+ const std::string& type_string);
+
+ // 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);
+
+ // Tries to prepopulate directory database for the given type strings.
+ // This tries from the first one in the given type_strings and stops
+ // once it succeeds to do so for one database (i.e. it prepopulates
+ // at most one database).
+ void MaybePrepopulateDatabase(
+ const std::vector<std::string>& type_strings_to_prepopulate);
+
+ private:
+ typedef SandboxDirectoryDatabase::FileId FileId;
+ typedef SandboxDirectoryDatabase::FileInfo FileInfo;
+
+ friend class ObfuscatedFileEnumerator;
+ friend class content::ObfuscatedFileUtilTest;
+ friend class content::QuotaBackendImplTest;
+
+ // Helper method to create an obfuscated file util for regular
+ // (temporary, persistent) file systems. Used only for testing.
+ // Note: this is implemented in sandbox_file_system_backend_delegate.cc.
+ static ObfuscatedFileUtil* CreateForTesting(
+ storage::SpecialStoragePolicy* special_storage_policy,
+ const base::FilePath& file_system_directory,
+ leveldb::Env* env_override,
+ base::SequencedTaskRunner* file_task_runner);
+
+ base::FilePath GetDirectoryForURL(
+ const FileSystemURL& url,
+ bool create,
+ base::File::Error* error_code);
+
+ // This just calls get_type_string_for_url_ callback that is given in ctor.
+ std::string CallGetTypeStringForURL(const FileSystemURL& url);
+
+ base::File::Error GetFileInfoInternal(
+ SandboxDirectoryDatabase* db,
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ FileId file_id,
+ FileInfo* local_info,
+ base::File::Info* 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. Returns the new file.
+ base::File CreateAndOpenFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& dest_url,
+ FileInfo* dest_file_info,
+ int file_flags);
+
+ // The same as CreateAndOpenFile except that a file is not returned and if a
+ // path is provided in |source_path|, it will be used as a source from which
+ // to COPY data.
+ base::File::Error CreateFile(
+ FileSystemOperationContext* context,
+ const base::FilePath& source_file_path,
+ const FileSystemURL& dest_url,
+ FileInfo* dest_file_info);
+
+ // Updates |db| and |dest_file_info| at the end of creating a new file.
+ base::File::Error CommitCreateFile(
+ const base::FilePath& root,
+ const base::FilePath& local_path,
+ SandboxDirectoryDatabase* db,
+ FileInfo* dest_file_info);
+
+ // 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 FileSystemURL& url,
+ const base::FilePath& data_file_path);
+
+ std::string GetDirectoryDatabaseKey(const GURL& origin,
+ const std::string& type_string);
+
+ // This returns NULL if |create| flag is false and a filesystem does not
+ // exist for the given |url|.
+ // For read operations |create| should be false.
+ SandboxDirectoryDatabase* GetDirectoryDatabase(const FileSystemURL& url,
+ 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::File::Error* error_code);
+
+ void InvalidateUsageCache(FileSystemOperationContext* context,
+ const GURL& origin,
+ FileSystemType type);
+
+ void MarkUsed();
+ void DropDatabases();
+
+ // Initializes the origin database. |origin_hint| may be used as a hint
+ // for initializing database if it's not empty.
+ bool InitOriginDatabase(const GURL& origin_hint, bool create);
+
+ base::File::Error GenerateNewLocalPath(
+ SandboxDirectoryDatabase* db,
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::FilePath* root,
+ base::FilePath* local_path);
+
+ base::File CreateOrOpenInternal(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ int file_flags);
+
+ bool HasIsolatedStorage(const GURL& origin);
+
+ typedef std::map<std::string, SandboxDirectoryDatabase*> DirectoryMap;
+ DirectoryMap directories_;
+ scoped_ptr<SandboxOriginDatabaseInterface> origin_database_;
+ scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_;
+ base::FilePath file_system_directory_;
+ leveldb::Env* env_override_;
+
+ // 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_;
+
+ GetTypeStringForURLCallback get_type_string_for_url_;
+ std::set<std::string> known_type_strings_;
+
+ // Not owned.
+ SandboxFileSystemBackendDelegate* sandbox_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(ObfuscatedFileUtil);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_OBFUSCATED_FILE_UTIL_H_
diff --git a/storage/browser/fileapi/open_file_system_mode.h b/storage/browser/fileapi/open_file_system_mode.h
new file mode 100644
index 0000000..03943fd
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_
+#define STORAGE_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_
+
+namespace storage {
+
+// 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_OPEN_FILE_SYSTEM_MODE_H_
diff --git a/storage/browser/fileapi/plugin_private_file_system_backend.cc b/storage/browser/fileapi/plugin_private_file_system_backend.cc
new file mode 100644
index 0000000..6906986
--- /dev/null
+++ b/storage/browser/fileapi/plugin_private_file_system_backend.cc
@@ -0,0 +1,311 @@
+// 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 "storage/browser/fileapi/plugin_private_file_system_backend.h"
+
+#include <map>
+
+#include "base/stl_util.h"
+#include "base/synchronization/lock.h"
+#include "base/task_runner_util.h"
+#include "net/base/net_util.h"
+#include "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/async_file_util_adapter.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_options.h"
+#include "storage/browser/fileapi/isolated_context.h"
+#include "storage/browser/fileapi/obfuscated_file_util.h"
+#include "storage/browser/fileapi/quota/quota_reservation.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace storage {
+
+class PluginPrivateFileSystemBackend::FileSystemIDToPluginMap {
+ public:
+ explicit FileSystemIDToPluginMap(base::SequencedTaskRunner* task_runner)
+ : task_runner_(task_runner) {}
+ ~FileSystemIDToPluginMap() {}
+
+ std::string GetPluginIDForURL(const FileSystemURL& url) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ Map::iterator found = map_.find(url.filesystem_id());
+ if (url.type() != kFileSystemTypePluginPrivate || found == map_.end()) {
+ NOTREACHED() << "Unsupported url is given: " << url.DebugString();
+ return std::string();
+ }
+ return found->second;
+ }
+
+ void RegisterFileSystem(const std::string& filesystem_id,
+ const std::string& plugin_id) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(!filesystem_id.empty());
+ DCHECK(!ContainsKey(map_, filesystem_id)) << filesystem_id;
+ map_[filesystem_id] = plugin_id;
+ }
+
+ void RemoveFileSystem(const std::string& filesystem_id) {
+ DCHECK(task_runner_->RunsTasksOnCurrentThread());
+ map_.erase(filesystem_id);
+ }
+
+ private:
+ typedef std::map<std::string, std::string> Map;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_;
+ Map map_;
+};
+
+namespace {
+
+const base::FilePath::CharType* kFileSystemDirectory =
+ SandboxFileSystemBackendDelegate::kFileSystemDirectory;
+const base::FilePath::CharType* kPluginPrivateDirectory =
+ FILE_PATH_LITERAL("Plugins");
+
+base::File::Error OpenFileSystemOnFileTaskRunner(
+ ObfuscatedFileUtil* file_util,
+ PluginPrivateFileSystemBackend::FileSystemIDToPluginMap* plugin_map,
+ const GURL& origin_url,
+ const std::string& filesystem_id,
+ const std::string& plugin_id,
+ OpenFileSystemMode mode) {
+ base::File::Error error = base::File::FILE_ERROR_FAILED;
+ const bool create = (mode == OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT);
+ file_util->GetDirectoryForOriginAndType(
+ origin_url, plugin_id, create, &error);
+ if (error == base::File::FILE_OK)
+ plugin_map->RegisterFileSystem(filesystem_id, plugin_id);
+ return error;
+}
+
+} // namespace
+
+PluginPrivateFileSystemBackend::PluginPrivateFileSystemBackend(
+ base::SequencedTaskRunner* file_task_runner,
+ const base::FilePath& profile_path,
+ storage::SpecialStoragePolicy* special_storage_policy,
+ const FileSystemOptions& file_system_options)
+ : file_task_runner_(file_task_runner),
+ file_system_options_(file_system_options),
+ base_path_(profile_path.Append(kFileSystemDirectory)
+ .Append(kPluginPrivateDirectory)),
+ plugin_map_(new FileSystemIDToPluginMap(file_task_runner)),
+ weak_factory_(this) {
+ file_util_.reset(
+ new AsyncFileUtilAdapter(new ObfuscatedFileUtil(
+ special_storage_policy,
+ base_path_, file_system_options.env_override(),
+ file_task_runner,
+ base::Bind(&FileSystemIDToPluginMap::GetPluginIDForURL,
+ base::Owned(plugin_map_)),
+ std::set<std::string>(),
+ NULL)));
+}
+
+PluginPrivateFileSystemBackend::~PluginPrivateFileSystemBackend() {
+ if (!file_task_runner_->RunsTasksOnCurrentThread()) {
+ AsyncFileUtil* file_util = file_util_.release();
+ if (!file_task_runner_->DeleteSoon(FROM_HERE, file_util))
+ delete file_util;
+ }
+}
+
+void PluginPrivateFileSystemBackend::OpenPrivateFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ const std::string& filesystem_id,
+ const std::string& plugin_id,
+ OpenFileSystemMode mode,
+ const StatusCallback& callback) {
+ if (!CanHandleType(type) || file_system_options_.is_incognito()) {
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE, base::Bind(callback, base::File::FILE_ERROR_SECURITY));
+ return;
+ }
+
+ PostTaskAndReplyWithResult(
+ file_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&OpenFileSystemOnFileTaskRunner,
+ obfuscated_file_util(), plugin_map_,
+ origin_url, filesystem_id, plugin_id, mode),
+ callback);
+}
+
+bool PluginPrivateFileSystemBackend::CanHandleType(FileSystemType type) const {
+ return type == kFileSystemTypePluginPrivate;
+}
+
+void PluginPrivateFileSystemBackend::Initialize(FileSystemContext* context) {
+}
+
+void PluginPrivateFileSystemBackend::ResolveURL(
+ const FileSystemURL& url,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) {
+ // We never allow opening a new plugin-private filesystem via usual
+ // ResolveURL.
+ base::MessageLoopProxy::current()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, GURL(), std::string(),
+ base::File::FILE_ERROR_SECURITY));
+}
+
+AsyncFileUtil*
+PluginPrivateFileSystemBackend::GetAsyncFileUtil(FileSystemType type) {
+ return file_util_.get();
+}
+
+WatcherManager* PluginPrivateFileSystemBackend::GetWatcherManager(
+ FileSystemType type) {
+ return NULL;
+}
+
+CopyOrMoveFileValidatorFactory*
+PluginPrivateFileSystemBackend::GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type,
+ base::File::Error* error_code) {
+ DCHECK(error_code);
+ *error_code = base::File::FILE_OK;
+ return NULL;
+}
+
+FileSystemOperation* PluginPrivateFileSystemBackend::CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::File::Error* error_code) const {
+ scoped_ptr<FileSystemOperationContext> operation_context(
+ new FileSystemOperationContext(context));
+ return FileSystemOperation::Create(url, context, operation_context.Pass());
+}
+
+bool PluginPrivateFileSystemBackend::SupportsStreaming(
+ const storage::FileSystemURL& url) const {
+ return false;
+}
+
+bool PluginPrivateFileSystemBackend::HasInplaceCopyImplementation(
+ storage::FileSystemType type) const {
+ return false;
+}
+
+scoped_ptr<storage::FileStreamReader>
+PluginPrivateFileSystemBackend::CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const {
+ return scoped_ptr<storage::FileStreamReader>();
+}
+
+scoped_ptr<FileStreamWriter>
+PluginPrivateFileSystemBackend::CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context) const {
+ return scoped_ptr<FileStreamWriter>();
+}
+
+FileSystemQuotaUtil* PluginPrivateFileSystemBackend::GetQuotaUtil() {
+ return this;
+}
+
+base::File::Error
+PluginPrivateFileSystemBackend::DeleteOriginDataOnFileTaskRunner(
+ FileSystemContext* context,
+ storage::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ FileSystemType type) {
+ if (!CanHandleType(type))
+ return base::File::FILE_ERROR_SECURITY;
+ bool result = obfuscated_file_util()->DeleteDirectoryForOriginAndType(
+ origin_url, std::string());
+ if (result)
+ return base::File::FILE_OK;
+ return base::File::FILE_ERROR_FAILED;
+}
+
+void PluginPrivateFileSystemBackend::GetOriginsForTypeOnFileTaskRunner(
+ FileSystemType type,
+ std::set<GURL>* origins) {
+ if (!CanHandleType(type))
+ return;
+ scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enumerator(
+ obfuscated_file_util()->CreateOriginEnumerator());
+ GURL origin;
+ while (!(origin = enumerator->Next()).is_empty())
+ origins->insert(origin);
+}
+
+void PluginPrivateFileSystemBackend::GetOriginsForHostOnFileTaskRunner(
+ FileSystemType type,
+ const std::string& host,
+ std::set<GURL>* origins) {
+ if (!CanHandleType(type))
+ return;
+ scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enumerator(
+ obfuscated_file_util()->CreateOriginEnumerator());
+ GURL origin;
+ while (!(origin = enumerator->Next()).is_empty()) {
+ if (host == net::GetHostOrSpecFromURL(origin))
+ origins->insert(origin);
+ }
+}
+
+int64 PluginPrivateFileSystemBackend::GetOriginUsageOnFileTaskRunner(
+ FileSystemContext* context,
+ const GURL& origin_url,
+ FileSystemType type) {
+ // We don't track usage on this filesystem.
+ return 0;
+}
+
+scoped_refptr<QuotaReservation>
+PluginPrivateFileSystemBackend::CreateQuotaReservationOnFileTaskRunner(
+ const GURL& origin_url,
+ FileSystemType type) {
+ // We don't track usage on this filesystem.
+ NOTREACHED();
+ return scoped_refptr<QuotaReservation>();
+}
+
+void PluginPrivateFileSystemBackend::AddFileUpdateObserver(
+ FileSystemType type,
+ FileUpdateObserver* observer,
+ base::SequencedTaskRunner* task_runner) {}
+
+void PluginPrivateFileSystemBackend::AddFileChangeObserver(
+ FileSystemType type,
+ FileChangeObserver* observer,
+ base::SequencedTaskRunner* task_runner) {}
+
+void PluginPrivateFileSystemBackend::AddFileAccessObserver(
+ FileSystemType type,
+ FileAccessObserver* observer,
+ base::SequencedTaskRunner* task_runner) {}
+
+const UpdateObserverList* PluginPrivateFileSystemBackend::GetUpdateObservers(
+ FileSystemType type) const {
+ return NULL;
+}
+
+const ChangeObserverList* PluginPrivateFileSystemBackend::GetChangeObservers(
+ FileSystemType type) const {
+ return NULL;
+}
+
+const AccessObserverList* PluginPrivateFileSystemBackend::GetAccessObservers(
+ FileSystemType type) const {
+ return NULL;
+}
+
+ObfuscatedFileUtil* PluginPrivateFileSystemBackend::obfuscated_file_util() {
+ return static_cast<ObfuscatedFileUtil*>(
+ static_cast<AsyncFileUtilAdapter*>(file_util_.get())->sync_file_util());
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/plugin_private_file_system_backend.h b/storage/browser/fileapi/plugin_private_file_system_backend.h
new file mode 100644
index 0000000..a49e07d
--- /dev/null
+++ b/storage/browser/fileapi/plugin_private_file_system_backend.h
@@ -0,0 +1,149 @@
+// 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 STORAGE_BROWSER_FILEAPI_PLUGIN_PRIVATE_FILE_SYSTEM_BACKEND_H_
+#define STORAGE_BROWSER_FILEAPI_PLUGIN_PRIVATE_FILE_SYSTEM_BACKEND_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/fileapi/file_system_backend.h"
+#include "storage/browser/fileapi/file_system_options.h"
+#include "storage/browser/fileapi/file_system_quota_util.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace content {
+class PluginPrivateFileSystemBackendTest;
+}
+
+namespace storage {
+class SpecialStoragePolicy;
+}
+
+namespace storage {
+
+class ObfuscatedFileUtil;
+class WatcherManager;
+
+class STORAGE_EXPORT PluginPrivateFileSystemBackend
+ : public FileSystemBackend,
+ public FileSystemQuotaUtil {
+ public:
+ class FileSystemIDToPluginMap;
+ typedef base::Callback<void(base::File::Error result)> StatusCallback;
+
+ PluginPrivateFileSystemBackend(
+ base::SequencedTaskRunner* file_task_runner,
+ const base::FilePath& profile_path,
+ storage::SpecialStoragePolicy* special_storage_policy,
+ const FileSystemOptions& file_system_options);
+ virtual ~PluginPrivateFileSystemBackend();
+
+ // This must be used to open 'private' filesystem instead of regular
+ // OpenFileSystem.
+ // |plugin_id| must be an identifier string for per-plugin
+ // isolation, e.g. name, MIME type etc.
+ // NOTE: |plugin_id| must be sanitized ASCII string that doesn't
+ // include *any* dangerous character like '/'.
+ void OpenPrivateFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ const std::string& filesystem_id,
+ const std::string& plugin_id,
+ OpenFileSystemMode mode,
+ const StatusCallback& callback);
+
+ // FileSystemBackend overrides.
+ virtual bool CanHandleType(FileSystemType type) const OVERRIDE;
+ virtual void Initialize(FileSystemContext* context) OVERRIDE;
+ virtual void ResolveURL(const FileSystemURL& url,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) OVERRIDE;
+ virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE;
+ virtual WatcherManager* GetWatcherManager(FileSystemType type) OVERRIDE;
+ virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type,
+ base::File::Error* error_code) OVERRIDE;
+ virtual FileSystemOperation* CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::File::Error* error_code) const OVERRIDE;
+ virtual bool SupportsStreaming(const FileSystemURL& url) const OVERRIDE;
+ virtual bool HasInplaceCopyImplementation(
+ storage::FileSystemType type) const OVERRIDE;
+ virtual scoped_ptr<storage::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;
+
+ // FileSystemQuotaUtil overrides.
+ virtual base::File::Error DeleteOriginDataOnFileTaskRunner(
+ FileSystemContext* context,
+ storage::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ FileSystemType type) OVERRIDE;
+ virtual void GetOriginsForTypeOnFileTaskRunner(
+ FileSystemType type,
+ std::set<GURL>* origins) OVERRIDE;
+ virtual void GetOriginsForHostOnFileTaskRunner(
+ FileSystemType type,
+ const std::string& host,
+ std::set<GURL>* origins) OVERRIDE;
+ virtual int64 GetOriginUsageOnFileTaskRunner(
+ FileSystemContext* context,
+ const GURL& origin_url,
+ FileSystemType type) OVERRIDE;
+ virtual scoped_refptr<QuotaReservation>
+ CreateQuotaReservationOnFileTaskRunner(
+ 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;
+
+ private:
+ friend class content::PluginPrivateFileSystemBackendTest;
+
+ ObfuscatedFileUtil* obfuscated_file_util();
+ const base::FilePath& base_path() const { return base_path_; }
+
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+ const FileSystemOptions file_system_options_;
+ const base::FilePath base_path_;
+ scoped_ptr<AsyncFileUtil> file_util_;
+ FileSystemIDToPluginMap* plugin_map_; // Owned by file_util_.
+ base::WeakPtrFactory<PluginPrivateFileSystemBackend> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PluginPrivateFileSystemBackend);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_PLUGIN_PRIVATE_FILE_SYSTEM_BACKEND_H_
diff --git a/storage/browser/fileapi/quota/open_file_handle.cc b/storage/browser/fileapi/quota/open_file_handle.cc
new file mode 100644
index 0000000..8842438
--- /dev/null
+++ b/storage/browser/fileapi/quota/open_file_handle.cc
@@ -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.
+
+#include "storage/browser/fileapi/quota/open_file_handle.h"
+
+#include "storage/browser/fileapi/quota/open_file_handle_context.h"
+#include "storage/browser/fileapi/quota/quota_reservation.h"
+
+namespace storage {
+
+OpenFileHandle::~OpenFileHandle() {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+}
+
+void OpenFileHandle::UpdateMaxWrittenOffset(int64 offset) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+
+ int64 growth = context_->UpdateMaxWrittenOffset(offset);
+ if (growth > 0)
+ reservation_->ConsumeReservation(growth);
+}
+
+void OpenFileHandle::AddAppendModeWriteAmount(int64 amount) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ if (amount <= 0)
+ return;
+
+ context_->AddAppendModeWriteAmount(amount);
+ reservation_->ConsumeReservation(amount);
+}
+
+int64 OpenFileHandle::GetEstimatedFileSize() const {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ return context_->GetEstimatedFileSize();
+}
+
+int64 OpenFileHandle::GetMaxWrittenOffset() const {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ return context_->GetMaxWrittenOffset();
+}
+
+const base::FilePath& OpenFileHandle::platform_path() const {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ return context_->platform_path();
+}
+
+OpenFileHandle::OpenFileHandle(QuotaReservation* reservation,
+ OpenFileHandleContext* context)
+ : reservation_(reservation),
+ context_(context) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/quota/open_file_handle.h b/storage/browser/fileapi/quota/open_file_handle.h
new file mode 100644
index 0000000..8954b1d
--- /dev/null
+++ b/storage/browser/fileapi/quota/open_file_handle.h
@@ -0,0 +1,70 @@
+// 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 STORAGE_BROWSER_FILEAPI_QUOTA_OPEN_FILE_HANDLE_H_
+#define STORAGE_BROWSER_FILEAPI_QUOTA_OPEN_FILE_HANDLE_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace storage {
+
+class QuotaReservation;
+class OpenFileHandleContext;
+class QuotaReservationBuffer;
+
+// Represents an open file like a file descriptor.
+// This should be alive while a consumer keeps a file opened and should be
+// deleted when the plugin closes the file.
+class STORAGE_EXPORT OpenFileHandle {
+ public:
+ ~OpenFileHandle();
+
+ // Updates cached file size and consumes quota for that.
+ // Both this and AddAppendModeWriteAmount should be called for each modified
+ // file before calling QuotaReservation::RefreshQuota and before closing the
+ // file.
+ void UpdateMaxWrittenOffset(int64 offset);
+
+ // Notifies that |amount| of data is written to the file in append mode, and
+ // consumes quota for that.
+ // Both this and UpdateMaxWrittenOffset should be called for each modified
+ // file before calling QuotaReservation::RefreshQuota and before closing the
+ // file.
+ void AddAppendModeWriteAmount(int64 amount);
+
+ // Returns the estimated file size for the quota consumption calculation.
+ // The client must consume its reserved quota when it writes data to the file
+ // beyond the estimated file size.
+ // The estimated file size is greater than or equal to actual file size after
+ // all clients report their file usage, and is monotonically increasing over
+ // OpenFileHandle object life cycle, so that client may cache the value.
+ int64 GetEstimatedFileSize() const;
+
+ int64 GetMaxWrittenOffset() const;
+ const base::FilePath& platform_path() const;
+
+ private:
+ friend class QuotaReservationBuffer;
+
+ OpenFileHandle(QuotaReservation* reservation,
+ OpenFileHandleContext* context);
+
+ scoped_refptr<QuotaReservation> reservation_;
+ scoped_refptr<OpenFileHandleContext> context_;
+
+ base::SequenceChecker sequence_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(OpenFileHandle);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_QUOTA_OPEN_FILE_HANDLE_H_
diff --git a/storage/browser/fileapi/quota/open_file_handle_context.cc b/storage/browser/fileapi/quota/open_file_handle_context.cc
new file mode 100644
index 0000000..816a777
--- /dev/null
+++ b/storage/browser/fileapi/quota/open_file_handle_context.cc
@@ -0,0 +1,72 @@
+// 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 "storage/browser/fileapi/quota/open_file_handle_context.h"
+
+#include "base/file_util.h"
+#include "storage/browser/fileapi/quota/quota_reservation_buffer.h"
+
+namespace storage {
+
+OpenFileHandleContext::OpenFileHandleContext(
+ const base::FilePath& platform_path,
+ QuotaReservationBuffer* reservation_buffer)
+ : initial_file_size_(0),
+ maximum_written_offset_(0),
+ append_mode_write_amount_(0),
+ platform_path_(platform_path),
+ reservation_buffer_(reservation_buffer) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+
+ base::GetFileSize(platform_path, &initial_file_size_);
+ maximum_written_offset_ = initial_file_size_;
+}
+
+int64 OpenFileHandleContext::UpdateMaxWrittenOffset(int64 offset) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ if (offset <= maximum_written_offset_)
+ return 0;
+
+ int64 growth = offset - maximum_written_offset_;
+ maximum_written_offset_ = offset;
+ return growth;
+}
+
+void OpenFileHandleContext::AddAppendModeWriteAmount(int64 amount) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ append_mode_write_amount_ += amount;
+}
+
+int64 OpenFileHandleContext::GetEstimatedFileSize() const {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ return maximum_written_offset_ + append_mode_write_amount_;
+}
+
+int64 OpenFileHandleContext::GetMaxWrittenOffset() const {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ return maximum_written_offset_;
+}
+
+OpenFileHandleContext::~OpenFileHandleContext() {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+
+ // TODO(tzik): Optimize this for single operation.
+
+ int64 file_size = 0;
+ base::GetFileSize(platform_path_, &file_size);
+ int64 usage_delta = file_size - initial_file_size_;
+
+ // |reserved_quota_consumption| may be greater than the recorded file growth
+ // when a plugin crashed before reporting its consumption.
+ // In this case, the reserved quota for the plugin should be handled as
+ // consumed quota.
+ int64 reserved_quota_consumption =
+ std::max(GetEstimatedFileSize(), file_size) - initial_file_size_;
+
+ reservation_buffer_->CommitFileGrowth(
+ reserved_quota_consumption, usage_delta);
+ reservation_buffer_->DetachOpenFileHandleContext(this);
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/quota/open_file_handle_context.h b/storage/browser/fileapi/quota/open_file_handle_context.h
new file mode 100644
index 0000000..cb37064
--- /dev/null
+++ b/storage/browser/fileapi/quota/open_file_handle_context.h
@@ -0,0 +1,61 @@
+// 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 STORAGE_BROWSER_FILEAPI_OPEN_FILE_HANDLE_CONTEXT_H_
+#define STORAGE_BROWSER_FILEAPI_OPEN_FILE_HANDLE_CONTEXT_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "url/gurl.h"
+
+namespace storage {
+
+class QuotaReservationBuffer;
+
+// This class represents a underlying file of a managed FileSystem file.
+// The instance keeps alive while at least one consumer keeps an open file
+// handle.
+// This class is usually manipulated only via OpenFileHandle.
+class OpenFileHandleContext : public base::RefCounted<OpenFileHandleContext> {
+ public:
+ OpenFileHandleContext(const base::FilePath& platform_path,
+ QuotaReservationBuffer* reservation_buffer);
+
+ // Updates the max written offset and returns the amount of growth.
+ int64 UpdateMaxWrittenOffset(int64 offset);
+
+ void AddAppendModeWriteAmount(int64 amount);
+
+ const base::FilePath& platform_path() const {
+ return platform_path_;
+ }
+
+ int64 GetEstimatedFileSize() const;
+ int64 GetMaxWrittenOffset() const;
+
+ private:
+ friend class base::RefCounted<OpenFileHandleContext>;
+ virtual ~OpenFileHandleContext();
+
+ int64 initial_file_size_;
+ int64 maximum_written_offset_;
+ int64 append_mode_write_amount_;
+ base::FilePath platform_path_;
+
+ scoped_refptr<QuotaReservationBuffer> reservation_buffer_;
+
+ base::SequenceChecker sequence_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(OpenFileHandleContext);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_OPEN_FILE_HANDLE_CONTEXT_H_
diff --git a/storage/browser/fileapi/quota/quota_backend_impl.cc b/storage/browser/fileapi/quota/quota_backend_impl.cc
new file mode 100644
index 0000000..6969f7f
--- /dev/null
+++ b/storage/browser/fileapi/quota/quota_backend_impl.cc
@@ -0,0 +1,172 @@
+// 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 "storage/browser/fileapi/quota/quota_backend_impl.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/sequenced_task_runner.h"
+#include "storage/browser/fileapi/file_system_usage_cache.h"
+#include "storage/browser/quota/quota_client.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace storage {
+
+QuotaBackendImpl::QuotaBackendImpl(
+ base::SequencedTaskRunner* file_task_runner,
+ ObfuscatedFileUtil* obfuscated_file_util,
+ FileSystemUsageCache* file_system_usage_cache,
+ storage::QuotaManagerProxy* quota_manager_proxy)
+ : file_task_runner_(file_task_runner),
+ obfuscated_file_util_(obfuscated_file_util),
+ file_system_usage_cache_(file_system_usage_cache),
+ quota_manager_proxy_(quota_manager_proxy),
+ weak_ptr_factory_(this) {
+}
+
+QuotaBackendImpl::~QuotaBackendImpl() {
+}
+
+void QuotaBackendImpl::ReserveQuota(const GURL& origin,
+ FileSystemType type,
+ int64 delta,
+ const ReserveQuotaCallback& callback) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(origin.is_valid());
+ if (!delta) {
+ callback.Run(base::File::FILE_OK, 0);
+ return;
+ }
+ DCHECK(quota_manager_proxy_.get());
+ quota_manager_proxy_->GetUsageAndQuota(
+ file_task_runner_.get(),
+ origin,
+ FileSystemTypeToQuotaStorageType(type),
+ base::Bind(&QuotaBackendImpl::DidGetUsageAndQuotaForReserveQuota,
+ weak_ptr_factory_.GetWeakPtr(),
+ QuotaReservationInfo(origin, type, delta),
+ callback));
+}
+
+void QuotaBackendImpl::ReleaseReservedQuota(const GURL& origin,
+ FileSystemType type,
+ int64 size) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(origin.is_valid());
+ DCHECK_LE(0, size);
+ if (!size)
+ return;
+ ReserveQuotaInternal(QuotaReservationInfo(origin, type, -size));
+}
+
+void QuotaBackendImpl::CommitQuotaUsage(const GURL& origin,
+ FileSystemType type,
+ int64 delta) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(origin.is_valid());
+ if (!delta)
+ return;
+ ReserveQuotaInternal(QuotaReservationInfo(origin, type, delta));
+ base::FilePath path;
+ if (GetUsageCachePath(origin, type, &path) != base::File::FILE_OK)
+ return;
+ bool result = file_system_usage_cache_->AtomicUpdateUsageByDelta(path, delta);
+ DCHECK(result);
+}
+
+void QuotaBackendImpl::IncrementDirtyCount(const GURL& origin,
+ FileSystemType type) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(origin.is_valid());
+ base::FilePath path;
+ if (GetUsageCachePath(origin, type, &path) != base::File::FILE_OK)
+ return;
+ DCHECK(file_system_usage_cache_);
+ file_system_usage_cache_->IncrementDirty(path);
+}
+
+void QuotaBackendImpl::DecrementDirtyCount(const GURL& origin,
+ FileSystemType type) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(origin.is_valid());
+ base::FilePath path;
+ if (GetUsageCachePath(origin, type, &path) != base::File::FILE_OK)
+ return;
+ DCHECK(file_system_usage_cache_);
+ file_system_usage_cache_->DecrementDirty(path);
+}
+
+void QuotaBackendImpl::DidGetUsageAndQuotaForReserveQuota(
+ const QuotaReservationInfo& info,
+ const ReserveQuotaCallback& callback,
+ storage::QuotaStatusCode status,
+ int64 usage,
+ int64 quota) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(info.origin.is_valid());
+ DCHECK_LE(0, usage);
+ DCHECK_LE(0, quota);
+ if (status != storage::kQuotaStatusOk) {
+ callback.Run(base::File::FILE_ERROR_FAILED, 0);
+ return;
+ }
+
+ QuotaReservationInfo normalized_info = info;
+ if (info.delta > 0) {
+ int64 new_usage =
+ base::saturated_cast<int64>(usage + static_cast<uint64>(info.delta));
+ if (quota < new_usage)
+ new_usage = quota;
+ normalized_info.delta = std::max(static_cast<int64>(0), new_usage - usage);
+ }
+
+ ReserveQuotaInternal(normalized_info);
+ if (callback.Run(base::File::FILE_OK, normalized_info.delta))
+ return;
+ // The requester could not accept the reserved quota. Revert it.
+ ReserveQuotaInternal(
+ QuotaReservationInfo(normalized_info.origin,
+ normalized_info.type,
+ -normalized_info.delta));
+}
+
+void QuotaBackendImpl::ReserveQuotaInternal(const QuotaReservationInfo& info) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(info.origin.is_valid());
+ DCHECK(quota_manager_proxy_.get());
+ quota_manager_proxy_->NotifyStorageModified(
+ storage::QuotaClient::kFileSystem,
+ info.origin,
+ FileSystemTypeToQuotaStorageType(info.type),
+ info.delta);
+}
+
+base::File::Error QuotaBackendImpl::GetUsageCachePath(
+ const GURL& origin,
+ FileSystemType type,
+ base::FilePath* usage_file_path) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(origin.is_valid());
+ DCHECK(usage_file_path);
+ base::File::Error error = base::File::FILE_OK;
+ *usage_file_path =
+ SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType(
+ obfuscated_file_util_, origin, type, &error);
+ return error;
+}
+
+QuotaBackendImpl::QuotaReservationInfo::QuotaReservationInfo(
+ const GURL& origin, FileSystemType type, int64 delta)
+ : origin(origin), type(type), delta(delta) {
+}
+
+QuotaBackendImpl::QuotaReservationInfo::~QuotaReservationInfo() {
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/quota/quota_backend_impl.h b/storage/browser/fileapi/quota/quota_backend_impl.h
new file mode 100644
index 0000000..ed15157
--- /dev/null
+++ b/storage/browser/fileapi/quota/quota_backend_impl.h
@@ -0,0 +1,106 @@
+// 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 STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_BACKEND_IMPL_H_
+#define STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_BACKEND_IMPL_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/fileapi/quota/quota_reservation_manager.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/quota/quota_status_code.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace content {
+class QuotaBackendImplTest;
+}
+
+namespace storage {
+class QuotaManagerProxy;
+}
+
+namespace storage {
+
+class FileSystemUsageCache;
+class ObfuscatedFileUtil;
+
+// An instance of this class is owned by QuotaReservationManager.
+class STORAGE_EXPORT QuotaBackendImpl
+ : public QuotaReservationManager::QuotaBackend {
+ public:
+ typedef QuotaReservationManager::ReserveQuotaCallback
+ ReserveQuotaCallback;
+
+ QuotaBackendImpl(base::SequencedTaskRunner* file_task_runner,
+ ObfuscatedFileUtil* obfuscated_file_util,
+ FileSystemUsageCache* file_system_usage_cache,
+ storage::QuotaManagerProxy* quota_manager_proxy);
+ virtual ~QuotaBackendImpl();
+
+ // QuotaReservationManager::QuotaBackend overrides.
+ virtual void ReserveQuota(
+ const GURL& origin,
+ FileSystemType type,
+ int64 delta,
+ const ReserveQuotaCallback& callback) OVERRIDE;
+ virtual void ReleaseReservedQuota(
+ const GURL& origin,
+ FileSystemType type,
+ int64 size) OVERRIDE;
+ virtual void CommitQuotaUsage(
+ const GURL& origin,
+ FileSystemType type,
+ int64 delta) OVERRIDE;
+ virtual void IncrementDirtyCount(
+ const GURL& origin,
+ FileSystemType type) OVERRIDE;
+ virtual void DecrementDirtyCount(
+ const GURL& origin,
+ FileSystemType type) OVERRIDE;
+
+ private:
+ friend class content::QuotaBackendImplTest;
+
+ struct QuotaReservationInfo {
+ QuotaReservationInfo(const GURL& origin, FileSystemType type, int64 delta);
+ ~QuotaReservationInfo();
+
+ GURL origin;
+ FileSystemType type;
+ int64 delta;
+ };
+
+ void DidGetUsageAndQuotaForReserveQuota(const QuotaReservationInfo& info,
+ const ReserveQuotaCallback& callback,
+ storage::QuotaStatusCode status,
+ int64 usage,
+ int64 quota);
+
+ void ReserveQuotaInternal(
+ const QuotaReservationInfo& info);
+ base::File::Error GetUsageCachePath(
+ const GURL& origin,
+ FileSystemType type,
+ base::FilePath* usage_file_path);
+
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+
+ // Owned by SandboxFileSystemBackendDelegate.
+ ObfuscatedFileUtil* obfuscated_file_util_;
+ FileSystemUsageCache* file_system_usage_cache_;
+
+ scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy_;
+
+ base::WeakPtrFactory<QuotaBackendImpl> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaBackendImpl);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_BACKEND_IMPL_H_
diff --git a/storage/browser/fileapi/quota/quota_reservation.cc b/storage/browser/fileapi/quota/quota_reservation.cc
new file mode 100644
index 0000000..fff7f16
--- /dev/null
+++ b/storage/browser/fileapi/quota/quota_reservation.cc
@@ -0,0 +1,127 @@
+// 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 "storage/browser/fileapi/quota/quota_reservation.h"
+
+#include "base/bind.h"
+#include "storage/browser/fileapi/quota/open_file_handle.h"
+#include "storage/browser/fileapi/quota/quota_reservation_buffer.h"
+
+namespace storage {
+
+void QuotaReservation::RefreshReservation(
+ int64 size,
+ const StatusCallback& callback) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ DCHECK(!running_refresh_request_);
+ DCHECK(!client_crashed_);
+ if (!reservation_manager())
+ return;
+
+ running_refresh_request_ = true;
+
+ reservation_manager()->ReserveQuota(
+ origin(), type(), size - remaining_quota_,
+ base::Bind(&QuotaReservation::AdaptDidUpdateReservedQuota,
+ weak_ptr_factory_.GetWeakPtr(),
+ remaining_quota_, callback));
+
+ if (running_refresh_request_)
+ remaining_quota_ = 0;
+}
+
+scoped_ptr<OpenFileHandle> QuotaReservation::GetOpenFileHandle(
+ const base::FilePath& platform_path) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ DCHECK(!client_crashed_);
+ return reservation_buffer_->GetOpenFileHandle(this, platform_path);
+}
+
+void QuotaReservation::OnClientCrash() {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ client_crashed_ = true;
+
+ if (remaining_quota_) {
+ reservation_buffer_->PutReservationToBuffer(remaining_quota_);
+ remaining_quota_ = 0;
+ }
+}
+
+void QuotaReservation::ConsumeReservation(int64 size) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ DCHECK_LT(0, size);
+ DCHECK_LE(size, remaining_quota_);
+ if (client_crashed_)
+ return;
+
+ remaining_quota_ -= size;
+ reservation_buffer_->PutReservationToBuffer(size);
+}
+
+QuotaReservationManager* QuotaReservation::reservation_manager() {
+ return reservation_buffer_->reservation_manager();
+}
+
+const GURL& QuotaReservation::origin() const {
+ return reservation_buffer_->origin();
+}
+
+FileSystemType QuotaReservation::type() const {
+ return reservation_buffer_->type();
+}
+
+QuotaReservation::QuotaReservation(
+ QuotaReservationBuffer* reservation_buffer)
+ : client_crashed_(false),
+ running_refresh_request_(false),
+ remaining_quota_(0),
+ reservation_buffer_(reservation_buffer),
+ weak_ptr_factory_(this) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+}
+
+QuotaReservation::~QuotaReservation() {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+
+ if (remaining_quota_ && reservation_manager()) {
+ reservation_manager()->ReleaseReservedQuota(
+ origin(), type(), remaining_quota_);
+ }
+}
+
+// static
+bool QuotaReservation::AdaptDidUpdateReservedQuota(
+ const base::WeakPtr<QuotaReservation>& reservation,
+ int64 previous_size,
+ const StatusCallback& callback,
+ base::File::Error error,
+ int64 delta) {
+ if (!reservation)
+ return false;
+
+ return reservation->DidUpdateReservedQuota(
+ previous_size, callback, error, delta);
+}
+
+bool QuotaReservation::DidUpdateReservedQuota(
+ int64 previous_size,
+ const StatusCallback& callback,
+ base::File::Error error,
+ int64 delta) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ DCHECK(running_refresh_request_);
+ running_refresh_request_ = false;
+
+ if (client_crashed_) {
+ callback.Run(base::File::FILE_ERROR_ABORT);
+ return false;
+ }
+
+ if (error == base::File::FILE_OK)
+ remaining_quota_ = previous_size + delta;
+ callback.Run(error);
+ return true;
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/quota/quota_reservation.h b/storage/browser/fileapi/quota/quota_reservation.h
new file mode 100644
index 0000000..be35b9e
--- /dev/null
+++ b/storage/browser/fileapi/quota/quota_reservation.h
@@ -0,0 +1,95 @@
+// 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 STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_H_
+#define STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_H_
+
+#include "base/basictypes.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/fileapi/quota/quota_reservation_manager.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_types.h"
+
+class GURL;
+
+namespace storage {
+
+class QuotaReservationBuffer;
+class OpenFileHandle;
+
+// Represents a unit of quota reservation.
+class STORAGE_EXPORT QuotaReservation
+ : public base::RefCounted<QuotaReservation> {
+ public:
+ typedef base::Callback<void(base::File::Error error)> StatusCallback;
+
+ // Reclaims unused quota and reserves another |size| of quota. So that the
+ // resulting new |remaining_quota_| will be same as |size| as far as available
+ // space is enough. |remaining_quota_| may be less than |size| if there is
+ // not enough space available.
+ // Invokes |callback| upon completion.
+ void RefreshReservation(int64 size, const StatusCallback& callback);
+
+ // Associates |platform_path| to the QuotaReservation instance.
+ // Returns an OpenFileHandle instance that represents a quota managed file.
+ scoped_ptr<OpenFileHandle> GetOpenFileHandle(
+ const base::FilePath& platform_path);
+
+ // Should be called when the associated client is crashed.
+ // This implies the client can no longer report its consumption of the
+ // reserved quota.
+ // QuotaReservation puts all remaining quota to the QuotaReservationBuffer, so
+ // that the remaining quota will be reclaimed after all open files associated
+ // to the origin and type.
+ void OnClientCrash();
+
+ // Consumes |size| of reserved quota for a associated file.
+ // Consumed quota is sent to associated QuotaReservationBuffer for staging.
+ void ConsumeReservation(int64 size);
+
+ // Returns amount of unused reserved quota.
+ int64 remaining_quota() const { return remaining_quota_; }
+
+ QuotaReservationManager* reservation_manager();
+ const GURL& origin() const;
+ FileSystemType type() const;
+
+ private:
+ friend class QuotaReservationBuffer;
+
+ // Use QuotaReservationManager as the entry point.
+ explicit QuotaReservation(QuotaReservationBuffer* reservation_buffer);
+
+ friend class base::RefCounted<QuotaReservation>;
+ virtual ~QuotaReservation();
+
+ static bool AdaptDidUpdateReservedQuota(
+ const base::WeakPtr<QuotaReservation>& reservation,
+ int64 previous_size,
+ const StatusCallback& callback,
+ base::File::Error error,
+ int64 delta);
+ bool DidUpdateReservedQuota(int64 previous_size,
+ const StatusCallback& callback,
+ base::File::Error error,
+ int64 delta);
+
+ bool client_crashed_;
+ bool running_refresh_request_;
+ int64 remaining_quota_;
+
+ scoped_refptr<QuotaReservationBuffer> reservation_buffer_;
+
+ base::SequenceChecker sequence_checker_;
+ base::WeakPtrFactory<QuotaReservation> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaReservation);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_H_
diff --git a/storage/browser/fileapi/quota/quota_reservation_buffer.cc b/storage/browser/fileapi/quota/quota_reservation_buffer.cc
new file mode 100644
index 0000000..faa70eb
--- /dev/null
+++ b/storage/browser/fileapi/quota/quota_reservation_buffer.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 "storage/browser/fileapi/quota/quota_reservation_buffer.h"
+
+#include "base/bind.h"
+#include "storage/browser/fileapi/quota/open_file_handle.h"
+#include "storage/browser/fileapi/quota/open_file_handle_context.h"
+#include "storage/browser/fileapi/quota/quota_reservation.h"
+
+namespace storage {
+
+QuotaReservationBuffer::QuotaReservationBuffer(
+ base::WeakPtr<QuotaReservationManager> reservation_manager,
+ const GURL& origin,
+ FileSystemType type)
+ : reservation_manager_(reservation_manager),
+ origin_(origin),
+ type_(type),
+ reserved_quota_(0) {
+ DCHECK(origin.is_valid());
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ reservation_manager_->IncrementDirtyCount(origin, type);
+}
+
+scoped_refptr<QuotaReservation> QuotaReservationBuffer::CreateReservation() {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ return make_scoped_refptr(new QuotaReservation(this));
+}
+
+scoped_ptr<OpenFileHandle> QuotaReservationBuffer::GetOpenFileHandle(
+ QuotaReservation* reservation,
+ const base::FilePath& platform_path) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ OpenFileHandleContext** open_file = &open_files_[platform_path];
+ if (!*open_file)
+ *open_file = new OpenFileHandleContext(platform_path, this);
+ return make_scoped_ptr(new OpenFileHandle(reservation, *open_file));
+}
+
+void QuotaReservationBuffer::CommitFileGrowth(int64 reserved_quota_consumption,
+ int64 usage_delta) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ if (!reservation_manager_)
+ return;
+ reservation_manager_->CommitQuotaUsage(origin_, type_, usage_delta);
+
+ if (reserved_quota_consumption > 0) {
+ if (reserved_quota_consumption > reserved_quota_) {
+ LOG(ERROR) << "Detected over consumption of the storage quota beyond its"
+ << " reservation";
+ reserved_quota_consumption = reserved_quota_;
+ }
+
+ reserved_quota_ -= reserved_quota_consumption;
+ reservation_manager_->ReleaseReservedQuota(
+ origin_, type_, reserved_quota_consumption);
+ }
+}
+
+void QuotaReservationBuffer::DetachOpenFileHandleContext(
+ OpenFileHandleContext* open_file) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ DCHECK_EQ(open_file, open_files_[open_file->platform_path()]);
+ open_files_.erase(open_file->platform_path());
+}
+
+void QuotaReservationBuffer::PutReservationToBuffer(int64 reservation) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ DCHECK_LE(0, reservation);
+ reserved_quota_ += reservation;
+}
+
+QuotaReservationBuffer::~QuotaReservationBuffer() {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ if (!reservation_manager_)
+ return;
+
+ DCHECK_LE(0, reserved_quota_);
+ if (reserved_quota_ && reservation_manager_) {
+ reservation_manager_->ReserveQuota(
+ origin_, type_, -reserved_quota_,
+ base::Bind(&QuotaReservationBuffer::DecrementDirtyCount,
+ reservation_manager_, origin_, type_));
+ }
+ reservation_manager_->ReleaseReservationBuffer(this);
+}
+
+// static
+bool QuotaReservationBuffer::DecrementDirtyCount(
+ base::WeakPtr<QuotaReservationManager> reservation_manager,
+ const GURL& origin,
+ FileSystemType type,
+ base::File::Error error,
+ int64 delta_unused) {
+ DCHECK(origin.is_valid());
+ if (error == base::File::FILE_OK && reservation_manager) {
+ reservation_manager->DecrementDirtyCount(origin, type);
+ return true;
+ }
+ return false;
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/quota/quota_reservation_buffer.h b/storage/browser/fileapi/quota/quota_reservation_buffer.h
new file mode 100644
index 0000000..0e893b6
--- /dev/null
+++ b/storage/browser/fileapi/quota/quota_reservation_buffer.h
@@ -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.
+
+#ifndef STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_BUFFER_H_
+#define STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_BUFFER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "url/gurl.h"
+
+namespace storage {
+
+class QuotaReservation;
+class OpenFileHandle;
+class OpenFileHandleContext;
+class QuotaReservationManager;
+
+// QuotaReservationBuffer manages QuotaReservation instances. All consumed
+// quota and leaked quota by associated QuotaReservation will be staged in
+// QuotaReservationBuffer, and will be committed on a modified file is closed.
+// The instance keeps alive while any associated QuotaReservation or
+// OpenFileHandle alive.
+// This class is usually manipulated only via OpenFileHandle and
+// QuotaReservation.
+class QuotaReservationBuffer : public base::RefCounted<QuotaReservationBuffer> {
+ public:
+ QuotaReservationBuffer(
+ base::WeakPtr<QuotaReservationManager> reservation_manager,
+ const GURL& origin,
+ FileSystemType type);
+
+ scoped_refptr<QuotaReservation> CreateReservation();
+ scoped_ptr<OpenFileHandle> GetOpenFileHandle(
+ QuotaReservation* reservation,
+ const base::FilePath& platform_path);
+ void CommitFileGrowth(int64 quota_consumption, int64 usage_delta);
+ void DetachOpenFileHandleContext(OpenFileHandleContext* context);
+ void PutReservationToBuffer(int64 size);
+
+ QuotaReservationManager* reservation_manager() {
+ return reservation_manager_.get();
+ }
+
+ const GURL& origin() const { return origin_; }
+ FileSystemType type() const { return type_; }
+
+ private:
+ friend class base::RefCounted<QuotaReservationBuffer>;
+ virtual ~QuotaReservationBuffer();
+
+ static bool DecrementDirtyCount(
+ base::WeakPtr<QuotaReservationManager> reservation_manager,
+ const GURL& origin,
+ FileSystemType type,
+ base::File::Error error,
+ int64 delta);
+
+ typedef std::map<base::FilePath, OpenFileHandleContext*>
+ OpenFileHandleContextByPath;
+
+ // Not owned. The destructor of OpenFileHandler should erase itself from
+ // |open_files_|.
+ OpenFileHandleContextByPath open_files_;
+
+ base::WeakPtr<QuotaReservationManager> reservation_manager_;
+
+ GURL origin_;
+ storage::FileSystemType type_;
+
+ int64 reserved_quota_;
+
+ base::SequenceChecker sequence_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaReservationBuffer);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_BUFFER_H_
diff --git a/storage/browser/fileapi/quota/quota_reservation_manager.cc b/storage/browser/fileapi/quota/quota_reservation_manager.cc
new file mode 100644
index 0000000..b5ee5d8
--- /dev/null
+++ b/storage/browser/fileapi/quota/quota_reservation_manager.cc
@@ -0,0 +1,91 @@
+// 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 "storage/browser/fileapi/quota/quota_reservation_manager.h"
+
+#include "storage/browser/fileapi/quota/quota_reservation.h"
+#include "storage/browser/fileapi/quota/quota_reservation_buffer.h"
+
+namespace storage {
+
+QuotaReservationManager::QuotaReservationManager(
+ scoped_ptr<QuotaBackend> backend)
+ : backend_(backend.Pass()),
+ weak_ptr_factory_(this) {
+ sequence_checker_.DetachFromSequence();
+}
+
+QuotaReservationManager::~QuotaReservationManager() {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+}
+
+void QuotaReservationManager::ReserveQuota(
+ const GURL& origin,
+ FileSystemType type,
+ int64 size,
+ const ReserveQuotaCallback& callback) {
+ DCHECK(origin.is_valid());
+ backend_->ReserveQuota(origin, type, size, callback);
+}
+
+void QuotaReservationManager::ReleaseReservedQuota(
+ const GURL& origin,
+ FileSystemType type,
+ int64 size) {
+ DCHECK(origin.is_valid());
+ backend_->ReleaseReservedQuota(origin, type, size);
+}
+
+void QuotaReservationManager::CommitQuotaUsage(
+ const GURL& origin,
+ FileSystemType type,
+ int64 delta) {
+ DCHECK(origin.is_valid());
+ backend_->CommitQuotaUsage(origin, type, delta);
+}
+
+void QuotaReservationManager::IncrementDirtyCount(const GURL& origin,
+ FileSystemType type) {
+ DCHECK(origin.is_valid());
+ backend_->IncrementDirtyCount(origin, type);
+}
+
+void QuotaReservationManager::DecrementDirtyCount(const GURL& origin,
+ FileSystemType type) {
+ DCHECK(origin.is_valid());
+ backend_->DecrementDirtyCount(origin, type);
+}
+
+scoped_refptr<QuotaReservationBuffer>
+QuotaReservationManager::GetReservationBuffer(
+ const GURL& origin,
+ FileSystemType type) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ DCHECK(origin.is_valid());
+ QuotaReservationBuffer** buffer =
+ &reservation_buffers_[std::make_pair(origin, type)];
+ if (!*buffer) {
+ *buffer = new QuotaReservationBuffer(
+ weak_ptr_factory_.GetWeakPtr(), origin, type);
+ }
+ return make_scoped_refptr(*buffer);
+}
+
+void QuotaReservationManager::ReleaseReservationBuffer(
+ QuotaReservationBuffer* reservation_buffer) {
+ DCHECK(sequence_checker_.CalledOnValidSequencedThread());
+ std::pair<GURL, FileSystemType> key(reservation_buffer->origin(),
+ reservation_buffer->type());
+ DCHECK_EQ(reservation_buffers_[key], reservation_buffer);
+ reservation_buffers_.erase(key);
+}
+
+scoped_refptr<QuotaReservation> QuotaReservationManager::CreateReservation(
+ const GURL& origin,
+ FileSystemType type) {
+ DCHECK(origin.is_valid());
+ return GetReservationBuffer(origin, type)->CreateReservation();;
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/quota/quota_reservation_manager.h b/storage/browser/fileapi/quota/quota_reservation_manager.h
new file mode 100644
index 0000000..ec04245
--- /dev/null
+++ b/storage/browser/fileapi/quota/quota_reservation_manager.h
@@ -0,0 +1,127 @@
+// 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 STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_MANAGER_H_
+#define STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_MANAGER_H_
+
+#include <map>
+#include <utility>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "url/gurl.h"
+
+namespace content {
+class QuotaReservationManagerTest;
+}
+
+namespace storage {
+
+class QuotaReservation;
+class QuotaReservationBuffer;
+class OpenFileHandle;
+class OpenFileHandleContext;
+
+class STORAGE_EXPORT QuotaReservationManager {
+ public:
+ // Callback for ReserveQuota. When this callback returns false, ReserveQuota
+ // operation should be reverted.
+ typedef base::Callback<bool(base::File::Error error, int64 delta)>
+ ReserveQuotaCallback;
+
+ // An abstraction of backing quota system.
+ class STORAGE_EXPORT QuotaBackend {
+ public:
+ QuotaBackend() {}
+ virtual ~QuotaBackend() {}
+
+ // Reserves or reclaims |delta| of quota for |origin| and |type| pair.
+ // Reserved quota should be counted as usage, but it should be on-memory
+ // and be cleared by a browser restart.
+ // Invokes |callback| upon completion with an error code.
+ // |callback| should return false if it can't accept the reservation, in
+ // that case, the backend should roll back the reservation.
+ virtual void ReserveQuota(const GURL& origin,
+ FileSystemType type,
+ int64 delta,
+ const ReserveQuotaCallback& callback) = 0;
+
+ // Reclaims |size| of quota for |origin| and |type|.
+ virtual void ReleaseReservedQuota(const GURL& origin,
+ FileSystemType type,
+ int64 size) = 0;
+
+ // Updates disk usage of |origin| and |type|.
+ // Invokes |callback| upon completion with an error code.
+ virtual void CommitQuotaUsage(const GURL& origin,
+ FileSystemType type,
+ int64 delta) = 0;
+
+ virtual void IncrementDirtyCount(const GURL& origin,
+ FileSystemType type) = 0;
+ virtual void DecrementDirtyCount(const GURL& origin,
+ FileSystemType type) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(QuotaBackend);
+ };
+
+ explicit QuotaReservationManager(scoped_ptr<QuotaBackend> backend);
+ ~QuotaReservationManager();
+
+ // The entry point of the quota reservation. Creates new reservation object
+ // for |origin| and |type|.
+ scoped_refptr<QuotaReservation> CreateReservation(
+ const GURL& origin,
+ FileSystemType type);
+
+ private:
+ typedef std::map<std::pair<GURL, FileSystemType>, QuotaReservationBuffer*>
+ ReservationBufferByOriginAndType;
+
+ friend class QuotaReservation;
+ friend class QuotaReservationBuffer;
+ friend class content::QuotaReservationManagerTest;
+
+ void ReserveQuota(const GURL& origin,
+ FileSystemType type,
+ int64 delta,
+ const ReserveQuotaCallback& callback);
+
+ void ReleaseReservedQuota(const GURL& origin,
+ FileSystemType type,
+ int64 size);
+
+ void CommitQuotaUsage(const GURL& origin,
+ FileSystemType type,
+ int64 delta);
+
+ void IncrementDirtyCount(const GURL& origin, FileSystemType type);
+ void DecrementDirtyCount(const GURL& origin, FileSystemType type);
+
+ scoped_refptr<QuotaReservationBuffer> GetReservationBuffer(
+ const GURL& origin,
+ FileSystemType type);
+ void ReleaseReservationBuffer(QuotaReservationBuffer* reservation_pool);
+
+ scoped_ptr<QuotaBackend> backend_;
+
+ // Not owned. The destructor of ReservationBuffer should erase itself from
+ // |reservation_buffers_| by calling ReleaseReservationBuffer.
+ ReservationBufferByOriginAndType reservation_buffers_;
+
+ base::SequenceChecker sequence_checker_;
+ base::WeakPtrFactory<QuotaReservationManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaReservationManager);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_QUOTA_QUOTA_RESERVATION_MANAGER_H_
diff --git a/storage/browser/fileapi/recursive_operation_delegate.cc b/storage/browser/fileapi/recursive_operation_delegate.cc
new file mode 100644
index 0000000..e9d1466
--- /dev/null
+++ b/storage/browser/fileapi/recursive_operation_delegate.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 "storage/browser/fileapi/recursive_operation_delegate.h"
+
+#include "base/bind.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+
+namespace storage {
+
+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),
+ canceled_(false) {
+}
+
+RecursiveOperationDelegate::~RecursiveOperationDelegate() {
+}
+
+void RecursiveOperationDelegate::Cancel() {
+ canceled_ = true;
+ OnCancel();
+}
+
+void RecursiveOperationDelegate::StartRecursiveOperation(
+ const FileSystemURL& root,
+ const StatusCallback& callback) {
+ DCHECK(pending_directory_stack_.empty());
+ DCHECK(pending_files_.empty());
+ DCHECK_EQ(0, inflight_operations_);
+
+ callback_ = callback;
+ ++inflight_operations_;
+ ProcessFile(
+ root,
+ base::Bind(&RecursiveOperationDelegate::DidTryProcessFile,
+ AsWeakPtr(), root));
+}
+
+FileSystemOperationRunner* RecursiveOperationDelegate::operation_runner() {
+ return file_system_context_->operation_runner();
+}
+
+void RecursiveOperationDelegate::OnCancel() {
+}
+
+void RecursiveOperationDelegate::DidTryProcessFile(
+ const FileSystemURL& root,
+ base::File::Error error) {
+ DCHECK(pending_directory_stack_.empty());
+ DCHECK(pending_files_.empty());
+ DCHECK_EQ(1, inflight_operations_);
+
+ --inflight_operations_;
+ if (canceled_ || error != base::File::FILE_ERROR_NOT_A_FILE) {
+ Done(error);
+ return;
+ }
+
+ pending_directory_stack_.push(std::queue<FileSystemURL>());
+ pending_directory_stack_.top().push(root);
+ ProcessNextDirectory();
+}
+
+void RecursiveOperationDelegate::ProcessNextDirectory() {
+ DCHECK(pending_files_.empty());
+ DCHECK(!pending_directory_stack_.empty());
+ DCHECK(!pending_directory_stack_.top().empty());
+ DCHECK_EQ(0, inflight_operations_);
+
+ const FileSystemURL& url = pending_directory_stack_.top().front();
+
+ ++inflight_operations_;
+ ProcessDirectory(
+ url,
+ base::Bind(
+ &RecursiveOperationDelegate::DidProcessDirectory, AsWeakPtr()));
+}
+
+void RecursiveOperationDelegate::DidProcessDirectory(
+ base::File::Error error) {
+ DCHECK(pending_files_.empty());
+ DCHECK(!pending_directory_stack_.empty());
+ DCHECK(!pending_directory_stack_.top().empty());
+ DCHECK_EQ(1, inflight_operations_);
+
+ --inflight_operations_;
+ if (canceled_ || error != base::File::FILE_OK) {
+ Done(error);
+ return;
+ }
+
+ const FileSystemURL& parent = pending_directory_stack_.top().front();
+ pending_directory_stack_.push(std::queue<FileSystemURL>());
+ operation_runner()->ReadDirectory(
+ parent,
+ base::Bind(&RecursiveOperationDelegate::DidReadDirectory,
+ AsWeakPtr(), parent));
+}
+
+void RecursiveOperationDelegate::DidReadDirectory(
+ const FileSystemURL& parent,
+ base::File::Error error,
+ const FileEntryList& entries,
+ bool has_more) {
+ DCHECK(!pending_directory_stack_.empty());
+ DCHECK_EQ(0, inflight_operations_);
+
+ if (canceled_ || error != base::File::FILE_OK) {
+ Done(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_directory_stack_.top().push(url);
+ else
+ pending_files_.push(url);
+ }
+
+ // Wait for next entries.
+ if (has_more)
+ return;
+
+ ProcessPendingFiles();
+}
+
+void RecursiveOperationDelegate::ProcessPendingFiles() {
+ DCHECK(!pending_directory_stack_.empty());
+
+ if ((pending_files_.empty() || canceled_) && inflight_operations_ == 0) {
+ ProcessSubDirectory();
+ return;
+ }
+
+ // Do not post any new tasks.
+ if (canceled_)
+ return;
+
+ // Run ProcessFile in parallel (upto kMaxInflightOperations).
+ scoped_refptr<base::MessageLoopProxy> current_message_loop =
+ base::MessageLoopProxy::current();
+ while (!pending_files_.empty() &&
+ inflight_operations_ < kMaxInflightOperations) {
+ ++inflight_operations_;
+ current_message_loop->PostTask(
+ FROM_HERE,
+ base::Bind(&RecursiveOperationDelegate::ProcessFile,
+ AsWeakPtr(), pending_files_.front(),
+ base::Bind(&RecursiveOperationDelegate::DidProcessFile,
+ AsWeakPtr())));
+ pending_files_.pop();
+ }
+}
+
+void RecursiveOperationDelegate::DidProcessFile(
+ base::File::Error error) {
+ --inflight_operations_;
+ if (error != base::File::FILE_OK) {
+ // If an error occurs, invoke Done immediately (even if there remain
+ // running operations). It is because in the callback, this instance is
+ // deleted.
+ Done(error);
+ return;
+ }
+
+ ProcessPendingFiles();
+}
+
+void RecursiveOperationDelegate::ProcessSubDirectory() {
+ DCHECK(pending_files_.empty());
+ DCHECK(!pending_directory_stack_.empty());
+ DCHECK_EQ(0, inflight_operations_);
+
+ if (canceled_) {
+ Done(base::File::FILE_ERROR_ABORT);
+ return;
+ }
+
+ if (!pending_directory_stack_.top().empty()) {
+ // There remain some sub directories. Process them first.
+ ProcessNextDirectory();
+ return;
+ }
+
+ // All subdirectories are processed.
+ pending_directory_stack_.pop();
+ if (pending_directory_stack_.empty()) {
+ // All files/directories are processed.
+ Done(base::File::FILE_OK);
+ return;
+ }
+
+ DCHECK(!pending_directory_stack_.top().empty());
+ ++inflight_operations_;
+ PostProcessDirectory(
+ pending_directory_stack_.top().front(),
+ base::Bind(&RecursiveOperationDelegate::DidPostProcessDirectory,
+ AsWeakPtr()));
+}
+
+void RecursiveOperationDelegate::DidPostProcessDirectory(
+ base::File::Error error) {
+ DCHECK(pending_files_.empty());
+ DCHECK(!pending_directory_stack_.empty());
+ DCHECK(!pending_directory_stack_.top().empty());
+ DCHECK_EQ(1, inflight_operations_);
+
+ --inflight_operations_;
+ pending_directory_stack_.top().pop();
+ if (canceled_ || error != base::File::FILE_OK) {
+ Done(error);
+ return;
+ }
+
+ ProcessSubDirectory();
+}
+
+void RecursiveOperationDelegate::Done(base::File::Error error) {
+ if (canceled_ && error == base::File::FILE_OK) {
+ callback_.Run(base::File::FILE_ERROR_ABORT);
+ } else {
+ callback_.Run(error);
+ }
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/recursive_operation_delegate.h b/storage/browser/fileapi/recursive_operation_delegate.h
new file mode 100644
index 0000000..f7f07c2
--- /dev/null
+++ b/storage/browser/fileapi/recursive_operation_delegate.h
@@ -0,0 +1,152 @@
+// 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 STORAGE_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_
+#define STORAGE_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_
+
+#include <queue>
+#include <stack>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_system_url.h"
+
+namespace storage {
+
+class FileSystemContext;
+class FileSystemOperationRunner;
+
+// A base class for recursive operation delegates.
+//
+// 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.
+class STORAGE_EXPORT RecursiveOperationDelegate
+ : public base::SupportsWeakPtr<RecursiveOperationDelegate> {
+ public:
+ typedef FileSystemOperation::StatusCallback StatusCallback;
+ typedef FileSystemOperation::FileEntryList FileEntryList;
+
+ 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;
+
+
+ // This is called each time after files and subdirectories for a
+ // directory is processed while recursively performing an operation.
+ virtual void PostProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) = 0;
+
+ // Cancels the currently running operation.
+ void Cancel();
+
+ protected:
+ explicit RecursiveOperationDelegate(FileSystemContext* file_system_context);
+
+ // Starts to process files/directories recursively from the given |root|.
+ // This will call ProcessFile and ProcessDirectory on each file or directory.
+ //
+ // First, this tries to call ProcessFile with |root| regardless whether it is
+ // actually a file or a directory. If it is a directory, ProcessFile should
+ // return File::FILE_NOT_A_FILE.
+ //
+ // For each directory, the recursive operation works as follows:
+ // ProcessDirectory is called first for the directory.
+ // Then the directory contents are read (to obtain its sub directories and
+ // files in it).
+ // ProcessFile is called for found files. This may run in parallel.
+ // The same step is recursively applied to each subdirectory.
+ // After all files and subdirectories in a directory are processed,
+ // PostProcessDirectory is called for the directory.
+ // Here is an example;
+ // a_dir/ -+- b1_dir/ -+- c1_dir/ -+- d1_file
+ // | | |
+ // | +- c2_file +- d2_file
+ // |
+ // +- b2_dir/ --- e_dir/
+ // |
+ // +- b3_file
+ // |
+ // +- b4_file
+ // Then traverse order is:
+ // ProcessFile(a_dir) (This should return File::FILE_NOT_A_FILE).
+ // ProcessDirectory(a_dir).
+ // ProcessFile(b3_file), ProcessFile(b4_file). (in parallel).
+ // ProcessDirectory(b1_dir).
+ // ProcessFile(c2_file)
+ // ProcessDirectory(c1_dir).
+ // ProcessFile(d1_file), ProcessFile(d2_file). (in parallel).
+ // PostProcessDirectory(c1_dir)
+ // PostProcessDirectory(b1_dir).
+ // ProcessDirectory(b2_dir)
+ // ProcessDirectory(e_dir)
+ // PostProcessDirectory(e_dir)
+ // PostProcessDirectory(b2_dir)
+ // PostProcessDirectory(a_dir)
+ //
+ // |callback| is fired with base::File::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();
+
+ // Called when Cancel() is called. This is a hook to do something more
+ // in a derived class. By default, do nothing.
+ virtual void OnCancel();
+
+ private:
+ void DidTryProcessFile(const FileSystemURL& root,
+ base::File::Error error);
+ void ProcessNextDirectory();
+ void DidProcessDirectory(base::File::Error error);
+ void DidReadDirectory(const FileSystemURL& parent,
+ base::File::Error error,
+ const FileEntryList& entries,
+ bool has_more);
+ void ProcessPendingFiles();
+ void DidProcessFile(base::File::Error error);
+ void ProcessSubDirectory();
+ void DidPostProcessDirectory(base::File::Error error);
+
+ // Called when all recursive operation is done (or an error occurs).
+ void Done(base::File::Error error);
+
+ FileSystemContext* file_system_context_;
+ StatusCallback callback_;
+ std::stack<FileSystemURL> pending_directories_;
+ std::stack<std::queue<FileSystemURL> > pending_directory_stack_;
+ std::queue<FileSystemURL> pending_files_;
+ int inflight_operations_;
+ bool canceled_;
+
+ DISALLOW_COPY_AND_ASSIGN(RecursiveOperationDelegate);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_RECURSIVE_OPERATION_DELEGATE_H_
diff --git a/storage/browser/fileapi/remove_operation_delegate.cc b/storage/browser/fileapi/remove_operation_delegate.cc
new file mode 100644
index 0000000..eb28587
--- /dev/null
+++ b/storage/browser/fileapi/remove_operation_delegate.cc
@@ -0,0 +1,82 @@
+// 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 "storage/browser/fileapi/remove_operation_delegate.h"
+
+#include "base/bind.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+
+namespace storage {
+
+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_, callback_);
+}
+
+void RemoveOperationDelegate::ProcessFile(const FileSystemURL& url,
+ const StatusCallback& callback) {
+ operation_runner()->RemoveFile(
+ url,
+ base::Bind(&RemoveOperationDelegate::DidRemoveFile,
+ weak_factory_.GetWeakPtr(), callback));
+}
+
+void RemoveOperationDelegate::ProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) {
+ callback.Run(base::File::FILE_OK);
+}
+
+void RemoveOperationDelegate::PostProcessDirectory(
+ const FileSystemURL& url, const StatusCallback& callback) {
+ operation_runner()->RemoveDirectory(url, callback);
+}
+
+void RemoveOperationDelegate::DidTryRemoveFile(base::File::Error error) {
+ if (error != base::File::FILE_ERROR_NOT_A_FILE &&
+ error != base::File::FILE_ERROR_SECURITY) {
+ callback_.Run(error);
+ return;
+ }
+ operation_runner()->RemoveDirectory(
+ url_,
+ base::Bind(&RemoveOperationDelegate::DidTryRemoveDirectory,
+ weak_factory_.GetWeakPtr(), error));
+}
+
+void RemoveOperationDelegate::DidTryRemoveDirectory(
+ base::File::Error remove_file_error,
+ base::File::Error remove_directory_error) {
+ callback_.Run(
+ remove_directory_error == base::File::FILE_ERROR_NOT_A_DIRECTORY ?
+ remove_file_error :
+ remove_directory_error);
+}
+
+void RemoveOperationDelegate::DidRemoveFile(const StatusCallback& callback,
+ base::File::Error error) {
+ if (error == base::File::FILE_ERROR_NOT_FOUND) {
+ callback.Run(base::File::FILE_OK);
+ return;
+ }
+ callback.Run(error);
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/remove_operation_delegate.h b/storage/browser/fileapi/remove_operation_delegate.h
new file mode 100644
index 0000000..436a641
--- /dev/null
+++ b/storage/browser/fileapi/remove_operation_delegate.h
@@ -0,0 +1,46 @@
+// 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 STORAGE_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_
+#define STORAGE_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_
+
+#include <stack>
+
+#include "storage/browser/fileapi/recursive_operation_delegate.h"
+
+namespace storage {
+
+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;
+ virtual void PostProcessDirectory(const FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+
+ private:
+ void DidTryRemoveFile(base::File::Error error);
+ void DidTryRemoveDirectory(base::File::Error remove_file_error,
+ base::File::Error remove_directory_error);
+ void DidRemoveFile(const StatusCallback& callback,
+ base::File::Error error);
+
+ FileSystemURL url_;
+ StatusCallback callback_;
+ base::WeakPtrFactory<RemoveOperationDelegate> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(RemoveOperationDelegate);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_REMOVE_OPERATION_DELEGATE_H_
diff --git a/storage/browser/fileapi/sandbox_directory_database.cc b/storage/browser/fileapi/sandbox_directory_database.cc
new file mode 100644
index 0000000..e34d324
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_directory_database.cc
@@ -0,0 +1,939 @@
+// 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 "storage/browser/fileapi/sandbox_directory_database.h"
+
+#include <math.h>
+#include <algorithm>
+#include <set>
+#include <stack>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.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 "storage/browser/fileapi/file_system_usage_cache.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
+
+namespace {
+
+bool PickleFromFileInfo(const storage::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 = storage::FilePathToString(info.data_path);
+ name = storage::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,
+ storage::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 = storage::StringToFilePath(data_path);
+ info->name = storage::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(
+ storage::SandboxDirectoryDatabase::FileId parent_id,
+ const base::FilePath::StringType& child_name) {
+ std::string name;
+ name = storage::FilePathToString(base::FilePath(child_name));
+ return std::string(kChildLookupPrefix) + base::Int64ToString(parent_id) +
+ std::string(kChildLookupSeparator) + name;
+}
+
+std::string GetChildListingKeyPrefix(
+ storage::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(
+ storage::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 storage::SandboxDirectoryDatabase::FileId FileId;
+ typedef storage::SandboxDirectoryDatabase::FileInfo FileInfo;
+
+ DatabaseCheckHelper(storage::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();
+
+ storage::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(
+ storage::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::File::Info platform_file_info;
+ if (!base::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(storage::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(storage::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 storage {
+
+SandboxDirectoryDatabase::FileInfo::FileInfo() : parent_id(0) {
+}
+
+SandboxDirectoryDatabase::FileInfo::~FileInfo() {
+}
+
+SandboxDirectoryDatabase::SandboxDirectoryDatabase(
+ const base::FilePath& filesystem_data_directory,
+ leveldb::Env* env_override)
+ : filesystem_data_directory_(filesystem_data_directory),
+ env_override_(env_override) {
+}
+
+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;
+}
+
+base::File::Error SandboxDirectoryDatabase::AddFileInfo(
+ const FileInfo& info, FileId* file_id) {
+ if (!Init(REPAIR_ON_CORRUPTION))
+ return base::File::FILE_ERROR_FAILED;
+ 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 base::File::FILE_ERROR_EXISTS;
+ }
+ if (!status.IsNotFound()) {
+ HandleError(FROM_HERE, status);
+ return base::File::FILE_ERROR_NOT_FOUND;
+ }
+
+ if (!IsDirectory(info.parent_id)) {
+ LOG(ERROR) << "New parent directory is a file!";
+ return base::File::FILE_ERROR_NOT_A_DIRECTORY;
+ }
+
+ // 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 base::File::FILE_ERROR_FAILED;
+ ++temp_id;
+
+ leveldb::WriteBatch batch;
+ if (!AddFileInfoHelper(info, temp_id, &batch))
+ return base::File::FILE_ERROR_FAILED;
+
+ batch.Put(LastFileIdKey(), base::Int64ToString(temp_id));
+ status = db_->Write(leveldb::WriteOptions(), &batch);
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ return base::File::FILE_ERROR_FAILED;
+ }
+ *file_id = temp_id;
+ return base::File::FILE_OK;
+}
+
+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 &&
+ !IsDirectory(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,
+ leveldb::Env* env_override) {
+ std::string name = FilePathToString(path.Append(kDirectoryDatabaseName));
+ leveldb::Options options;
+ if (env_override)
+ options.env = env_override;
+ leveldb::Status status = leveldb::DestroyDB(name, 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;
+ if (env_override_)
+ options.env = env_override_;
+ 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 (!base::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 (env_override_)
+ options.env = env_override_;
+ 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::IsDirectory(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())
+ return false;
+ return true;
+}
+
+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;
+}
+
+// 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 storage
diff --git a/storage/browser/fileapi/sandbox_directory_database.h b/storage/browser/fileapi/sandbox_directory_database.h
new file mode 100644
index 0000000..a966811
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_directory_database.h
@@ -0,0 +1,135 @@
+// 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 STORAGE_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_
+#define STORAGE_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace content {
+class SandboxDirectoryDatabaseTest;
+}
+
+namespace tracked_objects {
+class Location;
+}
+
+namespace leveldb {
+class DB;
+class Env;
+class Status;
+class WriteBatch;
+}
+
+namespace storage {
+
+// 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 STORAGE_EXPORT_PRIVATE SandboxDirectoryDatabase {
+ public:
+ typedef int64 FileId;
+
+ struct STORAGE_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;
+ };
+
+ SandboxDirectoryDatabase(
+ const base::FilePath& filesystem_data_directory,
+ leveldb::Env* env_override);
+ ~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);
+ base::File::Error 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);
+
+ bool IsDirectory(FileId file_id);
+
+ // Returns true if the database looks consistent with local filesystem.
+ bool IsFileSystemConsistent();
+
+ static bool DestroyDatabase(const base::FilePath& path,
+ leveldb::Env* env_override);
+
+ private:
+ enum RecoveryOption {
+ DELETE_ON_CORRUPTION,
+ REPAIR_ON_CORRUPTION,
+ FAIL_ON_CORRUPTION,
+ };
+
+ friend class content::SandboxDirectoryDatabaseTest;
+ friend class ObfuscatedFileUtil;
+
+ 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 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_;
+ leveldb::Env* env_override_;
+ scoped_ptr<leveldb::DB> db_;
+ base::Time last_reported_time_;
+ DISALLOW_COPY_AND_ASSIGN(SandboxDirectoryDatabase);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_DIRECTORY_DATABASE_H_
diff --git a/storage/browser/fileapi/sandbox_file_stream_writer.cc b/storage/browser/fileapi/sandbox_file_stream_writer.cc
new file mode 100644
index 0000000..d87c66d
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_file_stream_writer.cc
@@ -0,0 +1,247 @@
+// 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 "storage/browser/fileapi/sandbox_file_stream_writer.h"
+
+#include "base/files/file_util_proxy.h"
+#include "base/sequenced_task_runner.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/file_observers.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_runner.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace storage {
+
+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::File::Error file_error,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref) {
+ DCHECK(!file_ref.get());
+
+ if (CancelIfRequested())
+ return;
+ if (file_error != base::File::FILE_OK) {
+ callback.Run(net::FileErrorToNetError(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(FileStreamWriter::CreateForLocalFile(
+ file_system_context_->default_file_task_runner(),
+ platform_path,
+ initial_offset_,
+ FileStreamWriter::OPEN_EXISTING_FILE));
+
+ storage::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,
+ storage::QuotaStatusCode status,
+ int64 usage,
+ int64 quota) {
+ if (CancelIfRequested())
+ return;
+ if (status != storage::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 storage
diff --git a/storage/browser/fileapi/sandbox_file_stream_writer.h b/storage/browser/fileapi/sandbox_file_stream_writer.h
new file mode 100644
index 0000000..d24e5b9
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_file_stream_writer.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 STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_
+#define STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/task_runner_bound_observer_list.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/blob/shareable_file_reference.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/quota/quota_types.h"
+#include "url/gurl.h"
+
+namespace storage {
+
+class FileSystemContext;
+class FileSystemQuotaUtil;
+class FileStreamWriter;
+
+class STORAGE_EXPORT_PRIVATE SandboxFileStreamWriter
+ : public NON_EXPORTED_BASE(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::File::Error file_error,
+ const base::File::Info& file_info,
+ const base::FilePath& platform_path,
+ const scoped_refptr<storage::ShareableFileReference>& file_ref);
+ void DidGetUsageAndQuota(const net::CompletionCallback& callback,
+ storage::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<FileStreamWriter> 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_STREAM_WRITER_H_
diff --git a/storage/browser/fileapi/sandbox_file_system_backend.cc b/storage/browser/fileapi/sandbox_file_system_backend.cc
new file mode 100644
index 0000000..888d4f5
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_file_system_backend.cc
@@ -0,0 +1,166 @@
+// 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 "storage/browser/fileapi/sandbox_file_system_backend.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/metrics/histogram.h"
+#include "base/task_runner_util.h"
+#include "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/async_file_util_adapter.h"
+#include "storage/browser/fileapi/copy_or_move_file_validator.h"
+#include "storage/browser/fileapi/file_stream_writer.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_options.h"
+#include "storage/browser/fileapi/file_system_usage_cache.h"
+#include "storage/browser/fileapi/obfuscated_file_util.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "storage/browser/fileapi/sandbox_quota_observer.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/common/fileapi/file_system_types.h"
+#include "storage/common/fileapi/file_system_util.h"
+#include "url/gurl.h"
+
+using storage::QuotaManagerProxy;
+using storage::SpecialStoragePolicy;
+
+namespace storage {
+
+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) {
+ DCHECK(delegate_);
+
+ // Set quota observers.
+ delegate_->RegisterQuotaUpdateObserver(storage::kFileSystemTypeTemporary);
+ delegate_->AddFileAccessObserver(
+ storage::kFileSystemTypeTemporary, delegate_->quota_observer(), NULL);
+
+ delegate_->RegisterQuotaUpdateObserver(storage::kFileSystemTypePersistent);
+ delegate_->AddFileAccessObserver(
+ storage::kFileSystemTypePersistent, delegate_->quota_observer(), NULL);
+}
+
+void SandboxFileSystemBackend::ResolveURL(
+ const FileSystemURL& url,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) {
+ DCHECK(CanHandleType(url.type()));
+ DCHECK(delegate_);
+ if (delegate_->file_system_options().is_incognito() &&
+ !(url.type() == kFileSystemTypeTemporary &&
+ enable_temporary_file_system_in_incognito_)) {
+ // TODO(kinuko): return an isolated temporary directory.
+ callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY);
+ return;
+ }
+
+ delegate_->OpenFileSystem(url.origin(),
+ url.type(),
+ mode,
+ callback,
+ GetFileSystemRootURI(url.origin(), url.type()));
+}
+
+AsyncFileUtil* SandboxFileSystemBackend::GetAsyncFileUtil(
+ FileSystemType type) {
+ DCHECK(delegate_);
+ return delegate_->file_util();
+}
+
+WatcherManager* SandboxFileSystemBackend::GetWatcherManager(
+ FileSystemType type) {
+ return NULL;
+}
+
+CopyOrMoveFileValidatorFactory*
+SandboxFileSystemBackend::GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type,
+ base::File::Error* error_code) {
+ DCHECK(error_code);
+ *error_code = base::File::FILE_OK;
+ return NULL;
+}
+
+FileSystemOperation* SandboxFileSystemBackend::CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::File::Error* error_code) const {
+ DCHECK(CanHandleType(url.type()));
+ DCHECK(error_code);
+
+ DCHECK(delegate_);
+ scoped_ptr<FileSystemOperationContext> operation_context =
+ delegate_->CreateFileSystemOperationContext(url, context, error_code);
+ if (!operation_context)
+ return NULL;
+
+ SpecialStoragePolicy* policy = delegate_->special_storage_policy();
+ if (policy && policy->IsStorageUnlimited(url.origin()))
+ operation_context->set_quota_limit_type(storage::kQuotaLimitTypeUnlimited);
+ else
+ operation_context->set_quota_limit_type(storage::kQuotaLimitTypeLimited);
+
+ return FileSystemOperation::Create(url, context, operation_context.Pass());
+}
+
+bool SandboxFileSystemBackend::SupportsStreaming(
+ const storage::FileSystemURL& url) const {
+ return false;
+}
+
+bool SandboxFileSystemBackend::HasInplaceCopyImplementation(
+ storage::FileSystemType type) const {
+ return true;
+}
+
+scoped_ptr<storage::FileStreamReader>
+SandboxFileSystemBackend::CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const {
+ DCHECK(CanHandleType(url.type()));
+ DCHECK(delegate_);
+ return delegate_->CreateFileStreamReader(
+ url, offset, expected_modification_time, context);
+}
+
+scoped_ptr<storage::FileStreamWriter>
+SandboxFileSystemBackend::CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context) const {
+ DCHECK(CanHandleType(url.type()));
+ DCHECK(delegate_);
+ return delegate_->CreateFileStreamWriter(url, offset, context, url.type());
+}
+
+FileSystemQuotaUtil* SandboxFileSystemBackend::GetQuotaUtil() {
+ return delegate_;
+}
+
+SandboxFileSystemBackendDelegate::OriginEnumerator*
+SandboxFileSystemBackend::CreateOriginEnumerator() {
+ DCHECK(delegate_);
+ return delegate_->CreateOriginEnumerator();
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/sandbox_file_system_backend.h b/storage/browser/fileapi/sandbox_file_system_backend.h
new file mode 100644
index 0000000..1f939a5
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_file_system_backend.h
@@ -0,0 +1,85 @@
+// 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 STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_
+#define STORAGE_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 "storage/browser/fileapi/file_system_backend.h"
+#include "storage/browser/fileapi/file_system_quota_util.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "storage/browser/fileapi/task_runner_bound_observer_list.h"
+#include "storage/browser/quota/special_storage_policy.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace storage {
+
+// 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 STORAGE_EXPORT SandboxFileSystemBackend
+ : public FileSystemBackend {
+ public:
+ explicit SandboxFileSystemBackend(SandboxFileSystemBackendDelegate* delegate);
+ virtual ~SandboxFileSystemBackend();
+
+ // FileSystemBackend overrides.
+ virtual bool CanHandleType(FileSystemType type) const OVERRIDE;
+ virtual void Initialize(FileSystemContext* context) OVERRIDE;
+ virtual void ResolveURL(const FileSystemURL& url,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback) OVERRIDE;
+ virtual AsyncFileUtil* GetAsyncFileUtil(FileSystemType type) OVERRIDE;
+ virtual WatcherManager* GetWatcherManager(FileSystemType type) OVERRIDE;
+ virtual CopyOrMoveFileValidatorFactory* GetCopyOrMoveFileValidatorFactory(
+ FileSystemType type,
+ base::File::Error* error_code) OVERRIDE;
+ virtual FileSystemOperation* CreateFileSystemOperation(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::File::Error* error_code) const OVERRIDE;
+ virtual bool SupportsStreaming(const FileSystemURL& url) const OVERRIDE;
+ virtual bool HasInplaceCopyImplementation(
+ storage::FileSystemType type) const OVERRIDE;
+ virtual scoped_ptr<storage::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();
+
+ void set_enable_temporary_file_system_in_incognito(bool enable) {
+ enable_temporary_file_system_in_incognito_ = enable;
+ }
+ bool enable_temporary_file_system_in_incognito() const {
+ return enable_temporary_file_system_in_incognito_;
+ }
+
+
+ private:
+ SandboxFileSystemBackendDelegate* delegate_; // Not owned.
+
+ bool enable_temporary_file_system_in_incognito_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxFileSystemBackend);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_H_
diff --git a/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc b/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc
new file mode 100644
index 0000000..a5b1f63
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_file_system_backend_delegate.cc
@@ -0,0 +1,673 @@
+// 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 "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/files/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 "storage/browser/blob/file_stream_reader.h"
+#include "storage/browser/fileapi/async_file_util_adapter.h"
+#include "storage/browser/fileapi/file_system_context.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/file_system_usage_cache.h"
+#include "storage/browser/fileapi/obfuscated_file_util.h"
+#include "storage/browser/fileapi/quota/quota_backend_impl.h"
+#include "storage/browser/fileapi/quota/quota_reservation.h"
+#include "storage/browser/fileapi/quota/quota_reservation_manager.h"
+#include "storage/browser/fileapi/sandbox_file_stream_writer.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend.h"
+#include "storage/browser/fileapi/sandbox_quota_observer.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace storage {
+
+namespace {
+
+const char kTemporaryOriginsCountLabel[] = "FileSystem.TemporaryOriginsCount";
+const char kPersistentOriginsCountLabel[] = "FileSystem.PersistentOriginsCount";
+
+const char kOpenFileSystemLabel[] = "FileSystem.OpenFileSystem";
+const char kOpenFileSystemDetailLabel[] = "FileSystem.OpenFileSystemDetail";
+const char kOpenFileSystemDetailNonThrottledLabel[] =
+ "FileSystem.OpenFileSystemDetailNonthrottled";
+int64 kMinimumStatsCollectionIntervalHours = 1;
+
+// For type directory names in ObfuscatedFileUtil.
+// TODO(kinuko,nhiroki): Each type string registration should be done
+// via its own backend.
+const char kTemporaryDirectoryName[] = "t";
+const char kPersistentDirectoryName[] = "p";
+const char kSyncableDirectoryName[] = "s";
+
+const char* kPrepopulateTypes[] = {
+ kPersistentDirectoryName,
+ kTemporaryDirectoryName
+};
+
+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('\\'),
+};
+
+std::string GetTypeStringForURL(const FileSystemURL& url) {
+ return SandboxFileSystemBackendDelegate::GetTypeString(url.type());
+}
+
+std::set<std::string> GetKnownTypeStrings() {
+ std::set<std::string> known_type_strings;
+ known_type_strings.insert(kTemporaryDirectoryName);
+ known_type_strings.insert(kPersistentDirectoryName);
+ known_type_strings.insert(kSyncableDirectoryName);
+ return known_type_strings;
+}
+
+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(FileSystemType type) const OVERRIDE {
+ return enum_->HasTypeDirectory(
+ SandboxFileSystemBackendDelegate::GetTypeString(type));
+ }
+
+ private:
+ scoped_ptr<ObfuscatedFileUtil::AbstractOriginEnumerator> enum_;
+};
+
+void OpenFileSystemOnFileTaskRunner(
+ ObfuscatedFileUtil* file_util,
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ base::File::Error* error_ptr) {
+ DCHECK(error_ptr);
+ const bool create = (mode == OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT);
+ file_util->GetDirectoryForOriginAndType(
+ origin_url, SandboxFileSystemBackendDelegate::GetTypeString(type),
+ create, error_ptr);
+ if (*error_ptr != base::File::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::File::Error error)>& callback,
+ base::File::Error* error) {
+ if (delegate.get())
+ delegate.get()->CollectOpenFileSystemMetrics(*error);
+ callback.Run(*error);
+}
+
+template <typename T>
+void DeleteSoon(base::SequencedTaskRunner* runner, T* ptr) {
+ if (!runner->DeleteSoon(FROM_HERE, ptr))
+ delete ptr;
+}
+
+} // namespace
+
+const base::FilePath::CharType
+SandboxFileSystemBackendDelegate::kFileSystemDirectory[] =
+ FILE_PATH_LITERAL("File System");
+
+// static
+std::string SandboxFileSystemBackendDelegate::GetTypeString(
+ FileSystemType type) {
+ switch (type) {
+ case kFileSystemTypeTemporary:
+ return kTemporaryDirectoryName;
+ case kFileSystemTypePersistent:
+ return kPersistentDirectoryName;
+ case kFileSystemTypeSyncable:
+ case kFileSystemTypeSyncableForInternalSync:
+ return kSyncableDirectoryName;
+ case kFileSystemTypeUnknown:
+ default:
+ NOTREACHED() << "Unknown filesystem type requested:" << type;
+ return std::string();
+ }
+}
+
+SandboxFileSystemBackendDelegate::SandboxFileSystemBackendDelegate(
+ storage::QuotaManagerProxy* quota_manager_proxy,
+ base::SequencedTaskRunner* file_task_runner,
+ const base::FilePath& profile_path,
+ storage::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_system_options.env_override(),
+ file_task_runner,
+ base::Bind(&GetTypeStringForURL),
+ GetKnownTypeStrings(),
+ this))),
+ file_system_usage_cache_(new FileSystemUsageCache(file_task_runner)),
+ quota_observer_(new SandboxQuotaObserver(quota_manager_proxy,
+ file_task_runner,
+ obfuscated_file_util(),
+ usage_cache())),
+ quota_reservation_manager_(new QuotaReservationManager(
+ scoped_ptr<QuotaReservationManager::QuotaBackend>(
+ new QuotaBackendImpl(file_task_runner_.get(),
+ obfuscated_file_util(),
+ usage_cache(),
+ quota_manager_proxy)))),
+ special_storage_policy_(special_storage_policy),
+ file_system_options_(file_system_options),
+ is_filesystem_opened_(false),
+ weak_factory_(this) {
+ // Prepopulate database only if it can run asynchronously (i.e. the current
+ // thread is not file_task_runner). Usually this is the case but may not
+ // in test code.
+ if (!file_system_options.is_incognito() &&
+ !file_task_runner_->RunsTasksOnCurrentThread()) {
+ std::vector<std::string> types_to_prepopulate(
+ &kPrepopulateTypes[0],
+ &kPrepopulateTypes[arraysize(kPrepopulateTypes)]);
+ file_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&ObfuscatedFileUtil::MaybePrepopulateDatabase,
+ base::Unretained(obfuscated_file_util()),
+ types_to_prepopulate));
+ }
+}
+
+SandboxFileSystemBackendDelegate::~SandboxFileSystemBackendDelegate() {
+ io_thread_checker_.DetachFromThread();
+
+ if (!file_task_runner_->RunsTasksOnCurrentThread()) {
+ DeleteSoon(file_task_runner_.get(), quota_reservation_manager_.release());
+ DeleteSoon(file_task_runner_.get(), sandbox_file_util_.release());
+ DeleteSoon(file_task_runner_.get(), quota_observer_.release());
+ DeleteSoon(file_task_runner_.get(), file_system_usage_cache_.release());
+ }
+}
+
+SandboxFileSystemBackendDelegate::OriginEnumerator*
+SandboxFileSystemBackendDelegate::CreateOriginEnumerator() {
+ return new ObfuscatedOriginEnumerator(obfuscated_file_util());
+}
+
+base::FilePath
+SandboxFileSystemBackendDelegate::GetBaseDirectoryForOriginAndType(
+ const GURL& origin_url,
+ FileSystemType type,
+ bool create) {
+ base::File::Error error = base::File::FILE_OK;
+ base::FilePath path = obfuscated_file_util()->GetDirectoryForOriginAndType(
+ origin_url, GetTypeString(type), create, &error);
+ if (error != base::File::FILE_OK)
+ return base::FilePath();
+ return path;
+}
+
+void SandboxFileSystemBackendDelegate::OpenFileSystem(
+ const GURL& origin_url,
+ FileSystemType type,
+ OpenFileSystemMode mode,
+ const OpenFileSystemCallback& callback,
+ const GURL& root_url) {
+ if (!IsAllowedScheme(origin_url)) {
+ callback.Run(GURL(), std::string(), base::File::FILE_ERROR_SECURITY);
+ return;
+ }
+
+ std::string name = GetFileSystemName(origin_url, type);
+
+ base::File::Error* error_ptr = new base::File::Error;
+ file_task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&OpenFileSystemOnFileTaskRunner,
+ 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)));
+
+ io_thread_checker_.DetachFromThread();
+ is_filesystem_opened_ = true;
+}
+
+scoped_ptr<FileSystemOperationContext>
+SandboxFileSystemBackendDelegate::CreateFileSystemOperationContext(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::File::Error* error_code) const {
+ if (!IsAccessValid(url)) {
+ *error_code = base::File::FILE_ERROR_SECURITY;
+ return scoped_ptr<FileSystemOperationContext>();
+ }
+
+ const UpdateObserverList* update_observers = GetUpdateObservers(url.type());
+ const ChangeObserverList* change_observers = GetChangeObservers(url.type());
+ DCHECK(update_observers);
+
+ scoped_ptr<FileSystemOperationContext> operation_context(
+ new FileSystemOperationContext(context));
+ operation_context->set_update_observers(*update_observers);
+ operation_context->set_change_observers(
+ change_observers ? *change_observers : ChangeObserverList());
+
+ return operation_context.Pass();
+}
+
+scoped_ptr<storage::FileStreamReader>
+SandboxFileSystemBackendDelegate::CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const {
+ if (!IsAccessValid(url))
+ return scoped_ptr<storage::FileStreamReader>();
+ return scoped_ptr<storage::FileStreamReader>(
+ storage::FileStreamReader::CreateForFileSystemFile(
+ context, url, offset, expected_modification_time));
+}
+
+scoped_ptr<FileStreamWriter>
+SandboxFileSystemBackendDelegate::CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context,
+ FileSystemType type) const {
+ if (!IsAccessValid(url))
+ return scoped_ptr<FileStreamWriter>();
+ const UpdateObserverList* observers = GetUpdateObservers(type);
+ DCHECK(observers);
+ return scoped_ptr<FileStreamWriter>(
+ new SandboxFileStreamWriter(context, url, offset, *observers));
+}
+
+base::File::Error
+SandboxFileSystemBackendDelegate::DeleteOriginDataOnFileTaskRunner(
+ FileSystemContext* file_system_context,
+ storage::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ FileSystemType type) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ int64 usage = GetOriginUsageOnFileTaskRunner(
+ file_system_context, origin_url, type);
+ usage_cache()->CloseCacheFiles();
+ bool result = obfuscated_file_util()->DeleteDirectoryForOriginAndType(
+ origin_url, GetTypeString(type));
+ if (result && proxy) {
+ proxy->NotifyStorageModified(storage::QuotaClient::kFileSystem,
+ origin_url,
+ FileSystemTypeToQuotaStorageType(type),
+ -usage);
+ }
+
+ if (result)
+ return base::File::FILE_OK;
+ return base::File::FILE_ERROR_FAILED;
+}
+
+void SandboxFileSystemBackendDelegate::GetOriginsForTypeOnFileTaskRunner(
+ FileSystemType type, std::set<GURL>* origins) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(origins);
+ scoped_ptr<OriginEnumerator> enumerator(CreateOriginEnumerator());
+ GURL origin;
+ while (!(origin = enumerator->Next()).is_empty()) {
+ if (enumerator->HasFileSystemType(type))
+ origins->insert(origin);
+ }
+ switch (type) {
+ case kFileSystemTypeTemporary:
+ UMA_HISTOGRAM_COUNTS(kTemporaryOriginsCountLabel, origins->size());
+ break;
+ case kFileSystemTypePersistent:
+ UMA_HISTOGRAM_COUNTS(kPersistentOriginsCountLabel, origins->size());
+ break;
+ default:
+ break;
+ }
+}
+
+void SandboxFileSystemBackendDelegate::GetOriginsForHostOnFileTaskRunner(
+ FileSystemType type, const std::string& host,
+ std::set<GURL>* origins) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ 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::GetOriginUsageOnFileTaskRunner(
+ FileSystemContext* file_system_context,
+ const GURL& origin_url,
+ FileSystemType type) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+
+ // 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;
+}
+
+scoped_refptr<QuotaReservation>
+SandboxFileSystemBackendDelegate::CreateQuotaReservationOnFileTaskRunner(
+ const GURL& origin,
+ FileSystemType type) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(quota_reservation_manager_);
+ return quota_reservation_manager_->CreateReservation(origin, type);
+}
+
+void SandboxFileSystemBackendDelegate::AddFileUpdateObserver(
+ FileSystemType type,
+ FileUpdateObserver* observer,
+ base::SequencedTaskRunner* task_runner) {
+ DCHECK(!is_filesystem_opened_ || io_thread_checker_.CalledOnValidThread());
+ update_observers_[type] =
+ update_observers_[type].AddObserver(observer, task_runner);
+}
+
+void SandboxFileSystemBackendDelegate::AddFileChangeObserver(
+ FileSystemType type,
+ FileChangeObserver* observer,
+ base::SequencedTaskRunner* task_runner) {
+ DCHECK(!is_filesystem_opened_ || io_thread_checker_.CalledOnValidThread());
+ change_observers_[type] =
+ change_observers_[type].AddObserver(observer, task_runner);
+}
+
+void SandboxFileSystemBackendDelegate::AddFileAccessObserver(
+ FileSystemType type,
+ FileAccessObserver* observer,
+ base::SequencedTaskRunner* task_runner) {
+ DCHECK(!is_filesystem_opened_ || io_thread_checker_.CalledOnValidThread());
+ access_observers_[type] =
+ access_observers_[type].AddObserver(observer, task_runner);
+}
+
+const UpdateObserverList* SandboxFileSystemBackendDelegate::GetUpdateObservers(
+ FileSystemType type) const {
+ std::map<FileSystemType, UpdateObserverList>::const_iterator iter =
+ update_observers_.find(type);
+ if (iter == update_observers_.end())
+ return NULL;
+ return &iter->second;
+}
+
+const ChangeObserverList* SandboxFileSystemBackendDelegate::GetChangeObservers(
+ FileSystemType type) const {
+ std::map<FileSystemType, ChangeObserverList>::const_iterator iter =
+ change_observers_.find(type);
+ if (iter == change_observers_.end())
+ return NULL;
+ return &iter->second;
+}
+
+const AccessObserverList* SandboxFileSystemBackendDelegate::GetAccessObservers(
+ FileSystemType type) const {
+ std::map<FileSystemType, AccessObserverList>::const_iterator iter =
+ access_observers_.find(type);
+ if (iter == access_observers_.end())
+ return NULL;
+ return &iter->second;
+}
+
+void SandboxFileSystemBackendDelegate::RegisterQuotaUpdateObserver(
+ FileSystemType type) {
+ AddFileUpdateObserver(type, quota_observer_.get(), file_task_runner_.get());
+}
+
+void SandboxFileSystemBackendDelegate::InvalidateUsageCache(
+ const GURL& origin,
+ FileSystemType type) {
+ base::File::Error error = base::File::FILE_OK;
+ base::FilePath usage_file_path = GetUsageCachePathForOriginAndType(
+ obfuscated_file_util(), origin, type, &error);
+ if (error != base::File::FILE_OK)
+ return;
+ usage_cache()->IncrementDirty(usage_file_path);
+}
+
+void SandboxFileSystemBackendDelegate::StickyInvalidateUsageCache(
+ const GURL& origin,
+ 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();
+}
+
+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.SchemeIsHTTPOrHTTPS())
+ 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;
+}
+
+base::FilePath
+SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType(
+ const GURL& origin_url,
+ FileSystemType type) {
+ base::File::Error error;
+ base::FilePath path = GetUsageCachePathForOriginAndType(
+ obfuscated_file_util(), origin_url, type, &error);
+ if (error != base::File::FILE_OK)
+ return base::FilePath();
+ return path;
+}
+
+// static
+base::FilePath
+SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType(
+ ObfuscatedFileUtil* sandbox_file_util,
+ const GURL& origin_url,
+ FileSystemType type,
+ base::File::Error* error_out) {
+ DCHECK(error_out);
+ *error_out = base::File::FILE_OK;
+ base::FilePath base_path = sandbox_file_util->GetDirectoryForOriginAndType(
+ origin_url, GetTypeString(type), false /* create */, error_out);
+ if (*error_out != base::File::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::File::Error 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::File::FILE_OK:
+ REPORT(kOK);
+ break;
+ case base::File::FILE_ERROR_INVALID_URL:
+ REPORT(kInvalidSchemeError);
+ break;
+ case base::File::FILE_ERROR_NOT_FOUND:
+ REPORT(kNotFound);
+ break;
+ case base::File::FILE_ERROR_FAILED:
+ default:
+ REPORT(kUnknownError);
+ break;
+ }
+#undef REPORT
+}
+
+ObfuscatedFileUtil* SandboxFileSystemBackendDelegate::obfuscated_file_util() {
+ return static_cast<ObfuscatedFileUtil*>(sync_file_util());
+}
+
+// Declared in obfuscated_file_util.h.
+// static
+ObfuscatedFileUtil* ObfuscatedFileUtil::CreateForTesting(
+ storage::SpecialStoragePolicy* special_storage_policy,
+ const base::FilePath& file_system_directory,
+ leveldb::Env* env_override,
+ base::SequencedTaskRunner* file_task_runner) {
+ return new ObfuscatedFileUtil(special_storage_policy,
+ file_system_directory,
+ env_override,
+ file_task_runner,
+ base::Bind(&GetTypeStringForURL),
+ GetKnownTypeStrings(),
+ NULL);
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/sandbox_file_system_backend_delegate.h b/storage/browser/fileapi/sandbox_file_system_backend_delegate.h
new file mode 100644
index 0000000..d93bada
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_file_system_backend_delegate.h
@@ -0,0 +1,260 @@
+// 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 STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_
+#define STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "storage/browser/fileapi/file_system_backend.h"
+#include "storage/browser/fileapi/file_system_options.h"
+#include "storage/browser/fileapi/file_system_quota_util.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace content {
+class SandboxFileSystemBackendDelegateTest;
+class SandboxFileSystemTestHelper;
+}
+
+namespace storage {
+class QuotaManagerProxy;
+class SpecialStoragePolicy;
+}
+
+namespace storage {
+class FileStreamReader;
+}
+
+namespace storage {
+
+class AsyncFileUtil;
+class FileStreamWriter;
+class FileSystemFileUtil;
+class FileSystemOperationContext;
+class FileSystemURL;
+class FileSystemUsageCache;
+class ObfuscatedFileUtil;
+class QuotaReservationManager;
+class SandboxFileSystemBackend;
+class SandboxQuotaObserver;
+
+// Delegate implementation of the some methods in Sandbox/SyncFileSystemBackend.
+// An instance of this class is created and owned by FileSystemContext.
+class STORAGE_EXPORT SandboxFileSystemBackendDelegate
+ : public FileSystemQuotaUtil {
+ 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;
+ };
+
+ // Returns the type directory name in sandbox directory for given |type|.
+ static std::string GetTypeString(FileSystemType type);
+
+ SandboxFileSystemBackendDelegate(
+ storage::QuotaManagerProxy* quota_manager_proxy,
+ base::SequencedTaskRunner* file_task_runner,
+ const base::FilePath& profile_path,
+ storage::SpecialStoragePolicy* special_storage_policy,
+ const FileSystemOptions& file_system_options);
+
+ virtual ~SandboxFileSystemBackendDelegate();
+
+ // 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);
+ scoped_ptr<FileSystemOperationContext> CreateFileSystemOperationContext(
+ const FileSystemURL& url,
+ FileSystemContext* context,
+ base::File::Error* error_code) const;
+ scoped_ptr<storage::FileStreamReader> CreateFileStreamReader(
+ const FileSystemURL& url,
+ int64 offset,
+ const base::Time& expected_modification_time,
+ FileSystemContext* context) const;
+ scoped_ptr<FileStreamWriter> CreateFileStreamWriter(
+ const FileSystemURL& url,
+ int64 offset,
+ FileSystemContext* context,
+ FileSystemType type) const;
+
+ // FileSystemQuotaUtil overrides.
+ virtual base::File::Error DeleteOriginDataOnFileTaskRunner(
+ FileSystemContext* context,
+ storage::QuotaManagerProxy* proxy,
+ const GURL& origin_url,
+ FileSystemType type) OVERRIDE;
+ virtual void GetOriginsForTypeOnFileTaskRunner(
+ FileSystemType type,
+ std::set<GURL>* origins) OVERRIDE;
+ virtual void GetOriginsForHostOnFileTaskRunner(
+ FileSystemType type,
+ const std::string& host,
+ std::set<GURL>* origins) OVERRIDE;
+ virtual int64 GetOriginUsageOnFileTaskRunner(
+ FileSystemContext* context,
+ const GURL& origin_url,
+ FileSystemType type) OVERRIDE;
+ virtual scoped_refptr<QuotaReservation>
+ CreateQuotaReservationOnFileTaskRunner(
+ 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;
+
+ // Registers quota observer for file updates on filesystem of |type|.
+ void RegisterQuotaUpdateObserver(FileSystemType type);
+
+ void InvalidateUsageCache(const GURL& origin_url,
+ FileSystemType type);
+ void StickyInvalidateUsageCache(const GURL& origin_url,
+ FileSystemType type);
+
+ void CollectOpenFileSystemMetrics(base::File::Error 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(); }
+
+ storage::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 QuotaBackendImpl;
+ friend class SandboxQuotaObserver;
+ friend class content::SandboxFileSystemBackendDelegateTest;
+ friend class content::SandboxFileSystemTestHelper;
+
+ // 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 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::File::Error* 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_ptr<QuotaReservationManager> quota_reservation_manager_;
+
+ scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_;
+
+ FileSystemOptions file_system_options_;
+
+ bool is_filesystem_opened_;
+ base::ThreadChecker io_thread_checker_;
+
+ // Accessed only on the file thread.
+ std::set<GURL> visited_origins_;
+
+ std::set<std::pair<GURL, FileSystemType> > sticky_dirty_origins_;
+
+ std::map<FileSystemType, UpdateObserverList> update_observers_;
+ std::map<FileSystemType, ChangeObserverList> change_observers_;
+ std::map<FileSystemType, AccessObserverList> access_observers_;
+
+ base::Time next_release_time_for_open_filesystem_stat_;
+
+ base::WeakPtrFactory<SandboxFileSystemBackendDelegate> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxFileSystemBackendDelegate);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_FILE_SYSTEM_BACKEND_DELEGATE_H_
diff --git a/storage/browser/fileapi/sandbox_isolated_origin_database.cc b/storage/browser/fileapi/sandbox_isolated_origin_database.cc
new file mode 100644
index 0000000..3e3c218
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_isolated_origin_database.cc
@@ -0,0 +1,80 @@
+// 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 "storage/browser/fileapi/sandbox_isolated_origin_database.h"
+
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "storage/browser/fileapi/sandbox_origin_database.h"
+
+namespace storage {
+
+// Special directory name for isolated origin.
+const base::FilePath::CharType
+SandboxIsolatedOriginDatabase::kObsoleteOriginDirectory[] =
+ FILE_PATH_LITERAL("iso");
+
+SandboxIsolatedOriginDatabase::SandboxIsolatedOriginDatabase(
+ const std::string& origin,
+ const base::FilePath& file_system_directory,
+ const base::FilePath& origin_directory)
+ : migration_checked_(false),
+ origin_(origin),
+ file_system_directory_(file_system_directory),
+ origin_directory_(origin_directory) {
+}
+
+SandboxIsolatedOriginDatabase::~SandboxIsolatedOriginDatabase() {
+}
+
+bool SandboxIsolatedOriginDatabase::HasOriginPath(
+ const std::string& origin) {
+ return (origin_ == origin);
+}
+
+bool SandboxIsolatedOriginDatabase::GetPathForOrigin(
+ const std::string& origin, base::FilePath* directory) {
+ if (origin != origin_)
+ return false;
+ *directory = origin_directory_;
+ return true;
+}
+
+bool SandboxIsolatedOriginDatabase::RemovePathForOrigin(
+ const std::string& origin) {
+ return true;
+}
+
+bool SandboxIsolatedOriginDatabase::ListAllOrigins(
+ std::vector<OriginRecord>* origins) {
+ origins->push_back(OriginRecord(origin_, origin_directory_));
+ return true;
+}
+
+void SandboxIsolatedOriginDatabase::DropDatabase() {
+}
+
+void SandboxIsolatedOriginDatabase::MigrateBackFromObsoleteOriginDatabase(
+ const std::string& origin,
+ const base::FilePath& file_system_directory,
+ SandboxOriginDatabase* database) {
+ base::FilePath isolated_directory =
+ file_system_directory.Append(kObsoleteOriginDirectory);
+
+ 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);
+ }
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/sandbox_isolated_origin_database.h b/storage/browser/fileapi/sandbox_isolated_origin_database.h
new file mode 100644
index 0000000..f3ee49c
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_isolated_origin_database.h
@@ -0,0 +1,61 @@
+// 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 STORAGE_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_
+#define STORAGE_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_
+
+#include <string>
+#include <vector>
+
+#include "storage/browser/fileapi/sandbox_origin_database_interface.h"
+
+namespace storage {
+
+class SandboxOriginDatabase;
+
+// This origin database implementation supports only one origin
+// (therefore is expected to run very fast).
+class STORAGE_EXPORT_PRIVATE SandboxIsolatedOriginDatabase
+ : public SandboxOriginDatabaseInterface {
+ public:
+ static const base::FilePath::CharType kObsoleteOriginDirectory[];
+
+ // Initialize this database for |origin| which makes GetPathForOrigin return
+ // |origin_directory| (in |file_system_directory|).
+ SandboxIsolatedOriginDatabase(
+ const std::string& origin,
+ const base::FilePath& file_system_directory,
+ const base::FilePath& origin_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;
+
+ // TODO(kinuko): Deprecate this after a few release cycles, e.g. around M33.
+ static void MigrateBackFromObsoleteOriginDatabase(
+ const std::string& origin,
+ const base::FilePath& file_system_directory,
+ SandboxOriginDatabase* origin_database);
+
+ const std::string& origin() const { return origin_; }
+
+ private:
+ void MigrateDatabaseIfNeeded();
+
+ bool migration_checked_;
+ const std::string origin_;
+ const base::FilePath file_system_directory_;
+ const base::FilePath origin_directory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxIsolatedOriginDatabase);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_ISOLATED_ORIGIN_DATABASE_H_
diff --git a/storage/browser/fileapi/sandbox_origin_database.cc b/storage/browser/fileapi/sandbox_origin_database.cc
new file mode 100644
index 0000000..fa0521f
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_origin_database.cc
@@ -0,0 +1,347 @@
+// 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 "storage/browser/fileapi/sandbox_origin_database.h"
+
+#include <set>
+#include <utility>
+
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.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 "storage/common/fileapi/file_system_util.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "third_party/leveldatabase/src/include/leveldb/write_batch.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 storage {
+
+SandboxOriginDatabase::SandboxOriginDatabase(
+ const base::FilePath& file_system_directory,
+ leveldb::Env* env_override)
+ : file_system_directory_(file_system_directory),
+ env_override_(env_override) {
+}
+
+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;
+ if (env_override_)
+ options.env = env_override_;
+ 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 (!base::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 (env_override_)
+ options.env = env_override_;
+ 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 storage
diff --git a/storage/browser/fileapi/sandbox_origin_database.h b/storage/browser/fileapi/sandbox_origin_database.h
new file mode 100644
index 0000000..21779d5
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_origin_database.h
@@ -0,0 +1,77 @@
+// 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 STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_
+#define STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "storage/browser/fileapi/sandbox_origin_database_interface.h"
+
+namespace leveldb {
+class DB;
+class Env;
+class Status;
+}
+
+namespace tracked_objects {
+class Location;
+}
+
+namespace storage {
+
+// 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 STORAGE_EXPORT_PRIVATE SandboxOriginDatabase
+ : public SandboxOriginDatabaseInterface {
+ public:
+ // Only one instance of SandboxOriginDatabase should exist for a given path
+ // at a given time.
+ SandboxOriginDatabase(const base::FilePath& file_system_directory,
+ leveldb::Env* env_override);
+ 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_;
+ leveldb::Env* env_override_;
+ scoped_ptr<leveldb::DB> db_;
+ base::Time last_reported_time_;
+ DISALLOW_COPY_AND_ASSIGN(SandboxOriginDatabase);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_H_
diff --git a/storage/browser/fileapi/sandbox_origin_database_interface.cc b/storage/browser/fileapi/sandbox_origin_database_interface.cc
new file mode 100644
index 0000000..25ba1dc
--- /dev/null
+++ b/storage/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 "storage/browser/fileapi/sandbox_origin_database_interface.h"
+
+namespace storage {
+
+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 storage
diff --git a/storage/browser/fileapi/sandbox_origin_database_interface.h b/storage/browser/fileapi/sandbox_origin_database_interface.h
new file mode 100644
index 0000000..4a01e43
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_
+#define STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace storage {
+
+class STORAGE_EXPORT_PRIVATE SandboxOriginDatabaseInterface {
+ public:
+ struct STORAGE_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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_ORIGIN_DATABASE_INTERFACE_H_
diff --git a/storage/browser/fileapi/sandbox_prioritized_origin_database.cc b/storage/browser/fileapi/sandbox_prioritized_origin_database.cc
new file mode 100644
index 0000000..80ccd27
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_prioritized_origin_database.cc
@@ -0,0 +1,223 @@
+// 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 "storage/browser/fileapi/sandbox_prioritized_origin_database.h"
+
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/pickle.h"
+#include "storage/browser/fileapi/sandbox_isolated_origin_database.h"
+#include "storage/browser/fileapi/sandbox_origin_database.h"
+
+namespace storage {
+
+namespace {
+
+const base::FilePath::CharType kPrimaryDirectory[] =
+ FILE_PATH_LITERAL("primary");
+const base::FilePath::CharType kPrimaryOriginFile[] =
+ FILE_PATH_LITERAL("primary.origin");
+
+bool WritePrimaryOriginFile(const base::FilePath& path,
+ const std::string& origin) {
+ base::File file(path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_WRITE);
+ if (!file.IsValid())
+ return false;
+ if (!file.created())
+ file.SetLength(0);
+ Pickle pickle;
+ pickle.WriteString(origin);
+ file.Write(0, static_cast<const char*>(pickle.data()), pickle.size());
+ file.Flush();
+ return true;
+}
+
+bool ReadPrimaryOriginFile(const base::FilePath& path,
+ std::string* origin) {
+ std::string buffer;
+ if (!base::ReadFileToString(path, &buffer))
+ return false;
+ Pickle pickle(buffer.data(), buffer.size());
+ PickleIterator iter(pickle);
+ return pickle.ReadString(&iter, origin) && !origin->empty();
+}
+
+} // namespace
+
+SandboxPrioritizedOriginDatabase::SandboxPrioritizedOriginDatabase(
+ const base::FilePath& file_system_directory,
+ leveldb::Env* env_override)
+ : file_system_directory_(file_system_directory),
+ env_override_(env_override),
+ primary_origin_file_(
+ file_system_directory_.Append(kPrimaryOriginFile)) {
+}
+
+SandboxPrioritizedOriginDatabase::~SandboxPrioritizedOriginDatabase() {
+}
+
+bool SandboxPrioritizedOriginDatabase::InitializePrimaryOrigin(
+ const std::string& origin) {
+ if (!primary_origin_database_) {
+ if (!MaybeLoadPrimaryOrigin() && ResetPrimaryOrigin(origin)) {
+ MaybeMigrateDatabase(origin);
+ primary_origin_database_.reset(
+ new SandboxIsolatedOriginDatabase(
+ origin,
+ file_system_directory_,
+ base::FilePath(kPrimaryDirectory)));
+ return true;
+ }
+ }
+
+ if (primary_origin_database_)
+ return primary_origin_database_->HasOriginPath(origin);
+
+ return false;
+}
+
+std::string SandboxPrioritizedOriginDatabase::GetPrimaryOrigin() {
+ MaybeLoadPrimaryOrigin();
+ if (primary_origin_database_)
+ return primary_origin_database_->origin();
+ return std::string();
+}
+
+bool SandboxPrioritizedOriginDatabase::HasOriginPath(
+ const std::string& origin) {
+ MaybeInitializeDatabases(false);
+ if (primary_origin_database_ &&
+ primary_origin_database_->HasOriginPath(origin))
+ return true;
+ if (origin_database_)
+ return origin_database_->HasOriginPath(origin);
+ return false;
+}
+
+bool SandboxPrioritizedOriginDatabase::GetPathForOrigin(
+ const std::string& origin, base::FilePath* directory) {
+ MaybeInitializeDatabases(true);
+ if (primary_origin_database_ &&
+ primary_origin_database_->GetPathForOrigin(origin, directory))
+ return true;
+ DCHECK(origin_database_);
+ return origin_database_->GetPathForOrigin(origin, directory);
+}
+
+bool SandboxPrioritizedOriginDatabase::RemovePathForOrigin(
+ const std::string& origin) {
+ MaybeInitializeDatabases(false);
+ if (primary_origin_database_ &&
+ primary_origin_database_->HasOriginPath(origin)) {
+ primary_origin_database_.reset();
+ base::DeleteFile(file_system_directory_.Append(kPrimaryOriginFile),
+ true /* recursive */);
+ return true;
+ }
+ if (origin_database_)
+ return origin_database_->RemovePathForOrigin(origin);
+ return true;
+}
+
+bool SandboxPrioritizedOriginDatabase::ListAllOrigins(
+ std::vector<OriginRecord>* origins) {
+ // SandboxOriginDatabase may clear the |origins|, so call this before
+ // primary_origin_database_.
+ MaybeInitializeDatabases(false);
+ if (origin_database_ && !origin_database_->ListAllOrigins(origins))
+ return false;
+ if (primary_origin_database_)
+ return primary_origin_database_->ListAllOrigins(origins);
+ return true;
+}
+
+void SandboxPrioritizedOriginDatabase::DropDatabase() {
+ primary_origin_database_.reset();
+ origin_database_.reset();
+}
+
+bool SandboxPrioritizedOriginDatabase::MaybeLoadPrimaryOrigin() {
+ if (primary_origin_database_)
+ return true;
+ std::string saved_origin;
+ if (!ReadPrimaryOriginFile(primary_origin_file_, &saved_origin))
+ return false;
+ primary_origin_database_.reset(
+ new SandboxIsolatedOriginDatabase(
+ saved_origin,
+ file_system_directory_,
+ base::FilePath(kPrimaryDirectory)));
+ return true;
+}
+
+bool SandboxPrioritizedOriginDatabase::ResetPrimaryOrigin(
+ const std::string& origin) {
+ DCHECK(!primary_origin_database_);
+ if (!WritePrimaryOriginFile(primary_origin_file_, origin))
+ return false;
+ // We reset the primary origin directory too.
+ // (This means the origin file corruption causes data loss
+ // We could keep the directory there as the same origin will likely
+ // become the primary origin, but let's play conservatively.)
+ base::DeleteFile(file_system_directory_.Append(kPrimaryDirectory),
+ true /* recursive */);
+ return true;
+}
+
+void SandboxPrioritizedOriginDatabase::MaybeMigrateDatabase(
+ const std::string& origin) {
+ MaybeInitializeNonPrimaryDatabase(false);
+ if (!origin_database_)
+ return;
+ if (origin_database_->HasOriginPath(origin)) {
+ base::FilePath directory_name;
+ if (origin_database_->GetPathForOrigin(origin, &directory_name) &&
+ directory_name != base::FilePath(kPrimaryOriginFile)) {
+ base::FilePath from_path = file_system_directory_.Append(directory_name);
+ base::FilePath to_path = file_system_directory_.Append(kPrimaryDirectory);
+
+ if (base::PathExists(to_path))
+ base::DeleteFile(to_path, true /* recursive */);
+ base::Move(from_path, to_path);
+ }
+
+ origin_database_->RemovePathForOrigin(origin);
+ }
+
+ std::vector<OriginRecord> origins;
+ origin_database_->ListAllOrigins(&origins);
+ if (origins.empty()) {
+ origin_database_->RemoveDatabase();
+ origin_database_.reset();
+ }
+}
+
+void SandboxPrioritizedOriginDatabase::MaybeInitializeDatabases(
+ bool create) {
+ MaybeLoadPrimaryOrigin();
+ MaybeInitializeNonPrimaryDatabase(create);
+}
+
+void SandboxPrioritizedOriginDatabase::MaybeInitializeNonPrimaryDatabase(
+ bool create) {
+ if (origin_database_)
+ return;
+
+ origin_database_.reset(new SandboxOriginDatabase(file_system_directory_,
+ env_override_));
+ if (!create && !base::DirectoryExists(origin_database_->GetDatabasePath())) {
+ origin_database_.reset();
+ return;
+ }
+}
+
+SandboxOriginDatabase*
+SandboxPrioritizedOriginDatabase::GetSandboxOriginDatabase() {
+ MaybeInitializeNonPrimaryDatabase(true);
+ return origin_database_.get();
+}
+
+} // namespace storage
diff --git a/storage/browser/fileapi/sandbox_prioritized_origin_database.h b/storage/browser/fileapi/sandbox_prioritized_origin_database.h
new file mode 100644
index 0000000..bbe1420
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_prioritized_origin_database.h
@@ -0,0 +1,72 @@
+// 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 STORAGE_BROWSER_FILEAPI_SANDBOX_PRIORITIZED_ORIGIN_DATABASE_H_
+#define STORAGE_BROWSER_FILEAPI_SANDBOX_PRIORITIZED_ORIGIN_DATABASE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/sandbox_origin_database_interface.h"
+
+namespace leveldb {
+class Env;
+}
+
+namespace storage {
+
+class ObfuscatedFileUtil;
+class SandboxIsolatedOriginDatabase;
+class SandboxOriginDatabase;
+
+class STORAGE_EXPORT_PRIVATE SandboxPrioritizedOriginDatabase
+ : public SandboxOriginDatabaseInterface {
+ public:
+ SandboxPrioritizedOriginDatabase(const base::FilePath& file_system_directory,
+ leveldb::Env* env_override);
+ virtual ~SandboxPrioritizedOriginDatabase();
+
+ // Sets |origin| as primary origin in this database (e.g. may
+ // allow faster access).
+ // Returns false if this database already has a primary origin
+ // which is different from |origin|.
+ bool InitializePrimaryOrigin(const std::string& origin);
+ std::string GetPrimaryOrigin();
+
+ // 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;
+
+ const base::FilePath& primary_origin_file() const {
+ return primary_origin_file_;
+ }
+
+ private:
+ bool MaybeLoadPrimaryOrigin();
+ bool ResetPrimaryOrigin(const std::string& origin);
+ void MaybeMigrateDatabase(const std::string& origin);
+ void MaybeInitializeDatabases(bool create);
+ void MaybeInitializeNonPrimaryDatabase(bool create);
+
+ // For migration.
+ friend class ObfuscatedFileUtil;
+ SandboxOriginDatabase* GetSandboxOriginDatabase();
+
+ const base::FilePath file_system_directory_;
+ leveldb::Env* env_override_;
+ const base::FilePath primary_origin_file_;
+ scoped_ptr<SandboxOriginDatabase> origin_database_;
+ scoped_ptr<SandboxIsolatedOriginDatabase> primary_origin_database_;
+
+ DISALLOW_COPY_AND_ASSIGN(SandboxPrioritizedOriginDatabase);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_PRIORITIZED_ORIGIN_DATABASE_H_
diff --git a/storage/browser/fileapi/sandbox_quota_observer.cc b/storage/browser/fileapi/sandbox_quota_observer.cc
new file mode 100644
index 0000000..0c39464
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_quota_observer.cc
@@ -0,0 +1,140 @@
+// 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 "storage/browser/fileapi/sandbox_quota_observer.h"
+
+#include "base/sequenced_task_runner.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/file_system_usage_cache.h"
+#include "storage/browser/fileapi/sandbox_file_system_backend_delegate.h"
+#include "storage/browser/fileapi/timed_task_helper.h"
+#include "storage/browser/quota/quota_client.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/common/fileapi/file_system_util.h"
+
+namespace storage {
+
+SandboxQuotaObserver::SandboxQuotaObserver(
+ storage::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(
+ storage::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(
+ storage::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(
+ storage::QuotaClient::kFileSystem,
+ origin,
+ FileSystemTypeToQuotaStorageType(type),
+ enabled);
+ }
+}
+
+base::FilePath SandboxQuotaObserver::GetUsageCachePath(
+ const FileSystemURL& url) {
+ DCHECK(sandbox_file_util_);
+ base::File::Error error = base::File::FILE_OK;
+ base::FilePath path =
+ SandboxFileSystemBackendDelegate::GetUsageCachePathForOriginAndType(
+ sandbox_file_util_, url.origin(), url.type(), &error);
+ if (error != base::File::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 storage
diff --git a/storage/browser/fileapi/sandbox_quota_observer.h b/storage/browser/fileapi/sandbox_quota_observer.h
new file mode 100644
index 0000000..4ae2cf9
--- /dev/null
+++ b/storage/browser/fileapi/sandbox_quota_observer.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 STORAGE_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_
+#define STORAGE_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 "storage/browser/fileapi/file_observers.h"
+#include "storage/browser/fileapi/file_system_url.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace storage {
+class QuotaManagerProxy;
+}
+
+namespace storage {
+
+class FileSystemUsageCache;
+class FileSystemURL;
+class TimedTaskHelper;
+class ObfuscatedFileUtil;
+
+class SandboxQuotaObserver
+ : public FileUpdateObserver,
+ public FileAccessObserver {
+ public:
+ typedef std::map<base::FilePath, int64> PendingUpdateNotificationMap;
+
+ SandboxQuotaObserver(storage::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<storage::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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_SANDBOX_QUOTA_OBSERVER_H_
diff --git a/storage/browser/fileapi/task_runner_bound_observer_list.h b/storage/browser/fileapi/task_runner_bound_observer_list.h
new file mode 100644
index 0000000..304eccb
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_
+#define STORAGE_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 storage {
+
+// 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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_TASK_RUNNER_BOUND_OBSERVER_LIST_H_
diff --git a/storage/browser/fileapi/timed_task_helper.cc b/storage/browser/fileapi/timed_task_helper.cc
new file mode 100644
index 0000000..950df76
--- /dev/null
+++ b/storage/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 "storage/browser/fileapi/timed_task_helper.h"
+
+#include "base/bind.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+
+namespace storage {
+
+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 storage
diff --git a/storage/browser/fileapi/timed_task_helper.h b/storage/browser/fileapi/timed_task_helper.h
new file mode 100644
index 0000000..4663501
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_
+#define STORAGE_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 "storage/browser/storage_browser_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace storage {
+
+// 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 STORAGE_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 storage
+
+#endif // STORAGE_BROWSER_FILEAPI_TIMED_TASK_HELPER_H_
diff --git a/storage/browser/fileapi/transient_file_util.cc b/storage/browser/fileapi/transient_file_util.cc
new file mode 100644
index 0000000..c0c2a6b
--- /dev/null
+++ b/storage/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 "storage/browser/fileapi/transient_file_util.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "storage/browser/fileapi/file_system_operation_context.h"
+#include "storage/browser/fileapi/file_system_url.h"
+#include "storage/browser/fileapi/isolated_context.h"
+
+using storage::ScopedFile;
+
+namespace storage {
+
+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::File::Error* error,
+ base::File::Info* file_info,
+ base::FilePath* platform_path) {
+ DCHECK(file_info);
+ *error = GetFileInfo(context, url, file_info, platform_path);
+ if (*error == base::File::FILE_OK && file_info->is_directory)
+ *error = base::File::FILE_ERROR_NOT_A_FILE;
+ if (*error != base::File::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 storage
diff --git a/storage/browser/fileapi/transient_file_util.h b/storage/browser/fileapi/transient_file_util.h
new file mode 100644
index 0000000..16d710e
--- /dev/null
+++ b/storage/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 STORAGE_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_
+#define STORAGE_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_
+
+#include "base/memory/scoped_ptr.h"
+#include "storage/browser/fileapi/local_file_util.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace storage {
+
+class FileSystemOperationContext;
+
+class STORAGE_EXPORT_PRIVATE TransientFileUtil
+ : public LocalFileUtil {
+ public:
+ TransientFileUtil() {}
+ virtual ~TransientFileUtil() {}
+
+ // LocalFileUtil overrides.
+ virtual storage::ScopedFile CreateSnapshotFile(
+ FileSystemOperationContext* context,
+ const FileSystemURL& url,
+ base::File::Error* error,
+ base::File::Info* file_info,
+ base::FilePath* platform_path) OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TransientFileUtil);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_TRANSIENT_FILE_UTIL_H_
diff --git a/storage/browser/fileapi/watcher_manager.h b/storage/browser/fileapi/watcher_manager.h
new file mode 100644
index 0000000..40f3733
--- /dev/null
+++ b/storage/browser/fileapi/watcher_manager.h
@@ -0,0 +1,67 @@
+// Copyright 2014 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 STORAGE_BROWSER_FILEAPI_WATCHER_MANAGER_H_
+#define STORAGE_BROWSER_FILEAPI_WATCHER_MANAGER_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/files/file.h"
+
+namespace base {
+class Time;
+}
+
+namespace storage {
+
+class FileSystemOperationContext;
+class FileSystemURL;
+
+// An interface for providing entry observing capability for file system
+// backends.
+//
+// It is NOT valid to give null callback to this class, and implementors
+// can assume that they don't get any null callbacks.
+class WatcherManager {
+ public:
+ typedef base::Callback<void(base::File::Error result)> StatusCallback;
+
+ // Observes watched entries.
+ class Observer {
+ public:
+ Observer() {}
+ virtual ~Observer() {}
+
+ // Notifies about an entry represented by |url| being changed.
+ virtual void OnEntryChanged(const FileSystemURL& url) = 0;
+
+ // Notifies about an entry represented by |url| being removed.
+ virtual void OnEntryRemoved(const FileSystemURL& url) = 0;
+ };
+
+ virtual ~WatcherManager() {}
+
+ virtual void AddObserver(Observer* observer) = 0;
+ virtual void RemoveObserver(Observer* observer) = 0;
+ virtual bool HasObserver(Observer* observer) const = 0;
+
+ // Observes a directory entry. If the |recursive| mode is not supported then
+ // FILE_ERROR_INVALID_OPERATION must be returned as an error. If the |url| is
+ // already watched, or setting up the watcher fails, then |callback|
+ // must be called with a specific error code. Otherwise |callback| must be
+ // called with the FILE_OK error code.
+ virtual void WatchDirectory(const FileSystemURL& url,
+ bool recursive,
+ const StatusCallback& callback) = 0;
+
+ // Stops observing an entry represented by |url|.
+ virtual void UnwatchEntry(const FileSystemURL& url,
+ const StatusCallback& callback) = 0;
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_FILEAPI_WATCHER_MANAGER_H_
diff --git a/storage/browser/quota/OWNERS b/storage/browser/quota/OWNERS
new file mode 100644
index 0000000..66ba5d2
--- /dev/null
+++ b/storage/browser/quota/OWNERS
@@ -0,0 +1 @@
+tzik@chromium.org
diff --git a/storage/browser/quota/quota_callbacks.h b/storage/browser/quota/quota_callbacks.h
new file mode 100644
index 0000000..b499922
--- /dev/null
+++ b/storage/browser/quota/quota_callbacks.h
@@ -0,0 +1,129 @@
+// 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 STORAGE_BROWSER_QUOTA_QUOTA_CALLBACKS_H_
+#define STORAGE_BROWSER_QUOTA_QUOTA_CALLBACKS_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/tuple.h"
+#include "storage/common/quota/quota_status_code.h"
+#include "storage/common/quota/quota_types.h"
+
+class GURL;
+
+namespace storage {
+
+struct UsageInfo;
+typedef std::vector<UsageInfo> UsageInfoEntries;
+
+// Common callback types that are used throughout in the quota module.
+typedef base::Callback<void(int64 usage,
+ int64 unlimited_usage)> GlobalUsageCallback;
+typedef base::Callback<void(QuotaStatusCode status, int64 quota)> QuotaCallback;
+typedef base::Callback<void(int64 usage)> UsageCallback;
+typedef base::Callback<void(QuotaStatusCode, int64)> AvailableSpaceCallback;
+typedef base::Callback<void(QuotaStatusCode)> StatusCallback;
+typedef base::Callback<void(const std::set<GURL>& origins,
+ StorageType type)> GetOriginsCallback;
+typedef base::Callback<void(const UsageInfoEntries&)> GetUsageInfoCallback;
+
+template<typename CallbackType, typename Args>
+void DispatchToCallback(const CallbackType& callback,
+ const Args& args) {
+ DispatchToMethod(&callback, &CallbackType::Run, args);
+}
+
+// Simple template wrapper for a callback queue.
+template <typename CallbackType, typename Args>
+class CallbackQueue {
+ public:
+ // Returns true if the given |callback| is the first one added to the queue.
+ bool Add(const CallbackType& callback) {
+ callbacks_.push_back(callback);
+ return (callbacks_.size() == 1);
+ }
+
+ bool HasCallbacks() const {
+ return !callbacks_.empty();
+ }
+
+ // Runs the callbacks added to the queue and clears the queue.
+ void Run(const Args& args) {
+ typedef typename std::vector<CallbackType>::iterator iterator;
+ for (iterator iter = callbacks_.begin();
+ iter != callbacks_.end(); ++iter)
+ DispatchToCallback(*iter, args);
+ callbacks_.clear();
+ }
+
+ private:
+ std::vector<CallbackType> callbacks_;
+};
+
+typedef CallbackQueue<GlobalUsageCallback,
+ Tuple2<int64, int64> >
+ GlobalUsageCallbackQueue;
+typedef CallbackQueue<UsageCallback, Tuple1<int64> >
+ UsageCallbackQueue;
+typedef CallbackQueue<AvailableSpaceCallback,
+ Tuple2<QuotaStatusCode, int64> >
+ AvailableSpaceCallbackQueue;
+typedef CallbackQueue<QuotaCallback,
+ Tuple2<QuotaStatusCode, int64> >
+ GlobalQuotaCallbackQueue;
+typedef CallbackQueue<base::Closure, Tuple0> ClosureQueue;
+
+template <typename CallbackType, typename Key, typename Args>
+class CallbackQueueMap {
+ public:
+ typedef CallbackQueue<CallbackType, Args> CallbackQueueType;
+ typedef std::map<Key, CallbackQueueType> CallbackMap;
+ typedef typename CallbackMap::iterator iterator;
+
+ bool Add(const Key& key, const CallbackType& callback) {
+ return callback_map_[key].Add(callback);
+ }
+
+ bool HasCallbacks(const Key& key) const {
+ return (callback_map_.find(key) != callback_map_.end());
+ }
+
+ bool HasAnyCallbacks() const {
+ return !callback_map_.empty();
+ }
+
+ iterator Begin() { return callback_map_.begin(); }
+ iterator End() { return callback_map_.end(); }
+
+ void Clear() { callback_map_.clear(); }
+
+ // Runs the callbacks added for the given |key| and clears the key
+ // from the map.
+ void Run(const Key& key, const Args& args) {
+ if (!this->HasCallbacks(key))
+ return;
+ CallbackQueueType& queue = callback_map_[key];
+ queue.Run(args);
+ callback_map_.erase(key);
+ }
+
+ private:
+ CallbackMap callback_map_;
+};
+
+typedef CallbackQueueMap<UsageCallback, std::string, Tuple1<int64> >
+ HostUsageCallbackMap;
+typedef CallbackQueueMap<QuotaCallback, std::string,
+ Tuple2<QuotaStatusCode, int64> >
+ HostQuotaCallbackMap;
+
+} // namespace storage
+
+#endif // STORAGE_QUOTA_QUOTA_TYPES_H_
diff --git a/storage/browser/quota/quota_client.h b/storage/browser/quota/quota_client.h
new file mode 100644
index 0000000..8a6a294
--- /dev/null
+++ b/storage/browser/quota/quota_client.h
@@ -0,0 +1,81 @@
+// 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 STORAGE_BROWSER_QUOTA_QUOTA_CLIENT_H_
+#define STORAGE_BROWSER_QUOTA_QUOTA_CLIENT_H_
+
+#include <list>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/quota/quota_types.h"
+#include "url/gurl.h"
+
+namespace storage {
+
+// An abstract interface for quota manager clients.
+// Each storage API must provide an implementation of this interface and
+// register it to the quota manager.
+// All the methods are assumed to be called on the IO thread in the browser.
+class STORAGE_EXPORT QuotaClient {
+ public:
+ typedef base::Callback<void(int64 usage)> GetUsageCallback;
+ typedef base::Callback<void(const std::set<GURL>& origins)>
+ GetOriginsCallback;
+ typedef base::Callback<void(QuotaStatusCode status)> DeletionCallback;
+
+ virtual ~QuotaClient() {}
+
+ enum ID {
+ kUnknown = 1 << 0,
+ kFileSystem = 1 << 1,
+ kDatabase = 1 << 2,
+ kAppcache = 1 << 3,
+ kIndexedDatabase = 1 << 4,
+ kAllClientsMask = -1,
+ };
+
+ virtual ID id() const = 0;
+
+ // Called when the quota manager is destroyed.
+ virtual void OnQuotaManagerDestroyed() = 0;
+
+ // Called by the QuotaManager.
+ // Gets the amount of data stored in the storage specified by
+ // |origin_url| and |type|.
+ // Note it is safe to fire the callback after the QuotaClient is destructed.
+ virtual void GetOriginUsage(const GURL& origin_url,
+ StorageType type,
+ const GetUsageCallback& callback) = 0;
+
+ // Called by the QuotaManager.
+ // Returns a list of origins that has data in the |type| storage.
+ // Note it is safe to fire the callback after the QuotaClient is destructed.
+ virtual void GetOriginsForType(StorageType type,
+ const GetOriginsCallback& callback) = 0;
+
+ // Called by the QuotaManager.
+ // Returns a list of origins that match the |host|.
+ // Note it is safe to fire the callback after the QuotaClient is destructed.
+ virtual void GetOriginsForHost(StorageType type,
+ const std::string& host,
+ const GetOriginsCallback& callback) = 0;
+
+ // Called by the QuotaManager.
+ // Note it is safe to fire the callback after the QuotaClient is destructed.
+ virtual void DeleteOriginData(const GURL& origin,
+ StorageType type,
+ const DeletionCallback& callback) = 0;
+
+ virtual bool DoesSupport(StorageType type) const = 0;
+};
+
+// TODO(dmikurube): Replace it to std::vector for efficiency.
+typedef std::list<QuotaClient*> QuotaClientList;
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_QUOTA_QUOTA_CLIENT_H_
diff --git a/storage/browser/quota/quota_database.cc b/storage/browser/quota/quota_database.cc
new file mode 100644
index 0000000..d6c94b3
--- /dev/null
+++ b/storage/browser/quota/quota_database.cc
@@ -0,0 +1,657 @@
+// 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 "storage/browser/quota/quota_database.h"
+
+#include <string>
+#include <vector>
+
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/time/time.h"
+#include "sql/connection.h"
+#include "sql/meta_table.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+#include "storage/browser/quota/special_storage_policy.h"
+#include "url/gurl.h"
+
+namespace storage {
+namespace {
+
+// Definitions for database schema.
+
+const int kCurrentVersion = 4;
+const int kCompatibleVersion = 2;
+
+const char kHostQuotaTable[] = "HostQuotaTable";
+const char kOriginInfoTable[] = "OriginInfoTable";
+const char kIsOriginTableBootstrapped[] = "IsOriginTableBootstrapped";
+
+bool VerifyValidQuotaConfig(const char* key) {
+ return (key != NULL &&
+ (!strcmp(key, QuotaDatabase::kDesiredAvailableSpaceKey) ||
+ !strcmp(key, QuotaDatabase::kTemporaryQuotaOverrideKey)));
+}
+
+const int kCommitIntervalMs = 30000;
+
+} // anonymous namespace
+
+// static
+const char QuotaDatabase::kDesiredAvailableSpaceKey[] = "DesiredAvailableSpace";
+const char QuotaDatabase::kTemporaryQuotaOverrideKey[] =
+ "TemporaryQuotaOverride";
+
+const QuotaDatabase::TableSchema QuotaDatabase::kTables[] = {
+ { kHostQuotaTable,
+ "(host TEXT NOT NULL,"
+ " type INTEGER NOT NULL,"
+ " quota INTEGER DEFAULT 0,"
+ " UNIQUE(host, type))" },
+ { kOriginInfoTable,
+ "(origin TEXT NOT NULL,"
+ " type INTEGER NOT NULL,"
+ " used_count INTEGER DEFAULT 0,"
+ " last_access_time INTEGER DEFAULT 0,"
+ " last_modified_time INTEGER DEFAULT 0,"
+ " UNIQUE(origin, type))" },
+};
+
+// static
+const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = {
+ { "HostIndex",
+ kHostQuotaTable,
+ "(host)",
+ false },
+ { "OriginInfoIndex",
+ kOriginInfoTable,
+ "(origin)",
+ false },
+ { "OriginLastAccessTimeIndex",
+ kOriginInfoTable,
+ "(last_access_time)",
+ false },
+ { "OriginLastModifiedTimeIndex",
+ kOriginInfoTable,
+ "(last_modified_time)",
+ false },
+};
+
+struct QuotaDatabase::QuotaTableImporter {
+ bool Append(const QuotaTableEntry& entry) {
+ entries.push_back(entry);
+ return true;
+ }
+ std::vector<QuotaTableEntry> entries;
+};
+
+// Clang requires explicit out-of-line constructors for them.
+QuotaDatabase::QuotaTableEntry::QuotaTableEntry()
+ : type(kStorageTypeUnknown),
+ quota(0) {
+}
+
+QuotaDatabase::QuotaTableEntry::QuotaTableEntry(
+ const std::string& host,
+ StorageType type,
+ int64 quota)
+ : host(host),
+ type(type),
+ quota(quota) {
+}
+
+QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry()
+ : type(kStorageTypeUnknown),
+ used_count(0) {
+}
+
+QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry(
+ const GURL& origin,
+ StorageType type,
+ int used_count,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time)
+ : origin(origin),
+ type(type),
+ used_count(used_count),
+ last_access_time(last_access_time),
+ last_modified_time(last_modified_time) {
+}
+
+// QuotaDatabase ------------------------------------------------------------
+QuotaDatabase::QuotaDatabase(const base::FilePath& path)
+ : db_file_path_(path),
+ is_recreating_(false),
+ is_disabled_(false) {
+}
+
+QuotaDatabase::~QuotaDatabase() {
+ if (db_) {
+ db_->CommitTransaction();
+ }
+}
+
+void QuotaDatabase::CloseConnection() {
+ meta_table_.reset();
+ db_.reset();
+}
+
+bool QuotaDatabase::GetHostQuota(
+ const std::string& host, StorageType type, int64* quota) {
+ DCHECK(quota);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT quota"
+ " FROM HostQuotaTable"
+ " WHERE host = ? AND type = ?";
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindString(0, host);
+ statement.BindInt(1, static_cast<int>(type));
+
+ if (!statement.Step())
+ return false;
+
+ *quota = statement.ColumnInt64(0);
+ return true;
+}
+
+bool QuotaDatabase::SetHostQuota(
+ const std::string& host, StorageType type, int64 quota) {
+ DCHECK_GE(quota, 0);
+ if (!LazyOpen(true))
+ return false;
+
+ const char* kSql =
+ "INSERT OR REPLACE INTO HostQuotaTable"
+ " (quota, host, type)"
+ " VALUES (?, ?, ?)";
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindInt64(0, quota);
+ statement.BindString(1, host);
+ statement.BindInt(2, static_cast<int>(type));
+
+ if (!statement.Run())
+ return false;
+
+ ScheduleCommit();
+ return true;
+}
+
+bool QuotaDatabase::SetOriginLastAccessTime(
+ const GURL& origin, StorageType type, base::Time last_access_time) {
+ if (!LazyOpen(true))
+ return false;
+
+ sql::Statement statement;
+
+ int used_count = 1;
+ if (FindOriginUsedCount(origin, type, &used_count)) {
+ ++used_count;
+ const char* kSql =
+ "UPDATE OriginInfoTable"
+ " SET used_count = ?, last_access_time = ?"
+ " WHERE origin = ? AND type = ?";
+ statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ } else {
+ const char* kSql =
+ "INSERT INTO OriginInfoTable"
+ " (used_count, last_access_time, origin, type)"
+ " VALUES (?, ?, ?, ?)";
+ statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ }
+ statement.BindInt(0, used_count);
+ statement.BindInt64(1, last_access_time.ToInternalValue());
+ statement.BindString(2, origin.spec());
+ statement.BindInt(3, static_cast<int>(type));
+
+ if (!statement.Run())
+ return false;
+
+ ScheduleCommit();
+ return true;
+}
+
+bool QuotaDatabase::SetOriginLastModifiedTime(
+ const GURL& origin, StorageType type, base::Time last_modified_time) {
+ if (!LazyOpen(true))
+ return false;
+
+ sql::Statement statement;
+
+ int dummy;
+ if (FindOriginUsedCount(origin, type, &dummy)) {
+ const char* kSql =
+ "UPDATE OriginInfoTable"
+ " SET last_modified_time = ?"
+ " WHERE origin = ? AND type = ?";
+ statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ } else {
+ const char* kSql =
+ "INSERT INTO OriginInfoTable"
+ " (last_modified_time, origin, type) VALUES (?, ?, ?)";
+ statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ }
+ statement.BindInt64(0, last_modified_time.ToInternalValue());
+ statement.BindString(1, origin.spec());
+ statement.BindInt(2, static_cast<int>(type));
+
+ if (!statement.Run())
+ return false;
+
+ ScheduleCommit();
+ return true;
+}
+
+bool QuotaDatabase::RegisterInitialOriginInfo(
+ const std::set<GURL>& origins, StorageType type) {
+ if (!LazyOpen(true))
+ return false;
+
+ typedef std::set<GURL>::const_iterator itr_type;
+ for (itr_type itr = origins.begin(), end = origins.end();
+ itr != end; ++itr) {
+ const char* kSql =
+ "INSERT OR IGNORE INTO OriginInfoTable"
+ " (origin, type) VALUES (?, ?)";
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindString(0, itr->spec());
+ statement.BindInt(1, static_cast<int>(type));
+
+ if (!statement.Run())
+ return false;
+ }
+
+ ScheduleCommit();
+ return true;
+}
+
+bool QuotaDatabase::DeleteHostQuota(
+ const std::string& host, StorageType type) {
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "DELETE FROM HostQuotaTable"
+ " WHERE host = ? AND type = ?";
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindString(0, host);
+ statement.BindInt(1, static_cast<int>(type));
+
+ if (!statement.Run())
+ return false;
+
+ ScheduleCommit();
+ return true;
+}
+
+bool QuotaDatabase::DeleteOriginInfo(
+ const GURL& origin, StorageType type) {
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "DELETE FROM OriginInfoTable"
+ " WHERE origin = ? AND type = ?";
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindString(0, origin.spec());
+ statement.BindInt(1, static_cast<int>(type));
+
+ if (!statement.Run())
+ return false;
+
+ ScheduleCommit();
+ return true;
+}
+
+bool QuotaDatabase::GetQuotaConfigValue(const char* key, int64* value) {
+ if (!LazyOpen(false))
+ return false;
+ DCHECK(VerifyValidQuotaConfig(key));
+ return meta_table_->GetValue(key, value);
+}
+
+bool QuotaDatabase::SetQuotaConfigValue(const char* key, int64 value) {
+ if (!LazyOpen(true))
+ return false;
+ DCHECK(VerifyValidQuotaConfig(key));
+ return meta_table_->SetValue(key, value);
+}
+
+bool QuotaDatabase::GetLRUOrigin(
+ StorageType type,
+ const std::set<GURL>& exceptions,
+ SpecialStoragePolicy* special_storage_policy,
+ GURL* origin) {
+ DCHECK(origin);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql = "SELECT origin FROM OriginInfoTable"
+ " WHERE type = ?"
+ " ORDER BY last_access_time ASC";
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindInt(0, static_cast<int>(type));
+
+ while (statement.Step()) {
+ GURL url(statement.ColumnString(0));
+ if (exceptions.find(url) != exceptions.end())
+ continue;
+ if (special_storage_policy &&
+ special_storage_policy->IsStorageUnlimited(url))
+ continue;
+ *origin = url;
+ return true;
+ }
+
+ *origin = GURL();
+ return statement.Succeeded();
+}
+
+bool QuotaDatabase::GetOriginsModifiedSince(
+ StorageType type, std::set<GURL>* origins, base::Time modified_since) {
+ DCHECK(origins);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql = "SELECT origin FROM OriginInfoTable"
+ " WHERE type = ? AND last_modified_time >= ?";
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindInt(0, static_cast<int>(type));
+ statement.BindInt64(1, modified_since.ToInternalValue());
+
+ origins->clear();
+ while (statement.Step())
+ origins->insert(GURL(statement.ColumnString(0)));
+
+ return statement.Succeeded();
+}
+
+bool QuotaDatabase::IsOriginDatabaseBootstrapped() {
+ if (!LazyOpen(true))
+ return false;
+
+ int flag = 0;
+ return meta_table_->GetValue(kIsOriginTableBootstrapped, &flag) && flag;
+}
+
+bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag) {
+ if (!LazyOpen(true))
+ return false;
+
+ return meta_table_->SetValue(kIsOriginTableBootstrapped, bootstrap_flag);
+}
+
+void QuotaDatabase::Commit() {
+ if (!db_)
+ return;
+
+ if (timer_.IsRunning())
+ timer_.Stop();
+
+ db_->CommitTransaction();
+ db_->BeginTransaction();
+}
+
+void QuotaDatabase::ScheduleCommit() {
+ if (timer_.IsRunning())
+ return;
+ timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCommitIntervalMs),
+ this, &QuotaDatabase::Commit);
+}
+
+bool QuotaDatabase::FindOriginUsedCount(
+ const GURL& origin, StorageType type, int* used_count) {
+ DCHECK(used_count);
+ if (!LazyOpen(false))
+ return false;
+
+ const char* kSql =
+ "SELECT used_count FROM OriginInfoTable"
+ " WHERE origin = ? AND type = ?";
+
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+ statement.BindString(0, origin.spec());
+ statement.BindInt(1, static_cast<int>(type));
+
+ if (!statement.Step())
+ return false;
+
+ *used_count = statement.ColumnInt(0);
+ return true;
+}
+
+bool QuotaDatabase::LazyOpen(bool create_if_needed) {
+ if (db_)
+ return true;
+
+ // If we tried and failed once, don't try again in the same session
+ // to avoid creating an incoherent mess on disk.
+ if (is_disabled_)
+ return false;
+
+ bool in_memory_only = db_file_path_.empty();
+ if (!create_if_needed &&
+ (in_memory_only || !base::PathExists(db_file_path_))) {
+ return false;
+ }
+
+ db_.reset(new sql::Connection);
+ meta_table_.reset(new sql::MetaTable);
+
+ db_->set_histogram_tag("Quota");
+
+ bool opened = false;
+ if (in_memory_only) {
+ opened = db_->OpenInMemory();
+ } else if (!base::CreateDirectory(db_file_path_.DirName())) {
+ LOG(ERROR) << "Failed to create quota database directory.";
+ } else {
+ opened = db_->Open(db_file_path_);
+ if (opened)
+ db_->Preload();
+ }
+
+ if (!opened || !EnsureDatabaseVersion()) {
+ LOG(ERROR) << "Failed to open the quota database.";
+ is_disabled_ = true;
+ db_.reset();
+ meta_table_.reset();
+ return false;
+ }
+
+ // Start a long-running transaction.
+ db_->BeginTransaction();
+
+ return true;
+}
+
+bool QuotaDatabase::EnsureDatabaseVersion() {
+ static const size_t kTableCount = ARRAYSIZE_UNSAFE(kTables);
+ static const size_t kIndexCount = ARRAYSIZE_UNSAFE(kIndexes);
+ if (!sql::MetaTable::DoesTableExist(db_.get()))
+ return CreateSchema(db_.get(), meta_table_.get(),
+ kCurrentVersion, kCompatibleVersion,
+ kTables, kTableCount,
+ kIndexes, kIndexCount);
+
+ if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion))
+ return false;
+
+ if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) {
+ LOG(WARNING) << "Quota database is too new.";
+ return false;
+ }
+
+ if (meta_table_->GetVersionNumber() < kCurrentVersion) {
+ if (!UpgradeSchema(meta_table_->GetVersionNumber()))
+ return ResetSchema();
+ }
+
+#ifndef NDEBUG
+ DCHECK(sql::MetaTable::DoesTableExist(db_.get()));
+ for (size_t i = 0; i < kTableCount; ++i) {
+ DCHECK(db_->DoesTableExist(kTables[i].table_name));
+ }
+#endif
+
+ return true;
+}
+
+// static
+bool QuotaDatabase::CreateSchema(
+ sql::Connection* database,
+ sql::MetaTable* meta_table,
+ int schema_version, int compatible_version,
+ const TableSchema* tables, size_t tables_size,
+ const IndexSchema* indexes, size_t indexes_size) {
+ // TODO(kinuko): Factor out the common code to create databases.
+ sql::Transaction transaction(database);
+ if (!transaction.Begin())
+ return false;
+
+ if (!meta_table->Init(database, schema_version, compatible_version))
+ return false;
+
+ for (size_t i = 0; i < tables_size; ++i) {
+ std::string sql("CREATE TABLE ");
+ sql += tables[i].table_name;
+ sql += tables[i].columns;
+ if (!database->Execute(sql.c_str())) {
+ VLOG(1) << "Failed to execute " << sql;
+ return false;
+ }
+ }
+
+ for (size_t i = 0; i < indexes_size; ++i) {
+ std::string sql;
+ if (indexes[i].unique)
+ sql += "CREATE UNIQUE INDEX ";
+ else
+ sql += "CREATE INDEX ";
+ sql += indexes[i].index_name;
+ sql += " ON ";
+ sql += indexes[i].table_name;
+ sql += indexes[i].columns;
+ if (!database->Execute(sql.c_str())) {
+ VLOG(1) << "Failed to execute " << sql;
+ return false;
+ }
+ }
+
+ return transaction.Commit();
+}
+
+bool QuotaDatabase::ResetSchema() {
+ DCHECK(!db_file_path_.empty());
+ DCHECK(base::PathExists(db_file_path_));
+ VLOG(1) << "Deleting existing quota data and starting over.";
+
+ db_.reset();
+ meta_table_.reset();
+
+ if (!sql::Connection::Delete(db_file_path_))
+ return false;
+
+ // So we can't go recursive.
+ if (is_recreating_)
+ return false;
+
+ base::AutoReset<bool> auto_reset(&is_recreating_, true);
+ return LazyOpen(true);
+}
+
+bool QuotaDatabase::UpgradeSchema(int current_version) {
+ if (current_version == 2) {
+ QuotaTableImporter importer;
+ typedef std::vector<QuotaTableEntry> QuotaTableEntries;
+ if (!DumpQuotaTable(base::Bind(&QuotaTableImporter::Append,
+ base::Unretained(&importer)))) {
+ return false;
+ }
+ ResetSchema();
+ for (QuotaTableEntries::const_iterator iter = importer.entries.begin();
+ iter != importer.entries.end(); ++iter) {
+ if (!SetHostQuota(iter->host, iter->type, iter->quota))
+ return false;
+ }
+ Commit();
+ return true;
+ }
+ return false;
+}
+
+bool QuotaDatabase::DumpQuotaTable(const QuotaTableCallback& callback) {
+ if (!LazyOpen(true))
+ return false;
+
+ const char* kSql = "SELECT * FROM HostQuotaTable";
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+
+ while (statement.Step()) {
+ QuotaTableEntry entry = QuotaTableEntry(
+ statement.ColumnString(0),
+ static_cast<StorageType>(statement.ColumnInt(1)),
+ statement.ColumnInt64(2));
+
+ if (!callback.Run(entry))
+ return true;
+ }
+
+ return statement.Succeeded();
+}
+
+bool QuotaDatabase::DumpOriginInfoTable(
+ const OriginInfoTableCallback& callback) {
+
+ if (!LazyOpen(true))
+ return false;
+
+ const char* kSql = "SELECT * FROM OriginInfoTable";
+ sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql));
+
+ while (statement.Step()) {
+ OriginInfoTableEntry entry(
+ GURL(statement.ColumnString(0)),
+ static_cast<StorageType>(statement.ColumnInt(1)),
+ statement.ColumnInt(2),
+ base::Time::FromInternalValue(statement.ColumnInt64(3)),
+ base::Time::FromInternalValue(statement.ColumnInt64(4)));
+
+ if (!callback.Run(entry))
+ return true;
+ }
+
+ return statement.Succeeded();
+}
+
+bool operator<(const QuotaDatabase::QuotaTableEntry& lhs,
+ const QuotaDatabase::QuotaTableEntry& rhs) {
+ if (lhs.host < rhs.host) return true;
+ if (rhs.host < lhs.host) return false;
+ if (lhs.type < rhs.type) return true;
+ if (rhs.type < lhs.type) return false;
+ return lhs.quota < rhs.quota;
+}
+
+bool operator<(const QuotaDatabase::OriginInfoTableEntry& lhs,
+ const QuotaDatabase::OriginInfoTableEntry& rhs) {
+ if (lhs.origin < rhs.origin) return true;
+ if (rhs.origin < lhs.origin) return false;
+ if (lhs.type < rhs.type) return true;
+ if (rhs.type < lhs.type) return false;
+ if (lhs.used_count < rhs.used_count) return true;
+ if (rhs.used_count < lhs.used_count) return false;
+ return lhs.last_access_time < rhs.last_access_time;
+}
+
+} // namespace storage
diff --git a/storage/browser/quota/quota_database.h b/storage/browser/quota/quota_database.h
new file mode 100644
index 0000000..6ca5651
--- /dev/null
+++ b/storage/browser/quota/quota_database.h
@@ -0,0 +1,190 @@
+// 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 STORAGE_BROWSER_QUOTA_QUOTA_DATABASE_H_
+#define STORAGE_BROWSER_QUOTA_QUOTA_DATABASE_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/quota/quota_types.h"
+#include "url/gurl.h"
+
+namespace content {
+class QuotaDatabaseTest;
+}
+
+namespace sql {
+class Connection;
+class MetaTable;
+}
+
+class GURL;
+
+namespace storage {
+
+class SpecialStoragePolicy;
+
+// All the methods of this class must run on the DB thread.
+class STORAGE_EXPORT_PRIVATE QuotaDatabase {
+ public:
+ // Constants for {Get,Set}QuotaConfigValue keys.
+ static const char kDesiredAvailableSpaceKey[];
+ static const char kTemporaryQuotaOverrideKey[];
+
+ // If 'path' is empty, an in memory database will be used.
+ explicit QuotaDatabase(const base::FilePath& path);
+ ~QuotaDatabase();
+
+ void CloseConnection();
+
+ bool GetHostQuota(const std::string& host, StorageType type, int64* quota);
+ bool SetHostQuota(const std::string& host, StorageType type, int64 quota);
+ bool DeleteHostQuota(const std::string& host, StorageType type);
+
+ bool SetOriginLastAccessTime(const GURL& origin,
+ StorageType type,
+ base::Time last_access_time);
+
+ bool SetOriginLastModifiedTime(const GURL& origin,
+ StorageType type,
+ base::Time last_modified_time);
+
+ // Register initial |origins| info |type| to the database.
+ // This method is assumed to be called only after the installation or
+ // the database schema reset.
+ bool RegisterInitialOriginInfo(
+ const std::set<GURL>& origins, StorageType type);
+
+ bool DeleteOriginInfo(const GURL& origin, StorageType type);
+
+ bool GetQuotaConfigValue(const char* key, int64* value);
+ bool SetQuotaConfigValue(const char* key, int64 value);
+
+ // Sets |origin| to the least recently used origin of origins not included
+ // in |exceptions| and not granted the special unlimited storage right.
+ // It returns false when it failed in accessing the database.
+ // |origin| is set to empty when there is no matching origin.
+ bool GetLRUOrigin(StorageType type,
+ const std::set<GURL>& exceptions,
+ SpecialStoragePolicy* special_storage_policy,
+ GURL* origin);
+
+ // Populates |origins| with the ones that have been modified since
+ // the |modified_since|.
+ bool GetOriginsModifiedSince(StorageType type,
+ std::set<GURL>* origins,
+ base::Time modified_since);
+
+ // Returns false if SetOriginDatabaseBootstrapped has never
+ // been called before, which means existing origins may not have been
+ // registered.
+ bool IsOriginDatabaseBootstrapped();
+ bool SetOriginDatabaseBootstrapped(bool bootstrap_flag);
+
+ private:
+ struct STORAGE_EXPORT_PRIVATE QuotaTableEntry {
+ QuotaTableEntry();
+ QuotaTableEntry(
+ const std::string& host,
+ StorageType type,
+ int64 quota);
+ std::string host;
+ StorageType type;
+ int64 quota;
+ };
+ friend STORAGE_EXPORT_PRIVATE bool operator <(
+ const QuotaTableEntry& lhs, const QuotaTableEntry& rhs);
+
+ struct STORAGE_EXPORT_PRIVATE OriginInfoTableEntry {
+ OriginInfoTableEntry();
+ OriginInfoTableEntry(
+ const GURL& origin,
+ StorageType type,
+ int used_count,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time);
+ GURL origin;
+ StorageType type;
+ int used_count;
+ base::Time last_access_time;
+ base::Time last_modified_time;
+ };
+ friend STORAGE_EXPORT_PRIVATE bool operator <(
+ const OriginInfoTableEntry& lhs, const OriginInfoTableEntry& rhs);
+
+ // Structures used for CreateSchema.
+ struct TableSchema {
+ const char* table_name;
+ const char* columns;
+ };
+ struct IndexSchema {
+ const char* index_name;
+ const char* table_name;
+ const char* columns;
+ bool unique;
+ };
+
+ typedef base::Callback<bool (const QuotaTableEntry&)> QuotaTableCallback;
+ typedef base::Callback<bool (const OriginInfoTableEntry&)>
+ OriginInfoTableCallback;
+
+ struct QuotaTableImporter;
+
+ // For long-running transactions support. We always keep a transaction open
+ // so that multiple transactions can be batched. They are flushed
+ // with a delay after a modification has been made. We support neither
+ // nested transactions nor rollback (as we don't need them for now).
+ void Commit();
+ void ScheduleCommit();
+
+ bool FindOriginUsedCount(const GURL& origin,
+ StorageType type,
+ int* used_count);
+
+ bool LazyOpen(bool create_if_needed);
+ bool EnsureDatabaseVersion();
+ bool ResetSchema();
+ bool UpgradeSchema(int current_version);
+
+ static bool CreateSchema(
+ sql::Connection* database,
+ sql::MetaTable* meta_table,
+ int schema_version, int compatible_version,
+ const TableSchema* tables, size_t tables_size,
+ const IndexSchema* indexes, size_t indexes_size);
+
+ // |callback| may return false to stop reading data.
+ bool DumpQuotaTable(const QuotaTableCallback& callback);
+ bool DumpOriginInfoTable(const OriginInfoTableCallback& callback);
+
+ base::FilePath db_file_path_;
+
+ scoped_ptr<sql::Connection> db_;
+ scoped_ptr<sql::MetaTable> meta_table_;
+ bool is_recreating_;
+ bool is_disabled_;
+
+ base::OneShotTimer<QuotaDatabase> timer_;
+
+ friend class content::QuotaDatabaseTest;
+ friend class QuotaManager;
+
+ static const TableSchema kTables[];
+ static const IndexSchema kIndexes[];
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaDatabase);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_QUOTA_QUOTA_DATABASE_H_
diff --git a/storage/browser/quota/quota_manager.cc b/storage/browser/quota/quota_manager.cc
new file mode 100644
index 0000000..e0f3b14
--- /dev/null
+++ b/storage/browser/quota/quota_manager.cc
@@ -0,0 +1,1631 @@
+// 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 "storage/browser/quota/quota_manager.h"
+
+#include <algorithm>
+#include <deque>
+#include <functional>
+#include <set>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/metrics/histogram.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/sys_info.h"
+#include "base/task_runner_util.h"
+#include "base/time/time.h"
+#include "net/base/net_util.h"
+#include "storage/browser/quota/quota_database.h"
+#include "storage/browser/quota/quota_manager_proxy.h"
+#include "storage/browser/quota/quota_temporary_storage_evictor.h"
+#include "storage/browser/quota/storage_monitor.h"
+#include "storage/browser/quota/usage_tracker.h"
+#include "storage/common/quota/quota_types.h"
+
+#define UMA_HISTOGRAM_MBYTES(name, sample) \
+ UMA_HISTOGRAM_CUSTOM_COUNTS( \
+ (name), static_cast<int>((sample) / kMBytes), \
+ 1, 10 * 1024 * 1024 /* 10TB */, 100)
+
+namespace storage {
+
+namespace {
+
+const int64 kMBytes = 1024 * 1024;
+const int kMinutesInMilliSeconds = 60 * 1000;
+
+const int64 kReportHistogramInterval = 60 * 60 * 1000; // 1 hour
+const double kTemporaryQuotaRatioToAvail = 1.0 / 3.0; // 33%
+
+} // namespace
+
+// Arbitrary for now, but must be reasonably small so that
+// in-memory databases can fit.
+// TODO(kinuko): Refer SysInfo::AmountOfPhysicalMemory() to determine this.
+const int64 QuotaManager::kIncognitoDefaultQuotaLimit = 100 * kMBytes;
+
+const int64 QuotaManager::kNoLimit = kint64max;
+
+const int QuotaManager::kPerHostTemporaryPortion = 5; // 20%
+
+// Cap size for per-host persistent quota determined by the histogram.
+// This is a bit lax value because the histogram says nothing about per-host
+// persistent storage usage and we determined by global persistent storage
+// usage that is less than 10GB for almost all users.
+const int64 QuotaManager::kPerHostPersistentQuotaLimit = 10 * 1024 * kMBytes;
+
+const char QuotaManager::kDatabaseName[] = "QuotaManager";
+
+const int QuotaManager::kThresholdOfErrorsToBeBlacklisted = 3;
+
+// Preserve kMinimumPreserveForSystem disk space for system book-keeping
+// when returning the quota to unlimited apps/extensions.
+// TODO(kinuko): This should be like 10% of the actual disk space.
+// For now we simply use a constant as getting the disk size needs
+// platform-dependent code. (http://crbug.com/178976)
+int64 QuotaManager::kMinimumPreserveForSystem = 1024 * kMBytes;
+
+const int QuotaManager::kEvictionIntervalInMilliSeconds =
+ 30 * kMinutesInMilliSeconds;
+
+// Heuristics: assuming average cloud server allows a few Gigs storage
+// on the server side and the storage needs to be shared for user data
+// and by multiple apps.
+int64 QuotaManager::kSyncableStorageDefaultHostQuota = 500 * kMBytes;
+
+namespace {
+
+void CountOriginType(const std::set<GURL>& origins,
+ SpecialStoragePolicy* policy,
+ size_t* protected_origins,
+ size_t* unlimited_origins) {
+ DCHECK(protected_origins);
+ DCHECK(unlimited_origins);
+ *protected_origins = 0;
+ *unlimited_origins = 0;
+ if (!policy)
+ return;
+ for (std::set<GURL>::const_iterator itr = origins.begin();
+ itr != origins.end();
+ ++itr) {
+ if (policy->IsStorageProtected(*itr))
+ ++*protected_origins;
+ if (policy->IsStorageUnlimited(*itr))
+ ++*unlimited_origins;
+ }
+}
+
+bool SetTemporaryGlobalOverrideQuotaOnDBThread(int64* new_quota,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ if (!database->SetQuotaConfigValue(
+ QuotaDatabase::kTemporaryQuotaOverrideKey, *new_quota)) {
+ *new_quota = -1;
+ return false;
+ }
+ return true;
+}
+
+bool GetPersistentHostQuotaOnDBThread(const std::string& host,
+ int64* quota,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ database->GetHostQuota(host, kStorageTypePersistent, quota);
+ return true;
+}
+
+bool SetPersistentHostQuotaOnDBThread(const std::string& host,
+ int64* new_quota,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ if (database->SetHostQuota(host, kStorageTypePersistent, *new_quota))
+ return true;
+ *new_quota = 0;
+ return false;
+}
+
+bool InitializeOnDBThread(int64* temporary_quota_override,
+ int64* desired_available_space,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ database->GetQuotaConfigValue(QuotaDatabase::kTemporaryQuotaOverrideKey,
+ temporary_quota_override);
+ database->GetQuotaConfigValue(QuotaDatabase::kDesiredAvailableSpaceKey,
+ desired_available_space);
+ return true;
+}
+
+bool GetLRUOriginOnDBThread(StorageType type,
+ std::set<GURL>* exceptions,
+ SpecialStoragePolicy* policy,
+ GURL* url,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ database->GetLRUOrigin(type, *exceptions, policy, url);
+ return true;
+}
+
+bool DeleteOriginInfoOnDBThread(const GURL& origin,
+ StorageType type,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ return database->DeleteOriginInfo(origin, type);
+}
+
+bool InitializeTemporaryOriginsInfoOnDBThread(const std::set<GURL>* origins,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ if (database->IsOriginDatabaseBootstrapped())
+ return true;
+
+ // Register existing origins with 0 last time access.
+ if (database->RegisterInitialOriginInfo(*origins, kStorageTypeTemporary)) {
+ database->SetOriginDatabaseBootstrapped(true);
+ return true;
+ }
+ return false;
+}
+
+bool UpdateAccessTimeOnDBThread(const GURL& origin,
+ StorageType type,
+ base::Time accessed_time,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ return database->SetOriginLastAccessTime(origin, type, accessed_time);
+}
+
+bool UpdateModifiedTimeOnDBThread(const GURL& origin,
+ StorageType type,
+ base::Time modified_time,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ return database->SetOriginLastModifiedTime(origin, type, modified_time);
+}
+
+int64 CallSystemGetAmountOfFreeDiskSpace(const base::FilePath& profile_path) {
+ // Ensure the profile path exists.
+ if (!base::CreateDirectory(profile_path)) {
+ LOG(WARNING) << "Create directory failed for path" << profile_path.value();
+ return 0;
+ }
+ return base::SysInfo::AmountOfFreeDiskSpace(profile_path);
+}
+
+int64 CalculateTemporaryGlobalQuota(int64 global_limited_usage,
+ int64 available_space) {
+ DCHECK_GE(global_limited_usage, 0);
+ int64 avail_space = available_space;
+ if (avail_space < kint64max - global_limited_usage) {
+ // We basically calculate the temporary quota by
+ // [available_space + space_used_for_temp] * kTempQuotaRatio,
+ // but make sure we'll have no overflow.
+ avail_space += global_limited_usage;
+ }
+ return avail_space * kTemporaryQuotaRatioToAvail;
+}
+
+void DispatchTemporaryGlobalQuotaCallback(
+ const QuotaCallback& callback,
+ QuotaStatusCode status,
+ const UsageAndQuota& usage_and_quota) {
+ if (status != kQuotaStatusOk) {
+ callback.Run(status, 0);
+ return;
+ }
+
+ callback.Run(status, CalculateTemporaryGlobalQuota(
+ usage_and_quota.global_limited_usage,
+ usage_and_quota.available_disk_space));
+}
+
+int64 CalculateQuotaWithDiskSpace(
+ int64 available_disk_space, int64 usage, int64 quota) {
+ if (available_disk_space < QuotaManager::kMinimumPreserveForSystem) {
+ LOG(WARNING)
+ << "Running out of disk space for profile."
+ << " QuotaManager starts forbidding further quota consumption.";
+ return usage;
+ }
+
+ if (quota < usage) {
+ // No more space; cap the quota to the current usage.
+ return usage;
+ }
+
+ available_disk_space -= QuotaManager::kMinimumPreserveForSystem;
+ if (available_disk_space < quota - usage)
+ return available_disk_space + usage;
+
+ return quota;
+}
+
+int64 CalculateTemporaryHostQuota(int64 host_usage,
+ int64 global_quota,
+ int64 global_limited_usage) {
+ DCHECK_GE(global_limited_usage, 0);
+ int64 host_quota = global_quota / QuotaManager::kPerHostTemporaryPortion;
+ if (global_limited_usage > global_quota)
+ host_quota = std::min(host_quota, host_usage);
+ return host_quota;
+}
+
+void DispatchUsageAndQuotaForWebApps(
+ StorageType type,
+ bool is_incognito,
+ bool is_unlimited,
+ bool can_query_disk_size,
+ const QuotaManager::GetUsageAndQuotaCallback& callback,
+ QuotaStatusCode status,
+ const UsageAndQuota& usage_and_quota) {
+ if (status != kQuotaStatusOk) {
+ callback.Run(status, 0, 0);
+ return;
+ }
+
+ int64 usage = usage_and_quota.usage;
+ int64 quota = usage_and_quota.quota;
+
+ if (type == kStorageTypeTemporary && !is_unlimited) {
+ quota = CalculateTemporaryHostQuota(
+ usage, quota, usage_and_quota.global_limited_usage);
+ }
+
+ if (is_incognito) {
+ quota = std::min(quota, QuotaManager::kIncognitoDefaultQuotaLimit);
+ callback.Run(status, usage, quota);
+ return;
+ }
+
+ // For apps with unlimited permission or can_query_disk_size is true (and not
+ // in incognito mode).
+ // We assume we can expose the actual disk size for them and cap the quota by
+ // the available disk space.
+ if (is_unlimited || can_query_disk_size) {
+ callback.Run(
+ status, usage,
+ CalculateQuotaWithDiskSpace(
+ usage_and_quota.available_disk_space,
+ usage, quota));
+ return;
+ }
+
+ callback.Run(status, usage, quota);
+}
+
+} // namespace
+
+UsageAndQuota::UsageAndQuota()
+ : usage(0),
+ global_limited_usage(0),
+ quota(0),
+ available_disk_space(0) {
+}
+
+UsageAndQuota::UsageAndQuota(
+ int64 usage,
+ int64 global_limited_usage,
+ int64 quota,
+ int64 available_disk_space)
+ : usage(usage),
+ global_limited_usage(global_limited_usage),
+ quota(quota),
+ available_disk_space(available_disk_space) {
+}
+
+class UsageAndQuotaCallbackDispatcher
+ : public QuotaTask,
+ public base::SupportsWeakPtr<UsageAndQuotaCallbackDispatcher> {
+ public:
+ explicit UsageAndQuotaCallbackDispatcher(QuotaManager* manager)
+ : QuotaTask(manager),
+ has_usage_(false),
+ has_global_limited_usage_(false),
+ has_quota_(false),
+ has_available_disk_space_(false),
+ status_(kQuotaStatusUnknown),
+ usage_and_quota_(-1, -1, -1, -1),
+ waiting_callbacks_(1) {}
+
+ virtual ~UsageAndQuotaCallbackDispatcher() {}
+
+ void WaitForResults(const QuotaManager::UsageAndQuotaCallback& callback) {
+ callback_ = callback;
+ Start();
+ }
+
+ void set_usage(int64 usage) {
+ usage_and_quota_.usage = usage;
+ has_usage_ = true;
+ }
+
+ void set_global_limited_usage(int64 global_limited_usage) {
+ usage_and_quota_.global_limited_usage = global_limited_usage;
+ has_global_limited_usage_ = true;
+ }
+
+ void set_quota(int64 quota) {
+ usage_and_quota_.quota = quota;
+ has_quota_ = true;
+ }
+
+ void set_available_disk_space(int64 available_disk_space) {
+ usage_and_quota_.available_disk_space = available_disk_space;
+ has_available_disk_space_ = true;
+ }
+
+ UsageCallback GetHostUsageCallback() {
+ ++waiting_callbacks_;
+ has_usage_ = true;
+ return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetHostUsage,
+ AsWeakPtr());
+ }
+
+ UsageCallback GetGlobalLimitedUsageCallback() {
+ ++waiting_callbacks_;
+ has_global_limited_usage_ = true;
+ return base::Bind(
+ &UsageAndQuotaCallbackDispatcher::DidGetGlobalLimitedUsage,
+ AsWeakPtr());
+ }
+
+ QuotaCallback GetQuotaCallback() {
+ ++waiting_callbacks_;
+ has_quota_ = true;
+ return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetQuota,
+ AsWeakPtr());
+ }
+
+ QuotaCallback GetAvailableSpaceCallback() {
+ ++waiting_callbacks_;
+ has_available_disk_space_ = true;
+ return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetAvailableSpace,
+ AsWeakPtr());
+ }
+
+ private:
+ void DidGetHostUsage(int64 usage) {
+ if (status_ == kQuotaStatusUnknown)
+ status_ = kQuotaStatusOk;
+ usage_and_quota_.usage = usage;
+ CheckCompleted();
+ }
+
+ void DidGetGlobalLimitedUsage(int64 limited_usage) {
+ if (status_ == kQuotaStatusUnknown)
+ status_ = kQuotaStatusOk;
+ usage_and_quota_.global_limited_usage = limited_usage;
+ CheckCompleted();
+ }
+
+ void DidGetQuota(QuotaStatusCode status, int64 quota) {
+ if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk)
+ status_ = status;
+ usage_and_quota_.quota = quota;
+ CheckCompleted();
+ }
+
+ void DidGetAvailableSpace(QuotaStatusCode status, int64 space) {
+ DCHECK_GE(space, 0);
+ if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk)
+ status_ = status;
+ usage_and_quota_.available_disk_space = space;
+ CheckCompleted();
+ }
+
+ virtual void Run() OVERRIDE {
+ // We initialize waiting_callbacks to 1 so that we won't run
+ // the completion callback until here even some of the callbacks
+ // are dispatched synchronously.
+ CheckCompleted();
+ }
+
+ virtual void Aborted() OVERRIDE {
+ callback_.Run(kQuotaErrorAbort, UsageAndQuota());
+ DeleteSoon();
+ }
+
+ virtual void Completed() OVERRIDE {
+ DCHECK(!has_usage_ || usage_and_quota_.usage >= 0);
+ DCHECK(!has_global_limited_usage_ ||
+ usage_and_quota_.global_limited_usage >= 0);
+ DCHECK(!has_quota_ || usage_and_quota_.quota >= 0);
+ DCHECK(!has_available_disk_space_ ||
+ usage_and_quota_.available_disk_space >= 0);
+
+ callback_.Run(status_, usage_and_quota_);
+ DeleteSoon();
+ }
+
+ void CheckCompleted() {
+ if (--waiting_callbacks_ <= 0)
+ CallCompleted();
+ }
+
+ // For sanity checks, they're checked only when DCHECK is on.
+ bool has_usage_;
+ bool has_global_limited_usage_;
+ bool has_quota_;
+ bool has_available_disk_space_;
+
+ QuotaStatusCode status_;
+ UsageAndQuota usage_and_quota_;
+ QuotaManager::UsageAndQuotaCallback callback_;
+ int waiting_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(UsageAndQuotaCallbackDispatcher);
+};
+
+class QuotaManager::GetUsageInfoTask : public QuotaTask {
+ public:
+ GetUsageInfoTask(
+ QuotaManager* manager,
+ const GetUsageInfoCallback& callback)
+ : QuotaTask(manager),
+ callback_(callback),
+ weak_factory_(this) {
+ }
+
+ protected:
+ virtual void Run() OVERRIDE {
+ remaining_trackers_ = 3;
+ // This will populate cached hosts and usage info.
+ manager()->GetUsageTracker(kStorageTypeTemporary)->GetGlobalUsage(
+ base::Bind(&GetUsageInfoTask::DidGetGlobalUsage,
+ weak_factory_.GetWeakPtr(),
+ kStorageTypeTemporary));
+ manager()->GetUsageTracker(kStorageTypePersistent)->GetGlobalUsage(
+ base::Bind(&GetUsageInfoTask::DidGetGlobalUsage,
+ weak_factory_.GetWeakPtr(),
+ kStorageTypePersistent));
+ manager()->GetUsageTracker(kStorageTypeSyncable)->GetGlobalUsage(
+ base::Bind(&GetUsageInfoTask::DidGetGlobalUsage,
+ weak_factory_.GetWeakPtr(),
+ kStorageTypeSyncable));
+ }
+
+ virtual void Completed() OVERRIDE {
+ callback_.Run(entries_);
+ DeleteSoon();
+ }
+
+ virtual void Aborted() OVERRIDE {
+ callback_.Run(UsageInfoEntries());
+ DeleteSoon();
+ }
+
+ private:
+ void AddEntries(StorageType type, UsageTracker* tracker) {
+ std::map<std::string, int64> host_usage;
+ tracker->GetCachedHostsUsage(&host_usage);
+ for (std::map<std::string, int64>::const_iterator iter = host_usage.begin();
+ iter != host_usage.end();
+ ++iter) {
+ entries_.push_back(UsageInfo(iter->first, type, iter->second));
+ }
+ if (--remaining_trackers_ == 0)
+ CallCompleted();
+ }
+
+ void DidGetGlobalUsage(StorageType type, int64, int64) {
+ DCHECK(manager()->GetUsageTracker(type));
+ AddEntries(type, manager()->GetUsageTracker(type));
+ }
+
+ QuotaManager* manager() const {
+ return static_cast<QuotaManager*>(observer());
+ }
+
+ GetUsageInfoCallback callback_;
+ UsageInfoEntries entries_;
+ int remaining_trackers_;
+ base::WeakPtrFactory<GetUsageInfoTask> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(GetUsageInfoTask);
+};
+
+class QuotaManager::OriginDataDeleter : public QuotaTask {
+ public:
+ OriginDataDeleter(QuotaManager* manager,
+ const GURL& origin,
+ StorageType type,
+ int quota_client_mask,
+ const StatusCallback& callback)
+ : QuotaTask(manager),
+ origin_(origin),
+ type_(type),
+ quota_client_mask_(quota_client_mask),
+ error_count_(0),
+ remaining_clients_(-1),
+ skipped_clients_(0),
+ callback_(callback),
+ weak_factory_(this) {}
+
+ protected:
+ virtual void Run() OVERRIDE {
+ error_count_ = 0;
+ remaining_clients_ = manager()->clients_.size();
+ for (QuotaClientList::iterator iter = manager()->clients_.begin();
+ iter != manager()->clients_.end(); ++iter) {
+ if (quota_client_mask_ & (*iter)->id()) {
+ (*iter)->DeleteOriginData(
+ origin_, type_,
+ base::Bind(&OriginDataDeleter::DidDeleteOriginData,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ ++skipped_clients_;
+ if (--remaining_clients_ == 0)
+ CallCompleted();
+ }
+ }
+ }
+
+ virtual void Completed() OVERRIDE {
+ if (error_count_ == 0) {
+ // Only remove the entire origin if we didn't skip any client types.
+ if (skipped_clients_ == 0)
+ manager()->DeleteOriginFromDatabase(origin_, type_);
+ callback_.Run(kQuotaStatusOk);
+ } else {
+ callback_.Run(kQuotaErrorInvalidModification);
+ }
+ DeleteSoon();
+ }
+
+ virtual void Aborted() OVERRIDE {
+ callback_.Run(kQuotaErrorAbort);
+ DeleteSoon();
+ }
+
+ private:
+ void DidDeleteOriginData(QuotaStatusCode status) {
+ DCHECK_GT(remaining_clients_, 0);
+
+ if (status != kQuotaStatusOk)
+ ++error_count_;
+
+ if (--remaining_clients_ == 0)
+ CallCompleted();
+ }
+
+ QuotaManager* manager() const {
+ return static_cast<QuotaManager*>(observer());
+ }
+
+ GURL origin_;
+ StorageType type_;
+ int quota_client_mask_;
+ int error_count_;
+ int remaining_clients_;
+ int skipped_clients_;
+ StatusCallback callback_;
+
+ base::WeakPtrFactory<OriginDataDeleter> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(OriginDataDeleter);
+};
+
+class QuotaManager::HostDataDeleter : public QuotaTask {
+ public:
+ HostDataDeleter(QuotaManager* manager,
+ const std::string& host,
+ StorageType type,
+ int quota_client_mask,
+ const StatusCallback& callback)
+ : QuotaTask(manager),
+ host_(host),
+ type_(type),
+ quota_client_mask_(quota_client_mask),
+ error_count_(0),
+ remaining_clients_(-1),
+ remaining_deleters_(-1),
+ callback_(callback),
+ weak_factory_(this) {}
+
+ protected:
+ virtual void Run() OVERRIDE {
+ error_count_ = 0;
+ remaining_clients_ = manager()->clients_.size();
+ for (QuotaClientList::iterator iter = manager()->clients_.begin();
+ iter != manager()->clients_.end(); ++iter) {
+ (*iter)->GetOriginsForHost(
+ type_, host_,
+ base::Bind(&HostDataDeleter::DidGetOriginsForHost,
+ weak_factory_.GetWeakPtr()));
+ }
+ }
+
+ virtual void Completed() OVERRIDE {
+ if (error_count_ == 0) {
+ callback_.Run(kQuotaStatusOk);
+ } else {
+ callback_.Run(kQuotaErrorInvalidModification);
+ }
+ DeleteSoon();
+ }
+
+ virtual void Aborted() OVERRIDE {
+ callback_.Run(kQuotaErrorAbort);
+ DeleteSoon();
+ }
+
+ private:
+ void DidGetOriginsForHost(const std::set<GURL>& origins) {
+ DCHECK_GT(remaining_clients_, 0);
+
+ origins_.insert(origins.begin(), origins.end());
+
+ if (--remaining_clients_ == 0) {
+ if (!origins_.empty())
+ ScheduleOriginsDeletion();
+ else
+ CallCompleted();
+ }
+ }
+
+ void ScheduleOriginsDeletion() {
+ remaining_deleters_ = origins_.size();
+ for (std::set<GURL>::const_iterator p = origins_.begin();
+ p != origins_.end();
+ ++p) {
+ OriginDataDeleter* deleter =
+ new OriginDataDeleter(
+ manager(), *p, type_, quota_client_mask_,
+ base::Bind(&HostDataDeleter::DidDeleteOriginData,
+ weak_factory_.GetWeakPtr()));
+ deleter->Start();
+ }
+ }
+
+ void DidDeleteOriginData(QuotaStatusCode status) {
+ DCHECK_GT(remaining_deleters_, 0);
+
+ if (status != kQuotaStatusOk)
+ ++error_count_;
+
+ if (--remaining_deleters_ == 0)
+ CallCompleted();
+ }
+
+ QuotaManager* manager() const {
+ return static_cast<QuotaManager*>(observer());
+ }
+
+ std::string host_;
+ StorageType type_;
+ int quota_client_mask_;
+ std::set<GURL> origins_;
+ int error_count_;
+ int remaining_clients_;
+ int remaining_deleters_;
+ StatusCallback callback_;
+
+ base::WeakPtrFactory<HostDataDeleter> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(HostDataDeleter);
+};
+
+class QuotaManager::GetModifiedSinceHelper {
+ public:
+ bool GetModifiedSinceOnDBThread(StorageType type,
+ base::Time modified_since,
+ QuotaDatabase* database) {
+ DCHECK(database);
+ return database->GetOriginsModifiedSince(type, &origins_, modified_since);
+ }
+
+ void DidGetModifiedSince(const base::WeakPtr<QuotaManager>& manager,
+ const GetOriginsCallback& callback,
+ StorageType type,
+ bool success) {
+ if (!manager) {
+ // The operation was aborted.
+ callback.Run(std::set<GURL>(), type);
+ return;
+ }
+ manager->DidDatabaseWork(success);
+ callback.Run(origins_, type);
+ }
+
+ private:
+ std::set<GURL> origins_;
+};
+
+class QuotaManager::DumpQuotaTableHelper {
+ public:
+ bool DumpQuotaTableOnDBThread(QuotaDatabase* database) {
+ DCHECK(database);
+ return database->DumpQuotaTable(
+ base::Bind(&DumpQuotaTableHelper::AppendEntry, base::Unretained(this)));
+ }
+
+ void DidDumpQuotaTable(const base::WeakPtr<QuotaManager>& manager,
+ const DumpQuotaTableCallback& callback,
+ bool success) {
+ if (!manager) {
+ // The operation was aborted.
+ callback.Run(QuotaTableEntries());
+ return;
+ }
+ manager->DidDatabaseWork(success);
+ callback.Run(entries_);
+ }
+
+ private:
+ bool AppendEntry(const QuotaTableEntry& entry) {
+ entries_.push_back(entry);
+ return true;
+ }
+
+ QuotaTableEntries entries_;
+};
+
+class QuotaManager::DumpOriginInfoTableHelper {
+ public:
+ bool DumpOriginInfoTableOnDBThread(QuotaDatabase* database) {
+ DCHECK(database);
+ return database->DumpOriginInfoTable(
+ base::Bind(&DumpOriginInfoTableHelper::AppendEntry,
+ base::Unretained(this)));
+ }
+
+ void DidDumpOriginInfoTable(const base::WeakPtr<QuotaManager>& manager,
+ const DumpOriginInfoTableCallback& callback,
+ bool success) {
+ if (!manager) {
+ // The operation was aborted.
+ callback.Run(OriginInfoTableEntries());
+ return;
+ }
+ manager->DidDatabaseWork(success);
+ callback.Run(entries_);
+ }
+
+ private:
+ bool AppendEntry(const OriginInfoTableEntry& entry) {
+ entries_.push_back(entry);
+ return true;
+ }
+
+ OriginInfoTableEntries entries_;
+};
+
+// QuotaManager ---------------------------------------------------------------
+
+QuotaManager::QuotaManager(
+ bool is_incognito,
+ const base::FilePath& profile_path,
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& db_thread,
+ const scoped_refptr<SpecialStoragePolicy>& special_storage_policy)
+ : is_incognito_(is_incognito),
+ profile_path_(profile_path),
+ proxy_(new QuotaManagerProxy(this, io_thread)),
+ db_disabled_(false),
+ eviction_disabled_(false),
+ io_thread_(io_thread),
+ db_thread_(db_thread),
+ temporary_quota_initialized_(false),
+ temporary_quota_override_(-1),
+ desired_available_space_(-1),
+ special_storage_policy_(special_storage_policy),
+ get_disk_space_fn_(&CallSystemGetAmountOfFreeDiskSpace),
+ storage_monitor_(new StorageMonitor(this)),
+ weak_factory_(this) {
+}
+
+void QuotaManager::GetUsageInfo(const GetUsageInfoCallback& callback) {
+ LazyInitialize();
+ GetUsageInfoTask* get_usage_info = new GetUsageInfoTask(this, callback);
+ get_usage_info->Start();
+}
+
+void QuotaManager::GetUsageAndQuotaForWebApps(
+ const GURL& origin,
+ StorageType type,
+ const GetUsageAndQuotaCallback& callback) {
+ if (type != kStorageTypeTemporary &&
+ type != kStorageTypePersistent &&
+ type != kStorageTypeSyncable) {
+ callback.Run(kQuotaErrorNotSupported, 0, 0);
+ return;
+ }
+
+ DCHECK(origin == origin.GetOrigin());
+ LazyInitialize();
+
+ bool unlimited = IsStorageUnlimited(origin, type);
+ bool can_query_disk_size = CanQueryDiskSize(origin);
+
+ UsageAndQuotaCallbackDispatcher* dispatcher =
+ new UsageAndQuotaCallbackDispatcher(this);
+
+ UsageAndQuota usage_and_quota;
+ if (unlimited) {
+ dispatcher->set_quota(kNoLimit);
+ } else {
+ if (type == kStorageTypeTemporary) {
+ GetUsageTracker(type)->GetGlobalLimitedUsage(
+ dispatcher->GetGlobalLimitedUsageCallback());
+ GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback());
+ } else if (type == kStorageTypePersistent) {
+ GetPersistentHostQuota(net::GetHostOrSpecFromURL(origin),
+ dispatcher->GetQuotaCallback());
+ } else {
+ dispatcher->set_quota(kSyncableStorageDefaultHostQuota);
+ }
+ }
+
+ DCHECK(GetUsageTracker(type));
+ GetUsageTracker(type)->GetHostUsage(net::GetHostOrSpecFromURL(origin),
+ dispatcher->GetHostUsageCallback());
+
+ if (!is_incognito_ && (unlimited || can_query_disk_size))
+ GetAvailableSpace(dispatcher->GetAvailableSpaceCallback());
+
+ dispatcher->WaitForResults(base::Bind(
+ &DispatchUsageAndQuotaForWebApps,
+ type, is_incognito_, unlimited, can_query_disk_size,
+ callback));
+}
+
+void QuotaManager::GetUsageAndQuota(
+ const GURL& origin, StorageType type,
+ const GetUsageAndQuotaCallback& callback) {
+ DCHECK(origin == origin.GetOrigin());
+
+ if (IsStorageUnlimited(origin, type)) {
+ callback.Run(kQuotaStatusOk, 0, kNoLimit);
+ return;
+ }
+
+ GetUsageAndQuotaForWebApps(origin, type, callback);
+}
+
+void QuotaManager::NotifyStorageAccessed(
+ QuotaClient::ID client_id,
+ const GURL& origin, StorageType type) {
+ DCHECK(origin == origin.GetOrigin());
+ NotifyStorageAccessedInternal(client_id, origin, type, base::Time::Now());
+}
+
+void QuotaManager::NotifyStorageModified(
+ QuotaClient::ID client_id,
+ const GURL& origin, StorageType type, int64 delta) {
+ DCHECK(origin == origin.GetOrigin());
+ NotifyStorageModifiedInternal(client_id, origin, type, delta,
+ base::Time::Now());
+}
+
+void QuotaManager::NotifyOriginInUse(const GURL& origin) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+ origins_in_use_[origin]++;
+}
+
+void QuotaManager::NotifyOriginNoLongerInUse(const GURL& origin) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+ DCHECK(IsOriginInUse(origin));
+ int& count = origins_in_use_[origin];
+ if (--count == 0)
+ origins_in_use_.erase(origin);
+}
+
+void QuotaManager::SetUsageCacheEnabled(QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ bool enabled) {
+ LazyInitialize();
+ DCHECK(GetUsageTracker(type));
+ GetUsageTracker(type)->SetUsageCacheEnabled(client_id, origin, enabled);
+}
+
+void QuotaManager::DeleteOriginData(
+ const GURL& origin, StorageType type, int quota_client_mask,
+ const StatusCallback& callback) {
+ LazyInitialize();
+
+ if (origin.is_empty() || clients_.empty()) {
+ callback.Run(kQuotaStatusOk);
+ return;
+ }
+
+ DCHECK(origin == origin.GetOrigin());
+ OriginDataDeleter* deleter =
+ new OriginDataDeleter(this, origin, type, quota_client_mask, callback);
+ deleter->Start();
+}
+
+void QuotaManager::DeleteHostData(const std::string& host,
+ StorageType type,
+ int quota_client_mask,
+ const StatusCallback& callback) {
+ LazyInitialize();
+
+ if (host.empty() || clients_.empty()) {
+ callback.Run(kQuotaStatusOk);
+ return;
+ }
+
+ HostDataDeleter* deleter =
+ new HostDataDeleter(this, host, type, quota_client_mask, callback);
+ deleter->Start();
+}
+
+void QuotaManager::GetAvailableSpace(const AvailableSpaceCallback& callback) {
+ if (!available_space_callbacks_.Add(callback))
+ return;
+
+ PostTaskAndReplyWithResult(db_thread_.get(),
+ FROM_HERE,
+ base::Bind(get_disk_space_fn_, profile_path_),
+ base::Bind(&QuotaManager::DidGetAvailableSpace,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaManager::GetTemporaryGlobalQuota(const QuotaCallback& callback) {
+ LazyInitialize();
+ if (!temporary_quota_initialized_) {
+ db_initialization_callbacks_.Add(base::Bind(
+ &QuotaManager::GetTemporaryGlobalQuota,
+ weak_factory_.GetWeakPtr(), callback));
+ return;
+ }
+
+ if (temporary_quota_override_ > 0) {
+ callback.Run(kQuotaStatusOk, temporary_quota_override_);
+ return;
+ }
+
+ UsageAndQuotaCallbackDispatcher* dispatcher =
+ new UsageAndQuotaCallbackDispatcher(this);
+ GetUsageTracker(kStorageTypeTemporary)->
+ GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback());
+ GetAvailableSpace(dispatcher->GetAvailableSpaceCallback());
+ dispatcher->WaitForResults(
+ base::Bind(&DispatchTemporaryGlobalQuotaCallback, callback));
+}
+
+void QuotaManager::SetTemporaryGlobalOverrideQuota(
+ int64 new_quota, const QuotaCallback& callback) {
+ LazyInitialize();
+
+ if (new_quota < 0) {
+ if (!callback.is_null())
+ callback.Run(kQuotaErrorInvalidModification, -1);
+ return;
+ }
+
+ if (db_disabled_) {
+ if (!callback.is_null())
+ callback.Run(kQuotaErrorInvalidAccess, -1);
+ return;
+ }
+
+ int64* new_quota_ptr = new int64(new_quota);
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&SetTemporaryGlobalOverrideQuotaOnDBThread,
+ base::Unretained(new_quota_ptr)),
+ base::Bind(&QuotaManager::DidSetTemporaryGlobalOverrideQuota,
+ weak_factory_.GetWeakPtr(),
+ callback,
+ base::Owned(new_quota_ptr)));
+}
+
+void QuotaManager::GetPersistentHostQuota(const std::string& host,
+ const QuotaCallback& callback) {
+ LazyInitialize();
+ if (host.empty()) {
+ // This could happen if we are called on file:///.
+ // TODO(kinuko) We may want to respect --allow-file-access-from-files
+ // command line switch.
+ callback.Run(kQuotaStatusOk, 0);
+ return;
+ }
+
+ if (!persistent_host_quota_callbacks_.Add(host, callback))
+ return;
+
+ int64* quota_ptr = new int64(0);
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&GetPersistentHostQuotaOnDBThread,
+ host,
+ base::Unretained(quota_ptr)),
+ base::Bind(&QuotaManager::DidGetPersistentHostQuota,
+ weak_factory_.GetWeakPtr(),
+ host,
+ base::Owned(quota_ptr)));
+}
+
+void QuotaManager::SetPersistentHostQuota(const std::string& host,
+ int64 new_quota,
+ const QuotaCallback& callback) {
+ LazyInitialize();
+ if (host.empty()) {
+ // This could happen if we are called on file:///.
+ callback.Run(kQuotaErrorNotSupported, 0);
+ return;
+ }
+
+ if (new_quota < 0) {
+ callback.Run(kQuotaErrorInvalidModification, -1);
+ return;
+ }
+
+ if (kPerHostPersistentQuotaLimit < new_quota) {
+ // Cap the requested size at the per-host quota limit.
+ new_quota = kPerHostPersistentQuotaLimit;
+ }
+
+ if (db_disabled_) {
+ callback.Run(kQuotaErrorInvalidAccess, -1);
+ return;
+ }
+
+ int64* new_quota_ptr = new int64(new_quota);
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&SetPersistentHostQuotaOnDBThread,
+ host,
+ base::Unretained(new_quota_ptr)),
+ base::Bind(&QuotaManager::DidSetPersistentHostQuota,
+ weak_factory_.GetWeakPtr(),
+ host,
+ callback,
+ base::Owned(new_quota_ptr)));
+}
+
+void QuotaManager::GetGlobalUsage(StorageType type,
+ const GlobalUsageCallback& callback) {
+ LazyInitialize();
+ DCHECK(GetUsageTracker(type));
+ GetUsageTracker(type)->GetGlobalUsage(callback);
+}
+
+void QuotaManager::GetHostUsage(const std::string& host,
+ StorageType type,
+ const UsageCallback& callback) {
+ LazyInitialize();
+ DCHECK(GetUsageTracker(type));
+ GetUsageTracker(type)->GetHostUsage(host, callback);
+}
+
+void QuotaManager::GetHostUsage(const std::string& host,
+ StorageType type,
+ QuotaClient::ID client_id,
+ const UsageCallback& callback) {
+ LazyInitialize();
+ DCHECK(GetUsageTracker(type));
+ ClientUsageTracker* tracker =
+ GetUsageTracker(type)->GetClientTracker(client_id);
+ if (!tracker) {
+ callback.Run(0);
+ return;
+ }
+ tracker->GetHostUsage(host, callback);
+}
+
+bool QuotaManager::IsTrackingHostUsage(StorageType type,
+ QuotaClient::ID client_id) const {
+ UsageTracker* tracker = GetUsageTracker(type);
+ return tracker && tracker->GetClientTracker(client_id);
+}
+
+void QuotaManager::GetStatistics(
+ std::map<std::string, std::string>* statistics) {
+ DCHECK(statistics);
+ if (temporary_storage_evictor_) {
+ std::map<std::string, int64> stats;
+ temporary_storage_evictor_->GetStatistics(&stats);
+ for (std::map<std::string, int64>::iterator p = stats.begin();
+ p != stats.end();
+ ++p)
+ (*statistics)[p->first] = base::Int64ToString(p->second);
+ }
+}
+
+bool QuotaManager::IsStorageUnlimited(const GURL& origin,
+ StorageType type) const {
+ // For syncable storage we should always enforce quota (since the
+ // quota must be capped by the server limit).
+ if (type == kStorageTypeSyncable)
+ return false;
+ if (type == kStorageTypeQuotaNotManaged)
+ return true;
+ return special_storage_policy_.get() &&
+ special_storage_policy_->IsStorageUnlimited(origin);
+}
+
+void QuotaManager::GetOriginsModifiedSince(StorageType type,
+ base::Time modified_since,
+ const GetOriginsCallback& callback) {
+ LazyInitialize();
+ GetModifiedSinceHelper* helper = new GetModifiedSinceHelper;
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&GetModifiedSinceHelper::GetModifiedSinceOnDBThread,
+ base::Unretained(helper),
+ type,
+ modified_since),
+ base::Bind(&GetModifiedSinceHelper::DidGetModifiedSince,
+ base::Owned(helper),
+ weak_factory_.GetWeakPtr(),
+ callback,
+ type));
+}
+
+bool QuotaManager::ResetUsageTracker(StorageType type) {
+ DCHECK(GetUsageTracker(type));
+ if (GetUsageTracker(type)->IsWorking())
+ return false;
+ switch (type) {
+ case kStorageTypeTemporary:
+ temporary_usage_tracker_.reset(new UsageTracker(
+ clients_, kStorageTypeTemporary, special_storage_policy_.get(),
+ storage_monitor_.get()));
+ return true;
+ case kStorageTypePersistent:
+ persistent_usage_tracker_.reset(new UsageTracker(
+ clients_, kStorageTypePersistent, special_storage_policy_.get(),
+ storage_monitor_.get()));
+ return true;
+ case kStorageTypeSyncable:
+ syncable_usage_tracker_.reset(new UsageTracker(
+ clients_, kStorageTypeSyncable, special_storage_policy_.get(),
+ storage_monitor_.get()));
+ return true;
+ default:
+ NOTREACHED();
+ }
+ return true;
+}
+
+void QuotaManager::AddStorageObserver(
+ StorageObserver* observer, const StorageObserver::MonitorParams& params) {
+ DCHECK(observer);
+ storage_monitor_->AddObserver(observer, params);
+}
+
+void QuotaManager::RemoveStorageObserver(StorageObserver* observer) {
+ DCHECK(observer);
+ storage_monitor_->RemoveObserver(observer);
+}
+
+void QuotaManager::RemoveStorageObserverForFilter(
+ StorageObserver* observer, const StorageObserver::Filter& filter) {
+ DCHECK(observer);
+ storage_monitor_->RemoveObserverForFilter(observer, filter);
+}
+
+QuotaManager::~QuotaManager() {
+ proxy_->manager_ = NULL;
+ std::for_each(clients_.begin(), clients_.end(),
+ std::mem_fun(&QuotaClient::OnQuotaManagerDestroyed));
+ if (database_)
+ db_thread_->DeleteSoon(FROM_HERE, database_.release());
+}
+
+QuotaManager::EvictionContext::EvictionContext()
+ : evicted_type(kStorageTypeUnknown) {
+}
+
+QuotaManager::EvictionContext::~EvictionContext() {
+}
+
+void QuotaManager::LazyInitialize() {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+ if (database_) {
+ // Initialization seems to be done already.
+ return;
+ }
+
+ // Use an empty path to open an in-memory only databse for incognito.
+ database_.reset(new QuotaDatabase(is_incognito_ ? base::FilePath() :
+ profile_path_.AppendASCII(kDatabaseName)));
+
+ temporary_usage_tracker_.reset(new UsageTracker(
+ clients_, kStorageTypeTemporary, special_storage_policy_.get(),
+ storage_monitor_.get()));
+ persistent_usage_tracker_.reset(new UsageTracker(
+ clients_, kStorageTypePersistent, special_storage_policy_.get(),
+ storage_monitor_.get()));
+ syncable_usage_tracker_.reset(new UsageTracker(
+ clients_, kStorageTypeSyncable, special_storage_policy_.get(),
+ storage_monitor_.get()));
+
+ int64* temporary_quota_override = new int64(-1);
+ int64* desired_available_space = new int64(-1);
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&InitializeOnDBThread,
+ base::Unretained(temporary_quota_override),
+ base::Unretained(desired_available_space)),
+ base::Bind(&QuotaManager::DidInitialize,
+ weak_factory_.GetWeakPtr(),
+ base::Owned(temporary_quota_override),
+ base::Owned(desired_available_space)));
+}
+
+void QuotaManager::RegisterClient(QuotaClient* client) {
+ DCHECK(!database_.get());
+ clients_.push_back(client);
+}
+
+UsageTracker* QuotaManager::GetUsageTracker(StorageType type) const {
+ switch (type) {
+ case kStorageTypeTemporary:
+ return temporary_usage_tracker_.get();
+ case kStorageTypePersistent:
+ return persistent_usage_tracker_.get();
+ case kStorageTypeSyncable:
+ return syncable_usage_tracker_.get();
+ case kStorageTypeQuotaNotManaged:
+ return NULL;
+ case kStorageTypeUnknown:
+ NOTREACHED();
+ }
+ return NULL;
+}
+
+void QuotaManager::GetCachedOrigins(
+ StorageType type, std::set<GURL>* origins) {
+ DCHECK(origins);
+ LazyInitialize();
+ DCHECK(GetUsageTracker(type));
+ GetUsageTracker(type)->GetCachedOrigins(origins);
+}
+
+void QuotaManager::NotifyStorageAccessedInternal(
+ QuotaClient::ID client_id,
+ const GURL& origin, StorageType type,
+ base::Time accessed_time) {
+ LazyInitialize();
+ if (type == kStorageTypeTemporary && !lru_origin_callback_.is_null()) {
+ // Record the accessed origins while GetLRUOrigin task is runing
+ // to filter out them from eviction.
+ access_notified_origins_.insert(origin);
+ }
+
+ if (db_disabled_)
+ return;
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&UpdateAccessTimeOnDBThread, origin, type, accessed_time),
+ base::Bind(&QuotaManager::DidDatabaseWork,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaManager::NotifyStorageModifiedInternal(
+ QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ int64 delta,
+ base::Time modified_time) {
+ LazyInitialize();
+ DCHECK(GetUsageTracker(type));
+ GetUsageTracker(type)->UpdateUsageCache(client_id, origin, delta);
+
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&UpdateModifiedTimeOnDBThread, origin, type, modified_time),
+ base::Bind(&QuotaManager::DidDatabaseWork,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaManager::DumpQuotaTable(const DumpQuotaTableCallback& callback) {
+ DumpQuotaTableHelper* helper = new DumpQuotaTableHelper;
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&DumpQuotaTableHelper::DumpQuotaTableOnDBThread,
+ base::Unretained(helper)),
+ base::Bind(&DumpQuotaTableHelper::DidDumpQuotaTable,
+ base::Owned(helper),
+ weak_factory_.GetWeakPtr(),
+ callback));
+}
+
+void QuotaManager::DumpOriginInfoTable(
+ const DumpOriginInfoTableCallback& callback) {
+ DumpOriginInfoTableHelper* helper = new DumpOriginInfoTableHelper;
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&DumpOriginInfoTableHelper::DumpOriginInfoTableOnDBThread,
+ base::Unretained(helper)),
+ base::Bind(&DumpOriginInfoTableHelper::DidDumpOriginInfoTable,
+ base::Owned(helper),
+ weak_factory_.GetWeakPtr(),
+ callback));
+}
+
+void QuotaManager::StartEviction() {
+ DCHECK(!temporary_storage_evictor_.get());
+ temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor(
+ this, kEvictionIntervalInMilliSeconds));
+ if (desired_available_space_ >= 0)
+ temporary_storage_evictor_->set_min_available_disk_space_to_start_eviction(
+ desired_available_space_);
+ temporary_storage_evictor_->Start();
+}
+
+void QuotaManager::DeleteOriginFromDatabase(
+ const GURL& origin, StorageType type) {
+ LazyInitialize();
+ if (db_disabled_)
+ return;
+
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&DeleteOriginInfoOnDBThread, origin, type),
+ base::Bind(&QuotaManager::DidDatabaseWork,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaManager::DidOriginDataEvicted(QuotaStatusCode status) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+
+ // We only try evict origins that are not in use, so basically
+ // deletion attempt for eviction should not fail. Let's record
+ // the origin if we get error and exclude it from future eviction
+ // if the error happens consistently (> kThresholdOfErrorsToBeBlacklisted).
+ if (status != kQuotaStatusOk)
+ origins_in_error_[eviction_context_.evicted_origin]++;
+
+ eviction_context_.evict_origin_data_callback.Run(status);
+ eviction_context_.evict_origin_data_callback.Reset();
+}
+
+void QuotaManager::ReportHistogram() {
+ GetGlobalUsage(kStorageTypeTemporary,
+ base::Bind(
+ &QuotaManager::DidGetTemporaryGlobalUsageForHistogram,
+ weak_factory_.GetWeakPtr()));
+ GetGlobalUsage(kStorageTypePersistent,
+ base::Bind(
+ &QuotaManager::DidGetPersistentGlobalUsageForHistogram,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaManager::DidGetTemporaryGlobalUsageForHistogram(
+ int64 usage,
+ int64 unlimited_usage) {
+ UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfTemporaryStorage", usage);
+
+ std::set<GURL> origins;
+ GetCachedOrigins(kStorageTypeTemporary, &origins);
+
+ size_t num_origins = origins.size();
+ size_t protected_origins = 0;
+ size_t unlimited_origins = 0;
+ CountOriginType(origins,
+ special_storage_policy_.get(),
+ &protected_origins,
+ &unlimited_origins);
+
+ UMA_HISTOGRAM_COUNTS("Quota.NumberOfTemporaryStorageOrigins",
+ num_origins);
+ UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedTemporaryStorageOrigins",
+ protected_origins);
+ UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedTemporaryStorageOrigins",
+ unlimited_origins);
+}
+
+void QuotaManager::DidGetPersistentGlobalUsageForHistogram(
+ int64 usage,
+ int64 unlimited_usage) {
+ UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage);
+
+ std::set<GURL> origins;
+ GetCachedOrigins(kStorageTypePersistent, &origins);
+
+ size_t num_origins = origins.size();
+ size_t protected_origins = 0;
+ size_t unlimited_origins = 0;
+ CountOriginType(origins,
+ special_storage_policy_.get(),
+ &protected_origins,
+ &unlimited_origins);
+
+ UMA_HISTOGRAM_COUNTS("Quota.NumberOfPersistentStorageOrigins",
+ num_origins);
+ UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedPersistentStorageOrigins",
+ protected_origins);
+ UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedPersistentStorageOrigins",
+ unlimited_origins);
+}
+
+void QuotaManager::GetLRUOrigin(
+ StorageType type,
+ const GetLRUOriginCallback& callback) {
+ LazyInitialize();
+ // This must not be called while there's an in-flight task.
+ DCHECK(lru_origin_callback_.is_null());
+ lru_origin_callback_ = callback;
+ if (db_disabled_) {
+ lru_origin_callback_.Run(GURL());
+ lru_origin_callback_.Reset();
+ return;
+ }
+
+ std::set<GURL>* exceptions = new std::set<GURL>;
+ for (std::map<GURL, int>::const_iterator p = origins_in_use_.begin();
+ p != origins_in_use_.end();
+ ++p) {
+ if (p->second > 0)
+ exceptions->insert(p->first);
+ }
+ for (std::map<GURL, int>::const_iterator p = origins_in_error_.begin();
+ p != origins_in_error_.end();
+ ++p) {
+ if (p->second > QuotaManager::kThresholdOfErrorsToBeBlacklisted)
+ exceptions->insert(p->first);
+ }
+
+ GURL* url = new GURL;
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&GetLRUOriginOnDBThread,
+ type,
+ base::Owned(exceptions),
+ special_storage_policy_,
+ base::Unretained(url)),
+ base::Bind(&QuotaManager::DidGetLRUOrigin,
+ weak_factory_.GetWeakPtr(),
+ base::Owned(url)));
+}
+
+void QuotaManager::EvictOriginData(
+ const GURL& origin,
+ StorageType type,
+ const EvictOriginDataCallback& callback) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+ DCHECK_EQ(type, kStorageTypeTemporary);
+
+ eviction_context_.evicted_origin = origin;
+ eviction_context_.evicted_type = type;
+ eviction_context_.evict_origin_data_callback = callback;
+
+ DeleteOriginData(origin, type, QuotaClient::kAllClientsMask,
+ base::Bind(&QuotaManager::DidOriginDataEvicted,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaManager::GetUsageAndQuotaForEviction(
+ const UsageAndQuotaCallback& callback) {
+ DCHECK(io_thread_->BelongsToCurrentThread());
+ LazyInitialize();
+
+ UsageAndQuotaCallbackDispatcher* dispatcher =
+ new UsageAndQuotaCallbackDispatcher(this);
+ GetUsageTracker(kStorageTypeTemporary)->
+ GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback());
+ GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback());
+ GetAvailableSpace(dispatcher->GetAvailableSpaceCallback());
+ dispatcher->WaitForResults(callback);
+}
+
+void QuotaManager::DidSetTemporaryGlobalOverrideQuota(
+ const QuotaCallback& callback,
+ const int64* new_quota,
+ bool success) {
+ QuotaStatusCode status = kQuotaErrorInvalidAccess;
+ DidDatabaseWork(success);
+ if (success) {
+ temporary_quota_override_ = *new_quota;
+ status = kQuotaStatusOk;
+ }
+
+ if (callback.is_null())
+ return;
+
+ callback.Run(status, *new_quota);
+}
+
+void QuotaManager::DidGetPersistentHostQuota(const std::string& host,
+ const int64* quota,
+ bool success) {
+ DidDatabaseWork(success);
+ persistent_host_quota_callbacks_.Run(
+ host, MakeTuple(kQuotaStatusOk, *quota));
+}
+
+void QuotaManager::DidSetPersistentHostQuota(const std::string& host,
+ const QuotaCallback& callback,
+ const int64* new_quota,
+ bool success) {
+ DidDatabaseWork(success);
+ callback.Run(success ? kQuotaStatusOk : kQuotaErrorInvalidAccess, *new_quota);
+}
+
+void QuotaManager::DidInitialize(int64* temporary_quota_override,
+ int64* desired_available_space,
+ bool success) {
+ temporary_quota_override_ = *temporary_quota_override;
+ desired_available_space_ = *desired_available_space;
+ temporary_quota_initialized_ = true;
+ DidDatabaseWork(success);
+
+ histogram_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(
+ kReportHistogramInterval),
+ this, &QuotaManager::ReportHistogram);
+
+ db_initialization_callbacks_.Run(MakeTuple());
+ GetTemporaryGlobalQuota(
+ base::Bind(&QuotaManager::DidGetInitialTemporaryGlobalQuota,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaManager::DidGetLRUOrigin(const GURL* origin,
+ bool success) {
+ DidDatabaseWork(success);
+ // Make sure the returned origin is (still) not in the origin_in_use_ set
+ // and has not been accessed since we posted the task.
+ if (origins_in_use_.find(*origin) != origins_in_use_.end() ||
+ access_notified_origins_.find(*origin) != access_notified_origins_.end())
+ lru_origin_callback_.Run(GURL());
+ else
+ lru_origin_callback_.Run(*origin);
+ access_notified_origins_.clear();
+ lru_origin_callback_.Reset();
+}
+
+void QuotaManager::DidGetInitialTemporaryGlobalQuota(
+ QuotaStatusCode status, int64 quota_unused) {
+ if (eviction_disabled_)
+ return;
+
+ std::set<GURL>* origins = new std::set<GURL>;
+ temporary_usage_tracker_->GetCachedOrigins(origins);
+ // This will call the StartEviction() when initial origin registration
+ // is completed.
+ PostTaskAndReplyWithResultForDBThread(
+ FROM_HERE,
+ base::Bind(&InitializeTemporaryOriginsInfoOnDBThread,
+ base::Owned(origins)),
+ base::Bind(&QuotaManager::DidInitializeTemporaryOriginsInfo,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaManager::DidInitializeTemporaryOriginsInfo(bool success) {
+ DidDatabaseWork(success);
+ if (success)
+ StartEviction();
+}
+
+void QuotaManager::DidGetAvailableSpace(int64 space) {
+ available_space_callbacks_.Run(MakeTuple(kQuotaStatusOk, space));
+}
+
+void QuotaManager::DidDatabaseWork(bool success) {
+ db_disabled_ = !success;
+}
+
+void QuotaManager::DeleteOnCorrectThread() const {
+ if (!io_thread_->BelongsToCurrentThread() &&
+ io_thread_->DeleteSoon(FROM_HERE, this)) {
+ return;
+ }
+ delete this;
+}
+
+void QuotaManager::PostTaskAndReplyWithResultForDBThread(
+ const tracked_objects::Location& from_here,
+ const base::Callback<bool(QuotaDatabase*)>& task,
+ const base::Callback<void(bool)>& reply) {
+ // Deleting manager will post another task to DB thread to delete
+ // |database_|, therefore we can be sure that database_ is alive when this
+ // task runs.
+ base::PostTaskAndReplyWithResult(
+ db_thread_.get(),
+ from_here,
+ base::Bind(task, base::Unretained(database_.get())),
+ reply);
+}
+
+} // namespace storage
diff --git a/storage/browser/quota/quota_manager.h b/storage/browser/quota/quota_manager.h
new file mode 100644
index 0000000..98a9415
--- /dev/null
+++ b/storage/browser/quota/quota_manager.h
@@ -0,0 +1,460 @@
+// 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 STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_H_
+#define STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_H_
+
+#include <deque>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.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 "base/sequenced_task_runner_helpers.h"
+#include "storage/browser/quota/quota_callbacks.h"
+#include "storage/browser/quota/quota_client.h"
+#include "storage/browser/quota/quota_database.h"
+#include "storage/browser/quota/quota_task.h"
+#include "storage/browser/quota/special_storage_policy.h"
+#include "storage/browser/quota/storage_observer.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+class SingleThreadTaskRunner;
+}
+
+namespace quota_internals {
+class QuotaInternalsProxy;
+}
+
+namespace content {
+class MockQuotaManager;
+class MockStorageClient;
+class QuotaManagerTest;
+class StorageMonitorTest;
+
+}
+
+namespace storage {
+
+class QuotaDatabase;
+class QuotaManagerProxy;
+class QuotaTemporaryStorageEvictor;
+class StorageMonitor;
+class UsageTracker;
+
+struct QuotaManagerDeleter;
+
+struct STORAGE_EXPORT UsageAndQuota {
+ int64 usage;
+ int64 global_limited_usage;
+ int64 quota;
+ int64 available_disk_space;
+
+ UsageAndQuota();
+ UsageAndQuota(int64 usage,
+ int64 global_limited_usage,
+ int64 quota,
+ int64 available_disk_space);
+};
+
+// An interface called by QuotaTemporaryStorageEvictor.
+class STORAGE_EXPORT QuotaEvictionHandler {
+ public:
+ typedef base::Callback<void(const GURL&)> GetLRUOriginCallback;
+ typedef StatusCallback EvictOriginDataCallback;
+ typedef base::Callback<void(QuotaStatusCode status,
+ const UsageAndQuota& usage_and_quota)>
+ UsageAndQuotaCallback;
+
+ // Returns the least recently used origin. It might return empty
+ // GURL when there are no evictable origins.
+ virtual void GetLRUOrigin(
+ StorageType type,
+ const GetLRUOriginCallback& callback) = 0;
+
+ virtual void EvictOriginData(
+ const GURL& origin,
+ StorageType type,
+ const EvictOriginDataCallback& callback) = 0;
+
+ virtual void GetUsageAndQuotaForEviction(
+ const UsageAndQuotaCallback& callback) = 0;
+
+ protected:
+ virtual ~QuotaEvictionHandler() {}
+};
+
+struct UsageInfo {
+ UsageInfo(const std::string& host, StorageType type, int64 usage)
+ : host(host),
+ type(type),
+ usage(usage) {}
+ std::string host;
+ StorageType type;
+ int64 usage;
+};
+
+// The quota manager class. This class is instantiated per profile and
+// held by the profile. With the exception of the constructor and the
+// proxy() method, all methods should only be called on the IO thread.
+class STORAGE_EXPORT QuotaManager
+ : public QuotaTaskObserver,
+ public QuotaEvictionHandler,
+ public base::RefCountedThreadSafe<QuotaManager, QuotaManagerDeleter> {
+ public:
+ typedef base::Callback<void(QuotaStatusCode,
+ int64 /* usage */,
+ int64 /* quota */)>
+ GetUsageAndQuotaCallback;
+
+ static const int64 kIncognitoDefaultQuotaLimit;
+ static const int64 kNoLimit;
+
+ QuotaManager(
+ bool is_incognito,
+ const base::FilePath& profile_path,
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_thread,
+ const scoped_refptr<base::SequencedTaskRunner>& db_thread,
+ const scoped_refptr<SpecialStoragePolicy>& special_storage_policy);
+
+ // Returns a proxy object that can be used on any thread.
+ QuotaManagerProxy* proxy() { return proxy_.get(); }
+
+ // Called by clients or webapps. Returns usage per host.
+ void GetUsageInfo(const GetUsageInfoCallback& callback);
+
+ // Called by Web Apps.
+ // This method is declared as virtual to allow test code to override it.
+ virtual void GetUsageAndQuotaForWebApps(
+ const GURL& origin,
+ StorageType type,
+ const GetUsageAndQuotaCallback& callback);
+
+ // Called by StorageClients.
+ // This method is declared as virtual to allow test code to override it.
+ //
+ // For UnlimitedStorage origins, this version skips usage and quota handling
+ // to avoid extra query cost.
+ // Do not call this method for apps/user-facing code.
+ virtual void GetUsageAndQuota(
+ const GURL& origin,
+ StorageType type,
+ const GetUsageAndQuotaCallback& callback);
+
+ // Called by clients via proxy.
+ // Client storage should call this method when storage is accessed.
+ // Used to maintain LRU ordering.
+ void NotifyStorageAccessed(QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type);
+
+ // Called by clients via proxy.
+ // Client storage must call this method whenever they have made any
+ // modifications that change the amount of data stored in their storage.
+ void NotifyStorageModified(QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ int64 delta);
+
+ // Used to avoid evicting origins with open pages.
+ // A call to NotifyOriginInUse must be balanced by a later call
+ // to NotifyOriginNoLongerInUse.
+ void NotifyOriginInUse(const GURL& origin);
+ void NotifyOriginNoLongerInUse(const GURL& origin);
+ bool IsOriginInUse(const GURL& origin) const {
+ return origins_in_use_.find(origin) != origins_in_use_.end();
+ }
+
+ void SetUsageCacheEnabled(QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ bool enabled);
+
+ // DeleteOriginData and DeleteHostData (surprisingly enough) delete data of a
+ // particular StorageType associated with either a specific origin or set of
+ // origins. Each method additionally requires a |quota_client_mask| which
+ // specifies the types of QuotaClients to delete from the origin. This is
+ // specified by the caller as a bitmask built from QuotaClient::IDs. Setting
+ // the mask to QuotaClient::kAllClientsMask will remove all clients from the
+ // origin, regardless of type.
+ virtual void DeleteOriginData(const GURL& origin,
+ StorageType type,
+ int quota_client_mask,
+ const StatusCallback& callback);
+ void DeleteHostData(const std::string& host,
+ StorageType type,
+ int quota_client_mask,
+ const StatusCallback& callback);
+
+ // Called by UI and internal modules.
+ void GetAvailableSpace(const AvailableSpaceCallback& callback);
+ void GetTemporaryGlobalQuota(const QuotaCallback& callback);
+
+ // Ok to call with NULL callback.
+ void SetTemporaryGlobalOverrideQuota(int64 new_quota,
+ const QuotaCallback& callback);
+
+ void GetPersistentHostQuota(const std::string& host,
+ const QuotaCallback& callback);
+ void SetPersistentHostQuota(const std::string& host,
+ int64 new_quota,
+ const QuotaCallback& callback);
+ void GetGlobalUsage(StorageType type, const GlobalUsageCallback& callback);
+ void GetHostUsage(const std::string& host, StorageType type,
+ const UsageCallback& callback);
+ void GetHostUsage(const std::string& host, StorageType type,
+ QuotaClient::ID client_id,
+ const UsageCallback& callback);
+
+ bool IsTrackingHostUsage(StorageType type, QuotaClient::ID client_id) const;
+
+ void GetStatistics(std::map<std::string, std::string>* statistics);
+
+ bool IsStorageUnlimited(const GURL& origin, StorageType type) const;
+
+ bool CanQueryDiskSize(const GURL& origin) const {
+ return special_storage_policy_.get() &&
+ special_storage_policy_->CanQueryDiskSize(origin);
+ }
+
+ virtual void GetOriginsModifiedSince(StorageType type,
+ base::Time modified_since,
+ const GetOriginsCallback& callback);
+
+ bool ResetUsageTracker(StorageType type);
+
+ // Used to register/deregister observers that wish to monitor storage events.
+ void AddStorageObserver(StorageObserver* observer,
+ const StorageObserver::MonitorParams& params);
+ void RemoveStorageObserver(StorageObserver* observer);
+ void RemoveStorageObserverForFilter(StorageObserver* observer,
+ const StorageObserver::Filter& filter);
+
+ // Determines the portion of the temp pool that can be
+ // utilized by a single host (ie. 5 for 20%).
+ static const int kPerHostTemporaryPortion;
+
+ static const int64 kPerHostPersistentQuotaLimit;
+
+ static const char kDatabaseName[];
+
+ static const int kThresholdOfErrorsToBeBlacklisted;
+
+ static const int kEvictionIntervalInMilliSeconds;
+
+ // These are kept non-const so that test code can change the value.
+ // TODO(kinuko): Make this a real const value and add a proper way to set
+ // the quota for syncable storage. (http://crbug.com/155488)
+ static int64 kMinimumPreserveForSystem;
+ static int64 kSyncableStorageDefaultHostQuota;
+
+ protected:
+ virtual ~QuotaManager();
+
+ private:
+ friend class base::DeleteHelper<QuotaManager>;
+ friend class base::RefCountedThreadSafe<QuotaManager, QuotaManagerDeleter>;
+ friend class content::QuotaManagerTest;
+ friend class content::StorageMonitorTest;
+ friend class content::MockQuotaManager;
+ friend class content::MockStorageClient;
+ friend class quota_internals::QuotaInternalsProxy;
+ friend class QuotaManagerProxy;
+ friend class QuotaTemporaryStorageEvictor;
+ friend struct QuotaManagerDeleter;
+
+ class GetUsageInfoTask;
+
+ class OriginDataDeleter;
+ class HostDataDeleter;
+
+ class GetModifiedSinceHelper;
+ class DumpQuotaTableHelper;
+ class DumpOriginInfoTableHelper;
+
+ typedef QuotaDatabase::QuotaTableEntry QuotaTableEntry;
+ typedef QuotaDatabase::OriginInfoTableEntry OriginInfoTableEntry;
+ typedef std::vector<QuotaTableEntry> QuotaTableEntries;
+ typedef std::vector<OriginInfoTableEntry> OriginInfoTableEntries;
+
+ // Function pointer type used to store the function which returns the
+ // available disk space for the disk containing the given FilePath.
+ typedef int64 (*GetAvailableDiskSpaceFn)(const base::FilePath&);
+
+ typedef base::Callback<void(const QuotaTableEntries&)>
+ DumpQuotaTableCallback;
+ typedef base::Callback<void(const OriginInfoTableEntries&)>
+ DumpOriginInfoTableCallback;
+
+ struct EvictionContext {
+ EvictionContext();
+ virtual ~EvictionContext();
+ GURL evicted_origin;
+ StorageType evicted_type;
+
+ EvictOriginDataCallback evict_origin_data_callback;
+ };
+
+ typedef QuotaEvictionHandler::UsageAndQuotaCallback
+ UsageAndQuotaDispatcherCallback;
+
+ // This initialization method is lazily called on the IO thread
+ // when the first quota manager API is called.
+ // Initialize must be called after all quota clients are added to the
+ // manager by RegisterStorage.
+ void LazyInitialize();
+
+ // Called by clients via proxy.
+ // Registers a quota client to the manager.
+ // The client must remain valid until OnQuotaManagerDestored is called.
+ void RegisterClient(QuotaClient* client);
+
+ UsageTracker* GetUsageTracker(StorageType type) const;
+
+ // Extract cached origins list from the usage tracker.
+ // (Might return empty list if no origin is tracked by the tracker.)
+ void GetCachedOrigins(StorageType type, std::set<GURL>* origins);
+
+ // These internal methods are separately defined mainly for testing.
+ void NotifyStorageAccessedInternal(
+ QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ base::Time accessed_time);
+ void NotifyStorageModifiedInternal(
+ QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ int64 delta,
+ base::Time modified_time);
+
+ void DumpQuotaTable(const DumpQuotaTableCallback& callback);
+ void DumpOriginInfoTable(const DumpOriginInfoTableCallback& callback);
+
+ // Methods for eviction logic.
+ void StartEviction();
+ void DeleteOriginFromDatabase(const GURL& origin, StorageType type);
+
+ void DidOriginDataEvicted(QuotaStatusCode status);
+
+ void ReportHistogram();
+ void DidGetTemporaryGlobalUsageForHistogram(int64 usage,
+ int64 unlimited_usage);
+ void DidGetPersistentGlobalUsageForHistogram(int64 usage,
+ int64 unlimited_usage);
+
+ // QuotaEvictionHandler.
+ virtual void GetLRUOrigin(
+ StorageType type,
+ const GetLRUOriginCallback& callback) OVERRIDE;
+ virtual void EvictOriginData(
+ const GURL& origin,
+ StorageType type,
+ const EvictOriginDataCallback& callback) OVERRIDE;
+ virtual void GetUsageAndQuotaForEviction(
+ const UsageAndQuotaCallback& callback) OVERRIDE;
+
+ void DidSetTemporaryGlobalOverrideQuota(const QuotaCallback& callback,
+ const int64* new_quota,
+ bool success);
+ void DidGetPersistentHostQuota(const std::string& host,
+ const int64* quota,
+ bool success);
+ void DidSetPersistentHostQuota(const std::string& host,
+ const QuotaCallback& callback,
+ const int64* new_quota,
+ bool success);
+ void DidInitialize(int64* temporary_quota_override,
+ int64* desired_available_space,
+ bool success);
+ void DidGetLRUOrigin(const GURL* origin,
+ bool success);
+ void DidGetInitialTemporaryGlobalQuota(QuotaStatusCode status,
+ int64 quota_unused);
+ void DidInitializeTemporaryOriginsInfo(bool success);
+ void DidGetAvailableSpace(int64 space);
+ void DidDatabaseWork(bool success);
+
+ void DeleteOnCorrectThread() const;
+
+ void PostTaskAndReplyWithResultForDBThread(
+ const tracked_objects::Location& from_here,
+ const base::Callback<bool(QuotaDatabase*)>& task,
+ const base::Callback<void(bool)>& reply);
+
+ const bool is_incognito_;
+ const base::FilePath profile_path_;
+
+ scoped_refptr<QuotaManagerProxy> proxy_;
+ bool db_disabled_;
+ bool eviction_disabled_;
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_;
+ scoped_refptr<base::SequencedTaskRunner> db_thread_;
+ mutable scoped_ptr<QuotaDatabase> database_;
+
+ GetLRUOriginCallback lru_origin_callback_;
+ std::set<GURL> access_notified_origins_;
+
+ QuotaClientList clients_;
+
+ scoped_ptr<UsageTracker> temporary_usage_tracker_;
+ scoped_ptr<UsageTracker> persistent_usage_tracker_;
+ scoped_ptr<UsageTracker> syncable_usage_tracker_;
+ // TODO(michaeln): Need a way to clear the cache, drop and
+ // reinstantiate the trackers when they're not handling requests.
+
+ scoped_ptr<QuotaTemporaryStorageEvictor> temporary_storage_evictor_;
+ EvictionContext eviction_context_;
+
+ ClosureQueue db_initialization_callbacks_;
+ AvailableSpaceCallbackQueue available_space_callbacks_;
+ GlobalQuotaCallbackQueue temporary_global_quota_callbacks_;
+ HostQuotaCallbackMap persistent_host_quota_callbacks_;
+
+ bool temporary_quota_initialized_;
+ int64 temporary_quota_override_;
+
+ int64 desired_available_space_;
+
+ // Map from origin to count.
+ std::map<GURL, int> origins_in_use_;
+ // Map from origin to error count.
+ std::map<GURL, int> origins_in_error_;
+
+ scoped_refptr<SpecialStoragePolicy> special_storage_policy_;
+
+ base::RepeatingTimer<QuotaManager> histogram_timer_;
+
+ // Pointer to the function used to get the available disk space. This is
+ // overwritten by QuotaManagerTest in order to attain a deterministic reported
+ // value. The default value points to base::SysInfo::AmountOfFreeDiskSpace.
+ GetAvailableDiskSpaceFn get_disk_space_fn_;
+
+ scoped_ptr<StorageMonitor> storage_monitor_;
+
+ base::WeakPtrFactory<QuotaManager> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaManager);
+};
+
+struct QuotaManagerDeleter {
+ static void Destruct(const QuotaManager* manager) {
+ manager->DeleteOnCorrectThread();
+ }
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_H_
diff --git a/storage/browser/quota/quota_manager_proxy.cc b/storage/browser/quota/quota_manager_proxy.cc
new file mode 100644
index 0000000..c044258
--- /dev/null
+++ b/storage/browser/quota/quota_manager_proxy.cc
@@ -0,0 +1,161 @@
+// Copyright 2014 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 "storage/browser/quota/quota_manager_proxy.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task_runner_util.h"
+
+namespace storage {
+
+namespace {
+
+void DidGetUsageAndQuota(
+ base::SequencedTaskRunner* original_task_runner,
+ const QuotaManagerProxy::GetUsageAndQuotaCallback& callback,
+ QuotaStatusCode status, int64 usage, int64 quota) {
+ if (!original_task_runner->RunsTasksOnCurrentThread()) {
+ original_task_runner->PostTask(
+ FROM_HERE,
+ base::Bind(&DidGetUsageAndQuota,
+ make_scoped_refptr(original_task_runner),
+ callback, status, usage, quota));
+ return;
+ }
+ callback.Run(status, usage, quota);
+}
+
+} // namespace
+
+void QuotaManagerProxy::RegisterClient(QuotaClient* client) {
+ if (!io_thread_->BelongsToCurrentThread() &&
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&QuotaManagerProxy::RegisterClient, this, client))) {
+ return;
+ }
+
+ if (manager_)
+ manager_->RegisterClient(client);
+ else
+ client->OnQuotaManagerDestroyed();
+}
+
+void QuotaManagerProxy::NotifyStorageAccessed(
+ QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type) {
+ if (!io_thread_->BelongsToCurrentThread()) {
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&QuotaManagerProxy::NotifyStorageAccessed, this, client_id,
+ origin, type));
+ return;
+ }
+
+ if (manager_)
+ manager_->NotifyStorageAccessed(client_id, origin, type);
+}
+
+void QuotaManagerProxy::NotifyStorageModified(
+ QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ int64 delta) {
+ if (!io_thread_->BelongsToCurrentThread()) {
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&QuotaManagerProxy::NotifyStorageModified, this, client_id,
+ origin, type, delta));
+ return;
+ }
+
+ if (manager_)
+ manager_->NotifyStorageModified(client_id, origin, type, delta);
+}
+
+void QuotaManagerProxy::NotifyOriginInUse(
+ const GURL& origin) {
+ if (!io_thread_->BelongsToCurrentThread()) {
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&QuotaManagerProxy::NotifyOriginInUse, this, origin));
+ return;
+ }
+
+ if (manager_)
+ manager_->NotifyOriginInUse(origin);
+}
+
+void QuotaManagerProxy::NotifyOriginNoLongerInUse(
+ const GURL& origin) {
+ if (!io_thread_->BelongsToCurrentThread()) {
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&QuotaManagerProxy::NotifyOriginNoLongerInUse, this,
+ origin));
+ return;
+ }
+ if (manager_)
+ manager_->NotifyOriginNoLongerInUse(origin);
+}
+
+void QuotaManagerProxy::SetUsageCacheEnabled(QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ bool enabled) {
+ if (!io_thread_->BelongsToCurrentThread()) {
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&QuotaManagerProxy::SetUsageCacheEnabled, this,
+ client_id, origin, type, enabled));
+ return;
+ }
+ if (manager_)
+ manager_->SetUsageCacheEnabled(client_id, origin, type, enabled);
+}
+
+void QuotaManagerProxy::GetUsageAndQuota(
+ base::SequencedTaskRunner* original_task_runner,
+ const GURL& origin,
+ StorageType type,
+ const GetUsageAndQuotaCallback& callback) {
+ if (!io_thread_->BelongsToCurrentThread()) {
+ io_thread_->PostTask(
+ FROM_HERE,
+ base::Bind(&QuotaManagerProxy::GetUsageAndQuota, this,
+ make_scoped_refptr(original_task_runner),
+ origin, type, callback));
+ return;
+ }
+ if (!manager_) {
+ DidGetUsageAndQuota(original_task_runner, callback, kQuotaErrorAbort, 0, 0);
+ return;
+ }
+ manager_->GetUsageAndQuota(
+ origin, type,
+ base::Bind(&DidGetUsageAndQuota,
+ make_scoped_refptr(original_task_runner), callback));
+}
+
+QuotaManager* QuotaManagerProxy::quota_manager() const {
+ DCHECK(!io_thread_.get() || io_thread_->BelongsToCurrentThread());
+ return manager_;
+}
+
+QuotaManagerProxy::QuotaManagerProxy(
+ QuotaManager* manager,
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_thread)
+ : manager_(manager), io_thread_(io_thread) {
+}
+
+QuotaManagerProxy::~QuotaManagerProxy() {
+}
+
+} // namespace storage
diff --git a/storage/browser/quota/quota_manager_proxy.h b/storage/browser/quota/quota_manager_proxy.h
new file mode 100644
index 0000000..bf9d9ca
--- /dev/null
+++ b/storage/browser/quota/quota_manager_proxy.h
@@ -0,0 +1,79 @@
+// Copyright 2014 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 STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_PROXY_H_
+#define STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_PROXY_H_
+
+#include "base/basictypes.h"
+#include "base/callback.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 "base/sequenced_task_runner_helpers.h"
+#include "storage/browser/quota/quota_callbacks.h"
+#include "storage/browser/quota/quota_client.h"
+#include "storage/browser/quota/quota_database.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/browser/quota/quota_task.h"
+#include "storage/browser/quota/special_storage_policy.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+class SingleThreadTaskRunner;
+}
+
+namespace storage {
+
+// The proxy may be called and finally released on any thread.
+class STORAGE_EXPORT QuotaManagerProxy
+ : public base::RefCountedThreadSafe<QuotaManagerProxy> {
+ public:
+ typedef QuotaManager::GetUsageAndQuotaCallback
+ GetUsageAndQuotaCallback;
+
+ virtual void RegisterClient(QuotaClient* client);
+ virtual void NotifyStorageAccessed(QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type);
+ virtual void NotifyStorageModified(QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ int64 delta);
+ virtual void NotifyOriginInUse(const GURL& origin);
+ virtual void NotifyOriginNoLongerInUse(const GURL& origin);
+
+ virtual void SetUsageCacheEnabled(QuotaClient::ID client_id,
+ const GURL& origin,
+ StorageType type,
+ bool enabled);
+ virtual void GetUsageAndQuota(
+ base::SequencedTaskRunner* original_task_runner,
+ const GURL& origin,
+ StorageType type,
+ const GetUsageAndQuotaCallback& callback);
+
+ // This method may only be called on the IO thread.
+ // It may return NULL if the manager has already been deleted.
+ QuotaManager* quota_manager() const;
+
+ protected:
+ friend class QuotaManager;
+ friend class base::RefCountedThreadSafe<QuotaManagerProxy>;
+
+ QuotaManagerProxy(
+ QuotaManager* manager,
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_thread);
+ virtual ~QuotaManagerProxy();
+
+ QuotaManager* manager_; // only accessed on the io thread
+ scoped_refptr<base::SingleThreadTaskRunner> io_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaManagerProxy);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_QUOTA_QUOTA_MANAGER_PROXY_H_
diff --git a/storage/browser/quota/quota_task.cc b/storage/browser/quota/quota_task.cc
new file mode 100644
index 0000000..b8d4ed1
--- /dev/null
+++ b/storage/browser/quota/quota_task.cc
@@ -0,0 +1,79 @@
+// 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 "storage/browser/quota/quota_task.h"
+
+#include <algorithm>
+#include <functional>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/single_thread_task_runner.h"
+
+using base::TaskRunner;
+
+namespace storage {
+
+// QuotaTask ---------------------------------------------------------------
+
+QuotaTask::~QuotaTask() {
+}
+
+void QuotaTask::Start() {
+ DCHECK(observer_);
+ observer()->RegisterTask(this);
+ Run();
+}
+
+QuotaTask::QuotaTask(QuotaTaskObserver* observer)
+ : observer_(observer),
+ original_task_runner_(base::MessageLoopProxy::current()),
+ delete_scheduled_(false) {
+}
+
+void QuotaTask::CallCompleted() {
+ DCHECK(original_task_runner_->BelongsToCurrentThread());
+ if (observer_) {
+ observer_->UnregisterTask(this);
+ Completed();
+ }
+}
+
+void QuotaTask::Abort() {
+ DCHECK(original_task_runner_->BelongsToCurrentThread());
+ observer_ = NULL;
+ Aborted();
+}
+
+void QuotaTask::DeleteSoon() {
+ DCHECK(original_task_runner_->BelongsToCurrentThread());
+ if (delete_scheduled_)
+ return;
+ delete_scheduled_ = true;
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
+}
+
+// QuotaTaskObserver -------------------------------------------------------
+
+QuotaTaskObserver::~QuotaTaskObserver() {
+ std::for_each(running_quota_tasks_.begin(),
+ running_quota_tasks_.end(),
+ std::mem_fun(&QuotaTask::Abort));
+}
+
+QuotaTaskObserver::QuotaTaskObserver() {
+}
+
+void QuotaTaskObserver::RegisterTask(QuotaTask* task) {
+ running_quota_tasks_.insert(task);
+}
+
+void QuotaTaskObserver::UnregisterTask(QuotaTask* task) {
+ DCHECK(running_quota_tasks_.find(task) != running_quota_tasks_.end());
+ running_quota_tasks_.erase(task);
+}
+
+} // namespace storage
diff --git a/storage/browser/quota/quota_task.h b/storage/browser/quota/quota_task.h
new file mode 100644
index 0000000..35e004e
--- /dev/null
+++ b/storage/browser/quota/quota_task.h
@@ -0,0 +1,79 @@
+// 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 STORAGE_BROWSER_QUOTA_QUOTA_TASK_H_
+#define STORAGE_BROWSER_QUOTA_QUOTA_TASK_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "storage/browser/storage_browser_export.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+class TaskRunner;
+}
+
+namespace storage {
+
+class QuotaTaskObserver;
+
+// A base class for quota tasks.
+// TODO(kinuko): Revise this using base::Callback.
+class QuotaTask {
+ public:
+ void Start();
+
+ protected:
+ explicit QuotaTask(QuotaTaskObserver* observer);
+ virtual ~QuotaTask();
+
+ // The task body.
+ virtual void Run() = 0;
+
+ // Called upon completion, on the original message loop.
+ virtual void Completed() = 0;
+
+ // Called when the task is aborted.
+ virtual void Aborted() {}
+
+ void CallCompleted();
+
+ // Call this to delete itself.
+ void DeleteSoon();
+
+ QuotaTaskObserver* observer() const { return observer_; }
+ base::SingleThreadTaskRunner* original_task_runner() const {
+ return original_task_runner_.get();
+ }
+
+ private:
+ friend class base::DeleteHelper<QuotaTask>;
+ friend class QuotaTaskObserver;
+
+ void Abort();
+ QuotaTaskObserver* observer_;
+ scoped_refptr<base::SingleThreadTaskRunner> original_task_runner_;
+ bool delete_scheduled_;
+};
+
+class STORAGE_EXPORT QuotaTaskObserver {
+ protected:
+ friend class QuotaTask;
+
+ QuotaTaskObserver();
+ virtual ~QuotaTaskObserver();
+
+ void RegisterTask(QuotaTask* task);
+ void UnregisterTask(QuotaTask* task);
+
+ typedef std::set<QuotaTask*> TaskSet;
+ TaskSet running_quota_tasks_;
+};
+}
+
+#endif // STORAGE_BROWSER_QUOTA_QUOTA_TASK_H_
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.cc b/storage/browser/quota/quota_temporary_storage_evictor.cc
new file mode 100644
index 0000000..cb4c504
--- /dev/null
+++ b/storage/browser/quota/quota_temporary_storage_evictor.cc
@@ -0,0 +1,261 @@
+// 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 "storage/browser/quota/quota_temporary_storage_evictor.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/metrics/histogram.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "url/gurl.h"
+
+#define UMA_HISTOGRAM_MBYTES(name, sample) \
+ UMA_HISTOGRAM_CUSTOM_COUNTS( \
+ (name), static_cast<int>((sample) / kMBytes), \
+ 1, 10 * 1024 * 1024 /* 10TB */, 100)
+
+#define UMA_HISTOGRAM_MINUTES(name, sample) \
+ UMA_HISTOGRAM_CUSTOM_TIMES( \
+ (name), (sample), \
+ base::TimeDelta::FromMinutes(1), \
+ base::TimeDelta::FromDays(1), 50)
+
+namespace {
+const int64 kMBytes = 1024 * 1024;
+const double kUsageRatioToStartEviction = 0.7;
+const int kThresholdOfErrorsToStopEviction = 5;
+const int kHistogramReportIntervalMinutes = 60;
+}
+
+namespace storage {
+
+const int QuotaTemporaryStorageEvictor::
+ kMinAvailableDiskSpaceToStartEvictionNotSpecified = -1;
+
+QuotaTemporaryStorageEvictor::EvictionRoundStatistics::EvictionRoundStatistics()
+ : in_round(false),
+ is_initialized(false),
+ usage_overage_at_round(-1),
+ diskspace_shortage_at_round(-1),
+ usage_on_beginning_of_round(-1),
+ usage_on_end_of_round(-1),
+ num_evicted_origins_in_round(0) {
+}
+
+QuotaTemporaryStorageEvictor::QuotaTemporaryStorageEvictor(
+ QuotaEvictionHandler* quota_eviction_handler,
+ int64 interval_ms)
+ : min_available_disk_space_to_start_eviction_(
+ kMinAvailableDiskSpaceToStartEvictionNotSpecified),
+ quota_eviction_handler_(quota_eviction_handler),
+ interval_ms_(interval_ms),
+ repeated_eviction_(true),
+ weak_factory_(this) {
+ DCHECK(quota_eviction_handler);
+}
+
+QuotaTemporaryStorageEvictor::~QuotaTemporaryStorageEvictor() {
+}
+
+void QuotaTemporaryStorageEvictor::GetStatistics(
+ std::map<std::string, int64>* statistics) {
+ DCHECK(statistics);
+
+ (*statistics)["errors-on-evicting-origin"] =
+ statistics_.num_errors_on_evicting_origin;
+ (*statistics)["errors-on-getting-usage-and-quota"] =
+ statistics_.num_errors_on_getting_usage_and_quota;
+ (*statistics)["evicted-origins"] =
+ statistics_.num_evicted_origins;
+ (*statistics)["eviction-rounds"] =
+ statistics_.num_eviction_rounds;
+ (*statistics)["skipped-eviction-rounds"] =
+ statistics_.num_skipped_eviction_rounds;
+}
+
+void QuotaTemporaryStorageEvictor::ReportPerRoundHistogram() {
+ DCHECK(round_statistics_.in_round);
+ DCHECK(round_statistics_.is_initialized);
+
+ base::Time now = base::Time::Now();
+ UMA_HISTOGRAM_TIMES("Quota.TimeSpentToAEvictionRound",
+ now - round_statistics_.start_time);
+ if (!time_of_end_of_last_round_.is_null())
+ UMA_HISTOGRAM_MINUTES("Quota.TimeDeltaOfEvictionRounds",
+ now - time_of_end_of_last_round_);
+ UMA_HISTOGRAM_MBYTES("Quota.UsageOverageOfTemporaryGlobalStorage",
+ round_statistics_.usage_overage_at_round);
+ UMA_HISTOGRAM_MBYTES("Quota.DiskspaceShortage",
+ round_statistics_.diskspace_shortage_at_round);
+ UMA_HISTOGRAM_MBYTES("Quota.EvictedBytesPerRound",
+ round_statistics_.usage_on_beginning_of_round -
+ round_statistics_.usage_on_end_of_round);
+ UMA_HISTOGRAM_COUNTS("Quota.NumberOfEvictedOriginsPerRound",
+ round_statistics_.num_evicted_origins_in_round);
+}
+
+void QuotaTemporaryStorageEvictor::ReportPerHourHistogram() {
+ Statistics stats_in_hour(statistics_);
+ stats_in_hour.subtract_assign(previous_statistics_);
+ previous_statistics_ = statistics_;
+
+ UMA_HISTOGRAM_COUNTS("Quota.ErrorsOnEvictingOriginPerHour",
+ stats_in_hour.num_errors_on_evicting_origin);
+ UMA_HISTOGRAM_COUNTS("Quota.ErrorsOnGettingUsageAndQuotaPerHour",
+ stats_in_hour.num_errors_on_getting_usage_and_quota);
+ UMA_HISTOGRAM_COUNTS("Quota.EvictedOriginsPerHour",
+ stats_in_hour.num_evicted_origins);
+ UMA_HISTOGRAM_COUNTS("Quota.EvictionRoundsPerHour",
+ stats_in_hour.num_eviction_rounds);
+ UMA_HISTOGRAM_COUNTS("Quota.SkippedEvictionRoundsPerHour",
+ stats_in_hour.num_skipped_eviction_rounds);
+}
+
+void QuotaTemporaryStorageEvictor::OnEvictionRoundStarted() {
+ if (round_statistics_.in_round)
+ return;
+ round_statistics_.in_round = true;
+ round_statistics_.start_time = base::Time::Now();
+ ++statistics_.num_eviction_rounds;
+}
+
+void QuotaTemporaryStorageEvictor::OnEvictionRoundFinished() {
+ // Check if skipped round
+ if (round_statistics_.num_evicted_origins_in_round) {
+ ReportPerRoundHistogram();
+ time_of_end_of_last_nonskipped_round_ = base::Time::Now();
+ } else {
+ ++statistics_.num_skipped_eviction_rounds;
+ }
+ // Reset stats for next round.
+ round_statistics_ = EvictionRoundStatistics();
+}
+
+void QuotaTemporaryStorageEvictor::Start() {
+ DCHECK(CalledOnValidThread());
+ StartEvictionTimerWithDelay(0);
+
+ if (histogram_timer_.IsRunning())
+ return;
+
+ histogram_timer_.Start(
+ FROM_HERE, base::TimeDelta::FromMinutes(kHistogramReportIntervalMinutes),
+ this, &QuotaTemporaryStorageEvictor::ReportPerHourHistogram);
+}
+
+void QuotaTemporaryStorageEvictor::StartEvictionTimerWithDelay(int delay_ms) {
+ if (eviction_timer_.IsRunning())
+ return;
+ eviction_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(delay_ms),
+ this, &QuotaTemporaryStorageEvictor::ConsiderEviction);
+}
+
+void QuotaTemporaryStorageEvictor::ConsiderEviction() {
+ OnEvictionRoundStarted();
+
+ // Get usage and disk space, then continue.
+ quota_eviction_handler_->GetUsageAndQuotaForEviction(
+ base::Bind(&QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction(
+ QuotaStatusCode status,
+ const UsageAndQuota& qau) {
+ DCHECK(CalledOnValidThread());
+
+ int64 usage = qau.global_limited_usage;
+ DCHECK_GE(usage, 0);
+
+ if (status != kQuotaStatusOk)
+ ++statistics_.num_errors_on_getting_usage_and_quota;
+
+ int64 usage_overage = std::max(
+ static_cast<int64>(0),
+ usage - static_cast<int64>(qau.quota * kUsageRatioToStartEviction));
+
+ // min_available_disk_space_to_start_eviction_ might be < 0 if no value
+ // is explicitly configured yet.
+ int64 diskspace_shortage = std::max(
+ static_cast<int64>(0),
+ min_available_disk_space_to_start_eviction_ - qau.available_disk_space);
+
+ if (!round_statistics_.is_initialized) {
+ round_statistics_.usage_overage_at_round = usage_overage;
+ round_statistics_.diskspace_shortage_at_round = diskspace_shortage;
+ round_statistics_.usage_on_beginning_of_round = usage;
+ round_statistics_.is_initialized = true;
+ }
+ round_statistics_.usage_on_end_of_round = usage;
+
+ int64 amount_to_evict = std::max(usage_overage, diskspace_shortage);
+ if (status == kQuotaStatusOk && amount_to_evict > 0) {
+ // Space is getting tight. Get the least recently used origin and continue.
+ // TODO(michaeln): if the reason for eviction is low physical disk space,
+ // make 'unlimited' origins subject to eviction too.
+ quota_eviction_handler_->GetLRUOrigin(
+ kStorageTypeTemporary,
+ base::Bind(&QuotaTemporaryStorageEvictor::OnGotLRUOrigin,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ if (repeated_eviction_) {
+ // No action required, sleep for a while and check again later.
+ if (statistics_.num_errors_on_getting_usage_and_quota <
+ kThresholdOfErrorsToStopEviction) {
+ StartEvictionTimerWithDelay(interval_ms_);
+ } else {
+ // TODO(dmikurube): Try restarting eviction after a while.
+ LOG(WARNING) << "Stopped eviction of temporary storage due to errors "
+ "in GetUsageAndQuotaForEviction.";
+ }
+ }
+ OnEvictionRoundFinished();
+ }
+
+ // TODO(dmikurube): Add error handling for the case status != kQuotaStatusOk.
+}
+
+void QuotaTemporaryStorageEvictor::OnGotLRUOrigin(const GURL& origin) {
+ DCHECK(CalledOnValidThread());
+
+ if (origin.is_empty()) {
+ if (repeated_eviction_)
+ StartEvictionTimerWithDelay(interval_ms_);
+ OnEvictionRoundFinished();
+ return;
+ }
+
+ quota_eviction_handler_->EvictOriginData(origin, kStorageTypeTemporary,
+ base::Bind(
+ &QuotaTemporaryStorageEvictor::OnEvictionComplete,
+ weak_factory_.GetWeakPtr()));
+}
+
+void QuotaTemporaryStorageEvictor::OnEvictionComplete(
+ QuotaStatusCode status) {
+ DCHECK(CalledOnValidThread());
+
+ // Just calling ConsiderEviction() or StartEvictionTimerWithDelay() here is
+ // ok. No need to deal with the case that all of the Delete operations fail
+ // for a certain origin. It doesn't result in trying to evict the same
+ // origin permanently. The evictor skips origins which had deletion errors
+ // a few times.
+
+ if (status == kQuotaStatusOk) {
+ ++statistics_.num_evicted_origins;
+ ++round_statistics_.num_evicted_origins_in_round;
+ // We many need to get rid of more space so reconsider immediately.
+ ConsiderEviction();
+ } else {
+ ++statistics_.num_errors_on_evicting_origin;
+ if (repeated_eviction_) {
+ // Sleep for a while and retry again until we see too many errors.
+ StartEvictionTimerWithDelay(interval_ms_);
+ }
+ OnEvictionRoundFinished();
+ }
+}
+
+} // namespace storage
diff --git a/storage/browser/quota/quota_temporary_storage_evictor.h b/storage/browser/quota/quota_temporary_storage_evictor.h
new file mode 100644
index 0000000..706355d
--- /dev/null
+++ b/storage/browser/quota/quota_temporary_storage_evictor.h
@@ -0,0 +1,134 @@
+// 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 STORAGE_BROWSER_QUOTA_QUOTA_TEMPORARY_STORAGE_EVICTOR_H_
+#define STORAGE_BROWSER_QUOTA_QUOTA_TEMPORARY_STORAGE_EVICTOR_H_
+
+#include <map>
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/timer/timer.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/quota/quota_types.h"
+
+class GURL;
+
+namespace content {
+class QuotaTemporaryStorageEvictorTest;
+}
+
+namespace storage {
+
+class QuotaEvictionHandler;
+struct UsageAndQuota;
+
+class STORAGE_EXPORT_PRIVATE QuotaTemporaryStorageEvictor
+ : public base::NonThreadSafe {
+ public:
+ struct Statistics {
+ Statistics()
+ : num_errors_on_evicting_origin(0),
+ num_errors_on_getting_usage_and_quota(0),
+ num_evicted_origins(0),
+ num_eviction_rounds(0),
+ num_skipped_eviction_rounds(0) {}
+ int64 num_errors_on_evicting_origin;
+ int64 num_errors_on_getting_usage_and_quota;
+ int64 num_evicted_origins;
+ int64 num_eviction_rounds;
+ int64 num_skipped_eviction_rounds;
+
+ void subtract_assign(const Statistics& rhs) {
+ num_errors_on_evicting_origin -= rhs.num_errors_on_evicting_origin;
+ num_errors_on_getting_usage_and_quota -=
+ rhs.num_errors_on_getting_usage_and_quota;
+ num_evicted_origins -= rhs.num_evicted_origins;
+ num_eviction_rounds -= rhs.num_eviction_rounds;
+ num_skipped_eviction_rounds -= rhs.num_skipped_eviction_rounds;
+ }
+ };
+
+ struct EvictionRoundStatistics {
+ EvictionRoundStatistics();
+
+ bool in_round;
+ bool is_initialized;
+
+ base::Time start_time;
+ int64 usage_overage_at_round;
+ int64 diskspace_shortage_at_round;
+
+ int64 usage_on_beginning_of_round;
+ int64 usage_on_end_of_round;
+ int64 num_evicted_origins_in_round;
+ };
+
+ QuotaTemporaryStorageEvictor(
+ QuotaEvictionHandler* quota_eviction_handler,
+ int64 interval_ms);
+ virtual ~QuotaTemporaryStorageEvictor();
+
+ void GetStatistics(std::map<std::string, int64>* statistics);
+ void ReportPerRoundHistogram();
+ void ReportPerHourHistogram();
+ void Start();
+
+ int64 min_available_disk_space_to_start_eviction() {
+ return min_available_disk_space_to_start_eviction_;
+ }
+ void reset_min_available_disk_space_to_start_eviction() {
+ min_available_disk_space_to_start_eviction_ =
+ kMinAvailableDiskSpaceToStartEvictionNotSpecified;
+ }
+ void set_min_available_disk_space_to_start_eviction(int64 value) {
+ min_available_disk_space_to_start_eviction_ = value;
+ }
+
+ private:
+ friend class content::QuotaTemporaryStorageEvictorTest;
+
+ void StartEvictionTimerWithDelay(int delay_ms);
+ void ConsiderEviction();
+ void OnGotUsageAndQuotaForEviction(
+ QuotaStatusCode status,
+ const UsageAndQuota& quota_and_usage);
+ void OnGotLRUOrigin(const GURL& origin);
+ void OnEvictionComplete(QuotaStatusCode status);
+
+ void OnEvictionRoundStarted();
+ void OnEvictionRoundFinished();
+
+ // This is only used for tests.
+ void set_repeated_eviction(bool repeated_eviction) {
+ repeated_eviction_ = repeated_eviction;
+ }
+
+ static const int kMinAvailableDiskSpaceToStartEvictionNotSpecified;
+
+ int64 min_available_disk_space_to_start_eviction_;
+
+ // Not owned; quota_eviction_handler owns us.
+ QuotaEvictionHandler* quota_eviction_handler_;
+
+ Statistics statistics_;
+ Statistics previous_statistics_;
+ EvictionRoundStatistics round_statistics_;
+ base::Time time_of_end_of_last_nonskipped_round_;
+ base::Time time_of_end_of_last_round_;
+
+ int64 interval_ms_;
+ bool repeated_eviction_;
+
+ base::OneShotTimer<QuotaTemporaryStorageEvictor> eviction_timer_;
+ base::RepeatingTimer<QuotaTemporaryStorageEvictor> histogram_timer_;
+ base::WeakPtrFactory<QuotaTemporaryStorageEvictor> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuotaTemporaryStorageEvictor);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_QUOTA_QUOTA_TEMPORARY_STORAGE_EVICTOR_H_
diff --git a/storage/browser/quota/special_storage_policy.cc b/storage/browser/quota/special_storage_policy.cc
new file mode 100644
index 0000000..6e77450
--- /dev/null
+++ b/storage/browser/quota/special_storage_policy.cc
@@ -0,0 +1,38 @@
+// 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 "storage/browser/quota/special_storage_policy.h"
+
+namespace storage {
+
+SpecialStoragePolicy::Observer::~Observer() {}
+
+SpecialStoragePolicy::SpecialStoragePolicy() {}
+
+SpecialStoragePolicy::~SpecialStoragePolicy() {}
+
+void SpecialStoragePolicy::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void SpecialStoragePolicy::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+void SpecialStoragePolicy::NotifyGranted(const GURL& origin, int change_flags) {
+ scoped_refptr<SpecialStoragePolicy> protect(this);
+ FOR_EACH_OBSERVER(Observer, observers_, OnGranted(origin, change_flags));
+}
+
+void SpecialStoragePolicy::NotifyRevoked(const GURL& origin, int change_flags) {
+ scoped_refptr<SpecialStoragePolicy> protect(this);
+ FOR_EACH_OBSERVER(Observer, observers_, OnRevoked(origin, change_flags));
+}
+
+void SpecialStoragePolicy::NotifyCleared() {
+ scoped_refptr<SpecialStoragePolicy> protect(this);
+ FOR_EACH_OBSERVER(Observer, observers_, OnCleared());
+}
+
+} // namespace storage
diff --git a/storage/browser/quota/special_storage_policy.h b/storage/browser/quota/special_storage_policy.h
new file mode 100644
index 0000000..e6c37b5
--- /dev/null
+++ b/storage/browser/quota/special_storage_policy.h
@@ -0,0 +1,86 @@
+// 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 STORAGE_BROWSER_QUOTA_SPECIAL_STORAGE_POLICY_H_
+#define STORAGE_BROWSER_QUOTA_SPECIAL_STORAGE_POLICY_H_
+
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "storage/browser/storage_browser_export.h"
+
+class GURL;
+
+namespace storage {
+
+// Special rights are granted to 'extensions' and 'applications'. The
+// storage subsystems query this interface to determine which origins
+// have these rights. Chrome provides an impl that is cognizant of what
+// is currently installed in the extensions system.
+// The IsSomething() methods must be thread-safe, however Observers should
+// only be notified, added, and removed on the IO thead.
+class STORAGE_EXPORT SpecialStoragePolicy
+ : public base::RefCountedThreadSafe<SpecialStoragePolicy> {
+ public:
+ typedef int StoragePolicy;
+ enum ChangeFlags {
+ STORAGE_PROTECTED = 1 << 0,
+ STORAGE_UNLIMITED = 1 << 1,
+ };
+
+ class STORAGE_EXPORT Observer {
+ public:
+ virtual void OnGranted(const GURL& origin, int change_flags) = 0;
+ virtual void OnRevoked(const GURL& origin, int change_flags) = 0;
+ virtual void OnCleared() = 0;
+
+ protected:
+ virtual ~Observer();
+ };
+
+ SpecialStoragePolicy();
+
+ // Protected storage is not subject to removal by the browsing data remover.
+ virtual bool IsStorageProtected(const GURL& origin) = 0;
+
+ // Unlimited storage is not subject to 'quotas'.
+ virtual bool IsStorageUnlimited(const GURL& origin) = 0;
+
+ // Some origins (e.g. installed apps) have access to the size of the remaining
+ // disk capacity.
+ virtual bool CanQueryDiskSize(const GURL& origin) = 0;
+
+ // Checks if extension identified with |extension_id| is registered as
+ // file handler.
+ virtual bool IsFileHandler(const std::string& extension_id) = 0;
+
+ // Checks if the origin contains per-site isolated storage.
+ virtual bool HasIsolatedStorage(const GURL& origin) = 0;
+
+ // Some origins are only allowed to store session-only data which is deleted
+ // when the session ends.
+ virtual bool IsStorageSessionOnly(const GURL& origin) = 0;
+
+ // Returns true if some origins are only allowed session-only storage.
+ virtual bool HasSessionOnlyOrigins() = 0;
+
+ // Adds/removes an observer, the policy does not take
+ // ownership of the observer. Should only be called on the IO thread.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ protected:
+ friend class base::RefCountedThreadSafe<SpecialStoragePolicy>;
+ virtual ~SpecialStoragePolicy();
+ void NotifyGranted(const GURL& origin, int change_flags);
+ void NotifyRevoked(const GURL& origin, int change_flags);
+ void NotifyCleared();
+
+ ObserverList<Observer> observers_;
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_QUOTA_SPECIAL_STORAGE_POLICY_H_
diff --git a/storage/browser/quota/storage_monitor.cc b/storage/browser/quota/storage_monitor.cc
new file mode 100644
index 0000000..f312bd3
--- /dev/null
+++ b/storage/browser/quota/storage_monitor.cc
@@ -0,0 +1,379 @@
+// Copyright 2014 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 "storage/browser/quota/storage_monitor.h"
+
+#include <algorithm>
+
+#include "base/stl_util.h"
+#include "net/base/net_util.h"
+#include "storage/browser/quota/quota_manager.h"
+#include "storage/common/quota/quota_status_code.h"
+
+namespace storage {
+
+// StorageObserverList:
+
+StorageObserverList::ObserverState::ObserverState()
+ : requires_update(false) {
+}
+
+StorageObserverList::StorageObserverList() {}
+
+StorageObserverList::~StorageObserverList() {}
+
+void StorageObserverList::AddObserver(
+ StorageObserver* observer, const StorageObserver::MonitorParams& params) {
+ ObserverState& observer_state = observers_[observer];
+ observer_state.origin = params.filter.origin;
+ observer_state.rate = params.rate;
+}
+
+void StorageObserverList::RemoveObserver(StorageObserver* observer) {
+ observers_.erase(observer);
+}
+
+int StorageObserverList::ObserverCount() const {
+ return observers_.size();
+}
+
+void StorageObserverList::OnStorageChange(const StorageObserver::Event& event) {
+ for (StorageObserverStateMap::iterator it = observers_.begin();
+ it != observers_.end(); ++it) {
+ it->second.requires_update = true;
+ }
+
+ MaybeDispatchEvent(event);
+}
+
+void StorageObserverList::MaybeDispatchEvent(
+ const StorageObserver::Event& event) {
+ notification_timer_.Stop();
+ base::TimeDelta min_delay = base::TimeDelta::Max();
+ bool all_observers_notified = true;
+
+ for (StorageObserverStateMap::iterator it = observers_.begin();
+ it != observers_.end(); ++it) {
+ if (!it->second.requires_update)
+ continue;
+
+ base::TimeTicks current_time = base::TimeTicks::Now();
+ base::TimeDelta delta = current_time - it->second.last_notification_time;
+ if (it->second.last_notification_time.is_null() ||
+ delta >= it->second.rate) {
+ it->second.requires_update = false;
+ it->second.last_notification_time = current_time;
+
+ if (it->second.origin == event.filter.origin) {
+ it->first->OnStorageEvent(event);
+ } else {
+ // When the quota and usage of an origin is requested, QuotaManager
+ // returns the quota and usage of the host. Multiple origins can map to
+ // to the same host, so ensure the |origin| field in the dispatched
+ // event matches the |origin| specified by the observer when it was
+ // registered.
+ StorageObserver::Event dispatch_event(event);
+ dispatch_event.filter.origin = it->second.origin;
+ it->first->OnStorageEvent(dispatch_event);
+ }
+ } else {
+ all_observers_notified = false;
+ base::TimeDelta delay = it->second.rate - delta;
+ if (delay < min_delay)
+ min_delay = delay;
+ }
+ }
+
+ // We need to respect the notification rate specified by observers. So if it
+ // is too soon to dispatch an event to an observer, save the event and
+ // dispatch it after a delay. If we simply drop the event, another one may
+ // not arrive anytime soon and the observer will miss the most recent event.
+ if (!all_observers_notified) {
+ pending_event_ = event;
+ notification_timer_.Start(
+ FROM_HERE,
+ min_delay,
+ this,
+ &StorageObserverList::DispatchPendingEvent);
+ }
+}
+
+void StorageObserverList::ScheduleUpdateForObserver(StorageObserver* observer) {
+ DCHECK(ContainsKey(observers_, observer));
+ observers_[observer].requires_update = true;
+}
+
+void StorageObserverList::DispatchPendingEvent() {
+ MaybeDispatchEvent(pending_event_);
+}
+
+
+// HostStorageObservers:
+
+HostStorageObservers::HostStorageObservers(QuotaManager* quota_manager)
+ : quota_manager_(quota_manager),
+ initialized_(false),
+ initializing_(false),
+ event_occurred_before_init_(false),
+ usage_deltas_during_init_(0),
+ cached_usage_(0),
+ cached_quota_(0),
+ weak_factory_(this) {
+}
+
+HostStorageObservers::~HostStorageObservers() {}
+
+void HostStorageObservers::AddObserver(
+ StorageObserver* observer,
+ const StorageObserver::MonitorParams& params) {
+ observers_.AddObserver(observer, params);
+
+ if (!params.dispatch_initial_state)
+ return;
+
+ if (initialized_) {
+ StorageObserver::Event event(params.filter,
+ std::max<int64>(cached_usage_, 0),
+ std::max<int64>(cached_quota_, 0));
+ observer->OnStorageEvent(event);
+ return;
+ }
+
+ // Ensure the observer receives the initial storage state once initialization
+ // is complete.
+ observers_.ScheduleUpdateForObserver(observer);
+ StartInitialization(params.filter);
+}
+
+void HostStorageObservers::RemoveObserver(StorageObserver* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+bool HostStorageObservers::ContainsObservers() const {
+ return observers_.ObserverCount() > 0;
+}
+
+void HostStorageObservers::NotifyUsageChange(
+ const StorageObserver::Filter& filter, int64 delta) {
+ if (initialized_) {
+ cached_usage_ += delta;
+ DispatchEvent(filter, true);
+ return;
+ }
+
+ // If a storage change occurs before initialization, ensure all observers will
+ // receive an event once initialization is complete.
+ event_occurred_before_init_ = true;
+
+ // During QuotaManager::GetUsageAndQuotaForWebApps(), cached data is read
+ // synchronously, but other data may be retrieved asynchronously. A usage
+ // change may occur between the function call and callback. These deltas need
+ // to be added to the usage received by GotHostUsageAndQuota() to ensure
+ // |cached_usage_| is correctly initialized.
+ if (initializing_) {
+ usage_deltas_during_init_ += delta;
+ return;
+ }
+
+ StartInitialization(filter);
+}
+
+void HostStorageObservers::StartInitialization(
+ const StorageObserver::Filter& filter) {
+ if (initialized_ || initializing_)
+ return;
+
+ initializing_ = true;
+ quota_manager_->GetUsageAndQuotaForWebApps(
+ filter.origin,
+ filter.storage_type,
+ base::Bind(&HostStorageObservers::GotHostUsageAndQuota,
+ weak_factory_.GetWeakPtr(),
+ filter));
+}
+
+void HostStorageObservers::GotHostUsageAndQuota(
+ const StorageObserver::Filter& filter,
+ QuotaStatusCode status,
+ int64 usage,
+ int64 quota) {
+ initializing_ = false;
+ if (status != kQuotaStatusOk)
+ return;
+
+ initialized_ = true;
+ cached_quota_ = quota;
+ cached_usage_ = usage + usage_deltas_during_init_;
+ DispatchEvent(filter, event_occurred_before_init_);
+}
+
+void HostStorageObservers::DispatchEvent(
+ const StorageObserver::Filter& filter, bool is_update) {
+ StorageObserver::Event event(filter,
+ std::max<int64>(cached_usage_, 0),
+ std::max<int64>(cached_quota_, 0));
+ if (is_update)
+ observers_.OnStorageChange(event);
+ else
+ observers_.MaybeDispatchEvent(event);
+}
+
+
+// StorageTypeObservers:
+
+StorageTypeObservers::StorageTypeObservers(QuotaManager* quota_manager)
+ : quota_manager_(quota_manager) {
+}
+
+StorageTypeObservers::~StorageTypeObservers() {
+ STLDeleteValues(&host_observers_map_);
+}
+
+void StorageTypeObservers::AddObserver(
+ StorageObserver* observer, const StorageObserver::MonitorParams& params) {
+ std::string host = net::GetHostOrSpecFromURL(params.filter.origin);
+ if (host.empty())
+ return;
+
+ HostStorageObservers* host_observers = NULL;
+ HostObserversMap::iterator it = host_observers_map_.find(host);
+ if (it == host_observers_map_.end()) {
+ host_observers = new HostStorageObservers(quota_manager_);
+ host_observers_map_[host] = host_observers;
+ } else {
+ host_observers = it->second;
+ }
+
+ host_observers->AddObserver(observer, params);
+}
+
+void StorageTypeObservers::RemoveObserver(StorageObserver* observer) {
+ for (HostObserversMap::iterator it = host_observers_map_.begin();
+ it != host_observers_map_.end(); ) {
+ it->second->RemoveObserver(observer);
+ if (!it->second->ContainsObservers()) {
+ delete it->second;
+ host_observers_map_.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+}
+
+void StorageTypeObservers::RemoveObserverForFilter(
+ StorageObserver* observer, const StorageObserver::Filter& filter) {
+ std::string host = net::GetHostOrSpecFromURL(filter.origin);
+ HostObserversMap::iterator it = host_observers_map_.find(host);
+ if (it == host_observers_map_.end())
+ return;
+
+ it->second->RemoveObserver(observer);
+ if (!it->second->ContainsObservers()) {
+ delete it->second;
+ host_observers_map_.erase(it);
+ }
+}
+
+const HostStorageObservers* StorageTypeObservers::GetHostObservers(
+ const std::string& host) const {
+ HostObserversMap::const_iterator it = host_observers_map_.find(host);
+ if (it != host_observers_map_.end())
+ return it->second;
+
+ return NULL;
+}
+
+void StorageTypeObservers::NotifyUsageChange(
+ const StorageObserver::Filter& filter, int64 delta) {
+ std::string host = net::GetHostOrSpecFromURL(filter.origin);
+ HostObserversMap::iterator it = host_observers_map_.find(host);
+ if (it == host_observers_map_.end())
+ return;
+
+ it->second->NotifyUsageChange(filter, delta);
+}
+
+
+// StorageMonitor:
+
+StorageMonitor::StorageMonitor(QuotaManager* quota_manager)
+ : quota_manager_(quota_manager) {
+}
+
+StorageMonitor::~StorageMonitor() {
+ STLDeleteValues(&storage_type_observers_map_);
+}
+
+void StorageMonitor::AddObserver(
+ StorageObserver* observer, const StorageObserver::MonitorParams& params) {
+ DCHECK(observer);
+
+ // Check preconditions.
+ if (params.filter.storage_type == kStorageTypeUnknown ||
+ params.filter.storage_type == kStorageTypeQuotaNotManaged ||
+ params.filter.origin.is_empty()) {
+ NOTREACHED();
+ return;
+ }
+
+ StorageTypeObservers* type_observers = NULL;
+ StorageTypeObserversMap::iterator it =
+ storage_type_observers_map_.find(params.filter.storage_type);
+ if (it == storage_type_observers_map_.end()) {
+ type_observers = new StorageTypeObservers(quota_manager_);
+ storage_type_observers_map_[params.filter.storage_type] = type_observers;
+ } else {
+ type_observers = it->second;
+ }
+
+ type_observers->AddObserver(observer, params);
+}
+
+void StorageMonitor::RemoveObserver(StorageObserver* observer) {
+ for (StorageTypeObserversMap::iterator it =
+ storage_type_observers_map_.begin();
+ it != storage_type_observers_map_.end(); ++it) {
+ it->second->RemoveObserver(observer);
+ }
+}
+
+void StorageMonitor::RemoveObserverForFilter(
+ StorageObserver* observer, const StorageObserver::Filter& filter) {
+ StorageTypeObserversMap::iterator it =
+ storage_type_observers_map_.find(filter.storage_type);
+ if (it == storage_type_observers_map_.end())
+ return;
+
+ it->second->RemoveObserverForFilter(observer, filter);
+}
+
+const StorageTypeObservers* StorageMonitor::GetStorageTypeObservers(
+ StorageType storage_type) const {
+ StorageTypeObserversMap::const_iterator it =
+ storage_type_observers_map_.find(storage_type);
+ if (it != storage_type_observers_map_.end())
+ return it->second;
+
+ return NULL;
+}
+
+void StorageMonitor::NotifyUsageChange(
+ const StorageObserver::Filter& filter, int64 delta) {
+ // Check preconditions.
+ if (filter.storage_type == kStorageTypeUnknown ||
+ filter.storage_type == kStorageTypeQuotaNotManaged ||
+ filter.origin.is_empty()) {
+ NOTREACHED();
+ return;
+ }
+
+ StorageTypeObserversMap::iterator it =
+ storage_type_observers_map_.find(filter.storage_type);
+ if (it == storage_type_observers_map_.end())
+ return;
+
+ it->second->NotifyUsageChange(filter, delta);
+}
+
+} // namespace storage
diff --git a/storage/browser/quota/storage_monitor.h b/storage/browser/quota/storage_monitor.h
new file mode 100644
index 0000000..650c7b1
--- /dev/null
+++ b/storage/browser/quota/storage_monitor.h
@@ -0,0 +1,180 @@
+// Copyright 2014 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 STORAGE_BROWSER_QUOTA_STORAGE_MONITOR_H_
+#define STORAGE_BROWSER_QUOTA_STORAGE_MONITOR_H_
+
+#include <map>
+
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "storage/browser/quota/storage_observer.h"
+
+namespace content {
+class StorageMonitorTestBase;
+}
+
+namespace storage {
+
+class QuotaManager;
+
+// This class dispatches storage events to observers of a common
+// StorageObserver::Filter.
+class STORAGE_EXPORT_PRIVATE StorageObserverList {
+ public:
+ StorageObserverList();
+ virtual ~StorageObserverList();
+
+ // Adds/removes an observer.
+ void AddObserver(StorageObserver* observer,
+ const StorageObserver::MonitorParams& params);
+ void RemoveObserver(StorageObserver* observer);
+
+ // Returns the number of observers.
+ int ObserverCount() const;
+
+ // Forwards a storage change to observers. The event may be dispatched
+ // immediately to an observer or after a delay, depending on the desired event
+ // rate of the observer.
+ void OnStorageChange(const StorageObserver::Event& event);
+
+ // Dispatch an event to observers that require it.
+ void MaybeDispatchEvent(const StorageObserver::Event& event);
+
+ // Ensure the specified observer receives the next dispatched event.
+ void ScheduleUpdateForObserver(StorageObserver* observer);
+
+ private:
+ struct STORAGE_EXPORT_PRIVATE ObserverState {
+ GURL origin;
+ base::TimeTicks last_notification_time;
+ base::TimeDelta rate;
+ bool requires_update;
+
+ ObserverState();
+ };
+ typedef std::map<StorageObserver*, ObserverState> StorageObserverStateMap;
+
+ void DispatchPendingEvent();
+
+ StorageObserverStateMap observers_;
+ base::OneShotTimer<StorageObserverList> notification_timer_;
+ StorageObserver::Event pending_event_;
+
+ friend class content::StorageMonitorTestBase;
+
+ DISALLOW_COPY_AND_ASSIGN(StorageObserverList);
+};
+
+
+// Manages the storage observers of a common host. Caches the usage and quota of
+// the host to avoid accumulating for every change.
+class STORAGE_EXPORT_PRIVATE HostStorageObservers {
+ public:
+ explicit HostStorageObservers(QuotaManager* quota_manager);
+ virtual ~HostStorageObservers();
+
+ bool is_initialized() const { return initialized_; }
+
+ // Adds/removes an observer.
+ void AddObserver(
+ StorageObserver* observer,
+ const StorageObserver::MonitorParams& params);
+ void RemoveObserver(StorageObserver* observer);
+ bool ContainsObservers() const;
+
+ // Handles a usage change.
+ void NotifyUsageChange(const StorageObserver::Filter& filter, int64 delta);
+
+ private:
+ void StartInitialization(const StorageObserver::Filter& filter);
+ void GotHostUsageAndQuota(const StorageObserver::Filter& filter,
+ QuotaStatusCode status,
+ int64 usage,
+ int64 quota);
+ void DispatchEvent(const StorageObserver::Filter& filter, bool is_update);
+
+ QuotaManager* quota_manager_;
+ StorageObserverList observers_;
+
+ // Flags used during initialization of the cached properties.
+ bool initialized_;
+ bool initializing_;
+ bool event_occurred_before_init_;
+ int64 usage_deltas_during_init_;
+
+ // Cached accumulated usage and quota for the host.
+ int64 cached_usage_;
+ int64 cached_quota_;
+
+ base::WeakPtrFactory<HostStorageObservers> weak_factory_;
+
+ friend class content::StorageMonitorTestBase;
+
+ DISALLOW_COPY_AND_ASSIGN(HostStorageObservers);
+};
+
+
+// Manages the observers of a common storage type.
+class STORAGE_EXPORT_PRIVATE StorageTypeObservers {
+ public:
+ explicit StorageTypeObservers(QuotaManager* quota_manager);
+ virtual ~StorageTypeObservers();
+
+ // Adds and removes an observer.
+ void AddObserver(StorageObserver* observer,
+ const StorageObserver::MonitorParams& params);
+ void RemoveObserver(StorageObserver* observer);
+ void RemoveObserverForFilter(StorageObserver* observer,
+ const StorageObserver::Filter& filter);
+
+ // Returns the observers of a specific host.
+ const HostStorageObservers* GetHostObservers(const std::string& host) const;
+
+ // Handles a usage change.
+ void NotifyUsageChange(const StorageObserver::Filter& filter, int64 delta);
+
+ private:
+ typedef std::map<std::string, HostStorageObservers*> HostObserversMap;
+
+ QuotaManager* quota_manager_;
+ HostObserversMap host_observers_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(StorageTypeObservers);
+};
+
+
+// Storage monitor manages observers and dispatches storage events to them.
+class STORAGE_EXPORT_PRIVATE StorageMonitor {
+ public:
+ explicit StorageMonitor(QuotaManager* quota_manager);
+ virtual ~StorageMonitor();
+
+ // Adds and removes an observer.
+ void AddObserver(StorageObserver* observer,
+ const StorageObserver::MonitorParams& params);
+ void RemoveObserver(StorageObserver* observer);
+ void RemoveObserverForFilter(StorageObserver* observer,
+ const StorageObserver::Filter& filter);
+
+ // Returns the observers of a specific storage type.
+ const StorageTypeObservers* GetStorageTypeObservers(
+ StorageType storage_type) const;
+
+ // Handles a usage change.
+ void NotifyUsageChange(const StorageObserver::Filter& filter, int64 delta);
+
+ private:
+ typedef std::map<StorageType, StorageTypeObservers*> StorageTypeObserversMap;
+
+ QuotaManager* quota_manager_;
+ StorageTypeObserversMap storage_type_observers_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(StorageMonitor);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_QUOTA_STORAGE_MONITOR_H_
diff --git a/storage/browser/quota/storage_observer.cc b/storage/browser/quota/storage_observer.cc
new file mode 100644
index 0000000..986b639
--- /dev/null
+++ b/storage/browser/quota/storage_observer.cc
@@ -0,0 +1,65 @@
+// Copyright 2014 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 "storage/browser/quota/storage_observer.h"
+
+namespace storage {
+
+// StorageObserver::Filter
+
+StorageObserver::Filter::Filter()
+ : storage_type(kStorageTypeUnknown) {
+}
+
+StorageObserver::Filter::Filter(StorageType storage_type, const GURL& origin)
+ : storage_type(storage_type), origin(origin) {
+}
+
+bool StorageObserver::Filter::operator==(const Filter& other) const {
+ return storage_type == other.storage_type &&
+ origin == other.origin;
+}
+
+// StorageObserver::MonitorParams
+
+StorageObserver::MonitorParams::MonitorParams()
+ : dispatch_initial_state(false) {
+}
+
+StorageObserver::MonitorParams::MonitorParams(
+ StorageType storage_type,
+ const GURL& origin,
+ const base::TimeDelta& rate,
+ bool get_initial_state)
+ : filter(storage_type, origin),
+ rate(rate),
+ dispatch_initial_state(get_initial_state) {
+}
+
+StorageObserver::MonitorParams::MonitorParams(
+ const Filter& filter,
+ const base::TimeDelta& rate,
+ bool get_initial_state)
+ : filter(filter),
+ rate(rate),
+ dispatch_initial_state(get_initial_state) {
+}
+
+// StorageObserver::Event
+
+StorageObserver::Event::Event()
+ : usage(0), quota(0) {
+}
+
+StorageObserver::Event::Event(const Filter& filter, int64 usage, int64 quota)
+ : filter(filter), usage(usage), quota(quota) {
+}
+
+bool StorageObserver::Event::operator==(const Event& other) const {
+ return filter == other.filter &&
+ usage == other.usage &&
+ quota == other.quota;
+}
+
+} // namespace storage
diff --git a/storage/browser/quota/storage_observer.h b/storage/browser/quota/storage_observer.h
new file mode 100644
index 0000000..729dadd
--- /dev/null
+++ b/storage/browser/quota/storage_observer.h
@@ -0,0 +1,79 @@
+// Copyright 2014 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 STORAGE_BROWSER_QUOTA_STORAGE_OBSERVER_H_
+#define STORAGE_BROWSER_QUOTA_STORAGE_OBSERVER_H_
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "storage/browser/quota/quota_client.h"
+#include "storage/common/quota/quota_types.h"
+#include "url/gurl.h"
+
+namespace storage {
+
+// This interface is implemented by observers that wish to monitor storage
+// events, such as changes in quota or usage.
+class STORAGE_EXPORT StorageObserver {
+ public:
+ struct STORAGE_EXPORT Filter {
+ // The storage type to monitor. This must not be kStorageTypeUnknown or
+ // kStorageTypeQuotaNotManaged.
+ StorageType storage_type;
+
+ // The origin to monitor usage for. Must be specified.
+ GURL origin;
+
+ Filter();
+ Filter(StorageType storage_type, const GURL& origin);
+ bool operator==(const Filter& other) const;
+ };
+
+ struct STORAGE_EXPORT MonitorParams {
+ // Storage type and origin to monitor.
+ Filter filter;
+
+ // The rate at which storage events will be fired. Events will be fired at
+ // approximately this rate, or when a storage status change has been
+ // detected, whichever is the least frequent.
+ base::TimeDelta rate;
+
+ // If set to true, the observer will be dispatched an event when added.
+ bool dispatch_initial_state;
+
+ MonitorParams();
+ MonitorParams(StorageType storage_type,
+ const GURL& origin,
+ const base::TimeDelta& rate,
+ bool get_initial_state);
+ MonitorParams(const Filter& filter,
+ const base::TimeDelta& rate,
+ bool get_initial_state);
+ };
+
+ struct STORAGE_EXPORT Event {
+ // The storage type and origin monitored.
+ Filter filter;
+
+ // The current usage corresponding to the filter.
+ int64 usage;
+
+ // The quota corresponding to the filter.
+ int64 quota;
+
+ Event();
+ Event(const Filter& filter, int64 usage, int64 quota);
+ bool operator==(const Event& other) const;
+ };
+
+ // Will be called on the IO thread when a storage event occurs.
+ virtual void OnStorageEvent(const Event& event) = 0;
+
+ protected:
+ virtual ~StorageObserver() {}
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_QUOTA_STORAGE_OBSERVER_H_
diff --git a/storage/browser/quota/usage_tracker.cc b/storage/browser/quota/usage_tracker.cc
new file mode 100644
index 0000000..1c222bc
--- /dev/null
+++ b/storage/browser/quota/usage_tracker.cc
@@ -0,0 +1,694 @@
+// 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 "storage/browser/quota/usage_tracker.h"
+
+#include <algorithm>
+#include <deque>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/stl_util.h"
+#include "net/base/net_util.h"
+#include "storage/browser/quota/storage_monitor.h"
+#include "storage/browser/quota/storage_observer.h"
+
+namespace storage {
+
+namespace {
+
+typedef ClientUsageTracker::OriginUsageAccumulator OriginUsageAccumulator;
+typedef ClientUsageTracker::OriginSetByHost OriginSetByHost;
+
+void DidGetOriginUsage(const OriginUsageAccumulator& accumulator,
+ const GURL& origin,
+ int64 usage) {
+ accumulator.Run(origin, usage);
+}
+
+void DidGetHostUsage(const UsageCallback& callback,
+ int64 limited_usage,
+ int64 unlimited_usage) {
+ DCHECK_GE(limited_usage, 0);
+ DCHECK_GE(unlimited_usage, 0);
+ callback.Run(limited_usage + unlimited_usage);
+}
+
+bool EraseOriginFromOriginSet(OriginSetByHost* origins_by_host,
+ const std::string& host,
+ const GURL& origin) {
+ OriginSetByHost::iterator found = origins_by_host->find(host);
+ if (found == origins_by_host->end())
+ return false;
+
+ if (!found->second.erase(origin))
+ return false;
+
+ if (found->second.empty())
+ origins_by_host->erase(host);
+ return true;
+}
+
+bool OriginSetContainsOrigin(const OriginSetByHost& origins,
+ const std::string& host,
+ const GURL& origin) {
+ OriginSetByHost::const_iterator itr = origins.find(host);
+ return itr != origins.end() && ContainsKey(itr->second, origin);
+}
+
+void DidGetGlobalUsageForLimitedGlobalUsage(const UsageCallback& callback,
+ int64 total_global_usage,
+ int64 global_unlimited_usage) {
+ callback.Run(total_global_usage - global_unlimited_usage);
+}
+
+} // namespace
+
+// UsageTracker ----------------------------------------------------------
+
+UsageTracker::UsageTracker(const QuotaClientList& clients,
+ StorageType type,
+ SpecialStoragePolicy* special_storage_policy,
+ StorageMonitor* storage_monitor)
+ : type_(type),
+ storage_monitor_(storage_monitor),
+ weak_factory_(this) {
+ for (QuotaClientList::const_iterator iter = clients.begin();
+ iter != clients.end();
+ ++iter) {
+ if ((*iter)->DoesSupport(type)) {
+ client_tracker_map_[(*iter)->id()] =
+ new ClientUsageTracker(this, *iter, type, special_storage_policy,
+ storage_monitor_);
+ }
+ }
+}
+
+UsageTracker::~UsageTracker() {
+ STLDeleteValues(&client_tracker_map_);
+}
+
+ClientUsageTracker* UsageTracker::GetClientTracker(QuotaClient::ID client_id) {
+ ClientTrackerMap::iterator found = client_tracker_map_.find(client_id);
+ if (found != client_tracker_map_.end())
+ return found->second;
+ return NULL;
+}
+
+void UsageTracker::GetGlobalLimitedUsage(const UsageCallback& callback) {
+ if (global_usage_callbacks_.HasCallbacks()) {
+ global_usage_callbacks_.Add(base::Bind(
+ &DidGetGlobalUsageForLimitedGlobalUsage, callback));
+ return;
+ }
+
+ if (!global_limited_usage_callbacks_.Add(callback))
+ return;
+
+ AccumulateInfo* info = new AccumulateInfo;
+ // Calling GetGlobalLimitedUsage(accumulator) may synchronously
+ // return if the usage is cached, which may in turn dispatch
+ // the completion callback before we finish looping over
+ // all clients (because info->pending_clients may reach 0
+ // during the loop).
+ // To avoid this, we add one more pending client as a sentinel
+ // and fire the sentinel callback at the end.
+ info->pending_clients = client_tracker_map_.size() + 1;
+ UsageCallback accumulator = base::Bind(
+ &UsageTracker::AccumulateClientGlobalLimitedUsage,
+ weak_factory_.GetWeakPtr(), base::Owned(info));
+
+ for (ClientTrackerMap::iterator iter = client_tracker_map_.begin();
+ iter != client_tracker_map_.end();
+ ++iter)
+ iter->second->GetGlobalLimitedUsage(accumulator);
+
+ // Fire the sentinel as we've now called GetGlobalUsage for all clients.
+ accumulator.Run(0);
+}
+
+void UsageTracker::GetGlobalUsage(const GlobalUsageCallback& callback) {
+ if (!global_usage_callbacks_.Add(callback))
+ return;
+
+ AccumulateInfo* info = new AccumulateInfo;
+ // Calling GetGlobalUsage(accumulator) may synchronously
+ // return if the usage is cached, which may in turn dispatch
+ // the completion callback before we finish looping over
+ // all clients (because info->pending_clients may reach 0
+ // during the loop).
+ // To avoid this, we add one more pending client as a sentinel
+ // and fire the sentinel callback at the end.
+ info->pending_clients = client_tracker_map_.size() + 1;
+ GlobalUsageCallback accumulator = base::Bind(
+ &UsageTracker::AccumulateClientGlobalUsage, weak_factory_.GetWeakPtr(),
+ base::Owned(info));
+
+ for (ClientTrackerMap::iterator iter = client_tracker_map_.begin();
+ iter != client_tracker_map_.end();
+ ++iter)
+ iter->second->GetGlobalUsage(accumulator);
+
+ // Fire the sentinel as we've now called GetGlobalUsage for all clients.
+ accumulator.Run(0, 0);
+}
+
+void UsageTracker::GetHostUsage(const std::string& host,
+ const UsageCallback& callback) {
+ if (!host_usage_callbacks_.Add(host, callback))
+ return;
+
+ AccumulateInfo* info = new AccumulateInfo;
+ // Calling GetHostUsage(accumulator) may synchronously
+ // return if the usage is cached, which may in turn dispatch
+ // the completion callback before we finish looping over
+ // all clients (because info->pending_clients may reach 0
+ // during the loop).
+ // To avoid this, we add one more pending client as a sentinel
+ // and fire the sentinel callback at the end.
+ info->pending_clients = client_tracker_map_.size() + 1;
+ UsageCallback accumulator = base::Bind(
+ &UsageTracker::AccumulateClientHostUsage, weak_factory_.GetWeakPtr(),
+ base::Owned(info), host);
+
+ for (ClientTrackerMap::iterator iter = client_tracker_map_.begin();
+ iter != client_tracker_map_.end();
+ ++iter)
+ iter->second->GetHostUsage(host, accumulator);
+
+ // Fire the sentinel as we've now called GetHostUsage for all clients.
+ accumulator.Run(0);
+}
+
+void UsageTracker::UpdateUsageCache(
+ QuotaClient::ID client_id, const GURL& origin, int64 delta) {
+ ClientUsageTracker* client_tracker = GetClientTracker(client_id);
+ DCHECK(client_tracker);
+ client_tracker->UpdateUsageCache(origin, delta);
+}
+
+void UsageTracker::GetCachedHostsUsage(
+ std::map<std::string, int64>* host_usage) const {
+ DCHECK(host_usage);
+ host_usage->clear();
+ for (ClientTrackerMap::const_iterator iter = client_tracker_map_.begin();
+ iter != client_tracker_map_.end(); ++iter) {
+ iter->second->GetCachedHostsUsage(host_usage);
+ }
+}
+
+void UsageTracker::GetCachedOrigins(std::set<GURL>* origins) const {
+ DCHECK(origins);
+ origins->clear();
+ for (ClientTrackerMap::const_iterator iter = client_tracker_map_.begin();
+ iter != client_tracker_map_.end(); ++iter) {
+ iter->second->GetCachedOrigins(origins);
+ }
+}
+
+void UsageTracker::SetUsageCacheEnabled(QuotaClient::ID client_id,
+ const GURL& origin,
+ bool enabled) {
+ ClientUsageTracker* client_tracker = GetClientTracker(client_id);
+ DCHECK(client_tracker);
+
+ client_tracker->SetUsageCacheEnabled(origin, enabled);
+}
+
+void UsageTracker::AccumulateClientGlobalLimitedUsage(AccumulateInfo* info,
+ int64 limited_usage) {
+ info->usage += limited_usage;
+ if (--info->pending_clients)
+ return;
+
+ // All the clients have returned their usage data. Dispatch the
+ // pending callbacks.
+ global_limited_usage_callbacks_.Run(MakeTuple(info->usage));
+}
+
+void UsageTracker::AccumulateClientGlobalUsage(AccumulateInfo* info,
+ int64 usage,
+ int64 unlimited_usage) {
+ info->usage += usage;
+ info->unlimited_usage += unlimited_usage;
+ if (--info->pending_clients)
+ return;
+
+ // Defend against confusing inputs from clients.
+ if (info->usage < 0)
+ info->usage = 0;
+
+ // TODO(michaeln): The unlimited number is not trustworthy, it
+ // can get out of whack when apps are installed or uninstalled.
+ if (info->unlimited_usage > info->usage)
+ info->unlimited_usage = info->usage;
+ else if (info->unlimited_usage < 0)
+ info->unlimited_usage = 0;
+
+ // All the clients have returned their usage data. Dispatch the
+ // pending callbacks.
+ global_usage_callbacks_.Run(MakeTuple(info->usage, info->unlimited_usage));
+}
+
+void UsageTracker::AccumulateClientHostUsage(AccumulateInfo* info,
+ const std::string& host,
+ int64 usage) {
+ info->usage += usage;
+ if (--info->pending_clients)
+ return;
+
+ // Defend against confusing inputs from clients.
+ if (info->usage < 0)
+ info->usage = 0;
+
+ // All the clients have returned their usage data. Dispatch the
+ // pending callbacks.
+ host_usage_callbacks_.Run(host, MakeTuple(info->usage));
+}
+
+// ClientUsageTracker ----------------------------------------------------
+
+ClientUsageTracker::ClientUsageTracker(
+ UsageTracker* tracker, QuotaClient* client, StorageType type,
+ SpecialStoragePolicy* special_storage_policy,
+ StorageMonitor* storage_monitor)
+ : tracker_(tracker),
+ client_(client),
+ type_(type),
+ storage_monitor_(storage_monitor),
+ global_limited_usage_(0),
+ global_unlimited_usage_(0),
+ global_usage_retrieved_(false),
+ special_storage_policy_(special_storage_policy) {
+ DCHECK(tracker_);
+ DCHECK(client_);
+ if (special_storage_policy_.get())
+ special_storage_policy_->AddObserver(this);
+}
+
+ClientUsageTracker::~ClientUsageTracker() {
+ if (special_storage_policy_.get())
+ special_storage_policy_->RemoveObserver(this);
+}
+
+void ClientUsageTracker::GetGlobalLimitedUsage(const UsageCallback& callback) {
+ if (!global_usage_retrieved_) {
+ GetGlobalUsage(base::Bind(&DidGetGlobalUsageForLimitedGlobalUsage,
+ callback));
+ return;
+ }
+
+ if (non_cached_limited_origins_by_host_.empty()) {
+ callback.Run(global_limited_usage_);
+ return;
+ }
+
+ AccumulateInfo* info = new AccumulateInfo;
+ info->pending_jobs = non_cached_limited_origins_by_host_.size() + 1;
+ UsageCallback accumulator = base::Bind(
+ &ClientUsageTracker::AccumulateLimitedOriginUsage, AsWeakPtr(),
+ base::Owned(info), callback);
+
+ for (OriginSetByHost::iterator host_itr =
+ non_cached_limited_origins_by_host_.begin();
+ host_itr != non_cached_limited_origins_by_host_.end(); ++host_itr) {
+ for (std::set<GURL>::iterator origin_itr = host_itr->second.begin();
+ origin_itr != host_itr->second.end(); ++origin_itr)
+ client_->GetOriginUsage(*origin_itr, type_, accumulator);
+ }
+
+ accumulator.Run(global_limited_usage_);
+}
+
+void ClientUsageTracker::GetGlobalUsage(const GlobalUsageCallback& callback) {
+ if (global_usage_retrieved_ &&
+ non_cached_limited_origins_by_host_.empty() &&
+ non_cached_unlimited_origins_by_host_.empty()) {
+ callback.Run(global_limited_usage_ + global_unlimited_usage_,
+ global_unlimited_usage_);
+ return;
+ }
+
+ client_->GetOriginsForType(type_, base::Bind(
+ &ClientUsageTracker::DidGetOriginsForGlobalUsage, AsWeakPtr(),
+ callback));
+}
+
+void ClientUsageTracker::GetHostUsage(
+ const std::string& host, const UsageCallback& callback) {
+ if (ContainsKey(cached_hosts_, host) &&
+ !ContainsKey(non_cached_limited_origins_by_host_, host) &&
+ !ContainsKey(non_cached_unlimited_origins_by_host_, host)) {
+ // TODO(kinuko): Drop host_usage_map_ cache periodically.
+ callback.Run(GetCachedHostUsage(host));
+ return;
+ }
+
+ if (!host_usage_accumulators_.Add(
+ host, base::Bind(&DidGetHostUsage, callback)))
+ return;
+ client_->GetOriginsForHost(type_, host, base::Bind(
+ &ClientUsageTracker::DidGetOriginsForHostUsage, AsWeakPtr(), host));
+}
+
+void ClientUsageTracker::UpdateUsageCache(
+ const GURL& origin, int64 delta) {
+ std::string host = net::GetHostOrSpecFromURL(origin);
+ if (cached_hosts_.find(host) != cached_hosts_.end()) {
+ if (!IsUsageCacheEnabledForOrigin(origin))
+ return;
+
+ cached_usage_by_host_[host][origin] += delta;
+ if (IsStorageUnlimited(origin))
+ global_unlimited_usage_ += delta;
+ else
+ global_limited_usage_ += delta;
+ DCHECK_GE(cached_usage_by_host_[host][origin], 0);
+ DCHECK_GE(global_limited_usage_, 0);
+
+ // Notify the usage monitor that usage has changed. The storage monitor may
+ // be NULL during tests.
+ if (storage_monitor_) {
+ StorageObserver::Filter filter(type_, origin);
+ storage_monitor_->NotifyUsageChange(filter, delta);
+ }
+ return;
+ }
+
+ // We don't know about this host yet, so populate our cache for it.
+ GetHostUsage(host, base::Bind(&ClientUsageTracker::DidGetHostUsageAfterUpdate,
+ AsWeakPtr(), origin));
+}
+
+void ClientUsageTracker::GetCachedHostsUsage(
+ std::map<std::string, int64>* host_usage) const {
+ DCHECK(host_usage);
+ for (HostUsageMap::const_iterator host_iter = cached_usage_by_host_.begin();
+ host_iter != cached_usage_by_host_.end(); host_iter++) {
+ const std::string& host = host_iter->first;
+ (*host_usage)[host] += GetCachedHostUsage(host);
+ }
+}
+
+void ClientUsageTracker::GetCachedOrigins(std::set<GURL>* origins) const {
+ DCHECK(origins);
+ for (HostUsageMap::const_iterator host_iter = cached_usage_by_host_.begin();
+ host_iter != cached_usage_by_host_.end(); host_iter++) {
+ const UsageMap& origin_map = host_iter->second;
+ for (UsageMap::const_iterator origin_iter = origin_map.begin();
+ origin_iter != origin_map.end(); origin_iter++) {
+ origins->insert(origin_iter->first);
+ }
+ }
+}
+
+void ClientUsageTracker::SetUsageCacheEnabled(const GURL& origin,
+ bool enabled) {
+ std::string host = net::GetHostOrSpecFromURL(origin);
+ if (!enabled) {
+ // Erase |origin| from cache and subtract its usage.
+ HostUsageMap::iterator found_host = cached_usage_by_host_.find(host);
+ if (found_host != cached_usage_by_host_.end()) {
+ UsageMap& cached_usage_for_host = found_host->second;
+
+ UsageMap::iterator found = cached_usage_for_host.find(origin);
+ if (found != cached_usage_for_host.end()) {
+ int64 usage = found->second;
+ UpdateUsageCache(origin, -usage);
+ cached_usage_for_host.erase(found);
+ if (cached_usage_for_host.empty()) {
+ cached_usage_by_host_.erase(found_host);
+ cached_hosts_.erase(host);
+ }
+ }
+ }
+
+ if (IsStorageUnlimited(origin))
+ non_cached_unlimited_origins_by_host_[host].insert(origin);
+ else
+ non_cached_limited_origins_by_host_[host].insert(origin);
+ } else {
+ // Erase |origin| from |non_cached_origins_| and invalidate the usage cache
+ // for the host.
+ if (EraseOriginFromOriginSet(&non_cached_limited_origins_by_host_,
+ host, origin) ||
+ EraseOriginFromOriginSet(&non_cached_unlimited_origins_by_host_,
+ host, origin)) {
+ cached_hosts_.erase(host);
+ global_usage_retrieved_ = false;
+ }
+ }
+}
+
+void ClientUsageTracker::AccumulateLimitedOriginUsage(
+ AccumulateInfo* info,
+ const UsageCallback& callback,
+ int64 usage) {
+ info->limited_usage += usage;
+ if (--info->pending_jobs)
+ return;
+
+ callback.Run(info->limited_usage);
+}
+
+void ClientUsageTracker::DidGetOriginsForGlobalUsage(
+ const GlobalUsageCallback& callback,
+ const std::set<GURL>& origins) {
+ OriginSetByHost origins_by_host;
+ for (std::set<GURL>::const_iterator itr = origins.begin();
+ itr != origins.end(); ++itr)
+ origins_by_host[net::GetHostOrSpecFromURL(*itr)].insert(*itr);
+
+ AccumulateInfo* info = new AccumulateInfo;
+ // Getting host usage may synchronously return the result if the usage is
+ // cached, which may in turn dispatch the completion callback before we finish
+ // looping over all hosts (because info->pending_jobs may reach 0 during the
+ // loop). To avoid this, we add one more pending host as a sentinel and
+ // fire the sentinel callback at the end.
+ info->pending_jobs = origins_by_host.size() + 1;
+ HostUsageAccumulator accumulator =
+ base::Bind(&ClientUsageTracker::AccumulateHostUsage, AsWeakPtr(),
+ base::Owned(info), callback);
+
+ for (OriginSetByHost::iterator itr = origins_by_host.begin();
+ itr != origins_by_host.end(); ++itr) {
+ if (host_usage_accumulators_.Add(itr->first, accumulator))
+ GetUsageForOrigins(itr->first, itr->second);
+ }
+
+ // Fire the sentinel as we've now called GetUsageForOrigins for all clients.
+ accumulator.Run(0, 0);
+}
+
+void ClientUsageTracker::AccumulateHostUsage(
+ AccumulateInfo* info,
+ const GlobalUsageCallback& callback,
+ int64 limited_usage,
+ int64 unlimited_usage) {
+ info->limited_usage += limited_usage;
+ info->unlimited_usage += unlimited_usage;
+ if (--info->pending_jobs)
+ return;
+
+ DCHECK_GE(info->limited_usage, 0);
+ DCHECK_GE(info->unlimited_usage, 0);
+
+ global_usage_retrieved_ = true;
+ callback.Run(info->limited_usage + info->unlimited_usage,
+ info->unlimited_usage);
+}
+
+void ClientUsageTracker::DidGetOriginsForHostUsage(
+ const std::string& host,
+ const std::set<GURL>& origins) {
+ GetUsageForOrigins(host, origins);
+}
+
+void ClientUsageTracker::GetUsageForOrigins(
+ const std::string& host,
+ const std::set<GURL>& origins) {
+ AccumulateInfo* info = new AccumulateInfo;
+ // Getting origin usage may synchronously return the result if the usage is
+ // cached, which may in turn dispatch the completion callback before we finish
+ // looping over all origins (because info->pending_jobs may reach 0 during the
+ // loop). To avoid this, we add one more pending origin as a sentinel and
+ // fire the sentinel callback at the end.
+ info->pending_jobs = origins.size() + 1;
+ OriginUsageAccumulator accumulator =
+ base::Bind(&ClientUsageTracker::AccumulateOriginUsage, AsWeakPtr(),
+ base::Owned(info), host);
+
+ for (std::set<GURL>::const_iterator itr = origins.begin();
+ itr != origins.end(); ++itr) {
+ DCHECK_EQ(host, net::GetHostOrSpecFromURL(*itr));
+
+ int64 origin_usage = 0;
+ if (GetCachedOriginUsage(*itr, &origin_usage)) {
+ accumulator.Run(*itr, origin_usage);
+ } else {
+ client_->GetOriginUsage(*itr, type_, base::Bind(
+ &DidGetOriginUsage, accumulator, *itr));
+ }
+ }
+
+ // Fire the sentinel as we've now called GetOriginUsage for all clients.
+ accumulator.Run(GURL(), 0);
+}
+
+void ClientUsageTracker::AccumulateOriginUsage(AccumulateInfo* info,
+ const std::string& host,
+ const GURL& origin,
+ int64 usage) {
+ if (!origin.is_empty()) {
+ if (usage < 0)
+ usage = 0;
+
+ if (IsStorageUnlimited(origin))
+ info->unlimited_usage += usage;
+ else
+ info->limited_usage += usage;
+ if (IsUsageCacheEnabledForOrigin(origin))
+ AddCachedOrigin(origin, usage);
+ }
+ if (--info->pending_jobs)
+ return;
+
+ AddCachedHost(host);
+ host_usage_accumulators_.Run(
+ host, MakeTuple(info->limited_usage, info->unlimited_usage));
+}
+
+void ClientUsageTracker::DidGetHostUsageAfterUpdate(
+ const GURL& origin, int64 usage) {
+ if (!storage_monitor_)
+ return;
+
+ StorageObserver::Filter filter(type_, origin);
+ storage_monitor_->NotifyUsageChange(filter, 0);
+}
+
+void ClientUsageTracker::AddCachedOrigin(
+ const GURL& origin, int64 new_usage) {
+ DCHECK(IsUsageCacheEnabledForOrigin(origin));
+
+ std::string host = net::GetHostOrSpecFromURL(origin);
+ int64* usage = &cached_usage_by_host_[host][origin];
+ int64 delta = new_usage - *usage;
+ *usage = new_usage;
+ if (delta) {
+ if (IsStorageUnlimited(origin))
+ global_unlimited_usage_ += delta;
+ else
+ global_limited_usage_ += delta;
+ }
+ DCHECK_GE(*usage, 0);
+ DCHECK_GE(global_limited_usage_, 0);
+}
+
+void ClientUsageTracker::AddCachedHost(const std::string& host) {
+ cached_hosts_.insert(host);
+}
+
+int64 ClientUsageTracker::GetCachedHostUsage(const std::string& host) const {
+ HostUsageMap::const_iterator found = cached_usage_by_host_.find(host);
+ if (found == cached_usage_by_host_.end())
+ return 0;
+
+ int64 usage = 0;
+ const UsageMap& map = found->second;
+ for (UsageMap::const_iterator iter = map.begin();
+ iter != map.end(); ++iter) {
+ usage += iter->second;
+ }
+ return usage;
+}
+
+bool ClientUsageTracker::GetCachedOriginUsage(
+ const GURL& origin,
+ int64* usage) const {
+ std::string host = net::GetHostOrSpecFromURL(origin);
+ HostUsageMap::const_iterator found_host = cached_usage_by_host_.find(host);
+ if (found_host == cached_usage_by_host_.end())
+ return false;
+
+ UsageMap::const_iterator found = found_host->second.find(origin);
+ if (found == found_host->second.end())
+ return false;
+
+ DCHECK(IsUsageCacheEnabledForOrigin(origin));
+ *usage = found->second;
+ return true;
+}
+
+bool ClientUsageTracker::IsUsageCacheEnabledForOrigin(
+ const GURL& origin) const {
+ std::string host = net::GetHostOrSpecFromURL(origin);
+ return !OriginSetContainsOrigin(non_cached_limited_origins_by_host_,
+ host, origin) &&
+ !OriginSetContainsOrigin(non_cached_unlimited_origins_by_host_,
+ host, origin);
+}
+
+void ClientUsageTracker::OnGranted(const GURL& origin,
+ int change_flags) {
+ DCHECK(CalledOnValidThread());
+ if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
+ int64 usage = 0;
+ if (GetCachedOriginUsage(origin, &usage)) {
+ global_unlimited_usage_ += usage;
+ global_limited_usage_ -= usage;
+ }
+
+ std::string host = net::GetHostOrSpecFromURL(origin);
+ if (EraseOriginFromOriginSet(&non_cached_limited_origins_by_host_,
+ host, origin))
+ non_cached_unlimited_origins_by_host_[host].insert(origin);
+ }
+}
+
+void ClientUsageTracker::OnRevoked(const GURL& origin,
+ int change_flags) {
+ DCHECK(CalledOnValidThread());
+ if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) {
+ int64 usage = 0;
+ if (GetCachedOriginUsage(origin, &usage)) {
+ global_unlimited_usage_ -= usage;
+ global_limited_usage_ += usage;
+ }
+
+ std::string host = net::GetHostOrSpecFromURL(origin);
+ if (EraseOriginFromOriginSet(&non_cached_unlimited_origins_by_host_,
+ host, origin))
+ non_cached_limited_origins_by_host_[host].insert(origin);
+ }
+}
+
+void ClientUsageTracker::OnCleared() {
+ DCHECK(CalledOnValidThread());
+ global_limited_usage_ += global_unlimited_usage_;
+ global_unlimited_usage_ = 0;
+
+ for (OriginSetByHost::const_iterator host_itr =
+ non_cached_unlimited_origins_by_host_.begin();
+ host_itr != non_cached_unlimited_origins_by_host_.end();
+ ++host_itr) {
+ for (std::set<GURL>::const_iterator origin_itr = host_itr->second.begin();
+ origin_itr != host_itr->second.end();
+ ++origin_itr)
+ non_cached_limited_origins_by_host_[host_itr->first].insert(*origin_itr);
+ }
+ non_cached_unlimited_origins_by_host_.clear();
+}
+
+bool ClientUsageTracker::IsStorageUnlimited(const GURL& origin) const {
+ if (type_ == kStorageTypeSyncable)
+ return false;
+ return special_storage_policy_.get() &&
+ special_storage_policy_->IsStorageUnlimited(origin);
+}
+
+} // namespace storage
diff --git a/storage/browser/quota/usage_tracker.h b/storage/browser/quota/usage_tracker.h
new file mode 100644
index 0000000..fda1151
--- /dev/null
+++ b/storage/browser/quota/usage_tracker.h
@@ -0,0 +1,202 @@
+// 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 STORAGE_BROWSER_QUOTA_USAGE_TRACKER_H_
+#define STORAGE_BROWSER_QUOTA_USAGE_TRACKER_H_
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "storage/browser/quota/quota_callbacks.h"
+#include "storage/browser/quota/quota_client.h"
+#include "storage/browser/quota/quota_task.h"
+#include "storage/browser/quota/special_storage_policy.h"
+#include "storage/browser/storage_browser_export.h"
+#include "storage/common/quota/quota_types.h"
+#include "url/gurl.h"
+
+namespace storage {
+
+class ClientUsageTracker;
+class StorageMonitor;
+
+// A helper class that gathers and tracks the amount of data stored in
+// all quota clients.
+// An instance of this class is created per storage type.
+class STORAGE_EXPORT UsageTracker : public QuotaTaskObserver {
+ public:
+ UsageTracker(const QuotaClientList& clients, StorageType type,
+ SpecialStoragePolicy* special_storage_policy,
+ StorageMonitor* storage_monitor);
+ virtual ~UsageTracker();
+
+ StorageType type() const { return type_; }
+ ClientUsageTracker* GetClientTracker(QuotaClient::ID client_id);
+
+ void GetGlobalLimitedUsage(const UsageCallback& callback);
+ void GetGlobalUsage(const GlobalUsageCallback& callback);
+ void GetHostUsage(const std::string& host, const UsageCallback& callback);
+ void UpdateUsageCache(QuotaClient::ID client_id,
+ const GURL& origin,
+ int64 delta);
+ void GetCachedHostsUsage(std::map<std::string, int64>* host_usage) const;
+ void GetCachedOrigins(std::set<GURL>* origins) const;
+ bool IsWorking() const {
+ return global_usage_callbacks_.HasCallbacks() ||
+ host_usage_callbacks_.HasAnyCallbacks();
+ }
+
+ void SetUsageCacheEnabled(QuotaClient::ID client_id,
+ const GURL& origin,
+ bool enabled);
+
+ private:
+ struct AccumulateInfo {
+ AccumulateInfo() : pending_clients(0), usage(0), unlimited_usage(0) {}
+ int pending_clients;
+ int64 usage;
+ int64 unlimited_usage;
+ };
+
+ typedef std::map<QuotaClient::ID, ClientUsageTracker*> ClientTrackerMap;
+
+ friend class ClientUsageTracker;
+ void AccumulateClientGlobalLimitedUsage(AccumulateInfo* info,
+ int64 limited_usage);
+ void AccumulateClientGlobalUsage(AccumulateInfo* info,
+ int64 usage,
+ int64 unlimited_usage);
+ void AccumulateClientHostUsage(AccumulateInfo* info,
+ const std::string& host,
+ int64 usage);
+
+ const StorageType type_;
+ ClientTrackerMap client_tracker_map_;
+
+ UsageCallbackQueue global_limited_usage_callbacks_;
+ GlobalUsageCallbackQueue global_usage_callbacks_;
+ HostUsageCallbackMap host_usage_callbacks_;
+
+ StorageMonitor* storage_monitor_;
+
+ base::WeakPtrFactory<UsageTracker> weak_factory_;
+ DISALLOW_COPY_AND_ASSIGN(UsageTracker);
+};
+
+// This class holds per-client usage tracking information and caches per-host
+// usage data. An instance of this class is created per client.
+class ClientUsageTracker : public SpecialStoragePolicy::Observer,
+ public base::NonThreadSafe,
+ public base::SupportsWeakPtr<ClientUsageTracker> {
+ public:
+ typedef base::Callback<void(int64 limited_usage,
+ int64 unlimited_usage)> HostUsageAccumulator;
+ typedef base::Callback<void(const GURL& origin,
+ int64 usage)> OriginUsageAccumulator;
+ typedef std::map<std::string, std::set<GURL> > OriginSetByHost;
+
+ ClientUsageTracker(UsageTracker* tracker,
+ QuotaClient* client,
+ StorageType type,
+ SpecialStoragePolicy* special_storage_policy,
+ StorageMonitor* storage_monitor);
+ virtual ~ClientUsageTracker();
+
+ void GetGlobalLimitedUsage(const UsageCallback& callback);
+ void GetGlobalUsage(const GlobalUsageCallback& callback);
+ void GetHostUsage(const std::string& host, const UsageCallback& callback);
+ void UpdateUsageCache(const GURL& origin, int64 delta);
+ void GetCachedHostsUsage(std::map<std::string, int64>* host_usage) const;
+ void GetCachedOrigins(std::set<GURL>* origins) const;
+ int64 GetCachedOriginsUsage(const std::set<GURL>& origins,
+ std::vector<GURL>* origins_not_in_cache);
+ bool IsUsageCacheEnabledForOrigin(const GURL& origin) const;
+ void SetUsageCacheEnabled(const GURL& origin, bool enabled);
+
+ private:
+ typedef CallbackQueueMap<HostUsageAccumulator, std::string,
+ Tuple2<int64, int64> > HostUsageAccumulatorMap;
+
+ typedef std::set<std::string> HostSet;
+ typedef std::map<GURL, int64> UsageMap;
+ typedef std::map<std::string, UsageMap> HostUsageMap;
+
+ struct AccumulateInfo {
+ int pending_jobs;
+ int64 limited_usage;
+ int64 unlimited_usage;
+
+ AccumulateInfo()
+ : pending_jobs(0), limited_usage(0), unlimited_usage(0) {}
+ };
+
+ void AccumulateLimitedOriginUsage(AccumulateInfo* info,
+ const UsageCallback& callback,
+ int64 usage);
+ void DidGetOriginsForGlobalUsage(const GlobalUsageCallback& callback,
+ const std::set<GURL>& origins);
+ void AccumulateHostUsage(AccumulateInfo* info,
+ const GlobalUsageCallback& callback,
+ int64 limited_usage,
+ int64 unlimited_usage);
+
+ void DidGetOriginsForHostUsage(const std::string& host,
+ const std::set<GURL>& origins);
+
+ void GetUsageForOrigins(const std::string& host,
+ const std::set<GURL>& origins);
+ void AccumulateOriginUsage(AccumulateInfo* info,
+ const std::string& host,
+ const GURL& origin,
+ int64 usage);
+
+ void DidGetHostUsageAfterUpdate(const GURL& origin, int64 usage);
+
+ // Methods used by our GatherUsage tasks, as a task makes progress
+ // origins and hosts are added incrementally to the cache.
+ void AddCachedOrigin(const GURL& origin, int64 usage);
+ void AddCachedHost(const std::string& host);
+
+ int64 GetCachedHostUsage(const std::string& host) const;
+ int64 GetCachedGlobalUnlimitedUsage();
+ bool GetCachedOriginUsage(const GURL& origin, int64* usage) const;
+
+ // SpecialStoragePolicy::Observer overrides
+ virtual void OnGranted(const GURL& origin, int change_flags) OVERRIDE;
+ virtual void OnRevoked(const GURL& origin, int change_flags) OVERRIDE;
+ virtual void OnCleared() OVERRIDE;
+
+ bool IsStorageUnlimited(const GURL& origin) const;
+
+ UsageTracker* tracker_;
+ QuotaClient* client_;
+ const StorageType type_;
+ StorageMonitor* storage_monitor_;
+
+ int64 global_limited_usage_;
+ int64 global_unlimited_usage_;
+ bool global_usage_retrieved_;
+ HostSet cached_hosts_;
+ HostUsageMap cached_usage_by_host_;
+
+ OriginSetByHost non_cached_limited_origins_by_host_;
+ OriginSetByHost non_cached_unlimited_origins_by_host_;
+
+ GlobalUsageCallbackQueue global_usage_callback_;
+ HostUsageAccumulatorMap host_usage_accumulators_;
+
+ scoped_refptr<SpecialStoragePolicy> special_storage_policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientUsageTracker);
+};
+
+} // namespace storage
+
+#endif // STORAGE_BROWSER_QUOTA_USAGE_TRACKER_H_
diff --git a/storage/browser/storage_browser_export.h b/storage/browser/storage_browser_export.h
new file mode 100644
index 0000000..c6d6a37
--- /dev/null
+++ b/storage/browser/storage_browser_export.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 STORAGE_BROWSER_STORAGE_BROWSER_EXPORT_H__
+#define STORAGE_BROWSER_STORAGE_BROWSER_EXPORT_H__
+
+#if defined(COMPONENT_BUILD)
+#if defined(WIN32)
+
+#if defined(STORAGE_BROWSER_IMPLEMENTATION)
+#define STORAGE_EXPORT __declspec(dllexport)
+#define STORAGE_EXPORT_PRIVATE __declspec(dllexport)
+#else
+#define STORAGE_EXPORT __declspec(dllimport)
+#define STORAGE_EXPORT_PRIVATE __declspec(dllimport)
+#endif // defined(STORAGE_BROWSER_IMPLEMENTATION)
+
+#else // defined(WIN32)
+#if defined(STORAGE_BROWSER_IMPLEMENTATION)
+#define STORAGE_EXPORT __attribute__((visibility("default")))
+#define STORAGE_EXPORT_PRIVATE __attribute__((visibility("default")))
+#else
+#define STORAGE_EXPORT
+#define STORAGE_EXPORT_PRIVATE
+#endif
+#endif
+
+#else // defined(COMPONENT_BUILD)
+#define STORAGE_EXPORT
+#define STORAGE_EXPORT_PRIVATE
+#endif
+
+#endif // STORAGE_BROWSER_STORAGE_BROWSER_EXPORT_H__
diff --git a/storage/storage_browser.gyp b/storage/storage_browser.gyp
new file mode 100644
index 0000000..afa0b47
--- /dev/null
+++ b/storage/storage_browser.gyp
@@ -0,0 +1,199 @@
+# 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.
+
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ # GN version: //storage/browser
+ 'target_name': 'storage',
+ 'type': '<(component)',
+ 'variables': { 'enable_wexit_time_destructors': 1, },
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/base/base.gyp:base_i18n',
+ '<(DEPTH)/base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations',
+ '<(DEPTH)/net/net.gyp:net',
+ '<(DEPTH)/sql/sql.gyp:sql',
+ '<(DEPTH)/storage/storage_common.gyp:storage_common',
+ '<(DEPTH)/third_party/leveldatabase/leveldatabase.gyp:leveldatabase',
+ '<(DEPTH)/third_party/sqlite/sqlite.gyp:sqlite',
+ '<(DEPTH)/url/url.gyp:url_lib',
+ ],
+ 'defines': ['STORAGE_BROWSER_IMPLEMENTATION'],
+ 'sources': [
+ 'browser/blob/blob_data_handle.cc',
+ 'browser/blob/blob_data_handle.h',
+ 'browser/blob/blob_storage_context.cc',
+ 'browser/blob/blob_storage_context.h',
+ 'browser/blob/blob_url_request_job.cc',
+ 'browser/blob/blob_url_request_job.h',
+ 'browser/blob/blob_url_request_job_factory.cc',
+ 'browser/blob/blob_url_request_job_factory.h',
+ 'browser/blob/file_stream_reader.cc',
+ 'browser/blob/file_stream_reader.h',
+ 'browser/blob/local_file_stream_reader.cc',
+ 'browser/blob/local_file_stream_reader.h',
+ 'browser/blob/view_blob_internals_job.cc',
+ 'browser/blob/view_blob_internals_job.h',
+ 'browser/database/database_quota_client.cc',
+ 'browser/database/database_quota_client.h',
+ 'browser/database/database_tracker.cc',
+ 'browser/database/database_tracker.h',
+ 'browser/database/database_util.cc',
+ 'browser/database/database_util.h',
+ 'browser/database/databases_table.cc',
+ 'browser/database/databases_table.h',
+ 'browser/database/vfs_backend.cc',
+ 'browser/database/vfs_backend.h',
+ 'browser/fileapi/async_file_util.h',
+ 'browser/fileapi/async_file_util_adapter.cc',
+ 'browser/fileapi/async_file_util_adapter.h',
+ 'browser/fileapi/copy_or_move_file_validator.h',
+ 'browser/fileapi/copy_or_move_operation_delegate.cc',
+ 'browser/fileapi/copy_or_move_operation_delegate.h',
+ 'browser/fileapi/dragged_file_util.cc',
+ 'browser/fileapi/dragged_file_util.h',
+ 'browser/fileapi/external_mount_points.cc',
+ 'browser/fileapi/external_mount_points.h',
+ 'browser/fileapi/file_observers.h',
+ 'browser/fileapi/file_permission_policy.h',
+ 'browser/fileapi/file_stream_writer.h',
+ 'browser/fileapi/file_system_backend.h',
+ 'browser/fileapi/file_system_context.cc',
+ 'browser/fileapi/file_system_context.h',
+ 'browser/fileapi/file_system_dir_url_request_job.cc',
+ 'browser/fileapi/file_system_dir_url_request_job.h',
+ 'browser/fileapi/file_system_file_stream_reader.cc',
+ 'browser/fileapi/file_system_file_stream_reader.h',
+ 'browser/fileapi/file_system_file_util.cc',
+ 'browser/fileapi/file_system_file_util.h',
+ 'browser/fileapi/file_system_operation.h',
+ 'browser/fileapi/file_system_operation_context.cc',
+ 'browser/fileapi/file_system_operation_context.h',
+ 'browser/fileapi/file_system_operation_impl.cc',
+ 'browser/fileapi/file_system_operation_impl.h',
+ 'browser/fileapi/file_system_operation_runner.cc',
+ 'browser/fileapi/file_system_operation_runner.h',
+ 'browser/fileapi/file_system_options.cc',
+ 'browser/fileapi/file_system_options.h',
+ 'browser/fileapi/file_system_quota_client.cc',
+ 'browser/fileapi/file_system_quota_client.h',
+ 'browser/fileapi/file_system_quota_util.h',
+ 'browser/fileapi/file_system_url.cc',
+ 'browser/fileapi/file_system_url.h',
+ 'browser/fileapi/file_system_url_request_job.cc',
+ 'browser/fileapi/file_system_url_request_job.h',
+ 'browser/fileapi/file_system_url_request_job_factory.cc',
+ 'browser/fileapi/file_system_url_request_job_factory.h',
+ 'browser/fileapi/file_system_usage_cache.cc',
+ 'browser/fileapi/file_system_usage_cache.h',
+ 'browser/fileapi/file_writer_delegate.cc',
+ 'browser/fileapi/file_writer_delegate.h',
+ 'browser/fileapi/isolated_context.cc',
+ 'browser/fileapi/isolated_context.h',
+ 'browser/fileapi/isolated_file_system_backend.cc',
+ 'browser/fileapi/isolated_file_system_backend.h',
+ 'browser/fileapi/local_file_stream_writer.cc',
+ 'browser/fileapi/local_file_stream_writer.h',
+ 'browser/fileapi/local_file_util.cc',
+ 'browser/fileapi/local_file_util.h',
+ 'browser/fileapi/mount_points.cc',
+ 'browser/fileapi/mount_points.h',
+ 'browser/fileapi/native_file_util.cc',
+ 'browser/fileapi/native_file_util.h',
+ 'browser/fileapi/obfuscated_file_util.cc',
+ 'browser/fileapi/obfuscated_file_util.h',
+ 'browser/fileapi/open_file_system_mode.h',
+ 'browser/fileapi/plugin_private_file_system_backend.cc',
+ 'browser/fileapi/plugin_private_file_system_backend.h',
+ 'browser/fileapi/quota/open_file_handle.cc',
+ 'browser/fileapi/quota/open_file_handle.h',
+ 'browser/fileapi/quota/open_file_handle_context.cc',
+ 'browser/fileapi/quota/open_file_handle_context.h',
+ 'browser/fileapi/quota/quota_backend_impl.cc',
+ 'browser/fileapi/quota/quota_backend_impl.h',
+ 'browser/fileapi/quota/quota_reservation.cc',
+ 'browser/fileapi/quota/quota_reservation.h',
+ 'browser/fileapi/quota/quota_reservation_buffer.cc',
+ 'browser/fileapi/quota/quota_reservation_buffer.h',
+ 'browser/fileapi/quota/quota_reservation_manager.cc',
+ 'browser/fileapi/quota/quota_reservation_manager.h',
+ 'browser/fileapi/recursive_operation_delegate.cc',
+ 'browser/fileapi/recursive_operation_delegate.h',
+ 'browser/fileapi/remove_operation_delegate.cc',
+ 'browser/fileapi/remove_operation_delegate.h',
+ 'browser/fileapi/sandbox_directory_database.cc',
+ 'browser/fileapi/sandbox_directory_database.h',
+ 'browser/fileapi/sandbox_file_stream_writer.cc',
+ 'browser/fileapi/sandbox_file_stream_writer.h',
+ 'browser/fileapi/sandbox_file_system_backend.cc',
+ 'browser/fileapi/sandbox_file_system_backend.h',
+ 'browser/fileapi/sandbox_file_system_backend_delegate.cc',
+ 'browser/fileapi/sandbox_file_system_backend_delegate.h',
+ 'browser/fileapi/sandbox_isolated_origin_database.cc',
+ 'browser/fileapi/sandbox_isolated_origin_database.h',
+ 'browser/fileapi/sandbox_origin_database.cc',
+ 'browser/fileapi/sandbox_origin_database.h',
+ 'browser/fileapi/sandbox_origin_database_interface.cc',
+ 'browser/fileapi/sandbox_origin_database_interface.h',
+ 'browser/fileapi/sandbox_prioritized_origin_database.cc',
+ 'browser/fileapi/sandbox_prioritized_origin_database.h',
+ 'browser/fileapi/sandbox_quota_observer.cc',
+ 'browser/fileapi/sandbox_quota_observer.h',
+ 'browser/fileapi/task_runner_bound_observer_list.h',
+ 'browser/fileapi/timed_task_helper.cc',
+ 'browser/fileapi/timed_task_helper.h',
+ 'browser/fileapi/transient_file_util.cc',
+ 'browser/fileapi/transient_file_util.h',
+ 'browser/quota/quota_callbacks.h',
+ 'browser/quota/quota_client.h',
+ 'browser/quota/quota_database.cc',
+ 'browser/quota/quota_database.h',
+ 'browser/quota/quota_manager.cc',
+ 'browser/quota/quota_manager.h',
+ 'browser/quota/quota_manager_proxy.cc',
+ 'browser/quota/quota_manager_proxy.h',
+ 'browser/quota/quota_task.cc',
+ 'browser/quota/quota_task.h',
+ 'browser/quota/quota_temporary_storage_evictor.cc',
+ 'browser/quota/quota_temporary_storage_evictor.h',
+ 'browser/quota/special_storage_policy.cc',
+ 'browser/quota/special_storage_policy.h',
+ 'browser/quota/storage_monitor.cc',
+ 'browser/quota/storage_monitor.h',
+ 'browser/quota/storage_observer.cc',
+ 'browser/quota/storage_observer.h',
+ 'browser/quota/usage_tracker.cc',
+ 'browser/quota/usage_tracker.h',
+ 'browser/storage_browser_export.h',
+ ],
+ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations.
+ 'msvs_disabled_warnings': [ 4267, ],
+
+ 'conditions': [
+ ['chrome_multiple_dll!=1', {
+ 'dependencies': [
+ '<(DEPTH)/third_party/WebKit/public/blink.gyp:blink',
+ ],
+ }],
+ ],
+ },
+ {
+ # GN version: //webkit/browser:dump_file_system
+ 'target_name': 'dump_file_system',
+ 'type': 'executable',
+ 'sources': [
+ 'browser/fileapi/dump_file_system.cc',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/base/base.gyp:base',
+ '<(DEPTH)/storage/storage_common.gyp:storage_common',
+ 'storage',
+ ],
+ },
+ ],
+}