blob: b6ada3487a946236e8e4ad44d4f7260e299d7dfe [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_v1/remote_sync_delegate.h"
#include "base/file_util.h"
#include "chrome/browser/sync_file_system/drive_backend_v1/remote_sync_operation_resolver.h"
#include "chrome/browser/sync_file_system/logger.h"
#include "chrome/browser/sync_file_system/remote_change_processor.h"
#include "chrome/browser/sync_file_system/syncable_file_system_util.h"
#include "content/public/browser/browser_thread.h"
using fileapi::FileSystemURL;
namespace {
void EmptyStatusCallback(sync_file_system::SyncStatusCode status) {}
} // namespace
namespace sync_file_system {
namespace drive_backend {
RemoteSyncDelegate::RemoteSyncDelegate(
DriveFileSyncService* sync_service,
const RemoteChange& remote_change)
: sync_service_(sync_service),
remote_change_(remote_change),
sync_action_(SYNC_ACTION_NONE),
metadata_updated_(false),
clear_local_changes_(true) {
}
RemoteSyncDelegate::~RemoteSyncDelegate() {}
void RemoteSyncDelegate::Run(const SyncStatusCallback& callback) {
util::Log(logging::LOG_VERBOSE, FROM_HERE,
"ProcessRemoteChange for %s change:%s",
url().DebugString().c_str(),
remote_file_change().DebugString().c_str());
remote_change_processor()->PrepareForProcessRemoteChange(
url(),
base::Bind(&RemoteSyncDelegate::DidPrepareForProcessRemoteChange,
AsWeakPtr(), callback));
}
void RemoteSyncDelegate::DidPrepareForProcessRemoteChange(
const SyncStatusCallback& callback,
SyncStatusCode status,
const SyncFileMetadata& metadata,
const FileChangeList& local_changes) {
if (status != SYNC_STATUS_OK) {
AbortSync(callback, status);
return;
}
local_metadata_ = metadata;
status = metadata_store()->ReadEntry(url(), &drive_metadata_);
DCHECK(status == SYNC_STATUS_OK || status == SYNC_DATABASE_ERROR_NOT_FOUND);
bool missing_db_entry = (status != SYNC_STATUS_OK);
if (missing_db_entry) {
drive_metadata_.set_resource_id(remote_change_.resource_id);
drive_metadata_.set_md5_checksum(std::string());
drive_metadata_.set_conflicted(false);
drive_metadata_.set_to_be_fetched(false);
}
bool missing_local_file = (metadata.file_type == SYNC_FILE_TYPE_UNKNOWN);
if (drive_metadata_.resource_id().empty()) {
// This (missing_db_entry is false but resource_id is empty) could
// happen when the remote file gets deleted (this clears resource_id
// in drive_metadata) but then a file is added with the same name.
drive_metadata_.set_resource_id(remote_change_.resource_id);
}
SyncOperationType operation =
RemoteSyncOperationResolver::Resolve(remote_file_change(),
local_changes,
local_metadata_.file_type,
drive_metadata_.conflicted());
util::Log(logging::LOG_VERBOSE, FROM_HERE,
"ProcessRemoteChange for %s %s%sremote_change: %s ==> %s",
url().DebugString().c_str(),
drive_metadata_.conflicted() ? " (conflicted)" : " ",
missing_local_file ? " (missing local file)" : " ",
remote_file_change().DebugString().c_str(),
SyncOperationTypeToString(operation));
DCHECK_NE(SYNC_OPERATION_FAIL, operation);
switch (operation) {
case SYNC_OPERATION_ADD_FILE:
case SYNC_OPERATION_ADD_DIRECTORY:
sync_action_ = SYNC_ACTION_ADDED;
break;
case SYNC_OPERATION_UPDATE_FILE:
sync_action_ = SYNC_ACTION_UPDATED;
break;
case SYNC_OPERATION_DELETE:
sync_action_ = SYNC_ACTION_DELETED;
break;
case SYNC_OPERATION_NONE:
case SYNC_OPERATION_DELETE_METADATA:
sync_action_ = SYNC_ACTION_NONE;
break;
default:
break;
}
switch (operation) {
case SYNC_OPERATION_ADD_FILE:
case SYNC_OPERATION_UPDATE_FILE:
DownloadFile(callback);
return;
case SYNC_OPERATION_ADD_DIRECTORY:
case SYNC_OPERATION_DELETE:
ApplyRemoteChange(callback);
return;
case SYNC_OPERATION_NONE:
CompleteSync(callback, SYNC_STATUS_OK);
return;
case SYNC_OPERATION_CONFLICT:
HandleConflict(callback, remote_file_change().file_type());
return;
case SYNC_OPERATION_RESOLVE_TO_LOCAL:
ResolveToLocal(callback);
return;
case SYNC_OPERATION_RESOLVE_TO_REMOTE:
ResolveToRemote(callback);
return;
case SYNC_OPERATION_DELETE_METADATA:
if (missing_db_entry)
CompleteSync(callback, SYNC_STATUS_OK);
else
DeleteMetadata(callback);
return;
case SYNC_OPERATION_FAIL:
AbortSync(callback, SYNC_STATUS_FAILED);
return;
}
NOTREACHED();
AbortSync(callback, SYNC_STATUS_FAILED);
}
void RemoteSyncDelegate::ApplyRemoteChange(const SyncStatusCallback& callback) {
remote_change_processor()->ApplyRemoteChange(
remote_file_change(), temporary_file_.path(), url(),
base::Bind(&RemoteSyncDelegate::DidApplyRemoteChange, AsWeakPtr(),
callback));
}
void RemoteSyncDelegate::DidApplyRemoteChange(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK) {
AbortSync(callback, status);
return;
}
if (remote_file_change().IsDelete()) {
DeleteMetadata(callback);
return;
}
drive_metadata_.set_resource_id(remote_change_.resource_id);
drive_metadata_.set_conflicted(false);
if (remote_file_change().IsFile()) {
drive_metadata_.set_type(DriveMetadata::RESOURCE_TYPE_FILE);
} else {
DCHECK(IsSyncFSDirectoryOperationEnabled());
drive_metadata_.set_type(DriveMetadata::RESOURCE_TYPE_FOLDER);
}
metadata_store()->UpdateEntry(
url(), drive_metadata_,
base::Bind(&RemoteSyncDelegate::CompleteSync,
AsWeakPtr(), callback));
}
void RemoteSyncDelegate::DeleteMetadata(const SyncStatusCallback& callback) {
metadata_store()->DeleteEntry(
url(),
base::Bind(&RemoteSyncDelegate::CompleteSync, AsWeakPtr(), callback));
}
void RemoteSyncDelegate::DownloadFile(const SyncStatusCallback& callback) {
// We should not use the md5 in metadata for FETCH type to avoid the download
// finishes due to NOT_MODIFIED.
std::string md5_checksum;
if (!drive_metadata_.to_be_fetched())
md5_checksum = drive_metadata_.md5_checksum();
api_util()->DownloadFile(
remote_change_.resource_id,
md5_checksum,
base::Bind(&RemoteSyncDelegate::DidDownloadFile,
AsWeakPtr(),
callback));
}
void RemoteSyncDelegate::DidDownloadFile(
const SyncStatusCallback& callback,
google_apis::GDataErrorCode error,
const std::string& md5_checksum,
int64 file_size,
const base::Time& updated_time,
scoped_ptr<webkit_blob::ScopedFile> downloaded_file) {
if (error == google_apis::HTTP_NOT_MODIFIED) {
sync_action_ = SYNC_ACTION_NONE;
DidApplyRemoteChange(callback, SYNC_STATUS_OK);
return;
}
// File may be deleted. If this was for new file it's ok, if this was
// for existing file we'll process the delete change later.
if (error == google_apis::HTTP_NOT_FOUND) {
sync_action_ = SYNC_ACTION_NONE;
DidApplyRemoteChange(callback, SYNC_STATUS_OK);
return;
}
SyncStatusCode status = GDataErrorCodeToSyncStatusCodeWrapper(error);
if (status != SYNC_STATUS_OK) {
AbortSync(callback, status);
return;
}
temporary_file_ = downloaded_file->Pass();
drive_metadata_.set_md5_checksum(md5_checksum);
remote_change_processor()->ApplyRemoteChange(
remote_file_change(), temporary_file_.path(), url(),
base::Bind(&RemoteSyncDelegate::DidApplyRemoteChange,
AsWeakPtr(), callback));
}
void RemoteSyncDelegate::HandleConflict(
const SyncStatusCallback& callback,
SyncFileType remote_file_type) {
ConflictResolution resolution = conflict_resolution_resolver()->Resolve(
local_metadata_.file_type,
local_metadata_.last_modified,
remote_file_type,
remote_change_.updated_time);
switch (resolution) {
case CONFLICT_RESOLUTION_LOCAL_WIN:
HandleLocalWin(callback);
return;
case CONFLICT_RESOLUTION_REMOTE_WIN:
HandleRemoteWin(callback, remote_file_type);
return;
case CONFLICT_RESOLUTION_MARK_CONFLICT:
HandleManualResolutionCase(callback);
return;
case CONFLICT_RESOLUTION_UNKNOWN:
// Get remote file time and call this method again.
api_util()->GetResourceEntry(
remote_change_.resource_id,
base::Bind(
&RemoteSyncDelegate::DidGetEntryForConflictResolution,
AsWeakPtr(), callback));
return;
}
NOTREACHED();
AbortSync(callback, SYNC_STATUS_FAILED);
}
void RemoteSyncDelegate::HandleLocalWin(
const SyncStatusCallback& callback) {
util::Log(logging::LOG_VERBOSE, FROM_HERE,
"Resolving conflict for remote sync: %s: LOCAL WIN",
url().DebugString().c_str());
ResolveToLocal(callback);
}
void RemoteSyncDelegate::HandleRemoteWin(
const SyncStatusCallback& callback,
SyncFileType remote_file_type) {
// Make sure we reset the conflict flag and start over the remote sync
// with empty local changes.
util::Log(logging::LOG_VERBOSE, FROM_HERE,
"Resolving conflict for remote sync: %s: REMOTE WIN",
url().DebugString().c_str());
drive_metadata_.set_conflicted(false);
drive_metadata_.set_to_be_fetched(false);
drive_metadata_.set_type(
DriveFileSyncService::SyncFileTypeToDriveMetadataResourceType(
remote_file_type));
metadata_store()->UpdateEntry(
url(), drive_metadata_,
base::Bind(&RemoteSyncDelegate::StartOver, AsWeakPtr(), callback));
}
void RemoteSyncDelegate::HandleManualResolutionCase(
const SyncStatusCallback& callback) {
sync_action_ = SYNC_ACTION_NONE;
sync_service_->MarkConflict(
url(), &drive_metadata_,
base::Bind(&RemoteSyncDelegate::CompleteSync, AsWeakPtr(), callback));
}
void RemoteSyncDelegate::DidGetEntryForConflictResolution(
const SyncStatusCallback& callback,
google_apis::GDataErrorCode error,
scoped_ptr<google_apis::ResourceEntry> entry) {
SyncStatusCode status = GDataErrorCodeToSyncStatusCodeWrapper(error);
if (status != SYNC_STATUS_OK || entry->updated_time().is_null()) {
HandleLocalWin(callback);
return;
}
SyncFileType file_type = SYNC_FILE_TYPE_UNKNOWN;
if (entry->is_file())
file_type = SYNC_FILE_TYPE_FILE;
if (entry->is_folder())
file_type = SYNC_FILE_TYPE_DIRECTORY;
remote_change_.updated_time = entry->updated_time();
HandleConflict(callback, file_type);
}
void RemoteSyncDelegate::ResolveToLocal(
const SyncStatusCallback& callback) {
sync_action_ = SYNC_ACTION_NONE;
clear_local_changes_ = false;
// Re-add a fake local change to resolve it later in next LocalSync.
remote_change_processor()->RecordFakeLocalChange(
url(),
FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
local_metadata_.file_type),
base::Bind(&RemoteSyncDelegate::DidResolveToLocal,
AsWeakPtr(), callback));
}
void RemoteSyncDelegate::DidResolveToLocal(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK) {
DCHECK_NE(SYNC_STATUS_HAS_CONFLICT, status);
AbortSync(callback, status);
return;
}
if (remote_file_change().IsDelete()) {
metadata_store()->DeleteEntry(
url(),
base::Bind(&RemoteSyncDelegate::CompleteSync,
AsWeakPtr(), callback));
} else {
DCHECK(!remote_change_.resource_id.empty());
drive_metadata_.set_resource_id(remote_change_.resource_id);
drive_metadata_.set_conflicted(false);
drive_metadata_.set_to_be_fetched(false);
drive_metadata_.set_md5_checksum(std::string());
metadata_store()->UpdateEntry(
url(), drive_metadata_,
base::Bind(&RemoteSyncDelegate::CompleteSync,
AsWeakPtr(), callback));
}
}
void RemoteSyncDelegate::ResolveToRemote(
const SyncStatusCallback& callback) {
drive_metadata_.set_conflicted(false);
drive_metadata_.set_to_be_fetched(true);
metadata_store()->UpdateEntry(
url(), drive_metadata_,
base::Bind(&RemoteSyncDelegate::DidResolveToRemote,
AsWeakPtr(), callback));
}
void RemoteSyncDelegate::DidResolveToRemote(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK) {
AbortSync(callback, status);
return;
}
sync_action_ = SYNC_ACTION_ADDED;
if (remote_file_change().file_type() == SYNC_FILE_TYPE_FILE) {
DownloadFile(callback);
return;
}
// ApplyRemoteChange should replace any existing local file or
// directory with remote_change_.
ApplyRemoteChange(callback);
}
void RemoteSyncDelegate::StartOver(
const SyncStatusCallback& callback,
SyncStatusCode status) {
DidPrepareForProcessRemoteChange(
callback, status, local_metadata_, FileChangeList());
}
void RemoteSyncDelegate::CompleteSync(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK) {
AbortSync(callback, status);
return;
}
sync_service_->RemoveRemoteChange(url());
if (drive_metadata_.to_be_fetched()) {
// Clear |to_be_fetched| flag since we completed fetching the remote change
// and applying it to the local file.
DCHECK(!drive_metadata_.conflicted());
drive_metadata_.set_conflicted(false);
drive_metadata_.set_to_be_fetched(false);
metadata_store()->UpdateEntry(url(), drive_metadata_,
base::Bind(&EmptyStatusCallback));
}
if (remote_change_.changestamp > 0) {
DCHECK(metadata_store()->IsIncrementalSyncOrigin(url().origin()));
metadata_store()->SetLargestChangeStamp(
remote_change_.changestamp,
base::Bind(&RemoteSyncDelegate::DidFinish, AsWeakPtr(), callback));
return;
}
if (drive_metadata_.conflicted())
status = SYNC_STATUS_HAS_CONFLICT;
DidFinish(callback, status);
}
void RemoteSyncDelegate::AbortSync(
const SyncStatusCallback& callback,
SyncStatusCode status) {
DidFinish(callback, status);
}
void RemoteSyncDelegate::DidFinish(
const SyncStatusCallback& callback,
SyncStatusCode status) {
// Clear the local changes. If the operation was resolve-to-local, we should
// not clear them here since we added the fake local change to sync with the
// remote file.
if (clear_local_changes_) {
clear_local_changes_ = false;
remote_change_processor()->ClearLocalChanges(
url(), base::Bind(&RemoteSyncDelegate::DidFinish,
AsWeakPtr(), callback, status));
return;
}
if (status == SYNC_STATUS_OK && sync_action_ != SYNC_ACTION_NONE) {
sync_service_->NotifyObserversFileStatusChanged(
url(),
SYNC_FILE_STATUS_SYNCED,
sync_action_,
SYNC_DIRECTION_REMOTE_TO_LOCAL);
}
callback.Run(status);
}
SyncStatusCode RemoteSyncDelegate::GDataErrorCodeToSyncStatusCodeWrapper(
google_apis::GDataErrorCode error) {
return sync_service_->GDataErrorCodeToSyncStatusCodeWrapper(error);
}
DriveMetadataStore* RemoteSyncDelegate::metadata_store() {
return sync_service_->metadata_store_.get();
}
APIUtilInterface* RemoteSyncDelegate::api_util() {
return sync_service_->api_util_.get();
}
RemoteChangeHandler* RemoteSyncDelegate::remote_change_handler() {
return &sync_service_->remote_change_handler_;
}
RemoteChangeProcessor* RemoteSyncDelegate::remote_change_processor() {
return sync_service_->remote_change_processor_;
}
ConflictResolutionResolver* RemoteSyncDelegate::conflict_resolution_resolver() {
return &sync_service_->conflict_resolution_resolver_;
}
} // namespace drive_backend
} // namespace sync_file_system