blob: f75b9667f30babd020a8b81ded36f896f183e5bf [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/local_to_remote_syncer.h"
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/sequenced_task_runner.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/drive/drive_uploader.h"
#include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h"
#include "chrome/browser/sync_file_system/drive_backend/folder_creator.h"
#include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
#include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.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 "google_apis/drive/drive_api_parser.h"
#include "webkit/common/fileapi/file_system_util.h"
namespace sync_file_system {
namespace drive_backend {
namespace {
scoped_ptr<FileTracker> FindTrackerByID(MetadataDatabase* metadata_database,
int64 tracker_id) {
scoped_ptr<FileTracker> tracker(new FileTracker);
if (metadata_database->FindTrackerByTrackerID(tracker_id, tracker.get()))
return tracker.Pass();
return scoped_ptr<FileTracker>();
}
void ReturnRetryOnSuccess(const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status == SYNC_STATUS_OK)
status = SYNC_STATUS_RETRY;
callback.Run(status);
}
} // namespace
LocalToRemoteSyncer::LocalToRemoteSyncer(SyncEngineContext* sync_context,
const SyncFileMetadata& local_metadata,
const FileChange& local_change,
const base::FilePath& local_path,
const fileapi::FileSystemURL& url)
: sync_context_(sync_context),
local_metadata_(local_metadata),
local_change_(local_change),
local_path_(local_path),
url_(url),
sync_action_(SYNC_ACTION_NONE),
weak_ptr_factory_(this) {
}
LocalToRemoteSyncer::~LocalToRemoteSyncer() {
}
void LocalToRemoteSyncer::Run(const SyncStatusCallback& callback) {
if (!IsContextReady()) {
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
SyncStatusCallback wrapped_callback = base::Bind(
&LocalToRemoteSyncer::SyncCompleted, weak_ptr_factory_.GetWeakPtr(),
callback);
std::string app_id = url_.origin().host();
base::FilePath path = url_.path();
scoped_ptr<FileTracker> active_ancestor_tracker(new FileTracker);
base::FilePath active_ancestor_path;
if (!metadata_database()->FindNearestActiveAncestor(
app_id, path,
active_ancestor_tracker.get(), &active_ancestor_path)) {
// The app is disabled or not registered.
callback.Run(SYNC_STATUS_UNKNOWN_ORIGIN);
return;
}
DCHECK(active_ancestor_tracker->active());
DCHECK(active_ancestor_tracker->has_synced_details());
const FileDetails& active_ancestor_details =
active_ancestor_tracker->synced_details();
// TODO(tzik): Consider handling
// active_ancestor_tracker->synced_details().missing() case.
DCHECK(active_ancestor_details.file_kind() == FILE_KIND_FILE ||
active_ancestor_details.file_kind() == FILE_KIND_FOLDER);
base::FilePath missing_entries;
if (active_ancestor_path.empty()) {
missing_entries = path;
} else if (active_ancestor_path != path) {
bool should_success = active_ancestor_path.AppendRelativePath(
path, &missing_entries);
if (!should_success) {
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
}
std::vector<base::FilePath::StringType> missing_components;
fileapi::VirtualPath::GetComponents(missing_entries, &missing_components);
if (!missing_components.empty()) {
if (local_change_.IsDelete() ||
local_metadata_.file_type == SYNC_FILE_TYPE_UNKNOWN) {
// !IsDelete() but SYNC_FILE_TYPE_UNKNOWN could happen when a file is
// deleted by recursive deletion (which is not recorded by tracker)
// but there're remaining changes for the same file in the tracker.
// Local file is deleted and remote file is missing, already deleted or
// not yet synced. There is nothing to do for the file.
callback.Run(SYNC_STATUS_OK);
return;
}
}
if (missing_components.size() > 1) {
// The original target doesn't have remote file and parent.
// Try creating the parent first.
if (active_ancestor_details.file_kind() == FILE_KIND_FOLDER) {
remote_parent_folder_tracker_ = active_ancestor_tracker.Pass();
target_path_ = active_ancestor_path.Append(missing_components[0]);
CreateRemoteFolder(wrapped_callback);
return;
}
DCHECK(active_ancestor_details.file_kind() == FILE_KIND_FILE);
remote_parent_folder_tracker_ =
FindTrackerByID(metadata_database(),
active_ancestor_tracker->parent_tracker_id());
remote_file_tracker_ = active_ancestor_tracker.Pass();
target_path_ = active_ancestor_path;
DeleteRemoteFile(base::Bind(&LocalToRemoteSyncer::DidDeleteForCreateFolder,
weak_ptr_factory_.GetWeakPtr(),
wrapped_callback));
return;
}
if (missing_components.empty()) {
// The original target has remote active file/folder.
remote_parent_folder_tracker_ =
FindTrackerByID(metadata_database(),
active_ancestor_tracker->parent_tracker_id());
remote_file_tracker_ = active_ancestor_tracker.Pass();
target_path_ = url_.path();
DCHECK(target_path_ == active_ancestor_path);
if (remote_file_tracker_->dirty()) {
// Both local and remote file has pending modification.
HandleConflict(wrapped_callback);
return;
}
// Non-conflicting file/folder update case.
HandleExistingRemoteFile(wrapped_callback);
return;
}
DCHECK(local_change_.IsAddOrUpdate());
DCHECK_EQ(1u, missing_components.size());
// The original target has remote parent folder and doesn't have remote active
// file.
// Upload the file as a new file or create a folder.
remote_parent_folder_tracker_ = active_ancestor_tracker.Pass();
target_path_ = url_.path();
DCHECK(target_path_ == active_ancestor_path.Append(missing_components[0]));
if (local_change_.file_type() == SYNC_FILE_TYPE_FILE) {
UploadNewFile(wrapped_callback);
return;
}
CreateRemoteFolder(wrapped_callback);
}
void LocalToRemoteSyncer::SyncCompleted(const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status == SYNC_STATUS_OK && target_path_ != url_.path()) {
callback.Run(SYNC_STATUS_RETRY);
return;
}
callback.Run(status);
}
void LocalToRemoteSyncer::HandleConflict(const SyncStatusCallback& callback) {
DCHECK(remote_file_tracker_);
DCHECK(remote_file_tracker_->has_synced_details());
DCHECK(remote_file_tracker_->active());
DCHECK(remote_file_tracker_->dirty());
if (local_change_.IsFile()) {
UploadNewFile(callback);
return;
}
DCHECK(local_change_.IsDirectory());
// Check if we can reuse the remote folder.
FileMetadata remote_file_metadata;
bool should_success = metadata_database()->FindFileByFileID(
remote_file_tracker_->file_id(), &remote_file_metadata);
if (!should_success) {
NOTREACHED();
CreateRemoteFolder(callback);
return;
}
const FileDetails& remote_details = remote_file_metadata.details();
base::FilePath title = fileapi::VirtualPath::BaseName(target_path_);
if (!remote_details.missing() &&
remote_details.file_kind() == FILE_KIND_FOLDER &&
remote_details.title() == title.AsUTF8Unsafe() &&
HasFileAsParent(remote_details,
remote_parent_folder_tracker_->file_id())) {
metadata_database()->UpdateTracker(
remote_file_tracker_->tracker_id(), remote_details, callback);
return;
}
// Create new remote folder.
CreateRemoteFolder(callback);
}
void LocalToRemoteSyncer::HandleExistingRemoteFile(
const SyncStatusCallback& callback) {
DCHECK(remote_file_tracker_);
DCHECK(!remote_file_tracker_->dirty());
DCHECK(remote_file_tracker_->active());
DCHECK(remote_file_tracker_->has_synced_details());
if (local_change_.IsDelete() ||
local_metadata_.file_type == SYNC_FILE_TYPE_UNKNOWN) {
// Local file deletion for existing remote file.
DeleteRemoteFile(callback);
return;
}
DCHECK(local_change_.IsAddOrUpdate());
DCHECK(local_change_.file_type() == SYNC_FILE_TYPE_FILE ||
local_change_.file_type() == SYNC_FILE_TYPE_DIRECTORY);
const FileDetails& synced_details = remote_file_tracker_->synced_details();
DCHECK(synced_details.file_kind() == FILE_KIND_FILE ||
synced_details.file_kind() == FILE_KIND_FOLDER);
if (local_change_.file_type() == SYNC_FILE_TYPE_FILE) {
if (synced_details.file_kind() == FILE_KIND_FILE) {
// Non-conflicting local file update to existing remote regular file.
UploadExistingFile(callback);
return;
}
DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind());
// Non-conflicting local file update to existing remote *folder*.
// Assuming this case as local folder deletion + local file creation, delete
// the remote folder and upload the file.
DeleteRemoteFile(base::Bind(&LocalToRemoteSyncer::DidDeleteForUploadNewFile,
weak_ptr_factory_.GetWeakPtr(),
callback));
return;
}
DCHECK_EQ(SYNC_FILE_TYPE_DIRECTORY, local_change_.file_type());
if (synced_details.file_kind() == FILE_KIND_FILE) {
// Non-conflicting local folder creation to existing remote *file*.
// Assuming this case as local file deletion + local folder creation, delete
// the remote file and create a remote folder.
DeleteRemoteFile(base::Bind(&LocalToRemoteSyncer::DidDeleteForCreateFolder,
weak_ptr_factory_.GetWeakPtr(), callback));
return;
}
// Non-conflicting local folder creation to existing remote folder.
DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind());
callback.Run(SYNC_STATUS_OK);
}
void LocalToRemoteSyncer::DeleteRemoteFile(
const SyncStatusCallback& callback) {
DCHECK(remote_file_tracker_);
DCHECK(remote_file_tracker_->has_synced_details());
sync_action_ = SYNC_ACTION_DELETED;
drive_service()->DeleteResource(
remote_file_tracker_->file_id(),
remote_file_tracker_->synced_details().etag(),
base::Bind(&LocalToRemoteSyncer::DidDeleteRemoteFile,
weak_ptr_factory_.GetWeakPtr(),
callback));
}
void LocalToRemoteSyncer::DidDeleteRemoteFile(
const SyncStatusCallback& callback,
google_apis::GDataErrorCode error) {
if (error != google_apis::HTTP_SUCCESS &&
error != google_apis::HTTP_NOT_FOUND &&
error != google_apis::HTTP_PRECONDITION &&
error != google_apis::HTTP_CONFLICT) {
callback.Run(GDataErrorCodeToSyncStatusCode(error));
return;
}
// Handle NOT_FOUND case as SUCCESS case.
// For PRECONDITION / CONFLICT case, the remote file is modified since the
// last sync completed. As our policy for deletion-modification conflict
// resolution, ignore the local deletion.
callback.Run(SYNC_STATUS_OK);
}
void LocalToRemoteSyncer::UploadExistingFile(
const SyncStatusCallback& callback) {
DCHECK(remote_file_tracker_);
DCHECK(remote_file_tracker_->has_synced_details());
base::PostTaskAndReplyWithResult(
sync_context_->GetBlockingTaskRunner(), FROM_HERE,
base::Bind(&drive::util::GetMd5Digest, local_path_),
base::Bind(&LocalToRemoteSyncer::DidGetMD5ForUpload,
weak_ptr_factory_.GetWeakPtr(),
callback));
}
void LocalToRemoteSyncer::DidGetMD5ForUpload(
const SyncStatusCallback& callback,
const std::string& local_file_md5) {
if (local_file_md5 == remote_file_tracker_->synced_details().md5()) {
// Local file is not changed.
callback.Run(SYNC_STATUS_OK);
return;
}
sync_action_ = SYNC_ACTION_UPDATED;
drive_uploader()->UploadExistingFile(
remote_file_tracker_->file_id(),
local_path_,
"application/octet_stream",
remote_file_tracker_->synced_details().etag(),
base::Bind(&LocalToRemoteSyncer::DidUploadExistingFile,
weak_ptr_factory_.GetWeakPtr(),
callback),
google_apis::ProgressCallback());
}
void LocalToRemoteSyncer::DidUploadExistingFile(
const SyncStatusCallback& callback,
google_apis::GDataErrorCode error,
const GURL&,
scoped_ptr<google_apis::ResourceEntry> entry) {
if (error == google_apis::HTTP_PRECONDITION ||
error == google_apis::HTTP_CONFLICT) {
// The remote file has unfetched remote change. Fetch latest metadata and
// update database with it.
// TODO(tzik): Consider adding local side low-priority dirtiness handling to
// handle this as ListChangesTask.
UpdateRemoteMetadata(remote_file_tracker_->file_id(),
base::Bind(&ReturnRetryOnSuccess, callback));
return;
}
SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
if (!entry) {
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
DCHECK(entry);
metadata_database()->UpdateByFileResource(
*drive::util::ConvertResourceEntryToFileResource(*entry),
base::Bind(&LocalToRemoteSyncer::DidUpdateDatabaseForUploadExistingFile,
weak_ptr_factory_.GetWeakPtr(),
callback));
}
void LocalToRemoteSyncer::DidUpdateDatabaseForUploadExistingFile(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
FileMetadata file;
bool should_success = metadata_database()->FindFileByFileID(
remote_file_tracker_->file_id(), &file);
if (!should_success) {
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
const FileDetails& details = file.details();
base::FilePath title = fileapi::VirtualPath::BaseName(target_path_);
if (!details.missing() &&
details.file_kind() == FILE_KIND_FILE &&
details.title() == title.AsUTF8Unsafe() &&
HasFileAsParent(details,
remote_parent_folder_tracker_->file_id())) {
metadata_database()->UpdateTracker(
remote_file_tracker_->tracker_id(),
file.details(),
callback);
return;
}
callback.Run(SYNC_STATUS_RETRY);
}
void LocalToRemoteSyncer::UpdateRemoteMetadata(
const std::string& file_id,
const SyncStatusCallback& callback) {
DCHECK(remote_file_tracker_);
drive_service()->GetResourceEntry(
file_id,
base::Bind(&LocalToRemoteSyncer::DidGetRemoteMetadata,
weak_ptr_factory_.GetWeakPtr(),
file_id, callback));
}
void LocalToRemoteSyncer::DidGetRemoteMetadata(
const std::string& file_id,
const SyncStatusCallback& callback,
google_apis::GDataErrorCode error,
scoped_ptr<google_apis::ResourceEntry> entry) {
if (error == google_apis::HTTP_NOT_FOUND) {
metadata_database()->UpdateByDeletedRemoteFile(file_id, callback);
return;
}
SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
if (!entry) {
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
metadata_database()->UpdateByFileResource(
*drive::util::ConvertResourceEntryToFileResource(*entry), callback);
}
void LocalToRemoteSyncer::DidDeleteForUploadNewFile(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status == SYNC_STATUS_HAS_CONFLICT) {
UpdateRemoteMetadata(remote_file_tracker_->file_id(),
base::Bind(&ReturnRetryOnSuccess, callback));
return;
}
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
UploadNewFile(callback);
}
void LocalToRemoteSyncer::DidDeleteForCreateFolder(
const SyncStatusCallback& callback,
SyncStatusCode status) {
if (status == SYNC_STATUS_HAS_CONFLICT) {
UpdateRemoteMetadata(remote_file_tracker_->file_id(),
base::Bind(&ReturnRetryOnSuccess, callback));
return;
}
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
CreateRemoteFolder(callback);
}
void LocalToRemoteSyncer::UploadNewFile(const SyncStatusCallback& callback) {
DCHECK(remote_parent_folder_tracker_);
sync_action_ = SYNC_ACTION_ADDED;
base::FilePath title = fileapi::VirtualPath::BaseName(target_path_);
drive_uploader()->UploadNewFile(
remote_parent_folder_tracker_->file_id(),
local_path_,
title.AsUTF8Unsafe(),
GetMimeTypeFromTitle(title),
base::Bind(&LocalToRemoteSyncer::DidUploadNewFile,
weak_ptr_factory_.GetWeakPtr(),
callback),
google_apis::ProgressCallback());
}
void LocalToRemoteSyncer::DidUploadNewFile(
const SyncStatusCallback& callback,
google_apis::GDataErrorCode error,
const GURL& upload_location,
scoped_ptr<google_apis::ResourceEntry> entry) {
SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
if (!entry) {
NOTREACHED();
callback.Run(SYNC_STATUS_FAILED);
return;
}
metadata_database()->ReplaceActiveTrackerWithNewResource(
remote_parent_folder_tracker_->tracker_id(),
*drive::util::ConvertResourceEntryToFileResource(*entry),
callback);
}
void LocalToRemoteSyncer::CreateRemoteFolder(
const SyncStatusCallback& callback) {
DCHECK(remote_parent_folder_tracker_);
base::FilePath title = fileapi::VirtualPath::BaseName(target_path_);
sync_action_ = SYNC_ACTION_ADDED;
DCHECK(!folder_creator_);
folder_creator_.reset(new FolderCreator(
drive_service(), metadata_database(),
remote_parent_folder_tracker_->file_id(),
title.AsUTF8Unsafe()));
folder_creator_->Run(base::Bind(
&LocalToRemoteSyncer::DidCreateRemoteFolder,
weak_ptr_factory_.GetWeakPtr(),
callback));
}
void LocalToRemoteSyncer::DidCreateRemoteFolder(
const SyncStatusCallback& callback,
const std::string& file_id,
SyncStatusCode status) {
scoped_ptr<FolderCreator> deleter = folder_creator_.Pass();
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
if (metadata_database()->TryNoSideEffectActivation(
remote_parent_folder_tracker_->tracker_id(),
file_id, callback)) {
// |callback| will be invoked by MetadataDatabase in this case.
return;
}
drive_service()->RemoveResourceFromDirectory(
remote_parent_folder_tracker_->file_id(), file_id,
base::Bind(&LocalToRemoteSyncer::DidDetachResourceForCreationConflict,
weak_ptr_factory_.GetWeakPtr(),
callback));
}
void LocalToRemoteSyncer::DidDetachResourceForCreationConflict(
const SyncStatusCallback& callback,
google_apis::GDataErrorCode error) {
SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
if (status != SYNC_STATUS_OK) {
callback.Run(status);
return;
}
callback.Run(SYNC_STATUS_RETRY);
}
bool LocalToRemoteSyncer::IsContextReady() {
return sync_context_->GetDriveService() &&
sync_context_->GetDriveUploader() &&
sync_context_->GetMetadataDatabase();
}
drive::DriveServiceInterface* LocalToRemoteSyncer::drive_service() {
set_used_network(true);
return sync_context_->GetDriveService();
}
drive::DriveUploaderInterface* LocalToRemoteSyncer::drive_uploader() {
set_used_network(true);
return sync_context_->GetDriveUploader();
}
MetadataDatabase* LocalToRemoteSyncer::metadata_database() {
return sync_context_->GetMetadataDatabase();
}
} // namespace drive_backend
} // namespace sync_file_system