1 // Copyright (c) 2012 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 "chrome/browser/chromeos/drive/file_system/copy_operation.h"
9 #include "base/task_runner_util.h"
10 #include "chrome/browser/chromeos/drive/drive.pb.h"
11 #include "chrome/browser/chromeos/drive/file_cache.h"
12 #include "chrome/browser/chromeos/drive/file_change.h"
13 #include "chrome/browser/chromeos/drive/file_system/create_file_operation.h"
14 #include "chrome/browser/chromeos/drive/file_system/operation_delegate.h"
15 #include "chrome/browser/chromeos/drive/file_system_util.h"
16 #include "chrome/browser/chromeos/drive/job_scheduler.h"
17 #include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
18 #include "chrome/browser/chromeos/drive/resource_metadata.h"
19 #include "chrome/browser/drive/drive_api_util.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "google_apis/drive/drive_api_parser.h"
23 using content::BrowserThread
;
26 namespace file_system
{
28 struct CopyOperation::CopyParams
{
29 base::FilePath src_file_path
;
30 base::FilePath dest_file_path
;
31 bool preserve_last_modified
;
32 FileOperationCallback callback
;
33 ResourceEntry src_entry
;
34 ResourceEntry parent_entry
;
37 // Enum for categorizing where a gdoc represented by a JSON file exists.
38 enum JsonGdocLocationType
{
44 struct CopyOperation::TransferJsonGdocParams
{
45 TransferJsonGdocParams(const FileOperationCallback
& callback
,
46 const std::string
& resource_id
,
47 const ResourceEntry
& parent_entry
,
48 const std::string
& new_title
)
50 resource_id(resource_id
),
51 parent_resource_id(parent_entry
.resource_id()),
52 parent_local_id(parent_entry
.local_id()),
54 location_type(NOT_IN_METADATA
) {
56 // Parameters supplied or calculated from operation arguments.
57 const FileOperationCallback callback
;
58 const std::string resource_id
;
59 const std::string parent_resource_id
;
60 const std::string parent_local_id
;
61 const std::string new_title
;
63 // Values computed during operation.
64 JsonGdocLocationType location_type
; // types where the gdoc file is located.
65 std::string local_id
; // the local_id of the file (if exists in metadata.)
66 base::FilePath changed_path
;
71 FileError
TryToCopyLocally(internal::ResourceMetadata
* metadata
,
72 internal::FileCache
* cache
,
73 CopyOperation::CopyParams
* params
,
74 std::vector
<std::string
>* updated_local_ids
,
75 bool* directory_changed
,
76 bool* should_copy_on_server
) {
77 FileError error
= metadata
->GetResourceEntryByPath(params
->src_file_path
,
79 if (error
!= FILE_ERROR_OK
)
82 error
= metadata
->GetResourceEntryByPath(params
->dest_file_path
.DirName(),
83 ¶ms
->parent_entry
);
84 if (error
!= FILE_ERROR_OK
)
87 if (!params
->parent_entry
.file_info().is_directory())
88 return FILE_ERROR_NOT_A_DIRECTORY
;
90 // Drive File System doesn't support recursive copy.
91 if (params
->src_entry
.file_info().is_directory())
92 return FILE_ERROR_NOT_A_FILE
;
95 ResourceEntry dest_entry
;
96 error
= metadata
->GetResourceEntryByPath(params
->dest_file_path
, &dest_entry
);
99 // File API spec says it is an error to try to "copy a file to a path
100 // occupied by a directory".
101 if (dest_entry
.file_info().is_directory())
102 return FILE_ERROR_INVALID_OPERATION
;
104 // Move the existing entry to the trash.
105 dest_entry
.set_parent_local_id(util::kDriveTrashDirLocalId
);
106 error
= metadata
->RefreshEntry(dest_entry
);
107 if (error
!= FILE_ERROR_OK
)
109 updated_local_ids
->push_back(dest_entry
.local_id());
110 *directory_changed
= true;
112 case FILE_ERROR_NOT_FOUND
:
118 // If the cache file is not present and the entry exists on the server,
119 // server side copy should be used.
120 if (!params
->src_entry
.file_specific_info().cache_state().is_present() &&
121 !params
->src_entry
.resource_id().empty()) {
122 *should_copy_on_server
= true;
123 return FILE_ERROR_OK
;
128 const int64 now
= base::Time::Now().ToInternalValue();
129 entry
.set_title(params
->dest_file_path
.BaseName().AsUTF8Unsafe());
130 entry
.set_parent_local_id(params
->parent_entry
.local_id());
131 entry
.mutable_file_specific_info()->set_content_mime_type(
132 params
->src_entry
.file_specific_info().content_mime_type());
133 entry
.set_metadata_edit_state(ResourceEntry::DIRTY
);
134 entry
.set_modification_date(base::Time::Now().ToInternalValue());
135 entry
.mutable_file_info()->set_last_modified(
136 params
->preserve_last_modified
?
137 params
->src_entry
.file_info().last_modified() : now
);
138 entry
.mutable_file_info()->set_last_accessed(now
);
140 std::string local_id
;
141 error
= metadata
->AddEntry(entry
, &local_id
);
142 if (error
!= FILE_ERROR_OK
)
144 updated_local_ids
->push_back(local_id
);
145 *directory_changed
= true;
147 if (!params
->src_entry
.file_specific_info().cache_state().is_present()) {
148 DCHECK(params
->src_entry
.resource_id().empty());
149 // Locally created empty file may have no cache file.
150 return FILE_ERROR_OK
;
153 base::FilePath cache_file_path
;
154 error
= cache
->GetFile(params
->src_entry
.local_id(), &cache_file_path
);
155 if (error
!= FILE_ERROR_OK
)
158 return cache
->Store(local_id
, std::string(), cache_file_path
,
159 internal::FileCache::FILE_OPERATION_COPY
);
162 // Stores the entry returned from the server and returns its path.
163 FileError
UpdateLocalStateForServerSideOperation(
164 internal::ResourceMetadata
* metadata
,
165 scoped_ptr
<google_apis::FileResource
> file_resource
,
166 ResourceEntry
* entry
,
167 base::FilePath
* file_path
) {
168 DCHECK(file_resource
);
170 std::string parent_resource_id
;
171 if (!ConvertFileResourceToResourceEntry(
172 *file_resource
, entry
, &parent_resource_id
) ||
173 parent_resource_id
.empty())
174 return FILE_ERROR_NOT_A_FILE
;
176 std::string parent_local_id
;
177 FileError error
= metadata
->GetIdByResourceId(parent_resource_id
,
179 if (error
!= FILE_ERROR_OK
)
181 entry
->set_parent_local_id(parent_local_id
);
183 std::string local_id
;
184 error
= metadata
->AddEntry(*entry
, &local_id
);
185 // Depending on timing, the metadata may have inserted via change list
186 // already. So, FILE_ERROR_EXISTS is not an error.
187 if (error
== FILE_ERROR_EXISTS
)
188 error
= metadata
->GetIdByResourceId(entry
->resource_id(), &local_id
);
190 if (error
!= FILE_ERROR_OK
)
193 return metadata
->GetFilePath(local_id
, file_path
);
196 // Stores the file at |local_file_path| to the cache as a content of entry at
197 // |remote_dest_path|, and marks it dirty.
198 FileError
UpdateLocalStateForScheduleTransfer(
199 internal::ResourceMetadata
* metadata
,
200 internal::FileCache
* cache
,
201 const base::FilePath
& local_src_path
,
202 const base::FilePath
& remote_dest_path
,
203 ResourceEntry
* entry
,
204 std::string
* local_id
) {
205 FileError error
= metadata
->GetIdByPath(remote_dest_path
, local_id
);
206 if (error
!= FILE_ERROR_OK
)
209 error
= metadata
->GetResourceEntryById(*local_id
, entry
);
210 if (error
!= FILE_ERROR_OK
)
213 return cache
->Store(*local_id
, std::string(), local_src_path
,
214 internal::FileCache::FILE_OPERATION_COPY
);
217 // Gets the file size of the |local_path|, and the ResourceEntry for the parent
218 // of |remote_path| to prepare the necessary information for transfer.
219 FileError
PrepareTransferFileFromLocalToRemote(
220 internal::ResourceMetadata
* metadata
,
221 const base::FilePath
& local_src_path
,
222 const base::FilePath
& remote_dest_path
,
223 std::string
* gdoc_resource_id
,
224 ResourceEntry
* parent_entry
) {
225 FileError error
= metadata
->GetResourceEntryByPath(
226 remote_dest_path
.DirName(), parent_entry
);
227 if (error
!= FILE_ERROR_OK
)
230 // The destination's parent must be a directory.
231 if (!parent_entry
->file_info().is_directory())
232 return FILE_ERROR_NOT_A_DIRECTORY
;
234 // Try to parse GDoc File and extract the resource id, if necessary.
235 // Failing isn't problem. It'd be handled as a regular file, then.
236 if (util::HasHostedDocumentExtension(local_src_path
))
237 *gdoc_resource_id
= util::ReadResourceIdFromGDocFile(local_src_path
);
238 return FILE_ERROR_OK
;
241 // Performs local work before server-side work for transferring JSON-represented
243 FileError
LocalWorkForTransferJsonGdocFile(
244 internal::ResourceMetadata
* metadata
,
245 CopyOperation::TransferJsonGdocParams
* params
) {
246 std::string local_id
;
247 FileError error
= metadata
->GetIdByResourceId(params
->resource_id
, &local_id
);
248 if (error
!= FILE_ERROR_OK
) {
249 params
->location_type
= NOT_IN_METADATA
;
250 return error
== FILE_ERROR_NOT_FOUND
? FILE_ERROR_OK
: error
;
254 error
= metadata
->GetResourceEntryById(local_id
, &entry
);
255 if (error
!= FILE_ERROR_OK
)
257 params
->local_id
= entry
.local_id();
259 if (entry
.parent_local_id() == util::kDriveOtherDirLocalId
) {
260 params
->location_type
= IS_ORPHAN
;
261 entry
.set_title(params
->new_title
);
262 entry
.set_parent_local_id(params
->parent_local_id
);
263 entry
.set_metadata_edit_state(ResourceEntry::DIRTY
);
264 entry
.set_modification_date(base::Time::Now().ToInternalValue());
265 error
= metadata
->RefreshEntry(entry
);
266 if (error
!= FILE_ERROR_OK
)
268 return metadata
->GetFilePath(local_id
, ¶ms
->changed_path
);
271 params
->location_type
= HAS_PARENT
;
272 return FILE_ERROR_OK
;
277 CopyOperation::CopyOperation(base::SequencedTaskRunner
* blocking_task_runner
,
278 OperationDelegate
* delegate
,
279 JobScheduler
* scheduler
,
280 internal::ResourceMetadata
* metadata
,
281 internal::FileCache
* cache
)
282 : blocking_task_runner_(blocking_task_runner
),
284 scheduler_(scheduler
),
287 create_file_operation_(new CreateFileOperation(blocking_task_runner
,
290 weak_ptr_factory_(this) {
291 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
294 CopyOperation::~CopyOperation() {
295 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
298 void CopyOperation::Copy(const base::FilePath
& src_file_path
,
299 const base::FilePath
& dest_file_path
,
300 bool preserve_last_modified
,
301 const FileOperationCallback
& callback
) {
302 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
303 DCHECK(!callback
.is_null());
305 CopyParams
* params
= new CopyParams
;
306 params
->src_file_path
= src_file_path
;
307 params
->dest_file_path
= dest_file_path
;
308 params
->preserve_last_modified
= preserve_last_modified
;
309 params
->callback
= callback
;
311 std::vector
<std::string
>* updated_local_ids
= new std::vector
<std::string
>;
312 bool* directory_changed
= new bool(false);
313 bool* should_copy_on_server
= new bool(false);
314 base::PostTaskAndReplyWithResult(
315 blocking_task_runner_
.get(),
317 base::Bind(&TryToCopyLocally
, metadata_
, cache_
, params
,
318 updated_local_ids
, directory_changed
, should_copy_on_server
),
319 base::Bind(&CopyOperation::CopyAfterTryToCopyLocally
,
320 weak_ptr_factory_
.GetWeakPtr(), base::Owned(params
),
321 base::Owned(updated_local_ids
), base::Owned(directory_changed
),
322 base::Owned(should_copy_on_server
)));
325 void CopyOperation::CopyAfterTryToCopyLocally(
326 const CopyParams
* params
,
327 const std::vector
<std::string
>* updated_local_ids
,
328 const bool* directory_changed
,
329 const bool* should_copy_on_server
,
331 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
332 DCHECK(!params
->callback
.is_null());
334 for (const auto& id
: *updated_local_ids
) {
335 // Syncing for copy should be done in background, so pass the BACKGROUND
336 // context. See: crbug.com/420278.
337 delegate_
->OnEntryUpdatedByOperation(ClientContext(BACKGROUND
), id
);
340 if (*directory_changed
) {
341 FileChange changed_file
;
342 DCHECK(!params
->src_entry
.file_info().is_directory());
343 changed_file
.Update(params
->dest_file_path
,
344 FileChange::FILE_TYPE_FILE
,
345 FileChange::ADD_OR_UPDATE
);
346 delegate_
->OnFileChangedByOperation(changed_file
);
349 if (error
!= FILE_ERROR_OK
|| !*should_copy_on_server
) {
350 params
->callback
.Run(error
);
354 if (params
->parent_entry
.resource_id().empty()) {
355 // Parent entry may be being synced.
356 const bool waiting
= delegate_
->WaitForSyncComplete(
357 params
->parent_entry
.local_id(),
358 base::Bind(&CopyOperation::CopyAfterParentSync
,
359 weak_ptr_factory_
.GetWeakPtr(), *params
));
361 params
->callback
.Run(FILE_ERROR_NOT_FOUND
);
363 CopyAfterGetParentResourceId(*params
, ¶ms
->parent_entry
, FILE_ERROR_OK
);
367 void CopyOperation::CopyAfterParentSync(const CopyParams
& params
,
369 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
370 DCHECK(!params
.callback
.is_null());
372 if (error
!= FILE_ERROR_OK
) {
373 params
.callback
.Run(error
);
377 ResourceEntry
* parent
= new ResourceEntry
;
378 base::PostTaskAndReplyWithResult(
379 blocking_task_runner_
.get(),
381 base::Bind(&internal::ResourceMetadata::GetResourceEntryById
,
382 base::Unretained(metadata_
),
383 params
.parent_entry
.local_id(),
385 base::Bind(&CopyOperation::CopyAfterGetParentResourceId
,
386 weak_ptr_factory_
.GetWeakPtr(),
388 base::Owned(parent
)));
391 void CopyOperation::CopyAfterGetParentResourceId(const CopyParams
& params
,
392 const ResourceEntry
* parent
,
394 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
395 DCHECK(!params
.callback
.is_null());
397 if (error
!= FILE_ERROR_OK
) {
398 params
.callback
.Run(error
);
402 base::FilePath new_title
= params
.dest_file_path
.BaseName();
403 if (params
.src_entry
.file_specific_info().is_hosted_document()) {
404 // Drop the document extension, which should not be in the title.
405 // TODO(yoshiki): Remove this code with crbug.com/223304.
406 new_title
= new_title
.RemoveExtension();
409 base::Time last_modified
=
410 params
.preserve_last_modified
?
411 base::Time::FromInternalValue(
412 params
.src_entry
.file_info().last_modified()) : base::Time();
414 CopyResourceOnServer(
415 params
.src_entry
.resource_id(), parent
->resource_id(),
416 new_title
.AsUTF8Unsafe(), last_modified
, params
.callback
);
419 void CopyOperation::TransferFileFromLocalToRemote(
420 const base::FilePath
& local_src_path
,
421 const base::FilePath
& remote_dest_path
,
422 const FileOperationCallback
& callback
) {
423 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
424 DCHECK(!callback
.is_null());
426 std::string
* gdoc_resource_id
= new std::string
;
427 ResourceEntry
* parent_entry
= new ResourceEntry
;
428 base::PostTaskAndReplyWithResult(
429 blocking_task_runner_
.get(),
432 &PrepareTransferFileFromLocalToRemote
,
433 metadata_
, local_src_path
, remote_dest_path
,
434 gdoc_resource_id
, parent_entry
),
436 &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare
,
437 weak_ptr_factory_
.GetWeakPtr(),
438 local_src_path
, remote_dest_path
, callback
,
439 base::Owned(gdoc_resource_id
), base::Owned(parent_entry
)));
442 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
443 const base::FilePath
& local_src_path
,
444 const base::FilePath
& remote_dest_path
,
445 const FileOperationCallback
& callback
,
446 std::string
* gdoc_resource_id
,
447 ResourceEntry
* parent_entry
,
449 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
450 DCHECK(!callback
.is_null());
452 if (error
!= FILE_ERROR_OK
) {
457 // For regular files, schedule the transfer.
458 if (gdoc_resource_id
->empty()) {
459 ScheduleTransferRegularFile(local_src_path
, remote_dest_path
, callback
);
463 // GDoc file may contain a resource ID in the old format.
464 const std::string canonicalized_resource_id
=
465 util::CanonicalizeResourceId(*gdoc_resource_id
);
467 // Drop the document extension, which should not be in the title.
468 // TODO(yoshiki): Remove this code with crbug.com/223304.
469 const std::string new_title
=
470 remote_dest_path
.BaseName().RemoveExtension().AsUTF8Unsafe();
472 // This is uploading a JSON file representing a hosted document.
473 TransferJsonGdocParams
* params
= new TransferJsonGdocParams(
474 callback
, canonicalized_resource_id
, *parent_entry
, new_title
);
475 base::PostTaskAndReplyWithResult(
476 blocking_task_runner_
.get(),
478 base::Bind(&LocalWorkForTransferJsonGdocFile
, metadata_
, params
),
479 base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork
,
480 weak_ptr_factory_
.GetWeakPtr(), base::Owned(params
)));
483 void CopyOperation::TransferJsonGdocFileAfterLocalWork(
484 TransferJsonGdocParams
* params
,
486 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
488 if (error
!= FILE_ERROR_OK
) {
489 params
->callback
.Run(error
);
493 switch (params
->location_type
) {
494 // When |resource_id| is found in the local metadata and it has a specific
495 // parent folder, we assume the user's intention is to copy the document and
496 // thus perform the server-side copy operation.
498 CopyResourceOnServer(params
->resource_id
,
499 params
->parent_resource_id
,
504 // When |resource_id| has no parent, we just set the new destination folder
505 // as the parent, for sharing the document between the original source.
506 // This reparenting is already done in LocalWorkForTransferJsonGdocFile().
508 DCHECK(!params
->changed_path
.empty());
509 // Syncing for copy should be done in background, so pass the BACKGROUND
510 // context. See: crbug.com/420278.
511 delegate_
->OnEntryUpdatedByOperation(ClientContext(BACKGROUND
),
514 FileChange changed_file
;
516 params
->changed_path
,
517 FileChange::FILE_TYPE_FILE
, // This must be a hosted document.
518 FileChange::ADD_OR_UPDATE
);
519 delegate_
->OnFileChangedByOperation(changed_file
);
520 params
->callback
.Run(error
);
523 // When the |resource_id| is not in the local metadata, assume it to be a
524 // document just now shared on the server but not synced locally.
525 // Same as the IS_ORPHAN case, we want to deal the case by setting parent,
526 // but this time we need to resort to server side operation.
527 case NOT_IN_METADATA
:
528 scheduler_
->UpdateResource(
529 params
->resource_id
, params
->parent_resource_id
, params
->new_title
,
530 base::Time(), base::Time(), google_apis::drive::Properties(),
531 ClientContext(USER_INITIATED
),
532 base::Bind(&CopyOperation::UpdateAfterServerSideOperation
,
533 weak_ptr_factory_
.GetWeakPtr(), params
->callback
));
538 void CopyOperation::CopyResourceOnServer(
539 const std::string
& resource_id
,
540 const std::string
& parent_resource_id
,
541 const std::string
& new_title
,
542 const base::Time
& last_modified
,
543 const FileOperationCallback
& callback
) {
544 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
545 DCHECK(!callback
.is_null());
547 scheduler_
->CopyResource(
548 resource_id
, parent_resource_id
, new_title
, last_modified
,
549 base::Bind(&CopyOperation::UpdateAfterServerSideOperation
,
550 weak_ptr_factory_
.GetWeakPtr(),
554 void CopyOperation::UpdateAfterServerSideOperation(
555 const FileOperationCallback
& callback
,
556 google_apis::DriveApiErrorCode status
,
557 scoped_ptr
<google_apis::FileResource
> entry
) {
558 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
559 DCHECK(!callback
.is_null());
561 FileError error
= GDataToFileError(status
);
562 if (error
!= FILE_ERROR_OK
) {
567 ResourceEntry
* resource_entry
= new ResourceEntry
;
569 // The copy on the server side is completed successfully. Update the local
571 base::FilePath
* file_path
= new base::FilePath
;
572 base::PostTaskAndReplyWithResult(
573 blocking_task_runner_
.get(),
575 base::Bind(&UpdateLocalStateForServerSideOperation
,
577 base::Passed(&entry
),
580 base::Bind(&CopyOperation::UpdateAfterLocalStateUpdate
,
581 weak_ptr_factory_
.GetWeakPtr(),
583 base::Owned(file_path
),
584 base::Owned(resource_entry
)));
587 void CopyOperation::UpdateAfterLocalStateUpdate(
588 const FileOperationCallback
& callback
,
589 base::FilePath
* file_path
,
590 const ResourceEntry
* entry
,
592 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
593 DCHECK(!callback
.is_null());
595 if (error
== FILE_ERROR_OK
) {
596 FileChange changed_file
;
597 changed_file
.Update(*file_path
, *entry
, FileChange::ADD_OR_UPDATE
);
598 delegate_
->OnFileChangedByOperation(changed_file
);
603 void CopyOperation::ScheduleTransferRegularFile(
604 const base::FilePath
& local_src_path
,
605 const base::FilePath
& remote_dest_path
,
606 const FileOperationCallback
& callback
) {
607 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
608 DCHECK(!callback
.is_null());
610 create_file_operation_
->CreateFile(
612 false, // Not exclusive (OK even if a file already exists).
613 std::string(), // no specific mime type; CreateFile should guess it.
614 base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate
,
615 weak_ptr_factory_
.GetWeakPtr(),
616 local_src_path
, remote_dest_path
, callback
));
619 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
620 const base::FilePath
& local_src_path
,
621 const base::FilePath
& remote_dest_path
,
622 const FileOperationCallback
& callback
,
624 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
625 DCHECK(!callback
.is_null());
627 if (error
!= FILE_ERROR_OK
) {
632 std::string
* local_id
= new std::string
;
633 ResourceEntry
* entry
= new ResourceEntry
;
634 base::PostTaskAndReplyWithResult(
635 blocking_task_runner_
.get(),
637 base::Bind(&UpdateLocalStateForScheduleTransfer
,
645 &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState
,
646 weak_ptr_factory_
.GetWeakPtr(),
650 base::Owned(local_id
)));
653 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
654 const FileOperationCallback
& callback
,
655 const base::FilePath
& remote_dest_path
,
656 const ResourceEntry
* entry
,
657 std::string
* local_id
,
659 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
660 DCHECK(!callback
.is_null());
662 if (error
== FILE_ERROR_OK
) {
663 FileChange changed_file
;
664 changed_file
.Update(remote_dest_path
, *entry
, FileChange::ADD_OR_UPDATE
);
665 delegate_
->OnFileChangedByOperation(changed_file
);
666 // Syncing for copy should be done in background, so pass the BACKGROUND
667 // context. See: crbug.com/420278.
668 delegate_
->OnEntryUpdatedByOperation(ClientContext(BACKGROUND
), *local_id
);
673 } // namespace file_system