blob: a09e6f2de91baad63aa5fcdb3c8d239c8c19d966 [file] [log] [blame]
// 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 "chrome/browser/sync_file_system/drive_backend/remote_to_local_syncer.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/message_loop/message_loop_proxy.h"
#include "base/task_runner_util.h"
#include "chrome/browser/drive/drive_api_util.h"
#include "chrome/browser/drive/drive_service_interface.h"
#include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h"
#include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
#include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
#include "chrome/browser/sync_file_system/drive_backend_v1/drive_file_sync_util.h"
#include "chrome/browser/sync_file_system/syncable_file_system_util.h"
#include "extensions/common/extension.h"
#include "google_apis/drive/drive_api_parser.h"
#include "google_apis/drive/gdata_wapi_parser.h"
#include "webkit/common/fileapi/file_system_util.h"
namespace sync_file_system {
namespace drive_backend {
namespace {
bool BuildFileSystemURL(
MetadataDatabase* metadata_database,
const FileTracker& tracker,
fileapi::FileSystemURL* url) {
base::FilePath path;
if (!metadata_database->BuildPathForTracker(
tracker.tracker_id(), &path))
return false;
GURL origin =
extensions::Extension::GetBaseURLFromExtensionId(tracker.app_id());
*url = sync_file_system::CreateSyncableFileSystemURL(origin, path);
return true;
}
bool HasFolderAsParent(const FileDetails& details,
const std::string& folder_id) {
for (int i = 0; i < details.parent_folder_ids_size(); ++i) {
if (details.parent_folder_ids(i) == folder_id)
return true;
}
return false;
}
bool HasDisabledAppRoot(MetadataDatabase* database,
const FileTracker& tracker) {
DCHECK(tracker.active());
FileTracker app_root_tracker;
if (database->FindAppRootTracker(tracker.app_id(), &app_root_tracker)) {
DCHECK(app_root_tracker.tracker_kind() == TRACKER_KIND_APP_ROOT ||
app_root_tracker.tracker_kind() == TRACKER_KIND_DISABLED_APP_ROOT);
return app_root_tracker.tracker_kind() == TRACKER_KIND_DISABLED_APP_ROOT;
}
return false;
}
scoped_ptr<FileMetadata> GetFileMetadata(MetadataDatabase* database,
const std::string& file_id) {
scoped_ptr<FileMetadata> metadata(new FileMetadata);
if (!database->FindFileByFileID(file_id, metadata.get()))
metadata.reset();
return metadata.Pass();
}
} // namespace
RemoteToLocalSyncer::RemoteToLocalSyncer(SyncEngineContext* sync_context)
: sync_context_(sync_context),
sync_action_(SYNC_ACTION_NONE),
prepared_(false),
sync_root_deletion_(false),
weak_ptr_factory_(this) {
}
RemoteToLocalSyncer::~RemoteToLocalSyncer() {
}
void RemoteToLocalSyncer::Run(const SyncStatusCallback& callback) {
if (!drive_service() || !metadata_database() || !remote_change_processor()) {
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
SyncStatusCallback wrapped_callback = base::Bind(
&RemoteToLocalSyncer::SyncCompleted, weak_ptr_factory_.GetWeakPtr(),
base::Bind(&RemoteToLocalSyncer::FinalizeSync,
weak_ptr_factory_.GetWeakPtr(),
callback));
dirty_tracker_ = make_scoped_ptr(new FileTracker);
if (metadata_database()->GetNormalPriorityDirtyTracker(
dirty_tracker_.get())) {
ResolveRemoteChange(wrapped_callback);
return;
}
base::MessageLoopProxy::current()->PostTask(
FROM_HERE,
base::Bind(callback, SYNC_STATUS_NO_CHANGE_TO_SYNC));
}
void RemoteToLocalSyncer::ResolveRemoteChange(
const SyncStatusCallback& callback) {
DCHECK(dirty_tracker_);
remote_metadata_ = GetFileMetadata(
metadata_database(), dirty_tracker_->file_id());
if (!remote_metadata_ || !remote_metadata_->has_details()) {
if (remote_metadata_ && !remote_metadata_->has_details()) {
LOG(ERROR) << "Missing details of a remote file: "
<< remote_metadata_->file_id();
NOTREACHED();
}
HandleMissingRemoteMetadata(callback);
return;
}
DCHECK(remote_metadata_);
DCHECK(remote_metadata_->has_details());
const FileDetails& remote_details = remote_metadata_->details();
if (!dirty_tracker_->active() ||
HasDisabledAppRoot(metadata_database(), *dirty_tracker_)) {
// Handle inactive tracker in SyncCompleted.
callback.Run(SYNC_STATUS_OK);
return;
}
DCHECK(dirty_tracker_->active());
DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));
if (!dirty_tracker_->has_synced_details()) {
LOG(ERROR) << "Missing synced_details of an active tracker: "
<< dirty_tracker_->tracker_id();
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
DCHECK(dirty_tracker_->has_synced_details());
const FileDetails& synced_details = dirty_tracker_->synced_details();
if (dirty_tracker_->tracker_id() ==
metadata_database()->GetSyncRootTrackerID()) {
if (remote_details.missing() ||
synced_details.title() != remote_details.title() ||
remote_details.parent_folder_ids_size()) {
HandleSyncRootDeletion(callback);
return;
}
callback.Run(SYNC_STATUS_OK);
return;
}
DCHECK_NE(dirty_tracker_->tracker_id(),
metadata_database()->GetSyncRootTrackerID());
if (remote_details.missing()) {
if (!synced_details.missing()) {
HandleDeletion(callback);
return;
}
DCHECK(synced_details.missing());
LOG(ERROR) << "Found a stray missing tracker: "
<< dirty_tracker_->file_id();
NOTREACHED();
callback.Run(SYNC_STATUS_OK);
return;
}
// Most of remote_details field is valid from here.
DCHECK(!remote_details.missing());
if (synced_details.file_kind() != remote_details.file_kind()) {
LOG(ERROR) << "Found type mismatch between remote and local file: "
<< dirty_tracker_->file_id()
<< " type: (local) " << synced_details.file_kind()
<< " vs (remote) " << remote_details.file_kind();
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
DCHECK_EQ(synced_details.file_kind(), remote_details.file_kind());
if (synced_details.file_kind() == FILE_KIND_UNSUPPORTED) {
LOG(ERROR) << "Found an unsupported active file: "
<< remote_metadata_->file_id();
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
DCHECK(remote_details.file_kind() == FILE_KIND_FILE ||
remote_details.file_kind() == FILE_KIND_FOLDER);
if (synced_details.title() != remote_details.title()) {
// Handle rename as deletion + addition.
Prepare(base::Bind(&RemoteToLocalSyncer::DidPrepareForDeletion,
weak_ptr_factory_.GetWeakPtr(), callback));
return;
}
DCHECK_EQ(synced_details.title(), remote_details.title());
FileTracker parent_tracker;
if (!metadata_database()->FindTrackerByTrackerID(
dirty_tracker_->parent_tracker_id(), &parent_tracker)) {
LOG(ERROR) << "Missing parent tracker for a non sync-root tracker: "
<< dirty_tracker_->file_id();
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
if (!HasFolderAsParent(remote_details, parent_tracker.file_id())) {
// Handle reorganize as deletion + addition.
Prepare(base::Bind(&RemoteToLocalSyncer::DidPrepareForDeletion,
weak_ptr_factory_.GetWeakPtr(), callback));
return;
}
if (synced_details.file_kind() == FILE_KIND_FILE) {
if (synced_details.md5() != remote_details.md5()) {
HandleContentUpdate(callback);
return;
}
} else {
DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind());
HandleFolderUpdate(callback);
return;
}
callback.Run(SYNC_STATUS_OK);
}
void RemoteToLocalSyncer::HandleMissingRemoteMetadata(
const SyncStatusCallback& callback) {
DCHECK(dirty_tracker_);
drive_service()->GetResourceEntry(
dirty_tracker_->file_id(),
base::Bind(&RemoteToLocalSyncer::DidGetRemoteMetadata,
weak_ptr_factory_.GetWeakPtr(),
callback,
metadata_database()->GetLargestKnownChangeID()));
}
void RemoteToLocalSyncer::DidGetRemoteMetadata(
const SyncStatusCallback& callback,
int64 change_id,
google_apis::GDataErrorCode error,
scoped_ptr<google_apis::ResourceEntry> entry) {
metadata_database()->UpdateByFileResource(
*drive::util::ConvertResourceEntryToFileResource(*entry),
base::Bind(&RemoteToLocalSyncer::DidUpdateDatabaseForRemoteMetadata,
weak_ptr_factory_.GetWeakPtr(), callback));
}
void RemoteToLocalSyncer::DidUpdateDatabaseForRemoteMetadata(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
callback.Run(SYNC_STATUS_RETRY); // Do not update |dirty_tracker_|.
}
void RemoteToLocalSyncer::DidPrepareForAddOrUpdateFile(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
DCHECK(url_.is_valid());
DCHECK(local_metadata_);
DCHECK(local_changes_);
// Check if the local file exists.
if (local_metadata_->file_type == SYNC_FILE_TYPE_UNKNOWN ||
(!local_changes_->empty() && local_changes_->back().IsDelete())) {
sync_action_ = SYNC_ACTION_ADDED;
// Missing local file case.
// Download the file and add it to local as a new file.
DownloadFile(callback);
return;
}
DCHECK(local_changes_->empty() || local_changes_->back().IsAddOrUpdate());
if (local_changes_->empty()) {
if (local_metadata_->file_type == SYNC_FILE_TYPE_FILE) {
sync_action_ = SYNC_ACTION_UPDATED;
// Download the file and overwrite the existing local file.
DownloadFile(callback);
return;
}
DCHECK_EQ(SYNC_FILE_TYPE_DIRECTORY, local_metadata_->file_type);
// Got a remote regular file modification for existing local folder.
// Our policy prioritize folders in this case.
// Lower the priority of the tracker to prevent repeated remote sync to the
// same tracker, and let local-to-remote sync phase process this change.
metadata_database()->LowerTrackerPriority(dirty_tracker_->tracker_id());
remote_change_processor()->RecordFakeLocalChange(
url_,
FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
local_metadata_->file_type),
callback);
return;
}
DCHECK(local_changes_->back().IsAddOrUpdate());
// Conflict case.
// Do nothing for the change now, and handle this in LocalToRemoteSync phase.
// Lower the priority of the tracker to prevent repeated remote sync to the
// same tracker.
metadata_database()->LowerTrackerPriority(dirty_tracker_->tracker_id());
callback.Run(SYNC_STATUS_RETRY);
}
void RemoteToLocalSyncer::HandleFolderUpdate(
const SyncStatusCallback& callback) {
DCHECK(dirty_tracker_);
DCHECK(dirty_tracker_->active());
DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));
DCHECK(remote_metadata_);
DCHECK(remote_metadata_->has_details());
DCHECK(!remote_metadata_->details().missing());
DCHECK_EQ(FILE_KIND_FOLDER, remote_metadata_->details().file_kind());
Prepare(base::Bind(&RemoteToLocalSyncer::DidPrepareForFolderUpdate,
weak_ptr_factory_.GetWeakPtr(), callback));
}
void RemoteToLocalSyncer::DidPrepareForFolderUpdate(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
DCHECK(url_.is_valid());
DCHECK(local_metadata_);
DCHECK(local_changes_);
// Check if the local file exists.
if (local_metadata_->file_type == SYNC_FILE_TYPE_UNKNOWN ||
(!local_changes_->empty() && local_changes_->back().IsDelete())) {
sync_action_ = SYNC_ACTION_ADDED;
// No local file exists at the path.
CreateFolder(callback);
return;
}
if (local_metadata_->file_type == SYNC_FILE_TYPE_DIRECTORY) {
// There already exists a folder, nothing left to do.
if (dirty_tracker_->needs_folder_listing() &&
!dirty_tracker_->synced_details().missing()) {
ListFolderContent(callback);
} else {
callback.Run(SYNC_STATUS_OK);
}
return;
}
DCHECK_EQ(SYNC_FILE_TYPE_FILE, local_metadata_->file_type);
sync_action_ = SYNC_ACTION_ADDED;
// Got a remote folder for existing local file.
// Our policy prioritize folders in this case.
CreateFolder(callback);
}
void RemoteToLocalSyncer::HandleSyncRootDeletion(
const SyncStatusCallback& callback) {
sync_root_deletion_ = true;
callback.Run(SYNC_STATUS_OK);
}
void RemoteToLocalSyncer::HandleDeletion(
const SyncStatusCallback& callback) {
DCHECK(dirty_tracker_);
DCHECK(dirty_tracker_->active());
DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));
DCHECK(dirty_tracker_->has_synced_details());
DCHECK(!dirty_tracker_->synced_details().missing());
DCHECK(remote_metadata_);
DCHECK(remote_metadata_->has_details());
DCHECK(remote_metadata_->details().missing());
Prepare(base::Bind(&RemoteToLocalSyncer::DidPrepareForDeletion,
weak_ptr_factory_.GetWeakPtr(), callback));
}
void RemoteToLocalSyncer::DidPrepareForDeletion(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
DCHECK(url_.is_valid());
DCHECK(local_metadata_);
DCHECK(local_changes_);
// Check if the local file exists.
if (local_metadata_->file_type == SYNC_FILE_TYPE_UNKNOWN ||
(!local_changes_->empty() && local_changes_->back().IsDelete())) {
// No local file exists at the path.
callback.Run(SYNC_STATUS_OK);
return;
}
DCHECK(local_changes_->empty() || local_changes_->back().IsAddOrUpdate());
if (local_changes_->empty()) {
sync_action_ = SYNC_ACTION_DELETED;
DeleteLocalFile(callback);
return;
}
DCHECK(local_changes_->back().IsAddOrUpdate());
// File is remotely deleted and locally updated.
// Ignore the remote deletion and handle it as if applied successfully.
callback.Run(SYNC_STATUS_OK);
}
void RemoteToLocalSyncer::HandleContentUpdate(
const SyncStatusCallback& callback) {
DCHECK(dirty_tracker_);
DCHECK(dirty_tracker_->active());
DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));
DCHECK(dirty_tracker_->has_synced_details());
DCHECK_EQ(FILE_KIND_FILE, dirty_tracker_->synced_details().file_kind());
DCHECK(remote_metadata_);
DCHECK(remote_metadata_->has_details());
DCHECK(!remote_metadata_->details().missing());
DCHECK_NE(dirty_tracker_->synced_details().md5(),
remote_metadata_->details().md5());
Prepare(base::Bind(&RemoteToLocalSyncer::DidPrepareForAddOrUpdateFile,
weak_ptr_factory_.GetWeakPtr(), callback));
}
void RemoteToLocalSyncer::ListFolderContent(
const SyncStatusCallback& callback) {
DCHECK(dirty_tracker_);
DCHECK(dirty_tracker_->active());
DCHECK(!HasDisabledAppRoot(metadata_database(), *dirty_tracker_));
DCHECK(dirty_tracker_->has_synced_details());
DCHECK(!dirty_tracker_->synced_details().missing());
DCHECK_EQ(FILE_KIND_FOLDER, dirty_tracker_->synced_details().file_kind());
DCHECK(dirty_tracker_->needs_folder_listing());
DCHECK(remote_metadata_);
DCHECK(remote_metadata_->has_details());
DCHECK(!remote_metadata_->details().missing());
// TODO(tzik): Replace this call with ChildList version.
drive_service()->GetResourceListInDirectory(
dirty_tracker_->file_id(),
base::Bind(&RemoteToLocalSyncer::DidListFolderContent,
weak_ptr_factory_.GetWeakPtr(),
callback,
base::Passed(make_scoped_ptr(new FileIDList))));
}
void RemoteToLocalSyncer::DidListFolderContent(
const SyncStatusCallback& callback,
scoped_ptr<FileIDList> children,
google_apis::GDataErrorCode error,
scoped_ptr<google_apis::ResourceList> resource_list) {
if (error != google_apis::HTTP_SUCCESS) {
callback.Run(GDataErrorCodeToSyncStatusCode(error));
return;
}
children->reserve(children->size() + resource_list->entries().size());
for (ScopedVector<google_apis::ResourceEntry>::const_iterator itr =
resource_list->entries().begin();
itr != resource_list->entries().end();
++itr) {
children->push_back((*itr)->resource_id());
}
GURL next_feed;
if (resource_list->GetNextFeedURL(&next_feed)) {
drive_service()->GetRemainingFileList(
next_feed,
base::Bind(&RemoteToLocalSyncer::DidListFolderContent,
weak_ptr_factory_.GetWeakPtr(),
callback, base::Passed(&children)));
return;
}
metadata_database()->PopulateFolderByChildList(
dirty_tracker_->file_id(), *children, callback);
}
void RemoteToLocalSyncer::SyncCompleted(const SyncStatusCallback& callback,
SyncStatusCode status) {
if (sync_root_deletion_) {
callback.Run(SYNC_STATUS_OK);
return;
}
if (status == SYNC_STATUS_RETRY) {
callback.Run(SYNC_STATUS_OK);
return;
}
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
DCHECK(dirty_tracker_);
DCHECK(remote_metadata_);
DCHECK(remote_metadata_->has_details());
FileDetails updated_details = remote_metadata_->details();
if (!dirty_tracker_->active() ||
HasDisabledAppRoot(metadata_database(), *dirty_tracker_)) {
// Operations for an inactive tracker don't update file content.
if (dirty_tracker_->has_synced_details()) {
updated_details.set_md5(dirty_tracker_->synced_details().md5());
} else {
updated_details.clear_md5();
updated_details.set_missing(true);
}
}
metadata_database()->UpdateTracker(dirty_tracker_->tracker_id(),
updated_details,
callback);
}
void RemoteToLocalSyncer::FinalizeSync(const SyncStatusCallback& callback,
SyncStatusCode status) {
if (prepared_) {
remote_change_processor()->FinalizeRemoteSync(
url_, false /* clear_local_change */, base::Bind(callback, status));
return;
}
callback.Run(status);
}
void RemoteToLocalSyncer::Prepare(const SyncStatusCallback& callback) {
bool should_success = BuildFileSystemURL(
metadata_database(), *dirty_tracker_, &url_);
DCHECK(should_success);
DCHECK(url_.is_valid());
remote_change_processor()->PrepareForProcessRemoteChange(
url_,
base::Bind(&RemoteToLocalSyncer::DidPrepare,
weak_ptr_factory_.GetWeakPtr(),
callback));
}
void RemoteToLocalSyncer::DidPrepare(const SyncStatusCallback& callback,
SyncStatusCode status,
const SyncFileMetadata& local_metadata,
const FileChangeList& local_changes) {
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
prepared_ = true;
local_metadata_.reset(new SyncFileMetadata(local_metadata));
local_changes_.reset(new FileChangeList(local_changes));
callback.Run(status);
}
void RemoteToLocalSyncer::DeleteLocalFile(const SyncStatusCallback& callback) {
remote_change_processor()->ApplyRemoteChange(
FileChange(FileChange::FILE_CHANGE_DELETE, SYNC_FILE_TYPE_UNKNOWN),
base::FilePath(),
url_,
callback);
}
void RemoteToLocalSyncer::DownloadFile(const SyncStatusCallback& callback) {
base::PostTaskAndReplyWithResult(
sync_context_->GetBlockingTaskRunner(), FROM_HERE,
base::Bind(&sync_file_system::drive_backend::CreateTemporaryFile,
make_scoped_refptr(sync_context_->GetBlockingTaskRunner())),
base::Bind(&RemoteToLocalSyncer::DidCreateTemporaryFileForDownload,
weak_ptr_factory_.GetWeakPtr(), callback));
}
void RemoteToLocalSyncer::DidCreateTemporaryFileForDownload(
const SyncStatusCallback& callback,
webkit_blob::ScopedFile file) {
base::FilePath path = file.path();
drive_service()->DownloadFile(
path, remote_metadata_->file_id(),
base::Bind(&RemoteToLocalSyncer::DidDownloadFile,
weak_ptr_factory_.GetWeakPtr(),
callback, base::Passed(&file)),
google_apis::GetContentCallback(),
google_apis::ProgressCallback());
}
void RemoteToLocalSyncer::DidDownloadFile(const SyncStatusCallback& callback,
webkit_blob::ScopedFile file,
google_apis::GDataErrorCode error,
const base::FilePath&) {
if (error != google_apis::HTTP_SUCCESS) {
callback.Run(GDataErrorCodeToSyncStatusCode(error));
return;
}
base::FilePath path = file.path();
base::PostTaskAndReplyWithResult(
sync_context_->GetBlockingTaskRunner(), FROM_HERE,
base::Bind(&drive::util::GetMd5Digest, path),
base::Bind(&RemoteToLocalSyncer::DidCalculateMD5ForDownload,
weak_ptr_factory_.GetWeakPtr(),
callback, base::Passed(&file)));
}
void RemoteToLocalSyncer::DidCalculateMD5ForDownload(
const SyncStatusCallback& callback,
webkit_blob::ScopedFile file,
const std::string& md5) {
if (md5.empty()) {
callback.Run(SYNC_FILE_ERROR_NOT_FOUND);
return;
}
if (md5 != remote_metadata_->details().md5()) {
// File has been modified since last metadata retrieval.
// Lower the priority of the tracker to prevent repeated remote sync to the
// same tracker.
metadata_database()->LowerTrackerPriority(dirty_tracker_->tracker_id());
callback.Run(SYNC_STATUS_RETRY);
return;
}
base::FilePath path = file.path();
remote_change_processor()->ApplyRemoteChange(
FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, SYNC_FILE_TYPE_FILE),
path, url_,
base::Bind(&RemoteToLocalSyncer::DidApplyDownload,
weak_ptr_factory_.GetWeakPtr(),
callback, base::Passed(&file)));
}
void RemoteToLocalSyncer::DidApplyDownload(const SyncStatusCallback& callback,
webkit_blob::ScopedFile,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK)
metadata_database()->LowerTrackerPriority(dirty_tracker_->tracker_id());
callback.Run(status);
}
void RemoteToLocalSyncer::CreateFolder(const SyncStatusCallback& callback) {
remote_change_processor()->ApplyRemoteChange(
FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
SYNC_FILE_TYPE_DIRECTORY),
base::FilePath(), url_,
callback);
}
drive::DriveServiceInterface* RemoteToLocalSyncer::drive_service() {
return sync_context_->GetDriveService();
}
MetadataDatabase* RemoteToLocalSyncer::metadata_database() {
return sync_context_->GetMetadataDatabase();
}
RemoteChangeProcessor* RemoteToLocalSyncer::remote_change_processor() {
DCHECK(sync_context_->GetRemoteChangeProcessor());
return sync_context_->GetRemoteChangeProcessor();
}
} // namespace drive_backend
} // namespace sync_file_system