summaryrefslogtreecommitdiffstats
path: root/components/drive
diff options
context:
space:
mode:
authorlukasza <lukasza@chromium.org>2015-08-31 11:57:18 -0700
committerCommit bot <commit-bot@chromium.org>2015-08-31 18:57:52 +0000
commit2e964692340581cc6349a334d0f8ebbe601944df (patch)
tree3ddfb37629534ca0f475c8da4b8ccc1c2c3d145f /components/drive
parent47d95270f67843973794711eb34894cb0a03576d (diff)
downloadchromium_src-2e964692340581cc6349a334d0f8ebbe601944df.zip
chromium_src-2e964692340581cc6349a334d0f8ebbe601944df.tar.gz
chromium_src-2e964692340581cc6349a334d0f8ebbe601944df.tar.bz2
Move chrome/browser/chromeos/drive/file_system.cc (+deps) into components/drive.
Previous CL (crrev.com/1318543003) has moved the chrome/browser/chromeos/drive/file_system/ directory (and its dependencies) into components/drive. This CL moves chrome/browser/chromeos/drive/file_system.cc file (and its dependencies) into components/drive. Files moved from chrome/browser/chromeos/drive into components/drive: - directory_loader* - dummy_file_system* - fake_file_system* - file_system* - file_system_observer.h - remove_stale_cache_files* - search_metadata* - sync/* - sync_client* 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 Review URL: https://codereview.chromium.org/1314803004 Cr-Commit-Position: refs/heads/master@{#346429}
Diffstat (limited to 'components/drive')
-rw-r--r--components/drive/BUILD.gn21
-rw-r--r--components/drive/DEPS29
-rw-r--r--components/drive/directory_loader.cc571
-rw-r--r--components/drive/directory_loader.h150
-rw-r--r--components/drive/directory_loader_unittest.cc252
-rw-r--r--components/drive/dummy_file_system.cc17
-rw-r--r--components/drive/dummy_file_system.h111
-rw-r--r--components/drive/fake_file_system.cc420
-rw-r--r--components/drive/fake_file_system.h197
-rw-r--r--components/drive/fake_file_system_unittest.cc156
-rw-r--r--components/drive/file_system.cc1060
-rw-r--r--components/drive/file_system.h311
-rw-r--r--components/drive/file_system_observer.h41
-rw-r--r--components/drive/file_system_unittest.cc1064
-rw-r--r--components/drive/remove_stale_cache_files.cc34
-rw-r--r--components/drive/remove_stale_cache_files.h22
-rw-r--r--components/drive/remove_stale_cache_files_unittest.cc115
-rw-r--r--components/drive/search_metadata.cc335
-rw-r--r--components/drive/search_metadata.h65
-rw-r--r--components/drive/search_metadata_unittest.cc500
-rw-r--r--components/drive/sync/entry_revert_performer.cc178
-rw-r--r--components/drive/sync/entry_revert_performer.h93
-rw-r--r--components/drive/sync/entry_revert_performer_unittest.cc151
-rw-r--r--components/drive/sync/entry_update_performer.cc450
-rw-r--r--components/drive/sync/entry_update_performer.h106
-rw-r--r--components/drive/sync/entry_update_performer_unittest.cc658
-rw-r--r--components/drive/sync/remove_performer.cc255
-rw-r--r--components/drive/sync/remove_performer.h114
-rw-r--r--components/drive/sync/remove_performer_unittest.cc199
-rw-r--r--components/drive/sync_client.cc491
-rw-r--r--components/drive/sync_client.h199
-rw-r--r--components/drive/sync_client_unittest.cc523
32 files changed, 8879 insertions, 9 deletions
diff --git a/components/drive/BUILD.gn b/components/drive/BUILD.gn
index 4567878..f4596bd 100644
--- a/components/drive/BUILD.gn
+++ b/components/drive/BUILD.gn
@@ -11,6 +11,8 @@ source_set("drive") {
"change_list_loader_observer.h",
"change_list_processor.cc",
"change_list_processor.h",
+ "directory_loader.cc",
+ "directory_loader.h",
"drive_api_util.cc",
"drive_api_util.h",
"drive_app_registry.cc",
@@ -31,6 +33,8 @@ source_set("drive") {
"file_change.h",
"file_errors.cc",
"file_errors.h",
+ "file_system.cc",
+ "file_system.h",
"file_system/copy_operation.cc",
"file_system/copy_operation.h",
"file_system/create_directory_operation.cc",
@@ -63,6 +67,7 @@ source_set("drive") {
"file_system_interface.h",
"file_system_metadata.cc",
"file_system_metadata.h",
+ "file_system_observer.h",
"file_write_watcher.cc",
"file_write_watcher.h",
"job_list.cc",
@@ -73,16 +78,28 @@ source_set("drive") {
"job_scheduler.h",
"local_file_reader.cc",
"local_file_reader.h",
+ "remove_stale_cache_files.cc",
+ "remove_stale_cache_files.h",
"resource_entry_conversion.cc",
"resource_entry_conversion.h",
"resource_metadata.cc",
"resource_metadata.h",
"resource_metadata_storage.cc",
"resource_metadata_storage.h",
+ "search_metadata.cc",
+ "search_metadata.h",
"service/drive_api_service.cc",
"service/drive_api_service.h",
"service/drive_service_interface.cc",
"service/drive_service_interface.h",
+ "sync/entry_revert_performer.cc",
+ "sync/entry_revert_performer.h",
+ "sync/entry_update_performer.cc",
+ "sync/entry_update_performer.h",
+ "sync/remove_performer.cc",
+ "sync/remove_performer.h",
+ "sync_client.cc",
+ "sync_client.h",
]
deps = [
"//base:base",
@@ -113,6 +130,10 @@ source_set("test_support") {
sources = [
"drive_test_util.cc",
"drive_test_util.h",
+ "dummy_file_system.cc",
+ "dummy_file_system.h",
+ "fake_file_system.cc",
+ "fake_file_system.h",
"fake_free_disk_space_getter.cc",
"fake_free_disk_space_getter.h",
"service/dummy_drive_service.cc",
diff --git a/components/drive/DEPS b/components/drive/DEPS
index 9f76475..48aeee1 100644
--- a/components/drive/DEPS
+++ b/components/drive/DEPS
@@ -23,11 +23,14 @@ specific_include_rules = {
r"|create_file_operation_unittest\.cc"
r"|download_operation_unittest\.cc"
r"|drive_test_util\.h"
+ r"|entry_revert_performer_unittest\.cc"
+ r"|entry_update_performer_unittest\.cc"
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"|remove_performer_unittest\.cc"
r"|search_operation_unittest\.cc"
r"|set_property_operation_unittest\.cc"
r"|truncate_operation_unittest\.cc"
@@ -37,22 +40,30 @@ specific_include_rules = {
# The following test dependencies should be removed to fully componentize this
# directory. crbug.com/498951
- r"(change_list_loader_unittest.cc"
- 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"(change_list_loader_unittest\.cc"
+ r"|change_list_processor_unittest\.cc"
+ r"|directory_loader_unittest\.cc"
+ r"|fake_file_system_unittest\.cc"
+ r"|file_cache_unittest\.cc"
+ r"|file_system_core_util_unittest\.cc"
+ r"|file_system_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"|remove_stale_cache_files_unittest\.cc"
+ r"|resource_metadata_storage_unittest\.cc"
+ r"|resource_metadata_unittest\.cc"
+ r"|search_metadata_unittest\.cc"
+ r"|sync_client_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"(fake_file_system\.cc"
+ r"|file_system_unittest.cc"
+ r"|file_write_watcher_unittest\.cc"
r"|get_file_for_saving_operation_unittest\.cc"
r"|operation_test_base\.cc"
r")": [
diff --git a/components/drive/directory_loader.cc b/components/drive/directory_loader.cc
new file mode 100644
index 0000000..cd0f049
--- /dev/null
+++ b/components/drive/directory_loader.cc
@@ -0,0 +1,571 @@
+// 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/directory_loader.h"
+
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "components/drive/change_list_loader.h"
+#include "components/drive/change_list_loader_observer.h"
+#include "components/drive/change_list_processor.h"
+#include "components/drive/event_logger.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_parser.h"
+#include "url/gurl.h"
+
+namespace drive {
+namespace internal {
+
+namespace {
+
+// Minimum changestamp gap required to start loading directory.
+const int kMinimumChangestampGap = 50;
+
+FileError CheckLocalState(ResourceMetadata* resource_metadata,
+ const google_apis::AboutResource& about_resource,
+ const std::string& local_id,
+ ResourceEntry* entry,
+ int64* local_changestamp) {
+ // Fill My Drive resource ID.
+ ResourceEntry mydrive;
+ FileError error = resource_metadata->GetResourceEntryByPath(
+ util::GetDriveMyDriveRootPath(), &mydrive);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ if (mydrive.resource_id().empty()) {
+ mydrive.set_resource_id(about_resource.root_folder_id());
+ error = resource_metadata->RefreshEntry(mydrive);
+ if (error != FILE_ERROR_OK)
+ return error;
+ }
+
+ // Get entry.
+ error = resource_metadata->GetResourceEntryById(local_id, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ // Get the local changestamp.
+ return resource_metadata->GetLargestChangestamp(local_changestamp);
+}
+
+FileError UpdateChangestamp(ResourceMetadata* resource_metadata,
+ const DirectoryFetchInfo& directory_fetch_info,
+ base::FilePath* directory_path) {
+ // Update the directory changestamp.
+ ResourceEntry directory;
+ FileError error = resource_metadata->GetResourceEntryById(
+ directory_fetch_info.local_id(), &directory);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ if (!directory.file_info().is_directory())
+ return FILE_ERROR_NOT_A_DIRECTORY;
+
+ directory.mutable_directory_specific_info()->set_changestamp(
+ directory_fetch_info.changestamp());
+ error = resource_metadata->RefreshEntry(directory);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ // Get the directory path.
+ return resource_metadata->GetFilePath(directory_fetch_info.local_id(),
+ directory_path);
+}
+
+} // namespace
+
+struct DirectoryLoader::ReadDirectoryCallbackState {
+ ReadDirectoryEntriesCallback entries_callback;
+ FileOperationCallback completion_callback;
+ std::set<std::string> sent_entry_names;
+};
+
+// Fetches the resource entries in the directory with |directory_resource_id|.
+class DirectoryLoader::FeedFetcher {
+ public:
+ FeedFetcher(DirectoryLoader* loader,
+ const DirectoryFetchInfo& directory_fetch_info,
+ const std::string& root_folder_id)
+ : loader_(loader),
+ directory_fetch_info_(directory_fetch_info),
+ root_folder_id_(root_folder_id),
+ weak_ptr_factory_(this) {
+ }
+
+ ~FeedFetcher() {
+ }
+
+ void Run(const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ DCHECK(!directory_fetch_info_.resource_id().empty());
+
+ // Remember the time stamp for usage stats.
+ start_time_ = base::TimeTicks::Now();
+
+ loader_->scheduler_->GetFileListInDirectory(
+ directory_fetch_info_.resource_id(),
+ base::Bind(&FeedFetcher::OnFileListFetched,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ }
+
+ private:
+ void OnFileListFetched(const FileOperationCallback& callback,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileList> file_list) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileError error = GDataToFileError(status);
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ DCHECK(file_list);
+ scoped_ptr<ChangeList> change_list(new ChangeList(*file_list));
+ GURL next_url = file_list->next_link();
+
+ ResourceEntryVector* entries = new ResourceEntryVector;
+ loader_->loader_controller_->ScheduleRun(base::Bind(
+ base::IgnoreResult(
+ &base::PostTaskAndReplyWithResult<FileError, FileError>),
+ loader_->blocking_task_runner_,
+ FROM_HERE,
+ base::Bind(&ChangeListProcessor::RefreshDirectory,
+ loader_->resource_metadata_,
+ directory_fetch_info_,
+ base::Passed(&change_list),
+ entries),
+ base::Bind(&FeedFetcher::OnDirectoryRefreshed,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ next_url,
+ base::Owned(entries))));
+ }
+
+ void OnDirectoryRefreshed(
+ const FileOperationCallback& callback,
+ const GURL& next_url,
+ const std::vector<ResourceEntry>* refreshed_entries,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ loader_->SendEntries(directory_fetch_info_.local_id(), *refreshed_entries);
+
+ if (!next_url.is_empty()) {
+ // There is the remaining result so fetch it.
+ loader_->scheduler_->GetRemainingFileList(
+ next_url,
+ base::Bind(&FeedFetcher::OnFileListFetched,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ return;
+ }
+
+ UMA_HISTOGRAM_TIMES("Drive.DirectoryFeedLoadTime",
+ base::TimeTicks::Now() - start_time_);
+
+ // Note: The fetcher is managed by DirectoryLoader, and the instance
+ // will be deleted in the callback. Do not touch the fields after this
+ // invocation.
+ callback.Run(FILE_ERROR_OK);
+ }
+
+ DirectoryLoader* loader_;
+ DirectoryFetchInfo directory_fetch_info_;
+ std::string root_folder_id_;
+ base::TimeTicks start_time_;
+ base::ThreadChecker thread_checker_;
+ base::WeakPtrFactory<FeedFetcher> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(FeedFetcher);
+};
+
+DirectoryLoader::DirectoryLoader(
+ EventLogger* logger,
+ base::SequencedTaskRunner* blocking_task_runner,
+ ResourceMetadata* resource_metadata,
+ JobScheduler* scheduler,
+ AboutResourceLoader* about_resource_loader,
+ LoaderController* loader_controller)
+ : logger_(logger),
+ blocking_task_runner_(blocking_task_runner),
+ resource_metadata_(resource_metadata),
+ scheduler_(scheduler),
+ about_resource_loader_(about_resource_loader),
+ loader_controller_(loader_controller),
+ weak_ptr_factory_(this) {
+}
+
+DirectoryLoader::~DirectoryLoader() {
+ STLDeleteElements(&fast_fetch_feed_fetcher_set_);
+}
+
+void DirectoryLoader::AddObserver(ChangeListLoaderObserver* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observers_.AddObserver(observer);
+}
+
+void DirectoryLoader::RemoveObserver(ChangeListLoaderObserver* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observers_.RemoveObserver(observer);
+}
+
+void DirectoryLoader::ReadDirectory(
+ const base::FilePath& directory_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!completion_callback.is_null());
+
+ ResourceEntry* entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::GetResourceEntryByPath,
+ base::Unretained(resource_metadata_),
+ directory_path,
+ entry),
+ base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
+ weak_ptr_factory_.GetWeakPtr(),
+ directory_path,
+ entries_callback,
+ completion_callback,
+ true, // should_try_loading_parent
+ base::Owned(entry)));
+}
+
+void DirectoryLoader::ReadDirectoryAfterGetEntry(
+ const base::FilePath& directory_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback,
+ bool should_try_loading_parent,
+ const ResourceEntry* entry,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!completion_callback.is_null());
+
+ if (error == FILE_ERROR_NOT_FOUND &&
+ should_try_loading_parent &&
+ util::GetDriveGrandRootPath().IsParent(directory_path)) {
+ // This entry may be found after loading the parent.
+ ReadDirectory(directory_path.DirName(),
+ ReadDirectoryEntriesCallback(),
+ base::Bind(&DirectoryLoader::ReadDirectoryAfterLoadParent,
+ weak_ptr_factory_.GetWeakPtr(),
+ directory_path,
+ entries_callback,
+ completion_callback));
+ return;
+ }
+ if (error != FILE_ERROR_OK) {
+ completion_callback.Run(error);
+ return;
+ }
+
+ if (!entry->file_info().is_directory()) {
+ completion_callback.Run(FILE_ERROR_NOT_A_DIRECTORY);
+ return;
+ }
+
+ DirectoryFetchInfo directory_fetch_info(
+ entry->local_id(),
+ entry->resource_id(),
+ entry->directory_specific_info().changestamp());
+
+ // Register the callback function to be called when it is loaded.
+ const std::string& local_id = directory_fetch_info.local_id();
+ ReadDirectoryCallbackState callback_state;
+ callback_state.entries_callback = entries_callback;
+ callback_state.completion_callback = completion_callback;
+ pending_load_callback_[local_id].push_back(callback_state);
+
+ // If loading task for |local_id| is already running, do nothing.
+ if (pending_load_callback_[local_id].size() > 1)
+ return;
+
+ about_resource_loader_->GetAboutResource(
+ base::Bind(&DirectoryLoader::ReadDirectoryAfterGetAboutResource,
+ weak_ptr_factory_.GetWeakPtr(), local_id));
+}
+
+void DirectoryLoader::ReadDirectoryAfterLoadParent(
+ const base::FilePath& directory_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!completion_callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ completion_callback.Run(error);
+ return;
+ }
+
+ ResourceEntry* entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::GetResourceEntryByPath,
+ base::Unretained(resource_metadata_),
+ directory_path,
+ entry),
+ base::Bind(&DirectoryLoader::ReadDirectoryAfterGetEntry,
+ weak_ptr_factory_.GetWeakPtr(),
+ directory_path,
+ entries_callback,
+ completion_callback,
+ false, // should_try_loading_parent
+ base::Owned(entry)));
+}
+
+void DirectoryLoader::ReadDirectoryAfterGetAboutResource(
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::AboutResource> about_resource) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ FileError error = GDataToFileError(status);
+ if (error != FILE_ERROR_OK) {
+ OnDirectoryLoadComplete(local_id, error);
+ return;
+ }
+
+ DCHECK(about_resource);
+
+ // Check the current status of local metadata, and start loading if needed.
+ google_apis::AboutResource* about_resource_ptr = about_resource.get();
+ ResourceEntry* entry = new ResourceEntry;
+ int64* local_changestamp = new int64;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&CheckLocalState,
+ resource_metadata_,
+ *about_resource_ptr,
+ local_id,
+ entry,
+ local_changestamp),
+ base::Bind(&DirectoryLoader::ReadDirectoryAfterCheckLocalState,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&about_resource),
+ local_id,
+ base::Owned(entry),
+ base::Owned(local_changestamp)));
+}
+
+void DirectoryLoader::ReadDirectoryAfterCheckLocalState(
+ scoped_ptr<google_apis::AboutResource> about_resource,
+ const std::string& local_id,
+ const ResourceEntry* entry,
+ const int64* local_changestamp,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(about_resource);
+
+ if (error != FILE_ERROR_OK) {
+ OnDirectoryLoadComplete(local_id, error);
+ return;
+ }
+ // This entry does not exist on the server.
+ if (entry->resource_id().empty()) {
+ OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
+ return;
+ }
+
+ int64 remote_changestamp = about_resource->largest_change_id();
+
+ // Start loading the directory.
+ int64 directory_changestamp = std::max(
+ entry->directory_specific_info().changestamp(), *local_changestamp);
+
+ DirectoryFetchInfo directory_fetch_info(
+ local_id, entry->resource_id(), remote_changestamp);
+
+ // If the directory's changestamp is up-to-date or the global changestamp of
+ // the metadata DB is new enough (which means the normal changelist loading
+ // should finish very soon), just schedule to run the callback, as there is no
+ // need to fetch the directory.
+ if (directory_changestamp >= remote_changestamp ||
+ *local_changestamp + kMinimumChangestampGap > remote_changestamp) {
+ OnDirectoryLoadComplete(local_id, FILE_ERROR_OK);
+ } else {
+ // Start fetching the directory content, and mark it with the changestamp
+ // |remote_changestamp|.
+ LoadDirectoryFromServer(directory_fetch_info);
+ }
+}
+
+void DirectoryLoader::OnDirectoryLoadComplete(const std::string& local_id,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
+ if (it == pending_load_callback_.end())
+ return;
+
+ // No need to read metadata when no one needs entries.
+ bool needs_to_send_entries = false;
+ for (size_t i = 0; i < it->second.size(); ++i) {
+ const ReadDirectoryCallbackState& callback_state = it->second[i];
+ if (!callback_state.entries_callback.is_null())
+ needs_to_send_entries = true;
+ }
+
+ if (!needs_to_send_entries) {
+ OnDirectoryLoadCompleteAfterRead(local_id, NULL, FILE_ERROR_OK);
+ return;
+ }
+
+ ResourceEntryVector* entries = new ResourceEntryVector;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::ReadDirectoryById,
+ base::Unretained(resource_metadata_), local_id, entries),
+ base::Bind(&DirectoryLoader::OnDirectoryLoadCompleteAfterRead,
+ weak_ptr_factory_.GetWeakPtr(),
+ local_id,
+ base::Owned(entries)));
+}
+
+void DirectoryLoader::OnDirectoryLoadCompleteAfterRead(
+ const std::string& local_id,
+ const ResourceEntryVector* entries,
+ FileError error) {
+ LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
+ if (it != pending_load_callback_.end()) {
+ DVLOG(1) << "Running callback for " << local_id;
+
+ if (error == FILE_ERROR_OK && entries)
+ SendEntries(local_id, *entries);
+
+ for (size_t i = 0; i < it->second.size(); ++i) {
+ const ReadDirectoryCallbackState& callback_state = it->second[i];
+ callback_state.completion_callback.Run(error);
+ }
+ pending_load_callback_.erase(it);
+ }
+}
+
+void DirectoryLoader::SendEntries(const std::string& local_id,
+ const ResourceEntryVector& entries) {
+ LoadCallbackMap::iterator it = pending_load_callback_.find(local_id);
+ DCHECK(it != pending_load_callback_.end());
+
+ for (size_t i = 0; i < it->second.size(); ++i) {
+ ReadDirectoryCallbackState* callback_state = &it->second[i];
+ if (callback_state->entries_callback.is_null())
+ continue;
+
+ // Filter out entries which were already sent.
+ scoped_ptr<ResourceEntryVector> entries_to_send(new ResourceEntryVector);
+ for (size_t i = 0; i < entries.size(); ++i) {
+ const ResourceEntry& entry = entries[i];
+ if (!callback_state->sent_entry_names.count(entry.base_name())) {
+ callback_state->sent_entry_names.insert(entry.base_name());
+ entries_to_send->push_back(entry);
+ }
+ }
+ callback_state->entries_callback.Run(entries_to_send.Pass());
+ }
+}
+
+void DirectoryLoader::LoadDirectoryFromServer(
+ const DirectoryFetchInfo& directory_fetch_info) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!directory_fetch_info.empty());
+ DVLOG(1) << "Start loading directory: " << directory_fetch_info.ToString();
+
+ const google_apis::AboutResource* about_resource =
+ about_resource_loader_->cached_about_resource();
+ DCHECK(about_resource);
+
+ logger_->Log(logging::LOG_INFO,
+ "Fast-fetch start: %s; Server changestamp: %s",
+ directory_fetch_info.ToString().c_str(),
+ base::Int64ToString(
+ about_resource->largest_change_id()).c_str());
+
+ FeedFetcher* fetcher = new FeedFetcher(this,
+ directory_fetch_info,
+ about_resource->root_folder_id());
+ fast_fetch_feed_fetcher_set_.insert(fetcher);
+ fetcher->Run(
+ base::Bind(&DirectoryLoader::LoadDirectoryFromServerAfterLoad,
+ weak_ptr_factory_.GetWeakPtr(),
+ directory_fetch_info,
+ fetcher));
+}
+
+void DirectoryLoader::LoadDirectoryFromServerAfterLoad(
+ const DirectoryFetchInfo& directory_fetch_info,
+ FeedFetcher* fetcher,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!directory_fetch_info.empty());
+
+ // Delete the fetcher.
+ fast_fetch_feed_fetcher_set_.erase(fetcher);
+ delete fetcher;
+
+ logger_->Log(logging::LOG_INFO,
+ "Fast-fetch complete: %s => %s",
+ directory_fetch_info.ToString().c_str(),
+ FileErrorToString(error).c_str());
+
+ if (error != FILE_ERROR_OK) {
+ LOG(ERROR) << "Failed to load directory: "
+ << directory_fetch_info.local_id()
+ << ": " << FileErrorToString(error);
+ OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
+ return;
+ }
+
+ // Update changestamp and get the directory path.
+ base::FilePath* directory_path = new base::FilePath;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateChangestamp,
+ resource_metadata_,
+ directory_fetch_info,
+ directory_path),
+ base::Bind(
+ &DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp,
+ weak_ptr_factory_.GetWeakPtr(),
+ directory_fetch_info,
+ base::Owned(directory_path)));
+}
+
+void DirectoryLoader::LoadDirectoryFromServerAfterUpdateChangestamp(
+ const DirectoryFetchInfo& directory_fetch_info,
+ const base::FilePath* directory_path,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DVLOG(1) << "Directory loaded: " << directory_fetch_info.ToString();
+ OnDirectoryLoadComplete(directory_fetch_info.local_id(), error);
+
+ // Also notify the observers.
+ if (error == FILE_ERROR_OK && !directory_path->empty()) {
+ FOR_EACH_OBSERVER(ChangeListLoaderObserver,
+ observers_,
+ OnDirectoryReloaded(*directory_path));
+ }
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/directory_loader.h b/components/drive/directory_loader.h
new file mode 100644
index 0000000..32d72ef
--- /dev/null
+++ b/components/drive/directory_loader.h
@@ -0,0 +1,150 @@
+// 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_DIRECTORY_LOADER_H_
+#define COMPONENTS_DRIVE_DIRECTORY_LOADER_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.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"
+#include "google_apis/drive/drive_common_callbacks.h"
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+namespace google_apis {
+class AboutResource;
+} // namespace google_apis
+
+namespace drive {
+
+class EventLogger;
+class JobScheduler;
+class ResourceEntry;
+
+namespace internal {
+
+class AboutResourceLoader;
+class ChangeList;
+class ChangeListLoaderObserver;
+class DirectoryFetchInfo;
+class LoaderController;
+class ResourceMetadata;
+
+// DirectoryLoader is used to load directory contents.
+class DirectoryLoader {
+ public:
+ DirectoryLoader(EventLogger* logger,
+ base::SequencedTaskRunner* blocking_task_runner,
+ ResourceMetadata* resource_metadata,
+ JobScheduler* scheduler,
+ AboutResourceLoader* about_resource_loader,
+ LoaderController* apply_task_controller);
+ ~DirectoryLoader();
+
+ // Adds and removes the observer.
+ void AddObserver(ChangeListLoaderObserver* observer);
+ void RemoveObserver(ChangeListLoaderObserver* observer);
+
+ // Reads the directory contents.
+ // |entries_callback| can be null.
+ // |completion_callback| must not be null.
+ void ReadDirectory(const base::FilePath& directory_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback);
+
+ private:
+ class FeedFetcher;
+ struct ReadDirectoryCallbackState;
+
+ // Part of ReadDirectory().
+ void ReadDirectoryAfterGetEntry(
+ const base::FilePath& directory_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback,
+ bool should_try_loading_parent,
+ const ResourceEntry* entry,
+ FileError error);
+ void ReadDirectoryAfterLoadParent(
+ const base::FilePath& directory_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback,
+ FileError error);
+ void ReadDirectoryAfterGetAboutResource(
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::AboutResource> about_resource);
+ void ReadDirectoryAfterCheckLocalState(
+ scoped_ptr<google_apis::AboutResource> about_resource,
+ const std::string& local_id,
+ const ResourceEntry* entry,
+ const int64* local_changestamp,
+ FileError error);
+
+ // Part of ReadDirectory().
+ // This function should be called when the directory load is complete.
+ // Flushes the callbacks waiting for the directory to be loaded.
+ void OnDirectoryLoadComplete(const std::string& local_id, FileError error);
+ void OnDirectoryLoadCompleteAfterRead(const std::string& local_id,
+ const ResourceEntryVector* entries,
+ FileError error);
+
+ // Sends |entries| to the callbacks.
+ void SendEntries(const std::string& local_id,
+ const ResourceEntryVector& entries);
+
+ // ================= Implementation for directory loading =================
+ // Loads the directory contents from server, and updates the local metadata.
+ // Runs |callback| when it is finished.
+ void LoadDirectoryFromServer(const DirectoryFetchInfo& directory_fetch_info);
+
+ // Part of LoadDirectoryFromServer() for a normal directory.
+ void LoadDirectoryFromServerAfterLoad(
+ const DirectoryFetchInfo& directory_fetch_info,
+ FeedFetcher* fetcher,
+ FileError error);
+
+ // Part of LoadDirectoryFromServer().
+ void LoadDirectoryFromServerAfterUpdateChangestamp(
+ const DirectoryFetchInfo& directory_fetch_info,
+ const base::FilePath* directory_path,
+ FileError error);
+
+ EventLogger* logger_; // Not owned.
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ ResourceMetadata* resource_metadata_; // Not owned.
+ JobScheduler* scheduler_; // Not owned.
+ AboutResourceLoader* about_resource_loader_; // Not owned.
+ LoaderController* loader_controller_; // Not owned.
+ base::ObserverList<ChangeListLoaderObserver> observers_;
+ typedef std::map<std::string, std::vector<ReadDirectoryCallbackState> >
+ LoadCallbackMap;
+ LoadCallbackMap pending_load_callback_;
+
+ // Set of the running feed fetcher for the fast fetch.
+ std::set<FeedFetcher*> fast_fetch_feed_fetcher_set_;
+
+ 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<DirectoryLoader> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(DirectoryLoader);
+};
+
+} // namespace internal
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_DIRECTORY_LOADER_H_
diff --git a/components/drive/directory_loader_unittest.cc b/components/drive/directory_loader_unittest.cc
new file mode 100644
index 0000000..20e4ead
--- /dev/null
+++ b/components/drive/directory_loader_unittest.cc
@@ -0,0 +1,252 @@
+// 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/directory_loader.h"
+
+#include "base/callback_helpers.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/testing_pref_service.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/change_list_loader.h"
+#include "components/drive/change_list_loader_observer.h"
+#include "components/drive/drive_test_util.h"
+#include "components/drive/event_logger.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_system_core_util.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/test/test_browser_thread_bundle.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 internal {
+
+namespace {
+
+class TestDirectoryLoaderObserver : public ChangeListLoaderObserver {
+ public:
+ explicit TestDirectoryLoaderObserver(DirectoryLoader* loader)
+ : loader_(loader) {
+ loader_->AddObserver(this);
+ }
+
+ ~TestDirectoryLoaderObserver() override { loader_->RemoveObserver(this); }
+
+ const std::set<base::FilePath>& changed_directories() const {
+ return changed_directories_;
+ }
+ void clear_changed_directories() { changed_directories_.clear(); }
+
+ // ChageListObserver overrides:
+ void OnDirectoryReloaded(const base::FilePath& directory_path) override {
+ changed_directories_.insert(directory_path);
+ }
+
+ private:
+ DirectoryLoader* loader_;
+ std::set<base::FilePath> changed_directories_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestDirectoryLoaderObserver);
+};
+
+void AccumulateReadDirectoryResult(ResourceEntryVector* out_entries,
+ scoped_ptr<ResourceEntryVector> entries) {
+ ASSERT_TRUE(entries);
+ out_entries->insert(out_entries->end(), entries->begin(), entries->end());
+}
+
+} // namespace
+
+class DirectoryLoaderTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ pref_service_.reset(new TestingPrefServiceSimple);
+ test_util::RegisterDrivePrefs(pref_service_->registry());
+
+ logger_.reset(new EventLogger);
+
+ drive_service_.reset(new FakeDriveService);
+ ASSERT_TRUE(test_util::SetUpTestEntries(drive_service_.get()));
+
+ scheduler_.reset(new JobScheduler(
+ pref_service_.get(),
+ logger_.get(),
+ drive_service_.get(),
+ base::ThreadTaskRunnerHandle::Get().get()));
+ metadata_storage_.reset(new ResourceMetadataStorage(
+ temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+ ASSERT_TRUE(metadata_storage_->Initialize());
+
+ cache_.reset(new FileCache(metadata_storage_.get(),
+ temp_dir_.path(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ NULL /* free_disk_space_getter */));
+ ASSERT_TRUE(cache_->Initialize());
+
+ metadata_.reset(new ResourceMetadata(
+ metadata_storage_.get(), cache_.get(),
+ base::ThreadTaskRunnerHandle::Get().get()));
+ ASSERT_EQ(FILE_ERROR_OK, metadata_->Initialize());
+
+ about_resource_loader_.reset(new AboutResourceLoader(scheduler_.get()));
+ loader_controller_.reset(new LoaderController);
+ directory_loader_.reset(
+ new DirectoryLoader(logger_.get(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ metadata_.get(),
+ scheduler_.get(),
+ about_resource_loader_.get(),
+ loader_controller_.get()));
+ }
+
+ // Adds a new file to the root directory of the service.
+ scoped_ptr<google_apis::FileResource> AddNewFile(const std::string& title) {
+ google_apis::DriveApiErrorCode error = google_apis::DRIVE_FILE_ERROR;
+ scoped_ptr<google_apis::FileResource> entry;
+ drive_service_->AddNewFile(
+ "text/plain",
+ "content text",
+ drive_service_->GetRootResourceId(),
+ title,
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_CREATED, error);
+ return entry.Pass();
+ }
+
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<TestingPrefServiceSimple> pref_service_;
+ scoped_ptr<EventLogger> logger_;
+ scoped_ptr<FakeDriveService> drive_service_;
+ scoped_ptr<JobScheduler> scheduler_;
+ scoped_ptr<ResourceMetadataStorage,
+ test_util::DestroyHelperForTests> metadata_storage_;
+ scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
+ scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests> metadata_;
+ scoped_ptr<AboutResourceLoader> about_resource_loader_;
+ scoped_ptr<LoaderController> loader_controller_;
+ scoped_ptr<DirectoryLoader> directory_loader_;
+};
+
+TEST_F(DirectoryLoaderTest, ReadDirectory_GrandRoot) {
+ TestDirectoryLoaderObserver observer(directory_loader_.get());
+
+ // Load grand root.
+ FileError error = FILE_ERROR_FAILED;
+ ResourceEntryVector entries;
+ directory_loader_->ReadDirectory(
+ util::GetDriveGrandRootPath(),
+ base::Bind(&AccumulateReadDirectoryResult, &entries),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(0U, observer.changed_directories().size());
+ observer.clear_changed_directories();
+
+ // My Drive has resource ID.
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryByPath(util::GetDriveMyDriveRootPath(),
+ &entry));
+ EXPECT_EQ(drive_service_->GetRootResourceId(), entry.resource_id());
+}
+
+TEST_F(DirectoryLoaderTest, ReadDirectory_MyDrive) {
+ TestDirectoryLoaderObserver observer(directory_loader_.get());
+
+ // My Drive does not have resource ID yet.
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryByPath(util::GetDriveMyDriveRootPath(),
+ &entry));
+ EXPECT_TRUE(entry.resource_id().empty());
+
+ // Load My Drive.
+ FileError error = FILE_ERROR_FAILED;
+ ResourceEntryVector entries;
+ directory_loader_->ReadDirectory(
+ util::GetDriveMyDriveRootPath(),
+ base::Bind(&AccumulateReadDirectoryResult, &entries),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(1U, observer.changed_directories().count(
+ util::GetDriveMyDriveRootPath()));
+
+ // My Drive has resource ID.
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryByPath(util::GetDriveMyDriveRootPath(),
+ &entry));
+ EXPECT_EQ(drive_service_->GetRootResourceId(), entry.resource_id());
+ EXPECT_EQ(drive_service_->about_resource().largest_change_id(),
+ entry.directory_specific_info().changestamp());
+
+ // My Drive's child is present.
+ base::FilePath file_path =
+ util::GetDriveMyDriveRootPath().AppendASCII("File 1.txt");
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryByPath(file_path, &entry));
+}
+
+TEST_F(DirectoryLoaderTest, ReadDirectory_MultipleCalls) {
+ TestDirectoryLoaderObserver observer(directory_loader_.get());
+
+ // Load grand root.
+ FileError error = FILE_ERROR_FAILED;
+ ResourceEntryVector entries;
+ directory_loader_->ReadDirectory(
+ util::GetDriveGrandRootPath(),
+ base::Bind(&AccumulateReadDirectoryResult, &entries),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+
+ // Load grand root again without waiting for the result.
+ FileError error2 = FILE_ERROR_FAILED;
+ ResourceEntryVector entries2;
+ directory_loader_->ReadDirectory(
+ util::GetDriveGrandRootPath(),
+ base::Bind(&AccumulateReadDirectoryResult, &entries2),
+ google_apis::test_util::CreateCopyResultCallback(&error2));
+ base::RunLoop().RunUntilIdle();
+
+ // Callback is called for each method call.
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_OK, error2);
+}
+
+TEST_F(DirectoryLoaderTest, Lock) {
+ // Lock the loader.
+ scoped_ptr<base::ScopedClosureRunner> lock = loader_controller_->GetLock();
+
+ // Start loading.
+ TestDirectoryLoaderObserver observer(directory_loader_.get());
+ FileError error = FILE_ERROR_FAILED;
+ ResourceEntryVector entries;
+ directory_loader_->ReadDirectory(
+ util::GetDriveMyDriveRootPath(),
+ base::Bind(&AccumulateReadDirectoryResult, &entries),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+
+ // Update is pending due to the lock.
+ EXPECT_TRUE(observer.changed_directories().empty());
+
+ // Unlock the loader, this should resume the pending udpate.
+ lock.reset();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1U, observer.changed_directories().count(
+ util::GetDriveMyDriveRootPath()));
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/dummy_file_system.cc b/components/drive/dummy_file_system.cc
new file mode 100644
index 0000000..7c92271
--- /dev/null
+++ b/components/drive/dummy_file_system.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/dummy_file_system.h"
+
+namespace drive {
+
+base::Closure DummyFileSystem::GetFileContent(
+ const base::FilePath& file_path,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback) {
+ return base::Bind(&base::DoNothing);
+}
+
+} // namespace drive
diff --git a/components/drive/dummy_file_system.h b/components/drive/dummy_file_system.h
new file mode 100644
index 0000000..f8cdb48
--- /dev/null
+++ b/components/drive/dummy_file_system.h
@@ -0,0 +1,111 @@
+// 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_DUMMY_FILE_SYSTEM_H_
+#define COMPONENTS_DRIVE_DUMMY_FILE_SYSTEM_H_
+
+#include "components/drive/file_system_interface.h"
+
+namespace drive {
+
+// Dummy implementation of FileSystemInterface. All functions do nothing.
+class DummyFileSystem : public FileSystemInterface {
+ public:
+ ~DummyFileSystem() override {}
+ void AddObserver(FileSystemObserver* observer) override {}
+ void RemoveObserver(FileSystemObserver* observer) override {}
+ void CheckForUpdates() override {}
+ void TransferFileFromLocalToRemote(
+ const base::FilePath& local_src_file_path,
+ const base::FilePath& remote_dest_file_path,
+ const FileOperationCallback& callback) override {}
+ void OpenFile(const base::FilePath& file_path,
+ OpenMode open_mode,
+ const std::string& mime_type,
+ const OpenFileCallback& callback) override {}
+ void Copy(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ bool preserve_last_modified,
+ const FileOperationCallback& callback) override {}
+ void Move(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ const FileOperationCallback& callback) override {}
+ void Remove(const base::FilePath& file_path,
+ bool is_recursive,
+ const FileOperationCallback& callback) override {}
+ void CreateDirectory(const base::FilePath& directory_path,
+ bool is_exclusive,
+ bool is_recursive,
+ const FileOperationCallback& callback) override {}
+ void CreateFile(const base::FilePath& file_path,
+ bool is_exclusive,
+ const std::string& mime_type,
+ const FileOperationCallback& callback) override {}
+ void TouchFile(const base::FilePath& file_path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const FileOperationCallback& callback) override {}
+ void TruncateFile(const base::FilePath& file_path,
+ int64 length,
+ const FileOperationCallback& callback) override {}
+ void Pin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) override {}
+ void Unpin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) override {}
+ void GetFile(const base::FilePath& file_path,
+ const GetFileCallback& callback) override {}
+ void GetFileForSaving(const base::FilePath& file_path,
+ const GetFileCallback& callback) override {}
+ base::Closure GetFileContent(
+ const base::FilePath& file_path,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback) override;
+ void GetResourceEntry(const base::FilePath& file_path,
+ const GetResourceEntryCallback& callback) override {}
+ void ReadDirectory(
+ const base::FilePath& file_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback) override {}
+ void Search(const std::string& search_query,
+ const GURL& next_link,
+ const SearchCallback& callback) override {}
+ void SearchMetadata(const std::string& query,
+ int options,
+ int at_most_num_matches,
+ const SearchMetadataCallback& callback) override {}
+ void SearchByHashes(const std::set<std::string>& hashes,
+ const SearchByHashesCallback& callback) override {}
+ void GetAvailableSpace(const GetAvailableSpaceCallback& callback) override {}
+ void GetShareUrl(const base::FilePath& file_path,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback) override {}
+ void GetMetadata(const GetFilesystemMetadataCallback& callback) override {}
+ void MarkCacheFileAsMounted(const base::FilePath& drive_file_path,
+ const MarkMountedCallback& callback) override {}
+ void MarkCacheFileAsUnmounted(
+ const base::FilePath& cache_file_path,
+ const FileOperationCallback& callback) override {}
+ void AddPermission(const base::FilePath& drive_file_path,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const FileOperationCallback& callback) override {}
+ 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) override {}
+ void Reset(const FileOperationCallback& callback) override {}
+ void GetPathFromResourceId(const std::string& resource_id,
+ const GetFilePathCallback& callback) override {}
+ void FreeDiskSpaceIfNeededFor(
+ int64 num_bytes,
+ const FreeDiskSpaceCallback& callback) override {}
+ void CalculateEvictableCacheSize(
+ const EvictableCacheSizeCallback& callback) override {}
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_DUMMY_FILE_SYSTEM_H_
diff --git a/components/drive/fake_file_system.cc b/components/drive/fake_file_system.cc
new file mode 100644
index 0000000..929d693
--- /dev/null
+++ b/components/drive/fake_file_system.cc
@@ -0,0 +1,420 @@
+// 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/fake_file_system.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/resource_entry_conversion.h"
+#include "components/drive/service/drive_service_interface.h"
+#include "content/public/browser/browser_thread.h"
+#include "google_apis/drive/drive_api_parser.h"
+
+namespace drive {
+namespace test_util {
+
+using content::BrowserThread;
+
+FakeFileSystem::FakeFileSystem(DriveServiceInterface* drive_service)
+ : drive_service_(drive_service),
+ weak_ptr_factory_(this) {
+ CHECK(cache_dir_.CreateUniqueTempDir());
+}
+
+FakeFileSystem::~FakeFileSystem() {
+}
+
+void FakeFileSystem::AddObserver(FileSystemObserver* observer) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::RemoveObserver(FileSystemObserver* observer) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::CheckForUpdates() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::TransferFileFromLocalToRemote(
+ const base::FilePath& local_src_file_path,
+ const base::FilePath& remote_dest_file_path,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::OpenFile(const base::FilePath& file_path,
+ OpenMode open_mode,
+ const std::string& mime_type,
+ const OpenFileCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::Copy(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ bool preserve_last_modified,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::Move(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::Remove(const base::FilePath& file_path,
+ bool is_recursive,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::CreateDirectory(
+ const base::FilePath& directory_path,
+ bool is_exclusive,
+ bool is_recursive,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::CreateFile(const base::FilePath& file_path,
+ bool is_exclusive,
+ const std::string& mime_type,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::TouchFile(const base::FilePath& file_path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::TruncateFile(const base::FilePath& file_path,
+ int64 length,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::Pin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::Unpin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::GetFile(const base::FilePath& file_path,
+ const GetFileCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::GetFileForSaving(const base::FilePath& file_path,
+ const GetFileCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+base::Closure FakeFileSystem::GetFileContent(
+ const base::FilePath& file_path,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ GetResourceEntry(
+ file_path,
+ base::Bind(&FakeFileSystem::GetFileContentAfterGetResourceEntry,
+ weak_ptr_factory_.GetWeakPtr(),
+ initialized_callback, get_content_callback,
+ completion_callback));
+ return base::Bind(&base::DoNothing);
+}
+
+void FakeFileSystem::GetResourceEntry(
+ const base::FilePath& file_path,
+ const GetResourceEntryCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (file_path == util::GetDriveMyDriveRootPath()) {
+ // Specialized for the root entry.
+ drive_service_->GetAboutResource(
+ base::Bind(
+ &FakeFileSystem::GetResourceEntryAfterGetAboutResource,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+ return;
+ }
+
+ // Now, we only support files under my drive.
+ DCHECK(util::GetDriveMyDriveRootPath().IsParent(file_path));
+ GetResourceEntry(
+ file_path.DirName(),
+ base::Bind(
+ &FakeFileSystem::GetResourceEntryAfterGetParentEntryInfo,
+ weak_ptr_factory_.GetWeakPtr(), file_path.BaseName(), callback));
+}
+
+void FakeFileSystem::ReadDirectory(
+ const base::FilePath& file_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::Search(const std::string& search_query,
+ const GURL& next_link,
+ const SearchCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::SearchMetadata(
+ const std::string& query,
+ int options,
+ int at_most_num_matches,
+ const SearchMetadataCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::SearchByHashes(const std::set<std::string>& hashes,
+ const SearchByHashesCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::GetAvailableSpace(
+ const GetAvailableSpaceCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::GetShareUrl(
+ const base::FilePath& file_path,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::GetMetadata(
+ const GetFilesystemMetadataCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::MarkCacheFileAsMounted(
+ const base::FilePath& drive_file_path,
+ const MarkMountedCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::MarkCacheFileAsUnmounted(
+ const base::FilePath& cache_file_path,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::AddPermission(const base::FilePath& drive_file_path,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::SetProperty(
+ const base::FilePath& drive_file_path,
+ google_apis::drive::Property::Visibility visibility,
+ const std::string& key,
+ const std::string& value,
+ const FileOperationCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::Reset(const FileOperationCallback& callback) {
+}
+
+void FakeFileSystem::GetPathFromResourceId(
+ const std::string& resource_id,
+ const GetFilePathCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::FreeDiskSpaceIfNeededFor(
+ int64 num_bytes,
+ const FreeDiskSpaceCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+void FakeFileSystem::CalculateEvictableCacheSize(
+ const EvictableCacheSizeCallback& callback) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+}
+
+// Implementation of GetFileContent.
+void FakeFileSystem::GetFileContentAfterGetResourceEntry(
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback,
+ FileError error,
+ scoped_ptr<ResourceEntry> entry) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (error != FILE_ERROR_OK) {
+ completion_callback.Run(error);
+ return;
+ }
+ DCHECK(entry);
+
+ // We're only interested in a file.
+ if (entry->file_info().is_directory()) {
+ completion_callback.Run(FILE_ERROR_NOT_A_FILE);
+ return;
+ }
+
+ // Fetch google_apis::FileResource for its |download_url|.
+ drive_service_->GetFileResource(
+ entry->resource_id(),
+ base::Bind(
+ &FakeFileSystem::GetFileContentAfterGetFileResource,
+ weak_ptr_factory_.GetWeakPtr(),
+ initialized_callback,
+ get_content_callback,
+ completion_callback));
+}
+
+void FakeFileSystem::GetFileContentAfterGetFileResource(
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::FileResource> gdata_entry) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ FileError error = GDataToFileError(gdata_error);
+ if (error != FILE_ERROR_OK) {
+ completion_callback.Run(error);
+ return;
+ }
+ DCHECK(gdata_entry);
+
+ scoped_ptr<ResourceEntry> entry(new ResourceEntry);
+ std::string parent_resource_id;
+ bool converted = ConvertFileResourceToResourceEntry(
+ *gdata_entry, entry.get(), &parent_resource_id);
+ DCHECK(converted);
+ entry->set_parent_local_id(parent_resource_id);
+
+ base::FilePath cache_path =
+ cache_dir_.path().AppendASCII(entry->resource_id());
+ if (entry->file_specific_info().is_hosted_document()) {
+ // For hosted documents return a dummy cache without server request.
+ int result = base::WriteFile(cache_path, "", 0);
+ DCHECK_EQ(0, result);
+ }
+ if (base::PathExists(cache_path)) {
+ // Cache file is found.
+ initialized_callback.Run(FILE_ERROR_OK, cache_path, entry.Pass());
+ completion_callback.Run(FILE_ERROR_OK);
+ return;
+ }
+
+ initialized_callback.Run(FILE_ERROR_OK, base::FilePath(), entry.Pass());
+ drive_service_->DownloadFile(
+ cache_path,
+ gdata_entry->file_id(),
+ base::Bind(&FakeFileSystem::GetFileContentAfterDownloadFile,
+ weak_ptr_factory_.GetWeakPtr(),
+ completion_callback),
+ get_content_callback,
+ google_apis::ProgressCallback());
+}
+
+void FakeFileSystem::GetFileContentAfterDownloadFile(
+ const FileOperationCallback& completion_callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ const base::FilePath& temp_file) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ completion_callback.Run(GDataToFileError(gdata_error));
+}
+
+// Implementation of GetResourceEntry.
+void FakeFileSystem::GetResourceEntryAfterGetAboutResource(
+ const GetResourceEntryCallback& callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::AboutResource> about_resource) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ FileError error = GDataToFileError(gdata_error);
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, scoped_ptr<ResourceEntry>());
+ return;
+ }
+
+ DCHECK(about_resource);
+ scoped_ptr<ResourceEntry> root(new ResourceEntry);
+ root->mutable_file_info()->set_is_directory(true);
+ root->set_resource_id(about_resource->root_folder_id());
+ root->set_title(util::kDriveMyDriveRootDirName);
+ callback.Run(error, root.Pass());
+}
+
+void FakeFileSystem::GetResourceEntryAfterGetParentEntryInfo(
+ const base::FilePath& base_name,
+ const GetResourceEntryCallback& callback,
+ FileError error,
+ scoped_ptr<ResourceEntry> parent_entry) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, scoped_ptr<ResourceEntry>());
+ return;
+ }
+
+ DCHECK(parent_entry);
+ drive_service_->GetFileListInDirectory(
+ parent_entry->resource_id(),
+ base::Bind(
+ &FakeFileSystem::GetResourceEntryAfterGetFileList,
+ weak_ptr_factory_.GetWeakPtr(), base_name, callback));
+}
+
+void FakeFileSystem::GetResourceEntryAfterGetFileList(
+ const base::FilePath& base_name,
+ const GetResourceEntryCallback& callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::FileList> file_list) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ FileError error = GDataToFileError(gdata_error);
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, scoped_ptr<ResourceEntry>());
+ return;
+ }
+
+ DCHECK(file_list);
+ const ScopedVector<google_apis::FileResource>& entries = file_list->items();
+ for (size_t i = 0; i < entries.size(); ++i) {
+ scoped_ptr<ResourceEntry> entry(new ResourceEntry);
+ std::string parent_resource_id;
+ bool converted = ConvertFileResourceToResourceEntry(
+ *entries[i], entry.get(), &parent_resource_id);
+ DCHECK(converted);
+ entry->set_parent_local_id(parent_resource_id);
+
+ if (entry->base_name() == base_name.AsUTF8Unsafe()) {
+ // Found the target entry.
+ callback.Run(FILE_ERROR_OK, entry.Pass());
+ return;
+ }
+ }
+
+ callback.Run(FILE_ERROR_NOT_FOUND, scoped_ptr<ResourceEntry>());
+}
+
+} // namespace test_util
+} // namespace drive
diff --git a/components/drive/fake_file_system.h b/components/drive/fake_file_system.h
new file mode 100644
index 0000000..de5cec3f
--- /dev/null
+++ b/components/drive/fake_file_system.h
@@ -0,0 +1,197 @@
+// 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_FAKE_FILE_SYSTEM_H_
+#define COMPONENTS_DRIVE_FAKE_FILE_SYSTEM_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system_interface.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+namespace google_apis {
+
+class AboutResource;
+class FileResource;
+
+} // namespace google_apis
+
+namespace drive {
+
+class DriveServiceInterface;
+class FileSystemObserver;
+class ResourceEntry;
+
+namespace test_util {
+
+// This class implements a fake FileSystem which acts like a real Drive
+// file system with FakeDriveService, for testing purpose.
+// Note that this class doesn't support "caching" at the moment, so the number
+// of interactions to the FakeDriveService may be bigger than the real
+// implementation.
+// Currently most methods are empty (not implemented).
+class FakeFileSystem : public FileSystemInterface {
+ public:
+ explicit FakeFileSystem(DriveServiceInterface* drive_service);
+ ~FakeFileSystem() override;
+
+ // FileSystemInterface Overrides.
+ void AddObserver(FileSystemObserver* observer) override;
+ void RemoveObserver(FileSystemObserver* observer) override;
+ void CheckForUpdates() override;
+ void TransferFileFromLocalToRemote(
+ const base::FilePath& local_src_file_path,
+ const base::FilePath& remote_dest_file_path,
+ const FileOperationCallback& callback) override;
+ void OpenFile(const base::FilePath& file_path,
+ OpenMode open_mode,
+ const std::string& mime_type,
+ const OpenFileCallback& callback) override;
+ void Copy(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ bool preserve_last_modified,
+ const FileOperationCallback& callback) override;
+ void Move(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ const FileOperationCallback& callback) override;
+ void Remove(const base::FilePath& file_path,
+ bool is_recursive,
+ const FileOperationCallback& callback) override;
+ void CreateDirectory(const base::FilePath& directory_path,
+ bool is_exclusive,
+ bool is_recursive,
+ const FileOperationCallback& callback) override;
+ void CreateFile(const base::FilePath& file_path,
+ bool is_exclusive,
+ const std::string& mime_type,
+ const FileOperationCallback& callback) override;
+ void TouchFile(const base::FilePath& file_path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const FileOperationCallback& callback) override;
+ void TruncateFile(const base::FilePath& file_path,
+ int64 length,
+ const FileOperationCallback& callback) override;
+ void Pin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) override;
+ void Unpin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) override;
+ void GetFile(const base::FilePath& file_path,
+ const GetFileCallback& callback) override;
+ void GetFileForSaving(const base::FilePath& file_path,
+ const GetFileCallback& callback) override;
+ base::Closure GetFileContent(
+ const base::FilePath& file_path,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback) override;
+ void GetResourceEntry(const base::FilePath& file_path,
+ const GetResourceEntryCallback& callback) override;
+ void ReadDirectory(const base::FilePath& file_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback) override;
+ void Search(const std::string& search_query,
+ const GURL& next_link,
+ const SearchCallback& callback) override;
+ void SearchMetadata(const std::string& query,
+ int options,
+ int at_most_num_matches,
+ const SearchMetadataCallback& callback) override;
+ void SearchByHashes(const std::set<std::string>& hashes,
+ const SearchByHashesCallback& callback) override;
+ void GetAvailableSpace(const GetAvailableSpaceCallback& callback) override;
+ void GetShareUrl(const base::FilePath& file_path,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback) override;
+ void GetMetadata(const GetFilesystemMetadataCallback& callback) override;
+ void MarkCacheFileAsMounted(const base::FilePath& drive_file_path,
+ const MarkMountedCallback& callback) override;
+ void MarkCacheFileAsUnmounted(const base::FilePath& cache_file_path,
+ const FileOperationCallback& callback) override;
+ void AddPermission(const base::FilePath& drive_file_path,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const FileOperationCallback& callback) override;
+ 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) override;
+ void Reset(const FileOperationCallback& callback) override;
+ void GetPathFromResourceId(const std::string& resource_id,
+ const GetFilePathCallback& callback) override;
+ void FreeDiskSpaceIfNeededFor(int64 num_bytes,
+ const FreeDiskSpaceCallback& callback) override;
+ void CalculateEvictableCacheSize(
+ const EvictableCacheSizeCallback& callback) override;
+
+ private:
+ // Helpers of GetFileContent.
+ // How the method works:
+ // 1) Gets ResourceEntry of the path.
+ // 2) Look at if there is a cache file or not. If found return it.
+ // 3) Otherwise start DownloadFile.
+ // 4) Runs the |completion_callback| upon the download completion.
+ void GetFileContentAfterGetResourceEntry(
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback,
+ FileError error,
+ scoped_ptr<ResourceEntry> entry);
+ void GetFileContentAfterGetFileResource(
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::FileResource> gdata_entry);
+ void GetFileContentAfterDownloadFile(
+ const FileOperationCallback& completion_callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ const base::FilePath& temp_file);
+
+ // Helpers of GetResourceEntry.
+ // How the method works:
+ // 1) If the path is root, gets AboutResrouce from the drive service
+ // and create ResourceEntry.
+ // 2-1) Otherwise, gets the parent's ResourceEntry by recursive call.
+ // 2-2) Then, gets the resource list by restricting the parent with its id.
+ // 2-3) Search the results based on title, and return the ResourceEntry.
+ // Note that adding suffix (e.g. " (2)") for files sharing a same name is
+ // not supported in FakeFileSystem. Thus, even if the server has
+ // files sharing the same name under a directory, the second (or later)
+ // file cannot be taken with the suffixed name.
+ void GetResourceEntryAfterGetAboutResource(
+ const GetResourceEntryCallback& callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::AboutResource> about_resource);
+ void GetResourceEntryAfterGetParentEntryInfo(
+ const base::FilePath& base_name,
+ const GetResourceEntryCallback& callback,
+ FileError error,
+ scoped_ptr<ResourceEntry> parent_entry);
+ void GetResourceEntryAfterGetFileList(
+ const base::FilePath& base_name,
+ const GetResourceEntryCallback& callback,
+ google_apis::DriveApiErrorCode gdata_error,
+ scoped_ptr<google_apis::FileList> file_list);
+
+ DriveServiceInterface* drive_service_; // Not owned.
+ base::ScopedTempDir cache_dir_;
+
+ // 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<FakeFileSystem> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeFileSystem);
+};
+
+} // namespace test_util
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FAKE_FILE_SYSTEM_H_
diff --git a/components/drive/fake_file_system_unittest.cc b/components/drive/fake_file_system_unittest.cc
new file mode 100644
index 0000000..fcaa82c
--- /dev/null
+++ b/components/drive/fake_file_system_unittest.cc
@@ -0,0 +1,156 @@
+// 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/fake_file_system.h"
+
+#include "base/files/file_util.h"
+#include "base/run_loop.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/service/fake_drive_service.h"
+#include "components/drive/service/test_util.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace test_util {
+
+class FakeFileSystemTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // Initialize FakeDriveService.
+ fake_drive_service_.reset(new FakeDriveService);
+ ASSERT_TRUE(SetUpTestEntries(fake_drive_service_.get()));
+
+ // Create a testee instance.
+ fake_file_system_.reset(new FakeFileSystem(fake_drive_service_.get()));
+ }
+
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_ptr<FakeDriveService> fake_drive_service_;
+ scoped_ptr<FakeFileSystem> fake_file_system_;
+};
+
+TEST_F(FakeFileSystemTest, GetFileContent) {
+ FileError initialize_error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry;
+ base::FilePath cache_file_path;
+ google_apis::test_util::TestGetContentCallback get_content_callback;
+ FileError completion_error = FILE_ERROR_FAILED;
+
+ const base::FilePath kDriveFile =
+ util::GetDriveMyDriveRootPath().AppendASCII("File 1.txt");
+
+ // For the first time, the file should be downloaded from the service.
+ base::Closure cancel_download = fake_file_system_->GetFileContent(
+ kDriveFile,
+ google_apis::test_util::CreateCopyResultCallback(
+ &initialize_error, &cache_file_path, &entry),
+ get_content_callback.callback(),
+ google_apis::test_util::CreateCopyResultCallback(&completion_error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, initialize_error);
+ EXPECT_TRUE(entry);
+
+ // No cache file is available yet.
+ EXPECT_TRUE(cache_file_path.empty());
+
+ // The download should be happened so the |get_content_callback|
+ // should have the actual data.
+ std::string content = get_content_callback.GetConcatenatedData();
+ EXPECT_EQ(26U, content.size());
+ EXPECT_EQ(FILE_ERROR_OK, completion_error);
+
+ initialize_error = FILE_ERROR_FAILED;
+ entry.reset();
+ get_content_callback.mutable_data()->clear();
+ completion_error = FILE_ERROR_FAILED;
+
+ // For the second time, the cache file should be found.
+ cancel_download = fake_file_system_->GetFileContent(
+ kDriveFile,
+ google_apis::test_util::CreateCopyResultCallback(
+ &initialize_error, &cache_file_path, &entry),
+ get_content_callback.callback(),
+ google_apis::test_util::CreateCopyResultCallback(&completion_error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, initialize_error);
+ EXPECT_TRUE(entry);
+
+ // Cache file should be available.
+ ASSERT_FALSE(cache_file_path.empty());
+
+ // There should be a cache file so no data should be downloaded.
+ EXPECT_TRUE(get_content_callback.data().empty());
+ EXPECT_EQ(FILE_ERROR_OK, completion_error);
+
+ // Make sure the cached file's content.
+ std::string cache_file_content;
+ ASSERT_TRUE(
+ base::ReadFileToString(cache_file_path, &cache_file_content));
+ EXPECT_EQ(content, cache_file_content);
+}
+
+TEST_F(FakeFileSystemTest, GetFileContent_Directory) {
+ FileError initialize_error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry;
+ base::FilePath cache_file_path;
+ google_apis::test_util::TestGetContentCallback get_content_callback;
+ FileError completion_error = FILE_ERROR_FAILED;
+ base::Closure cancel_download = fake_file_system_->GetFileContent(
+ util::GetDriveMyDriveRootPath(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &initialize_error, &cache_file_path, &entry),
+ get_content_callback.callback(),
+ google_apis::test_util::CreateCopyResultCallback(&completion_error));
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_NOT_A_FILE, completion_error);
+}
+
+TEST_F(FakeFileSystemTest, GetResourceEntry) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry;
+ fake_file_system_->GetResourceEntry(
+ util::GetDriveMyDriveRootPath().AppendASCII(
+ "Directory 1/Sub Directory Folder"),
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ("sub_dir_folder_resource_id", entry->resource_id());
+}
+
+TEST_F(FakeFileSystemTest, GetResourceEntry_Root) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry;
+ fake_file_system_->GetResourceEntry(
+ util::GetDriveMyDriveRootPath(),
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->file_info().is_directory());
+ EXPECT_EQ(fake_drive_service_->GetRootResourceId(), entry->resource_id());
+ EXPECT_EQ(util::kDriveMyDriveRootDirName, entry->title());
+}
+
+TEST_F(FakeFileSystemTest, GetResourceEntry_Invalid) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry;
+ fake_file_system_->GetResourceEntry(
+ util::GetDriveMyDriveRootPath().AppendASCII("Invalid File Name"),
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_EQ(FILE_ERROR_NOT_FOUND, error);
+ ASSERT_FALSE(entry);
+}
+
+} // namespace test_util
+} // namespace drive
diff --git a/components/drive/file_system.cc b/components/drive/file_system.cc
new file mode 100644
index 0000000..78670b3
--- /dev/null
+++ b/components/drive/file_system.cc
@@ -0,0 +1,1060 @@
+// 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.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/prefs/pref_service.h"
+#include "components/drive/change_list_loader.h"
+#include "components/drive/directory_loader.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_pref_names.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system/copy_operation.h"
+#include "components/drive/file_system/create_directory_operation.h"
+#include "components/drive/file_system/create_file_operation.h"
+#include "components/drive/file_system/download_operation.h"
+#include "components/drive/file_system/get_file_for_saving_operation.h"
+#include "components/drive/file_system/move_operation.h"
+#include "components/drive/file_system/open_file_operation.h"
+#include "components/drive/file_system/remove_operation.h"
+#include "components/drive/file_system/search_operation.h"
+#include "components/drive/file_system/set_property_operation.h"
+#include "components/drive/file_system/touch_operation.h"
+#include "components/drive/file_system/truncate_operation.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/file_system_observer.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/remove_stale_cache_files.h"
+#include "components/drive/resource_entry_conversion.h"
+#include "components/drive/search_metadata.h"
+#include "components/drive/sync_client.h"
+#include "google_apis/drive/drive_api_parser.h"
+
+namespace drive {
+namespace {
+
+// Gets a ResourceEntry from the metadata, and overwrites its file info when the
+// cached file is dirty.
+FileError GetLocallyStoredResourceEntry(
+ internal::ResourceMetadata* resource_metadata,
+ internal::FileCache* cache,
+ const base::FilePath& file_path,
+ ResourceEntry* entry) {
+ std::string local_id;
+ FileError error = resource_metadata->GetIdByPath(file_path, &local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ error = resource_metadata->GetResourceEntryById(local_id, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ // For entries that will never be cached, use the original resource entry
+ // as is.
+ if (!entry->has_file_specific_info() ||
+ entry->file_specific_info().is_hosted_document())
+ return FILE_ERROR_OK;
+
+ // When cache is not found, use the original resource entry as is.
+ if (!entry->file_specific_info().has_cache_state())
+ return FILE_ERROR_OK;
+
+ // When cache is non-dirty and obsolete (old hash), use the original entry.
+ if (!entry->file_specific_info().cache_state().is_dirty() &&
+ entry->file_specific_info().md5() !=
+ entry->file_specific_info().cache_state().md5())
+ return FILE_ERROR_OK;
+
+ // If there's a valid cache, obtain the file info from the cache file itself.
+ base::FilePath local_cache_path;
+ error = cache->GetFile(local_id, &local_cache_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ base::File::Info file_info;
+ if (!base::GetFileInfo(local_cache_path, &file_info))
+ return FILE_ERROR_NOT_FOUND;
+
+ entry->mutable_file_info()->set_size(file_info.size);
+ return FILE_ERROR_OK;
+}
+
+// Runs the callback with parameters.
+void RunGetResourceEntryCallback(const GetResourceEntryCallback& callback,
+ scoped_ptr<ResourceEntry> entry,
+ FileError error) {
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK)
+ entry.reset();
+ callback.Run(error, entry.Pass());
+}
+
+// Used to implement Pin().
+FileError PinInternal(internal::ResourceMetadata* resource_metadata,
+ internal::FileCache* cache,
+ const base::FilePath& file_path,
+ std::string* local_id) {
+ FileError error = resource_metadata->GetIdByPath(file_path, local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ ResourceEntry entry;
+ error = resource_metadata->GetResourceEntryById(*local_id, &entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ // TODO(hashimoto): Support pinning directories. crbug.com/127831
+ if (entry.file_info().is_directory())
+ return FILE_ERROR_NOT_A_FILE;
+
+ return cache->Pin(*local_id);
+}
+
+// Used to implement Unpin().
+FileError UnpinInternal(internal::ResourceMetadata* resource_metadata,
+ internal::FileCache* cache,
+ const base::FilePath& file_path,
+ std::string* local_id) {
+ FileError error = resource_metadata->GetIdByPath(file_path, local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ return cache->Unpin(*local_id);
+}
+
+// Used to implement MarkCacheFileAsMounted().
+FileError MarkCacheFileAsMountedInternal(
+ internal::ResourceMetadata* resource_metadata,
+ internal::FileCache* cache,
+ const base::FilePath& drive_file_path,
+ base::FilePath* cache_file_path) {
+ std::string local_id;
+ FileError error = resource_metadata->GetIdByPath(drive_file_path, &local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ return cache->MarkAsMounted(local_id, cache_file_path);
+}
+
+// Runs the callback with arguments.
+void RunMarkMountedCallback(const MarkMountedCallback& callback,
+ base::FilePath* cache_file_path,
+ FileError error) {
+ DCHECK(!callback.is_null());
+ callback.Run(error, *cache_file_path);
+}
+
+// Callback for ResourceMetadata::GetLargestChangestamp.
+// |callback| must not be null.
+void OnGetLargestChangestamp(
+ FileSystemMetadata metadata, // Will be modified.
+ const GetFilesystemMetadataCallback& callback,
+ const int64* largest_changestamp,
+ FileError error) {
+ DCHECK(!callback.is_null());
+
+ metadata.largest_changestamp = *largest_changestamp;
+ callback.Run(metadata);
+}
+
+// Thin adapter to map GetFileCallback to FileOperationCallback.
+void GetFileCallbackToFileOperationCallbackAdapter(
+ const FileOperationCallback& callback,
+ FileError error,
+ const base::FilePath& unused_file_path,
+ scoped_ptr<ResourceEntry> unused_entry) {
+ callback.Run(error);
+}
+
+// Clears |resource_metadata| and |cache|.
+FileError ResetOnBlockingPool(internal::ResourceMetadata* resource_metadata,
+ internal::FileCache* cache) {
+ FileError error = resource_metadata->Reset();
+ if (error != FILE_ERROR_OK)
+ return error;
+ return cache->ClearAll() ? FILE_ERROR_OK : FILE_ERROR_FAILED;
+}
+
+// Part of GetPathFromResourceId().
+// Obtains |file_path| from |resource_id|. The function should be run on the
+// blocking pool.
+FileError GetPathFromResourceIdOnBlockingPool(
+ internal::ResourceMetadata* resource_metadata,
+ const std::string& resource_id,
+ base::FilePath* file_path) {
+ std::string local_id;
+ const FileError error =
+ resource_metadata->GetIdByResourceId(resource_id, &local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+ return resource_metadata->GetFilePath(local_id, file_path);
+}
+
+// Part of GetPathFromResourceId().
+// Called when GetPathFromResourceIdInBlockingPool is complete.
+void GetPathFromResourceIdAfterGetPath(base::FilePath* file_path,
+ const GetFilePathCallback& callback,
+ FileError error) {
+ callback.Run(error, *file_path);
+}
+
+bool FreeDiskSpaceIfNeededForOnBlockingPool(internal::FileCache* cache,
+ int64 num_bytes) {
+ return cache->FreeDiskSpaceIfNeededFor(num_bytes);
+}
+
+uint64_t CalculateEvictableCacheSizeOnBlockingPool(internal::FileCache* cache) {
+ return cache->CalculateEvictableCacheSize();
+}
+
+// Excludes hosted documents from the given entries.
+// Used to implement ReadDirectory().
+void FilterHostedDocuments(const ReadDirectoryEntriesCallback& callback,
+ scoped_ptr<ResourceEntryVector> entries) {
+ DCHECK(!callback.is_null());
+
+ if (entries) {
+ // TODO(kinaba): Stop handling hide_hosted_docs here. crbug.com/256520.
+ scoped_ptr<ResourceEntryVector> filtered(new ResourceEntryVector);
+ for (size_t i = 0; i < entries->size(); ++i) {
+ if (entries->at(i).file_specific_info().is_hosted_document()) {
+ continue;
+ }
+ filtered->push_back(entries->at(i));
+ }
+ entries.swap(filtered);
+ }
+ callback.Run(entries.Pass());
+}
+
+// Adapter for using FileOperationCallback as google_apis::EntryActionCallback.
+void RunFileOperationCallbackAsEntryActionCallback(
+ const FileOperationCallback& callback,
+ google_apis::DriveApiErrorCode error) {
+ callback.Run(GDataToFileError(error));
+}
+
+// Checks if the |entry|'s hash is included in |hashes|.
+bool CheckHashes(const std::set<std::string>& hashes,
+ const ResourceEntry& entry) {
+ return hashes.find(entry.file_specific_info().md5()) != hashes.end();
+}
+
+// Runs |callback| with |error| and the list of HashAndFilePath obtained from
+// |original_result|.
+void RunSearchByHashesCallback(
+ const SearchByHashesCallback& callback,
+ FileError error,
+ scoped_ptr<MetadataSearchResultVector> original_result) {
+ std::vector<HashAndFilePath> result;
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, result);
+ return;
+ }
+ for (const auto& search_result : *original_result) {
+ HashAndFilePath hash_and_path;
+ hash_and_path.hash = search_result.md5;
+ hash_and_path.path = search_result.path;
+ result.push_back(hash_and_path);
+ }
+ callback.Run(FILE_ERROR_OK, result);
+}
+
+} // namespace
+
+struct FileSystem::CreateDirectoryParams {
+ base::FilePath directory_path;
+ bool is_exclusive;
+ bool is_recursive;
+ FileOperationCallback callback;
+};
+
+FileSystem::FileSystem(PrefService* pref_service,
+ EventLogger* logger,
+ internal::FileCache* cache,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* resource_metadata,
+ base::SequencedTaskRunner* blocking_task_runner,
+ base::SingleThreadTaskRunner* file_task_runner,
+ const base::FilePath& temporary_file_directory)
+ : pref_service_(pref_service),
+ logger_(logger),
+ cache_(cache),
+ scheduler_(scheduler),
+ resource_metadata_(resource_metadata),
+ last_update_check_error_(FILE_ERROR_OK),
+ blocking_task_runner_(blocking_task_runner),
+ file_task_runner_(file_task_runner),
+ temporary_file_directory_(temporary_file_directory),
+ weak_ptr_factory_(this) {
+ ResetComponents();
+}
+
+FileSystem::~FileSystem() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ directory_loader_->RemoveObserver(this);
+ change_list_loader_->RemoveObserver(this);
+}
+
+void FileSystem::Reset(const FileOperationCallback& callback) {
+ // Discard the current loader and operation objects and renew them. This is to
+ // avoid that changes initiated before the metadata reset is applied after the
+ // reset, which may cause an inconsistent state.
+ // TODO(kinaba): callbacks held in the subcomponents are discarded. We might
+ // want to have a way to abort and flush callbacks in in-flight operations.
+ ResetComponents();
+
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&ResetOnBlockingPool, resource_metadata_, cache_),
+ callback);
+}
+
+void FileSystem::ResetComponents() {
+ file_system::OperationDelegate* delegate = this;
+
+ about_resource_loader_.reset(new internal::AboutResourceLoader(scheduler_));
+ loader_controller_.reset(new internal::LoaderController);
+ change_list_loader_.reset(new internal::ChangeListLoader(
+ logger_,
+ blocking_task_runner_.get(),
+ resource_metadata_,
+ scheduler_,
+ about_resource_loader_.get(),
+ loader_controller_.get()));
+ change_list_loader_->AddObserver(this);
+ directory_loader_.reset(new internal::DirectoryLoader(
+ logger_,
+ blocking_task_runner_.get(),
+ resource_metadata_,
+ scheduler_,
+ about_resource_loader_.get(),
+ loader_controller_.get()));
+ directory_loader_->AddObserver(this);
+
+ sync_client_.reset(new internal::SyncClient(blocking_task_runner_.get(),
+ delegate,
+ scheduler_,
+ resource_metadata_,
+ cache_,
+ loader_controller_.get(),
+ temporary_file_directory_));
+
+ copy_operation_.reset(
+ new file_system::CopyOperation(blocking_task_runner_.get(),
+ delegate,
+ scheduler_,
+ resource_metadata_,
+ cache_));
+ create_directory_operation_.reset(new file_system::CreateDirectoryOperation(
+ blocking_task_runner_.get(), delegate, resource_metadata_));
+ create_file_operation_.reset(
+ new file_system::CreateFileOperation(blocking_task_runner_.get(),
+ delegate,
+ resource_metadata_));
+ move_operation_.reset(
+ new file_system::MoveOperation(blocking_task_runner_.get(),
+ delegate,
+ resource_metadata_));
+ open_file_operation_.reset(
+ new file_system::OpenFileOperation(blocking_task_runner_.get(),
+ delegate,
+ scheduler_,
+ resource_metadata_,
+ cache_,
+ temporary_file_directory_));
+ remove_operation_.reset(
+ new file_system::RemoveOperation(blocking_task_runner_.get(),
+ delegate,
+ resource_metadata_,
+ cache_));
+ touch_operation_.reset(new file_system::TouchOperation(
+ blocking_task_runner_.get(), delegate, resource_metadata_));
+ truncate_operation_.reset(
+ new file_system::TruncateOperation(blocking_task_runner_.get(),
+ delegate,
+ scheduler_,
+ resource_metadata_,
+ cache_,
+ temporary_file_directory_));
+ download_operation_.reset(
+ new file_system::DownloadOperation(blocking_task_runner_.get(),
+ delegate,
+ scheduler_,
+ resource_metadata_,
+ cache_,
+ temporary_file_directory_));
+ search_operation_.reset(new file_system::SearchOperation(
+ blocking_task_runner_.get(), scheduler_, resource_metadata_,
+ loader_controller_.get()));
+ get_file_for_saving_operation_.reset(
+ new file_system::GetFileForSavingOperation(
+ logger_, blocking_task_runner_.get(), file_task_runner_.get(),
+ delegate, scheduler_, resource_metadata_, cache_,
+ temporary_file_directory_));
+ set_property_operation_.reset(new file_system::SetPropertyOperation(
+ blocking_task_runner_.get(), delegate, resource_metadata_));
+}
+
+void FileSystem::CheckForUpdates() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "CheckForUpdates";
+
+ change_list_loader_->CheckForUpdates(
+ base::Bind(&FileSystem::OnUpdateChecked, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void FileSystem::OnUpdateChecked(FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DVLOG(1) << "CheckForUpdates finished: " << FileErrorToString(error);
+ last_update_check_time_ = base::Time::Now();
+ last_update_check_error_ = error;
+}
+
+void FileSystem::AddObserver(FileSystemObserver* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observers_.AddObserver(observer);
+}
+
+void FileSystem::RemoveObserver(FileSystemObserver* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observers_.RemoveObserver(observer);
+}
+
+void FileSystem::TransferFileFromLocalToRemote(
+ const base::FilePath& local_src_file_path,
+ const base::FilePath& remote_dest_file_path,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ copy_operation_->TransferFileFromLocalToRemote(local_src_file_path,
+ remote_dest_file_path,
+ callback);
+}
+
+void FileSystem::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());
+ copy_operation_->Copy(
+ src_file_path, dest_file_path, preserve_last_modified, callback);
+}
+
+void FileSystem::Move(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ move_operation_->Move(src_file_path, dest_file_path, callback);
+}
+
+void FileSystem::Remove(const base::FilePath& file_path,
+ bool is_recursive,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ remove_operation_->Remove(file_path, is_recursive, callback);
+}
+
+void FileSystem::CreateDirectory(
+ const base::FilePath& directory_path,
+ bool is_exclusive,
+ bool is_recursive,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ CreateDirectoryParams params;
+ params.directory_path = directory_path;
+ params.is_exclusive = is_exclusive;
+ params.is_recursive = is_recursive;
+ params.callback = callback;
+
+ // Ensure its parent directory is loaded to the local metadata.
+ ReadDirectory(directory_path.DirName(),
+ ReadDirectoryEntriesCallback(),
+ base::Bind(&FileSystem::CreateDirectoryAfterRead,
+ weak_ptr_factory_.GetWeakPtr(), params));
+}
+
+void FileSystem::CreateDirectoryAfterRead(const CreateDirectoryParams& params,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!params.callback.is_null());
+
+ DVLOG_IF(1, error != FILE_ERROR_OK) << "ReadDirectory failed. "
+ << FileErrorToString(error);
+
+ create_directory_operation_->CreateDirectory(
+ params.directory_path, params.is_exclusive, params.is_recursive,
+ params.callback);
+}
+
+void FileSystem::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());
+ create_file_operation_->CreateFile(
+ file_path, is_exclusive, mime_type, callback);
+}
+
+void FileSystem::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());
+ touch_operation_->TouchFile(
+ file_path, last_access_time, last_modified_time, callback);
+}
+
+void FileSystem::TruncateFile(const base::FilePath& file_path,
+ int64 length,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ truncate_operation_->Truncate(file_path, length, callback);
+}
+
+void FileSystem::Pin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ std::string* local_id = new std::string;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&PinInternal, resource_metadata_, cache_, file_path, local_id),
+ base::Bind(&FileSystem::FinishPin,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ base::Owned(local_id)));
+}
+
+void FileSystem::FinishPin(const FileOperationCallback& callback,
+ const std::string* local_id,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error == FILE_ERROR_OK)
+ sync_client_->AddFetchTask(*local_id);
+ callback.Run(error);
+}
+
+void FileSystem::Unpin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ std::string* local_id = new std::string;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(
+ &UnpinInternal, resource_metadata_, cache_, file_path, local_id),
+ base::Bind(&FileSystem::FinishUnpin,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ base::Owned(local_id)));
+}
+
+void FileSystem::FinishUnpin(const FileOperationCallback& callback,
+ const std::string* local_id,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error == FILE_ERROR_OK)
+ sync_client_->RemoveFetchTask(*local_id);
+ callback.Run(error);
+}
+
+void FileSystem::GetFile(const base::FilePath& file_path,
+ const GetFileCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ download_operation_->EnsureFileDownloadedByPath(
+ file_path,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ callback);
+}
+
+void FileSystem::GetFileForSaving(const base::FilePath& file_path,
+ const GetFileCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ get_file_for_saving_operation_->GetFileForSaving(file_path, callback);
+}
+
+base::Closure FileSystem::GetFileContent(
+ const base::FilePath& file_path,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!initialized_callback.is_null());
+ DCHECK(!get_content_callback.is_null());
+ DCHECK(!completion_callback.is_null());
+
+ return download_operation_->EnsureFileDownloadedByPath(
+ file_path,
+ ClientContext(USER_INITIATED),
+ initialized_callback,
+ get_content_callback,
+ base::Bind(&GetFileCallbackToFileOperationCallbackAdapter,
+ completion_callback));
+}
+
+void FileSystem::GetResourceEntry(
+ const base::FilePath& file_path,
+ const GetResourceEntryCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ ReadDirectory(file_path.DirName(),
+ ReadDirectoryEntriesCallback(),
+ base::Bind(&FileSystem::GetResourceEntryAfterRead,
+ weak_ptr_factory_.GetWeakPtr(),
+ file_path,
+ callback));
+}
+
+void FileSystem::GetResourceEntryAfterRead(
+ const base::FilePath& file_path,
+ const GetResourceEntryCallback& callback,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ DVLOG_IF(1, error != FILE_ERROR_OK) << "ReadDirectory failed. "
+ << FileErrorToString(error);
+
+ scoped_ptr<ResourceEntry> entry(new ResourceEntry);
+ ResourceEntry* entry_ptr = entry.get();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&GetLocallyStoredResourceEntry,
+ resource_metadata_,
+ cache_,
+ file_path,
+ entry_ptr),
+ base::Bind(&RunGetResourceEntryCallback, callback, base::Passed(&entry)));
+}
+
+void FileSystem::ReadDirectory(
+ const base::FilePath& directory_path,
+ const ReadDirectoryEntriesCallback& entries_callback_in,
+ const FileOperationCallback& completion_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!completion_callback.is_null());
+
+ const bool hide_hosted_docs =
+ pref_service_->GetBoolean(prefs::kDisableDriveHostedFiles);
+ ReadDirectoryEntriesCallback entries_callback = entries_callback_in;
+ if (!entries_callback.is_null() && hide_hosted_docs)
+ entries_callback = base::Bind(&FilterHostedDocuments, entries_callback);
+
+ directory_loader_->ReadDirectory(
+ directory_path, entries_callback, completion_callback);
+
+ // Also start loading all of the user's contents.
+ change_list_loader_->LoadIfNeeded(
+ base::Bind(&util::EmptyFileOperationCallback));
+}
+
+void FileSystem::GetAvailableSpace(
+ const GetAvailableSpaceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ about_resource_loader_->GetAboutResource(
+ base::Bind(&FileSystem::OnGetAboutResource,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback));
+}
+
+void FileSystem::OnGetAboutResource(
+ const GetAvailableSpaceCallback& callback,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::AboutResource> about_resource) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileError error = GDataToFileError(status);
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, -1, -1);
+ return;
+ }
+ DCHECK(about_resource);
+
+ callback.Run(FILE_ERROR_OK, about_resource->quota_bytes_total(),
+ about_resource->quota_bytes_used_aggregate());
+}
+
+void FileSystem::GetShareUrl(const base::FilePath& file_path,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ // Resolve the resource id.
+ ResourceEntry* entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::GetResourceEntryByPath,
+ base::Unretained(resource_metadata_),
+ file_path,
+ entry),
+ base::Bind(&FileSystem::GetShareUrlAfterGetResourceEntry,
+ weak_ptr_factory_.GetWeakPtr(),
+ file_path,
+ embed_origin,
+ callback,
+ base::Owned(entry)));
+}
+
+void FileSystem::GetShareUrlAfterGetResourceEntry(
+ const base::FilePath& file_path,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback,
+ ResourceEntry* entry,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, GURL());
+ return;
+ }
+ if (entry->resource_id().empty()) {
+ // This entry does not exist on the server. Just return.
+ callback.Run(FILE_ERROR_FAILED, GURL());
+ return;
+ }
+
+ scheduler_->GetShareUrl(
+ entry->resource_id(),
+ embed_origin,
+ ClientContext(USER_INITIATED),
+ base::Bind(&FileSystem::OnGetResourceEntryForGetShareUrl,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback));
+}
+
+void FileSystem::OnGetResourceEntryForGetShareUrl(
+ const GetShareUrlCallback& callback,
+ google_apis::DriveApiErrorCode status,
+ const GURL& share_url) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileError error = GDataToFileError(status);
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error, GURL());
+ return;
+ }
+
+ if (share_url.is_empty()) {
+ callback.Run(FILE_ERROR_FAILED, GURL());
+ return;
+ }
+
+ callback.Run(FILE_ERROR_OK, share_url);
+}
+
+void FileSystem::Search(const std::string& search_query,
+ const GURL& next_link,
+ const SearchCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ search_operation_->Search(search_query, next_link, callback);
+}
+
+void FileSystem::SearchMetadata(const std::string& query,
+ int options,
+ int at_most_num_matches,
+ const SearchMetadataCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // TODO(satorux): Stop handling hide_hosted_docs here. crbug.com/256520.
+ if (pref_service_->GetBoolean(prefs::kDisableDriveHostedFiles))
+ options |= SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS;
+
+ drive::internal::SearchMetadata(
+ blocking_task_runner_, resource_metadata_, query,
+ base::Bind(&drive::internal::MatchesType, options), at_most_num_matches,
+ callback);
+}
+
+void FileSystem::SearchByHashes(const std::set<std::string>& hashes,
+ const SearchByHashesCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ drive::internal::SearchMetadata(
+ blocking_task_runner_, resource_metadata_,
+ /* any file name */ "", base::Bind(&CheckHashes, hashes),
+ std::numeric_limits<size_t>::max(),
+ base::Bind(&RunSearchByHashesCallback, callback));
+}
+
+void FileSystem::OnFileChangedByOperation(const FileChange& changed_files) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ FOR_EACH_OBSERVER(
+ FileSystemObserver, observers_, OnFileChanged(changed_files));
+}
+
+void FileSystem::OnEntryUpdatedByOperation(const ClientContext& context,
+ const std::string& local_id) {
+ sync_client_->AddUpdateTask(context, local_id);
+}
+
+void FileSystem::OnDriveSyncError(file_system::DriveSyncErrorType type,
+ const std::string& local_id) {
+ base::FilePath* file_path = new base::FilePath;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::GetFilePath,
+ base::Unretained(resource_metadata_),
+ local_id,
+ file_path),
+ base::Bind(&FileSystem::OnDriveSyncErrorAfterGetFilePath,
+ weak_ptr_factory_.GetWeakPtr(),
+ type,
+ base::Owned(file_path)));
+}
+
+void FileSystem::OnDriveSyncErrorAfterGetFilePath(
+ file_system::DriveSyncErrorType type,
+ const base::FilePath* file_path,
+ FileError error) {
+ if (error != FILE_ERROR_OK)
+ return;
+ FOR_EACH_OBSERVER(FileSystemObserver,
+ observers_,
+ OnDriveSyncError(type, *file_path));
+}
+
+bool FileSystem::WaitForSyncComplete(const std::string& local_id,
+ const FileOperationCallback& callback) {
+ return sync_client_->WaitForUpdateTaskToComplete(local_id, callback);
+}
+
+void FileSystem::OnDirectoryReloaded(const base::FilePath& directory_path) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ FOR_EACH_OBSERVER(
+ FileSystemObserver, observers_, OnDirectoryChanged(directory_path));
+}
+
+void FileSystem::OnFileChanged(const FileChange& changed_files) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ FOR_EACH_OBSERVER(
+ FileSystemObserver, observers_, OnFileChanged(changed_files));
+}
+
+void FileSystem::OnLoadFromServerComplete() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ sync_client_->StartCheckingExistingPinnedFiles();
+}
+
+void FileSystem::OnInitialLoadComplete() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ blocking_task_runner_->PostTask(FROM_HERE,
+ base::Bind(&internal::RemoveStaleCacheFiles,
+ cache_,
+ resource_metadata_));
+ sync_client_->StartProcessingBacklog();
+}
+
+void FileSystem::GetMetadata(
+ const GetFilesystemMetadataCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileSystemMetadata metadata;
+ metadata.refreshing = change_list_loader_->IsRefreshing();
+
+ // Metadata related to delta update.
+ metadata.last_update_check_time = last_update_check_time_;
+ metadata.last_update_check_error = last_update_check_error_;
+
+ int64* largest_changestamp = new int64(0);
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::GetLargestChangestamp,
+ base::Unretained(resource_metadata_),
+ largest_changestamp),
+ base::Bind(&OnGetLargestChangestamp,
+ metadata,
+ callback,
+ base::Owned(largest_changestamp)));
+}
+
+void FileSystem::MarkCacheFileAsMounted(
+ const base::FilePath& drive_file_path,
+ const MarkMountedCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ base::FilePath* cache_file_path = new base::FilePath;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&MarkCacheFileAsMountedInternal,
+ resource_metadata_,
+ cache_,
+ drive_file_path,
+ cache_file_path),
+ base::Bind(
+ &RunMarkMountedCallback, callback, base::Owned(cache_file_path)));
+}
+
+void FileSystem::MarkCacheFileAsUnmounted(
+ const base::FilePath& cache_file_path,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (!cache_->IsUnderFileCacheDirectory(cache_file_path)) {
+ callback.Run(FILE_ERROR_FAILED);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::FileCache::MarkAsUnmounted,
+ base::Unretained(cache_),
+ cache_file_path),
+ callback);
+}
+
+void FileSystem::AddPermission(const base::FilePath& drive_file_path,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ // Resolve the resource id.
+ ResourceEntry* const entry = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::GetResourceEntryByPath,
+ base::Unretained(resource_metadata_),
+ drive_file_path,
+ entry),
+ base::Bind(&FileSystem::AddPermissionAfterGetResourceEntry,
+ weak_ptr_factory_.GetWeakPtr(),
+ email,
+ role,
+ callback,
+ base::Owned(entry)));
+}
+
+void FileSystem::AddPermissionAfterGetResourceEntry(
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const FileOperationCallback& callback,
+ ResourceEntry* entry,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ scheduler_->AddPermission(
+ entry->resource_id(),
+ email,
+ role,
+ base::Bind(&RunFileOperationCallbackAsEntryActionCallback, callback));
+}
+
+void FileSystem::SetProperty(
+ const base::FilePath& drive_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());
+
+ set_property_operation_->SetProperty(drive_file_path, visibility, key, value,
+ callback);
+}
+
+void FileSystem::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());
+
+ open_file_operation_->OpenFile(file_path, open_mode, mime_type, callback);
+}
+
+void FileSystem::GetPathFromResourceId(const std::string& resource_id,
+ const GetFilePathCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ base::FilePath* const file_path = new base::FilePath();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&GetPathFromResourceIdOnBlockingPool,
+ resource_metadata_,
+ resource_id,
+ file_path),
+ base::Bind(&GetPathFromResourceIdAfterGetPath,
+ base::Owned(file_path),
+ callback));
+}
+
+void FileSystem::FreeDiskSpaceIfNeededFor(
+ int64 num_bytes,
+ const FreeDiskSpaceCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(), FROM_HERE,
+ base::Bind(&FreeDiskSpaceIfNeededForOnBlockingPool, cache_, num_bytes),
+ callback);
+}
+
+void FileSystem::CalculateEvictableCacheSize(
+ const EvictableCacheSizeCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(), FROM_HERE,
+ base::Bind(&CalculateEvictableCacheSizeOnBlockingPool, cache_), callback);
+}
+} // namespace drive
diff --git a/components/drive/file_system.h b/components/drive/file_system.h
new file mode 100644
index 0000000..0c143842
--- /dev/null
+++ b/components/drive/file_system.h
@@ -0,0 +1,311 @@
+// 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_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "components/drive/change_list_loader_observer.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/file_system_interface.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+class PrefService;
+
+namespace base {
+class SequencedTaskRunner;
+} // namespace base
+
+namespace google_apis {
+class AboutResource;
+class ResourceEntry;
+} // namespace google_apis
+
+namespace drive {
+struct ClientContext;
+class EventLogger;
+class FileCacheEntry;
+class FileSystemObserver;
+class JobScheduler;
+
+namespace internal {
+class AboutResourceLoader;
+class ChangeListLoader;
+class DirectoryLoader;
+class FileCache;
+class LoaderController;
+class ResourceMetadata;
+class SyncClient;
+} // namespace internal
+
+namespace file_system {
+class CopyOperation;
+class CreateDirectoryOperation;
+class CreateFileOperation;
+class DownloadOperation;
+class GetFileForSavingOperation;
+class MoveOperation;
+class OpenFileOperation;
+class RemoveOperation;
+class SearchOperation;
+class SetPropertyOperation;
+class TouchOperation;
+class TruncateOperation;
+} // namespace file_system
+
+// The production implementation of FileSystemInterface.
+class FileSystem : public FileSystemInterface,
+ public internal::ChangeListLoaderObserver,
+ public file_system::OperationDelegate {
+ public:
+ FileSystem(PrefService* pref_service,
+ EventLogger* logger,
+ internal::FileCache* cache,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* resource_metadata,
+ base::SequencedTaskRunner* blocking_task_runner,
+ base::SingleThreadTaskRunner* file_task_runner,
+ const base::FilePath& temporary_file_directory);
+ ~FileSystem() override;
+
+ // FileSystemInterface overrides.
+ void AddObserver(FileSystemObserver* observer) override;
+ void RemoveObserver(FileSystemObserver* observer) override;
+ void CheckForUpdates() override;
+ void Search(const std::string& search_query,
+ const GURL& next_link,
+ const SearchCallback& callback) override;
+ void SearchMetadata(const std::string& query,
+ int options,
+ int at_most_num_matches,
+ const SearchMetadataCallback& callback) override;
+ void SearchByHashes(const std::set<std::string>& hashes,
+ const SearchByHashesCallback& callback) override;
+ void TransferFileFromLocalToRemote(
+ const base::FilePath& local_src_file_path,
+ const base::FilePath& remote_dest_file_path,
+ const FileOperationCallback& callback) override;
+ void OpenFile(const base::FilePath& file_path,
+ OpenMode open_mode,
+ const std::string& mime_type,
+ const OpenFileCallback& callback) override;
+ void Copy(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ bool preserve_last_modified,
+ const FileOperationCallback& callback) override;
+ void Move(const base::FilePath& src_file_path,
+ const base::FilePath& dest_file_path,
+ const FileOperationCallback& callback) override;
+ void Remove(const base::FilePath& file_path,
+ bool is_recursive,
+ const FileOperationCallback& callback) override;
+ void CreateDirectory(const base::FilePath& directory_path,
+ bool is_exclusive,
+ bool is_recursive,
+ const FileOperationCallback& callback) override;
+ void CreateFile(const base::FilePath& file_path,
+ bool is_exclusive,
+ const std::string& mime_type,
+ const FileOperationCallback& callback) override;
+ void TouchFile(const base::FilePath& file_path,
+ const base::Time& last_access_time,
+ const base::Time& last_modified_time,
+ const FileOperationCallback& callback) override;
+ void TruncateFile(const base::FilePath& file_path,
+ int64 length,
+ const FileOperationCallback& callback) override;
+ void Pin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) override;
+ void Unpin(const base::FilePath& file_path,
+ const FileOperationCallback& callback) override;
+ void GetFile(const base::FilePath& file_path,
+ const GetFileCallback& callback) override;
+ void GetFileForSaving(const base::FilePath& file_path,
+ const GetFileCallback& callback) override;
+ base::Closure GetFileContent(
+ const base::FilePath& file_path,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const FileOperationCallback& completion_callback) override;
+ void GetResourceEntry(const base::FilePath& file_path,
+ const GetResourceEntryCallback& callback) override;
+ void ReadDirectory(const base::FilePath& directory_path,
+ const ReadDirectoryEntriesCallback& entries_callback,
+ const FileOperationCallback& completion_callback) override;
+ void GetAvailableSpace(const GetAvailableSpaceCallback& callback) override;
+ void GetShareUrl(const base::FilePath& file_path,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback) override;
+ void GetMetadata(const GetFilesystemMetadataCallback& callback) override;
+ void MarkCacheFileAsMounted(const base::FilePath& drive_file_path,
+ const MarkMountedCallback& callback) override;
+ void MarkCacheFileAsUnmounted(const base::FilePath& cache_file_path,
+ const FileOperationCallback& callback) override;
+ void AddPermission(const base::FilePath& drive_file_path,
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const FileOperationCallback& callback) override;
+ 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) override;
+ void Reset(const FileOperationCallback& callback) override;
+ void GetPathFromResourceId(const std::string& resource_id,
+ const GetFilePathCallback& callback) override;
+ void FreeDiskSpaceIfNeededFor(int64 num_bytes,
+ const FreeDiskSpaceCallback& callback) override;
+ void CalculateEvictableCacheSize(
+ const EvictableCacheSizeCallback& callback) override;
+
+ // file_system::OperationDelegate overrides.
+ void OnFileChangedByOperation(const FileChange& changed_files) override;
+ void OnEntryUpdatedByOperation(const ClientContext& context,
+ const std::string& local_id) override;
+ void OnDriveSyncError(file_system::DriveSyncErrorType type,
+ const std::string& local_id) override;
+ bool WaitForSyncComplete(const std::string& local_id,
+ const FileOperationCallback& callback) override;
+
+ // ChangeListLoader::Observer overrides.
+ // Used to propagate events from ChangeListLoader.
+ void OnDirectoryReloaded(const base::FilePath& directory_path) override;
+ void OnFileChanged(const FileChange& changed_files) override;
+ void OnLoadFromServerComplete() override;
+ void OnInitialLoadComplete() override;
+
+ // Used by tests.
+ internal::ChangeListLoader* change_list_loader_for_testing() {
+ return change_list_loader_.get();
+ }
+ internal::SyncClient* sync_client_for_testing() { return sync_client_.get(); }
+
+ private:
+ struct CreateDirectoryParams;
+
+ // Used for initialization and Reset(). (Re-)initializes sub components that
+ // need to be recreated during the reset of resource metadata and the cache.
+ void ResetComponents();
+
+ // Part of CreateDirectory(). Called after ReadDirectory()
+ // is called and made sure that the resource metadata is loaded.
+ void CreateDirectoryAfterRead(const CreateDirectoryParams& params,
+ FileError error);
+
+ void FinishPin(const FileOperationCallback& callback,
+ const std::string* local_id,
+ FileError error);
+
+ void FinishUnpin(const FileOperationCallback& callback,
+ const std::string* local_id,
+ FileError error);
+
+ // Callback for handling about resource fetch.
+ void OnGetAboutResource(
+ const GetAvailableSpaceCallback& callback,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::AboutResource> about_resource);
+
+ // Part of CheckForUpdates(). Called when
+ // ChangeListLoader::CheckForUpdates() is complete.
+ void OnUpdateChecked(FileError error);
+
+ // Part of GetResourceEntry().
+ // Called when ReadDirectory() is complete.
+ void GetResourceEntryAfterRead(const base::FilePath& file_path,
+ const GetResourceEntryCallback& callback,
+ FileError error);
+
+ // Part of GetShareUrl. Resolves the resource entry to get the resource it,
+ // and then uses it to ask for the share url. |callback| must not be null.
+ void GetShareUrlAfterGetResourceEntry(const base::FilePath& file_path,
+ const GURL& embed_origin,
+ const GetShareUrlCallback& callback,
+ ResourceEntry* entry,
+ FileError error);
+ void OnGetResourceEntryForGetShareUrl(const GetShareUrlCallback& callback,
+ google_apis::DriveApiErrorCode status,
+ const GURL& share_url);
+ // Part of AddPermission.
+ void AddPermissionAfterGetResourceEntry(
+ const std::string& email,
+ google_apis::drive::PermissionRole role,
+ const FileOperationCallback& callback,
+ ResourceEntry* entry,
+ FileError error);
+
+ // Part of OnDriveSyncError().
+ virtual void OnDriveSyncErrorAfterGetFilePath(
+ file_system::DriveSyncErrorType type,
+ const base::FilePath* file_path,
+ FileError error);
+
+ // Used to get Drive related preferences.
+ PrefService* pref_service_;
+
+ // Sub components owned by DriveIntegrationService.
+ EventLogger* logger_;
+ internal::FileCache* cache_;
+ JobScheduler* scheduler_;
+ internal::ResourceMetadata* resource_metadata_;
+
+ // Time of the last update check.
+ base::Time last_update_check_time_;
+
+ // Error of the last update check.
+ FileError last_update_check_error_;
+
+ // Used to load about resource.
+ scoped_ptr<internal::AboutResourceLoader> about_resource_loader_;
+
+ // Used to control ChangeListLoader.
+ scoped_ptr<internal::LoaderController> loader_controller_;
+
+ // The loader is used to load the change lists.
+ scoped_ptr<internal::ChangeListLoader> change_list_loader_;
+
+ scoped_ptr<internal::DirectoryLoader> directory_loader_;
+
+ scoped_ptr<internal::SyncClient> sync_client_;
+
+ base::ObserverList<FileSystemObserver> observers_;
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
+
+ base::FilePath temporary_file_directory_;
+
+ // Implementation of each file system operation.
+ scoped_ptr<file_system::CopyOperation> copy_operation_;
+ scoped_ptr<file_system::CreateDirectoryOperation> create_directory_operation_;
+ scoped_ptr<file_system::CreateFileOperation> create_file_operation_;
+ scoped_ptr<file_system::MoveOperation> move_operation_;
+ scoped_ptr<file_system::OpenFileOperation> open_file_operation_;
+ scoped_ptr<file_system::RemoveOperation> remove_operation_;
+ scoped_ptr<file_system::TouchOperation> touch_operation_;
+ scoped_ptr<file_system::TruncateOperation> truncate_operation_;
+ scoped_ptr<file_system::DownloadOperation> download_operation_;
+ scoped_ptr<file_system::SearchOperation> search_operation_;
+ scoped_ptr<file_system::GetFileForSavingOperation>
+ get_file_for_saving_operation_;
+ scoped_ptr<file_system::SetPropertyOperation> set_property_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<FileSystem> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileSystem);
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_H_
diff --git a/components/drive/file_system_observer.h b/components/drive/file_system_observer.h
new file mode 100644
index 0000000..cf0d141
--- /dev/null
+++ b/components/drive/file_system_observer.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_FILE_SYSTEM_OBSERVER_H_
+#define COMPONENTS_DRIVE_FILE_SYSTEM_OBSERVER_H_
+
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_delegate.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace drive {
+
+class FileChange;
+
+// Interface for classes that need to observe events from classes implementing
+// FileSystemInterface.
+// All events are notified on UI thread.
+class FileSystemObserver {
+ public:
+ // Triggered when a content of a directory has been changed.
+ // |directory_path| is a virtual directory path (/drive/...) representing
+ // changed directory.
+ virtual void OnDirectoryChanged(const base::FilePath& directory_path) {}
+ virtual void OnFileChanged(const FileChange& file_change) {}
+
+ // Triggared when a specific drive error occurred.
+ // |type| is a type of the error. |file_name| is a virtual path of the entry.
+ virtual void OnDriveSyncError(file_system::DriveSyncErrorType type,
+ const base::FilePath& file_path) {}
+
+ protected:
+ virtual ~FileSystemObserver() {}
+};
+
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_FILE_SYSTEM_OBSERVER_H_
diff --git a/components/drive/file_system_unittest.cc b/components/drive/file_system_unittest.cc
new file mode 100644
index 0000000..a502a2a
--- /dev/null
+++ b/components/drive/file_system_unittest.cc
@@ -0,0 +1,1064 @@
+// 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.h"
+
+#include <string>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/testing_pref_service.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/change_list_loader.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/drive_test_util.h"
+#include "components/drive/event_logger.h"
+#include "components/drive/fake_free_disk_space_getter.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/file_system_observer.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/service/fake_drive_service.h"
+#include "components/drive/service/test_util.h"
+#include "components/drive/sync_client.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.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 {
+
+// Counts the number of invocation, and if it increased up to |expected_counter|
+// quits the current message loop by calling |quit|.
+void AsyncInitializationCallback(
+ int* counter, int expected_counter, const base::Closure& quit,
+ FileError error, scoped_ptr<ResourceEntry> entry) {
+ if (error != FILE_ERROR_OK || !entry) {
+ // If we hit an error case, quit the message loop immediately.
+ // Then the expectation in the test case can find it because the actual
+ // value of |counter| is different from the expected one.
+ quit.Run();
+ return;
+ }
+
+ (*counter)++;
+ if (*counter >= expected_counter)
+ quit.Run();
+}
+
+bool CompareHashAndFilePath(const HashAndFilePath& a,
+ const HashAndFilePath& b) {
+ const int result = a.hash.compare(b.hash);
+ if (result < 0)
+ return true;
+ if (result > 0)
+ return false;
+ return a.path.AsUTF8Unsafe().compare(b.path.AsUTF8Unsafe()) < 0;
+}
+
+// This class is used to record directory changes and examine them later.
+class MockDirectoryChangeObserver : public FileSystemObserver {
+ public:
+ MockDirectoryChangeObserver() {}
+ ~MockDirectoryChangeObserver() override {}
+
+ // FileSystemObserver overrides.
+ void OnDirectoryChanged(const base::FilePath& directory_path) override {
+ changed_directories_.push_back(directory_path);
+ }
+
+ void OnFileChanged(const FileChange& new_file_change) override {
+ changed_files_.Apply(new_file_change);
+ }
+
+ const std::vector<base::FilePath>& changed_directories() const {
+ return changed_directories_;
+ }
+
+ const FileChange& changed_files() const { return changed_files_; }
+
+ private:
+ std::vector<base::FilePath> changed_directories_;
+ FileChange changed_files_;
+ DISALLOW_COPY_AND_ASSIGN(MockDirectoryChangeObserver);
+};
+
+} // namespace
+
+class FileSystemTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ pref_service_.reset(new TestingPrefServiceSimple);
+ test_util::RegisterDrivePrefs(pref_service_->registry());
+
+ logger_.reset(new EventLogger);
+ fake_drive_service_.reset(new FakeDriveService);
+ test_util::SetUpTestEntries(fake_drive_service_.get());
+
+ fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
+
+ scheduler_.reset(new JobScheduler(
+ pref_service_.get(),
+ logger_.get(),
+ fake_drive_service_.get(),
+ base::ThreadTaskRunnerHandle::Get().get()));
+
+ mock_directory_observer_.reset(new MockDirectoryChangeObserver);
+
+ SetUpResourceMetadataAndFileSystem();
+ }
+
+ void SetUpResourceMetadataAndFileSystem() {
+ const base::FilePath metadata_dir = temp_dir_.path().AppendASCII("meta");
+ ASSERT_TRUE(base::CreateDirectory(metadata_dir));
+ metadata_storage_.reset(new internal::ResourceMetadataStorage(
+ metadata_dir, base::ThreadTaskRunnerHandle::Get().get()));
+ ASSERT_TRUE(metadata_storage_->Initialize());
+
+ const base::FilePath cache_dir = temp_dir_.path().AppendASCII("files");
+ ASSERT_TRUE(base::CreateDirectory(cache_dir));
+ cache_.reset(new internal::FileCache(
+ metadata_storage_.get(),
+ cache_dir,
+ base::ThreadTaskRunnerHandle::Get().get(),
+ fake_free_disk_space_getter_.get()));
+ ASSERT_TRUE(cache_->Initialize());
+
+ resource_metadata_.reset(new internal::ResourceMetadata(
+ metadata_storage_.get(), cache_.get(),
+ base::ThreadTaskRunnerHandle::Get()));
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->Initialize());
+
+ const base::FilePath temp_file_dir = temp_dir_.path().AppendASCII("tmp");
+ ASSERT_TRUE(base::CreateDirectory(temp_file_dir));
+ file_task_runner_ = content::BrowserThread::GetMessageLoopProxyForThread(
+ content::BrowserThread::FILE);
+ file_system_.reset(new FileSystem(
+ pref_service_.get(), logger_.get(), cache_.get(), scheduler_.get(),
+ resource_metadata_.get(), base::ThreadTaskRunnerHandle::Get().get(),
+ file_task_runner_.get(), temp_file_dir));
+ file_system_->AddObserver(mock_directory_observer_.get());
+
+ // Disable delaying so that the sync starts immediately.
+ file_system_->sync_client_for_testing()->set_delay_for_testing(
+ base::TimeDelta::FromSeconds(0));
+ }
+
+ // Loads the full resource list via FakeDriveService.
+ bool LoadFullResourceList() {
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->change_list_loader_for_testing()->LoadIfNeeded(
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ return error == FILE_ERROR_OK;
+ }
+
+ // Gets resource entry by path synchronously.
+ scoped_ptr<ResourceEntry> GetResourceEntrySync(
+ const base::FilePath& file_path) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntry> entry;
+ file_system_->GetResourceEntry(
+ file_path,
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ return entry.Pass();
+ }
+
+ // Gets directory info by path synchronously.
+ scoped_ptr<ResourceEntryVector> ReadDirectorySync(
+ const base::FilePath& file_path) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<ResourceEntryVector> entries(new ResourceEntryVector);
+ file_system_->ReadDirectory(
+ file_path,
+ base::Bind(&AccumulateReadDirectoryResult, entries.get()),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ if (error != FILE_ERROR_OK)
+ entries.reset();
+ return entries.Pass();
+ }
+
+ // Used to implement ReadDirectorySync().
+ static void AccumulateReadDirectoryResult(
+ ResourceEntryVector* out_entries,
+ scoped_ptr<ResourceEntryVector> entries) {
+ ASSERT_TRUE(entries);
+ out_entries->insert(out_entries->end(), entries->begin(), entries->end());
+ }
+
+ // Returns true if an entry exists at |file_path|.
+ bool EntryExists(const base::FilePath& file_path) {
+ return GetResourceEntrySync(file_path);
+ }
+
+ // Flag for specifying the timestamp of the test filesystem cache.
+ enum SetUpTestFileSystemParam {
+ USE_OLD_TIMESTAMP,
+ USE_SERVER_TIMESTAMP,
+ };
+
+ // Sets up a filesystem with directories: drive/root, drive/root/Dir1,
+ // drive/root/Dir1/SubDir2 and files drive/root/File1, drive/root/Dir1/File2,
+ // drive/root/Dir1/SubDir2/File3. If |use_up_to_date_timestamp| is true, sets
+ // the changestamp to that of FakeDriveService, indicating the cache is
+ // holding the latest file system info.
+ void SetUpTestFileSystem(SetUpTestFileSystemParam param) {
+ // Destroy the existing resource metadata to close DB.
+ resource_metadata_.reset();
+
+ const base::FilePath metadata_dir = temp_dir_.path().AppendASCII("meta");
+ ASSERT_TRUE(base::CreateDirectory(metadata_dir));
+ scoped_ptr<internal::ResourceMetadataStorage,
+ test_util::DestroyHelperForTests> metadata_storage(
+ new internal::ResourceMetadataStorage(
+ metadata_dir, base::ThreadTaskRunnerHandle::Get().get()));
+
+ const base::FilePath cache_dir = temp_dir_.path().AppendASCII("files");
+ scoped_ptr<internal::FileCache, test_util::DestroyHelperForTests> cache(
+ new internal::FileCache(metadata_storage.get(),
+ cache_dir,
+ base::ThreadTaskRunnerHandle::Get().get(),
+ fake_free_disk_space_getter_.get()));
+
+ scoped_ptr<internal::ResourceMetadata, test_util::DestroyHelperForTests>
+ resource_metadata(new internal::ResourceMetadata(
+ metadata_storage_.get(), cache.get(),
+ base::ThreadTaskRunnerHandle::Get()));
+
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata->Initialize());
+
+ const int64 changestamp = param == USE_SERVER_TIMESTAMP ?
+ fake_drive_service_->about_resource().largest_change_id() : 1;
+ ASSERT_EQ(FILE_ERROR_OK,
+ resource_metadata->SetLargestChangestamp(changestamp));
+
+ // drive/root
+ ResourceEntry root;
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata->GetResourceEntryByPath(
+ util::GetDriveMyDriveRootPath(), &root));
+ root.set_resource_id(fake_drive_service_->GetRootResourceId());
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata->RefreshEntry(root));
+
+ std::string local_id;
+
+ // drive/root/File1
+ ResourceEntry file1;
+ file1.set_title("File1");
+ file1.set_resource_id("resource_id:File1");
+ file1.set_parent_local_id(root.local_id());
+ file1.mutable_file_specific_info()->set_md5("md5#1");
+ file1.mutable_file_info()->set_is_directory(false);
+ file1.mutable_file_info()->set_size(1048576);
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(file1, &local_id));
+
+ // drive/root/Dir1
+ ResourceEntry dir1;
+ dir1.set_title("Dir1");
+ dir1.set_resource_id("resource_id:Dir1");
+ dir1.set_parent_local_id(root.local_id());
+ dir1.mutable_file_info()->set_is_directory(true);
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(dir1, &local_id));
+ const std::string dir1_local_id = local_id;
+
+ // drive/root/Dir1/File2
+ ResourceEntry file2;
+ file2.set_title("File2");
+ file2.set_resource_id("resource_id:File2");
+ file2.set_parent_local_id(dir1_local_id);
+ file2.mutable_file_specific_info()->set_md5("md5#2");
+ file2.mutable_file_info()->set_is_directory(false);
+ file2.mutable_file_info()->set_size(555);
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(file2, &local_id));
+
+ // drive/root/Dir1/SubDir2
+ ResourceEntry dir2;
+ dir2.set_title("SubDir2");
+ dir2.set_resource_id("resource_id:SubDir2");
+ dir2.set_parent_local_id(dir1_local_id);
+ dir2.mutable_file_info()->set_is_directory(true);
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(dir2, &local_id));
+ const std::string dir2_local_id = local_id;
+
+ // drive/root/Dir1/SubDir2/File3
+ ResourceEntry file3;
+ file3.set_title("File3");
+ file3.set_resource_id("resource_id:File3");
+ file3.set_parent_local_id(dir2_local_id);
+ file3.mutable_file_specific_info()->set_md5("md5#2");
+ file3.mutable_file_info()->set_is_directory(false);
+ file3.mutable_file_info()->set_size(12345);
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata->AddEntry(file3, &local_id));
+
+ // Recreate resource metadata.
+ SetUpResourceMetadataAndFileSystem();
+ }
+
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir temp_dir_;
+ // We don't use TestingProfile::GetPrefs() in favor of having less
+ // dependencies to Profile in general.
+ scoped_ptr<TestingPrefServiceSimple> pref_service_;
+
+ scoped_ptr<EventLogger> logger_;
+ scoped_ptr<FakeDriveService> fake_drive_service_;
+ scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
+ scoped_ptr<JobScheduler> scheduler_;
+ scoped_ptr<MockDirectoryChangeObserver> mock_directory_observer_;
+
+ scoped_ptr<internal::ResourceMetadataStorage,
+ test_util::DestroyHelperForTests> metadata_storage_;
+ scoped_ptr<internal::FileCache, test_util::DestroyHelperForTests> cache_;
+ scoped_ptr<internal::ResourceMetadata, test_util::DestroyHelperForTests>
+ resource_metadata_;
+ scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
+ scoped_ptr<FileSystem> file_system_;
+};
+
+TEST_F(FileSystemTest, SearchByHashes) {
+ ASSERT_NO_FATAL_FAILURE(SetUpTestFileSystem(USE_SERVER_TIMESTAMP));
+
+ std::set<std::string> hashes;
+ FileError error;
+ std::vector<HashAndFilePath> results;
+
+ hashes.insert("md5#1");
+ file_system_->SearchByHashes(
+ hashes,
+ google_apis::test_util::CreateCopyResultCallback(&error, &results));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_EQ(1u, results.size());
+ EXPECT_EQ(FILE_PATH_LITERAL("drive/root/File1"), results[0].path.value());
+
+ hashes.clear();
+ hashes.insert("md5#2");
+ file_system_->SearchByHashes(
+ hashes,
+ google_apis::test_util::CreateCopyResultCallback(&error, &results));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_EQ(2u, results.size());
+ std::sort(results.begin(), results.end(), &CompareHashAndFilePath);
+ EXPECT_EQ(FILE_PATH_LITERAL("drive/root/Dir1/File2"),
+ results[0].path.value());
+ EXPECT_EQ(FILE_PATH_LITERAL("drive/root/Dir1/SubDir2/File3"),
+ results[1].path.value());
+
+ hashes.clear();
+ hashes.insert("md5#1");
+ hashes.insert("md5#2");
+ file_system_->SearchByHashes(
+ hashes,
+ google_apis::test_util::CreateCopyResultCallback(&error, &results));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_EQ(3u, results.size());
+ std::sort(results.begin(), results.end(), &CompareHashAndFilePath);
+ EXPECT_EQ(FILE_PATH_LITERAL("drive/root/File1"), results[0].path.value());
+ EXPECT_EQ(FILE_PATH_LITERAL("drive/root/Dir1/File2"),
+ results[1].path.value());
+ EXPECT_EQ(FILE_PATH_LITERAL("drive/root/Dir1/SubDir2/File3"),
+ results[2].path.value());
+}
+
+TEST_F(FileSystemTest, Copy) {
+ base::FilePath src_file_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_file_path(FILE_PATH_LITERAL("drive/root/Copied.txt"));
+ EXPECT_TRUE(GetResourceEntrySync(src_file_path));
+ EXPECT_FALSE(GetResourceEntrySync(dest_file_path));
+
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->Copy(src_file_path,
+ dest_file_path,
+ false, // preserve_last_modified,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Entry is added on the server.
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(dest_file_path);
+ ASSERT_TRUE(entry);
+
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_drive_service_->GetFileResource(
+ entry->resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_EQ(entry->title(), server_entry->title());
+ EXPECT_FALSE(server_entry->IsDirectory());
+}
+
+TEST_F(FileSystemTest, Move) {
+ base::FilePath src_file_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ base::FilePath dest_file_path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/Moved.txt"));
+ EXPECT_TRUE(GetResourceEntrySync(src_file_path));
+ EXPECT_FALSE(GetResourceEntrySync(dest_file_path));
+ scoped_ptr<ResourceEntry> parent =
+ GetResourceEntrySync(dest_file_path.DirName());
+ ASSERT_TRUE(parent);
+
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->Move(src_file_path,
+ dest_file_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Entry is moved on the server.
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(dest_file_path);
+ ASSERT_TRUE(entry);
+
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_drive_service_->GetFileResource(
+ entry->resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_EQ(entry->title(), server_entry->title());
+
+ ASSERT_FALSE(server_entry->parents().empty());
+ EXPECT_EQ(parent->resource_id(), server_entry->parents()[0].file_id());
+}
+
+TEST_F(FileSystemTest, Remove) {
+ base::FilePath file_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(file_path);
+ ASSERT_TRUE(entry);
+
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->Remove(
+ file_path,
+ false, // is_resursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Entry is removed on the server.
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_drive_service_->GetFileResource(
+ entry->resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_TRUE(server_entry->labels().is_trashed());
+}
+
+TEST_F(FileSystemTest, CreateDirectory) {
+ base::FilePath directory_path(FILE_PATH_LITERAL("drive/root/New Directory"));
+ EXPECT_FALSE(GetResourceEntrySync(directory_path));
+
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->CreateDirectory(
+ directory_path,
+ true, // is_exclusive
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Directory is created on the server.
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(directory_path);
+ ASSERT_TRUE(entry);
+
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_drive_service_->GetFileResource(
+ entry->resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_EQ(entry->title(), server_entry->title());
+ EXPECT_TRUE(server_entry->IsDirectory());
+}
+
+TEST_F(FileSystemTest, CreateFile) {
+ base::FilePath file_path(FILE_PATH_LITERAL("drive/root/New File.txt"));
+ EXPECT_FALSE(GetResourceEntrySync(file_path));
+
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->CreateFile(
+ file_path,
+ true, // is_exclusive
+ "text/plain",
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // File is created on the server.
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(file_path);
+ ASSERT_TRUE(entry);
+
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_drive_service_->GetFileResource(
+ entry->resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_EQ(entry->title(), server_entry->title());
+ EXPECT_FALSE(server_entry->IsDirectory());
+}
+
+TEST_F(FileSystemTest, TouchFile) {
+ base::FilePath file_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(file_path);
+ ASSERT_TRUE(entry);
+
+ base::Time last_accessed =
+ base::Time::FromInternalValue(entry->file_info().last_accessed()) +
+ base::TimeDelta::FromSeconds(1);
+ base::Time last_modified =
+ base::Time::FromInternalValue(entry->file_info().last_modified()) +
+ base::TimeDelta::FromSeconds(1);
+
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->TouchFile(
+ file_path,
+ last_accessed,
+ last_modified,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // File is touched on the server.
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_drive_service_->GetFileResource(
+ entry->resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_EQ(last_accessed, server_entry->last_viewed_by_me_date());
+ EXPECT_EQ(last_modified, server_entry->modified_date());
+}
+
+TEST_F(FileSystemTest, TruncateFile) {
+ base::FilePath file_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(file_path);
+ ASSERT_TRUE(entry);
+
+ const int64 kLength = entry->file_info().size() + 100;
+
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->TruncateFile(
+ file_path,
+ kLength,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // File is touched on the server.
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_drive_service_->GetFileResource(
+ entry->resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_EQ(kLength, server_entry->file_size());
+}
+
+TEST_F(FileSystemTest, DuplicatedAsyncInitialization) {
+ base::RunLoop loop;
+
+ int counter = 0;
+ const GetResourceEntryCallback& callback = base::Bind(
+ &AsyncInitializationCallback, &counter, 2, loop.QuitClosure());
+
+ file_system_->GetResourceEntry(
+ base::FilePath(FILE_PATH_LITERAL("drive/root")), callback);
+ file_system_->GetResourceEntry(
+ base::FilePath(FILE_PATH_LITERAL("drive/root")), callback);
+ loop.Run(); // Wait to get our result
+ EXPECT_EQ(2, counter);
+
+ EXPECT_EQ(1, fake_drive_service_->file_list_load_count());
+}
+
+TEST_F(FileSystemTest, GetGrandRootEntry) {
+ const base::FilePath kFilePath(FILE_PATH_LITERAL("drive"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(kFilePath);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(util::kDriveGrandRootLocalId, entry->local_id());
+}
+
+TEST_F(FileSystemTest, GetOtherDirEntry) {
+ const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/other"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(kFilePath);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(util::kDriveOtherDirLocalId, entry->local_id());
+}
+
+TEST_F(FileSystemTest, GetMyDriveRoot) {
+ const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(kFilePath);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(fake_drive_service_->GetRootResourceId(), entry->resource_id());
+
+ // After "fast fetch" is done, full resource list is fetched.
+ EXPECT_EQ(1, fake_drive_service_->file_list_load_count());
+}
+
+TEST_F(FileSystemTest, GetExistingFile) {
+ // Simulate the situation that full feed fetching takes very long time,
+ // to test the recursive "fast fetch" feature is properly working.
+ fake_drive_service_->set_never_return_all_file_list(true);
+
+ const base::FilePath kFilePath(
+ FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(kFilePath);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ("subdirectory_file_1_id", entry->resource_id());
+
+ EXPECT_EQ(1, fake_drive_service_->about_resource_load_count());
+ EXPECT_EQ(2, fake_drive_service_->directory_load_count());
+ EXPECT_EQ(1, fake_drive_service_->blocked_file_list_load_count());
+}
+
+TEST_F(FileSystemTest, GetExistingDocument) {
+ const base::FilePath kFilePath(
+ FILE_PATH_LITERAL("drive/root/Document 1 excludeDir-test.gdoc"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(kFilePath);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ("5_document_resource_id", entry->resource_id());
+}
+
+TEST_F(FileSystemTest, GetNonExistingFile) {
+ const base::FilePath kFilePath(
+ FILE_PATH_LITERAL("drive/root/nonexisting.file"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(kFilePath);
+ EXPECT_FALSE(entry);
+}
+
+TEST_F(FileSystemTest, GetInSubSubdir) {
+ const base::FilePath kFilePath(
+ FILE_PATH_LITERAL("drive/root/Directory 1/Sub Directory Folder/"
+ "Sub Sub Directory Folder"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(kFilePath);
+ ASSERT_TRUE(entry);
+ ASSERT_EQ("sub_sub_directory_folder_id", entry->resource_id());
+}
+
+TEST_F(FileSystemTest, GetOrphanFile) {
+ ASSERT_TRUE(LoadFullResourceList());
+
+ // Entry without parents are placed under "drive/other".
+ const base::FilePath kFilePath(
+ FILE_PATH_LITERAL("drive/other/Orphan File 1.txt"));
+ scoped_ptr<ResourceEntry> entry = GetResourceEntrySync(kFilePath);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ("1_orphanfile_resource_id", entry->resource_id());
+}
+
+TEST_F(FileSystemTest, ReadDirectory_Root) {
+ // ReadDirectory() should kick off the resource list loading.
+ scoped_ptr<ResourceEntryVector> entries(
+ ReadDirectorySync(base::FilePath::FromUTF8Unsafe("drive")));
+ // The root directory should be read correctly.
+ ASSERT_TRUE(entries);
+ ASSERT_EQ(3U, entries->size());
+
+ // The found three directories should be /drive/root, /drive/other and
+ // /drive/trash.
+ std::set<base::FilePath> found;
+ for (size_t i = 0; i < entries->size(); ++i)
+ found.insert(base::FilePath::FromUTF8Unsafe((*entries)[i].title()));
+ EXPECT_EQ(3U, found.size());
+ EXPECT_EQ(1U, found.count(base::FilePath::FromUTF8Unsafe(
+ util::kDriveMyDriveRootDirName)));
+ EXPECT_EQ(1U, found.count(
+ base::FilePath::FromUTF8Unsafe(util::kDriveOtherDirName)));
+ EXPECT_EQ(1U, found.count(
+ base::FilePath::FromUTF8Unsafe(util::kDriveTrashDirName)));
+}
+
+TEST_F(FileSystemTest, ReadDirectory_NonRootDirectory) {
+ // ReadDirectory() should kick off the resource list loading.
+ scoped_ptr<ResourceEntryVector> entries(
+ ReadDirectorySync(
+ base::FilePath::FromUTF8Unsafe("drive/root/Directory 1")));
+ // The non root directory should also be read correctly.
+ // There was a bug (crbug.com/181487), which broke this behavior.
+ // Make sure this is fixed.
+ ASSERT_TRUE(entries);
+ EXPECT_EQ(3U, entries->size());
+}
+
+TEST_F(FileSystemTest, LoadFileSystemFromUpToDateCache) {
+ ASSERT_NO_FATAL_FAILURE(SetUpTestFileSystem(USE_SERVER_TIMESTAMP));
+
+ // Kicks loading of cached file system and query for server update.
+ EXPECT_TRUE(ReadDirectorySync(util::GetDriveMyDriveRootPath()));
+
+ // SetUpTestFileSystem and FakeDriveService have the same
+ // changestamp (i.e. the local metadata is up-to-date), so no request for
+ // new resource list (i.e., call to GetResourceList) should happen.
+ EXPECT_EQ(0, fake_drive_service_->file_list_load_count());
+
+ // Since the file system has verified that it holds the latest snapshot,
+ // it should change its state to "loaded", which admits periodic refresh.
+ // To test it, call CheckForUpdates and verify it does try to check updates.
+ const int about_resource_load_count_before =
+ fake_drive_service_->about_resource_load_count();
+ file_system_->CheckForUpdates();
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_LT(about_resource_load_count_before,
+ fake_drive_service_->about_resource_load_count());
+}
+
+TEST_F(FileSystemTest, LoadFileSystemFromCacheWhileOffline) {
+ ASSERT_NO_FATAL_FAILURE(SetUpTestFileSystem(USE_OLD_TIMESTAMP));
+
+ // Make GetResourceList fail for simulating offline situation. This will
+ // leave the file system "loaded from cache, but not synced with server"
+ // state.
+ fake_drive_service_->set_offline(true);
+
+ // Load the root.
+ EXPECT_TRUE(ReadDirectorySync(util::GetDriveGrandRootPath()));
+ // Loading of about resource should not happen as it's offline.
+ EXPECT_EQ(0, fake_drive_service_->about_resource_load_count());
+
+ // Load "My Drive".
+ EXPECT_TRUE(ReadDirectorySync(util::GetDriveMyDriveRootPath()));
+ EXPECT_EQ(0, fake_drive_service_->about_resource_load_count());
+
+ // Tests that cached data can be loaded even if the server is not reachable.
+ EXPECT_TRUE(EntryExists(base::FilePath(
+ FILE_PATH_LITERAL("drive/root/File1"))));
+ EXPECT_TRUE(EntryExists(base::FilePath(
+ FILE_PATH_LITERAL("drive/root/Dir1"))));
+ EXPECT_TRUE(EntryExists(base::FilePath(
+ FILE_PATH_LITERAL("drive/root/Dir1/File2"))));
+ EXPECT_TRUE(EntryExists(base::FilePath(
+ FILE_PATH_LITERAL("drive/root/Dir1/SubDir2"))));
+ EXPECT_TRUE(EntryExists(base::FilePath(
+ FILE_PATH_LITERAL("drive/root/Dir1/SubDir2/File3"))));
+
+ // Since the file system has at least succeeded to load cached snapshot,
+ // the file system should be able to start periodic refresh.
+ // To test it, call CheckForUpdates and verify it does try to check
+ // updates, which will cause directory changes.
+ fake_drive_service_->set_offline(false);
+
+ file_system_->CheckForUpdates();
+
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(1, fake_drive_service_->about_resource_load_count());
+ EXPECT_EQ(1, fake_drive_service_->change_list_load_count());
+
+ ASSERT_LE(0u, mock_directory_observer_->changed_directories().size());
+ ASSERT_LE(1u, mock_directory_observer_->changed_files().size());
+}
+
+TEST_F(FileSystemTest, ReadDirectoryWhileRefreshing) {
+ // Use old timestamp so the fast fetch will be performed.
+ ASSERT_NO_FATAL_FAILURE(SetUpTestFileSystem(USE_OLD_TIMESTAMP));
+
+ // The list of resources in "drive/root/Dir1" should be fetched.
+ EXPECT_TRUE(ReadDirectorySync(base::FilePath(
+ FILE_PATH_LITERAL("drive/root/Dir1"))));
+ EXPECT_EQ(1, fake_drive_service_->directory_load_count());
+
+ ASSERT_LE(1u, mock_directory_observer_->changed_directories().size());
+}
+
+TEST_F(FileSystemTest, GetResourceEntryNonExistentWhileRefreshing) {
+ // Use old timestamp so the fast fetch will be performed.
+ ASSERT_NO_FATAL_FAILURE(SetUpTestFileSystem(USE_OLD_TIMESTAMP));
+
+ // If an entry is not found, parent directory's resource list is fetched.
+ EXPECT_FALSE(GetResourceEntrySync(base::FilePath(
+ FILE_PATH_LITERAL("drive/root/Dir1/NonExistentFile"))));
+ EXPECT_EQ(1, fake_drive_service_->directory_load_count());
+
+ ASSERT_LE(1u, mock_directory_observer_->changed_directories().size());
+}
+
+TEST_F(FileSystemTest, CreateDirectoryByImplicitLoad) {
+ // Intentionally *not* calling LoadFullResourceList(), for testing that
+ // CreateDirectory ensures the resource list is loaded before it runs.
+
+ base::FilePath existing_directory(
+ FILE_PATH_LITERAL("drive/root/Directory 1"));
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->CreateDirectory(
+ existing_directory,
+ true, // is_exclusive
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ // It should fail because is_exclusive is set to true.
+ EXPECT_EQ(FILE_ERROR_EXISTS, error);
+}
+
+TEST_F(FileSystemTest, CreateDirectoryRecursively) {
+ // Intentionally *not* calling LoadFullResourceList(), for testing that
+ // CreateDirectory ensures the resource list is loaded before it runs.
+
+ base::FilePath new_directory(
+ FILE_PATH_LITERAL("drive/root/Directory 1/a/b/c/d"));
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->CreateDirectory(
+ new_directory,
+ true, // is_exclusive
+ true, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ scoped_ptr<ResourceEntry> entry(GetResourceEntrySync(new_directory));
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->file_info().is_directory());
+}
+
+TEST_F(FileSystemTest, ReadDirectoryAfterUpdateWhileLoading) {
+ // Simulate the situation that full feed fetching takes very long time,
+ // to test the recursive "fast fetch" feature is properly working.
+ fake_drive_service_->set_never_return_all_file_list(true);
+
+ // On the fake server, create the test directory.
+ scoped_ptr<google_apis::FileResource> parent;
+ {
+ google_apis::DriveApiErrorCode error = google_apis::DRIVE_OTHER_ERROR;
+ fake_drive_service_->AddNewDirectory(
+ fake_drive_service_->GetRootResourceId(), "UpdateWhileLoadingTestDir",
+ AddNewDirectoryOptions(),
+ google_apis::test_util::CreateCopyResultCallback(&error, &parent));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(google_apis::HTTP_CREATED, error);
+ }
+
+ // Fetch the directory. Currently it is empty.
+ scoped_ptr<ResourceEntryVector> before = ReadDirectorySync(base::FilePath(
+ FILE_PATH_LITERAL("drive/root/UpdateWhileLoadingTestDir")));
+ ASSERT_TRUE(before);
+ EXPECT_EQ(0u, before->size());
+
+ // Create a file in the test directory.
+ scoped_ptr<google_apis::FileResource> entry;
+ {
+ google_apis::DriveApiErrorCode error = google_apis::DRIVE_OTHER_ERROR;
+ fake_drive_service_->AddNewFile(
+ "text/plain",
+ "(dummy data)",
+ parent->file_id(),
+ "TestFile",
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(google_apis::HTTP_CREATED, error);
+ }
+
+ // Notify the update to the file system.
+ file_system_->CheckForUpdates();
+
+ // Read the directory once again. Although the full feed fetching is not yet
+ // finished, the "fast fetch" of the directory works and the refreshed content
+ // is returned.
+ scoped_ptr<ResourceEntryVector> after = ReadDirectorySync(base::FilePath(
+ FILE_PATH_LITERAL("drive/root/UpdateWhileLoadingTestDir")));
+ ASSERT_TRUE(after);
+ EXPECT_EQ(1u, after->size());
+}
+
+TEST_F(FileSystemTest, PinAndUnpin) {
+ ASSERT_TRUE(LoadFullResourceList());
+
+ base::FilePath file_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+
+ // Get the file info.
+ scoped_ptr<ResourceEntry> entry(GetResourceEntrySync(file_path));
+ ASSERT_TRUE(entry);
+
+ // Pin the file.
+ FileError error = FILE_ERROR_FAILED;
+ file_system_->Pin(file_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ entry = GetResourceEntrySync(file_path);
+ ASSERT_TRUE(entry);
+ EXPECT_TRUE(entry->file_specific_info().cache_state().is_pinned());
+ EXPECT_TRUE(entry->file_specific_info().cache_state().is_present());
+
+ // Unpin the file.
+ error = FILE_ERROR_FAILED;
+ file_system_->Unpin(file_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ entry = GetResourceEntrySync(file_path);
+ ASSERT_TRUE(entry);
+ EXPECT_FALSE(entry->file_specific_info().cache_state().is_pinned());
+
+ // Pinned file gets synced and it results in entry state changes.
+ ASSERT_EQ(0u, mock_directory_observer_->changed_directories().size());
+ ASSERT_EQ(1u, mock_directory_observer_->changed_files().size());
+ EXPECT_EQ(1u,
+ mock_directory_observer_->changed_files().CountDirectory(
+ base::FilePath(FILE_PATH_LITERAL("drive/root"))));
+}
+
+TEST_F(FileSystemTest, PinAndUnpin_NotSynced) {
+ ASSERT_TRUE(LoadFullResourceList());
+
+ base::FilePath file_path(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+
+ // Get the file info.
+ scoped_ptr<ResourceEntry> entry(GetResourceEntrySync(file_path));
+ ASSERT_TRUE(entry);
+
+ // Unpin the file just after pinning. File fetch should be cancelled.
+ FileError error_pin = FILE_ERROR_FAILED;
+ file_system_->Pin(
+ file_path,
+ google_apis::test_util::CreateCopyResultCallback(&error_pin));
+
+ FileError error_unpin = FILE_ERROR_FAILED;
+ file_system_->Unpin(
+ file_path,
+ google_apis::test_util::CreateCopyResultCallback(&error_unpin));
+
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error_pin);
+ EXPECT_EQ(FILE_ERROR_OK, error_unpin);
+
+ // No cache file available because the sync was cancelled by Unpin().
+ entry = GetResourceEntrySync(file_path);
+ ASSERT_TRUE(entry);
+ EXPECT_FALSE(entry->file_specific_info().cache_state().is_present());
+}
+
+TEST_F(FileSystemTest, GetAvailableSpace) {
+ FileError error = FILE_ERROR_OK;
+ int64 bytes_total;
+ int64 bytes_used;
+ file_system_->GetAvailableSpace(
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &bytes_total, &bytes_used));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(6789012345LL, bytes_used);
+ EXPECT_EQ(9876543210LL, bytes_total);
+}
+
+TEST_F(FileSystemTest, MarkCacheFileAsMountedAndUnmounted) {
+ ASSERT_TRUE(LoadFullResourceList());
+
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+
+ // Make the file cached.
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ file_system_->GetFile(
+ file_in_root,
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &file_path, &entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Test for mounting.
+ error = FILE_ERROR_FAILED;
+ file_path.clear();
+ file_system_->MarkCacheFileAsMounted(
+ file_in_root,
+ google_apis::test_util::CreateCopyResultCallback(&error, &file_path));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Cannot remove a cache entry while it's being mounted.
+ EXPECT_EQ(FILE_ERROR_IN_USE, cache_->Remove(entry->local_id()));
+
+ // Test for unmounting.
+ error = FILE_ERROR_FAILED;
+ file_system_->MarkCacheFileAsUnmounted(
+ file_path,
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Now able to remove the cache entry.
+ EXPECT_EQ(FILE_ERROR_OK, cache_->Remove(entry->local_id()));
+}
+
+TEST_F(FileSystemTest, GetShareUrl) {
+ ASSERT_TRUE(LoadFullResourceList());
+
+ const base::FilePath kFileInRoot(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ const GURL kEmbedOrigin("chrome-extension://test-id");
+
+ // Try to fetch the URL for the sharing dialog.
+ FileError error = FILE_ERROR_FAILED;
+ GURL share_url;
+ file_system_->GetShareUrl(
+ kFileInRoot,
+ kEmbedOrigin,
+ google_apis::test_util::CreateCopyResultCallback(&error, &share_url));
+ content::RunAllBlockingPoolTasksUntilIdle();
+
+ // Verify the share url to the sharing dialog.
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_TRUE(share_url.is_valid());
+}
+
+TEST_F(FileSystemTest, FreeDiskSpaceIfNeededFor) {
+ ASSERT_TRUE(LoadFullResourceList());
+
+ base::FilePath file_in_root(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+
+ // Make the file cached.
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath file_path;
+ scoped_ptr<ResourceEntry> entry;
+ file_system_->GetFile(file_in_root,
+ 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());
+
+ bool available;
+ file_system_->FreeDiskSpaceIfNeededFor(
+ 512LL << 40,
+ google_apis::test_util::CreateCopyResultCallback(&available));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_FALSE(available);
+
+ entry = GetResourceEntrySync(file_in_root);
+ ASSERT_TRUE(entry);
+ EXPECT_FALSE(entry->file_specific_info().cache_state().is_present());
+}
+
+} // namespace drive
diff --git a/components/drive/remove_stale_cache_files.cc b/components/drive/remove_stale_cache_files.cc
new file mode 100644
index 0000000..93cab01
--- /dev/null
+++ b/components/drive/remove_stale_cache_files.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/remove_stale_cache_files.h"
+
+#include "base/logging.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/resource_metadata.h"
+
+namespace drive {
+namespace internal {
+
+void RemoveStaleCacheFiles(FileCache* cache,
+ ResourceMetadata* resource_metadata) {
+ scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator();
+ for (; !it->IsAtEnd(); it->Advance()) {
+ const ResourceEntry& entry = it->GetValue();
+ const FileCacheEntry& cache_state =
+ entry.file_specific_info().cache_state();
+ // Stale = not dirty but the MD5 does not match.
+ if (!cache_state.is_dirty() &&
+ cache_state.md5() != entry.file_specific_info().md5()) {
+ FileError error = cache->Remove(it->GetID());
+ LOG_IF(WARNING, error != FILE_ERROR_OK)
+ << "Failed to remove a stale cache file. resource_id: "
+ << it->GetID();
+ }
+ }
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/remove_stale_cache_files.h b/components/drive/remove_stale_cache_files.h
new file mode 100644
index 0000000..d3e922c
--- /dev/null
+++ b/components/drive/remove_stale_cache_files.h
@@ -0,0 +1,22 @@
+// 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_REMOVE_STALE_CACHE_FILES_H_
+#define COMPONENTS_DRIVE_REMOVE_STALE_CACHE_FILES_H_
+
+namespace drive{
+namespace internal {
+
+class FileCache;
+class ResourceMetadata;
+
+// Removes files from |cache| which are not dirty but the MD5 is obsolete.
+// Must be run on the same task runner as |cache| and |resource_metadata| use.
+void RemoveStaleCacheFiles(FileCache* cache,
+ ResourceMetadata* resource_metadata);
+
+} // namespace internal
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_REMOVE_STALE_CACHE_FILES_H_
diff --git a/components/drive/remove_stale_cache_files_unittest.cc b/components/drive/remove_stale_cache_files_unittest.cc
new file mode 100644
index 0000000..4fa16e6
--- /dev/null
+++ b/components/drive/remove_stale_cache_files_unittest.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string>
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/single_thread_task_runner.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_test_util.h"
+#include "components/drive/fake_free_disk_space_getter.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/remove_stale_cache_files.h"
+#include "components/drive/resource_metadata.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "google_apis/drive/test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace internal {
+
+class RemoveStaleCacheFilesTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
+
+ metadata_storage_.reset(new ResourceMetadataStorage(
+ temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+
+ cache_.reset(new FileCache(metadata_storage_.get(),
+ temp_dir_.path(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ fake_free_disk_space_getter_.get()));
+
+ resource_metadata_.reset(new ResourceMetadata(
+ metadata_storage_.get(), cache_.get(),
+ base::ThreadTaskRunnerHandle::Get()));
+
+ ASSERT_TRUE(metadata_storage_->Initialize());
+ ASSERT_TRUE(cache_->Initialize());
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->Initialize());
+ }
+
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir temp_dir_;
+
+ scoped_ptr<ResourceMetadataStorage,
+ test_util::DestroyHelperForTests> metadata_storage_;
+ scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
+ scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests>
+ resource_metadata_;
+ scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
+};
+
+TEST_F(RemoveStaleCacheFilesTest, RemoveStaleCacheFiles) {
+ base::FilePath dummy_file;
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &dummy_file));
+ std::string md5_metadata("abcdef0123456789"), md5_cache("ABCDEF9876543210");
+
+ // Create a stale cache file.
+ ResourceEntry entry;
+ std::string local_id;
+ entry.mutable_file_specific_info()->set_md5(md5_metadata);
+ entry.set_parent_local_id(util::kDriveGrandRootLocalId);
+ entry.set_title("File.txt");
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
+
+ EXPECT_EQ(FILE_ERROR_OK,
+ cache_->Store(local_id, md5_cache, dummy_file,
+ FileCache::FILE_OPERATION_COPY));
+
+ // Remove stale cache files.
+ RemoveStaleCacheFiles(cache_.get(), resource_metadata_.get());
+
+ // Verify that the cache is deleted.
+ EXPECT_EQ(FILE_ERROR_OK,
+ resource_metadata_->GetResourceEntryById(local_id, &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_present());
+}
+
+TEST_F(RemoveStaleCacheFilesTest, DirtyCacheFiles) {
+ base::FilePath dummy_file;
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &dummy_file));
+
+ // Dirty entry.
+ std::string md5_metadata("abcdef0123456789");
+
+ ResourceEntry entry;
+ std::string local_id;
+ entry.mutable_file_specific_info()->set_md5(md5_metadata);
+ entry.set_parent_local_id(util::kDriveGrandRootLocalId);
+ entry.set_title("file.txt");
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
+
+ EXPECT_EQ(FILE_ERROR_OK,
+ cache_->Store(local_id, std::string(), dummy_file,
+ FileCache::FILE_OPERATION_COPY));
+
+ // Remove stale cache files.
+ RemoveStaleCacheFiles(cache_.get(), resource_metadata_.get());
+
+ // Dirty cache should not be removed even though its MD5 doesn't match.
+ EXPECT_EQ(FILE_ERROR_OK,
+ resource_metadata_->GetResourceEntryById(local_id, &entry));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/search_metadata.cc b/components/drive/search_metadata.cc
new file mode 100644
index 0000000..381e81f
--- /dev/null
+++ b/components/drive/search_metadata.cc
@@ -0,0 +1,335 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/search_metadata.h"
+
+#include <algorithm>
+#include <queue>
+
+#include "base/bind.h"
+#include "base/i18n/string_search.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/file_system_core_util.h"
+#include "net/base/escape.h"
+
+namespace drive {
+namespace internal {
+
+namespace {
+
+struct ResultCandidate {
+ ResultCandidate(const std::string& local_id,
+ const ResourceEntry& entry,
+ const std::string& highlighted_base_name)
+ : local_id(local_id),
+ entry(entry),
+ highlighted_base_name(highlighted_base_name) {
+ }
+
+ std::string local_id;
+ ResourceEntry entry;
+ std::string highlighted_base_name;
+};
+
+// Used to sort the result candidates per the last accessed/modified time. The
+// recently accessed/modified files come first.
+bool CompareByTimestamp(const ResourceEntry& a, const ResourceEntry& b) {
+ const PlatformFileInfoProto& a_file_info = a.file_info();
+ const PlatformFileInfoProto& b_file_info = b.file_info();
+
+ if (a_file_info.last_accessed() != b_file_info.last_accessed())
+ return a_file_info.last_accessed() > b_file_info.last_accessed();
+
+ // When the entries have the same last access time (which happens quite often
+ // because Drive server doesn't set the field until an entry is viewed via
+ // drive.google.com), we use last modified time as the tie breaker.
+ return a_file_info.last_modified() > b_file_info.last_modified();
+}
+
+struct ResultCandidateComparator {
+ bool operator()(const ResultCandidate* a, const ResultCandidate* b) const {
+ return CompareByTimestamp(a->entry, b->entry);
+ }
+};
+
+// A wrapper of std::priority_queue which deals with pointers of values.
+template<typename T, typename Compare>
+class ScopedPriorityQueue {
+ public:
+ ScopedPriorityQueue() {}
+
+ ~ScopedPriorityQueue() {
+ while (!empty())
+ pop();
+ }
+
+ bool empty() const { return queue_.empty(); }
+
+ size_t size() const { return queue_.size(); }
+
+ const T* top() const { return queue_.top(); }
+
+ void push(T* x) { queue_.push(x); }
+
+ void pop() {
+ // Keep top alive for the pop() call so that debug checks can access
+ // underlying data (e.g. validating heap property of the priority queue
+ // will call the comparator).
+ T* saved_top = queue_.top();
+ queue_.pop();
+ delete saved_top;
+ }
+
+ private:
+ std::priority_queue<T*, std::vector<T*>, Compare> queue_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedPriorityQueue);
+};
+
+// Classifies the given entry as hidden if it's not under specific directories.
+class HiddenEntryClassifier {
+ public:
+ HiddenEntryClassifier(ResourceMetadata* metadata,
+ const std::string& mydrive_local_id)
+ : metadata_(metadata) {
+ // Only things under My Drive and drive/other are not hidden.
+ is_hiding_child_[mydrive_local_id] = false;
+ is_hiding_child_[util::kDriveOtherDirLocalId] = false;
+
+ // Everything else is hidden, including the directories mentioned above
+ // themselves.
+ is_hiding_child_[""] = true;
+ }
+
+ // |result| is set to true if |entry| is hidden.
+ FileError IsHidden(const ResourceEntry& entry, bool* result) {
+ // Look up for parents recursively.
+ std::vector<std::string> undetermined_ids;
+ undetermined_ids.push_back(entry.parent_local_id());
+
+ std::map<std::string, bool>::iterator it =
+ is_hiding_child_.find(undetermined_ids.back());
+ for (; it == is_hiding_child_.end();
+ it = is_hiding_child_.find(undetermined_ids.back())) {
+ ResourceEntry parent;
+ FileError error =
+ metadata_->GetResourceEntryById(undetermined_ids.back(), &parent);
+ if (error != FILE_ERROR_OK)
+ return error;
+ undetermined_ids.push_back(parent.parent_local_id());
+ }
+
+ // Cache the result.
+ undetermined_ids.pop_back(); // The last one is already in the map.
+ for (size_t i = 0; i < undetermined_ids.size(); ++i)
+ is_hiding_child_[undetermined_ids[i]] = it->second;
+
+ *result = it->second;
+ return FILE_ERROR_OK;
+ }
+
+ private:
+ ResourceMetadata* metadata_;
+
+ // local ID to is_hidden map.
+ std::map<std::string, bool> is_hiding_child_;
+};
+
+// Used to implement SearchMetadata.
+// Adds entry to the result when appropriate.
+// In particular, if |query| is non-null, only adds files with the name matching
+// the query.
+FileError MaybeAddEntryToResult(
+ ResourceMetadata* resource_metadata,
+ ResourceMetadata::Iterator* it,
+ base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query,
+ const SearchMetadataPredicate& predicate,
+ size_t at_most_num_matches,
+ HiddenEntryClassifier* hidden_entry_classifier,
+ ScopedPriorityQueue<ResultCandidate, ResultCandidateComparator>*
+ result_candidates) {
+ DCHECK_GE(at_most_num_matches, result_candidates->size());
+
+ const ResourceEntry& entry = it->GetValue();
+
+ // If the candidate set is already full, and this |entry| is old, do nothing.
+ // We perform this check first in order to avoid the costly find-and-highlight
+ // or FilePath lookup as much as possible.
+ if (result_candidates->size() == at_most_num_matches &&
+ !CompareByTimestamp(entry, result_candidates->top()->entry))
+ return FILE_ERROR_OK;
+
+ // Add |entry| to the result if the entry is eligible for the given
+ // |options| and matches the query. The base name of the entry must
+ // contain |query| to match the query.
+ std::string highlighted;
+ if (!predicate.Run(entry) ||
+ (query && !FindAndHighlight(entry.base_name(), query, &highlighted)))
+ return FILE_ERROR_OK;
+
+ // Hidden entry should not be returned.
+ bool hidden = false;
+ FileError error = hidden_entry_classifier->IsHidden(entry, &hidden);
+ if (error != FILE_ERROR_OK || hidden)
+ return error;
+
+ // Make space for |entry| when appropriate.
+ if (result_candidates->size() == at_most_num_matches)
+ result_candidates->pop();
+ result_candidates->push(new ResultCandidate(it->GetID(), entry, highlighted));
+ return FILE_ERROR_OK;
+}
+
+// Implements SearchMetadata().
+FileError SearchMetadataOnBlockingPool(ResourceMetadata* resource_metadata,
+ const std::string& query_text,
+ const SearchMetadataPredicate& predicate,
+ int at_most_num_matches,
+ MetadataSearchResultVector* results) {
+ ScopedPriorityQueue<ResultCandidate,
+ ResultCandidateComparator> result_candidates;
+
+ // Prepare data structure for searching.
+ base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
+ base::UTF8ToUTF16(query_text));
+
+ // Prepare an object to filter out hidden entries.
+ ResourceEntry mydrive;
+ FileError error = resource_metadata->GetResourceEntryByPath(
+ util::GetDriveMyDriveRootPath(), &mydrive);
+ if (error != FILE_ERROR_OK)
+ return error;
+ HiddenEntryClassifier hidden_entry_classifier(resource_metadata,
+ mydrive.local_id());
+
+ // Iterate over entries.
+ scoped_ptr<ResourceMetadata::Iterator> it = resource_metadata->GetIterator();
+ for (; !it->IsAtEnd(); it->Advance()) {
+ FileError error = MaybeAddEntryToResult(
+ resource_metadata, it.get(), query_text.empty() ? NULL : &query,
+ predicate, at_most_num_matches, &hidden_entry_classifier,
+ &result_candidates);
+ if (error != FILE_ERROR_OK)
+ return error;
+ }
+
+ // Prepare the result.
+ for (; !result_candidates.empty(); result_candidates.pop()) {
+ const ResultCandidate& candidate = *result_candidates.top();
+ // The path field of entries in result_candidates are empty at this point,
+ // because we don't want to run the expensive metadata DB look up except for
+ // the final results. Hence, here we fill the part.
+ base::FilePath path;
+ error = resource_metadata->GetFilePath(candidate.local_id, &path);
+ if (error != FILE_ERROR_OK)
+ return error;
+ bool is_directory = candidate.entry.file_info().is_directory();
+ results->push_back(MetadataSearchResult(
+ path, is_directory, candidate.highlighted_base_name,
+ candidate.entry.file_specific_info().md5()));
+ }
+
+ // Reverse the order here because |result_candidates| puts the most
+ // uninteresting candidate at the top.
+ std::reverse(results->begin(), results->end());
+
+ return FILE_ERROR_OK;
+}
+
+// Runs the SearchMetadataCallback and updates the histogram.
+void RunSearchMetadataCallback(const SearchMetadataCallback& callback,
+ const base::TimeTicks& start_time,
+ scoped_ptr<MetadataSearchResultVector> results,
+ FileError error) {
+ if (error != FILE_ERROR_OK)
+ results.reset();
+ callback.Run(error, results.Pass());
+
+ UMA_HISTOGRAM_TIMES("Drive.SearchMetadataTime",
+ base::TimeTicks::Now() - start_time);
+}
+
+} // namespace
+
+void SearchMetadata(
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+ ResourceMetadata* resource_metadata,
+ const std::string& query,
+ const SearchMetadataPredicate& predicate,
+ size_t at_most_num_matches,
+ const SearchMetadataCallback& callback) {
+ DCHECK(!callback.is_null());
+
+ const base::TimeTicks start_time = base::TimeTicks::Now();
+
+ scoped_ptr<MetadataSearchResultVector> results(
+ new MetadataSearchResultVector);
+ MetadataSearchResultVector* results_ptr = results.get();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner.get(), FROM_HERE,
+ base::Bind(&SearchMetadataOnBlockingPool, resource_metadata, query,
+ predicate, at_most_num_matches, results_ptr),
+ base::Bind(&RunSearchMetadataCallback, callback, start_time,
+ base::Passed(&results)));
+}
+
+bool MatchesType(int options, const ResourceEntry& entry) {
+ if ((options & SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS) &&
+ entry.file_specific_info().is_hosted_document())
+ return false;
+
+ if ((options & SEARCH_METADATA_EXCLUDE_DIRECTORIES) &&
+ entry.file_info().is_directory())
+ return false;
+
+ if (options & SEARCH_METADATA_SHARED_WITH_ME)
+ return entry.shared_with_me();
+
+ if (options & SEARCH_METADATA_OFFLINE) {
+ if (entry.file_specific_info().is_hosted_document()) {
+ // Not all hosted documents are cached by Drive offline app.
+ // https://support.google.com/drive/answer/2375012
+ std::string mime_type = entry.file_specific_info().content_mime_type();
+ return mime_type == drive::util::kGoogleDocumentMimeType ||
+ mime_type == drive::util::kGoogleSpreadsheetMimeType ||
+ mime_type == drive::util::kGooglePresentationMimeType ||
+ mime_type == drive::util::kGoogleDrawingMimeType;
+ } else {
+ return entry.file_specific_info().cache_state().is_present();
+ }
+ }
+
+ return true;
+}
+
+bool FindAndHighlight(
+ const std::string& text,
+ base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query,
+ std::string* highlighted_text) {
+ DCHECK(query);
+ DCHECK(highlighted_text);
+ highlighted_text->clear();
+
+ base::string16 text16 = base::UTF8ToUTF16(text);
+ size_t match_start = 0;
+ size_t match_length = 0;
+ if (!query->Search(text16, &match_start, &match_length))
+ return false;
+
+ base::string16 pre = text16.substr(0, match_start);
+ base::string16 match = text16.substr(match_start, match_length);
+ base::string16 post = text16.substr(match_start + match_length);
+ highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(pre)));
+ highlighted_text->append("<b>");
+ highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(match)));
+ highlighted_text->append("</b>");
+ highlighted_text->append(net::EscapeForHTML(base::UTF16ToUTF8(post)));
+ return true;
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/search_metadata.h b/components/drive/search_metadata.h
new file mode 100644
index 0000000..ff8d491
--- /dev/null
+++ b/components/drive/search_metadata.h
@@ -0,0 +1,65 @@
+// 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_SEARCH_METADATA_H_
+#define COMPONENTS_DRIVE_SEARCH_METADATA_H_
+
+#include <string>
+
+#include "components/drive/file_system_interface.h"
+
+namespace base {
+namespace i18n {
+class FixedPatternStringSearchIgnoringCaseAndAccents;
+} // namespace i18n
+} // namespace base
+
+namespace drive {
+namespace internal {
+
+class ResourceMetadata;
+
+typedef base::Callback<bool(const ResourceEntry&)> SearchMetadataPredicate;
+
+// 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.
+// |callback| must not be null. Must be called on UI thread. Empty |query|
+// matches any base name. i.e. returns everything. |blocking_task_runner| must
+// be the same one as |resource_metadata| uses.
+void SearchMetadata(
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner,
+ ResourceMetadata* resource_metadata,
+ const std::string& query,
+ const SearchMetadataPredicate& predicate,
+ size_t at_most_num_matches,
+ const SearchMetadataCallback& callback);
+
+// Returns true if |entry| is eligible for the search |options| and should be
+// tested for the match with the query. If
+// SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS is requested, the hosted documents
+// are skipped. If SEARCH_METADATA_EXCLUDE_DIRECTORIES is requested, the
+// directories are skipped. If SEARCH_METADATA_SHARED_WITH_ME is requested, only
+// the entries with shared-with-me label will be tested. If
+// SEARCH_METADATA_OFFLINE is requested, only hosted documents and cached files
+// match with the query. This option can not be used with other options.
+bool MatchesType(int options, const ResourceEntry& entry);
+
+// Finds |query| in |text| while ignoring cases or accents. Cases of non-ASCII
+// characters are also ignored; they are compared in the 'Primary Level' of
+// http://userguide.icu-project.org/collation/concepts.
+// Returns true if |query| is found. |highlighted_text| will have the original
+// text with matched portions highlighted with <b> tag (only the first match
+// is highlighted). Meta characters are escaped like &lt;. The original
+// contents of |highlighted_text| will be lost.
+bool FindAndHighlight(
+ const std::string& text,
+ base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents* query,
+ std::string* highlighted_text);
+
+} // namespace internal
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SEARCH_METADATA_H_
diff --git a/components/drive/search_metadata_unittest.cc b/components/drive/search_metadata_unittest.cc
new file mode 100644
index 0000000..c37ec23
--- /dev/null
+++ b/components/drive/search_metadata_unittest.cc
@@ -0,0 +1,500 @@
+// 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/search_metadata.h"
+
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/i18n/string_search.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/drive_test_util.h"
+#include "components/drive/fake_free_disk_space_getter.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_system_core_util.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace drive {
+namespace internal {
+
+namespace {
+
+const int kDefaultAtMostNumMatches = 10;
+
+// A simple wrapper for testing FindAndHighlightWrapper(). It just converts the
+// query text parameter to FixedPatternStringSearchIgnoringCaseAndAccents.
+bool FindAndHighlightWrapper(
+ const std::string& text,
+ const std::string& query_text,
+ std::string* highlighted_text) {
+ base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
+ base::UTF8ToUTF16(query_text));
+ return FindAndHighlight(text, &query, highlighted_text);
+}
+
+} // namespace
+
+class SearchMetadataTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
+
+ metadata_storage_.reset(new ResourceMetadataStorage(
+ temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+ ASSERT_TRUE(metadata_storage_->Initialize());
+
+ cache_.reset(new FileCache(metadata_storage_.get(),
+ temp_dir_.path(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ fake_free_disk_space_getter_.get()));
+ ASSERT_TRUE(cache_->Initialize());
+
+ resource_metadata_.reset(
+ new ResourceMetadata(metadata_storage_.get(),
+ cache_.get(),
+ base::ThreadTaskRunnerHandle::Get().get()));
+ ASSERT_EQ(FILE_ERROR_OK, resource_metadata_->Initialize());
+
+ AddEntriesToMetadata();
+ }
+
+ void AddEntriesToMetadata() {
+ base::FilePath temp_file;
+ EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &temp_file));
+ const std::string temp_file_md5 = "md5";
+
+ ResourceEntry entry;
+ std::string local_id;
+
+ // drive/root
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->GetIdByPath(
+ util::GetDriveMyDriveRootPath(), &local_id));
+ const std::string root_local_id = local_id;
+
+ // drive/root/Directory 1
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry(
+ "Directory 1", "dir1", 1, root_local_id), &local_id));
+ const std::string dir1_local_id = local_id;
+
+ // drive/root/Directory 1/SubDirectory File 1.txt
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry(
+ "SubDirectory File 1.txt", "file1a", 2, dir1_local_id), &local_id));
+ EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
+ local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY));
+
+ // drive/root/Directory 1/Shared To The Account Owner.txt
+ entry = GetFileEntry(
+ "Shared To The Account Owner.txt", "file1b", 3, dir1_local_id);
+ entry.set_shared_with_me(true);
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
+
+ // drive/root/Directory 2 excludeDir-test
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetDirectoryEntry(
+ "Directory 2 excludeDir-test", "dir2", 4, root_local_id), &local_id));
+
+ // drive/root/Slash \xE2\x88\x95 in directory
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(
+ GetDirectoryEntry("Slash \xE2\x88\x95 in directory", "dir3", 5,
+ root_local_id), &local_id));
+ const std::string dir3_local_id = local_id;
+
+ // drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry(
+ "Slash SubDir File.txt", "file3a", 6, dir3_local_id), &local_id));
+
+ // drive/root/File 2.txt
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(GetFileEntry(
+ "File 2.txt", "file2", 7, root_local_id), &local_id));
+ EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
+ local_id, temp_file_md5, temp_file, FileCache::FILE_OPERATION_COPY));
+
+ // drive/root/Document 1 excludeDir-test
+ entry = GetFileEntry(
+ "Document 1 excludeDir-test", "doc1", 8, root_local_id);
+ entry.mutable_file_specific_info()->set_is_hosted_document(true);
+ entry.mutable_file_specific_info()->set_document_extension(".gdoc");
+ entry.mutable_file_specific_info()->set_content_mime_type(
+ drive::util::kGoogleDocumentMimeType);
+ EXPECT_EQ(FILE_ERROR_OK, resource_metadata_->AddEntry(entry, &local_id));
+ }
+
+ ResourceEntry GetFileEntry(const std::string& name,
+ const std::string& resource_id,
+ int64 last_accessed,
+ const std::string& parent_local_id) {
+ ResourceEntry entry;
+ entry.set_title(name);
+ entry.set_resource_id(resource_id);
+ entry.set_parent_local_id(parent_local_id);
+ entry.mutable_file_info()->set_last_accessed(last_accessed);
+ return entry;
+ }
+
+ ResourceEntry GetDirectoryEntry(const std::string& name,
+ const std::string& resource_id,
+ int64 last_accessed,
+ const std::string& parent_local_id) {
+ ResourceEntry entry;
+ entry.set_title(name);
+ entry.set_resource_id(resource_id);
+ entry.set_parent_local_id(parent_local_id);
+ entry.mutable_file_info()->set_last_accessed(last_accessed);
+ entry.mutable_file_info()->set_is_directory(true);
+ return entry;
+ }
+
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
+ scoped_ptr<ResourceMetadataStorage,
+ test_util::DestroyHelperForTests> metadata_storage_;
+ scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests>
+ resource_metadata_;
+ scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
+};
+
+TEST_F(SearchMetadataTest, SearchMetadata_ZeroMatches) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(),
+ "NonExistent", base::Bind(&MatchesType, SEARCH_METADATA_ALL),
+ kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(0U, result->size());
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_RegularFile) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(),
+ "SubDirectory File 1.txt", base::Bind(&MatchesType, SEARCH_METADATA_ALL),
+ kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(1U, result->size());
+ EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
+ result->at(0).path.AsUTF8Unsafe());
+}
+
+// This test checks if |FindAndHighlightWrapper| does case-insensitive search.
+// Tricker test cases for |FindAndHighlightWrapper| can be found below.
+TEST_F(SearchMetadataTest, SearchMetadata_CaseInsensitiveSearch) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ // The query is all in lower case.
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(),
+ "subdirectory file 1.txt", base::Bind(&MatchesType, SEARCH_METADATA_ALL),
+ kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(1U, result->size());
+ EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
+ result->at(0).path.AsUTF8Unsafe());
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_RegularFiles) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "SubDir",
+ base::Bind(&MatchesType, SEARCH_METADATA_ALL), kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(2U, result->size());
+
+ // All base names should contain "File". The results should be sorted by the
+ // last accessed time in descending order.
+ EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt",
+ result->at(0).path.AsUTF8Unsafe());
+ EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
+ result->at(1).path.AsUTF8Unsafe());
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_AtMostOneFile) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ // There are two files matching "SubDir" but only one file should be
+ // returned.
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "SubDir",
+ base::Bind(&MatchesType, SEARCH_METADATA_ALL),
+ 1, // at_most_num_matches
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(1U, result->size());
+ EXPECT_EQ("drive/root/Slash \xE2\x88\x95 in directory/Slash SubDir File.txt",
+ result->at(0).path.AsUTF8Unsafe());
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_Directory) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(),
+ "Directory 1", base::Bind(&MatchesType, SEARCH_METADATA_ALL),
+ kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(1U, result->size());
+ EXPECT_EQ("drive/root/Directory 1", result->at(0).path.AsUTF8Unsafe());
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_HostedDocument) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "Document",
+ base::Bind(&MatchesType, SEARCH_METADATA_ALL), kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(1U, result->size());
+
+ EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
+ result->at(0).path.AsUTF8Unsafe());
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_ExcludeHostedDocument) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "Document",
+ base::Bind(&MatchesType, SEARCH_METADATA_EXCLUDE_HOSTED_DOCUMENTS),
+ kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(0U, result->size());
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_SharedWithMe) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "",
+ base::Bind(&MatchesType, SEARCH_METADATA_SHARED_WITH_ME),
+ kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(1U, result->size());
+ EXPECT_EQ("drive/root/Directory 1/Shared To The Account Owner.txt",
+ result->at(0).path.AsUTF8Unsafe());
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_FileAndDirectory) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(),
+ "excludeDir-test", base::Bind(&MatchesType, SEARCH_METADATA_ALL),
+ kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(2U, result->size());
+
+ EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
+ result->at(0).path.AsUTF8Unsafe());
+ EXPECT_EQ("drive/root/Directory 2 excludeDir-test",
+ result->at(1).path.AsUTF8Unsafe());
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_ExcludeDirectory) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(),
+ "excludeDir-test",
+ base::Bind(&MatchesType, SEARCH_METADATA_EXCLUDE_DIRECTORIES),
+ kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_EQ(1U, result->size());
+
+ EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
+ result->at(0).path.AsUTF8Unsafe());
+}
+
+// "drive", "drive/root", "drive/other" should be excluded.
+TEST_F(SearchMetadataTest, SearchMetadata_ExcludeSpecialDirectories) {
+ const char* const kQueries[] = { "drive", "root", "other" };
+ for (size_t i = 0; i < arraysize(kQueries); ++i) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ const std::string query = kQueries[i];
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), query,
+ base::Bind(&MatchesType, SEARCH_METADATA_ALL), kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(result);
+ ASSERT_TRUE(result->empty()) << ": " << query << " should not match";
+ }
+}
+
+TEST_F(SearchMetadataTest, SearchMetadata_Offline) {
+ FileError error = FILE_ERROR_FAILED;
+ scoped_ptr<MetadataSearchResultVector> result;
+
+ SearchMetadata(
+ base::ThreadTaskRunnerHandle::Get(), resource_metadata_.get(), "",
+ base::Bind(&MatchesType, SEARCH_METADATA_OFFLINE),
+ kDefaultAtMostNumMatches,
+ google_apis::test_util::CreateCopyResultCallback(&error, &result));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_EQ(3U, result->size());
+
+ // This is not included in the cache but is a hosted document.
+ EXPECT_EQ("drive/root/Document 1 excludeDir-test.gdoc",
+ result->at(0).path.AsUTF8Unsafe());
+
+ EXPECT_EQ("drive/root/File 2.txt",
+ result->at(1).path.AsUTF8Unsafe());
+ EXPECT_EQ("drive/root/Directory 1/SubDirectory File 1.txt",
+ result->at(2).path.AsUTF8Unsafe());
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_ZeroMatches) {
+ std::string highlighted_text;
+ EXPECT_FALSE(FindAndHighlightWrapper("text", "query", &highlighted_text));
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_EmptyText) {
+ std::string highlighted_text;
+ EXPECT_FALSE(FindAndHighlightWrapper("", "query", &highlighted_text));
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_FullMatch) {
+ std::string highlighted_text;
+ EXPECT_TRUE(FindAndHighlightWrapper("hello", "hello", &highlighted_text));
+ EXPECT_EQ("<b>hello</b>", highlighted_text);
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_StartWith) {
+ std::string highlighted_text;
+ EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "hello",
+ &highlighted_text));
+ EXPECT_EQ("<b>hello</b>, world", highlighted_text);
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_EndWith) {
+ std::string highlighted_text;
+ EXPECT_TRUE(FindAndHighlightWrapper("hello, world", "world",
+ &highlighted_text));
+ EXPECT_EQ("hello, <b>world</b>", highlighted_text);
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_InTheMiddle) {
+ std::string highlighted_text;
+ EXPECT_TRUE(FindAndHighlightWrapper("yo hello, world", "hello",
+ &highlighted_text));
+ EXPECT_EQ("yo <b>hello</b>, world", highlighted_text);
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_MultipeMatches) {
+ std::string highlighted_text;
+ EXPECT_TRUE(FindAndHighlightWrapper("yoyoyoyoy", "yoy", &highlighted_text));
+ // Only the first match is highlighted.
+ EXPECT_EQ("<b>yoy</b>oyoyoy", highlighted_text);
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCase) {
+ std::string highlighted_text;
+ EXPECT_TRUE(FindAndHighlightWrapper("HeLLo", "hello", &highlighted_text));
+ EXPECT_EQ("<b>HeLLo</b>", highlighted_text);
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_IgnoreCaseNonASCII) {
+ std::string highlighted_text;
+
+ // Case and accent ignorance in Greek. Find "socra" in "Socra'tes".
+ EXPECT_TRUE(FindAndHighlightWrapper(
+ "\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC\xCF\x84\xCE\xB7\xCF\x82",
+ "\xCF\x83\xCF\x89\xCE\xBA\xCF\x81\xCE\xB1", &highlighted_text));
+ EXPECT_EQ(
+ "<b>\xCE\xA3\xCF\x89\xCE\xBA\xCF\x81\xCE\xAC</b>\xCF\x84\xCE\xB7\xCF\x82",
+ highlighted_text);
+
+ // In Japanese characters.
+ // Find Hiragana "pi" + "(small)ya" in Katakana "hi" + semi-voiced-mark + "ya"
+ EXPECT_TRUE(FindAndHighlightWrapper(
+ "\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83\xE3\x83\xBC",
+ "\xE3\x83\x94\xE3\x83\xA4",
+ &highlighted_text));
+ EXPECT_EQ(
+ "<b>\xE3\x81\xB2\xE3\x82\x9A\xE3\x82\x83</b>\xE3\x83\xBC",
+ highlighted_text);
+}
+
+TEST(SearchMetadataSimpleTest, MultiTextBySingleQuery) {
+ base::i18n::FixedPatternStringSearchIgnoringCaseAndAccents query(
+ base::UTF8ToUTF16("hello"));
+
+ std::string highlighted_text;
+ EXPECT_TRUE(FindAndHighlight("hello", &query, &highlighted_text));
+ EXPECT_EQ("<b>hello</b>", highlighted_text);
+ EXPECT_FALSE(FindAndHighlight("goodbye", &query, &highlighted_text));
+ EXPECT_TRUE(FindAndHighlight("1hello2", &query, &highlighted_text));
+ EXPECT_EQ("1<b>hello</b>2", highlighted_text);
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_MetaChars) {
+ std::string highlighted_text;
+ EXPECT_TRUE(FindAndHighlightWrapper("<hello>", "hello", &highlighted_text));
+ EXPECT_EQ("&lt;<b>hello</b>&gt;", highlighted_text);
+}
+
+TEST(SearchMetadataSimpleTest, FindAndHighlight_MoreMetaChars) {
+ std::string highlighted_text;
+ EXPECT_TRUE(FindAndHighlightWrapper("a&b&c&d", "b&c", &highlighted_text));
+ EXPECT_EQ("a&amp;<b>b&amp;c</b>&amp;d", highlighted_text);
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/sync/entry_revert_performer.cc b/components/drive/sync/entry_revert_performer.cc
new file mode 100644
index 0000000..7b99737
--- /dev/null
+++ b/components/drive/sync/entry_revert_performer.cc
@@ -0,0 +1,178 @@
+// 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/sync/entry_revert_performer.h"
+
+#include "components/drive/change_list_processor.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_api_util.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_entry_conversion.h"
+#include "components/drive/resource_metadata.h"
+#include "google_apis/drive/drive_api_parser.h"
+
+namespace drive {
+namespace internal {
+namespace {
+
+FileError FinishRevert(ResourceMetadata* metadata,
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileResource> file_resource,
+ FileChange* changed_files) {
+ ResourceEntry entry;
+ std::string parent_resource_id;
+ FileError error = GDataToFileError(status);
+ switch (error) {
+ case FILE_ERROR_OK:
+ if (!ConvertFileResourceToResourceEntry(*file_resource, &entry,
+ &parent_resource_id))
+ return FILE_ERROR_NOT_A_FILE;
+ break;
+
+ case FILE_ERROR_NOT_FOUND:
+ entry.set_deleted(true);
+ break;
+
+ default:
+ return error;
+ }
+
+ base::FilePath original_path;
+ error = metadata->GetFilePath(local_id, &original_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ if (entry.deleted()) {
+ error = metadata->RemoveEntry(local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ changed_files->Update(
+ original_path,
+ FileChange::FILE_TYPE_NO_INFO, // Undetermined type for deleted file.
+ FileChange::CHANGE_TYPE_DELETE);
+ } else {
+ changed_files->Update(original_path, entry, FileChange::CHANGE_TYPE_DELETE);
+
+ error = ChangeListProcessor::SetParentLocalIdOfEntry(metadata, &entry,
+ parent_resource_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ entry.set_local_id(local_id);
+ error = metadata->RefreshEntry(entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ base::FilePath new_path;
+ error = metadata->GetFilePath(local_id, &new_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ changed_files->Update(new_path, entry,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+ }
+ return FILE_ERROR_OK;
+}
+
+} // namespace
+
+EntryRevertPerformer::EntryRevertPerformer(
+ base::SequencedTaskRunner* blocking_task_runner,
+ file_system::OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ ResourceMetadata* metadata)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ scheduler_(scheduler),
+ metadata_(metadata),
+ weak_ptr_factory_(this) {
+}
+
+EntryRevertPerformer::~EntryRevertPerformer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void EntryRevertPerformer::RevertEntry(const std::string& local_id,
+ const ClientContext& context,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ scoped_ptr<ResourceEntry> entry(new ResourceEntry);
+ ResourceEntry* entry_ptr = entry.get();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::GetResourceEntryById,
+ base::Unretained(metadata_), local_id, entry_ptr),
+ base::Bind(&EntryRevertPerformer::RevertEntryAfterPrepare,
+ weak_ptr_factory_.GetWeakPtr(), context, callback,
+ base::Passed(&entry)));
+}
+
+void EntryRevertPerformer::RevertEntryAfterPrepare(
+ const ClientContext& context,
+ const FileOperationCallback& callback,
+ scoped_ptr<ResourceEntry> entry,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error == FILE_ERROR_OK && entry->resource_id().empty())
+ error = FILE_ERROR_INVALID_OPERATION;
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ scheduler_->GetFileResource(
+ entry->resource_id(),
+ context,
+ base::Bind(&EntryRevertPerformer::RevertEntryAfterGetFileResource,
+ weak_ptr_factory_.GetWeakPtr(), callback, entry->local_id()));
+}
+
+void EntryRevertPerformer::RevertEntryAfterGetFileResource(
+ const FileOperationCallback& callback,
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileResource> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileChange* changed_files = new FileChange;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&FinishRevert,
+ metadata_,
+ local_id,
+ status,
+ base::Passed(&entry),
+ changed_files),
+ base::Bind(&EntryRevertPerformer::RevertEntryAfterFinishRevert,
+ weak_ptr_factory_.GetWeakPtr(),
+ callback,
+ base::Owned(changed_files)));
+}
+
+void EntryRevertPerformer::RevertEntryAfterFinishRevert(
+ const FileOperationCallback& callback,
+ const FileChange* changed_files,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ delegate_->OnFileChangedByOperation(*changed_files);
+
+ callback.Run(error);
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/sync/entry_revert_performer.h b/components/drive/sync/entry_revert_performer.h
new file mode 100644
index 0000000..003c601
--- /dev/null
+++ b/components/drive/sync/entry_revert_performer.h
@@ -0,0 +1,93 @@
+// 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_SYNC_ENTRY_REVERT_PERFORMER_H_
+#define COMPONENTS_DRIVE_SYNC_ENTRY_REVERT_PERFORMER_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 SequencedTaskRunner;
+} // namespace base
+
+namespace google_apis {
+class FileResource;
+} // namespace google_apis
+
+namespace drive {
+
+class FileChange;
+class JobScheduler;
+class ResourceEntry;
+struct ClientContext;
+
+namespace file_system {
+class OperationDelegate;
+} // namespace file_system
+
+namespace internal {
+
+class ResourceMetadata;
+
+// This class is responsible to revert local changes of an entry.
+class EntryRevertPerformer {
+ public:
+ EntryRevertPerformer(base::SequencedTaskRunner* blocking_task_runner,
+ file_system::OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ ResourceMetadata* metadata);
+ ~EntryRevertPerformer();
+
+ // Requests the server for metadata of the entry specified by |local_id|
+ // and overwrites the locally stored entry with it.
+ // Invokes |callback| when finished with the result of the operation.
+ // |callback| must not be null.
+ void RevertEntry(const std::string& local_id,
+ const ClientContext& context,
+ const FileOperationCallback& callback);
+
+ private:
+ // Part of RevertEntry(). Called after local metadata look up.
+ void RevertEntryAfterPrepare(const ClientContext& context,
+ const FileOperationCallback& callback,
+ scoped_ptr<ResourceEntry> entry,
+ FileError error);
+
+ // Part of RevertEntry(). Called after GetFileResource is completed.
+ void RevertEntryAfterGetFileResource(
+ const FileOperationCallback& callback,
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileResource> entry);
+
+ // Part of RevertEntry(). Called after local metadata is updated.
+ void RevertEntryAfterFinishRevert(const FileOperationCallback& callback,
+ const FileChange* changed_files,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ file_system::OperationDelegate* delegate_;
+ JobScheduler* scheduler_;
+ 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<EntryRevertPerformer> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(EntryRevertPerformer);
+};
+
+} // namespace internal
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SYNC_ENTRY_REVERT_PERFORMER_H_
diff --git a/components/drive/sync/entry_revert_performer_unittest.cc b/components/drive/sync/entry_revert_performer_unittest.cc
new file mode 100644
index 0000000..421749a
--- /dev/null
+++ b/components/drive/sync/entry_revert_performer_unittest.cc
@@ -0,0 +1,151 @@
+// 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/sync/entry_revert_performer.h"
+
+#include "base/task_runner_util.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/resource_metadata.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 internal {
+
+class EntryRevertPerformerTest : public file_system::OperationTestBase {
+ protected:
+ void SetUp() override {
+ OperationTestBase::SetUp();
+ performer_.reset(new EntryRevertPerformer(blocking_task_runner(),
+ delegate(),
+ scheduler(),
+ metadata()));
+ }
+
+ scoped_ptr<EntryRevertPerformer> performer_;
+};
+
+TEST_F(EntryRevertPerformerTest, RevertEntry) {
+ base::FilePath path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
+ base::FilePath updated_path(FILE_PATH_LITERAL(
+ "drive/root/Directory 1/UpdatedSubDirectory File 1.txt"));
+
+ ResourceEntry src_entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(path, &src_entry));
+
+ // Update local entry.
+ ResourceEntry updated_entry = src_entry;
+ updated_entry.set_title("Updated" + src_entry.title());
+ updated_entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::RefreshEntry,
+ base::Unretained(metadata()), updated_entry),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Revert local change.
+ error = FILE_ERROR_FAILED;
+ performer_->RevertEntry(
+ src_entry.local_id(),
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Verify local change is reverted.
+ ResourceEntry result_entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntryById(src_entry.local_id(), &result_entry));
+ EXPECT_EQ(src_entry.title(), result_entry.title());
+ EXPECT_EQ(ResourceEntry::CLEAN, result_entry.metadata_edit_state());
+
+ EXPECT_EQ(2U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(path));
+ EXPECT_TRUE(delegate()->get_changed_files().count(updated_path));
+}
+
+TEST_F(EntryRevertPerformerTest, RevertEntry_NotFoundOnServer) {
+ ResourceEntry my_drive;
+ EXPECT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntry(util::GetDriveMyDriveRootPath(), &my_drive));
+
+ // Add a new entry with a nonexistent resource ID.
+ ResourceEntry entry;
+ entry.set_title("New File.txt");
+ entry.set_resource_id("non-existent-resource-id");
+ entry.set_parent_local_id(my_drive.local_id());
+
+ FileError error = FILE_ERROR_FAILED;
+ std::string local_id;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::AddEntry,
+ base::Unretained(metadata()), entry, &local_id),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Revert local change. The added entry should be removed.
+ error = FILE_ERROR_FAILED;
+ performer_->RevertEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Verify the entry was deleted locally.
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntryById(local_id, &entry));
+
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().CountDirectory(
+ util::GetDriveMyDriveRootPath()));
+}
+
+TEST_F(EntryRevertPerformerTest, RevertEntry_TrashedOnServer) {
+ base::FilePath path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(path, &entry));
+
+ // Trash the entry on the server.
+ google_apis::DriveApiErrorCode gdata_error = google_apis::DRIVE_OTHER_ERROR;
+ fake_service()->TrashResource(
+ entry.resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&gdata_error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
+
+ // Revert local change. The entry should be removed.
+ FileError error = FILE_ERROR_FAILED;
+ performer_->RevertEntry(
+ entry.local_id(),
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Verify the entry was deleted locally.
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntryById(entry.local_id(), &entry));
+
+ EXPECT_EQ(1U, delegate()->get_changed_files().size());
+ EXPECT_TRUE(delegate()->get_changed_files().count(path));
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/sync/entry_update_performer.cc b/components/drive/sync/entry_update_performer.cc
new file mode 100644
index 0000000..5418623
--- /dev/null
+++ b/components/drive/sync/entry_update_performer.cc
@@ -0,0 +1,450 @@
+// 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/sync/entry_update_performer.h"
+
+#include <set>
+
+#include "base/callback_helpers.h"
+#include "base/files/file_util.h"
+#include "components/drive/change_list_loader.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"
+#include "components/drive/sync/entry_revert_performer.h"
+#include "components/drive/sync/remove_performer.h"
+#include "google_apis/drive/drive_api_parser.h"
+
+namespace drive {
+namespace internal {
+
+struct EntryUpdatePerformer::LocalState {
+ LocalState() : cache_file_size(0), should_content_update(false) {}
+
+ ResourceEntry entry;
+ ResourceEntry parent_entry;
+ base::FilePath drive_file_path;
+ base::FilePath cache_file_path;
+ int64 cache_file_size;
+ bool should_content_update;
+};
+
+namespace {
+
+struct PropertyCompare {
+ bool operator()(const drive::Property& x, const drive::Property& y) const {
+ if (x.key() < y.key())
+ return true;
+ if (x.key() > y.key())
+ return false;
+ if (x.value() < y.value())
+ return true;
+ if (y.value() > y.value())
+ return false;
+ return x.visibility() < y.visibility();
+ }
+};
+
+// Looks up ResourceEntry for source entry and its parent.
+FileError PrepareUpdate(ResourceMetadata* metadata,
+ FileCache* cache,
+ const std::string& local_id,
+ EntryUpdatePerformer::LocalState* local_state) {
+ FileError error = metadata->GetResourceEntryById(local_id,
+ &local_state->entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ error = metadata->GetResourceEntryById(local_state->entry.parent_local_id(),
+ &local_state->parent_entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ error = metadata->GetFilePath(local_id, &local_state->drive_file_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ if (!local_state->entry.file_info().is_directory() &&
+ !local_state->entry.file_specific_info().cache_state().is_present() &&
+ local_state->entry.resource_id().empty()) {
+ // Locally created file with no cache file, store an empty file.
+ base::FilePath empty_file;
+ if (!base::CreateTemporaryFile(&empty_file))
+ return FILE_ERROR_FAILED;
+ error = cache->Store(local_id, std::string(), empty_file,
+ FileCache::FILE_OPERATION_MOVE);
+ if (error != FILE_ERROR_OK)
+ return error;
+ error = metadata->GetResourceEntryById(local_id, &local_state->entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ }
+
+ // Check if content update is needed or not.
+ if (local_state->entry.file_specific_info().cache_state().is_dirty() &&
+ !cache->IsOpenedForWrite(local_id)) {
+ // Update cache entry's MD5 if needed.
+ if (local_state->entry.file_specific_info().cache_state().md5().empty()) {
+ error = cache->UpdateMd5(local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+ error = metadata->GetResourceEntryById(local_id, &local_state->entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ }
+
+ if (local_state->entry.file_specific_info().cache_state().md5() ==
+ local_state->entry.file_specific_info().md5()) {
+ error = cache->ClearDirty(local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+ } else {
+ error = cache->GetFile(local_id, &local_state->cache_file_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+ const bool result = base::GetFileSize(local_state->cache_file_path,
+ &local_state->cache_file_size);
+ if (!result)
+ return FILE_ERROR_FAILED;
+ local_state->should_content_update = true;
+ }
+ }
+
+ // Update metadata_edit_state.
+ switch (local_state->entry.metadata_edit_state()) {
+ case ResourceEntry::CLEAN: // Nothing to do.
+ case ResourceEntry::SYNCING: // Error during the last update. Go ahead.
+ break;
+
+ case ResourceEntry::DIRTY:
+ local_state->entry.set_metadata_edit_state(ResourceEntry::SYNCING);
+ error = metadata->RefreshEntry(local_state->entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ break;
+ }
+ return FILE_ERROR_OK;
+}
+
+FileError FinishUpdate(ResourceMetadata* metadata,
+ FileCache* cache,
+ scoped_ptr<EntryUpdatePerformer::LocalState> local_state,
+ scoped_ptr<google_apis::FileResource> file_resource,
+ FileChange* changed_files) {
+ ResourceEntry entry;
+ FileError error =
+ metadata->GetResourceEntryById(local_state->entry.local_id(), &entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ // When creating new entries, update check may add a new entry with the same
+ // resource ID before us. If such an entry exists, remove it.
+ std::string existing_local_id;
+ error =
+ metadata->GetIdByResourceId(file_resource->file_id(), &existing_local_id);
+
+ switch (error) {
+ case FILE_ERROR_OK:
+ if (existing_local_id != local_state->entry.local_id()) {
+ base::FilePath existing_entry_path;
+ error = metadata->GetFilePath(existing_local_id, &existing_entry_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+ error = metadata->RemoveEntry(existing_local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+ changed_files->Update(existing_entry_path, entry,
+ FileChange::CHANGE_TYPE_DELETE);
+ }
+ break;
+ case FILE_ERROR_NOT_FOUND:
+ break;
+ default:
+ return error;
+ }
+
+ // Update metadata_edit_state and MD5.
+ switch (entry.metadata_edit_state()) {
+ case ResourceEntry::CLEAN: // Nothing to do.
+ case ResourceEntry::DIRTY: // Entry was edited again during the update.
+ break;
+
+ case ResourceEntry::SYNCING:
+ entry.set_metadata_edit_state(ResourceEntry::CLEAN);
+ break;
+ }
+ if (!entry.file_info().is_directory())
+ entry.mutable_file_specific_info()->set_md5(file_resource->md5_checksum());
+ entry.set_resource_id(file_resource->file_id());
+
+ // Keep only those properties which have been added or changed in the proto
+ // during the update.
+ std::set<drive::Property, PropertyCompare> synced_properties(
+ local_state->entry.new_properties().begin(),
+ local_state->entry.new_properties().end());
+
+ google::protobuf::RepeatedPtrField<drive::Property> not_synced_properties;
+ for (const auto& property : entry.new_properties()) {
+ if (!synced_properties.count(property)) {
+ Property* const not_synced_property = not_synced_properties.Add();
+ not_synced_property->CopyFrom(property);
+ }
+ }
+ entry.mutable_new_properties()->Swap(&not_synced_properties);
+
+ error = metadata->RefreshEntry(entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ base::FilePath entry_path;
+ error = metadata->GetFilePath(local_state->entry.local_id(), &entry_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+ changed_files->Update(entry_path, entry,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+
+ // Clear dirty bit unless the file has been edited during update.
+ if (entry.file_specific_info().cache_state().is_dirty() &&
+ entry.file_specific_info().cache_state().md5() ==
+ entry.file_specific_info().md5()) {
+ error = cache->ClearDirty(local_state->entry.local_id());
+ if (error != FILE_ERROR_OK)
+ return error;
+ }
+ return FILE_ERROR_OK;
+}
+
+} // namespace
+
+EntryUpdatePerformer::EntryUpdatePerformer(
+ base::SequencedTaskRunner* blocking_task_runner,
+ file_system::OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ ResourceMetadata* metadata,
+ FileCache* cache,
+ LoaderController* loader_controller)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ scheduler_(scheduler),
+ metadata_(metadata),
+ cache_(cache),
+ loader_controller_(loader_controller),
+ remove_performer_(new RemovePerformer(blocking_task_runner,
+ delegate,
+ scheduler,
+ metadata)),
+ entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner,
+ delegate,
+ scheduler,
+ metadata)),
+ weak_ptr_factory_(this) {
+}
+
+EntryUpdatePerformer::~EntryUpdatePerformer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void EntryUpdatePerformer::UpdateEntry(const std::string& local_id,
+ const ClientContext& context,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ scoped_ptr<LocalState> local_state(new LocalState);
+ LocalState* const local_state_ptr = local_state.get();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&PrepareUpdate, metadata_, cache_, local_id, local_state_ptr),
+ base::Bind(&EntryUpdatePerformer::UpdateEntryAfterPrepare,
+ weak_ptr_factory_.GetWeakPtr(), context, callback,
+ base::Passed(&local_state)));
+}
+
+void EntryUpdatePerformer::UpdateEntryAfterPrepare(
+ const ClientContext& context,
+ const FileOperationCallback& callback,
+ scoped_ptr<LocalState> local_state,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ // Trashed entry should be removed.
+ if (local_state->entry.parent_local_id() == util::kDriveTrashDirLocalId) {
+ remove_performer_->Remove(local_state->entry.local_id(), context, callback);
+ return;
+ }
+
+ // Parent was locally created and needs update. Just return for now.
+ // This entry should be updated again after the parent update completes.
+ if (local_state->parent_entry.resource_id().empty() &&
+ local_state->parent_entry.metadata_edit_state() != ResourceEntry::CLEAN) {
+ callback.Run(FILE_ERROR_OK);
+ return;
+ }
+
+ base::Time last_modified = base::Time::FromInternalValue(
+ local_state->entry.file_info().last_modified());
+ base::Time last_accessed = base::Time::FromInternalValue(
+ local_state->entry.file_info().last_accessed());
+
+ // Compose a list of new properties from the proto.
+ google_apis::drive::Properties properties;
+ for (const auto& proto_property : local_state->entry.new_properties()) {
+ google_apis::drive::Property property;
+ switch (proto_property.visibility()) {
+ case Property_Visibility_PRIVATE:
+ property.set_visibility(
+ google_apis::drive::Property::VISIBILITY_PRIVATE);
+ break;
+ case Property_Visibility_PUBLIC:
+ property.set_visibility(
+ google_apis::drive::Property::VISIBILITY_PUBLIC);
+ break;
+ }
+ property.set_key(proto_property.key());
+ property.set_value(proto_property.value());
+ properties.push_back(property);
+ }
+
+ // Perform content update.
+ if (local_state->should_content_update) {
+ if (local_state->entry.resource_id().empty()) {
+ // Not locking the loader intentionally here to avoid making the UI
+ // unresponsive while uploading large files.
+ // FinishUpdate() is responsible to resolve conflicts caused by this.
+ scoped_ptr<base::ScopedClosureRunner> null_loader_lock;
+
+ UploadNewFileOptions options;
+ options.modified_date = last_modified;
+ options.last_viewed_by_me_date = last_accessed;
+ options.properties = properties;
+ LocalState* const local_state_ptr = local_state.get();
+ scheduler_->UploadNewFile(
+ local_state_ptr->parent_entry.resource_id(),
+ local_state_ptr->cache_file_size, local_state_ptr->drive_file_path,
+ local_state_ptr->cache_file_path, local_state_ptr->entry.title(),
+ local_state_ptr->entry.file_specific_info().content_mime_type(),
+ options, context,
+ base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
+ weak_ptr_factory_.GetWeakPtr(), context, callback,
+ base::Passed(&local_state),
+ base::Passed(&null_loader_lock)));
+ } else {
+ UploadExistingFileOptions options;
+ options.title = local_state->entry.title();
+ options.parent_resource_id = local_state->parent_entry.resource_id();
+ options.modified_date = last_modified;
+ options.last_viewed_by_me_date = last_accessed;
+ options.properties = properties;
+ LocalState* const local_state_ptr = local_state.get();
+ scheduler_->UploadExistingFile(
+ local_state_ptr->entry.resource_id(),
+ local_state_ptr->cache_file_size, local_state_ptr->drive_file_path,
+ local_state_ptr->cache_file_path,
+ local_state_ptr->entry.file_specific_info().content_mime_type(),
+ options, context,
+ base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
+ weak_ptr_factory_.GetWeakPtr(), context, callback,
+ base::Passed(&local_state),
+ base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
+ }
+ return;
+ }
+
+ // Create directory.
+ if (local_state->entry.file_info().is_directory() &&
+ local_state->entry.resource_id().empty()) {
+ // Lock the loader to avoid race conditions.
+ scoped_ptr<base::ScopedClosureRunner> loader_lock =
+ loader_controller_->GetLock();
+
+ AddNewDirectoryOptions options;
+ options.modified_date = last_modified;
+ options.last_viewed_by_me_date = last_accessed;
+ options.properties = properties;
+ LocalState* const local_state_ptr = local_state.get();
+ scheduler_->AddNewDirectory(
+ local_state_ptr->parent_entry.resource_id(),
+ local_state_ptr->entry.title(), options, context,
+ base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
+ weak_ptr_factory_.GetWeakPtr(), context, callback,
+ base::Passed(&local_state), base::Passed(&loader_lock)));
+ return;
+ }
+
+ // No need to perform update.
+ if (local_state->entry.metadata_edit_state() == ResourceEntry::CLEAN ||
+ local_state->entry.resource_id().empty()) {
+ callback.Run(FILE_ERROR_OK);
+ return;
+ }
+
+ // Perform metadata update.
+ LocalState* const local_state_ptr = local_state.get();
+ scheduler_->UpdateResource(
+ local_state_ptr->entry.resource_id(),
+ local_state_ptr->parent_entry.resource_id(),
+ local_state_ptr->entry.title(), last_modified, last_accessed, properties,
+ context,
+ base::Bind(&EntryUpdatePerformer::UpdateEntryAfterUpdateResource,
+ weak_ptr_factory_.GetWeakPtr(), context, callback,
+ base::Passed(&local_state),
+ base::Passed(scoped_ptr<base::ScopedClosureRunner>())));
+}
+
+void EntryUpdatePerformer::UpdateEntryAfterUpdateResource(
+ const ClientContext& context,
+ const FileOperationCallback& callback,
+ scoped_ptr<LocalState> local_state,
+ scoped_ptr<base::ScopedClosureRunner> loader_lock,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileResource> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (status == google_apis::HTTP_FORBIDDEN) {
+ // Editing this entry is not allowed, revert local changes.
+ entry_revert_performer_->RevertEntry(local_state->entry.local_id(), context,
+ callback);
+ return;
+ }
+
+ FileError error = GDataToFileError(status);
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ FileChange* changed_files = new FileChange;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(), FROM_HERE,
+ base::Bind(&FinishUpdate, metadata_, cache_, base::Passed(&local_state),
+ base::Passed(&entry), changed_files),
+ base::Bind(&EntryUpdatePerformer::UpdateEntryAfterFinish,
+ weak_ptr_factory_.GetWeakPtr(), callback,
+ base::Owned(changed_files)));
+}
+
+void EntryUpdatePerformer::UpdateEntryAfterFinish(
+ const FileOperationCallback& callback,
+ const FileChange* changed_files,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ delegate_->OnFileChangedByOperation(*changed_files);
+ callback.Run(error);
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/sync/entry_update_performer.h b/components/drive/sync/entry_update_performer.h
new file mode 100644
index 0000000..55fad7e
--- /dev/null
+++ b/components/drive/sync/entry_update_performer.h
@@ -0,0 +1,106 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_DRIVE_SYNC_ENTRY_UPDATE_PERFORMER_H_
+#define COMPONENTS_DRIVE_SYNC_ENTRY_UPDATE_PERFORMER_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 ScopedClosureRunner;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace google_apis {
+class FileResource;
+} // namespace google_apis
+
+namespace drive {
+
+class FileChange;
+class JobScheduler;
+class ResourceEntry;
+struct ClientContext;
+
+namespace file_system {
+class OperationDelegate;
+} // namespace file_system
+
+namespace internal {
+
+class EntryRevertPerformer;
+class FileCache;
+class LoaderController;
+class RemovePerformer;
+class ResourceMetadata;
+
+// This class is responsible to perform server side update of an entry.
+class EntryUpdatePerformer {
+ public:
+ EntryUpdatePerformer(base::SequencedTaskRunner* blocking_task_runner,
+ file_system::OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ ResourceMetadata* metadata,
+ FileCache* cache,
+ LoaderController* loader_controller);
+ ~EntryUpdatePerformer();
+
+ // Requests the server to update the metadata of the entry specified by
+ // |local_id| with the locally stored one.
+ // Invokes |callback| when finished with the result of the operation.
+ // |callback| must not be null.
+ void UpdateEntry(const std::string& local_id,
+ const ClientContext& context,
+ const FileOperationCallback& callback);
+
+ struct LocalState;
+
+ private:
+ // Part of UpdateEntry(). Called after local metadata look up.
+ void UpdateEntryAfterPrepare(const ClientContext& context,
+ const FileOperationCallback& callback,
+ scoped_ptr<LocalState> local_state,
+ FileError error);
+
+ // Part of UpdateEntry(). Called after UpdateResource is completed.
+ void UpdateEntryAfterUpdateResource(
+ const ClientContext& context,
+ const FileOperationCallback& callback,
+ scoped_ptr<LocalState> local_state,
+ scoped_ptr<base::ScopedClosureRunner> loader_lock,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileResource> entry);
+
+ // Part of UpdateEntry(). Called after FinishUpdate is completed.
+ void UpdateEntryAfterFinish(const FileOperationCallback& callback,
+ const FileChange* changed_files,
+ FileError error);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ file_system::OperationDelegate* delegate_;
+ JobScheduler* scheduler_;
+ ResourceMetadata* metadata_;
+ FileCache* cache_;
+ LoaderController* loader_controller_;
+ scoped_ptr<RemovePerformer> remove_performer_;
+ scoped_ptr<EntryRevertPerformer> entry_revert_performer_;
+
+ 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<EntryUpdatePerformer> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(EntryUpdatePerformer);
+};
+
+} // namespace internal
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SYNC_ENTRY_UPDATE_PERFORMER_H_
diff --git a/components/drive/sync/entry_update_performer_unittest.cc b/components/drive/sync/entry_update_performer_unittest.cc
new file mode 100644
index 0000000..145e1ff
--- /dev/null
+++ b/components/drive/sync/entry_update_performer_unittest.cc
@@ -0,0 +1,658 @@
+// 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/sync/entry_update_performer.h"
+
+#include "base/callback_helpers.h"
+#include "base/files/file_util.h"
+#include "base/md5.h"
+#include "base/task_runner_util.h"
+#include "components/drive/drive_api_util.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_system/download_operation.h"
+#include "components/drive/file_system/operation_test_base.h"
+#include "components/drive/job_scheduler.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 internal {
+
+class EntryUpdatePerformerTest : public file_system::OperationTestBase {
+ protected:
+ void SetUp() override {
+ OperationTestBase::SetUp();
+ performer_.reset(new EntryUpdatePerformer(blocking_task_runner(),
+ delegate(),
+ scheduler(),
+ metadata(),
+ cache(),
+ loader_controller()));
+ }
+
+ // Stores |content| to the cache and mark it as dirty.
+ FileError StoreAndMarkDirty(const std::string& local_id,
+ const std::string& content) {
+ base::FilePath path;
+ if (!base::CreateTemporaryFileInDir(temp_dir(), &path) ||
+ !google_apis::test_util::WriteStringToFile(path, content))
+ return FILE_ERROR_FAILED;
+
+ // Store the file to cache.
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&FileCache::Store,
+ base::Unretained(cache()),
+ local_id, std::string(), path,
+ FileCache::FILE_OPERATION_COPY),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ return error;
+ }
+
+ scoped_ptr<EntryUpdatePerformer> performer_;
+};
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry) {
+ base::FilePath src_path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
+ base::FilePath dest_path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/Sub Directory Folder"));
+
+ ResourceEntry src_entry, dest_entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &src_entry));
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(dest_path, &dest_entry));
+
+ // Update local entry.
+ base::Time new_last_modified = base::Time::FromInternalValue(
+ src_entry.file_info().last_modified()) + base::TimeDelta::FromSeconds(1);
+ base::Time new_last_accessed = base::Time::FromInternalValue(
+ src_entry.file_info().last_accessed()) + base::TimeDelta::FromSeconds(2);
+
+ src_entry.set_parent_local_id(dest_entry.local_id());
+ src_entry.set_title("Moved" + src_entry.title());
+ src_entry.mutable_file_info()->set_last_modified(
+ new_last_modified.ToInternalValue());
+ src_entry.mutable_file_info()->set_last_accessed(
+ new_last_accessed.ToInternalValue());
+ src_entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::RefreshEntry,
+ base::Unretained(metadata()),
+ src_entry),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Perform server side update.
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ src_entry.local_id(),
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Verify the file is updated on the server.
+ google_apis::DriveApiErrorCode gdata_error = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> gdata_entry;
+ fake_service()->GetFileResource(
+ src_entry.resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&gdata_error,
+ &gdata_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
+ ASSERT_TRUE(gdata_entry);
+
+ EXPECT_EQ(src_entry.title(), gdata_entry->title());
+ EXPECT_EQ(new_last_modified, gdata_entry->modified_date());
+ EXPECT_EQ(new_last_accessed, gdata_entry->last_viewed_by_me_date());
+
+ ASSERT_FALSE(gdata_entry->parents().empty());
+ EXPECT_EQ(dest_entry.resource_id(), gdata_entry->parents()[0].file_id());
+}
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_SetProperties) {
+ base::FilePath entry_path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(entry_path, &entry));
+
+ Property* const first_property = entry.mutable_new_properties()->Add();
+ first_property->set_key("hello");
+ first_property->set_value("world");
+ first_property->set_visibility(Property_Visibility_PUBLIC);
+
+ Property* const second_property = entry.mutable_new_properties()->Add();
+ second_property->set_key("this");
+ second_property->set_value("will-change");
+ second_property->set_visibility(Property_Visibility_PUBLIC);
+ entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(), FROM_HERE,
+ base::Bind(&ResourceMetadata::RefreshEntry, base::Unretained(metadata()),
+ entry),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Perform server side update.
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ entry.local_id(), ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+
+ // Add a new property during an update.
+ Property* const property = entry.mutable_new_properties()->Add();
+ property->set_key("tokyo");
+ property->set_value("kyoto");
+ property->set_visibility(Property_Visibility_PUBLIC);
+
+ // Modify an existing property during an update.
+ second_property->set_value("changed");
+
+ // Change the resource entry during an update.
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(), FROM_HERE,
+ base::Bind(&ResourceMetadata::RefreshEntry, base::Unretained(metadata()),
+ entry),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+
+ // Wait until the update is fully completed.
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // List of synced properties should be removed from the proto.
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(entry_path, &entry));
+ ASSERT_EQ(2, entry.new_properties().size());
+ EXPECT_EQ("changed", entry.new_properties().Get(0).value());
+ EXPECT_EQ("tokyo", entry.new_properties().Get(1).key());
+}
+
+// Tests updating metadata of a file with a non-dirty cache file.
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_WithNonDirtyCache) {
+ base::FilePath src_path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
+
+ // Download the file content to prepare a non-dirty cache file.
+ file_system::DownloadOperation download_operation(
+ blocking_task_runner(), delegate(), scheduler(), metadata(), cache(),
+ temp_dir());
+ FileError error = FILE_ERROR_FAILED;
+ base::FilePath cache_file_path;
+ scoped_ptr<ResourceEntry> src_entry;
+ download_operation.EnsureFileDownloadedByPath(
+ src_path,
+ ClientContext(USER_INITIATED),
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ google_apis::test_util::CreateCopyResultCallback(
+ &error, &cache_file_path, &src_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ ASSERT_TRUE(src_entry);
+
+ // Update the entry locally.
+ src_entry->set_title("Updated" + src_entry->title());
+ src_entry->set_metadata_edit_state(ResourceEntry::DIRTY);
+
+ error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::RefreshEntry,
+ base::Unretained(metadata()),
+ *src_entry),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Perform server side update. This shouldn't fail. (crbug.com/358590)
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ src_entry->local_id(),
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Verify the file is updated on the server.
+ google_apis::DriveApiErrorCode gdata_error = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> gdata_entry;
+ fake_service()->GetFileResource(
+ src_entry->resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&gdata_error,
+ &gdata_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
+ ASSERT_TRUE(gdata_entry);
+ EXPECT_EQ(src_entry->title(), gdata_entry->title());
+}
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_NotFound) {
+ const std::string id = "this ID should result in NOT_FOUND";
+ FileError error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ id, ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
+}
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_ContentUpdate) {
+ const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ const std::string kResourceId("2_file_resource_id");
+
+ const std::string local_id = GetLocalId(kFilePath);
+ EXPECT_FALSE(local_id.empty());
+
+ const std::string kTestFileContent = "I'm being uploaded! Yay!";
+ EXPECT_EQ(FILE_ERROR_OK, StoreAndMarkDirty(local_id, kTestFileContent));
+
+ int64 original_changestamp =
+ fake_service()->about_resource().largest_change_id();
+
+ // The callback will be called upon completion of UpdateEntry().
+ FileError error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Check that the server has received an update.
+ EXPECT_LT(original_changestamp,
+ fake_service()->about_resource().largest_change_id());
+
+ // Check that the file size is updated to that of the updated content.
+ google_apis::DriveApiErrorCode gdata_error = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_service()->GetFileResource(
+ kResourceId,
+ google_apis::test_util::CreateCopyResultCallback(&gdata_error,
+ &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
+ EXPECT_EQ(static_cast<int64>(kTestFileContent.size()),
+ server_entry->file_size());
+
+ // Make sure that the cache is no longer dirty.
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
+}
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_ContentUpdateMd5Check) {
+ const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ const std::string kResourceId("2_file_resource_id");
+
+ const std::string local_id = GetLocalId(kFilePath);
+ EXPECT_FALSE(local_id.empty());
+
+ const std::string kTestFileContent = "I'm being uploaded! Yay!";
+ EXPECT_EQ(FILE_ERROR_OK, StoreAndMarkDirty(local_id, kTestFileContent));
+
+ int64 original_changestamp =
+ fake_service()->about_resource().largest_change_id();
+
+ // The callback will be called upon completion of UpdateEntry().
+ FileError error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Check that the server has received an update.
+ EXPECT_LT(original_changestamp,
+ fake_service()->about_resource().largest_change_id());
+
+ // Check that the file size is updated to that of the updated content.
+ google_apis::DriveApiErrorCode gdata_error = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_service()->GetFileResource(
+ kResourceId,
+ google_apis::test_util::CreateCopyResultCallback(&gdata_error,
+ &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
+ EXPECT_EQ(static_cast<int64>(kTestFileContent.size()),
+ server_entry->file_size());
+
+ // Make sure that the cache is no longer dirty.
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
+
+ // Again mark the cache file dirty.
+ scoped_ptr<base::ScopedClosureRunner> file_closer;
+ error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&FileCache::OpenForWrite,
+ base::Unretained(cache()),
+ local_id,
+ &file_closer),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ file_closer.reset();
+
+ // And call UpdateEntry again.
+ // In this case, although the file is marked as dirty, but the content
+ // hasn't been changed. Thus, the actual uploading should be skipped.
+ original_changestamp = fake_service()->about_resource().largest_change_id();
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ EXPECT_EQ(original_changestamp,
+ fake_service()->about_resource().largest_change_id());
+
+ // Make sure that the cache is no longer dirty.
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
+}
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_OpenedForWrite) {
+ const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/File 1.txt"));
+ const std::string kResourceId("2_file_resource_id");
+
+ const std::string local_id = GetLocalId(kFilePath);
+ EXPECT_FALSE(local_id.empty());
+
+ const std::string kTestFileContent = "I'm being uploaded! Yay!";
+ EXPECT_EQ(FILE_ERROR_OK, StoreAndMarkDirty(local_id, kTestFileContent));
+
+ // Emulate a situation where someone is writing to the file.
+ scoped_ptr<base::ScopedClosureRunner> file_closer;
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&FileCache::OpenForWrite,
+ base::Unretained(cache()),
+ local_id,
+ &file_closer),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Update. This should not clear the dirty bit.
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Make sure that the cache is still dirty.
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
+
+ // Close the file.
+ file_closer.reset();
+
+ // Update. This should clear the dirty bit.
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Make sure that the cache is no longer dirty.
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
+}
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_UploadNewFile) {
+ // Create a new file locally.
+ const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/New File.txt"));
+
+ ResourceEntry parent;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath.DirName(), &parent));
+
+ ResourceEntry entry;
+ entry.set_parent_local_id(parent.local_id());
+ entry.set_title(kFilePath.BaseName().AsUTF8Unsafe());
+ entry.mutable_file_specific_info()->set_content_mime_type("text/plain");
+ entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+
+ FileError error = FILE_ERROR_FAILED;
+ std::string local_id;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::AddEntry,
+ base::Unretained(metadata()),
+ entry,
+ &local_id),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Update. This should result in creating a new file on the server.
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // The entry got a resource ID.
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
+ EXPECT_FALSE(entry.resource_id().empty());
+ EXPECT_EQ(ResourceEntry::CLEAN, entry.metadata_edit_state());
+
+ // Make sure that the cache is no longer dirty.
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
+
+ // Make sure that we really created a file.
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_service()->GetFileResource(
+ entry.resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_FALSE(server_entry->IsDirectory());
+}
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_NewFileOpendForWrite) {
+ // Create a new file locally.
+ const base::FilePath kFilePath(FILE_PATH_LITERAL("drive/root/New File.txt"));
+
+ ResourceEntry parent;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath.DirName(), &parent));
+
+ ResourceEntry entry;
+ entry.set_parent_local_id(parent.local_id());
+ entry.set_title(kFilePath.BaseName().AsUTF8Unsafe());
+ entry.mutable_file_specific_info()->set_content_mime_type("text/plain");
+ entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+
+ FileError error = FILE_ERROR_FAILED;
+ std::string local_id;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::AddEntry,
+ base::Unretained(metadata()),
+ entry,
+ &local_id),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ const std::string kTestFileContent = "This is a new file.";
+ EXPECT_EQ(FILE_ERROR_OK, StoreAndMarkDirty(local_id, kTestFileContent));
+
+ // Emulate a situation where someone is writing to the file.
+ scoped_ptr<base::ScopedClosureRunner> file_closer;
+ error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&FileCache::OpenForWrite,
+ base::Unretained(cache()),
+ local_id,
+ &file_closer),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Update, but no update is performed because the file is opened.
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // The entry hasn't got a resource ID yet.
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
+ EXPECT_TRUE(entry.resource_id().empty());
+
+ // Close the file.
+ file_closer.reset();
+
+ // Update. This should result in creating a new file on the server.
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // The entry got a resource ID.
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kFilePath, &entry));
+ EXPECT_FALSE(entry.resource_id().empty());
+ EXPECT_EQ(ResourceEntry::CLEAN, entry.metadata_edit_state());
+}
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_CreateDirectory) {
+ // Create a new directory locally.
+ const base::FilePath kPath(FILE_PATH_LITERAL("drive/root/New Directory"));
+
+ ResourceEntry parent;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kPath.DirName(), &parent));
+
+ ResourceEntry entry;
+ entry.set_parent_local_id(parent.local_id());
+ entry.set_title(kPath.BaseName().AsUTF8Unsafe());
+ entry.mutable_file_info()->set_is_directory(true);
+ entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+
+ FileError error = FILE_ERROR_FAILED;
+ std::string local_id;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&internal::ResourceMetadata::AddEntry,
+ base::Unretained(metadata()),
+ entry,
+ &local_id),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Update. This should result in creating a new directory on the server.
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ local_id,
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // The entry got a resource ID.
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kPath, &entry));
+ EXPECT_FALSE(entry.resource_id().empty());
+ EXPECT_EQ(ResourceEntry::CLEAN, entry.metadata_edit_state());
+
+ // Make sure that we really created a directory.
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ fake_service()->GetFileResource(
+ entry.resource_id(),
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_TRUE(server_entry->IsDirectory());
+}
+
+TEST_F(EntryUpdatePerformerTest, UpdateEntry_InsufficientPermission) {
+ base::FilePath src_path(
+ FILE_PATH_LITERAL("drive/root/Directory 1/SubDirectory File 1.txt"));
+
+ ResourceEntry src_entry;
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(src_path, &src_entry));
+
+ // Update local entry.
+ ResourceEntry updated_entry(src_entry);
+ updated_entry.set_title("Moved" + src_entry.title());
+ updated_entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::RefreshEntry,
+ base::Unretained(metadata()),
+ updated_entry),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Set user permission to forbid server side update.
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, fake_service()->SetUserPermission(
+ src_entry.resource_id(), google_apis::drive::PERMISSION_ROLE_READER));
+
+ // Try to perform update.
+ error = FILE_ERROR_FAILED;
+ performer_->UpdateEntry(
+ src_entry.local_id(),
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // This should result in reverting the local change.
+ ResourceEntry result_entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntryById(src_entry.local_id(), &result_entry));
+ EXPECT_EQ(src_entry.title(), result_entry.title());
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/sync/remove_performer.cc b/components/drive/sync/remove_performer.cc
new file mode 100644
index 0000000..499f070
--- /dev/null
+++ b/components/drive/sync/remove_performer.cc
@@ -0,0 +1,255 @@
+// 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/sync/remove_performer.h"
+
+#include "base/sequenced_task_runner.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_api_util.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 "components/drive/sync/entry_revert_performer.h"
+#include "google_apis/drive/drive_api_parser.h"
+
+namespace drive {
+namespace internal {
+
+namespace {
+
+// Updates local metadata and after remote unparenting.
+FileError UpdateLocalStateAfterUnparent(ResourceMetadata* metadata,
+ const std::string& local_id) {
+ ResourceEntry entry;
+ FileError error = metadata->GetResourceEntryById(local_id, &entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ entry.set_parent_local_id(util::kDriveOtherDirLocalId);
+ return metadata->RefreshEntry(entry);
+}
+
+// Utility function to run ResourceMetadata::RemoveEntry from UI thread.
+void RemoveEntryOnUIThread(base::SequencedTaskRunner* blocking_task_runner,
+ ResourceMetadata* metadata,
+ const std::string& local_id,
+ const FileOperationCallback& callback) {
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner,
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::RemoveEntry,
+ base::Unretained(metadata), local_id),
+ callback);
+}
+
+} // namespace
+
+RemovePerformer::RemovePerformer(
+ base::SequencedTaskRunner* blocking_task_runner,
+ file_system::OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ ResourceMetadata* metadata)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ scheduler_(scheduler),
+ metadata_(metadata),
+ entry_revert_performer_(new EntryRevertPerformer(blocking_task_runner,
+ delegate,
+ scheduler,
+ metadata)),
+ weak_ptr_factory_(this) {
+}
+
+RemovePerformer::~RemovePerformer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+// Returns |entry| corresponding to |local_id|.
+// Adding to that, removes the entry when it does not exist on the server.
+FileError TryToRemoveLocally(ResourceMetadata* metadata,
+ const std::string& local_id,
+ ResourceEntry* entry) {
+ FileError error = metadata->GetResourceEntryById(local_id, entry);
+ if (error != FILE_ERROR_OK || !entry->resource_id().empty())
+ return error;
+ return metadata->RemoveEntry(local_id);
+}
+
+void RemovePerformer::Remove(const std::string& local_id,
+ const ClientContext& context,
+ 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(&TryToRemoveLocally, metadata_, local_id, entry),
+ base::Bind(&RemovePerformer::RemoveAfterGetResourceEntry,
+ weak_ptr_factory_.GetWeakPtr(),
+ context,
+ callback,
+ base::Owned(entry)));
+}
+
+void RemovePerformer::RemoveAfterGetResourceEntry(
+ const ClientContext& context,
+ const FileOperationCallback& callback,
+ const ResourceEntry* entry,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (error != FILE_ERROR_OK || entry->resource_id().empty()) {
+ callback.Run(error);
+ return;
+ }
+
+ // To match with the behavior of drive.google.com:
+ // Removal of shared entries under MyDrive is just removing from the parent.
+ // The entry will stay in shared-with-me (in other words, in "drive/other".)
+ //
+ // TODO(kinaba): to be more precise, we might be better to branch by whether
+ // or not the current account is an owner of the file. The code below is
+ // written under the assumption that |shared_with_me| coincides with that.
+ if (entry->shared_with_me()) {
+ UnparentResource(context, callback, entry->resource_id(),
+ entry->local_id());
+ } else {
+ // Otherwise try sending the entry to trash.
+ TrashResource(context, callback, entry->resource_id(), entry->local_id());
+ }
+}
+
+void RemovePerformer::TrashResource(const ClientContext& context,
+ const FileOperationCallback& callback,
+ const std::string& resource_id,
+ const std::string& local_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ scheduler_->TrashResource(
+ resource_id,
+ context,
+ base::Bind(&RemovePerformer::TrashResourceAfterUpdateRemoteState,
+ weak_ptr_factory_.GetWeakPtr(), context, callback, local_id));
+}
+
+void RemovePerformer::TrashResourceAfterUpdateRemoteState(
+ const ClientContext& context,
+ const FileOperationCallback& callback,
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ if (status == google_apis::HTTP_FORBIDDEN) {
+ // Editing this entry is not allowed, revert local changes.
+ entry_revert_performer_->RevertEntry(local_id, context, callback);
+ delegate_->OnDriveSyncError(
+ file_system::DRIVE_SYNC_ERROR_DELETE_WITHOUT_PERMISSION, local_id);
+ return;
+ }
+
+ FileError error = GDataToFileError(status);
+ if (error == FILE_ERROR_NOT_FOUND) { // Remove local entry when not found.
+ RemoveEntryOnUIThread(blocking_task_runner_.get(), metadata_, local_id,
+ callback);
+ return;
+ }
+
+ // Now we're done. If the entry is trashed on the server, it'll be also
+ // deleted locally on the next update.
+ callback.Run(error);
+}
+
+void RemovePerformer::UnparentResource(const ClientContext& context,
+ const FileOperationCallback& callback,
+ const std::string& resource_id,
+ const std::string& local_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ scheduler_->GetFileResource(
+ resource_id,
+ context,
+ base::Bind(&RemovePerformer::UnparentResourceAfterGetFileResource,
+ weak_ptr_factory_.GetWeakPtr(), context, callback, local_id));
+}
+
+void RemovePerformer::UnparentResourceAfterGetFileResource(
+ const ClientContext& context,
+ const FileOperationCallback& callback,
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileResource> file_resource) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileError error = GDataToFileError(status);
+ if (error == FILE_ERROR_NOT_FOUND) { // Remove local entry when not found.
+ RemoveEntryOnUIThread(blocking_task_runner_.get(), metadata_, local_id,
+ callback);
+ return;
+ }
+
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ ResourceEntry entry;
+ std::string parent_resource_id;
+ if (!ConvertFileResourceToResourceEntry(*file_resource, &entry,
+ &parent_resource_id)) {
+ callback.Run(FILE_ERROR_NOT_A_FILE);
+ return;
+ }
+
+ if (!entry.shared_with_me()) {
+ // shared_with_me() has changed on the server.
+ UnparentResourceAfterUpdateRemoteState(callback, local_id,
+ google_apis::HTTP_CONFLICT);
+ return;
+ }
+
+ if (parent_resource_id.empty()) {
+ // This entry is unparented already.
+ UnparentResourceAfterUpdateRemoteState(callback, local_id,
+ google_apis::HTTP_NO_CONTENT);
+ return;
+ }
+
+ scheduler_->RemoveResourceFromDirectory(
+ parent_resource_id,
+ entry.resource_id(),
+ context,
+ base::Bind(&RemovePerformer::UnparentResourceAfterUpdateRemoteState,
+ weak_ptr_factory_.GetWeakPtr(), callback, local_id));
+}
+
+void RemovePerformer::UnparentResourceAfterUpdateRemoteState(
+ const FileOperationCallback& callback,
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!callback.is_null());
+
+ FileError error = GDataToFileError(status);
+ if (error != FILE_ERROR_OK) {
+ callback.Run(error);
+ return;
+ }
+
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateLocalStateAfterUnparent, metadata_, local_id),
+ callback);
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/sync/remove_performer.h b/components/drive/sync/remove_performer.h
new file mode 100644
index 0000000..bce22e0
--- /dev/null
+++ b/components/drive/sync/remove_performer.h
@@ -0,0 +1,114 @@
+// 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_SYNC_REMOVE_PERFORMER_H_
+#define COMPONENTS_DRIVE_SYNC_REMOVE_PERFORMER_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 SequencedTaskRunner;
+}
+
+namespace google_apis {
+class FileResource;
+}
+
+namespace drive {
+
+class JobScheduler;
+class ResourceEntry;
+struct ClientContext;
+
+namespace file_system {
+class OperationDelegate;
+} // namespace file_system
+
+namespace internal {
+
+class EntryRevertPerformer;
+class ResourceMetadata;
+
+// This class encapsulates the drive Remove 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 RemovePerformer {
+ public:
+ RemovePerformer(base::SequencedTaskRunner* blocking_task_runner,
+ file_system::OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ ResourceMetadata* metadata);
+ ~RemovePerformer();
+
+ // Removes the resource.
+ //
+ // |callback| must not be null.
+ void Remove(const std::string& local_id,
+ const ClientContext& context,
+ const FileOperationCallback& callback);
+
+ private:
+ // Part of Remove(). Called after GetResourceEntry() completion.
+ void RemoveAfterGetResourceEntry(const ClientContext& context,
+ const FileOperationCallback& callback,
+ const ResourceEntry* entry,
+ FileError error);
+
+ // Requests the server to move the specified resource to the trash.
+ void TrashResource(const ClientContext& context,
+ const FileOperationCallback& callback,
+ const std::string& resource_id,
+ const std::string& local_id);
+
+ // Part of TrashResource(). Called after server-side removal is done.
+ void TrashResourceAfterUpdateRemoteState(
+ const ClientContext& context,
+ const FileOperationCallback& callback,
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status);
+
+ // Requests the server to detach the specified resource from its parent.
+ void UnparentResource(const ClientContext& context,
+ const FileOperationCallback& callback,
+ const std::string& resource_id,
+ const std::string& local_id);
+
+ // Part of UnparentResource().
+ void UnparentResourceAfterGetFileResource(
+ const ClientContext& context,
+ const FileOperationCallback& callback,
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status,
+ scoped_ptr<google_apis::FileResource> file_resource);
+
+ // Part of UnparentResource().
+ void UnparentResourceAfterUpdateRemoteState(
+ const FileOperationCallback& callback,
+ const std::string& local_id,
+ google_apis::DriveApiErrorCode status);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ file_system::OperationDelegate* delegate_;
+ JobScheduler* scheduler_;
+ ResourceMetadata* metadata_;
+ scoped_ptr<EntryRevertPerformer> entry_revert_performer_;
+
+ 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<RemovePerformer> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(RemovePerformer);
+};
+
+} // namespace internal
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SYNC_REMOVE_PERFORMER_H_
diff --git a/components/drive/sync/remove_performer_unittest.cc b/components/drive/sync/remove_performer_unittest.cc
new file mode 100644
index 0000000..7404d00
--- /dev/null
+++ b/components/drive/sync/remove_performer_unittest.cc
@@ -0,0 +1,199 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/drive/sync/remove_performer.h"
+
+#include "base/task_runner_util.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/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"
+
+namespace drive {
+namespace internal {
+
+typedef file_system::OperationTestBase RemovePerformerTest;
+
+TEST_F(RemovePerformerTest, RemoveFile) {
+ RemovePerformer performer(blocking_task_runner(), delegate(), scheduler(),
+ metadata());
+
+ 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));
+ performer.Remove(entry.local_id(),
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // 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();
+
+ performer.Remove(entry.local_id(),
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Verify the file is indeed removed in the server.
+ google_apis::DriveApiErrorCode gdata_error = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> gdata_entry;
+ fake_service()->GetFileResource(
+ resource_id,
+ google_apis::test_util::CreateCopyResultCallback(&gdata_error,
+ &gdata_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
+ EXPECT_TRUE(gdata_entry->labels().is_trashed());
+
+ // Try removing non-existing file.
+ error = FILE_ERROR_FAILED;
+ performer.Remove("non-existing-id",
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, error);
+}
+
+TEST_F(RemovePerformerTest, RemoveShared) {
+ RemovePerformer performer(blocking_task_runner(), delegate(), scheduler(),
+ metadata());
+
+ const base::FilePath kPathInMyDrive(FILE_PATH_LITERAL(
+ "drive/root/shared.txt"));
+ const base::FilePath kPathInOther(FILE_PATH_LITERAL(
+ "drive/other/shared.txt"));
+
+ // Prepare a shared file to the root folder.
+ google_apis::DriveApiErrorCode gdata_error = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> gdata_entry;
+ fake_service()->AddNewFile(
+ "text/plain",
+ "dummy content",
+ fake_service()->GetRootResourceId(),
+ kPathInMyDrive.BaseName().AsUTF8Unsafe(),
+ true, // shared_with_me,
+ google_apis::test_util::CreateCopyResultCallback(&gdata_error,
+ &gdata_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_EQ(google_apis::HTTP_CREATED, gdata_error);
+ CheckForUpdates();
+
+ // Remove it. Locally, the file should be moved to drive/other.
+ ResourceEntry entry;
+ ASSERT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kPathInMyDrive, &entry));
+ FileError error = FILE_ERROR_FAILED;
+ performer.Remove(entry.local_id(),
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND,
+ GetLocalResourceEntry(kPathInMyDrive, &entry));
+ EXPECT_EQ(FILE_ERROR_OK, GetLocalResourceEntry(kPathInOther, &entry));
+
+ // Remotely, the entry should have lost its parent.
+ gdata_error = google_apis::DRIVE_OTHER_ERROR;
+ const std::string resource_id = gdata_entry->file_id();
+ fake_service()->GetFileResource(
+ resource_id,
+ google_apis::test_util::CreateCopyResultCallback(&gdata_error,
+ &gdata_entry));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ ASSERT_EQ(google_apis::HTTP_SUCCESS, gdata_error);
+ EXPECT_FALSE(gdata_entry->labels().is_trashed()); // It's not deleted.
+ EXPECT_TRUE(gdata_entry->parents().empty());
+}
+
+TEST_F(RemovePerformerTest, RemoveLocallyCreatedFile) {
+ RemovePerformer performer(blocking_task_runner(), delegate(), scheduler(),
+ metadata());
+
+ // Add an entry without resource ID.
+ ResourceEntry entry;
+ entry.set_title("New File.txt");
+ entry.set_parent_local_id(util::kDriveTrashDirLocalId);
+
+ FileError error = FILE_ERROR_FAILED;
+ std::string local_id;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::AddEntry,
+ base::Unretained(metadata()),
+ entry,
+ &local_id),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Remove the entry.
+ performer.Remove(local_id, ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ EXPECT_EQ(FILE_ERROR_NOT_FOUND, GetLocalResourceEntryById(local_id, &entry));
+}
+
+TEST_F(RemovePerformerTest, Remove_InsufficientPermission) {
+ RemovePerformer performer(blocking_task_runner(), delegate(), scheduler(),
+ metadata());
+
+ 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));
+
+ // Remove locally.
+ ResourceEntry updated_entry(src_entry);
+ updated_entry.set_parent_local_id(util::kDriveTrashDirLocalId);
+
+ FileError error = FILE_ERROR_FAILED;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner(),
+ FROM_HERE,
+ base::Bind(&ResourceMetadata::RefreshEntry,
+ base::Unretained(metadata()),
+ updated_entry),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Set user permission to forbid server side update.
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, fake_service()->SetUserPermission(
+ src_entry.resource_id(), google_apis::drive::PERMISSION_ROLE_READER));
+
+ // Try to perform remove.
+ error = FILE_ERROR_FAILED;
+ performer.Remove(src_entry.local_id(),
+ ClientContext(USER_INITIATED),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ content::RunAllBlockingPoolTasksUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // This should result in reverting the local change.
+ ResourceEntry result_entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ GetLocalResourceEntryById(src_entry.local_id(), &result_entry));
+ EXPECT_EQ(src_entry.parent_local_id(), result_entry.parent_local_id());
+
+ ASSERT_EQ(1U, delegate()->drive_sync_errors().size());
+ EXPECT_EQ(file_system::DRIVE_SYNC_ERROR_DELETE_WITHOUT_PERMISSION,
+ delegate()->drive_sync_errors()[0]);
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/sync_client.cc b/components/drive/sync_client.cc
new file mode 100644
index 0000000..e11a6ce
--- /dev/null
+++ b/components/drive/sync_client.cc
@@ -0,0 +1,491 @@
+// 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/sync_client.h"
+
+#include <vector>
+
+#include "base/bind.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_system/download_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/sync/entry_update_performer.h"
+#include "google_apis/drive/task_util.h"
+
+namespace drive {
+namespace internal {
+
+namespace {
+
+// The delay constant is used to delay processing a sync task. We should not
+// process SyncTasks immediately for the following reasons:
+//
+// 1) For fetching, the user may accidentally click on "Make available
+// offline" checkbox on a file, and immediately cancel it in a second.
+// It's a waste to fetch the file in this case.
+//
+// 2) For uploading, file writing via HTML5 file system API is performed in
+// two steps: 1) truncate a file to 0 bytes, 2) write contents. We
+// shouldn't start uploading right after the step 1). Besides, the user
+// may edit the same file repeatedly in a short period of time.
+//
+// TODO(satorux): We should find a way to handle the upload case more nicely,
+// and shorten the delay. crbug.com/134774
+const int kDelaySeconds = 1;
+
+// The delay constant is used to delay retrying a sync task on server errors.
+const int kLongDelaySeconds = 600;
+
+// Iterates entries and appends IDs to |to_fetch| if the file is pinned but not
+// fetched (not present locally), to |to_update| if the file needs update.
+void CollectBacklog(ResourceMetadata* metadata,
+ std::vector<std::string>* to_fetch,
+ std::vector<std::string>* to_update) {
+ DCHECK(to_fetch);
+ DCHECK(to_update);
+
+ scoped_ptr<ResourceMetadata::Iterator> it = metadata->GetIterator();
+ for (; !it->IsAtEnd(); it->Advance()) {
+ const std::string& local_id = it->GetID();
+ const ResourceEntry& entry = it->GetValue();
+ if (entry.parent_local_id() == util::kDriveTrashDirLocalId) {
+ to_update->push_back(local_id);
+ continue;
+ }
+
+ bool should_update = false;
+ switch (entry.metadata_edit_state()) {
+ case ResourceEntry::CLEAN:
+ break;
+ case ResourceEntry::SYNCING:
+ case ResourceEntry::DIRTY:
+ should_update = true;
+ break;
+ }
+
+ if (entry.file_specific_info().cache_state().is_pinned() &&
+ !entry.file_specific_info().cache_state().is_present())
+ to_fetch->push_back(local_id);
+
+ if (entry.file_specific_info().cache_state().is_dirty())
+ should_update = true;
+
+ if (should_update)
+ to_update->push_back(local_id);
+ }
+ DCHECK(!it->HasError());
+}
+
+// Iterates cache entries and collects IDs of ones with obsolete cache files.
+void CheckExistingPinnedFiles(ResourceMetadata* metadata,
+ FileCache* cache,
+ std::vector<std::string>* local_ids) {
+ scoped_ptr<ResourceMetadata::Iterator> it = metadata->GetIterator();
+ for (; !it->IsAtEnd(); it->Advance()) {
+ const ResourceEntry& entry = it->GetValue();
+ const FileCacheEntry& cache_state =
+ entry.file_specific_info().cache_state();
+ const std::string& local_id = it->GetID();
+ if (!cache_state.is_pinned() || !cache_state.is_present())
+ continue;
+
+ // If MD5s don't match, it indicates the local cache file is stale, unless
+ // the file is dirty (the MD5 is "local"). We should never re-fetch the
+ // file when we have a locally modified version.
+ if (entry.file_specific_info().md5() == cache_state.md5() ||
+ cache_state.is_dirty())
+ continue;
+
+ FileError error = cache->Remove(local_id);
+ if (error != FILE_ERROR_OK) {
+ LOG(WARNING) << "Failed to remove cache entry: " << local_id;
+ continue;
+ }
+
+ error = cache->Pin(local_id);
+ if (error != FILE_ERROR_OK) {
+ LOG(WARNING) << "Failed to pin cache entry: " << local_id;
+ continue;
+ }
+
+ local_ids->push_back(local_id);
+ }
+ DCHECK(!it->HasError());
+}
+
+// Gets the parent entry of the entry specified by the ID.
+FileError GetParentResourceEntry(ResourceMetadata* metadata,
+ const std::string& local_id,
+ ResourceEntry* parent) {
+ ResourceEntry entry;
+ FileError error = metadata->GetResourceEntryById(local_id, &entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ return metadata->GetResourceEntryById(entry.parent_local_id(), parent);
+}
+
+} // namespace
+
+SyncClient::SyncTask::SyncTask()
+ : state(SUSPENDED), context(BACKGROUND), should_run_again(false) {}
+SyncClient::SyncTask::~SyncTask() {}
+
+SyncClient::SyncClient(base::SequencedTaskRunner* blocking_task_runner,
+ file_system::OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ ResourceMetadata* metadata,
+ FileCache* cache,
+ LoaderController* loader_controller,
+ const base::FilePath& temporary_file_directory)
+ : blocking_task_runner_(blocking_task_runner),
+ operation_delegate_(delegate),
+ metadata_(metadata),
+ cache_(cache),
+ download_operation_(new file_system::DownloadOperation(
+ blocking_task_runner,
+ delegate,
+ scheduler,
+ metadata,
+ cache,
+ temporary_file_directory)),
+ entry_update_performer_(new EntryUpdatePerformer(blocking_task_runner,
+ delegate,
+ scheduler,
+ metadata,
+ cache,
+ loader_controller)),
+ delay_(base::TimeDelta::FromSeconds(kDelaySeconds)),
+ long_delay_(base::TimeDelta::FromSeconds(kLongDelaySeconds)),
+ weak_ptr_factory_(this) {
+}
+
+SyncClient::~SyncClient() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void SyncClient::StartProcessingBacklog() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::vector<std::string>* to_fetch = new std::vector<std::string>;
+ std::vector<std::string>* to_update = new std::vector<std::string>;
+ blocking_task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&CollectBacklog, metadata_, to_fetch, to_update),
+ base::Bind(&SyncClient::OnGetLocalIdsOfBacklog,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(to_fetch),
+ base::Owned(to_update)));
+}
+
+void SyncClient::StartCheckingExistingPinnedFiles() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ std::vector<std::string>* local_ids = new std::vector<std::string>;
+ blocking_task_runner_->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&CheckExistingPinnedFiles,
+ metadata_,
+ cache_,
+ local_ids),
+ base::Bind(&SyncClient::AddFetchTasks,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(local_ids)));
+}
+
+void SyncClient::AddFetchTask(const std::string& local_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ AddFetchTaskInternal(local_id, delay_);
+}
+
+void SyncClient::RemoveFetchTask(const std::string& local_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SyncTasks::iterator it = tasks_.find(SyncTasks::key_type(FETCH, local_id));
+ if (it == tasks_.end())
+ return;
+
+ SyncTask* task = &it->second;
+ switch (task->state) {
+ case SUSPENDED:
+ case PENDING:
+ OnTaskComplete(FETCH, local_id, FILE_ERROR_ABORT);
+ break;
+ case RUNNING:
+ if (!task->cancel_closure.is_null())
+ task->cancel_closure.Run();
+ break;
+ }
+}
+
+void SyncClient::AddUpdateTask(const ClientContext& context,
+ const std::string& local_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ AddUpdateTaskInternal(context, local_id, delay_);
+}
+
+bool SyncClient:: WaitForUpdateTaskToComplete(
+ const std::string& local_id,
+ const FileOperationCallback& callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SyncTasks::iterator it = tasks_.find(SyncTasks::key_type(UPDATE, local_id));
+ if (it == tasks_.end())
+ return false;
+
+ SyncTask* task = &it->second;
+ task->waiting_callbacks.push_back(callback);
+ return true;
+}
+
+base::Closure SyncClient::PerformFetchTask(const std::string& local_id,
+ const ClientContext& context) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return download_operation_->EnsureFileDownloadedByLocalId(
+ local_id,
+ context,
+ GetFileContentInitializedCallback(),
+ google_apis::GetContentCallback(),
+ base::Bind(&SyncClient::OnFetchFileComplete,
+ weak_ptr_factory_.GetWeakPtr(),
+ local_id));
+}
+
+void SyncClient::AddFetchTaskInternal(const std::string& local_id,
+ const base::TimeDelta& delay) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SyncTask task;
+ task.state = PENDING;
+ task.context = ClientContext(BACKGROUND);
+ task.task = base::Bind(&SyncClient::PerformFetchTask,
+ base::Unretained(this),
+ local_id);
+ AddTask(SyncTasks::key_type(FETCH, local_id), task, delay);
+}
+
+base::Closure SyncClient::PerformUpdateTask(const std::string& local_id,
+ const ClientContext& context) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ entry_update_performer_->UpdateEntry(
+ local_id,
+ context,
+ base::Bind(&SyncClient::OnTaskComplete,
+ weak_ptr_factory_.GetWeakPtr(),
+ UPDATE,
+ local_id));
+ return base::Closure();
+}
+
+void SyncClient::AddUpdateTaskInternal(const ClientContext& context,
+ const std::string& local_id,
+ const base::TimeDelta& delay) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SyncTask task;
+ task.state = PENDING;
+ task.context = context;
+ task.task = base::Bind(&SyncClient::PerformUpdateTask,
+ base::Unretained(this),
+ local_id);
+ AddTask(SyncTasks::key_type(UPDATE, local_id), task, delay);
+}
+
+void SyncClient::AddTask(const SyncTasks::key_type& key,
+ const SyncTask& task,
+ const base::TimeDelta& delay) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ SyncTasks::iterator it = tasks_.find(key);
+ if (it != tasks_.end()) {
+ switch (it->second.state) {
+ case SUSPENDED:
+ // Activate the task.
+ it->second.state = PENDING;
+ break;
+ case PENDING:
+ // The same task will run, do nothing.
+ return;
+ case RUNNING:
+ // Something has changed since the task started. Schedule rerun.
+ it->second.should_run_again = true;
+ return;
+ }
+ } else {
+ tasks_[key] = task;
+ }
+ DCHECK_EQ(PENDING, task.state);
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&SyncClient::StartTask, weak_ptr_factory_.GetWeakPtr(), key),
+ delay);
+}
+
+void SyncClient::StartTask(const SyncTasks::key_type& key) {
+ ResourceEntry* parent = new ResourceEntry;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&GetParentResourceEntry, metadata_, key.second, parent),
+ base::Bind(&SyncClient::StartTaskAfterGetParentResourceEntry,
+ weak_ptr_factory_.GetWeakPtr(),
+ key,
+ base::Owned(parent)));
+}
+
+void SyncClient::StartTaskAfterGetParentResourceEntry(
+ const SyncTasks::key_type& key,
+ const ResourceEntry* parent,
+ FileError error) {
+ const SyncType type = key.first;
+ const std::string& local_id = key.second;
+ SyncTasks::iterator it = tasks_.find(key);
+ if (it == tasks_.end())
+ return;
+
+ SyncTask* task = &it->second;
+ switch (task->state) {
+ case SUSPENDED:
+ case PENDING:
+ break;
+ case RUNNING: // Do nothing.
+ return;
+ }
+
+ if (error != FILE_ERROR_OK) {
+ OnTaskComplete(type, local_id, error);
+ return;
+ }
+
+ if (type == UPDATE &&
+ parent->resource_id().empty() &&
+ parent->local_id() != util::kDriveTrashDirLocalId) {
+ // Parent entry needs to be synced to get a resource ID.
+ // Suspend the task and register it as a dependent task of the parent.
+ const SyncTasks::key_type key_parent(type, parent->local_id());
+ SyncTasks::iterator it_parent = tasks_.find(key_parent);
+ if (it_parent == tasks_.end()) {
+ OnTaskComplete(type, local_id, FILE_ERROR_INVALID_OPERATION);
+ LOG(WARNING) << "Parent task not found: type = " << type << ", id = "
+ << local_id << ", parent_id = " << parent->local_id();
+ return;
+ }
+ task->state = SUSPENDED;
+ it_parent->second.dependent_tasks.push_back(key);
+ return;
+ }
+
+ // Run the task.
+ task->state = RUNNING;
+ task->cancel_closure = task->task.Run(task->context);
+}
+
+void SyncClient::OnGetLocalIdsOfBacklog(
+ const std::vector<std::string>* to_fetch,
+ const std::vector<std::string>* to_update) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Give priority to upload tasks over fetch tasks, so that dirty files are
+ // uploaded as soon as possible.
+ for (size_t i = 0; i < to_update->size(); ++i) {
+ const std::string& local_id = (*to_update)[i];
+ DVLOG(1) << "Queuing to update: " << local_id;
+ AddUpdateTask(ClientContext(BACKGROUND), local_id);
+ }
+
+ for (size_t i = 0; i < to_fetch->size(); ++i) {
+ const std::string& local_id = (*to_fetch)[i];
+ DVLOG(1) << "Queuing to fetch: " << local_id;
+ AddFetchTaskInternal(local_id, delay_);
+ }
+}
+
+void SyncClient::AddFetchTasks(const std::vector<std::string>* local_ids) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ for (size_t i = 0; i < local_ids->size(); ++i)
+ AddFetchTask((*local_ids)[i]);
+}
+
+void SyncClient::OnTaskComplete(SyncType type,
+ const std::string& local_id,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const SyncTasks::key_type key(type, local_id);
+ SyncTasks::iterator it = tasks_.find(key);
+ DCHECK(it != tasks_.end());
+
+ base::TimeDelta retry_delay = base::TimeDelta::FromSeconds(0);
+
+ switch (error) {
+ case FILE_ERROR_OK:
+ DVLOG(1) << "Completed: type = " << type << ", id = " << local_id;
+ break;
+ case FILE_ERROR_ABORT:
+ // Ignore it because this is caused by user's cancel operations.
+ break;
+ case FILE_ERROR_NO_CONNECTION:
+ // Run the task again so that we'll retry once the connection is back.
+ it->second.should_run_again = true;
+ it->second.context = ClientContext(BACKGROUND);
+ break;
+ case FILE_ERROR_SERVICE_UNAVAILABLE:
+ // Run the task again so that we'll retry once the service is back.
+ it->second.should_run_again = true;
+ it->second.context = ClientContext(BACKGROUND);
+ retry_delay = long_delay_;
+ operation_delegate_->OnDriveSyncError(
+ file_system::DRIVE_SYNC_ERROR_SERVICE_UNAVAILABLE, local_id);
+ break;
+ default:
+ operation_delegate_->OnDriveSyncError(
+ file_system::DRIVE_SYNC_ERROR_MISC, local_id);
+ LOG(WARNING) << "Failed: type = " << type << ", id = " << local_id
+ << ": " << FileErrorToString(error);
+ }
+
+ for (size_t i = 0; i < it->second.waiting_callbacks.size(); ++i) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(it->second.waiting_callbacks[i], error));
+ }
+ it->second.waiting_callbacks.clear();
+
+ if (it->second.should_run_again) {
+ DVLOG(1) << "Running again: type = " << type << ", id = " << local_id;
+ it->second.state = PENDING;
+ it->second.should_run_again = false;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&SyncClient::StartTask, weak_ptr_factory_.GetWeakPtr(), key),
+ retry_delay);
+ } else {
+ for (size_t i = 0; i < it->second.dependent_tasks.size(); ++i)
+ StartTask(it->second.dependent_tasks[i]);
+ tasks_.erase(it);
+ }
+}
+
+void SyncClient::OnFetchFileComplete(const std::string& local_id,
+ FileError error,
+ const base::FilePath& local_path,
+ scoped_ptr<ResourceEntry> entry) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ OnTaskComplete(FETCH, local_id, error);
+ if (error == FILE_ERROR_ABORT) {
+ // If user cancels download, unpin the file so that we do not sync the file
+ // again.
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&FileCache::Unpin, base::Unretained(cache_), local_id),
+ base::Bind(&util::EmptyFileOperationCallback));
+ }
+}
+
+} // namespace internal
+} // namespace drive
diff --git a/components/drive/sync_client.h b/components/drive/sync_client.h
new file mode 100644
index 0000000..1c53ddc
--- /dev/null
+++ b/components/drive/sync_client.h
@@ -0,0 +1,199 @@
+// 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_SYNC_CLIENT_H_
+#define COMPONENTS_DRIVE_SYNC_CLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+namespace drive {
+
+class FileCacheEntry;
+class JobScheduler;
+class ResourceEntry;
+struct ClientContext;
+
+namespace file_system {
+class DownloadOperation;
+class OperationDelegate;
+}
+
+namespace internal {
+
+class ChangeListLoader;
+class EntryUpdatePerformer;
+class FileCache;
+class LoaderController;
+class ResourceMetadata;
+
+// The SyncClient is used to synchronize pinned files on Drive and the
+// cache on the local drive.
+//
+// If the user logs out before fetching of the pinned files is complete, this
+// client resumes fetching operations next time the user logs in, based on
+// the states left in the cache.
+class SyncClient {
+ public:
+ SyncClient(base::SequencedTaskRunner* blocking_task_runner,
+ file_system::OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ ResourceMetadata* metadata,
+ FileCache* cache,
+ LoaderController* loader_controller,
+ const base::FilePath& temporary_file_directory);
+ virtual ~SyncClient();
+
+ // Adds a fetch task.
+ void AddFetchTask(const std::string& local_id);
+
+ // Removes a fetch task.
+ void RemoveFetchTask(const std::string& local_id);
+
+ // Adds a update task.
+ void AddUpdateTask(const ClientContext& context, const std::string& local_id);
+
+ // Waits for the update task to complete and runs the callback.
+ // Returns false if no task is found for the spcecified ID.
+ bool WaitForUpdateTaskToComplete(const std::string& local_id,
+ const FileOperationCallback& callback);
+
+ // Starts processing the backlog (i.e. pinned-but-not-filed files and
+ // dirty-but-not-uploaded files). Kicks off retrieval of the local
+ // IDs of these files, and then starts the sync loop.
+ void StartProcessingBacklog();
+
+ // Starts checking the existing pinned files to see if these are
+ // up-to-date. If stale files are detected, the local IDs of these files
+ // are added and the sync loop is started.
+ void StartCheckingExistingPinnedFiles();
+
+ // Sets a delay for testing.
+ void set_delay_for_testing(const base::TimeDelta& delay) {
+ delay_ = delay;
+ }
+
+ private:
+ // Types of sync tasks.
+ enum SyncType {
+ FETCH, // Fetch a file from the Drive server.
+ UPDATE, // Updates an entry's metadata or content on the Drive server.
+ };
+
+ // States of sync tasks.
+ enum SyncState {
+ SUSPENDED, // Task is currently inactive.
+ PENDING, // Task is going to run.
+ RUNNING, // Task is running.
+ };
+
+ typedef std::pair<SyncType, std::string> SyncTaskKey;
+
+ struct SyncTask {
+ SyncTask();
+ ~SyncTask();
+ SyncState state;
+ ClientContext context;
+ base::Callback<base::Closure(const ClientContext& context)> task;
+ bool should_run_again;
+ base::Closure cancel_closure;
+ std::vector<SyncTaskKey> dependent_tasks;
+ std::vector<FileOperationCallback> waiting_callbacks;
+ };
+
+ typedef std::map<SyncTaskKey, SyncTask> SyncTasks;
+
+ // Performs a FETCH task.
+ base::Closure PerformFetchTask(const std::string& local_id,
+ const ClientContext& context);
+
+ // Adds a FETCH task.
+ void AddFetchTaskInternal(const std::string& local_id,
+ const base::TimeDelta& delay);
+
+ // Performs a UPDATE task.
+ base::Closure PerformUpdateTask(const std::string& local_id,
+ const ClientContext& context);
+
+ // Adds a UPDATE task.
+ void AddUpdateTaskInternal(const ClientContext& context,
+ const std::string& local_id,
+ const base::TimeDelta& delay);
+
+ // Adds the given task. If the same task is found, does nothing.
+ void AddTask(const SyncTasks::key_type& key,
+ const SyncTask& task,
+ const base::TimeDelta& delay);
+
+ // Called when a task is ready to start.
+ void StartTask(const SyncTasks::key_type& key);
+ void StartTaskAfterGetParentResourceEntry(const SyncTasks::key_type& key,
+ const ResourceEntry* parent,
+ FileError error);
+
+ // Called when the local IDs of files in the backlog are obtained.
+ void OnGetLocalIdsOfBacklog(const std::vector<std::string>* to_fetch,
+ const std::vector<std::string>* to_update);
+
+ // Adds fetch tasks.
+ void AddFetchTasks(const std::vector<std::string>* local_ids);
+
+ // Called when a task is completed.
+ void OnTaskComplete(SyncType type,
+ const std::string& local_id,
+ FileError error);
+
+ // Called when the file for |local_id| is fetched.
+ void OnFetchFileComplete(const std::string& local_id,
+ FileError error,
+ const base::FilePath& local_path,
+ scoped_ptr<ResourceEntry> entry);
+
+ scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_;
+ file_system::OperationDelegate* operation_delegate_;
+ ResourceMetadata* metadata_;
+ FileCache* cache_;
+
+ // Used to fetch pinned files.
+ scoped_ptr<file_system::DownloadOperation> download_operation_;
+
+ // Used to update entry metadata.
+ scoped_ptr<EntryUpdatePerformer> entry_update_performer_;
+
+ // Sync tasks to be processed.
+ SyncTasks tasks_;
+
+ // The delay is used for delaying processing tasks in AddTask().
+ base::TimeDelta delay_;
+
+ // The delay is used for delaying retry of tasks on server errors.
+ base::TimeDelta long_delay_;
+
+ 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<SyncClient> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(SyncClient);
+};
+
+} // namespace internal
+} // namespace drive
+
+#endif // COMPONENTS_DRIVE_SYNC_CLIENT_H_
diff --git a/components/drive/sync_client_unittest.cc b/components/drive/sync_client_unittest.cc
new file mode 100644
index 0000000..250b85d
--- /dev/null
+++ b/components/drive/sync_client_unittest.cc
@@ -0,0 +1,523 @@
+// 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/sync_client.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/testing_pref_service.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/test/test_timeouts.h"
+#include "base/thread_task_runner_handle.h"
+#include "components/drive/change_list_loader.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/drive_test_util.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/move_operation.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/file_system/remove_operation.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 "components/drive/service/fake_drive_service.h"
+#include "content/public/test/test_browser_thread_bundle.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 internal {
+
+namespace {
+
+// The content of files initially stored in the cache.
+const char kLocalContent[] = "Hello!";
+
+// The content of files stored in the service.
+const char kRemoteContent[] = "World!";
+
+// SyncClientTestDriveService will return DRIVE_CANCELLED when a request is
+// made with the specified resource ID.
+class SyncClientTestDriveService : public ::drive::FakeDriveService {
+ public:
+ SyncClientTestDriveService() : download_file_count_(0) {}
+
+ // FakeDriveService override:
+ google_apis::CancelCallback DownloadFile(
+ const base::FilePath& local_cache_path,
+ const std::string& resource_id,
+ const google_apis::DownloadActionCallback& download_action_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const google_apis::ProgressCallback& progress_callback) override {
+ ++download_file_count_;
+ if (resource_id == resource_id_to_be_cancelled_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(download_action_callback,
+ google_apis::DRIVE_CANCELLED,
+ base::FilePath()));
+ return google_apis::CancelCallback();
+ }
+ if (resource_id == resource_id_to_be_paused_) {
+ paused_action_ = base::Bind(download_action_callback,
+ google_apis::DRIVE_OTHER_ERROR,
+ base::FilePath());
+ return google_apis::CancelCallback();
+ }
+ return FakeDriveService::DownloadFile(local_cache_path,
+ resource_id,
+ download_action_callback,
+ get_content_callback,
+ progress_callback);
+ }
+
+ int download_file_count() const { return download_file_count_; }
+
+ void set_resource_id_to_be_cancelled(const std::string& resource_id) {
+ resource_id_to_be_cancelled_ = resource_id;
+ }
+
+ void set_resource_id_to_be_paused(const std::string& resource_id) {
+ resource_id_to_be_paused_ = resource_id;
+ }
+
+ const base::Closure& paused_action() const { return paused_action_; }
+
+ private:
+ int download_file_count_;
+ std::string resource_id_to_be_cancelled_;
+ std::string resource_id_to_be_paused_;
+ base::Closure paused_action_;
+};
+
+} // namespace
+
+class SyncClientTest : public testing::Test {
+ public:
+ void SetUp() override {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+ pref_service_.reset(new TestingPrefServiceSimple);
+ test_util::RegisterDrivePrefs(pref_service_->registry());
+
+ fake_network_change_notifier_.reset(
+ new test_util::FakeNetworkChangeNotifier);
+
+ logger_.reset(new EventLogger);
+
+ drive_service_.reset(new SyncClientTestDriveService);
+
+ scheduler_.reset(new JobScheduler(
+ pref_service_.get(),
+ logger_.get(),
+ drive_service_.get(),
+ base::ThreadTaskRunnerHandle::Get().get()));
+
+ metadata_storage_.reset(new ResourceMetadataStorage(
+ temp_dir_.path(), base::ThreadTaskRunnerHandle::Get().get()));
+ ASSERT_TRUE(metadata_storage_->Initialize());
+
+ cache_.reset(new FileCache(metadata_storage_.get(),
+ temp_dir_.path(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ NULL /* free_disk_space_getter */));
+ ASSERT_TRUE(cache_->Initialize());
+
+ metadata_.reset(new internal::ResourceMetadata(
+ metadata_storage_.get(), cache_.get(),
+ base::ThreadTaskRunnerHandle::Get()));
+ ASSERT_EQ(FILE_ERROR_OK, metadata_->Initialize());
+
+ about_resource_loader_.reset(new AboutResourceLoader(scheduler_.get()));
+ loader_controller_.reset(new LoaderController);
+ change_list_loader_.reset(new ChangeListLoader(
+ logger_.get(),
+ base::ThreadTaskRunnerHandle::Get().get(),
+ metadata_.get(),
+ scheduler_.get(),
+ about_resource_loader_.get(),
+ loader_controller_.get()));
+ ASSERT_NO_FATAL_FAILURE(SetUpTestData());
+
+ sync_client_.reset(new SyncClient(base::ThreadTaskRunnerHandle::Get().get(),
+ &delegate_,
+ scheduler_.get(),
+ metadata_.get(),
+ cache_.get(),
+ loader_controller_.get(),
+ temp_dir_.path()));
+
+ // Disable delaying so that DoSyncLoop() starts immediately.
+ sync_client_->set_delay_for_testing(base::TimeDelta::FromSeconds(0));
+ }
+
+ // Adds a file to the service root and |resource_ids_|.
+ void AddFileEntry(const std::string& title) {
+ google_apis::DriveApiErrorCode error = google_apis::DRIVE_FILE_ERROR;
+ scoped_ptr<google_apis::FileResource> entry;
+ drive_service_->AddNewFile(
+ "text/plain",
+ kRemoteContent,
+ drive_service_->GetRootResourceId(),
+ title,
+ false, // shared_with_me
+ google_apis::test_util::CreateCopyResultCallback(&error, &entry));
+ base::RunLoop().RunUntilIdle();
+ ASSERT_EQ(google_apis::HTTP_CREATED, error);
+ ASSERT_TRUE(entry);
+ resource_ids_[title] = entry->file_id();
+ }
+
+ // Sets up data for tests.
+ void SetUpTestData() {
+ // Prepare a temp file.
+ base::FilePath temp_file;
+ EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &temp_file));
+ ASSERT_TRUE(google_apis::test_util::WriteStringToFile(temp_file,
+ kLocalContent));
+
+ // Add file entries to the service.
+ ASSERT_NO_FATAL_FAILURE(AddFileEntry("foo"));
+ ASSERT_NO_FATAL_FAILURE(AddFileEntry("bar"));
+ ASSERT_NO_FATAL_FAILURE(AddFileEntry("baz"));
+ ASSERT_NO_FATAL_FAILURE(AddFileEntry("fetched"));
+ ASSERT_NO_FATAL_FAILURE(AddFileEntry("dirty"));
+ ASSERT_NO_FATAL_FAILURE(AddFileEntry("removed"));
+ ASSERT_NO_FATAL_FAILURE(AddFileEntry("moved"));
+
+ // Load data from the service to the metadata.
+ FileError error = FILE_ERROR_FAILED;
+ change_list_loader_->LoadIfNeeded(
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Prepare 3 pinned-but-not-present files.
+ EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(GetLocalId("foo")));
+ EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(GetLocalId("bar")));
+ EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(GetLocalId("baz")));
+
+ // Prepare a pinned-and-fetched file.
+ const std::string md5_fetched = "md5";
+ EXPECT_EQ(FILE_ERROR_OK,
+ cache_->Store(GetLocalId("fetched"), md5_fetched,
+ temp_file, FileCache::FILE_OPERATION_COPY));
+ EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(GetLocalId("fetched")));
+
+ // Prepare a pinned-and-fetched-and-dirty file.
+ EXPECT_EQ(FILE_ERROR_OK,
+ cache_->Store(GetLocalId("dirty"), std::string(),
+ temp_file, FileCache::FILE_OPERATION_COPY));
+ EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(GetLocalId("dirty")));
+
+ // Prepare a removed file.
+ file_system::RemoveOperation remove_operation(
+ base::ThreadTaskRunnerHandle::Get().get(), &delegate_, metadata_.get(),
+ cache_.get());
+ remove_operation.Remove(
+ util::GetDriveMyDriveRootPath().AppendASCII("removed"),
+ false, // is_recursive
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+
+ // Prepare a moved file.
+ file_system::MoveOperation move_operation(
+ base::ThreadTaskRunnerHandle::Get().get(), &delegate_, metadata_.get());
+ move_operation.Move(
+ util::GetDriveMyDriveRootPath().AppendASCII("moved"),
+ util::GetDriveMyDriveRootPath().AppendASCII("moved_new_title"),
+ google_apis::test_util::CreateCopyResultCallback(&error));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(FILE_ERROR_OK, error);
+ }
+
+ protected:
+ std::string GetLocalId(const std::string& title) {
+ EXPECT_EQ(1U, resource_ids_.count(title));
+ std::string local_id;
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetIdByResourceId(resource_ids_[title], &local_id));
+ return local_id;
+ }
+
+ content::TestBrowserThreadBundle thread_bundle_;
+ base::ScopedTempDir temp_dir_;
+ scoped_ptr<TestingPrefServiceSimple> pref_service_;
+ scoped_ptr<test_util::FakeNetworkChangeNotifier>
+ fake_network_change_notifier_;
+ scoped_ptr<EventLogger> logger_;
+ scoped_ptr<SyncClientTestDriveService> drive_service_;
+ file_system::OperationDelegate delegate_;
+ scoped_ptr<JobScheduler> scheduler_;
+ scoped_ptr<ResourceMetadataStorage,
+ test_util::DestroyHelperForTests> metadata_storage_;
+ scoped_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
+ scoped_ptr<ResourceMetadata, test_util::DestroyHelperForTests> metadata_;
+ scoped_ptr<AboutResourceLoader> about_resource_loader_;
+ scoped_ptr<LoaderController> loader_controller_;
+ scoped_ptr<ChangeListLoader> change_list_loader_;
+ scoped_ptr<SyncClient> sync_client_;
+
+ std::map<std::string, std::string> resource_ids_; // Name-to-id map.
+};
+
+TEST_F(SyncClientTest, StartProcessingBacklog) {
+ sync_client_->StartProcessingBacklog();
+ base::RunLoop().RunUntilIdle();
+
+ ResourceEntry entry;
+ // Pinned files get downloaded.
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("foo"), &entry));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("bar"), &entry));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("baz"), &entry));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+
+ // Dirty file gets uploaded.
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("dirty"), &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
+
+ // Removed entry is not found.
+ google_apis::DriveApiErrorCode status = google_apis::DRIVE_OTHER_ERROR;
+ scoped_ptr<google_apis::FileResource> server_entry;
+ drive_service_->GetFileResource(
+ resource_ids_["removed"],
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_TRUE(server_entry->labels().is_trashed());
+
+ // Moved entry was moved.
+ status = google_apis::DRIVE_OTHER_ERROR;
+ drive_service_->GetFileResource(
+ resource_ids_["moved"],
+ google_apis::test_util::CreateCopyResultCallback(&status, &server_entry));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(google_apis::HTTP_SUCCESS, status);
+ ASSERT_TRUE(server_entry);
+ EXPECT_EQ("moved_new_title", server_entry->title());
+}
+
+TEST_F(SyncClientTest, AddFetchTask) {
+ sync_client_->AddFetchTask(GetLocalId("foo"));
+ base::RunLoop().RunUntilIdle();
+
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("foo"), &entry));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+}
+
+TEST_F(SyncClientTest, AddFetchTaskAndCancelled) {
+ // Trigger fetching of a file which results in cancellation.
+ drive_service_->set_resource_id_to_be_cancelled(resource_ids_["foo"]);
+ sync_client_->AddFetchTask(GetLocalId("foo"));
+ base::RunLoop().RunUntilIdle();
+
+ // The file should be unpinned if the user wants the download to be cancelled.
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("foo"), &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_pinned());
+}
+
+TEST_F(SyncClientTest, RemoveFetchTask) {
+ sync_client_->AddFetchTask(GetLocalId("foo"));
+ sync_client_->AddFetchTask(GetLocalId("bar"));
+ sync_client_->AddFetchTask(GetLocalId("baz"));
+
+ sync_client_->RemoveFetchTask(GetLocalId("foo"));
+ sync_client_->RemoveFetchTask(GetLocalId("baz"));
+ base::RunLoop().RunUntilIdle();
+
+ // Only "bar" should be fetched.
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("foo"), &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_present());
+
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("bar"), &entry));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("baz"), &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_present());
+
+}
+
+TEST_F(SyncClientTest, ExistingPinnedFiles) {
+ // Start checking the existing pinned files. This will collect the resource
+ // IDs of pinned files, with stale local cache files.
+ sync_client_->StartCheckingExistingPinnedFiles();
+ base::RunLoop().RunUntilIdle();
+
+ // "fetched" and "dirty" are the existing pinned files.
+ // The non-dirty one should be synced, but the dirty one should not.
+ base::FilePath cache_file;
+ std::string content;
+ EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(GetLocalId("fetched"), &cache_file));
+ EXPECT_TRUE(base::ReadFileToString(cache_file, &content));
+ EXPECT_EQ(kRemoteContent, content);
+ content.clear();
+
+ EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(GetLocalId("dirty"), &cache_file));
+ EXPECT_TRUE(base::ReadFileToString(cache_file, &content));
+ EXPECT_EQ(kLocalContent, content);
+}
+
+TEST_F(SyncClientTest, RetryOnDisconnection) {
+ // Let the service go down.
+ drive_service_->set_offline(true);
+ // Change the network connection state after some delay, to test that
+ // FILE_ERROR_NO_CONNECTION is handled by SyncClient correctly.
+ // Without this delay, JobScheduler will keep the jobs unrun and SyncClient
+ // will receive no error.
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&test_util::FakeNetworkChangeNotifier::SetConnectionType,
+ base::Unretained(fake_network_change_notifier_.get()),
+ net::NetworkChangeNotifier::CONNECTION_NONE),
+ TestTimeouts::tiny_timeout());
+
+ // Try fetch and upload.
+ sync_client_->AddFetchTask(GetLocalId("foo"));
+ sync_client_->AddUpdateTask(ClientContext(USER_INITIATED),
+ GetLocalId("dirty"));
+ base::RunLoop().RunUntilIdle();
+
+ // Not yet fetched nor uploaded.
+ ResourceEntry entry;
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("foo"), &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_present());
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("dirty"), &entry));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
+
+ // Switch to online.
+ fake_network_change_notifier_->SetConnectionType(
+ net::NetworkChangeNotifier::CONNECTION_WIFI);
+ drive_service_->set_offline(false);
+ base::RunLoop().RunUntilIdle();
+
+ // Fetched and uploaded.
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("foo"), &entry));
+ EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryById(GetLocalId("dirty"), &entry));
+ EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
+}
+
+TEST_F(SyncClientTest, ScheduleRerun) {
+ // Add a fetch task for "foo", this should result in being paused.
+ drive_service_->set_resource_id_to_be_paused(resource_ids_["foo"]);
+ sync_client_->AddFetchTask(GetLocalId("foo"));
+ base::RunLoop().RunUntilIdle();
+
+ // While the first task is paused, add a task again.
+ // This results in scheduling rerun of the task.
+ sync_client_->AddFetchTask(GetLocalId("foo"));
+ base::RunLoop().RunUntilIdle();
+
+ // Resume the paused task.
+ drive_service_->set_resource_id_to_be_paused(std::string());
+ ASSERT_FALSE(drive_service_->paused_action().is_null());
+ drive_service_->paused_action().Run();
+ base::RunLoop().RunUntilIdle();
+
+ // Task should be run twice.
+ EXPECT_EQ(2, drive_service_->download_file_count());
+}
+
+TEST_F(SyncClientTest, Dependencies) {
+ // Create directories locally.
+ const base::FilePath kPath1(FILE_PATH_LITERAL("drive/root/dir1"));
+ const base::FilePath kPath2 = kPath1.AppendASCII("dir2");
+
+ ResourceEntry parent;
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryByPath(kPath1.DirName(), &parent));
+
+ ResourceEntry entry1;
+ entry1.set_parent_local_id(parent.local_id());
+ entry1.set_title(kPath1.BaseName().AsUTF8Unsafe());
+ entry1.mutable_file_info()->set_is_directory(true);
+ entry1.set_metadata_edit_state(ResourceEntry::DIRTY);
+ std::string local_id1;
+ EXPECT_EQ(FILE_ERROR_OK, metadata_->AddEntry(entry1, &local_id1));
+
+ ResourceEntry entry2;
+ entry2.set_parent_local_id(local_id1);
+ entry2.set_title(kPath2.BaseName().AsUTF8Unsafe());
+ entry2.mutable_file_info()->set_is_directory(true);
+ entry2.set_metadata_edit_state(ResourceEntry::DIRTY);
+ std::string local_id2;
+ EXPECT_EQ(FILE_ERROR_OK, metadata_->AddEntry(entry2, &local_id2));
+
+ // Start syncing the child first.
+ sync_client_->AddUpdateTask(ClientContext(USER_INITIATED), local_id2);
+ // Start syncing the parent later.
+ sync_client_->AddUpdateTask(ClientContext(USER_INITIATED), local_id1);
+ base::RunLoop().RunUntilIdle();
+
+ // Both entries are synced.
+ EXPECT_EQ(FILE_ERROR_OK, metadata_->GetResourceEntryById(local_id1, &entry1));
+ EXPECT_EQ(ResourceEntry::CLEAN, entry1.metadata_edit_state());
+ EXPECT_EQ(FILE_ERROR_OK, metadata_->GetResourceEntryById(local_id2, &entry2));
+ EXPECT_EQ(ResourceEntry::CLEAN, entry2.metadata_edit_state());
+}
+
+TEST_F(SyncClientTest, WaitForUpdateTaskToComplete) {
+ // Create a directory locally.
+ const base::FilePath kPath(FILE_PATH_LITERAL("drive/root/dir1"));
+
+ ResourceEntry parent;
+ EXPECT_EQ(FILE_ERROR_OK,
+ metadata_->GetResourceEntryByPath(kPath.DirName(), &parent));
+
+ ResourceEntry entry;
+ entry.set_parent_local_id(parent.local_id());
+ entry.set_title(kPath.BaseName().AsUTF8Unsafe());
+ entry.mutable_file_info()->set_is_directory(true);
+ entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+ std::string local_id;
+ EXPECT_EQ(FILE_ERROR_OK, metadata_->AddEntry(entry, &local_id));
+
+ // Sync task is not yet avialable.
+ FileError error = FILE_ERROR_FAILED;
+ EXPECT_FALSE(sync_client_->WaitForUpdateTaskToComplete(
+ local_id, google_apis::test_util::CreateCopyResultCallback(&error)));
+
+ // Start syncing the directory and wait for it to complete.
+ sync_client_->AddUpdateTask(ClientContext(USER_INITIATED), local_id);
+
+ EXPECT_TRUE(sync_client_->WaitForUpdateTaskToComplete(
+ local_id, google_apis::test_util::CreateCopyResultCallback(&error)));
+
+ base::RunLoop().RunUntilIdle();
+
+ // The callback is called.
+ EXPECT_EQ(FILE_ERROR_OK, error);
+}
+
+} // namespace internal
+} // namespace drive