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.
5 #include "webkit/browser/fileapi/copy_or_move_operation_delegate.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"
19 class CopyOrMoveOperationDelegate::CopyOrMoveImpl
{
21 virtual ~CopyOrMoveImpl() {}
23 const CopyOrMoveOperationDelegate::StatusCallback
& callback
) = 0;
26 DISALLOW_COPY_AND_ASSIGN(CopyOrMoveImpl
);
31 // Copies a file on a (same) file system. Just delegate the operation to
32 // |operation_runner|.
33 class CopyOrMoveOnSameFileSystemImpl
34 : public CopyOrMoveOperationDelegate::CopyOrMoveImpl
{
36 CopyOrMoveOnSameFileSystemImpl(
37 FileSystemOperationRunner
* operation_runner
,
38 CopyOrMoveOperationDelegate::OperationType operation_type
,
39 const FileSystemURL
& src_url
,
40 const FileSystemURL
& dest_url
,
41 const FileSystemOperation::CopyFileProgressCallback
&
42 file_progress_callback
)
43 : operation_runner_(operation_runner
),
44 operation_type_(operation_type
),
47 file_progress_callback_(file_progress_callback
) {
51 const CopyOrMoveOperationDelegate::StatusCallback
& callback
) OVERRIDE
{
52 if (operation_type_
== CopyOrMoveOperationDelegate::OPERATION_MOVE
) {
53 operation_runner_
->MoveFileLocal(src_url_
, dest_url_
, callback
);
55 operation_runner_
->CopyFileLocal(
56 src_url_
, dest_url_
, file_progress_callback_
, callback
);
61 FileSystemOperationRunner
* operation_runner_
;
62 CopyOrMoveOperationDelegate::OperationType operation_type_
;
63 FileSystemURL src_url_
;
64 FileSystemURL dest_url_
;
65 FileSystemOperation::CopyFileProgressCallback file_progress_callback_
;
66 DISALLOW_COPY_AND_ASSIGN(CopyOrMoveOnSameFileSystemImpl
);
69 // Specifically for cross file system copy/move operation, this class creates
70 // a snapshot file, validates it if necessary, runs copying process,
71 // validates the created file, and removes source file for move (noop for
73 class SnapshotCopyOrMoveImpl
74 : public CopyOrMoveOperationDelegate::CopyOrMoveImpl
{
76 SnapshotCopyOrMoveImpl(
77 FileSystemOperationRunner
* operation_runner
,
78 CopyOrMoveOperationDelegate::OperationType operation_type
,
79 const FileSystemURL
& src_url
,
80 const FileSystemURL
& dest_url
,
81 CopyOrMoveFileValidatorFactory
* validator_factory
,
82 const FileSystemOperation::CopyFileProgressCallback
&
83 file_progress_callback
)
84 : operation_runner_(operation_runner
),
85 operation_type_(operation_type
),
88 validator_factory_(validator_factory
),
89 file_progress_callback_(file_progress_callback
),
94 const CopyOrMoveOperationDelegate::StatusCallback
& callback
) OVERRIDE
{
95 file_progress_callback_
.Run(0);
96 operation_runner_
->CreateSnapshotFile(
98 base::Bind(&SnapshotCopyOrMoveImpl::RunAfterCreateSnapshot
,
99 weak_factory_
.GetWeakPtr(), callback
));
103 void RunAfterCreateSnapshot(
104 const CopyOrMoveOperationDelegate::StatusCallback
& callback
,
105 base::PlatformFileError error
,
106 const base::PlatformFileInfo
& file_info
,
107 const base::FilePath
& platform_path
,
108 const scoped_refptr
<webkit_blob::ShareableFileReference
>& file_ref
) {
109 if (error
!= base::PLATFORM_FILE_OK
) {
114 // For now we assume CreateSnapshotFile always return a valid local file
116 DCHECK(!platform_path
.empty());
118 if (!validator_factory_
) {
119 // No validation is needed.
120 RunAfterPreWriteValidation(platform_path
, file_info
, file_ref
, callback
,
121 base::PLATFORM_FILE_OK
);
125 // Run pre write validation.
128 base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPreWriteValidation
,
129 weak_factory_
.GetWeakPtr(),
130 platform_path
, file_info
, file_ref
, callback
));
133 void RunAfterPreWriteValidation(
134 const base::FilePath
& platform_path
,
135 const base::PlatformFileInfo
& file_info
,
136 const scoped_refptr
<webkit_blob::ShareableFileReference
>& file_ref
,
137 const CopyOrMoveOperationDelegate::StatusCallback
& callback
,
138 base::PlatformFileError error
) {
139 if (error
!= base::PLATFORM_FILE_OK
) {
144 // |file_ref| is unused but necessary to keep the file alive until
145 // CopyInForeignFile() is completed.
146 operation_runner_
->CopyInForeignFile(
147 platform_path
, dest_url_
,
148 base::Bind(&SnapshotCopyOrMoveImpl::RunAfterCopyInForeignFile
,
149 weak_factory_
.GetWeakPtr(), file_info
, file_ref
, callback
));
152 void RunAfterCopyInForeignFile(
153 const base::PlatformFileInfo
& file_info
,
154 const scoped_refptr
<webkit_blob::ShareableFileReference
>& file_ref
,
155 const CopyOrMoveOperationDelegate::StatusCallback
& callback
,
156 base::PlatformFileError error
) {
157 if (error
!= base::PLATFORM_FILE_OK
) {
162 file_progress_callback_
.Run(file_info
.size
);
164 // |validator_| is NULL when the destination filesystem does not do
167 // No validation is needed.
168 RunAfterPostWriteValidation(callback
, base::PLATFORM_FILE_OK
);
173 base::Bind(&SnapshotCopyOrMoveImpl::RunAfterPostWriteValidation
,
174 weak_factory_
.GetWeakPtr(), callback
));
177 void RunAfterPostWriteValidation(
178 const CopyOrMoveOperationDelegate::StatusCallback
& callback
,
179 base::PlatformFileError error
) {
180 if (error
!= base::PLATFORM_FILE_OK
) {
181 // Failed to validate. Remove the destination file.
182 operation_runner_
->Remove(
183 dest_url_
, true /* recursive */,
184 base::Bind(&SnapshotCopyOrMoveImpl::DidRemoveDestForError
,
185 weak_factory_
.GetWeakPtr(), error
, callback
));
189 if (operation_type_
== CopyOrMoveOperationDelegate::OPERATION_COPY
) {
190 callback
.Run(base::PLATFORM_FILE_OK
);
194 DCHECK_EQ(CopyOrMoveOperationDelegate::OPERATION_MOVE
, operation_type_
);
196 // Remove the source for finalizing move operation.
197 operation_runner_
->Remove(
198 src_url_
, true /* recursive */,
199 base::Bind(&SnapshotCopyOrMoveImpl::RunAfterRemoveSourceForMove
,
200 weak_factory_
.GetWeakPtr(), callback
));
203 void RunAfterRemoveSourceForMove(
204 const CopyOrMoveOperationDelegate::StatusCallback
& callback
,
205 base::PlatformFileError error
) {
206 if (error
== base::PLATFORM_FILE_ERROR_NOT_FOUND
)
207 error
= base::PLATFORM_FILE_OK
;
211 void DidRemoveDestForError(
212 base::PlatformFileError prior_error
,
213 const CopyOrMoveOperationDelegate::StatusCallback
& callback
,
214 base::PlatformFileError error
) {
215 if (error
!= base::PLATFORM_FILE_OK
) {
216 VLOG(1) << "Error removing destination file after validation error: "
219 callback
.Run(prior_error
);
222 // Runs pre-write validation.
223 void PreWriteValidation(
224 const base::FilePath
& platform_path
,
225 const CopyOrMoveOperationDelegate::StatusCallback
& callback
) {
226 DCHECK(validator_factory_
);
228 validator_factory_
->CreateCopyOrMoveFileValidator(
229 src_url_
, platform_path
));
230 validator_
->StartPreWriteValidation(callback
);
233 // Runs post-write validation.
234 void PostWriteValidation(
235 const CopyOrMoveOperationDelegate::StatusCallback
& callback
) {
236 operation_runner_
->CreateSnapshotFile(
239 &SnapshotCopyOrMoveImpl::PostWriteValidationAfterCreateSnapshotFile
,
240 weak_factory_
.GetWeakPtr(), callback
));
243 void PostWriteValidationAfterCreateSnapshotFile(
244 const CopyOrMoveOperationDelegate::StatusCallback
& callback
,
245 base::PlatformFileError error
,
246 const base::PlatformFileInfo
& file_info
,
247 const base::FilePath
& platform_path
,
248 const scoped_refptr
<webkit_blob::ShareableFileReference
>& file_ref
) {
249 if (error
!= base::PLATFORM_FILE_OK
) {
255 // Note: file_ref passed here to keep the file alive until after
256 // the StartPostWriteValidation operation finishes.
257 validator_
->StartPostWriteValidation(
259 base::Bind(&SnapshotCopyOrMoveImpl::DidPostWriteValidation
,
260 weak_factory_
.GetWeakPtr(), file_ref
, callback
));
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 DidPostWriteValidation(
266 const scoped_refptr
<webkit_blob::ShareableFileReference
>& file_ref
,
267 const CopyOrMoveOperationDelegate::StatusCallback
& callback
,
268 base::PlatformFileError error
) {
272 FileSystemOperationRunner
* operation_runner_
;
273 CopyOrMoveOperationDelegate::OperationType operation_type_
;
274 FileSystemURL src_url_
;
275 FileSystemURL dest_url_
;
276 CopyOrMoveFileValidatorFactory
* validator_factory_
;
277 scoped_ptr
<CopyOrMoveFileValidator
> validator_
;
278 FileSystemOperation::CopyFileProgressCallback file_progress_callback_
;
280 base::WeakPtrFactory
<SnapshotCopyOrMoveImpl
> weak_factory_
;
281 DISALLOW_COPY_AND_ASSIGN(SnapshotCopyOrMoveImpl
);
287 CopyOrMoveOperationDelegate::CopyOrMoveOperationDelegate(
288 FileSystemContext
* file_system_context
,
289 const FileSystemURL
& src_root
,
290 const FileSystemURL
& dest_root
,
291 OperationType operation_type
,
292 const CopyProgressCallback
& progress_callback
,
293 const StatusCallback
& callback
)
294 : RecursiveOperationDelegate(file_system_context
),
296 dest_root_(dest_root
),
297 operation_type_(operation_type
),
298 progress_callback_(progress_callback
),
300 weak_factory_(this) {
301 same_file_system_
= src_root_
.IsInSameFileSystem(dest_root_
);
304 CopyOrMoveOperationDelegate::~CopyOrMoveOperationDelegate() {
305 STLDeleteElements(&running_copy_set_
);
308 void CopyOrMoveOperationDelegate::Run() {
309 // Not supported; this should never be called.
313 void CopyOrMoveOperationDelegate::RunRecursively() {
314 // Perform light-weight checks first.
316 // It is an error to try to copy/move an entry into its child.
317 if (same_file_system_
&& src_root_
.path().IsParent(dest_root_
.path())) {
318 callback_
.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION
);
322 // It is an error to copy/move an entry into the same path.
323 if (same_file_system_
&& src_root_
.path() == dest_root_
.path()) {
324 callback_
.Run(base::PLATFORM_FILE_ERROR_EXISTS
);
328 if (!progress_callback_
.is_null()) {
329 progress_callback_
.Run(
330 FileSystemOperation::BEGIN_COPY_ENTRY
, src_root_
, 0);
333 // First try to copy/move it as a file.
334 CopyOrMoveFile(src_root_
, dest_root_
,
335 base::Bind(&CopyOrMoveOperationDelegate::DidTryCopyOrMoveFile
,
336 weak_factory_
.GetWeakPtr()));
339 void CopyOrMoveOperationDelegate::ProcessFile(
340 const FileSystemURL
& src_url
,
341 const StatusCallback
& callback
) {
342 if (!progress_callback_
.is_null())
343 progress_callback_
.Run(FileSystemOperation::BEGIN_COPY_ENTRY
, src_url
, 0);
345 CopyOrMoveFile(src_url
, CreateDestURL(src_url
),
346 base::Bind(&CopyOrMoveOperationDelegate::DidCopyEntry
,
347 weak_factory_
.GetWeakPtr(), src_url
, callback
));
350 void CopyOrMoveOperationDelegate::ProcessDirectory(
351 const FileSystemURL
& src_url
,
352 const StatusCallback
& callback
) {
353 FileSystemURL dest_url
= CreateDestURL(src_url
);
355 if (!progress_callback_
.is_null() && src_url
!= src_root_
) {
356 // We do not invoke |progress_callback_| for source root, because it is
357 // already called in RunRecursively().
358 progress_callback_
.Run(FileSystemOperation::BEGIN_COPY_ENTRY
, src_url
, 0);
361 // If operation_type == Move we may need to record directories and
362 // restore directory timestamps in the end, though it may have
363 // negative performance impact.
364 // See http://crbug.com/171284 for more details.
365 operation_runner()->CreateDirectory(
366 dest_url
, false /* exclusive */, false /* recursive */,
367 base::Bind(&CopyOrMoveOperationDelegate::DidCopyEntry
,
368 weak_factory_
.GetWeakPtr(), src_url
, callback
));
371 void CopyOrMoveOperationDelegate::PostProcessDirectory(
372 const FileSystemURL
& src_url
,
373 const StatusCallback
& callback
) {
374 callback
.Run(base::PLATFORM_FILE_OK
);
377 void CopyOrMoveOperationDelegate::DidTryCopyOrMoveFile(
378 base::PlatformFileError error
) {
379 if (error
!= base::PLATFORM_FILE_ERROR_NOT_A_FILE
) {
380 if (error
== base::PLATFORM_FILE_OK
&& !progress_callback_
.is_null()) {
381 progress_callback_
.Run(
382 FileSystemOperation::END_COPY_ENTRY
, src_root_
, 0);
385 callback_
.Run(error
);
389 // The src_root_ looks to be a directory.
390 // Try removing the dest_root_ to see if it exists and/or it is an
392 operation_runner()->RemoveDirectory(
394 base::Bind(&CopyOrMoveOperationDelegate::DidTryRemoveDestRoot
,
395 weak_factory_
.GetWeakPtr()));
398 void CopyOrMoveOperationDelegate::DidTryRemoveDestRoot(
399 base::PlatformFileError error
) {
400 if (error
== base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY
) {
401 callback_
.Run(base::PLATFORM_FILE_ERROR_INVALID_OPERATION
);
404 if (error
!= base::PLATFORM_FILE_OK
&&
405 error
!= base::PLATFORM_FILE_ERROR_NOT_FOUND
) {
406 callback_
.Run(error
);
410 // Start to process the source directory recursively.
411 // TODO(kinuko): This could be too expensive for same_file_system_==true
412 // and operation==MOVE case, probably we can just rename the root directory.
413 // http://crbug.com/172187
414 StartRecursiveOperation(
416 base::Bind(&CopyOrMoveOperationDelegate::DidFinishRecursiveCopyDir
,
417 weak_factory_
.GetWeakPtr(), src_root_
, callback_
));
420 void CopyOrMoveOperationDelegate::DidFinishRecursiveCopyDir(
421 const FileSystemURL
& src
,
422 const StatusCallback
& callback
,
423 base::PlatformFileError error
) {
424 if (error
!= base::PLATFORM_FILE_OK
||
425 operation_type_
== OPERATION_COPY
) {
430 DCHECK_EQ(OPERATION_MOVE
, operation_type_
);
432 // Remove the source for finalizing move operation.
433 operation_runner()->Remove(
434 src
, true /* recursive */,
435 base::Bind(&CopyOrMoveOperationDelegate::DidRemoveSourceForMove
,
436 weak_factory_
.GetWeakPtr(), callback
));
439 void CopyOrMoveOperationDelegate::DidRemoveSourceForMove(
440 const StatusCallback
& callback
,
441 base::PlatformFileError error
) {
442 if (error
== base::PLATFORM_FILE_ERROR_NOT_FOUND
)
443 error
= base::PLATFORM_FILE_OK
;
447 void CopyOrMoveOperationDelegate::CopyOrMoveFile(
448 const FileSystemURL
& src_url
,
449 const FileSystemURL
& dest_url
,
450 const StatusCallback
& callback
) {
451 CopyOrMoveImpl
* impl
= NULL
;
452 if (same_file_system_
) {
453 impl
= new CopyOrMoveOnSameFileSystemImpl(
454 operation_runner(), operation_type_
, src_url
, dest_url
,
455 base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress
,
456 weak_factory_
.GetWeakPtr(), src_url
));
458 // Cross filesystem case.
459 // TODO(hidehiko): Support stream based copy. crbug.com/279287.
460 base::PlatformFileError error
= base::PLATFORM_FILE_ERROR_FAILED
;
461 CopyOrMoveFileValidatorFactory
* validator_factory
=
462 file_system_context()->GetCopyOrMoveFileValidatorFactory(
463 dest_root_
.type(), &error
);
464 if (error
!= base::PLATFORM_FILE_OK
) {
469 impl
= new SnapshotCopyOrMoveImpl(
470 operation_runner(), operation_type_
, src_url
, dest_url
,
472 base::Bind(&CopyOrMoveOperationDelegate::OnCopyFileProgress
,
473 weak_factory_
.GetWeakPtr(), src_url
));
476 // Register the running task.
477 running_copy_set_
.insert(impl
);
478 impl
->Run(base::Bind(&CopyOrMoveOperationDelegate::DidCopyOrMoveFile
,
479 weak_factory_
.GetWeakPtr(), impl
, callback
));
482 void CopyOrMoveOperationDelegate::DidCopyEntry(
483 const FileSystemURL
& src_url
,
484 const StatusCallback
& callback
,
485 base::PlatformFileError error
) {
486 if (!progress_callback_
.is_null() && error
== base::PLATFORM_FILE_OK
)
487 progress_callback_
.Run(FileSystemOperation::END_COPY_ENTRY
, src_url
, 0);
492 void CopyOrMoveOperationDelegate::DidCopyOrMoveFile(
493 CopyOrMoveImpl
* impl
,
494 const StatusCallback
& callback
,
495 base::PlatformFileError error
) {
496 running_copy_set_
.erase(impl
);
501 void CopyOrMoveOperationDelegate::OnCopyFileProgress(
502 const FileSystemURL
& src_url
, int64 size
) {
503 if (!progress_callback_
.is_null())
504 progress_callback_
.Run(FileSystemOperation::PROGRESS
, src_url
, size
);
507 FileSystemURL
CopyOrMoveOperationDelegate::CreateDestURL(
508 const FileSystemURL
& src_url
) const {
509 DCHECK_EQ(src_root_
.type(), src_url
.type());
510 DCHECK_EQ(src_root_
.origin(), src_url
.origin());
512 base::FilePath relative
= dest_root_
.virtual_path();
513 src_root_
.virtual_path().AppendRelativePath(src_url
.virtual_path(),
515 return file_system_context()->CreateCrackedFileSystemURL(
517 dest_root_
.mount_type(),
521 } // namespace fileapi