Home | History | Annotate | Download | only in fileapi
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "webkit/browser/fileapi/copy_or_move_operation_delegate.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/files/file_path.h"
      9 #include "webkit/browser/fileapi/copy_or_move_file_validator.h"
     10 #include "webkit/browser/fileapi/file_system_context.h"
     11 #include "webkit/browser/fileapi/file_system_operation_runner.h"
     12 #include "webkit/browser/fileapi/file_system_url.h"
     13 #include "webkit/browser/fileapi/recursive_operation_delegate.h"
     14 #include "webkit/common/blob/shareable_file_reference.h"
     15 #include "webkit/common/fileapi/file_system_util.h"
     16 
     17 namespace fileapi {
     18 
     19 CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate(
     20     FileSystemContext* file_system_context,
     21     const FileSystemURL& src_root,
     22     const FileSystemURL& dest_root,
     23     OperationType operation_type,
     24     const StatusCallback& callback)
     25     : RecursiveOperationDelegate(file_system_context),
     26       src_root_(src_root),
     27       dest_root_(dest_root),
     28       operation_type_(operation_type),
     29       callback_(callback),
     30       weak_factory_(this) {
     31   same_file_system_ = src_root_.IsInSameFileSystem(dest_root_);
     32 }
     33 
     34 CopyOrMoveOperationDelegate::~CopyOrMoveOperationDelegate() {
     35 }
     36 
     37 void CopyOrMoveOperationDelegate::Run() {
     38   // Not supported; this should never be called.
     39   NOTREACHED();
     40 }
     41 
     42 void CopyOrMoveOperationDelegate::RunRecursively() {
     43   // Perform light-weight checks first.
     44 
     45   // It is an error to try to copy/move an entry into its child.
     46   if (same_file_system_ && src_root_.path().IsParent(dest_root_.path())) {
     47     callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
     48     return;
     49   }
     50 
     51   // It is an error to copy/move an entry into the same path.
     52   if (same_file_system_ && src_root_.path() == dest_root_.path()) {
     53     callback_.Run(base::PLATFORM_FILE_ERROR_EXISTS);
     54     return;
     55   }
     56 
     57   // First try to copy/move it as a file.
     58   CopyOrMoveFile(URLPair(src_root_, dest_root_),
     59                  base::Bind(&CopyOrMoveOperationDelegate::DidTryCopyOrMoveFile,
     60                             weak_factory_.GetWeakPtr()));
     61 }
     62 
     63 void CopyOrMoveOperationDelegate::ProcessFile(const FileSystemURL& src_url,
     64                                          const StatusCallback& callback) {
     65   CopyOrMoveFile(URLPair(src_url, CreateDestURL(src_url)), callback);
     66 }
     67 
     68 void CopyOrMoveOperationDelegate::ProcessDirectory(const FileSystemURL& src_url,
     69                                               const StatusCallback& callback) {
     70   FileSystemURL dest_url = CreateDestURL(src_url);
     71 
     72   // If operation_type == Move we may need to record directories and
     73   // restore directory timestamps in the end, though it may have
     74   // negative performance impact.
     75   // See http://crbug.com/171284 for more details.
     76   operation_runner()->CreateDirectory(
     77       dest_url, false /* exclusive */, false /* recursive */, callback);
     78 }
     79 
     80 void CopyOrMoveOperationDelegate::DidTryCopyOrMoveFile(
     81     base::PlatformFileError error) {
     82   if (error == base::PLATFORM_FILE_OK ||
     83       error != base::PLATFORM_FILE_ERROR_NOT_A_FILE) {
     84     callback_.Run(error);
     85     return;
     86   }
     87 
     88   // The src_root_ looks to be a directory.
     89   // Try removing the dest_root_ to see if it exists and/or it is an
     90   // empty directory.
     91   operation_runner()->RemoveDirectory(
     92       dest_root_,
     93       base::Bind(&CopyOrMoveOperationDelegate::DidTryRemoveDestRoot,
     94                  weak_factory_.GetWeakPtr()));
     95 }
     96 
     97 void CopyOrMoveOperationDelegate::DidTryRemoveDestRoot(
     98     base::PlatformFileError error) {
     99   if (error == base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY) {
    100     callback_.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION);
    101     return;
    102   }
    103   if (error != base::PLATFORM_FILE_OK &&
    104       error != base::PLATFORM_FILE_ERROR_NOT_FOUND) {
    105     callback_.Run(error);
    106     return;
    107   }
    108 
    109   // Start to process the source directory recursively.
    110   // TODO(kinuko): This could be too expensive for same_file_system_==true
    111   // and operation==MOVE case, probably we can just rename the root directory.
    112   // http://crbug.com/172187
    113   StartRecursiveOperation(
    114       src_root_,
    115       base::Bind(&CopyOrMoveOperationDelegate::DidFinishRecursiveCopyDir,
    116                  weak_factory_.GetWeakPtr(), src_root_, callback_));
    117 }
    118 
    119 void CopyOrMoveOperationDelegate::CopyOrMoveFile(
    120     const URLPair& url_pair,
    121     const StatusCallback& callback) {
    122   // Same filesystem case.
    123   if (same_file_system_) {
    124     if (operation_type_ == OPERATION_MOVE) {
    125       operation_runner()->MoveFileLocal(url_pair.src, url_pair.dest, callback);
    126     } else {
    127       operation_runner()->CopyFileLocal(url_pair.src, url_pair.dest, callback);
    128     }
    129     return;
    130   }
    131 
    132   // Cross filesystem case.
    133   // Perform CreateSnapshotFile, CopyInForeignFile and then calls
    134   // copy_callback which removes the source file if operation_type == MOVE.
    135   StatusCallback copy_callback =
    136       base::Bind(&CopyOrMoveOperationDelegate::DidFinishCopy,
    137                  weak_factory_.GetWeakPtr(), url_pair, callback);
    138   operation_runner()->CreateSnapshotFile(
    139       url_pair.src,
    140       base::Bind(&CopyOrMoveOperationDelegate::DidCreateSnapshot,
    141                  weak_factory_.GetWeakPtr(), url_pair, copy_callback));
    142 }
    143 
    144 void CopyOrMoveOperationDelegate::DidCreateSnapshot(
    145     const URLPair& url_pair,
    146     const StatusCallback& callback,
    147     base::PlatformFileError error,
    148     const base::PlatformFileInfo& file_info,
    149     const base::FilePath& platform_path,
    150     const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) {
    151   if (error != base::PLATFORM_FILE_OK) {
    152     callback.Run(error);
    153     return;
    154   }
    155   current_file_ref_ = file_ref;
    156 
    157   // For now we assume CreateSnapshotFile always return a valid local file path.
    158   // TODO(kinuko): Otherwise create a FileStreamReader to perform a copy/move.
    159   DCHECK(!platform_path.empty());
    160 
    161   CopyOrMoveFileValidatorFactory* factory =
    162       file_system_context()->GetCopyOrMoveFileValidatorFactory(
    163           dest_root_.type(), &error);
    164   if (error != base::PLATFORM_FILE_OK) {
    165     callback.Run(error);
    166     return;
    167   }
    168   if (!factory) {
    169     DidValidateFile(url_pair.dest, callback, file_info, platform_path, error);
    170     return;
    171   }
    172 
    173   validator_.reset(
    174       factory->CreateCopyOrMoveFileValidator(url_pair.src, platform_path));
    175   validator_->StartPreWriteValidation(
    176       base::Bind(&CopyOrMoveOperationDelegate::DidValidateFile,
    177                  weak_factory_.GetWeakPtr(),
    178                  url_pair.dest, callback, file_info, platform_path));
    179 }
    180 
    181 void CopyOrMoveOperationDelegate::DidValidateFile(
    182     const FileSystemURL& dest,
    183     const StatusCallback& callback,
    184     const base::PlatformFileInfo& file_info,
    185     const base::FilePath& platform_path,
    186     base::PlatformFileError error) {
    187   if (error != base::PLATFORM_FILE_OK) {
    188     callback.Run(error);
    189     return;
    190   }
    191 
    192   operation_runner()->CopyInForeignFile(platform_path, dest, callback);
    193 }
    194 
    195 void CopyOrMoveOperationDelegate::DidFinishRecursiveCopyDir(
    196     const FileSystemURL& src,
    197     const StatusCallback& callback,
    198     base::PlatformFileError error) {
    199   if (error != base::PLATFORM_FILE_OK ||
    200       operation_type_ == OPERATION_COPY) {
    201     callback.Run(error);
    202     return;
    203   }
    204 
    205   DCHECK_EQ(OPERATION_MOVE, operation_type_);
    206 
    207   // Remove the source for finalizing move operation.
    208   operation_runner()->Remove(
    209       src, true /* recursive */,
    210       base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove,
    211                  weak_factory_.GetWeakPtr(), callback));
    212 }
    213 
    214 void CopyOrMoveOperationDelegate::DidFinishCopy(
    215     const URLPair& url_pair,
    216     const StatusCallback& callback,
    217     base::PlatformFileError error) {
    218   if (error != base::PLATFORM_FILE_OK) {
    219     callback.Run(error);
    220     return;
    221   }
    222 
    223   // |validator_| is NULL in the same-filesystem case or when the destination
    224   // filesystem does not do validation.
    225   if (!validator_.get()) {
    226     scoped_refptr<webkit_blob::ShareableFileReference> file_ref;
    227     DidPostWriteValidation(url_pair, callback, file_ref,
    228                            base::PLATFORM_FILE_OK);
    229     return;
    230   }
    231 
    232   DCHECK(!same_file_system_);
    233   operation_runner()->CreateSnapshotFile(
    234       url_pair.dest,
    235       base::Bind(&CopyOrMoveOperationDelegate::DoPostWriteValidation,
    236                  weak_factory_.GetWeakPtr(), url_pair, callback));
    237 }
    238 
    239 void CopyOrMoveOperationDelegate::DoPostWriteValidation(
    240     const URLPair& url_pair,
    241     const StatusCallback& callback,
    242     base::PlatformFileError error,
    243     const base::PlatformFileInfo& file_info,
    244     const base::FilePath& platform_path,
    245     const scoped_refptr<webkit_blob::ShareableFileReference>& file_ref) {
    246   if (error != base::PLATFORM_FILE_OK) {
    247     operation_runner()->Remove(
    248         url_pair.dest, true,
    249         base::Bind(&CopyOrMoveOperationDelegate::DidRemoveDestForError,
    250                    weak_factory_.GetWeakPtr(), error, callback));
    251     return;
    252   }
    253 
    254   DCHECK(validator_.get());
    255   // Note: file_ref passed here to keep the file alive until after
    256   // the StartPostWriteValidation operation finishes.
    257   validator_->StartPostWriteValidation(
    258       platform_path,
    259       base::Bind(&CopyOrMoveOperationDelegate::DidPostWriteValidation,
    260                  weak_factory_.GetWeakPtr(), url_pair, callback, file_ref));
    261 }
    262 
    263 // |file_ref| is unused; it is passed here to make sure the reference is
    264 // alive until after post-write validation is complete.
    265 void CopyOrMoveOperationDelegate::DidPostWriteValidation(
    266     const URLPair& url_pair,
    267     const StatusCallback& callback,
    268     const scoped_refptr<webkit_blob::ShareableFileReference>& /*file_ref*/,
    269     base::PlatformFileError error) {
    270   if (error != base::PLATFORM_FILE_OK) {
    271     operation_runner()->Remove(
    272         url_pair.dest, true,
    273         base::Bind(&CopyOrMoveOperationDelegate::DidRemoveDestForError,
    274                    weak_factory_.GetWeakPtr(), error, callback));
    275     return;
    276   }
    277 
    278   if (operation_type_ == OPERATION_COPY) {
    279     callback.Run(error);
    280     return;
    281   }
    282 
    283   DCHECK_EQ(OPERATION_MOVE, operation_type_);
    284 
    285   // Remove the source for finalizing move operation.
    286   operation_runner()->Remove(
    287       url_pair.src, true /* recursive */,
    288       base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove,
    289                  weak_factory_.GetWeakPtr(), callback));
    290 }
    291 
    292 void CopyOrMoveOperationDelegate::DidRemoveSourceForMove(
    293     const StatusCallback& callback,
    294     base::PlatformFileError error) {
    295   if (error == base::PLATFORM_FILE_ERROR_NOT_FOUND)
    296     error = base::PLATFORM_FILE_OK;
    297   callback.Run(error);
    298 }
    299 
    300 FileSystemURL CopyOrMoveOperationDelegate::CreateDestURL(
    301     const FileSystemURL& src_url) const {
    302   DCHECK_EQ(src_root_.type(), src_url.type());
    303   DCHECK_EQ(src_root_.origin(), src_url.origin());
    304 
    305   base::FilePath relative = dest_root_.virtual_path();
    306   src_root_.virtual_path().AppendRelativePath(src_url.virtual_path(),
    307                                               &relative);
    308   return file_system_context()->CreateCrackedFileSystemURL(
    309       dest_root_.origin(),
    310       dest_root_.mount_type(),
    311       relative);
    312 }
    313 
    314 void CopyOrMoveOperationDelegate::DidRemoveDestForError(
    315     base::PlatformFileError prior_error,
    316     const StatusCallback& callback,
    317     base::PlatformFileError error) {
    318   if (error != base::PLATFORM_FILE_OK) {
    319     VLOG(1) << "Error removing destination file after validation error: "
    320             << error;
    321   }
    322   callback.Run(prior_error);
    323 }
    324 
    325 }  // namespace fileapi
    326