summaryrefslogtreecommitdiffstats
path: root/components/drive
diff options
context:
space:
mode:
authorlukasza <lukasza@chromium.org>2015-08-27 14:04:34 -0700
committerCommit bot <commit-bot@chromium.org>2015-08-27 21:05:12 +0000
commit3fb2262ad354f56e9860d51f7b73a2edbf7c782a (patch)
tree3ed44a3a1a75a1baf1f80466269e935d3e8af4d3 /components/drive
parentfb6a9e2b690bb29089fbb608a2e0720fa40835f3 (diff)
downloadchromium_src-3fb2262ad354f56e9860d51f7b73a2edbf7c782a.zip
chromium_src-3fb2262ad354f56e9860d51f7b73a2edbf7c782a.tar.gz
chromium_src-3fb2262ad354f56e9860d51f7b73a2edbf7c782a.tar.bz2
Move chrome/browser/chromeos/drive/file_system (+deps) into components/drive.
Files moved from chrome/browser/chromeos/drive into components/drive: - all the files under drive/file_system/... - file_system_interface* - file_system_metadata* - file_write_watcher* - resource_metadata* - resource_metadata_storage* Other changes: - Refactoring to limit usage of cryptohome::kMinFreeSpaceInBytes to only drive/file_cache.h Test steps: 1. Verify that things still build via GYP. $ GYP_DEFINES="use_goma=1 chromeos=1 component=shared_library" gclient sync $ ninja -C out/Debug -j 150 chrome unit_tests \ interactive_ui_tests browser_tests drive 2. Verify that things still build via GN. $ gn gen out/Default --args='target_os="chromeos" use_goma=true is_component_build=true' $ ninja -C out/Default -j 150 chrome unit_tests \ interactive_ui_tests browser_tests components/drive TEST=Please see "Test steps" above. BUG=257943, 498951 TBR=stevejb@chromium.org and kinuko@chromium.org Review URL: https://codereview.chromium.org/1318543003 Cr-Commit-Position: refs/heads/master@{#345984}
Diffstat (limited to 'components/drive')
-rw-r--r--components/drive/BUILD.gn32
-rw-r--r--components/drive/DEPS29
-rw-r--r--components/drive/drive_test_util.h10
-rw-r--r--components/drive/file_cache.cc10
-rw-r--r--components/drive/file_cache.h14
-rw-r--r--components/drive/file_system/copy_operation.cc671
-rw-r--r--components/drive/file_system/copy_operation.h181
-rw-r--r--components/drive/file_system/copy_operation_unittest.cc517
-rw-r--r--components/drive/file_system/create_directory_operation.cc182
-rw-r--r--components/drive/file_system/create_directory_operation.h79
-rw-r--r--components/drive/file_system/create_directory_operation_unittest.cc126
-rw-r--r--components/drive/file_system/create_file_operation.cc139
-rw-r--r--components/drive/file_system/create_file_operation.h76
-rw-r--r--components/drive/file_system/create_file_operation_unittest.cc161
-rw-r--r--components/drive/file_system/download_operation.cc540
-rw-r--r--components/drive/file_system/download_operation.h133
-rw-r--r--components/drive/file_system/download_operation_unittest.cc504
-rw-r--r--components/drive/file_system/get_file_for_saving_operation.cc206
-rw-r--r--components/drive/file_system/get_file_for_saving_operation.h109
-rw-r--r--components/drive/file_system/get_file_for_saving_operation_unittest.cc158
-rw-r--r--components/drive/file_system/move_operation.cc121
-rw-r--r--components/drive/file_system/move_operation.h79
-rw-r--r--components/drive/file_system/move_operation_unittest.cc141
-rw-r--r--components/drive/file_system/open_file_operation.cc185
-rw-r--r--components/drive/file_system/open_file_operation.h109
-rw-r--r--components/drive/file_system/open_file_operation_unittest.cc257
-rw-r--r--components/drive/file_system/operation_delegate.cc17
-rw-r--r--components/drive/file_system/operation_delegate.h59
-rw-r--r--components/drive/file_system/operation_test_base.cc195
-rw-r--r--components/drive/file_system/operation_test_base.h166
-rw-r--r--components/drive/file_system/remove_operation.cc127
-rw-r--r--components/drive/file_system/remove_operation.h77
-rw-r--r--components/drive/file_system/remove_operation_unittest.cc132
-rw-r--r--components/drive/file_system/search_operation.cc180
-rw-r--r--components/drive/file_system/search_operation.h86
-rw-r--r--components/drive/file_system/search_operation_unittest.cc157
-rw-r--r--components/drive/file_system/set_property_operation.cc122
-rw-r--r--components/drive/file_system/set_property_operation.h70
-rw-r--r--components/drive/file_system/set_property_operation_unittest.cc153
-rw-r--r--components/drive/file_system/touch_operation.cc96
-rw-r--r--components/drive/file_system/touch_operation.h68
-rw-r--r--components/drive/file_system/touch_operation_unittest.cc59
-rw-r--r--components/drive/file_system/truncate_operation.cc145
-rw-r--r--components/drive/file_system/truncate_operation.h88
-rw-r--r--components/drive/file_system/truncate_operation_unittest.cc134
-rw-r--r--components/drive/file_system_interface.cc20
-rw-r--r--components/drive/file_system_interface.h474
-rw-r--r--components/drive/file_system_metadata.cc18
-rw-r--r--components/drive/file_system_metadata.h35
-rw-r--r--components/drive/file_write_watcher.cc207
-rw-r--r--components/drive/file_write_watcher.h66
-rw-r--r--components/drive/file_write_watcher_unittest.cc122
52 files changed, 7819 insertions, 23 deletions
diff --git a/components/drive/BUILD.gn b/components/drive/BUILD.gn
index 93f8320..4567878 100644
--- a/components/drive/BUILD.gn
+++ b/components/drive/BUILD.gn
@@ -31,8 +31,40 @@ source_set("drive") {
"file_change.h",
"file_errors.cc",
"file_errors.h",
+ "file_system/copy_operation.cc",
+ "file_system/copy_operation.h",
+ "file_system/create_directory_operation.cc",
+ "file_system/create_directory_operation.h",
+ "file_system/create_file_operation.cc",
+ "file_system/create_file_operation.h",
+ "file_system/download_operation.cc",
+ "file_system/download_operation.h",
+ "file_system/get_file_for_saving_operation.cc",
+ "file_system/get_file_for_saving_operation.h",
+ "file_system/move_operation.cc",
+ "file_system/move_operation.h",
+ "file_system/open_file_operation.cc",
+ "file_system/open_file_operation.h",
+ "file_system/operation_delegate.cc",
+ "file_system/operation_delegate.h",
+ "file_system/remove_operation.cc",
+ "file_system/remove_operation.h",
+ "file_system/search_operation.cc",
+ "file_system/search_operation.h",
+ "file_system/set_property_operation.cc",
+ "file_system/set_property_operation.h",
+ "file_system/touch_operation.cc",
+ "file_system/touch_operation.h",
+ "file_system/truncate_operation.cc",
+ "file_system/truncate_operation.h",
"file_system_core_util.cc",
"file_system_core_util.h",
+ "file_system_interface.cc",
+ "file_system_interface.h",
+ "file_system_metadata.cc",
+ "file_system_metadata.h",
+ "file_write_watcher.cc",
+ "file_write_watcher.h",
"job_list.cc",
"job_list.h",
"job_queue.cc",
diff --git a/components/drive/DEPS b/components/drive/DEPS
index 888602d..9f76475 100644
--- a/components/drive/DEPS
+++ b/components/drive/DEPS
@@ -18,7 +18,19 @@ specific_include_rules = {
# The following test dependencies should be removed to fully componentize this
# directory. crbug.com/498951
- r"(drive_test_util\.h"
+ r"(copy_operation_unittest\.cc"
+ r"|create_directory_operation_unittest\.cc"
+ r"|create_file_operation_unittest\.cc"
+ r"|download_operation_unittest\.cc"
+ r"|drive_test_util\.h"
+ r"|get_file_for_saving_operation_unittest\.cc"
+ r"|move_operation_unittest\.cc"
+ r"|open_file_operation_unittest\.cc"
+ r"|operation_test_base\.cc"
+ r"|remove_operation_unittest\.cc"
+ r"|search_operation_unittest\.cc"
+ r"|set_property_operation_unittest\.cc"
+ r"|truncate_operation_unittest\.cc"
r")": [
"+content/public/test/test_utils.h",
],
@@ -29,18 +41,27 @@ specific_include_rules = {
r"|change_list_processor_unittest.cc"
r"|file_cache_unittest.cc"
r"|file_system_core_util_unittest.cc"
+ r"|file_write_watcher_unittest.cc"
r"|job_scheduler_unittest.cc"
+ r"|operation_test_base\.h"
r"|resource_metadata_storage_unittest.cc"
r"|resource_metadata_unittest.cc"
r")": [
"+content/public/test/test_browser_thread_bundle.h",
],
+ # The following test dependencies should be removed to fully componentize this
+ # directory. crbug.com/498951
+ r"(file_write_watcher_unittest\.cc"
+ r"|get_file_for_saving_operation_unittest\.cc"
+ r"|operation_test_base\.cc"
+ r")": [
+ "+content/public/browser/browser_thread.h",
+ ],
+
# The dependency below is ok and can stay here for the long-term, because it
# is guarded by #if defined(OS_CHROMEOS) in the source code.
- r"(drive_test_util.h"
- r"|file_cache.cc"
- r")": [
+ "file_cache\.h": [
"+third_party/cros_system_api/constants/cryptohome.h",
],
}
diff --git a/components/drive/drive_test_util.h b/components/drive/drive_test_util.h
index 41b5acb..327dc15 100644
--- a/components/drive/drive_test_util.h
+++ b/components/drive/drive_test_util.h
@@ -7,15 +7,13 @@
#include <string>
+#include "components/drive/file_cache.h"
#include "content/public/test/test_utils.h"
#include "google_apis/drive/test_util.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/network_change_notifier.h"
#include "net/base/test_completion_callback.h"
-#if defined(OS_CHROMEOS)
-#include "third_party/cros_system_api/constants/cryptohome.h"
-#endif
class PrefRegistrySimple;
@@ -28,11 +26,7 @@ namespace drive {
namespace test_util {
// Disk space size used by FakeFreeDiskSpaceGetter.
-#if defined(OS_CHROMEOS)
-const int64 kLotsOfSpace = cryptohome::kMinFreeSpaceInBytes * 10;
-#else
-const int64 kLotsOfSpace = 5ull * 1024ull * 1024ull * 1024ull; // 5GB
-#endif
+const int64 kLotsOfSpace = drive::internal::kMinFreeSpaceInBytes * 10;
// Helper to destroy objects which needs Destroy() to be called on destruction.
// Note: When using this helper, you should destruct objects before
diff --git a/components/drive/file_cache.cc b/components/drive/file_cache.cc
index 0638844..572316c 100644
--- a/components/drive/file_cache.cc
+++ b/components/drive/file_cache.cc
@@ -25,9 +25,6 @@
#include "net/base/filename_util.h"
#include "net/base/mime_sniffer.h"
#include "net/base/mime_util.h"
-#if defined(OS_CHROMEOS)
-#include "third_party/cros_system_api/constants/cryptohome.h"
-#endif
namespace drive {
namespace internal {
@@ -581,12 +578,7 @@ bool FileCache::HasEnoughSpaceFor(int64 num_bytes,
free_space = base::SysInfo::AmountOfFreeDiskSpace(path);
// Subtract this as if this portion does not exist.
-#if defined(OS_CHROMEOS)
- const int64 kMinFreeBytes = cryptohome::kMinFreeSpaceInBytes;
-#else
- const int64 kMinFreeBytes = 512ull * 1024ull * 1024ull; // 512MB
-#endif
- free_space -= kMinFreeBytes;
+ free_space -= drive::internal::kMinFreeSpaceInBytes;
return (free_space >= num_bytes);
}
diff --git a/components/drive/file_cache.h b/components/drive/file_cache.h
index c4345ad..b205a77 100644
--- a/components/drive/file_cache.h
+++ b/components/drive/file_cache.h
@@ -15,6 +15,9 @@
#include "base/threading/thread_checker.h"
#include "components/drive/file_errors.h"
#include "components/drive/resource_metadata_storage.h"
+#if defined(OS_CHROMEOS)
+#include "third_party/cros_system_api/constants/cryptohome.h"
+#endif
namespace base {
class ScopedClosureRunner;
@@ -25,6 +28,12 @@ namespace drive {
namespace internal {
+#if defined(OS_CHROMEOS)
+ const int64 kMinFreeSpaceInBytes = cryptohome::kMinFreeSpaceInBytes;
+#else
+ const int64 kMinFreeSpaceInBytes = 512ull * 1024ull * 1024ull; // 512MB
+#endif
+
// Interface class used for getting the free disk space. Tests can inject an
// implementation that reports fake free disk space.
class FreeDiskSpaceGetterInterface {
@@ -67,7 +76,7 @@ class FileCache {
bool IsUnderFileCacheDirectory(const base::FilePath& path) const;
// Frees up disk space to store a file with |num_bytes| size content, while
- // keeping cryptohome::kMinFreeSpaceInBytes bytes on the disk, if needed.
+ // keeping drive::internal::kMinFreeSpaceInBytes bytes on the disk, if needed.
// Returns true if we successfully manage to have enough space, otherwise
// false.
bool FreeDiskSpaceIfNeededFor(int64 num_bytes);
@@ -157,7 +166,8 @@ class FileCache {
void DestroyOnBlockingPool();
// Returns true if we have sufficient space to store the given number of
- // bytes, while keeping cryptohome::kMinFreeSpaceInBytes bytes on the disk.
+ // bytes, while keeping drive::internal::kMinFreeSpaceInBytes bytes on the
+ // disk.
bool HasEnoughSpaceFor(int64 num_bytes, const base::FilePath& path);
// Renames cache files from old "prefix:id.md5" format to the new format.
diff --git a/components/drive/file_system/copy_operation.cc b/components/drive/file_system/copy_operation.cc
new file mode 100644
index 0000000..595e8e1
--- /dev/null
+++ b/components/drive/file_system/copy_operation.cc
@@ -0,0 +1,671 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system/copy_operation.h"
+
+#include <string>
+
+#include "base/task_runner_util.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/create_file_operation.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_entry_conversion.h"
+#include "components/drive/resource_metadata.h"
+#include "google_apis/drive/drive_api_parser.h"
+
+namespace drive {
+namespace file_system {
+
+struct CopyOperation::CopyParams {
+ base::FilePath src_file_path;
+ base::FilePath dest_file_path;
+ bool preserve_last_modified;
+ FileOperationCallback callback;
+ ResourceEntry src_entry;
+ ResourceEntry parent_entry;
+};
+
+// Enum for categorizing where a gdoc represented by a JSON file exists.
+enum JsonGdocLocationType {
+ NOT_IN_METADATA,
+ IS_ORPHAN,
+ HAS_PARENT,
+};
+
+struct CopyOperation::TransferJsonGdocParams {
+ TransferJsonGdocParams(const FileOperationCallback& callback,
+ const std::string& resource_id,
+ const ResourceEntry& parent_entry,
+ const std::string& new_title)
+ : callback(callback),
+ resource_id(resource_id),
+ parent_resource_id(parent_entry.resource_id()),
+ parent_local_id(parent_entry.local_id()),
+ new_title(new_title),
+ location_type(NOT_IN_METADATA) {
+ }
+ // Parameters supplied or calculated from operation arguments.
+ const FileOperationCallback callback;
+ const std::string resource_id;
+ const std::string parent_resource_id;
+ const std::string parent_local_id;
+ const std::string new_title;
+
+ // Values computed during operation.
+ JsonGdocLocationType location_type; // types where the gdoc file is located.
+ std::string local_id; // the local_id of the file (if exists in metadata.)
+ base::FilePath changed_path;
+};
+
+namespace {
+
+FileError TryToCopyLocally(internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ CopyOperation::CopyParams* params,
+ std::vector<std::string>* updated_local_ids,
+ bool* directory_changed,
+ bool* should_copy_on_server) {
+ FileError error = metadata->GetResourceEntryByPath(params->src_file_path,
+ &params->src_entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ error = metadata->GetResourceEntryByPath(params->dest_file_path.DirName(),
+ &params->parent_entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ if (!params->parent_entry.file_info().is_directory())
+ return FILE_ERROR_NOT_A_DIRECTORY;
+
+ // Drive File System doesn't support recursive copy.
+ if (params->src_entry.file_info().is_directory())
+ return FILE_ERROR_NOT_A_FILE;
+
+ // Check destination.
+ ResourceEntry dest_entry;
+ error = metadata->GetResourceEntryByPath(params->dest_file_path, &dest_entry);
+ switch (error) {
+ case FILE_ERROR_OK:
+ // File API spec says it is an error to try to "copy a file to a path
+ // occupied by a directory".
+ if (dest_entry.file_info().is_directory())
+ return FILE_ERROR_INVALID_OPERATION;
+
+ // Move the existing entry to the trash.
+ dest_entry.set_parent_local_id(util::kDriveTrashDirLocalId);
+ error = metadata->RefreshEntry(dest_entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ updated_local_ids->push_back(dest_entry.local_id());
+ *directory_changed = true;
+ break;
+ case FILE_ERROR_NOT_FOUND:
+ break;
+ default:
+ return error;
+ }
+
+ // If the cache file is not present and the entry exists on the server,
+ // server side copy should be used.
+ if (!params->src_entry.file_specific_info().cache_state().is_present() &&
+ !params->src_entry.resource_id().empty()) {
+ *should_copy_on_server = true;
+ return FILE_ERROR_OK;
+ }
+
+ // Copy locally.
+ ResourceEntry entry;
+ const int64 now = base::Time::Now().ToInternalValue();
+ entry.set_title(params->dest_file_path.BaseName().AsUTF8Unsafe());
+ entry.set_parent_local_id(params->parent_entry.local_id());
+ entry.mutable_file_specific_info()->set_content_mime_type(
+ params->src_entry.file_specific_info().content_mime_type());
+ entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+ entry.set_modification_date(base::Time::Now().ToInternalValue());
+ entry.mutable_file_info()->set_last_modified(
+ params->preserve_last_modified ?
+ params->src_entry.file_info().last_modified() : now);
+ entry.mutable_file_info()->set_last_accessed(now);
+
+ std::string local_id;
+ error = metadata->AddEntry(entry, &local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+ updated_local_ids->push_back(local_id);
+ *directory_changed = true;
+
+ if (!params->src_entry.file_specific_info().cache_state().is_present()) {
+ DCHECK(params->src_entry.resource_id().empty());
+ // Locally created empty file may have no cache file.
+ return FILE_ERROR_OK;
+ }
+
+ base::FilePath cache_file_path;
+ error = cache->GetFile(params->src_entry.local_id(), &cache_file_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ return cache->Store(local_id, std::string(), cache_file_path,
+ internal::FileCache::FILE_OPERATION_COPY);
+}
+
+// Stores the entry returned from the server and returns its path.
+FileError UpdateLocalStateForServerSideOperation(
+ internal::ResourceMetadata* metadata,
+ scoped_ptr<google_apis::FileResource> file_resource,
+ ResourceEntry* entry,
+ base::FilePath* file_path) {
+ DCHECK(file_resource);
+
+ std::string parent_resource_id;
+ if (!ConvertFileResourceToResourceEntry(
+ *file_resource, entry, &parent_resource_id) ||
+ parent_resource_id.empty())
+ return FILE_ERROR_NOT_A_FILE;
+
+ std::string parent_local_id;
+ FileError error = metadata->GetIdByResourceId(parent_resource_id,
+ &parent_local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+ entry->set_parent_local_id(parent_local_id);
+
+ std::string local_id;
+ error = metadata->AddEntry(*entry, &local_id);
+ // Depending on timing, the metadata may have inserted via change list
+ // already. So, FILE_ERROR_EXISTS is not an error.
+ if (error == FILE_ERROR_EXISTS)
+ error = metadata->GetIdByResourceId(entry->resource_id(), &local_id);
+
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ return metadata->GetFilePath(local_id, file_path);
+}
+
+// Stores the file at |local_file_path| to the cache as a content of entry at
+// |remote_dest_path|, and marks it dirty.
+FileError UpdateLocalStateForScheduleTransfer(
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& local_src_path,
+ const base::FilePath& remote_dest_path,
+ ResourceEntry* entry,
+ std::string* local_id) {
+ FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ error = metadata->GetResourceEntryById(*local_id, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ return cache->Store(*local_id, std::string(), local_src_path,
+ internal::FileCache::FILE_OPERATION_COPY);
+}
+
+// Gets the file size of the |local_path|, and the ResourceEntry for the parent
+// of |remote_path| to prepare the necessary information for transfer.
+FileError PrepareTransferFileFromLocalToRemote(
+ internal::ResourceMetadata* metadata,
+ const base::FilePath& local_src_path,
+ const base::FilePath& remote_dest_path,
+ std::string* gdoc_resource_id,
+ ResourceEntry* parent_entry) {
+ FileError error = metadata->GetResourceEntryByPath(
+ remote_dest_path.DirName(), parent_entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ // The destination's parent must be a directory.
+ if (!parent_entry->file_info().is_directory())
+ return FILE_ERROR_NOT_A_DIRECTORY;
+
+ // Try to parse GDoc File and extract the resource id, if necessary.
+ // Failing isn't problem. It'd be handled as a regular file, then.
+ if (util::HasHostedDocumentExtension(local_src_path))
+ *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
+ return FILE_ERROR_OK;
+}
+
+// Performs local work before server-side work for transferring JSON-represented
+// gdoc files.
+FileError LocalWorkForTransferJsonGdocFile(
+ internal::ResourceMetadata* metadata,
+ CopyOperation::TransferJsonGdocParams* params) {
+ std::string local_id;
+ FileError error = metadata->GetIdByResourceId(params->resource_id, &local_id);
+ if (error != FILE_ERROR_OK) {
+ params->location_type = NOT_IN_METADATA;
+ return error == FILE_ERROR_NOT_FOUND ? FILE_ERROR_OK : error;
+ }
+
+ ResourceEntry entry;
+ error = metadata->GetResourceEntryById(local_id, &entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ params->local_id = entry.local_id();
+
+ if (entry.parent_local_id() == util::kDriveOtherDirLocalId) {
+ params->location_type = IS_ORPHAN;
+ entry.set_title(params->new_title);
+ entry.set_parent_local_id(params->parent_local_id);
+ entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+ entry.set_modification_date(base::Time::Now().ToInternalValue());
+ error = metadata->RefreshEntry(entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ return metadata->GetFilePath(local_id, &params->changed_path);
+ }
+
+ params->location_type = HAS_PARENT;
+ return FILE_ERROR_OK;
+}
+
+} // namespace
+
+CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ scheduler_(scheduler),
+ metadata_(metadata),
+ cache_(cache),
+ create_file_operation_(new CreateFileOperation(blocking_task_runner,
+ delegate,
+ metadata)),
+ weak_ptr_factory_(this) {
+}
+
+CopyOperation::~CopyOperation() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void CopyOperation::Copy(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ bool preserve_last_modified,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ CopyParams* params = new CopyParams;
+ params->src_file_path = src_file_path;
+ params->dest_file_path = dest_file_path;
+ params->preserve_last_modified = preserve_last_modified;
+ params->callback = callback;
+
+ std::vector<std::string>* updated_local_ids = new std::vector<std::string>;
+ bool* directory_changed = new bool(false);
+ bool* should_copy_on_server = new bool(false);
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&TryToCopyLocally, metadata_, cache_, params,
+ updated_local_ids, directory_changed, should_copy_on_server),
+ base::Bind(&CopyOperation::CopyAfterTryToCopyLocally,
+ weak_ptr_factory_.GetWeakPtr(), base::Owned(params),
+ base::Owned(updated_local_ids), base::Owned(directory_changed),
+ base::Owned(should_copy_on_server)));
+}
+
+void CopyOperation::CopyAfterTryToCopyLocally(
+ const CopyParams* params,
+ const std::vector<std::string>* updated_local_ids,
+ const bool* directory_changed,
+ const bool* should_copy_on_server,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!params->callback.is_null());
+
+ for (const auto& id : *updated_local_ids) {
+ // Syncing for copy should be done in background, so pass the BACKGROUND
+ // context. See: crbug.com/420278.
+ delegate_->OnEntryUpdatedByOperation(ClientContext(BACKGROUND), id);
+ }
+
+ if (*directory_changed) {
+ FileChange changed_file;
+ DCHECK(!params->src_entry.file_info().is_directory());
+ changed_file.Update(params->dest_file_path, FileChange::FILE_TYPE_FILE,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+ delegate_->OnFileChangedByOperation(changed_file);
+ }
+
+ if (error != FILE_ERROR_OK || !*should_copy_on_server) {
+ params->callback.Run(error);
+ return;
+ }
+
+ if (params->parent_entry.resource_id().empty()) {
+ // Parent entry may be being synced.
+ const bool waiting = delegate_->WaitForSyncComplete(
+ params->parent_entry.local_id(),
+ base::Bind(&CopyOperation::CopyAfterParentSync,
+ weak_ptr_factory_.GetWeakPtr(), *params));
+ if (!waiting)
+ params->callback.Run(FILE_ERROR_NOT_FOUND);
+ } else {
+ CopyAfterGetParentResourceId(*params, &params->parent_entry, FILE_ERROR_OK);
+ }
+}
+
+void CopyOperation::CopyAfterParentSync(const CopyParams& params,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!params.callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ params.callback.Run(error);
+ return;
+ }
+
+ ResourceEntry* parent = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::GetResourceEntryById,
+ base::Unretained(metadata_),
+ params.parent_entry.local_id(),
+ parent),
+ base::Bind(&CopyOperation::CopyAfterGetParentResourceId,
+ weak_ptr_factory_.GetWeakPtr(),
+ params,
+ base::Owned(parent)));
+}
+
+void CopyOperation::CopyAfterGetParentResourceId(const CopyParams& params,
+ const ResourceEntry* parent,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!params.callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ params.callback.Run(error);
+ return;
+ }
+
+ base::FilePath new_title = params.dest_file_path.BaseName();
+ if (params.src_entry.file_specific_info().is_hosted_document()) {
+ // Drop the document extension, which should not be in the title.
+ // TODO(yoshiki): Remove this code with crbug.com/223304.
+ new_title = new_title.RemoveExtension();
+ }
+
+ base::Time last_modified =
+ params.preserve_last_modified ?
+ base::Time::FromInternalValue(
+ params.src_entry.file_info().last_modified()) : base::Time();
+
+ CopyResourceOnServer(
+ params.src_entry.resource_id(), parent->resource_id(),
+ new_title.AsUTF8Unsafe(), last_modified, params.callback);
+}
+
+void CopyOperation::TransferFileFromLocalToRemote(
+ const base::FilePath& local_src_path,
+ const base::FilePath& remote_dest_path,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ std::string* gdoc_resource_id = new std::string;
+ ResourceEntry* parent_entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(
+ &PrepareTransferFileFromLocalToRemote,
+ metadata_, local_src_path, remote_dest_path,
+ gdoc_resource_id, parent_entry),
+ base::Bind(
+ &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
+ weak_ptr_factory_.GetWeakPtr(),
+ local_src_path, remote_dest_path, callback,
+ base::Owned(gdoc_resource_id), base::Owned(parent_entry)));
+}
+
+void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
+ const base::FilePath& local_src_path,
+ const base::FilePath& remote_dest_path,
+ const FileOperationCallback& callback,
+ std::string* gdoc_resource_id,
+ ResourceEntry* parent_entry,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ // For regular files, schedule the transfer.
+ if (gdoc_resource_id->empty()) {
+ ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
+ return;
+ }
+
+ // GDoc file may contain a resource ID in the old format.
+ const std::string canonicalized_resource_id =
+ util::CanonicalizeResourceId(*gdoc_resource_id);
+
+ // Drop the document extension, which should not be in the title.
+ // TODO(yoshiki): Remove this code with crbug.com/223304.
+ const std::string new_title =
+ remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe();
+
+ // This is uploading a JSON file representing a hosted document.
+ TransferJsonGdocParams* params = new TransferJsonGdocParams(
+ callback, canonicalized_resource_id, *parent_entry, new_title);
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&LocalWorkForTransferJsonGdocFile, metadata_, params),
+ base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork,
+ weak_ptr_factory_.GetWeakPtr(), base::Owned(params)));
+}
+
+void CopyOperation::TransferJsonGdocFileAfterLocalWork(
+ TransferJsonGdocParams* params,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (error != FILE_ERROR_OK) {
+ params->callback.Run(error);
+ return;
+ }
+
+ switch (params->location_type) {
+ // When |resource_id| is found in the local metadata and it has a specific
+ // parent folder, we assume the user's intention is to copy the document and
+ // thus perform the server-side copy operation.
+ case HAS_PARENT:
+ CopyResourceOnServer(params->resource_id,
+ params->parent_resource_id,
+ params->new_title,
+ base::Time(),
+ params->callback);
+ break;
+ // When |resource_id| has no parent, we just set the new destination folder
+ // as the parent, for sharing the document between the original source.
+ // This reparenting is already done in LocalWorkForTransferJsonGdocFile().
+ case IS_ORPHAN: {
+ DCHECK(!params->changed_path.empty());
+ // Syncing for copy should be done in background, so pass the BACKGROUND
+ // context. See: crbug.com/420278.
+ delegate_->OnEntryUpdatedByOperation(ClientContext(BACKGROUND),
+ params->local_id);
+
+ FileChange changed_file;
+ changed_file.Update(
+ params->changed_path,
+ FileChange::FILE_TYPE_FILE, // This must be a hosted document.
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+ delegate_->OnFileChangedByOperation(changed_file);
+ params->callback.Run(error);
+ break;
+ }
+ // When the |resource_id| is not in the local metadata, assume it to be a
+ // document just now shared on the server but not synced locally.
+ // Same as the IS_ORPHAN case, we want to deal the case by setting parent,
+ // but this time we need to resort to server side operation.
+ case NOT_IN_METADATA:
+ scheduler_->UpdateResource(
+ params->resource_id, params->parent_resource_id, params->new_title,
+ base::Time(), base::Time(), google_apis::drive::Properties(),
+ ClientContext(USER_INITIATED),
+ base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
+ weak_ptr_factory_.GetWeakPtr(), params->callback));
+ break;
+ }
+}
+
+void CopyOperation::CopyResourceOnServer(
+ const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ scheduler_->CopyResource(
+ resource_id, parent_resource_id, new_title, last_modified,
+ base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback));
+}
+
+void CopyOperation::UpdateAfterServerSideOperation(
+ const FileOperationCallback& callback,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileResource> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileError error = GDataToFileError(status);
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ ResourceEntry* resource_entry = new ResourceEntry;
+
+ // The copy on the server side is completed successfully. Update the local
+ // metadata.
+ base::FilePath* file_path = new base::FilePath;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateLocalStateForServerSideOperation,
+ metadata_,
+ base::Passed(&entry),
+ resource_entry,
+ file_path),
+ base::Bind(&CopyOperation::UpdateAfterLocalStateUpdate,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ base::Owned(file_path),
+ base::Owned(resource_entry)));
+}
+
+void CopyOperation::UpdateAfterLocalStateUpdate(
+ const FileOperationCallback& callback,
+ base::FilePath* file_path,
+ const ResourceEntry* entry,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error == FILE_ERROR_OK) {
+ FileChange changed_file;
+ changed_file.Update(*file_path, *entry,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+ delegate_->OnFileChangedByOperation(changed_file);
+ }
+ callback.Run(error);
+}
+
+void CopyOperation::ScheduleTransferRegularFile(
+ const base::FilePath& local_src_path,
+ const base::FilePath& remote_dest_path,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ create_file_operation_->CreateFile(
+ remote_dest_path,
+ false, // Not exclusive (OK even if a file already exists).
+ std::string(), // no specific mime type; CreateFile should guess it.
+ base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
+ weak_ptr_factory_.GetWeakPtr(),
+ local_src_path, remote_dest_path, callback));
+}
+
+void CopyOperation::ScheduleTransferRegularFileAfterCreate(
+ const base::FilePath& local_src_path,
+ const base::FilePath& remote_dest_path,
+ const FileOperationCallback& callback,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ std::string* local_id = new std::string;
+ ResourceEntry* entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateLocalStateForScheduleTransfer,
+ metadata_,
+ cache_,
+ local_src_path,
+ remote_dest_path,
+ entry,
+ local_id),
+ base::Bind(
+ &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ remote_dest_path,
+ base::Owned(entry),
+ base::Owned(local_id)));
+}
+
+void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
+ const FileOperationCallback& callback,
+ const base::FilePath& remote_dest_path,
+ const ResourceEntry* entry,
+ std::string* local_id,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error == FILE_ERROR_OK) {
+ FileChange changed_file;
+ changed_file.Update(remote_dest_path, *entry,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+ delegate_->OnFileChangedByOperation(changed_file);
+ // Syncing for copy should be done in background, so pass the BACKGROUND
+ // context. See: crbug.com/420278.
+ delegate_->OnEntryUpdatedByOperation(ClientContext(BACKGROUND), *local_id);
+ }
+ callback.Run(error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/copy_operation.h b/components/drive/file_system/copy_operation.h
new file mode 100644
index 0000000..1f92bd4
--- /dev/null
+++ b/components/drive/file_system/copy_operation.h
@@ -0,0 +1,181 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_FILE_SYSTEM_COPY_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_COPY_OPERATION_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+class Time;
+} // namespace base
+
+namespace google_apis {
+class FileResource;
+} // namespace google_apis
+
+namespace drive {
+
+class JobScheduler;
+class ResourceEntry;
+
+namespace internal {
+class FileCache;
+class ResourceMetadata;
+} // namespace internal
+
+namespace file_system {
+
+class CreateFileOperation;
+class OperationDelegate;
+
+// This class encapsulates the drive Copy function. It is responsible for
+// sending the request to the drive API, then updating the local state and
+// metadata to reflect the new state.
+class CopyOperation {
+ public:
+ CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache);
+ ~CopyOperation();
+
+ // Performs the copy operation on the file at drive path |src_file_path|
+ // with a target of |dest_file_path|.
+ // If |preserve_last_modified| is set to true, this tries to preserve
+ // last modified time stamp. This is supported only on Drive API v2.
+ // Invokes |callback| when finished with the result of the operation.
+ // |callback| must not be null.
+ void Copy(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ bool preserve_last_modified,
+ const FileOperationCallback& callback);
+
+ // Initiates transfer of |local_src_file_path| to |remote_dest_file_path|.
+ // |local_src_file_path| must be a file from the local file system.
+ // |remote_dest_file_path| is the virtual destination path within Drive file
+ // system.
+ //
+ // |callback| must not be null.
+ void TransferFileFromLocalToRemote(
+ const base::FilePath& local_src_file_path,
+ const base::FilePath& remote_dest_file_path,
+ const FileOperationCallback& callback);
+
+ // Params for Copy().
+ struct CopyParams;
+
+ // Params for TransferJsonGdocFileAfterLocalWork.
+ struct TransferJsonGdocParams;
+
+ private:
+ // Part of Copy(). Called after trying to copy locally.
+ void CopyAfterTryToCopyLocally(
+ const CopyParams* params,
+ const std::vector<std::string>* updated_local_ids,
+ const bool* directory_changed,
+ const bool* should_copy_on_server,
+ FileError error);
+
+ // Part of Copy(). Called after the parent entry gets synced.
+ void CopyAfterParentSync(const CopyParams& params, FileError error);
+
+ // Part of Copy(). Called after the parent resource ID is resolved.
+ void CopyAfterGetParentResourceId(const CopyParams& params,
+ const ResourceEntry* parent,
+ FileError error);
+
+ // Part of TransferFileFromLocalToRemote(). Called after preparation is done.
+ // |gdoc_resource_id| and |parent_resource_id| is available only if the file
+ // is JSON GDoc file.
+ void TransferFileFromLocalToRemoteAfterPrepare(
+ const base::FilePath& local_src_path,
+ const base::FilePath& remote_dest_path,
+ const FileOperationCallback& callback,
+ std::string* gdoc_resource_id,
+ ResourceEntry* parent_entry,
+ FileError error);
+
+ // Part of TransferFileFromLocalToRemote().
+ void TransferJsonGdocFileAfterLocalWork(TransferJsonGdocParams* params,
+ FileError error);
+
+ // Copies resource with |resource_id| into the directory |parent_resource_id|
+ // with renaming it to |new_title|.
+ void CopyResourceOnServer(const std::string& resource_id,
+ const std::string& parent_resource_id,
+ const std::string& new_title,
+ const base::Time& last_modified,
+ const FileOperationCallback& callback);
+
+ // Part of CopyResourceOnServer and TransferFileFromLocalToRemote.
+ // Called after server side operation is done.
+ void UpdateAfterServerSideOperation(
+ const FileOperationCallback& callback,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileResource> entry);
+
+ // Part of CopyResourceOnServer and TransferFileFromLocalToRemote.
+ // Called after local state update is done.
+ void UpdateAfterLocalStateUpdate(const FileOperationCallback& callback,
+ base::FilePath* file_path,
+ const ResourceEntry* entry,
+ FileError error);
+
+ // Creates an empty file on the server at |remote_dest_path| to ensure
+ // the location, stores a file at |local_file_path| in cache and marks it
+ // dirty, so that SyncClient will upload the data later.
+ void ScheduleTransferRegularFile(const base::FilePath& local_src_path,
+ const base::FilePath& remote_dest_path,
+ const FileOperationCallback& callback);
+
+ // Part of ScheduleTransferRegularFile(). Called after file creation.
+ void ScheduleTransferRegularFileAfterCreate(
+ const base::FilePath& local_src_path,
+ const base::FilePath& remote_dest_path,
+ const FileOperationCallback& callback,
+ FileError error);
+
+ // Part of ScheduleTransferRegularFile(). Called after updating local state
+ // is completed.
+ void ScheduleTransferRegularFileAfterUpdateLocalState(
+ const FileOperationCallback& callback,
+ const base::FilePath& remote_dest_path,
+ const ResourceEntry* entry,
+ std::string* local_id,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ JobScheduler* scheduler_;
+ internal::ResourceMetadata* metadata_;
+ internal::FileCache* cache_;
+
+ // Uploading a new file is internally implemented by creating a dirty file.
+ scoped_ptr<CreateFileOperation> create_file_operation_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<CopyOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(CopyOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_COPY_OPERATION_H_
diff --git a/components/drive/file_system/copy_operation_unittest.cc b/components/drive/file_system/copy_operation_unittest.cc
new file mode 100644
index 0000000..3491746
--- /dev/null
+++ b/components/drive/file_system/copy_operation_unittest.cc
@@ -0,0 +1,517 @@
+// 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 "components/drive/file_system/copy_operation.h"
+
+#include "base/files/file_util.h"
+#include "base/task_runner_util.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/resource_metadata.h"
+#include "components/drive/service/fake_drive_service.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+namespace {
+
+// Used to handle WaitForSyncComplete() calls.
+bool CopyWaitForSyncCompleteArguments(std::string* out_local_id,
+ FileOperationCallback* out_callback,
+ const std::string& local_id,
+ const FileOperationCallback& callback) {
+ *out_local_id = local_id;
+ *out_callback = callback;
+ return true;
+}
+
+} // namespace
+
+class CopyOperationTest : public OperationTestBase {
+ protected:
+ void SetUp() override {
+ OperationTestBase::SetUp();
+ operation_.reset(new CopyOperation(
+ blocking_task_runner(), delegate(), scheduler(), metadata(), cache()));
+ }
+
+ scoped_ptr<CopyOperation> operation_;
+};
+
+TEST_F(CopyOperationTest, TransferFileFromLocalToRemote_RegularFile) {
+ const base::FilePath local_src_path = temp_dir().AppendASCII("local.txt");
+ const base::FilePath remote_dest_path(
+ FILE_PATH_LITERAL("drive/root/remote.txt"));
+
+ // Prepare a local file.
+ ASSERT_TRUE(
+ google_apis::test_util::WriteStringToFile(local_src_path, "hello"));
+ // Confirm that the remote file does not exist.
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(remote_dest_path, &entry));
+
+ // Transfer the local file to Drive.
+ FileError error = FILE_ERROR_FAILED;
+ operation_->TransferFileFromLocalToRemote(
+ local_src_path,
+ remote_dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // TransferFileFromLocalToRemote stores a copy of the local file in the cache,
+ // marks it dirty and requests the observer to upload the file.
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(remote_dest_path, &entry));
+ EXPECT_EQ(1U, delegate()->updated_local_ids().count(entry.local_id()));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
+
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(remote_dest_path));
+}
+
+TEST_F(CopyOperationTest, TransferFileFromLocalToRemote_Overwrite) {
+ const base::FilePath local_src_path = temp_dir().AppendASCII("local.txt");
+ const base::FilePath remote_dest_path(
+ FILE_PATH_LITERAL("drive/root/File 1.txt"));
+
+ // Prepare a local file.
+ EXPECT_TRUE(
+ google_apis::test_util::WriteStringToFile(local_src_path, "hello"));
+ // Confirm that the remote file exists.
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(remote_dest_path, &entry));
+
+ // Transfer the local file to Drive.
+ FileError error = FILE_ERROR_FAILED;
+ operation_->TransferFileFromLocalToRemote(
+ local_src_path,
+ remote_dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // TransferFileFromLocalToRemote stores a copy of the local file in the cache,
+ // marks it dirty and requests the observer to upload the file.
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(remote_dest_path, &entry));
+ EXPECT_EQ(1U, delegate()->updated_local_ids().count(entry.local_id()));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
+
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(remote_dest_path));
+}
+
+TEST_F(CopyOperationTest,
+ TransferFileFromLocalToRemote_ExistingHostedDocument) {
+ const base::FilePath local_src_path = temp_dir().AppendASCII("local.gdoc");
+ const base::FilePath remote_dest_path(FILE_PATH_LITERAL(
+ "drive/root/Directory 1/copied.gdoc"));
+
+ // Prepare a local file, which is a json file of a hosted document, which
+ // matches "drive/root/Document 1 excludeDir-test".
+ ASSERT_TRUE(util::CreateGDocFile(
+ local_src_path,
+ GURL("https://3_document_self_link/5_document_resource_id"),
+ "5_document_resource_id"));
+
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(remote_dest_path, &entry));
+
+ // Transfer the local file to Drive.
+ FileError error = FILE_ERROR_FAILED;
+ operation_->TransferFileFromLocalToRemote(
+ local_src_path,
+ remote_dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(remote_dest_path, &entry));
+
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(remote_dest_path));
+ // New copy is created.
+ EXPECT_NE("5_document_resource_id", entry.resource_id());
+}
+
+TEST_F(CopyOperationTest, TransferFileFromLocalToRemote_OrphanHostedDocument) {
+ const base::FilePath local_src_path = temp_dir().AppendASCII("local.gdoc");
+ const base::FilePath remote_dest_path(FILE_PATH_LITERAL(
+ "drive/root/Directory 1/moved.gdoc"));
+
+ // Prepare a local file, which is a json file of a hosted document, which
+ // matches "drive/other/Orphan Document".
+ ASSERT_TRUE(util::CreateGDocFile(
+ local_src_path,
+ GURL("https://3_document_self_link/orphan_doc_1"),
+ "orphan_doc_1"));
+
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(remote_dest_path, &entry));
+
+ // Transfer the local file to Drive.
+ FileError error = FILE_ERROR_FAILED;
+ operation_->TransferFileFromLocalToRemote(
+ local_src_path,
+ remote_dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(remote_dest_path, &entry));
+ EXPECT_EQ(ResourceEntry::DIRTY, entry.metadata_edit_state());
+ EXPECT_TRUE(delegate()->updated_local_ids().count(entry.local_id()));
+
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(remote_dest_path));
+ // The original document got new parent.
+ EXPECT_EQ("orphan_doc_1", entry.resource_id());
+}
+
+TEST_F(CopyOperationTest, TransferFileFromLocalToRemote_NewHostedDocument) {
+ const base::FilePath local_src_path = temp_dir().AppendASCII("local.gdoc");
+ const base::FilePath remote_dest_path(FILE_PATH_LITERAL(
+ "drive/root/Directory 1/moved.gdoc"));
+
+ // Create a hosted document on the server that is not synced to local yet.
+ google_apis::DriveApiErrorCode gdata_error = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> new_gdoc_entry;
+ fake_service()->AddNewFile(
+ "application/vnd.google-apps.document", "", "", "title", true,
+ google_apis::test_util::CreateCopyResultCallback(&gdata_error,
+ &new_gdoc_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_EQ(google_apis::HTTP_CREATED, gdata_error);
+
+ // Prepare a local file, which is a json file of the added hosted document.
+ ASSERT_TRUE(util::CreateGDocFile(
+ local_src_path,
+ GURL("https://3_document_self_link/" + new_gdoc_entry->file_id()),
+ new_gdoc_entry->file_id()));
+
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(remote_dest_path, &entry));
+
+ // Transfer the local file to Drive.
+ FileError error = FILE_ERROR_FAILED;
+ operation_->TransferFileFromLocalToRemote(
+ local_src_path,
+ remote_dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(remote_dest_path, &entry));
+
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(remote_dest_path));
+ // The original document got new parent.
+ EXPECT_EQ(new_gdoc_entry->file_id(), entry.resource_id());
+}
+
+TEST_F(CopyOperationTest, CopyNotExistingFile) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/Dummy file.txt"));
+ base::FilePath dest_path(FILE_PATH_LITERAL("drive/root/Test.log"));
+
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(src_path, &entry));
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Copy(src_path,
+ dest_path,
+ false,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
+
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(src_path, &entry));
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(dest_path, &entry));
+ EXPECT_TRUE(delegate()->get_changed_files().empty());
+}
+
+TEST_F(CopyOperationTest, CopyFileToNonExistingDirectory) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_path(FILE_PATH_LITERAL("drive/root/Dummy/Test.log"));
+
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &entry));
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(dest_path.DirName(), &entry));
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Copy(src_path,
+ dest_path,
+ false,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
+
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &entry));
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(dest_path, &entry));
+ EXPECT_TRUE(delegate()->get_changed_files().empty());
+}
+
+// Test the case where the parent of the destination path is an existing file,
+// not a directory.
+TEST_F(CopyOperationTest, CopyFileToInvalidPath) {
+ base::FilePath src_path(FILE_PATH_LITERAL(
+ "drive/root/Document 1 excludeDir-test.gdoc"));
+ base::FilePath dest_path(FILE_PATH_LITERAL(
+ "drive/root/Duplicate Name.txt/Document 1 excludeDir-test.gdoc"));
+
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &entry));
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path.DirName(), &entry));
+ ASSERT_FALSE(entry.file_info().is_directory());
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Copy(src_path,
+ dest_path,
+ false,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_A_DIRECTORY, error);
+
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &entry));
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(dest_path, &entry));
+ EXPECT_TRUE(delegate()->get_changed_files().empty());
+}
+
+TEST_F(CopyOperationTest, CopyDirtyFile) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_path(FILE_PATH_LITERAL(
+ "drive/root/Directory 1/New File.txt"));
+
+ ResourceEntry src_entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &src_entry));
+
+ // Store a dirty cache file.
+ base::FilePath temp_file;
+ EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir(), &temp_file));
+ std::string contents = "test content";
+ EXPECT_TRUE(google_apis::test_util::WriteStringToFile(temp_file, contents));
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::Store,
+ base::Unretained(cache()),
+ src_entry.local_id(),
+ std::string(),
+ temp_file,
+ internal::FileCache::FILE_OPERATION_MOVE),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Copy.
+ operation_->Copy(src_path,
+ dest_path,
+ false,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ ResourceEntry dest_entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path, &dest_entry));
+ EXPECT_EQ(ResourceEntry::DIRTY, dest_entry.metadata_edit_state());
+
+ EXPECT_EQ(1u, delegate()->updated_local_ids().size());
+ EXPECT_TRUE(delegate()->updated_local_ids().count(dest_entry.local_id()));
+ EXPECT_EQ(1u, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(dest_path));
+
+ // Copied cache file should be dirty.
+ EXPECT_TRUE(dest_entry.file_specific_info().cache_state().is_dirty());
+
+ // File contents should match.
+ base::FilePath cache_file_path;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::GetFile,
+ base::Unretained(cache()),
+ dest_entry.local_id(),
+ &cache_file_path),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ std::string copied_contents;
+ EXPECT_TRUE(base::ReadFileToString(cache_file_path, &copied_contents));
+ EXPECT_EQ(contents, copied_contents);
+}
+
+TEST_F(CopyOperationTest, CopyFileOverwriteFile) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_path(FILE_PATH_LITERAL(
+ "drive/root/Directory 1/SubDirectory File 1.txt"));
+
+ ResourceEntry old_dest_entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path, &old_dest_entry));
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Copy(src_path,
+ dest_path,
+ false,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ ResourceEntry new_dest_entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path, &new_dest_entry));
+
+ EXPECT_EQ(1u, delegate()->updated_local_ids().size());
+ EXPECT_TRUE(delegate()->updated_local_ids().count(old_dest_entry.local_id()));
+ EXPECT_EQ(1u, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(dest_path));
+}
+
+TEST_F(CopyOperationTest, CopyFileOverwriteDirectory) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_path(FILE_PATH_LITERAL("drive/root/Directory 1"));
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Copy(src_path,
+ dest_path,
+ false,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_INVALID_OPERATION, error);
+}
+
+TEST_F(CopyOperationTest, CopyDirectory) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/Directory 1"));
+ base::FilePath dest_path(FILE_PATH_LITERAL("drive/root/New Directory"));
+
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &entry));
+ ASSERT_TRUE(entry.file_info().is_directory());
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path.DirName(), &entry));
+ ASSERT_TRUE(entry.file_info().is_directory());
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Copy(src_path,
+ dest_path,
+ false,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_A_FILE, error);
+}
+
+TEST_F(CopyOperationTest, PreserveLastModified) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_path(FILE_PATH_LITERAL("drive/root/File 2.txt"));
+
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &entry));
+ ASSERT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntry(dest_path.DirName(), &entry));
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Copy(src_path,
+ dest_path,
+ true, // Preserve last modified.
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ ResourceEntry entry2;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &entry));
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path, &entry2));
+ EXPECT_EQ(entry.file_info().last_modified(),
+ entry2.file_info().last_modified());
+}
+
+TEST_F(CopyOperationTest, WaitForSyncComplete) {
+ // Create a directory locally.
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath directory_path(FILE_PATH_LITERAL("drive/root/New Directory"));
+ base::FilePath dest_path = directory_path.AppendASCII("File 1.txt");
+
+ ResourceEntry directory_parent;
+ EXPECT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntry(directory_path.DirName(), &directory_parent));
+
+ ResourceEntry directory;
+ directory.set_parent_local_id(directory_parent.local_id());
+ directory.set_title(directory_path.BaseName().AsUTF8Unsafe());
+ directory.mutable_file_info()->set_is_directory(true);
+ directory.set_metadata_edit_state(ResourceEntry::DIRTY);
+
+ std::string directory_local_id;
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::AddEntry,
+ base::Unretained(metadata()), directory, &directory_local_id),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Try to copy a file to the new directory which lacks resource ID.
+ // This should result in waiting for the directory to sync.
+ std::string waited_local_id;
+ FileOperationCallback pending_callback;
+ delegate()->set_wait_for_sync_complete_handler(
+ base::Bind(&CopyWaitForSyncCompleteArguments,
+ &waited_local_id, &pending_callback));
+
+ FileError copy_error = FILE_ERROR_FAILED;
+ operation_->Copy(src_path,
+ dest_path,
+ true, // Preserve last modified.
+ google_apis::test_util::CreateCopyResultCallback(
+ &copy_error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(directory_local_id, waited_local_id);
+ ASSERT_FALSE(pending_callback.is_null());
+
+ // Add a new directory to the server and store the resource ID locally.
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> file_resource;
+ fake_service()->AddNewDirectory(
+ directory_parent.resource_id(), directory.title(),
+ AddNewDirectoryOptions(),
+ google_apis::test_util::CreateCopyResultCallback(&status,
+ &file_resource));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_CREATED, status);
+ ASSERT_TRUE(file_resource);
+
+ directory.set_local_id(directory_local_id);
+ directory.set_resource_id(file_resource->file_id());
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::RefreshEntry,
+ base::Unretained(metadata()), directory),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Resume the copy operation.
+ pending_callback.Run(FILE_ERROR_OK);
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, copy_error);
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path, &entry));
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/create_directory_operation.cc b/components/drive/file_system/create_directory_operation.cc
new file mode 100644
index 0000000..a06ef5b
--- /dev/null
+++ b/components/drive/file_system/create_directory_operation.cc
@@ -0,0 +1,182 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system/create_directory_operation.h"
+
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+
+namespace drive {
+namespace file_system {
+
+namespace {
+
+FileError CreateDirectoryRecursively(internal::ResourceMetadata* metadata,
+ const std::string& parent_local_id,
+ const base::FilePath& relative_file_path,
+ std::set<std::string>* updated_local_ids,
+ FileChange* changed_files) {
+ // Split the first component and remaining ones of |relative_file_path|.
+ std::vector<base::FilePath::StringType> components;
+ relative_file_path.GetComponents(&components);
+ DCHECK(!components.empty());
+ base::FilePath title(components[0]);
+ base::FilePath remaining_path;
+ title.AppendRelativePath(relative_file_path, &remaining_path);
+
+ ResourceEntry entry;
+ const base::Time now = base::Time::Now();
+ entry.set_title(title.AsUTF8Unsafe());
+ entry.mutable_file_info()->set_is_directory(true);
+ entry.mutable_file_info()->set_last_modified(now.ToInternalValue());
+ entry.mutable_file_info()->set_last_accessed(now.ToInternalValue());
+ entry.set_parent_local_id(parent_local_id);
+ entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+ entry.set_modification_date(base::Time::Now().ToInternalValue());
+
+ std::string local_id;
+ FileError error = metadata->AddEntry(entry, &local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ base::FilePath path;
+ error = metadata->GetFilePath(local_id, &path);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ updated_local_ids->insert(local_id);
+ DCHECK(changed_files);
+ changed_files->Update(path, FileChange::FILE_TYPE_DIRECTORY,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+
+ if (remaining_path.empty()) // All directories are created successfully.
+ return FILE_ERROR_OK;
+
+ // Create descendant directories.
+ return CreateDirectoryRecursively(
+ metadata, local_id, remaining_path, updated_local_ids, changed_files);
+}
+
+FileError UpdateLocalState(internal::ResourceMetadata* metadata,
+ const base::FilePath& directory_path,
+ bool is_exclusive,
+ bool is_recursive,
+ std::set<std::string>* updated_local_ids,
+ FileChange* changed_files) {
+ // Get the existing deepest entry.
+ std::vector<base::FilePath::StringType> components;
+ directory_path.GetComponents(&components);
+
+ if (components.empty() ||
+ components[0] != util::GetDriveGrandRootPath().value())
+ return FILE_ERROR_NOT_FOUND;
+
+ base::FilePath existing_deepest_path(components[0]);
+ std::string local_id = util::kDriveGrandRootLocalId;
+ for (size_t i = 1; i < components.size(); ++i) {
+ const std::string component = base::FilePath(components[i]).AsUTF8Unsafe();
+ std::string child_local_id;
+ FileError error =
+ metadata->GetChildId(local_id, component, &child_local_id);
+ if (error == FILE_ERROR_NOT_FOUND)
+ break;
+ if (error != FILE_ERROR_OK)
+ return error;
+ existing_deepest_path = existing_deepest_path.Append(components[i]);
+ local_id = child_local_id;
+ }
+
+ ResourceEntry entry;
+ FileError error = metadata->GetResourceEntryById(local_id, &entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ if (!entry.file_info().is_directory())
+ return FILE_ERROR_NOT_A_DIRECTORY;
+
+ if (directory_path == existing_deepest_path)
+ return is_exclusive ? FILE_ERROR_EXISTS : FILE_ERROR_OK;
+
+ // If it is not recursive creation, the found directory must be the direct
+ // parent of |directory_path| to ensure creating exact one directory.
+ if (!is_recursive && existing_deepest_path != directory_path.DirName())
+ return FILE_ERROR_NOT_FOUND;
+
+ // Create directories under the found directory.
+ base::FilePath remaining_path;
+ existing_deepest_path.AppendRelativePath(directory_path, &remaining_path);
+ return CreateDirectoryRecursively(metadata,
+ entry.local_id(),
+ remaining_path,
+ updated_local_ids,
+ changed_files);
+}
+
+} // namespace
+
+CreateDirectoryOperation::CreateDirectoryOperation(
+ base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ metadata_(metadata),
+ weak_ptr_factory_(this) {
+}
+
+CreateDirectoryOperation::~CreateDirectoryOperation() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void CreateDirectoryOperation::CreateDirectory(
+ const base::FilePath& directory_path,
+ bool is_exclusive,
+ bool is_recursive,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ std::set<std::string>* updated_local_ids = new std::set<std::string>;
+ FileChange* changed_files(new FileChange);
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateLocalState,
+ metadata_,
+ directory_path,
+ is_exclusive,
+ is_recursive,
+ updated_local_ids,
+ changed_files),
+ base::Bind(
+ &CreateDirectoryOperation::CreateDirectoryAfterUpdateLocalState,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ base::Owned(updated_local_ids),
+ base::Owned(changed_files)));
+}
+
+void CreateDirectoryOperation::CreateDirectoryAfterUpdateLocalState(
+ const FileOperationCallback& callback,
+ const std::set<std::string>* updated_local_ids,
+ const FileChange* changed_files,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ for (const auto& id : *updated_local_ids) {
+ delegate_->OnEntryUpdatedByOperation(ClientContext(USER_INITIATED), id);
+ }
+
+ delegate_->OnFileChangedByOperation(*changed_files);
+
+ callback.Run(error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/create_directory_operation.h b/components/drive/file_system/create_directory_operation.h
new file mode 100644
index 0000000..8467db9
--- /dev/null
+++ b/components/drive/file_system/create_directory_operation.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_FILE_SYSTEM_CREATE_DIRECTORY_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_CREATE_DIRECTORY_OPERATION_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace drive {
+
+class FileChange;
+class ResourceEntry;
+
+namespace internal {
+class ResourceMetadata;
+} // namespace internal
+
+namespace file_system {
+
+class OperationDelegate;
+
+// This class encapsulates the drive Create Directory function. It is
+// responsible for sending the request to the drive API, then updating the
+// local state and metadata to reflect the new state.
+class CreateDirectoryOperation {
+ public:
+ CreateDirectoryOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata);
+ ~CreateDirectoryOperation();
+
+ // Creates a new directory at |directory_path|.
+ // If |is_exclusive| is true, an error is raised in case a directory exists
+ // already at the |directory_path|.
+ // If |is_recursive| is true, the invocation creates parent directories as
+ // needed just like mkdir -p does.
+ // Invokes |callback| when finished with the result of the operation.
+ // |callback| must not be null.
+ void CreateDirectory(const base::FilePath& directory_path,
+ bool is_exclusive,
+ bool is_recursive,
+ const FileOperationCallback& callback);
+
+ private:
+ // Part of CreateDirectory(). Called after UpdateLocalState().
+ void CreateDirectoryAfterUpdateLocalState(
+ const FileOperationCallback& callback,
+ const std::set<std::string>* updated_local_ids,
+ const FileChange* changed_directories,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ internal::ResourceMetadata* metadata_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<CreateDirectoryOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(CreateDirectoryOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_CREATE_DIRECTORY_OPERATION_H_
diff --git a/components/drive/file_system/create_directory_operation_unittest.cc b/components/drive/file_system/create_directory_operation_unittest.cc
new file mode 100644
index 0000000..df70157
--- /dev/null
+++ b/components/drive/file_system/create_directory_operation_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system/create_directory_operation.h"
+
+#include "components/drive/file_system/operation_test_base.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+class CreateDirectoryOperationTest : public OperationTestBase {
+ protected:
+ // Returns FILE_ERROR_OK if a directory is found at |path|.
+ FileError FindDirectory(const base::FilePath& path) {
+ ResourceEntry entry;
+ FileError error = GetLocalResourceEntry(path, &entry);
+ if (error == FILE_ERROR_OK && !entry.file_info().is_directory())
+ error = FILE_ERROR_NOT_A_DIRECTORY;
+ return error;
+ }
+};
+
+TEST_F(CreateDirectoryOperationTest, CreateDirectory) {
+ CreateDirectoryOperation operation(blocking_task_runner(),
+ delegate(),
+ metadata());
+
+ const base::FilePath kExistingFile(
+ FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ const base::FilePath kExistingDirectory(
+ FILE_PATH_LITERAL("drive/root/Directory 1"));
+ const base::FilePath kNewDirectory1(
+ FILE_PATH_LITERAL("drive/root/New Directory"));
+ const base::FilePath kNewDirectory2 =
+ kNewDirectory1.AppendASCII("New Directory 2/a/b/c");
+
+ // Create a new directory, not recursively.
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, FindDirectory(kNewDirectory1));
+
+ FileError error = FILE_ERROR_FAILED;
+ operation.CreateDirectory(
+ kNewDirectory1,
+ true, // is_exclusive
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_OK, FindDirectory(kNewDirectory1));
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_EQ(
+ 1U,
+ delegate()->get_changed_files().CountDirectory(kNewDirectory1.DirName()));
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kNewDirectory1, &entry));
+ EXPECT_EQ(ResourceEntry::DIRTY, entry.metadata_edit_state());
+ EXPECT_TRUE(entry.file_info().is_directory());
+ EXPECT_FALSE(base::Time::FromInternalValue(
+ entry.file_info().last_modified()).is_null());
+ EXPECT_FALSE(base::Time::FromInternalValue(
+ entry.file_info().last_accessed()).is_null());
+ EXPECT_EQ(1U, delegate()->updated_local_ids().size());
+ EXPECT_EQ(1U, delegate()->updated_local_ids().count(entry.local_id()));
+
+ // Create a new directory recursively.
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, FindDirectory(kNewDirectory2));
+ operation.CreateDirectory(
+ kNewDirectory2,
+ true, // is_exclusive
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, FindDirectory(kNewDirectory2));
+
+ operation.CreateDirectory(
+ kNewDirectory2,
+ true, // is_exclusive
+ true, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_OK, FindDirectory(kNewDirectory2));
+
+ // Try to create an existing directory.
+ operation.CreateDirectory(
+ kExistingDirectory,
+ true, // is_exclusive
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_EXISTS, error);
+
+ operation.CreateDirectory(
+ kExistingDirectory,
+ false, // is_exclusive
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Try to create a directory with a path for an existing file.
+ operation.CreateDirectory(
+ kExistingFile,
+ false, // is_exclusive
+ true, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_A_DIRECTORY, error);
+
+ // Try to create a directory under a file.
+ operation.CreateDirectory(
+ kExistingFile.AppendASCII("New Directory"),
+ false, // is_exclusive
+ true, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_A_DIRECTORY, error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/create_file_operation.cc b/components/drive/file_system/create_file_operation.cc
new file mode 100644
index 0000000..19a238e
--- /dev/null
+++ b/components/drive/file_system/create_file_operation.cc
@@ -0,0 +1,139 @@
+// 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 "components/drive/file_system/create_file_operation.h"
+
+#include <string>
+
+#include "base/files/file_util.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+#include "net/base/mime_util.h"
+
+namespace drive {
+namespace file_system {
+
+namespace {
+
+const char kMimeTypeOctetStream[] = "application/octet-stream";
+
+// Updates local state.
+FileError UpdateLocalState(internal::ResourceMetadata* metadata,
+ const base::FilePath& file_path,
+ const std::string& mime_type_in,
+ ResourceEntry* entry) {
+ DCHECK(metadata);
+
+ FileError error = metadata->GetResourceEntryByPath(file_path, entry);
+ if (error == FILE_ERROR_OK)
+ return FILE_ERROR_EXISTS;
+
+ if (error != FILE_ERROR_NOT_FOUND)
+ return error;
+
+ // If parent path is not a directory, it is an error.
+ ResourceEntry parent;
+ if (metadata->GetResourceEntryByPath(
+ file_path.DirName(), &parent) != FILE_ERROR_OK ||
+ !parent.file_info().is_directory())
+ return FILE_ERROR_NOT_A_DIRECTORY;
+
+ // If mime_type is not set or "application/octet-stream", guess from the
+ // |file_path|. If it is still unsure, use octet-stream by default.
+ std::string mime_type = mime_type_in;
+ if ((mime_type.empty() || mime_type == kMimeTypeOctetStream) &&
+ !net::GetMimeTypeFromFile(file_path, &mime_type))
+ mime_type = kMimeTypeOctetStream;
+
+ // Add the entry to the local resource metadata.
+ const base::Time now = base::Time::Now();
+ entry->mutable_file_info()->set_last_modified(now.ToInternalValue());
+ entry->mutable_file_info()->set_last_accessed(now.ToInternalValue());
+ entry->set_title(file_path.BaseName().AsUTF8Unsafe());
+ entry->set_parent_local_id(parent.local_id());
+ entry->set_metadata_edit_state(ResourceEntry::DIRTY);
+ entry->set_modification_date(base::Time::Now().ToInternalValue());
+ entry->mutable_file_specific_info()->set_content_mime_type(mime_type);
+
+ std::string local_id;
+ error = metadata->AddEntry(*entry, &local_id);
+ entry->set_local_id(local_id);
+ return error;
+}
+
+} // namespace
+
+CreateFileOperation::CreateFileOperation(
+ base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ metadata_(metadata),
+ weak_ptr_factory_(this) {
+}
+
+CreateFileOperation::~CreateFileOperation() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void CreateFileOperation::CreateFile(const base::FilePath& file_path,
+ bool is_exclusive,
+ const std::string& mime_type,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ ResourceEntry* entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateLocalState,
+ metadata_,
+ file_path,
+ mime_type,
+ entry),
+ base::Bind(&CreateFileOperation::CreateFileAfterUpdateLocalState,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ file_path,
+ is_exclusive,
+ base::Owned(entry)));
+}
+
+void CreateFileOperation::CreateFileAfterUpdateLocalState(
+ const FileOperationCallback& callback,
+ const base::FilePath& file_path,
+ bool is_exclusive,
+ ResourceEntry* entry,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error == FILE_ERROR_EXISTS) {
+ // Error if an exclusive mode is requested, or the entry is not a file.
+ error = (is_exclusive ||
+ entry->file_info().is_directory() ||
+ entry->file_specific_info().is_hosted_document()) ?
+ FILE_ERROR_EXISTS : FILE_ERROR_OK;
+ } else if (error == FILE_ERROR_OK) {
+ DCHECK(!entry->file_info().is_directory());
+
+ // Notify delegate if the file was newly created.
+ FileChange changed_file;
+ changed_file.Update(file_path, FileChange::FILE_TYPE_FILE,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+ delegate_->OnFileChangedByOperation(changed_file);
+ // Synchronize in the background.
+ delegate_->OnEntryUpdatedByOperation(ClientContext(BACKGROUND),
+ entry->local_id());
+ }
+ callback.Run(error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/create_file_operation.h b/components/drive/file_system/create_file_operation.h
new file mode 100644
index 0000000..b43132f
--- /dev/null
+++ b/components/drive/file_system/create_file_operation.h
@@ -0,0 +1,76 @@
+// 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 COMPONENTS_DRIVE_FILE_SYSTEM_CREATE_FILE_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_CREATE_FILE_OPERATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace drive {
+
+namespace internal {
+class ResourceMetadata;
+} // namespace internal
+
+class ResourceEntry;
+
+namespace file_system {
+
+class OperationDelegate;
+
+// This class encapsulates the drive CreateFile function. It is responsible for
+// sending the request to the drive API, then updating the local state and
+// metadata to reflect the new state.
+class CreateFileOperation {
+ public:
+ CreateFileOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata);
+ ~CreateFileOperation();
+
+ // Creates an empty file at |file_path|. When the file
+ // already exists at that path, the operation fails if |is_exclusive| is true,
+ // and it succeeds without doing anything if the flag is false.
+ // If |mime_type| is non-empty, it is used as the mime type of the entry. If
+ // the parameter is empty, the type is guessed from |file_path|.
+ //
+ // |callback| must not be null.
+ void CreateFile(const base::FilePath& file_path,
+ bool is_exclusive,
+ const std::string& mime_type,
+ const FileOperationCallback& callback);
+
+ private:
+ // Part of CreateFile(). Called after the updating local state is completed.
+ void CreateFileAfterUpdateLocalState(const FileOperationCallback& callback,
+ const base::FilePath& file_path,
+ bool is_exclusive,
+ ResourceEntry* entry,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ internal::ResourceMetadata* metadata_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<CreateFileOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(CreateFileOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_CREATE_FILE_OPERATION_H_
diff --git a/components/drive/file_system/create_file_operation_unittest.cc b/components/drive/file_system/create_file_operation_unittest.cc
new file mode 100644
index 0000000..79b4456
--- /dev/null
+++ b/components/drive/file_system/create_file_operation_unittest.cc
@@ -0,0 +1,161 @@
+// 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 "components/drive/file_system/create_file_operation.h"
+
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+typedef OperationTestBase CreateFileOperationTest;
+
+TEST_F(CreateFileOperationTest, CreateFile) {
+ CreateFileOperation operation(blocking_task_runner(),
+ delegate(),
+ metadata());
+
+ const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/New File.txt"));
+ FileError error = FILE_ERROR_FAILED;
+ operation.CreateFile(
+ kFilePath,
+ true, // is_exclusive
+ std::string(), // no predetermined mime type
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
+ EXPECT_EQ(ResourceEntry::DIRTY, entry.metadata_edit_state());
+ EXPECT_FALSE(base::Time::FromInternalValue(
+ entry.file_info().last_modified()).is_null());
+ EXPECT_FALSE(base::Time::FromInternalValue(
+ entry.file_info().last_accessed()).is_null());
+
+ EXPECT_EQ(1u, delegate()->get_changed_files().size());
+ EXPECT_EQ(1u, delegate()->get_changed_files().count(kFilePath));
+ EXPECT_EQ(1u, delegate()->updated_local_ids().size());
+ EXPECT_EQ(1u, delegate()->updated_local_ids().count(entry.local_id()));
+}
+
+TEST_F(CreateFileOperationTest, CreateFileIsExclusive) {
+ CreateFileOperation operation(blocking_task_runner(),
+ delegate(),
+ metadata());
+
+ const base::FilePath kExistingFile(
+ FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ const base::FilePath kExistingDirectory(
+ FILE_PATH_LITERAL("drive/root/Directory 1"));
+ const base::FilePath kNonExistingFile(
+ FILE_PATH_LITERAL("drive/root/Directory 1/not exist.png"));
+ const base::FilePath kFileInNonExistingDirectory(
+ FILE_PATH_LITERAL("drive/root/not exist/not exist.png"));
+
+ // Create fails if is_exclusive = true and a file exists.
+ FileError error = FILE_ERROR_FAILED;
+ operation.CreateFile(
+ kExistingFile,
+ true, // is_exclusive
+ std::string(), // no predetermined mime type
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_EXISTS, error);
+
+ // Create succeeds if is_exclusive = false and a file exists.
+ operation.CreateFile(
+ kExistingFile,
+ false, // is_exclusive
+ std::string(), // no predetermined mime type
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Create fails if a directory existed even when is_exclusive = false.
+ operation.CreateFile(
+ kExistingDirectory,
+ false, // is_exclusive
+ std::string(), // no predetermined mime type
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_EXISTS, error);
+
+ // Create succeeds if no entry exists.
+ operation.CreateFile(
+ kNonExistingFile,
+ true, // is_exclusive
+ std::string(), // no predetermined mime type
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Create fails if the parent directory does not exist.
+ operation.CreateFile(
+ kFileInNonExistingDirectory,
+ false, // is_exclusive
+ std::string(), // no predetermined mime type
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_A_DIRECTORY, error);
+}
+
+TEST_F(CreateFileOperationTest, CreateFileMimeType) {
+ CreateFileOperation operation(blocking_task_runner(),
+ delegate(),
+ metadata());
+
+ const base::FilePath kPng1(FILE_PATH_LITERAL("drive/root/1.png"));
+ const base::FilePath kPng2(FILE_PATH_LITERAL("drive/root/2.png"));
+ const base::FilePath kUnknown(FILE_PATH_LITERAL("drive/root/3.unknown"));
+ const std::string kSpecialMimeType("application/x-createfile-test");
+
+ FileError error = FILE_ERROR_FAILED;
+ operation.CreateFile(
+ kPng1,
+ false, // is_exclusive
+ std::string(), // no predetermined mime type
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // If no mime type is specified, it is guessed from the file name.
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kPng1, &entry));
+ EXPECT_EQ("image/png", entry.file_specific_info().content_mime_type());
+
+ error = FILE_ERROR_FAILED;
+ operation.CreateFile(
+ kPng2,
+ false, // is_exclusive
+ kSpecialMimeType,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // If the mime type is explicitly set, respect it.
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kPng2, &entry));
+ EXPECT_EQ(kSpecialMimeType, entry.file_specific_info().content_mime_type());
+
+ error = FILE_ERROR_FAILED;
+ operation.CreateFile(
+ kUnknown,
+ false, // is_exclusive
+ std::string(), // no predetermined mime type
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // If the mime type is not set and unknown, default to octet-stream.
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kUnknown, &entry));
+ EXPECT_EQ("application/octet-stream",
+ entry.file_specific_info().content_mime_type());
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/download_operation.cc b/components/drive/file_system/download_operation.cc
new file mode 100644
index 0000000..150009c
--- /dev/null
+++ b/components/drive/file_system/download_operation.cc
@@ -0,0 +1,540 @@
+// 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 "components/drive/file_system/download_operation.h"
+
+#include "base/callback_helpers.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/task_runner_util.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+namespace drive {
+namespace file_system {
+namespace {
+
+// Generates an unused file path with |extension| to |out_path|, as a descendant
+// of |dir|, with its parent directory created.
+bool GeneratesUniquePathWithExtension(
+ const base::FilePath& dir,
+ const base::FilePath::StringType& extension,
+ base::FilePath* out_path) {
+ base::FilePath subdir;
+ if (!base::CreateTemporaryDirInDir(dir, base::FilePath::StringType(),
+ &subdir)) {
+ return false;
+ }
+ *out_path = subdir.Append(FILE_PATH_LITERAL("tmp") + extension);
+ return true;
+}
+
+// Prepares for downloading the file. Allocates the enough space for the file
+// in the cache.
+// If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the
+// path to the file in the cache.
+FileError PrepareForDownloadFile(internal::FileCache* cache,
+ int64 expected_file_size,
+ const base::FilePath& temporary_file_directory,
+ base::FilePath* temp_download_file) {
+ DCHECK(cache);
+ DCHECK(temp_download_file);
+
+ // Ensure enough space in the cache.
+ if (!cache->FreeDiskSpaceIfNeededFor(expected_file_size))
+ return FILE_ERROR_NO_LOCAL_SPACE;
+
+ return base::CreateTemporaryFileInDir(
+ temporary_file_directory,
+ temp_download_file) ? FILE_ERROR_OK : FILE_ERROR_FAILED;
+}
+
+// If the resource is a hosted document, creates a JSON file representing the
+// resource locally, and returns FILE_ERROR_OK with |cache_file_path| storing
+// the path to the JSON file.
+// If the resource is a regular file and its local cache is available,
+// returns FILE_ERROR_OK with |cache_file_path| storing the path to the
+// cache file.
+// If the resource is a regular file but its local cache is NOT available,
+// returns FILE_ERROR_OK, but |cache_file_path| is kept empty.
+// Otherwise returns error code.
+FileError CheckPreConditionForEnsureFileDownloaded(
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory,
+ const std::string& local_id,
+ ResourceEntry* entry,
+ base::FilePath* cache_file_path,
+ base::FilePath* temp_download_file_path) {
+ DCHECK(metadata);
+ DCHECK(cache);
+ DCHECK(cache_file_path);
+
+ FileError error = metadata->GetResourceEntryById(local_id, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ if (entry->file_info().is_directory())
+ return FILE_ERROR_NOT_A_FILE;
+
+ // For a hosted document, we create a special JSON file to represent the
+ // document instead of fetching the document content in one of the exported
+ // formats. The JSON file contains the edit URL and resource ID of the
+ // document.
+ if (entry->file_specific_info().is_hosted_document()) {
+ base::FilePath::StringType extension = base::FilePath::FromUTF8Unsafe(
+ entry->file_specific_info().document_extension()).value();
+ base::FilePath gdoc_file_path;
+ base::File::Info file_info;
+ // We add the gdoc file extension in the temporary file, so that in cross
+ // profile drag-and-drop between Drive folders, the destination profiles's
+ // CopyOperation can detect the special JSON file only by the path.
+ if (!GeneratesUniquePathWithExtension(temporary_file_directory,
+ extension,
+ &gdoc_file_path) ||
+ !util::CreateGDocFile(gdoc_file_path,
+ GURL(entry->file_specific_info().alternate_url()),
+ entry->resource_id()) ||
+ !base::GetFileInfo(gdoc_file_path,
+ reinterpret_cast<base::File::Info*>(&file_info)))
+ return FILE_ERROR_FAILED;
+
+ *cache_file_path = gdoc_file_path;
+ entry->mutable_file_info()->set_size(file_info.size);
+ return FILE_ERROR_OK;
+ }
+
+ if (!entry->file_specific_info().cache_state().is_present()) {
+ // This file has no cache file.
+ if (!entry->resource_id().empty()) {
+ // This entry exists on the server, leave |cache_file_path| empty to
+ // start download.
+ return PrepareForDownloadFile(cache, entry->file_info().size(),
+ temporary_file_directory,
+ temp_download_file_path);
+ }
+
+ // This entry does not exist on the server, store an empty file and mark it
+ // as dirty.
+ base::FilePath empty_file;
+ if (!base::CreateTemporaryFileInDir(temporary_file_directory, &empty_file))
+ return FILE_ERROR_FAILED;
+ error = cache->Store(local_id, std::string(), empty_file,
+ internal::FileCache::FILE_OPERATION_MOVE);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ error = metadata->GetResourceEntryById(local_id, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ }
+
+ // Leave |cache_file_path| empty when the stored file is obsolete and has no
+ // local modification.
+ if (!entry->file_specific_info().cache_state().is_dirty() &&
+ entry->file_specific_info().md5() !=
+ entry->file_specific_info().cache_state().md5()) {
+ return PrepareForDownloadFile(cache, entry->file_info().size(),
+ temporary_file_directory,
+ temp_download_file_path);
+ }
+
+ // Fill |cache_file_path| with the path to the cached file.
+ error = cache->GetFile(local_id, cache_file_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ // If the cache file is to be returned as the download result, the file info
+ // of the cache needs to be returned via |entry|.
+ // TODO(kinaba): crbug.com/246469. The logic below is similar to that in
+ // drive::FileSystem::CheckLocalModificationAndRun. We should merge them.
+ base::File::Info file_info;
+ if (base::GetFileInfo(*cache_file_path, &file_info))
+ entry->mutable_file_info()->set_size(file_info.size);
+
+ return FILE_ERROR_OK;
+}
+
+struct CheckPreconditionForEnsureFileDownloadedParams {
+ internal::ResourceMetadata* metadata;
+ internal::FileCache* cache;
+ base::FilePath temporary_file_directory;
+};
+
+// Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
+// the given ID. Also fills |drive_file_path| with the path of the entry.
+FileError CheckPreConditionForEnsureFileDownloadedByLocalId(
+ const CheckPreconditionForEnsureFileDownloadedParams& params,
+ const std::string& local_id,
+ base::FilePath* drive_file_path,
+ base::FilePath* cache_file_path,
+ base::FilePath* temp_download_file_path,
+ ResourceEntry* entry) {
+ FileError error = params.metadata->GetFilePath(local_id, drive_file_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+ return CheckPreConditionForEnsureFileDownloaded(
+ params.metadata, params.cache, params.temporary_file_directory, local_id,
+ entry, cache_file_path, temp_download_file_path);
+}
+
+// Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
+// the given file path.
+FileError CheckPreConditionForEnsureFileDownloadedByPath(
+ const CheckPreconditionForEnsureFileDownloadedParams& params,
+ const base::FilePath& file_path,
+ base::FilePath* cache_file_path,
+ base::FilePath* temp_download_file_path,
+ ResourceEntry* entry) {
+ std::string local_id;
+ FileError error = params.metadata->GetIdByPath(file_path, &local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+ return CheckPreConditionForEnsureFileDownloaded(
+ params.metadata, params.cache, params.temporary_file_directory, local_id,
+ entry, cache_file_path, temp_download_file_path);
+}
+
+// Stores the downloaded file at |downloaded_file_path| into |cache|.
+// If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
+// path to the cache file.
+// If failed, returns an error code with deleting |downloaded_file_path|.
+FileError UpdateLocalStateForDownloadFile(
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const ResourceEntry& entry_before_download,
+ google_apis::DriveApiErrorCode gdata_error,
+ const base::FilePath& downloaded_file_path,
+ ResourceEntry* entry_after_update,
+ base::FilePath* cache_file_path) {
+ DCHECK(cache);
+
+ // Downloaded file should be deleted on errors.
+ base::ScopedClosureRunner file_deleter(base::Bind(
+ base::IgnoreResult(&base::DeleteFile),
+ downloaded_file_path, false /* recursive */));
+
+ FileError error = GDataToFileError(gdata_error);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ const std::string& local_id = entry_before_download.local_id();
+
+ // Do not overwrite locally edited file with server side contents.
+ ResourceEntry entry;
+ error = metadata->GetResourceEntryById(local_id, &entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ if (entry.file_specific_info().cache_state().is_dirty())
+ return FILE_ERROR_IN_USE;
+
+ // Here the download is completed successfully, so store it into the cache.
+ error = cache->Store(local_id,
+ entry_before_download.file_specific_info().md5(),
+ downloaded_file_path,
+ internal::FileCache::FILE_OPERATION_MOVE);
+ if (error != FILE_ERROR_OK)
+ return error;
+ base::Closure unused_file_deleter_closure = file_deleter.Release();
+
+ error = metadata->GetResourceEntryById(local_id, entry_after_update);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ return cache->GetFile(local_id, cache_file_path);
+}
+
+} // namespace
+
+class DownloadOperation::DownloadParams {
+ public:
+ DownloadParams(
+ const GetFileContentInitializedCallback initialized_callback,
+ const google_apis::GetContentCallback get_content_callback,
+ const GetFileCallback completion_callback,
+ scoped_ptr<ResourceEntry> entry)
+ : initialized_callback_(initialized_callback),
+ get_content_callback_(get_content_callback),
+ completion_callback_(completion_callback),
+ entry_(entry.Pass()),
+ was_cancelled_(false),
+ weak_ptr_factory_(this) {
+ DCHECK(!completion_callback_.is_null());
+ DCHECK(entry_);
+ }
+
+ base::Closure GetCancelClosure() {
+ return base::Bind(&DownloadParams::Cancel, weak_ptr_factory_.GetWeakPtr());
+ }
+
+ void OnCacheFileFound(const base::FilePath& cache_file_path) {
+ if (!initialized_callback_.is_null()) {
+ initialized_callback_.Run(FILE_ERROR_OK, cache_file_path,
+ make_scoped_ptr(new ResourceEntry(*entry_)));
+ }
+ completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry_.Pass());
+ }
+
+ void OnStartDownloading(const base::Closure& cancel_download_closure) {
+ cancel_download_closure_ = cancel_download_closure;
+ if (initialized_callback_.is_null()) {
+ return;
+ }
+
+ DCHECK(entry_);
+ initialized_callback_.Run(FILE_ERROR_OK, base::FilePath(),
+ make_scoped_ptr(new ResourceEntry(*entry_)));
+ }
+
+ void OnError(FileError error) const {
+ completion_callback_.Run(
+ error, base::FilePath(), scoped_ptr<ResourceEntry>());
+ }
+
+ void OnDownloadCompleted(const base::FilePath& cache_file_path,
+ scoped_ptr<ResourceEntry> entry) const {
+ completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry.Pass());
+ }
+
+ const google_apis::GetContentCallback& get_content_callback() const {
+ return get_content_callback_;
+ }
+
+ const ResourceEntry& entry() const { return *entry_; }
+
+ bool was_cancelled() const { return was_cancelled_; }
+
+ private:
+ void Cancel() {
+ was_cancelled_ = true;
+ if (!cancel_download_closure_.is_null())
+ cancel_download_closure_.Run();
+ }
+
+ const GetFileContentInitializedCallback initialized_callback_;
+ const google_apis::GetContentCallback get_content_callback_;
+ const GetFileCallback completion_callback_;
+
+ scoped_ptr<ResourceEntry> entry_;
+ base::Closure cancel_download_closure_;
+ bool was_cancelled_;
+
+ base::WeakPtrFactory<DownloadParams> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(DownloadParams);
+};
+
+DownloadOperation::DownloadOperation(
+ base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ scheduler_(scheduler),
+ metadata_(metadata),
+ cache_(cache),
+ temporary_file_directory_(temporary_file_directory),
+ weak_ptr_factory_(this) {
+}
+
+DownloadOperation::~DownloadOperation() {
+}
+
+base::Closure DownloadOperation::EnsureFileDownloadedByLocalId(
+ const std::string& local_id,
+ const ClientContext& context,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const GetFileCallback& completion_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!completion_callback.is_null());
+
+ CheckPreconditionForEnsureFileDownloadedParams params;
+ params.metadata = metadata_;
+ params.cache = cache_;
+ params.temporary_file_directory = temporary_file_directory_;
+ base::FilePath* drive_file_path = new base::FilePath;
+ base::FilePath* cache_file_path = new base::FilePath;
+ base::FilePath* temp_download_file_path = new base::FilePath;
+ ResourceEntry* entry = new ResourceEntry;
+ scoped_ptr<DownloadParams> download_params(new DownloadParams(
+ initialized_callback, get_content_callback, completion_callback,
+ make_scoped_ptr(entry)));
+ base::Closure cancel_closure = download_params->GetCancelClosure();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId,
+ params,
+ local_id,
+ drive_file_path,
+ cache_file_path,
+ temp_download_file_path,
+ entry),
+ base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&download_params),
+ context,
+ base::Owned(drive_file_path),
+ base::Owned(cache_file_path),
+ base::Owned(temp_download_file_path)));
+ return cancel_closure;
+}
+
+base::Closure DownloadOperation::EnsureFileDownloadedByPath(
+ const base::FilePath& file_path,
+ const ClientContext& context,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const GetFileCallback& completion_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!completion_callback.is_null());
+
+ CheckPreconditionForEnsureFileDownloadedParams params;
+ params.metadata = metadata_;
+ params.cache = cache_;
+ params.temporary_file_directory = temporary_file_directory_;
+ base::FilePath* drive_file_path = new base::FilePath(file_path);
+ base::FilePath* cache_file_path = new base::FilePath;
+ base::FilePath* temp_download_file_path = new base::FilePath;
+ ResourceEntry* entry = new ResourceEntry;
+ scoped_ptr<DownloadParams> download_params(new DownloadParams(
+ initialized_callback, get_content_callback, completion_callback,
+ make_scoped_ptr(entry)));
+ base::Closure cancel_closure = download_params->GetCancelClosure();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath,
+ params,
+ file_path,
+ cache_file_path,
+ temp_download_file_path,
+ entry),
+ base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&download_params),
+ context,
+ base::Owned(drive_file_path),
+ base::Owned(cache_file_path),
+ base::Owned(temp_download_file_path)));
+ return cancel_closure;
+}
+
+void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
+ scoped_ptr<DownloadParams> params,
+ const ClientContext& context,
+ base::FilePath* drive_file_path,
+ base::FilePath* cache_file_path,
+ base::FilePath* temp_download_file_path,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(params);
+ DCHECK(drive_file_path);
+ DCHECK(cache_file_path);
+
+ if (error != FILE_ERROR_OK) {
+ // During precondition check, an error is found.
+ params->OnError(error);
+ return;
+ }
+
+ if (!cache_file_path->empty()) {
+ // The cache file is found.
+ params->OnCacheFileFound(*cache_file_path);
+ return;
+ }
+
+ if (params->was_cancelled()) {
+ params->OnError(FILE_ERROR_ABORT);
+ return;
+ }
+
+ DCHECK(!params->entry().resource_id().empty());
+ DownloadParams* params_ptr = params.get();
+ JobID id = scheduler_->DownloadFile(
+ *drive_file_path,
+ params_ptr->entry().file_info().size(),
+ *temp_download_file_path,
+ params_ptr->entry().resource_id(),
+ context,
+ base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile,
+ weak_ptr_factory_.GetWeakPtr(),
+ *drive_file_path,
+ base::Passed(&params)),
+ params_ptr->get_content_callback());
+
+ // Notify via |initialized_callback| if necessary.
+ params_ptr->OnStartDownloading(
+ base::Bind(&DownloadOperation::CancelJob,
+ weak_ptr_factory_.GetWeakPtr(), id));
+}
+
+void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
+ const base::FilePath& drive_file_path,
+ scoped_ptr<DownloadParams> params,
+ google_apis::DriveApiErrorCode gdata_error,
+ const base::FilePath& downloaded_file_path) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DownloadParams* params_ptr = params.get();
+ ResourceEntry* entry_after_update = new ResourceEntry;
+ base::FilePath* cache_file_path = new base::FilePath;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateLocalStateForDownloadFile,
+ metadata_,
+ cache_,
+ params_ptr->entry(),
+ gdata_error,
+ downloaded_file_path,
+ entry_after_update,
+ cache_file_path),
+ base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState,
+ weak_ptr_factory_.GetWeakPtr(),
+ drive_file_path,
+ base::Passed(&params),
+ base::Passed(make_scoped_ptr(entry_after_update)),
+ base::Owned(cache_file_path)));
+}
+
+void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
+ const base::FilePath& file_path,
+ scoped_ptr<DownloadParams> params,
+ scoped_ptr<ResourceEntry> entry_after_update,
+ base::FilePath* cache_file_path,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (error != FILE_ERROR_OK) {
+ params->OnError(error);
+ return;
+ }
+ DCHECK(!entry_after_update->file_info().is_directory());
+
+ FileChange changed_files;
+ changed_files.Update(file_path, FileChange::FILE_TYPE_FILE,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+ // Storing to cache changes the "offline available" status, hence notify.
+ delegate_->OnFileChangedByOperation(changed_files);
+ params->OnDownloadCompleted(*cache_file_path, entry_after_update.Pass());
+}
+
+void DownloadOperation::CancelJob(JobID job_id) {
+ scheduler_->CancelJob(job_id);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/download_operation.h b/components/drive/file_system/download_operation.h
new file mode 100644
index 0000000..e45902b
--- /dev/null
+++ b/components/drive/file_system/download_operation.h
@@ -0,0 +1,133 @@
+// 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 COMPONENTS_DRIVE_FILE_SYSTEM_DOWNLOAD_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_DOWNLOAD_OPERATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system_interface.h"
+#include "components/drive/job_list.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace google_apis {
+class ResourceEntry;
+} // namespace google_apis
+
+namespace drive {
+
+class JobScheduler;
+class ResourceEntry;
+struct ClientContext;
+
+namespace internal {
+class FileCache;
+class ResourceMetadata;
+} // namespace internal
+
+namespace file_system {
+
+class OperationDelegate;
+
+class DownloadOperation {
+ public:
+ DownloadOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory);
+ ~DownloadOperation();
+
+ // Ensures that the file content specified by |local_id| is locally
+ // downloaded and returns a closure to cancel the task.
+ // For hosted documents, this method may create a JSON file representing the
+ // file.
+ // For regular files, if the locally cached file is found, returns it.
+ // If not found, start to download the file from the server.
+ // When a JSON file is created, the cache file is found or downloading is
+ // being started, |initialized_callback| is called with |local_file|
+ // for JSON file or the cache file, or with |cancel_download_closure| for
+ // downloading.
+ // During the downloading |get_content_callback| will be called periodically
+ // with the downloaded content.
+ // Upon completion or an error is found, |completion_callback| will be called.
+ // |initialized_callback| and |get_content_callback| can be null if not
+ // needed.
+ // |completion_callback| must not be null.
+ base::Closure EnsureFileDownloadedByLocalId(
+ const std::string& local_id,
+ const ClientContext& context,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const GetFileCallback& completion_callback);
+
+ // Does the same thing as EnsureFileDownloadedByLocalId for the file
+ // specified by |file_path|.
+ base::Closure EnsureFileDownloadedByPath(
+ const base::FilePath& file_path,
+ const ClientContext& context,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const GetFileCallback& completion_callback);
+
+ private:
+ // Parameters for EnsureFileDownloaded.
+ class DownloadParams;
+
+ // Part of EnsureFileDownloaded(). Called upon the completion of precondition
+ // check.
+ void EnsureFileDownloadedAfterCheckPreCondition(
+ scoped_ptr<DownloadParams> params,
+ const ClientContext& context,
+ base::FilePath* drive_file_path,
+ base::FilePath* cache_file_path,
+ base::FilePath* temp_download_file_path,
+ FileError error);
+
+ // Part of EnsureFileDownloaded(). Called after the actual downloading.
+ void EnsureFileDownloadedAfterDownloadFile(
+ const base::FilePath& drive_file_path,
+ scoped_ptr<DownloadParams> params,
+ google_apis::DriveApiErrorCode gdata_error,
+ const base::FilePath& downloaded_file_path);
+
+ // Part of EnsureFileDownloaded(). Called after updating local state is
+ // completed.
+ void EnsureFileDownloadedAfterUpdateLocalState(
+ const base::FilePath& file_path,
+ scoped_ptr<DownloadParams> params,
+ scoped_ptr<ResourceEntry> entry_after_update,
+ base::FilePath* cache_file_path,
+ FileError error);
+
+ // Cancels the job with |job_id| in the scheduler.
+ void CancelJob(JobID job_id);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ JobScheduler* scheduler_;
+ internal::ResourceMetadata* metadata_;
+ internal::FileCache* cache_;
+ const base::FilePath temporary_file_directory_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate its weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<DownloadOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(DownloadOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_DOWNLOAD_OPERATION_H_
diff --git a/components/drive/file_system/download_operation_unittest.cc b/components/drive/file_system/download_operation_unittest.cc
new file mode 100644
index 0000000..b4dcf53
--- /dev/null
+++ b/components/drive/file_system/download_operation_unittest.cc
@@ -0,0 +1,504 @@
+// 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 "components/drive/file_system/download_operation.h"
+
+#include "base/files/file_util.h"
+#include "base/task_runner_util.h"
+#include "components/drive/fake_free_disk_space_getter.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/service/fake_drive_service.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+class DownloadOperationTest : public OperationTestBase {
+ protected:
+ void SetUp() override {
+ OperationTestBase::SetUp();
+
+ operation_.reset(new DownloadOperation(
+ blocking_task_runner(), delegate(), scheduler(), metadata(), cache(),
+ temp_dir()));
+ }
+
+ scoped_ptr<DownloadOperation> operation_;
+};
+
+TEST_F(DownloadOperationTest,
+ EnsureFileDownloadedByPath_FromServer_EnoughSpace) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+ const int64 file_size = src_entry.file_info().size();
+
+ // Pretend we have enough space.
+ fake_free_disk_space_getter()->set_default_value(
+ file_size + drive::internal::kMinFreeSpaceInBytes);
+
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ operation_->EnsureFileDownloadedByPath(
+ file_in_root,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_FALSE(entry->file_specific_info().is_hosted_document());
+
+ // The transfered file is cached and the change of "offline available"
+ // attribute is notified.
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_EQ(1U, delegate()->get_changed_files().count(file_in_root));
+}
+
+TEST_F(DownloadOperationTest,
+ EnsureFileDownloadedByPath_FromServer_NoSpaceAtAll) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+
+ // Pretend we have no space at all.
+ fake_free_disk_space_getter()->set_default_value(0);
+
+ FileError error = FILE_ERROR_OK;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ operation_->EnsureFileDownloadedByPath(
+ file_in_root,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_NO_LOCAL_SPACE, error);
+}
+
+TEST_F(DownloadOperationTest,
+ EnsureFileDownloadedByPath_FromServer_NoEnoughSpaceButCanFreeUp) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+ const int64 file_size = src_entry.file_info().size();
+
+ // Make another file cached.
+ // This file's cache file will be removed to free up the disk space.
+ base::FilePath cached_file(
+ FILE_PATH_LITERAL("drive/root/Duplicate Name.txt"));
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ operation_->EnsureFileDownloadedByPath(
+ cached_file,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->file_specific_info().cache_state().is_present());
+
+ // Pretend we have no space first (checked before downloading a file),
+ // but then start reporting we have space. This is to emulate that
+ // the disk space was freed up by removing temporary files.
+ fake_free_disk_space_getter()->set_default_value(
+ file_size + drive::internal::kMinFreeSpaceInBytes);
+ fake_free_disk_space_getter()->PushFakeValue(0);
+
+ operation_->EnsureFileDownloadedByPath(
+ file_in_root,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_FALSE(entry->file_specific_info().is_hosted_document());
+
+ // The transfered file is cached and the change of "offline available"
+ // attribute is notified.
+ EXPECT_EQ(2U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(file_in_root));
+ EXPECT_TRUE(delegate()->get_changed_files().count(cached_file));
+
+ // The cache for the other file should be removed in order to free up space.
+ ResourceEntry cached_file_entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntry(cached_file, &cached_file_entry));
+ EXPECT_FALSE(
+ cached_file_entry.file_specific_info().cache_state().is_present());
+}
+
+TEST_F(DownloadOperationTest,
+ EnsureFileDownloadedByPath_FromServer_EnoughSpaceButBecomeFull) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+ const int64 file_size = src_entry.file_info().size();
+
+ // Pretend we have enough space first (checked before downloading a file),
+ // but then start reporting we have not enough space. This is to emulate that
+ // the disk space becomes full after the file is downloaded for some reason
+ // (ex. the actual file was larger than the expected size).
+ fake_free_disk_space_getter()->PushFakeValue(
+ file_size + drive::internal::kMinFreeSpaceInBytes);
+ fake_free_disk_space_getter()->set_default_value(
+ drive::internal::kMinFreeSpaceInBytes - 1);
+
+ FileError error = FILE_ERROR_OK;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ operation_->EnsureFileDownloadedByPath(
+ file_in_root,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_NO_LOCAL_SPACE, error);
+}
+
+TEST_F(DownloadOperationTest, EnsureFileDownloadedByPath_FromCache) {
+ base::FilePath temp_file;
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir(), &temp_file));
+
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+
+ // Store something as cached version of this file.
+ FileError error = FILE_ERROR_OK;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::Store,
+ base::Unretained(cache()),
+ GetLocalId(file_in_root),
+ src_entry.file_specific_info().md5(),
+ temp_file,
+ internal::FileCache::FILE_OPERATION_COPY),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ operation_->EnsureFileDownloadedByPath(
+ file_in_root,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_FALSE(entry->file_specific_info().is_hosted_document());
+}
+
+TEST_F(DownloadOperationTest, EnsureFileDownloadedByPath_HostedDocument) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL(
+ "drive/root/Document 1 excludeDir-test.gdoc"));
+
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ operation_->EnsureFileDownloadedByPath(
+ file_in_root,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->file_specific_info().is_hosted_document());
+ EXPECT_FALSE(file_path.empty());
+
+ EXPECT_EQ(GURL(entry->file_specific_info().alternate_url()),
+ util::ReadUrlFromGDocFile(file_path));
+ EXPECT_EQ(entry->resource_id(), util::ReadResourceIdFromGDocFile(file_path));
+ EXPECT_EQ(FILE_PATH_LITERAL(".gdoc"), file_path.Extension());
+}
+
+TEST_F(DownloadOperationTest, EnsureFileDownloadedByLocalId) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+
+ FileError error = FILE_ERROR_OK;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ operation_->EnsureFileDownloadedByLocalId(
+ GetLocalId(file_in_root),
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_FALSE(entry->file_specific_info().is_hosted_document());
+
+ // The transfered file is cached and the change of "offline available"
+ // attribute is notified.
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_EQ(1U, delegate()->get_changed_files().count(file_in_root));
+}
+
+TEST_F(DownloadOperationTest,
+ EnsureFileDownloadedByPath_WithGetContentCallback) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+
+ {
+ FileError initialized_error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry, entry_dontcare;
+ base::FilePath local_path, local_path_dontcare;
+ google_apis::test_util::TestGetContentCallback get_content_callback;
+ FileError completion_error = FILE_ERROR_FAILED;
+ base::Closure cancel_download = operation_->EnsureFileDownloadedByPath(
+ file_in_root,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(
+ &initialized_error, &local_path, &entry),
+ get_content_callback.callback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &completion_error, &local_path_dontcare, &entry_dontcare));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ // For the first time, file is downloaded from the remote server.
+ // In this case, |local_path| is empty.
+ EXPECT_EQ(FILE_ERROR_OK, initialized_error);
+ ASSERT_TRUE(entry);
+ ASSERT_TRUE(local_path.empty());
+ EXPECT_FALSE(cancel_download.is_null());
+ // Content is available through the second callback argument.
+ EXPECT_EQ(static_cast<size_t>(entry->file_info().size()),
+ get_content_callback.GetConcatenatedData().size());
+ EXPECT_EQ(FILE_ERROR_OK, completion_error);
+
+ // The transfered file is cached and the change of "offline available"
+ // attribute is notified.
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_EQ(1U, delegate()->get_changed_files().count(file_in_root));
+ }
+
+ {
+ FileError initialized_error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry, entry_dontcare;
+ base::FilePath local_path, local_path_dontcare;
+ google_apis::test_util::TestGetContentCallback get_content_callback;
+ FileError completion_error = FILE_ERROR_FAILED;
+ base::Closure cancel_download = operation_->EnsureFileDownloadedByPath(
+ file_in_root,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(
+ &initialized_error, &local_path, &entry),
+ get_content_callback.callback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &completion_error, &local_path_dontcare, &entry_dontcare));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ // Try second download. In this case, the file should be cached, so
+ // |local_path| should not be empty.
+ EXPECT_EQ(FILE_ERROR_OK, initialized_error);
+ ASSERT_TRUE(entry);
+ ASSERT_TRUE(!local_path.empty());
+ EXPECT_FALSE(cancel_download.is_null());
+ // The content is available from the cache file.
+ EXPECT_TRUE(get_content_callback.data().empty());
+ int64 local_file_size = 0;
+ base::GetFileSize(local_path, &local_file_size);
+ EXPECT_EQ(entry->file_info().size(), local_file_size);
+ EXPECT_EQ(FILE_ERROR_OK, completion_error);
+ }
+}
+
+TEST_F(DownloadOperationTest, EnsureFileDownloadedByLocalId_FromCache) {
+ base::FilePath temp_file;
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir(), &temp_file));
+
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+
+ // Store something as cached version of this file.
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::Store,
+ base::Unretained(cache()),
+ GetLocalId(file_in_root),
+ src_entry.file_specific_info().md5(),
+ temp_file,
+ internal::FileCache::FILE_OPERATION_COPY),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // The file is obtained from the cache.
+ // Hence the downloading should work even if the drive service is offline.
+ fake_service()->set_offline(true);
+
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ operation_->EnsureFileDownloadedByLocalId(
+ GetLocalId(file_in_root),
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_FALSE(entry->file_specific_info().is_hosted_document());
+}
+
+TEST_F(DownloadOperationTest, EnsureFileDownloadedByPath_DirtyCache) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+
+ // Prepare a dirty file to store to cache that has a different size than
+ // stored in resource metadata.
+ base::FilePath dirty_file = temp_dir().AppendASCII("dirty.txt");
+ size_t dirty_size = src_entry.file_info().size() + 10;
+ google_apis::test_util::WriteStringToFile(dirty_file,
+ std::string(dirty_size, 'x'));
+
+ // Store the file as a cache, marking it to be dirty.
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::Store,
+ base::Unretained(cache()),
+ GetLocalId(file_in_root),
+ std::string(),
+ dirty_file,
+ internal::FileCache::FILE_OPERATION_COPY),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Record values passed to GetFileContentInitializedCallback().
+ FileError init_error;
+ base::FilePath init_path;
+ scoped_ptr<ResourceEntry> init_entry;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ base::Closure cancel_callback = operation_->EnsureFileDownloadedByPath(
+ file_in_root,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(
+ &init_error, &init_path, &init_entry),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ // Check that the result of local modification is propagated.
+ EXPECT_EQ(static_cast<int64>(dirty_size), init_entry->file_info().size());
+ EXPECT_EQ(static_cast<int64>(dirty_size), entry->file_info().size());
+}
+
+TEST_F(DownloadOperationTest, EnsureFileDownloadedByPath_LocallyCreatedFile) {
+ // Add a new file with an empty resource ID.
+ base::FilePath file_path(FILE_PATH_LITERAL("drive/root/New File.txt"));
+ ResourceEntry parent;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_path.DirName(), &parent));
+
+ ResourceEntry new_file;
+ new_file.set_title("New File.txt");
+ new_file.set_parent_local_id(parent.local_id());
+
+ FileError error = FILE_ERROR_FAILED;
+ std::string local_id;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::AddEntry,
+ base::Unretained(metadata()),
+ new_file,
+ &local_id),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Empty cache file should be returned.
+ base::FilePath cache_file_path;
+ scoped_ptr<ResourceEntry> entry;
+ operation_->EnsureFileDownloadedByPath(
+ file_path,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &cache_file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ int64 cache_file_size = 0;
+ EXPECT_TRUE(base::GetFileSize(cache_file_path, &cache_file_size));
+ EXPECT_EQ(static_cast<int64>(0), cache_file_size);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(cache_file_size, entry->file_info().size());
+}
+
+TEST_F(DownloadOperationTest, CancelBeforeDownloadStarts) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+
+ // Start operation.
+ FileError error = FILE_ERROR_OK;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ base::Closure cancel_closure = operation_->EnsureFileDownloadedByLocalId(
+ GetLocalId(file_in_root),
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+
+ // Cancel immediately.
+ ASSERT_FALSE(cancel_closure.is_null());
+ cancel_closure.Run();
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_ABORT, error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/get_file_for_saving_operation.cc b/components/drive/file_system/get_file_for_saving_operation.cc
new file mode 100644
index 0000000..30aefd6
--- /dev/null
+++ b/components/drive/file_system/get_file_for_saving_operation.cc
@@ -0,0 +1,206 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system/get_file_for_saving_operation.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "components/drive/event_logger.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_system/create_file_operation.h"
+#include "components/drive/file_system/download_operation.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/file_write_watcher.h"
+#include "components/drive/job_scheduler.h"
+
+namespace drive {
+namespace file_system {
+
+namespace {
+
+FileError OpenCacheFileForWrite(
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const std::string& local_id,
+ scoped_ptr<base::ScopedClosureRunner>* file_closer,
+ ResourceEntry* entry) {
+ FileError error = cache->OpenForWrite(local_id, file_closer);
+ if (error != FILE_ERROR_OK)
+ return error;
+ return metadata->GetResourceEntryById(local_id, entry);
+}
+
+} // namespace
+
+GetFileForSavingOperation::GetFileForSavingOperation(
+ EventLogger* logger,
+ base::SequencedTaskRunner* blocking_task_runner,
+ base::SingleThreadTaskRunner* file_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory)
+ : logger_(logger),
+ create_file_operation_(
+ new CreateFileOperation(blocking_task_runner, delegate, metadata)),
+ download_operation_(new DownloadOperation(blocking_task_runner,
+ delegate,
+ scheduler,
+ metadata,
+ cache,
+ temporary_file_directory)),
+ file_write_watcher_(new internal::FileWriteWatcher(file_task_runner)),
+ blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ metadata_(metadata),
+ cache_(cache),
+ weak_ptr_factory_(this) {
+}
+
+GetFileForSavingOperation::~GetFileForSavingOperation() {
+}
+
+void GetFileForSavingOperation::GetFileForSaving(
+ const base::FilePath& file_path,
+ const GetFileCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ create_file_operation_->CreateFile(
+ file_path,
+ false, // error_if_already_exists
+ std::string(), // no specific mime type
+ base::Bind(&GetFileForSavingOperation::GetFileForSavingAfterCreate,
+ weak_ptr_factory_.GetWeakPtr(),
+ file_path,
+ callback));
+}
+
+void GetFileForSavingOperation::GetFileForSavingAfterCreate(
+ const base::FilePath& file_path,
+ const GetFileCallback& callback,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, base::FilePath(), scoped_ptr<ResourceEntry>());
+ return;
+ }
+
+ download_operation_->EnsureFileDownloadedByPath(
+ file_path,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ base::Bind(&GetFileForSavingOperation::GetFileForSavingAfterDownload,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback));
+}
+
+void GetFileForSavingOperation::GetFileForSavingAfterDownload(
+ const GetFileCallback& callback,
+ FileError error,
+ const base::FilePath& cache_path,
+ scoped_ptr<ResourceEntry> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, base::FilePath(), scoped_ptr<ResourceEntry>());
+ return;
+ }
+
+ const std::string& local_id = entry->local_id();
+ ResourceEntry* entry_ptr = entry.get();
+ scoped_ptr<base::ScopedClosureRunner>* file_closer =
+ new scoped_ptr<base::ScopedClosureRunner>;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&OpenCacheFileForWrite,
+ metadata_,
+ cache_,
+ local_id,
+ file_closer,
+ entry_ptr),
+ base::Bind(&GetFileForSavingOperation::GetFileForSavingAfterOpenForWrite,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ cache_path,
+ base::Passed(&entry),
+ base::Owned(file_closer)));
+}
+
+void GetFileForSavingOperation::GetFileForSavingAfterOpenForWrite(
+ const GetFileCallback& callback,
+ const base::FilePath& cache_path,
+ scoped_ptr<ResourceEntry> entry,
+ scoped_ptr<base::ScopedClosureRunner>* file_closer,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, base::FilePath(), scoped_ptr<ResourceEntry>());
+ return;
+ }
+
+ const std::string& local_id = entry->local_id();
+ file_write_watcher_->StartWatch(
+ cache_path,
+ base::Bind(&GetFileForSavingOperation::GetFileForSavingAfterWatch,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ cache_path,
+ base::Passed(&entry)),
+ base::Bind(&GetFileForSavingOperation::OnWriteEvent,
+ weak_ptr_factory_.GetWeakPtr(),
+ local_id,
+ base::Passed(file_closer)));
+}
+
+void GetFileForSavingOperation::GetFileForSavingAfterWatch(
+ const GetFileCallback& callback,
+ const base::FilePath& cache_path,
+ scoped_ptr<ResourceEntry> entry,
+ bool success) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ logger_->Log(logging::LOG_INFO, "Started watching modification to %s [%s].",
+ entry->local_id().c_str(),
+ success ? "ok" : "fail");
+
+ if (!success) {
+ callback.Run(FILE_ERROR_FAILED,
+ base::FilePath(), scoped_ptr<ResourceEntry>());
+ return;
+ }
+
+ callback.Run(FILE_ERROR_OK, cache_path, entry.Pass());
+}
+
+void GetFileForSavingOperation::OnWriteEvent(
+ const std::string& local_id,
+ scoped_ptr<base::ScopedClosureRunner> file_closer) {
+ logger_->Log(logging::LOG_INFO, "Detected modification to %s.",
+ local_id.c_str());
+
+ delegate_->OnEntryUpdatedByOperation(ClientContext(USER_INITIATED), local_id);
+
+ // Clients may have enlarged the file. By FreeDiskpSpaceIfNeededFor(0),
+ // we try to ensure (0 + the-minimum-safe-margin = 512MB as of now) space.
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(
+ base::Bind(&internal::FileCache::FreeDiskSpaceIfNeededFor,
+ base::Unretained(cache_),
+ 0))));
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/get_file_for_saving_operation.h b/components/drive/file_system/get_file_for_saving_operation.h
new file mode 100644
index 0000000..9b2fc7c
--- /dev/null
+++ b/components/drive/file_system/get_file_for_saving_operation.h
@@ -0,0 +1,109 @@
+// 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 COMPONENTS_DRIVE_FILE_SYSTEM_GET_FILE_FOR_SAVING_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_GET_FILE_FOR_SAVING_OPERATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system_interface.h"
+
+namespace base {
+class FilePath;
+class ScopedClosureRunner;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace drive {
+namespace internal {
+class FileCache;
+class FileWriteWatcher;
+class ResourceMetadata;
+} // namespace internal
+
+class EventLogger;
+class JobScheduler;
+class ResourceEntry;
+
+namespace file_system {
+
+class CreateFileOperation;
+class DownloadOperation;
+class OperationDelegate;
+
+// Implements GetFileForSaving() operation that prepares a local cache for
+// a Drive file whose next modification is monitored and notified to the
+// OperationDelegate.
+// TODO(kinaba): crbug.com/269424: we might want to monitor all the changes
+// to the cache directory, not just the one immediately after the save dialog.
+class GetFileForSavingOperation {
+ public:
+ GetFileForSavingOperation(EventLogger* logger,
+ base::SequencedTaskRunner* blocking_task_runner,
+ base::SingleThreadTaskRunner* file_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory);
+ ~GetFileForSavingOperation();
+
+ // Makes sure that |file_path| in the file system is available in the local
+ // cache, and marks it as dirty. The next modification to the cache file is
+ // watched and is automatically notified to the delegate. If the entry is not
+ // present in the file system, it is created.
+ void GetFileForSaving(const base::FilePath& file_path,
+ const GetFileCallback& callback);
+
+ internal::FileWriteWatcher* file_write_watcher_for_testing() {
+ return file_write_watcher_.get();
+ }
+
+ private:
+ void GetFileForSavingAfterCreate(const base::FilePath& file_path,
+ const GetFileCallback& callback,
+ FileError error);
+ void GetFileForSavingAfterDownload(const GetFileCallback& callback,
+ FileError error,
+ const base::FilePath& cache_path,
+ scoped_ptr<ResourceEntry> entry);
+ void GetFileForSavingAfterOpenForWrite(
+ const GetFileCallback& callback,
+ const base::FilePath& cache_path,
+ scoped_ptr<ResourceEntry> entry,
+ scoped_ptr<base::ScopedClosureRunner>* file_closer,
+ FileError error);
+ void GetFileForSavingAfterWatch(const GetFileCallback& callback,
+ const base::FilePath& cache_path,
+ scoped_ptr<ResourceEntry> entry,
+ bool success);
+ // Called when the cache file for |local_id| is written.
+ void OnWriteEvent(const std::string& local_id,
+ scoped_ptr<base::ScopedClosureRunner> file_closer);
+
+ EventLogger* logger_;
+ scoped_ptr<CreateFileOperation> create_file_operation_;
+ scoped_ptr<DownloadOperation> download_operation_;
+ scoped_ptr<internal::FileWriteWatcher> file_write_watcher_;
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ internal::ResourceMetadata* metadata_;
+ internal::FileCache* cache_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<GetFileForSavingOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(GetFileForSavingOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_GET_FILE_FOR_SAVING_OPERATION_H_
diff --git a/components/drive/file_system/get_file_for_saving_operation_unittest.cc b/components/drive/file_system/get_file_for_saving_operation_unittest.cc
new file mode 100644
index 0000000..984d0b6
--- /dev/null
+++ b/components/drive/file_system/get_file_for_saving_operation_unittest.cc
@@ -0,0 +1,158 @@
+// 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 "components/drive/file_system/get_file_for_saving_operation.h"
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/run_loop.h"
+#include "base/task_runner_util.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "components/drive/file_write_watcher.h"
+#include "components/drive/job_scheduler.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+namespace {
+
+// If OnCacheFileUploadNeededByOperation is called, records the local ID and
+// calls |quit_closure|.
+class TestDelegate : public OperationDelegate {
+ public:
+ void set_quit_closure(const base::Closure& quit_closure) {
+ quit_closure_ = quit_closure;
+ }
+
+ const std::string& updated_local_id() const {
+ return updated_local_id_;
+ }
+
+ // OperationDelegate overrides.
+ void OnEntryUpdatedByOperation(const ClientContext& /* context */,
+ const std::string& local_id) override {
+ updated_local_id_ = local_id;
+ if (!quit_closure_.is_null())
+ quit_closure_.Run();
+ }
+
+ private:
+ std::string updated_local_id_;
+ base::Closure quit_closure_;
+};
+
+} // namespace
+
+class GetFileForSavingOperationTest : public OperationTestBase {
+ protected:
+ // FileWriteWatcher requires TYPE_IO message loop to run.
+ GetFileForSavingOperationTest()
+ : OperationTestBase(content::TestBrowserThreadBundle::IO_MAINLOOP) {
+ }
+
+ void SetUp() override {
+ OperationTestBase::SetUp();
+
+ file_task_runner_ = content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::FILE);
+
+ operation_.reset(new GetFileForSavingOperation(
+ logger(), blocking_task_runner(), file_task_runner_.get(), &delegate_,
+ scheduler(), metadata(), cache(), temp_dir()));
+ operation_->file_write_watcher_for_testing()->DisableDelayForTesting();
+ }
+
+ TestDelegate delegate_;
+ scoped_ptr<GetFileForSavingOperation> operation_;
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
+};
+
+TEST_F(GetFileForSavingOperationTest, GetFileForSaving_Exist) {
+ base::FilePath drive_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(drive_path, &src_entry));
+
+ // Run the operation.
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry;
+ base::FilePath local_path;
+ operation_->GetFileForSaving(
+ drive_path,
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &local_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ // Checks that the file is retrieved.
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(src_entry.resource_id(), entry->resource_id());
+
+ // Checks that it presents in cache and marked dirty.
+ EXPECT_TRUE(entry->file_specific_info().cache_state().is_present());
+ EXPECT_TRUE(entry->file_specific_info().cache_state().is_dirty());
+
+ // Write something to the cache and checks that the event is reported.
+ {
+ base::RunLoop run_loop;
+ delegate_.set_quit_closure(run_loop.QuitClosure());
+ google_apis::test_util::WriteStringToFile(local_path, "hello");
+ run_loop.Run();
+ EXPECT_EQ(GetLocalId(drive_path), delegate_.updated_local_id());
+ }
+}
+
+TEST_F(GetFileForSavingOperationTest, GetFileForSaving_NotExist) {
+ base::FilePath drive_path(FILE_PATH_LITERAL("drive/root/NotExist.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(drive_path, &src_entry));
+
+ // Run the operation.
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry;
+ base::FilePath local_path;
+ operation_->GetFileForSaving(
+ drive_path,
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &local_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ // Checks that the file is created and retrieved.
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(drive_path, &src_entry));
+ int64 size = -1;
+ EXPECT_TRUE(base::GetFileSize(local_path, &size));
+ EXPECT_EQ(0, size);
+}
+
+TEST_F(GetFileForSavingOperationTest, GetFileForSaving_Directory) {
+ base::FilePath drive_path(FILE_PATH_LITERAL("drive/root/Directory 1"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(drive_path, &src_entry));
+ ASSERT_TRUE(src_entry.file_info().is_directory());
+
+ // Run the operation.
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry;
+ base::FilePath local_path;
+ operation_->GetFileForSaving(
+ drive_path,
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &local_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ // Checks that an error is returned.
+ EXPECT_EQ(FILE_ERROR_EXISTS, error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/move_operation.cc b/components/drive/file_system/move_operation.cc
new file mode 100644
index 0000000..ff1edd6
--- /dev/null
+++ b/components/drive/file_system/move_operation.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system/move_operation.h"
+
+#include "base/sequenced_task_runner.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+
+namespace drive {
+namespace file_system {
+namespace {
+
+// Looks up ResourceEntry for source entry and the destination directory.
+FileError UpdateLocalState(internal::ResourceMetadata* metadata,
+ const base::FilePath& src_path,
+ const base::FilePath& dest_path,
+ FileChange* changed_files,
+ std::string* local_id) {
+ ResourceEntry entry;
+ FileError error = metadata->GetResourceEntryByPath(src_path, &entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ *local_id = entry.local_id();
+
+ ResourceEntry parent_entry;
+ error = metadata->GetResourceEntryByPath(dest_path.DirName(), &parent_entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ // The parent must be a directory.
+ if (!parent_entry.file_info().is_directory())
+ return FILE_ERROR_NOT_A_DIRECTORY;
+
+ changed_files->Update(src_path, entry, FileChange::CHANGE_TYPE_DELETE);
+
+ // Strip the extension for a hosted document if necessary.
+ const std::string new_extension =
+ base::FilePath(dest_path.Extension()).AsUTF8Unsafe();
+ const bool has_hosted_document_extension =
+ entry.has_file_specific_info() &&
+ entry.file_specific_info().is_hosted_document() &&
+ new_extension == entry.file_specific_info().document_extension();
+ const std::string new_title =
+ has_hosted_document_extension ?
+ dest_path.BaseName().RemoveExtension().AsUTF8Unsafe() :
+ dest_path.BaseName().AsUTF8Unsafe();
+
+ entry.set_title(new_title);
+ entry.set_parent_local_id(parent_entry.local_id());
+ entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+ entry.set_modification_date(base::Time::Now().ToInternalValue());
+ error = metadata->RefreshEntry(entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ changed_files->Update(dest_path, entry,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+ return FILE_ERROR_OK;
+}
+
+} // namespace
+
+MoveOperation::MoveOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ metadata_(metadata),
+ weak_ptr_factory_(this) {
+}
+
+MoveOperation::~MoveOperation() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void MoveOperation::Move(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileChange* changed_files = new FileChange;
+ std::string* local_id = new std::string;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateLocalState,
+ metadata_,
+ src_file_path,
+ dest_file_path,
+ changed_files,
+ local_id),
+ base::Bind(&MoveOperation::MoveAfterUpdateLocalState,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ base::Owned(changed_files),
+ base::Owned(local_id)));
+}
+
+void MoveOperation::MoveAfterUpdateLocalState(
+ const FileOperationCallback& callback,
+ const FileChange* changed_files,
+ const std::string* local_id,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (error == FILE_ERROR_OK) {
+ // Notify the change of directory.
+ delegate_->OnFileChangedByOperation(*changed_files);
+ delegate_->OnEntryUpdatedByOperation(ClientContext(USER_INITIATED),
+ *local_id);
+ }
+ callback.Run(error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/move_operation.h b/components/drive/file_system/move_operation.h
new file mode 100644
index 0000000..db6cee8
--- /dev/null
+++ b/components/drive/file_system/move_operation.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_FILE_SYSTEM_MOVE_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_MOVE_OPERATION_H_
+
+#include <set>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace google_apis {
+class ResourceEntry;
+} // namespace google_apis
+
+namespace drive {
+
+class FileChange;
+class ResourceEntry;
+
+namespace internal {
+class ResourceMetadata;
+} // namespace internal
+
+namespace file_system {
+
+class OperationDelegate;
+
+// This class encapsulates the drive Move function. It is responsible for
+// updating the local metadata entry.
+class MoveOperation {
+ public:
+ MoveOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata);
+ ~MoveOperation();
+
+ // Performs the move operation on the file at drive path |src_file_path|
+ // with a target of |dest_file_path|.
+ // Invokes |callback| when finished with the result of the operation.
+ // |callback| must not be null.
+ void Move(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ const FileOperationCallback& callback);
+
+ private:
+ // Part of Move(). Called after updating the local state.
+ void MoveAfterUpdateLocalState(const FileOperationCallback& callback,
+ const FileChange* changed_file,
+ const std::string* local_id,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ internal::ResourceMetadata* metadata_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<MoveOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(MoveOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_MOVE_OPERATION_H_
diff --git a/components/drive/file_system/move_operation_unittest.cc b/components/drive/file_system/move_operation_unittest.cc
new file mode 100644
index 0000000..4f097b3
--- /dev/null
+++ b/components/drive/file_system/move_operation_unittest.cc
@@ -0,0 +1,141 @@
+// 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 "components/drive/file_system/move_operation.h"
+
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+class MoveOperationTest : public OperationTestBase {
+ protected:
+ void SetUp() override {
+ OperationTestBase::SetUp();
+ operation_.reset(new MoveOperation(blocking_task_runner(),
+ delegate(),
+ metadata()));
+ }
+
+ scoped_ptr<MoveOperation> operation_;
+};
+
+TEST_F(MoveOperationTest, MoveFileInSameDirectory) {
+ const base::FilePath src_path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
+ const base::FilePath dest_path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/Test.log"));
+
+ ResourceEntry src_entry, dest_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &src_entry));
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(dest_path, &dest_entry));
+
+ FileError error = FILE_ERROR_FAILED;
+ operation_->Move(src_path,
+ dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path, &dest_entry));
+ EXPECT_EQ(src_entry.local_id(), dest_entry.local_id());
+ EXPECT_EQ(ResourceEntry::DIRTY, dest_entry.metadata_edit_state());
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(src_path, &src_entry));
+
+ EXPECT_EQ(2U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(src_path));
+ EXPECT_TRUE(delegate()->get_changed_files().count(dest_path));
+
+ EXPECT_EQ(1U, delegate()->updated_local_ids().size());
+ EXPECT_TRUE(delegate()->updated_local_ids().count(src_entry.local_id()));
+}
+
+TEST_F(MoveOperationTest, MoveFileFromRootToSubDirectory) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/Test.log"));
+
+ ResourceEntry src_entry, dest_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &src_entry));
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(dest_path, &dest_entry));
+
+ FileError error = FILE_ERROR_FAILED;
+ operation_->Move(src_path,
+ dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path, &dest_entry));
+ EXPECT_EQ(src_entry.local_id(), dest_entry.local_id());
+ EXPECT_EQ(ResourceEntry::DIRTY, dest_entry.metadata_edit_state());
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(src_path, &src_entry));
+
+ EXPECT_EQ(2U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(src_path));
+ EXPECT_TRUE(delegate()->get_changed_files().count(dest_path));
+
+ EXPECT_EQ(1U, delegate()->updated_local_ids().size());
+ EXPECT_TRUE(delegate()->updated_local_ids().count(src_entry.local_id()));
+}
+
+TEST_F(MoveOperationTest, MoveNotExistingFile) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/Dummy file.txt"));
+ base::FilePath dest_path(FILE_PATH_LITERAL("drive/root/Test.log"));
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Move(src_path,
+ dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(src_path, &entry));
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(dest_path, &entry));
+}
+
+TEST_F(MoveOperationTest, MoveFileToNonExistingDirectory) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_path(FILE_PATH_LITERAL("drive/root/Dummy/Test.log"));
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Move(src_path,
+ dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &entry));
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(dest_path, &entry));
+}
+
+// Test the case where the parent of |dest_file_path| is a existing file,
+// not a directory.
+TEST_F(MoveOperationTest, MoveFileToInvalidPath) {
+ base::FilePath src_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_path(
+ FILE_PATH_LITERAL("drive/root/Duplicate Name.txt/Test.log"));
+
+ FileError error = FILE_ERROR_OK;
+ operation_->Move(src_path,
+ dest_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_A_DIRECTORY, error);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &entry));
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(dest_path, &entry));
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/open_file_operation.cc b/components/drive/file_system/open_file_operation.cc
new file mode 100644
index 0000000..54da778
--- /dev/null
+++ b/components/drive/file_system/open_file_operation.cc
@@ -0,0 +1,185 @@
+// 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 "components/drive/file_system/open_file_operation.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/task_runner_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/create_file_operation.h"
+#include "components/drive/file_system/download_operation.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/job_scheduler.h"
+
+namespace drive {
+namespace file_system {
+
+OpenFileOperation::OpenFileOperation(
+ base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ cache_(cache),
+ create_file_operation_(new CreateFileOperation(
+ blocking_task_runner, delegate, metadata)),
+ download_operation_(new DownloadOperation(
+ blocking_task_runner, delegate, scheduler,
+ metadata, cache, temporary_file_directory)),
+ weak_ptr_factory_(this) {
+}
+
+OpenFileOperation::~OpenFileOperation() {
+}
+
+void OpenFileOperation::OpenFile(const base::FilePath& file_path,
+ OpenMode open_mode,
+ const std::string& mime_type,
+ const OpenFileCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ switch (open_mode) {
+ case OPEN_FILE:
+ // It is not necessary to create a new file even if not exists.
+ // So call OpenFileAfterCreateFile directly with FILE_ERROR_OK
+ // to skip file creation.
+ OpenFileAfterCreateFile(file_path, callback, FILE_ERROR_OK);
+ break;
+ case CREATE_FILE:
+ create_file_operation_->CreateFile(
+ file_path,
+ true, // exclusive: fail if already exists
+ mime_type,
+ base::Bind(&OpenFileOperation::OpenFileAfterCreateFile,
+ weak_ptr_factory_.GetWeakPtr(), file_path, callback));
+ break;
+ case OPEN_OR_CREATE_FILE:
+ create_file_operation_->CreateFile(
+ file_path,
+ false, // not-exclusive
+ mime_type,
+ base::Bind(&OpenFileOperation::OpenFileAfterCreateFile,
+ weak_ptr_factory_.GetWeakPtr(), file_path, callback));
+ break;
+ }
+}
+
+void OpenFileOperation::OpenFileAfterCreateFile(
+ const base::FilePath& file_path,
+ const OpenFileCallback& callback,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, base::FilePath(), base::Closure());
+ return;
+ }
+
+ download_operation_->EnsureFileDownloadedByPath(
+ file_path,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ base::Bind(
+ &OpenFileOperation::OpenFileAfterFileDownloaded,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void OpenFileOperation::OpenFileAfterFileDownloaded(
+ const OpenFileCallback& callback,
+ FileError error,
+ const base::FilePath& local_file_path,
+ scoped_ptr<ResourceEntry> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error == FILE_ERROR_OK) {
+ DCHECK(entry);
+ DCHECK(entry->has_file_specific_info());
+ if (entry->file_specific_info().is_hosted_document())
+ // No support for opening a hosted document.
+ error = FILE_ERROR_INVALID_OPERATION;
+ }
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, base::FilePath(), base::Closure());
+ return;
+ }
+
+ scoped_ptr<base::ScopedClosureRunner>* file_closer =
+ new scoped_ptr<base::ScopedClosureRunner>;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::OpenForWrite,
+ base::Unretained(cache_),
+ entry->local_id(),
+ file_closer),
+ base::Bind(&OpenFileOperation::OpenFileAfterOpenForWrite,
+ weak_ptr_factory_.GetWeakPtr(),
+ local_file_path,
+ entry->local_id(),
+ callback,
+ base::Owned(file_closer)));
+}
+
+void OpenFileOperation::OpenFileAfterOpenForWrite(
+ const base::FilePath& local_file_path,
+ const std::string& local_id,
+ const OpenFileCallback& callback,
+ scoped_ptr<base::ScopedClosureRunner>* file_closer,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, base::FilePath(), base::Closure());
+ return;
+ }
+
+ ++open_files_[local_id];
+ callback.Run(error, local_file_path,
+ base::Bind(&OpenFileOperation::CloseFile,
+ weak_ptr_factory_.GetWeakPtr(),
+ local_id,
+ base::Passed(file_closer)));
+}
+
+void OpenFileOperation::CloseFile(
+ const std::string& local_id,
+ scoped_ptr<base::ScopedClosureRunner> file_closer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_GT(open_files_[local_id], 0);
+
+ if (--open_files_[local_id] == 0) {
+ // All clients closes this file, so notify to upload the file.
+ open_files_.erase(local_id);
+ delegate_->OnEntryUpdatedByOperation(ClientContext(USER_INITIATED),
+ local_id);
+
+ // Clients may have enlarged the file. By FreeDiskpSpaceIfNeededFor(0),
+ // we try to ensure (0 + the-minimum-safe-margin = 512MB as of now) space.
+ blocking_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(base::IgnoreResult(
+ base::Bind(&internal::FileCache::FreeDiskSpaceIfNeededFor,
+ base::Unretained(cache_),
+ 0))));
+ }
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/open_file_operation.h b/components/drive/file_system/open_file_operation.h
new file mode 100644
index 0000000..4f0f554
--- /dev/null
+++ b/components/drive/file_system/open_file_operation.h
@@ -0,0 +1,109 @@
+// 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 COMPONENTS_DRIVE_FILE_SYSTEM_OPEN_FILE_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_OPEN_FILE_OPERATION_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system_interface.h"
+
+namespace base {
+class FilePath;
+class ScopedClosureRunner;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace drive {
+
+class JobScheduler;
+class ResourceEntry;
+
+namespace internal {
+class ResourceMetadata;
+class FileCache;
+} // namespace internal
+
+namespace file_system {
+
+class CreateFileOperation;
+class DownloadOperation;
+class OperationDelegate;
+
+class OpenFileOperation {
+ public:
+ OpenFileOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory);
+ ~OpenFileOperation();
+
+ // Opens the file at |file_path|.
+ // If the file is not actually downloaded, this method starts
+ // to download it to the cache, and then runs |callback| upon the
+ // completion with the path to the local cache file.
+ // See also the definition of OpenMode for its meaning.
+ // If |mime_type| is non empty and the file is created by this OpenFile()
+ // call, the mime type is used as the file's property.
+ // |callback| must not be null.
+ void OpenFile(const base::FilePath& file_path,
+ OpenMode open_mode,
+ const std::string& mime_type,
+ const OpenFileCallback& callback);
+
+ private:
+ // Part of OpenFile(). Called after file creation is completed.
+ void OpenFileAfterCreateFile(const base::FilePath& file_path,
+ const OpenFileCallback& callback,
+ FileError error);
+
+ // Part of OpenFile(). Called after file downloading is completed.
+ void OpenFileAfterFileDownloaded(const OpenFileCallback& callback,
+ FileError error,
+ const base::FilePath& local_file_path,
+ scoped_ptr<ResourceEntry> entry);
+
+ // Part of OpenFile(). Called after opening the cache file.
+ void OpenFileAfterOpenForWrite(
+ const base::FilePath& local_file_path,
+ const std::string& local_id,
+ const OpenFileCallback& callback,
+ scoped_ptr<base::ScopedClosureRunner>* file_closer,
+ FileError error);
+
+ // Closes the file with |local_id|.
+ void CloseFile(const std::string& local_id,
+ scoped_ptr<base::ScopedClosureRunner> file_closer);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ internal::FileCache* cache_;
+
+ scoped_ptr<CreateFileOperation> create_file_operation_;
+ scoped_ptr<DownloadOperation> download_operation_;
+
+ // The map from local id for an opened file to the number how many times
+ // the file is opened.
+ std::map<std::string, int> open_files_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate its weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<OpenFileOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(OpenFileOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_OPEN_FILE_OPERATION_H_
diff --git a/components/drive/file_system/open_file_operation_unittest.cc b/components/drive/file_system/open_file_operation_unittest.cc
new file mode 100644
index 0000000..2e6f674
--- /dev/null
+++ b/components/drive/file_system/open_file_operation_unittest.cc
@@ -0,0 +1,257 @@
+// 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 "components/drive/file_system/open_file_operation.h"
+
+#include <map>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/task_runner_util.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+class OpenFileOperationTest : public OperationTestBase {
+ protected:
+ void SetUp() override {
+ OperationTestBase::SetUp();
+
+ operation_.reset(new OpenFileOperation(
+ blocking_task_runner(), delegate(), scheduler(), metadata(), cache(),
+ temp_dir()));
+ }
+
+ scoped_ptr<OpenFileOperation> operation_;
+};
+
+TEST_F(OpenFileOperationTest, OpenExistingFile) {
+ const base::FilePath file_in_root(
+ FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+ const int64 file_size = src_entry.file_info().size();
+
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ base::Closure close_callback;
+ operation_->OpenFile(
+ file_in_root,
+ OPEN_FILE,
+ std::string(), // mime_type
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &close_callback));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(base::PathExists(file_path));
+ int64 local_file_size;
+ ASSERT_TRUE(base::GetFileSize(file_path, &local_file_size));
+ EXPECT_EQ(file_size, local_file_size);
+
+ ASSERT_FALSE(close_callback.is_null());
+ close_callback.Run();
+ EXPECT_EQ(1U, delegate()->updated_local_ids().count(src_entry.local_id()));
+}
+
+TEST_F(OpenFileOperationTest, OpenNonExistingFile) {
+ const base::FilePath file_in_root(
+ FILE_PATH_LITERAL("drive/root/not-exist.txt"));
+
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ base::Closure close_callback;
+ operation_->OpenFile(
+ file_in_root,
+ OPEN_FILE,
+ std::string(), // mime_type
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &close_callback));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
+ EXPECT_TRUE(close_callback.is_null());
+}
+
+TEST_F(OpenFileOperationTest, CreateExistingFile) {
+ const base::FilePath file_in_root(
+ FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ base::Closure close_callback;
+ operation_->OpenFile(
+ file_in_root,
+ CREATE_FILE,
+ std::string(), // mime_type
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &close_callback));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_EXISTS, error);
+ EXPECT_TRUE(close_callback.is_null());
+}
+
+TEST_F(OpenFileOperationTest, CreateNonExistingFile) {
+ const base::FilePath file_in_root(
+ FILE_PATH_LITERAL("drive/root/not-exist.txt"));
+
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ base::Closure close_callback;
+ operation_->OpenFile(
+ file_in_root,
+ CREATE_FILE,
+ std::string(), // mime_type
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &close_callback));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(file_in_root));
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(base::PathExists(file_path));
+ int64 local_file_size;
+ ASSERT_TRUE(base::GetFileSize(file_path, &local_file_size));
+ EXPECT_EQ(0, local_file_size); // Should be an empty file.
+
+ ASSERT_FALSE(close_callback.is_null());
+ close_callback.Run();
+ EXPECT_EQ(1U,
+ delegate()->updated_local_ids().count(GetLocalId(file_in_root)));
+}
+
+TEST_F(OpenFileOperationTest, OpenOrCreateExistingFile) {
+ const base::FilePath file_in_root(
+ FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+ const int64 file_size = src_entry.file_info().size();
+
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ base::Closure close_callback;
+ operation_->OpenFile(
+ file_in_root,
+ OPEN_OR_CREATE_FILE,
+ std::string(), // mime_type
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &close_callback));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ // Notified because 'available offline' status of the existing file changes.
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(file_in_root));
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(base::PathExists(file_path));
+ int64 local_file_size;
+ ASSERT_TRUE(base::GetFileSize(file_path, &local_file_size));
+ EXPECT_EQ(file_size, local_file_size);
+
+ ASSERT_FALSE(close_callback.is_null());
+ close_callback.Run();
+ EXPECT_EQ(1U, delegate()->updated_local_ids().count(src_entry.local_id()));
+
+ ResourceEntry result_entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &result_entry));
+ EXPECT_TRUE(result_entry.file_specific_info().cache_state().is_present());
+ EXPECT_TRUE(result_entry.file_specific_info().cache_state().is_dirty());
+}
+
+TEST_F(OpenFileOperationTest, OpenOrCreateNonExistingFile) {
+ const base::FilePath file_in_root(
+ FILE_PATH_LITERAL("drive/root/not-exist.txt"));
+
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ base::Closure close_callback;
+ operation_->OpenFile(
+ file_in_root,
+ OPEN_OR_CREATE_FILE,
+ std::string(), // mime_type
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &close_callback));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(base::PathExists(file_path));
+ int64 local_file_size;
+ ASSERT_TRUE(base::GetFileSize(file_path, &local_file_size));
+ EXPECT_EQ(0, local_file_size); // Should be an empty file.
+
+ ASSERT_FALSE(close_callback.is_null());
+ close_callback.Run();
+ EXPECT_EQ(1U,
+ delegate()->updated_local_ids().count(GetLocalId(file_in_root)));
+}
+
+TEST_F(OpenFileOperationTest, OpenFileTwice) {
+ const base::FilePath file_in_root(
+ FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+ const int64 file_size = src_entry.file_info().size();
+
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ base::Closure close_callback;
+ operation_->OpenFile(
+ file_in_root,
+ OPEN_FILE,
+ std::string(), // mime_type
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &close_callback));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(base::PathExists(file_path));
+ int64 local_file_size;
+ ASSERT_TRUE(base::GetFileSize(file_path, &local_file_size));
+ EXPECT_EQ(file_size, local_file_size);
+
+ // Open again.
+ error = FILE_ERROR_FAILED;
+ base::Closure close_callback2;
+ operation_->OpenFile(
+ file_in_root,
+ OPEN_FILE,
+ std::string(), // mime_type
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &close_callback2));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(base::PathExists(file_path));
+ ASSERT_TRUE(base::GetFileSize(file_path, &local_file_size));
+ EXPECT_EQ(file_size, local_file_size);
+
+ ASSERT_FALSE(close_callback.is_null());
+ ASSERT_FALSE(close_callback2.is_null());
+
+ close_callback.Run();
+
+ // There still remains a client opening the file, so it shouldn't be
+ // uploaded yet.
+ EXPECT_TRUE(delegate()->updated_local_ids().empty());
+
+ close_callback2.Run();
+
+ // Here, all the clients close the file, so it should be uploaded then.
+ EXPECT_EQ(1U, delegate()->updated_local_ids().count(src_entry.local_id()));
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/operation_delegate.cc b/components/drive/file_system/operation_delegate.cc
new file mode 100644
index 0000000..f6e2dad
--- /dev/null
+++ b/components/drive/file_system/operation_delegate.cc
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system/operation_delegate.h"
+
+namespace drive {
+namespace file_system {
+
+bool OperationDelegate::WaitForSyncComplete(
+ const std::string& local_id,
+ const FileOperationCallback& callback) {
+ return false;
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/operation_delegate.h b/components/drive/file_system/operation_delegate.h
new file mode 100644
index 0000000..24f50a8
--- /dev/null
+++ b/components/drive/file_system/operation_delegate.h
@@ -0,0 +1,59 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_FILE_SYSTEM_OPERATION_DELEGATE_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_OPERATION_DELEGATE_H_
+
+#include "components/drive/file_errors.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace drive {
+
+struct ClientContext;
+class FileChange;
+
+namespace file_system {
+
+// Error type of sync client.
+// Keep it synced with "DriveSyncErrorType" in file_manager_private.idl.
+enum DriveSyncErrorType {
+ // Request to delete a file without permission.
+ DRIVE_SYNC_ERROR_DELETE_WITHOUT_PERMISSION,
+ // Google Drive is temporary unavailable.
+ DRIVE_SYNC_ERROR_SERVICE_UNAVAILABLE,
+ // Errors other than above ones. No fallback is provided for the error.
+ DRIVE_SYNC_ERROR_MISC,
+};
+
+// Passes notifications from Drive operations back to the file system.
+class OperationDelegate {
+ public:
+ // Sent when a content of a directory has been changed.
+ // |directory_path| is a virtual directory path representing the
+ // changed directory.
+ virtual void OnFileChangedByOperation(const FileChange& changed_files) {}
+
+ // Sent when an entry is updated and sync is needed. The passed |context| is
+ // used for syncing.
+ virtual void OnEntryUpdatedByOperation(const ClientContext& context,
+ const std::string& local_id) {}
+
+ // Sent when a specific drive sync error occurred.
+ // |local_id| is the local ID of the resource entry.
+ virtual void OnDriveSyncError(DriveSyncErrorType type,
+ const std::string& local_id) {}
+
+ // Waits for the sync task to complete and runs the callback.
+ // Returns false if no task is found for the spcecified ID.
+ virtual bool WaitForSyncComplete(const std::string& local_id,
+ const FileOperationCallback& callback);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_OPERATION_DELEGATE_H_
diff --git a/components/drive/file_system/operation_test_base.cc b/components/drive/file_system/operation_test_base.cc
new file mode 100644
index 0000000..603aed4
--- /dev/null
+++ b/components/drive/file_system/operation_test_base.cc
@@ -0,0 +1,195 @@
+// 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 "components/drive/file_system/operation_test_base.h"
+
+#include "base/prefs/testing_pref_service.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "components/drive/change_list_loader.h"
+#include "components/drive/event_logger.h"
+#include "components/drive/fake_free_disk_space_getter.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+#include "components/drive/service/fake_drive_service.h"
+#include "components/drive/service/test_util.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/test_util.h"
+
+namespace drive {
+namespace file_system {
+
+OperationTestBase::LoggingDelegate::LoggingDelegate() {
+}
+
+OperationTestBase::LoggingDelegate::~LoggingDelegate() {
+}
+
+void OperationTestBase::LoggingDelegate::OnFileChangedByOperation(
+ const FileChange& changed_files) {
+ changed_files_.Apply(changed_files);
+}
+
+void OperationTestBase::LoggingDelegate::OnEntryUpdatedByOperation(
+ const ClientContext& /* client_context */,
+ const std::string& local_id) {
+ updated_local_ids_.insert(local_id);
+}
+
+void OperationTestBase::LoggingDelegate::OnDriveSyncError(
+ DriveSyncErrorType type, const std::string& local_id) {
+ drive_sync_errors_.push_back(type);
+}
+
+bool OperationTestBase::LoggingDelegate::WaitForSyncComplete(
+ const std::string& local_id,
+ const FileOperationCallback& callback) {
+ return wait_for_sync_complete_handler_.is_null() ?
+ false : wait_for_sync_complete_handler_.Run(local_id, callback);
+}
+
+OperationTestBase::OperationTestBase() {
+}
+
+OperationTestBase::OperationTestBase(int test_thread_bundle_options)
+ : thread_bundle_(test_thread_bundle_options) {
+}
+
+OperationTestBase::~OperationTestBase() {
+}
+
+void OperationTestBase::SetUp() {
+ scoped_refptr<base::SequencedWorkerPool> pool =
+ content::BrowserThread::GetBlockingPool();
+ blocking_task_runner_ =
+ pool->GetSequencedTaskRunner(pool->GetSequenceToken());
+
+ pref_service_.reset(new TestingPrefServiceSimple);
+ test_util::RegisterDrivePrefs(pref_service_->registry());
+
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ logger_.reset(new EventLogger);
+
+ fake_drive_service_.reset(new FakeDriveService);
+ ASSERT_TRUE(test_util::SetUpTestEntries(fake_drive_service_.get()));
+
+ scheduler_.reset(new JobScheduler(
+ pref_service_.get(),
+ logger_.get(),
+ fake_drive_service_.get(),
+ blocking_task_runner_.get()));
+
+ metadata_storage_.reset(new internal::ResourceMetadataStorage(
+ temp_dir_.path(), blocking_task_runner_.get()));
+ bool success = false;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadataStorage::Initialize,
+ base::Unretained(metadata_storage_.get())),
+ google_apis::test_util::CreateCopyResultCallback(&success));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_TRUE(success);
+
+ fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
+ cache_.reset(new internal::FileCache(metadata_storage_.get(),
+ temp_dir_.path(),
+ blocking_task_runner_.get(),
+ fake_free_disk_space_getter_.get()));
+ success = false;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::Initialize,
+ base::Unretained(cache_.get())),
+ google_apis::test_util::CreateCopyResultCallback(&success));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_TRUE(success);
+
+ metadata_.reset(new internal::ResourceMetadata(metadata_storage_.get(),
+ cache_.get(),
+ blocking_task_runner_));
+
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::Initialize,
+ base::Unretained(metadata_.get())),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_EQ(FILE_ERROR_OK, error);
+
+ // Makes sure the FakeDriveService's content is loaded to the metadata_.
+ about_resource_loader_.reset(new internal::AboutResourceLoader(
+ scheduler_.get()));
+ loader_controller_.reset(new internal::LoaderController);
+ change_list_loader_.reset(new internal::ChangeListLoader(
+ logger_.get(),
+ blocking_task_runner_.get(),
+ metadata_.get(),
+ scheduler_.get(),
+ about_resource_loader_.get(),
+ loader_controller_.get()));
+ change_list_loader_->LoadIfNeeded(
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_EQ(FILE_ERROR_OK, error);
+}
+
+FileError OperationTestBase::GetLocalResourceEntry(const base::FilePath& path,
+ ResourceEntry* entry) {
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::GetResourceEntryByPath,
+ base::Unretained(metadata()), path, entry),
+ base::Bind(google_apis::test_util::CreateCopyResultCallback(&error)));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ return error;
+}
+
+FileError OperationTestBase::GetLocalResourceEntryById(
+ const std::string& local_id,
+ ResourceEntry* entry) {
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::GetResourceEntryById,
+ base::Unretained(metadata()), local_id, entry),
+ base::Bind(google_apis::test_util::CreateCopyResultCallback(&error)));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ return error;
+}
+
+std::string OperationTestBase::GetLocalId(const base::FilePath& path) {
+ std::string local_id;
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::GetIdByPath,
+ base::Unretained(metadata()), path, &local_id),
+ base::Bind(google_apis::test_util::CreateCopyResultCallback(&error)));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error) << path.value();
+ return local_id;
+}
+
+FileError OperationTestBase::CheckForUpdates() {
+ FileError error = FILE_ERROR_FAILED;
+ change_list_loader_->CheckForUpdates(
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ return error;
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/operation_test_base.h b/components/drive/file_system/operation_test_base.h
new file mode 100644
index 0000000..bfdece00
--- /dev/null
+++ b/components/drive/file_system/operation_test_base.h
@@ -0,0 +1,166 @@
+// 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 COMPONENTS_DRIVE_FILE_SYSTEM_OPERATION_TEST_BASE_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_OPERATION_TEST_BASE_H_
+
+#include <set>
+
+#include "base/files/scoped_temp_dir.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_test_util.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+class TestingPrefServiceSimple;
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+namespace drive {
+struct ClientContext;
+class EventLogger;
+class FakeDriveService;
+class FakeFreeDiskSpaceGetter;
+class JobScheduler;
+
+namespace internal {
+class AboutResourceLoader;
+class ChangeListLoader;
+class FileCache;
+class LoaderController;
+class ResourceMetadata;
+class ResourceMetadataStorage;
+} // namespace internal
+
+namespace file_system {
+
+// Base fixture class for testing Drive file system operations. It sets up the
+// basic set of Drive internal classes (ResourceMetadata, Cache, etc) on top of
+// FakeDriveService for testing.
+class OperationTestBase : public testing::Test {
+ protected:
+ // OperationDelegate that records all the events.
+ class LoggingDelegate : public OperationDelegate {
+ public:
+ typedef base::Callback<bool(
+ const std::string& local_id,
+ const FileOperationCallback& callback)> WaitForSyncCompleteHandler;
+
+ LoggingDelegate();
+ ~LoggingDelegate();
+
+ // OperationDelegate overrides.
+ void OnFileChangedByOperation(const FileChange& changed_files) override;
+ void OnEntryUpdatedByOperation(const ClientContext& context,
+ const std::string& local_id) override;
+ void OnDriveSyncError(DriveSyncErrorType type,
+ const std::string& local_id) override;
+ bool WaitForSyncComplete(const std::string& local_id,
+ const FileOperationCallback& callback) override;
+
+ // Gets the set of changed paths.
+ const FileChange& get_changed_files() { return changed_files_; }
+
+ // Gets the set of updated local IDs.
+ const std::set<std::string>& updated_local_ids() const {
+ return updated_local_ids_;
+ }
+
+ // Gets the list of drive sync errors.
+ const std::vector<DriveSyncErrorType>& drive_sync_errors() const {
+ return drive_sync_errors_;
+ }
+
+ // Sets the callback used to handle WaitForSyncComplete() method calls.
+ void set_wait_for_sync_complete_handler(
+ const WaitForSyncCompleteHandler& wait_for_sync_complete_handler) {
+ wait_for_sync_complete_handler_ = wait_for_sync_complete_handler;
+ }
+
+ private:
+ FileChange changed_files_;
+ std::set<std::string> updated_local_ids_;
+ std::vector<DriveSyncErrorType> drive_sync_errors_;
+ WaitForSyncCompleteHandler wait_for_sync_complete_handler_;
+ };
+
+ OperationTestBase();
+ explicit OperationTestBase(int test_thread_bundle_options);
+ ~OperationTestBase() override;
+
+ // testing::Test overrides.
+ void SetUp() override;
+
+ // Returns the path of the temporary directory for putting test files.
+ base::FilePath temp_dir() const { return temp_dir_.path(); }
+
+ // Synchronously gets the resource entry corresponding to the path from local
+ // ResourceMetadta.
+ FileError GetLocalResourceEntry(const base::FilePath& path,
+ ResourceEntry* entry);
+
+ // Synchronously gets the resource entry corresponding to the ID from local
+ // ResourceMetadta.
+ FileError GetLocalResourceEntryById(const std::string& local_id,
+ ResourceEntry* entry);
+
+ // Gets the local ID of the entry specified by the path.
+ std::string GetLocalId(const base::FilePath& path);
+
+ // Synchronously updates |metadata_| by fetching the change feed from the
+ // |fake_service_|.
+ FileError CheckForUpdates();
+
+ // Accessors for the components.
+ FakeDriveService* fake_service() {
+ return fake_drive_service_.get();
+ }
+ EventLogger* logger() { return logger_.get(); }
+ LoggingDelegate* delegate() { return &delegate_; }
+ JobScheduler* scheduler() { return scheduler_.get(); }
+ base::SequencedTaskRunner* blocking_task_runner() {
+ return blocking_task_runner_.get();
+ }
+ FakeFreeDiskSpaceGetter* fake_free_disk_space_getter() {
+ return fake_free_disk_space_getter_.get();
+ }
+ internal::FileCache* cache() { return cache_.get(); }
+ internal::ResourceMetadata* metadata() { return metadata_.get(); }
+ internal::LoaderController* loader_controller() {
+ return loader_controller_.get();
+ }
+ internal::ChangeListLoader* change_list_loader() {
+ return change_list_loader_.get();
+ }
+
+ private:
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ scoped_ptr<TestingPrefServiceSimple> pref_service_;
+ base::ScopedTempDir temp_dir_;
+
+ LoggingDelegate delegate_;
+ scoped_ptr<EventLogger> logger_;
+ scoped_ptr<FakeDriveService> fake_drive_service_;
+ scoped_ptr<JobScheduler> scheduler_;
+ scoped_ptr<internal::ResourceMetadataStorage,
+ test_util::DestroyHelperForTests> metadata_storage_;
+ scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
+ scoped_ptr<internal::FileCache, test_util::DestroyHelperForTests> cache_;
+ scoped_ptr<internal::ResourceMetadata, test_util::DestroyHelperForTests>
+ metadata_;
+ scoped_ptr<internal::AboutResourceLoader> about_resource_loader_;
+ scoped_ptr<internal::LoaderController> loader_controller_;
+ scoped_ptr<internal::ChangeListLoader> change_list_loader_;
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_OPERATION_TEST_BASE_H_
diff --git a/components/drive/file_system/remove_operation.cc b/components/drive/file_system/remove_operation.cc
new file mode 100644
index 0000000..9cfc56c
--- /dev/null
+++ b/components/drive/file_system/remove_operation.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system/remove_operation.h"
+
+#include "base/sequenced_task_runner.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+
+namespace drive {
+namespace file_system {
+
+namespace {
+
+// Removes cache file and moves the metadata entry to the trash.
+FileError UpdateLocalState(internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& path,
+ bool is_recursive,
+ std::string* local_id,
+ ResourceEntry* entry,
+ base::FilePath* changed_path) {
+ FileError error = metadata->GetIdByPath(path, local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ error = metadata->GetResourceEntryById(*local_id, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ if (entry->file_info().is_directory() && !is_recursive) {
+ // Check emptiness of the directory.
+ ResourceEntryVector entries;
+ error = metadata->ReadDirectoryByPath(path, &entries);
+ if (error != FILE_ERROR_OK)
+ return error;
+ if (!entries.empty())
+ return FILE_ERROR_NOT_EMPTY;
+ }
+
+ error = cache->Remove(*local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ *changed_path = path;
+
+ // Move to the trash.
+ entry->set_parent_local_id(util::kDriveTrashDirLocalId);
+ return metadata->RefreshEntry(*entry);
+}
+
+} // namespace
+
+RemoveOperation::RemoveOperation(
+ base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ metadata_(metadata),
+ cache_(cache),
+ weak_ptr_factory_(this) {
+}
+
+RemoveOperation::~RemoveOperation() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void RemoveOperation::Remove(const base::FilePath& path,
+ bool is_recursive,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ std::string* local_id = new std::string;
+ base::FilePath* changed_path = new base::FilePath;
+ ResourceEntry* entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateLocalState,
+ metadata_,
+ cache_,
+ path,
+ is_recursive,
+ local_id,
+ entry,
+ changed_path),
+ base::Bind(&RemoveOperation::RemoveAfterUpdateLocalState,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ base::Owned(local_id),
+ base::Owned(entry),
+ base::Owned(changed_path)));
+}
+
+void RemoveOperation::RemoveAfterUpdateLocalState(
+ const FileOperationCallback& callback,
+ const std::string* local_id,
+ const ResourceEntry* entry,
+ const base::FilePath* changed_path,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (!changed_path->empty()) {
+ FileChange changed_file;
+ changed_file.Update(*changed_path, *entry, FileChange::CHANGE_TYPE_DELETE);
+ if (error == FILE_ERROR_OK) {
+ delegate_->OnFileChangedByOperation(changed_file);
+ delegate_->OnEntryUpdatedByOperation(ClientContext(USER_INITIATED),
+ *local_id);
+ }
+ }
+
+ callback.Run(error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/remove_operation.h b/components/drive/file_system/remove_operation.h
new file mode 100644
index 0000000..538a91c
--- /dev/null
+++ b/components/drive/file_system/remove_operation.h
@@ -0,0 +1,77 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_FILE_SYSTEM_REMOVE_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_REMOVE_OPERATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace drive {
+
+class ResourceEntry;
+
+namespace internal {
+class FileCache;
+class ResourceMetadata;
+} // namespace internal
+
+namespace file_system {
+
+class OperationDelegate;
+
+// This class encapsulates the drive Remove function. It is responsible for
+// moving the removed entry to the trash.
+class RemoveOperation {
+ public:
+ RemoveOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache);
+ ~RemoveOperation();
+
+ // Removes the resource at |path|. If |path| is a directory and |is_recursive|
+ // is set, it recursively removes all the descendants. If |is_recursive| is
+ // not set, it succeeds only when the directory is empty.
+ //
+ // |callback| must not be null.
+ void Remove(const base::FilePath& path,
+ bool is_recursive,
+ const FileOperationCallback& callback);
+
+ private:
+ // Part of Remove(). Called after UpdateLocalState() completion.
+ void RemoveAfterUpdateLocalState(const FileOperationCallback& callback,
+ const std::string* local_id,
+ const ResourceEntry* entry,
+ const base::FilePath* changed_directory_path,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ internal::ResourceMetadata* metadata_;
+ internal::FileCache* cache_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<RemoveOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(RemoveOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_REMOVE_OPERATION_H_
diff --git a/components/drive/file_system/remove_operation_unittest.cc b/components/drive/file_system/remove_operation_unittest.cc
new file mode 100644
index 0000000..5401275
--- /dev/null
+++ b/components/drive/file_system/remove_operation_unittest.cc
@@ -0,0 +1,132 @@
+// 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 "components/drive/file_system/remove_operation.h"
+
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "components/drive/file_system_core_util.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+typedef OperationTestBase RemoveOperationTest;
+
+TEST_F(RemoveOperationTest, RemoveFile) {
+ RemoveOperation operation(blocking_task_runner(), delegate(), metadata(),
+ cache());
+
+ base::FilePath nonexisting_file(
+ FILE_PATH_LITERAL("drive/root/Dummy file.txt"));
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath file_in_subdir(
+ FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
+
+ // Remove a file in root.
+ ResourceEntry entry;
+ FileError error = FILE_ERROR_FAILED;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &entry));
+ operation.Remove(file_in_root,
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntry(file_in_root, &entry));
+
+ const std::string id_file_in_root = entry.local_id();
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntryById(id_file_in_root, &entry));
+ EXPECT_EQ(util::kDriveTrashDirLocalId, entry.parent_local_id());
+
+ // Remove a file in subdirectory.
+ error = FILE_ERROR_FAILED;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_subdir, &entry));
+ const std::string resource_id = entry.resource_id();
+
+ operation.Remove(file_in_subdir,
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(file_in_subdir, &entry));
+
+ const std::string id_file_in_subdir = entry.local_id();
+ EXPECT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntryById(id_file_in_subdir, &entry));
+ EXPECT_EQ(util::kDriveTrashDirLocalId, entry.parent_local_id());
+
+ // Try removing non-existing file.
+ error = FILE_ERROR_FAILED;
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(nonexisting_file, &entry));
+ operation.Remove(nonexisting_file,
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
+
+ // Verify delegate notifications.
+ EXPECT_EQ(2U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(file_in_root));
+ EXPECT_TRUE(delegate()->get_changed_files().count(file_in_subdir));
+
+ EXPECT_EQ(2U, delegate()->updated_local_ids().size());
+ EXPECT_TRUE(delegate()->updated_local_ids().count(id_file_in_root));
+ EXPECT_TRUE(delegate()->updated_local_ids().count(id_file_in_subdir));
+}
+
+TEST_F(RemoveOperationTest, RemoveDirectory) {
+ RemoveOperation operation(blocking_task_runner(), delegate(), metadata(),
+ cache());
+
+ base::FilePath empty_dir(FILE_PATH_LITERAL(
+ "drive/root/Directory 1/Sub Directory Folder/Sub Sub Directory Folder"));
+ base::FilePath non_empty_dir(FILE_PATH_LITERAL(
+ "drive/root/Directory 1"));
+ base::FilePath file_in_non_empty_dir(FILE_PATH_LITERAL(
+ "drive/root/Directory 1/SubDirectory File 1.txt"));
+
+ // Empty directory can be removed even with is_recursive = false.
+ FileError error = FILE_ERROR_FAILED;
+ ResourceEntry entry;
+ operation.Remove(empty_dir,
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(empty_dir, &entry));
+
+ // Non-empty directory, cannot.
+ error = FILE_ERROR_FAILED;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(non_empty_dir, &entry));
+ operation.Remove(non_empty_dir,
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_EMPTY, error);
+ EXPECT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntry(non_empty_dir, &entry));
+
+ // With is_recursive = true, it can be deleted, however. Descendant entries
+ // are removed together.
+ error = FILE_ERROR_FAILED;
+ ASSERT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntry(file_in_non_empty_dir, &entry));
+ operation.Remove(non_empty_dir,
+ true, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(non_empty_dir, &entry));
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(file_in_non_empty_dir, &entry));
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/search_operation.cc b/components/drive/file_system/search_operation.cc
new file mode 100644
index 0000000..a0be870
--- /dev/null
+++ b/components/drive/file_system/search_operation.cc
@@ -0,0 +1,180 @@
+// 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 "components/drive/file_system/search_operation.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/task_runner_util.h"
+#include "components/drive/change_list_loader.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_entry_conversion.h"
+#include "components/drive/resource_metadata.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "url/gurl.h"
+
+namespace drive {
+namespace file_system {
+namespace {
+
+// Computes the path of each item in |file_list| returned from the server
+// and stores to |result|, by using |resource_metadata|. If the metadata is not
+// up-to-date and did not contain an item, adds the item to "drive/other" for
+// temporally assigning a path.
+FileError ResolveSearchResultOnBlockingPool(
+ internal::ResourceMetadata* resource_metadata,
+ scoped_ptr<google_apis::FileList> file_list,
+ std::vector<SearchResultInfo>* result) {
+ DCHECK(resource_metadata);
+ DCHECK(result);
+
+ const ScopedVector<google_apis::FileResource>& entries = file_list->items();
+ result->reserve(entries.size());
+ for (size_t i = 0; i < entries.size(); ++i) {
+ std::string local_id;
+ FileError error = resource_metadata->GetIdByResourceId(
+ entries[i]->file_id(), &local_id);
+
+ ResourceEntry entry;
+ if (error == FILE_ERROR_OK)
+ error = resource_metadata->GetResourceEntryById(local_id, &entry);
+
+ if (error == FILE_ERROR_NOT_FOUND) {
+ std::string original_parent_id;
+ if (!ConvertFileResourceToResourceEntry(*entries[i], &entry,
+ &original_parent_id))
+ continue; // Skip non-file entries.
+
+ // The result is absent in local resource metadata. This can happen if
+ // the metadata is not synced to the latest server state yet. In that
+ // case, we temporarily add the file to the special "drive/other"
+ // directory in order to assign a path, which is needed to access the
+ // file through FileSystem API.
+ //
+ // It will be moved to the right place when the metadata gets synced
+ // in normal loading process in ChangeListProcessor.
+ entry.set_parent_local_id(util::kDriveOtherDirLocalId);
+ error = resource_metadata->AddEntry(entry, &local_id);
+ }
+ if (error != FILE_ERROR_OK)
+ return error;
+ base::FilePath path;
+ error = resource_metadata->GetFilePath(local_id, &path);
+ if (error != FILE_ERROR_OK)
+ return error;
+ result->push_back(SearchResultInfo(path, entry.file_info().is_directory()));
+ }
+
+ return FILE_ERROR_OK;
+}
+
+} // namespace
+
+SearchOperation::SearchOperation(
+ base::SequencedTaskRunner* blocking_task_runner,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::LoaderController* loader_controller)
+ : blocking_task_runner_(blocking_task_runner),
+ scheduler_(scheduler),
+ metadata_(metadata),
+ loader_controller_(loader_controller),
+ weak_ptr_factory_(this) {
+}
+
+SearchOperation::~SearchOperation() {
+}
+
+void SearchOperation::Search(const std::string& search_query,
+ const GURL& next_link,
+ const SearchCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (next_link.is_empty()) {
+ // This is first request for the |search_query|.
+ scheduler_->Search(
+ search_query,
+ base::Bind(&SearchOperation::SearchAfterGetFileList,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ } else {
+ // There is the remaining result so fetch it.
+ scheduler_->GetRemainingFileList(
+ next_link,
+ base::Bind(&SearchOperation::SearchAfterGetFileList,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ }
+}
+
+void SearchOperation::SearchAfterGetFileList(
+ const SearchCallback& callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::FileList> file_list) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileError error = GDataToFileError(gdata_error);
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, GURL(), scoped_ptr<std::vector<SearchResultInfo> >());
+ return;
+ }
+
+ DCHECK(file_list);
+
+ GURL next_url = file_list->next_link();
+
+ scoped_ptr<std::vector<SearchResultInfo> > result(
+ new std::vector<SearchResultInfo>);
+ if (file_list->items().empty()) {
+ // Short cut. If the resource entry is empty, we don't need to refresh
+ // the resource metadata.
+ callback.Run(FILE_ERROR_OK, next_url, result.Pass());
+ return;
+ }
+
+ // ResolveSearchResultOnBlockingPool() may add entries newly created on the
+ // server to the local metadata.
+ // This may race with sync tasks so we should ask LoaderController here.
+ std::vector<SearchResultInfo>* result_ptr = result.get();
+ loader_controller_->ScheduleRun(base::Bind(
+ base::IgnoreResult(
+ &base::PostTaskAndReplyWithResult<FileError, FileError>),
+ blocking_task_runner_,
+ FROM_HERE,
+ base::Bind(&ResolveSearchResultOnBlockingPool,
+ metadata_,
+ base::Passed(&file_list),
+ result_ptr),
+ base::Bind(&SearchOperation::SearchAfterResolveSearchResult,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ next_url,
+ base::Passed(&result))));
+}
+
+void SearchOperation::SearchAfterResolveSearchResult(
+ const SearchCallback& callback,
+ const GURL& next_link,
+ scoped_ptr<std::vector<SearchResultInfo> > result,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ DCHECK(result);
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, GURL(), scoped_ptr<std::vector<SearchResultInfo> >());
+ return;
+ }
+
+ callback.Run(error, next_link, result.Pass());
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/search_operation.h b/components/drive/file_system/search_operation.h
new file mode 100644
index 0000000..920c52b
--- /dev/null
+++ b/components/drive/file_system/search_operation.h
@@ -0,0 +1,86 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_FILE_SYSTEM_SEARCH_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_SEARCH_OPERATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system_interface.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+namespace google_apis {
+class FileList;
+} // namespace google_apis
+
+namespace drive {
+
+class JobScheduler;
+
+namespace internal {
+class LoaderController;
+class ResourceMetadata;
+} // namespace internal
+
+namespace file_system {
+
+// This class encapsulates the drive Search function. It is responsible for
+// sending the request to the drive API.
+class SearchOperation {
+ public:
+ SearchOperation(base::SequencedTaskRunner* blocking_task_runner,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::LoaderController* loader_controller);
+ ~SearchOperation();
+
+ // Performs server side content search operation for |search_query|. If
+ // |next_link| is set, this is the search result url that will be fetched.
+ // Upon completion, |callback| will be called with the result. This is
+ // implementation of FileSystemInterface::Search() method.
+ //
+ // |callback| must not be null.
+ void Search(const std::string& search_query,
+ const GURL& next_link,
+ const SearchCallback& callback);
+
+ private:
+ // Part of Search(), called after the FileList is fetched from the server.
+ void SearchAfterGetFileList(
+ const SearchCallback& callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::FileList> file_list);
+
+ // Part of Search(), called after |result| is filled on the blocking pool.
+ void SearchAfterResolveSearchResult(
+ const SearchCallback& callback,
+ const GURL& next_link,
+ scoped_ptr<std::vector<SearchResultInfo> > result,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ JobScheduler* scheduler_;
+ internal::ResourceMetadata* metadata_;
+ internal::LoaderController* loader_controller_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<SearchOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(SearchOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_SEARCH_OPERATION_H_
diff --git a/components/drive/file_system/search_operation_unittest.cc b/components/drive/file_system/search_operation_unittest.cc
new file mode 100644
index 0000000..83ab746
--- /dev/null
+++ b/components/drive/file_system/search_operation_unittest.cc
@@ -0,0 +1,157 @@
+// 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 "components/drive/file_system/search_operation.h"
+
+#include "base/callback_helpers.h"
+#include "components/drive/change_list_loader.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "components/drive/service/fake_drive_service.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/drive_api_parser.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+typedef OperationTestBase SearchOperationTest;
+
+TEST_F(SearchOperationTest, ContentSearch) {
+ SearchOperation operation(blocking_task_runner(), scheduler(), metadata(),
+ loader_controller());
+
+ std::set<std::string> expected_results;
+ expected_results.insert(
+ "drive/root/Directory 1/Sub Directory Folder/Sub Sub Directory Folder");
+ expected_results.insert("drive/root/Directory 1/Sub Directory Folder");
+ expected_results.insert("drive/root/Directory 1/SubDirectory File 1.txt");
+ expected_results.insert("drive/root/Directory 1");
+ expected_results.insert("drive/root/Directory 2 excludeDir-test");
+
+ FileError error = FILE_ERROR_FAILED;
+ GURL next_link;
+ scoped_ptr<std::vector<SearchResultInfo> > results;
+
+ operation.Search("Directory", GURL(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &next_link, &results));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_TRUE(next_link.is_empty());
+ EXPECT_EQ(expected_results.size(), results->size());
+ for (size_t i = 0; i < results->size(); i++) {
+ EXPECT_TRUE(expected_results.count(results->at(i).path.AsUTF8Unsafe()))
+ << results->at(i).path.AsUTF8Unsafe();
+ }
+}
+
+TEST_F(SearchOperationTest, ContentSearchWithNewEntry) {
+ SearchOperation operation(blocking_task_runner(), scheduler(), metadata(),
+ loader_controller());
+
+ // Create a new directory in the drive service.
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_service()->AddNewDirectory(
+ fake_service()->GetRootResourceId(), "New Directory 1!",
+ AddNewDirectoryOptions(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_EQ(google_apis::HTTP_CREATED, status);
+
+ // As the result of the first Search(), only entries in the current file
+ // system snapshot are expected to be returned in the "right" path. New
+ // entries like "New Directory 1!" is temporarily added to "drive/other".
+ std::set<std::string> expected_results;
+ expected_results.insert("drive/root/Directory 1");
+ expected_results.insert("drive/other/New Directory 1!");
+
+ FileError error = FILE_ERROR_FAILED;
+ GURL next_link;
+ scoped_ptr<std::vector<SearchResultInfo> > results;
+
+ operation.Search("\"Directory 1\"", GURL(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &next_link, &results));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_TRUE(next_link.is_empty());
+ ASSERT_EQ(expected_results.size(), results->size());
+ for (size_t i = 0; i < results->size(); i++) {
+ EXPECT_TRUE(expected_results.count(results->at(i).path.AsUTF8Unsafe()))
+ << results->at(i).path.AsUTF8Unsafe();
+ }
+
+ // Load the change from FakeDriveService.
+ ASSERT_EQ(FILE_ERROR_OK, CheckForUpdates());
+
+ // Now the new entry must be reported to be in the right directory.
+ expected_results.clear();
+ expected_results.insert("drive/root/Directory 1");
+ expected_results.insert("drive/root/New Directory 1!");
+ error = FILE_ERROR_FAILED;
+ operation.Search("\"Directory 1\"", GURL(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &next_link, &results));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_TRUE(next_link.is_empty());
+ ASSERT_EQ(expected_results.size(), results->size());
+ for (size_t i = 0; i < results->size(); i++) {
+ EXPECT_TRUE(expected_results.count(results->at(i).path.AsUTF8Unsafe()))
+ << results->at(i).path.AsUTF8Unsafe();
+ }
+}
+
+TEST_F(SearchOperationTest, ContentSearchEmptyResult) {
+ SearchOperation operation(blocking_task_runner(), scheduler(), metadata(),
+ loader_controller());
+
+ FileError error = FILE_ERROR_FAILED;
+ GURL next_link;
+ scoped_ptr<std::vector<SearchResultInfo> > results;
+
+ operation.Search("\"no-match query\"", GURL(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &next_link, &results));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_TRUE(next_link.is_empty());
+ EXPECT_EQ(0U, results->size());
+}
+
+TEST_F(SearchOperationTest, Lock) {
+ SearchOperation operation(blocking_task_runner(), scheduler(), metadata(),
+ loader_controller());
+
+ // Lock.
+ scoped_ptr<base::ScopedClosureRunner> lock = loader_controller()->GetLock();
+
+ // Search does not return the result as long as lock is alive.
+ FileError error = FILE_ERROR_FAILED;
+ GURL next_link;
+ scoped_ptr<std::vector<SearchResultInfo> > results;
+
+ operation.Search("\"Directory 1\"", GURL(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &next_link, &results));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_FAILED, error);
+ EXPECT_FALSE(results);
+
+ // Unlock, this should resume the pending search.
+ lock.reset();
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(results);
+ EXPECT_EQ(1u, results->size());
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/set_property_operation.cc b/components/drive/file_system/set_property_operation.cc
new file mode 100644
index 0000000..0620f2e
--- /dev/null
+++ b/components/drive/file_system/set_property_operation.cc
@@ -0,0 +1,122 @@
+// Copyright 2015 The Chromium 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 "components/drive/file_system/set_property_operation.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/sequenced_task_runner.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+
+namespace drive {
+namespace file_system {
+
+namespace {
+
+// Adds the property to resource entry. Overwrites existing property if exists.
+// If no change has been made (same key, visibility and value is already added)
+// then FILE_ERROR_EXISTS is returned.
+FileError UpdateLocalState(internal::ResourceMetadata* metadata,
+ const base::FilePath& file_path,
+ google_apis::drive::Property::Visibility visibility,
+ const std::string& key,
+ const std::string& value,
+ ResourceEntry* entry) {
+ using google_apis::drive::Property;
+ FileError error = metadata->GetResourceEntryByPath(file_path, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ Property_Visibility proto_visibility = Property_Visibility_PRIVATE;
+ switch (visibility) {
+ case Property::VISIBILITY_PRIVATE:
+ proto_visibility = Property_Visibility_PRIVATE;
+ break;
+ case Property::VISIBILITY_PUBLIC:
+ proto_visibility = Property_Visibility_PUBLIC;
+ break;
+ }
+
+ ::drive::Property* property_to_update = nullptr;
+ for (auto& property : *entry->mutable_new_properties()) {
+ if (property.visibility() == proto_visibility && property.key() == key) {
+ // Exactly the same property exists, so don't update the local state.
+ if (property.value() == value)
+ return FILE_ERROR_EXISTS;
+ property_to_update = &property;
+ break;
+ }
+ }
+
+ // If no property to update has been found, then add a new one.
+ if (!property_to_update)
+ property_to_update = entry->mutable_new_properties()->Add();
+
+ property_to_update->set_visibility(proto_visibility);
+ property_to_update->set_key(key);
+ property_to_update->set_value(value);
+ entry->set_metadata_edit_state(ResourceEntry::DIRTY);
+ entry->set_modification_date(base::Time::Now().ToInternalValue());
+
+ return metadata->RefreshEntry(*entry);
+}
+
+} // namespace
+
+SetPropertyOperation::SetPropertyOperation(
+ base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ metadata_(metadata),
+ weak_ptr_factory_(this) {
+}
+
+SetPropertyOperation::~SetPropertyOperation() {
+}
+
+void SetPropertyOperation::SetProperty(
+ const base::FilePath& file_path,
+ google_apis::drive::Property::Visibility visibility,
+ const std::string& key,
+ const std::string& value,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ ResourceEntry* entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(), FROM_HERE,
+ base::Bind(&UpdateLocalState, metadata_, file_path, visibility, key,
+ value, entry),
+ base::Bind(&SetPropertyOperation::SetPropertyAfterUpdateLocalState,
+ weak_ptr_factory_.GetWeakPtr(), callback, base::Owned(entry)));
+}
+
+void SetPropertyOperation::SetPropertyAfterUpdateLocalState(
+ const FileOperationCallback& callback,
+ const ResourceEntry* entry,
+ FileError result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (result == FILE_ERROR_OK) {
+ // Do not notify about the file change, as properties are write only and
+ // cannot be read, so there is no visible change.
+ delegate_->OnEntryUpdatedByOperation(ClientContext(USER_INITIATED),
+ entry->local_id());
+ }
+
+ // Even if exists, return success, as the set property operation always
+ // overwrites existing values.
+ callback.Run(result == FILE_ERROR_EXISTS ? FILE_ERROR_OK : result);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/set_property_operation.h b/components/drive/file_system/set_property_operation.h
new file mode 100644
index 0000000..2e04441
--- /dev/null
+++ b/components/drive/file_system/set_property_operation.h
@@ -0,0 +1,70 @@
+// Copyright 2015 The Chromium 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 COMPONENTS_DRIVE_FILE_SYSTEM_SET_PROPERTY_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_SET_PROPERTY_OPERATION_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+#include "google_apis/drive/drive_api_requests.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace drive {
+namespace internal {
+class ResourceMetadata;
+} // namespace internal
+
+class ResourceEntry;
+
+namespace file_system {
+
+class OperationDelegate;
+
+class SetPropertyOperation {
+ public:
+ SetPropertyOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata);
+ ~SetPropertyOperation();
+
+ // Sets the |key| property on the entry at |drive_file_path| with the
+ // specified |visibility|. If the property already exists, it will be
+ // overwritten.
+ void SetProperty(const base::FilePath& drive_file_path,
+ google_apis::drive::Property::Visibility visibility,
+ const std::string& key,
+ const std::string& value,
+ const FileOperationCallback& callback);
+
+ private:
+ // Part of SetProperty(). Runs after updating the local state.
+ void SetPropertyAfterUpdateLocalState(const FileOperationCallback& callback,
+ const ResourceEntry* entry,
+ FileError result);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ internal::ResourceMetadata* metadata_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<SetPropertyOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(SetPropertyOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_SET_PROPERTY_OPERATION_H_
diff --git a/components/drive/file_system/set_property_operation_unittest.cc b/components/drive/file_system/set_property_operation_unittest.cc
new file mode 100644
index 0000000..093fbb2
--- /dev/null
+++ b/components/drive/file_system/set_property_operation_unittest.cc
@@ -0,0 +1,153 @@
+// Copyright 2015 The Chromium 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 "components/drive/file_system/set_property_operation.h"
+
+#include "base/files/file_path.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "content/public/test/test_utils.h"
+#include "google_apis/drive/drive_api_requests.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+namespace {
+
+const base::FilePath::CharType kTestPath[] =
+ FILE_PATH_LITERAL("drive/root/File 1.txt");
+const char kTestKey[] = "key";
+const char kTestValue[] = "value";
+const char kTestAnotherValue[] = "another-value";
+
+} // namespace
+
+typedef OperationTestBase SetPropertyOperationTest;
+
+TEST_F(SetPropertyOperationTest, SetProperty) {
+ SetPropertyOperation operation(blocking_task_runner(), delegate(),
+ metadata());
+
+ const base::FilePath test_path(kTestPath);
+ FileError result = FILE_ERROR_FAILED;
+ operation.SetProperty(
+ test_path, google_apis::drive::Property::Visibility::VISIBILITY_PRIVATE,
+ kTestKey, kTestValue,
+ google_apis::test_util::CreateCopyResultCallback(&result));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, result);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(test_path, &entry));
+ EXPECT_EQ(ResourceEntry::DIRTY, entry.metadata_edit_state());
+ ASSERT_EQ(1, entry.new_properties().size());
+ const drive::Property property = entry.new_properties().Get(0);
+ EXPECT_EQ(Property_Visibility_PRIVATE, property.visibility());
+ EXPECT_EQ(kTestKey, property.key());
+ EXPECT_EQ(kTestValue, property.value());
+
+ EXPECT_EQ(0u, delegate()->get_changed_files().size());
+ EXPECT_FALSE(delegate()->get_changed_files().count(test_path));
+
+ EXPECT_EQ(1u, delegate()->updated_local_ids().size());
+ EXPECT_TRUE(delegate()->updated_local_ids().count(entry.local_id()));
+}
+
+TEST_F(SetPropertyOperationTest, SetProperty_Duplicate) {
+ SetPropertyOperation operation(blocking_task_runner(), delegate(),
+ metadata());
+
+ const base::FilePath test_path(kTestPath);
+ FileError result = FILE_ERROR_FAILED;
+ operation.SetProperty(
+ test_path, google_apis::drive::Property::Visibility::VISIBILITY_PRIVATE,
+ kTestKey, kTestValue,
+ google_apis::test_util::CreateCopyResultCallback(&result));
+ operation.SetProperty(
+ test_path, google_apis::drive::Property::Visibility::VISIBILITY_PRIVATE,
+ kTestKey, kTestValue,
+ google_apis::test_util::CreateCopyResultCallback(&result));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, result);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(test_path, &entry));
+ EXPECT_EQ(1, entry.new_properties().size());
+}
+
+TEST_F(SetPropertyOperationTest, SetProperty_Overwrite) {
+ SetPropertyOperation operation(blocking_task_runner(), delegate(),
+ metadata());
+
+ const base::FilePath test_path(kTestPath);
+ FileError result = FILE_ERROR_FAILED;
+ operation.SetProperty(
+ test_path, google_apis::drive::Property::Visibility::VISIBILITY_PUBLIC,
+ kTestKey, kTestValue,
+ google_apis::test_util::CreateCopyResultCallback(&result));
+ operation.SetProperty(
+ test_path, google_apis::drive::Property::Visibility::VISIBILITY_PUBLIC,
+ kTestKey, kTestAnotherValue,
+ google_apis::test_util::CreateCopyResultCallback(&result));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, result);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(test_path, &entry));
+ ASSERT_EQ(1, entry.new_properties().size());
+ const drive::Property property = entry.new_properties().Get(0);
+ EXPECT_EQ(Property_Visibility_PUBLIC, property.visibility());
+ EXPECT_EQ(kTestKey, property.key());
+ EXPECT_EQ(kTestAnotherValue, property.value());
+}
+
+TEST_F(SetPropertyOperationTest, SetProperty_DifferentVisibilities) {
+ SetPropertyOperation operation(blocking_task_runner(), delegate(),
+ metadata());
+
+ {
+ const base::FilePath test_path(kTestPath);
+ FileError result = FILE_ERROR_FAILED;
+ operation.SetProperty(
+ test_path, google_apis::drive::Property::Visibility::VISIBILITY_PRIVATE,
+ kTestKey, kTestValue,
+ google_apis::test_util::CreateCopyResultCallback(&result));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, result);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(test_path, &entry));
+ ASSERT_EQ(1, entry.new_properties().size());
+ const drive::Property property = entry.new_properties().Get(0);
+ EXPECT_EQ(Property_Visibility_PRIVATE, property.visibility());
+ EXPECT_EQ(kTestKey, property.key());
+ EXPECT_EQ(kTestValue, property.value());
+ }
+
+ // Insert another property with the same key, same value but different
+ // visibility.
+ {
+ const base::FilePath test_path(kTestPath);
+ FileError result = FILE_ERROR_FAILED;
+ operation.SetProperty(
+ test_path, google_apis::drive::Property::Visibility::VISIBILITY_PUBLIC,
+ kTestKey, kTestAnotherValue,
+ google_apis::test_util::CreateCopyResultCallback(&result));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, result);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(test_path, &entry));
+ ASSERT_EQ(2, entry.new_properties().size());
+ const drive::Property property = entry.new_properties().Get(1);
+ EXPECT_EQ(Property_Visibility_PUBLIC, property.visibility());
+ EXPECT_EQ(kTestKey, property.key());
+ EXPECT_EQ(kTestAnotherValue, property.value());
+ }
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/touch_operation.cc b/components/drive/file_system/touch_operation.cc
new file mode 100644
index 0000000..63272d7
--- /dev/null
+++ b/components/drive/file_system/touch_operation.cc
@@ -0,0 +1,96 @@
+// 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 "components/drive/file_system/touch_operation.h"
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/sequenced_task_runner.h"
+#include "base/time/time.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+
+namespace drive {
+namespace file_system {
+
+namespace {
+
+// Updates the timestamps of the entry specified by |file_path|.
+FileError UpdateLocalState(internal::ResourceMetadata* metadata,
+ const base::FilePath& file_path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ ResourceEntry* entry) {
+ FileError error = metadata->GetResourceEntryByPath(file_path, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ PlatformFileInfoProto* file_info = entry->mutable_file_info();
+ if (!last_access_time.is_null())
+ file_info->set_last_accessed(last_access_time.ToInternalValue());
+ if (!last_modified_time.is_null())
+ file_info->set_last_modified(last_modified_time.ToInternalValue());
+ entry->set_metadata_edit_state(ResourceEntry::DIRTY);
+ entry->set_modification_date(base::Time::Now().ToInternalValue());
+ return metadata->RefreshEntry(*entry);
+}
+
+} // namespace
+
+TouchOperation::TouchOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ metadata_(metadata),
+ weak_ptr_factory_(this) {
+}
+
+TouchOperation::~TouchOperation() {
+}
+
+void TouchOperation::TouchFile(const base::FilePath& file_path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ ResourceEntry* entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(), FROM_HERE,
+ base::Bind(&UpdateLocalState, metadata_, file_path, last_access_time,
+ last_modified_time, entry),
+ base::Bind(&TouchOperation::TouchFileAfterUpdateLocalState,
+ weak_ptr_factory_.GetWeakPtr(), file_path, callback,
+ base::Owned(entry)));
+}
+
+void TouchOperation::TouchFileAfterUpdateLocalState(
+ const base::FilePath& file_path,
+ const FileOperationCallback& callback,
+ const ResourceEntry* entry,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileChange changed_files;
+ changed_files.Update(file_path, entry->file_info().is_directory()
+ ? FileChange::FILE_TYPE_DIRECTORY
+ : FileChange::FILE_TYPE_FILE,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+
+ if (error == FILE_ERROR_OK) {
+ delegate_->OnFileChangedByOperation(changed_files);
+ delegate_->OnEntryUpdatedByOperation(ClientContext(USER_INITIATED),
+ entry->local_id());
+ }
+ callback.Run(error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/touch_operation.h b/components/drive/file_system/touch_operation.h
new file mode 100644
index 0000000..46c53e7
--- /dev/null
+++ b/components/drive/file_system/touch_operation.h
@@ -0,0 +1,68 @@
+// 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 COMPONENTS_DRIVE_FILE_SYSTEM_TOUCH_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_TOUCH_OPERATION_H_
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+class Time;
+} // namespace base
+
+namespace drive {
+namespace internal {
+class ResourceMetadata;
+} // namespace internal
+
+class ResourceEntry;
+
+namespace file_system {
+
+class OperationDelegate;
+
+class TouchOperation {
+ public:
+ TouchOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ internal::ResourceMetadata* metadata);
+ ~TouchOperation();
+
+ // Touches the file by updating last access time and last modified time.
+ // Upon completion, invokes |callback|.
+ // |last_access_time|, |last_modified_time| and |callback| must not be null.
+ void TouchFile(const base::FilePath& file_path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const FileOperationCallback& callback);
+
+ private:
+ // Part of TouchFile(). Runs after updating the local state.
+ void TouchFileAfterUpdateLocalState(const base::FilePath& file_path,
+ const FileOperationCallback& callback,
+ const ResourceEntry* entry,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ internal::ResourceMetadata* metadata_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<TouchOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(TouchOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_TOUCH_OPERATION_H_
diff --git a/components/drive/file_system/touch_operation_unittest.cc b/components/drive/file_system/touch_operation_unittest.cc
new file mode 100644
index 0000000..c7b9cfe
--- /dev/null
+++ b/components/drive/file_system/touch_operation_unittest.cc
@@ -0,0 +1,59 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system/touch_operation.h"
+
+#include "base/files/file_path.h"
+#include "base/time/time.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "components/drive/resource_metadata.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+typedef OperationTestBase TouchOperationTest;
+
+TEST_F(TouchOperationTest, TouchFile) {
+ TouchOperation operation(blocking_task_runner(),
+ delegate(),
+ metadata());
+
+ const base::FilePath kTestPath(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ const base::Time::Exploded kLastAccessTime = {
+ 2012, 7, 0, 19, 15, 59, 13, 123
+ };
+ const base::Time::Exploded kLastModifiedTime = {
+ 2013, 7, 0, 19, 15, 59, 13, 123
+ };
+
+ FileError error = FILE_ERROR_FAILED;
+ operation.TouchFile(
+ kTestPath,
+ base::Time::FromUTCExploded(kLastAccessTime),
+ base::Time::FromUTCExploded(kLastModifiedTime),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kTestPath, &entry));
+ EXPECT_EQ(base::Time::FromUTCExploded(kLastAccessTime),
+ base::Time::FromInternalValue(entry.file_info().last_accessed()));
+ EXPECT_EQ(base::Time::FromUTCExploded(kLastModifiedTime),
+ base::Time::FromInternalValue(entry.file_info().last_modified()));
+ EXPECT_EQ(ResourceEntry::DIRTY, entry.metadata_edit_state());
+
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(kTestPath));
+
+ EXPECT_EQ(1U, delegate()->updated_local_ids().size());
+ EXPECT_TRUE(delegate()->updated_local_ids().count(entry.local_id()));
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/truncate_operation.cc b/components/drive/file_system/truncate_operation.cc
new file mode 100644
index 0000000..7ab44b4
--- /dev/null
+++ b/components/drive/file_system/truncate_operation.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 "components/drive/file_system/truncate_operation.h"
+
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/files/file.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task_runner_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/download_operation.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/job_scheduler.h"
+
+namespace drive {
+namespace file_system {
+namespace {
+
+// Truncates the local file at |local_cache_path| to the |length| bytes,
+// then marks the resource is dirty on |cache|.
+FileError TruncateOnBlockingPool(internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const std::string& local_id,
+ const base::FilePath& local_cache_path,
+ int64 length) {
+ DCHECK(metadata);
+ DCHECK(cache);
+
+ scoped_ptr<base::ScopedClosureRunner> file_closer;
+ FileError error = cache->OpenForWrite(local_id, &file_closer);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ base::File file(local_cache_path,
+ base::File::FLAG_OPEN | base::File::FLAG_WRITE);
+ if (!file.IsValid())
+ return FILE_ERROR_FAILED;
+
+ if (!file.SetLength(length))
+ return FILE_ERROR_FAILED;
+
+ return FILE_ERROR_OK;
+}
+
+} // namespace
+
+TruncateOperation::TruncateOperation(
+ base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ metadata_(metadata),
+ cache_(cache),
+ download_operation_(new DownloadOperation(blocking_task_runner,
+ delegate,
+ scheduler,
+ metadata,
+ cache,
+ temporary_file_directory)),
+ weak_ptr_factory_(this) {
+}
+
+TruncateOperation::~TruncateOperation() {
+}
+
+void TruncateOperation::Truncate(const base::FilePath& file_path,
+ int64 length,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (length < 0) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, FILE_ERROR_INVALID_OPERATION));
+ return;
+ }
+
+ // TODO(kinaba): http://crbug.com/132780.
+ // Optimize the cases for small |length|, at least for |length| == 0.
+ download_operation_->EnsureFileDownloadedByPath(
+ file_path,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ base::Bind(&TruncateOperation::TruncateAfterEnsureFileDownloadedByPath,
+ weak_ptr_factory_.GetWeakPtr(), length, callback));
+}
+
+void TruncateOperation::TruncateAfterEnsureFileDownloadedByPath(
+ int64 length,
+ const FileOperationCallback& callback,
+ FileError error,
+ const base::FilePath& local_file_path,
+ scoped_ptr<ResourceEntry> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+ DCHECK(entry);
+ DCHECK(entry->has_file_specific_info());
+
+ if (entry->file_specific_info().is_hosted_document()) {
+ callback.Run(FILE_ERROR_INVALID_OPERATION);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&TruncateOnBlockingPool,
+ metadata_, cache_, entry->local_id(), local_file_path, length),
+ base::Bind(
+ &TruncateOperation::TruncateAfterTruncateOnBlockingPool,
+ weak_ptr_factory_.GetWeakPtr(), entry->local_id(), callback));
+}
+
+void TruncateOperation::TruncateAfterTruncateOnBlockingPool(
+ const std::string& local_id,
+ const FileOperationCallback& callback,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ delegate_->OnEntryUpdatedByOperation(ClientContext(USER_INITIATED), local_id);
+
+ callback.Run(error);
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system/truncate_operation.h b/components/drive/file_system/truncate_operation.h
new file mode 100644
index 0000000..ff22806
--- /dev/null
+++ b/components/drive/file_system/truncate_operation.h
@@ -0,0 +1,88 @@
+// 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 COMPONENTS_DRIVE_FILE_SYSTEM_TRUNCATE_OPERATION_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_TRUNCATE_OPERATION_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_errors.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace drive {
+
+class JobScheduler;
+class ResourceEntry;
+
+namespace internal {
+class FileCache;
+class ResourceMetadata;
+} // namespace internal
+
+namespace file_system {
+
+class OperationDelegate;
+class DownloadOperation;
+
+// This class encapsulates the drive Truncate function. It is responsible for
+// fetching the content from the Drive server if necessary, truncating the
+// file content actually, and then notifying the file is locally modified and
+// that it is necessary to upload the file to the server.
+class TruncateOperation {
+ public:
+ TruncateOperation(base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory);
+ ~TruncateOperation();
+
+ // Performs the truncate operation on the file at drive path |file_path| to
+ // |length| bytes. Invokes |callback| when finished with the result of the
+ // operation. |callback| must not be null.
+ void Truncate(const base::FilePath& file_path,
+ int64 length,
+ const FileOperationCallback& callback);
+ private:
+ // Part of Truncate(). Called after EnsureFileDownloadedByPath() is complete.
+ void TruncateAfterEnsureFileDownloadedByPath(
+ int64 length,
+ const FileOperationCallback& callback,
+ FileError error,
+ const base::FilePath& local_file_path,
+ scoped_ptr<ResourceEntry> resource_entry);
+
+ // Part of Truncate(). Called after TruncateOnBlockingPool() is complete.
+ void TruncateAfterTruncateOnBlockingPool(
+ const std::string& local_id,
+ const FileOperationCallback& callback,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ OperationDelegate* delegate_;
+ internal::ResourceMetadata* metadata_;
+ internal::FileCache* cache_;
+
+ scoped_ptr<DownloadOperation> download_operation_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate the weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<TruncateOperation> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(TruncateOperation);
+};
+
+} // namespace file_system
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_TRUNCATE_OPERATION_H_
diff --git a/components/drive/file_system/truncate_operation_unittest.cc b/components/drive/file_system/truncate_operation_unittest.cc
new file mode 100644
index 0000000..31c5ad5
--- /dev/null
+++ b/components/drive/file_system/truncate_operation_unittest.cc
@@ -0,0 +1,134 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system/truncate_operation.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/task_runner_util.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/fake_free_disk_space_getter.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace file_system {
+
+class TruncateOperationTest : public OperationTestBase {
+ protected:
+ void SetUp() override {
+ OperationTestBase::SetUp();
+
+ operation_.reset(new TruncateOperation(
+ blocking_task_runner(), delegate(), scheduler(),
+ metadata(), cache(), temp_dir()));
+ }
+
+ scoped_ptr<TruncateOperation> operation_;
+};
+
+TEST_F(TruncateOperationTest, Truncate) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+ const int64 file_size = src_entry.file_info().size();
+
+ // Make sure the file has at least 2 bytes.
+ ASSERT_GE(file_size, 2);
+
+ FileError error = FILE_ERROR_FAILED;
+ operation_->Truncate(
+ file_in_root,
+ 1, // Truncate to 1 byte.
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ base::FilePath local_path;
+ error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::GetFile,
+ base::Unretained(cache()),
+ GetLocalId(file_in_root), &local_path),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_EQ(FILE_ERROR_OK, error);
+
+ // The local file should be truncated.
+ int64 local_file_size = 0;
+ base::GetFileSize(local_path, &local_file_size);
+ EXPECT_EQ(1, local_file_size);
+}
+
+TEST_F(TruncateOperationTest, NegativeSize) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+ const int64 file_size = src_entry.file_info().size();
+
+ // Make sure the file has at least 2 bytes.
+ ASSERT_GE(file_size, 2);
+
+ FileError error = FILE_ERROR_FAILED;
+ operation_->Truncate(
+ file_in_root,
+ -1, // Truncate to "-1" byte.
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_INVALID_OPERATION, error);
+}
+
+TEST_F(TruncateOperationTest, HostedDocument) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL(
+ "drive/root/Document 1 excludeDir-test.gdoc"));
+
+ FileError error = FILE_ERROR_FAILED;
+ operation_->Truncate(
+ file_in_root,
+ 1, // Truncate to 1 byte.
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_INVALID_OPERATION, error);
+}
+
+TEST_F(TruncateOperationTest, Extend) {
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ ResourceEntry src_entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(file_in_root, &src_entry));
+ const int64 file_size = src_entry.file_info().size();
+
+ FileError error = FILE_ERROR_FAILED;
+ operation_->Truncate(
+ file_in_root,
+ file_size + 10, // Extend to 10 bytes.
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ base::FilePath local_path;
+ error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::GetFile,
+ base::Unretained(cache()),
+ GetLocalId(file_in_root), &local_path),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_EQ(FILE_ERROR_OK, error);
+
+ // The local file should be truncated.
+ std::string content;
+ ASSERT_TRUE(base::ReadFileToString(local_path, &content));
+
+ EXPECT_EQ(file_size + 10, static_cast<int64>(content.size()));
+ // All trailing 10 bytes should be '\0'.
+ EXPECT_EQ(std::string(10, '\0'), content.substr(file_size));
+}
+
+} // namespace file_system
+} // namespace drive
diff --git a/components/drive/file_system_interface.cc b/components/drive/file_system_interface.cc
new file mode 100644
index 0000000..931b680
--- /dev/null
+++ b/components/drive/file_system_interface.cc
@@ -0,0 +1,20 @@
+// Copyright 2015 The Chromium 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 "components/drive/file_system_interface.h"
+
+namespace drive {
+
+MetadataSearchResult::MetadataSearchResult(
+ const base::FilePath& path,
+ bool is_directory,
+ const std::string& highlighted_base_name,
+ const std::string& md5)
+ : path(path),
+ is_directory(is_directory),
+ highlighted_base_name(highlighted_base_name),
+ md5(md5) {
+}
+
+} // namespace drive
diff --git a/components/drive/file_system_interface.h b/components/drive/file_system_interface.h
new file mode 100644
index 0000000..aa6b099
--- /dev/null
+++ b/components/drive/file_system_interface.h
@@ -0,0 +1,474 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_FILE_SYSTEM_INTERFACE_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_system_metadata.h"
+#include "components/drive/resource_metadata.h"
+#include "google_apis/drive/base_requests.h"
+#include "google_apis/drive/drive_api_requests.h"
+
+namespace drive {
+
+class FileSystemObserver;
+
+// Information about search result returned by Search Async callback.
+// This is data needed to create a file system entry that will be used by file
+// browser.
+struct SearchResultInfo {
+ SearchResultInfo(const base::FilePath& path, bool is_directory)
+ : path(path), is_directory(is_directory) {}
+
+ base::FilePath path;
+ bool is_directory;
+};
+
+// File path and its MD5 hash obtained from drive API.
+struct HashAndFilePath {
+ std::string hash;
+ base::FilePath path;
+};
+
+// Struct to represent a search result for SearchMetadata().
+struct MetadataSearchResult {
+ MetadataSearchResult(const base::FilePath& path,
+ bool is_directory,
+ const std::string& highlighted_base_name,
+ const std::string& md5);
+
+ // The two members are used to create FileEntry object.
+ base::FilePath path;
+ bool is_directory;
+
+ // The base name to be displayed in the UI. The parts matched the search
+ // query are highlighted with <b> tag. Meta characters are escaped like &lt;
+ //
+ // Why HTML? we could instead provide matched ranges using pairs of
+ // integers, but this is fragile as we'll eventually converting strings
+ // from UTF-8 (StringValue in base/values.h uses std::string) to UTF-16
+ // when sending strings from C++ to JavaScript.
+ //
+ // Why <b> instead of <strong>? Because <b> is shorter.
+ std::string highlighted_base_name;
+
+ // MD5 hash of the file.
+ std::string md5;
+};
+
+typedef std::vector<MetadataSearchResult> MetadataSearchResultVector;
+
+// Used to get a resource entry from the file system.
+// If |error| is not FILE_ERROR_OK, |entry_info| is set to NULL.
+typedef base::Callback<void(FileError error,
+ scoped_ptr<ResourceEntry> entry)>
+ GetResourceEntryCallback;
+
+// Used to get files from the file system.
+typedef base::Callback<void(FileError error,
+ const base::FilePath& file_path,
+ scoped_ptr<ResourceEntry> entry)> GetFileCallback;
+
+// Used to get file content from the file system.
+// If the file content is available in local cache, |local_file| is filled with
+// the path to the cache file. If the file content starts to be downloaded from
+// the server, |local_file| is empty.
+typedef base::Callback<void(FileError error,
+ const base::FilePath& local_file,
+ scoped_ptr<ResourceEntry> entry)>
+ GetFileContentInitializedCallback;
+
+// Used to get list of entries under a directory.
+typedef base::Callback<void(scoped_ptr<ResourceEntryVector> entries)>
+ ReadDirectoryEntriesCallback;
+
+// Used to get drive content search results.
+// If |error| is not FILE_ERROR_OK, |result_paths| is empty.
+typedef base::Callback<void(
+ FileError error,
+ const GURL& next_link,
+ scoped_ptr<std::vector<SearchResultInfo> > result_paths)> SearchCallback;
+
+// Callback for SearchMetadata(). On success, |error| is FILE_ERROR_OK, and
+// |result| contains the search result.
+typedef base::Callback<void(
+ FileError error,
+ scoped_ptr<MetadataSearchResultVector> result)> SearchMetadataCallback;
+
+// Callback for SearchByHashesCallback. On success, vector contains hash and
+// corresponding files. The vector can include multiple entries for one hash.
+typedef base::Callback<void(FileError, const std::vector<HashAndFilePath>&)>
+ SearchByHashesCallback;
+
+// Used to open files from the file system. |file_path| is the path on the local
+// file system for the opened file.
+// If |close_callback| is not null, it must be called when the
+// modification to the cache is done. Otherwise, Drive file system does not
+// pick up the file for uploading.
+// |close_callback| must not be called more than once.
+typedef base::Callback<void(FileError error,
+ const base::FilePath& file_path,
+ const base::Closure& close_callback)>
+ OpenFileCallback;
+
+// Used to get available space for the account from Drive.
+typedef base::Callback<void(FileError error,
+ int64 bytes_total,
+ int64 bytes_used)> GetAvailableSpaceCallback;
+
+// Used to get the url to the sharing dialog.
+typedef base::Callback<void(FileError error,
+ const GURL& share_url)> GetShareUrlCallback;
+
+// Used to get filesystem metadata.
+typedef base::Callback<void(const FileSystemMetadata&)>
+ GetFilesystemMetadataCallback;
+
+// Used to mark cached files mounted.
+typedef base::Callback<void(FileError error,
+ const base::FilePath& file_path)>
+ MarkMountedCallback;
+
+// Used to get file path.
+typedef base::Callback<void(FileError error, const base::FilePath& file_path)>
+ GetFilePathCallback;
+
+// Used to free space.
+typedef base::Callback<void(bool)> FreeDiskSpaceCallback;
+
+// Used for returning result of calculated cache size.
+typedef base::Callback<void(uint64_t)> EvictableCacheSizeCallback;
+
+// The mode of opening a file.
+enum OpenMode {
+ // Open the file if exists. If not, failed.
+ OPEN_FILE,
+
+ // Create a new file if not exists, and then open it. If exists, failed.
+ CREATE_FILE,
+
+ // Open the file if exists. If not, create a new file and then open it.
+ OPEN_OR_CREATE_FILE,
+};
+
+// Option enum to control eligible entries for SearchMetadata().
+// SEARCH_METADATA_ALL is the default to investigate all the entries.
+// SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS excludes the hosted documents.
+// SEARCH_METADATA_EXCLUDE_DIRECTORIES excludes the directories from the result.
+// SEARCH_METADATA_SHARED_WITH_ME targets only "shared-with-me" entries.
+// SEARCH_METADATA_OFFLINE targets only "offline" entries. This option can not
+// be used with other options.
+enum SearchMetadataOptions {
+ SEARCH_METADATA_ALL = 0,
+ SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS = 1,
+ SEARCH_METADATA_EXCLUDE_DIRECTORIES = 1 << 1,
+ SEARCH_METADATA_SHARED_WITH_ME = 1 << 2,
+ SEARCH_METADATA_OFFLINE = 1 << 3,
+};
+
+// Drive file system abstraction layer.
+// The interface is defined to make FileSystem mockable.
+class FileSystemInterface {
+ public:
+ virtual ~FileSystemInterface() {}
+
+ // Adds and removes the observer.
+ virtual void AddObserver(FileSystemObserver* observer) = 0;
+ virtual void RemoveObserver(FileSystemObserver* observer) = 0;
+
+ // Checks for updates on the server.
+ virtual void CheckForUpdates() = 0;
+
+ // Initiates transfer of |local_src_file_path| to |remote_dest_file_path|.
+ // |local_src_file_path| must be a file from the local file system.
+ // |remote_dest_file_path| is the virtual destination path within Drive file
+ // system.
+ //
+ // |callback| must not be null.
+ virtual void TransferFileFromLocalToRemote(
+ const base::FilePath& local_src_file_path,
+ const base::FilePath& remote_dest_file_path,
+ const FileOperationCallback& callback) = 0;
+
+ // Retrieves a file at the virtual path |file_path| on the Drive file system
+ // onto the cache, and mark it dirty. The local path to the cache file is
+ // returned to |callback|. After opening the file, both read and write
+ // on the file can be done with normal local file operations.
+ // If |mime_type| is set and the file is newly created, the mime type is
+ // set to the specified value. If |mime_type| is empty, it is guessed from
+ // |file_path|.
+ //
+ // |callback| must not be null.
+ virtual void OpenFile(const base::FilePath& file_path,
+ OpenMode open_mode,
+ const std::string& mime_type,
+ const OpenFileCallback& callback) = 0;
+
+ // Copies |src_file_path| to |dest_file_path| on the file system.
+ // |src_file_path| can be a hosted document (see limitations below).
+ // |dest_file_path| is expected to be of the same type of |src_file_path|
+ // (i.e. if |src_file_path| is a file, |dest_file_path| will be created as
+ // a file).
+ // If |preserve_last_modified| is set to true, the last modified time will be
+ // preserved. This feature is only supported on Drive API v2 protocol because
+ // GData WAPI doesn't support updating modification time.
+ //
+ // This method also has the following assumptions/limitations that may be
+ // relaxed or addressed later:
+ // - |src_file_path| cannot be a regular file (i.e. non-hosted document)
+ // or a directory.
+ // - |dest_file_path| must not exist.
+ // - The parent of |dest_file_path| must already exist.
+ //
+ // The file entries represented by |src_file_path| and the parent directory
+ // of |dest_file_path| need to be present in the in-memory representation
+ // of the file system.
+ //
+ // |callback| must not be null.
+ virtual void Copy(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ bool preserve_last_modified,
+ const FileOperationCallback& callback) = 0;
+
+ // Moves |src_file_path| to |dest_file_path| on the file system.
+ // |src_file_path| can be a file (regular or hosted document) or a directory.
+ // |dest_file_path| is expected to be of the same type of |src_file_path|
+ // (i.e. if |src_file_path| is a file, |dest_file_path| will be created as
+ // a file).
+ //
+ // This method also has the following assumptions/limitations that may be
+ // relaxed or addressed later:
+ // - |dest_file_path| must not exist.
+ // - The parent of |dest_file_path| must already exist.
+ //
+ // The file entries represented by |src_file_path| and the parent directory
+ // of |dest_file_path| need to be present in the in-memory representation
+ // of the file system.
+ //
+ // |callback| must not be null.
+ virtual void Move(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ const FileOperationCallback& callback) = 0;
+
+ // Removes |file_path| from the file system. If |is_recursive| is set and
+ // |file_path| represents a directory, we will also delete all of its
+ // contained children elements. The file entry represented by |file_path|
+ // needs to be present in in-memory representation of the file system that
+ // in order to be removed.
+ //
+ // |callback| must not be null.
+ virtual void Remove(const base::FilePath& file_path,
+ bool is_recursive,
+ const FileOperationCallback& callback) = 0;
+
+ // Creates new directory under |directory_path|. If |is_exclusive| is true,
+ // an error is raised in case a directory is already present at the
+ // |directory_path|. If |is_recursive| is true, the call creates parent
+ // directories as needed just like mkdir -p does.
+ //
+ // |callback| must not be null.
+ virtual void CreateDirectory(const base::FilePath& directory_path,
+ bool is_exclusive,
+ bool is_recursive,
+ const FileOperationCallback& callback) = 0;
+
+ // Creates a file at |file_path|. If the flag |is_exclusive| is true, an
+ // error is raised when a file already exists at the path. It is
+ // an error if a directory or a hosted document is already present at the
+ // path, or the parent directory of the path is not present yet.
+ // If |mime_type| is set and the file is newly created, the mime type is
+ // set to the specified value. If |mime_type| is empty, it is guessed from
+ // |file_path|.
+ //
+ // |callback| must not be null.
+ virtual void CreateFile(const base::FilePath& file_path,
+ bool is_exclusive,
+ const std::string& mime_type,
+ const FileOperationCallback& callback) = 0;
+
+ // Touches the file at |file_path| by updating the timestamp to
+ // |last_access_time| and |last_modified_time|.
+ // Upon completion, invokes |callback|.
+ // Note that, differently from unix touch command, this doesn't create a file
+ // if the target file doesn't exist.
+ //
+ // |last_access_time|, |last_modified_time| and |callback| must not be null.
+ virtual void TouchFile(const base::FilePath& file_path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const FileOperationCallback& callback) = 0;
+
+ // Truncates the file content at |file_path| to the |length|.
+ //
+ // |callback| must not be null.
+ virtual void TruncateFile(const base::FilePath& file_path,
+ int64 length,
+ const FileOperationCallback& callback) = 0;
+
+ // Pins a file at |file_path|.
+ //
+ // |callback| must not be null.
+ virtual void Pin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) = 0;
+
+ // Unpins a file at |file_path|.
+ //
+ // |callback| must not be null.
+ virtual void Unpin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) = 0;
+
+ // Makes sure that |file_path| in the file system is available in the local
+ // cache. If the file is not cached, the file will be downloaded. The entry
+ // needs to be present in the file system.
+ //
+ // Returns the cache path and entry info to |callback|. It must not be null.
+ virtual void GetFile(const base::FilePath& file_path,
+ const GetFileCallback& callback) = 0;
+
+ // Makes sure that |file_path| in the file system is available in the local
+ // cache, and mark it as dirty. The next modification to the cache file is
+ // watched and is automatically uploaded to the server. If the entry is not
+ // present in the file system, it is created.
+ //
+ // Returns the cache path and entry info to |callback|. It must not be null.
+ virtual void GetFileForSaving(const base::FilePath& file_path,
+ const GetFileCallback& callback) = 0;
+
+ // Gets a file by the given |file_path| and returns a closure to cancel the
+ // task.
+ // Calls |initialized_callback| when either:
+ // 1) The cached file (or JSON file for hosted file) is found, or
+ // 2) Starting to download the file from drive server.
+ // In case of 2), the given FilePath is empty, and |get_content_callback| is
+ // called repeatedly with downloaded content following the
+ // |initialized_callback| invocation.
+ // |completion_callback| is invoked if an error is found, or the operation
+ // is successfully done.
+ // |initialized_callback|, |get_content_callback| and |completion_callback|
+ // must not be null.
+ virtual base::Closure GetFileContent(
+ const base::FilePath& file_path,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback) = 0;
+
+ // Finds an entry (a file or a directory) by |file_path|. This call will also
+ // retrieve and refresh file system content from server and disk cache.
+ //
+ // |callback| must not be null.
+ virtual void GetResourceEntry(const base::FilePath& file_path,
+ const GetResourceEntryCallback& callback) = 0;
+
+ // Finds and reads a directory by |file_path|. This call will also retrieve
+ // and refresh file system content from server and disk cache.
+ // |entries_callback| can be a null callback when not interested in entries.
+ //
+ // |completion_callback| must not be null.
+ virtual void ReadDirectory(
+ const base::FilePath& file_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback) = 0;
+
+ // Does server side content search for |search_query|.
+ // If |next_link| is set, this is the search result url that will be
+ // fetched. Search results will be returned as a list of results'
+ // |SearchResultInfo| structs, which contains file's path and is_directory
+ // flag.
+ //
+ // |callback| must not be null.
+ virtual void Search(const std::string& search_query,
+ const GURL& next_link,
+ const SearchCallback& callback) = 0;
+
+ // Searches the local resource metadata, and returns the entries
+ // |at_most_num_matches| that contain |query| in their base names. Search is
+ // done in a case-insensitive fashion. The eligible entries are selected based
+ // on the given |options|, which is a bit-wise OR of SearchMetadataOptions.
+ // SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS will be automatically added based
+ // on the preference. |callback| must not be null. Must be called on UI
+ // thread. Empty |query| matches any base name. i.e. returns everything.
+ virtual void SearchMetadata(const std::string& query,
+ int options,
+ int at_most_num_matches,
+ const SearchMetadataCallback& callback) = 0;
+
+ // Searches the local resource metadata, and returns the entries that have the
+ // given |hashes|. The list of resource entries are passed to |callback|. The
+ // item of the list can be null if the corresponding file is not found.
+ // |callback| must not be null.
+ virtual void SearchByHashes(const std::set<std::string>& hashes,
+ const SearchByHashesCallback& callback) = 0;
+
+ // Fetches the user's Account Metadata to find out current quota information
+ // and returns it to the callback.
+ virtual void GetAvailableSpace(const GetAvailableSpaceCallback& callback) = 0;
+
+ // Fetches the url to the sharing dialog to be embedded in |embed_origin|,
+ // for the specified file or directory. |callback| must not be null.
+ virtual void GetShareUrl(
+ const base::FilePath& file_path,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback) = 0;
+
+ // Returns miscellaneous metadata of the file system like the largest
+ // timestamp. Used in chrome:drive-internals. |callback| must not be null.
+ virtual void GetMetadata(
+ const GetFilesystemMetadataCallback& callback) = 0;
+
+ // Marks the cached file as mounted, and runs |callback| upon completion.
+ // If succeeded, the cached file path will be passed to the |callback|.
+ // |callback| must not be null.
+ virtual void MarkCacheFileAsMounted(const base::FilePath& drive_file_path,
+ const MarkMountedCallback& callback) = 0;
+
+ // Marks the cached file as unmounted, and runs |callback| upon completion.
+ // Note that this method expects that the |cached_file_path| is the path
+ // returned by MarkCacheFileAsMounted().
+ // |callback| must not be null.
+ virtual void MarkCacheFileAsUnmounted(
+ const base::FilePath& cache_file_path,
+ const FileOperationCallback& callback) = 0;
+
+ // Adds permission as |role| to |email| for the entry at |drive_file_path|.
+ // |callback| must not be null.
+ virtual void AddPermission(const base::FilePath& drive_file_path,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const FileOperationCallback& callback) = 0;
+
+ // Sets the |key| property on the file or directory at |drive_file_path| with
+ // the specified |visibility|. If already exists, then it will be overwritten.
+ virtual void SetProperty(const base::FilePath& drive_file_path,
+ google_apis::drive::Property::Visibility visibility,
+ const std::string& key,
+ const std::string& value,
+ const FileOperationCallback& callback) = 0;
+
+ // Resets local data.
+ virtual void Reset(const FileOperationCallback& callback) = 0;
+
+ // Finds a path of an entry (a file or a directory) by |resource_id|.
+ virtual void GetPathFromResourceId(const std::string& resource_id,
+ const GetFilePathCallback& callback) = 0;
+
+ // Free drive caches if needed to secure given available spaces. |callback|
+ // takes whether given bytes are available or not.
+ virtual void FreeDiskSpaceIfNeededFor(
+ int64 num_bytes,
+ const FreeDiskSpaceCallback& callback) = 0;
+
+ // Calculates evictable cache size.
+ // |callback| must not be null.
+ virtual void CalculateEvictableCacheSize(
+ const EvictableCacheSizeCallback& callback) = 0;
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_INTERFACE_H_
diff --git a/components/drive/file_system_metadata.cc b/components/drive/file_system_metadata.cc
new file mode 100644
index 0000000..7c53d53
--- /dev/null
+++ b/components/drive/file_system_metadata.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_system_metadata.h"
+
+namespace drive {
+
+FileSystemMetadata::FileSystemMetadata()
+ : largest_changestamp(0),
+ refreshing(false),
+ last_update_check_error(FILE_ERROR_OK) {
+}
+
+FileSystemMetadata::~FileSystemMetadata() {
+}
+
+} // namespace drive
diff --git a/components/drive/file_system_metadata.h b/components/drive/file_system_metadata.h
new file mode 100644
index 0000000..199f106
--- /dev/null
+++ b/components/drive/file_system_metadata.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_FILE_SYSTEM_METADATA_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_METADATA_H_
+
+#include "base/basictypes.h"
+#include "base/time/time.h"
+#include "components/drive/file_errors.h"
+
+namespace drive {
+
+// Metadata of FileSystem. Used by FileSystem::GetMetadata().
+struct FileSystemMetadata {
+ FileSystemMetadata();
+ ~FileSystemMetadata();
+
+ // The largest changestamp that the file system holds (may be different
+ // from the one on the server)
+ int64 largest_changestamp;
+
+ // True if the resource metadata is now being fetched from the server.
+ bool refreshing;
+
+ // Time of the last update check.
+ base::Time last_update_check_time;
+
+ // Error code of the last update check.
+ FileError last_update_check_error;
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_METADATA_H_
diff --git a/components/drive/file_write_watcher.cc b/components/drive/file_write_watcher.cc
new file mode 100644
index 0000000..c1b4815
--- /dev/null
+++ b/components/drive/file_write_watcher.cc
@@ -0,0 +1,207 @@
+// 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 "components/drive/file_write_watcher.h"
+
+#include <map>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path_watcher.h"
+#include "base/stl_util.h"
+#include "base/timer/timer.h"
+#include "google_apis/drive/task_util.h"
+
+namespace drive {
+namespace internal {
+
+namespace {
+const int64 kWriteEventDelayInSeconds = 5;
+} // namespace
+
+// base::FileWatcher needs to live in a thread that is allowed to do File IO
+// and has a TYPE_IO message loop: that is, FILE thread. This class bridges the
+// UI thread and FILE thread, and does all the main tasks in the FILE thread.
+class FileWriteWatcher::FileWriteWatcherImpl {
+ public:
+ explicit FileWriteWatcherImpl(base::SingleThreadTaskRunner* file_task_runner);
+
+ // Forwards the call to DestoryOnFileThread(). This method must be used to
+ // destruct the instance.
+ void Destroy();
+
+ // Forwards the call to StartWatchOnFileThread(). |on_start_callback| is
+ // called back on the caller (UI) thread when the watch has started.
+ // |on_write_callback| is called when a write has happened to the path.
+ void StartWatch(const base::FilePath& path,
+ const StartWatchCallback& on_start_callback,
+ const base::Closure& on_write_callback);
+
+ void set_delay(base::TimeDelta delay) { delay_ = delay; }
+
+ private:
+ ~FileWriteWatcherImpl();
+
+ void DestroyOnFileThread();
+
+ void StartWatchOnFileThread(const base::FilePath& path,
+ const StartWatchCallback& on_start_callback,
+ const base::Closure& on_write_callback);
+
+ void OnWriteEvent(const base::FilePath& path, bool error);
+
+ void InvokeCallback(const base::FilePath& path);
+
+ struct PathWatchInfo {
+ std::vector<base::Closure> on_write_callbacks;
+ base::FilePathWatcher watcher;
+ base::Timer timer;
+
+ explicit PathWatchInfo(const base::Closure& on_write_callback)
+ : on_write_callbacks(1, on_write_callback),
+ timer(false /* retain_closure_on_reset */, false /* is_repeating */) {
+ }
+ };
+
+ base::TimeDelta delay_;
+ std::map<base::FilePath, PathWatchInfo*> watchers_;
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
+
+ base::ThreadChecker thread_checker_;
+
+ // Note: This should remain the last member so it'll be destroyed and
+ // invalidate its weak pointers before any other members are destroyed.
+ base::WeakPtrFactory<FileWriteWatcherImpl> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(FileWriteWatcherImpl);
+};
+
+FileWriteWatcher::FileWriteWatcherImpl::FileWriteWatcherImpl(
+ base::SingleThreadTaskRunner* file_task_runner)
+ : delay_(base::TimeDelta::FromSeconds(kWriteEventDelayInSeconds)),
+ file_task_runner_(file_task_runner),
+ weak_ptr_factory_(this) {
+}
+
+void FileWriteWatcher::FileWriteWatcherImpl::Destroy() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Just forwarding the call to FILE thread.
+ file_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&FileWriteWatcherImpl::DestroyOnFileThread,
+ base::Unretained(this)));
+}
+
+void FileWriteWatcher::FileWriteWatcherImpl::StartWatch(
+ const base::FilePath& path,
+ const StartWatchCallback& on_start_callback,
+ const base::Closure& on_write_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Forwarding the call to FILE thread and relaying the |callback|.
+ file_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&FileWriteWatcherImpl::StartWatchOnFileThread,
+ base::Unretained(this), path,
+ google_apis::CreateRelayCallback(on_start_callback),
+ google_apis::CreateRelayCallback(on_write_callback)));
+}
+
+FileWriteWatcher::FileWriteWatcherImpl::~FileWriteWatcherImpl() {
+ DCHECK(file_task_runner_->BelongsToCurrentThread());
+
+ STLDeleteContainerPairSecondPointers(watchers_.begin(), watchers_.end());
+}
+
+void FileWriteWatcher::FileWriteWatcherImpl::DestroyOnFileThread() {
+ DCHECK(file_task_runner_->BelongsToCurrentThread());
+
+ delete this;
+}
+
+void FileWriteWatcher::FileWriteWatcherImpl::StartWatchOnFileThread(
+ const base::FilePath& path,
+ const StartWatchCallback& on_start_callback,
+ const base::Closure& on_write_callback) {
+ DCHECK(file_task_runner_->BelongsToCurrentThread());
+
+ std::map<base::FilePath, PathWatchInfo*>::iterator it = watchers_.find(path);
+ if (it != watchers_.end()) {
+ // We are already watching the path.
+ on_start_callback.Run(true);
+ it->second->on_write_callbacks.push_back(on_write_callback);
+ return;
+ }
+
+ // Start watching |path|.
+ scoped_ptr<PathWatchInfo> info(new PathWatchInfo(on_write_callback));
+ bool ok = info->watcher.Watch(
+ path,
+ false, // recursive
+ base::Bind(&FileWriteWatcherImpl::OnWriteEvent,
+ weak_ptr_factory_.GetWeakPtr()));
+ watchers_[path] = info.release();
+ on_start_callback.Run(ok);
+}
+
+void FileWriteWatcher::FileWriteWatcherImpl::OnWriteEvent(
+ const base::FilePath& path,
+ bool error) {
+ DCHECK(file_task_runner_->BelongsToCurrentThread());
+
+ if (error)
+ return;
+
+ std::map<base::FilePath, PathWatchInfo*>::iterator it = watchers_.find(path);
+ DCHECK(it != watchers_.end());
+
+ // Heuristics for detecting the end of successive write operations.
+ // Delay running on_write_event_callback by |delay_| time, and if OnWriteEvent
+ // is called again in the period, the timer is reset. In other words, we
+ // invoke callback when |delay_| has passed after the last OnWriteEvent().
+ it->second->timer.Start(FROM_HERE,
+ delay_,
+ base::Bind(&FileWriteWatcherImpl::InvokeCallback,
+ weak_ptr_factory_.GetWeakPtr(),
+ path));
+}
+
+void FileWriteWatcher::FileWriteWatcherImpl::InvokeCallback(
+ const base::FilePath& path) {
+ DCHECK(file_task_runner_->BelongsToCurrentThread());
+
+ std::map<base::FilePath, PathWatchInfo*>::iterator it = watchers_.find(path);
+ DCHECK(it != watchers_.end());
+
+ std::vector<base::Closure> callbacks;
+ callbacks.swap(it->second->on_write_callbacks);
+ delete it->second;
+ watchers_.erase(it);
+
+ for (size_t i = 0; i < callbacks.size(); ++i)
+ callbacks[i].Run();
+}
+
+FileWriteWatcher::FileWriteWatcher(
+ base::SingleThreadTaskRunner* file_task_runner)
+ : watcher_impl_(new FileWriteWatcherImpl(file_task_runner)) {
+}
+
+FileWriteWatcher::~FileWriteWatcher() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void FileWriteWatcher::StartWatch(const base::FilePath& file_path,
+ const StartWatchCallback& on_start_callback,
+ const base::Closure& on_write_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ watcher_impl_->StartWatch(file_path, on_start_callback, on_write_callback);
+}
+
+void FileWriteWatcher::DisableDelayForTesting() {
+ watcher_impl_->set_delay(base::TimeDelta());
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/file_write_watcher.h b/components/drive/file_write_watcher.h
new file mode 100644
index 0000000..719a8b9
--- /dev/null
+++ b/components/drive/file_write_watcher.h
@@ -0,0 +1,66 @@
+// 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 COMPONENTS_DRIVE_FILE_WRITE_WATCHER_H_
+#define COMPONENTS_DRIVE_FILE_WRITE_WATCHER_H_
+
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/file_system_core_util.h"
+
+namespace base {
+class FilePath;
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace drive {
+
+namespace internal {
+
+typedef base::Callback<void(bool)> StartWatchCallback;
+
+// The class watches modification to Drive files in the cache directory.
+// This is used for returning a local writable snapshot of Drive files from the
+// Save-As file dialog, so that the callers of the dialog can save to Drive
+// without any special handling about Drive.
+class FileWriteWatcher {
+ public:
+ explicit FileWriteWatcher(base::SingleThreadTaskRunner* file_task_runner);
+ ~FileWriteWatcher();
+
+ // Starts watching the modification to |path|. When it successfully started
+ // watching, it runs |on_start_callback| by passing true as the argument.
+ // Or if it failed, the callback is run with false.
+ // Detected modification is notified by calling |on_write_callback|.
+ //
+ // Currently, the modification is watched in "one-shot" manner. That is, once
+ // a modification is notified, the watch is deactivated for freeing system
+ // resources. As a heuristic to capture the real end of write operations that
+ // might be done by several chunked writes, the notification is fired after
+ // 5 seconds has passed after the last write operation is detected.
+ //
+ // TODO(kinaba): investigate the possibility to continuously watch the whole
+ // cache directory. http://crbug.com/269424
+ void StartWatch(const base::FilePath& path,
+ const StartWatchCallback& on_start_callback,
+ const base::Closure& on_write_callback);
+
+ // For testing purpose, stops inserting delay between the write detection and
+ // notification to the |on_write_callback|.
+ void DisableDelayForTesting();
+
+ private:
+ class FileWriteWatcherImpl;
+ scoped_ptr<FileWriteWatcherImpl, util::DestroyHelper> watcher_impl_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileWriteWatcher);
+};
+
+} // namespace internal
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_WRITE_WATCHER_H_
diff --git a/components/drive/file_write_watcher_unittest.cc b/components/drive/file_write_watcher_unittest.cc
new file mode 100644
index 0000000..abd9d51
--- /dev/null
+++ b/components/drive/file_write_watcher_unittest.cc
@@ -0,0 +1,122 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/file_write_watcher.h"
+
+#include <set>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace internal {
+
+namespace {
+
+class TestObserver {
+ public:
+ // After all the resource_ids in |expected_upload| are notified for the
+ // need of uploading, runs |quit_closure|. Also checks that each id is
+ // notified only once.
+ TestObserver(const std::set<std::string>& expected_upload,
+ const base::Closure& quit_closure)
+ : expected_upload_(expected_upload),
+ quit_closure_(quit_closure) {
+ }
+
+ void OnWrite(const std::string& id) {
+ EXPECT_EQ(1U, expected_upload_.count(id)) << id;
+ expected_upload_.erase(id);
+ if (expected_upload_.empty())
+ quit_closure_.Run();
+ }
+
+ private:
+ std::set<std::string> expected_upload_;
+ base::Closure quit_closure_;
+};
+
+// Writes something on the file at |path|.
+void WriteSomethingAfterStartWatch(const base::FilePath& path,
+ bool watch_success) {
+ EXPECT_TRUE(watch_success) << path.value();
+
+ const char kDummy[] = "hello";
+ ASSERT_TRUE(base::WriteFile(path, kDummy, arraysize(kDummy)));
+}
+
+class FileWriteWatcherTest : public testing::Test {
+ protected:
+ // The test requires UI thread (FileWriteWatcher DCHECKs that its public
+ // interface is running on UI thread) and FILE thread (Linux version of
+ // base::FilePathWatcher needs to live on an IOAllowed thread with TYPE_IO,
+ // which is FILE thread in the production environment).
+ //
+ // By using the IO_MAINLOOP test thread bundle, the main thread is used
+ // both as UI and FILE thread, with TYPE_IO message loop.
+ FileWriteWatcherTest()
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {
+ }
+
+ void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+ base::FilePath GetTempPath(const std::string& name) {
+ return temp_dir_.path().Append(name);
+ }
+
+ private:
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir temp_dir_;
+};
+
+} // namespace
+
+TEST_F(FileWriteWatcherTest, WatchThreeFiles) {
+ std::set<std::string> expected;
+ expected.insert("1");
+ expected.insert("2");
+ expected.insert("3");
+
+ base::RunLoop loop;
+ TestObserver observer(expected, loop.QuitClosure());
+
+ // Set up the watcher.
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner =
+ content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::FILE);
+ FileWriteWatcher watcher(file_task_runner.get());
+ watcher.DisableDelayForTesting();
+
+ // Start watching and running.
+ base::FilePath path1 = GetTempPath("foo.txt");
+ base::FilePath path2 = GetTempPath("bar.png");
+ base::FilePath path3 = GetTempPath("buz.doc");
+ base::FilePath path4 = GetTempPath("mya.mp3");
+ watcher.StartWatch(
+ path1,
+ base::Bind(&WriteSomethingAfterStartWatch, path1),
+ base::Bind(&TestObserver::OnWrite, base::Unretained(&observer), "1"));
+ watcher.StartWatch(
+ path2,
+ base::Bind(&WriteSomethingAfterStartWatch, path2),
+ base::Bind(&TestObserver::OnWrite, base::Unretained(&observer), "2"));
+ watcher.StartWatch(
+ path3,
+ base::Bind(&WriteSomethingAfterStartWatch, path3),
+ base::Bind(&TestObserver::OnWrite, base::Unretained(&observer), "3"));
+
+ // Unwatched write. It shouldn't be notified.
+ WriteSomethingAfterStartWatch(path4, true);
+
+ // The loop should quit if all the three paths are notified to be written.
+ loop.Run();
+}
+
+} // namespace internal
+} // namespace drive