diff options
Diffstat (limited to 'webkit/browser/fileapi/syncable')
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, "a)); + + // 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, "a)); + 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, "a)); + 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, "a)); + + // 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, "a)); + 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, "a)); + 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, "a)); + + // 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, "a)); + 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, "a)); + 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, "a)); + 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 |