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