summaryrefslogtreecommitdiffstats
path: root/webkit/browser/fileapi/syncable
diff options
context:
space:
mode:
Diffstat (limited to 'webkit/browser/fileapi/syncable')
-rw-r--r--webkit/browser/fileapi/syncable/OWNERS1
-rw-r--r--webkit/browser/fileapi/syncable/canned_syncable_file_system.cc606
-rw-r--r--webkit/browser/fileapi/syncable/canned_syncable_file_system.h223
-rw-r--r--webkit/browser/fileapi/syncable/file_change.cc89
-rw-r--r--webkit/browser/fileapi/syncable/file_change.h77
-rw-r--r--webkit/browser/fileapi/syncable/file_change_unittest.cc136
-rw-r--r--webkit/browser/fileapi/syncable/local_file_change_tracker.cc426
-rw-r--r--webkit/browser/fileapi/syncable/local_file_change_tracker.h150
-rw-r--r--webkit/browser/fileapi/syncable/local_file_change_tracker_unittest.cc602
-rw-r--r--webkit/browser/fileapi/syncable/local_file_sync_context.cc745
-rw-r--r--webkit/browser/fileapi/syncable/local_file_sync_context.h306
-rw-r--r--webkit/browser/fileapi/syncable/local_file_sync_context_unittest.cc681
-rw-r--r--webkit/browser/fileapi/syncable/local_file_sync_status.cc97
-rw-r--r--webkit/browser/fileapi/syncable/local_file_sync_status.h94
-rw-r--r--webkit/browser/fileapi/syncable/local_file_sync_status_unittest.cc89
-rw-r--r--webkit/browser/fileapi/syncable/local_origin_change_observer.h28
-rw-r--r--webkit/browser/fileapi/syncable/mock_sync_status_observer.cc15
-rw-r--r--webkit/browser/fileapi/syncable/mock_sync_status_observer.h28
-rw-r--r--webkit/browser/fileapi/syncable/sync_action.h26
-rw-r--r--webkit/browser/fileapi/syncable/sync_callbacks.h42
-rw-r--r--webkit/browser/fileapi/syncable/sync_direction.h18
-rw-r--r--webkit/browser/fileapi/syncable/sync_file_metadata.cc36
-rw-r--r--webkit/browser/fileapi/syncable/sync_file_metadata.h43
-rw-r--r--webkit/browser/fileapi/syncable/sync_file_status.h29
-rw-r--r--webkit/browser/fileapi/syncable/sync_file_type.h23
-rw-r--r--webkit/browser/fileapi/syncable/sync_status_code.cc145
-rw-r--r--webkit/browser/fileapi/syncable/sync_status_code.h74
-rw-r--r--webkit/browser/fileapi/syncable/syncable_file_operation_runner.cc107
-rw-r--r--webkit/browser/fileapi/syncable/syncable_file_operation_runner.h105
-rw-r--r--webkit/browser/fileapi/syncable/syncable_file_operation_runner_unittest.cc371
-rw-r--r--webkit/browser/fileapi/syncable/syncable_file_system_operation.cc408
-rw-r--r--webkit/browser/fileapi/syncable/syncable_file_system_operation.h119
-rw-r--r--webkit/browser/fileapi/syncable/syncable_file_system_unittest.cc284
-rw-r--r--webkit/browser/fileapi/syncable/syncable_file_system_util.cc110
-rw-r--r--webkit/browser/fileapi/syncable/syncable_file_system_util.h103
-rw-r--r--webkit/browser/fileapi/syncable/syncable_file_system_util_unittest.cc177
36 files changed, 6613 insertions, 0 deletions
diff --git a/webkit/browser/fileapi/syncable/OWNERS b/webkit/browser/fileapi/syncable/OWNERS
new file mode 100644
index 0000000..66ba5d2
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/OWNERS
@@ -0,0 +1 @@
+tzik@chromium.org
diff --git a/webkit/browser/fileapi/syncable/canned_syncable_file_system.cc b/webkit/browser/fileapi/syncable/canned_syncable_file_system.cc
new file mode 100644
index 0000000..c03d354
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/canned_syncable_file_system.cc
@@ -0,0 +1,606 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/canned_syncable_file_system.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/message_loop_proxy.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task_runner_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/blob/mock_blob_url_request_context.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_mount_point_provider.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_task_runners.h"
+#include "webkit/browser/fileapi/local_file_system_operation.h"
+#include "webkit/browser/fileapi/mock_file_system_options.h"
+#include "webkit/browser/fileapi/sandbox_mount_point_provider.h"
+#include "webkit/browser/fileapi/syncable/local_file_change_tracker.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_context.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+#include "webkit/quota/mock_special_storage_policy.h"
+#include "webkit/quota/quota_manager.h"
+
+using base::PlatformFileError;
+using fileapi::FileSystemContext;
+using fileapi::FileSystemOperation;
+using fileapi::FileSystemURL;
+using fileapi::FileSystemURLSet;
+using quota::QuotaManager;
+using webkit_blob::MockBlobURLRequestContext;
+using webkit_blob::ScopedTextBlob;
+
+namespace sync_file_system {
+
+namespace {
+
+void Quit() { base::MessageLoop::current()->Quit(); }
+
+template <typename R>
+void AssignAndQuit(base::TaskRunner* original_task_runner,
+ R* result_out, R result) {
+ DCHECK(result_out);
+ *result_out = result;
+ original_task_runner->PostTask(FROM_HERE, base::Bind(&Quit));
+}
+
+template <typename R>
+R RunOnThread(
+ base::SingleThreadTaskRunner* task_runner,
+ const tracked_objects::Location& location,
+ const base::Callback<void(const base::Callback<void(R)>& callback)>& task) {
+ R result;
+ task_runner->PostTask(
+ location,
+ base::Bind(task, base::Bind(&AssignAndQuit<R>,
+ base::MessageLoopProxy::current(),
+ &result)));
+ base::MessageLoop::current()->Run();
+ return result;
+}
+
+void RunOnThread(base::SingleThreadTaskRunner* task_runner,
+ const tracked_objects::Location& location,
+ const base::Closure& task) {
+ task_runner->PostTaskAndReply(
+ location, task,
+ base::Bind(base::IgnoreResult(
+ base::Bind(&base::MessageLoopProxy::PostTask,
+ base::MessageLoopProxy::current(),
+ FROM_HERE, base::Bind(&Quit)))));
+ base::MessageLoop::current()->Run();
+}
+
+void EnsureRunningOn(base::SingleThreadTaskRunner* runner) {
+ EXPECT_TRUE(runner->RunsTasksOnCurrentThread());
+}
+
+void VerifySameTaskRunner(
+ base::SingleThreadTaskRunner* runner1,
+ base::SingleThreadTaskRunner* runner2) {
+ ASSERT_TRUE(runner1 != NULL);
+ ASSERT_TRUE(runner2 != NULL);
+ runner1->PostTask(FROM_HERE,
+ base::Bind(&EnsureRunningOn, make_scoped_refptr(runner2)));
+}
+
+void OnGetMetadataAndVerifyData(
+ const std::string& expected_data,
+ const CannedSyncableFileSystem::StatusCallback& callback,
+ base::PlatformFileError result,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path) {
+ if (result != base::PLATFORM_FILE_OK) {
+ callback.Run(result);
+ return;
+ }
+ EXPECT_EQ(expected_data.size(), static_cast<size_t>(file_info.size));
+ std::string data;
+ const bool read_status = file_util::ReadFileToString(platform_path, &data);
+ EXPECT_TRUE(read_status);
+ EXPECT_EQ(expected_data, data);
+ callback.Run(result);
+}
+
+void OnGetMetadata(
+ base::PlatformFileInfo* file_info_out,
+ base::FilePath* platform_path_out,
+ const CannedSyncableFileSystem::StatusCallback& callback,
+ base::PlatformFileError result,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path) {
+ DCHECK(file_info_out);
+ DCHECK(platform_path_out);
+ *file_info_out = file_info;
+ *platform_path_out = platform_path;
+ callback.Run(result);
+}
+
+class WriteHelper {
+ public:
+ WriteHelper() : bytes_written_(0) {}
+ WriteHelper(MockBlobURLRequestContext* request_context,
+ const GURL& blob_url,
+ const std::string& blob_data)
+ : bytes_written_(0),
+ request_context_(request_context),
+ blob_data_(new ScopedTextBlob(*request_context, blob_url, blob_data)) {}
+
+ ~WriteHelper() {
+ if (request_context_) {
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE,
+ request_context_.release());
+ }
+ }
+
+ void DidWrite(const base::Callback<void(int64 result)>& completion_callback,
+ PlatformFileError error, int64 bytes, bool complete) {
+ if (error == base::PLATFORM_FILE_OK) {
+ bytes_written_ += bytes;
+ if (!complete)
+ return;
+ }
+ completion_callback.Run(error == base::PLATFORM_FILE_OK
+ ? bytes_written_ : static_cast<int64>(error));
+ }
+
+ private:
+ int64 bytes_written_;
+ scoped_ptr<MockBlobURLRequestContext> request_context_;
+ scoped_ptr<ScopedTextBlob> blob_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(WriteHelper);
+};
+
+void DidGetUsageAndQuota(const quota::StatusCallback& callback,
+ int64* usage_out, int64* quota_out,
+ quota::QuotaStatusCode status,
+ int64 usage, int64 quota) {
+ *usage_out = usage;
+ *quota_out = quota;
+ callback.Run(status);
+}
+
+void EnsureLastTaskRuns(base::SingleThreadTaskRunner* runner) {
+ base::RunLoop run_loop;
+ runner->PostTaskAndReply(
+ FROM_HERE, base::Bind(&base::DoNothing), run_loop.QuitClosure());
+ run_loop.Run();
+}
+
+} // namespace
+
+CannedSyncableFileSystem::CannedSyncableFileSystem(
+ const GURL& origin, const std::string& service,
+ base::SingleThreadTaskRunner* io_task_runner,
+ base::SingleThreadTaskRunner* file_task_runner)
+ : service_name_(service),
+ origin_(origin),
+ type_(fileapi::kFileSystemTypeSyncable),
+ result_(base::PLATFORM_FILE_OK),
+ sync_status_(sync_file_system::SYNC_STATUS_OK),
+ io_task_runner_(io_task_runner),
+ file_task_runner_(file_task_runner),
+ is_filesystem_set_up_(false),
+ is_filesystem_opened_(false),
+ sync_status_observers_(new ObserverList) {
+}
+
+CannedSyncableFileSystem::~CannedSyncableFileSystem() {}
+
+void CannedSyncableFileSystem::SetUp() {
+ ASSERT_FALSE(is_filesystem_set_up_);
+ ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
+
+ scoped_refptr<quota::SpecialStoragePolicy> storage_policy =
+ new quota::MockSpecialStoragePolicy();
+
+ quota_manager_ = new QuotaManager(
+ false /* is_incognito */,
+ data_dir_.path(),
+ io_task_runner_,
+ base::MessageLoopProxy::current(),
+ storage_policy);
+
+ file_system_context_ = new FileSystemContext(
+ make_scoped_ptr(new fileapi::FileSystemTaskRunners(
+ io_task_runner_,
+ file_task_runner_,
+ file_task_runner_)),
+ fileapi::ExternalMountPoints::CreateRefCounted().get(),
+ storage_policy,
+ quota_manager_->proxy(),
+ ScopedVector<fileapi::FileSystemMountPointProvider>(),
+ data_dir_.path(),
+ fileapi::CreateAllowFileAccessOptions());
+
+ // In testing we override this setting to support directory operations
+ // by default.
+ SetEnableSyncFSDirectoryOperation(true);
+
+ is_filesystem_set_up_ = true;
+}
+
+void CannedSyncableFileSystem::TearDown() {
+ quota_manager_ = NULL;
+ file_system_context_ = NULL;
+ SetEnableSyncFSDirectoryOperation(false);
+
+ // Make sure we give some more time to finish tasks on other threads.
+ EnsureLastTaskRuns(io_task_runner_);
+ EnsureLastTaskRuns(file_task_runner_);
+}
+
+FileSystemURL CannedSyncableFileSystem::URL(const std::string& path) const {
+ EXPECT_TRUE(is_filesystem_set_up_);
+ EXPECT_TRUE(is_filesystem_opened_);
+
+ GURL url(root_url_.spec() + path);
+ return file_system_context_->CrackURL(url);
+}
+
+PlatformFileError CannedSyncableFileSystem::OpenFileSystem() {
+ EXPECT_TRUE(is_filesystem_set_up_);
+ EXPECT_FALSE(is_filesystem_opened_);
+ file_system_context_->OpenSyncableFileSystem(
+ service_name_, origin_, type_, true /* create */,
+ base::Bind(&CannedSyncableFileSystem::DidOpenFileSystem,
+ base::Unretained(this)));
+ base::MessageLoop::current()->Run();
+ if (file_system_context_->sync_context()) {
+ // Register 'this' as a sync status observer.
+ RunOnThread(io_task_runner_,
+ FROM_HERE,
+ base::Bind(
+ &CannedSyncableFileSystem::InitializeSyncStatusObserver,
+ base::Unretained(this)));
+ }
+ return result_;
+}
+
+void CannedSyncableFileSystem::AddSyncStatusObserver(
+ LocalFileSyncStatus::Observer* observer) {
+ sync_status_observers_->AddObserver(observer);
+}
+
+void CannedSyncableFileSystem::RemoveSyncStatusObserver(
+ LocalFileSyncStatus::Observer* observer) {
+ sync_status_observers_->RemoveObserver(observer);
+}
+
+SyncStatusCode CannedSyncableFileSystem::MaybeInitializeFileSystemContext(
+ LocalFileSyncContext* sync_context) {
+ DCHECK(sync_context);
+ sync_status_ = sync_file_system::SYNC_STATUS_UNKNOWN;
+ VerifySameTaskRunner(io_task_runner_, sync_context->io_task_runner_);
+ sync_context->MaybeInitializeFileSystemContext(
+ origin_, service_name_, file_system_context_,
+ base::Bind(&CannedSyncableFileSystem::DidInitializeFileSystemContext,
+ base::Unretained(this)));
+ base::MessageLoop::current()->Run();
+ return sync_status_;
+}
+
+PlatformFileError CannedSyncableFileSystem::CreateDirectory(
+ const FileSystemURL& url) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoCreateDirectory,
+ base::Unretained(this), url));
+}
+
+PlatformFileError CannedSyncableFileSystem::CreateFile(
+ const FileSystemURL& url) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoCreateFile,
+ base::Unretained(this), url));
+}
+
+PlatformFileError CannedSyncableFileSystem::Copy(
+ const FileSystemURL& src_url, const FileSystemURL& dest_url) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoCopy,
+ base::Unretained(this), src_url, dest_url));
+}
+
+PlatformFileError CannedSyncableFileSystem::Move(
+ const FileSystemURL& src_url, const FileSystemURL& dest_url) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoMove,
+ base::Unretained(this), src_url, dest_url));
+}
+
+PlatformFileError CannedSyncableFileSystem::TruncateFile(
+ const FileSystemURL& url, int64 size) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoTruncateFile,
+ base::Unretained(this), url, size));
+}
+
+PlatformFileError CannedSyncableFileSystem::TouchFile(
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoTouchFile,
+ base::Unretained(this), url,
+ last_access_time, last_modified_time));
+}
+
+PlatformFileError CannedSyncableFileSystem::Remove(
+ const FileSystemURL& url, bool recursive) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoRemove,
+ base::Unretained(this), url, recursive));
+}
+
+PlatformFileError CannedSyncableFileSystem::FileExists(
+ const FileSystemURL& url) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoFileExists,
+ base::Unretained(this), url));
+}
+
+PlatformFileError CannedSyncableFileSystem::DirectoryExists(
+ const FileSystemURL& url) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoDirectoryExists,
+ base::Unretained(this), url));
+}
+
+PlatformFileError CannedSyncableFileSystem::VerifyFile(
+ const FileSystemURL& url,
+ const std::string& expected_data) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoVerifyFile,
+ base::Unretained(this), url, expected_data));
+}
+
+PlatformFileError CannedSyncableFileSystem::GetMetadata(
+ const FileSystemURL& url,
+ base::PlatformFileInfo* info,
+ base::FilePath* platform_path) {
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoGetMetadata,
+ base::Unretained(this), url, info, platform_path));
+}
+
+int64 CannedSyncableFileSystem::Write(
+ net::URLRequestContext* url_request_context,
+ const FileSystemURL& url, const GURL& blob_url) {
+ return RunOnThread<int64>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoWrite,
+ base::Unretained(this), url_request_context, url, blob_url));
+}
+
+int64 CannedSyncableFileSystem::WriteString(
+ const FileSystemURL& url, const std::string& data) {
+ return RunOnThread<int64>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoWriteString,
+ base::Unretained(this), url, data));
+}
+
+PlatformFileError CannedSyncableFileSystem::DeleteFileSystem() {
+ EXPECT_TRUE(is_filesystem_set_up_);
+ return RunOnThread<PlatformFileError>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&FileSystemContext::DeleteFileSystem,
+ file_system_context_, origin_, type_));
+}
+
+quota::QuotaStatusCode CannedSyncableFileSystem::GetUsageAndQuota(
+ int64* usage, int64* quota) {
+ return RunOnThread<quota::QuotaStatusCode>(
+ io_task_runner_,
+ FROM_HERE,
+ base::Bind(&CannedSyncableFileSystem::DoGetUsageAndQuota,
+ base::Unretained(this), usage, quota));
+}
+
+void CannedSyncableFileSystem::GetChangedURLsInTracker(
+ FileSystemURLSet* urls) {
+ return RunOnThread(
+ file_task_runner_,
+ FROM_HERE,
+ base::Bind(&LocalFileChangeTracker::GetAllChangedURLs,
+ base::Unretained(file_system_context_->change_tracker()),
+ urls));
+}
+
+void CannedSyncableFileSystem::ClearChangeForURLInTracker(
+ const FileSystemURL& url) {
+ return RunOnThread(
+ file_task_runner_,
+ FROM_HERE,
+ base::Bind(&LocalFileChangeTracker::ClearChangesForURL,
+ base::Unretained(file_system_context_->change_tracker()),
+ url));
+}
+
+FileSystemOperation* CannedSyncableFileSystem::NewOperation() {
+ return file_system_context_->CreateFileSystemOperation(URL(std::string()),
+ NULL);
+}
+
+void CannedSyncableFileSystem::OnSyncEnabled(const FileSystemURL& url) {
+ sync_status_observers_->Notify(&LocalFileSyncStatus::Observer::OnSyncEnabled,
+ url);
+}
+
+void CannedSyncableFileSystem::OnWriteEnabled(const FileSystemURL& url) {
+ sync_status_observers_->Notify(&LocalFileSyncStatus::Observer::OnWriteEnabled,
+ url);
+}
+
+void CannedSyncableFileSystem::DoCreateDirectory(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->CreateDirectory(
+ url, false /* exclusive */, false /* recursive */, callback);
+}
+
+void CannedSyncableFileSystem::DoCreateFile(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->CreateFile(url, false /* exclusive */, callback);
+}
+
+void CannedSyncableFileSystem::DoCopy(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->Copy(src_url, dest_url, callback);
+}
+
+void CannedSyncableFileSystem::DoMove(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->Move(src_url, dest_url, callback);
+}
+
+void CannedSyncableFileSystem::DoTruncateFile(
+ const FileSystemURL& url, int64 size,
+ const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->Truncate(url, size, callback);
+}
+
+void CannedSyncableFileSystem::DoTouchFile(
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->TouchFile(url, last_access_time,
+ last_modified_time, callback);
+}
+
+void CannedSyncableFileSystem::DoRemove(
+ const FileSystemURL& url, bool recursive,
+ const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->Remove(url, recursive, callback);
+}
+
+void CannedSyncableFileSystem::DoFileExists(
+ const FileSystemURL& url, const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->FileExists(url, callback);
+}
+
+void CannedSyncableFileSystem::DoDirectoryExists(
+ const FileSystemURL& url, const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->DirectoryExists(url, callback);
+}
+
+void CannedSyncableFileSystem::DoVerifyFile(
+ const FileSystemURL& url,
+ const std::string& expected_data,
+ const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->GetMetadata(
+ url, base::Bind(&OnGetMetadataAndVerifyData,
+ expected_data, callback));
+}
+
+void CannedSyncableFileSystem::DoGetMetadata(
+ const FileSystemURL& url,
+ base::PlatformFileInfo* info,
+ base::FilePath* platform_path,
+ const StatusCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ NewOperation()->GetMetadata(
+ url, base::Bind(&OnGetMetadata, info, platform_path, callback));
+}
+
+void CannedSyncableFileSystem::DoWrite(
+ net::URLRequestContext* url_request_context,
+ const FileSystemURL& url, const GURL& blob_url,
+ const WriteCallback& callback) {
+ EXPECT_TRUE(is_filesystem_opened_);
+ WriteHelper* helper = new WriteHelper;
+ NewOperation()->Write(url_request_context, url, blob_url, 0,
+ base::Bind(&WriteHelper::DidWrite,
+ base::Owned(helper), callback));
+}
+
+void CannedSyncableFileSystem::DoWriteString(
+ const FileSystemURL& url,
+ const std::string& data,
+ const WriteCallback& callback) {
+ MockBlobURLRequestContext* url_request_context(
+ new MockBlobURLRequestContext(file_system_context_));
+ const GURL blob_url(std::string("blob:") + data);
+ WriteHelper* helper = new WriteHelper(url_request_context, blob_url, data);
+ NewOperation()->Write(url_request_context, url, blob_url, 0,
+ base::Bind(&WriteHelper::DidWrite,
+ base::Owned(helper), callback));
+}
+
+void CannedSyncableFileSystem::DoGetUsageAndQuota(
+ int64* usage,
+ int64* quota,
+ const quota::StatusCallback& callback) {
+ quota_manager_->GetUsageAndQuota(
+ origin_, storage_type(),
+ base::Bind(&DidGetUsageAndQuota, callback, usage, quota));
+}
+
+void CannedSyncableFileSystem::DidOpenFileSystem(
+ PlatformFileError result, const std::string& name, const GURL& root) {
+ result_ = result;
+ root_url_ = root;
+ is_filesystem_opened_ = true;
+ base::MessageLoop::current()->Quit();
+}
+
+void CannedSyncableFileSystem::DidInitializeFileSystemContext(
+ SyncStatusCode status) {
+ sync_status_ = status;
+ base::MessageLoop::current()->Quit();
+}
+
+void CannedSyncableFileSystem::InitializeSyncStatusObserver() {
+ ASSERT_TRUE(io_task_runner_->RunsTasksOnCurrentThread());
+ file_system_context_->sync_context()->sync_status()->AddObserver(this);
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/canned_syncable_file_system.h b/webkit/browser/fileapi/syncable/canned_syncable_file_system.h
new file mode 100644
index 0000000..0725b1e
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/canned_syncable_file_system.h
@@ -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.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_CANNED_SYNCABLE_FILE_SYSTEM_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_CANNED_SYNCABLE_FILE_SYSTEM_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop.h"
+#include "base/observer_list_threadsafe.h"
+#include "base/platform_file.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
+#include "webkit/browser/fileapi/syncable/sync_status_code.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/common/fileapi/file_system_util.h"
+#include "webkit/quota/quota_types.h"
+
+namespace base {
+class MessageLoopProxy;
+class SingleThreadTaskRunner;
+class Thread;
+}
+
+namespace fileapi {
+class FileSystemContext;
+class FileSystemOperation;
+class FileSystemURL;
+}
+
+namespace net {
+class URLRequestContext;
+}
+
+namespace quota {
+class QuotaManager;
+}
+
+namespace sync_file_system {
+
+class LocalFileSyncContext;
+
+// A canned syncable filesystem for testing.
+// This internally creates its own QuotaManager and FileSystemContext
+// (as we do so for each isolated application).
+class CannedSyncableFileSystem
+ : public LocalFileSyncStatus::Observer {
+ public:
+ typedef base::Callback<void(base::PlatformFileError)> StatusCallback;
+ typedef base::Callback<void(int64)> WriteCallback;
+
+ CannedSyncableFileSystem(const GURL& origin,
+ const std::string& service,
+ base::SingleThreadTaskRunner* io_task_runner,
+ base::SingleThreadTaskRunner* file_task_runner);
+ virtual ~CannedSyncableFileSystem();
+
+ // SetUp must be called before using this instance.
+ void SetUp();
+
+ // TearDown must be called before destructing this instance.
+ void TearDown();
+
+ // Creates a FileSystemURL for the given (utf8) path string.
+ fileapi::FileSystemURL URL(const std::string& path) const;
+
+ // Initialize this with given |sync_context| if it hasn't
+ // been initialized.
+ sync_file_system::SyncStatusCode MaybeInitializeFileSystemContext(
+ LocalFileSyncContext* sync_context);
+
+ // Opens a new syncable file system.
+ base::PlatformFileError OpenFileSystem();
+
+ // Register sync status observers. Unlike original
+ // LocalFileSyncStatus::Observer implementation the observer methods
+ // are called on the same thread where AddSyncStatusObserver were called.
+ void AddSyncStatusObserver(LocalFileSyncStatus::Observer* observer);
+ void RemoveSyncStatusObserver(LocalFileSyncStatus::Observer* observer);
+
+ // Accessors.
+ fileapi::FileSystemContext* file_system_context() {
+ return file_system_context_.get();
+ }
+ quota::QuotaManager* quota_manager() { return quota_manager_.get(); }
+ GURL origin() const { return origin_; }
+ fileapi::FileSystemType type() const { return type_; }
+ quota::StorageType storage_type() const {
+ return FileSystemTypeToQuotaStorageType(type_);
+ }
+
+ // Helper routines to perform file system operations.
+ // OpenFileSystem() must have been called before calling any of them.
+ // They create an operation and run it on IO task runner, and the operation
+ // posts a task on file runner.
+ base::PlatformFileError CreateDirectory(const fileapi::FileSystemURL& url);
+ base::PlatformFileError CreateFile(const fileapi::FileSystemURL& url);
+ base::PlatformFileError Copy(const fileapi::FileSystemURL& src_url,
+ const fileapi::FileSystemURL& dest_url);
+ base::PlatformFileError Move(const fileapi::FileSystemURL& src_url,
+ const fileapi::FileSystemURL& dest_url);
+ base::PlatformFileError TruncateFile(const fileapi::FileSystemURL& url,
+ int64 size);
+ base::PlatformFileError TouchFile(const fileapi::FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time);
+ base::PlatformFileError Remove(const fileapi::FileSystemURL& url,
+ bool recursive);
+ base::PlatformFileError FileExists(const fileapi::FileSystemURL& url);
+ base::PlatformFileError DirectoryExists(const fileapi::FileSystemURL& url);
+ base::PlatformFileError VerifyFile(const fileapi::FileSystemURL& url,
+ const std::string& expected_data);
+ base::PlatformFileError GetMetadata(const fileapi::FileSystemURL& url,
+ base::PlatformFileInfo* info,
+ base::FilePath* platform_path);
+
+ // Returns the # of bytes written (>=0) or an error code (<0).
+ int64 Write(net::URLRequestContext* url_request_context,
+ const fileapi::FileSystemURL& url, const GURL& blob_url);
+ int64 WriteString(const fileapi::FileSystemURL& url, const std::string& data);
+
+ // Purges the file system local storage.
+ base::PlatformFileError DeleteFileSystem();
+
+ // Retrieves the quota and usage.
+ quota::QuotaStatusCode GetUsageAndQuota(int64* usage, int64* quota);
+
+ // ChangeTracker related methods. They run on file task runner.
+ void GetChangedURLsInTracker(fileapi::FileSystemURLSet* urls);
+ void ClearChangeForURLInTracker(const fileapi::FileSystemURL& url);
+
+ // Returns new FileSystemOperation.
+ fileapi::FileSystemOperation* NewOperation();
+
+ // LocalFileSyncStatus::Observer overrides.
+ virtual void OnSyncEnabled(const fileapi::FileSystemURL& url) OVERRIDE;
+ virtual void OnWriteEnabled(const fileapi::FileSystemURL& url) OVERRIDE;
+
+ // Operation methods body.
+ // They can be also called directly if the caller is already on IO thread.
+ void DoCreateDirectory(const fileapi::FileSystemURL& url,
+ const StatusCallback& callback);
+ void DoCreateFile(const fileapi::FileSystemURL& url,
+ const StatusCallback& callback);
+ void DoCopy(const fileapi::FileSystemURL& src_url,
+ const fileapi::FileSystemURL& dest_url,
+ const StatusCallback& callback);
+ void DoMove(const fileapi::FileSystemURL& src_url,
+ const fileapi::FileSystemURL& dest_url,
+ const StatusCallback& callback);
+ void DoTruncateFile(const fileapi::FileSystemURL& url,
+ int64 size,
+ const StatusCallback& callback);
+ void DoTouchFile(const fileapi::FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback);
+ void DoRemove(const fileapi::FileSystemURL& url,
+ bool recursive,
+ const StatusCallback& callback);
+ void DoFileExists(const fileapi::FileSystemURL& url,
+ const StatusCallback& callback);
+ void DoDirectoryExists(const fileapi::FileSystemURL& url,
+ const StatusCallback& callback);
+ void DoVerifyFile(const fileapi::FileSystemURL& url,
+ const std::string& expected_data,
+ const StatusCallback& callback);
+ void DoGetMetadata(const fileapi::FileSystemURL& url,
+ base::PlatformFileInfo* info,
+ base::FilePath* platform_path,
+ const StatusCallback& callback);
+ void DoWrite(net::URLRequestContext* url_request_context,
+ const fileapi::FileSystemURL& url,
+ const GURL& blob_url,
+ const WriteCallback& callback);
+ void DoWriteString(const fileapi::FileSystemURL& url,
+ const std::string& data,
+ const WriteCallback& callback);
+ void DoGetUsageAndQuota(int64* usage,
+ int64* quota,
+ const quota::StatusCallback& callback);
+
+ private:
+ typedef ObserverListThreadSafe<LocalFileSyncStatus::Observer> ObserverList;
+
+ // Callbacks.
+ void DidOpenFileSystem(base::PlatformFileError result,
+ const std::string& name,
+ const GURL& root);
+ void DidInitializeFileSystemContext(sync_file_system::SyncStatusCode status);
+
+ void InitializeSyncStatusObserver();
+
+ base::ScopedTempDir data_dir_;
+ const std::string service_name_;
+
+ scoped_refptr<quota::QuotaManager> quota_manager_;
+ scoped_refptr<fileapi::FileSystemContext> file_system_context_;
+ const GURL origin_;
+ const fileapi::FileSystemType type_;
+ GURL root_url_;
+ base::PlatformFileError result_;
+ sync_file_system::SyncStatusCode sync_status_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
+
+ // Boolean flags mainly for helping debug.
+ bool is_filesystem_set_up_;
+ bool is_filesystem_opened_;
+
+ scoped_refptr<ObserverList> sync_status_observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(CannedSyncableFileSystem);
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_CANNED_SYNCABLE_FILE_SYSTEM_H_
diff --git a/webkit/browser/fileapi/syncable/file_change.cc b/webkit/browser/fileapi/syncable/file_change.cc
new file mode 100644
index 0000000..4795438
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/file_change.cc
@@ -0,0 +1,89 @@
+// 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 <sstream>
+
+#include "base/logging.h"
+#include "base/stringprintf.h"
+#include "webkit/browser/fileapi/syncable/file_change.h"
+
+namespace sync_file_system {
+
+FileChange::FileChange(
+ ChangeType change,
+ SyncFileType file_type)
+ : change_(change),
+ file_type_(file_type) {}
+
+std::string FileChange::DebugString() const {
+ const char* change_string = NULL;
+ switch (change()) {
+ case FILE_CHANGE_ADD_OR_UPDATE:
+ change_string = "ADD_OR_UPDATE";
+ break;
+ case FILE_CHANGE_DELETE:
+ change_string = "DELETE";
+ break;
+ }
+ const char* type_string = "UNKNOWN";
+ switch (file_type()) {
+ case SYNC_FILE_TYPE_FILE:
+ type_string = "FILE";
+ break;
+ case SYNC_FILE_TYPE_DIRECTORY:
+ type_string = "DIRECTORY";
+ break;
+ case SYNC_FILE_TYPE_UNKNOWN:
+ type_string = "UNKNOWN";
+ break;
+ }
+ return base::StringPrintf("%s:%s", change_string, type_string);
+}
+
+FileChangeList::FileChangeList() {}
+FileChangeList::~FileChangeList() {}
+
+void FileChangeList::Update(const FileChange& new_change) {
+ if (list_.empty()) {
+ list_.push_back(new_change);
+ return;
+ }
+
+ FileChange& last = list_.back();
+ if (last.IsFile() != new_change.IsFile()) {
+ list_.push_back(new_change);
+ return;
+ }
+
+ if (last.change() == new_change.change())
+ return;
+
+ // ADD + DELETE on directory -> revert
+ if (!last.IsFile() && last.IsAddOrUpdate() && new_change.IsDelete()) {
+ list_.pop_back();
+ return;
+ }
+
+ // DELETE + ADD/UPDATE -> ADD/UPDATE
+ // ADD/UPDATE + DELETE -> DELETE
+ last = new_change;
+}
+
+FileChangeList FileChangeList::PopAndGetNewList() const {
+ FileChangeList changes;
+ changes.list_ = this->list_;
+ changes.list_.pop_front();
+ return changes;
+}
+
+std::string FileChangeList::DebugString() const {
+ std::ostringstream ss;
+ ss << "{ ";
+ for (size_t i = 0; i < list_.size(); ++i)
+ ss << list_[i].DebugString() << ", ";
+ ss << "}";
+ return ss.str();
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/file_change.h b/webkit/browser/fileapi/syncable/file_change.h
new file mode 100644
index 0000000..9afc623
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/file_change.h
@@ -0,0 +1,77 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_FILE_CHANGE_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_FILE_CHANGE_H_
+
+#include <deque>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/files/file_path.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/syncable/sync_file_type.h"
+#include "webkit/storage/webkit_storage_export.h"
+
+namespace sync_file_system {
+
+class WEBKIT_STORAGE_EXPORT FileChange {
+ public:
+ enum ChangeType {
+ FILE_CHANGE_ADD_OR_UPDATE,
+ FILE_CHANGE_DELETE,
+ };
+
+ FileChange(ChangeType change, SyncFileType file_type);
+
+ bool IsAddOrUpdate() const { return change_ == FILE_CHANGE_ADD_OR_UPDATE; }
+ bool IsDelete() const { return change_ == FILE_CHANGE_DELETE; }
+
+ bool IsFile() const { return file_type_ == SYNC_FILE_TYPE_FILE; }
+ bool IsDirectory() const { return file_type_ == SYNC_FILE_TYPE_DIRECTORY; }
+ bool IsTypeUnknown() const { return !IsFile() && !IsDirectory(); }
+
+ ChangeType change() const { return change_; }
+ SyncFileType file_type() const { return file_type_; }
+
+ std::string DebugString() const;
+
+ bool operator==(const FileChange& that) const {
+ return change() == that.change() &&
+ file_type() == that.file_type();
+ }
+
+ private:
+ ChangeType change_;
+ SyncFileType file_type_;
+};
+
+class WEBKIT_STORAGE_EXPORT FileChangeList {
+ public:
+ typedef std::deque<FileChange> List;
+
+ FileChangeList();
+ ~FileChangeList();
+
+ // Updates the list with the |new_change|.
+ void Update(const FileChange& new_change);
+
+ size_t size() const { return list_.size(); }
+ bool empty() const { return list_.empty(); }
+ void clear() { list_.clear(); }
+ const List& list() const { return list_; }
+ const FileChange& front() const { return list_.front(); }
+ const FileChange& back() const { return list_.back(); }
+
+ FileChangeList PopAndGetNewList() const;
+
+ std::string DebugString() const;
+
+ private:
+ List list_;
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_FILE_CHANGE_H_
diff --git a/webkit/browser/fileapi/syncable/file_change_unittest.cc b/webkit/browser/fileapi/syncable/file_change_unittest.cc
new file mode 100644
index 0000000..23f4e5a
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/file_change_unittest.cc
@@ -0,0 +1,136 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/file_change.h"
+
+#include "base/basictypes.h"
+#include "base/logging.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace sync_file_system {
+
+namespace {
+
+FileChange AddOrUpdateFile() {
+ return FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE);
+}
+
+FileChange DeleteFile() {
+ return FileChange(FileChange::FILE_CHANGE_DELETE, SYNC_FILE_TYPE_FILE);
+}
+
+FileChange AddDirectory() {
+ return FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_DIRECTORY);
+}
+
+FileChange DeleteDirectory() {
+ return FileChange(FileChange::FILE_CHANGE_DELETE, SYNC_FILE_TYPE_DIRECTORY);
+}
+
+template <size_t INPUT_SIZE>
+void CreateList(FileChangeList* list, const FileChange (&inputs)[INPUT_SIZE]) {
+ list->clear();
+ for (size_t i = 0; i < INPUT_SIZE; ++i)
+ list->Update(inputs[i]);
+}
+
+template <size_t EXPECTED_SIZE>
+void VerifyList(const FileChangeList& list,
+ const FileChange (&expected)[EXPECTED_SIZE]) {
+ SCOPED_TRACE(testing::Message() << "actual:" << list.DebugString());
+ ASSERT_EQ(EXPECTED_SIZE, list.size());
+ for (size_t i = 0; i < list.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << i << ": "
+ << " expected:" << expected[i].DebugString()
+ << " actual:" << list.list().at(i).DebugString());
+ EXPECT_EQ(expected[i], list.list().at(i));
+ }
+}
+
+} // namespace
+
+TEST(FileChangeListTest, UpdateSimple) {
+ FileChangeList list;
+ const FileChange kInput1[] = { AddOrUpdateFile() };
+ const FileChange kExpected1[] = { AddOrUpdateFile() };
+ CreateList(&list, kInput1);
+ VerifyList(list, kExpected1);
+
+ // AddOrUpdate + Delete -> Delete.
+ const FileChange kInput2[] = { AddOrUpdateFile(), DeleteFile() };
+ const FileChange kExpected2[] = { DeleteFile() };
+ CreateList(&list, kInput2);
+ VerifyList(list, kExpected2);
+
+ // Add + Delete -> empty (directory).
+ const FileChange kInput3[] = { AddDirectory(), DeleteDirectory() };
+ CreateList(&list, kInput3);
+ ASSERT_TRUE(list.empty());
+
+ // Delete + AddOrUpdate -> AddOrUpdate.
+ const FileChange kInput4[] = { DeleteFile(), AddOrUpdateFile() };
+ const FileChange kExpected4[] = { AddOrUpdateFile() };
+ CreateList(&list, kInput4);
+ VerifyList(list, kExpected4);
+
+ // Delete + Add -> Add (directory).
+ const FileChange kInput5[] = { DeleteDirectory(), AddDirectory() };
+ const FileChange kExpected5[] = { AddDirectory() };
+ CreateList(&list, kInput5);
+ VerifyList(list, kExpected5);
+}
+
+TEST(FileChangeListTest, UpdateCombined) {
+ FileChangeList list;
+
+ // Longer ones.
+ const FileChange kInput1[] = {
+ AddOrUpdateFile(),
+ AddOrUpdateFile(),
+ AddOrUpdateFile(),
+ AddOrUpdateFile(),
+ DeleteFile(),
+ AddDirectory(),
+ };
+ const FileChange kExpected1[] = { DeleteFile(), AddDirectory() };
+ CreateList(&list, kInput1);
+ VerifyList(list, kExpected1);
+
+ const FileChange kInput2[] = {
+ AddOrUpdateFile(),
+ DeleteFile(),
+ AddOrUpdateFile(),
+ AddOrUpdateFile(),
+ AddOrUpdateFile(),
+ };
+ const FileChange kExpected2[] = { AddOrUpdateFile() };
+ CreateList(&list, kInput2);
+ VerifyList(list, kExpected2);
+
+ const FileChange kInput3[] = {
+ AddDirectory(),
+ DeleteDirectory(),
+ AddOrUpdateFile(),
+ AddOrUpdateFile(),
+ AddOrUpdateFile(),
+ };
+ const FileChange kExpected3[] = { AddOrUpdateFile() };
+ CreateList(&list, kInput3);
+ VerifyList(list, kExpected3);
+
+ const FileChange kInput4[] = {
+ AddDirectory(),
+ DeleteDirectory(),
+ AddOrUpdateFile(),
+ DeleteFile(),
+ AddOrUpdateFile(),
+ DeleteFile(),
+ };
+ const FileChange kExpected4[] = { DeleteFile() };
+ CreateList(&list, kInput4);
+ VerifyList(list, kExpected4);
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/local_file_change_tracker.cc b/webkit/browser/fileapi/syncable/local_file_change_tracker.cc
new file mode 100644
index 0000000..7e189a1
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_file_change_tracker.cc
@@ -0,0 +1,426 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/local_file_change_tracker.h"
+
+#include <queue>
+
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/stl_util.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using fileapi::FileSystemContext;
+using fileapi::FileSystemFileUtil;
+using fileapi::FileSystemOperationContext;
+using fileapi::FileSystemURL;
+using fileapi::FileSystemURLSet;
+
+namespace sync_file_system {
+
+namespace {
+const base::FilePath::CharType kDatabaseName[] =
+ FILE_PATH_LITERAL("LocalFileChangeTracker");
+const char kMark[] = "d";
+} // namespace
+
+// A database class that stores local file changes in a local database. This
+// object must be destructed on file_task_runner.
+class LocalFileChangeTracker::TrackerDB {
+ public:
+ explicit TrackerDB(const base::FilePath& base_path);
+
+ SyncStatusCode MarkDirty(const std::string& url);
+ SyncStatusCode ClearDirty(const std::string& url);
+ SyncStatusCode GetDirtyEntries(
+ std::queue<FileSystemURL>* dirty_files);
+
+ private:
+ enum RecoveryOption {
+ REPAIR_ON_CORRUPTION,
+ FAIL_ON_CORRUPTION,
+ };
+
+ SyncStatusCode Init(RecoveryOption recovery_option);
+ SyncStatusCode Repair(const std::string& db_path);
+ void HandleError(const tracked_objects::Location& from_here,
+ const leveldb::Status& status);
+
+ const base::FilePath base_path_;
+ scoped_ptr<leveldb::DB> db_;
+ SyncStatusCode db_status_;
+
+ DISALLOW_COPY_AND_ASSIGN(TrackerDB);
+};
+
+LocalFileChangeTracker::ChangeInfo::ChangeInfo() : change_seq(-1) {}
+LocalFileChangeTracker::ChangeInfo::~ChangeInfo() {}
+
+// LocalFileChangeTracker ------------------------------------------------------
+
+LocalFileChangeTracker::LocalFileChangeTracker(
+ const base::FilePath& base_path,
+ base::SequencedTaskRunner* file_task_runner)
+ : initialized_(false),
+ file_task_runner_(file_task_runner),
+ tracker_db_(new TrackerDB(base_path)),
+ current_change_seq_(0),
+ num_changes_(0) {
+}
+
+LocalFileChangeTracker::~LocalFileChangeTracker() {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ tracker_db_.reset();
+}
+
+void LocalFileChangeTracker::OnStartUpdate(const FileSystemURL& url) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ if (ContainsKey(changes_, url))
+ return;
+ // TODO(nhiroki): propagate the error code (see http://crbug.com/152127).
+ MarkDirtyOnDatabase(url);
+}
+
+void LocalFileChangeTracker::OnEndUpdate(const FileSystemURL& url) {}
+
+void LocalFileChangeTracker::OnCreateFile(const FileSystemURL& url) {
+ RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_FILE));
+}
+
+void LocalFileChangeTracker::OnCreateFileFrom(const FileSystemURL& url,
+ const FileSystemURL& src) {
+ RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_FILE));
+}
+
+void LocalFileChangeTracker::OnRemoveFile(const FileSystemURL& url) {
+ RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
+ SYNC_FILE_TYPE_FILE));
+}
+
+void LocalFileChangeTracker::OnModifyFile(const FileSystemURL& url) {
+ RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_FILE));
+}
+
+void LocalFileChangeTracker::OnCreateDirectory(const FileSystemURL& url) {
+ RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_DIRECTORY));
+}
+
+void LocalFileChangeTracker::OnRemoveDirectory(const FileSystemURL& url) {
+ RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
+ SYNC_FILE_TYPE_DIRECTORY));
+}
+
+void LocalFileChangeTracker::GetNextChangedURLs(
+ std::deque<FileSystemURL>* urls, int max_urls) {
+ DCHECK(urls);
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ urls->clear();
+ // Mildly prioritizes the URLs that older changes and have not been updated
+ // for a while.
+ for (ChangeSeqMap::iterator iter = change_seqs_.begin();
+ iter != change_seqs_.end() &&
+ (max_urls == 0 || urls->size() < static_cast<size_t>(max_urls));
+ ++iter) {
+ urls->push_back(iter->second);
+ }
+}
+
+void LocalFileChangeTracker::GetChangesForURL(
+ const FileSystemURL& url, FileChangeList* changes) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(changes);
+ changes->clear();
+ FileChangeMap::iterator found = changes_.find(url);
+ if (found == changes_.end())
+ return;
+ *changes = found->second.change_list;
+}
+
+void LocalFileChangeTracker::ClearChangesForURL(const FileSystemURL& url) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ // TODO(nhiroki): propagate the error code (see http://crbug.com/152127).
+ ClearDirtyOnDatabase(url);
+
+ FileChangeMap::iterator found = changes_.find(url);
+ if (found == changes_.end())
+ return;
+ change_seqs_.erase(found->second.change_seq);
+ changes_.erase(found);
+ UpdateNumChanges();
+}
+
+SyncStatusCode LocalFileChangeTracker::Initialize(
+ FileSystemContext* file_system_context) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(!initialized_);
+ DCHECK(file_system_context);
+
+ SyncStatusCode status = CollectLastDirtyChanges(file_system_context);
+ if (status == SYNC_STATUS_OK)
+ initialized_ = true;
+ return status;
+}
+
+void LocalFileChangeTracker::UpdateNumChanges() {
+ base::AutoLock lock(num_changes_lock_);
+ num_changes_ = static_cast<int64>(change_seqs_.size());
+}
+
+void LocalFileChangeTracker::GetAllChangedURLs(FileSystemURLSet* urls) {
+ std::deque<FileSystemURL> url_deque;
+ GetNextChangedURLs(&url_deque, 0);
+ urls->clear();
+ urls->insert(url_deque.begin(), url_deque.end());
+}
+
+void LocalFileChangeTracker::DropAllChanges() {
+ changes_.clear();
+ change_seqs_.clear();
+}
+
+SyncStatusCode LocalFileChangeTracker::MarkDirtyOnDatabase(
+ const FileSystemURL& url) {
+ std::string serialized_url;
+ if (!SerializeSyncableFileSystemURL(url, &serialized_url))
+ return SYNC_FILE_ERROR_INVALID_URL;
+
+ return tracker_db_->MarkDirty(serialized_url);
+}
+
+SyncStatusCode LocalFileChangeTracker::ClearDirtyOnDatabase(
+ const FileSystemURL& url) {
+ std::string serialized_url;
+ if (!SerializeSyncableFileSystemURL(url, &serialized_url))
+ return SYNC_FILE_ERROR_INVALID_URL;
+
+ return tracker_db_->ClearDirty(serialized_url);
+}
+
+SyncStatusCode LocalFileChangeTracker::CollectLastDirtyChanges(
+ FileSystemContext* file_system_context) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+
+ std::queue<FileSystemURL> dirty_files;
+ const SyncStatusCode status = tracker_db_->GetDirtyEntries(&dirty_files);
+ if (status != SYNC_STATUS_OK)
+ return status;
+
+ FileSystemFileUtil* file_util =
+ file_system_context->GetFileUtil(fileapi::kFileSystemTypeSyncable);
+ DCHECK(file_util);
+ scoped_ptr<FileSystemOperationContext> context(
+ new FileSystemOperationContext(file_system_context));
+
+ base::PlatformFileInfo file_info;
+ base::FilePath platform_path;
+
+ while (!dirty_files.empty()) {
+ const FileSystemURL url = dirty_files.front();
+ dirty_files.pop();
+ DCHECK_EQ(url.type(), fileapi::kFileSystemTypeSyncable);
+
+ switch (file_util->GetFileInfo(context.get(), url,
+ &file_info, &platform_path)) {
+ case base::PLATFORM_FILE_OK: {
+ if (!file_info.is_directory) {
+ RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_FILE));
+ break;
+ }
+
+ RecordChange(url, FileChange(
+ FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_DIRECTORY));
+
+ // Push files and directories in this directory into |dirty_files|.
+ scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator(
+ file_util->CreateFileEnumerator(context.get(), url));
+ base::FilePath path_each;
+ while (!(path_each = enumerator->Next()).empty()) {
+ dirty_files.push(CreateSyncableFileSystemURL(
+ url.origin(), url.filesystem_id(), path_each));
+ }
+ break;
+ }
+ case base::PLATFORM_FILE_ERROR_NOT_FOUND: {
+ // File represented by |url| has already been deleted. Since we cannot
+ // figure out if this file was directory or not from the URL, file
+ // type is treated as SYNC_FILE_TYPE_UNKNOWN.
+ //
+ // NOTE: Directory to have been reverted (that is, ADD -> DELETE) is
+ // also treated as FILE_CHANGE_DELETE.
+ RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
+ SYNC_FILE_TYPE_UNKNOWN));
+ break;
+ }
+ case base::PLATFORM_FILE_ERROR_FAILED:
+ default:
+ // TODO(nhiroki): handle file access error (http://crbug.com/155251).
+ LOG(WARNING) << "Failed to access local file.";
+ break;
+ }
+ }
+ return SYNC_STATUS_OK;
+}
+
+void LocalFileChangeTracker::RecordChange(
+ const FileSystemURL& url, const FileChange& change) {
+ DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
+ ChangeInfo& info = changes_[url];
+ if (info.change_seq >= 0)
+ change_seqs_.erase(info.change_seq);
+ info.change_list.Update(change);
+ if (info.change_list.empty()) {
+ changes_.erase(url);
+ UpdateNumChanges();
+ return;
+ }
+ info.change_seq = current_change_seq_++;
+ change_seqs_[info.change_seq] = url;
+ UpdateNumChanges();
+}
+
+// TrackerDB -------------------------------------------------------------------
+
+LocalFileChangeTracker::TrackerDB::TrackerDB(const base::FilePath& base_path)
+ : base_path_(base_path),
+ db_status_(SYNC_STATUS_OK) {}
+
+SyncStatusCode LocalFileChangeTracker::TrackerDB::Init(
+ RecoveryOption recovery_option) {
+ if (db_.get() && db_status_ == SYNC_STATUS_OK)
+ return SYNC_STATUS_OK;
+
+ std::string path = fileapi::FilePathToString(
+ base_path_.Append(kDatabaseName));
+ leveldb::Options options;
+ options.create_if_missing = true;
+ leveldb::DB* db;
+ leveldb::Status status = leveldb::DB::Open(options, path, &db);
+ if (status.ok()) {
+ db_.reset(db);
+ return SYNC_STATUS_OK;
+ }
+
+ HandleError(FROM_HERE, status);
+ if (!status.IsCorruption())
+ return LevelDBStatusToSyncStatusCode(status);
+
+ // Try to repair the corrupted DB.
+ switch (recovery_option) {
+ case FAIL_ON_CORRUPTION:
+ return SYNC_DATABASE_ERROR_CORRUPTION;
+ case REPAIR_ON_CORRUPTION:
+ return Repair(path);
+ }
+ NOTREACHED();
+ return SYNC_DATABASE_ERROR_FAILED;
+}
+
+SyncStatusCode LocalFileChangeTracker::TrackerDB::Repair(
+ const std::string& db_path) {
+ DCHECK(!db_.get());
+ LOG(WARNING) << "Attempting to repair TrackerDB.";
+
+ if (leveldb::RepairDB(db_path, leveldb::Options()).ok() &&
+ Init(FAIL_ON_CORRUPTION) == SYNC_STATUS_OK) {
+ // TODO(nhiroki): perform some consistency checks between TrackerDB and
+ // syncable file system.
+ LOG(WARNING) << "Repairing TrackerDB completed.";
+ return SYNC_STATUS_OK;
+ }
+
+ LOG(WARNING) << "Failed to repair TrackerDB.";
+ return SYNC_DATABASE_ERROR_CORRUPTION;
+}
+
+// TODO(nhiroki): factor out the common methods into somewhere else.
+void LocalFileChangeTracker::TrackerDB::HandleError(
+ const tracked_objects::Location& from_here,
+ const leveldb::Status& status) {
+ LOG(ERROR) << "LocalFileChangeTracker::TrackerDB failed at: "
+ << from_here.ToString() << " with error: " << status.ToString();
+}
+
+SyncStatusCode LocalFileChangeTracker::TrackerDB::MarkDirty(
+ const std::string& url) {
+ if (db_status_ != SYNC_STATUS_OK)
+ return db_status_;
+
+ db_status_ = Init(REPAIR_ON_CORRUPTION);
+ if (db_status_ != SYNC_STATUS_OK) {
+ db_.reset();
+ return db_status_;
+ }
+
+ leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark);
+ if (!status.ok()) {
+ HandleError(FROM_HERE, status);
+ db_status_ = LevelDBStatusToSyncStatusCode(status);
+ db_.reset();
+ return db_status_;
+ }
+ return SYNC_STATUS_OK;
+}
+
+SyncStatusCode LocalFileChangeTracker::TrackerDB::ClearDirty(
+ const std::string& url) {
+ if (db_status_ != SYNC_STATUS_OK)
+ return db_status_;
+
+ // Should not reach here before initializing the database. The database should
+ // be cleared after read, and should be initialized during read if
+ // uninitialized.
+ DCHECK(db_.get());
+
+ leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url);
+ if (!status.ok() && !status.IsNotFound()) {
+ HandleError(FROM_HERE, status);
+ db_status_ = LevelDBStatusToSyncStatusCode(status);
+ db_.reset();
+ return db_status_;
+ }
+ return SYNC_STATUS_OK;
+}
+
+SyncStatusCode LocalFileChangeTracker::TrackerDB::GetDirtyEntries(
+ std::queue<FileSystemURL>* dirty_files) {
+ if (db_status_ != SYNC_STATUS_OK)
+ return db_status_;
+
+ db_status_ = Init(REPAIR_ON_CORRUPTION);
+ if (db_status_ != SYNC_STATUS_OK) {
+ db_.reset();
+ return db_status_;
+ }
+
+ scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
+ iter->SeekToFirst();
+ FileSystemURL url;
+ while (iter->Valid()) {
+ if (!DeserializeSyncableFileSystemURL(iter->key().ToString(), &url)) {
+ LOG(WARNING) << "Failed to deserialize an URL. "
+ << "TrackerDB might be corrupted.";
+ db_status_ = SYNC_DATABASE_ERROR_CORRUPTION;
+ db_.reset();
+ return db_status_;
+ }
+ dirty_files->push(url);
+ iter->Next();
+ }
+ return SYNC_STATUS_OK;
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/local_file_change_tracker.h b/webkit/browser/fileapi/syncable/local_file_change_tracker.h
new file mode 100644
index 0000000..6d58cf2
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_file_change_tracker.h
@@ -0,0 +1,150 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_FILE_CHANGE_TRACKER_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_FILE_CHANGE_TRACKER_H_
+
+#include <deque>
+#include <map>
+#include <string>
+
+#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/synchronization/lock.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/syncable/file_change.h"
+#include "webkit/browser/fileapi/syncable/sync_status_code.h"
+#include "webkit/storage/webkit_storage_export.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace fileapi {
+class FileSystemContext;
+class FileSystemURL;
+}
+
+namespace sync_file_system {
+
+// Tracks local file changes for cloud-backed file systems.
+// All methods must be called on the file_task_runner given to the constructor.
+// Owned by FileSystemContext.
+class WEBKIT_STORAGE_EXPORT LocalFileChangeTracker
+ : public fileapi::FileUpdateObserver,
+ public fileapi::FileChangeObserver {
+ public:
+ // |file_task_runner| must be the one where the observee file operations run.
+ // (So that we can make sure DB operations are done before actual update
+ // happens)
+ LocalFileChangeTracker(const base::FilePath& base_path,
+ base::SequencedTaskRunner* file_task_runner);
+ virtual ~LocalFileChangeTracker();
+
+ // FileUpdateObserver overrides.
+ virtual void OnStartUpdate(const fileapi::FileSystemURL& url) OVERRIDE;
+ virtual void OnUpdate(
+ const fileapi::FileSystemURL& url, int64 delta) OVERRIDE {}
+ virtual void OnEndUpdate(const fileapi::FileSystemURL& url) OVERRIDE;
+
+ // FileChangeObserver overrides.
+ virtual void OnCreateFile(const fileapi::FileSystemURL& url) OVERRIDE;
+ virtual void OnCreateFileFrom(const fileapi::FileSystemURL& url,
+ const fileapi::FileSystemURL& src) OVERRIDE;
+ virtual void OnRemoveFile(const fileapi::FileSystemURL& url) OVERRIDE;
+ virtual void OnModifyFile(const fileapi::FileSystemURL& url) OVERRIDE;
+ virtual void OnCreateDirectory(const fileapi::FileSystemURL& url) OVERRIDE;
+ virtual void OnRemoveDirectory(const fileapi::FileSystemURL& url) OVERRIDE;
+
+ // Retrieves an array of |url| which have more than one pending changes.
+ // If |max_urls| is non-zero (recommended in production code) this
+ // returns URLs up to the number from the ones that have smallest
+ // change_seq numbers (i.e. older changes).
+ void GetNextChangedURLs(std::deque<fileapi::FileSystemURL>* urls,
+ int max_urls);
+
+ // Returns all changes recorded for the given |url|.
+ // This should be called after writing is disabled.
+ void GetChangesForURL(const fileapi::FileSystemURL& url,
+ FileChangeList* changes);
+
+ // Clears the pending changes recorded in this tracker for |url|.
+ void ClearChangesForURL(const fileapi::FileSystemURL& url);
+
+ // Called by FileSyncService at the startup time to restore last dirty changes
+ // left after the last shutdown (if any).
+ SyncStatusCode Initialize(fileapi::FileSystemContext* file_system_context);
+
+ // This method is (exceptionally) thread-safe.
+ int64 num_changes() const {
+ base::AutoLock lock(num_changes_lock_);
+ return num_changes_;
+ }
+
+ void UpdateNumChanges();
+
+ private:
+ class TrackerDB;
+ friend class CannedSyncableFileSystem;
+ friend class LocalFileChangeTrackerTest;
+ friend class LocalFileSyncContext;
+ friend class SyncableFileSystemTest;
+
+ struct ChangeInfo {
+ ChangeInfo();
+ ~ChangeInfo();
+ FileChangeList change_list;
+ int64 change_seq;
+ };
+
+ typedef std::map<fileapi::FileSystemURL, ChangeInfo,
+ fileapi::FileSystemURL::Comparator>
+ FileChangeMap;
+ typedef std::map<int64, fileapi::FileSystemURL> ChangeSeqMap;
+
+ // This does mostly same as calling GetNextChangedURLs with max_url=0
+ // except that it returns urls in set rather than in deque.
+ // Used only in testings.
+ void GetAllChangedURLs(fileapi::FileSystemURLSet* urls);
+
+ // Used only in testings.
+ void DropAllChanges();
+
+ // Database related methods.
+ SyncStatusCode MarkDirtyOnDatabase(const fileapi::FileSystemURL& url);
+ SyncStatusCode ClearDirtyOnDatabase(const fileapi::FileSystemURL& url);
+
+ SyncStatusCode CollectLastDirtyChanges(
+ fileapi::FileSystemContext* file_system_context);
+ void RecordChange(const fileapi::FileSystemURL& url,
+ const FileChange& change);
+
+ bool initialized_;
+
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+
+ FileChangeMap changes_;
+ ChangeSeqMap change_seqs_;
+
+ scoped_ptr<TrackerDB> tracker_db_;
+
+ // Change sequence number. Briefly gives a hint about the order of changes,
+ // but they are updated when a new change comes on the same file (as
+ // well as Drive's changestamps).
+ int64 current_change_seq_;
+
+ // This can be accessed on any threads (with num_changes_lock_).
+ int64 num_changes_;
+ mutable base::Lock num_changes_lock_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalFileChangeTracker);
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_FILE_CHANGE_TRACKER_H_
diff --git a/webkit/browser/fileapi/syncable/local_file_change_tracker_unittest.cc b/webkit/browser/fileapi/syncable/local_file_change_tracker_unittest.cc
new file mode 100644
index 0000000..817438c
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_file_change_tracker_unittest.cc
@@ -0,0 +1,602 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/local_file_change_tracker.h"
+
+#include <deque>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/blob/mock_blob_url_request_context.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_task_runners.h"
+#include "webkit/browser/fileapi/syncable/canned_syncable_file_system.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_context.h"
+#include "webkit/browser/fileapi/syncable/sync_status_code.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+#include "webkit/quota/quota_manager.h"
+
+using fileapi::FileSystemContext;
+using fileapi::FileSystemURL;
+using fileapi::FileSystemURLSet;
+using webkit_blob::MockBlobURLRequestContext;
+using webkit_blob::ScopedTextBlob;
+
+namespace sync_file_system {
+
+class LocalFileChangeTrackerTest : public testing::Test {
+ public:
+ LocalFileChangeTrackerTest()
+ : message_loop_(base::MessageLoop::TYPE_IO),
+ file_system_(GURL("http://example.com"),
+ "test",
+ base::MessageLoopProxy::current(),
+ base::MessageLoopProxy::current()) {}
+
+ virtual void SetUp() OVERRIDE {
+ file_system_.SetUp();
+
+ sync_context_ = new LocalFileSyncContext(base::MessageLoopProxy::current(),
+ base::MessageLoopProxy::current());
+ ASSERT_EQ(sync_file_system::SYNC_STATUS_OK,
+ file_system_.MaybeInitializeFileSystemContext(sync_context_));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (sync_context_)
+ sync_context_->ShutdownOnUIThread();
+ sync_context_ = NULL;
+
+ message_loop_.RunUntilIdle();
+ file_system_.TearDown();
+ // Make sure we don't leave the external filesystem.
+ // (CannedSyncableFileSystem::TearDown does not do this as there may be
+ // multiple syncable file systems registered for the name)
+ RevokeSyncableFileSystem("test");
+ }
+
+ protected:
+ FileSystemURL URL(const std::string& path) {
+ return file_system_.URL(path);
+ }
+
+ FileSystemContext* file_system_context() {
+ return file_system_.file_system_context();
+ }
+
+ LocalFileChangeTracker* change_tracker() {
+ return file_system_context()->change_tracker();
+ }
+
+ void VerifyAndClearChange(const FileSystemURL& url,
+ const FileChange& expected_change) {
+ SCOPED_TRACE(testing::Message() << url.DebugString() <<
+ " expecting:" << expected_change.DebugString());
+ // Get the changes for URL and verify.
+ FileChangeList changes;
+ change_tracker()->GetChangesForURL(url, &changes);
+ ASSERT_EQ(1U, changes.size());
+ SCOPED_TRACE(testing::Message() << url.DebugString() <<
+ " actual:" << changes.DebugString());
+ EXPECT_EQ(expected_change, changes.list()[0]);
+
+ // Clear the URL from the change tracker.
+ change_tracker()->ClearChangesForURL(url);
+ }
+
+ void DropChangesInTracker() {
+ change_tracker()->DropAllChanges();
+ }
+
+ void RestoreChangesFromTrackerDB() {
+ change_tracker()->CollectLastDirtyChanges(file_system_context());
+ }
+
+ base::MessageLoop message_loop_;
+
+ CannedSyncableFileSystem file_system_;
+ scoped_refptr<LocalFileSyncContext> sync_context_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalFileChangeTrackerTest);
+};
+
+TEST_F(LocalFileChangeTrackerTest, GetChanges) {
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.OpenFileSystem());
+
+ // Test URLs (no parent/child relationships, as we test such cases
+ // mainly in LocalFileSyncStatusTest).
+ const char kPath0[] = "test/dir a/dir";
+ const char kPath1[] = "test/dir b";
+ const char kPath2[] = "test/foo.txt";
+ const char kPath3[] = "test/bar";
+ const char kPath4[] = "temporary/dir a";
+ const char kPath5[] = "temporary/foo";
+
+ change_tracker()->OnCreateDirectory(URL(kPath0));
+ change_tracker()->OnRemoveDirectory(URL(kPath0)); // Offset the create.
+ change_tracker()->OnRemoveDirectory(URL(kPath1));
+ change_tracker()->OnCreateDirectory(URL(kPath2));
+ change_tracker()->OnRemoveFile(URL(kPath3));
+ change_tracker()->OnModifyFile(URL(kPath4));
+ change_tracker()->OnCreateFile(URL(kPath5));
+ change_tracker()->OnRemoveFile(URL(kPath5)); // Recorded as 'delete'.
+
+ FileSystemURLSet urls;
+ file_system_.GetChangedURLsInTracker(&urls);
+
+ EXPECT_EQ(5U, urls.size());
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath1)));
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath2)));
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath3)));
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath4)));
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath5)));
+
+ // Changes for kPath0 must have been offset and removed.
+ EXPECT_FALSE(ContainsKey(urls, URL(kPath0)));
+
+ // GetNextChangedURLs only returns up to max_urls (i.e. 3) urls.
+ std::deque<FileSystemURL> urls_to_process;
+ change_tracker()->GetNextChangedURLs(&urls_to_process, 3);
+ ASSERT_EQ(3U, urls_to_process.size());
+
+ // Let it return all.
+ urls_to_process.clear();
+ change_tracker()->GetNextChangedURLs(&urls_to_process, 0);
+ ASSERT_EQ(5U, urls_to_process.size());
+
+ // The changes must be in the last-modified-time order.
+ EXPECT_EQ(URL(kPath1), urls_to_process[0]);
+ EXPECT_EQ(URL(kPath2), urls_to_process[1]);
+ EXPECT_EQ(URL(kPath3), urls_to_process[2]);
+ EXPECT_EQ(URL(kPath4), urls_to_process[3]);
+ EXPECT_EQ(URL(kPath5), urls_to_process[4]);
+
+ // Modify kPath4 again.
+ change_tracker()->OnModifyFile(URL(kPath4));
+
+ // Now the order must be changed.
+ urls_to_process.clear();
+ change_tracker()->GetNextChangedURLs(&urls_to_process, 0);
+ ASSERT_EQ(5U, urls_to_process.size());
+ EXPECT_EQ(URL(kPath1), urls_to_process[0]);
+ EXPECT_EQ(URL(kPath2), urls_to_process[1]);
+ EXPECT_EQ(URL(kPath3), urls_to_process[2]);
+ EXPECT_EQ(URL(kPath5), urls_to_process[3]);
+ EXPECT_EQ(URL(kPath4), urls_to_process[4]);
+
+ VerifyAndClearChange(URL(kPath1),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath2),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath3),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+ VerifyAndClearChange(URL(kPath4),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+ VerifyAndClearChange(URL(kPath5),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+}
+
+TEST_F(LocalFileChangeTrackerTest, RestoreCreateAndModifyChanges) {
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.OpenFileSystem());
+
+ FileSystemURLSet urls;
+
+ const char kPath0[] = "file a";
+ const char kPath1[] = "dir a";
+ const char kPath2[] = "dir a/dir";
+ const char kPath3[] = "dir a/file a";
+ const char kPath4[] = "dir a/file b";
+
+ file_system_.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(0U, urls.size());
+
+ const GURL blob_url("blob:test");
+ const std::string kData("Lorem ipsum.");
+ MockBlobURLRequestContext url_request_context(file_system_context());
+ ScopedTextBlob blob(url_request_context, blob_url, kData);
+
+ // Create files and nested directories.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath0))); // Creates a file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath1))); // Creates a dir.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath2))); // Creates another dir.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath3))); // Creates a file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.TruncateFile(URL(kPath3), 1)); // Modifies the file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath4))); // Creates another file.
+ EXPECT_EQ(static_cast<int64>(kData.size()),
+ file_system_.Write(&url_request_context,
+ URL(kPath4), blob_url)); // Modifies the file.
+
+ // Verify the changes.
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_EQ(5U, urls.size());
+
+ // Reset the changes in in-memory tracker.
+ DropChangesInTracker();
+
+ // Make sure we have no in-memory changes in the tracker.
+ file_system_.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(0U, urls.size());
+
+ RestoreChangesFromTrackerDB();
+
+ // Make sure the changes are restored from the DB.
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_EQ(5U, urls.size());
+
+ VerifyAndClearChange(URL(kPath0),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+ VerifyAndClearChange(URL(kPath1),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath2),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath3),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+ VerifyAndClearChange(URL(kPath4),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+}
+
+TEST_F(LocalFileChangeTrackerTest, RestoreRemoveChanges) {
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.OpenFileSystem());
+
+ FileSystemURLSet urls;
+
+ const char kPath0[] = "file";
+ const char kPath1[] = "dir a";
+ const char kPath2[] = "dir b";
+ const char kPath3[] = "dir b/file";
+ const char kPath4[] = "dir b/dir c";
+ const char kPath5[] = "dir b/dir c/file";
+
+ file_system_.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(0U, urls.size());
+
+ // Creates and removes a same file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath0)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Remove(URL(kPath0), false /* recursive */));
+
+ // Creates and removes a same directory.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath1)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Remove(URL(kPath1), false /* recursive */));
+
+ // Creates files and nested directories, then removes the parent directory.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath2)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath3)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath4)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath5)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Remove(URL(kPath2), true /* recursive */));
+
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_EQ(3U, urls.size());
+
+ DropChangesInTracker();
+
+ // Make sure we have no in-memory changes in the tracker.
+ file_system_.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(0U, urls.size());
+
+ RestoreChangesFromTrackerDB();
+
+ // Make sure the changes are restored from the DB.
+ file_system_.GetChangedURLsInTracker(&urls);
+ // Since directories to have been reverted (kPath1, kPath2, kPath4) are
+ // treated as FILE_CHANGE_DELETE, the number of changes should be 6.
+ EXPECT_EQ(6U, urls.size());
+
+ VerifyAndClearChange(URL(kPath0),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
+ VerifyAndClearChange(URL(kPath1),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
+ VerifyAndClearChange(URL(kPath2),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
+ VerifyAndClearChange(URL(kPath3),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
+ VerifyAndClearChange(URL(kPath4),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
+ VerifyAndClearChange(URL(kPath5),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
+}
+
+TEST_F(LocalFileChangeTrackerTest, RestoreCopyChanges) {
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.OpenFileSystem());
+
+ FileSystemURLSet urls;
+
+ const char kPath0[] = "file a";
+ const char kPath1[] = "dir a";
+ const char kPath2[] = "dir a/dir";
+ const char kPath3[] = "dir a/file a";
+ const char kPath4[] = "dir a/file b";
+
+ const char kPath0Copy[] = "file b"; // To be copied from kPath0
+ const char kPath1Copy[] = "dir b"; // To be copied from kPath1
+ const char kPath2Copy[] = "dir b/dir"; // To be copied from kPath2
+ const char kPath3Copy[] = "dir b/file a"; // To be copied from kPath3
+ const char kPath4Copy[] = "dir b/file b"; // To be copied from kPath4
+
+ file_system_.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(0U, urls.size());
+
+ const GURL blob_url("blob:test");
+ const std::string kData("Lorem ipsum.");
+ MockBlobURLRequestContext url_request_context(file_system_context());
+ ScopedTextBlob blob(url_request_context, blob_url, kData);
+
+ // Create files and nested directories.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath0))); // Creates a file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath1))); // Creates a dir.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath2))); // Creates another dir.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath3))); // Creates a file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.TruncateFile(URL(kPath3), 1)); // Modifies the file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath4))); // Creates another file.
+ EXPECT_EQ(static_cast<int64>(kData.size()),
+ file_system_.Write(&url_request_context,
+ URL(kPath4), blob_url)); // Modifies the file.
+
+ // Verify we have 5 changes for preparation.
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_EQ(5U, urls.size());
+ change_tracker()->ClearChangesForURL(URL(kPath0));
+ change_tracker()->ClearChangesForURL(URL(kPath1));
+ change_tracker()->ClearChangesForURL(URL(kPath2));
+ change_tracker()->ClearChangesForURL(URL(kPath3));
+ change_tracker()->ClearChangesForURL(URL(kPath4));
+
+ // Make sure we have no changes.
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_TRUE(urls.empty());
+
+ // Copy the file and the parent directory.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Copy(URL(kPath0), URL(kPath0Copy))); // Copy the file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Copy(URL(kPath1), URL(kPath1Copy))); // Copy the dir.
+
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_EQ(5U, urls.size());
+ DropChangesInTracker();
+
+ // Make sure we have no in-memory changes in the tracker.
+ file_system_.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(0U, urls.size());
+
+ RestoreChangesFromTrackerDB();
+
+ // Make sure the changes are restored from the DB.
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_EQ(5U, urls.size());
+
+ VerifyAndClearChange(URL(kPath0Copy),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+ VerifyAndClearChange(URL(kPath1Copy),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath2Copy),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath3Copy),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+ VerifyAndClearChange(URL(kPath4Copy),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+}
+
+TEST_F(LocalFileChangeTrackerTest, RestoreMoveChanges) {
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.OpenFileSystem());
+
+ FileSystemURLSet urls;
+
+ const char kPath0[] = "file a";
+ const char kPath1[] = "dir a";
+ const char kPath2[] = "dir a/file";
+ const char kPath3[] = "dir a/dir";
+ const char kPath4[] = "dir a/dir/file";
+
+ const char kPath5[] = "file b"; // To be moved from kPath0.
+ const char kPath6[] = "dir b"; // To be moved from kPath1.
+ const char kPath7[] = "dir b/file"; // To be moved from kPath2.
+ const char kPath8[] = "dir b/dir"; // To be moved from kPath3.
+ const char kPath9[] = "dir b/dir/file"; // To be moved from kPath4.
+
+ file_system_.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(0U, urls.size());
+
+ // Create files and nested directories.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath0)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath1)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath2)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath3)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath4)));
+
+ // Verify we have 5 changes for preparation.
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_EQ(5U, urls.size());
+ change_tracker()->ClearChangesForURL(URL(kPath0));
+ change_tracker()->ClearChangesForURL(URL(kPath1));
+ change_tracker()->ClearChangesForURL(URL(kPath2));
+ change_tracker()->ClearChangesForURL(URL(kPath3));
+ change_tracker()->ClearChangesForURL(URL(kPath4));
+
+ // Make sure we have no changes.
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_TRUE(urls.empty());
+
+ // Move the file and the parent directory.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Move(URL(kPath0), URL(kPath5)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Move(URL(kPath1), URL(kPath6)));
+
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_EQ(10U, urls.size());
+
+ DropChangesInTracker();
+
+ // Make sure we have no in-memory changes in the tracker.
+ file_system_.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(0U, urls.size());
+
+ RestoreChangesFromTrackerDB();
+
+ // Make sure the changes are restored from the DB.
+ file_system_.GetChangedURLsInTracker(&urls);
+ // Deletion for children in the deleted directory cannot be restored,
+ // so we will only have 7 changes.
+ EXPECT_EQ(7U, urls.size());
+
+ VerifyAndClearChange(URL(kPath0),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
+ VerifyAndClearChange(URL(kPath1),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_UNKNOWN));
+ VerifyAndClearChange(URL(kPath5),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+ VerifyAndClearChange(URL(kPath6),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath7),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+ VerifyAndClearChange(URL(kPath8),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath9),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+}
+
+TEST_F(LocalFileChangeTrackerTest, NextChangedURLsWithRecursiveCopy) {
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.OpenFileSystem());
+
+ FileSystemURLSet urls;
+
+ const char kPath0[] = "dir a";
+ const char kPath1[] = "dir a/file";
+ const char kPath2[] = "dir a/dir";
+
+ const char kPath0Copy[] = "dir b";
+ const char kPath1Copy[] = "dir b/file";
+ const char kPath2Copy[] = "dir b/dir";
+
+ // Creates kPath0,1,2 and then copies them all.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath0)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath1)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath2)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Copy(URL(kPath0), URL(kPath0Copy)));
+
+ std::deque<FileSystemURL> urls_to_process;
+ change_tracker()->GetNextChangedURLs(&urls_to_process, 0);
+ ASSERT_EQ(6U, urls_to_process.size());
+
+ // Creation must have occured first.
+ EXPECT_EQ(URL(kPath0), urls_to_process[0]);
+ EXPECT_EQ(URL(kPath1), urls_to_process[1]);
+ EXPECT_EQ(URL(kPath2), urls_to_process[2]);
+
+ // Then recursive copy took place. The exact order cannot be determined
+ // but the parent directory must have been created first.
+ EXPECT_EQ(URL(kPath0Copy), urls_to_process[3]);
+ EXPECT_TRUE(URL(kPath1Copy) == urls_to_process[4] ||
+ URL(kPath2Copy) == urls_to_process[4]);
+ EXPECT_TRUE(URL(kPath1Copy) == urls_to_process[5] ||
+ URL(kPath2Copy) == urls_to_process[5]);
+}
+
+TEST_F(LocalFileChangeTrackerTest, NextChangedURLsWithRecursiveRemove) {
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.OpenFileSystem());
+
+ FileSystemURLSet urls;
+
+ const char kPath0[] = "dir a";
+ const char kPath1[] = "dir a/file1";
+ const char kPath2[] = "dir a/file2";
+
+ // Creates kPath0,1,2 and then removes them all.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath0)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath1)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath2)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Remove(URL(kPath0), true /* recursive */));
+
+ std::deque<FileSystemURL> urls_to_process;
+ change_tracker()->GetNextChangedURLs(&urls_to_process, 0);
+
+ // This is actually not really desirable, but since the directory
+ // creation and deletion have been offset now we only have two
+ // file deletion changes.
+ //
+ // NOTE: This will cause 2 local sync for deleting nonexistent files
+ // on the remote side.
+ //
+ // TODO(kinuko): For micro optimization we could probably restore the ADD
+ // change type (other than ADD_OR_UPDATE) and offset file ADD+DELETE
+ // changes too.
+ ASSERT_EQ(2U, urls_to_process.size());
+
+ // The exact order of recursive removal cannot be determined.
+ EXPECT_TRUE(URL(kPath1) == urls_to_process[0] ||
+ URL(kPath2) == urls_to_process[0]);
+ EXPECT_TRUE(URL(kPath1) == urls_to_process[1] ||
+ URL(kPath2) == urls_to_process[1]);
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/local_file_sync_context.cc b/webkit/browser/fileapi/syncable/local_file_sync_context.cc
new file mode 100644
index 0000000..9aa434e
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_file_sync_context.cc
@@ -0,0 +1,745 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/local_file_sync_context.h"
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/platform_file.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/task_runner_util.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_task_runners.h"
+#include "webkit/browser/fileapi/local_file_system_operation.h"
+#include "webkit/browser/fileapi/syncable/file_change.h"
+#include "webkit/browser/fileapi/syncable/local_file_change_tracker.h"
+#include "webkit/browser/fileapi/syncable/local_origin_change_observer.h"
+#include "webkit/browser/fileapi/syncable/sync_file_metadata.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_operation_runner.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using fileapi::FileSystemContext;
+using fileapi::FileSystemFileUtil;
+using fileapi::FileSystemOperation;
+using fileapi::FileSystemOperationContext;
+using fileapi::FileSystemURL;
+using fileapi::LocalFileSystemOperation;
+
+namespace sync_file_system {
+
+namespace {
+const int kMaxConcurrentSyncableOperation = 3;
+const int kNotifyChangesDurationInSec = 1;
+const int kMaxURLsToFetchForLocalSync = 5;
+} // namespace
+
+LocalFileSyncContext::LocalFileSyncContext(
+ base::SingleThreadTaskRunner* ui_task_runner,
+ base::SingleThreadTaskRunner* io_task_runner)
+ : ui_task_runner_(ui_task_runner),
+ io_task_runner_(io_task_runner),
+ shutdown_on_ui_(false),
+ mock_notify_changes_duration_in_sec_(-1) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+}
+
+void LocalFileSyncContext::MaybeInitializeFileSystemContext(
+ const GURL& source_url,
+ const std::string& service_name,
+ FileSystemContext* file_system_context,
+ const SyncStatusCallback& callback) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ if (ContainsKey(file_system_contexts_, file_system_context)) {
+ // The context has been already initialized. Just dispatch the callback
+ // with SYNC_STATUS_OK.
+ ui_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ SYNC_STATUS_OK));
+ return;
+ }
+
+ StatusCallbackQueue& callback_queue =
+ pending_initialize_callbacks_[file_system_context];
+ callback_queue.push_back(callback);
+ if (callback_queue.size() > 1)
+ return;
+
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::InitializeFileSystemContextOnIOThread,
+ this, source_url, service_name,
+ make_scoped_refptr(file_system_context)));
+}
+
+void LocalFileSyncContext::ShutdownOnUIThread() {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ shutdown_on_ui_ = true;
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::ShutdownOnIOThread,
+ this));
+}
+
+void LocalFileSyncContext::GetFileForLocalSync(
+ FileSystemContext* file_system_context,
+ const LocalFileSyncInfoCallback& callback) {
+ DCHECK(file_system_context);
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+
+ std::deque<FileSystemURL>* urls = new std::deque<FileSystemURL>;
+ file_system_context->task_runners()->file_task_runner()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::GetNextURLsForSyncOnFileThread,
+ this, make_scoped_refptr(file_system_context),
+ base::Unretained(urls)),
+ base::Bind(&LocalFileSyncContext::TryPrepareForLocalSync,
+ this, make_scoped_refptr(file_system_context),
+ base::Owned(urls), callback));
+}
+
+void LocalFileSyncContext::ClearChangesForURL(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ const base::Closure& done_callback) {
+ // This is initially called on UI thread and to be relayed to FILE thread.
+ DCHECK(file_system_context);
+ if (!file_system_context->task_runners()->file_task_runner()->
+ RunsTasksOnCurrentThread()) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ file_system_context->task_runners()->file_task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::ClearChangesForURL,
+ this, make_scoped_refptr(file_system_context),
+ url, done_callback));
+ return;
+ }
+ DCHECK(file_system_context->change_tracker());
+ file_system_context->change_tracker()->ClearChangesForURL(url);
+
+ // Call the completion callback on UI thread.
+ ui_task_runner_->PostTask(FROM_HERE, done_callback);
+}
+
+void LocalFileSyncContext::ClearSyncFlagForURL(const FileSystemURL& url) {
+ // This is initially called on UI thread and to be relayed to IO thread.
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::EnableWritingOnIOThread,
+ this, url));
+}
+
+void LocalFileSyncContext::PrepareForSync(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ const LocalFileSyncInfoCallback& callback) {
+ // This is initially called on UI thread and to be relayed to IO thread.
+ if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::PrepareForSync, this,
+ make_scoped_refptr(file_system_context), url, callback));
+ return;
+ }
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ const bool syncable = sync_status()->IsSyncable(url);
+ // Disable writing if it's ready to be synced.
+ if (syncable)
+ sync_status()->StartSyncing(url);
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::DidGetWritingStatusForSync,
+ this, make_scoped_refptr(file_system_context),
+ syncable ? SYNC_STATUS_OK :
+ SYNC_STATUS_FILE_BUSY,
+ url, callback));
+}
+
+void LocalFileSyncContext::RegisterURLForWaitingSync(
+ const FileSystemURL& url,
+ const base::Closure& on_syncable_callback) {
+ // This is initially called on UI thread and to be relayed to IO thread.
+ if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::RegisterURLForWaitingSync,
+ this, url, on_syncable_callback));
+ return;
+ }
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ if (sync_status()->IsSyncable(url)) {
+ // No need to register; fire the callback now.
+ ui_task_runner_->PostTask(FROM_HERE, on_syncable_callback);
+ return;
+ }
+ url_waiting_sync_on_io_ = url;
+ url_syncable_callback_ = on_syncable_callback;
+}
+
+void LocalFileSyncContext::ApplyRemoteChange(
+ FileSystemContext* file_system_context,
+ const FileChange& change,
+ const base::FilePath& local_path,
+ const FileSystemURL& url,
+ const SyncStatusCallback& callback) {
+ if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::ApplyRemoteChange, this,
+ make_scoped_refptr(file_system_context),
+ change, local_path, url, callback));
+ return;
+ }
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(!sync_status()->IsWritable(url));
+ DCHECK(!sync_status()->IsWriting(url));
+ LocalFileSystemOperation* operation = CreateFileSystemOperationForSync(
+ file_system_context);
+ DCHECK(operation);
+
+ FileSystemOperation::StatusCallback operation_callback;
+ if (change.change() == FileChange::FILE_CHANGE_ADD_OR_UPDATE) {
+ operation_callback = base::Bind(
+ &LocalFileSyncContext::DidRemoveExistingEntryForApplyRemoteChange,
+ this,
+ make_scoped_refptr(file_system_context),
+ change,
+ local_path,
+ url,
+ callback);
+ } else {
+ DCHECK_EQ(FileChange::FILE_CHANGE_DELETE, change.change());
+ operation_callback = base::Bind(
+ &LocalFileSyncContext::DidApplyRemoteChange, this, url, callback);
+ }
+ operation->Remove(url, true /* recursive */, operation_callback);
+}
+
+void LocalFileSyncContext::DidRemoveExistingEntryForApplyRemoteChange(
+ FileSystemContext* file_system_context,
+ const FileChange& change,
+ const base::FilePath& local_path,
+ const FileSystemURL& url,
+ const SyncStatusCallback& callback,
+ base::PlatformFileError error) {
+ // Remove() may fail if the target entry does not exist (which is ok),
+ // so we ignore |error| here.
+
+ if (!sync_status()) {
+ callback.Run(SYNC_FILE_ERROR_ABORT);
+ return;
+ }
+
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(!sync_status()->IsWritable(url));
+ DCHECK(!sync_status()->IsWriting(url));
+ LocalFileSystemOperation* operation =
+ CreateFileSystemOperationForSync(file_system_context);
+ DCHECK(operation);
+ FileSystemOperation::StatusCallback operation_callback = base::Bind(
+ &LocalFileSyncContext::DidApplyRemoteChange, this, url, callback);
+
+ DCHECK_EQ(FileChange::FILE_CHANGE_ADD_OR_UPDATE, change.change());
+ switch (change.file_type()) {
+ case SYNC_FILE_TYPE_FILE: {
+ DCHECK(!local_path.empty());
+ base::FilePath dir_path = fileapi::VirtualPath::DirName(url.path());
+ if (dir_path.empty() ||
+ fileapi::VirtualPath::DirName(dir_path) == dir_path) {
+ // Copying into the root directory.
+ operation->CopyInForeignFile(local_path, url, operation_callback);
+ } else {
+ FileSystemURL dir_url = file_system_context->CreateCrackedFileSystemURL(
+ url.origin(),
+ url.mount_type(),
+ fileapi::VirtualPath::DirName(url.virtual_path()));
+ operation->CreateDirectory(
+ dir_url,
+ false /* exclusive */,
+ true /* recursive */,
+ base::Bind(&LocalFileSyncContext::DidCreateDirectoryForCopyIn,
+ this,
+ make_scoped_refptr(file_system_context),
+ local_path,
+ url,
+ operation_callback));
+ }
+ break;
+ }
+ case SYNC_FILE_TYPE_DIRECTORY:
+ operation->CreateDirectory(
+ url, false /* exclusive */, true /* recursive */, operation_callback);
+ break;
+ case SYNC_FILE_TYPE_UNKNOWN:
+ NOTREACHED() << "File type unknown for ADD_OR_UPDATE change";
+ }
+}
+
+void LocalFileSyncContext::RecordFakeLocalChange(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ const FileChange& change,
+ const SyncStatusCallback& callback) {
+ // This is called on UI thread and to be relayed to FILE thread.
+ DCHECK(file_system_context);
+ if (!file_system_context->task_runners()->file_task_runner()->
+ RunsTasksOnCurrentThread()) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ file_system_context->task_runners()->file_task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::RecordFakeLocalChange,
+ this, make_scoped_refptr(file_system_context),
+ url, change, callback));
+ return;
+ }
+
+ DCHECK(file_system_context->change_tracker());
+ file_system_context->change_tracker()->MarkDirtyOnDatabase(url);
+ file_system_context->change_tracker()->RecordChange(url, change);
+
+ // Fire the callback on UI thread.
+ ui_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ SYNC_STATUS_OK));
+}
+
+void LocalFileSyncContext::GetFileMetadata(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ const SyncFileMetadataCallback& callback) {
+ // This is initially called on UI thread and to be relayed to IO thread.
+ if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::GetFileMetadata, this,
+ make_scoped_refptr(file_system_context), url, callback));
+ return;
+ }
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ LocalFileSystemOperation* operation = CreateFileSystemOperationForSync(
+ file_system_context);
+ DCHECK(operation);
+ operation->GetMetadata(
+ url, base::Bind(&LocalFileSyncContext::DidGetFileMetadata,
+ this, callback));
+}
+
+void LocalFileSyncContext::HasPendingLocalChanges(
+ FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ const HasPendingLocalChangeCallback& callback) {
+ // This gets called on UI thread and relays the task on FILE thread.
+ DCHECK(file_system_context);
+ if (!file_system_context->task_runners()->file_task_runner()->
+ RunsTasksOnCurrentThread()) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ file_system_context->task_runners()->file_task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::HasPendingLocalChanges,
+ this, make_scoped_refptr(file_system_context),
+ url, callback));
+ return;
+ }
+
+ DCHECK(file_system_context->change_tracker());
+ FileChangeList changes;
+ file_system_context->change_tracker()->GetChangesForURL(url, &changes);
+
+ // Fire the callback on UI thread.
+ ui_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback,
+ SYNC_STATUS_OK,
+ !changes.empty()));
+}
+
+void LocalFileSyncContext::AddOriginChangeObserver(
+ LocalOriginChangeObserver* observer) {
+ origin_change_observers_.AddObserver(observer);
+}
+
+void LocalFileSyncContext::RemoveOriginChangeObserver(
+ LocalOriginChangeObserver* observer) {
+ origin_change_observers_.RemoveObserver(observer);
+}
+
+base::WeakPtr<SyncableFileOperationRunner>
+LocalFileSyncContext::operation_runner() const {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ if (operation_runner_)
+ return operation_runner_->AsWeakPtr();
+ return base::WeakPtr<SyncableFileOperationRunner>();
+}
+
+LocalFileSyncStatus* LocalFileSyncContext::sync_status() const {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ return sync_status_.get();
+}
+
+void LocalFileSyncContext::OnSyncEnabled(const FileSystemURL& url) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ origins_with_pending_changes_.insert(url.origin());
+ ScheduleNotifyChangesUpdatedOnIOThread();
+ if (url_syncable_callback_.is_null() ||
+ sync_status()->IsWriting(url_waiting_sync_on_io_)) {
+ return;
+ }
+ // TODO(kinuko): may want to check how many pending tasks we have.
+ ui_task_runner_->PostTask(FROM_HERE, url_syncable_callback_);
+ url_syncable_callback_.Reset();
+}
+
+void LocalFileSyncContext::OnWriteEnabled(const FileSystemURL& url) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ // Nothing to do for now.
+}
+
+LocalFileSyncContext::~LocalFileSyncContext() {
+}
+
+void LocalFileSyncContext::ScheduleNotifyChangesUpdatedOnIOThread() {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ if (base::Time::Now() > last_notified_changes_ + NotifyChangesDuration()) {
+ NotifyAvailableChangesOnIOThread();
+ } else if (!timer_on_io_->IsRunning()) {
+ timer_on_io_->Start(
+ FROM_HERE, NotifyChangesDuration(), this,
+ &LocalFileSyncContext::NotifyAvailableChangesOnIOThread);
+ }
+}
+
+void LocalFileSyncContext::NotifyAvailableChangesOnIOThread() {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::NotifyAvailableChanges,
+ this, origins_with_pending_changes_));
+ last_notified_changes_ = base::Time::Now();
+ origins_with_pending_changes_.clear();
+}
+
+void LocalFileSyncContext::NotifyAvailableChanges(
+ const std::set<GURL>& origins) {
+ FOR_EACH_OBSERVER(LocalOriginChangeObserver, origin_change_observers_,
+ OnChangesAvailableInOrigins(origins));
+}
+
+void LocalFileSyncContext::ShutdownOnIOThread() {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ operation_runner_.reset();
+ sync_status_.reset();
+ timer_on_io_.reset();
+}
+
+void LocalFileSyncContext::InitializeFileSystemContextOnIOThread(
+ const GURL& source_url,
+ const std::string& service_name,
+ FileSystemContext* file_system_context) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_system_context);
+ if (!file_system_context->change_tracker()) {
+ // First registers the service name.
+ RegisterSyncableFileSystem(service_name);
+ // Create and initialize LocalFileChangeTracker and call back this method
+ // later again.
+ std::set<GURL>* origins_with_changes = new std::set<GURL>;
+ scoped_ptr<LocalFileChangeTracker>* tracker_ptr(
+ new scoped_ptr<LocalFileChangeTracker>);
+ base::PostTaskAndReplyWithResult(
+ file_system_context->task_runners()->file_task_runner(),
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::InitializeChangeTrackerOnFileThread,
+ this, tracker_ptr,
+ make_scoped_refptr(file_system_context),
+ origins_with_changes),
+ base::Bind(&LocalFileSyncContext::DidInitializeChangeTrackerOnIOThread,
+ this, base::Owned(tracker_ptr),
+ source_url, service_name,
+ make_scoped_refptr(file_system_context),
+ base::Owned(origins_with_changes)));
+ return;
+ }
+ if (!operation_runner_) {
+ DCHECK(!sync_status_);
+ DCHECK(!timer_on_io_);
+ sync_status_.reset(new LocalFileSyncStatus);
+ timer_on_io_.reset(new base::OneShotTimer<LocalFileSyncContext>);
+ operation_runner_.reset(new SyncableFileOperationRunner(
+ kMaxConcurrentSyncableOperation,
+ sync_status_.get()));
+ sync_status_->AddObserver(this);
+ }
+ file_system_context->set_sync_context(this);
+ DidInitialize(source_url, file_system_context,
+ SYNC_STATUS_OK);
+}
+
+SyncStatusCode LocalFileSyncContext::InitializeChangeTrackerOnFileThread(
+ scoped_ptr<LocalFileChangeTracker>* tracker_ptr,
+ FileSystemContext* file_system_context,
+ std::set<GURL>* origins_with_changes) {
+ DCHECK(file_system_context);
+ DCHECK(tracker_ptr);
+ DCHECK(origins_with_changes);
+ tracker_ptr->reset(new LocalFileChangeTracker(
+ file_system_context->partition_path(),
+ file_system_context->task_runners()->file_task_runner()));
+ const SyncStatusCode status = (*tracker_ptr)->Initialize(file_system_context);
+ if (status != SYNC_STATUS_OK)
+ return status;
+
+ // Get all origins that have pending changes.
+ std::deque<FileSystemURL> urls;
+ (*tracker_ptr)->GetNextChangedURLs(&urls, 0);
+ for (std::deque<FileSystemURL>::iterator iter = urls.begin();
+ iter != urls.end(); ++iter) {
+ origins_with_changes->insert(iter->origin());
+ }
+ return status;
+}
+
+void LocalFileSyncContext::DidInitializeChangeTrackerOnIOThread(
+ scoped_ptr<LocalFileChangeTracker>* tracker_ptr,
+ const GURL& source_url,
+ const std::string& service_name,
+ FileSystemContext* file_system_context,
+ std::set<GURL>* origins_with_changes,
+ SyncStatusCode status) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(file_system_context);
+ DCHECK(origins_with_changes);
+ if (status != SYNC_STATUS_OK) {
+ DidInitialize(source_url, file_system_context, status);
+ return;
+ }
+ file_system_context->SetLocalFileChangeTracker(tracker_ptr->Pass());
+
+ origins_with_pending_changes_.insert(origins_with_changes->begin(),
+ origins_with_changes->end());
+ ScheduleNotifyChangesUpdatedOnIOThread();
+
+ InitializeFileSystemContextOnIOThread(source_url, service_name,
+ file_system_context);
+}
+
+void LocalFileSyncContext::DidInitialize(
+ const GURL& source_url,
+ FileSystemContext* file_system_context,
+ SyncStatusCode status) {
+ if (!ui_task_runner_->RunsTasksOnCurrentThread()) {
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::DidInitialize,
+ this, source_url,
+ make_scoped_refptr(file_system_context), status));
+ return;
+ }
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(!ContainsKey(file_system_contexts_, file_system_context));
+ DCHECK(ContainsKey(pending_initialize_callbacks_, file_system_context));
+ DCHECK(file_system_context->change_tracker());
+
+ file_system_contexts_.insert(file_system_context);
+
+ StatusCallbackQueue& callback_queue =
+ pending_initialize_callbacks_[file_system_context];
+ for (StatusCallbackQueue::iterator iter = callback_queue.begin();
+ iter != callback_queue.end(); ++iter) {
+ ui_task_runner_->PostTask(FROM_HERE, base::Bind(*iter, status));
+ }
+ pending_initialize_callbacks_.erase(file_system_context);
+}
+
+void LocalFileSyncContext::GetNextURLsForSyncOnFileThread(
+ FileSystemContext* file_system_context,
+ std::deque<FileSystemURL>* urls) {
+ DCHECK(file_system_context);
+ DCHECK(file_system_context->task_runners()->file_task_runner()->
+ RunsTasksOnCurrentThread());
+ DCHECK(file_system_context->change_tracker());
+ file_system_context->change_tracker()->GetNextChangedURLs(
+ urls, kMaxURLsToFetchForLocalSync);
+}
+
+void LocalFileSyncContext::TryPrepareForLocalSync(
+ FileSystemContext* file_system_context,
+ std::deque<FileSystemURL>* urls,
+ const LocalFileSyncInfoCallback& callback) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ DCHECK(urls);
+
+ if (shutdown_on_ui_) {
+ callback.Run(SYNC_STATUS_ABORT, LocalFileSyncInfo());
+ return;
+ }
+
+ if (urls->empty()) {
+ callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC,
+ LocalFileSyncInfo());
+ return;
+ }
+
+ const FileSystemURL url = urls->front();
+ urls->pop_front();
+ std::deque<FileSystemURL>* remaining = new std::deque<FileSystemURL>;
+ remaining->swap(*urls);
+
+ PrepareForSync(
+ file_system_context, url,
+ base::Bind(&LocalFileSyncContext::DidTryPrepareForLocalSync,
+ this, make_scoped_refptr(file_system_context),
+ base::Owned(remaining), callback));
+}
+
+void LocalFileSyncContext::DidTryPrepareForLocalSync(
+ FileSystemContext* file_system_context,
+ std::deque<FileSystemURL>* remaining_urls,
+ const LocalFileSyncInfoCallback& callback,
+ SyncStatusCode status,
+ const LocalFileSyncInfo& sync_file_info) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ if (status != SYNC_STATUS_FILE_BUSY) {
+ callback.Run(status, sync_file_info);
+ return;
+ }
+ // Recursively call TryPrepareForLocalSync with remaining_urls.
+ TryPrepareForLocalSync(file_system_context, remaining_urls, callback);
+}
+
+void LocalFileSyncContext::DidGetWritingStatusForSync(
+ FileSystemContext* file_system_context,
+ SyncStatusCode status,
+ const FileSystemURL& url,
+ const LocalFileSyncInfoCallback& callback) {
+ // This gets called on UI thread and relays the task on FILE thread.
+ DCHECK(file_system_context);
+ if (!file_system_context->task_runners()->file_task_runner()->
+ RunsTasksOnCurrentThread()) {
+ DCHECK(ui_task_runner_->RunsTasksOnCurrentThread());
+ if (shutdown_on_ui_) {
+ callback.Run(SYNC_STATUS_ABORT, LocalFileSyncInfo());
+ return;
+ }
+ file_system_context->task_runners()->file_task_runner()->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContext::DidGetWritingStatusForSync,
+ this, make_scoped_refptr(file_system_context),
+ status, url, callback));
+ return;
+ }
+
+ DCHECK(file_system_context->change_tracker());
+ FileChangeList changes;
+ file_system_context->change_tracker()->GetChangesForURL(url, &changes);
+
+ base::FilePath platform_path;
+ base::PlatformFileInfo file_info;
+ FileSystemFileUtil* file_util = file_system_context->GetFileUtil(url.type());
+ DCHECK(file_util);
+ base::PlatformFileError file_error = file_util->GetFileInfo(
+ make_scoped_ptr(
+ new FileSystemOperationContext(file_system_context)).get(),
+ url,
+ &file_info,
+ &platform_path);
+ if (status == SYNC_STATUS_OK &&
+ file_error != base::PLATFORM_FILE_OK &&
+ file_error != base::PLATFORM_FILE_ERROR_NOT_FOUND)
+ status = PlatformFileErrorToSyncStatusCode(file_error);
+
+ DCHECK(!file_info.is_symbolic_link);
+
+ SyncFileType file_type = SYNC_FILE_TYPE_FILE;
+ if (file_error == base::PLATFORM_FILE_ERROR_NOT_FOUND)
+ file_type = SYNC_FILE_TYPE_UNKNOWN;
+ else if (file_info.is_directory)
+ file_type = SYNC_FILE_TYPE_DIRECTORY;
+
+ LocalFileSyncInfo sync_file_info;
+ sync_file_info.url = url;
+ sync_file_info.local_file_path = platform_path;
+ sync_file_info.metadata.file_type = file_type;
+ sync_file_info.metadata.size = file_info.size;
+ sync_file_info.metadata.last_modified = file_info.last_modified;
+ sync_file_info.changes = changes;
+
+ ui_task_runner_->PostTask(FROM_HERE,
+ base::Bind(callback, status, sync_file_info));
+}
+
+void LocalFileSyncContext::EnableWritingOnIOThread(
+ const FileSystemURL& url) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ if (!sync_status()) {
+ // The service might have been shut down.
+ return;
+ }
+ sync_status()->EndSyncing(url);
+ // Since a sync has finished the number of changes must have been updated.
+ origins_with_pending_changes_.insert(url.origin());
+ ScheduleNotifyChangesUpdatedOnIOThread();
+}
+
+void LocalFileSyncContext::DidApplyRemoteChange(
+ const FileSystemURL& url,
+ const SyncStatusCallback& callback_on_ui,
+ base::PlatformFileError file_error) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(callback_on_ui,
+ PlatformFileErrorToSyncStatusCode(file_error)));
+ EnableWritingOnIOThread(url);
+}
+
+void LocalFileSyncContext::DidGetFileMetadata(
+ const SyncFileMetadataCallback& callback,
+ base::PlatformFileError file_error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path) {
+ DCHECK(io_task_runner_->RunsTasksOnCurrentThread());
+ SyncFileMetadata metadata;
+ if (file_error == base::PLATFORM_FILE_OK) {
+ metadata.file_type = file_info.is_directory ?
+ SYNC_FILE_TYPE_DIRECTORY : SYNC_FILE_TYPE_FILE;
+ metadata.size = file_info.size;
+ metadata.last_modified = file_info.last_modified;
+ }
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(callback,
+ PlatformFileErrorToSyncStatusCode(file_error),
+ metadata));
+}
+
+base::TimeDelta LocalFileSyncContext::NotifyChangesDuration() {
+ if (mock_notify_changes_duration_in_sec_ >= 0)
+ return base::TimeDelta::FromSeconds(mock_notify_changes_duration_in_sec_);
+ return base::TimeDelta::FromSeconds(kNotifyChangesDurationInSec);
+}
+
+void LocalFileSyncContext::DidCreateDirectoryForCopyIn(
+ FileSystemContext* file_system_context,
+ const base::FilePath& local_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback,
+ base::PlatformFileError error) {
+ if (error != base::PLATFORM_FILE_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ LocalFileSystemOperation* operation = CreateFileSystemOperationForSync(
+ file_system_context);
+ DCHECK(operation);
+ operation->CopyInForeignFile(local_path, dest_url, callback);
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/local_file_sync_context.h b/webkit/browser/fileapi/syncable/local_file_sync_context.h
new file mode 100644
index 0000000..3ad650e
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_file_sync_context.h
@@ -0,0 +1,306 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_FILE_SYNC_CONTEXT_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_FILE_SYNC_CONTEXT_H_
+
+#include <deque>
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/timer.h"
+#include "googleurl/src/gurl.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
+#include "webkit/browser/fileapi/syncable/sync_callbacks.h"
+#include "webkit/browser/fileapi/syncable/sync_status_code.h"
+#include "webkit/storage/webkit_storage_export.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+}
+
+namespace fileapi {
+class FileSystemContext;
+class FileSystemURL;
+}
+
+namespace sync_file_system {
+
+class FileChange;
+class LocalFileChangeTracker;
+struct LocalFileSyncInfo;
+class LocalOriginChangeObserver;
+class SyncableFileOperationRunner;
+
+// This class works as a bridge between LocalFileSyncService (which is a
+// per-profile object) and FileSystemContext's (which is a per-storage-partition
+// object and may exist multiple in a profile).
+// An instance of this class is shared by FileSystemContexts and outlives
+// LocalFileSyncService.
+class WEBKIT_STORAGE_EXPORT LocalFileSyncContext
+ : public base::RefCountedThreadSafe<LocalFileSyncContext>,
+ public LocalFileSyncStatus::Observer {
+ public:
+ typedef base::Callback<void(
+ SyncStatusCode status, const LocalFileSyncInfo& sync_file_info)>
+ LocalFileSyncInfoCallback;
+
+ typedef base::Callback<void(SyncStatusCode status,
+ bool has_pending_changes)>
+ HasPendingLocalChangeCallback;
+
+ LocalFileSyncContext(base::SingleThreadTaskRunner* ui_task_runner,
+ base::SingleThreadTaskRunner* io_task_runner);
+
+ // Initializes |file_system_context| for syncable file operations for
+ // |service_name| and registers the it into the internal map.
+ // Calling this multiple times for the same file_system_context is valid.
+ // This method must be called on UI thread.
+ void MaybeInitializeFileSystemContext(
+ const GURL& source_url,
+ const std::string& service_name,
+ fileapi::FileSystemContext* file_system_context,
+ const SyncStatusCallback& callback);
+
+ // Called when the corresponding LocalFileSyncService exits.
+ // This method must be called on UI thread.
+ void ShutdownOnUIThread();
+
+ // Picks a file for next local sync and returns it after disabling writes
+ // for the file.
+ // This method must be called on UI thread.
+ void GetFileForLocalSync(fileapi::FileSystemContext* file_system_context,
+ const LocalFileSyncInfoCallback& callback);
+
+ // Clears all pending local changes for |url|. |done_callback| is called
+ // when the changes are cleared.
+ // This method must be called on UI thread.
+ void ClearChangesForURL(fileapi::FileSystemContext* file_system_context,
+ const fileapi::FileSystemURL& url,
+ const base::Closure& done_callback);
+
+ // A local or remote sync has been finished (either successfully or
+ // with an error). Clears the internal sync flag and enable writing for |url|.
+ // This method must be called on UI thread.
+ void ClearSyncFlagForURL(const fileapi::FileSystemURL& url);
+
+ // Prepares for sync |url| by disabling writes on |url|.
+ // If the target |url| is being written and cannot start sync it
+ // returns SYNC_STATUS_WRITING status code via |callback|.
+ // Otherwise it disables writes, marks the |url| syncing and returns
+ // the current change set made on |url|.
+ // This method must be called on UI thread.
+ void PrepareForSync(fileapi::FileSystemContext* file_system_context,
+ const fileapi::FileSystemURL& url,
+ const LocalFileSyncInfoCallback& callback);
+
+ // Registers |url| to wait until sync is enabled for |url|.
+ // |on_syncable_callback| is to be called when |url| becomes syncable
+ // (i.e. when we have no pending writes and the file is successfully locked
+ // for sync).
+ //
+ // Calling this method again while this already has another URL waiting
+ // for sync will overwrite the previously registered URL.
+ //
+ // This method must be called on UI thread.
+ void RegisterURLForWaitingSync(const fileapi::FileSystemURL& url,
+ const base::Closure& on_syncable_callback);
+
+ // Applies a remote change.
+ // This method must be called on UI thread.
+ void ApplyRemoteChange(
+ fileapi::FileSystemContext* file_system_context,
+ const FileChange& change,
+ const base::FilePath& local_path,
+ const fileapi::FileSystemURL& url,
+ const SyncStatusCallback& callback);
+
+ // Records a fake local change in the local change tracker.
+ void RecordFakeLocalChange(
+ fileapi::FileSystemContext* file_system_context,
+ const fileapi::FileSystemURL& url,
+ const FileChange& change,
+ const SyncStatusCallback& callback);
+
+ // This must be called on UI thread.
+ void GetFileMetadata(
+ fileapi::FileSystemContext* file_system_context,
+ const fileapi::FileSystemURL& url,
+ const SyncFileMetadataCallback& callback);
+
+ // Returns true via |callback| if the given file |url| has local pending
+ // changes.
+ void HasPendingLocalChanges(
+ fileapi::FileSystemContext* file_system_context,
+ const fileapi::FileSystemURL& url,
+ const HasPendingLocalChangeCallback& callback);
+
+ // They must be called on UI thread.
+ void AddOriginChangeObserver(LocalOriginChangeObserver* observer);
+ void RemoveOriginChangeObserver(LocalOriginChangeObserver* observer);
+
+ // OperationRunner is accessible only on IO thread.
+ base::WeakPtr<SyncableFileOperationRunner> operation_runner() const;
+
+ // SyncContext is accessible only on IO thread.
+ LocalFileSyncStatus* sync_status() const;
+
+ // For testing; override the duration to notify changes from the
+ // default value.
+ void set_mock_notify_changes_duration_in_sec(int duration) {
+ mock_notify_changes_duration_in_sec_ = duration;
+ }
+
+ protected:
+ // LocalFileSyncStatus::Observer overrides. They are called on IO thread.
+ virtual void OnSyncEnabled(const fileapi::FileSystemURL& url) OVERRIDE;
+ virtual void OnWriteEnabled(const fileapi::FileSystemURL& url) OVERRIDE;
+
+ private:
+ typedef base::Callback<void(base::PlatformFileError result)> StatusCallback;
+ typedef std::deque<SyncStatusCallback> StatusCallbackQueue;
+ friend class base::RefCountedThreadSafe<LocalFileSyncContext>;
+ friend class CannedSyncableFileSystem;
+
+ virtual ~LocalFileSyncContext();
+
+ void ShutdownOnIOThread();
+
+ // Starts a timer to eventually call NotifyAvailableChangesOnIOThread.
+ // The caller is expected to update origins_with_pending_changes_ before
+ // calling this.
+ void ScheduleNotifyChangesUpdatedOnIOThread();
+
+ // Called by the internal timer on IO thread to notify changes to UI thread.
+ void NotifyAvailableChangesOnIOThread();
+
+ // Called from NotifyAvailableChangesOnIOThread.
+ void NotifyAvailableChanges(const std::set<GURL>& origins);
+
+ // Helper routines for MaybeInitializeFileSystemContext.
+ void InitializeFileSystemContextOnIOThread(
+ const GURL& source_url,
+ const std::string& service_name,
+ fileapi::FileSystemContext* file_system_context);
+ SyncStatusCode InitializeChangeTrackerOnFileThread(
+ scoped_ptr<LocalFileChangeTracker>* tracker_ptr,
+ fileapi::FileSystemContext* file_system_context,
+ std::set<GURL>* origins_with_changes);
+ void DidInitializeChangeTrackerOnIOThread(
+ scoped_ptr<LocalFileChangeTracker>* tracker_ptr,
+ const GURL& source_url,
+ const std::string& service_name,
+ fileapi::FileSystemContext* file_system_context,
+ std::set<GURL>* origins_with_changes,
+ SyncStatusCode status);
+ void DidInitialize(
+ const GURL& source_url,
+ fileapi::FileSystemContext* file_system_context,
+ SyncStatusCode status);
+
+ // Helper routines for GetFileForLocalSync.
+ void GetNextURLsForSyncOnFileThread(
+ fileapi::FileSystemContext* file_system_context,
+ std::deque<fileapi::FileSystemURL>* urls);
+ void TryPrepareForLocalSync(
+ fileapi::FileSystemContext* file_system_context,
+ std::deque<fileapi::FileSystemURL>* urls,
+ const LocalFileSyncInfoCallback& callback);
+ void DidTryPrepareForLocalSync(
+ fileapi::FileSystemContext* file_system_context,
+ std::deque<fileapi::FileSystemURL>* remaining_urls,
+ const LocalFileSyncInfoCallback& callback,
+ SyncStatusCode status,
+ const LocalFileSyncInfo& sync_file_info);
+
+ // Callback routine for PrepareForSync and GetFileForLocalSync.
+ void DidGetWritingStatusForSync(
+ fileapi::FileSystemContext* file_system_context,
+ SyncStatusCode status,
+ const fileapi::FileSystemURL& url,
+ const LocalFileSyncInfoCallback& callback);
+
+ // Helper routine for ClearSyncFlagForURL.
+ void EnableWritingOnIOThread(const fileapi::FileSystemURL& url);
+
+ void DidRemoveExistingEntryForApplyRemoteChange(
+ fileapi::FileSystemContext* file_system_context,
+ const FileChange& change,
+ const base::FilePath& local_path,
+ const fileapi::FileSystemURL& url,
+ const SyncStatusCallback& callback,
+ base::PlatformFileError error);
+
+ // Callback routine for ApplyRemoteChange.
+ void DidApplyRemoteChange(
+ const fileapi::FileSystemURL& url,
+ const SyncStatusCallback& callback_on_ui,
+ base::PlatformFileError file_error);
+
+ void DidGetFileMetadata(
+ const SyncFileMetadataCallback& callback,
+ base::PlatformFileError file_error,
+ const base::PlatformFileInfo& file_info,
+ const base::FilePath& platform_path);
+
+ base::TimeDelta NotifyChangesDuration();
+
+ void DidCreateDirectoryForCopyIn(
+ fileapi::FileSystemContext* file_system_context,
+ const base::FilePath& local_file_path,
+ const fileapi::FileSystemURL& dest_url,
+ const StatusCallback& callback,
+ base::PlatformFileError error);
+
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+
+ // Indicates if the sync service is shutdown on UI thread.
+ bool shutdown_on_ui_;
+
+ // OperationRunner. This must be accessed only on IO thread.
+ scoped_ptr<SyncableFileOperationRunner> operation_runner_;
+
+ // Keeps track of writing/syncing status.
+ // This must be accessed only on IO thread.
+ scoped_ptr<LocalFileSyncStatus> sync_status_;
+
+ // Pointers to file system contexts that have been initialized for
+ // synchronization (i.e. that own this instance).
+ // This must be accessed only on UI thread.
+ std::set<fileapi::FileSystemContext*> file_system_contexts_;
+
+ // Accessed only on UI thread.
+ std::map<fileapi::FileSystemContext*, StatusCallbackQueue>
+ pending_initialize_callbacks_;
+
+ // A URL and associated callback waiting for sync is enabled.
+ // Accessed only on IO thread.
+ fileapi::FileSystemURL url_waiting_sync_on_io_;
+ base::Closure url_syncable_callback_;
+
+ // Used only on IO thread for available changes notifications.
+ base::Time last_notified_changes_;
+ scoped_ptr<base::OneShotTimer<LocalFileSyncContext> > timer_on_io_;
+ std::set<GURL> origins_with_pending_changes_;
+
+ ObserverList<LocalOriginChangeObserver> origin_change_observers_;
+
+ int mock_notify_changes_duration_in_sec_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalFileSyncContext);
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_FILE_SYNC_CONTEXT_H_
diff --git a/webkit/browser/fileapi/syncable/local_file_sync_context_unittest.cc b/webkit/browser/fileapi/syncable/local_file_sync_context_unittest.cc
new file mode 100644
index 0000000..e02afc4
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_file_sync_context_unittest.cc
@@ -0,0 +1,681 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/local_file_sync_context.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/message_loop.h"
+#include "base/platform_file.h"
+#include "base/single_thread_task_runner.h"
+#include "base/stl_util.h"
+#include "base/threading/thread.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/blob/mock_blob_url_request_context.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/fileapi/syncable/canned_syncable_file_system.h"
+#include "webkit/browser/fileapi/syncable/file_change.h"
+#include "webkit/browser/fileapi/syncable/local_file_change_tracker.h"
+#include "webkit/browser/fileapi/syncable/sync_file_metadata.h"
+#include "webkit/browser/fileapi/syncable/sync_status_code.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+
+#define FPL FILE_PATH_LITERAL
+
+using fileapi::FileSystemContext;
+using fileapi::FileSystemURL;
+using fileapi::FileSystemURLSet;
+
+// This tests LocalFileSyncContext behavior in multi-thread /
+// multi-file-system-context environment.
+// Basic combined tests (single-thread / single-file-system-context)
+// that involve LocalFileSyncContext are also in
+// syncable_file_system_unittests.cc.
+
+namespace sync_file_system {
+
+namespace {
+const char kOrigin1[] = "http://example.com";
+const char kOrigin2[] = "http://chromium.org";
+const char kServiceName[] = "test";
+}
+
+class LocalFileSyncContextTest : public testing::Test {
+ protected:
+ LocalFileSyncContextTest()
+ : status_(SYNC_FILE_ERROR_FAILED),
+ file_error_(base::PLATFORM_FILE_ERROR_FAILED),
+ async_modify_finished_(false),
+ has_inflight_prepare_for_sync_(false) {}
+
+ virtual void SetUp() OVERRIDE {
+ EXPECT_TRUE(RegisterSyncableFileSystem(kServiceName));
+
+ io_thread_.reset(new base::Thread("Thread_IO"));
+ io_thread_->StartWithOptions(
+ base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
+
+ file_thread_.reset(new base::Thread("Thread_File"));
+ file_thread_->Start();
+
+ ui_task_runner_ = base::MessageLoop::current()->message_loop_proxy();
+ io_task_runner_ = io_thread_->message_loop_proxy();
+ file_task_runner_ = file_thread_->message_loop_proxy();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ EXPECT_TRUE(RevokeSyncableFileSystem(kServiceName));
+ io_thread_->Stop();
+ file_thread_->Stop();
+ }
+
+ void StartPrepareForSync(FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ SyncFileMetadata* metadata,
+ FileChangeList* changes) {
+ ASSERT_TRUE(changes != NULL);
+ ASSERT_FALSE(has_inflight_prepare_for_sync_);
+ status_ = SYNC_STATUS_UNKNOWN;
+ has_inflight_prepare_for_sync_ = true;
+ sync_context_->PrepareForSync(
+ file_system_context,
+ url,
+ base::Bind(&LocalFileSyncContextTest::DidPrepareForSync,
+ base::Unretained(this), metadata, changes));
+ }
+
+ SyncStatusCode PrepareForSync(FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ SyncFileMetadata* metadata,
+ FileChangeList* changes) {
+ StartPrepareForSync(file_system_context, url, metadata, changes);
+ base::MessageLoop::current()->Run();
+ return status_;
+ }
+
+ base::Closure GetPrepareForSyncClosure(FileSystemContext* file_system_context,
+ const FileSystemURL& url,
+ SyncFileMetadata* metadata,
+ FileChangeList* changes) {
+ return base::Bind(&LocalFileSyncContextTest::StartPrepareForSync,
+ base::Unretained(this),
+ base::Unretained(file_system_context),
+ url, metadata, changes);
+ }
+
+ void DidPrepareForSync(SyncFileMetadata* metadata_out,
+ FileChangeList* changes_out,
+ SyncStatusCode status,
+ const LocalFileSyncInfo& sync_file_info) {
+ ASSERT_TRUE(ui_task_runner_->RunsTasksOnCurrentThread());
+ has_inflight_prepare_for_sync_ = false;
+ status_ = status;
+ *metadata_out = sync_file_info.metadata;
+ *changes_out = sync_file_info.changes;
+ base::MessageLoop::current()->Quit();
+ }
+
+ SyncStatusCode ApplyRemoteChange(FileSystemContext* file_system_context,
+ const FileChange& change,
+ const base::FilePath& local_path,
+ const FileSystemURL& url,
+ SyncFileType expected_file_type) {
+ SCOPED_TRACE(testing::Message() << "ApplyChange for " <<
+ url.DebugString());
+
+ // First we should call PrepareForSync to disable writing.
+ SyncFileMetadata metadata;
+ FileChangeList changes;
+ EXPECT_EQ(SYNC_STATUS_OK,
+ PrepareForSync(file_system_context, url, &metadata, &changes));
+ EXPECT_EQ(expected_file_type, metadata.file_type);
+
+ status_ = SYNC_STATUS_UNKNOWN;
+ sync_context_->ApplyRemoteChange(
+ file_system_context, change, local_path, url,
+ base::Bind(&LocalFileSyncContextTest::DidApplyRemoteChange,
+ base::Unretained(this)));
+ base::MessageLoop::current()->Run();
+ return status_;
+ }
+
+ void DidApplyRemoteChange(SyncStatusCode status) {
+ base::MessageLoop::current()->Quit();
+ status_ = status;
+ }
+
+ void StartModifyFileOnIOThread(CannedSyncableFileSystem* file_system,
+ const FileSystemURL& url) {
+ async_modify_finished_ = false;
+ ASSERT_TRUE(file_system != NULL);
+ if (!io_task_runner_->RunsTasksOnCurrentThread()) {
+ ASSERT_TRUE(ui_task_runner_->RunsTasksOnCurrentThread());
+ io_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContextTest::StartModifyFileOnIOThread,
+ base::Unretained(this), file_system, url));
+ return;
+ }
+ ASSERT_TRUE(io_task_runner_->RunsTasksOnCurrentThread());
+ file_error_ = base::PLATFORM_FILE_ERROR_FAILED;
+ file_system->NewOperation()->Truncate(
+ url, 1, base::Bind(&LocalFileSyncContextTest::DidModifyFile,
+ base::Unretained(this)));
+ }
+
+ base::PlatformFileError WaitUntilModifyFileIsDone() {
+ while (!async_modify_finished_)
+ base::MessageLoop::current()->RunUntilIdle();
+ return file_error_;
+ }
+
+ void DidModifyFile(base::PlatformFileError error) {
+ if (!ui_task_runner_->RunsTasksOnCurrentThread()) {
+ ASSERT_TRUE(io_task_runner_->RunsTasksOnCurrentThread());
+ ui_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&LocalFileSyncContextTest::DidModifyFile,
+ base::Unretained(this), error));
+ return;
+ }
+ ASSERT_TRUE(ui_task_runner_->RunsTasksOnCurrentThread());
+ file_error_ = error;
+ async_modify_finished_ = true;
+ }
+
+ // These need to remain until the very end.
+ scoped_ptr<base::Thread> io_thread_;
+ scoped_ptr<base::Thread> file_thread_;
+ base::MessageLoop loop_;
+
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
+
+ scoped_refptr<LocalFileSyncContext> sync_context_;
+
+ SyncStatusCode status_;
+ base::PlatformFileError file_error_;
+ bool async_modify_finished_;
+ bool has_inflight_prepare_for_sync_;
+};
+
+TEST_F(LocalFileSyncContextTest, ConstructAndDestruct) {
+ sync_context_ = new LocalFileSyncContext(
+ ui_task_runner_, io_task_runner_);
+ sync_context_->ShutdownOnUIThread();
+}
+
+TEST_F(LocalFileSyncContextTest, InitializeFileSystemContext) {
+ CannedSyncableFileSystem file_system(GURL(kOrigin1), kServiceName,
+ io_task_runner_, file_task_runner_);
+ file_system.SetUp();
+
+ sync_context_ = new LocalFileSyncContext(ui_task_runner_, io_task_runner_);
+
+ // Initializes file_system using |sync_context_|.
+ EXPECT_EQ(SYNC_STATUS_OK,
+ file_system.MaybeInitializeFileSystemContext(sync_context_));
+
+ // Make sure everything's set up for file_system to be able to handle
+ // syncable file system operations.
+ EXPECT_TRUE(file_system.file_system_context()->sync_context() != NULL);
+ EXPECT_TRUE(file_system.file_system_context()->change_tracker() != NULL);
+ EXPECT_EQ(sync_context_.get(),
+ file_system.file_system_context()->sync_context());
+
+ // Calling MaybeInitialize for the same context multiple times must be ok.
+ EXPECT_EQ(SYNC_STATUS_OK,
+ file_system.MaybeInitializeFileSystemContext(sync_context_));
+ EXPECT_EQ(sync_context_.get(),
+ file_system.file_system_context()->sync_context());
+
+ // Opens the file_system, perform some operation and see if the change tracker
+ // correctly captures the change.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem());
+
+ const FileSystemURL kURL(file_system.URL("foo"));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kURL));
+
+ FileSystemURLSet urls;
+ file_system.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(1U, urls.size());
+ EXPECT_TRUE(ContainsKey(urls, kURL));
+
+ // Finishing the test.
+ sync_context_->ShutdownOnUIThread();
+ file_system.TearDown();
+}
+
+TEST_F(LocalFileSyncContextTest, MultipleFileSystemContexts) {
+ CannedSyncableFileSystem file_system1(GURL(kOrigin1), kServiceName,
+ io_task_runner_, file_task_runner_);
+ CannedSyncableFileSystem file_system2(GURL(kOrigin2), kServiceName,
+ io_task_runner_, file_task_runner_);
+ file_system1.SetUp();
+ file_system2.SetUp();
+
+ sync_context_ = new LocalFileSyncContext(ui_task_runner_, io_task_runner_);
+
+ // Initializes file_system1 and file_system2.
+ EXPECT_EQ(SYNC_STATUS_OK,
+ file_system1.MaybeInitializeFileSystemContext(sync_context_));
+ EXPECT_EQ(SYNC_STATUS_OK,
+ file_system2.MaybeInitializeFileSystemContext(sync_context_));
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system1.OpenFileSystem());
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system2.OpenFileSystem());
+
+ const FileSystemURL kURL1(file_system1.URL("foo"));
+ const FileSystemURL kURL2(file_system2.URL("bar"));
+
+ // Creates a file in file_system1.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system1.CreateFile(kURL1));
+
+ // file_system1's tracker must have recorded the change.
+ FileSystemURLSet urls;
+ file_system1.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(1U, urls.size());
+ EXPECT_TRUE(ContainsKey(urls, kURL1));
+
+ // file_system1's tracker must have no change.
+ urls.clear();
+ file_system2.GetChangedURLsInTracker(&urls);
+ ASSERT_TRUE(urls.empty());
+
+ // Creates a directory in file_system2.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system2.CreateDirectory(kURL2));
+
+ // file_system1's tracker must have the change for kURL1 as before.
+ urls.clear();
+ file_system1.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(1U, urls.size());
+ EXPECT_TRUE(ContainsKey(urls, kURL1));
+
+ // file_system2's tracker now must have the change for kURL2.
+ urls.clear();
+ file_system2.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(1U, urls.size());
+ EXPECT_TRUE(ContainsKey(urls, kURL2));
+
+ SyncFileMetadata metadata;
+ FileChangeList changes;
+ EXPECT_EQ(SYNC_STATUS_OK,
+ PrepareForSync(file_system1.file_system_context(), kURL1,
+ &metadata, &changes));
+ EXPECT_EQ(1U, changes.size());
+ EXPECT_TRUE(changes.list().back().IsFile());
+ EXPECT_TRUE(changes.list().back().IsAddOrUpdate());
+ EXPECT_EQ(SYNC_FILE_TYPE_FILE, metadata.file_type);
+ EXPECT_EQ(0, metadata.size);
+
+ changes.clear();
+ EXPECT_EQ(SYNC_STATUS_OK,
+ PrepareForSync(file_system2.file_system_context(), kURL2,
+ &metadata, &changes));
+ EXPECT_EQ(1U, changes.size());
+ EXPECT_FALSE(changes.list().back().IsFile());
+ EXPECT_TRUE(changes.list().back().IsAddOrUpdate());
+ EXPECT_EQ(SYNC_FILE_TYPE_DIRECTORY, metadata.file_type);
+ EXPECT_EQ(0, metadata.size);
+
+ sync_context_->ShutdownOnUIThread();
+ sync_context_ = NULL;
+
+ file_system1.TearDown();
+ file_system2.TearDown();
+}
+
+// LocalFileSyncContextTest.PrepareSyncWhileWriting is flaky on android.
+// http://crbug.com/239793
+#if defined(OS_ANDROID)
+#define MAYBE_PrepareSyncWhileWriting DISABLED_PrepareSyncWhileWriting
+#else
+#define MAYBE_PrepareSyncWhileWriting PrepareSyncWhileWriting
+#endif
+TEST_F(LocalFileSyncContextTest, MAYBE_PrepareSyncWhileWriting) {
+ CannedSyncableFileSystem file_system(GURL(kOrigin1), kServiceName,
+ io_task_runner_, file_task_runner_);
+ file_system.SetUp();
+ sync_context_ = new LocalFileSyncContext(ui_task_runner_, io_task_runner_);
+ EXPECT_EQ(SYNC_STATUS_OK,
+ file_system.MaybeInitializeFileSystemContext(sync_context_));
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem());
+
+ const FileSystemURL kURL1(file_system.URL("foo"));
+
+ // Creates a file in file_system.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kURL1));
+
+ // Kick file write on IO thread.
+ StartModifyFileOnIOThread(&file_system, kURL1);
+
+ // Until the operation finishes PrepareForSync should return BUSY error.
+ SyncFileMetadata metadata;
+ metadata.file_type = SYNC_FILE_TYPE_UNKNOWN;
+ FileChangeList changes;
+ EXPECT_EQ(SYNC_STATUS_FILE_BUSY,
+ PrepareForSync(file_system.file_system_context(),
+ kURL1, &metadata, &changes));
+ EXPECT_EQ(SYNC_FILE_TYPE_FILE, metadata.file_type);
+
+ // Register PrepareForSync method to be invoked when kURL1 becomes
+ // syncable. (Actually this may be done after all operations are done
+ // on IO thread in this test.)
+ metadata.file_type = SYNC_FILE_TYPE_UNKNOWN;
+ changes.clear();
+ sync_context_->RegisterURLForWaitingSync(
+ kURL1, GetPrepareForSyncClosure(file_system.file_system_context(),
+ kURL1, &metadata, &changes));
+
+ // Wait for the completion.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, WaitUntilModifyFileIsDone());
+
+ // The PrepareForSync must have been started; wait until DidPrepareForSync
+ // is done.
+ base::MessageLoop::current()->Run();
+ ASSERT_FALSE(has_inflight_prepare_for_sync_);
+
+ // Now PrepareForSync should have run and returned OK.
+ EXPECT_EQ(SYNC_STATUS_OK, status_);
+ EXPECT_EQ(1U, changes.size());
+ EXPECT_TRUE(changes.list().back().IsFile());
+ EXPECT_TRUE(changes.list().back().IsAddOrUpdate());
+ EXPECT_EQ(SYNC_FILE_TYPE_FILE, metadata.file_type);
+ EXPECT_EQ(1, metadata.size);
+
+ sync_context_->ShutdownOnUIThread();
+ sync_context_ = NULL;
+ file_system.TearDown();
+}
+
+TEST_F(LocalFileSyncContextTest, ApplyRemoteChangeForDeletion) {
+ CannedSyncableFileSystem file_system(GURL(kOrigin1), kServiceName,
+ io_task_runner_, file_task_runner_);
+ file_system.SetUp();
+
+ sync_context_ = new LocalFileSyncContext(ui_task_runner_, io_task_runner_);
+ ASSERT_EQ(SYNC_STATUS_OK,
+ file_system.MaybeInitializeFileSystemContext(sync_context_));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem());
+
+ // Record the initial usage (likely 0).
+ int64 initial_usage = -1;
+ int64 quota = -1;
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system.GetUsageAndQuota(&initial_usage, &quota));
+
+ // Create a file and directory in the file_system.
+ const FileSystemURL kFile(file_system.URL("file"));
+ const FileSystemURL kDir(file_system.URL("dir"));
+ const FileSystemURL kChild(file_system.URL("dir/child"));
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kFile));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateDirectory(kDir));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kChild));
+
+ // file_system's change tracker must have recorded the creation.
+ FileSystemURLSet urls;
+ file_system.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(3U, urls.size());
+ ASSERT_TRUE(ContainsKey(urls, kFile));
+ ASSERT_TRUE(ContainsKey(urls, kDir));
+ ASSERT_TRUE(ContainsKey(urls, kChild));
+ for (FileSystemURLSet::iterator iter = urls.begin();
+ iter != urls.end(); ++iter) {
+ file_system.ClearChangeForURLInTracker(*iter);
+ }
+
+ // At this point the usage must be greater than the initial usage.
+ int64 new_usage = -1;
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system.GetUsageAndQuota(&new_usage, &quota));
+ EXPECT_GT(new_usage, initial_usage);
+
+ // Now let's apply remote deletion changes.
+ FileChange change(FileChange::FILE_CHANGE_DELETE,
+ SYNC_FILE_TYPE_FILE);
+ EXPECT_EQ(SYNC_STATUS_OK,
+ ApplyRemoteChange(file_system.file_system_context(),
+ change, base::FilePath(), kFile,
+ SYNC_FILE_TYPE_FILE));
+
+ // The implementation doesn't check file type for deletion, and it must be ok
+ // even if we don't know if the deletion change was for a file or a directory.
+ change = FileChange(FileChange::FILE_CHANGE_DELETE,
+ SYNC_FILE_TYPE_UNKNOWN);
+ EXPECT_EQ(SYNC_STATUS_OK,
+ ApplyRemoteChange(file_system.file_system_context(),
+ change, base::FilePath(), kDir,
+ SYNC_FILE_TYPE_DIRECTORY));
+
+ // Check the directory/files are deleted successfully.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ file_system.FileExists(kFile));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ file_system.DirectoryExists(kDir));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ file_system.FileExists(kChild));
+
+ // The changes applied by ApplyRemoteChange should not be recorded in
+ // the change tracker.
+ urls.clear();
+ file_system.GetChangedURLsInTracker(&urls);
+ EXPECT_TRUE(urls.empty());
+
+ // The quota usage data must have reflected the deletion.
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system.GetUsageAndQuota(&new_usage, &quota));
+ EXPECT_EQ(new_usage, initial_usage);
+
+ sync_context_->ShutdownOnUIThread();
+ sync_context_ = NULL;
+ file_system.TearDown();
+}
+
+TEST_F(LocalFileSyncContextTest, ApplyRemoteChangeForAddOrUpdate) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ CannedSyncableFileSystem file_system(GURL(kOrigin1), kServiceName,
+ io_task_runner_, file_task_runner_);
+ file_system.SetUp();
+
+ sync_context_ = new LocalFileSyncContext(ui_task_runner_, io_task_runner_);
+ ASSERT_EQ(SYNC_STATUS_OK,
+ file_system.MaybeInitializeFileSystemContext(sync_context_));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem());
+
+ const FileSystemURL kFile1(file_system.URL("file1"));
+ const FileSystemURL kFile2(file_system.URL("file2"));
+ const FileSystemURL kDir(file_system.URL("dir"));
+
+ const char kTestFileData0[] = "0123456789";
+ const char kTestFileData1[] = "Lorem ipsum!";
+ const char kTestFileData2[] = "This is sample test data.";
+
+ // Create kFile1 and populate it with kTestFileData0.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kFile1));
+ EXPECT_EQ(static_cast<int64>(arraysize(kTestFileData0) - 1),
+ file_system.WriteString(kFile1, kTestFileData0));
+
+ // kFile2 and kDir are not there yet.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ file_system.FileExists(kFile2));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ file_system.DirectoryExists(kDir));
+
+ // file_system's change tracker must have recorded the creation.
+ FileSystemURLSet urls;
+ file_system.GetChangedURLsInTracker(&urls);
+ ASSERT_EQ(1U, urls.size());
+ EXPECT_TRUE(ContainsKey(urls, kFile1));
+ file_system.ClearChangeForURLInTracker(*urls.begin());
+
+ // Prepare temporary files which represent the remote file data.
+ const base::FilePath kFilePath1(temp_dir.path().Append(FPL("file1")));
+ const base::FilePath kFilePath2(temp_dir.path().Append(FPL("file2")));
+
+ ASSERT_EQ(static_cast<int>(arraysize(kTestFileData1) - 1),
+ file_util::WriteFile(kFilePath1, kTestFileData1,
+ arraysize(kTestFileData1) - 1));
+ ASSERT_EQ(static_cast<int>(arraysize(kTestFileData2) - 1),
+ file_util::WriteFile(kFilePath2, kTestFileData2,
+ arraysize(kTestFileData2) - 1));
+
+ // Record the usage.
+ int64 usage = -1, new_usage = -1;
+ int64 quota = -1;
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system.GetUsageAndQuota(&usage, &quota));
+
+ // Here in the local filesystem we have:
+ // * kFile1 with kTestFileData0
+ //
+ // In the remote side let's assume we have:
+ // * kFile1 with kTestFileData1
+ // * kFile2 with kTestFileData2
+ // * kDir
+ //
+ // By calling ApplyChange's:
+ // * kFile1 will be updated to have kTestFileData1
+ // * kFile2 will be created
+ // * kDir will be created
+
+ // Apply the remote change to kFile1 (which will update the file).
+ FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_FILE);
+ EXPECT_EQ(SYNC_STATUS_OK,
+ ApplyRemoteChange(file_system.file_system_context(),
+ change, kFilePath1, kFile1,
+ SYNC_FILE_TYPE_FILE));
+
+ // Check if the usage has been increased by (kTestFileData1 - kTestFileData0).
+ const int updated_size =
+ arraysize(kTestFileData1) - arraysize(kTestFileData0);
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system.GetUsageAndQuota(&new_usage, &quota));
+ EXPECT_EQ(updated_size, new_usage - usage);
+
+ // Apply remote changes to kFile2 and kDir (should create a file and
+ // directory respectively).
+ // They are non-existent yet so their expected file type (the last
+ // parameter of ApplyRemoteChange) are
+ // SYNC_FILE_TYPE_UNKNOWN.
+ change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_FILE);
+ EXPECT_EQ(SYNC_STATUS_OK,
+ ApplyRemoteChange(file_system.file_system_context(),
+ change, kFilePath2, kFile2,
+ SYNC_FILE_TYPE_UNKNOWN));
+
+ change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_DIRECTORY);
+ EXPECT_EQ(SYNC_STATUS_OK,
+ ApplyRemoteChange(file_system.file_system_context(),
+ change, base::FilePath(), kDir,
+ SYNC_FILE_TYPE_UNKNOWN));
+
+ // Calling ApplyRemoteChange with different file type should be handled as
+ // overwrite.
+ change =
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE);
+ EXPECT_EQ(SYNC_STATUS_OK,
+ ApplyRemoteChange(file_system.file_system_context(),
+ change,
+ kFilePath1,
+ kDir,
+ SYNC_FILE_TYPE_DIRECTORY));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kDir));
+
+ change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_DIRECTORY);
+ EXPECT_EQ(SYNC_STATUS_OK,
+ ApplyRemoteChange(file_system.file_system_context(),
+ change,
+ kFilePath1,
+ kDir,
+ SYNC_FILE_TYPE_FILE));
+
+ // Creating a file/directory must have increased the usage more than
+ // the size of kTestFileData2.
+ new_usage = usage;
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system.GetUsageAndQuota(&new_usage, &quota));
+ EXPECT_GT(new_usage,
+ static_cast<int64>(usage + arraysize(kTestFileData2) - 1));
+
+ // The changes applied by ApplyRemoteChange should not be recorded in
+ // the change tracker.
+ urls.clear();
+ file_system.GetChangedURLsInTracker(&urls);
+ EXPECT_TRUE(urls.empty());
+
+ // Make sure all three files/directory exist.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile1));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile2));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.DirectoryExists(kDir));
+
+ sync_context_->ShutdownOnUIThread();
+ file_system.TearDown();
+}
+
+TEST_F(LocalFileSyncContextTest, ApplyRemoteChangeForAddOrUpdate_NoParent) {
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ CannedSyncableFileSystem file_system(GURL(kOrigin1), kServiceName,
+ io_task_runner_, file_task_runner_);
+ file_system.SetUp();
+
+ sync_context_ = new LocalFileSyncContext(ui_task_runner_, io_task_runner_);
+ ASSERT_EQ(SYNC_STATUS_OK,
+ file_system.MaybeInitializeFileSystemContext(sync_context_));
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem());
+
+ const char kTestFileData[] = "Lorem ipsum!";
+ const FileSystemURL kDir(file_system.URL("dir"));
+ const FileSystemURL kFile(file_system.URL("dir/file"));
+
+ // Either kDir or kFile not exist yet.
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, file_system.FileExists(kDir));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, file_system.FileExists(kFile));
+
+ // Prepare a temporary file which represents remote file data.
+ const base::FilePath kFilePath(temp_dir.path().Append(FPL("file")));
+ ASSERT_EQ(static_cast<int>(arraysize(kTestFileData) - 1),
+ file_util::WriteFile(kFilePath, kTestFileData,
+ arraysize(kTestFileData) - 1));
+
+ // Calling ApplyChange's with kFilePath should create
+ // kFile along with kDir.
+ FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ SYNC_FILE_TYPE_FILE);
+ EXPECT_EQ(SYNC_STATUS_OK,
+ ApplyRemoteChange(file_system.file_system_context(),
+ change, kFilePath, kFile,
+ SYNC_FILE_TYPE_UNKNOWN));
+
+ // The changes applied by ApplyRemoteChange should not be recorded in
+ // the change tracker.
+ FileSystemURLSet urls;
+ urls.clear();
+ file_system.GetChangedURLsInTracker(&urls);
+ EXPECT_TRUE(urls.empty());
+
+ // Make sure kDir and kFile are created by ApplyRemoteChange.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.DirectoryExists(kDir));
+
+ sync_context_->ShutdownOnUIThread();
+ file_system.TearDown();
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/local_file_sync_status.cc b/webkit/browser/fileapi/syncable/local_file_sync_status.cc
new file mode 100644
index 0000000..9ed4348
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_file_sync_status.cc
@@ -0,0 +1,97 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
+
+#include "base/logging.h"
+
+using fileapi::FileSystemURL;
+using fileapi::FileSystemURLSet;
+
+namespace sync_file_system {
+
+LocalFileSyncStatus::LocalFileSyncStatus() {}
+
+LocalFileSyncStatus::~LocalFileSyncStatus() {}
+
+void LocalFileSyncStatus::StartWriting(const FileSystemURL& url) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!IsChildOrParentSyncing(url));
+ writing_[url]++;
+}
+
+void LocalFileSyncStatus::EndWriting(const FileSystemURL& url) {
+ DCHECK(CalledOnValidThread());
+ int count = --writing_[url];
+ if (count == 0) {
+ writing_.erase(url);
+ FOR_EACH_OBSERVER(Observer, observer_list_, OnSyncEnabled(url));
+ }
+}
+
+void LocalFileSyncStatus::StartSyncing(const FileSystemURL& url) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!IsChildOrParentWriting(url));
+ DCHECK(!IsChildOrParentSyncing(url));
+ syncing_.insert(url);
+}
+
+void LocalFileSyncStatus::EndSyncing(const FileSystemURL& url) {
+ DCHECK(CalledOnValidThread());
+ syncing_.erase(url);
+ FOR_EACH_OBSERVER(Observer, observer_list_, OnWriteEnabled(url));
+}
+
+bool LocalFileSyncStatus::IsWriting(const FileSystemURL& url) const {
+ DCHECK(CalledOnValidThread());
+ return IsChildOrParentWriting(url);
+}
+
+bool LocalFileSyncStatus::IsWritable(const FileSystemURL& url) const {
+ DCHECK(CalledOnValidThread());
+ return !IsChildOrParentSyncing(url);
+}
+
+bool LocalFileSyncStatus::IsSyncable(const FileSystemURL& url) const {
+ DCHECK(CalledOnValidThread());
+ return !IsChildOrParentSyncing(url) && !IsChildOrParentWriting(url);
+}
+
+void LocalFileSyncStatus::AddObserver(Observer* observer) {
+ DCHECK(CalledOnValidThread());
+ observer_list_.AddObserver(observer);
+}
+
+void LocalFileSyncStatus::RemoveObserver(Observer* observer) {
+ DCHECK(CalledOnValidThread());
+ observer_list_.RemoveObserver(observer);
+}
+
+bool LocalFileSyncStatus::IsChildOrParentWriting(
+ const FileSystemURL& url) const {
+ DCHECK(CalledOnValidThread());
+ URLCountMap::const_iterator upper = writing_.upper_bound(url);
+ URLCountMap::const_reverse_iterator rupper(upper);
+ if (upper != writing_.end() && url.IsParent(upper->first))
+ return true;
+ if (rupper != writing_.rend() &&
+ (rupper->first == url || rupper->first.IsParent(url)))
+ return true;
+ return false;
+}
+
+bool LocalFileSyncStatus::IsChildOrParentSyncing(
+ const FileSystemURL& url) const {
+ DCHECK(CalledOnValidThread());
+ FileSystemURLSet::const_iterator upper = syncing_.upper_bound(url);
+ FileSystemURLSet::const_reverse_iterator rupper(upper);
+ if (upper != syncing_.end() && url.IsParent(*upper))
+ return true;
+ if (rupper != syncing_.rend() &&
+ (*rupper == url || rupper->IsParent(url)))
+ return true;
+ return false;
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/local_file_sync_status.h b/webkit/browser/fileapi/syncable/local_file_sync_status.h
new file mode 100644
index 0000000..99558cc
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_file_sync_status.h
@@ -0,0 +1,94 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_FILE_SYNC_STATUS_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_FILE_SYNC_STATUS_H_
+
+#include <map>
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/observer_list.h"
+#include "base/threading/non_thread_safe.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+
+namespace fileapi {
+class FileSystemURL;
+}
+
+namespace sync_file_system {
+
+// Represents local file sync status.
+// This class is supposed to run only on IO thread.
+//
+// This class manages two important synchronization flags: writing (counter)
+// and syncing (flag). Writing counter keeps track of which URL is in
+// writing and syncing flag indicates which URL is in syncing.
+//
+// An entry can have multiple writers but sync is exclusive and cannot overwrap
+// with any writes or syncs.
+class WEBKIT_STORAGE_EXPORT LocalFileSyncStatus : public base::NonThreadSafe {
+ public:
+ class WEBKIT_STORAGE_EXPORT Observer {
+ public:
+ Observer() {}
+ virtual ~Observer() {}
+ virtual void OnSyncEnabled(const fileapi::FileSystemURL& url) = 0;
+ virtual void OnWriteEnabled(const fileapi::FileSystemURL& url) = 0;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Observer);
+ };
+
+ LocalFileSyncStatus();
+ ~LocalFileSyncStatus();
+
+ // Increment writing counter for |url|.
+ // This should not be called if the |url| is not writable.
+ void StartWriting(const fileapi::FileSystemURL& url);
+
+ // Decrement writing counter for |url|.
+ void EndWriting(const fileapi::FileSystemURL& url);
+
+ // Start syncing for |url| and disable writing.
+ // This should not be called if |url| is in syncing or in writing.
+ void StartSyncing(const fileapi::FileSystemURL& url);
+
+ // Clears the syncing flag for |url| and enable writing.
+ void EndSyncing(const fileapi::FileSystemURL& url);
+
+ // Returns true if the |url| or its parent or child is in writing.
+ bool IsWriting(const fileapi::FileSystemURL& url) const;
+
+ // Returns true if the |url| is enabled for writing (i.e. not in syncing).
+ bool IsWritable(const fileapi::FileSystemURL& url) const;
+
+ // Returns true if the |url| is enabled for syncing (i.e. neither in
+ // syncing nor writing).
+ bool IsSyncable(const fileapi::FileSystemURL& url) const;
+
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ private:
+ typedef std::map<fileapi::FileSystemURL,int64,
+ fileapi::FileSystemURL::Comparator> URLCountMap;
+
+ bool IsChildOrParentWriting(const fileapi::FileSystemURL& url) const;
+ bool IsChildOrParentSyncing(const fileapi::FileSystemURL& url) const;
+
+ // If this count is non-zero positive there're ongoing write operations.
+ URLCountMap writing_;
+
+ // If this flag is set sync process is running on the file.
+ fileapi::FileSystemURLSet syncing_;
+
+ ObserverList<Observer> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalFileSyncStatus);
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_FILE_SYNC_STATUS_H_
diff --git a/webkit/browser/fileapi/syncable/local_file_sync_status_unittest.cc b/webkit/browser/fileapi/syncable/local_file_sync_status_unittest.cc
new file mode 100644
index 0000000..ff46322
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_file_sync_status_unittest.cc
@@ -0,0 +1,89 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
+
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using fileapi::FileSystemURL;
+
+namespace sync_file_system {
+
+namespace {
+
+const char kParent[] = "filesystem:http://foo.com/test/dir a";
+const char kFile[] = "filesystem:http://foo.com/test/dir a/dir b";
+const char kChild[] = "filesystem:http://foo.com/test/dir a/dir b/file";
+
+const char kOther1[] = "filesystem:http://foo.com/test/dir b";
+const char kOther2[] = "filesystem:http://foo.com/temporary/dir a";
+
+FileSystemURL URL(const char* spec) {
+ return FileSystemURL::CreateForTest((GURL(spec)));
+}
+
+} // namespace
+
+TEST(LocalFileSyncStatusTest, WritingSimple) {
+ LocalFileSyncStatus status;
+
+ status.StartWriting(URL(kFile));
+ status.StartWriting(URL(kFile));
+ status.EndWriting(URL(kFile));
+
+ EXPECT_TRUE(status.IsWriting(URL(kFile)));
+ EXPECT_TRUE(status.IsWriting(URL(kParent)));
+ EXPECT_TRUE(status.IsWriting(URL(kChild)));
+ EXPECT_FALSE(status.IsWriting(URL(kOther1)));
+ EXPECT_FALSE(status.IsWriting(URL(kOther2)));
+
+ // Adding writers doesn't change the entry's writability.
+ EXPECT_TRUE(status.IsWritable(URL(kFile)));
+ EXPECT_TRUE(status.IsWritable(URL(kParent)));
+ EXPECT_TRUE(status.IsWritable(URL(kChild)));
+ EXPECT_TRUE(status.IsWritable(URL(kOther1)));
+ EXPECT_TRUE(status.IsWritable(URL(kOther2)));
+
+ // Adding writers makes the entry non-syncable.
+ EXPECT_FALSE(status.IsSyncable(URL(kFile)));
+ EXPECT_FALSE(status.IsSyncable(URL(kParent)));
+ EXPECT_FALSE(status.IsSyncable(URL(kChild)));
+ EXPECT_TRUE(status.IsSyncable(URL(kOther1)));
+ EXPECT_TRUE(status.IsSyncable(URL(kOther2)));
+
+ status.EndWriting(URL(kFile));
+
+ EXPECT_FALSE(status.IsWriting(URL(kFile)));
+ EXPECT_FALSE(status.IsWriting(URL(kParent)));
+ EXPECT_FALSE(status.IsWriting(URL(kChild)));
+}
+
+TEST(LocalFileSyncStatusTest, SyncingSimple) {
+ LocalFileSyncStatus status;
+
+ status.StartSyncing(URL(kFile));
+
+ EXPECT_FALSE(status.IsWritable(URL(kFile)));
+ EXPECT_FALSE(status.IsWritable(URL(kParent)));
+ EXPECT_FALSE(status.IsWritable(URL(kChild)));
+ EXPECT_TRUE(status.IsWritable(URL(kOther1)));
+ EXPECT_TRUE(status.IsWritable(URL(kOther2)));
+
+ // New sync cannot be started for entries that are already in syncing.
+ EXPECT_FALSE(status.IsSyncable(URL(kFile)));
+ EXPECT_FALSE(status.IsSyncable(URL(kParent)));
+ EXPECT_FALSE(status.IsSyncable(URL(kChild)));
+ EXPECT_TRUE(status.IsSyncable(URL(kOther1)));
+ EXPECT_TRUE(status.IsSyncable(URL(kOther2)));
+
+ status.EndSyncing(URL(kFile));
+
+ EXPECT_TRUE(status.IsWritable(URL(kFile)));
+ EXPECT_TRUE(status.IsWritable(URL(kParent)));
+ EXPECT_TRUE(status.IsWritable(URL(kChild)));
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/local_origin_change_observer.h b/webkit/browser/fileapi/syncable/local_origin_change_observer.h
new file mode 100644
index 0000000..111f0a4
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/local_origin_change_observer.h
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_ORIGIN_CHANGE_OBSERVER_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_ORIGIN_CHANGE_OBSERVER_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "googleurl/src/gurl.h"
+
+namespace sync_file_system {
+
+class LocalOriginChangeObserver {
+ public:
+ LocalOriginChangeObserver() {}
+ ~LocalOriginChangeObserver() {}
+
+ virtual void OnChangesAvailableInOrigins(const std::set<GURL>& origins) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LocalOriginChangeObserver);
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_LOCAL_ORIGIN_CHANGE_OBSERVER_H_
diff --git a/webkit/browser/fileapi/syncable/mock_sync_status_observer.cc b/webkit/browser/fileapi/syncable/mock_sync_status_observer.cc
new file mode 100644
index 0000000..5fe7355
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/mock_sync_status_observer.cc
@@ -0,0 +1,15 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/mock_sync_status_observer.h"
+
+namespace sync_file_system {
+
+MockSyncStatusObserver::MockSyncStatusObserver() {
+}
+
+MockSyncStatusObserver::~MockSyncStatusObserver() {
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/mock_sync_status_observer.h b/webkit/browser/fileapi/syncable/mock_sync_status_observer.h
new file mode 100644
index 0000000..6061697
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/mock_sync_status_observer.h
@@ -0,0 +1,28 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_MOCK_SYNC_STATUS_OBSERVER_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_MOCK_SYNC_STATUS_OBSERVER_H_
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
+
+namespace sync_file_system {
+
+class MockSyncStatusObserver : public LocalFileSyncStatus::Observer {
+ public:
+ MockSyncStatusObserver();
+ virtual ~MockSyncStatusObserver();
+
+ // LocalFileSyncStatus::Observer overrides.
+ MOCK_METHOD1(OnSyncEnabled, void(const fileapi::FileSystemURL& url));
+ MOCK_METHOD1(OnWriteEnabled, void(const fileapi::FileSystemURL& url));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockSyncStatusObserver);
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_MOCK_SYNC_STATUS_OBSERVER_H_
diff --git a/webkit/browser/fileapi/syncable/sync_action.h b/webkit/browser/fileapi/syncable/sync_action.h
new file mode 100644
index 0000000..b0d66fc
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/sync_action.h
@@ -0,0 +1,26 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_ACTION_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_ACTION_H_
+
+namespace sync_file_system {
+
+enum SyncAction {
+ // Indicates no action has been made.
+ SYNC_ACTION_NONE,
+
+ // Indicates a new file or directory has been added.
+ SYNC_ACTION_ADDED,
+
+ // Indicates an existing file or directory has been updated.
+ SYNC_ACTION_UPDATED,
+
+ // Indicates a file or directory has been deleted.
+ SYNC_ACTION_DELETED,
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_ACTION_H_
diff --git a/webkit/browser/fileapi/syncable/sync_callbacks.h b/webkit/browser/fileapi/syncable/sync_callbacks.h
new file mode 100644
index 0000000..f02f512
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/sync_callbacks.h
@@ -0,0 +1,42 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_CALLBACKS_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_CALLBACKS_H_
+
+#include "base/callback_forward.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/syncable/sync_file_status.h"
+#include "webkit/browser/fileapi/syncable/sync_status_code.h"
+
+namespace fileapi {
+class FileSystemURL;
+}
+
+namespace sync_file_system {
+
+class SyncFileMetadata;
+
+typedef base::Callback<void(SyncStatusCode status)>
+ SyncStatusCallback;
+
+typedef base::Callback<void(SyncStatusCode status,
+ const fileapi::FileSystemURL& url)>
+ SyncFileCallback;
+
+typedef base::Callback<void(SyncStatusCode status,
+ const SyncFileMetadata& metadata)>
+ SyncFileMetadataCallback;
+
+typedef base::Callback<void(SyncStatusCode status,
+ const fileapi::FileSystemURLSet& urls)>
+ SyncFileSetCallback;
+
+typedef base::Callback<void(SyncStatusCode status,
+ SyncFileStatus sync_file_status)>
+ SyncFileStatusCallback;
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_CALLBACKS_H_
diff --git a/webkit/browser/fileapi/syncable/sync_direction.h b/webkit/browser/fileapi/syncable/sync_direction.h
new file mode 100644
index 0000000..e3ebc4c
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/sync_direction.h
@@ -0,0 +1,18 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_DIRECTION_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_DIRECTION_H_
+
+namespace sync_file_system {
+
+enum SyncDirection {
+ SYNC_DIRECTION_NONE,
+ SYNC_DIRECTION_LOCAL_TO_REMOTE,
+ SYNC_DIRECTION_REMOTE_TO_LOCAL,
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_DIRECTION_H_
diff --git a/webkit/browser/fileapi/syncable/sync_file_metadata.cc b/webkit/browser/fileapi/syncable/sync_file_metadata.cc
new file mode 100644
index 0000000..308940e
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/sync_file_metadata.cc
@@ -0,0 +1,36 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/sync_file_metadata.h"
+
+using sync_file_system::SyncFileType;
+
+namespace sync_file_system {
+
+SyncFileMetadata::SyncFileMetadata()
+ : file_type(sync_file_system::SYNC_FILE_TYPE_UNKNOWN),
+ size(-1) {
+}
+
+SyncFileMetadata::SyncFileMetadata(
+ SyncFileType file_type,
+ int64 size,
+ const base::Time& last_modified)
+ : file_type(file_type),
+ size(size),
+ last_modified(last_modified) {
+}
+
+SyncFileMetadata::~SyncFileMetadata() {}
+
+bool SyncFileMetadata::operator==(const SyncFileMetadata& that) const {
+ return file_type == that.file_type &&
+ size == that.size &&
+ last_modified == that.last_modified;
+}
+
+LocalFileSyncInfo::LocalFileSyncInfo() {}
+LocalFileSyncInfo::~LocalFileSyncInfo() {}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/sync_file_metadata.h b/webkit/browser/fileapi/syncable/sync_file_metadata.h
new file mode 100644
index 0000000..9e6dedc
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/sync_file_metadata.h
@@ -0,0 +1,43 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_FILE_METADATA_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_FILE_METADATA_H_
+
+#include "base/basictypes.h"
+#include "base/time.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/syncable/file_change.h"
+#include "webkit/storage/webkit_storage_export.h"
+
+namespace sync_file_system {
+
+class WEBKIT_STORAGE_EXPORT SyncFileMetadata {
+ public:
+ SyncFileMetadata();
+ SyncFileMetadata(SyncFileType file_type,
+ int64 size,
+ const base::Time& last_modified);
+ ~SyncFileMetadata();
+
+ SyncFileType file_type;
+ int64 size;
+ base::Time last_modified;
+
+ bool operator==(const SyncFileMetadata& that) const;
+};
+
+struct WEBKIT_STORAGE_EXPORT LocalFileSyncInfo {
+ LocalFileSyncInfo();
+ ~LocalFileSyncInfo();
+
+ fileapi::FileSystemURL url;
+ base::FilePath local_file_path;
+ SyncFileMetadata metadata;
+ FileChangeList changes;
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_FILE_METADATA_H_
diff --git a/webkit/browser/fileapi/syncable/sync_file_status.h b/webkit/browser/fileapi/syncable/sync_file_status.h
new file mode 100644
index 0000000..50b4e65
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/sync_file_status.h
@@ -0,0 +1,29 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_FILE_STATUS_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_FILE_STATUS_H_
+
+namespace sync_file_system {
+
+enum SyncFileStatus {
+ // The file has unknown sync status (e.g. the file is missing or there
+ // was an error while retrieving the sync status for the file).
+ SYNC_FILE_STATUS_UNKNOWN,
+
+ // The file has no pending local changes (may have pending remote changes
+ // though).
+ SYNC_FILE_STATUS_SYNCED,
+
+ // The file has pending local changes that haven't been reflected to the
+ // remote side.
+ SYNC_FILE_STATUS_HAS_PENDING_CHANGES,
+
+ // The file is in conflicting state.
+ SYNC_FILE_STATUS_CONFLICTING,
+};
+
+} // namespace fileapi
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_FILE_STATUS_H_
diff --git a/webkit/browser/fileapi/syncable/sync_file_type.h b/webkit/browser/fileapi/syncable/sync_file_type.h
new file mode 100644
index 0000000..8c47655
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/sync_file_type.h
@@ -0,0 +1,23 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_FILE_TYPE_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_FILE_TYPE_H_
+
+namespace sync_file_system {
+
+enum SyncFileType {
+ // For non-existent files or for files whose type is not known yet.
+ SYNC_FILE_TYPE_UNKNOWN,
+
+ // For regular files.
+ SYNC_FILE_TYPE_FILE,
+
+ // For directories/folders.
+ SYNC_FILE_TYPE_DIRECTORY,
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_FILE_TYPE_H_
diff --git a/webkit/browser/fileapi/syncable/sync_status_code.cc b/webkit/browser/fileapi/syncable/sync_status_code.cc
new file mode 100644
index 0000000..3809f6d
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/sync_status_code.cc
@@ -0,0 +1,145 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/sync_status_code.h"
+
+#include "base/logging.h"
+#include "third_party/leveldatabase/src/include/leveldb/db.h"
+
+namespace sync_file_system {
+
+std::string SyncStatusCodeToString(SyncStatusCode status) {
+ switch (status) {
+ case SYNC_STATUS_OK:
+ return "OK.";
+ case SYNC_STATUS_UNKNOWN:
+ return "Unknown sync status.";
+ case SYNC_STATUS_FAILED:
+ return "Failed.";
+
+ // PlatformFile related errors.
+ // TODO(nhiroki): add stringize function for PlatformFileError into base/.
+ case SYNC_FILE_ERROR_FAILED:
+ return "File operation failed.";
+ case SYNC_FILE_ERROR_IN_USE:
+ return "File currently in use.";
+ case SYNC_FILE_ERROR_EXISTS:
+ return "File already exists.";
+ case SYNC_FILE_ERROR_NOT_FOUND:
+ return "File not found.";
+ case SYNC_FILE_ERROR_ACCESS_DENIED:
+ return "File access denied.";
+ case SYNC_FILE_ERROR_TOO_MANY_OPENED:
+ return "Too many files open.";
+ case SYNC_FILE_ERROR_NO_MEMORY:
+ return "Out of memory.";
+ case SYNC_FILE_ERROR_NO_SPACE:
+ return "No space left on disk.";
+ case SYNC_FILE_ERROR_NOT_A_DIRECTORY:
+ return "Not a directory.";
+ case SYNC_FILE_ERROR_INVALID_OPERATION:
+ return "Invalid file operation.";
+ case SYNC_FILE_ERROR_SECURITY:
+ return "Security error.";
+ case SYNC_FILE_ERROR_ABORT:
+ return "File operation aborted.";
+ case SYNC_FILE_ERROR_NOT_A_FILE:
+ return "Not a file.";
+ case SYNC_FILE_ERROR_NOT_EMPTY:
+ return "File not empty.";
+ case SYNC_FILE_ERROR_INVALID_URL:
+ return "Invalid URL.";
+
+ // Database related errors.
+ case SYNC_DATABASE_ERROR_NOT_FOUND:
+ return "Database not found.";
+ case SYNC_DATABASE_ERROR_CORRUPTION:
+ return "Database was corrupted.";
+ case SYNC_DATABASE_ERROR_IO_ERROR:
+ return "Database I/O error.";
+ case SYNC_DATABASE_ERROR_FAILED:
+ return "Database operation failed.";
+
+ // Sync specific status code.
+ case SYNC_STATUS_FILE_BUSY:
+ return "Sync: file is busy.";
+ case SYNC_STATUS_HAS_CONFLICT:
+ return "Sync: file has conflict.";
+ case SYNC_STATUS_NO_CONFLICT:
+ return "Sync: file has no conflict.";
+ case SYNC_STATUS_ABORT:
+ return "Sync: operation aborted.";
+ case SYNC_STATUS_NO_CHANGE_TO_SYNC:
+ return "Sync: no change to synchronize.";
+ case SYNC_STATUS_RETRY:
+ return "Sync: retry to synchronize.";
+ case SYNC_STATUS_NETWORK_ERROR:
+ return "Sync: network error.";
+ case SYNC_STATUS_AUTHENTICATION_FAILED:
+ return "Sync: authentication failed.";
+ case SYNC_STATUS_UNKNOWN_ORIGIN:
+ return "Sync: unknown origin.";
+ case SYNC_STATUS_NOT_MODIFIED:
+ return "Sync: file not modified.";
+ case SYNC_STATUS_SYNC_DISABLED:
+ return "Sync: sync is disabled.";
+ }
+ NOTREACHED();
+ return "Unknown error.";
+}
+
+SyncStatusCode LevelDBStatusToSyncStatusCode(const leveldb::Status& status) {
+ if (status.ok())
+ return SYNC_STATUS_OK;
+ else if (status.IsNotFound())
+ return SYNC_DATABASE_ERROR_NOT_FOUND;
+ else if (status.IsCorruption())
+ return SYNC_DATABASE_ERROR_CORRUPTION;
+ else if (status.IsIOError())
+ return SYNC_DATABASE_ERROR_IO_ERROR;
+ else
+ return SYNC_DATABASE_ERROR_FAILED;
+}
+
+SyncStatusCode PlatformFileErrorToSyncStatusCode(
+ base::PlatformFileError file_error) {
+ switch (file_error) {
+ case base::PLATFORM_FILE_OK:
+ return SYNC_STATUS_OK;
+ case base::PLATFORM_FILE_ERROR_FAILED:
+ return SYNC_FILE_ERROR_FAILED;
+ case base::PLATFORM_FILE_ERROR_IN_USE:
+ return SYNC_FILE_ERROR_IN_USE;
+ case base::PLATFORM_FILE_ERROR_EXISTS:
+ return SYNC_FILE_ERROR_EXISTS;
+ case base::PLATFORM_FILE_ERROR_NOT_FOUND:
+ return SYNC_FILE_ERROR_NOT_FOUND;
+ case base::PLATFORM_FILE_ERROR_ACCESS_DENIED:
+ return SYNC_FILE_ERROR_ACCESS_DENIED;
+ case base::PLATFORM_FILE_ERROR_TOO_MANY_OPENED:
+ return SYNC_FILE_ERROR_TOO_MANY_OPENED;
+ case base::PLATFORM_FILE_ERROR_NO_MEMORY:
+ return SYNC_FILE_ERROR_NO_MEMORY;
+ case base::PLATFORM_FILE_ERROR_NO_SPACE:
+ return SYNC_FILE_ERROR_NO_SPACE;
+ case base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY:
+ return SYNC_FILE_ERROR_NOT_A_DIRECTORY;
+ case base::PLATFORM_FILE_ERROR_INVALID_OPERATION:
+ return SYNC_FILE_ERROR_INVALID_OPERATION;
+ case base::PLATFORM_FILE_ERROR_SECURITY:
+ return SYNC_FILE_ERROR_SECURITY;
+ case base::PLATFORM_FILE_ERROR_ABORT:
+ return SYNC_FILE_ERROR_ABORT;
+ case base::PLATFORM_FILE_ERROR_NOT_A_FILE:
+ return SYNC_FILE_ERROR_NOT_A_FILE;
+ case base::PLATFORM_FILE_ERROR_NOT_EMPTY:
+ return SYNC_FILE_ERROR_NOT_EMPTY;
+ case base::PLATFORM_FILE_ERROR_INVALID_URL:
+ return SYNC_FILE_ERROR_INVALID_URL;
+ default:
+ return SYNC_FILE_ERROR_FAILED;
+ }
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/sync_status_code.h b/webkit/browser/fileapi/syncable/sync_status_code.h
new file mode 100644
index 0000000..a93027f
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/sync_status_code.h
@@ -0,0 +1,74 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_STATUS_CODE_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_STATUS_CODE_H_
+
+#include <string>
+
+#include "base/platform_file.h"
+#include "webkit/storage/webkit_storage_export.h"
+
+namespace leveldb {
+class Status;
+}
+
+namespace sync_file_system {
+
+enum SyncStatusCode {
+ SYNC_STATUS_OK = 0,
+ SYNC_STATUS_UNKNOWN = -1000,
+
+ // Generic error code which is not specifically related to a specific
+ // submodule error code (yet).
+ SYNC_STATUS_FAILED = -1001,
+
+ // Basic ones that could be directly mapped to PlatformFileError.
+ SYNC_FILE_ERROR_FAILED = -1,
+ SYNC_FILE_ERROR_IN_USE = -2,
+ SYNC_FILE_ERROR_EXISTS = -3,
+ SYNC_FILE_ERROR_NOT_FOUND = -4,
+ SYNC_FILE_ERROR_ACCESS_DENIED = -5,
+ SYNC_FILE_ERROR_TOO_MANY_OPENED = -6,
+ SYNC_FILE_ERROR_NO_MEMORY = -7,
+ SYNC_FILE_ERROR_NO_SPACE = -8,
+ SYNC_FILE_ERROR_NOT_A_DIRECTORY = -9,
+ SYNC_FILE_ERROR_INVALID_OPERATION = -10,
+ SYNC_FILE_ERROR_SECURITY = -11,
+ SYNC_FILE_ERROR_ABORT = -12,
+ SYNC_FILE_ERROR_NOT_A_FILE = -13,
+ SYNC_FILE_ERROR_NOT_EMPTY = -14,
+ SYNC_FILE_ERROR_INVALID_URL = -15,
+
+ // Database related errors.
+ SYNC_DATABASE_ERROR_NOT_FOUND = -16,
+ SYNC_DATABASE_ERROR_CORRUPTION = -17,
+ SYNC_DATABASE_ERROR_IO_ERROR = -18,
+ SYNC_DATABASE_ERROR_FAILED = -19,
+
+ // Sync specific status code.
+ SYNC_STATUS_FILE_BUSY = -100,
+ SYNC_STATUS_HAS_CONFLICT = -101,
+ SYNC_STATUS_NO_CONFLICT = -102,
+ SYNC_STATUS_ABORT = -103,
+ SYNC_STATUS_NO_CHANGE_TO_SYNC = -104,
+ SYNC_STATUS_RETRY = -105,
+ SYNC_STATUS_NETWORK_ERROR = -106,
+ SYNC_STATUS_AUTHENTICATION_FAILED = -107,
+ SYNC_STATUS_UNKNOWN_ORIGIN = -108,
+ SYNC_STATUS_NOT_MODIFIED = -109,
+ SYNC_STATUS_SYNC_DISABLED = -110,
+};
+
+WEBKIT_STORAGE_EXPORT std::string SyncStatusCodeToString(SyncStatusCode status);
+
+WEBKIT_STORAGE_EXPORT SyncStatusCode LevelDBStatusToSyncStatusCode(
+ const leveldb::Status& status);
+
+WEBKIT_STORAGE_EXPORT SyncStatusCode PlatformFileErrorToSyncStatusCode(
+ base::PlatformFileError file_error);
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNC_STATUS_CODE_H_
diff --git a/webkit/browser/fileapi/syncable/syncable_file_operation_runner.cc b/webkit/browser/fileapi/syncable/syncable_file_operation_runner.cc
new file mode 100644
index 0000000..ecad964
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/syncable_file_operation_runner.cc
@@ -0,0 +1,107 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/syncable_file_operation_runner.h"
+
+#include <algorithm>
+#include <functional>
+
+#include "base/callback.h"
+#include "base/stl_util.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
+
+using fileapi::FileSystemURL;
+
+namespace sync_file_system {
+
+// SyncableFileOperationRunner::Task -------------------------------------------
+
+// static
+void SyncableFileOperationRunner::Task::CancelAndDelete(
+ SyncableFileOperationRunner::Task* task) {
+ task->Cancel();
+ delete task;
+}
+
+bool SyncableFileOperationRunner::Task::IsRunnable(
+ LocalFileSyncStatus* status) const {
+ for (size_t i = 0; i < target_paths().size(); ++i) {
+ if (!status->IsWritable(target_paths()[i]))
+ return false;
+ }
+ return true;
+}
+
+void SyncableFileOperationRunner::Task::Start(LocalFileSyncStatus* status) {
+ for (size_t i = 0; i < target_paths().size(); ++i) {
+ DCHECK(status->IsWritable(target_paths()[i]));
+ status->StartWriting(target_paths()[i]);
+ }
+ Run();
+}
+
+// SyncableFileOperationRunner -------------------------------------------------
+
+SyncableFileOperationRunner::SyncableFileOperationRunner(
+ int64 max_inflight_tasks,
+ LocalFileSyncStatus* sync_status)
+ : sync_status_(sync_status),
+ max_inflight_tasks_(max_inflight_tasks),
+ num_inflight_tasks_(0) {
+ DCHECK(CalledOnValidThread());
+ sync_status_->AddObserver(this);
+}
+
+SyncableFileOperationRunner::~SyncableFileOperationRunner() {
+ DCHECK(CalledOnValidThread());
+ for_each(pending_tasks_.begin(), pending_tasks_.end(),
+ SyncableFileOperationRunner::Task::CancelAndDelete);
+}
+
+void SyncableFileOperationRunner::OnSyncEnabled(const FileSystemURL& url) {
+}
+
+void SyncableFileOperationRunner::OnWriteEnabled(const FileSystemURL& url) {
+ DCHECK(CalledOnValidThread());
+ RunNextRunnableTask();
+}
+
+void SyncableFileOperationRunner::PostOperationTask(scoped_ptr<Task> task) {
+ DCHECK(CalledOnValidThread());
+ pending_tasks_.push_back(task.release());
+ RunNextRunnableTask();
+}
+
+void SyncableFileOperationRunner::RunNextRunnableTask() {
+ DCHECK(CalledOnValidThread());
+ for (std::list<Task*>::iterator iter = pending_tasks_.begin();
+ iter != pending_tasks_.end() && ShouldStartMoreTasks();) {
+ if ((*iter)->IsRunnable(sync_status())) {
+ ++num_inflight_tasks_;
+ DCHECK_GE(num_inflight_tasks_, 1);
+ scoped_ptr<Task> task(*iter);
+ pending_tasks_.erase(iter++);
+ task->Start(sync_status());
+ continue;
+ }
+ ++iter;
+ }
+}
+
+void SyncableFileOperationRunner::OnOperationCompleted(
+ const std::vector<FileSystemURL>& target_paths) {
+ --num_inflight_tasks_;
+ DCHECK_GE(num_inflight_tasks_, 0);
+ for (size_t i = 0; i < target_paths.size(); ++i) {
+ DCHECK(sync_status()->IsWriting(target_paths[i]));
+ sync_status()->EndWriting(target_paths[i]);
+ }
+ RunNextRunnableTask();
+}
+
+bool SyncableFileOperationRunner::ShouldStartMoreTasks() const {
+ return num_inflight_tasks_ < max_inflight_tasks_;
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/syncable_file_operation_runner.h b/webkit/browser/fileapi/syncable/syncable_file_operation_runner.h
new file mode 100644
index 0000000..2b2f9d0
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/syncable_file_operation_runner.h
@@ -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.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNCABLE_FILE_OPERATION_RUNNER_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNCABLE_FILE_OPERATION_RUNNER_H_
+
+#include <list>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
+#include "webkit/storage/webkit_storage_export.h"
+
+namespace fileapi {
+class FileSystemURL;
+}
+
+namespace sync_file_system {
+
+// This class must run only on IO thread.
+// Owned by LocalFileSyncContext.
+class WEBKIT_STORAGE_EXPORT SyncableFileOperationRunner
+ : public base::NonThreadSafe,
+ public base::SupportsWeakPtr<SyncableFileOperationRunner>,
+ public LocalFileSyncStatus::Observer {
+ public:
+ // Represents an operation task (which usually wraps one FileSystemOperation).
+ class Task {
+ public:
+ Task() {}
+ virtual ~Task() {}
+
+ // Only one of Run() or Cancel() is called.
+ virtual void Run() = 0;
+ virtual void Cancel() = 0;
+
+ protected:
+ // This is never called after Run() or Cancel() is called.
+ virtual const std::vector<fileapi::FileSystemURL>& target_paths() const = 0;
+
+ private:
+ friend class SyncableFileOperationRunner;
+ bool IsRunnable(LocalFileSyncStatus* status) const;
+ void Start(LocalFileSyncStatus* status);
+ static void CancelAndDelete(Task* task);
+
+ DISALLOW_COPY_AND_ASSIGN(Task);
+ };
+
+ SyncableFileOperationRunner(int64 max_inflight_tasks,
+ LocalFileSyncStatus* sync_status);
+ virtual ~SyncableFileOperationRunner();
+
+ // LocalFileSyncStatus::Observer overrides.
+ virtual void OnSyncEnabled(const fileapi::FileSystemURL& url) OVERRIDE;
+ virtual void OnWriteEnabled(const fileapi::FileSystemURL& url) OVERRIDE;
+
+ // Runs the given |task| if no sync operation is running on any of
+ // its target_paths(). This also runs pending tasks that have become
+ // runnable (before running the given operation).
+ // If there're ongoing sync tasks on the target_paths this method
+ // just queues up the |task|.
+ // Pending tasks are cancelled when this class is destructed.
+ void PostOperationTask(scoped_ptr<Task> task);
+
+ // Runs a next runnable task (if there's any).
+ void RunNextRunnableTask();
+
+ // Called when an operation is completed. This will make |target_paths|
+ // writable and may start a next runnable task.
+ void OnOperationCompleted(
+ const std::vector<fileapi::FileSystemURL>& target_paths);
+
+ LocalFileSyncStatus* sync_status() const { return sync_status_; }
+
+ int64 num_pending_tasks() const {
+ return static_cast<int64>(pending_tasks_.size());
+ }
+
+ int64 num_inflight_tasks() const { return num_inflight_tasks_; }
+
+ private:
+ // Returns true if we should start more tasks.
+ bool ShouldStartMoreTasks() const;
+
+ // Keeps track of the writing/syncing status. Not owned.
+ LocalFileSyncStatus* sync_status_;
+
+ std::list<Task*> pending_tasks_;
+
+ const int64 max_inflight_tasks_;
+ int64 num_inflight_tasks_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncableFileOperationRunner);
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNCABLE_FILE_OPERATION_RUNNER_H_
diff --git a/webkit/browser/fileapi/syncable/syncable_file_operation_runner_unittest.cc b/webkit/browser/fileapi/syncable/syncable_file_operation_runner_unittest.cc
new file mode 100644
index 0000000..2313a92
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/syncable_file_operation_runner_unittest.cc
@@ -0,0 +1,371 @@
+// 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 <string>
+
+#include "base/basictypes.h"
+#include "base/file_util.h"
+#include "base/location.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/blob/mock_blob_url_request_context.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/syncable/canned_syncable_file_system.h"
+#include "webkit/browser/fileapi/syncable/local_file_change_tracker.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_context.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_status.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_operation_runner.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_system_operation.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+
+using fileapi::FileSystemOperation;
+using fileapi::FileSystemURL;
+using webkit_blob::MockBlobURLRequestContext;
+using webkit_blob::ScopedTextBlob;
+using base::PlatformFileError;
+
+namespace sync_file_system {
+
+namespace {
+const std::string kServiceName = "test";
+const std::string kParent = "foo";
+const std::string kFile = "foo/file";
+const std::string kDir = "foo/dir";
+const std::string kChild = "foo/dir/bar";
+const std::string kOther = "bar";
+} // namespace
+
+class SyncableFileOperationRunnerTest : public testing::Test {
+ protected:
+ typedef FileSystemOperation::StatusCallback StatusCallback;
+
+ // Use the current thread as IO thread so that we can directly call
+ // operations in the tests.
+ SyncableFileOperationRunnerTest()
+ : message_loop_(base::MessageLoop::TYPE_IO),
+ file_system_(GURL("http://example.com"), kServiceName,
+ base::MessageLoopProxy::current(),
+ base::MessageLoopProxy::current()),
+ callback_count_(0),
+ write_status_(base::PLATFORM_FILE_ERROR_FAILED),
+ write_bytes_(0),
+ write_complete_(false),
+ url_request_context_(file_system_.file_system_context()),
+ weak_factory_(this) {}
+
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(dir_.CreateUniqueTempDir());
+ file_system_.SetUp();
+ sync_context_ = new LocalFileSyncContext(base::MessageLoopProxy::current(),
+ base::MessageLoopProxy::current());
+ ASSERT_EQ(SYNC_STATUS_OK,
+ file_system_.MaybeInitializeFileSystemContext(sync_context_));
+
+ ASSERT_EQ(base::PLATFORM_FILE_OK, file_system_.OpenFileSystem());
+ ASSERT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kParent)));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (sync_context_)
+ sync_context_->ShutdownOnUIThread();
+ sync_context_ = NULL;
+
+ file_system_.TearDown();
+ message_loop_.RunUntilIdle();
+ RevokeSyncableFileSystem(kServiceName);
+ }
+
+ FileSystemURL URL(const std::string& path) {
+ return file_system_.URL(path);
+ }
+
+ LocalFileSyncStatus* sync_status() {
+ return file_system_.file_system_context()->sync_context()->sync_status();
+ }
+
+ void ResetCallbackStatus() {
+ write_status_ = base::PLATFORM_FILE_ERROR_FAILED;
+ write_bytes_ = 0;
+ write_complete_ = false;
+ callback_count_ = 0;
+ }
+
+ StatusCallback ExpectStatus(const tracked_objects::Location& location,
+ PlatformFileError expect) {
+ return base::Bind(&SyncableFileOperationRunnerTest::DidFinish,
+ weak_factory_.GetWeakPtr(), location, expect);
+ }
+
+ FileSystemOperation::WriteCallback GetWriteCallback(
+ const tracked_objects::Location& location) {
+ return base::Bind(&SyncableFileOperationRunnerTest::DidWrite,
+ weak_factory_.GetWeakPtr(), location);
+ }
+
+ void DidWrite(const tracked_objects::Location& location,
+ PlatformFileError status, int64 bytes, bool complete) {
+ SCOPED_TRACE(testing::Message() << location.ToString());
+ write_status_ = status;
+ write_bytes_ += bytes;
+ write_complete_ = complete;
+ ++callback_count_;
+ }
+
+ void DidFinish(const tracked_objects::Location& location,
+ PlatformFileError expect, PlatformFileError status) {
+ SCOPED_TRACE(testing::Message() << location.ToString());
+ EXPECT_EQ(expect, status);
+ ++callback_count_;
+ }
+
+ bool CreateTempFile(base::FilePath* path) {
+ return file_util::CreateTemporaryFileInDir(dir_.path(), path);
+ }
+
+ base::ScopedTempDir dir_;
+
+ base::MessageLoop message_loop_;
+ CannedSyncableFileSystem file_system_;
+ scoped_refptr<LocalFileSyncContext> sync_context_;
+
+ int callback_count_;
+ PlatformFileError write_status_;
+ size_t write_bytes_;
+ bool write_complete_;
+
+ MockBlobURLRequestContext url_request_context_;
+
+ base::WeakPtrFactory<SyncableFileOperationRunnerTest> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncableFileOperationRunnerTest);
+};
+
+TEST_F(SyncableFileOperationRunnerTest, SimpleQueue) {
+ sync_status()->StartSyncing(URL(kFile));
+ ASSERT_FALSE(sync_status()->IsWritable(URL(kFile)));
+
+ // The URL is in syncing so the write operations won't run.
+ ResetCallbackStatus();
+ file_system_.NewOperation()->CreateFile(
+ URL(kFile), false /* exclusive */,
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ file_system_.NewOperation()->Truncate(
+ URL(kFile), 1,
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, callback_count_);
+
+ // Read operations are not blocked (and are executed before queued ones).
+ file_system_.NewOperation()->FileExists(
+ URL(kFile), ExpectStatus(FROM_HERE, base::PLATFORM_FILE_ERROR_NOT_FOUND));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, callback_count_);
+
+ // End syncing (to enable write).
+ sync_status()->EndSyncing(URL(kFile));
+ ASSERT_TRUE(sync_status()->IsWritable(URL(kFile)));
+
+ ResetCallbackStatus();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(2, callback_count_);
+
+ // Now the file must have been created and updated.
+ ResetCallbackStatus();
+ file_system_.NewOperation()->FileExists(
+ URL(kFile), ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, callback_count_);
+}
+
+TEST_F(SyncableFileOperationRunnerTest, WriteToParentAndChild) {
+ // First create the kDir directory and kChild in the dir.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.CreateDirectory(URL(kDir)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.CreateFile(URL(kChild)));
+
+ // Start syncing the kDir directory.
+ sync_status()->StartSyncing(URL(kDir));
+ ASSERT_FALSE(sync_status()->IsWritable(URL(kDir)));
+
+ // Writes to kParent and kChild should be all queued up.
+ ResetCallbackStatus();
+ file_system_.NewOperation()->Truncate(
+ URL(kChild), 1, ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ file_system_.NewOperation()->Remove(
+ URL(kParent), true /* recursive */,
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, callback_count_);
+
+ // Read operations are not blocked (and are executed before queued ones).
+ file_system_.NewOperation()->DirectoryExists(
+ URL(kDir), ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, callback_count_);
+
+ // Writes to unrelated files must succeed as well.
+ ResetCallbackStatus();
+ file_system_.NewOperation()->CreateDirectory(
+ URL(kOther), false /* exclusive */, false /* recursive */,
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, callback_count_);
+
+ // End syncing (to enable write).
+ sync_status()->EndSyncing(URL(kDir));
+ ASSERT_TRUE(sync_status()->IsWritable(URL(kDir)));
+
+ ResetCallbackStatus();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(2, callback_count_);
+}
+
+TEST_F(SyncableFileOperationRunnerTest, CopyAndMove) {
+ // First create the kDir directory and kChild in the dir.
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.CreateDirectory(URL(kDir)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.CreateFile(URL(kChild)));
+
+ // Start syncing the kParent directory.
+ sync_status()->StartSyncing(URL(kParent));
+
+ // Copying kDir to other directory should succeed, while moving would fail
+ // (since the source directory is in syncing).
+ ResetCallbackStatus();
+ file_system_.NewOperation()->Copy(
+ URL(kDir), URL("dest-copy"),
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ file_system_.NewOperation()->Move(
+ URL(kDir), URL("dest-move"),
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, callback_count_);
+
+ // Only "dest-copy1" should exist.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.DirectoryExists(URL("dest-copy")));
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ file_system_.DirectoryExists(URL("dest-move")));
+
+ // Start syncing the "dest-copy2" directory.
+ sync_status()->StartSyncing(URL("dest-copy2"));
+
+ // Now the destination is also locked copying kDir should be queued.
+ ResetCallbackStatus();
+ file_system_.NewOperation()->Copy(
+ URL(kDir), URL("dest-copy2"),
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, callback_count_);
+
+ // Finish syncing the "dest-copy2" directory to unlock Copy.
+ sync_status()->EndSyncing(URL("dest-copy2"));
+ ResetCallbackStatus();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, callback_count_);
+
+ // Now we should have "dest-copy2".
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.DirectoryExists(URL("dest-copy2")));
+
+ // Finish syncing the kParent to unlock Move.
+ sync_status()->EndSyncing(URL(kParent));
+ ResetCallbackStatus();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, callback_count_);
+
+ // Now we should have "dest-move".
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.DirectoryExists(URL("dest-move")));
+}
+
+TEST_F(SyncableFileOperationRunnerTest, Write) {
+ EXPECT_EQ(base::PLATFORM_FILE_OK, file_system_.CreateFile(URL(kFile)));
+ const GURL kBlobURL("blob:foo");
+ const std::string kData("Lorem ipsum.");
+ ScopedTextBlob blob(url_request_context_, kBlobURL, kData);
+
+ sync_status()->StartSyncing(URL(kFile));
+
+ ResetCallbackStatus();
+ file_system_.NewOperation()->Write(
+ &url_request_context_,
+ URL(kFile), kBlobURL, 0, GetWriteCallback(FROM_HERE));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, callback_count_);
+
+ sync_status()->EndSyncing(URL(kFile));
+ ResetCallbackStatus();
+
+ while (!write_complete_)
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK, write_status_);
+ EXPECT_EQ(kData.size(), write_bytes_);
+ EXPECT_TRUE(write_complete_);
+}
+
+TEST_F(SyncableFileOperationRunnerTest, QueueAndCancel) {
+ sync_status()->StartSyncing(URL(kFile));
+ ASSERT_FALSE(sync_status()->IsWritable(URL(kFile)));
+
+ ResetCallbackStatus();
+ file_system_.NewOperation()->CreateFile(
+ URL(kFile), false /* exclusive */,
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_ERROR_ABORT));
+ file_system_.NewOperation()->Truncate(
+ URL(kFile), 1,
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_ERROR_ABORT));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, callback_count_);
+
+ ResetCallbackStatus();
+
+ // This shouldn't crash nor leak memory.
+ sync_context_->ShutdownOnUIThread();
+ sync_context_ = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(2, callback_count_);
+}
+
+// Test if CopyInForeignFile runs cooperatively with other Sync operations
+// when it is called directly via AsLocalFileSystemOperation.
+TEST_F(SyncableFileOperationRunnerTest, CopyInForeignFile) {
+ const std::string kTestData("test data");
+
+ base::FilePath temp_path;
+ ASSERT_TRUE(CreateTempFile(&temp_path));
+ ASSERT_EQ(static_cast<int>(kTestData.size()),
+ file_util::WriteFile(
+ temp_path, kTestData.data(), kTestData.size()));
+
+ sync_status()->StartSyncing(URL(kFile));
+ ASSERT_FALSE(sync_status()->IsWritable(URL(kFile)));
+
+ // The URL is in syncing so CopyIn (which is a write operation) won't run.
+ ResetCallbackStatus();
+ file_system_.NewOperation()->AsLocalFileSystemOperation()->CopyInForeignFile(
+ temp_path, URL(kFile),
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, callback_count_);
+
+ // End syncing (to enable write).
+ sync_status()->EndSyncing(URL(kFile));
+ ASSERT_TRUE(sync_status()->IsWritable(URL(kFile)));
+
+ ResetCallbackStatus();
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, callback_count_);
+
+ // Now the file must have been created and have the same content as temp_path.
+ ResetCallbackStatus();
+ file_system_.DoVerifyFile(
+ URL(kFile), kTestData,
+ ExpectStatus(FROM_HERE, base::PLATFORM_FILE_OK));
+ base::MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(1, callback_count_);
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/syncable_file_system_operation.cc b/webkit/browser/fileapi/syncable/syncable_file_system_operation.cc
new file mode 100644
index 0000000..7a7dc33
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/syncable_file_system_operation.cc
@@ -0,0 +1,408 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/syncable_file_system_operation.h"
+
+#include "base/logging.h"
+#include "webkit/blob/shareable_file_reference.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/browser/fileapi/sandbox_mount_point_provider.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_context.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_operation_runner.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+
+using fileapi::FileSystemURL;
+using fileapi::FileSystemOperationContext;
+using fileapi::LocalFileSystemOperation;
+
+namespace sync_file_system {
+
+namespace {
+
+void WriteCallbackAdapter(
+ const SyncableFileSystemOperation::WriteCallback& callback,
+ base::PlatformFileError status) {
+ callback.Run(status, 0, true);
+}
+
+} // namespace
+
+class SyncableFileSystemOperation::QueueableTask
+ : public SyncableFileOperationRunner::Task {
+ public:
+ QueueableTask(SyncableFileSystemOperation* operation,
+ const base::Closure& task)
+ : operation_(operation), task_(task) {}
+
+ virtual ~QueueableTask() {
+ DCHECK(!operation_);
+ }
+
+ virtual void Run() OVERRIDE {
+ DCHECK(!task_.is_null());
+ task_.Run();
+ operation_ = NULL;
+ }
+
+ virtual void Cancel() OVERRIDE {
+ DCHECK(!task_.is_null());
+ DCHECK(operation_);
+ operation_->OnCancelled();
+ task_.Reset(); // This will delete operation_.
+ operation_ = NULL;
+ }
+
+ virtual std::vector<FileSystemURL>& target_paths() const OVERRIDE {
+ DCHECK(operation_);
+ return operation_->target_paths_;
+ }
+
+ private:
+ SyncableFileSystemOperation* operation_;
+ base::Closure task_;
+ DISALLOW_COPY_AND_ASSIGN(QueueableTask);
+};
+
+SyncableFileSystemOperation::~SyncableFileSystemOperation() {}
+
+void SyncableFileSystemOperation::CreateFile(
+ const FileSystemURL& url,
+ bool exclusive,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ DCHECK(operation_runner_.get());
+ target_paths_.push_back(url);
+ completion_callback_ = callback;
+ scoped_ptr<SyncableFileOperationRunner::Task> task(new QueueableTask(
+ this,
+ base::Bind(&FileSystemOperation::CreateFile,
+ base::Unretained(NewOperation()),
+ url, exclusive,
+ base::Bind(&self::DidFinish, base::Owned(this)))));
+ operation_runner_->PostOperationTask(task.Pass());
+}
+
+void SyncableFileSystemOperation::CreateDirectory(
+ const FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ if (!is_directory_operation_enabled_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
+ return;
+ }
+ DCHECK(operation_runner_.get());
+ target_paths_.push_back(url);
+ completion_callback_ = callback;
+ scoped_ptr<SyncableFileOperationRunner::Task> task(new QueueableTask(
+ this,
+ base::Bind(&FileSystemOperation::CreateDirectory,
+ base::Unretained(NewOperation()),
+ url, exclusive, recursive,
+ base::Bind(&self::DidFinish, base::Owned(this)))));
+ operation_runner_->PostOperationTask(task.Pass());
+}
+
+void SyncableFileSystemOperation::Copy(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ DCHECK(operation_runner_.get());
+ target_paths_.push_back(dest_url);
+ completion_callback_ = callback;
+ scoped_ptr<SyncableFileOperationRunner::Task> task(new QueueableTask(
+ this,
+ base::Bind(&FileSystemOperation::Copy,
+ base::Unretained(NewOperation()),
+ src_url, dest_url,
+ base::Bind(&self::DidFinish, base::Owned(this)))));
+ operation_runner_->PostOperationTask(task.Pass());
+}
+
+void SyncableFileSystemOperation::Move(
+ const FileSystemURL& src_url,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ DCHECK(operation_runner_.get());
+ target_paths_.push_back(src_url);
+ target_paths_.push_back(dest_url);
+ completion_callback_ = callback;
+ scoped_ptr<SyncableFileOperationRunner::Task> task(new QueueableTask(
+ this,
+ base::Bind(&FileSystemOperation::Move,
+ base::Unretained(NewOperation()),
+ src_url, dest_url,
+ base::Bind(&self::DidFinish, base::Owned(this)))));
+ operation_runner_->PostOperationTask(task.Pass());
+}
+
+void SyncableFileSystemOperation::DirectoryExists(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ NewOperation()->DirectoryExists(url, callback);
+ delete this;
+}
+
+void SyncableFileSystemOperation::FileExists(
+ const FileSystemURL& url,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ NewOperation()->FileExists(url, callback);
+ delete this;
+}
+
+void SyncableFileSystemOperation::GetMetadata(
+ const FileSystemURL& url,
+ const GetMetadataCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ base::PlatformFileInfo(), base::FilePath());
+ delete this;
+ return;
+ }
+ NewOperation()->GetMetadata(url, callback);
+ delete this;
+}
+
+void SyncableFileSystemOperation::ReadDirectory(
+ const FileSystemURL& url,
+ const ReadDirectoryCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND, FileEntryList(), false);
+ delete this;
+ return;
+ }
+ // This is a read operation and there'd be no hard to let it go even if
+ // directory operation is disabled. (And we should allow this if it's made
+ // on the root directory)
+ NewOperation()->ReadDirectory(url, callback);
+ delete this;
+}
+
+void SyncableFileSystemOperation::Remove(
+ const FileSystemURL& url, bool recursive,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ DCHECK(operation_runner_.get());
+ target_paths_.push_back(url);
+ completion_callback_ = callback;
+ scoped_ptr<SyncableFileOperationRunner::Task> task(new QueueableTask(
+ this,
+ base::Bind(&FileSystemOperation::Remove,
+ base::Unretained(NewOperation()),
+ url, recursive,
+ base::Bind(&self::DidFinish, base::Owned(this)))));
+ operation_runner_->PostOperationTask(task.Pass());
+}
+
+void SyncableFileSystemOperation::Write(
+ const net::URLRequestContext* url_request_context,
+ const FileSystemURL& url,
+ const GURL& blob_url,
+ int64 offset,
+ const WriteCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND, 0, true);
+ delete this;
+ return;
+ }
+ DCHECK(operation_runner_.get());
+ target_paths_.push_back(url);
+ completion_callback_ = base::Bind(&WriteCallbackAdapter, callback);
+ scoped_ptr<SyncableFileOperationRunner::Task> task(new QueueableTask(
+ this,
+ NewOperation()->GetWriteClosure(
+ url_request_context, url, blob_url, offset,
+ base::Bind(&self::DidWrite, base::Owned(this), callback))));
+ operation_runner_->PostOperationTask(task.Pass());
+}
+
+void SyncableFileSystemOperation::Truncate(
+ const FileSystemURL& url, int64 length,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ DCHECK(operation_runner_.get());
+ target_paths_.push_back(url);
+ completion_callback_ = callback;
+ scoped_ptr<SyncableFileOperationRunner::Task> task(new QueueableTask(
+ this,
+ base::Bind(&FileSystemOperation::Truncate,
+ base::Unretained(NewOperation()),
+ url, length,
+ base::Bind(&self::DidFinish, base::Owned(this)))));
+ operation_runner_->PostOperationTask(task.Pass());
+}
+
+void SyncableFileSystemOperation::TouchFile(
+ const FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ NewOperation()->TouchFile(url, last_access_time,
+ last_modified_time, callback);
+ delete this;
+}
+
+void SyncableFileSystemOperation::OpenFile(
+ const FileSystemURL& url,
+ int file_flags,
+ base::ProcessHandle peer_handle,
+ const OpenFileCallback& callback) {
+ NOTREACHED();
+ delete this;
+}
+
+void SyncableFileSystemOperation::Cancel(
+ const StatusCallback& cancel_callback) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(inflight_operation_);
+ completion_callback_ = cancel_callback;
+ NewOperation()->Cancel(base::Bind(&self::DidFinish, base::Owned(this)));
+}
+
+void SyncableFileSystemOperation::CreateSnapshotFile(
+ const FileSystemURL& path,
+ const SnapshotFileCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ callback.Run(base::PLATFORM_FILE_ERROR_NOT_FOUND,
+ base::PlatformFileInfo(), base::FilePath(), NULL);
+ delete this;
+ return;
+ }
+ NewOperation()->CreateSnapshotFile(path, callback);
+ delete this;
+}
+
+void SyncableFileSystemOperation::CopyInForeignFile(
+ const base::FilePath& src_local_disk_path,
+ const FileSystemURL& dest_url,
+ const StatusCallback& callback) {
+ DCHECK(CalledOnValidThread());
+ if (!operation_runner_) {
+ AbortOperation(callback, base::PLATFORM_FILE_ERROR_NOT_FOUND);
+ return;
+ }
+ DCHECK(operation_runner_.get());
+ target_paths_.push_back(dest_url);
+ completion_callback_ = callback;
+ scoped_ptr<SyncableFileOperationRunner::Task> task(new QueueableTask(
+ this,
+ base::Bind(&LocalFileSystemOperation::CopyInForeignFile,
+ base::Unretained(NewOperation()),
+ src_local_disk_path, dest_url,
+ base::Bind(&self::DidFinish, base::Owned(this)))));
+ operation_runner_->PostOperationTask(task.Pass());
+}
+
+SyncableFileSystemOperation::SyncableFileSystemOperation(
+ fileapi::FileSystemContext* file_system_context,
+ scoped_ptr<FileSystemOperationContext> operation_context)
+ : LocalFileSystemOperation(file_system_context,
+ operation_context.Pass()),
+ inflight_operation_(NULL) {
+ DCHECK(file_system_context);
+ if (!file_system_context->sync_context()) {
+ // Syncable FileSystem is opened in a file system context which doesn't
+ // support (or is not initialized for) the API.
+ // Returning here to leave operation_runner_ as NULL.
+ return;
+ }
+ operation_runner_ = file_system_context->sync_context()->operation_runner();
+ is_directory_operation_enabled_ = IsSyncFSDirectoryOperationEnabled();
+}
+
+LocalFileSystemOperation* SyncableFileSystemOperation::NewOperation() {
+ DCHECK(operation_context_);
+ inflight_operation_ = new LocalFileSystemOperation(
+ file_system_context(),
+ operation_context_.Pass());
+ DCHECK(inflight_operation_);
+ return inflight_operation_;
+}
+
+void SyncableFileSystemOperation::DidFinish(base::PlatformFileError status) {
+ DCHECK(CalledOnValidThread());
+ DCHECK(!completion_callback_.is_null());
+ if (operation_runner_)
+ operation_runner_->OnOperationCompleted(target_paths_);
+ completion_callback_.Run(status);
+}
+
+void SyncableFileSystemOperation::DidWrite(
+ const WriteCallback& callback,
+ base::PlatformFileError result,
+ int64 bytes,
+ bool complete) {
+ DCHECK(CalledOnValidThread());
+ if (!complete) {
+ callback.Run(result, bytes, complete);
+ return;
+ }
+ if (operation_runner_)
+ operation_runner_->OnOperationCompleted(target_paths_);
+ callback.Run(result, bytes, complete);
+}
+
+void SyncableFileSystemOperation::OnCancelled() {
+ DCHECK(!completion_callback_.is_null());
+ completion_callback_.Run(base::PLATFORM_FILE_ERROR_ABORT);
+ delete inflight_operation_;
+}
+
+void SyncableFileSystemOperation::AbortOperation(
+ const StatusCallback& callback,
+ base::PlatformFileError error) {
+ callback.Run(error);
+ delete inflight_operation_;
+ delete this;
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/syncable_file_system_operation.h b/webkit/browser/fileapi/syncable/syncable_file_system_operation.h
new file mode 100644
index 0000000..4dbc4ae
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/syncable_file_system_operation.h
@@ -0,0 +1,119 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNCABLE_FILE_SYSTEM_OPERATION_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNCABLE_FILE_SYSTEM_OPERATION_H_
+
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "webkit/browser/fileapi/local_file_system_operation.h"
+#include "webkit/storage/webkit_storage_export.h"
+
+namespace fileapi {
+class FileSystemContext;
+class FileSystemOperationContext;
+class SandboxMountPointProvider;
+}
+
+namespace sync_file_system {
+
+class SyncableFileOperationRunner;
+
+// A wrapper class of LocalFileSystemOperation for syncable file system.
+class WEBKIT_STORAGE_EXPORT SyncableFileSystemOperation
+ : public fileapi::LocalFileSystemOperation,
+ public base::NonThreadSafe {
+ public:
+ virtual ~SyncableFileSystemOperation();
+
+ // fileapi::FileSystemOperation overrides.
+ virtual void CreateFile(const fileapi::FileSystemURL& url,
+ bool exclusive,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void CreateDirectory(const fileapi::FileSystemURL& url,
+ bool exclusive,
+ bool recursive,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void Copy(const fileapi::FileSystemURL& src_url,
+ const fileapi::FileSystemURL& dest_url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void Move(const fileapi::FileSystemURL& src_url,
+ const fileapi::FileSystemURL& dest_url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void DirectoryExists(const fileapi::FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void FileExists(const fileapi::FileSystemURL& url,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void GetMetadata(const fileapi::FileSystemURL& url,
+ const GetMetadataCallback& callback) OVERRIDE;
+ virtual void ReadDirectory(const fileapi::FileSystemURL& url,
+ const ReadDirectoryCallback& callback) OVERRIDE;
+ virtual void Remove(const fileapi::FileSystemURL& url, bool recursive,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void Write(const net::URLRequestContext* url_request_context,
+ const fileapi::FileSystemURL& url,
+ const GURL& blob_url,
+ int64 offset,
+ const WriteCallback& callback) OVERRIDE;
+ virtual void Truncate(const fileapi::FileSystemURL& url, int64 length,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void TouchFile(const fileapi::FileSystemURL& url,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const StatusCallback& callback) OVERRIDE;
+ virtual void OpenFile(const fileapi::FileSystemURL& url,
+ int file_flags,
+ base::ProcessHandle peer_handle,
+ const OpenFileCallback& callback) OVERRIDE;
+ virtual void Cancel(const StatusCallback& cancel_callback) OVERRIDE;
+ virtual void CreateSnapshotFile(
+ const fileapi::FileSystemURL& path,
+ const SnapshotFileCallback& callback) OVERRIDE;
+
+ // LocalFileSystemOperation overrides.
+ virtual void CopyInForeignFile(const base::FilePath& src_local_disk_path,
+ const fileapi::FileSystemURL& dest_url,
+ const StatusCallback& callback) OVERRIDE;
+
+ private:
+ typedef SyncableFileSystemOperation self;
+ class QueueableTask;
+
+ // Only MountPointProviders can create a new operation directly.
+ friend class fileapi::SandboxMountPointProvider;
+ friend class SandboxMountPointProvider;
+ SyncableFileSystemOperation(
+ fileapi::FileSystemContext* file_system_context,
+ scoped_ptr<fileapi::FileSystemOperationContext> operation_context);
+ fileapi::LocalFileSystemOperation* NewOperation();
+
+ void DidFinish(base::PlatformFileError status);
+ void DidWrite(const WriteCallback& callback,
+ base::PlatformFileError result,
+ int64 bytes,
+ bool complete);
+
+ void OnCancelled();
+ void AbortOperation(const StatusCallback& callback,
+ base::PlatformFileError error);
+
+ base::WeakPtr<SyncableFileOperationRunner> operation_runner_;
+ fileapi::LocalFileSystemOperation* inflight_operation_;
+ std::vector<fileapi::FileSystemURL> target_paths_;
+
+ StatusCallback completion_callback_;
+
+ bool is_directory_operation_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncableFileSystemOperation);
+};
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNCABLE_FILE_SYSTEM_OPERATION_H_
diff --git a/webkit/browser/fileapi/syncable/syncable_file_system_unittest.cc b/webkit/browser/fileapi/syncable/syncable_file_system_unittest.cc
new file mode 100644
index 0000000..a1d25e1
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/syncable_file_system_unittest.cc
@@ -0,0 +1,284 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/stl_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/file_system_file_util.h"
+#include "webkit/browser/fileapi/file_system_operation_context.h"
+#include "webkit/browser/fileapi/file_system_task_runners.h"
+#include "webkit/browser/fileapi/isolated_context.h"
+#include "webkit/browser/fileapi/local_file_system_operation.h"
+#include "webkit/browser/fileapi/sandbox_file_system_test_helper.h"
+#include "webkit/browser/fileapi/syncable/canned_syncable_file_system.h"
+#include "webkit/browser/fileapi/syncable/local_file_change_tracker.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_context.h"
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+#include "webkit/common/fileapi/file_system_types.h"
+#include "webkit/quota/quota_manager.h"
+#include "webkit/quota/quota_types.h"
+
+using base::PlatformFileError;
+using fileapi::FileSystemContext;
+using fileapi::FileSystemOperationContext;
+using fileapi::FileSystemURL;
+using fileapi::FileSystemURLSet;
+using fileapi::SandboxFileSystemTestHelper;
+using quota::QuotaManager;
+using quota::QuotaStatusCode;
+
+namespace sync_file_system {
+
+class SyncableFileSystemTest : public testing::Test {
+ public:
+ SyncableFileSystemTest()
+ : file_system_(GURL("http://example.com/"), "test",
+ base::MessageLoopProxy::current(),
+ base::MessageLoopProxy::current()),
+ weak_factory_(this) {}
+
+ virtual void SetUp() {
+ file_system_.SetUp();
+
+ sync_context_ = new LocalFileSyncContext(base::MessageLoopProxy::current(),
+ base::MessageLoopProxy::current());
+ ASSERT_EQ(sync_file_system::SYNC_STATUS_OK,
+ file_system_.MaybeInitializeFileSystemContext(sync_context_));
+ }
+
+ virtual void TearDown() {
+ if (sync_context_)
+ sync_context_->ShutdownOnUIThread();
+ sync_context_ = NULL;
+
+ file_system_.TearDown();
+
+ // Make sure we don't leave the external filesystem.
+ // (CannedSyncableFileSystem::TearDown does not do this as there may be
+ // multiple syncable file systems registered for the name)
+ RevokeSyncableFileSystem("test");
+ }
+
+ protected:
+ void VerifyAndClearChange(const FileSystemURL& url,
+ const FileChange& expected_change) {
+ SCOPED_TRACE(testing::Message() << url.DebugString() <<
+ " expecting:" << expected_change.DebugString());
+ // Get the changes for URL and verify.
+ FileChangeList changes;
+ change_tracker()->GetChangesForURL(url, &changes);
+ ASSERT_EQ(1U, changes.size());
+ SCOPED_TRACE(testing::Message() << url.DebugString() <<
+ " actual:" << changes.DebugString());
+ EXPECT_EQ(expected_change, changes.front());
+
+ // Clear the URL from the change tracker.
+ change_tracker()->ClearChangesForURL(url);
+ }
+
+ FileSystemURL URL(const std::string& path) {
+ return file_system_.URL(path);
+ }
+
+ FileSystemContext* file_system_context() {
+ return file_system_.file_system_context();
+ }
+
+ LocalFileChangeTracker* change_tracker() {
+ return file_system_context()->change_tracker();
+ }
+
+ base::ScopedTempDir data_dir_;
+ base::MessageLoop message_loop_;
+
+ CannedSyncableFileSystem file_system_;
+ scoped_refptr<LocalFileSyncContext> sync_context_;
+
+ base::WeakPtrFactory<SyncableFileSystemTest> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncableFileSystemTest);
+};
+
+// Brief combined testing. Just see if all the sandbox feature works.
+TEST_F(SyncableFileSystemTest, SyncableLocalSandboxCombined) {
+ // Opens a syncable file system.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.OpenFileSystem());
+
+ // Do some operations.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL("dir")));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL("dir/foo")));
+
+ const int64 kOriginalQuota = QuotaManager::kSyncableStorageDefaultHostQuota;
+
+ const int64 kQuota = 12345 * 1024;
+ QuotaManager::kSyncableStorageDefaultHostQuota = kQuota;
+ int64 usage, quota;
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system_.GetUsageAndQuota(&usage, &quota));
+
+ // Returned quota must be what we overrode. Usage must be greater than 0
+ // as creating a file or directory consumes some space.
+ EXPECT_EQ(kQuota, quota);
+ EXPECT_GT(usage, 0);
+
+ // Truncate to extend an existing file and see if the usage reflects it.
+ const int64 kFileSizeToExtend = 333;
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL("dir/foo")));
+
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.TruncateFile(URL("dir/foo"), kFileSizeToExtend));
+
+ int64 new_usage;
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system_.GetUsageAndQuota(&new_usage, &quota));
+ EXPECT_EQ(kFileSizeToExtend, new_usage - usage);
+
+ // Shrink the quota to the current usage, try to extend the file further
+ // and see if it fails.
+ QuotaManager::kSyncableStorageDefaultHostQuota = new_usage;
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_NO_SPACE,
+ file_system_.TruncateFile(URL("dir/foo"), kFileSizeToExtend + 1));
+
+ usage = new_usage;
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system_.GetUsageAndQuota(&new_usage, &quota));
+ EXPECT_EQ(usage, new_usage);
+
+ // Deletes the file system.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.DeleteFileSystem());
+
+ // Now the usage must be zero.
+ EXPECT_EQ(quota::kQuotaStatusOk,
+ file_system_.GetUsageAndQuota(&usage, &quota));
+ EXPECT_EQ(0, usage);
+
+ // Restore the system default quota.
+ QuotaManager::kSyncableStorageDefaultHostQuota = kOriginalQuota;
+}
+
+// Combined testing with LocalFileChangeTracker.
+TEST_F(SyncableFileSystemTest, ChangeTrackerSimple) {
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.OpenFileSystem());
+
+ const char kPath0[] = "dir a";
+ const char kPath1[] = "dir a/dir"; // child of kPath0
+ const char kPath2[] = "dir a/file"; // child of kPath0
+ const char kPath3[] = "dir b";
+
+ // Do some operations.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath0))); // Creates a dir.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath1))); // Creates another.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateFile(URL(kPath2))); // Creates a file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.TruncateFile(URL(kPath2), 1)); // Modifies the file.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.TruncateFile(URL(kPath2), 2)); // Modifies it again.
+
+ FileSystemURLSet urls;
+ file_system_.GetChangedURLsInTracker(&urls);
+
+ EXPECT_EQ(3U, urls.size());
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath0)));
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath1)));
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath2)));
+
+ VerifyAndClearChange(URL(kPath0),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath1),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath2),
+ FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+
+ // Creates and removes a same directory.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.CreateDirectory(URL(kPath3)));
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Remove(URL(kPath3), false /* recursive */));
+
+ // The changes will be offset.
+ urls.clear();
+ file_system_.GetChangedURLsInTracker(&urls);
+ EXPECT_TRUE(urls.empty());
+
+ // Recursively removes the kPath0 directory.
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.Remove(URL(kPath0), true /* recursive */));
+
+ urls.clear();
+ file_system_.GetChangedURLsInTracker(&urls);
+
+ // kPath0 and its all chidren (kPath1 and kPath2) must have been deleted.
+ EXPECT_EQ(3U, urls.size());
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath0)));
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath1)));
+ EXPECT_TRUE(ContainsKey(urls, URL(kPath2)));
+
+ VerifyAndClearChange(URL(kPath0),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath1),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_DIRECTORY));
+ VerifyAndClearChange(URL(kPath2),
+ FileChange(FileChange::FILE_CHANGE_DELETE,
+ sync_file_system::SYNC_FILE_TYPE_FILE));
+}
+
+// Make sure directory operation is disabled (when it's configured so).
+TEST_F(SyncableFileSystemTest, DisableDirectoryOperations) {
+ SetEnableSyncFSDirectoryOperation(false);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ file_system_.OpenFileSystem());
+
+ // Try some directory operations (which should fail).
+ EXPECT_EQ(base::PLATFORM_FILE_ERROR_INVALID_OPERATION,
+ file_system_.CreateDirectory(URL("dir")));
+
+ // Set up another (non-syncable) local file system.
+ SandboxFileSystemTestHelper other_file_system_(
+ GURL("http://foo.com/"), fileapi::kFileSystemTypeTemporary);
+ other_file_system_.SetUp(file_system_.file_system_context());
+
+ // Create directory '/a' and file '/a/b' in the other file system.
+ const FileSystemURL kSrcDir = other_file_system_.CreateURLFromUTF8("/a");
+ const FileSystemURL kSrcChild = other_file_system_.CreateURLFromUTF8("/a/b");
+
+ bool created = false;
+ scoped_ptr<FileSystemOperationContext> operation_context;
+
+ operation_context.reset(other_file_system_.NewOperationContext());
+ operation_context->set_allowed_bytes_growth(1024);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ other_file_system_.file_util()->CreateDirectory(
+ operation_context.get(),
+ kSrcDir, false /* exclusive */, false /* recursive */));
+
+ operation_context.reset(other_file_system_.NewOperationContext());
+ operation_context->set_allowed_bytes_growth(1024);
+ EXPECT_EQ(base::PLATFORM_FILE_OK,
+ other_file_system_.file_util()->EnsureFileExists(
+ operation_context.get(), kSrcChild, &created));
+ EXPECT_TRUE(created);
+
+ // Now try copying the directory into the syncable file system, which should
+ // fail if directory operation is disabled. (http://crbug.com/161442)
+ EXPECT_NE(base::PLATFORM_FILE_OK,
+ file_system_.Copy(kSrcDir, URL("dest")));
+
+ other_file_system_.TearDown();
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/syncable_file_system_util.cc b/webkit/browser/fileapi/syncable/syncable_file_system_util.cc
new file mode 100644
index 0000000..84ae92f
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/syncable_file_system_util.cc
@@ -0,0 +1,110 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+
+#include "base/command_line.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/file_observers.h"
+#include "webkit/browser/fileapi/file_system_context.h"
+#include "webkit/browser/fileapi/sandbox_mount_point_provider.h"
+#include "webkit/common/fileapi/file_system_util.h"
+
+using fileapi::ExternalMountPoints;
+using fileapi::FileSystemContext;
+using fileapi::FileSystemURL;
+using fileapi::LocalFileSystemOperation;
+
+namespace sync_file_system {
+
+namespace {
+
+// A command switch to enable syncing directory operations in Sync FileSystem
+// API. (http://crbug.com/161442)
+// TODO(kinuko): this command-line switch should be temporary.
+const char kEnableSyncFSDirectoryOperation[] =
+ "enable-syncfs-directory-operation";
+
+bool is_directory_operation_enabled = false;
+
+}
+
+bool RegisterSyncableFileSystem(const std::string& service_name) {
+ return ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
+ service_name, fileapi::kFileSystemTypeSyncable, base::FilePath());
+}
+
+bool RevokeSyncableFileSystem(const std::string& service_name) {
+ return ExternalMountPoints::GetSystemInstance()->RevokeFileSystem(
+ service_name);
+}
+
+GURL GetSyncableFileSystemRootURI(const GURL& origin,
+ const std::string& service_name) {
+ const GURL url = GetFileSystemRootURI(origin,
+ fileapi::kFileSystemTypeExternal);
+ const std::string path = service_name + "/";
+ url_canon::Replacements<char> replacements;
+ replacements.SetPath(path.c_str(), url_parse::Component(0, path.length()));
+ return url.ReplaceComponents(replacements);
+}
+
+FileSystemURL CreateSyncableFileSystemURL(const GURL& origin,
+ const std::string& service_name,
+ const base::FilePath& path) {
+ // Avoid using FilePath::Append as path may be an absolute path.
+ base::FilePath::StringType virtual_path =
+ base::FilePath::FromUTF8Unsafe(service_name + "/").value() +
+ path.value();
+ return ExternalMountPoints::GetSystemInstance()->CreateCrackedFileSystemURL(
+ origin,
+ fileapi::kFileSystemTypeExternal,
+ base::FilePath(virtual_path));
+}
+
+bool SerializeSyncableFileSystemURL(const FileSystemURL& url,
+ std::string* serialized_url) {
+ if (!url.is_valid() || url.type() != fileapi::kFileSystemTypeSyncable)
+ return false;
+ *serialized_url =
+ GetSyncableFileSystemRootURI(url.origin(), url.filesystem_id()).spec() +
+ url.path().AsUTF8Unsafe();
+ return true;
+}
+
+bool DeserializeSyncableFileSystemURL(
+ const std::string& serialized_url, FileSystemURL* url) {
+#if !defined(FILE_PATH_USES_WIN_SEPARATORS)
+ DCHECK(serialized_url.find('\\') == std::string::npos);
+#endif // FILE_PATH_USES_WIN_SEPARATORS
+
+ FileSystemURL deserialized =
+ ExternalMountPoints::GetSystemInstance()->CrackURL(GURL(serialized_url));
+ if (!deserialized.is_valid() ||
+ deserialized.type() != fileapi::kFileSystemTypeSyncable) {
+ return false;
+ }
+
+ *url = deserialized;
+ return true;
+}
+
+LocalFileSystemOperation* CreateFileSystemOperationForSync(
+ FileSystemContext* file_system_context) {
+ DCHECK(file_system_context);
+ return file_system_context->sandbox_provider()->
+ CreateFileSystemOperationForSync(file_system_context);
+}
+
+void SetEnableSyncFSDirectoryOperation(bool flag) {
+ is_directory_operation_enabled = flag;
+}
+
+bool IsSyncFSDirectoryOperationEnabled() {
+ return is_directory_operation_enabled ||
+ CommandLine::ForCurrentProcess()->HasSwitch(
+ kEnableSyncFSDirectoryOperation);
+}
+
+} // namespace sync_file_system
diff --git a/webkit/browser/fileapi/syncable/syncable_file_system_util.h b/webkit/browser/fileapi/syncable/syncable_file_system_util.h
new file mode 100644
index 0000000..3c296c0
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/syncable_file_system_util.h
@@ -0,0 +1,103 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNCABLE_FILE_SYSTEM_UTIL_H_
+#define WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNCABLE_FILE_SYSTEM_UTIL_H_
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "webkit/browser/fileapi/file_system_url.h"
+#include "webkit/storage/webkit_storage_export.h"
+
+namespace fileapi {
+class FileSystemContext;
+class FileSystemURL;
+class LocalFileSystemOperation;
+}
+
+namespace sync_file_system {
+
+// Registers a syncable filesystem with the given |service_name|.
+WEBKIT_STORAGE_EXPORT bool RegisterSyncableFileSystem(
+ const std::string& service_name);
+
+// Revokes the syncable filesystem that was registered with |service_name|.
+WEBKIT_STORAGE_EXPORT bool RevokeSyncableFileSystem(
+ const std::string& service_name);
+
+// Returns the root URI of the syncable filesystem that can be specified by a
+// pair of |origin| and |service_name|.
+WEBKIT_STORAGE_EXPORT GURL GetSyncableFileSystemRootURI(
+ const GURL& origin, const std::string& service_name);
+
+// Creates a FileSystem URL for the |path| in a syncable filesystem
+// identifiable by a pair of |origin| and |service_name|.
+//
+// Example: Assume following arguments are given:
+// origin: 'http://www.example.com/',
+// service_name: 'service_name',
+// path: '/foo/bar',
+// returns 'filesystem:http://www.example.com/external/service_name/foo/bar'
+WEBKIT_STORAGE_EXPORT fileapi::FileSystemURL CreateSyncableFileSystemURL(
+ const GURL& origin, const std::string& service_name,
+ const base::FilePath& path);
+
+// Serializes a given FileSystemURL |url| and sets the serialized string to
+// |serialized_url|. If the URL does not represent a syncable filesystem,
+// |serialized_url| is not filled in, and returns false. Separators of the
+// path will be normalized depending on its platform.
+//
+// Example: Assume a following FileSystemURL object is given:
+// origin() returns 'http://www.example.com/',
+// type() returns the kFileSystemTypeSyncable,
+// filesystem_id() returns 'service_name',
+// path() returns '/foo/bar',
+// this URL will be serialized to
+// (on Windows)
+// 'filesystem:http://www.example.com/external/service_name/foo\\bar'
+// (on others)
+// 'filesystem:http://www.example.com/external/service_name/foo/bar'
+WEBKIT_STORAGE_EXPORT bool SerializeSyncableFileSystemURL(
+ const fileapi::FileSystemURL& url, std::string* serialized_url);
+
+// Deserializes a serialized FileSystem URL string |serialized_url| and sets the
+// deserialized value to |url|. If the reconstructed object is invalid or does
+// not represent a syncable filesystem, returns false.
+//
+// NOTE: On any platform other than Windows, this function assumes that
+// |serialized_url| does not contain '\\'. If it contains '\\' on such
+// platforms, '\\' may be replaced with '/' (It would not be an expected
+// behavior).
+//
+// See the comment of SerializeSyncableFileSystemURL() for more details.
+WEBKIT_STORAGE_EXPORT bool DeserializeSyncableFileSystemURL(
+ const std::string& serialized_url, fileapi::FileSystemURL* url);
+
+// Returns a new FileSystemOperation that can be used to apply changes
+// for sync. The operation returned by this method:
+// * does NOT notify the file change tracker, but
+// * notifies the regular sandboxed quota observer
+// therefore quota will be updated appropriately without bothering the
+// change tracker.
+WEBKIT_STORAGE_EXPORT fileapi::LocalFileSystemOperation*
+ CreateFileSystemOperationForSync(
+ fileapi::FileSystemContext* file_system_context);
+
+// Enables or disables directory operations in Sync FileSystem API.
+// TODO(nhiroki): This method should be used only for testing and should go
+// away when we fully support directory operations. (http://crbug.com/161442)
+WEBKIT_STORAGE_EXPORT void SetEnableSyncFSDirectoryOperation(bool flag);
+
+// Returns true if we allow directory operations in Sync FileSystem API.
+// It is disabled by default but can be overridden by a command-line switch
+// (--enable-syncfs-directory-operations) or by calling
+// SetEnableSyncFSDirectoryOperation().
+// TODO(nhiroki): This method should be used only for testing and should go
+// away when we fully support directory operations. (http://crbug.com/161442)
+WEBKIT_STORAGE_EXPORT bool IsSyncFSDirectoryOperationEnabled();
+
+} // namespace sync_file_system
+
+#endif // WEBKIT_BROWSER_FILEAPI_SYNCABLE_SYNCABLE_FILE_SYSTEM_UTIL_H_
diff --git a/webkit/browser/fileapi/syncable/syncable_file_system_util_unittest.cc b/webkit/browser/fileapi/syncable/syncable_file_system_util_unittest.cc
new file mode 100644
index 0000000..d62deb5
--- /dev/null
+++ b/webkit/browser/fileapi/syncable/syncable_file_system_util_unittest.cc
@@ -0,0 +1,177 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "webkit/browser/fileapi/syncable/syncable_file_system_util.h"
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/message_loop_proxy.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "webkit/browser/fileapi/external_mount_points.h"
+#include "webkit/browser/fileapi/syncable/canned_syncable_file_system.h"
+#include "webkit/browser/fileapi/syncable/local_file_sync_context.h"
+#include "webkit/common/fileapi/file_system_types.h"
+
+using fileapi::ExternalMountPoints;
+using fileapi::FileSystemURL;
+using fileapi::ScopedExternalFileSystem;
+
+namespace sync_file_system {
+
+namespace {
+
+const char kSyncableFileSystemRootURI[] =
+ "filesystem:http://www.example.com/external/service/";
+const char kNonRegisteredFileSystemRootURI[] =
+ "filesystem:http://www.example.com/external/non_registered/";
+const char kNonSyncableFileSystemRootURI[] =
+ "filesystem:http://www.example.com/temporary/";
+
+const char kOrigin[] = "http://www.example.com/";
+const char kServiceName[] = "service";
+const base::FilePath::CharType kPath[] = FILE_PATH_LITERAL("dir/file");
+
+FileSystemURL CreateFileSystemURL(const std::string& url) {
+ return ExternalMountPoints::GetSystemInstance()->CrackURL(GURL(url));
+}
+
+base::FilePath CreateNormalizedFilePath(const base::FilePath::CharType* path) {
+ return base::FilePath(path).NormalizePathSeparators();
+}
+
+} // namespace
+
+TEST(SyncableFileSystemUtilTest, GetSyncableFileSystemRootURI) {
+ const GURL root = GetSyncableFileSystemRootURI(GURL(kOrigin), kServiceName);
+ EXPECT_TRUE(root.is_valid());
+ EXPECT_EQ(GURL(kSyncableFileSystemRootURI), root);
+}
+
+TEST(SyncableFileSystemUtilTest, CreateSyncableFileSystemURL) {
+ ScopedExternalFileSystem scoped_fs(
+ kServiceName, fileapi::kFileSystemTypeSyncable, base::FilePath());
+
+ const base::FilePath path(kPath);
+ const FileSystemURL expected_url =
+ CreateFileSystemURL(kSyncableFileSystemRootURI + path.AsUTF8Unsafe());
+ const FileSystemURL url =
+ CreateSyncableFileSystemURL(GURL(kOrigin), kServiceName, path);
+
+ EXPECT_TRUE(url.is_valid());
+ EXPECT_EQ(expected_url, url);
+}
+
+TEST(SyncableFileSystemUtilTest,
+ SerializeAndDesirializeSyncableFileSystemURL) {
+ ScopedExternalFileSystem scoped_fs(
+ kServiceName, fileapi::kFileSystemTypeSyncable, base::FilePath());
+
+ const std::string expected_url_str = kSyncableFileSystemRootURI +
+ CreateNormalizedFilePath(kPath).AsUTF8Unsafe();
+ const FileSystemURL expected_url = CreateFileSystemURL(expected_url_str);
+ const FileSystemURL url = CreateSyncableFileSystemURL(
+ GURL(kOrigin), kServiceName, base::FilePath(kPath));
+
+ std::string serialized;
+ EXPECT_TRUE(SerializeSyncableFileSystemURL(url, &serialized));
+ EXPECT_EQ(expected_url_str, serialized);
+
+ FileSystemURL deserialized;
+ EXPECT_TRUE(DeserializeSyncableFileSystemURL(serialized, &deserialized));
+ EXPECT_TRUE(deserialized.is_valid());
+ EXPECT_EQ(expected_url, deserialized);
+}
+
+TEST(SyncableFileSystemUtilTest,
+ FailInSerializingAndDeserializingSyncableFileSystemURL) {
+ ScopedExternalFileSystem scoped_fs(
+ kServiceName, fileapi::kFileSystemTypeSyncable, base::FilePath());
+
+ const base::FilePath normalized_path = CreateNormalizedFilePath(kPath);
+ const std::string non_registered_url =
+ kNonRegisteredFileSystemRootURI + normalized_path.AsUTF8Unsafe();
+ const std::string non_syncable_url =
+ kNonSyncableFileSystemRootURI + normalized_path.AsUTF8Unsafe();
+
+ // Expected to fail in serializing URLs of non-registered filesystem and
+ // non-syncable filesystem.
+ std::string serialized;
+ EXPECT_FALSE(SerializeSyncableFileSystemURL(
+ CreateFileSystemURL(non_registered_url), &serialized));
+ EXPECT_FALSE(SerializeSyncableFileSystemURL(
+ CreateFileSystemURL(non_syncable_url), &serialized));
+
+ // Expected to fail in deserializing a string that represents URLs of
+ // non-registered filesystem and non-syncable filesystem.
+ FileSystemURL deserialized;
+ EXPECT_FALSE(DeserializeSyncableFileSystemURL(
+ non_registered_url, &deserialized));
+ EXPECT_FALSE(DeserializeSyncableFileSystemURL(
+ non_syncable_url, &deserialized));
+}
+
+TEST(SyncableFileSystemUtilTest, SerializeBeforeOpenFileSystem) {
+ const std::string serialized = kSyncableFileSystemRootURI +
+ CreateNormalizedFilePath(kPath).AsUTF8Unsafe();
+ FileSystemURL deserialized;
+ base::MessageLoop message_loop;
+
+ // Setting up a full syncable filesystem environment.
+ CannedSyncableFileSystem file_system(GURL(kOrigin), kServiceName,
+ base::MessageLoopProxy::current(),
+ base::MessageLoopProxy::current());
+ file_system.SetUp();
+ scoped_refptr<LocalFileSyncContext> sync_context =
+ new LocalFileSyncContext(base::MessageLoopProxy::current(),
+ base::MessageLoopProxy::current());
+
+ // Before calling initialization we would not be able to get a valid
+ // deserialized URL.
+ EXPECT_FALSE(DeserializeSyncableFileSystemURL(serialized, &deserialized));
+ EXPECT_FALSE(deserialized.is_valid());
+
+ ASSERT_EQ(sync_file_system::SYNC_STATUS_OK,
+ file_system.MaybeInitializeFileSystemContext(sync_context));
+
+ // After initialization this should be ok (even before opening the file
+ // system).
+ EXPECT_TRUE(DeserializeSyncableFileSystemURL(serialized, &deserialized));
+ EXPECT_TRUE(deserialized.is_valid());
+
+ // Shutting down.
+ file_system.TearDown();
+ RevokeSyncableFileSystem(kServiceName);
+ sync_context->ShutdownOnUIThread();
+ sync_context = NULL;
+ base::MessageLoop::current()->RunUntilIdle();
+}
+
+TEST(SyncableFileSystemUtilTest, SyncableFileSystemURL_IsParent) {
+ ScopedExternalFileSystem scoped1("foo", fileapi::kFileSystemTypeSyncable,
+ base::FilePath());
+ ScopedExternalFileSystem scoped2("bar", fileapi::kFileSystemTypeSyncable,
+ base::FilePath());
+
+ const std::string root1 = sync_file_system::GetSyncableFileSystemRootURI(
+ GURL("http://example.com"), "foo").spec();
+ const std::string root2 = sync_file_system::GetSyncableFileSystemRootURI(
+ GURL("http://example.com"), "bar").spec();
+
+ const std::string parent("dir");
+ const std::string child("dir/child");
+
+ // True case.
+ EXPECT_TRUE(CreateFileSystemURL(root1 + parent).IsParent(
+ CreateFileSystemURL(root1 + child)));
+ EXPECT_TRUE(CreateFileSystemURL(root2 + parent).IsParent(
+ CreateFileSystemURL(root2 + child)));
+
+ // False case: different filesystem ID.
+ EXPECT_FALSE(CreateFileSystemURL(root1 + parent).IsParent(
+ CreateFileSystemURL(root2 + child)));
+ EXPECT_FALSE(CreateFileSystemURL(root2 + parent).IsParent(
+ CreateFileSystemURL(root1 + child)));
+}
+
+} // namespace sync_file_system