blob: 28bd6da77703fe50e8409509a772872baf4d75ae [file] [log] [blame]
// 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 "webkit/fileapi/cross_operation_delegate.h"
#include "base/bind.h"
#include "webkit/blob/shareable_file_reference.h"
#include "webkit/fileapi/file_system_context.h"
#include "webkit/fileapi/file_system_operation_context.h"
#include "webkit/fileapi/local_file_system_operation.h"
namespace fileapi {
CrossOperationDelegate::CrossOperationDelegate(
LocalFileSystemOperation* original_operation,
const FileSystemURL& src_root,
const FileSystemURL& dest_root,
OperationType operation_type,
const StatusCallback& callback)
: RecursiveOperationDelegate(original_operation),
src_root_(src_root),
dest_root_(dest_root),
operation_type_(operation_type),
callback_(callback),
src_root_operation_(NULL) {
same_file_system_ =
src_root_.origin() == dest_root_.origin() &&
src_root_.type() == dest_root_.type();
}
CrossOperationDelegate::~CrossOperationDelegate() {
if (src_root_operation_)
delete src_root_operation_;
}
void CrossOperationDelegate::Run() {
// Not supported; this should never be called.
NOTREACHED();
}
void CrossOperationDelegate::RunRecursively() {
// Perform light-weight checks first.
// It is an error to try to copy/move an entry into its child.
if (same_file_system_ && src_root_.path().IsParent(dest_root_.path())) {
callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
return;
}
// It is an error to copy/move an entry into the same path.
if (same_file_system_ && src_root_.path() == dest_root_.path()) {
callback_.Run(base::PLATFORM_FILE_ERROR_EXISTS);
return;
}
// Initialize the src_root_operation_ for the src root URL.
DCHECK(!src_root_operation_);
if (!same_file_system_) {
base::PlatformFileError error = base::PLATFORM_FILE_OK;
FileSystemOperation* operation = file_system_context()->
CreateFileSystemOperation(src_root_, &error);
if (error != base::PLATFORM_FILE_OK) {
DCHECK(!operation);
callback_.Run(error);
return;
}
src_root_operation_ = operation->AsLocalFileSystemOperation();
DCHECK(src_root_operation_);
}
// First try to copy/move it as a file.
CopyOrMoveFile(src_root_, dest_root_,
base::Bind(&CrossOperationDelegate::DidTryCopyOrMoveFile,
AsWeakPtr()));
}
void CrossOperationDelegate::ProcessFile(const FileSystemURL& src_url,
const StatusCallback& callback) {
CopyOrMoveFile(src_url, CreateDestURL(src_url), callback);
}
void CrossOperationDelegate::ProcessDirectory(const FileSystemURL& src_url,
const StatusCallback& callback) {
FileSystemURL dest_url = CreateDestURL(src_url);
LocalFileSystemOperation* dest_operation = NewDestOperation(dest_url);
if (!dest_operation) {
return;
}
// If operation_type == Move we may need to record directories and
// restore directory timestamps in the end, though it may have
// negative performance impact.
// See http://crbug.com/171284 for more details.
dest_operation->CreateDirectory(
dest_url, false /* exclusive */, false /* recursive */, callback);
}
void CrossOperationDelegate::DidTryCopyOrMoveFile(
base::PlatformFileError error) {
if (error == base::PLATFORM_FILE_OK ||
error != base::PLATFORM_FILE_ERROR_NOT_A_FILE) {
callback_.Run(error);
return;
}
// The src_root_ looks to be a directory.
// Try removing the dest_root_ to see if it exists and/or it is an
// empty directory.
LocalFileSystemOperation* dest_operation = NewDestOperation(dest_root_);
if (!dest_operation)
return;
dest_operation->RemoveDirectory(
dest_root_, base::Bind(&CrossOperationDelegate::DidTryRemoveDestRoot,
AsWeakPtr()));
}
void CrossOperationDelegate::DidTryRemoveDestRoot(
base::PlatformFileError error) {
if (error == base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY) {
callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
return;
}
if (error != base::PLATFORM_FILE_OK &&
error != base::PLATFORM_FILE_ERROR_NOT_FOUND) {
callback_.Run(error);
return;
}
// Start to process the source directory recursively.
// TODO(kinuko): This could be too expensive for same_file_system_==true
// and operation==MOVE case, probably we can just rename the root directory.
// http://crbug.com/172187
StartRecursiveOperation(
src_root_, base::Bind(&CrossOperationDelegate::DidFinishCopy,
AsWeakPtr(), src_root_, callback_));
}
void CrossOperationDelegate::CopyOrMoveFile(
const FileSystemURL& src,
const FileSystemURL& dest,
const StatusCallback& callback) {
LocalFileSystemOperation* src_operation = NewSourceOperation(src);
if (!src_operation)
return;
// Same filesystem case.
if (same_file_system_) {
if (operation_type_ == OPERATION_MOVE)
src_operation->MoveFileLocal(src, dest, callback);
else
src_operation->CopyFileLocal(src, dest, callback);
return;
}
// Cross filesystem case.
// Perform CreateSnapshotFile, CopyInForeignFile and then calls
// copy_callback which removes the source file if operation_type == MOVE.
StatusCallback copy_callback =
base::Bind(&CrossOperationDelegate::DidFinishCopy, AsWeakPtr(),
src, callback);
src_operation->CreateSnapshotFile(
src, base::Bind(&CrossOperationDelegate::DidCreateSnapshot, AsWeakPtr(),
dest, copy_callback));
}
void CrossOperationDelegate::DidCreateSnapshot(
const FileSystemURL& dest,
const StatusCallback& callback,
base::PlatformFileError error,
const base::PlatformFileInfo& file_info,
const base::FilePath& platform_path,
const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) {
if (error != base::PLATFORM_FILE_OK) {
callback.Run(error);
return;
}
current_file_ref_ = file_ref;
// For now we assume CreateSnapshotFile always return a valid local file path.
// TODO(kinuko): Otherwise create a FileStreamReader to perform a copy/move.
DCHECK(!platform_path.empty());
LocalFileSystemOperation* dest_operation = NewDestOperation(dest);
if (!dest_operation)
return;
dest_operation->CopyInForeignFile(platform_path, dest, callback);
}
void CrossOperationDelegate::DidFinishCopy(
const FileSystemURL& src,
const StatusCallback& callback,
base::PlatformFileError error) {
if (error != base::PLATFORM_FILE_OK ||
operation_type_ == OPERATION_COPY) {
callback.Run(error);
return;
}
DCHECK_EQ(OPERATION_MOVE, operation_type_);
// Remove the source for finalizing move operation.
LocalFileSystemOperation* src_operation = NewSourceOperation(src);
if (!src_operation)
return;
src_operation->Remove(
src_root_, true /* recursive */,
base::Bind(&CrossOperationDelegate::DidRemoveSourceForMove,
AsWeakPtr(), callback));
}
void CrossOperationDelegate::DidRemoveSourceForMove(
const StatusCallback& callback,
base::PlatformFileError error) {
if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND)
error = base::PLATFORM_FILE_OK;
callback.Run(error);
}
FileSystemURL CrossOperationDelegate::CreateDestURL(
const FileSystemURL& src_url) const {
DCHECK_EQ(src_root_.type(), src_url.type());
DCHECK_EQ(src_root_.origin(), src_url.origin());
base::FilePath path = dest_root_.path();
src_root_.path().AppendRelativePath(src_url.path(), &path);
return dest_root_.WithPath(path);
}
LocalFileSystemOperation* CrossOperationDelegate::NewDestOperation(
const FileSystemURL& url) {
base::PlatformFileError error = base::PLATFORM_FILE_OK;
LocalFileSystemOperation* operation =
RecursiveOperationDelegate::NewOperation(url, &error);
if (!operation) {
DCHECK_NE(base::PLATFORM_FILE_OK, error);
callback_.Run(error);
return NULL;
}
return operation;
}
LocalFileSystemOperation* CrossOperationDelegate::NewSourceOperation(
const FileSystemURL& url) {
if (same_file_system_)
return NewDestOperation(url);
base::PlatformFileError error = base::PLATFORM_FILE_OK;
FileSystemOperation* operation = file_system_context()->
CreateFileSystemOperation(url, &error);
if (!operation) {
DCHECK_NE(base::PLATFORM_FILE_OK, error);
callback_.Run(error);
return NULL;
}
LocalFileSystemOperation* local_operation =
operation->AsLocalFileSystemOperation();
DCHECK(local_operation);
DCHECK(src_root_operation_);
// Let the new operation inherit from the root operation.
local_operation->set_overriding_operation_context(
src_root_operation_->operation_context());
return local_operation;
}
} // namespace fileapi