blob: 0f0899ac48a5e07b3d7b42a0c5d8af99c33bd9ac [file] [log] [blame]
// 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 "chrome/browser/chromeos/drive/file_system/copy_operation.h"
#include <string>
#include "base/file_util.h"
#include "base/task_runner_util.h"
#include "chrome/browser/chromeos/drive/drive.pb.h"
#include "chrome/browser/chromeos/drive/file_cache.h"
#include "chrome/browser/chromeos/drive/file_system/create_file_operation.h"
#include "chrome/browser/chromeos/drive/file_system/download_operation.h"
#include "chrome/browser/chromeos/drive/file_system/move_operation.h"
#include "chrome/browser/chromeos/drive/file_system/operation_observer.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/drive/job_scheduler.h"
#include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
#include "chrome/browser/drive/drive_api_util.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace drive {
namespace file_system {
namespace {
FileError PrepareCopy(internal::ResourceMetadata* metadata,
const base::FilePath& src_path,
const base::FilePath& dest_path,
ResourceEntry* src_entry,
std::string* parent_resource_id) {
FileError error = metadata->GetResourceEntryByPath(src_path, src_entry);
if (error != FILE_ERROR_OK)
return error;
ResourceEntry parent_entry;
error = metadata->GetResourceEntryByPath(dest_path.DirName(), &parent_entry);
if (error != FILE_ERROR_OK)
return error;
if (!parent_entry.file_info().is_directory())
return FILE_ERROR_NOT_A_DIRECTORY;
// Drive File System doesn't support recursive copy.
if (src_entry->file_info().is_directory())
return FILE_ERROR_NOT_A_FILE;
*parent_resource_id = parent_entry.resource_id();
return FILE_ERROR_OK;
}
int64 GetFileSize(const base::FilePath& file_path) {
int64 file_size;
if (!file_util::GetFileSize(file_path, &file_size))
return -1;
return file_size;
}
// Stores the copied entry and returns its path.
FileError UpdateLocalStateForServerSideCopy(
internal::ResourceMetadata* metadata,
scoped_ptr<google_apis::ResourceEntry> resource_entry,
base::FilePath* file_path) {
DCHECK(resource_entry);
ResourceEntry entry;
std::string parent_resource_id;
if (!ConvertToResourceEntry(*resource_entry, &entry, &parent_resource_id))
return FILE_ERROR_NOT_A_FILE;
std::string parent_local_id;
FileError error = metadata->GetIdByResourceId(parent_resource_id,
&parent_local_id);
if (error != FILE_ERROR_OK)
return error;
entry.set_parent_local_id(parent_local_id);
std::string local_id;
error = metadata->AddEntry(entry, &local_id);
// Depending on timing, the metadata may have inserted via change list
// already. So, FILE_ERROR_EXISTS is not an error.
if (error == FILE_ERROR_EXISTS)
error = metadata->GetIdByResourceId(entry.resource_id(), &local_id);
if (error == FILE_ERROR_OK)
*file_path = metadata->GetFilePath(local_id);
return error;
}
// Stores the file at |local_file_path| to the cache as a content of entry at
// |remote_dest_path|, and marks it dirty.
FileError UpdateLocalStateForScheduleTransfer(
internal::ResourceMetadata* metadata,
internal::FileCache* cache,
const base::FilePath& local_src_path,
const base::FilePath& remote_dest_path,
std::string* local_id) {
FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
if (error != FILE_ERROR_OK)
return error;
ResourceEntry entry;
error = metadata->GetResourceEntryById(*local_id, &entry);
if (error != FILE_ERROR_OK)
return error;
error = cache->Store(
*local_id, entry.file_specific_info().md5(), local_src_path,
internal::FileCache::FILE_OPERATION_COPY);
if (error != FILE_ERROR_OK)
return error;
error = cache->MarkDirty(*local_id);
if (error != FILE_ERROR_OK)
return error;
return FILE_ERROR_OK;
}
// Gets the file size of the |local_path|, and the ResourceEntry for the parent
// of |remote_path| to prepare the necessary information for transfer.
FileError PrepareTransferFileFromLocalToRemote(
internal::ResourceMetadata* metadata,
const base::FilePath& local_src_path,
const base::FilePath& remote_dest_path,
std::string* gdoc_resource_id,
std::string* parent_resource_id) {
ResourceEntry parent_entry;
FileError error = metadata->GetResourceEntryByPath(
remote_dest_path.DirName(), &parent_entry);
if (error != FILE_ERROR_OK)
return error;
// The destination's parent must be a directory.
if (!parent_entry.file_info().is_directory())
return FILE_ERROR_NOT_A_DIRECTORY;
// Try to parse GDoc File and extract the resource id, if necessary.
// Failing isn't problem. It'd be handled as a regular file, then.
if (util::HasGDocFileExtension(local_src_path)) {
*gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
*parent_resource_id = parent_entry.resource_id();
}
return FILE_ERROR_OK;
}
} // namespace
struct CopyOperation::CopyParams {
base::FilePath src_file_path;
base::FilePath dest_file_path;
bool preserve_last_modified;
FileOperationCallback callback;
};
CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
OperationObserver* observer,
JobScheduler* scheduler,
internal::ResourceMetadata* metadata,
internal::FileCache* cache,
DriveServiceInterface* drive_service,
const base::FilePath& temporary_file_directory)
: blocking_task_runner_(blocking_task_runner),
observer_(observer),
scheduler_(scheduler),
metadata_(metadata),
cache_(cache),
drive_service_(drive_service),
create_file_operation_(new CreateFileOperation(blocking_task_runner,
observer,
scheduler,
metadata,
cache)),
download_operation_(new DownloadOperation(blocking_task_runner,
observer,
scheduler,
metadata,
cache,
temporary_file_directory)),
move_operation_(new MoveOperation(blocking_task_runner,
observer,
scheduler,
metadata)),
weak_ptr_factory_(this) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
CopyOperation::~CopyOperation() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
void CopyOperation::Copy(const base::FilePath& src_file_path,
const base::FilePath& dest_file_path,
bool preserve_last_modified,
const FileOperationCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
CopyParams params;
params.src_file_path = src_file_path;
params.dest_file_path = dest_file_path;
params.preserve_last_modified = preserve_last_modified;
params.callback = callback;
ResourceEntry* src_entry = new ResourceEntry;
std::string* parent_resource_id = new std::string;
base::PostTaskAndReplyWithResult(
blocking_task_runner_.get(),
FROM_HERE,
base::Bind(&PrepareCopy,
metadata_, src_file_path, dest_file_path,
src_entry, parent_resource_id),
base::Bind(&CopyOperation::CopyAfterPrepare,
weak_ptr_factory_.GetWeakPtr(), params,
base::Owned(src_entry), base::Owned(parent_resource_id)));
}
void CopyOperation::CopyAfterPrepare(
const CopyParams& params,
ResourceEntry* src_entry,
std::string* parent_resource_id,
FileError error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!params.callback.is_null());
if (error != FILE_ERROR_OK) {
params.callback.Run(error);
return;
}
// If Drive API v2 is enabled, we can copy resources on server side.
if (util::IsDriveV2ApiEnabled()) {
base::FilePath new_title = params.dest_file_path.BaseName();
if (src_entry->file_specific_info().is_hosted_document()) {
// Drop the document extension, which should not be in the title.
// TODO(yoshiki): Remove this code with crbug.com/223304.
new_title = new_title.RemoveExtension();
}
base::Time last_modified =
params.preserve_last_modified ?
base::Time::FromInternalValue(src_entry->file_info().last_modified()) :
base::Time();
CopyResourceOnServer(
src_entry->resource_id(), *parent_resource_id,
new_title.AsUTF8Unsafe(), last_modified, params.callback);
return;
}
// Here after GData WAPI's code.
if (src_entry->file_specific_info().is_hosted_document()) {
// For hosted documents, GData WAPI copies it on server.
CopyHostedDocument(
src_entry->resource_id(),
// Drop the document extension, which should not be in the title.
// TODO(yoshiki): Remove this code with crbug.com/223304.
params.dest_file_path.RemoveExtension(),
params.callback);
return;
}
// For regular files, download the content, and then re-upload.
// Note that upload is done later by SyncClient.
download_operation_->EnsureFileDownloadedByPath(
params.src_file_path,
ClientContext(USER_INITIATED),
GetFileContentInitializedCallback(),
google_apis::GetContentCallback(),
base::Bind(&CopyOperation::CopyAfterDownload,
weak_ptr_factory_.GetWeakPtr(),
params.dest_file_path, params.callback));
}
void CopyOperation::CopyAfterDownload(
const base::FilePath& dest_file_path,
const FileOperationCallback& callback,
FileError error,
const base::FilePath& local_file_path,
scoped_ptr<ResourceEntry> entry) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
if (error != FILE_ERROR_OK) {
callback.Run(error);
return;
}
// This callback is only triggered for a regular file via Copy().
DCHECK(entry && !entry->file_specific_info().is_hosted_document());
ScheduleTransferRegularFile(local_file_path, dest_file_path, callback);
}
void CopyOperation::TransferFileFromLocalToRemote(
const base::FilePath& local_src_path,
const base::FilePath& remote_dest_path,
const FileOperationCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
std::string* gdoc_resource_id = new std::string;
std::string* parent_resource_id = new std::string;
base::PostTaskAndReplyWithResult(
blocking_task_runner_.get(),
FROM_HERE,
base::Bind(
&PrepareTransferFileFromLocalToRemote,
metadata_, local_src_path, remote_dest_path,
gdoc_resource_id, parent_resource_id),
base::Bind(
&CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
weak_ptr_factory_.GetWeakPtr(),
local_src_path, remote_dest_path, callback,
base::Owned(gdoc_resource_id), base::Owned(parent_resource_id)));
}
void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
const base::FilePath& local_src_path,
const base::FilePath& remote_dest_path,
const FileOperationCallback& callback,
std::string* gdoc_resource_id,
std::string* parent_resource_id,
FileError error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
if (error != FILE_ERROR_OK) {
callback.Run(error);
return;
}
// For regular files, schedule the transfer.
if (gdoc_resource_id->empty()) {
ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
return;
}
// This is uploading a JSON file representing a hosted document.
// Copy the document on the Drive server.
// GDoc file may contain a resource ID in the old format.
const std::string canonicalized_resource_id =
drive_service_->GetResourceIdCanonicalizer().Run(*gdoc_resource_id);
// If Drive API v2 is enabled, we can copy resources on server side.
if (util::IsDriveV2ApiEnabled()) {
CopyResourceOnServer(
*gdoc_resource_id, *parent_resource_id,
// Drop the document extension, which should not be in the title.
// TODO(yoshiki): Remove this code with crbug.com/223304.
remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe(),
base::Time(),
callback);
return;
}
CopyHostedDocument(
canonicalized_resource_id,
// Drop the document extension, which should not be
// in the document title.
// TODO(yoshiki): Remove this code with crbug.com/223304.
remote_dest_path.RemoveExtension(),
callback);
}
void CopyOperation::CopyResourceOnServer(
const std::string& resource_id,
const std::string& parent_resource_id,
const std::string& new_title,
const base::Time& last_modified,
const FileOperationCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
scheduler_->CopyResource(
resource_id, parent_resource_id, new_title, last_modified,
base::Bind(&CopyOperation::CopyResourceOnServerAfterServerSideCopy,
weak_ptr_factory_.GetWeakPtr(),
callback));
}
void CopyOperation::CopyResourceOnServerAfterServerSideCopy(
const FileOperationCallback& callback,
google_apis::GDataErrorCode status,
scoped_ptr<google_apis::ResourceEntry> resource_entry) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
FileError error = GDataToFileError(status);
if (error != FILE_ERROR_OK) {
callback.Run(error);
return;
}
// The copy on the server side is completed successfully. Update the local
// metadata.
base::FilePath* file_path = new base::FilePath;
base::PostTaskAndReplyWithResult(
blocking_task_runner_.get(),
FROM_HERE,
base::Bind(&UpdateLocalStateForServerSideCopy,
metadata_, base::Passed(&resource_entry), file_path),
base::Bind(&CopyOperation::CopyResourceOnServerAfterUpdateLocalState,
weak_ptr_factory_.GetWeakPtr(),
callback, base::Owned(file_path)));
}
void CopyOperation::CopyResourceOnServerAfterUpdateLocalState(
const FileOperationCallback& callback,
base::FilePath* file_path,
FileError error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
if (error == FILE_ERROR_OK)
observer_->OnDirectoryChangedByOperation(file_path->DirName());
callback.Run(error);
}
void CopyOperation::ScheduleTransferRegularFile(
const base::FilePath& local_src_path,
const base::FilePath& remote_dest_path,
const FileOperationCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
base::PostTaskAndReplyWithResult(
blocking_task_runner_.get(),
FROM_HERE,
base::Bind(&GetFileSize, local_src_path),
base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterGetFileSize,
weak_ptr_factory_.GetWeakPtr(),
local_src_path, remote_dest_path, callback));
}
void CopyOperation::ScheduleTransferRegularFileAfterGetFileSize(
const base::FilePath& local_src_path,
const base::FilePath& remote_dest_path,
const FileOperationCallback& callback,
int64 local_file_size) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
if (local_file_size < 0) {
callback.Run(FILE_ERROR_NOT_FOUND);
return;
}
// For regular files, check the server-side quota whether sufficient space
// is available for the file to be uploaded.
scheduler_->GetAboutResource(
base::Bind(
&CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource,
weak_ptr_factory_.GetWeakPtr(),
local_src_path, remote_dest_path, callback, local_file_size));
}
void CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource(
const base::FilePath& local_src_path,
const base::FilePath& remote_dest_path,
const FileOperationCallback& callback,
int64 local_file_size,
google_apis::GDataErrorCode status,
scoped_ptr<google_apis::AboutResource> about_resource) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
FileError error = GDataToFileError(status);
if (error != FILE_ERROR_OK) {
callback.Run(error);
return;
}
DCHECK(about_resource);
const int64 space =
about_resource->quota_bytes_total() - about_resource->quota_bytes_used();
if (space < local_file_size) {
callback.Run(FILE_ERROR_NO_SERVER_SPACE);
return;
}
create_file_operation_->CreateFile(
remote_dest_path,
true, // Exclusive (i.e. fail if a file already exists).
std::string(), // no specific mime type; CreateFile should guess it.
base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
weak_ptr_factory_.GetWeakPtr(),
local_src_path, remote_dest_path, callback));
}
void CopyOperation::ScheduleTransferRegularFileAfterCreate(
const base::FilePath& local_src_path,
const base::FilePath& remote_dest_path,
const FileOperationCallback& callback,
FileError error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
if (error != FILE_ERROR_OK) {
callback.Run(error);
return;
}
std::string* local_id = new std::string;
base::PostTaskAndReplyWithResult(
blocking_task_runner_.get(),
FROM_HERE,
base::Bind(
&UpdateLocalStateForScheduleTransfer,
metadata_, cache_, local_src_path, remote_dest_path, local_id),
base::Bind(
&CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
weak_ptr_factory_.GetWeakPtr(), callback, base::Owned(local_id)));
}
void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
const FileOperationCallback& callback,
std::string* local_id,
FileError error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
if (error == FILE_ERROR_OK)
observer_->OnCacheFileUploadNeededByOperation(*local_id);
callback.Run(error);
}
void CopyOperation::CopyHostedDocument(
const std::string& resource_id,
const base::FilePath& dest_path,
const FileOperationCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
scheduler_->CopyHostedDocument(
resource_id,
dest_path.BaseName().AsUTF8Unsafe(),
base::Bind(&CopyOperation::CopyHostedDocumentAfterServerSideCopy,
weak_ptr_factory_.GetWeakPtr(), dest_path, callback));
}
void CopyOperation::CopyHostedDocumentAfterServerSideCopy(
const base::FilePath& dest_path,
const FileOperationCallback& callback,
google_apis::GDataErrorCode status,
scoped_ptr<google_apis::ResourceEntry> resource_entry) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
FileError error = GDataToFileError(status);
if (error != FILE_ERROR_OK) {
callback.Run(error);
return;
}
// The document is copied into the root directory (temporarily) on the server,
// so we should first update the local metadata, then move it to the
// destination directory.
base::FilePath* file_path = new base::FilePath;
base::PostTaskAndReplyWithResult(
blocking_task_runner_.get(),
FROM_HERE,
base::Bind(&UpdateLocalStateForServerSideCopy,
metadata_, base::Passed(&resource_entry), file_path),
base::Bind(&CopyOperation::CopyHostedDocumentAfterUpdateLocalState,
weak_ptr_factory_.GetWeakPtr(),
dest_path, callback, base::Owned(file_path)));
}
void CopyOperation::CopyHostedDocumentAfterUpdateLocalState(
const base::FilePath& dest_path,
const FileOperationCallback& callback,
base::FilePath* file_path,
FileError error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!callback.is_null());
// If an error is found, or the destination directory is mydrive root
// (i.e. it is unnecessary to move the copied document), return the status.
if (error != FILE_ERROR_OK ||
dest_path.DirName() == util::GetDriveMyDriveRootPath()) {
callback.Run(error);
return;
}
DCHECK_EQ(util::GetDriveMyDriveRootPath().value(),
file_path->DirName().value()) << file_path->value();
move_operation_->Move(*file_path, dest_path, false, callback);
}
} // namespace file_system
} // namespace drive